Version in base suite: 3.7.1+dfsg-2 Base version: botan3_3.7.1+dfsg-2 Target version: botan3_3.12.0+dfsg-2~deb13u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/b/botan3/botan3_3.7.1+dfsg-2.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/b/botan3/botan3_3.12.0+dfsg-2~deb13u1.dsc /srv/release.debian.org/tmp/oKBJC3rrE3/botan3-3.12.0+dfsg/src/tests/data/x509/ocsp/byKey_responderID.der |binary botan3-3.12.0+dfsg/.clang-format | 59 botan3-3.12.0+dfsg/.devcontainer/Dockerfile | 70 botan3-3.12.0+dfsg/.devcontainer/devcontainer.json | 48 botan3-3.12.0+dfsg/.devcontainer/startup.sh | 28 botan3-3.12.0+dfsg/.github/actions/setup-build-agent/action.yml | 12 botan3-3.12.0+dfsg/.github/workflows/ci.yml | 149 botan3-3.12.0+dfsg/.github/workflows/codeql.yml | 19 botan3-3.12.0+dfsg/.github/workflows/nightly.yml | 223 botan3-3.12.0+dfsg/.gitignore | 9 botan3-3.12.0+dfsg/configure.py | 450 - botan3-3.12.0+dfsg/debian/changelog | 88 botan3-3.12.0+dfsg/debian/control | 15 botan3-3.12.0+dfsg/debian/copyright | 27 botan3-3.12.0+dfsg/debian/libbotan-3-12.install | 1 botan3-3.12.0+dfsg/debian/libbotan-3-7.install | 1 botan3-3.12.0+dfsg/debian/patches/readdir_hurd.patch | 19 botan3-3.12.0+dfsg/debian/patches/series | 2 botan3-3.12.0+dfsg/debian/patches/use_python3.patch | 185 botan3-3.12.0+dfsg/debian/rules | 1 botan3-3.12.0+dfsg/debian/watch | 18 botan3-3.12.0+dfsg/doc/api_ref/bigint.rst | 174 botan3-3.12.0+dfsg/doc/api_ref/cipher_modes.rst | 32 botan3-3.12.0+dfsg/doc/api_ref/ecc.rst | 18 botan3-3.12.0+dfsg/doc/api_ref/env_vars.rst | 3 botan3-3.12.0+dfsg/doc/api_ref/ffi.rst | 745 + botan3-3.12.0+dfsg/doc/api_ref/filters.rst | 2 botan3-3.12.0+dfsg/doc/api_ref/footguns.rst | 2 botan3-3.12.0+dfsg/doc/api_ref/fpe.rst | 4 botan3-3.12.0+dfsg/doc/api_ref/hash.rst | 38 botan3-3.12.0+dfsg/doc/api_ref/message_auth_codes.rst | 13 botan3-3.12.0+dfsg/doc/api_ref/otp.rst | 6 botan3-3.12.0+dfsg/doc/api_ref/pbkdf.rst | 22 botan3-3.12.0+dfsg/doc/api_ref/pkcs11.rst | 13 botan3-3.12.0+dfsg/doc/api_ref/pubkey.rst | 76 botan3-3.12.0+dfsg/doc/api_ref/python.rst | 125 botan3-3.12.0+dfsg/doc/api_ref/tls.rst | 59 botan3-3.12.0+dfsg/doc/api_ref/tpm.rst | 2 botan3-3.12.0+dfsg/doc/api_ref/versions.rst | 53 botan3-3.12.0+dfsg/doc/api_ref/x509.rst | 14 botan3-3.12.0+dfsg/doc/api_ref/zfec.rst | 2 botan3-3.12.0+dfsg/doc/authors.txt | 7 botan3-3.12.0+dfsg/doc/building.rst | 233 botan3-3.12.0+dfsg/doc/cli.rst | 29 botan3-3.12.0+dfsg/doc/contents.rst | 1 botan3-3.12.0+dfsg/doc/credits.rst | 9 botan3-3.12.0+dfsg/doc/deprecated.rst | 34 botan3-3.12.0+dfsg/doc/dev_ref/configure.rst | 4 botan3-3.12.0+dfsg/doc/dev_ref/contents.rst | 1 botan3-3.12.0+dfsg/doc/dev_ref/contributing.rst | 44 botan3-3.12.0+dfsg/doc/dev_ref/fuzzing.rst | 1 botan3-3.12.0+dfsg/doc/dev_ref/mistakes.rst | 2 botan3-3.12.0+dfsg/doc/dev_ref/next_major.rst | 8 botan3-3.12.0+dfsg/doc/dev_ref/os.rst | 17 botan3-3.12.0+dfsg/doc/dev_ref/pcurves.rst | 62 botan3-3.12.0+dfsg/doc/dev_ref/reading_list.rst | 4 botan3-3.12.0+dfsg/doc/dev_ref/release_process.rst | 21 botan3-3.12.0+dfsg/doc/dev_ref/test_framework.rst | 145 botan3-3.12.0+dfsg/doc/dev_ref/todo.rst | 42 botan3-3.12.0+dfsg/doc/goals.rst | 12 botan3-3.12.0+dfsg/doc/hardware_acceleration.rst | 337 botan3-3.12.0+dfsg/doc/migration_guide.rst | 6 botan3-3.12.0+dfsg/doc/news_2x.rst | 4 botan3-3.12.0+dfsg/doc/old_news.rst | 48 botan3-3.12.0+dfsg/doc/openssl_migration_guide.rst | 2 botan3-3.12.0+dfsg/doc/packaging.rst | 14 botan3-3.12.0+dfsg/doc/roadmap.rst | 35 botan3-3.12.0+dfsg/doc/security.rst | 73 botan3-3.12.0+dfsg/doc/sem_ver.rst | 2 botan3-3.12.0+dfsg/doc/side_channels.rst | 37 botan3-3.12.0+dfsg/doc/support.rst | 23 botan3-3.12.0+dfsg/doc/threat_model.rst | 66 botan3-3.12.0+dfsg/license.txt | 2 botan3-3.12.0+dfsg/news.rst | 487 + botan3-3.12.0+dfsg/readme.rst | 16 botan3-3.12.0+dfsg/src/.clang-tidy | 75 botan3-3.12.0+dfsg/src/bogo_shim/bogo_shim.cpp | 693 + botan3-3.12.0+dfsg/src/bogo_shim/config.json | 214 botan3-3.12.0+dfsg/src/bogo_shim/config_no_tls12.json | 334 botan3-3.12.0+dfsg/src/bogo_shim/config_no_tls13.json | 332 botan3-3.12.0+dfsg/src/build-data/arch/alpha.txt | 2 botan3-3.12.0+dfsg/src/build-data/arch/arm32.txt | 1 botan3-3.12.0+dfsg/src/build-data/arch/arm64.txt | 2 botan3-3.12.0+dfsg/src/build-data/arch/generic.txt | 2 botan3-3.12.0+dfsg/src/build-data/arch/ia64.txt | 1 botan3-3.12.0+dfsg/src/build-data/arch/llvm.txt | 1 botan3-3.12.0+dfsg/src/build-data/arch/loongarch64.txt | 7 botan3-3.12.0+dfsg/src/build-data/arch/m68k.txt | 1 botan3-3.12.0+dfsg/src/build-data/arch/mips64.txt | 1 botan3-3.12.0+dfsg/src/build-data/arch/powerpcspe.txt | 1 botan3-3.12.0+dfsg/src/build-data/arch/ppc32.txt | 5 botan3-3.12.0+dfsg/src/build-data/arch/ppc64.txt | 2 botan3-3.12.0+dfsg/src/build-data/arch/riscv32.txt | 1 botan3-3.12.0+dfsg/src/build-data/arch/riscv64.txt | 2 botan3-3.12.0+dfsg/src/build-data/arch/s390.txt | 1 botan3-3.12.0+dfsg/src/build-data/arch/s390x.txt | 2 botan3-3.12.0+dfsg/src/build-data/arch/sparc32.txt | 1 botan3-3.12.0+dfsg/src/build-data/arch/sparc64.txt | 2 botan3-3.12.0+dfsg/src/build-data/arch/wasm.txt | 5 botan3-3.12.0+dfsg/src/build-data/arch/x32.txt | 1 botan3-3.12.0+dfsg/src/build-data/arch/x86_32.txt | 1 botan3-3.12.0+dfsg/src/build-data/arch/x86_64.txt | 3 botan3-3.12.0+dfsg/src/build-data/botan-config-version.cmake.in | 23 botan3-3.12.0+dfsg/src/build-data/botan-config.cmake.in | 109 botan3-3.12.0+dfsg/src/build-data/botan.pc.in | 4 botan3-3.12.0+dfsg/src/build-data/buildh.in | 241 botan3-3.12.0+dfsg/src/build-data/cc/clang.txt | 7 botan3-3.12.0+dfsg/src/build-data/cc/clangcl.txt | 106 botan3-3.12.0+dfsg/src/build-data/cc/emcc.txt | 8 botan3-3.12.0+dfsg/src/build-data/cc/gcc.txt | 6 botan3-3.12.0+dfsg/src/build-data/cc/msvc.txt | 9 botan3-3.12.0+dfsg/src/build-data/cc/sunstudio.txt | 43 botan3-3.12.0+dfsg/src/build-data/cc/xcode.txt | 2 botan3-3.12.0+dfsg/src/build-data/compile_commands.json.in | 10 botan3-3.12.0+dfsg/src/build-data/detect_version.cpp | 2 botan3-3.12.0+dfsg/src/build-data/ec_groups.txt | 37 botan3-3.12.0+dfsg/src/build-data/ec_named.cpp.in | 39 botan3-3.12.0+dfsg/src/build-data/makefile.in | 10 botan3-3.12.0+dfsg/src/build-data/ninja.in | 19 botan3-3.12.0+dfsg/src/build-data/oids.txt | 119 botan3-3.12.0+dfsg/src/build-data/os/hurd.txt | 4 botan3-3.12.0+dfsg/src/build-data/os/netbsd.txt | 1 botan3-3.12.0+dfsg/src/build-data/os/openbsd.txt | 1 botan3-3.12.0+dfsg/src/build-data/policy/bsi.txt | 57 botan3-3.12.0+dfsg/src/build-data/policy/fips140.txt | 9 botan3-3.12.0+dfsg/src/build-data/policy/modern.txt | 21 botan3-3.12.0+dfsg/src/build-data/target_info.h.in | 110 botan3-3.12.0+dfsg/src/build-data/templates/ec_named.cpp.in | 56 botan3-3.12.0+dfsg/src/build-data/templates/pcurves.cpp.in | 44 botan3-3.12.0+dfsg/src/build-data/templates/pcurves_instance.h.in | 42 botan3-3.12.0+dfsg/src/build-data/templates/pcurves_stub.cpp.in | 80 botan3-3.12.0+dfsg/src/build-data/templates/static_oids.cpp.in | 91 botan3-3.12.0+dfsg/src/build-data/templates/tls_suite_info.cpp.in | 58 botan3-3.12.0+dfsg/src/build-data/version.txt | 12 botan3-3.12.0+dfsg/src/build-data/version_info.h.in | 20 botan3-3.12.0+dfsg/src/cli/argon2.cpp | 8 botan3-3.12.0+dfsg/src/cli/argparse.h | 42 botan3-3.12.0+dfsg/src/cli/asn1.cpp | 14 botan3-3.12.0+dfsg/src/cli/bcrypt.cpp | 8 botan3-3.12.0+dfsg/src/cli/cc_enc.cpp | 20 botan3-3.12.0+dfsg/src/cli/cipher.cpp | 6 botan3-3.12.0+dfsg/src/cli/cli.cpp | 8 botan3-3.12.0+dfsg/src/cli/cli.h | 12 botan3-3.12.0+dfsg/src/cli/cli_exceptions.h | 2 botan3-3.12.0+dfsg/src/cli/cli_rng.cpp | 21 botan3-3.12.0+dfsg/src/cli/codec.cpp | 13 botan3-3.12.0+dfsg/src/cli/compress.cpp | 7 botan3-3.12.0+dfsg/src/cli/entropy.cpp | 40 botan3-3.12.0+dfsg/src/cli/hash.cpp | 4 botan3-3.12.0+dfsg/src/cli/hmac.cpp | 6 botan3-3.12.0+dfsg/src/cli/main.cpp | 2 botan3-3.12.0+dfsg/src/cli/math.cpp | 40 botan3-3.12.0+dfsg/src/cli/pbkdf.cpp | 14 botan3-3.12.0+dfsg/src/cli/perf.cpp | 3 botan3-3.12.0+dfsg/src/cli/perf.h | 25 botan3-3.12.0+dfsg/src/cli/perf_ec.cpp | 244 botan3-3.12.0+dfsg/src/cli/perf_math.cpp | 73 botan3-3.12.0+dfsg/src/cli/perf_misc.cpp | 83 botan3-3.12.0+dfsg/src/cli/perf_pk_enc.cpp | 27 botan3-3.12.0+dfsg/src/cli/perf_pk_ka.cpp | 23 botan3-3.12.0+dfsg/src/cli/perf_pk_kem.cpp | 68 botan3-3.12.0+dfsg/src/cli/perf_pk_misc.cpp | 59 botan3-3.12.0+dfsg/src/cli/perf_pk_sig.cpp | 67 botan3-3.12.0+dfsg/src/cli/perf_pwdhash.cpp | 62 botan3-3.12.0+dfsg/src/cli/perf_rng.cpp | 7 botan3-3.12.0+dfsg/src/cli/perf_sym.cpp | 70 botan3-3.12.0+dfsg/src/cli/perf_x509.cpp | 157 botan3-3.12.0+dfsg/src/cli/pk_crypt.cpp | 11 botan3-3.12.0+dfsg/src/cli/psk.cpp | 7 botan3-3.12.0+dfsg/src/cli/pubkey.cpp | 33 botan3-3.12.0+dfsg/src/cli/roughtime.cpp | 15 botan3-3.12.0+dfsg/src/cli/sandbox.cpp | 32 botan3-3.12.0+dfsg/src/cli/sandbox.h | 5 botan3-3.12.0+dfsg/src/cli/socket_utils.h | 13 botan3-3.12.0+dfsg/src/cli/speed.cpp | 117 botan3-3.12.0+dfsg/src/cli/timer.cpp | 3 botan3-3.12.0+dfsg/src/cli/timer.h | 48 botan3-3.12.0+dfsg/src/cli/timing_tests.cpp | 254 botan3-3.12.0+dfsg/src/cli/tls_client.cpp | 50 botan3-3.12.0+dfsg/src/cli/tls_helpers.h | 30 botan3-3.12.0+dfsg/src/cli/tls_http_server.cpp | 17 botan3-3.12.0+dfsg/src/cli/tls_proxy.cpp | 86 botan3-3.12.0+dfsg/src/cli/tls_server.cpp | 29 botan3-3.12.0+dfsg/src/cli/tls_utils.cpp | 47 botan3-3.12.0+dfsg/src/cli/tss.cpp | 6 botan3-3.12.0+dfsg/src/cli/utils.cpp | 21 botan3-3.12.0+dfsg/src/cli/x509.cpp | 102 botan3-3.12.0+dfsg/src/cli/zfec.cpp | 22 botan3-3.12.0+dfsg/src/configs/ci_deps.conf | 28 botan3-3.12.0+dfsg/src/configs/clang-format | 60 botan3-3.12.0+dfsg/src/configs/pylint.rc | 5 botan3-3.12.0+dfsg/src/configs/repo_config.env | 23 botan3-3.12.0+dfsg/src/configs/sphinx/conf.py | 10 botan3-3.12.0+dfsg/src/configs/typos.toml | 61 botan3-3.12.0+dfsg/src/configs/zizmor.yml | 8 botan3-3.12.0+dfsg/src/ct_selftest/ct_selftest.cpp | 6 botan3-3.12.0+dfsg/src/editors/vscode/extensions.json | 11 botan3-3.12.0+dfsg/src/editors/vscode/launch.json | 55 botan3-3.12.0+dfsg/src/editors/vscode/scripts/bogo.py | 33 botan3-3.12.0+dfsg/src/editors/vscode/scripts/test.py | 50 botan3-3.12.0+dfsg/src/editors/vscode/settings.json | 9 botan3-3.12.0+dfsg/src/editors/vscode/tasks.json | 129 botan3-3.12.0+dfsg/src/examples/check_key.cpp | 2 botan3-3.12.0+dfsg/src/examples/custom_system_rng.cpp | 7 botan3-3.12.0+dfsg/src/examples/ecc_raw_private_key.cpp | 1 botan3-3.12.0+dfsg/src/examples/ecc_raw_public_key.cpp | 1 botan3-3.12.0+dfsg/src/examples/ecdh.cpp | 8 botan3-3.12.0+dfsg/src/examples/ecdsa.cpp | 2 botan3-3.12.0+dfsg/src/examples/entropy.cpp | 4 botan3-3.12.0+dfsg/src/examples/ffi.c | 53 botan3-3.12.0+dfsg/src/examples/fpe_alnum.cpp | 122 botan3-3.12.0+dfsg/src/examples/fpe_dictionary.cpp | 84 botan3-3.12.0+dfsg/src/examples/hash.cpp | 4 botan3-3.12.0+dfsg/src/examples/hmac.cpp | 6 botan3-3.12.0+dfsg/src/examples/hybrid_encryption.cpp | 4 botan3-3.12.0+dfsg/src/examples/hybrid_key_encapsulation.cpp | 49 botan3-3.12.0+dfsg/src/examples/ml_kem.cpp | 2 botan3-3.12.0+dfsg/src/examples/password_encryption.cpp | 20 botan3-3.12.0+dfsg/src/examples/pkcs10_csr_on_tpm2.cpp | 4 botan3-3.12.0+dfsg/src/examples/pkcs11_ecdh.cpp | 28 botan3-3.12.0+dfsg/src/examples/pkcs11_ecdsa.cpp | 18 botan3-3.12.0+dfsg/src/examples/pkcs11_low_level.cpp | 4 botan3-3.12.0+dfsg/src/examples/pkcs11_module.cpp | 2 botan3-3.12.0+dfsg/src/examples/pkcs11_objects.cpp | 15 botan3-3.12.0+dfsg/src/examples/pkcs11_rsa.cpp | 22 botan3-3.12.0+dfsg/src/examples/pkcs11_session.cpp | 20 botan3-3.12.0+dfsg/src/examples/pkcs11_slot.cpp | 14 botan3-3.12.0+dfsg/src/examples/pkcs11_token_management.cpp | 6 botan3-3.12.0+dfsg/src/examples/pkcs11_x509.cpp | 6 botan3-3.12.0+dfsg/src/examples/pwdhash.cpp | 10 botan3-3.12.0+dfsg/src/examples/rsa_encrypt.cpp | 6 botan3-3.12.0+dfsg/src/examples/tls_13_hybrid_key_exchange_client.cpp | 30 botan3-3.12.0+dfsg/src/examples/tls_client.cpp | 46 botan3-3.12.0+dfsg/src/examples/tls_custom_curves_client.cpp | 33 botan3-3.12.0+dfsg/src/examples/tls_proxy.cpp | 34 botan3-3.12.0+dfsg/src/examples/tls_ssl_key_log_file.cpp | 95 botan3-3.12.0+dfsg/src/examples/tls_stream_client.cpp | 37 botan3-3.12.0+dfsg/src/examples/tls_stream_coroutine_client.cpp | 4 botan3-3.12.0+dfsg/src/examples/x509_path.cpp | 10 botan3-3.12.0+dfsg/src/examples/xmss.cpp | 2 botan3-3.12.0+dfsg/src/fuzzer/asn1.cpp | 22 botan3-3.12.0+dfsg/src/fuzzer/barrett.cpp | 40 botan3-3.12.0+dfsg/src/fuzzer/bn_cmp.cpp | 20 botan3-3.12.0+dfsg/src/fuzzer/bn_sqr.cpp | 6 botan3-3.12.0+dfsg/src/fuzzer/cert.cpp | 4 botan3-3.12.0+dfsg/src/fuzzer/crl.cpp | 4 botan3-3.12.0+dfsg/src/fuzzer/divide.cpp | 10 botan3-3.12.0+dfsg/src/fuzzer/ec_scalar.cpp | 138 botan3-3.12.0+dfsg/src/fuzzer/ecc_bp256.cpp | 2 botan3-3.12.0+dfsg/src/fuzzer/ecc_bp384.cpp | 16 botan3-3.12.0+dfsg/src/fuzzer/ecc_bp512.cpp | 16 botan3-3.12.0+dfsg/src/fuzzer/ecc_frp256.cpp | 16 botan3-3.12.0+dfsg/src/fuzzer/ecc_helper.h | 8 botan3-3.12.0+dfsg/src/fuzzer/ecc_numsp512.cpp | 16 botan3-3.12.0+dfsg/src/fuzzer/ecc_p224.cpp | 16 botan3-3.12.0+dfsg/src/fuzzer/ecc_p256.cpp | 2 botan3-3.12.0+dfsg/src/fuzzer/ecc_p384.cpp | 2 botan3-3.12.0+dfsg/src/fuzzer/ecc_p521.cpp | 2 botan3-3.12.0+dfsg/src/fuzzer/ecc_secp256k1.cpp | 16 botan3-3.12.0+dfsg/src/fuzzer/ecc_sm2p256.cpp | 16 botan3-3.12.0+dfsg/src/fuzzer/fuzzers.h | 81 botan3-3.12.0+dfsg/src/fuzzer/gcd.cpp | 5 botan3-3.12.0+dfsg/src/fuzzer/invert.cpp | 14 botan3-3.12.0+dfsg/src/fuzzer/ipv4.cpp | 8 botan3-3.12.0+dfsg/src/fuzzer/mem_pool.cpp | 21 botan3-3.12.0+dfsg/src/fuzzer/mode_padding.cpp | 28 botan3-3.12.0+dfsg/src/fuzzer/mp_comba_mul.cpp | 2 botan3-3.12.0+dfsg/src/fuzzer/mp_fuzzers.h | 30 botan3-3.12.0+dfsg/src/fuzzer/mp_redc.cpp | 32 botan3-3.12.0+dfsg/src/fuzzer/mp_redc_crandall.cpp | 24 botan3-3.12.0+dfsg/src/fuzzer/ocsp.cpp | 4 botan3-3.12.0+dfsg/src/fuzzer/os2ecp.cpp | 16 botan3-3.12.0+dfsg/src/fuzzer/pkcs1.cpp | 9 botan3-3.12.0+dfsg/src/fuzzer/pkcs8.cpp | 7 botan3-3.12.0+dfsg/src/fuzzer/pow_mod.cpp | 6 botan3-3.12.0+dfsg/src/fuzzer/ressol.cpp | 11 botan3-3.12.0+dfsg/src/fuzzer/tls_13_handshake_layer.cpp | 9 botan3-3.12.0+dfsg/src/fuzzer/tls_client.cpp | 30 botan3-3.12.0+dfsg/src/fuzzer/tls_client_hello.cpp | 8 botan3-3.12.0+dfsg/src/fuzzer/tls_server.cpp | 32 botan3-3.12.0+dfsg/src/fuzzer/uri.cpp | 2 botan3-3.12.0+dfsg/src/fuzzer/x509_path.cpp | 10 botan3-3.12.0+dfsg/src/lib/asn1/alg_id.cpp | 9 botan3-3.12.0+dfsg/src/lib/asn1/asn1_obj.cpp | 22 botan3-3.12.0+dfsg/src/lib/asn1/asn1_obj.h | 146 botan3-3.12.0+dfsg/src/lib/asn1/asn1_oid.cpp | 36 botan3-3.12.0+dfsg/src/lib/asn1/asn1_print.cpp | 53 botan3-3.12.0+dfsg/src/lib/asn1/asn1_print.h | 26 botan3-3.12.0+dfsg/src/lib/asn1/asn1_str.cpp | 134 botan3-3.12.0+dfsg/src/lib/asn1/asn1_time.cpp | 35 botan3-3.12.0+dfsg/src/lib/asn1/asn1_time.h | 84 botan3-3.12.0+dfsg/src/lib/asn1/ber_dec.cpp | 474 - botan3-3.12.0+dfsg/src/lib/asn1/ber_dec.h | 131 botan3-3.12.0+dfsg/src/lib/asn1/der_enc.cpp | 50 botan3-3.12.0+dfsg/src/lib/asn1/der_enc.h | 50 botan3-3.12.0+dfsg/src/lib/asn1/info.txt | 1 botan3-3.12.0+dfsg/src/lib/asn1/oid_map.cpp | 34 botan3-3.12.0+dfsg/src/lib/asn1/oid_map.h | 6 botan3-3.12.0+dfsg/src/lib/asn1/oid_maps.cpp | 693 - botan3-3.12.0+dfsg/src/lib/asn1/pss_params.cpp | 24 botan3-3.12.0+dfsg/src/lib/asn1/pss_params.h | 18 botan3-3.12.0+dfsg/src/lib/asn1/static_oids.cpp | 1459 +++ botan3-3.12.0+dfsg/src/lib/base/buf_comp.cpp | 10 botan3-3.12.0+dfsg/src/lib/base/buf_comp.h | 10 botan3-3.12.0+dfsg/src/lib/base/secmem.h | 56 botan3-3.12.0+dfsg/src/lib/base/sym_algo.cpp | 5 botan3-3.12.0+dfsg/src/lib/base/sym_algo.h | 14 botan3-3.12.0+dfsg/src/lib/base/symkey.cpp | 6 botan3-3.12.0+dfsg/src/lib/block/aes/aes.cpp | 181 botan3-3.12.0+dfsg/src/lib/block/aes/aes.h | 1 botan3-3.12.0+dfsg/src/lib/block/aes/aes_armv8/aes_armv8.cpp | 51 botan3-3.12.0+dfsg/src/lib/block/aes/aes_armv8/info.txt | 8 botan3-3.12.0+dfsg/src/lib/block/aes/aes_ni/aes_ni.cpp | 67 botan3-3.12.0+dfsg/src/lib/block/aes/aes_ni/info.txt | 7 botan3-3.12.0+dfsg/src/lib/block/aes/aes_power8/aes_power8.cpp | 452 - botan3-3.12.0+dfsg/src/lib/block/aes/aes_power8/info.txt | 8 botan3-3.12.0+dfsg/src/lib/block/aes/aes_vaes/aes_vaes.cpp | 47 botan3-3.12.0+dfsg/src/lib/block/aes/aes_vaes/info.txt | 5 botan3-3.12.0+dfsg/src/lib/block/aes/aes_vperm/aes_vperm.cpp | 537 - botan3-3.12.0+dfsg/src/lib/block/aes/aes_vperm/info.txt | 19 botan3-3.12.0+dfsg/src/lib/block/aria/aria.cpp | 149 botan3-3.12.0+dfsg/src/lib/block/aria/aria.h | 37 botan3-3.12.0+dfsg/src/lib/block/aria/aria_avx512_gfni/aria_avx512_gfni.cpp | 390 + botan3-3.12.0+dfsg/src/lib/block/aria/aria_avx512_gfni/info.txt | 18 botan3-3.12.0+dfsg/src/lib/block/aria/aria_hwaes/aria_hwaes.cpp | 248 botan3-3.12.0+dfsg/src/lib/block/aria/aria_hwaes/info.txt | 12 botan3-3.12.0+dfsg/src/lib/block/block_cipher.cpp | 5 botan3-3.12.0+dfsg/src/lib/block/block_cipher.h | 41 botan3-3.12.0+dfsg/src/lib/block/blowfish/blowfish.cpp | 45 botan3-3.12.0+dfsg/src/lib/block/blowfish/blowfish.h | 1 botan3-3.12.0+dfsg/src/lib/block/camellia/camellia.cpp | 198 botan3-3.12.0+dfsg/src/lib/block/camellia/camellia.h | 55 botan3-3.12.0+dfsg/src/lib/block/camellia/camellia_avx2_gfni/camellia_avx2_gfni.cpp | 444 + botan3-3.12.0+dfsg/src/lib/block/camellia/camellia_avx2_gfni/info.txt | 18 botan3-3.12.0+dfsg/src/lib/block/camellia/camellia_avx512_gfni/camellia_avx512_gfni.cpp | 761 + botan3-3.12.0+dfsg/src/lib/block/camellia/camellia_avx512_gfni/info.txt | 18 botan3-3.12.0+dfsg/src/lib/block/camellia/camellia_hwaes/camellia_hwaes.cpp | 437 + botan3-3.12.0+dfsg/src/lib/block/camellia/camellia_hwaes/info.txt | 12 botan3-3.12.0+dfsg/src/lib/block/cascade/cascade.cpp | 10 botan3-3.12.0+dfsg/src/lib/block/cascade/cascade.h | 5 botan3-3.12.0+dfsg/src/lib/block/cast128/cast128.cpp | 24 botan3-3.12.0+dfsg/src/lib/block/cast128/cast128.h | 1 botan3-3.12.0+dfsg/src/lib/block/des/des.cpp | 1084 +- botan3-3.12.0+dfsg/src/lib/block/des/des.h | 9 botan3-3.12.0+dfsg/src/lib/block/gost_28147/gost_28147.cpp | 2 botan3-3.12.0+dfsg/src/lib/block/gost_28147/gost_28147.h | 1 botan3-3.12.0+dfsg/src/lib/block/idea/idea.cpp | 69 botan3-3.12.0+dfsg/src/lib/block/idea/idea.h | 5 botan3-3.12.0+dfsg/src/lib/block/idea/idea_avx2/idea_avx2.cpp | 219 botan3-3.12.0+dfsg/src/lib/block/idea/idea_avx2/info.txt | 16 botan3-3.12.0+dfsg/src/lib/block/idea/idea_sse2/idea_sse2.cpp | 98 botan3-3.12.0+dfsg/src/lib/block/idea/idea_sse2/info.txt | 8 botan3-3.12.0+dfsg/src/lib/block/kuznyechik/kuznyechik.cpp | 194 botan3-3.12.0+dfsg/src/lib/block/kuznyechik/kuznyechik.h | 10 botan3-3.12.0+dfsg/src/lib/block/lion/lion.cpp | 1 botan3-3.12.0+dfsg/src/lib/block/noekeon/noekeon.cpp | 17 botan3-3.12.0+dfsg/src/lib/block/noekeon/noekeon.h | 1 botan3-3.12.0+dfsg/src/lib/block/noekeon/noekeon_simd/info.txt | 20 botan3-3.12.0+dfsg/src/lib/block/noekeon/noekeon_simd/noekeon_simd.cpp | 26 botan3-3.12.0+dfsg/src/lib/block/seed/seed.cpp | 165 botan3-3.12.0+dfsg/src/lib/block/seed/seed.h | 13 botan3-3.12.0+dfsg/src/lib/block/seed/seed_avx512_gfni/info.txt | 17 botan3-3.12.0+dfsg/src/lib/block/seed/seed_avx512_gfni/seed_avx512_gfni.cpp | 331 botan3-3.12.0+dfsg/src/lib/block/seed/seed_hwaes/info.txt | 12 botan3-3.12.0+dfsg/src/lib/block/seed/seed_hwaes/seed_hwaes.cpp | 196 botan3-3.12.0+dfsg/src/lib/block/serpent/serpent.cpp | 38 botan3-3.12.0+dfsg/src/lib/block/serpent/serpent.h | 1 botan3-3.12.0+dfsg/src/lib/block/serpent/serpent_avx2/info.txt | 10 botan3-3.12.0+dfsg/src/lib/block/serpent/serpent_avx2/serpent_avx2.cpp | 10 botan3-3.12.0+dfsg/src/lib/block/serpent/serpent_avx512/info.txt | 7 botan3-3.12.0+dfsg/src/lib/block/serpent/serpent_avx512/serpent_avx512.cpp | 50 botan3-3.12.0+dfsg/src/lib/block/serpent/serpent_fn.h | 20 botan3-3.12.0+dfsg/src/lib/block/serpent/serpent_sbox.h | 32 botan3-3.12.0+dfsg/src/lib/block/serpent/serpent_simd/info.txt | 19 botan3-3.12.0+dfsg/src/lib/block/serpent/serpent_simd/serpent_simd.cpp | 6 botan3-3.12.0+dfsg/src/lib/block/shacal2/shacal2.cpp | 71 botan3-3.12.0+dfsg/src/lib/block/shacal2/shacal2.h | 6 botan3-3.12.0+dfsg/src/lib/block/shacal2/shacal2_armv8/info.txt | 6 botan3-3.12.0+dfsg/src/lib/block/shacal2/shacal2_armv8/shacal2_arvm8.cpp | 4 botan3-3.12.0+dfsg/src/lib/block/shacal2/shacal2_avx2/info.txt | 5 botan3-3.12.0+dfsg/src/lib/block/shacal2/shacal2_avx2/shacal2_avx2.cpp | 48 botan3-3.12.0+dfsg/src/lib/block/shacal2/shacal2_avx512/info.txt | 18 botan3-3.12.0+dfsg/src/lib/block/shacal2/shacal2_avx512/shacal2_avx512.cpp | 337 botan3-3.12.0+dfsg/src/lib/block/shacal2/shacal2_simd/info.txt | 20 botan3-3.12.0+dfsg/src/lib/block/shacal2/shacal2_simd/shacal2_simd.cpp | 42 botan3-3.12.0+dfsg/src/lib/block/shacal2/shacal2_x86/info.txt | 6 botan3-3.12.0+dfsg/src/lib/block/shacal2/shacal2_x86/shacal2_x86.cpp | 12 botan3-3.12.0+dfsg/src/lib/block/sm4/sm4.cpp | 85 botan3-3.12.0+dfsg/src/lib/block/sm4/sm4.h | 16 botan3-3.12.0+dfsg/src/lib/block/sm4/sm4_armv8/info.txt | 8 botan3-3.12.0+dfsg/src/lib/block/sm4/sm4_armv8/sm4_armv8.cpp | 29 botan3-3.12.0+dfsg/src/lib/block/sm4/sm4_avx512/info.txt | 19 botan3-3.12.0+dfsg/src/lib/block/sm4/sm4_avx512/sm4_avx512.cpp | 302 botan3-3.12.0+dfsg/src/lib/block/sm4/sm4_gfni/info.txt | 5 botan3-3.12.0+dfsg/src/lib/block/sm4/sm4_gfni/sm4_gfni.cpp | 22 botan3-3.12.0+dfsg/src/lib/block/sm4/sm4_hwaes/info.txt | 12 botan3-3.12.0+dfsg/src/lib/block/sm4/sm4_hwaes/sm4_hwaes.cpp | 274 botan3-3.12.0+dfsg/src/lib/block/sm4/sm4_x86/info.txt | 23 botan3-3.12.0+dfsg/src/lib/block/sm4/sm4_x86/sm4_x86.cpp | 142 botan3-3.12.0+dfsg/src/lib/block/threefish_512/threefish_512.cpp | 21 botan3-3.12.0+dfsg/src/lib/block/threefish_512/threefish_512.h | 1 botan3-3.12.0+dfsg/src/lib/block/twofish/twofish.cpp | 321 botan3-3.12.0+dfsg/src/lib/block/twofish/twofish.h | 22 botan3-3.12.0+dfsg/src/lib/block/twofish/twofish_avx512/info.txt | 22 botan3-3.12.0+dfsg/src/lib/block/twofish/twofish_avx512/twofish_avx512.cpp | 197 botan3-3.12.0+dfsg/src/lib/block/twofish/twofish_tab.cpp | 204 botan3-3.12.0+dfsg/src/lib/codec/base32/base32.cpp | 71 botan3-3.12.0+dfsg/src/lib/codec/base32/base32.h | 14 botan3-3.12.0+dfsg/src/lib/codec/base58/base58.cpp | 191 botan3-3.12.0+dfsg/src/lib/codec/base58/base58.h | 1 botan3-3.12.0+dfsg/src/lib/codec/base64/base64.cpp | 27 botan3-3.12.0+dfsg/src/lib/codec/hex/hex.cpp | 11 botan3-3.12.0+dfsg/src/lib/compat/sodium/sodium.h | 12 botan3-3.12.0+dfsg/src/lib/compat/sodium/sodium_25519.cpp | 8 botan3-3.12.0+dfsg/src/lib/compat/sodium/sodium_aead.cpp | 17 botan3-3.12.0+dfsg/src/lib/compat/sodium/sodium_auth.cpp | 9 botan3-3.12.0+dfsg/src/lib/compat/sodium/sodium_chacha.cpp | 4 botan3-3.12.0+dfsg/src/lib/compat/sodium/sodium_salsa.cpp | 4 botan3-3.12.0+dfsg/src/lib/compat/sodium/sodium_secretbox.cpp | 1 botan3-3.12.0+dfsg/src/lib/compat/sodium/sodium_utils.cpp | 40 botan3-3.12.0+dfsg/src/lib/compression/bzip2/bzip2.cpp | 10 botan3-3.12.0+dfsg/src/lib/compression/compress_utils.cpp | 28 botan3-3.12.0+dfsg/src/lib/compression/compression.h | 9 botan3-3.12.0+dfsg/src/lib/compression/lzma/lzma.cpp | 8 botan3-3.12.0+dfsg/src/lib/compression/zlib/zlib.cpp | 16 botan3-3.12.0+dfsg/src/lib/entropy/entropy_src.h | 19 botan3-3.12.0+dfsg/src/lib/entropy/entropy_srcs.cpp | 27 botan3-3.12.0+dfsg/src/lib/entropy/getentropy/getentropy.cpp | 2 botan3-3.12.0+dfsg/src/lib/entropy/getentropy/info.txt | 4 botan3-3.12.0+dfsg/src/lib/entropy/rdseed/info.txt | 8 botan3-3.12.0+dfsg/src/lib/entropy/rdseed/rdseed.cpp | 22 botan3-3.12.0+dfsg/src/lib/entropy/win32_stats/es_win32.cpp | 2 botan3-3.12.0+dfsg/src/lib/entropy/win32_stats/info.txt | 4 botan3-3.12.0+dfsg/src/lib/ffi/ffi.cpp | 145 botan3-3.12.0+dfsg/src/lib/ffi/ffi.h | 1038 ++ botan3-3.12.0+dfsg/src/lib/ffi/ffi_block.cpp | 3 botan3-3.12.0+dfsg/src/lib/ffi/ffi_cert.cpp | 1104 ++ botan3-3.12.0+dfsg/src/lib/ffi/ffi_cert.h | 31 botan3-3.12.0+dfsg/src/lib/ffi/ffi_cipher.cpp | 50 botan3-3.12.0+dfsg/src/lib/ffi/ffi_ec.cpp | 355 botan3-3.12.0+dfsg/src/lib/ffi/ffi_ec.h | 21 botan3-3.12.0+dfsg/src/lib/ffi/ffi_fpe.cpp | 12 botan3-3.12.0+dfsg/src/lib/ffi/ffi_hash.cpp | 8 botan3-3.12.0+dfsg/src/lib/ffi/ffi_hotp.cpp | 5 botan3-3.12.0+dfsg/src/lib/ffi/ffi_kdf.cpp | 34 botan3-3.12.0+dfsg/src/lib/ffi/ffi_keywrap.cpp | 11 botan3-3.12.0+dfsg/src/lib/ffi/ffi_mac.cpp | 28 botan3-3.12.0+dfsg/src/lib/ffi/ffi_mp.cpp | 83 botan3-3.12.0+dfsg/src/lib/ffi/ffi_oid.cpp | 80 botan3-3.12.0+dfsg/src/lib/ffi/ffi_oid.h | 19 botan3-3.12.0+dfsg/src/lib/ffi/ffi_pk_op.cpp | 31 botan3-3.12.0+dfsg/src/lib/ffi/ffi_pkey.cpp | 146 botan3-3.12.0+dfsg/src/lib/ffi/ffi_pkey_algs.cpp | 597 + botan3-3.12.0+dfsg/src/lib/ffi/ffi_rng.cpp | 86 botan3-3.12.0+dfsg/src/lib/ffi/ffi_srp6.cpp | 48 botan3-3.12.0+dfsg/src/lib/ffi/ffi_totp.cpp | 5 botan3-3.12.0+dfsg/src/lib/ffi/ffi_tpm2.cpp | 28 botan3-3.12.0+dfsg/src/lib/ffi/ffi_util.h | 129 botan3-3.12.0+dfsg/src/lib/ffi/ffi_xof.cpp | 96 botan3-3.12.0+dfsg/src/lib/ffi/ffi_zfec.cpp | 7 botan3-3.12.0+dfsg/src/lib/ffi/info.txt | 14 botan3-3.12.0+dfsg/src/lib/filters/algo_filt.cpp | 8 botan3-3.12.0+dfsg/src/lib/filters/b64_filt.cpp | 28 botan3-3.12.0+dfsg/src/lib/filters/basefilt.cpp | 10 botan3-3.12.0+dfsg/src/lib/filters/buf_filt.cpp | 16 botan3-3.12.0+dfsg/src/lib/filters/cipher_filter.cpp | 2 botan3-3.12.0+dfsg/src/lib/filters/comp_filter.cpp | 5 botan3-3.12.0+dfsg/src/lib/filters/data_snk.cpp | 4 botan3-3.12.0+dfsg/src/lib/filters/data_snk.h | 17 botan3-3.12.0+dfsg/src/lib/filters/fd_unix/fd_unix.cpp | 12 botan3-3.12.0+dfsg/src/lib/filters/filter.cpp | 25 botan3-3.12.0+dfsg/src/lib/filters/filter.h | 29 botan3-3.12.0+dfsg/src/lib/filters/filters.h | 82 botan3-3.12.0+dfsg/src/lib/filters/hex_filt.cpp | 21 botan3-3.12.0+dfsg/src/lib/filters/out_buf.cpp | 21 botan3-3.12.0+dfsg/src/lib/filters/out_buf.h | 16 botan3-3.12.0+dfsg/src/lib/filters/pipe.cpp | 61 botan3-3.12.0+dfsg/src/lib/filters/pipe.h | 34 botan3-3.12.0+dfsg/src/lib/filters/pipe_io.cpp | 10 botan3-3.12.0+dfsg/src/lib/filters/pipe_rw.cpp | 17 botan3-3.12.0+dfsg/src/lib/filters/secqueue.cpp | 59 botan3-3.12.0+dfsg/src/lib/filters/secqueue.h | 6 botan3-3.12.0+dfsg/src/lib/filters/threaded_fork.cpp | 7 botan3-3.12.0+dfsg/src/lib/hash/ascon_hash256/ascon_hash256.cpp | 56 botan3-3.12.0+dfsg/src/lib/hash/ascon_hash256/ascon_hash256.h | 45 botan3-3.12.0+dfsg/src/lib/hash/ascon_hash256/info.txt | 11 botan3-3.12.0+dfsg/src/lib/hash/blake2/blake2b.cpp | 6 botan3-3.12.0+dfsg/src/lib/hash/blake2/blake2b.h | 3 botan3-3.12.0+dfsg/src/lib/hash/blake2/info.txt | 2 botan3-3.12.0+dfsg/src/lib/hash/blake2s/blake2s.cpp | 150 botan3-3.12.0+dfsg/src/lib/hash/blake2s/blake2s.h | 36 botan3-3.12.0+dfsg/src/lib/hash/checksum/adler32/adler32.h | 11 botan3-3.12.0+dfsg/src/lib/hash/checksum/crc24/crc24.cpp | 2 botan3-3.12.0+dfsg/src/lib/hash/checksum/crc24/crc24.h | 12 botan3-3.12.0+dfsg/src/lib/hash/checksum/crc32/crc32.cpp | 40 botan3-3.12.0+dfsg/src/lib/hash/checksum/crc32/crc32.h | 10 botan3-3.12.0+dfsg/src/lib/hash/comb4p/comb4p.cpp | 3 botan3-3.12.0+dfsg/src/lib/hash/gost_3411/gost_3411.cpp | 17 botan3-3.12.0+dfsg/src/lib/hash/gost_3411/gost_3411.h | 4 botan3-3.12.0+dfsg/src/lib/hash/hash.cpp | 10 botan3-3.12.0+dfsg/src/lib/hash/hash.h | 2 botan3-3.12.0+dfsg/src/lib/hash/keccak/keccak.cpp | 5 botan3-3.12.0+dfsg/src/lib/hash/md4/md4.cpp | 8 botan3-3.12.0+dfsg/src/lib/hash/md5/md5.cpp | 9 botan3-3.12.0+dfsg/src/lib/hash/mdx_hash/info.txt | 4 botan3-3.12.0+dfsg/src/lib/hash/mdx_hash/mdx_hash.h | 6 botan3-3.12.0+dfsg/src/lib/hash/par_hash/par_hash.cpp | 2 botan3-3.12.0+dfsg/src/lib/hash/par_hash/par_hash.h | 7 botan3-3.12.0+dfsg/src/lib/hash/rmd160/rmd160.cpp | 28 botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1.cpp | 258 botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1.h | 8 botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1_armv8/info.txt | 8 botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1_armv8/sha1_armv8.cpp | 40 botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1_avx2/info.txt | 24 botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1_avx2/sha1_avx2.cpp | 554 + botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1_f.h | 44 botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1_simd/info.txt | 37 botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1_simd/sha1_simd.cpp | 254 botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1_sse2/info.txt | 16 botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1_sse2/sha1_sse2.cpp | 286 botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1_x86/info.txt | 9 botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1_x86/sha1_x86.cpp | 300 botan3-3.12.0+dfsg/src/lib/hash/sha2_32/info.txt | 4 botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32.cpp | 68 botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32.h | 8 botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32_armv8/info.txt | 9 botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32_armv8/sha2_32_armv8.cpp | 200 botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32_avx2/info.txt | 19 botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32_avx2/sha2_32_avx2.cpp | 362 botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32_bmi2/info.txt | 12 botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32_bmi2/sha2_32_bmi2.cpp | 118 botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32_f.h | 20 botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32_simd/info.txt | 51 botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32_simd/sha2_32_simd.cpp | 155 botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32_x86/info.txt | 9 botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32_x86/sha2_32_x86.cpp | 266 botan3-3.12.0+dfsg/src/lib/hash/sha2_64/info.txt | 4 botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64.cpp | 63 botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64.h | 12 botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64_armv8/info.txt | 9 botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64_armv8/sha2_64_armv8.cpp | 15 botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64_avx2/info.txt | 25 botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64_avx2/sha2_64_avx2.cpp | 346 botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64_avx512/info.txt | 25 botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64_avx512/sha2_64_avx512.cpp | 235 botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64_bmi2/info.txt | 17 botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64_bmi2/sha2_64_bmi2.cpp | 124 botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64_f.h | 20 botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64_x86/info.txt | 26 botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64_x86/sha2_64_x86.cpp | 125 botan3-3.12.0+dfsg/src/lib/hash/sha3/sha3.cpp | 5 botan3-3.12.0+dfsg/src/lib/hash/shake/shake.cpp | 10 botan3-3.12.0+dfsg/src/lib/hash/skein/skein_512.cpp | 25 botan3-3.12.0+dfsg/src/lib/hash/skein/skein_512.h | 9 botan3-3.12.0+dfsg/src/lib/hash/sm3/sm3.cpp | 249 botan3-3.12.0+dfsg/src/lib/hash/sm3/sm3.h | 14 botan3-3.12.0+dfsg/src/lib/hash/sm3/sm3_armv8/info.txt | 16 botan3-3.12.0+dfsg/src/lib/hash/sm3/sm3_armv8/sm3_armv8.cpp | 170 botan3-3.12.0+dfsg/src/lib/hash/sm3/sm3_avx2_bmi2/info.txt | 19 botan3-3.12.0+dfsg/src/lib/hash/sm3/sm3_avx2_bmi2/sm3_avx2_bmi2.cpp | 422 + botan3-3.12.0+dfsg/src/lib/hash/sm3/sm3_fn.h | 75 botan3-3.12.0+dfsg/src/lib/hash/sm3/sm3_x86/info.txt | 25 botan3-3.12.0+dfsg/src/lib/hash/sm3/sm3_x86/sm3_x86.cpp | 134 botan3-3.12.0+dfsg/src/lib/hash/streebog/streebog.cpp | 155 botan3-3.12.0+dfsg/src/lib/hash/streebog/streebog_precalc.cpp | 549 - botan3-3.12.0+dfsg/src/lib/hash/trunc_hash/trunc_hash.cpp | 10 botan3-3.12.0+dfsg/src/lib/hash/trunc_hash/trunc_hash.h | 4 botan3-3.12.0+dfsg/src/lib/hash/whirlpool/whirlpool.cpp | 180 botan3-3.12.0+dfsg/src/lib/hash/whirlpool/whirlpool.h | 10 botan3-3.12.0+dfsg/src/lib/hash/whirlpool/whirlpool_avx2/info.txt | 16 botan3-3.12.0+dfsg/src/lib/hash/whirlpool/whirlpool_avx2/whirlpool_avx2.cpp | 254 botan3-3.12.0+dfsg/src/lib/hash/whirlpool/whirlpool_avx512/info.txt | 20 botan3-3.12.0+dfsg/src/lib/hash/whirlpool/whirlpool_avx512/whirlpool_avx512.cpp | 239 botan3-3.12.0+dfsg/src/lib/kdf/hkdf/hkdf.cpp | 17 botan3-3.12.0+dfsg/src/lib/kdf/info.txt | 3 botan3-3.12.0+dfsg/src/lib/kdf/kdf.cpp | 7 botan3-3.12.0+dfsg/src/lib/kdf/kdf.h | 36 botan3-3.12.0+dfsg/src/lib/kdf/kdf1/kdf1.cpp | 4 botan3-3.12.0+dfsg/src/lib/kdf/kdf1_iso18033/kdf1_iso18033.cpp | 5 botan3-3.12.0+dfsg/src/lib/kdf/kdf2/kdf2.cpp | 5 botan3-3.12.0+dfsg/src/lib/kdf/prf_tls/prf_tls.cpp | 4 botan3-3.12.0+dfsg/src/lib/kdf/prf_x942/prf_x942.cpp | 10 botan3-3.12.0+dfsg/src/lib/kdf/sp800_108/sp800_108.cpp | 31 botan3-3.12.0+dfsg/src/lib/kdf/sp800_56a/sp800_56c_one_step.cpp | 36 botan3-3.12.0+dfsg/src/lib/kdf/xmd/xmd.cpp | 3 botan3-3.12.0+dfsg/src/lib/kdf/xmd/xmd.h | 11 botan3-3.12.0+dfsg/src/lib/mac/blake2mac/blake2bmac.h | 3 botan3-3.12.0+dfsg/src/lib/mac/cmac/cmac.cpp | 5 botan3-3.12.0+dfsg/src/lib/mac/cmac/cmac.h | 9 botan3-3.12.0+dfsg/src/lib/mac/gmac/gmac.cpp | 16 botan3-3.12.0+dfsg/src/lib/mac/gmac/gmac.h | 11 botan3-3.12.0+dfsg/src/lib/mac/hmac/hmac.cpp | 3 botan3-3.12.0+dfsg/src/lib/mac/hmac/hmac.h | 9 botan3-3.12.0+dfsg/src/lib/mac/kmac/kmac.cpp | 5 botan3-3.12.0+dfsg/src/lib/mac/kmac/kmac.h | 14 botan3-3.12.0+dfsg/src/lib/mac/mac.cpp | 1 botan3-3.12.0+dfsg/src/lib/mac/mac.h | 4 botan3-3.12.0+dfsg/src/lib/mac/poly1305/poly1305.cpp | 346 botan3-3.12.0+dfsg/src/lib/mac/poly1305/poly1305.h | 17 botan3-3.12.0+dfsg/src/lib/mac/poly1305/poly1305_avx2/info.txt | 17 botan3-3.12.0+dfsg/src/lib/mac/poly1305/poly1305_avx2/poly1305_avx2.cpp | 255 botan3-3.12.0+dfsg/src/lib/mac/poly1305/poly1305_avx512/info.txt | 17 botan3-3.12.0+dfsg/src/lib/mac/poly1305/poly1305_avx512/poly1305_avx512.cpp | 222 botan3-3.12.0+dfsg/src/lib/mac/siphash/info.txt | 1 botan3-3.12.0+dfsg/src/lib/mac/siphash/siphash.cpp | 9 botan3-3.12.0+dfsg/src/lib/mac/siphash/siphash.h | 8 botan3-3.12.0+dfsg/src/lib/mac/x919_mac/x919_mac.cpp | 7 botan3-3.12.0+dfsg/src/lib/mac/x919_mac/x919_mac.h | 11 botan3-3.12.0+dfsg/src/lib/math/bigint/big_code.cpp | 148 botan3-3.12.0+dfsg/src/lib/math/bigint/big_io.cpp | 8 botan3-3.12.0+dfsg/src/lib/math/bigint/big_ops2.cpp | 67 botan3-3.12.0+dfsg/src/lib/math/bigint/big_ops3.cpp | 62 botan3-3.12.0+dfsg/src/lib/math/bigint/big_rand.cpp | 21 botan3-3.12.0+dfsg/src/lib/math/bigint/bigint.cpp | 199 botan3-3.12.0+dfsg/src/lib/math/bigint/bigint.h | 183 botan3-3.12.0+dfsg/src/lib/math/bigint/divide.cpp | 200 botan3-3.12.0+dfsg/src/lib/math/bigint/divide.h | 29 botan3-3.12.0+dfsg/src/lib/math/mp/info.txt | 4 botan3-3.12.0+dfsg/src/lib/math/mp/mp_asmi.h | 441 - botan3-3.12.0+dfsg/src/lib/math/mp/mp_comba.cpp | 3 botan3-3.12.0+dfsg/src/lib/math/mp/mp_core.h | 615 - botan3-3.12.0+dfsg/src/lib/math/mp/mp_karat.cpp | 59 botan3-3.12.0+dfsg/src/lib/math/mp/mp_monty.cpp | 87 botan3-3.12.0+dfsg/src/lib/math/mp/mp_monty_n.cpp | 232 botan3-3.12.0+dfsg/src/lib/math/numbertheory/barrett.cpp | 203 botan3-3.12.0+dfsg/src/lib/math/numbertheory/barrett.h | 84 botan3-3.12.0+dfsg/src/lib/math/numbertheory/dsa_gen.cpp | 13 botan3-3.12.0+dfsg/src/lib/math/numbertheory/info.txt | 1 botan3-3.12.0+dfsg/src/lib/math/numbertheory/make_prm.cpp | 53 botan3-3.12.0+dfsg/src/lib/math/numbertheory/mod_inv.cpp | 60 botan3-3.12.0+dfsg/src/lib/math/numbertheory/mod_inv.h | 2 botan3-3.12.0+dfsg/src/lib/math/numbertheory/monty.cpp | 467 - botan3-3.12.0+dfsg/src/lib/math/numbertheory/monty.h | 207 botan3-3.12.0+dfsg/src/lib/math/numbertheory/monty_exp.cpp | 87 botan3-3.12.0+dfsg/src/lib/math/numbertheory/monty_exp.h | 35 botan3-3.12.0+dfsg/src/lib/math/numbertheory/numthry.cpp | 115 botan3-3.12.0+dfsg/src/lib/math/numbertheory/numthry.h | 19 botan3-3.12.0+dfsg/src/lib/math/numbertheory/primality.cpp | 47 botan3-3.12.0+dfsg/src/lib/math/numbertheory/primality.h | 23 botan3-3.12.0+dfsg/src/lib/math/numbertheory/reducer.cpp | 128 botan3-3.12.0+dfsg/src/lib/math/numbertheory/reducer.h | 20 botan3-3.12.0+dfsg/src/lib/math/pcurves/info.txt | 7 botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves.cpp | 224 botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves.h | 232 botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_algos.h | 503 + botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_brainpool256r1/info.txt | 4 botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_brainpool256r1/pcurves_brainpool256r1.cpp | 9 botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_brainpool384r1/info.txt | 4 botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_brainpool384r1/pcurves_brainpool384r1.cpp | 10 botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_brainpool512r1/info.txt | 4 botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_brainpool512r1/pcurves_brainpool512r1.cpp | 9 botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_frp256v1/info.txt | 4 botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_frp256v1/pcurves_frp256v1.cpp | 9 botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_generic/info.txt | 13 botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_generic/pcurves_generic.cpp | 1751 ++++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_generic/pcurves_generic.h | 136 botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_id.h | 76 botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_impl/info.txt | 4 botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_impl/pcurves_impl.h | 1330 +-- botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_impl/pcurves_solinas.h | 26 botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_impl/pcurves_util.h | 81 botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_impl/pcurves_wrap.h | 118 botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_instance.h | 61 botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_mul.h | 546 + botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_numsp512d1/info.txt | 4 botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_numsp512d1/pcurves_numsp512d1.cpp | 46 botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_secp192r1/info.txt | 4 botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_secp192r1/pcurves_secp192r1.cpp | 70 botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_secp224r1/info.txt | 4 botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_secp224r1/pcurves_secp224r1.cpp | 3 botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_secp256k1/info.txt | 4 botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_secp256k1/pcurves_secp256k1.cpp | 48 botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_secp256r1/info.txt | 4 botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_secp256r1/pcurves_secp256r1.cpp | 28 botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_secp384r1/info.txt | 4 botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_secp384r1/pcurves_secp384r1.cpp | 45 botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_secp521r1/info.txt | 4 botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_secp521r1/pcurves_secp521r1.cpp | 46 botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_sm2p256v1/info.txt | 4 botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_sm2p256v1/pcurves_sm2p256v1.cpp | 40 botan3-3.12.0+dfsg/src/lib/misc/cryptobox/cryptobox.cpp | 25 botan3-3.12.0+dfsg/src/lib/misc/cryptobox/info.txt | 3 botan3-3.12.0+dfsg/src/lib/misc/fpe_fe1/fpe_fe1.cpp | 33 botan3-3.12.0+dfsg/src/lib/misc/fpe_fe1/fpe_fe1.h | 23 botan3-3.12.0+dfsg/src/lib/misc/hotp/hotp.cpp | 31 botan3-3.12.0+dfsg/src/lib/misc/hotp/otp.h | 14 botan3-3.12.0+dfsg/src/lib/misc/hotp/totp.cpp | 3 botan3-3.12.0+dfsg/src/lib/misc/nist_keywrap/nist_keywrap.cpp | 30 botan3-3.12.0+dfsg/src/lib/misc/nist_keywrap/nist_keywrap.h | 41 botan3-3.12.0+dfsg/src/lib/misc/rfc3394/rfc3394.cpp | 1 botan3-3.12.0+dfsg/src/lib/misc/roughtime/roughtime.cpp | 48 botan3-3.12.0+dfsg/src/lib/misc/roughtime/roughtime.h | 8 botan3-3.12.0+dfsg/src/lib/misc/srp6/srp6.cpp | 8 botan3-3.12.0+dfsg/src/lib/misc/srp6/srp6.h | 2 botan3-3.12.0+dfsg/src/lib/misc/tss/tss.cpp | 135 botan3-3.12.0+dfsg/src/lib/misc/zfec/zfec.cpp | 52 botan3-3.12.0+dfsg/src/lib/misc/zfec/zfec.h | 4 botan3-3.12.0+dfsg/src/lib/misc/zfec/zfec_sse2/info.txt | 16 botan3-3.12.0+dfsg/src/lib/misc/zfec/zfec_sse2/zfec_sse2.cpp | 93 botan3-3.12.0+dfsg/src/lib/misc/zfec/zfec_vperm/info.txt | 13 botan3-3.12.0+dfsg/src/lib/misc/zfec/zfec_vperm/zfec_vperm.cpp | 33 botan3-3.12.0+dfsg/src/lib/modes/aead/aead.cpp | 30 botan3-3.12.0+dfsg/src/lib/modes/aead/aead.h | 2 botan3-3.12.0+dfsg/src/lib/modes/aead/ascon_aead128/ascon_aead128.cpp | 156 botan3-3.12.0+dfsg/src/lib/modes/aead/ascon_aead128/ascon_aead128.h | 99 botan3-3.12.0+dfsg/src/lib/modes/aead/ascon_aead128/info.txt | 11 botan3-3.12.0+dfsg/src/lib/modes/aead/ccm/ccm.cpp | 22 botan3-3.12.0+dfsg/src/lib/modes/aead/ccm/ccm.h | 8 botan3-3.12.0+dfsg/src/lib/modes/aead/chacha20poly1305/chacha20poly1305.cpp | 34 botan3-3.12.0+dfsg/src/lib/modes/aead/chacha20poly1305/chacha20poly1305.h | 14 botan3-3.12.0+dfsg/src/lib/modes/aead/eax/eax.cpp | 8 botan3-3.12.0+dfsg/src/lib/modes/aead/eax/eax.h | 20 botan3-3.12.0+dfsg/src/lib/modes/aead/gcm/gcm.cpp | 38 botan3-3.12.0+dfsg/src/lib/modes/aead/gcm/gcm.h | 42 botan3-3.12.0+dfsg/src/lib/modes/aead/ocb/ocb.cpp | 41 botan3-3.12.0+dfsg/src/lib/modes/aead/ocb/ocb.h | 44 botan3-3.12.0+dfsg/src/lib/modes/aead/siv/siv.cpp | 13 botan3-3.12.0+dfsg/src/lib/modes/aead/siv/siv.h | 36 botan3-3.12.0+dfsg/src/lib/modes/cbc/cbc.cpp | 22 botan3-3.12.0+dfsg/src/lib/modes/cbc/cbc.h | 1 botan3-3.12.0+dfsg/src/lib/modes/cfb/cfb.cpp | 9 botan3-3.12.0+dfsg/src/lib/modes/cfb/cfb.h | 6 botan3-3.12.0+dfsg/src/lib/modes/cipher_mode.cpp | 7 botan3-3.12.0+dfsg/src/lib/modes/cipher_mode.h | 11 botan3-3.12.0+dfsg/src/lib/modes/mode_pad/mode_pad.cpp | 229 botan3-3.12.0+dfsg/src/lib/modes/mode_pad/mode_pad.h | 84 botan3-3.12.0+dfsg/src/lib/modes/stream_mode.h | 4 botan3-3.12.0+dfsg/src/lib/modes/xts/xts.cpp | 69 botan3-3.12.0+dfsg/src/lib/modes/xts/xts.h | 18 botan3-3.12.0+dfsg/src/lib/modes/xts/xts_avx512_clmul/info.txt | 15 botan3-3.12.0+dfsg/src/lib/modes/xts/xts_avx512_clmul/xts_avx512_clmul.cpp | 65 botan3-3.12.0+dfsg/src/lib/passhash/argon2fmt/argon2fmt.cpp | 108 botan3-3.12.0+dfsg/src/lib/passhash/bcrypt/bcrypt.cpp | 51 botan3-3.12.0+dfsg/src/lib/passhash/passhash9/passhash9.cpp | 11 botan3-3.12.0+dfsg/src/lib/pbkdf/argon2/argon2.cpp | 53 botan3-3.12.0+dfsg/src/lib/pbkdf/argon2/argon2.h | 23 botan3-3.12.0+dfsg/src/lib/pbkdf/argon2/argon2_avx2/argon2_avx2.cpp | 128 botan3-3.12.0+dfsg/src/lib/pbkdf/argon2/argon2_avx2/info.txt | 9 botan3-3.12.0+dfsg/src/lib/pbkdf/argon2/argon2_avx512/argon2_avx512.cpp | 88 botan3-3.12.0+dfsg/src/lib/pbkdf/argon2/argon2_avx512/info.txt | 17 botan3-3.12.0+dfsg/src/lib/pbkdf/argon2/argon2_simd64/argon2_simd64.cpp | 114 botan3-3.12.0+dfsg/src/lib/pbkdf/argon2/argon2_simd64/info.txt | 18 botan3-3.12.0+dfsg/src/lib/pbkdf/argon2/argon2_ssse3/argon2_ssse3.cpp | 234 botan3-3.12.0+dfsg/src/lib/pbkdf/argon2/argon2_ssse3/info.txt | 17 botan3-3.12.0+dfsg/src/lib/pbkdf/argon2/argon2pwhash.cpp | 17 botan3-3.12.0+dfsg/src/lib/pbkdf/bcrypt_pbkdf/bcrypt_pbkdf.cpp | 23 botan3-3.12.0+dfsg/src/lib/pbkdf/bcrypt_pbkdf/bcrypt_pbkdf.h | 17 botan3-3.12.0+dfsg/src/lib/pbkdf/pbkdf.h | 7 botan3-3.12.0+dfsg/src/lib/pbkdf/pbkdf2/pbkdf2.cpp | 41 botan3-3.12.0+dfsg/src/lib/pbkdf/pbkdf2/pbkdf2.h | 14 botan3-3.12.0+dfsg/src/lib/pbkdf/pgp_s2k/pgp_s2k.cpp | 38 botan3-3.12.0+dfsg/src/lib/pbkdf/pgp_s2k/pgp_s2k.h | 14 botan3-3.12.0+dfsg/src/lib/pbkdf/pwdhash.h | 68 botan3-3.12.0+dfsg/src/lib/pbkdf/scrypt/scrypt.cpp | 31 botan3-3.12.0+dfsg/src/lib/pbkdf/scrypt/scrypt.h | 11 botan3-3.12.0+dfsg/src/lib/permutations/ascon_perm/ascon_perm.cpp | 115 botan3-3.12.0+dfsg/src/lib/permutations/ascon_perm/ascon_perm.h | 74 botan3-3.12.0+dfsg/src/lib/permutations/ascon_perm/info.txt | 8 botan3-3.12.0+dfsg/src/lib/permutations/keccak_perm/info.txt | 8 botan3-3.12.0+dfsg/src/lib/permutations/keccak_perm/keccak_helpers.cpp | 9 botan3-3.12.0+dfsg/src/lib/permutations/keccak_perm/keccak_helpers.h | 4 botan3-3.12.0+dfsg/src/lib/permutations/keccak_perm/keccak_perm.cpp | 143 botan3-3.12.0+dfsg/src/lib/permutations/keccak_perm/keccak_perm.h | 61 botan3-3.12.0+dfsg/src/lib/permutations/keccak_perm/keccak_perm_avx512/info.txt | 15 botan3-3.12.0+dfsg/src/lib/permutations/keccak_perm/keccak_perm_avx512/keccak_perm_avx512.cpp | 153 botan3-3.12.0+dfsg/src/lib/permutations/keccak_perm/keccak_perm_bmi2/info.txt | 14 botan3-3.12.0+dfsg/src/lib/permutations/keccak_perm/keccak_perm_bmi2/keccak_perm_bmi2.cpp | 7 botan3-3.12.0+dfsg/src/lib/permutations/keccak_perm/keccak_perm_round.h | 2 botan3-3.12.0+dfsg/src/lib/permutations/sponge/info.txt | 4 botan3-3.12.0+dfsg/src/lib/permutations/sponge/sponge.h | 72 botan3-3.12.0+dfsg/src/lib/permutations/sponge/sponge_processing.h | 264 botan3-3.12.0+dfsg/src/lib/pk_pad/eme.cpp | 71 botan3-3.12.0+dfsg/src/lib/pk_pad/eme.h | 67 botan3-3.12.0+dfsg/src/lib/pk_pad/eme_oaep/info.txt | 12 botan3-3.12.0+dfsg/src/lib/pk_pad/eme_oaep/oaep.cpp | 159 botan3-3.12.0+dfsg/src/lib/pk_pad/eme_oaep/oaep.h | 55 botan3-3.12.0+dfsg/src/lib/pk_pad/eme_pkcs1/eme_pkcs.cpp | 106 botan3-3.12.0+dfsg/src/lib/pk_pad/eme_pkcs1/eme_pkcs.h | 32 botan3-3.12.0+dfsg/src/lib/pk_pad/eme_pkcs1/info.txt | 9 botan3-3.12.0+dfsg/src/lib/pk_pad/eme_raw/eme_raw.cpp | 40 botan3-3.12.0+dfsg/src/lib/pk_pad/eme_raw/eme_raw.h | 31 botan3-3.12.0+dfsg/src/lib/pk_pad/eme_raw/info.txt | 7 botan3-3.12.0+dfsg/src/lib/pk_pad/emsa.cpp | 143 botan3-3.12.0+dfsg/src/lib/pk_pad/emsa.h | 88 botan3-3.12.0+dfsg/src/lib/pk_pad/emsa_pkcs1/emsa_pkcs1.cpp | 133 botan3-3.12.0+dfsg/src/lib/pk_pad/emsa_pkcs1/emsa_pkcs1.h | 81 botan3-3.12.0+dfsg/src/lib/pk_pad/emsa_pkcs1/info.txt | 12 botan3-3.12.0+dfsg/src/lib/pk_pad/emsa_pssr/info.txt | 13 botan3-3.12.0+dfsg/src/lib/pk_pad/emsa_pssr/pssr.cpp | 245 botan3-3.12.0+dfsg/src/lib/pk_pad/emsa_pssr/pssr.h | 92 botan3-3.12.0+dfsg/src/lib/pk_pad/emsa_raw/emsa_raw.cpp | 92 botan3-3.12.0+dfsg/src/lib/pk_pad/emsa_raw/emsa_raw.h | 41 botan3-3.12.0+dfsg/src/lib/pk_pad/emsa_raw/info.txt | 7 botan3-3.12.0+dfsg/src/lib/pk_pad/emsa_x931/emsa_x931.cpp | 97 botan3-3.12.0+dfsg/src/lib/pk_pad/emsa_x931/emsa_x931.h | 47 botan3-3.12.0+dfsg/src/lib/pk_pad/emsa_x931/info.txt | 11 botan3-3.12.0+dfsg/src/lib/pk_pad/enc_padding/eme_oaep/info.txt | 12 botan3-3.12.0+dfsg/src/lib/pk_pad/enc_padding/eme_oaep/oaep.cpp | 159 botan3-3.12.0+dfsg/src/lib/pk_pad/enc_padding/eme_oaep/oaep.h | 55 botan3-3.12.0+dfsg/src/lib/pk_pad/enc_padding/eme_pkcs1/eme_pkcs.cpp | 105 botan3-3.12.0+dfsg/src/lib/pk_pad/enc_padding/eme_pkcs1/eme_pkcs.h | 32 botan3-3.12.0+dfsg/src/lib/pk_pad/enc_padding/eme_pkcs1/info.txt | 11 botan3-3.12.0+dfsg/src/lib/pk_pad/enc_padding/eme_raw/eme_raw.cpp | 40 botan3-3.12.0+dfsg/src/lib/pk_pad/enc_padding/eme_raw/eme_raw.h | 31 botan3-3.12.0+dfsg/src/lib/pk_pad/enc_padding/eme_raw/info.txt | 7 botan3-3.12.0+dfsg/src/lib/pk_pad/enc_padding/enc_padding.cpp | 70 botan3-3.12.0+dfsg/src/lib/pk_pad/enc_padding/enc_padding.h | 68 botan3-3.12.0+dfsg/src/lib/pk_pad/enc_padding/info.txt | 13 botan3-3.12.0+dfsg/src/lib/pk_pad/hash_id/info.txt | 4 botan3-3.12.0+dfsg/src/lib/pk_pad/info.txt | 13 botan3-3.12.0+dfsg/src/lib/pk_pad/iso9796/info.txt | 13 botan3-3.12.0+dfsg/src/lib/pk_pad/iso9796/iso9796.cpp | 281 botan3-3.12.0+dfsg/src/lib/pk_pad/iso9796/iso9796.h | 91 botan3-3.12.0+dfsg/src/lib/pk_pad/mgf1/mgf1.cpp | 25 botan3-3.12.0+dfsg/src/lib/pk_pad/mgf1/mgf1.h | 12 botan3-3.12.0+dfsg/src/lib/pk_pad/raw_hash/info.txt | 4 botan3-3.12.0+dfsg/src/lib/pk_pad/raw_hash/raw_hash.cpp | 1 botan3-3.12.0+dfsg/src/lib/pk_pad/raw_hash/raw_hash.h | 3 botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/emsa_pkcs1/info.txt | 12 botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/emsa_pkcs1/pkcs1_sig_padding.cpp | 151 botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/emsa_pkcs1/pkcs1_sig_padding.h | 91 botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/emsa_pssr/info.txt | 13 botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/emsa_pssr/pssr.cpp | 256 botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/emsa_pssr/pssr.h | 97 botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/emsa_raw/info.txt | 8 botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/emsa_raw/raw_sig_padding.cpp | 75 botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/emsa_raw/raw_sig_padding.h | 47 botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/emsa_x931/info.txt | 12 botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/emsa_x931/x931_sig_padding.cpp | 103 botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/emsa_x931/x931_sig_padding.h | 52 botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/info.txt | 13 botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/iso9796/info.txt | 13 botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/iso9796/iso9796.cpp | 304 botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/iso9796/iso9796.h | 88 botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/sig_padding.cpp | 143 botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/sig_padding.h | 90 botan3-3.12.0+dfsg/src/lib/prov/commoncrypto/commoncrypto.h | 2 botan3-3.12.0+dfsg/src/lib/prov/commoncrypto/commoncrypto_hash.cpp | 3 botan3-3.12.0+dfsg/src/lib/prov/commoncrypto/commoncrypto_mode.cpp | 8 botan3-3.12.0+dfsg/src/lib/prov/tpm/info.txt | 4 botan3-3.12.0+dfsg/src/lib/prov/tpm/tpm.cpp | 1 botan3-3.12.0+dfsg/src/lib/prov/tpm2/info.txt | 2 botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_algo_mappings.h | 2 botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_context.cpp | 35 botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_context.h | 5 botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_crypto_backend/info.txt | 2 botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_crypto_backend/tpm2_crypto_backend.h | 2 botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_crypto_backend/tpm2_crypto_backend_impl.cpp | 96 botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_ecc/tpm2_ecc.cpp | 28 botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_ecc/tpm2_ecc.h | 6 botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_hash.cpp | 16 botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_key.cpp | 27 botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_key.h | 2 botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_object.cpp | 6 botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_pkops.cpp | 6 botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_rng.cpp | 7 botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_rng.h | 2 botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_rsa/info.txt | 2 botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_rsa/tpm2_rsa.cpp | 38 botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_rsa/tpm2_rsa.h | 9 botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_session.cpp | 27 botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_session.h | 16 botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_util.h | 44 botan3-3.12.0+dfsg/src/lib/psk_db/psk_db.cpp | 21 botan3-3.12.0+dfsg/src/lib/psk_db/psk_db.h | 11 botan3-3.12.0+dfsg/src/lib/pubkey/blinding/blinding.cpp | 8 botan3-3.12.0+dfsg/src/lib/pubkey/blinding/blinding.h | 37 botan3-3.12.0+dfsg/src/lib/pubkey/blinding/info.txt | 4 botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce.cpp | 8 botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce.h | 7 botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_decaps.cpp | 13 botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_decaps.h | 1 botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_encaps.cpp | 13 botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_field_ordering.cpp | 16 botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_field_ordering.h | 4 botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_gf.cpp | 4 botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_gf.h | 5 botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_keys_internal.cpp | 3 botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_keys_internal.h | 8 botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_matrix.cpp | 29 botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_matrix.h | 4 botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_parameter_set.cpp | 2 botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_parameter_set.h | 5 botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_parameters.cpp | 2 botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_parameters.h | 5 botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_poly.cpp | 13 botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_poly.h | 8 botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_types.h | 2 botan3-3.12.0+dfsg/src/lib/pubkey/curve448/curve448_gf.cpp | 367 botan3-3.12.0+dfsg/src/lib/pubkey/curve448/curve448_gf.h | 32 botan3-3.12.0+dfsg/src/lib/pubkey/curve448/curve448_scalar.cpp | 52 botan3-3.12.0+dfsg/src/lib/pubkey/curve448/curve448_scalar.h | 11 botan3-3.12.0+dfsg/src/lib/pubkey/curve448/ed448/ed448.cpp | 34 botan3-3.12.0+dfsg/src/lib/pubkey/curve448/ed448/ed448.h | 6 botan3-3.12.0+dfsg/src/lib/pubkey/curve448/ed448/ed448_internal.cpp | 239 botan3-3.12.0+dfsg/src/lib/pubkey/curve448/ed448/ed448_internal.h | 20 botan3-3.12.0+dfsg/src/lib/pubkey/curve448/info.txt | 8 botan3-3.12.0+dfsg/src/lib/pubkey/curve448/x448/x448.cpp | 20 botan3-3.12.0+dfsg/src/lib/pubkey/curve448/x448/x448.h | 8 botan3-3.12.0+dfsg/src/lib/pubkey/curve448/x448/x448_internal.cpp | 20 botan3-3.12.0+dfsg/src/lib/pubkey/dh/dh.cpp | 12 botan3-3.12.0+dfsg/src/lib/pubkey/dh/dh.h | 10 botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium.cpp | 27 botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium.h | 23 botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_algos.cpp | 74 botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_algos.h | 1 botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_constants.cpp | 6 botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_constants.h | 18 botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_keys.h | 6 botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_polynomial.h | 10 botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_shake/dilithium_shake_xof.cpp | 25 botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_shake/dilithium_shake_xof.h | 31 botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_symmetric_primitives.cpp | 18 botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_symmetric_primitives.h | 77 botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/info.txt | 1 botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_round3/dilithium/dilithium_round3.h | 2 botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_round3/dilithium_aes/dilithium_aes.cpp | 18 botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_round3/dilithium_aes/dilithium_aes.h | 2 botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_round3/dilithium_round3_symmetric_primitives.cpp | 1 botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/ml_dsa/ml_dsa.h | 2 botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/ml_dsa/ml_dsa_impl.h | 2 botan3-3.12.0+dfsg/src/lib/pubkey/dl_algo/dl_scheme.cpp | 16 botan3-3.12.0+dfsg/src/lib/pubkey/dl_group/dl_group.cpp | 180 botan3-3.12.0+dfsg/src/lib/pubkey/dl_group/dl_group.h | 56 botan3-3.12.0+dfsg/src/lib/pubkey/dlies/dlies.cpp | 27 botan3-3.12.0+dfsg/src/lib/pubkey/dlies/dlies.h | 2 botan3-3.12.0+dfsg/src/lib/pubkey/dsa/dsa.cpp | 23 botan3-3.12.0+dfsg/src/lib/pubkey/dsa/dsa.h | 2 botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/ec_apoint.cpp | 39 botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/ec_apoint.h | 54 botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/ec_group.cpp | 358 botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/ec_group.h | 120 botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/ec_inner_data.cpp | 214 botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/ec_inner_data.h | 49 botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/ec_inner_pc.cpp | 78 botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/ec_inner_pc.h | 16 botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/ec_named.cpp | 48 botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/ec_point_format.h | 22 botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/ec_scalar.cpp | 38 botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/ec_scalar.h | 34 botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/legacy_ec_point/curve_gfp.h | 13 botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/legacy_ec_point/ec_inner_bn.cpp | 50 botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/legacy_ec_point/ec_inner_bn.h | 14 botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/legacy_ec_point/ec_point.cpp | 92 botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/legacy_ec_point/ec_point.h | 39 botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/legacy_ec_point/point_mul.cpp | 82 botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/legacy_ec_point/point_mul.h | 14 botan3-3.12.0+dfsg/src/lib/pubkey/ecc_key/ec_key_data.cpp | 32 botan3-3.12.0+dfsg/src/lib/pubkey/ecc_key/ec_key_data.h | 17 botan3-3.12.0+dfsg/src/lib/pubkey/ecc_key/ecc_key.cpp | 53 botan3-3.12.0+dfsg/src/lib/pubkey/ecc_key/ecc_key.h | 30 botan3-3.12.0+dfsg/src/lib/pubkey/ecdh/ecdh.cpp | 34 botan3-3.12.0+dfsg/src/lib/pubkey/ecdh/ecdh.h | 10 botan3-3.12.0+dfsg/src/lib/pubkey/ecdsa/ecdsa.cpp | 52 botan3-3.12.0+dfsg/src/lib/pubkey/ecdsa/ecdsa.h | 14 botan3-3.12.0+dfsg/src/lib/pubkey/ecgdsa/ecgdsa.cpp | 12 botan3-3.12.0+dfsg/src/lib/pubkey/ecgdsa/ecgdsa.h | 10 botan3-3.12.0+dfsg/src/lib/pubkey/ecies/ecies.cpp | 144 botan3-3.12.0+dfsg/src/lib/pubkey/ecies/ecies.h | 131 botan3-3.12.0+dfsg/src/lib/pubkey/eckcdsa/eckcdsa.cpp | 13 botan3-3.12.0+dfsg/src/lib/pubkey/eckcdsa/eckcdsa.h | 10 botan3-3.12.0+dfsg/src/lib/pubkey/ed25519/ed25519.cpp | 68 botan3-3.12.0+dfsg/src/lib/pubkey/ed25519/ed25519.h | 43 botan3-3.12.0+dfsg/src/lib/pubkey/ed25519/ed25519_fe.cpp | 219 botan3-3.12.0+dfsg/src/lib/pubkey/ed25519/ed25519_fe.h | 196 botan3-3.12.0+dfsg/src/lib/pubkey/ed25519/ed25519_internal.h | 40 botan3-3.12.0+dfsg/src/lib/pubkey/ed25519/ed25519_key.cpp | 79 botan3-3.12.0+dfsg/src/lib/pubkey/ed25519/ge.cpp | 951 -- botan3-3.12.0+dfsg/src/lib/pubkey/elgamal/elgamal.cpp | 26 botan3-3.12.0+dfsg/src/lib/pubkey/elgamal/elgamal.h | 4 botan3-3.12.0+dfsg/src/lib/pubkey/elgamal/info.txt | 2 botan3-3.12.0+dfsg/src/lib/pubkey/frodokem/frodokem_aes/frodo_aes_generator.h | 4 botan3-3.12.0+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodo_constants.cpp | 6 botan3-3.12.0+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodo_constants.h | 8 botan3-3.12.0+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodo_matrix.cpp | 33 botan3-3.12.0+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodo_matrix.h | 4 botan3-3.12.0+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodo_mode.cpp | 25 botan3-3.12.0+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodo_mode.h | 19 botan3-3.12.0+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodo_types.h | 4 botan3-3.12.0+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodokem.cpp | 242 botan3-3.12.0+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodokem.h | 9 botan3-3.12.0+dfsg/src/lib/pubkey/gost_3410/gost_3410.cpp | 20 botan3-3.12.0+dfsg/src/lib/pubkey/gost_3410/gost_3410.h | 8 botan3-3.12.0+dfsg/src/lib/pubkey/hss_lms/hss.cpp | 92 botan3-3.12.0+dfsg/src/lib/pubkey/hss_lms/hss.h | 12 botan3-3.12.0+dfsg/src/lib/pubkey/hss_lms/hss_lms.cpp | 20 botan3-3.12.0+dfsg/src/lib/pubkey/hss_lms/hss_lms.h | 18 botan3-3.12.0+dfsg/src/lib/pubkey/hss_lms/hss_lms_utils.cpp | 2 botan3-3.12.0+dfsg/src/lib/pubkey/hss_lms/hss_lms_utils.h | 2 botan3-3.12.0+dfsg/src/lib/pubkey/hss_lms/info.txt | 1 botan3-3.12.0+dfsg/src/lib/pubkey/hss_lms/lm_ots.cpp | 16 botan3-3.12.0+dfsg/src/lib/pubkey/hss_lms/lm_ots.h | 17 botan3-3.12.0+dfsg/src/lib/pubkey/hss_lms/lms.cpp | 27 botan3-3.12.0+dfsg/src/lib/pubkey/hss_lms/lms.h | 10 botan3-3.12.0+dfsg/src/lib/pubkey/hybrid_kem/hybrid_kem.cpp | 82 botan3-3.12.0+dfsg/src/lib/pubkey/hybrid_kem/hybrid_kem.h | 135 botan3-3.12.0+dfsg/src/lib/pubkey/hybrid_kem/hybrid_kem_ops.cpp | 110 botan3-3.12.0+dfsg/src/lib/pubkey/hybrid_kem/hybrid_kem_ops.h | 140 botan3-3.12.0+dfsg/src/lib/pubkey/hybrid_kem/info.txt | 17 botan3-3.12.0+dfsg/src/lib/pubkey/info.txt | 2 botan3-3.12.0+dfsg/src/lib/pubkey/kex_to_kem_adapter/info.txt | 18 botan3-3.12.0+dfsg/src/lib/pubkey/kex_to_kem_adapter/kex_to_kem_adapter.cpp | 270 botan3-3.12.0+dfsg/src/lib/pubkey/kex_to_kem_adapter/kex_to_kem_adapter.h | 92 botan3-3.12.0+dfsg/src/lib/pubkey/keypair/keypair.cpp | 6 botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_common/kyber.cpp | 50 botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_common/kyber.h | 92 botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_algos.cpp | 10 botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_algos.h | 11 botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_constants.cpp | 18 botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_constants.h | 24 botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_encaps_base.h | 14 botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_helpers.h | 2 botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_keys.cpp | 82 botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_keys.h | 28 botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_polynomial.h | 19 botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_symmetric_primitives.h | 78 botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_round3/kyber/kyber_modern.h | 68 botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_round3/kyber_90s/kyber_90s.h | 52 botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_round3/kyber_round3_impl.cpp | 67 botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_round3/kyber_round3_impl.h | 10 botan3-3.12.0+dfsg/src/lib/pubkey/kyber/ml_kem/ml_kem_impl.cpp | 30 botan3-3.12.0+dfsg/src/lib/pubkey/kyber/ml_kem/ml_kem_impl.h | 67 botan3-3.12.0+dfsg/src/lib/pubkey/mce/code_based_key_gen.cpp | 22 botan3-3.12.0+dfsg/src/lib/pubkey/mce/gf2m_rootfind_dcmp.cpp | 89 botan3-3.12.0+dfsg/src/lib/pubkey/mce/gf2m_small_m.cpp | 8 botan3-3.12.0+dfsg/src/lib/pubkey/mce/gf2m_small_m.h | 12 botan3-3.12.0+dfsg/src/lib/pubkey/mce/goppa_code.cpp | 36 botan3-3.12.0+dfsg/src/lib/pubkey/mce/mce_workfactor.cpp | 2 botan3-3.12.0+dfsg/src/lib/pubkey/mce/mceliece.cpp | 12 botan3-3.12.0+dfsg/src/lib/pubkey/mce/mceliece.h | 10 botan3-3.12.0+dfsg/src/lib/pubkey/mce/mceliece_key.cpp | 119 botan3-3.12.0+dfsg/src/lib/pubkey/mce/polyn_gf2m.cpp | 162 botan3-3.12.0+dfsg/src/lib/pubkey/mce/polyn_gf2m.h | 5 botan3-3.12.0+dfsg/src/lib/pubkey/pbes2/pbes2.cpp | 57 botan3-3.12.0+dfsg/src/lib/pubkey/pbes2/pbes2.h | 1 botan3-3.12.0+dfsg/src/lib/pubkey/pem/pem.cpp | 32 botan3-3.12.0+dfsg/src/lib/pubkey/pk_algs.cpp | 14 botan3-3.12.0+dfsg/src/lib/pubkey/pk_algs.h | 15 botan3-3.12.0+dfsg/src/lib/pubkey/pk_keys.cpp | 17 botan3-3.12.0+dfsg/src/lib/pubkey/pk_keys.h | 47 botan3-3.12.0+dfsg/src/lib/pubkey/pk_ops.cpp | 34 botan3-3.12.0+dfsg/src/lib/pubkey/pk_ops.h | 17 botan3-3.12.0+dfsg/src/lib/pubkey/pk_ops_impl.h | 22 botan3-3.12.0+dfsg/src/lib/pubkey/pkcs8.cpp | 25 botan3-3.12.0+dfsg/src/lib/pubkey/pkcs8.h | 2 botan3-3.12.0+dfsg/src/lib/pubkey/pqcrystals/info.txt | 4 botan3-3.12.0+dfsg/src/lib/pubkey/pqcrystals/pqcrystals.h | 15 botan3-3.12.0+dfsg/src/lib/pubkey/pqcrystals/pqcrystals_encoding.h | 7 botan3-3.12.0+dfsg/src/lib/pubkey/pqcrystals/pqcrystals_helpers.h | 17 botan3-3.12.0+dfsg/src/lib/pubkey/pubkey.cpp | 84 botan3-3.12.0+dfsg/src/lib/pubkey/pubkey.h | 32 botan3-3.12.0+dfsg/src/lib/pubkey/rfc6979/info.txt | 4 botan3-3.12.0+dfsg/src/lib/pubkey/rfc6979/rfc6979.cpp | 12 botan3-3.12.0+dfsg/src/lib/pubkey/rfc6979/rfc6979.h | 6 botan3-3.12.0+dfsg/src/lib/pubkey/rsa/info.txt | 3 botan3-3.12.0+dfsg/src/lib/pubkey/rsa/rsa.cpp | 225 botan3-3.12.0+dfsg/src/lib/pubkey/rsa/rsa.h | 10 botan3-3.12.0+dfsg/src/lib/pubkey/sm2/sm2.cpp | 42 botan3-3.12.0+dfsg/src/lib/pubkey/sm2/sm2.h | 20 botan3-3.12.0+dfsg/src/lib/pubkey/sm2/sm2_enc.cpp | 46 botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_address.h | 19 botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_fors.cpp | 13 botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_hash.cpp | 6 botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_hash.h | 16 botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_hypertree.cpp | 3 botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_hypertree.h | 1 botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_parameters.cpp | 4 botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_parameters.h | 4 botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_treehash.cpp | 2 botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_types.h | 2 botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_wots.cpp | 9 botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_xmss.cpp | 6 botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus.cpp | 20 botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus.h | 12 botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus_sha2_base/sp_hash_sha2.h | 4 botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus_shake_base/sp_hash_shake.h | 6 botan3-3.12.0+dfsg/src/lib/pubkey/stateful_key_index/info.txt | 9 botan3-3.12.0+dfsg/src/lib/pubkey/stateful_key_index/stateful_key_index_registry.cpp | 82 botan3-3.12.0+dfsg/src/lib/pubkey/stateful_key_index/stateful_key_index_registry.h | 97 botan3-3.12.0+dfsg/src/lib/pubkey/workfactor.cpp | 70 botan3-3.12.0+dfsg/src/lib/pubkey/x25519/donna.cpp | 64 botan3-3.12.0+dfsg/src/lib/pubkey/x25519/x25519.cpp | 6 botan3-3.12.0+dfsg/src/lib/pubkey/x25519/x25519.h | 10 botan3-3.12.0+dfsg/src/lib/pubkey/x509_key.cpp | 14 botan3-3.12.0+dfsg/src/lib/pubkey/x509_key.h | 1 botan3-3.12.0+dfsg/src/lib/pubkey/xmss/atomic.h | 56 botan3-3.12.0+dfsg/src/lib/pubkey/xmss/info.txt | 8 botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss.h | 64 botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_address.h | 26 botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_common_ops.cpp | 6 botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_common_ops.h | 10 botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_hash.cpp | 1 botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_hash.h | 2 botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_index_registry.cpp | 71 botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_index_registry.h | 99 botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_parameters.cpp | 384 botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_parameters.h | 130 botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_privatekey.cpp | 171 botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_publickey.cpp | 125 botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_signature.cpp | 21 botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_signature.h | 1 botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_signature_operation.cpp | 61 botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_signature_operation.h | 31 botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_tools.h | 71 botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_verification_operation.cpp | 16 botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_verification_operation.h | 5 botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_wots.cpp | 76 botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_wots.h | 31 botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_wots_parameters.cpp | 156 botan3-3.12.0+dfsg/src/lib/rng/auto_rng/auto_rng.cpp | 10 botan3-3.12.0+dfsg/src/lib/rng/auto_rng/auto_rng.h | 21 botan3-3.12.0+dfsg/src/lib/rng/auto_rng/info.txt | 4 botan3-3.12.0+dfsg/src/lib/rng/chacha_rng/chacha_rng.cpp | 6 botan3-3.12.0+dfsg/src/lib/rng/chacha_rng/chacha_rng.h | 10 botan3-3.12.0+dfsg/src/lib/rng/esdm_rng/esdm_rng.cpp | 3 botan3-3.12.0+dfsg/src/lib/rng/esdm_rng/esdm_rng.h | 4 botan3-3.12.0+dfsg/src/lib/rng/hmac_drbg/hmac_drbg.cpp | 38 botan3-3.12.0+dfsg/src/lib/rng/hmac_drbg/hmac_drbg.h | 17 botan3-3.12.0+dfsg/src/lib/rng/jitter_rng/jitter_rng.cpp | 23 botan3-3.12.0+dfsg/src/lib/rng/jitter_rng/jitter_rng.h | 3 botan3-3.12.0+dfsg/src/lib/rng/processor_rng/info.txt | 8 botan3-3.12.0+dfsg/src/lib/rng/processor_rng/processor_rng.cpp | 47 botan3-3.12.0+dfsg/src/lib/rng/processor_rng/processor_rng.h | 5 botan3-3.12.0+dfsg/src/lib/rng/rng.cpp | 7 botan3-3.12.0+dfsg/src/lib/rng/rng.h | 94 botan3-3.12.0+dfsg/src/lib/rng/stateful_rng/stateful_rng.cpp | 27 botan3-3.12.0+dfsg/src/lib/rng/stateful_rng/stateful_rng.h | 12 botan3-3.12.0+dfsg/src/lib/rng/system_rng/info.txt | 3 botan3-3.12.0+dfsg/src/lib/rng/system_rng/system_rng.cpp | 9 botan3-3.12.0+dfsg/src/lib/stream/chacha/chacha.cpp | 69 botan3-3.12.0+dfsg/src/lib/stream/chacha/chacha_avx2/chacha_avx2.cpp | 5 botan3-3.12.0+dfsg/src/lib/stream/chacha/chacha_avx2/info.txt | 5 botan3-3.12.0+dfsg/src/lib/stream/chacha/chacha_avx512/chacha_avx512.cpp | 9 botan3-3.12.0+dfsg/src/lib/stream/chacha/chacha_avx512/info.txt | 5 botan3-3.12.0+dfsg/src/lib/stream/chacha/chacha_simd32/chacha_simd32.cpp | 7 botan3-3.12.0+dfsg/src/lib/stream/chacha/chacha_simd32/info.txt | 19 botan3-3.12.0+dfsg/src/lib/stream/ctr/ctr.cpp | 57 botan3-3.12.0+dfsg/src/lib/stream/ctr/ctr.h | 8 botan3-3.12.0+dfsg/src/lib/stream/ctr/ctr_avx2/ctr_avx2.cpp | 83 botan3-3.12.0+dfsg/src/lib/stream/ctr/ctr_avx2/info.txt | 17 botan3-3.12.0+dfsg/src/lib/stream/ctr/ctr_simd32/ctr_simd32.cpp | 89 botan3-3.12.0+dfsg/src/lib/stream/ctr/ctr_simd32/info.txt | 26 botan3-3.12.0+dfsg/src/lib/stream/ofb/ofb.cpp | 3 botan3-3.12.0+dfsg/src/lib/stream/rc4/rc4.cpp | 5 botan3-3.12.0+dfsg/src/lib/stream/rc4/rc4.h | 2 botan3-3.12.0+dfsg/src/lib/stream/salsa20/salsa20.cpp | 50 botan3-3.12.0+dfsg/src/lib/stream/shake_cipher/shake_cipher.cpp | 2 botan3-3.12.0+dfsg/src/lib/stream/stream_cipher.cpp | 7 botan3-3.12.0+dfsg/src/lib/stream/stream_cipher.h | 11 botan3-3.12.0+dfsg/src/lib/tls/asio/asio_async_ops.h | 32 botan3-3.12.0+dfsg/src/lib/tls/asio/asio_compat.h | 5 botan3-3.12.0+dfsg/src/lib/tls/asio/asio_context.cpp | 8 botan3-3.12.0+dfsg/src/lib/tls/asio/asio_context.h | 23 botan3-3.12.0+dfsg/src/lib/tls/asio/asio_error.h | 42 botan3-3.12.0+dfsg/src/lib/tls/asio/asio_stream.h | 134 botan3-3.12.0+dfsg/src/lib/tls/credentials_manager.cpp | 2 botan3-3.12.0+dfsg/src/lib/tls/credentials_manager.h | 24 botan3-3.12.0+dfsg/src/lib/tls/info.txt | 10 botan3-3.12.0+dfsg/src/lib/tls/msg_cert_req.cpp | 155 botan3-3.12.0+dfsg/src/lib/tls/msg_cert_status.cpp | 54 botan3-3.12.0+dfsg/src/lib/tls/msg_cert_verify.cpp | 153 botan3-3.12.0+dfsg/src/lib/tls/msg_client_hello.cpp | 965 -- botan3-3.12.0+dfsg/src/lib/tls/msg_finished.cpp | 88 botan3-3.12.0+dfsg/src/lib/tls/msg_server_hello.cpp | 756 - botan3-3.12.0+dfsg/src/lib/tls/msg_session_ticket.cpp | 140 botan3-3.12.0+dfsg/src/lib/tls/sessions_sql/tls_session_manager_sql.cpp | 22 botan3-3.12.0+dfsg/src/lib/tls/sessions_sql/tls_session_manager_sql.h | 6 botan3-3.12.0+dfsg/src/lib/tls/tls12/info.txt | 11 botan3-3.12.0+dfsg/src/lib/tls/tls12/msg_cert_status.cpp | 72 botan3-3.12.0+dfsg/src/lib/tls/tls12/msg_cert_status_12.cpp | 22 botan3-3.12.0+dfsg/src/lib/tls/tls12/msg_cert_verify_12.cpp | 61 botan3-3.12.0+dfsg/src/lib/tls/tls12/msg_certificate_12.cpp | 20 botan3-3.12.0+dfsg/src/lib/tls/tls12/msg_certificate_req_12.cpp | 164 botan3-3.12.0+dfsg/src/lib/tls/tls12/msg_client_hello_12.cpp | 297 botan3-3.12.0+dfsg/src/lib/tls/tls12/msg_client_kex.cpp | 170 botan3-3.12.0+dfsg/src/lib/tls/tls12/msg_finished_12.cpp | 59 botan3-3.12.0+dfsg/src/lib/tls/tls12/msg_hello_verify.cpp | 8 botan3-3.12.0+dfsg/src/lib/tls/tls12/msg_server_hello_12.cpp | 229 botan3-3.12.0+dfsg/src/lib/tls/tls12/msg_server_kex.cpp | 68 botan3-3.12.0+dfsg/src/lib/tls/tls12/msg_session_ticket_12.cpp | 46 botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_cbc/tls_cbc.cpp | 70 botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_cbc/tls_cbc.h | 67 botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_channel_impl_12.cpp | 277 botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_channel_impl_12.h | 32 botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_client_impl_12.cpp | 215 botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_client_impl_12.h | 8 botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_connection_state_12.cpp | 47 botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_connection_state_12.h | 105 botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_extensions_12.cpp | 118 botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_extensions_12.h | 155 botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_handshake_io.cpp | 166 botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_handshake_io.h | 50 botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_handshake_state.cpp | 198 botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_handshake_state.h | 54 botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_messages_12.h | 424 + botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_null/info.txt | 19 botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_null/tls_null.cpp | 154 botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_null/tls_null.h | 112 botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_record.cpp | 84 botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_record.h | 25 botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_seq_numbers.h | 25 botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_server_impl_12.cpp | 296 botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_server_impl_12.h | 14 botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_session_key.cpp | 39 botan3-3.12.0+dfsg/src/lib/tls/tls13/info.txt | 5 botan3-3.12.0+dfsg/src/lib/tls/tls13/msg_cert_verify_13.cpp | 123 botan3-3.12.0+dfsg/src/lib/tls/tls13/msg_certificate_13.cpp | 91 botan3-3.12.0+dfsg/src/lib/tls/tls13/msg_certificate_req_13.cpp | 26 botan3-3.12.0+dfsg/src/lib/tls/tls13/msg_client_hello_13.cpp | 512 + botan3-3.12.0+dfsg/src/lib/tls/tls13/msg_encrypted_extensions.cpp | 68 botan3-3.12.0+dfsg/src/lib/tls/tls13/msg_finished_13.cpp | 24 botan3-3.12.0+dfsg/src/lib/tls/tls13/msg_key_update.cpp | 2 botan3-3.12.0+dfsg/src/lib/tls/tls13/msg_server_hello_13.cpp | 412 + botan3-3.12.0+dfsg/src/lib/tls/tls13/msg_session_ticket_13.cpp | 104 botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_channel_impl_13.cpp | 48 botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_channel_impl_13.h | 26 botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_cipher_state.cpp | 87 botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_cipher_state.h | 16 botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_client_impl_13.cpp | 334 botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_client_impl_13.h | 19 botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_connection_state_13.cpp | 46 botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_connection_state_13.h | 82 botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_extensions_13.cpp | 170 botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_extensions_13.h | 334 botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_extensions_key_share.cpp | 105 botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_extensions_psk.cpp | 69 botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_handshake_layer_13.cpp | 50 botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_handshake_layer_13.h | 11 botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_handshake_state_13.cpp | 18 botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_handshake_state_13.h | 36 botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_messages_13.h | 475 + botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_psk_13.h | 120 botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_psk_identity_13.cpp | 13 botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_psk_identity_13.h | 64 botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_psk_importer_13.cpp | 121 botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_record_layer_13.cpp | 74 botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_record_layer_13.h | 22 botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_server_impl_13.cpp | 308 botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_server_impl_13.h | 15 botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_transcript_hash_13.cpp | 24 botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_transcript_hash_13.h | 21 botan3-3.12.0+dfsg/src/lib/tls/tls13_pqc/hybrid_public_key.cpp | 360 botan3-3.12.0+dfsg/src/lib/tls/tls13_pqc/hybrid_public_key.h | 51 botan3-3.12.0+dfsg/src/lib/tls/tls13_pqc/info.txt | 3 botan3-3.12.0+dfsg/src/lib/tls/tls13_pqc/kex_to_kem_adapter.cpp | 251 botan3-3.12.0+dfsg/src/lib/tls/tls13_pqc/kex_to_kem_adapter.h | 89 botan3-3.12.0+dfsg/src/lib/tls/tls_alert.h | 6 botan3-3.12.0+dfsg/src/lib/tls/tls_algos.cpp | 150 botan3-3.12.0+dfsg/src/lib/tls/tls_algos.h | 62 botan3-3.12.0+dfsg/src/lib/tls/tls_callbacks.cpp | 104 botan3-3.12.0+dfsg/src/lib/tls/tls_callbacks.h | 109 botan3-3.12.0+dfsg/src/lib/tls/tls_channel.h | 24 botan3-3.12.0+dfsg/src/lib/tls/tls_channel_impl.h | 26 botan3-3.12.0+dfsg/src/lib/tls/tls_ciphersuite.cpp | 138 botan3-3.12.0+dfsg/src/lib/tls/tls_ciphersuite.h | 22 botan3-3.12.0+dfsg/src/lib/tls/tls_client.cpp | 46 botan3-3.12.0+dfsg/src/lib/tls/tls_client.h | 14 botan3-3.12.0+dfsg/src/lib/tls/tls_exceptn.h | 2 botan3-3.12.0+dfsg/src/lib/tls/tls_extensions.cpp | 598 - botan3-3.12.0+dfsg/src/lib/tls/tls_extensions.h | 577 - botan3-3.12.0+dfsg/src/lib/tls/tls_extensions_cert_status_req.cpp | 29 botan3-3.12.0+dfsg/src/lib/tls/tls_external_psk.cpp | 21 botan3-3.12.0+dfsg/src/lib/tls/tls_external_psk.h | 26 botan3-3.12.0+dfsg/src/lib/tls/tls_handshake_transitions.cpp | 2 botan3-3.12.0+dfsg/src/lib/tls/tls_handshake_transitions.h | 3 botan3-3.12.0+dfsg/src/lib/tls/tls_magic.cpp | 82 botan3-3.12.0+dfsg/src/lib/tls/tls_magic.h | 59 botan3-3.12.0+dfsg/src/lib/tls/tls_messages.h | 910 -- botan3-3.12.0+dfsg/src/lib/tls/tls_messages_internal.h | 161 botan3-3.12.0+dfsg/src/lib/tls/tls_policy.cpp | 52 botan3-3.12.0+dfsg/src/lib/tls/tls_policy.h | 56 botan3-3.12.0+dfsg/src/lib/tls/tls_reader.cpp | 25 botan3-3.12.0+dfsg/src/lib/tls/tls_reader.h | 68 botan3-3.12.0+dfsg/src/lib/tls/tls_server.cpp | 32 botan3-3.12.0+dfsg/src/lib/tls/tls_server.h | 11 botan3-3.12.0+dfsg/src/lib/tls/tls_server_info.h | 11 botan3-3.12.0+dfsg/src/lib/tls/tls_session.cpp | 114 botan3-3.12.0+dfsg/src/lib/tls/tls_session.h | 219 botan3-3.12.0+dfsg/src/lib/tls/tls_session_id.h | 121 botan3-3.12.0+dfsg/src/lib/tls/tls_session_manager.cpp | 117 botan3-3.12.0+dfsg/src/lib/tls/tls_session_manager.h | 29 botan3-3.12.0+dfsg/src/lib/tls/tls_session_manager_hybrid.cpp | 19 botan3-3.12.0+dfsg/src/lib/tls/tls_session_manager_hybrid.h | 14 botan3-3.12.0+dfsg/src/lib/tls/tls_session_manager_memory.cpp | 27 botan3-3.12.0+dfsg/src/lib/tls/tls_session_manager_memory.h | 6 botan3-3.12.0+dfsg/src/lib/tls/tls_session_manager_noop.cpp | 16 botan3-3.12.0+dfsg/src/lib/tls/tls_session_manager_noop.h | 16 botan3-3.12.0+dfsg/src/lib/tls/tls_session_manager_stateless.cpp | 15 botan3-3.12.0+dfsg/src/lib/tls/tls_session_manager_stateless.h | 9 botan3-3.12.0+dfsg/src/lib/tls/tls_signature_scheme.cpp | 33 botan3-3.12.0+dfsg/src/lib/tls/tls_signature_scheme.h | 27 botan3-3.12.0+dfsg/src/lib/tls/tls_suite_info.cpp | 341 botan3-3.12.0+dfsg/src/lib/tls/tls_text_policy.cpp | 9 botan3-3.12.0+dfsg/src/lib/tls/tls_version.cpp | 35 botan3-3.12.0+dfsg/src/lib/tls/tls_version.h | 19 botan3-3.12.0+dfsg/src/lib/utils/alignment_buffer.h | 23 botan3-3.12.0+dfsg/src/lib/utils/allocator.cpp | 9 botan3-3.12.0+dfsg/src/lib/utils/allocator.h | 1 botan3-3.12.0+dfsg/src/lib/utils/api.h | 16 botan3-3.12.0+dfsg/src/lib/utils/assert.cpp | 11 botan3-3.12.0+dfsg/src/lib/utils/assert.h | 58 botan3-3.12.0+dfsg/src/lib/utils/bit_ops.h | 167 botan3-3.12.0+dfsg/src/lib/utils/bitvector/bitvector.h | 46 botan3-3.12.0+dfsg/src/lib/utils/bitvector/info.txt | 4 botan3-3.12.0+dfsg/src/lib/utils/boost/info.txt | 4 botan3-3.12.0+dfsg/src/lib/utils/bswap.h | 1 botan3-3.12.0+dfsg/src/lib/utils/buffer_slicer.h | 76 botan3-3.12.0+dfsg/src/lib/utils/buffer_stuffer.h | 79 botan3-3.12.0+dfsg/src/lib/utils/calendar.cpp | 17 botan3-3.12.0+dfsg/src/lib/utils/calendar.h | 4 botan3-3.12.0+dfsg/src/lib/utils/charset.cpp | 101 botan3-3.12.0+dfsg/src/lib/utils/charset.h | 30 botan3-3.12.0+dfsg/src/lib/utils/codec_base.h | 35 botan3-3.12.0+dfsg/src/lib/utils/compiler.h | 20 botan3-3.12.0+dfsg/src/lib/utils/concat_util.h | 121 botan3-3.12.0+dfsg/src/lib/utils/concepts.h | 141 botan3-3.12.0+dfsg/src/lib/utils/cpuid/cpuid.cpp | 216 botan3-3.12.0+dfsg/src/lib/utils/cpuid/cpuid.h | 399 - botan3-3.12.0+dfsg/src/lib/utils/cpuid/cpuid_aarch64.cpp | 193 botan3-3.12.0+dfsg/src/lib/utils/cpuid/cpuid_aarch64/cpuid_aarch64.cpp | 192 botan3-3.12.0+dfsg/src/lib/utils/cpuid/cpuid_aarch64/cpuid_features.cpp | 67 botan3-3.12.0+dfsg/src/lib/utils/cpuid/cpuid_aarch64/cpuid_features.h | 51 botan3-3.12.0+dfsg/src/lib/utils/cpuid/cpuid_aarch64/info.txt | 11 botan3-3.12.0+dfsg/src/lib/utils/cpuid/cpuid_arm32.cpp | 57 botan3-3.12.0+dfsg/src/lib/utils/cpuid/cpuid_arm32/cpuid_arm32.cpp | 55 botan3-3.12.0+dfsg/src/lib/utils/cpuid/cpuid_arm32/cpuid_features.cpp | 47 botan3-3.12.0+dfsg/src/lib/utils/cpuid/cpuid_arm32/cpuid_features.h | 46 botan3-3.12.0+dfsg/src/lib/utils/cpuid/cpuid_arm32/info.txt | 11 botan3-3.12.0+dfsg/src/lib/utils/cpuid/cpuid_loongarch64/cpuid_features.cpp | 38 botan3-3.12.0+dfsg/src/lib/utils/cpuid/cpuid_loongarch64/cpuid_features.h | 42 botan3-3.12.0+dfsg/src/lib/utils/cpuid/cpuid_loongarch64/cpuid_loongarch64.cpp | 41 botan3-3.12.0+dfsg/src/lib/utils/cpuid/cpuid_loongarch64/info.txt | 11 botan3-3.12.0+dfsg/src/lib/utils/cpuid/cpuid_ppc.cpp | 87 botan3-3.12.0+dfsg/src/lib/utils/cpuid/cpuid_ppc/cpuid_features.cpp | 39 botan3-3.12.0+dfsg/src/lib/utils/cpuid/cpuid_ppc/cpuid_features.h | 44 botan3-3.12.0+dfsg/src/lib/utils/cpuid/cpuid_ppc/cpuid_ppc.cpp | 90 botan3-3.12.0+dfsg/src/lib/utils/cpuid/cpuid_ppc/info.txt | 12 botan3-3.12.0+dfsg/src/lib/utils/cpuid/cpuid_riscv64/cpuid_features.cpp | 62 botan3-3.12.0+dfsg/src/lib/utils/cpuid/cpuid_riscv64/cpuid_features.h | 47 botan3-3.12.0+dfsg/src/lib/utils/cpuid/cpuid_riscv64/cpuid_riscv64.cpp | 83 botan3-3.12.0+dfsg/src/lib/utils/cpuid/cpuid_riscv64/info.txt | 11 botan3-3.12.0+dfsg/src/lib/utils/cpuid/cpuid_wasm/cpuid_features.cpp | 31 botan3-3.12.0+dfsg/src/lib/utils/cpuid/cpuid_wasm/cpuid_features.h | 42 botan3-3.12.0+dfsg/src/lib/utils/cpuid/cpuid_wasm/cpuid_wasm.cpp | 30 botan3-3.12.0+dfsg/src/lib/utils/cpuid/cpuid_wasm/info.txt | 11 botan3-3.12.0+dfsg/src/lib/utils/cpuid/cpuid_x86.cpp | 225 botan3-3.12.0+dfsg/src/lib/utils/cpuid/cpuid_x86/cpuid_features.cpp | 108 botan3-3.12.0+dfsg/src/lib/utils/cpuid/cpuid_x86/cpuid_features.h | 65 botan3-3.12.0+dfsg/src/lib/utils/cpuid/cpuid_x86/cpuid_x86.cpp | 224 botan3-3.12.0+dfsg/src/lib/utils/cpuid/cpuid_x86/info.txt | 13 botan3-3.12.0+dfsg/src/lib/utils/cpuid/info.txt | 22 botan3-3.12.0+dfsg/src/lib/utils/ct_utils.cpp | 4 botan3-3.12.0+dfsg/src/lib/utils/ct_utils.h | 229 botan3-3.12.0+dfsg/src/lib/utils/data_src.cpp | 45 botan3-3.12.0+dfsg/src/lib/utils/data_src.h | 28 botan3-3.12.0+dfsg/src/lib/utils/database.h | 6 botan3-3.12.0+dfsg/src/lib/utils/donna128.h | 90 botan3-3.12.0+dfsg/src/lib/utils/dyn_load/dyn_load.cpp | 53 botan3-3.12.0+dfsg/src/lib/utils/dyn_load/dyn_load.h | 38 botan3-3.12.0+dfsg/src/lib/utils/dyn_load/info.txt | 4 botan3-3.12.0+dfsg/src/lib/utils/exceptn.h | 47 botan3-3.12.0+dfsg/src/lib/utils/filesystem.cpp | 9 botan3-3.12.0+dfsg/src/lib/utils/gfni_utils.h | 51 botan3-3.12.0+dfsg/src/lib/utils/ghash/ghash.cpp | 101 botan3-3.12.0+dfsg/src/lib/utils/ghash/ghash.h | 33 botan3-3.12.0+dfsg/src/lib/utils/ghash/ghash_avx512_clmul/ghash_avx512_clmul.cpp | 207 botan3-3.12.0+dfsg/src/lib/utils/ghash/ghash_avx512_clmul/info.txt | 16 botan3-3.12.0+dfsg/src/lib/utils/ghash/ghash_cpu/ghash_cpu.cpp | 263 botan3-3.12.0+dfsg/src/lib/utils/ghash/ghash_cpu/info.txt | 9 botan3-3.12.0+dfsg/src/lib/utils/ghash/ghash_cpu/polyval_fn.h | 141 botan3-3.12.0+dfsg/src/lib/utils/ghash/ghash_vperm/ghash_vperm.cpp | 60 botan3-3.12.0+dfsg/src/lib/utils/ghash/ghash_vperm/info.txt | 12 botan3-3.12.0+dfsg/src/lib/utils/ghash/info.txt | 4 botan3-3.12.0+dfsg/src/lib/utils/http_util/http_util.cpp | 37 botan3-3.12.0+dfsg/src/lib/utils/http_util/info.txt | 4 botan3-3.12.0+dfsg/src/lib/utils/info.txt | 23 botan3-3.12.0+dfsg/src/lib/utils/int_utils.h | 2 botan3-3.12.0+dfsg/src/lib/utils/ip_address/info.txt | 8 botan3-3.12.0+dfsg/src/lib/utils/ip_address/ipv4_address.cpp | 131 botan3-3.12.0+dfsg/src/lib/utils/ip_address/ipv4_address.h | 133 botan3-3.12.0+dfsg/src/lib/utils/ip_address/ipv6_address.cpp | 164 botan3-3.12.0+dfsg/src/lib/utils/ip_address/ipv6_address.h | 134 botan3-3.12.0+dfsg/src/lib/utils/isa_extn.h | 91 botan3-3.12.0+dfsg/src/lib/utils/loadstor.h | 180 botan3-3.12.0+dfsg/src/lib/utils/locking_allocator/info.txt | 4 botan3-3.12.0+dfsg/src/lib/utils/locking_allocator/locking_allocator.cpp | 8 botan3-3.12.0+dfsg/src/lib/utils/locking_allocator/locking_allocator.h | 7 botan3-3.12.0+dfsg/src/lib/utils/mem_ops.h | 49 botan3-3.12.0+dfsg/src/lib/utils/mem_pool/info.txt | 4 botan3-3.12.0+dfsg/src/lib/utils/mem_pool/mem_pool.cpp | 71 botan3-3.12.0+dfsg/src/lib/utils/mem_pool/mem_pool.h | 8 botan3-3.12.0+dfsg/src/lib/utils/mem_utils.cpp | 43 botan3-3.12.0+dfsg/src/lib/utils/mem_utils.h | 82 botan3-3.12.0+dfsg/src/lib/utils/mul128.h | 3 botan3-3.12.0+dfsg/src/lib/utils/mutex.h | 2 botan3-3.12.0+dfsg/src/lib/utils/os_utils/info.txt | 4 botan3-3.12.0+dfsg/src/lib/utils/os_utils/os_utils.cpp | 152 botan3-3.12.0+dfsg/src/lib/utils/os_utils/os_utils.h | 7 botan3-3.12.0+dfsg/src/lib/utils/parsing.cpp | 295 botan3-3.12.0+dfsg/src/lib/utils/parsing.h | 29 botan3-3.12.0+dfsg/src/lib/utils/poly_dbl/info.txt | 4 botan3-3.12.0+dfsg/src/lib/utils/poly_dbl/poly_dbl.cpp | 6 botan3-3.12.0+dfsg/src/lib/utils/poly_dbl/poly_dbl.h | 7 botan3-3.12.0+dfsg/src/lib/utils/prefetch.h | 8 botan3-3.12.0+dfsg/src/lib/utils/range_concepts.h | 119 botan3-3.12.0+dfsg/src/lib/utils/read_cfg.cpp | 1 botan3-3.12.0+dfsg/src/lib/utils/read_kv.cpp | 10 botan3-3.12.0+dfsg/src/lib/utils/rotate.h | 42 botan3-3.12.0+dfsg/src/lib/utils/rounding.h | 1 botan3-3.12.0+dfsg/src/lib/utils/scan_name.cpp | 4 botan3-3.12.0+dfsg/src/lib/utils/scoped_cleanup.h | 58 botan3-3.12.0+dfsg/src/lib/utils/simd/info.txt | 31 botan3-3.12.0+dfsg/src/lib/utils/simd/simd_2x64/info.txt | 28 botan3-3.12.0+dfsg/src/lib/utils/simd/simd_2x64/simd_2x64.h | 326 botan3-3.12.0+dfsg/src/lib/utils/simd/simd_32.h | 640 - botan3-3.12.0+dfsg/src/lib/utils/simd/simd_4x32/info.txt | 34 botan3-3.12.0+dfsg/src/lib/utils/simd/simd_4x32/simd_4x32.h | 970 ++ botan3-3.12.0+dfsg/src/lib/utils/simd/simd_4x64/info.txt | 21 botan3-3.12.0+dfsg/src/lib/utils/simd/simd_4x64/simd_4x64.h | 206 botan3-3.12.0+dfsg/src/lib/utils/simd/simd_8x64/info.txt | 21 botan3-3.12.0+dfsg/src/lib/utils/simd/simd_8x64/simd_8x64.h | 193 botan3-3.12.0+dfsg/src/lib/utils/simd/simd_avx2/info.txt | 4 botan3-3.12.0+dfsg/src/lib/utils/simd/simd_avx2/simd_avx2.h | 198 botan3-3.12.0+dfsg/src/lib/utils/simd/simd_avx2/simd_avx2_gfni.h | 48 botan3-3.12.0+dfsg/src/lib/utils/simd/simd_avx512/info.txt | 5 botan3-3.12.0+dfsg/src/lib/utils/simd/simd_avx512/simd_avx512.h | 116 botan3-3.12.0+dfsg/src/lib/utils/simd/simd_avx512/simd_avx512_gfni.h | 29 botan3-3.12.0+dfsg/src/lib/utils/simd/simd_hwaes/info.txt | 29 botan3-3.12.0+dfsg/src/lib/utils/simd/simd_hwaes/simd_hwaes.h | 170 botan3-3.12.0+dfsg/src/lib/utils/socket/info.txt | 4 botan3-3.12.0+dfsg/src/lib/utils/socket/socket.cpp | 123 botan3-3.12.0+dfsg/src/lib/utils/socket/socket.h | 8 botan3-3.12.0+dfsg/src/lib/utils/socket/socket_udp.cpp | 121 botan3-3.12.0+dfsg/src/lib/utils/socket/socket_udp.h | 5 botan3-3.12.0+dfsg/src/lib/utils/socket/uri.cpp | 16 botan3-3.12.0+dfsg/src/lib/utils/socket/uri.h | 3 botan3-3.12.0+dfsg/src/lib/utils/sqlite3/info.txt | 4 botan3-3.12.0+dfsg/src/lib/utils/sqlite3/sqlite3.cpp | 25 botan3-3.12.0+dfsg/src/lib/utils/sqlite3/sqlite3.h | 14 botan3-3.12.0+dfsg/src/lib/utils/stack_scrubbing.h | 37 botan3-3.12.0+dfsg/src/lib/utils/stl_util.h | 361 botan3-3.12.0+dfsg/src/lib/utils/strong_type.h | 75 botan3-3.12.0+dfsg/src/lib/utils/thread_utils/barrier.cpp | 3 botan3-3.12.0+dfsg/src/lib/utils/thread_utils/barrier.h | 4 botan3-3.12.0+dfsg/src/lib/utils/thread_utils/info.txt | 4 botan3-3.12.0+dfsg/src/lib/utils/thread_utils/rwlock.cpp | 12 botan3-3.12.0+dfsg/src/lib/utils/thread_utils/rwlock.h | 4 botan3-3.12.0+dfsg/src/lib/utils/thread_utils/semaphore.cpp | 4 botan3-3.12.0+dfsg/src/lib/utils/thread_utils/semaphore.h | 4 botan3-3.12.0+dfsg/src/lib/utils/thread_utils/thread_pool.cpp | 28 botan3-3.12.0+dfsg/src/lib/utils/thread_utils/thread_pool.h | 12 botan3-3.12.0+dfsg/src/lib/utils/time_utils.h | 6 botan3-3.12.0+dfsg/src/lib/utils/tree_hash/info.txt | 4 botan3-3.12.0+dfsg/src/lib/utils/tree_hash/tree_hash.h | 12 botan3-3.12.0+dfsg/src/lib/utils/types.h | 31 botan3-3.12.0+dfsg/src/lib/utils/uuid/info.txt | 4 botan3-3.12.0+dfsg/src/lib/utils/uuid/uuid.cpp | 5 botan3-3.12.0+dfsg/src/lib/utils/uuid/uuid.h | 11 botan3-3.12.0+dfsg/src/lib/utils/value_barrier.h | 68 botan3-3.12.0+dfsg/src/lib/utils/version.cpp | 75 botan3-3.12.0+dfsg/src/lib/utils/version.h | 39 botan3-3.12.0+dfsg/src/lib/x509/alt_name.cpp | 47 botan3-3.12.0+dfsg/src/lib/x509/asn1_alt_name.cpp | 8 botan3-3.12.0+dfsg/src/lib/x509/cert_status.cpp | 12 botan3-3.12.0+dfsg/src/lib/x509/certstor.cpp | 183 botan3-3.12.0+dfsg/src/lib/x509/certstor.h | 59 botan3-3.12.0+dfsg/src/lib/x509/certstor_flatfile/certstor_flatfile.cpp | 62 botan3-3.12.0+dfsg/src/lib/x509/certstor_flatfile/certstor_flatfile.h | 13 botan3-3.12.0+dfsg/src/lib/x509/certstor_sql/certstor_sql.cpp | 55 botan3-3.12.0+dfsg/src/lib/x509/certstor_sql/certstor_sql.h | 16 botan3-3.12.0+dfsg/src/lib/x509/certstor_system/certstor_system.cpp | 10 botan3-3.12.0+dfsg/src/lib/x509/certstor_system/certstor_system.h | 5 botan3-3.12.0+dfsg/src/lib/x509/certstor_system_macos/certstor_macos.cpp | 56 botan3-3.12.0+dfsg/src/lib/x509/certstor_system_macos/certstor_macos.h | 12 botan3-3.12.0+dfsg/src/lib/x509/certstor_system_windows/certstor_windows.cpp | 494 - botan3-3.12.0+dfsg/src/lib/x509/certstor_system_windows/certstor_windows.h | 26 botan3-3.12.0+dfsg/src/lib/x509/crl_ent.cpp | 40 botan3-3.12.0+dfsg/src/lib/x509/info.txt | 6 botan3-3.12.0+dfsg/src/lib/x509/key_constraint.cpp | 20 botan3-3.12.0+dfsg/src/lib/x509/name_constraint.cpp | 295 botan3-3.12.0+dfsg/src/lib/x509/ocsp.cpp | 316 botan3-3.12.0+dfsg/src/lib/x509/ocsp.h | 38 botan3-3.12.0+dfsg/src/lib/x509/ocsp_types.cpp | 103 botan3-3.12.0+dfsg/src/lib/x509/pkcs10.cpp | 60 botan3-3.12.0+dfsg/src/lib/x509/pkcs10.h | 19 botan3-3.12.0+dfsg/src/lib/x509/pkix_enums.h | 44 botan3-3.12.0+dfsg/src/lib/x509/pkix_types.h | 143 botan3-3.12.0+dfsg/src/lib/x509/x509_ca.cpp | 17 botan3-3.12.0+dfsg/src/lib/x509/x509_ca.h | 4 botan3-3.12.0+dfsg/src/lib/x509/x509_cert_cache.cpp | 57 botan3-3.12.0+dfsg/src/lib/x509/x509_cert_cache.h | 87 botan3-3.12.0+dfsg/src/lib/x509/x509_crl.cpp | 133 botan3-3.12.0+dfsg/src/lib/x509/x509_crl.h | 53 botan3-3.12.0+dfsg/src/lib/x509/x509_dn.cpp | 172 botan3-3.12.0+dfsg/src/lib/x509/x509_dn_ub.cpp | 102 botan3-3.12.0+dfsg/src/lib/x509/x509_ext.cpp | 1332 +++ botan3-3.12.0+dfsg/src/lib/x509/x509_ext.h | 523 + botan3-3.12.0+dfsg/src/lib/x509/x509_obj.cpp | 47 botan3-3.12.0+dfsg/src/lib/x509/x509_obj.h | 30 botan3-3.12.0+dfsg/src/lib/x509/x509_utils.h | 37 botan3-3.12.0+dfsg/src/lib/x509/x509cert.cpp | 249 botan3-3.12.0+dfsg/src/lib/x509/x509cert.h | 113 botan3-3.12.0+dfsg/src/lib/x509/x509opt.cpp | 5 botan3-3.12.0+dfsg/src/lib/x509/x509path.cpp | 782 +- botan3-3.12.0+dfsg/src/lib/x509/x509path.h | 65 botan3-3.12.0+dfsg/src/lib/x509/x509self.cpp | 13 botan3-3.12.0+dfsg/src/lib/x509/x509self.h | 57 botan3-3.12.0+dfsg/src/lib/xof/aes_crystals_xof/aes_crystals_xof.h | 4 botan3-3.12.0+dfsg/src/lib/xof/aes_crystals_xof/info.txt | 4 botan3-3.12.0+dfsg/src/lib/xof/ascon_xof128/ascon_xof128.cpp | 64 botan3-3.12.0+dfsg/src/lib/xof/ascon_xof128/ascon_xof128.h | 48 botan3-3.12.0+dfsg/src/lib/xof/ascon_xof128/info.txt | 11 botan3-3.12.0+dfsg/src/lib/xof/cshake_xof/cshake_xof.cpp | 12 botan3-3.12.0+dfsg/src/lib/xof/cshake_xof/cshake_xof.h | 12 botan3-3.12.0+dfsg/src/lib/xof/shake_xof/shake_xof.cpp | 5 botan3-3.12.0+dfsg/src/lib/xof/shake_xof/shake_xof.h | 4 botan3-3.12.0+dfsg/src/lib/xof/xof.cpp | 12 botan3-3.12.0+dfsg/src/lib/xof/xof.h | 12 botan3-3.12.0+dfsg/src/python/botan3.py | 1914 +++- botan3-3.12.0+dfsg/src/scripts/Dockerfile.android | 17 botan3-3.12.0+dfsg/src/scripts/acvp_tests.py | 2954 +++++++ botan3-3.12.0+dfsg/src/scripts/bench.py | 38 botan3-3.12.0+dfsg/src/scripts/build_docs.py | 8 botan3-3.12.0+dfsg/src/scripts/ci/ci_tlsanvil_check.py | 199 botan3-3.12.0+dfsg/src/scripts/ci/ci_tlsanvil_test.py | 139 botan3-3.12.0+dfsg/src/scripts/ci/cmake_tests/CMakeLists.txt | 19 botan3-3.12.0+dfsg/src/scripts/ci/download_ci_dep.py | 115 botan3-3.12.0+dfsg/src/scripts/ci/gh_clang_tidy_fixes_in_pr.py | 12 botan3-3.12.0+dfsg/src/scripts/ci/gh_get_changes_in_pr.py | 80 botan3-3.12.0+dfsg/src/scripts/ci/gha_linux_packages.py | 162 botan3-3.12.0+dfsg/src/scripts/ci/setup_gh_actions.ps1 | 23 botan3-3.12.0+dfsg/src/scripts/ci/setup_gh_actions.sh | 163 botan3-3.12.0+dfsg/src/scripts/ci/setup_gh_actions_after_ccache.sh | 26 botan3-3.12.0+dfsg/src/scripts/ci/setup_gh_actions_after_vcvars.ps1 | 2 botan3-3.12.0+dfsg/src/scripts/ci/start_tpm2_simulator.sh | 77 botan3-3.12.0+dfsg/src/scripts/ci_build.py | 346 botan3-3.12.0+dfsg/src/scripts/ci_check_generated_files.py | 123 botan3-3.12.0+dfsg/src/scripts/ci_check_install.py | 2 botan3-3.12.0+dfsg/src/scripts/ci_report_sizes.py | 51 botan3-3.12.0+dfsg/src/scripts/compare_perf.py | 181 botan3-3.12.0+dfsg/src/scripts/dev_tools/addchain.py | 100 botan3-3.12.0+dfsg/src/scripts/dev_tools/analyze_timing_results.py | 2 botan3-3.12.0+dfsg/src/scripts/dev_tools/file_size_check.py | 62 botan3-3.12.0+dfsg/src/scripts/dev_tools/gen_dilithium_kat.py | 4 botan3-3.12.0+dfsg/src/scripts/dev_tools/gen_ec_groups.py | 210 botan3-3.12.0+dfsg/src/scripts/dev_tools/gen_frodo_kat.py | 11 botan3-3.12.0+dfsg/src/scripts/dev_tools/gen_kyber_kat.py | 7 botan3-3.12.0+dfsg/src/scripts/dev_tools/gen_mlkem_acvp_kat.py | 2 botan3-3.12.0+dfsg/src/scripts/dev_tools/gen_mp_comba.py | 1 botan3-3.12.0+dfsg/src/scripts/dev_tools/gen_mp_monty.py | 54 botan3-3.12.0+dfsg/src/scripts/dev_tools/gen_oids.py | 259 botan3-3.12.0+dfsg/src/scripts/dev_tools/gen_os_features.py | 13 botan3-3.12.0+dfsg/src/scripts/dev_tools/gen_pqc_dsa_kats.py | 2 botan3-3.12.0+dfsg/src/scripts/dev_tools/gen_sphincsplus_kat.py | 8 botan3-3.12.0+dfsg/src/scripts/dev_tools/gen_tls_suite_info.py | 164 botan3-3.12.0+dfsg/src/scripts/dev_tools/mychain_creater.sh | 224 botan3-3.12.0+dfsg/src/scripts/dev_tools/mychain_creator.sh | 222 botan3-3.12.0+dfsg/src/scripts/dev_tools/randombit_ocsp_forger.sh | 22 botan3-3.12.0+dfsg/src/scripts/dev_tools/run_clang_format.py | 47 botan3-3.12.0+dfsg/src/scripts/dev_tools/run_clang_tidy.py | 295 botan3-3.12.0+dfsg/src/scripts/dev_tools/show_dependencies.py | 4 botan3-3.12.0+dfsg/src/scripts/dist.py | 2 botan3-3.12.0+dfsg/src/scripts/docker-android.sh | 11 botan3-3.12.0+dfsg/src/scripts/gdb/strubtest.py | 179 botan3-3.12.0+dfsg/src/scripts/install.py | 6 botan3-3.12.0+dfsg/src/scripts/run_limbo_tests.py | 30 botan3-3.12.0+dfsg/src/scripts/run_tests_under_valgrind.py | 138 botan3-3.12.0+dfsg/src/scripts/run_tls_attacker.py | 6 botan3-3.12.0+dfsg/src/scripts/run_tls_fuzzer.py | 2 botan3-3.12.0+dfsg/src/scripts/test_cli.py | 161 botan3-3.12.0+dfsg/src/scripts/test_cli_crypt.py | 2 botan3-3.12.0+dfsg/src/scripts/test_fuzzers.py | 6 botan3-3.12.0+dfsg/src/scripts/test_python.py | 543 + botan3-3.12.0+dfsg/src/scripts/test_strubbed_symbols.py | 165 botan3-3.12.0+dfsg/src/scripts/tls_anvil/analyze_tls_anvil_report.py | 250 botan3-3.12.0+dfsg/src/scripts/tls_anvil/anvil_policy.txt | 13 botan3-3.12.0+dfsg/src/scripts/tls_anvil/run_tls_anvil_tests.py | 172 botan3-3.12.0+dfsg/src/scripts/tls_anvil/tls_anvil_trigger_server.py | 184 botan3-3.12.0+dfsg/src/scripts/tls_scanner/tls_scanner.py | 4 botan3-3.12.0+dfsg/src/scripts/wycheproof.py | 1831 ++++ botan3-3.12.0+dfsg/src/tests/data/aead/ascon_aead128.vec | 730 + botan3-3.12.0+dfsg/src/tests/data/aead/chacha20poly1305.vec | 948 ++ botan3-3.12.0+dfsg/src/tests/data/aead/gcm.vec | 71 botan3-3.12.0+dfsg/src/tests/data/argon2.vec | 2 botan3-3.12.0+dfsg/src/tests/data/asn1_decoding.vec | 252 botan3-3.12.0+dfsg/src/tests/data/asn1_oid_invalid.vec | 4 botan3-3.12.0+dfsg/src/tests/data/asn1_print/output7.txt | 14 botan3-3.12.0+dfsg/src/tests/data/asn1_string_validation.vec | 56 botan3-3.12.0+dfsg/src/tests/data/block/aes.vec | 32 botan3-3.12.0+dfsg/src/tests/data/block/aria.vec | 18 botan3-3.12.0+dfsg/src/tests/data/block/blowfish.vec | 5 botan3-3.12.0+dfsg/src/tests/data/block/camellia.vec | 17 botan3-3.12.0+dfsg/src/tests/data/block/des.vec | 269 botan3-3.12.0+dfsg/src/tests/data/block/noekeon.vec | 2 botan3-3.12.0+dfsg/src/tests/data/block/seed.vec | 7 botan3-3.12.0+dfsg/src/tests/data/block/serpent.vec | 2 botan3-3.12.0+dfsg/src/tests/data/block/shacal2.vec | 2 botan3-3.12.0+dfsg/src/tests/data/block/sm4.vec | 20 botan3-3.12.0+dfsg/src/tests/data/block/twofish.vec | 1547 --- botan3-3.12.0+dfsg/src/tests/data/bn/divide.vec | 12 botan3-3.12.0+dfsg/src/tests/data/bn/from_radix.vec | 233 botan3-3.12.0+dfsg/src/tests/data/bn/lshift.vec | 5 botan3-3.12.0+dfsg/src/tests/data/bn/mod.vec | 4 botan3-3.12.0+dfsg/src/tests/data/bn/rshift.vec | 5 botan3-3.12.0+dfsg/src/tests/data/charset.vec | 184 botan3-3.12.0+dfsg/src/tests/data/codec/base58.vec | 67 botan3-3.12.0+dfsg/src/tests/data/hash/ascon_hash256.vec | 325 botan3-3.12.0+dfsg/src/tests/data/hash/blake2b.vec | 3885 +++------- botan3-3.12.0+dfsg/src/tests/data/hash/sha1.vec | 8 botan3-3.12.0+dfsg/src/tests/data/hash/sha2_32.vec | 10 botan3-3.12.0+dfsg/src/tests/data/hash/sha2_64.vec | 2 botan3-3.12.0+dfsg/src/tests/data/hash/sha3.vec | 2 botan3-3.12.0+dfsg/src/tests/data/hash/shake.vec | 2 botan3-3.12.0+dfsg/src/tests/data/hash/sm3.vec | 2 botan3-3.12.0+dfsg/src/tests/data/hash/whirlpool.vec | 14 botan3-3.12.0+dfsg/src/tests/data/hostnames.vec | 18 botan3-3.12.0+dfsg/src/tests/data/kdf/hkdf.vec | 1 botan3-3.12.0+dfsg/src/tests/data/mac/cmac.vec | 6 botan3-3.12.0+dfsg/src/tests/data/mac/gmac.vec | 645 + botan3-3.12.0+dfsg/src/tests/data/mac/poly1305.vec | 36 botan3-3.12.0+dfsg/src/tests/data/modes/xts.vec | 7 botan3-3.12.0+dfsg/src/tests/data/pubkey/ec_h2s.vec | 98 botan3-3.12.0+dfsg/src/tests/data/pubkey/ecc_explicit_curve.vec | 63 botan3-3.12.0+dfsg/src/tests/data/pubkey/ecdsa_explicit.vec | 38 botan3-3.12.0+dfsg/src/tests/data/pubkey/ecdsa_keygen.vec | 6 botan3-3.12.0+dfsg/src/tests/data/pubkey/ecdsa_verify.vec | 117 botan3-3.12.0+dfsg/src/tests/data/pubkey/ecies.vec | 8 botan3-3.12.0+dfsg/src/tests/data/pubkey/eckcdsa.vec | 2 botan3-3.12.0+dfsg/src/tests/data/pubkey/frodokem_kat.vec | 2 botan3-3.12.0+dfsg/src/tests/data/pubkey/hss_lms_sig.vec | 18 botan3-3.12.0+dfsg/src/tests/data/pubkey/kyber_encodings.vec | 22 botan3-3.12.0+dfsg/src/tests/data/pubkey/kyber_kat.vec | 2 botan3-3.12.0+dfsg/src/tests/data/pubkey/rsa_verify.vec | 12 botan3-3.12.0+dfsg/src/tests/data/pubkey/sm2_invalid.vec | 60 botan3-3.12.0+dfsg/src/tests/data/pubkey/workfactor.vec | 21 botan3-3.12.0+dfsg/src/tests/data/roughtime/roughtime_response.vec | 4 botan3-3.12.0+dfsg/src/tests/data/stream/chacha.vec | 2 botan3-3.12.0+dfsg/src/tests/data/stream/ctr.vec | 7 botan3-3.12.0+dfsg/src/tests/data/tls-policy/compat.txt | 2 botan3-3.12.0+dfsg/src/tests/data/tls-policy/datagram.txt | 4 botan3-3.12.0+dfsg/src/tests/data/tls-policy/default.txt | 4 botan3-3.12.0+dfsg/src/tests/data/tls-policy/default_tls13.txt | 4 botan3-3.12.0+dfsg/src/tests/data/tls-policy/strict.txt | 2 botan3-3.12.0+dfsg/src/tests/data/tls-policy/strict_tls13.txt | 4 botan3-3.12.0+dfsg/src/tests/data/tls_13/client_hello.vec | 12 botan3-3.12.0+dfsg/src/tests/data/tls_13_psk_import.vec | 47 botan3-3.12.0+dfsg/src/tests/data/tls_13_rfc8448/transcripts.vec | 4 botan3-3.12.0+dfsg/src/tests/data/tls_cbc_kat.vec | 123 botan3-3.12.0+dfsg/src/tests/data/tls_extensions/generation/key_share_CH_offers.vec | 18 botan3-3.12.0+dfsg/src/tests/data/tls_extensions/parsing/alpn.vec | 32 botan3-3.12.0+dfsg/src/tests/data/tls_extensions/parsing/cookie.vec | 23 botan3-3.12.0+dfsg/src/tests/data/tls_extensions/parsing/key_share_CH.vec | 6 botan3-3.12.0+dfsg/src/tests/data/tls_extensions/parsing/key_share_SH.vec | 2 botan3-3.12.0+dfsg/src/tests/data/tls_extensions/parsing/signature_algorithms_cert.vec | 4 botan3-3.12.0+dfsg/src/tests/data/tls_extensions/parsing/supported_groups.vec | 4 botan3-3.12.0+dfsg/src/tests/data/tls_null.vec | 32 botan3-3.12.0+dfsg/src/tests/data/utils/dns.vec | 64 botan3-3.12.0+dfsg/src/tests/data/utils/ipv6.vec | 60 botan3-3.12.0+dfsg/src/tests/data/utils/ipv6_nc.vec | 46 botan3-3.12.0+dfsg/src/tests/data/x509/bsi/expected.txt | 6 botan3-3.12.0+dfsg/src/tests/data/x509/crl/ca.crt | 12 botan3-3.12.0+dfsg/src/tests/data/x509/crl/sub1.crt | 12 botan3-3.12.0+dfsg/src/tests/data/x509/crl/sub2.crt | 12 botan3-3.12.0+dfsg/src/tests/data/x509/cve_2026_35580/end_entity.pem | 20 botan3-3.12.0+dfsg/src/tests/data/x509/cve_2026_35580/root.pem | 22 botan3-3.12.0+dfsg/src/tests/data/x509/ecc/nodompar_private.pkcs8.pem | 12 botan3-3.12.0+dfsg/src/tests/data/x509/ecc/withdompar_private.pkcs8.pem | 5 botan3-3.12.0+dfsg/src/tests/data/x509/general_name_ip.vec | 64 botan3-3.12.0+dfsg/src/tests/data/x509/misc/contains_any_extended_key_usage.pem | 16 botan3-3.12.0+dfsg/src/tests/data/x509/misc/contains_multiple_ocsp_responders.pem | 55 botan3-3.12.0+dfsg/src/tests/data/x509/misc/crl_without_nextupdate/01.pem | 15 botan3-3.12.0+dfsg/src/tests/data/x509/misc/crl_without_nextupdate/42.pem | 15 botan3-3.12.0+dfsg/src/tests/data/x509/misc/crl_without_nextupdate/README.md | 21 botan3-3.12.0+dfsg/src/tests/data/x509/misc/crl_without_nextupdate/ca.pem | 15 botan3-3.12.0+dfsg/src/tests/data/x509/misc/crl_without_nextupdate/valid_forever.crl | 12 botan3-3.12.0+dfsg/src/tests/data/x509/misc/multiple_alternative_names.pem | 65 botan3-3.12.0+dfsg/src/tests/data/x509/misc/no_alternative_names.pem | 13 botan3-3.12.0+dfsg/src/tests/data/x509/misc/self-signed-end-entity.pem | 11 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint/Invalid_DNS_Excluded_Mixed_Case_CN.crt | 19 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint/Root_DNS_Excluded_Mixed_Case_CN.crt | 19 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint_empty_subject/intermediate.pem | 21 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint_empty_subject/leaf.pem | 20 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint_empty_subject/root.pem | 19 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint_ipv6/cross_v4only/leaf_invalid.pem | 20 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint_ipv6/cross_v4only/leaf_valid.pem | 20 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint_ipv6/cross_v4only/root.pem | 20 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint_ipv6/cross_v6only/leaf_invalid.pem | 20 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint_ipv6/cross_v6only/leaf_valid.pem | 20 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint_ipv6/cross_v6only/root.pem | 20 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint_ipv6/excluded/leaf_invalid.pem | 20 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint_ipv6/excluded/leaf_valid.pem | 20 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint_ipv6/excluded/root.pem | 20 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint_ipv6/mixed_multi/int.pem | 22 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint_ipv6/mixed_multi/leaf_invalid_excl_v4.pem | 20 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint_ipv6/mixed_multi/leaf_invalid_excl_v6.pem | 20 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint_ipv6/mixed_multi/leaf_invalid_int_v4.pem | 20 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint_ipv6/mixed_multi/leaf_invalid_int_v6.pem | 20 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint_ipv6/mixed_multi/leaf_invalid_root_v4.pem | 20 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint_ipv6/mixed_multi/leaf_invalid_root_v6.pem | 20 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint_ipv6/mixed_multi/leaf_valid.pem | 20 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint_ipv6/mixed_multi/root.pem | 20 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint_ipv6/permitted/leaf_invalid.pem | 20 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint_ipv6/permitted/leaf_valid.pem | 20 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint_ipv6/permitted/root.pem | 20 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint_ipv6/v4_exclude_mapped/leaf_invalid.pem | 20 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint_ipv6/v4_exclude_mapped/leaf_valid.pem | 20 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint_ipv6/v4_exclude_mapped/root.pem | 20 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint_ipv6/v4_permit_mapped/leaf_invalid.pem | 20 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint_ipv6/v4_permit_mapped/leaf_valid.pem | 20 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint_ipv6/v4_permit_mapped/root.pem | 20 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint_prefix/nc_prefix_strongswan_ca_accepted.pem | 15 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint_prefix/nc_prefix_strongswan_ca_not_accepted.pem | 15 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint_prefix/nc_prefix_strongswan_im_accepted.pem | 15 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint_prefix/nc_prefix_strongswan_im_not_accepted.pem | 15 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint_prefix/nc_prefix_strongswan_subject_accepted.pem | 13 botan3-3.12.0+dfsg/src/tests/data/x509/name_constraint_prefix/nc_prefix_strongswan_subject_not_accepted.pem | 13 botan3-3.12.0+dfsg/src/tests/data/x509/nist/expected.txt | 6 botan3-3.12.0+dfsg/src/tests/data/x509/ocsp/byKey_responder.pem | 19 botan3-3.12.0+dfsg/src/tests/data/x509/path_building/end01.pem | 12 botan3-3.12.0+dfsg/src/tests/data/x509/path_building/end02.pem | 12 botan3-3.12.0+dfsg/src/tests/data/x509/path_building/end03.pem | 12 botan3-3.12.0+dfsg/src/tests/data/x509/path_building/end04.pem | 12 botan3-3.12.0+dfsg/src/tests/data/x509/path_building/end05.pem | 12 botan3-3.12.0+dfsg/src/tests/data/x509/path_building/end06.pem | 12 botan3-3.12.0+dfsg/src/tests/data/x509/path_building/end07.pem | 12 botan3-3.12.0+dfsg/src/tests/data/x509/path_building/expected.txt | 7 botan3-3.12.0+dfsg/src/tests/data/x509/path_building/level1_0.pem | 12 botan3-3.12.0+dfsg/src/tests/data/x509/path_building/level1_1.pem | 12 botan3-3.12.0+dfsg/src/tests/data/x509/path_building/level1_2.pem | 12 botan3-3.12.0+dfsg/src/tests/data/x509/path_building/level1_3.pem | 12 botan3-3.12.0+dfsg/src/tests/data/x509/path_building/level1_4.pem | 12 botan3-3.12.0+dfsg/src/tests/data/x509/path_building/level1_5.pem | 12 botan3-3.12.0+dfsg/src/tests/data/x509/path_building/level1_6.pem | 12 botan3-3.12.0+dfsg/src/tests/data/x509/path_building/level2_0.pem | 12 botan3-3.12.0+dfsg/src/tests/data/x509/path_building/level2_1.pem | 12 botan3-3.12.0+dfsg/src/tests/data/x509/path_building/level2_2.pem | 12 botan3-3.12.0+dfsg/src/tests/data/x509/path_building/level2_3.pem | 12 botan3-3.12.0+dfsg/src/tests/data/x509/path_building/level2_4.pem | 12 botan3-3.12.0+dfsg/src/tests/data/x509/path_building/level2_5.pem | 12 botan3-3.12.0+dfsg/src/tests/data/x509/path_building/level2_6.pem | 12 botan3-3.12.0+dfsg/src/tests/data/x509/path_building/root.pem | 11 botan3-3.12.0+dfsg/src/tests/data/x509/pss_certs/expected.txt | 42 botan3-3.12.0+dfsg/src/tests/data/x509/x509test/ASNumberCert.pem | 11 botan3-3.12.0+dfsg/src/tests/data/x509/x509test/ASNumberInherit.pem | 11 botan3-3.12.0+dfsg/src/tests/data/x509/x509test/ASNumberOnly.pem | 11 botan3-3.12.0+dfsg/src/tests/data/x509/x509test/ASRdiOnly.pem | 11 botan3-3.12.0+dfsg/src/tests/data/x509/x509test/IPAddrBlocksAll.pem | 23 botan3-3.12.0+dfsg/src/tests/data/x509/x509test/IPAddrBlocksUnsorted.pem | 17 botan3-3.12.0+dfsg/src/tests/data/x509/x509test/InvalidIPAddrBlocks.pem | 15 botan3-3.12.0+dfsg/src/tests/data/x509_dn.vec | 8 botan3-3.12.0+dfsg/src/tests/data/xof/ascon_xof128.vec | 326 botan3-3.12.0+dfsg/src/tests/data/zfec.vec | 2 botan3-3.12.0+dfsg/src/tests/main.cpp | 18 botan3-3.12.0+dfsg/src/tests/runner/test_reporter.cpp | 25 botan3-3.12.0+dfsg/src/tests/runner/test_reporter.h | 54 botan3-3.12.0+dfsg/src/tests/runner/test_runner.cpp | 62 botan3-3.12.0+dfsg/src/tests/runner/test_runner.h | 5 botan3-3.12.0+dfsg/src/tests/runner/test_stdout_reporter.cpp | 4 botan3-3.12.0+dfsg/src/tests/runner/test_stdout_reporter.h | 6 botan3-3.12.0+dfsg/src/tests/runner/test_xml_reporter.cpp | 53 botan3-3.12.0+dfsg/src/tests/test_aead.cpp | 191 botan3-3.12.0+dfsg/src/tests/test_alt_name.cpp | 38 botan3-3.12.0+dfsg/src/tests/test_arb_eq.h | 128 botan3-3.12.0+dfsg/src/tests/test_asn1.cpp | 385 botan3-3.12.0+dfsg/src/tests/test_bigint.cpp | 338 botan3-3.12.0+dfsg/src/tests/test_block.cpp | 153 botan3-3.12.0+dfsg/src/tests/test_blowfish.cpp | 6 botan3-3.12.0+dfsg/src/tests/test_bufcomp.cpp | 28 botan3-3.12.0+dfsg/src/tests/test_certstor.cpp | 114 botan3-3.12.0+dfsg/src/tests/test_certstor_flatfile.cpp | 93 botan3-3.12.0+dfsg/src/tests/test_certstor_system.cpp | 105 botan3-3.12.0+dfsg/src/tests/test_certstor_utils.cpp | 7 botan3-3.12.0+dfsg/src/tests/test_certstor_utils.h | 1 botan3-3.12.0+dfsg/src/tests/test_cmce.cpp | 66 botan3-3.12.0+dfsg/src/tests/test_codec.cpp | 30 botan3-3.12.0+dfsg/src/tests/test_compression.cpp | 111 botan3-3.12.0+dfsg/src/tests/test_concurrent_pk.cpp | 516 + botan3-3.12.0+dfsg/src/tests/test_cryptobox.cpp | 9 botan3-3.12.0+dfsg/src/tests/test_crystals.cpp | 229 botan3-3.12.0+dfsg/src/tests/test_ct_utils.cpp | 155 botan3-3.12.0+dfsg/src/tests/test_dh.cpp | 14 botan3-3.12.0+dfsg/src/tests/test_dilithium.cpp | 98 botan3-3.12.0+dfsg/src/tests/test_dl_group.cpp | 109 botan3-3.12.0+dfsg/src/tests/test_dlies.cpp | 29 botan3-3.12.0+dfsg/src/tests/test_ec_group.cpp | 632 - botan3-3.12.0+dfsg/src/tests/test_ecc_explicit_params.cpp | 80 botan3-3.12.0+dfsg/src/tests/test_ecc_h2c.cpp | 51 botan3-3.12.0+dfsg/src/tests/test_ecc_pointmul.cpp | 127 botan3-3.12.0+dfsg/src/tests/test_ecdh.cpp | 43 botan3-3.12.0+dfsg/src/tests/test_ecdsa.cpp | 87 botan3-3.12.0+dfsg/src/tests/test_ecgdsa.cpp | 3 botan3-3.12.0+dfsg/src/tests/test_ecies.cpp | 100 botan3-3.12.0+dfsg/src/tests/test_eckcdsa.cpp | 3 botan3-3.12.0+dfsg/src/tests/test_ed25519.cpp | 13 botan3-3.12.0+dfsg/src/tests/test_ed448.cpp | 23 botan3-3.12.0+dfsg/src/tests/test_entropy.cpp | 22 botan3-3.12.0+dfsg/src/tests/test_ffi.cpp | 2500 +++++- botan3-3.12.0+dfsg/src/tests/test_filters.cpp | 251 botan3-3.12.0+dfsg/src/tests/test_fpe.cpp | 8 botan3-3.12.0+dfsg/src/tests/test_frodokem.cpp | 61 botan3-3.12.0+dfsg/src/tests/test_gf2m.cpp | 12 botan3-3.12.0+dfsg/src/tests/test_gost_3410.cpp | 18 botan3-3.12.0+dfsg/src/tests/test_hash.cpp | 34 botan3-3.12.0+dfsg/src/tests/test_hash_id.cpp | 8 botan3-3.12.0+dfsg/src/tests/test_hss_lms.cpp | 115 botan3-3.12.0+dfsg/src/tests/test_jitter_rng.cpp | 6 botan3-3.12.0+dfsg/src/tests/test_kdf.cpp | 16 botan3-3.12.0+dfsg/src/tests/test_keccak_helpers.cpp | 139 botan3-3.12.0+dfsg/src/tests/test_keywrap.cpp | 9 botan3-3.12.0+dfsg/src/tests/test_kyber.cpp | 71 botan3-3.12.0+dfsg/src/tests/test_lmots.cpp | 15 botan3-3.12.0+dfsg/src/tests/test_lms.cpp | 15 botan3-3.12.0+dfsg/src/tests/test_mac.cpp | 30 botan3-3.12.0+dfsg/src/tests/test_mceliece.cpp | 34 botan3-3.12.0+dfsg/src/tests/test_ml_dsa.cpp | 2 botan3-3.12.0+dfsg/src/tests/test_modes.cpp | 153 botan3-3.12.0+dfsg/src/tests/test_monty.cpp | 64 botan3-3.12.0+dfsg/src/tests/test_mp.cpp | 45 botan3-3.12.0+dfsg/src/tests/test_name_constraint.cpp | 229 botan3-3.12.0+dfsg/src/tests/test_ocb.cpp | 26 botan3-3.12.0+dfsg/src/tests/test_ocsp.cpp | 295 botan3-3.12.0+dfsg/src/tests/test_octetstring.cpp | 59 botan3-3.12.0+dfsg/src/tests/test_oid.cpp | 40 botan3-3.12.0+dfsg/src/tests/test_os_utils.cpp | 74 botan3-3.12.0+dfsg/src/tests/test_otp.cpp | 44 botan3-3.12.0+dfsg/src/tests/test_pad.cpp | 32 botan3-3.12.0+dfsg/src/tests/test_passhash.cpp | 26 botan3-3.12.0+dfsg/src/tests/test_pbkdf.cpp | 85 botan3-3.12.0+dfsg/src/tests/test_pem.cpp | 10 botan3-3.12.0+dfsg/src/tests/test_pk_pad.cpp | 78 botan3-3.12.0+dfsg/src/tests/test_pkcs11.h | 11 botan3-3.12.0+dfsg/src/tests/test_pkcs11_high_level.cpp | 404 - botan3-3.12.0+dfsg/src/tests/test_pkcs11_low_level.cpp | 281 botan3-3.12.0+dfsg/src/tests/test_psk_db.cpp | 72 botan3-3.12.0+dfsg/src/tests/test_pubkey.cpp | 381 botan3-3.12.0+dfsg/src/tests/test_pubkey.h | 58 botan3-3.12.0+dfsg/src/tests/test_pubkey_pqc.h | 85 botan3-3.12.0+dfsg/src/tests/test_rfc6979.cpp | 10 botan3-3.12.0+dfsg/src/tests/test_rng.h | 65 botan3-3.12.0+dfsg/src/tests/test_rng_behavior.cpp | 228 botan3-3.12.0+dfsg/src/tests/test_rng_kat.cpp | 5 botan3-3.12.0+dfsg/src/tests/test_rngs.cpp | 41 botan3-3.12.0+dfsg/src/tests/test_roughtime.cpp | 109 botan3-3.12.0+dfsg/src/tests/test_rsa.cpp | 50 botan3-3.12.0+dfsg/src/tests/test_simd.cpp | 273 botan3-3.12.0+dfsg/src/tests/test_siv.cpp | 3 botan3-3.12.0+dfsg/src/tests/test_sm2.cpp | 44 botan3-3.12.0+dfsg/src/tests/test_sodium.cpp | 180 botan3-3.12.0+dfsg/src/tests/test_sphincsplus.cpp | 72 botan3-3.12.0+dfsg/src/tests/test_sphincsplus_fors.cpp | 22 botan3-3.12.0+dfsg/src/tests/test_sphincsplus_utils.cpp | 43 botan3-3.12.0+dfsg/src/tests/test_sphincsplus_wots.cpp | 26 botan3-3.12.0+dfsg/src/tests/test_srp6.cpp | 14 botan3-3.12.0+dfsg/src/tests/test_stream.cpp | 44 botan3-3.12.0+dfsg/src/tests/test_strong_type.cpp | 1246 +-- botan3-3.12.0+dfsg/src/tests/test_tests.cpp | 55 botan3-3.12.0+dfsg/src/tests/test_thread_utils.cpp | 4 botan3-3.12.0+dfsg/src/tests/test_tls.cpp | 502 + botan3-3.12.0+dfsg/src/tests/test_tls_cipher_state.cpp | 475 - botan3-3.12.0+dfsg/src/tests/test_tls_handshake_layer_13.cpp | 111 botan3-3.12.0+dfsg/src/tests/test_tls_handshake_state_13.cpp | 63 botan3-3.12.0+dfsg/src/tests/test_tls_handshake_transitions.cpp | 20 botan3-3.12.0+dfsg/src/tests/test_tls_hybrid_kem_key.cpp | 160 botan3-3.12.0+dfsg/src/tests/test_tls_messages.cpp | 306 botan3-3.12.0+dfsg/src/tests/test_tls_record_layer_13.cpp | 244 botan3-3.12.0+dfsg/src/tests/test_tls_rfc8448.cpp | 912 +- botan3-3.12.0+dfsg/src/tests/test_tls_session_manager.cpp | 556 - botan3-3.12.0+dfsg/src/tests/test_tls_signature_scheme.cpp | 44 botan3-3.12.0+dfsg/src/tests/test_tls_stream_integration.cpp | 74 botan3-3.12.0+dfsg/src/tests/test_tls_transcript_hash_13.cpp | 31 botan3-3.12.0+dfsg/src/tests/test_tpm.cpp | 18 botan3-3.12.0+dfsg/src/tests/test_tpm2.cpp | 453 - botan3-3.12.0+dfsg/src/tests/test_tss.cpp | 18 botan3-3.12.0+dfsg/src/tests/test_uri.cpp | 49 botan3-3.12.0+dfsg/src/tests/test_utils.cpp | 932 +- botan3-3.12.0+dfsg/src/tests/test_utils_bitvector.cpp | 529 - botan3-3.12.0+dfsg/src/tests/test_utils_buffer.cpp | 464 - botan3-3.12.0+dfsg/src/tests/test_workfactor.cpp | 6 botan3-3.12.0+dfsg/src/tests/test_x25519.cpp | 25 botan3-3.12.0+dfsg/src/tests/test_x509_dn.cpp | 14 botan3-3.12.0+dfsg/src/tests/test_x509_path.cpp | 898 +- botan3-3.12.0+dfsg/src/tests/test_x509_rpki.cpp | 2488 ++++++ botan3-3.12.0+dfsg/src/tests/test_xmss.cpp | 64 botan3-3.12.0+dfsg/src/tests/test_xof.cpp | 65 botan3-3.12.0+dfsg/src/tests/test_zfec.cpp | 27 botan3-3.12.0+dfsg/src/tests/tests.cpp | 919 +- botan3-3.12.0+dfsg/src/tests/tests.h | 628 - botan3-3.12.0+dfsg/src/tests/unit_asio_stream.cpp | 196 botan3-3.12.0+dfsg/src/tests/unit_ecdsa.cpp | 109 botan3-3.12.0+dfsg/src/tests/unit_tls.cpp | 523 + botan3-3.12.0+dfsg/src/tests/unit_tls_policy.cpp | 61 botan3-3.12.0+dfsg/src/tests/unit_x509.cpp | 858 +- 1892 files changed, 104386 insertions(+), 47530 deletions(-) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpc5m3mi89/botan3_3.7.1+dfsg-2.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpc5m3mi89/botan3_3.12.0+dfsg-2~deb13u1.dsc: no acceptable signature found diff -Nru botan3-3.7.1+dfsg/.clang-format botan3-3.12.0+dfsg/.clang-format --- botan3-3.7.1+dfsg/.clang-format 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/.clang-format 1970-01-01 00:00:00.000000000 +0000 @@ -1,59 +0,0 @@ -Language: Cpp -Standard: c++20 - -BasedOnStyle: Chromium - -ColumnLimit: 120 -AccessModifierOffset: -3 -IndentWidth: 3 -ContinuationIndentWidth: 3 -ConstructorInitializerIndentWidth: 6 - -PointerAlignment: Left -ReferenceAlignment: Left -QualifierAlignment: Left - -IncludeBlocks: Preserve -IncludeCategories: - - Regex: '^' - Priority: 3 - CaseSensitive: false - - Regex: '^' - Priority: 2 - CaseSensitive: false - - Regex: '^<.*' - Priority: 4 - CaseSensitive: false - - Regex: '^<.*\.h>' - Priority: 3 - CaseSensitive: false - - Regex: '.*' - Priority: 1 - CaseSensitive: false - -AttributeMacros: ['BOTAN_FUNC_ISA', - 'BOTAN_FUNC_ISA_INLINE', - 'BOTAN_FORCE_INLINE', - 'BOTAN_DEPRECATED', - 'BOTAN_DEPRECATED_API'] - -BinPackArguments: false -BreakStringLiterals: false -AllowAllArgumentsOnNextLine: true -AllowAllParametersOfDeclarationOnNextLine: true -ConstructorInitializerAllOnOneLineOrOnePerLine: true -EmptyLineBeforeAccessModifier: Always - -BreakConstructorInitializers: AfterColon -BreakInheritanceList: AfterComma -AllowShortBlocksOnASingleLine: Empty -AllowShortFunctionsOnASingleLine: Inline -SpaceBeforeParens: Never -IndentPPDirectives: BeforeHash -FixNamespaceComments: true -SeparateDefinitionBlocks: Always -KeepEmptyLinesAtTheStartOfBlocks: false -IndentAccessModifiers: true -ReflowComments: false -RequiresClausePosition: OwnLine -IndentRequiresClause: true diff -Nru botan3-3.7.1+dfsg/.devcontainer/Dockerfile botan3-3.12.0+dfsg/.devcontainer/Dockerfile --- botan3-3.7.1+dfsg/.devcontainer/Dockerfile 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/.devcontainer/Dockerfile 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,70 @@ +FROM ubuntu:24.04 + +ARG LANG=en_US.UTF-8 +ARG LANGUAGE=en_US.UTF-8 +ARG LC_ALL=en_US.UTF-8 + +RUN echo "unminimize the ubuntu base image" \ + && yes | unminimize + +RUN echo "updating packages of base image" \ + && apt-get update \ + && apt-get -y dist-upgrade \ + && DEBIAN_FRONTEND=noninteractive apt-get install -y locales \ + && sed -i '/'${LANG}'/s/^# //g' /etc/locale.gen \ + && locale-gen + +ENV LANG=${LANG} +ENV LANGUAGE=${LANGUAGE} +ENV LC_ALL=${LC_ALL} + +RUN echo "installing essential devtools" \ + && DEBIAN_FRONTEND=noninteractive apt-get -y --no-install-recommends install \ + apt-transport-https \ + build-essential \ + ca-certificates \ + ccache \ + clang \ + clang-format-17 \ + clangd-20 \ + curl \ + fzf \ + gdb \ + git \ + jq \ + less \ + ninja-build \ + pipx \ + procps \ + pylint \ + python3-pip \ + python3.12 \ + shellcheck \ + software-properties-common \ + sudo \ + tig \ + unzip \ + valgrind \ + vim \ + wget \ + zsh + +RUN echo "installing Botan-specific tools and dependencies" \ + && DEBIAN_FRONTEND=noninteractive apt-get -y --no-install-recommends install \ + doxygen \ + golang \ + libboost-dev \ + libtss2-tcti-tabrmd0 \ + python3-docutils \ + python3-sphinx \ + softhsm2 \ + swtpm \ + swtpm-tools \ + tpm2-abrmd \ + tpm2-tools \ + && apt-get autoremove --purge \ + && rm -fR /var/cache/apt/archives \ + && pipx install \ + ruff + +ENTRYPOINT ["/usr/local/bin/docker-entrypoint"] diff -Nru botan3-3.7.1+dfsg/.devcontainer/devcontainer.json botan3-3.12.0+dfsg/.devcontainer/devcontainer.json --- botan3-3.7.1+dfsg/.devcontainer/devcontainer.json 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/.devcontainer/devcontainer.json 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,48 @@ +{ + "name": "Botan", + "build": { + "dockerfile": "Dockerfile" + }, + "features": { + "ghcr.io/devcontainers/features/common-utils": { + "installOhMyZsh": true, + "installOhMyZshConfig": true, + "configureZshAsDefaultShell": true + }, + "ghcr.io/devcontainers-extra/features/zsh-plugins:0": { + "plugins": "fzf" + } + }, + "postStartCommand": "cd ${containerWorkspaceFolder}; .devcontainer/startup.sh", + "remoteUser": "ubuntu", + "customizations": { + "vscode": { + "settings": { + "[cpp]": { + "editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd", + "editor.formatOnSave": true + }, + "clangd": { + "path": "clangd-20", + "arguments": [ + "--header-insertion=never" + ], + "checkUpdates": false + }, + "clang-format.executable": "clang-format-17", + "ruff.enable": true, + "pylint.args": [ + "--rcfile=src/configs/pylint.rc" + ] + }, + "extensions": [ + "llvm-vs-code-extensions.vscode-clangd", + "ms-vscode.cpptools", + "ms-vscode.cpptools-themes", + "ms-python.python", + "charliermarsh.ruff", + "editorconfig.editorconfig" + ] + } + } +} diff -Nru botan3-3.7.1+dfsg/.devcontainer/startup.sh botan3-3.12.0+dfsg/.devcontainer/startup.sh --- botan3-3.7.1+dfsg/.devcontainer/startup.sh 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/.devcontainer/startup.sh 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,28 @@ +#!/bin/bash + +# Development Container Startup Script +# +# This runs whenever the container starts. Use it to set up common things in the +# repository. The current working directory is always at the repository's root. +# +# (C) 2025 Jack Lloyd +# (C) René Meusel, Rohde & Schwarz Cybersecurity +# +# Botan is released under the Simplified BSD License (see license.txt) + +create_symlink() { + if [ ! -L "$1" ]; then + echo "Creating symlink from '$1' to '$2'" + ln -s "$2" "$1" + else + echo "Symlink '$1' already exists" + fi +} + +create_symlink .vscode src/editors/vscode +create_symlink .editorconfig src/editors/editorconfig +create_symlink .clang-format src/configs/clang-format + +echo "Setting up git blame to ignore certain commits" +git config --local blame.ignoreRevsFile src/configs/git-blame-ignore-revs +git config --local blame.markIgnoredLines true diff -Nru botan3-3.7.1+dfsg/.github/actions/setup-build-agent/action.yml botan3-3.12.0+dfsg/.github/actions/setup-build-agent/action.yml --- botan3-3.7.1+dfsg/.github/actions/setup-build-agent/action.yml 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/.github/actions/setup-build-agent/action.yml 2026-05-07 01:38:28.000000000 +0000 @@ -11,6 +11,10 @@ target: description: The ci_build.py target going to be built on this agent required: true + compiler: + description: The compiler used to build this target + required: false + default: unknown cache-key: description: The actions/cache key to be used for this runs, caching will be disabled when no key is provided required: false @@ -23,11 +27,11 @@ using: composite steps: - name: Setup Build Agent (Windows) - run: ${{ github.action_path }}/../../../src/scripts/ci/setup_gh_actions.ps1 "${{ inputs.target }}" "${{ inputs.arch }}" + run: ${{ github.action_path }}/../../../src/scripts/ci/setup_gh_actions.ps1 "${{ inputs.target }}" "${{ inputs.compiler }}" "${{ inputs.arch }}" shell: pwsh if: runner.os == 'Windows' - name: Setup Build Agent (Unix-like) - run: ${{ github.action_path }}/../../../src/scripts/ci/setup_gh_actions.sh "${{ inputs.target }}" "${{ inputs.arch }}" + run: ${{ github.action_path }}/../../../src/scripts/ci/setup_gh_actions.sh "${{ inputs.target }}" "${{ inputs.compiler }}" "${{ inputs.arch }}" shell: bash if: runner.os != 'Windows' @@ -38,7 +42,9 @@ - uses: actions/cache@v4 if: env.COMPILER_CACHE_LOCATION != '' && inputs.cache-key != '' with: - path: ${{ env.COMPILER_CACHE_LOCATION }} + path: | + ${{ env.COMPILER_CACHE_LOCATION }} + ${{ env.BOTAN_CLANG_TIDY_CACHE }} key: ${{ inputs.cache-key }}-${{ github.run_id }} restore-keys: ${{ inputs.cache-key }} save-always: true diff -Nru botan3-3.7.1+dfsg/.github/workflows/ci.yml botan3-3.12.0+dfsg/.github/workflows/ci.yml --- botan3-3.7.1+dfsg/.github/workflows/ci.yml 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/.github/workflows/ci.yml 2026-05-07 01:38:28.000000000 +0000 @@ -33,20 +33,38 @@ - target: shared arch: x86_64 host_os: windows-2022 + compiler: msvc - target: static arch: x86_64 host_os: windows-2022 + compiler: msvc - target: amalgamation arch: x86_64 host_os: windows-2022 + compiler: msvc - target: shared arch: x86 host_os: windows-2022 + compiler: msvc + - target: amalgamation + arch: x86 + host_os: windows-2022 + compiler: msvc + - target: shared + arch: x86_64 + host_os: windows-2022 + compiler: clangcl + - target: static + arch: x86 + host_os: windows-2022 + compiler: clangcl runs-on: ${{ matrix.host_os }} steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Read Repository Configuration uses: ./.github/actions/read-repo-config @@ -55,11 +73,12 @@ uses: ./.github/actions/setup-build-agent with: target: ${{ matrix.target }} - cache-key: ${{ matrix.host_os }}-msvc-${{ matrix.arch }}-${{ matrix.target }} + compiler: msvc + cache-key: ${{ matrix.host_os }}-${{ matrix.compiler }}-${{ matrix.arch }}-${{ matrix.target }} arch: ${{ matrix.arch }} - name: Build and Test Botan - run: python3 ./src/scripts/ci_build.py --cc='msvc' --make-tool='ninja' --cpu='${{ matrix.arch }}' --test-results-dir=junit_results ${{ matrix.target }} + run: python3 ./src/scripts/ci_build.py --cc='${{ matrix.compiler }}' --ci-image='${{ matrix.host_os }}' --make-tool='ninja' --cpu='${{ matrix.arch }}' --test-results-dir=junit_results ${{ matrix.target }} linux: name: "Linux" @@ -70,17 +89,23 @@ include: - compiler: gcc target: shared + host_os: ubuntu-22.04 - compiler: gcc target: amalgamation + host_os: ubuntu-24.04 - compiler: gcc target: static + host_os: ubuntu-22.04 - compiler: clang target: shared + host_os: ubuntu-22.04 - runs-on: ubuntu-22.04 + runs-on: ${{ matrix.host_os }} steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Read Repository Configuration uses: ./.github/actions/read-repo-config @@ -89,10 +114,11 @@ uses: ./.github/actions/setup-build-agent with: target: ${{ matrix.target }} - cache-key: linux-${{ matrix.compiler }}-x86_64-${{ matrix.target }} + compiler: ${{ matrix.compiler }} + cache-key: ${{ matrix.host_os }}-${{ matrix.compiler }}-x86_64-${{ matrix.target }} - name: Build and Test Botan - run: python3 ./src/scripts/ci_build.py --cc='${{ matrix.compiler }}' --test-results-dir=junit_results ${{ matrix.target }} + run: python3 ./src/scripts/ci_build.py --cc='${{ matrix.compiler }}' --ci-image='${{ matrix.host_os }}' --test-results-dir=junit_results ${{ matrix.target }} macos: name: "macOS" @@ -103,21 +129,25 @@ include: - target: shared compiler: xcode - os: macos-13 + host_os: macos-15-intel - target: amalgamation compiler: xcode - os: macos-13 + host_os: macos-15-intel + make_tool: ninja - target: shared compiler: xcode - os: macos-14 # uses Apple Silicon + host_os: macos-15 # uses Apple Silicon + make_tool: ninja - target: amalgamation compiler: xcode - os: macos-14 # uses Apple Silicon + host_os: macos-15 # uses Apple Silicon - runs-on: ${{ matrix.os }} + runs-on: ${{ matrix.host_os }} steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Read Repository Configuration uses: ./.github/actions/read-repo-config @@ -126,16 +156,37 @@ uses: ./.github/actions/setup-build-agent with: target: ${{ matrix.target }} - cache-key: macos-${{ matrix.compiler }}-${{ matrix.os }}-${{ matrix.target }} + compiler: ${{ matrix.compiler }} + cache-key: macos-${{ matrix.compiler }}-${{ matrix.host_os }}-${{ matrix.target }} - name: Build and Test Botan - run: python3 ./src/scripts/ci_build.py --cc='${{ matrix.compiler }}' --test-results-dir=junit_results ${{ matrix.target }} + run: python3 ./src/scripts/ci_build.py --cc='${{ matrix.compiler }}' --ci-image='${{ matrix.host_os }}' --make-tool='${{ matrix.make_tool }}' --test-results-dir=junit_results ${{ matrix.target }} + + acvp: + name: "ACVP" + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Read Repository Configuration + uses: ./.github/actions/read-repo-config + - name: Setup Build Agent + uses: ./.github/actions/setup-build-agent + with: + target: acvp + compiler: gcc + cache-key: linux-x86_64-acvp + - name: Run ACVP Tests + run: python3 ./src/scripts/ci_build.py --cc=gcc --make-tool=make acvp clang-tidy: name: "Clang Tidy" runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Read Repository Configuration uses: ./.github/actions/read-repo-config @@ -144,6 +195,7 @@ uses: ./.github/actions/setup-build-agent with: target: clang-tidy + compiler: clang cache-key: linux-x86_64-clang-tidy - name: Configure Build @@ -151,8 +203,7 @@ - name: Run Clang Tidy run: | - ./src/scripts/ci/gh_get_changes_in_pr.py $(git rev-parse HEAD) --api-token=${{ secrets.GITHUB_TOKEN }} | \ - python3 ./src/scripts/dev_tools/run_clang_tidy.py --verbose --take-file-list-from-stdin --export-fixes-dir=clang_tidy_diagnostics + python3 ./src/scripts/dev_tools/run_clang_tidy.py - name: Display Clang Tidy Results if: failure() @@ -165,9 +216,6 @@ matrix: include: - - target: coverage - compiler: gcc - host_os: ubuntu-24.04 - target: sanitizer compiler: clang host_os: ubuntu-24.04 @@ -186,16 +234,17 @@ - target: limbo compiler: gcc host_os: ubuntu-24.04 + - target: typos + compiler: gcc + host_os: ubuntu-24.04 runs-on: ${{ matrix.host_os }} - env: - COVERALLS_REPO_TOKEN: pbLoTMBxC1DFvbws9WfrzVOvfEdEZTcCS - steps: - uses: actions/checkout@v4 with: path: ./source + persist-credentials: false - name: Read Repository Configuration uses: ./source/.github/actions/read-repo-config @@ -203,19 +252,21 @@ - name: Fetch BoringSSL fork for BoGo tests uses: actions/checkout@v4 with: + persist-credentials: false repository: ${{ env.BORINGSSL_REPO }} ref: ${{ env.BORINGSSL_BRANCH }} path: ./boringssl - if: matrix.target == 'coverage' || matrix.target == 'sanitizer' + if: matrix.target == 'sanitizer' - name: Setup Build Agent uses: ./source/.github/actions/setup-build-agent with: target: ${{ matrix.target }} + compiler: ${{ matrix.compiler }} cache-key: ${{ matrix.host_os }}-${{ matrix.compiler }}-x86_64-${{ matrix.target }} - name: Build and Test Botan - run: python3 ./source/src/scripts/ci_build.py --root-dir=${{ github.workspace }}/source --build-dir=${{ github.workspace }}/build --boringssl-dir=${{ github.workspace }}/boringssl --cc='${{ matrix.compiler }}' --make-tool='${{ matrix.make_tool }}' --test-results-dir=junit_results ${{ matrix.target }} + run: python3 ./source/src/scripts/ci_build.py --root-dir=${{ github.workspace }}/source --build-dir=${{ github.workspace }}/build --boringssl-dir=${{ github.workspace }}/boringssl --cc='${{ matrix.compiler }}' --ci-image='${{ matrix.host_os }}' --make-tool='${{ matrix.make_tool }}' --test-results-dir=junit_results ${{ matrix.target }} specials: name: "Special" @@ -230,7 +281,19 @@ - target: minimized compiler: gcc host_os: ubuntu-24.04 - - target: bsi + - target: no_tls12 + compiler: gcc + host_os: ubuntu-24.04 + - target: no_tls13 + compiler: gcc + host_os: ubuntu-24.04 + - target: policy-bsi + compiler: gcc + host_os: ubuntu-24.04 + - target: policy-fips140 + compiler: gcc + host_os: ubuntu-24.04 + - target: policy-modern compiler: gcc host_os: ubuntu-24.04 - target: docs @@ -239,25 +302,42 @@ - target: no_pcurves compiler: gcc host_os: ubuntu-24.04 + - target: optional-rngs + compiler: gcc + host_os: ubuntu-24.04 + - target: pkcs11 + compiler: gcc + host_os: ubuntu-24.04 runs-on: ${{ matrix.host_os }} steps: - uses: actions/checkout@v4 with: + persist-credentials: false path: ./source - name: Read Repository Configuration uses: ./source/.github/actions/read-repo-config + - name: Fetch BoringSSL fork for BoGo tests + uses: actions/checkout@v4 + with: + persist-credentials: false + repository: ${{ env.BORINGSSL_REPO }} + ref: ${{ env.BORINGSSL_BRANCH }} + path: ./boringssl + if: matrix.target == 'no_tls12' || matrix.target == 'no_tls13' + - name: Setup Build Agent uses: ./source/.github/actions/setup-build-agent with: target: ${{ matrix.target }} + compiler: ${{ matrix.compiler }} cache-key: ${{ matrix.host_os }}-${{ matrix.compiler }}-x86_64-${{ matrix.target }} - name: Build and Test Botan - run: python3 ./source/src/scripts/ci_build.py --root-dir=${{ github.workspace }}/source --build-dir=${{ github.workspace }}/build --boringssl-dir=${{ github.workspace }}/boringssl --cc='${{ matrix.compiler }}' --test-results-dir=junit_results ${{ matrix.target }} + run: python3 ./source/src/scripts/ci_build.py --root-dir=${{ github.workspace }}/source --build-dir=${{ github.workspace }}/build --boringssl-dir=${{ github.workspace }}/boringssl --cc='${{ matrix.compiler }}' --ci-image='${{ matrix.host_os }}' --test-results-dir=junit_results ${{ matrix.target }} x-compile: name: "Cross" @@ -269,18 +349,24 @@ - target: cross-i386 compiler: gcc host_os: ubuntu-22.04 - - target: cross-arm32 + - target: shared compiler: gcc - host_os: ubuntu-24.04 - - target: cross-arm64 + host_os: ubuntu-24.04-arm + - target: amalgamation compiler: gcc - host_os: ubuntu-24.04 + host_os: ubuntu-24.04-arm + - target: shared + compiler: clang + host_os: ubuntu-24.04-arm - target: cross-ppc64 compiler: gcc host_os: ubuntu-24.04 - target: cross-mips64 compiler: gcc host_os: ubuntu-24.04 + - target: cross-loongarch64 + compiler: gcc + host_os: ubuntu-24.04 - target: cross-android-arm64 compiler: clang host_os: ubuntu-24.04 @@ -290,7 +376,7 @@ make_tool: make - target: cross-ios-arm64 compiler: xcode - host_os: macos-13 + host_os: macos-26 - target: cross-arm32-baremetal compiler: gcc host_os: ubuntu-24.04 @@ -299,6 +385,8 @@ steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Read Repository Configuration uses: ./.github/actions/read-repo-config @@ -307,7 +395,8 @@ uses: ./.github/actions/setup-build-agent with: target: ${{ matrix.target }} + compiler: ${{ matrix.compiler }} cache-key: ${{ matrix.host_os }}-${{ matrix.compiler }}-xcompile-${{ matrix.target }} - name: Build and Test Botan - run: python3 ./src/scripts/ci_build.py --cc='${{ matrix.compiler }}' --make-tool='${{ matrix.make_tool }}' --test-results-dir=junit_results ${{ matrix.target }} + run: python3 ./src/scripts/ci_build.py --cc='${{ matrix.compiler }}' --ci-image='${{ matrix.host_os }}' --make-tool='${{ matrix.make_tool }}' --test-results-dir=junit_results ${{ matrix.target }} diff -Nru botan3-3.7.1+dfsg/.github/workflows/codeql.yml botan3-3.12.0+dfsg/.github/workflows/codeql.yml --- botan3-3.7.1+dfsg/.github/workflows/codeql.yml 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/.github/workflows/codeql.yml 2026-05-07 01:38:28.000000000 +0000 @@ -3,9 +3,6 @@ on: push: branches: ["master"] - pull_request: - # The branches below must be a subset of the branches above - branches: ["master"] schedule: # runs every day at 4:23 AM UTC - cron: "23 4 * * *" @@ -16,7 +13,7 @@ jobs: codeql_cpp: name: C++ - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 permissions: actions: read contents: read @@ -25,6 +22,8 @@ steps: - name: Checkout repository uses: actions/checkout@v4 + with: + persist-credentials: false - name: Read Repository Configuration uses: ./.github/actions/read-repo-config @@ -36,7 +35,7 @@ cache-key: linux-gcc-x86_64-codeql - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: cpp config-file: ./src/configs/codeql.yml @@ -45,13 +44,13 @@ run: ./src/scripts/ci_build.py --compiler-cache=none codeql - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 with: category: cpp codeql_py: name: Python - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 permissions: actions: read contents: read @@ -60,14 +59,16 @@ steps: - name: Checkout repository uses: actions/checkout@v4 + with: + persist-credentials: false - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: python config-file: ./src/configs/codeql.yml - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 with: category: python diff -Nru botan3-3.7.1+dfsg/.github/workflows/nightly.yml botan3-3.12.0+dfsg/.github/workflows/nightly.yml --- botan3-3.7.1+dfsg/.github/workflows/nightly.yml 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/.github/workflows/nightly.yml 2026-05-07 01:38:28.000000000 +0000 @@ -12,7 +12,7 @@ on: workflow_dispatch: - push: + pull_request: paths: # Run if a pull request changes this workflow to # validate it works properly before merging. @@ -22,6 +22,75 @@ - cron: '14 3 * * *' jobs: + coverage: + name: "Coverage" + + runs-on: ubuntu-24.04 + + steps: + - uses: actions/checkout@v4 + with: + path: ./source + persist-credentials: false + + - name: Read Repository Configuration + uses: ./source/.github/actions/read-repo-config + + - name: Fetch BoringSSL fork for BoGo tests + uses: actions/checkout@v4 + with: + persist-credentials: false + repository: ${{ env.BORINGSSL_REPO }} + ref: ${{ env.BORINGSSL_BRANCH }} + path: ./boringssl + + - name: Setup Build Agent + uses: ./source/.github/actions/setup-build-agent + with: + target: coverage + compiler: gcc + cache-key: ubuntu-24.04-gcc-x86_64-coverage + + - name: Build and Test Botan + env: + COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} + run: python3 ./source/src/scripts/ci_build.py --root-dir=${{ github.workspace }}/source --build-dir=${{ github.workspace }}/build --boringssl-dir=${{ github.workspace }}/boringssl --cc='gcc' --ci-image='ubuntu-24.04' --test-results-dir=junit_results coverage + + strubbing: + name: "Strubbing" + strategy: + fail-fast: false + + matrix: + include: + - target: strubbing + compiler: gcc-14 + host_os: ubuntu-24.04 + - target: strubbing + compiler: gcc-14 + host_os: ubuntu-24.04-arm + + runs-on: ${{ matrix.host_os }} + + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + path: ./source + + - name: Read Repository Configuration + uses: ./source/.github/actions/read-repo-config + + - name: Setup Build Agent + uses: ./source/.github/actions/setup-build-agent + with: + target: ${{ matrix.target }} + compiler: ${{ matrix.compiler }} + cache-key: ${{ matrix.host_os }}-${{ matrix.compiler }}-x86_64-${{ matrix.target }} + + - name: Build and Test Botan + run: python3 ./source/src/scripts/ci_build.py --root-dir=${{ github.workspace }}/source --build-dir=${{ github.workspace }}/build --cc='${{ matrix.compiler }}' --ci-image='${{ matrix.host_os }}' --test-results-dir=junit_results ${{ matrix.target }} + sanitizer: name: "Sanitizers" strategy: @@ -34,7 +103,7 @@ host_os: windows-2022 make_tool: ninja - target: sanitizer - compiler: gcc + compiler: gcc-14 host_os: ubuntu-24.04 runs-on: ${{ matrix.host_os }} @@ -42,6 +111,7 @@ steps: - uses: actions/checkout@v4 with: + persist-credentials: false path: ./source - name: Read Repository Configuration @@ -50,6 +120,7 @@ - name: Fetch BoringSSL fork for BoGo tests uses: actions/checkout@v4 with: + persist-credentials: false repository: ${{ env.BORINGSSL_REPO }} ref: ${{ env.BORINGSSL_BRANCH }} path: ./boringssl @@ -58,10 +129,11 @@ uses: ./source/.github/actions/setup-build-agent with: target: ${{ matrix.target }} + compiler: ${{ matrix.compiler }} cache-key: ${{ matrix.host_os }}-${{ matrix.compiler }}-x86_64-${{ matrix.target }} - name: Build and Test Botan - run: python3 ./source/src/scripts/ci_build.py --root-dir=${{ github.workspace }}/source --build-dir=${{ github.workspace }}/build --boringssl-dir=${{ github.workspace }}/boringssl --cc='${{ matrix.compiler }}' --make-tool='${{ matrix.make_tool }}' --test-results-dir=junit_results ${{ matrix.target }} + run: python3 ./source/src/scripts/ci_build.py --root-dir=${{ github.workspace }}/source --build-dir=${{ github.workspace }}/build --boringssl-dir=${{ github.workspace }}/boringssl --cc='${{ matrix.compiler }}' --ci-image='${{ matrix.host_os }}' --make-tool='${{ matrix.make_tool }}' --test-results-dir=junit_results ${{ matrix.target }} x-compile: name: "Cross" @@ -70,13 +142,13 @@ matrix: include: - - target: cross-alpha + - target: cross-arm32 compiler: gcc host_os: ubuntu-24.04 - - target: cross-hppa64 + - target: cross-alpha compiler: gcc host_os: ubuntu-24.04 - - target: cross-m68k + - target: cross-hppa64 compiler: gcc host_os: ubuntu-24.04 - target: cross-mips @@ -100,12 +172,9 @@ - target: cross-android-arm64-amalgamation compiler: clang host_os: ubuntu-24.04 - - target: cross-arm64-amalgamation - compiler: gcc - host_os: ubuntu-24.04 - target: emscripten compiler: emcc - host_os: macos-14 + host_os: macos-26 - target: sde compiler: gcc host_os: ubuntu-24.04 @@ -114,6 +183,8 @@ steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Read Repository Configuration uses: ./.github/actions/read-repo-config @@ -122,36 +193,11 @@ uses: ./.github/actions/setup-build-agent with: target: ${{ matrix.target }} + compiler: ${{ matrix.compiler }} cache-key: ${{ matrix.host_os }}-${{ matrix.compiler }}-xcompile-${{ matrix.target }} - name: Build and Test Botan - run: python3 ./src/scripts/ci_build.py --cc='${{ matrix.compiler }}' --make-tool='${{ matrix.make_tool }}' --test-results-dir=junit_results ${{ matrix.target }} - - clang_tidy: - name: "clang-tidy" - - runs-on: ubuntu-24.04 - - steps: - - uses: actions/checkout@v4 - - - name: Read Repository Configuration - uses: ./.github/actions/read-repo-config - - - name: Setup Build Agent - uses: ./.github/actions/setup-build-agent - with: - target: clang-tidy - cache-key: linux-x86_64-clang-tidy - - - name: Install dependencies - run: sudo apt-get -qq install libboost-dev libbz2-dev liblzma-dev libsqlite3-dev - - - name: Configure Build - run: python3 ./configure.py --cc=clang --build-targets=shared,cli,tests,examples,bogo_shim --build-fuzzers=test --with-boost --with-sqlite --with-zlib --with-lzma --with-bzip2 - - - name: Run Clang Tidy - run: python3 ./src/scripts/dev_tools/run_clang_tidy.py --verbose + run: python3 ./src/scripts/ci_build.py --cc='${{ matrix.compiler }}' --ci-image='${{ matrix.host_os }}' --make-tool='${{ matrix.make_tool }}' --test-results-dir=junit_results ${{ matrix.target }} valgrind: name: "valgrind" @@ -167,28 +213,40 @@ matrix: # Run a matrix of compiler and optimization flag combinations to maximize # the signal of secret-dependent execution issues introduced by compilers. - compiler: ["clang", "gcc"] + compiler: ["clang", "gcc-14"] cxxflags: ["-O1", "-O2", "-O3"] target: ["valgrind-ct-full"] + host_os: ["ubuntu-24.04", "ubuntu-24.04-arm"] + + exclude: + - host_os: "ubuntu-24.04-arm" + compiler: clang + - host_os: "ubuntu-24.04-arm" + cxxflags: "-O1" include: - compiler: clang cxxflags: "" # default compilation flags target: "valgrind-full" # memory bug detection + host_os: "ubuntu-24.04" - compiler: clang cxxflags: "-Os" # Clang's -Os generated binary is fast enough to run the full test suite. target: "valgrind-ct-full" - - compiler: gcc + host_os: "ubuntu-24.04" + - compiler: gcc-14 cxxflags: "-Os" # GCC with -Os generates a much slower binary, that won't finish # before timing out on GH Actions, so we run a reduced set of tests. target: "valgrind-ct" + host_os: "ubuntu-24.04" - runs-on: ubuntu-24.04 + runs-on: ${{ matrix.host_os }} steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Read Repository Configuration uses: ./.github/actions/read-repo-config @@ -197,10 +255,29 @@ uses: ./.github/actions/setup-build-agent with: target: ${{ matrix.target }} - cache-key: linux-x86_64-${{ matrix.compiler }}-${{ matrix.target }}-${{ matrix.cxxflags }} + compiler: ${{ matrix.compiler }} + cache-key: ${{ matrix.host_os }}-${{ matrix.compiler }}-${{ matrix.target }}-${{ matrix.cxxflags }} - name: Valgrind Checks - run: python3 ./src/scripts/ci_build.py --make-tool=make --cc=${{ matrix.compiler }} --custom-optimization-flags="${{ matrix.cxxflags }}" ${{ matrix.target }} + run: python3 ./src/scripts/ci_build.py --make-tool=make --cc=${{ matrix.compiler }} --ci-image='${{ matrix.host_os }}' --custom-optimization-flags="${{ matrix.cxxflags }}" ${{ matrix.target }} + + wycheproof: + name: "Wycheproof" + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Read Repository Configuration + uses: ./.github/actions/read-repo-config + - name: Setup Build Agent + uses: ./.github/actions/setup-build-agent + with: + target: wycheproof + compiler: gcc + cache-key: linux-x86_64-wycheproof + - name: Run Wycheproof Tests + run: python3 ./src/scripts/ci_build.py --cc=gcc --make-tool=make wycheproof hybrid_tls_interop: name: "PQ/T TLS 1.3" @@ -209,6 +286,8 @@ steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Read Repository Configuration uses: ./.github/actions/read-repo-config @@ -217,6 +296,7 @@ uses: ./.github/actions/setup-build-agent with: target: hybrid-tls13-interop-test + compiler: gcc cache-key: linux-x86_64-hybrid_tls - name: Hybrid PQ/T TLS 1.3 Online Interop Checks @@ -230,6 +310,8 @@ steps: - name: Fetch Botan Repository uses: actions/checkout@v4 + with: + persist-credentials: false - name: Read Repository Configuration uses: ./.github/actions/read-repo-config @@ -238,14 +320,18 @@ uses: ./.github/actions/setup-build-agent with: target: tlsanvil - cache-key: linux-x86_64-tlsanvil + cache-key: linux-x86_64-tlsanvil-server + + - name: Build Botan + run: | + python3 ./configure.py --compiler-cache=ccache --build-targets=static,cli --without-documentation --with-boost + make -j$(nproc) - - name: Build and Test Botan Server with TLS-Anvil + - name: Test Botan Server with TLS-Anvil run: > - python3 ./src/scripts/ci/ci_tlsanvil_test.py - --botan-dir . + python3 ./src/scripts/tls_anvil/run_tls_anvil_tests.py + --botan-cli ./botan --test-target server - --parallel $(nproc) - uses: actions/upload-artifact@v4 with: @@ -255,4 +341,45 @@ ./logs/ - name: Check TLS-Anvil Test Results - run: python3 ./src/scripts/ci/ci_tlsanvil_check.py --verbose ./TestSuiteResults + run: python3 ./src/scripts/tls_anvil/analyze_tls_anvil_report.py --verbose server ./TestSuiteResults + + tls_anvil_client_test: + name: "TLS-Anvil (client)" + + runs-on: ubuntu-24.04 + + steps: + - name: Fetch Botan Repository + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Read Repository Configuration + uses: ./.github/actions/read-repo-config + + - name: Setup Build Agent + uses: ./.github/actions/setup-build-agent + with: + target: tlsanvil + cache-key: linux-x86_64-tlsanvil-client + + - name: Build Botan + run: | + python3 ./configure.py --compiler-cache=ccache --build-targets=static,cli --without-documentation --with-boost + make -j$(nproc) + + - name: Test Botan Client with TLS-Anvil + run: > + python3 ./src/scripts/tls_anvil/run_tls_anvil_tests.py + --botan-cli ./botan + --test-target client + + - uses: actions/upload-artifact@v4 + with: + name: tls-anvil-client-test-results + path: | + ./TestSuiteResults/ + ./logs/ + + - name: Check TLS-Anvil Test Results + run: python3 ./src/scripts/tls_anvil/analyze_tls_anvil_report.py --verbose client ./TestSuiteResults diff -Nru botan3-3.7.1+dfsg/.gitignore botan3-3.12.0+dfsg/.gitignore --- botan3-3.7.1+dfsg/.gitignore 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/.gitignore 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,7 @@ /Makefile build.ninja .ninja_log +.ninja_deps libbotan*.so.* *.a *.so @@ -27,10 +28,18 @@ \#*\# .\#* +# Benchmark output in top level +/*.json + +# Misc dev scripts in top level +/*.sh + # Editor configuration files (top level) /*.sublime-project /*.sublime-workspace /.editorconfig +/.vscode +/.clang-format # Archive files *.tgz diff -Nru botan3-3.7.1+dfsg/configure.py botan3-3.12.0+dfsg/configure.py --- botan3-3.7.1+dfsg/configure.py 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/configure.py 2026-05-07 01:38:28.000000000 +0000 @@ -3,16 +3,14 @@ """ Configuration program for botan +This script supports Python 3 syntax only. At least CPython 3.10 is recommended. +Latest PyPy3 should also work, but this is only occasionally tested. + (C) 2009-2020 Jack Lloyd (C) 2015,2016,2017 Simon Warta (Kullo GmbH) (C) 2019-2022 René Meusel (neXenio GmbH, Rohde & Schwarz Cybersecurity GmbH) Botan is released under the Simplified BSD License (see license.txt) - -This script is regularly tested with CPython 3.x, and -occasionally tested PyPy 4. - -On Jython target detection does not work (use --os and --cpu). """ import collections @@ -43,8 +41,8 @@ pass -def flatten(l): - return sum(l, []) +def flatten(lst): + return sum(lst, []) def normalize_source_path(source): """ @@ -55,6 +53,21 @@ def normalize_source_paths(sources): return [normalize_source_path(p) for p in sources] +def is_subpath(child_path, parent_path): + """ + Check if child_path is a subpath of parent_path + """ + + child_abs = os.path.abspath(child_path) + parent_abs = os.path.abspath(parent_path) + try: + rel = os.path.relpath(child_abs, parent_abs) + return (not rel.startswith(os.pardir + os.sep) + and rel != os.pardir + and rel != os.curdir) + except ValueError: # This can happen if on different drives under Windows + return False + def parse_version_file(version_path): version_file = open(version_path, encoding='utf8') key_and_val = re.compile(r"([a-z_]+) = ([a-zA-Z0-9:\-\']+)") @@ -132,6 +145,39 @@ return Version.get_data()["release_datestamp"] @staticmethod + def short_version_string(): + return "%d.%d.%d%s" % (Version.major(), Version.minor(), Version.patch(), Version.suffix()) + + @staticmethod + def full_version_string(options): + version = "Botan %s" % (Version.short_version_string()) + + if options.unsafe_fuzzer_mode or options.unsafe_terminate_on_asserts: + version += " UNSAFE " + if options.unsafe_fuzzer_mode: + version += "FUZZER MODE " + if options.unsafe_terminate_on_asserts: + version += "TERMINATE ON ASSERTS " + version += "BUILD" + + version += " (" + version += Version.release_type() + + if Version.datestamp() != 0: + version += ", dated %d" % (Version.datestamp()) + + if Version.vc_rev() != "unknown": + version += ", revision %s" % (Version.vc_rev()) + + if options.distribution_info is not None: + version += ", distribution '%s'" % (options.distribution_info) + + version += ")" + + return version + + + @staticmethod def as_string(): return '%d.%d.%d%s' % (Version.major(), Version.minor(), Version.patch(), Version.suffix()) @@ -400,9 +446,6 @@ target_group.add_option('--compiler-cache', help='specify a compiler cache to use') - target_group.add_option('--with-endian', metavar='ORDER', default=None, - help='override byte order guess') - target_group.add_option('--ct-value-barrier-type', metavar='TYPE', default=None, help=optparse.SUPPRESS_HELP) @@ -413,18 +456,17 @@ add_with_without_pair(target_group, 'compilation-database', True, 'disable compile_commands.json') - isa_extensions = [ - 'SSE2', 'SSSE3', 'SSE4.1', 'SSE4.2', 'AVX2', 'BMI2', 'RDRAND', 'RDSEED', - 'AES-NI', 'SHA-NI', - 'AltiVec', 'NEON', 'ARMv8 Crypto', 'POWER Crypto'] + isa_extensions_that_can_be_disabled = [('NEON', 'arm32')] - for isa_extn_name in isa_extensions: + for (isa_extn_name,arch) in isa_extensions_that_can_be_disabled: isa_extn = isa_extn_name.lower().replace(' ', '') + nm = isa_extn.replace('-', '').replace('.', '').replace(' ', '') + target_group.add_option('--disable-%s' % (isa_extn), help='disable %s intrinsics' % (isa_extn_name), action='append_const', - const=isa_extn.replace('-', '').replace('.', '').replace(' ', ''), + const=(nm,arch), dest='disable_intrinsics') build_group = optparse.OptionGroup(parser, 'Build options') @@ -438,10 +480,12 @@ add_enable_disable_pair(build_group, 'asm', True, 'disable inline assembly') + add_enable_disable_pair(build_group, 'stack-scrubbing', False, 'enable compiler-assisted stack scrubbing') + build_group.add_option('--enable-sanitizers', metavar='SAN', default='', help='enable specific sanitizers') - add_with_without_pair(build_group, 'stack-protector', True, 'disable stack smashing protections') + add_with_without_pair(build_group, 'stack-protector', None, 'disable stack smashing protections') add_with_without_pair(build_group, 'coverage-info', False, 'add coverage info') @@ -497,13 +541,8 @@ choices=link_methods, help='choose how links to include headers are created (%s)' % ', '.join(link_methods)) - build_group.add_option('--with-local-config', - dest='local_config', metavar='FILE', - help='include the contents of FILE into build.h') - build_group.add_option('--distribution-info', metavar='STRING', - help='distribution specific version', - default='unspecified') + help='distribution specific version', default=None) build_group.add_option('--maintainer-mode', dest='maintainer_mode', action='store_true', default=False, @@ -513,16 +552,13 @@ action='store_true', default=False, help="Prohibit compiler warnings") - build_group.add_option('--no-store-vc-rev', action='store_true', default=False, - help=optparse.SUPPRESS_HELP) - build_group.add_option('--no-install-python-module', action='store_true', default=False, help='skip installing Python module') build_group.add_option('--with-python-versions', dest='python_version', metavar='N.M', default='%d.%d' % (sys.version_info[0], sys.version_info[1]), - help='where to install botan2.py (def %default)') + help='where to install botan3.py (def %default)') build_group.add_option('--disable-cc-tests', dest='enable_cc_tests', default=True, action='store_false', @@ -562,6 +598,8 @@ add_with_without_pair(docs_group, 'pdf', False, 'run Sphinx to generate PDF doc') + add_with_without_pair(docs_group, 'texinfo', False, 'run Sphinx to generate texinfo doc') + add_with_without_pair(docs_group, 'rst2man', None, 'run rst2man to generate man page') add_with_without_pair(docs_group, 'doxygen', False, 'run Doxygen') @@ -624,6 +662,9 @@ help='set the install dir for man pages') install_group.add_option('--includedir', metavar='DIR', help='set the include file install dir') + install_group.add_option('--cmakeconfigdir', metavar='DIR', + help='set the CMake config (botan-config.cmake, botan-config-version.cmake) install dir') + add_with_without_pair(install_group, 'include-namespace', default=True, msg="don't add a 'botan-%d/' namespace to the include path" % (Version.major())) info_group = optparse.OptionGroup(parser, 'Informational') @@ -669,9 +710,6 @@ if args != []: raise UserError('Unhandled option(s): ' + ' '.join(args)) - if options.with_endian not in [None, 'little', 'big']: - raise UserError('Bad value to --with-endian "%s"' % (options.with_endian)) - if options.debug_mode: options.no_optimizations = True options.with_debug_info = True @@ -688,7 +726,7 @@ options.with_os_features = parse_multiple_enable(options.with_os_features) options.without_os_features = parse_multiple_enable(options.without_os_features) - options.disable_intrinsics = parse_multiple_enable(options.disable_intrinsics) + options.disable_intrinsics = [] if options.disable_intrinsics is None else options.disable_intrinsics return options @@ -838,10 +876,9 @@ infofile, ['header:internal', 'header:public', 'header:external', 'requires', 'os_features', 'arch', 'isa', 'cc', 'comment', 'warning'], - ['defines', 'libs', 'frameworks', 'module_info'], + ['defines', 'internal_defines', 'libs', 'frameworks', 'module_info'], { 'load_on': 'auto', - 'endian': 'any', }) def check_header_duplicates(header_list_public, header_list_internal): @@ -888,6 +925,8 @@ self.comment = combine_lines(lex.comment) self._defines = lex.defines self._validate_defines_content(self._defines) + self._internal_defines = lex.internal_defines + self._validate_defines_content(self._internal_defines) self.frameworks = convert_lib_list(lex.frameworks) self.libs = convert_lib_list(lex.libs) self.load_on = lex.load_on @@ -895,7 +934,6 @@ self.os_features = lex.os_features self.requires = lex.requires self.warning = combine_lines(lex.warning) - self.endian = lex.endian self._parse_module_info(lex) # Modify members @@ -1016,14 +1054,13 @@ def defines(self): return [(key + ' ' + value) for key, value in self._defines.items()] + def internal_defines(self): + return [(key + ' ' + value) for key, value in self._internal_defines.items()] + def compatible_cpu(self, archinfo, options): arch_name = archinfo.basename cpu_name = options.arch - if self.endian != 'any': - if self.endian != options.with_endian: - return False - for isa in self.isa: if isa.find(':') > 0: (arch, isa) = isa.split(':') @@ -1031,7 +1068,7 @@ if arch != arch_name: continue - if isa in options.disable_intrinsics: + if (isa, arch_name) in options.disable_intrinsics: return False # explicitly disabled if isa not in archinfo.isa_extensions: @@ -1104,7 +1141,23 @@ return supported_isa_flags(ccinfo, arch) and supported_compiler(ccinfo, cc_min_version) - def dependencies(self, osinfo): + def compatible_compiler_flags(self, ccinfo, arch, options): + if ccinfo.basename != 'emcc': + return True + + # Wasm SIMD optimizations are always opt-in. Binaries with unknown instructions cannot be instantiated. + compile_flags = " ".join(ccinfo.cc_compile_flags(options)) + for isa in self.isa: + isa_flags = ccinfo.isa_flags_for(isa, arch.basename) + if not isa_flags: + continue + + if isa_flags not in compile_flags: + return False + + return True + + def dependencies(self, osinfo, archinfo): # base is an implicit dep for all submodules deps = ['base'] if self.parent_module is not None: @@ -1113,8 +1166,11 @@ for req in self.requires: if req.find('?') != -1: (cond, dep) = req.split('?') - if osinfo is None or cond in osinfo.target_features: + if osinfo is None and archinfo is None: deps.append(dep) + else: + if cond == archinfo.basename or cond in osinfo.target_features: + deps.append(dep) else: deps.append(req) @@ -1135,7 +1191,7 @@ return True - missing = [s for s in self.dependencies(None) if s not in modules or is_dependency_on_virtual(self, modules[s])] + missing = [s for s in self.dependencies(None, None) if s not in modules or is_dependency_on_virtual(self, modules[s])] for modname in missing: if modname not in modules: @@ -1178,15 +1234,19 @@ self.prohibited = lex.prohibited def cross_check(self, modules): - def check(tp, lst): + def check(tp, lst, required): + msg = "Module policy %s includes non-existent module %s in <%s>" + for mod in lst: if mod not in modules: - logging.error("Module policy %s includes non-existent module %s in <%s>", - self.infofile, mod, tp) - - check('required', self.required) - check('if_available', self.if_available) - check('prohibited', self.prohibited) + if required: + logging.error(msg, self.infofile, mod, tp) + else: + logging.warning(msg, self.infofile, mod, tp) + + check('required', self.required, True) + check('if_available', self.if_available, False) + check('prohibited', self.prohibited, False) class ArchInfo(InfoObject): @@ -1197,21 +1257,14 @@ ['aliases', 'isa_extensions'], [], { - 'endian': None, 'family': None, - 'wordsize': 32 }) self.aliases = lex.aliases - self.endian = lex.endian self.family = lex.family self.isa_extensions = lex.isa_extensions - self.wordsize = int(lex.wordsize) - - if self.wordsize not in [32, 64]: - logging.error('Unexpected wordsize %d for arch %s', self.wordsize, infofile) - alphanumeric = re.compile('^[a-z0-9]+$') + alphanumeric = re.compile('^[a-z0-9_]+$') for isa in self.isa_extensions: if alphanumeric.match(isa) is None: logging.error('Invalid name for ISA extension "%s"', isa) @@ -1220,7 +1273,7 @@ isas = [] for isa in self.isa_extensions: - if isa not in options.disable_intrinsics: + if (isa, self.basename) not in options.disable_intrinsics: if cc.isa_flags_for(isa, self.basename) is not None: isas.append(isa) @@ -1359,32 +1412,6 @@ return None - def get_isa_specific_flags(self, isas, arch, options): - flags = set() - - def simd32_impl(): - for simd_isa in ['sse2', 'altivec', 'neon']: - if simd_isa in arch.isa_extensions and \ - simd_isa not in options.disable_intrinsics and \ - self.isa_flags_for(simd_isa, arch.basename): - return simd_isa - return None - - for isa in isas: - - if isa == 'simd': - isa = simd32_impl() - - if isa is None: - continue - - flagset = self.isa_flags_for(isa, arch.basename) - if flagset is None: - raise UserError('Compiler %s does not support %s' % (self.basename, isa)) - flags.add(flagset) - - return " ".join(sorted(flags)) - def gen_lib_flags(self, options, variables): """ Return any flags specific to building the library @@ -1392,8 +1419,11 @@ """ def flag_builder(): + # We always emit -fPIC or equivalent so that position independent executables + # can be created that link to the static library + yield self.shared_flags + if options.build_shared_lib: - yield self.shared_flags yield self.visibility_build_flags if 'debug' in self.lib_flags and options.with_debug_info: @@ -1557,8 +1587,7 @@ if not (options.debug_mode or sanitizers_enabled): yield self.cpu_flags_no_debug[options.arch] - for flag in options.extra_cxxflags: - yield flag + yield from options.extra_cxxflags for definition in options.define_build_macro: yield self.add_compile_definition_option + definition @@ -1567,7 +1596,7 @@ def _so_link_search(osname, debug_info): so_link_typ = [osname, 'default'] if debug_info: - so_link_typ = [l + '-debug' for l in so_link_typ] + so_link_typ + so_link_typ = [link + '-debug' for link in so_link_typ] + so_link_typ return so_link_typ def so_link_command_for(self, osname, options): @@ -1707,6 +1736,14 @@ return sorted(feats) + def enabled_features_public(self, options): + public_feat = set(['threads', 'filesystem']) + return sorted(list(set(self.enabled_features(options)) & public_feat)) + + def enabled_features_internal(self, options): + public_feat = set(['threads', 'filesystem']) + return sorted(list(set(self.enabled_features(options)) - public_feat)) + def macros(self, cc): value = [cc.add_compile_definition_option + define for define in self.feature_macros] @@ -1794,18 +1831,26 @@ k = match.group(1) if k.endswith('|upper'): k = k.replace('|upper', '') - v = get_replacement(k).upper() + return get_replacement(k).upper() elif k.endswith('|concat'): k = k.replace('|concat', '') if not match.group(2): raise InternalError("|concat must be of the form '%{val|concat:}'") v = get_replacement(k) if v: - v = f"{v}{match.group(2)}" - else: - v = get_replacement(k) + return f"{v}{match.group(2)}" + else: + return v + elif k.endswith('|as_bool'): + k = k.replace('|as_bool', '') + + if k not in self.vals: + raise KeyError(k) + v = self.vals.get(k) - return v + return str(bool(v)).lower() + else: + return get_replacement(k) def insert_join(match): var = match.group(1) @@ -1818,6 +1863,7 @@ output = "" idx = 0 + # pylint: disable=too-many-nested-blocks while idx < len(lines): cond_match = self.cond_pattern.match(lines[idx]) for_match = self.for_pattern.match(lines[idx]) @@ -1869,12 +1915,13 @@ else: output += for_body.replace('%{i}', v).replace('%{i|upper}', v.upper()) - omitlast_match = self.omitlast_pattern.match(output) - if omitlast_match: - output = omitlast_match.group(1) - if i + 1 < len(var): - output += omitlast_match.group(2) - output += omitlast_match.group(3) + if output.find('%{omitlast') >= 0: + omitlast_match = self.omitlast_pattern.match(output) + if omitlast_match: + output = omitlast_match.group(1) + if i + 1 < len(var): + output += omitlast_match.group(2) + output += omitlast_match.group(3) output += "\n" else: @@ -1936,7 +1983,7 @@ name = name.replace('.cpp', obj_suffix) yield normalize_source_path(os.path.join(obj_dir, name)) -def generate_build_info(build_paths, modules, cc, arch, osinfo, options): +def generate_build_info(build_paths, modules, osinfo, options): # first create a map of src_file->owning module module_that_owns = {} @@ -1945,27 +1992,12 @@ for src in mod.sources(): module_that_owns[src] = mod - def _isa_specific_flags(src): - if os.path.basename(src) == 'test_simd.cpp': - return cc.get_isa_specific_flags(['simd'], arch, options) - - if src in module_that_owns: - module = module_that_owns[src] - isas = module.isas_needed(arch.basename) - if 'simd' in module.dependencies(osinfo): - isas.append('simd') - - return cc.get_isa_specific_flags(isas, arch, options) - - return '' - def _build_info(sources, objects, target_type): output = [] for (obj_file, src) in zip(objects, sources): info = { 'src': src, 'obj': obj_file, - 'isa_flags': _isa_specific_flags(src) } if target_type in ['fuzzer', 'examples']: @@ -1985,8 +2017,6 @@ targets = ['lib', 'cli', 'test', 'fuzzer', 'examples'] - out['isa_build_info'] = [] - fuzzer_bin = [] example_bin = [] @@ -2005,10 +2035,6 @@ objects = list(yield_objectfile_list(src_list, src_dir, osinfo.obj_suffix, options)) build_info = _build_info(src_list, objects, t) - for b in build_info: - if b['isa_flags'] != '': - out['isa_build_info'].append(b) - if t == 'fuzzer': fuzzer_bin = [b['exe'] for b in build_info] elif t == 'examples': @@ -2053,11 +2079,6 @@ return sorted(libs) - def choose_mp_bits(): - mp_bits = arch.wordsize # allow command line override? - logging.debug('Using MP bits %d', mp_bits) - return mp_bits - def configure_command_line(): # Cut absolute path from main executable (e.g. configure.py or python interpreter) # to get the same result when configuring the same thing on different machines @@ -2147,6 +2168,11 @@ def test_exe_extra_ldflags(): if osinfo.matches_name("emscripten"): + # It doesn't make much sense (and it's not even possible) to preload files when FS is not virtualized. + virtualized_fs = '-sNODERAWFS=1' not in cc.ldflags(options) + if not virtualized_fs: + return '' + return '--preload-file=%s@src/tests/data' % source_paths.test_data_dir return '' @@ -2155,8 +2181,10 @@ 'version_major': Version.major(), 'version_minor': Version.minor(), 'version_patch': Version.patch(), - 'version_suffix': Version.suffix(), - 'version_vc_rev': 'unknown' if options.no_store_vc_rev else Version.vc_rev(), + 'version_vc_rev': None if Version.vc_rev() == 'unknown' else Version.vc_rev(), + + 'version_vc_rev_or_unknown': 'unknown' if Version.datestamp() == 0 else Version.vc_rev(), + 'abi_rev': Version.so_rev(), 'version': Version.as_string(), @@ -2164,6 +2192,10 @@ 'version_datestamp': Version.datestamp(), 'distribution_info': options.distribution_info, + 'distribution_info_or_unspecified': options.distribution_info or 'unspecified', + + 'full_version_string': Version.full_version_string(options), + 'short_version_string': Version.short_version_string(), 'macos_so_compat_ver': '%s.%s.0' % (Version.packed(), Version.so_rev()), 'macos_so_current_ver': '%s.%s.%s' % (Version.packed(), Version.so_rev(), Version.patch()), @@ -2197,7 +2229,6 @@ suffix=options.library_suffix), 'command_line': configure_command_line(), - 'local_config': read_textfile(options.local_config), 'program_suffix': program_suffix, @@ -2205,12 +2236,13 @@ 'bindir': absolute_install_dir(options.bindir or osinfo.bin_dir), 'libdir': absolute_install_dir(options.libdir or osinfo.lib_dir), 'mandir': options.mandir or osinfo.man_dir, - 'includedir': options.includedir or osinfo.header_dir, + 'includedir': absolute_install_dir(options.includedir or osinfo.header_dir), 'docdir': options.docdir or osinfo.doc_dir, 'with_documentation': options.with_documentation, 'with_sphinx': options.with_sphinx, 'with_pdf': options.with_pdf, + 'with_texinfo': options.with_texinfo, 'with_rst2man': options.with_rst2man, 'sphinx_config_dir': source_paths.sphinx_config_dir, 'with_doxygen': options.with_doxygen, @@ -2224,6 +2256,10 @@ 'makefile_path': os.path.join(build_paths.build_dir, '..', 'Makefile'), 'ninja_build_path': os.path.join(build_paths.build_dir, '..', 'build.ninja'), + # Use response files for the archive command on windows + # Note: macOS (and perhaps other OSes) do not support this + 'build_static_lib_using_cmdline_args': options.build_static_lib and osinfo.basename != 'windows', + 'build_static_lib_using_response_file': options.build_static_lib and osinfo.basename == 'windows', 'build_static_lib': options.build_static_lib, 'build_shared_lib': options.build_shared_lib, @@ -2249,10 +2285,6 @@ 'arch': options.arch, 'compiler': options.compiler, 'cpu_family': arch.family, - 'endian': options.with_endian, - 'cpu_is_64bit': arch.wordsize == 64, - - 'mp_bits': choose_mp_bits(), 'python_exe': choose_python_exe(), 'python_version': options.python_version, @@ -2264,6 +2296,7 @@ 'make_supports_phony': osinfo.basename != 'windows', 'cxx_supports_gcc_inline_asm': cc.supports_gcc_inline_asm and options.enable_asm, + 'compiler_assisted_stack_scrubbing': options.enable_stack_scrubbing, 'cxx_ct_value_barrier_type': cc.ct_value_barrier_type(options), @@ -2313,6 +2346,7 @@ 'internal_include_flags': build_paths.format_internal_include_flags(cc), 'external_include_flags': build_paths.format_external_include_flags(cc, options.with_external_includedir), 'module_defines': sorted(flatten([m.defines() for m in modules])), + 'module_internal_defines': sorted(flatten([m.internal_defines() for m in modules])), 'build_bogo_shim': bool('bogo_shim' in options.build_targets), 'bogo_shim_src': os.path.join(source_paths.src_dir, 'bogo_shim', 'bogo_shim.cpp'), @@ -2320,7 +2354,8 @@ 'build_ct_selftest': bool('ct_selftest' in options.build_targets), 'ct_selftest_src': os.path.join(source_paths.src_dir, 'ct_selftest', 'ct_selftest.cpp'), - 'os_features': osinfo.enabled_features(options), + 'os_features': osinfo.enabled_features_internal(options), + 'os_features_public': osinfo.enabled_features_public(options), 'os_name': osinfo.basename, 'cpu_features': arch.supported_isa_extensions(cc, options), 'system_cert_bundle': options.system_cert_bundle, @@ -2341,16 +2376,48 @@ 'disabled_mod_list': sorted([m.basename for m in disabled_modules]), } - variables['installed_include_dir'] = os.path.join( - variables['prefix'], + if not os.path.isabs(variables['prefix']): + raise UserError("The installation root must be an absolute path") + + if not is_subpath(variables['libdir'], variables['prefix']): + raise UserError("The libdir must be a subdirectory of the prefix") + + if not is_subpath(variables['includedir'], variables['prefix']): + raise UserError("The includedir must be a subdirectory of the prefix") + + variables['namespaced_includedir'] = os.path.join( variables['includedir'], - 'botan-%d' % (Version.major()), 'botan') + ('botan-%d' % Version.major()) if options.with_include_namespace else '') + variables['installed_include_dir'] = os.path.join( + variables['namespaced_includedir'], + 'botan') + + # A long time ago some packages required a bindir that was outside the installation + # prefix. In the CMake config we need the bindir to find DLLs on Windows. If the + # bindir is configured to be outside the prefix, CMake will fall back to a hard-coded + # path instead of a relative path for relocatability. + if is_subpath(variables['bindir'], variables['prefix']): + variables['bindir_rel'] = normalize_source_path(os.path.relpath(variables['bindir'], variables['prefix'])) + + variables['libdir_rel'] = normalize_source_path(os.path.relpath(variables['libdir'], variables['prefix'])) + variables['includedir_rel'] = normalize_source_path(os.path.relpath(variables['includedir'], variables['prefix'])) + variables['namespaced_includedir_rel'] = normalize_source_path(os.path.relpath(variables['namespaced_includedir'], variables['prefix'])) + + # On MSVC, the "ABI flags" should be passed to the compiler only, on other platforms, the + # ABI flags are passed to both the compiler and the linker and the compiler flags are also + # passed to the linker(?) + # + # TODO: Extend the build-data/cc/xxx.txt format to allow specifying different CFLAGS for + # different configurations, then /MD etc could be specified there rather than hijacking the ABI + # flags for it and having to special-case their exclusion from the linker command line. - if cc.basename == 'msvc' and variables['cxx_abi_flags'] != '': - # MSVC linker doesn't support/need the ABI options, - # just transfer them over to just the compiler invocations + if cc.basename in ('msvc', 'clangcl'): + # Move the "ABI flags" (/MD etc) into the compiler flags to exclude it from linker invocations variables['cc_compile_flags'] = '%s %s' % (variables['cxx_abi_flags'], variables['cc_compile_flags']) variables['cxx_abi_flags'] = '' + else: + # Append the compiler flags to the linker flags + variables['ldflags'] = '%s %s' % (variables['ldflags'], variables['cc_compile_flags']) variables['lib_flags'] = cc.gen_lib_flags(options, variables) @@ -2359,6 +2426,13 @@ if options.with_cmake_config: variables['botan_cmake_config'] = os.path.join(build_paths.build_dir, 'cmake', 'botan-config.cmake') variables['botan_cmake_version_config'] = os.path.join(build_paths.build_dir, 'cmake', 'botan-config-version.cmake') + cmake_install_dir = absolute_install_dir(options.cmakeconfigdir) if options.cmakeconfigdir else \ + os.path.join(variables['libdir'], 'cmake', 'Botan-%s' % variables['version']) + if not is_subpath(cmake_install_dir, variables['prefix']): + logging.error("The CMake module must be installed into a subdirectory of the install prefix.") + variables['cmake_install_dir'] = normalize_source_path(cmake_install_dir) + cmake_rel = os.path.relpath(cmake_install_dir, variables['prefix']) + variables['cmake_relpath_components'] = [p for p in cmake_rel.replace('\\', '/').split('/') if p and p != '.'] # The name is always set because Windows build needs it variables['static_lib_name'] = '%s%s.%s' % (variables['lib_prefix'], variables['libname'], @@ -2422,6 +2496,8 @@ self._not_using_because = collections.defaultdict(set) ModulesChooser._validate_dependencies_exist(self._modules) + self._options.enabled_modules = ModulesChooser._expand_wildcards_in_user_selection(self._modules, self._options.enabled_modules) + self._options.disabled_modules = ModulesChooser._expand_wildcards_in_user_selection(self._modules, self._options.disabled_modules) ModulesChooser._validate_user_selection( self._modules, self._options.enabled_modules, self._options.disabled_modules) @@ -2435,6 +2511,9 @@ elif not module.compatible_compiler(self._ccinfo, self._cc_min_version, self._archinfo.basename): self._not_using_because['incompatible compiler'].add(modname) return False + elif not module.compatible_compiler_flags(self._ccinfo, self._archinfo, self._options): + self._not_using_because['incompatible compiler flags'].add(modname) + return False elif module.is_deprecated() and not self._options.enable_deprecated_features and modname not in self._options.enabled_modules: self._not_using_because['deprecated'].add(modname) return False @@ -2500,6 +2579,21 @@ module.dependencies_exist(modules) @staticmethod + def _expand_wildcards_in_user_selection(modules, user_selected_modules): + valid_module_name_with_wildcard = re.compile(r'^[a-z0-9_*]+$') + public_modules = [modname for modname, modinfo in modules.items() if modinfo.is_public()] + def expand(user_selected_module): + if not valid_module_name_with_wildcard.match(user_selected_module): + logging.error("Invalid module name with wildcard: %s", user_selected_module) + return [] + regex_from_wildcards = re.compile("^%s$" % user_selected_module.replace('*', '[a-z0-9_]+')) + matching_modules = [mod for mod in public_modules if regex_from_wildcards.match(mod)] + if not matching_modules: + logging.warning("Wildcard '%s' did not match any modules", user_selected_module) + return matching_modules + return flatten([expand(mod) if '*' in mod else [mod] for mod in user_selected_modules]) + + @staticmethod def _validate_user_selection(modules, enabled_modules, disabled_modules): for modname in enabled_modules: if modname not in modules: @@ -2584,7 +2678,7 @@ def _modules_dependency_table(self): out = {} for modname in self._modules: - out[modname] = self._modules[modname].dependencies(self._osinfo) + out[modname] = self._modules[modname].dependencies(self._osinfo, self._archinfo) return out def _resolve_dependencies_for_all_modules(self): @@ -2819,8 +2913,7 @@ for line in self.file_contents[name]: header = AmalgamationHelper.is_botan_include(line) if header: - for c in self.header_contents(header): - yield c + yield from self.header_contents(header) else: std_header = AmalgamationHelper.is_unconditional_std_include(line) @@ -3081,15 +3174,23 @@ return os_name_variant # not found options.os = find_canonical_os_name(options.os) - def deduce_compiler_type_from_cc_bin(cc_bin): + def deduce_compiler_type_from_cc_bin(options): + cc_bin = options.compiler_binary if cc_bin.find('clang') != -1 or cc_bin in ['emcc', 'em++']: return 'clang' - if cc_bin.find('-g++') != -1 or cc_bin.find('g++') != -1: + if cc_bin.find('g++') != -1: return 'gcc' + + vers = run_compiler(options, None, '', ['--version']) + if vers.find('clang') != -1: + return 'clang' + if vers.find('Free Software Foundation') != -1: + return 'gcc' + return None if options.compiler is None and options.compiler_binary is not None: - options.compiler = deduce_compiler_type_from_cc_bin(options.compiler_binary) + options.compiler = deduce_compiler_type_from_cc_bin(options) if options.compiler is None: logging.error("Could not figure out what compiler type '%s' is, use --cc to set", @@ -3113,10 +3214,6 @@ options.cpu = cpu logging.info('Guessing target processor is a %s (use --cpu to set)', options.arch) - # OpenBSD uses an old binutils that does not support AVX2 - if options.os == 'openbsd': - del info_cc['gcc'].isa_flags['avx2'] - if options.with_documentation is True: if options.with_sphinx is None and have_program('sphinx-build'): logging.info('Found sphinx-build (use --without-sphinx to disable)') @@ -3132,6 +3229,7 @@ default_paths = [ '/etc/ssl/certs/ca-certificates.crt', # Ubuntu, Debian, Arch, Gentoo '/etc/pki/tls/certs/ca-bundle.crt', # RHEL + '/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem', # Fedora '/etc/ssl/ca-bundle.pem', # SuSE '/etc/ssl/cert.pem', # OpenBSD, FreeBSD, Alpine '/etc/certs/ca-certificates.crt', # Solaris @@ -3301,20 +3399,25 @@ raise UserError('Using --with-sphinx plus --without-documentation makes no sense') if options.with_pdf: raise UserError('Using --with-pdf plus --without-documentation makes no sense') + if options.with_texinfo: + raise UserError('Using --with-texinfo --without-documentation makes no sense') if options.with_pdf and not options.with_sphinx: raise UserError('Option --with-pdf requires --with-sphinx') + if options.with_texinfo and not options.with_sphinx: + raise UserError('Option --with-texinfo requires --with-sphinx') + if options.ct_value_barrier_type: if options.ct_value_barrier_type not in ['asm', 'volatile', 'none']: raise UserError('Unknown setting "%s" for --ct-value-barrier-type' % (options.ct_value_barrier_type)) # Warnings - if options.os == 'windows' and options.compiler != 'msvc': + if options.os == 'windows' and options.compiler not in ('msvc', 'clangcl'): logging.warning('The windows target is oriented towards MSVC; maybe you want --os=cygwin or --os=mingw') if options.msvc_runtime: - if options.compiler != 'msvc': + if options.compiler not in ('msvc', 'clangcl'): raise UserError("Makes no sense to specify MSVC runtime for %s" % (options.compiler)) if options.msvc_runtime not in ['MT', 'MD', 'MTd', 'MDd']: @@ -3330,7 +3433,7 @@ cc_output = run_compiler(options, ccinfo, default_return, ccinfo.preproc_flags.split(' ') + extra_flags + [source_file]) def cleanup_output(output): - return ('\n'.join([l for l in output.splitlines() if l.startswith('#') is False])).strip() + return ('\n'.join([line for line in output.splitlines() if not line.startswith('#')])).strip() return cleanup_output(cc_output) @@ -3339,6 +3442,7 @@ 'msvc': r'^ *MSVC ([0-9]{2})([0-9]{2})$', 'gcc': r'^ *GCC ([0-9]+) ([0-9]+)$', 'clang': r'^ *CLANG ([0-9]+) ([0-9]+)$', + 'clangcl': r'^ *CLANG ([0-9]+) ([0-9]+)$', 'xcode': r'^ *XCODE ([0-9]+) ([0-9]+)$', 'xlc': r'^ *XLC ([0-9]+) ([0-9]+)$', 'emcc': r'^ *EMCC ([0-9]+) ([0-9]+)$', @@ -3406,7 +3510,7 @@ logging.info('Auto-detected compiler arch %s', cc_output) return cc_output -def do_io_for_build(cc, arch, osinfo, using_mods, info_modules, build_paths, source_paths, template_vars, options): +def do_io_for_build(osinfo, using_mods, info_modules, build_paths, source_paths, template_vars, options): try: robust_rmtree(build_paths.build_dir) except OSError as ex: @@ -3439,6 +3543,8 @@ return os.path.join(build_paths.doc_module_info, p) write_template(in_build_dir('build.h'), in_build_data('buildh.in')) + write_template(in_build_dir('target_info.h'), in_build_data('target_info.h.in')) + write_template(in_build_dir('version_info.h'), in_build_data('version_info.h.in')) write_template(in_build_dir('botan.doxy'), in_build_data('botan.doxy.in')) if options.with_cmake_config: @@ -3484,7 +3590,7 @@ if options.build_shared_lib: logging.warning('Unless you are building a DLL or .so from the amalgamation, use --disable-shared as well') - template_vars.update(generate_build_info(build_paths, using_mods, cc, arch, osinfo, options)) + template_vars.update(generate_build_info(build_paths, using_mods, osinfo, options)) with open(os.path.join(build_paths.build_dir, 'build_config.json'), 'w', encoding='utf8') as f: json.dump(template_vars, f, sort_keys=True, indent=2) @@ -3640,18 +3746,16 @@ set_defaults_for_unset_options(options, info_arch, info_cc, info_os) canonicalize_options(options, info_os, info_arch) + validate_options(options, info_os, info_cc, info_module_policies) cc = info_cc[options.compiler] - arch = info_arch[options.arch] - osinfo = info_os[options.os] - module_policy = info_module_policies[options.module_policy] if options.module_policy else None if options.enable_cc_tests: cc_min_version = options.cc_min_version or calculate_cc_min_version(options, cc, source_paths) - cc_arch = check_compiler_arch(options, cc, info_arch, source_paths) - if options.arch != 'generic': + if options.arch not in ['generic', 'llvm']: + cc_arch = check_compiler_arch(options, cc, info_arch, source_paths) if cc_arch is not None and cc_arch != options.arch: logging.error("Configured target is %s but compiler probe indicates %s", options.arch, cc_arch) else: @@ -3660,20 +3764,12 @@ logging.info('Target is %s:%s-%s-%s', options.compiler, cc_min_version, options.os, options.arch) - def choose_endian(arch_info, options): - if options.with_endian is not None: - return options.with_endian - - if options.cpu.endswith('eb') or options.cpu.endswith('be'): - return 'big' - if options.cpu.endswith('el') or options.cpu.endswith('le'): - return 'little' - - if arch_info.endian: - logging.info('Assuming target %s is %s endian', arch_info.basename, arch_info.endian) - return arch_info.endian + if options.enable_stack_scrubbing and (options.compiler not in ['gcc'] or float(cc_min_version) < 14): + logging.warning('Your compiler does not support stack scrubbing. Only GCC 14 and newer support this at the moment.') - options.with_endian = choose_endian(arch, options) + arch = info_arch[options.arch] + osinfo = info_os[options.os] + module_policy = info_module_policies[options.module_policy] if options.module_policy else None chooser = ModulesChooser(info_modules, module_policy, arch, osinfo, cc, cc_min_version, options) loaded_module_names = chooser.choose() @@ -3682,11 +3778,13 @@ build_paths = BuildPaths(source_paths, options, using_mods) build_paths.public_headers.append(os.path.join(build_paths.build_dir, 'build.h')) + for internal_headers in ['target_info.h', 'version_info.h']: + build_paths.internal_headers.append(os.path.join(build_paths.build_dir, internal_headers)) template_vars = create_template_vars(source_paths, build_paths, options, using_mods, not_using_mods, cc, arch, osinfo) # Now we start writing to disk - do_io_for_build(cc, arch, osinfo, using_mods, info_modules, build_paths, source_paths, template_vars, options) + do_io_for_build(osinfo, using_mods, info_modules, build_paths, source_paths, template_vars, options) return 0 @@ -3696,7 +3794,7 @@ except UserError as e: logging.debug(traceback.format_exc()) logging.error(e) - except Exception as e: # pylint: disable=broad-except + except Exception: # pylint: disable=broad-except # error() will stop script, so wrap all information into one call logging.error("""%s An internal error occurred. diff -Nru botan3-3.7.1+dfsg/debian/changelog botan3-3.12.0+dfsg/debian/changelog --- botan3-3.7.1+dfsg/debian/changelog 2025-03-22 15:53:54.000000000 +0000 +++ botan3-3.12.0+dfsg/debian/changelog 2026-07-24 21:25:37.000000000 +0000 @@ -1,3 +1,91 @@ +botan3 (3.12.0+dfsg-2~deb13u1) trixie-security; urgency=high + + * Upload to trixie-security (CVE-2026-44378), there is no reverse + dependency of the library in trixie so moving with an ABI bump. + + -- Aron Xu Sat, 25 Jul 2026 05:25:37 +0800 + +botan3 (3.12.0+dfsg-2) unstable; urgency=medium + + * Upload to Sid. + + -- Laszlo Boszormenyi (GCS) Sat, 23 May 2026 07:58:39 +0200 + +botan3 (3.12.0+dfsg-1) experimental; urgency=medium + + * New upstream release: + - fixes CVE-2026-44378: CPU based denial of service when decoding BER + encoded data. + * Library transition from libbotan-3-11 to libbotan-3-12 . + + -- Laszlo Boszormenyi (GCS) Sat, 09 May 2026 08:45:18 +0200 + +botan3 (3.11.1+dfsg-2) unstable; urgency=medium + + * Remove obsolete readdir_hurd.patch (closes: #1132623). + * Upload to Sid. + + -- Laszlo Boszormenyi (GCS) Thu, 09 Apr 2026 07:04:12 +0200 + +botan3 (3.11.1+dfsg-1) experimental; urgency=medium + + * New upstream release: + - fixes CVE-2026-35580: resolve certificate verification bypass, + - fixes CVE-2026-35582: resolve TLS 1.3 client authentication bypass. + + -- Laszlo Boszormenyi (GCS) Fri, 03 Apr 2026 11:56:38 +0200 + +botan3 (3.11.0+dfsg-1) experimental; urgency=medium + + * New upstream release. + * Library transition from libbotan-3-10 to libbotan-3-11 . + + -- Laszlo Boszormenyi (GCS) Sun, 22 Mar 2026 08:51:26 +0100 + +botan3 (3.10.0+dfsg-2) unstable; urgency=medium + + * Upload to Sid (closes: #1114987). + + -- Laszlo Boszormenyi (GCS) Sun, 30 Nov 2025 22:35:28 +0100 + +botan3 (3.10.0+dfsg-1) experimental; urgency=medium + + * New upstream release. + * Library transition from libbotan-3-9 to libbotan-3-10 . + * Remove now redundant Rules-Requires-Root value. + * Update watch file. + + -- Laszlo Boszormenyi (GCS) Fri, 07 Nov 2025 17:22:38 +0100 + +botan3 (3.9.0+dfsg-2.1) experimental; urgency=medium + + * Non-maintainer upload. + * Let libbotan-3-dev depend on the dev packages that are in its pkgconf. + (closes: #1114813) + + -- Bastian Germann Thu, 11 Sep 2025 17:57:35 +0200 + +botan3 (3.9.0+dfsg-2) experimental; urgency=medium + + * Do not try to disable NEON on armel and armhf, it's not needed anymore. + + -- Laszlo Boszormenyi (GCS) Wed, 20 Aug 2025 16:57:51 +0200 + +botan3 (3.9.0+dfsg-1) experimental; urgency=medium + + * New upstream release. + * Library transition from libbotan-3-8 to libbotan-3-9 . + * Update Standards-Version to 4.7.2 . + + -- Laszlo Boszormenyi (GCS) Sat, 16 Aug 2025 15:01:29 +0200 + +botan3 (3.8.1+dfsg-1) experimental; urgency=medium + + * New upstream release. + * Library transition from libbotan-3-7 to libbotan-3-8 . + + -- Laszlo Boszormenyi (GCS) Wed, 14 May 2025 18:23:23 +0200 + botan3 (3.7.1+dfsg-2) unstable; urgency=medium * Build depend on ca-certificates. diff -Nru botan3-3.7.1+dfsg/debian/control botan3-3.12.0+dfsg/debian/control --- botan3-3.7.1+dfsg/debian/control 2025-03-22 15:53:54.000000000 +0000 +++ botan3-3.12.0+dfsg/debian/control 2026-05-09 06:45:18.000000000 +0000 @@ -13,11 +13,9 @@ zlib1g-dev, python3:any, python3-docutils, - ca-certificates, Build-Depends-Indep: python3-sphinx, -Standards-Version: 4.7.0 -Rules-Requires-Root: no +Standards-Version: 4.7.2 Homepage: https://botan.randombit.net/ Package: botan @@ -34,7 +32,7 @@ . This package contains the 3.x version of Botan. -Package: libbotan-3-7 +Package: libbotan-3-12 Section: libs Architecture: any Multi-Arch: same @@ -50,7 +48,12 @@ Package: libbotan-3-dev Section: libdevel Architecture: any -Depends: ${misc:Depends}, libbotan-3-7 (= ${binary:Version}) +Depends: ${misc:Depends}, libbotan-3-12 (= ${binary:Version}), + libbz2-dev, + liblzma-dev, + libsqlite3-dev, + libtspi-dev, + zlib1g-dev, Description: multiplatform crypto library (3.x version) Botan is a C++ library which provides support for many common cryptographic operations, including encryption, authentication, and X.509v3 certificates and @@ -75,7 +78,7 @@ Package: python3-botan Section: python Architecture: any -Depends: ${misc:Depends}, ${python3:Depends}, libbotan-3-7 (= ${binary:Version}) +Depends: ${misc:Depends}, ${python3:Depends}, libbotan-3-12 (= ${binary:Version}) Breaks: python3-botan (<< 3-1~) Replaces: python3-botan (<< 3-1~) Description: multiplatform crypto library (3.x version), Python3 module diff -Nru botan3-3.7.1+dfsg/debian/copyright botan3-3.12.0+dfsg/debian/copyright --- botan3-3.7.1+dfsg/debian/copyright 2024-07-14 09:24:20.000000000 +0000 +++ botan3-3.12.0+dfsg/debian/copyright 2026-03-22 07:51:26.000000000 +0000 @@ -5,8 +5,8 @@ Files-Excluded: src/lib/prov/pkcs11/* Files: * -Copyright: 1999-2023 The Botan Authors, - 1999-2023 Jack Lloyd +Copyright: 1999-2025 The Botan Authors, + 1999-2025 Jack Lloyd License: BSD-2-clause Files: configure.py @@ -14,6 +14,10 @@ 2015,2016,2017 Simon Warta (Kullo GmbH) License: BSD-2-clause +Files: src/build-data/botan-config.cmake.in src/build-data/botan-config-version.cmake.in +Copyright: 2023- The Botan Authors +License: MIT + Files: src/cli/* Copyright: 2015,2017 Simon Warta (Kullo GmbH), 2018 Ribose Inc, @@ -172,3 +176,22 @@ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +License: MIT + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + ( copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + . + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + . + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. diff -Nru botan3-3.7.1+dfsg/debian/libbotan-3-12.install botan3-3.12.0+dfsg/debian/libbotan-3-12.install --- botan3-3.7.1+dfsg/debian/libbotan-3-12.install 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/debian/libbotan-3-12.install 2024-02-23 21:36:42.000000000 +0000 @@ -0,0 +1 @@ +usr/lib/${DEB_HOST_MULTIARCH}/libbotan-3.so.* diff -Nru botan3-3.7.1+dfsg/debian/libbotan-3-7.install botan3-3.12.0+dfsg/debian/libbotan-3-7.install --- botan3-3.7.1+dfsg/debian/libbotan-3-7.install 2024-02-23 21:36:42.000000000 +0000 +++ botan3-3.12.0+dfsg/debian/libbotan-3-7.install 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -usr/lib/${DEB_HOST_MULTIARCH}/libbotan-3.so.* diff -Nru botan3-3.7.1+dfsg/debian/patches/readdir_hurd.patch botan3-3.12.0+dfsg/debian/patches/readdir_hurd.patch --- botan3-3.7.1+dfsg/debian/patches/readdir_hurd.patch 2019-10-07 15:13:12.000000000 +0000 +++ botan3-3.12.0+dfsg/debian/patches/readdir_hurd.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,19 +0,0 @@ -Description: Hurd FTBFS fix - Add readdir possibility to Hurd architecture. -Origin: upstream -Author: Jack Lloyd -Forwarded: not-needed -Last-Update: 2019-10-07 - ---- - ---- botan-2.12.0.orig/src/build-data/os/hurd.txt -+++ botan-2.12.0/src/build-data/os/hurd.txt -@@ -11,6 +11,7 @@ sockets - threads - thread_local - filesystem -+readdir - - - diff -Nru botan3-3.7.1+dfsg/debian/patches/series botan3-3.12.0+dfsg/debian/patches/series --- botan3-3.7.1+dfsg/debian/patches/series 2023-07-08 18:09:33.000000000 +0000 +++ botan3-3.12.0+dfsg/debian/patches/series 1970-01-01 00:00:00.000000000 +0000 @@ -1,2 +0,0 @@ -readdir_hurd.patch -#use_python3.patch diff -Nru botan3-3.7.1+dfsg/debian/patches/use_python3.patch botan3-3.12.0+dfsg/debian/patches/use_python3.patch --- botan3-3.7.1+dfsg/debian/patches/use_python3.patch 2022-01-23 19:17:06.000000000 +0000 +++ botan3-3.12.0+dfsg/debian/patches/use_python3.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,185 +0,0 @@ -Description: use Python 3 everywhere - Execute python3 binary instead of simple python which is the 2.x version. -Author: Laszlo Boszormenyi (GCS) -Bug-Debian: https://bugs.debian.org/936230 -Forwarded: no -Last-Update: 2020-01-26 - ---- - ---- botan-2.13.0.orig/configure.py -+++ botan-2.13.0/configure.py -@@ -1,4 +1,4 @@ --#!/usr/bin/env python -+#!/usr/bin/env python3 - - """ - Configuration program for botan ---- botan-2.13.0.orig/src/python/botan2.py -+++ botan-2.13.0/src/python/botan2.py -@@ -1,4 +1,4 @@ --#!/usr/bin/python -+#!/usr/bin/python3 - - """ - Python wrapper of the botan crypto library ---- botan-2.13.0.orig/src/scripts/bench.py -+++ botan-2.13.0/src/scripts/bench.py -@@ -1,4 +1,4 @@ --#!/usr/bin/python -+#!/usr/bin/python3 - - """ - Compare Botan with OpenSSL using their respective benchmark utils ---- botan-2.13.0.orig/src/scripts/build_docs.py -+++ botan-2.13.0/src/scripts/build_docs.py -@@ -1,4 +1,4 @@ --#!/usr/bin/env python -+#!/usr/bin/env python3 - - """ - Botan doc generation script ---- botan-2.13.0.orig/src/scripts/ci_build.py -+++ botan-2.13.0/src/scripts/ci_build.py -@@ -1,4 +1,4 @@ --#!/usr/bin/env python -+#!/usr/bin/env python3 - - """ - CI build script ---- botan-2.13.0.orig/src/scripts/cleanup.py -+++ botan-2.13.0/src/scripts/cleanup.py -@@ -1,4 +1,4 @@ --#!/usr/bin/env python -+#!/usr/bin/env python3 - - """ - Implements the "make clean" target ---- botan-2.13.0.orig/src/scripts/create_corpus_zip.py -+++ botan-2.13.0/src/scripts/create_corpus_zip.py -@@ -1,4 +1,4 @@ --#!/usr/bin/python -+#!/usr/bin/python3 - - # These is used to create fuzzer corpus zip files - ---- botan-2.13.0.orig/src/scripts/dist.py -+++ botan-2.13.0/src/scripts/dist.py -@@ -1,4 +1,4 @@ --#!/usr/bin/env python -+#!/usr/bin/env python3 - - """ - Release script for botan (https://botan.randombit.net/) ---- botan-2.13.0.orig/src/scripts/ffi_decls.py -+++ botan-2.13.0/src/scripts/ffi_decls.py -@@ -1,4 +1,4 @@ --#!/usr/bin/python -+#!/usr/bin/python3 - - """ - Automatically generate declarations for the FFI layer ---- botan-2.13.0.orig/src/scripts/install.py -+++ botan-2.13.0/src/scripts/install.py -@@ -1,4 +1,4 @@ --#!/usr/bin/env python -+#!/usr/bin/env python3 - - """ - Botan install script ---- botan-2.13.0.orig/src/scripts/macro_checks.py -+++ botan-2.13.0/src/scripts/macro_checks.py -@@ -1,4 +1,4 @@ --#!/usr/bin/python -+#!/usr/bin/python3 - - # (C) 2018 Jack Lloyd - # Botan is released under the Simplified BSD License (see license.txt) ---- botan-2.13.0.orig/src/scripts/oids.py -+++ botan-2.13.0/src/scripts/oids.py -@@ -1,4 +1,4 @@ --#!/usr/bin/python -+#!/usr/bin/python3 - - """ - (C) 2016 Jack Lloyd ---- botan-2.13.0.orig/src/scripts/run_tls_attacker.py -+++ botan-2.13.0/src/scripts/run_tls_attacker.py -@@ -1,4 +1,4 @@ --#!/usr/bin/python -+#!/usr/bin/python3 - - import os - import sys ---- botan-2.13.0.orig/src/scripts/run_tls_fuzzer.py -+++ botan-2.13.0/src/scripts/run_tls_fuzzer.py -@@ -1,4 +1,4 @@ --#!/usr/bin/python -+#!/usr/bin/python3 - - import argparse - import subprocess ---- botan-2.13.0.orig/src/scripts/show_dependencies.py -+++ botan-2.13.0/src/scripts/show_dependencies.py -@@ -1,4 +1,4 @@ --#!/usr/bin/env python -+#!/usr/bin/env python3 - - """ - Show Botan module dependencies as a list or graph. ---- botan-2.13.0.orig/src/scripts/test_all_configs.py -+++ botan-2.13.0/src/scripts/test_all_configs.py -@@ -1,4 +1,4 @@ --#!/usr/bin/python -+#!/usr/bin/python3 - - """ - This configures and builds with many different sub-configurations ---- botan-2.13.0.orig/src/scripts/test_cli_crypt.py -+++ botan-2.13.0/src/scripts/test_cli_crypt.py -@@ -1,4 +1,4 @@ --#!/usr/bin/env python -+#!/usr/bin/env python3 - - import binascii - import argparse ---- botan-2.13.0.orig/src/scripts/test_fuzzers.py -+++ botan-2.13.0/src/scripts/test_fuzzers.py -@@ -1,4 +1,4 @@ --#!/usr/bin/python -+#!/usr/bin/python3 - - # (C) 2017,2018 Jack Lloyd - ---- botan-2.13.0.orig/src/scripts/test_python.py -+++ botan-2.13.0/src/scripts/test_python.py -@@ -1,4 +1,4 @@ --#!/usr/bin/env python -+#!/usr/bin/env python3 - - """ - (C) 2015,2017,2018,2019 Jack Lloyd ---- botan-2.13.0.orig/src/scripts/tls_scanner/tls_scanner.py -+++ botan-2.13.0/src/scripts/tls_scanner/tls_scanner.py -@@ -1,4 +1,4 @@ --#!/usr/bin/python2 -+#!/usr/bin/python3 - - import sys - import time ---- botan-2.13.0.orig/src/scripts/tls_suite_info.py -+++ botan-2.13.0/src/scripts/tls_suite_info.py -@@ -1,4 +1,4 @@ --#!/usr/bin/env python2 -+#!/usr/bin/env python3 - - """ - Used to generate lib/tls/tls_suite_info.cpp from IANA params ---- botan-2.13.0.orig/src/scripts/website.py -+++ botan-2.13.0/src/scripts/website.py -@@ -1,4 +1,4 @@ --#!/usr/bin/python -+#!/usr/bin/python3 - - """ - Generate the Botan website diff -Nru botan3-3.7.1+dfsg/debian/rules botan3-3.12.0+dfsg/debian/rules --- botan3-3.7.1+dfsg/debian/rules 2024-02-23 21:36:42.000000000 +0000 +++ botan3-3.12.0+dfsg/debian/rules 2025-08-20 14:57:48.000000000 +0000 @@ -39,7 +39,6 @@ $(CROSS_FLAGS) \ --prefix=/usr/ \ --libdir=/usr/lib/$(DEB_HOST_MULTIARCH) \ - $(if $(filter $(DEB_HOST_ARCH), armel armhf),--disable-neon) \ --with-rst2man \ --with-bzip2 \ --with-lzma \ diff -Nru botan3-3.7.1+dfsg/debian/watch botan3-3.12.0+dfsg/debian/watch --- botan3-3.7.1+dfsg/debian/watch 2023-11-10 19:04:33.000000000 +0000 +++ botan3-3.12.0+dfsg/debian/watch 2025-11-07 16:22:38.000000000 +0000 @@ -1,13 +1,7 @@ -version=4 -# GitHub -opts=uversionmangle=s/(\d)[_\.\-\+]?((rc|pre|dev|beta|alpha|b|a)[\-\.]?\d*)$/$1~$2/i,\ -dversionmangle=s/\+(debian|dfsg|ds|deb)(\.?\d+)?$//i,\ -pgpsigurlmangle=s/$/.asc/ \ -https://github.com/randombit/botan/tags \ -(?:|.*/)v?(3.\d\S*)@ARCHIVE_EXT@ +Version: 5 -# Upstream -opts=dversionmangle=s/\+(debian|dfsg|ds|deb)(\.?\d+)?$//,\ -uversionmangle=s/-?(beta)-?/~$1/;s/-?(alpha)-?/~$1/;s/-?(rc)-?/~rc/;s/\.?(RC)-?/~rc/,\ -pgpsigurlmangle=s/$/.asc/ \ -https://botan.randombit.net/releases/ Botan-(3.\d\S+)@ARCHIVE_EXT@ +Template: Github +Dversionmangle: s/\+(debian|dfsg|ds|deb)(\.?\d+)?$//i +Owner: randombit +Project: botan +Pgpsigurlmangle: s/$/.asc/ diff -Nru botan3-3.7.1+dfsg/doc/api_ref/bigint.rst botan3-3.12.0+dfsg/doc/api_ref/bigint.rst --- botan3-3.7.1+dfsg/doc/api_ref/bigint.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/api_ref/bigint.rst 2026-05-07 01:38:28.000000000 +0000 @@ -1,35 +1,74 @@ BigInt ======================================== -``BigInt`` is Botan's implementation of a multiple-precision integer. Thanks to -C++'s operator overloading features, using ``BigInt`` is often quite similar to -using a native integer type. The number of functions related to ``BigInt`` is -quite large, and not all of them are documented here. You can find the complete -declarations in ``botan/bigint.h`` and ``botan/numthry.h``. +``BigInt``, in ``bigint.h``, is an implementation of a signed magnitude +multiple-precision integer, which is used to implement certain older public key +algorithms such as RSA. It also appears in other contexts, for example X.509 +certificate serial numbers are technically integer values and can be quite +large, and so are represented using a ``BigInt``. + +A ``BigInt`` is a sequence of smaller integers of type ``word``; this type is +defined to be either ``uint32_t`` or ``uint64_t``, depending on the word size of +the processor. + +.. warning:: + + While it is possible to use the APIs provided by ``BigInt`` as a general + calculation facility, it is **extremely inadvisable** that you attempt to + implement a cryptographic scheme of any kind directly using ``BigInt``. + Botan internally has many facilities for fast and side channel safe + arithmetic which are not exposed to callers. + + In general, as a library user, avoid doing anything with ``BigInt`` besides + serializing or deserializing them as required to call other interfaces. + Some of the general calculation facilities of ``BigInt`` may be made internal + to the library in a future major release. .. cpp:class:: BigInt - .. cpp:function:: BigInt() + .. cpp:function:: static BigInt BigInt::from_string(std::string_view str) - Create a BigInt with value zero + Create a BigInt from a string. By default decimal is expected. With an 0x + prefix, instead it is treated as hexadecimal. A ``-`` prefix to indicate + negative numbers is also accepted. - .. cpp:function:: BigInt::from_u64(uint64_t n) + .. cpp:function:: static BigInt::from_bytes(std::span buf) - Create a BigInt with value *n* + Create a BigInt from a binary array (big-endian encoding). The result of + this function will always be positive; there is no support for a sign bit, + 2s complement encoding, or similar methods for indicating a negative value. - .. cpp:function:: BigInt(std::string_view str) + .. cpp:function:: void serialize_to(std::span buf) - Create a BigInt from a string. By default decimal is expected. With an 0x - prefix instead it is treated as hexadecimal. A ``-`` prefix to indicate - negative numbers is also accepted. + Encode this BigInt as a big-endian integer. The sign is ignored. - .. cpp:function:: BigInt(std::span buf) + There must be sufficient space to encode the entire integer in ``buf``. + If ``buf`` is larger than required, sufficient zero bytes will be + prefixed. + + .. cpp:function:: size_t bytes() const + + Return number of bytes needed to represent value of ``*this`` + + .. cpp:function:: size_t bits() const - Create a BigInt from a binary array (big-endian encoding). + Return number of bits needed to represent value of ``*this`` - .. cpp:function:: BigInt(RandomNumberGenerator& rng, size_t bits, bool set_high_bit = true) + .. cpp:function:: std::string to_dec_string() const - Create a random BigInt of the specified size. + Encode the integer as a decimal string. + + .. cpp:function:: std::string to_hex_string() const + + Encode the integer as a hexadecimal string, with "0x" prefix + + .. cpp:function:: BigInt::zero() + + Create a BigInt with value zero + + .. cpp:function:: BigInt::from_u64(uint64_t n) + + Create a BigInt with value *n* .. cpp:function:: BigInt operator+(const BigInt& x, const BigInt& y) @@ -147,13 +186,10 @@ Set ``*this`` to zero - .. cpp:function:: size_t bytes() const - - Return number of bytes need to represent value of ``*this`` - - .. cpp:function:: size_t bits() const + .. cpp:function:: uint32_t to_u32bit() const - Return number of bits need to represent value of ``*this`` + Return value of ``*this`` as a 32-bit integer, if possible. + If the integer is negative or not in range, an exception is thrown. .. cpp:function:: bool is_even() const @@ -171,103 +207,15 @@ Return true if ``*this`` is zero - .. cpp:function:: void set_bit(size_t n) - - Set bit *n* of ``*this`` - - .. cpp:function:: void clear_bit(size_t n) - - Clear bit *n* of ``*this`` - - .. cpp:function:: bool get_bit(size_t n) const - - Get bit *n* of ``*this`` - - .. cpp:function:: uint32_t to_u32bit() const - - Return value of ``*this`` as a 32-bit integer, if possible. - If the integer is negative or not in range, an exception is thrown. - .. cpp:function:: bool is_negative() const - Return true if ``*this`` is negative + Return true if ``*this`` is less than zero .. cpp:function:: bool is_positive() const - Return true if ``*this`` is negative + Return true if ``*this`` is greater than or equal to zero .. cpp:function:: BigInt abs() const Return absolute value of ``*this`` - .. cpp:function:: void serialize_to(std::span buf) - - Encode this BigInt as a big-endian integer. The sign is ignored. - - There must be sufficient space to encode the entire integer in ``buf``. - If ``buf`` is larger than required, sufficient zero bytes will be - prefixed. - - .. cpp:function:: std::string to_dec_string() const - - Encode the integer as a decimal string. - - .. cpp:function:: std::string to_hex_string() const - - Encode the integer as a hexadecimal string, with "0x" prefix - -Number Theory ----------------------------------------- - -Number theoretic functions available include: - -.. cpp:function:: BigInt gcd(BigInt x, BigInt y) - - Returns the greatest common divisor of x and y - -.. cpp:function:: BigInt lcm(BigInt x, BigInt y) - - Returns an integer z which is the smallest integer such that z % x - == 0 and z % y == 0 - -.. cpp:function:: BigInt jacobi(BigInt a, BigInt n) - - Return Jacobi symbol of (a|n). - -.. cpp:function:: BigInt inverse_mod(BigInt x, BigInt m) - - Returns the modular inverse of x modulo m, that is, an integer - y such that (x*y) % m == 1. If no such y exists, returns zero. - -.. cpp:function:: BigInt power_mod(BigInt b, BigInt x, BigInt m) - - Returns b to the xth power modulo m. If you are doing many - exponentiations with a single fixed modulus, it is faster to use a - ``Power_Mod`` implementation. - -.. cpp:function:: BigInt ressol(BigInt x, BigInt p) - - Returns the square root modulo a prime, that is, returns a number y - such that (y*y) % p == x. Returns -1 if no such integer exists. - -.. cpp:function:: bool is_prime(BigInt n, RandomNumberGenerator& rng, \ - size_t prob = 56, double is_random = false) - - Test *n* for primality using a probabilistic algorithm (Miller-Rabin). With - this algorithm, there is some non-zero probability that true will be returned - even if *n* is actually composite. Modifying *prob* allows you to decrease the - chance of such a false positive, at the cost of increased runtime. Sufficient - tests will be run such that the chance *n* is composite is no more than 1 in - 2\ :sup:`prob`. Set *is_random* to true if (and only if) *n* was randomly - chosen (ie, there is no danger it was chosen maliciously) as far fewer tests - are needed in that case. - -.. cpp:function:: BigInt random_prime(RandomNumberGenerator& rng, \ - size_t bits, \ - BigInt coprime = 1, \ - size_t equiv = 1, \ - size_t equiv_mod = 2) - - Return a random prime number of ``bits`` bits long that is - relatively prime to ``coprime``, and equivalent to ``equiv`` modulo - ``equiv_mod``. diff -Nru botan3-3.7.1+dfsg/doc/api_ref/cipher_modes.rst botan3-3.12.0+dfsg/doc/api_ref/cipher_modes.rst --- botan3-3.7.1+dfsg/doc/api_ref/cipher_modes.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/api_ref/cipher_modes.rst 2026-05-07 01:38:28.000000000 +0000 @@ -158,6 +158,11 @@ Available Unauthenticated Cipher Modes ----------------------------------------- +.. warning:: + + As noted above these modes are insecure if used without an authentication code. + Prefer using an AEAD. + .. note:: CTR and OFB modes are also implemented, but these are treated as :cpp:class:`Stream_Cipher`\s instead. @@ -167,6 +172,12 @@ Available if ``BOTAN_HAS_MODE_CBC`` is defined. +CBC mode has a significant drawback, namely that due to its structure, when +encrypting a message it is not possible to process multiple blocks simultaneously. +This effectively prevents any use of optimizations based on SIMD or interleaving, +resulting in relatively poor performance compared to the same cipher in another +mode. + CBC requires the plaintext be padded using a reversible rule. The following padding schemes are implemented @@ -350,6 +361,27 @@ more obscure (and is slower than either GCM or ChaCha20Poly1305), but has excellent security properties. +Ascon-AEAD128 +~~~~~~~~~~~~~ + +Available if ``BOTAN_HAS_ASCON_AEAD128`` is defined. + +An AEAD scheme based on the Ascon permutation, specifically designed to allow +small footprint implementations. Its main use case is in constrained +environments, such as IoT devices where traditional cryptographic functions +may be too resource intensive. + +Unless you are interoperating with an existing device which due to resource +constraints can only use Ascon, prefer more typical AEADs such as AES-256/GCM, +AES-256/SIV, or ChaCha20Poly1305. + +This AEAD scheme is standardized by NIST in SP.800-232. It is not compatible +with earlier versions of the Ascon specification. The current implementation +does not provide explicit support for the tag truncation and nonce masking +features specified in the standard. + +Algorithm specification name: ``Ascon-AEAD128`` + CCM ~~~~~ diff -Nru botan3-3.7.1+dfsg/doc/api_ref/ecc.rst botan3-3.12.0+dfsg/doc/api_ref/ecc.rst --- botan3-3.7.1+dfsg/doc/api_ref/ecc.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/api_ref/ecc.rst 2026-05-07 01:38:28.000000000 +0000 @@ -57,7 +57,7 @@ Return a random non-zero scalar value - .. cpp:function:: static EC_Scalar gk_x_mod_order(const EC_Scalar& k, RandomNumberGenerator& rng, std::vector& ws) + .. cpp:function:: static EC_Scalar gk_x_mod_order(const EC_Scalar& k, RandomNumberGenerator& rng) Compute the elliptic curve scalar multiplication (``g*k``) where ``g`` is the standard base point on the curve. Then extract the ``x`` coordinate @@ -66,6 +66,18 @@ If ``k`` is zero (resulting in the scalar multiplication producing the identity element) then this function returns zero. + .. cpp:function:: static EC_Scalar hash(const EC_Group& group, \ + std::string_view hash_fn, \ + std::span input, \ + std::span domain_sep) + + Hash to scalar following RFC 9380. + + This deterministically and portably hashes the provided input and domain + separator into an integer modulo the group order. + + This function is supported for all groups. + .. cpp:function:: size_t bytes() const Return the byte length of the scalar @@ -140,12 +152,12 @@ Return true if this point is the identity element. - .. cpp:function:: EC_AffinePoint mul(const EC_Scalar& scalar, RandomNumberGenerator& rng, std::vector& ws) const + .. cpp:function:: EC_AffinePoint mul(const EC_Scalar& scalar, RandomNumberGenerator& rng) const Variable base scalar multiplication. Constant time. If the rng object is seeded, also uses blinding and point rerandomization. - .. cpp:function:: static EC_AffinePoint g_mul(const EC_Scalar& scalar, RandomNumberGenerator& rng, std::vector& ws) + .. cpp:function:: static EC_AffinePoint g_mul(const EC_Scalar& scalar, RandomNumberGenerator& rng) Fixed base scalar multiplication. Constant time. If the rng object is seeded, also uses blinding and point rerandomization. diff -Nru botan3-3.7.1+dfsg/doc/api_ref/env_vars.rst botan3-3.12.0+dfsg/doc/api_ref/env_vars.rst --- botan3-3.7.1+dfsg/doc/api_ref/env_vars.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/api_ref/env_vars.rst 2026-05-07 01:38:28.000000000 +0000 @@ -6,6 +6,9 @@ Certain environment variables can affect or tune the behavior of the library. The variables and their behavior are described here. +These values can be set in the environment before the program starts, or using +``setenv`` somewhere at the start of ``main``, before Botan has been invoked. + * ``BOTAN_THREAD_POOL_SIZE`` controls the number of threads which will be created for a thread pool used for some purposes within the library. If not set, or set to 0, then it defaults to the number of CPUs available on the diff -Nru botan3-3.7.1+dfsg/doc/api_ref/ffi.rst botan3-3.12.0+dfsg/doc/api_ref/ffi.rst --- botan3-3.7.1+dfsg/doc/api_ref/ffi.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/api_ref/ffi.rst 2026-05-07 01:38:28.000000000 +0000 @@ -20,7 +20,7 @@ Writing language bindings for C or C++ libraries is typically a tedious and bug-prone experience. This FFI layer was designed to make the experience, if not -pleasant, at least straighforward. +pleasant, at least straightforward. * All objects manipulated by the API are opaque structs. Each struct is tagged with a 32-bit magic number which is unique to its type; accidentally passing @@ -103,6 +103,11 @@ While decrypting in an AEAD mode, the tag failed to verify. +.. cpp:enumerator:: BOTAN_FFI_ERROR_NO_VALUE = -3 + + Given the context of the invocation no semantically reasonable value could + be produced, any provided out-parameters must be ignored. + .. cpp:enumerator:: BOTAN_FFI_ERROR_INSUFFICIENT_BUFFER_SPACE = -10 Functions which write a variable amount of space return this if the indicated @@ -165,6 +170,11 @@ An operation was invoked that makes sense for the object, but it is in the wrong state to perform it. +.. cpp:enumerator:: BOTAN_FFI_ERROR_OUT_OF_RANGE = -36 + + Querying an enumerable value resulted in an "out of range" error. This error + code may be used as the marker for the end of a value enumeration. + .. cpp:enumerator:: BOTAN_FFI_ERROR_NOT_IMPLEMENTED = -40 This is returned if the functionality is not available for some reason. For @@ -179,7 +189,7 @@ .. cpp:enumerator:: BOTAN_FFI_TPM_ERROR = -78 - An error occured when performing TPM2 interactions. + An error occurred when performing TPM2 interactions. .. cpp:enumerator:: BOTAN_FFI_ERROR_UNKNOWN_ERROR = -100 @@ -188,7 +198,7 @@ Error values below -10000 are reserved for the application (these can be returned from view functions). -Further information about the error that occured is available via +Further information about the error that occurred is available via .. cpp:function:: const char* botan_error_last_exception_message() @@ -249,6 +259,13 @@ ============== =================== FFI Version Supported Starting ============== =================== +20260506 3.12.0 +20260303 3.11.0 +20250829 3.10.0 +20250506 3.8.0 +20240408 3.4.0 +20231009 3.2.0 +20230711 3.1.0 20230403 3.0.0 20210220 2.18.0 20191214 2.13.0 @@ -642,7 +659,7 @@ Multiple Precision Integers ---------------------------------------- -.. versionadded: 2.1.0 +.. versionadded:: 2.1.0 .. cpp:type:: opaque* botan_mp_t @@ -658,11 +675,34 @@ .. cpp:function:: int botan_mp_to_hex(botan_mp_t mp, char* out) - Writes exactly ``botan_mp_num_bytes(mp)*2 + 1`` bytes to out + Writes the hex encoding to the ``out`` parameter. This must point to a pre-allocated + buffer of at least ``botan_mp_num_bytes(mp)*2 + 5`` bytes. Some number of bytes will + be written, followed by a null terminator. + + .. warning:: + + This function is error-prone to use since the caller is not able to specify the + length of the buffer, so if insufficient space is allocated an overwrite will occur, + instead of the function returning ``BOTAN_FFI_ERROR_INSUFFICIENT_BUFFER_SPACE`` as + is typical for FFI. Prefer :cpp:func:`botan_mp_view_hex` which avoids this problem. + +.. cpp:function:: int botan_mp_view_hex(botan_mp_t mp, botan_view_ctx ctx, botan_view_str_fn view) -.. cpp:function:: int botan_mp_to_str(botan_mp_t mp, uint8_t base, char* out, size_t* out_len) + View the hex encoding of the integer. - Base can be either 10 or 16. +.. cpp:function:: int botan_mp_to_str(botan_mp_t mp, uint8_t radix, char* out, size_t* out_len) + + The ``radix`` can currently be either 10 or 16. If ``radix`` is 16 this behaves + identically to :cpp:func:`botan_mp_to_hex` with the addition that the output length is + checked rather than assumed. + + .. note:: + + Prefer using :cpp:func:`botan_mp_view_str` + +.. cpp:function:: int botan_mp_view_str(botan_mp_t mp, uint8_t radix, botan_view_ctx ctx, botan_view_str_fn view) + + View the string encoding of the integer. The radix can currently be either 10 or 16. .. cpp:function:: int botan_mp_set_from_int(botan_mp_t mp, int initial_value) @@ -688,6 +728,16 @@ Writes exactly ``botan_mp_num_bytes(mp)`` to ``vec``. + Note that the sign of ``mp`` is ignored. + + .. note:: + + Prefer :cpp:func:`botan_mp_view_bin`. + +.. cpp:function:: int botan_mp_view_bin(botan_mp_t mp, botan_view_ctx ctx, botan_view_bin_fn view) + + View the big-endian byte encoding of the integer. Note that the sign of ``mp`` is ignored. + .. cpp:function:: int botan_mp_from_bin(botan_mp_t mp, const uint8_t vec[], size_t vec_len) Loads ``botan_mp_t`` from a binary vector (as produced by ``botan_mp_to_bin``). @@ -830,6 +880,243 @@ if the combination is not valid (but otherwise well formed), negative on error. + +Object Identifiers +---------------------------------------- + +.. versionadded:: 3.8.0 + +.. cpp:type:: opaque* botan_asn1_oid_t + + An opaque data type for an object identifier. Don't mess with it. + +.. cpp:function:: int botan_oid_destroy(botan_asn1_oid_t oid) + + Destroy an object. + +.. cpp:function:: int botan_oid_from_string(botan_asn1_oid_t* oid, const char* oid_str) + + Create an OID from a string, either dot notation (e.g. '1.2.3.4') or a registered name (e.g. 'RSA') + +.. cpp:function:: int botan_oid_register(botan_asn1_oid_t oid, const char* name) + + Register an OID so that it may later be retrieved by name + +.. cpp:function:: int botan_oid_view_string(botan_asn1_oid_t oid, botan_view_ctx ctx, botan_view_str_fn view) + + View the OID in dot notation + +.. cpp:function:: int botan_oid_view_name(botan_asn1_oid_t oid, botan_view_ctx ctx, botan_view_str_fn view) + + View the OID as a name if it has one, otherwise as dot notation + +.. cpp:function:: int botan_oid_equal(botan_asn1_oid_t a, botan_asn1_oid_t b) + + Three way comparison: set result to -1 if ``a`` is less than ``b``, + 0 if ``a`` is equal to ``b``, and 1 if ``a`` is greater than ``b``. + +.. cpp:function:: int botan_oid_cmp(int* result, botan_asn1_oid_t a, botan_asn1_oid_t b) + + Return 1 if ``a`` is equal to ``b``, 0 if ``a`` is not equal to ``b`` + + +EC Groups +---------------------------------------- + +.. versionadded:: 3.8.0 + +.. cpp:type:: opaque* botan_ec_group_t + + An opaque data type for an EC Group. Don't mess with it. + +.. cpp:function:: int botan_ec_group_destroy(botan_ec_group_t oid) + + Destroy an object. + +.. cpp:function:: int botan_ec_group_supports_application_specific_group(int* out) + + Checks if in this build configuration it is possible to register an application specific elliptic curve, + and sets ``out`` to 1 if so, 0 otherwise. + +.. cpp:function:: int botan_ec_group_supports_named_group(const char* name, int* out) + + Checks if in this build configuration botan_ec_group_from_name(group_ptr, name) will succeed, + and sets ``out`` to 1 if so, 0 otherwise. + +.. cpp:function:: int botan_ec_group_from_params(botan_ec_group_t* ec_group, \ + botan_asn1_oid_t oid, \ + botan_mp_t p, \ + botan_mp_t a, \ + botan_mp_t b, \ + botan_mp_t base_x, \ + botan_mp_t base_y, \ + botan_mp_t order) + + Create a new EC Group from the given parameters. + + .. warning:: + Use only elliptic curve parameters you trust. + +.. cpp:function:: int botan_ec_group_from_ber(botan_ec_group_t* ec_group, const uint8_t* ber, size_t ber_len) + + Decode a BER encoded ECC domain parameter set + +.. cpp:function:: int botan_ec_group_from_pem(botan_ec_group_t* ec_group, const char* pem) + + Initialize an EC Group from the PEM/ASN.1 encoding + +.. cpp:function:: int botan_ec_group_from_oid(botan_ec_group_t* ec_group, botan_asn1_oid_t oid) + + Initialize an EC Group from a group named by an object identifier + +.. cpp:function:: int botan_ec_group_from_name(botan_ec_group_t* ec_group, const char* name) + + Initialize an EC Group from a common group name (eg "secp256r1") + +.. cpp:function:: int botan_ec_group_unregister(botan_asn1_oid_t oid) + + Unregister a previously registered group. Returns 1 if the group was found and unregistered, else 0. + + Using this is discouraged for normal use. This is only useful or necessary if + you are registering a very large number of distinct groups, and need to worry about memory constraints. + +.. cpp:function:: int botan_ec_group_view_der(botan_ec_group_t ec_group, botan_view_ctx ctx, botan_view_bin_fn view) + + View an EC Group in DER encoding + +.. cpp:function:: int botan_ec_group_view_pem(botan_ec_group_t ec_group, botan_view_ctx ctx, botan_view_str_fn view) + + View an EC Group in PEM encoding + +.. cpp:function:: int botan_ec_group_get_curve_oid(botan_asn1_oid_t* oid, botan_ec_group_t ec_group) + + Get the curve OID of an EC Group + +.. cpp:function:: int botan_ec_group_get_p(botan_mp_t* p, botan_ec_group_t ec_group) + + Get the prime modulus of the field + +.. cpp:function:: int botan_ec_group_get_a(botan_mp_t* a, botan_ec_group_t ec_group) + + Get the a parameter of the elliptic curve equation + +.. cpp:function:: int botan_ec_group_get_b(botan_mp_t* b, botan_ec_group_t ec_group) + + Get the b parameter of the elliptic curve equation + +.. cpp:function:: int botan_ec_group_get_g_x(botan_mp_t* g_x, botan_ec_group_t ec_group) + + Get the x coordinate of the base point + +.. cpp:function:: int botan_ec_group_get_g_y(botan_mp_t* g_y, botan_ec_group_t ec_group) + + Get the y coordinate of the base point + +.. cpp:function:: int botan_ec_group_get_order(botan_mp_t* order, botan_ec_group_t ec_group) + + Get the order of the base point + +.. cpp:function:: int botan_ec_group_equal(botan_ec_group_t curve1, botan_ec_group_t curve2) + + Return 1 if ``curve1`` is equal to ``curve2``, 0 if ``curve1`` is not equal to ``curve2`` + + +EC Points and Scalars +---------------------------------------- + +.. versionadded:: 3.12.0 + +.. cpp:type:: opaque* botan_ec_scalar_t + + An opaque data type for an EC Scalar. Don't mess with it. + +.. cpp:type:: opaque* botan_ec_point_t + + An opaque data type for an EC Point. Don't mess with it. + +.. cpp:function:: int botan_ec_scalar_destroy(botan_ec_scalar_t ec_scalar) + + Destroy an object. + +.. cpp:function:: int botan_ec_scalar_random(botan_ec_scalar_t* ec_scalar, botan_ec_group_t ec_group, botan_rng_t rng); + + Create a scalar with a random value. + +.. cpp:function:: int botan_ec_scalar_from_mp(botan_ec_scalar_t* ec_scalar, botan_ec_group_t ec_group, botan_mp_t mp); + + Convert from an MPI to a scalar. + +.. cpp:function:: int botan_ec_scalar_to_mp(botan_ec_scalar_t ec_scalar, botan_mp_t* mp) + + Convert from a scalar to an MPI. + +.. cpp:function:: int botan_ec_point_destroy(botan_ec_point_t ec_point) + + Destroy an object. + +.. cpp:function:: int botan_ec_point_identity(botan_ec_point_t* ec_point, botan_ec_group_t ec_group); + + Create a point set to the identity element of the group. + +.. cpp:function:: int botan_ec_point_generator(botan_ec_point_t* ec_point, botan_ec_group_t ec_group); + + Create a point set to the standard group generator. + +.. cpp:function:: int botan_ec_point_from_xy(botan_ec_point_t* ec_point, botan_ec_group_t ec_group, botan_mp_t x, botan_mp_t y); + + Create a point from a pair (x,y) of integers. + The integers must be within the field and must satisfy the curve equation. + +.. cpp:function:: int botan_ec_point_from_bytes(botan_ec_point_t* ec_point, \ + botan_ec_group_t ec_group, \ + const uint8_t* bytes, \ + size_t bytes_len); + + Create a point from a SEC1 compressed or uncompressed format. + +.. cpp:function:: int botan_ec_point_view_x_bytes(botan_ec_point_t ec_point, botan_view_ctx ctx, botan_view_bin_fn view); + + View the fixed length encoding of the affine x coordinate. + +.. cpp:function:: int botan_ec_point_view_y_bytes(botan_ec_point_t ec_point, botan_view_ctx ctx, botan_view_bin_fn view); + + View the fixed length encoding of the affine y coordinate. + +.. cpp:function:: int botan_ec_point_view_xy_bytes(botan_ec_point_t ec_point, botan_view_ctx ctx, botan_view_bin_fn view); + + View the fixed length encoding of the affine x and y coordinates. + +.. cpp:function:: int botan_ec_point_view_uncompressed(botan_ec_point_t ec_point, botan_view_ctx ctx, botan_view_bin_fn view); + + View the fixed length SEC1 uncompressed encoding. + +.. cpp:function:: int botan_ec_point_view_compressed(botan_ec_point_t ec_point, botan_view_ctx ctx, botan_view_bin_fn view); + + View the fixed length SEC1 compressed encoding. + +.. cpp:function:: int botan_ec_point_is_identity(botan_ec_point_t ec_point); + + Returns 1 if ``ec_point`` is equal to the group's identity element, otherwise 0. + +.. cpp:function:: int botan_ec_point_equal(botan_ec_point_t x, botan_ec_point_t y); + + Returns 1 if ``x`` == ``y``, otherwise 0. + +.. cpp:function:: int botan_ec_point_negate(botan_ec_point_t* result, botan_ec_point_t ec_point); + + Negates the provided point. + +.. cpp:function:: int botan_ec_point_add(botan_ec_point_t* result, botan_ec_point_t x, botan_ec_point_t y); + + Computes ``x`` + ``y``. + +.. cpp:function:: int botan_ec_point_mul(botan_ec_point_t* result, \ + botan_ec_point_t ec_point, \ + botan_ec_scalar_t ec_scalar, \ + botan_rng_t rng); + + Multiplies ``ec_point`` by the given ``ec_scalar``. + Public Key Creation, Import and Export ---------------------------------------- @@ -846,6 +1133,11 @@ const char* algo_params, \ botan_rng_t rng) +.. cpp:function:: int botan_ec_privkey_create(botan_privkey_t* key, \ + const char* algo_name, \ + botan_ec_group_t ec_group, \ + botan_rng_t rng) + .. cpp:function:: int botan_privkey_create_rsa(botan_privkey_t* key, botan_rng_t rng, size_t n_bits) Create an RSA key of the given size @@ -962,7 +1254,7 @@ Deprecated, use ``botan_privkey_export_encrypted_msec`` or ``botan_privkey_export_encrypted_iter`` -.. cpp::function:: int botan_privkey_export_encrypted_pbkdf_msec(botan_privkey_t key, +.. cpp:function:: int botan_privkey_export_encrypted_pbkdf_msec(botan_privkey_t key, \ uint8_t out[], size_t* out_len, \ botan_rng_t rng, \ const char* passphrase, \ @@ -978,7 +1270,7 @@ ``cipher_algo`` must specify a CBC mode cipher (such as "AES-128/CBC") or as a Botan-specific extension a GCM mode may be used. -.. cpp::function:: int botan_privkey_export_encrypted_pbkdf_iter(botan_privkey_t key, \ +.. cpp:function:: int botan_privkey_export_encrypted_pbkdf_iter(botan_privkey_t key, \ uint8_t out[], size_t* out_len, \ botan_rng_t rng, \ const char* passphrase, \ @@ -999,6 +1291,19 @@ Read an algorithm specific field from the private key object, placing it into output. For example "p" or "q" for RSA keys, or "x" for DSA keys or ECC keys. +.. cpp:function:: int botan_privkey_oid(botan_asn1_oid_t* oid, botan_privkey_t key) + + Get the key's associated OID. + +.. cpp:function:: int botan_privkey_stateful_operation(botan_privkey_t key, int* out) + + Checks whether a key is stateful and set ``out`` to 1 if it is, 0 otherwise. + +.. cpp:function:: int botan_privkey_remaining_operations(botan_privkey_t key, uint64_t* out) + + Set ``out`` to the number of remaining operations. + If the key is not stateful, an error will be returned. + .. cpp:type:: opaque* botan_pubkey_t An opaque data type for a public key. Don't mess with it. @@ -1039,6 +1344,10 @@ Read an algorithm specific field from the public key object, placing it into output. For example "n" or "e" for RSA keys or "p", "q", "g", and "y" for DSA keys. +.. cpp:function:: int botan_pubkey_oid(botan_asn1_oid_t* oid, botan_privkey_t key) + + Get the key's associated OID. + RSA specific functions ---------------------------------------- @@ -1084,6 +1393,21 @@ Initialize a public RSA key using parameters n and e. +EC specific functions +---------------------------------------- + +.. cpp:function:: int botan_ec_privkey_get_private_key(botan_privkey_t key, botan_ec_scalar_t* value) + + Get the private value of the EC key. + +.. cpp:function:: int botan_ec_privkey_get_group(botan_privkey_t key, botan_ec_group_t* ec_group) + + Get the group of this EC private key. + +.. cpp:function:: int botan_ec_pubkey_get_group(botan_pubkey_t key, botan_ec_group_t* ec_group) + + Get the group of this EC public key. + DSA specific functions ---------------------------------------- @@ -1449,6 +1773,11 @@ An opaque data type for an X.509 certificate. Don't mess with it. +.. cpp:type:: opaque* botan_x509_general_name_t + + An opaque data type for an X.509 GeneralName used to query subject/issuer + alternative names and name constraints. Don't mess with it. + .. cpp:function:: int botan_x509_cert_load(botan_x509_cert_t* cert_obj, \ const uint8_t cert[], size_t cert_len) @@ -1472,6 +1801,52 @@ const char* common_name, \ const char* org_name) +.. cpp:function:: int botan_x509_cert_view_binary_values(botan_x509_cert_t cert, \ + botan_x509_value_type value_type, \ + size_t index, \ + botan_view_ctx ctx, \ + botan_view_bin_fn view_fn) + + Access various binary fields of information contained in the certificate. + + Some of those may be multi-value fields, the `index` parameter may be used + to enumerate such values until :cpp:enumerator:`BOTAN_FFI_ERROR_OUT_OF_RANGE` + is returned. For singular values, an `index` of 0 must be used. + + See :ref:`x509_getter_function` for further information about the available + values. If a value does not exist :cpp:enumerator:`BOTAN_FFI_ERROR_NO_VALUE` + is returned. + +.. cpp:function:: int botan_x509_cert_view_binary_values_count(botan_x509_cert_t cert, \ + botan_x509_value_type value_type, \ + size_t* count) + + Get the number of entries for multi-value binary fields of information + contained in the certificate. + +.. cpp:function:: int botan_x509_cert_view_string_values(botan_x509_cert_t cert, \ + botan_x509_value_type value_type, \ + size_t index, \ + botan_view_ctx ctx, \ + botan_view_str_fn view_fn) + + Access various string fields of information contained in the certificate. + + Some of those may be multi-value fields, the `index` parameter may be used + to enumerate such values until :cpp:enumerator:`BOTAN_FFI_ERROR_OUT_OF_RANGE` + is returned. For singular values, an `index` of 0 must be used. + + See :ref:`x509_getter_function` for further information about the available + values. If a value does not exist :cpp:enumerator:`BOTAN_FFI_ERROR_NO_VALUE` + is returned. + +.. cpp:function:: int botan_x509_cert_view_string_values_count(botan_x509_cert_t cert, \ + botan_x509_value_type value_type, \ + size_t* count) + + Get the number of entries for multi-value string fields of information + contained in the certificate. + .. cpp:function:: int botan_x509_cert_get_time_starts(botan_x509_cert_t cert, char out[], size_t* out_len) Return the time the certificate becomes valid, as a string in form @@ -1496,7 +1871,19 @@ .. cpp:function:: int botan_x509_cert_get_serial_number(botan_x509_cert_t cert, uint8_t out[], size_t* out_len) - Return the serial number of the certificate. + Return the serial number of the certificate as big-endian encoded bytes. + +.. cpp:function:: int botan_x509_cert_serial_number(botan_x509_cert_t cert, botan_mp_t* serial_number) + + Return the serial number of the certificate as a multi-precision integer. + +.. cpp:function:: int botan_x509_cert_is_ca(botan_x509_cert_t cert) + + Check whether the certificate is marked as a CA certificate. + +.. cpp:function:: int botan_x509_cert_get_path_length_constraint(botan_x509_cert_t cert, size_t* path_len) + + Get the path length constraint for a CA certificate. .. cpp:function:: int botan_x509_cert_get_authority_key_id(botan_x509_cert_t cert, uint8_t out[], size_t* out_len) @@ -1524,13 +1911,27 @@ const char* key, size_t index, \ uint8_t out[], size_t* out_len) - Get a value from the issuer DN field. + Get a value from the issuer DN field. If the index is out of range, + :cpp:enumerator:`BOTAN_FFI_ERROR_BAD_PARAMETER` is returned for historical + reasons. + +.. cpp:function:: int botan_x509_cert_get_issuer_dn_count(botan_x509_cert_t cert, \ + const char* key, size_t* count) + + Get the number of values for a given key in the issuer DN field. .. cpp:function:: int botan_x509_cert_get_subject_dn(botan_x509_cert_t cert, \ const char* key, size_t index, \ uint8_t out[], size_t* out_len) - Get a value from the subject DN field. + Get a value from the subject DN field. If the index is out of range, + :cpp:enumerator:`BOTAN_FFI_ERROR_BAD_PARAMETER` is returned for historical + reasons. + +.. cpp:function:: int botan_x509_cert_get_subject_dn_count(botan_x509_cert_t cert, \ + const char* key, size_t* count) + + Get the number of values for a given key in the subject DN field. .. cpp:function:: int botan_x509_cert_to_string(botan_x509_cert_t cert, char out[], size_t* out_len) @@ -1550,6 +1951,112 @@ .. cpp:function:: int botan_x509_cert_allowed_usage(botan_x509_cert_t cert, unsigned int key_usage) +.. cpp:function:: int botan_x509_cert_allowed_extended_usage_str(botan_x509_cert_t cert, const char* oid) + + Check whether the certificate has the specified extended key usage OID from + `RFC 5280 - 4.2.1.12 `_. + If the certificate has no extended key usage extension, this will always + behave as if the requested OID is *not present*. + +.. cpp:function:: int botan_x509_cert_allowed_extended_usage_oid(botan_x509_cert_t cert, botan_asn1_oid_t oid) + + Check whether the certificate has the specified extended key usage OID from + `RFC 5280 - 4.2.1.12 `_. + If the certificate has no extended key usage extension, this will always + behave as if the requested OID is *not present*. + +.. cpp:enum:: botan_x509_general_name_types + + GeneralName data types. Allowed values: + `BOTAN_X509_OTHER_NAME`, `BOTAN_X509_EMAIL_ADDRESS`, `BOTAN_X509_DNS_NAME`, + `BOTAN_X509_DIRECTORY_NAME`, `BOTAN_X509_URI`, `BOTAN_X509_IP_ADDRESS`. + +.. cpp:function:: int botan_x509_general_name_get_type(botan_x509_general_name_t name, unsigned int* type) + + Get the data type of the GeneralName object as a member of + :cpp:enum:`botan_x509_general_name_types`. Depending on this type, one of the + view functions below can be used to extract the value. + + `BOTAN_X509_DIRECTORY_NAME` is a binary DER encoding of a distinguished name. + `BOTAN_X509_IP_ADDRESS` is a big endian binary encoding of the IP address + optionally concatenated with the subnet mask. + `BOTAN_X509_EMAIL_ADDRESS`, `BOTAN_X509_DNS_NAME`, and `BOTAN_X509_URI` are + characters arrays. + Support for `BOTAN_X509_OTHER_NAME` is deprecated and cannot be viewed using + these functions. + +.. cpp:function:: int botan_x509_general_name_view_string_value(botan_x509_general_name_t name, \ + botan_view_ctx ctx, \ + botan_view_str_fn view) + + Allows querying the value of GeneralName objects of type + `BOTAN_X509_EMAIL_ADDRESS`, `BOTAN_X509_DNS_NAME`, `BOTAN_X509_URI`, and + `BOTAN_X509_IP_ADDRESS`. + +.. cpp:function:: int botan_x509_general_name_view_binary_value(botan_x509_general_name_t name, \ + botan_view_ctx ctx, \ + botan_view_bin_fn view) + + Allows querying the value of GeneralName objects of type + `BOTAN_X509_DIRECTORY_NAME` (as DER encoded distinguished name) and + `BOTAN_X509_IP_ADDRESS` (as big-endian encoded IP address + subnet mask). + +.. cpp:function:: int botan_x509_general_name_destroy(botan_x509_general_name_t alt_names) + + Destroy the GeneralName object. + +.. cpp:function:: int botan_x509_cert_permitted_name_constraints(botan_x509_cert_t cert, \ + size_t index, \ + botan_x509_general_name_t* constraint) + + Enumerate the permitted name constraints in the certificate as GeneralName + objects. If the given index is not available, + :cpp:enumerator:`BOTAN_FFI_ERROR_OUT_OF_RANGE` is returned. + +.. cpp:function:: int botan_x509_cert_permitted_name_constraints_count(botan_x509_cert_t cert, \ + size_t* count) + + Get the number of permitted name constraints in the certificate. + +.. cpp:function:: int botan_x509_cert_excluded_name_constraints(botan_x509_cert_t cert, \ + size_t index, \ + botan_x509_general_name_t* constraint) + + Enumerate the excluded name constraints in the certificate as GeneralName + objects. If the given index is not available, + :cpp:enumerator:`BOTAN_FFI_ERROR_OUT_OF_RANGE` is returned. + +.. cpp:function:: int botan_x509_cert_excluded_name_constraints_count(botan_x509_cert_t cert, \ + size_t* count) + + Get the number of excluded name constraints in the certificate. + +.. cpp:function:: int botan_x509_cert_subject_alternative_names(botan_x509_cert_t cert, \ + size_t index, \ + botan_x509_general_name_t* alt_name) + + Enumerate the subject alternative names in the certificate as GeneralName + objects. If the given index is not available, + :cpp:enumerator:`BOTAN_FFI_ERROR_OUT_OF_RANGE` is returned. + +.. cpp:function:: int botan_x509_cert_subject_alternative_names_count(botan_x509_cert_t cert, \ + size_t* count) + + Get the number of subject alternative names in the certificate. + +.. cpp:function:: int botan_x509_cert_issuer_alternative_names(botan_x509_cert_t cert, \ + size_t index, \ + botan_x509_general_name_t* alt_name) + + Enumerate the issuer alternative names in the certificate as GeneralName + objects. If the given index is not available, + :cpp:enumerator:`BOTAN_FFI_ERROR_OUT_OF_RANGE` is returned. + +.. cpp:function:: int botan_x509_cert_issuer_alternative_names_count(botan_x509_cert_t cert, \ + size_t* count) + + Get the number of issuer alternative names in the certificate. + .. cpp:function:: int botan_x509_cert_verify(int* validation_result, \ botan_x509_cert_t cert, \ const botan_x509_cert_t* intermediates, \ @@ -1597,7 +2104,7 @@ Certificate path validation supporting Certificate Revocation Lists. - Works the same as ``botan_x509_cert_cerify``. + Works the same as ``botan_x509_cert_verify``. ``crls`` is an array of ``botan_x509_crl_t`` objects, ``crls_len`` is its length. @@ -1613,6 +2120,10 @@ An opaque data type for an X.509 CRL. +.. cpp:type:: opaque* botan_x509_crl_entry_t + + An opaque data type for an X.509 CRL entry. + .. cpp:function:: int botan_x509_crl_load(botan_x509_crl_t* crl_obj, \ const uint8_t crl[], size_t crl_len) @@ -1622,15 +2133,223 @@ Load a CRL from a file. +.. cpp:function:: int botan_x509_crl_create(botan_x509_crl_t* crl_obj, \ + botan_rng_t rng, \ + botan_x509_cert_t ca_cert, \ + botan_privkey_t ca_key, \ + uint64_t issue_time, \ + uint32_t next_update, \ + const char* hash_fn, \ + const char* padding) + + Create a new CRL. ``issue_time`` is expected to be a UNIX timestamp, in seconds. + ``next_update`` is the number of seconds after ``issue_time`` until the CRL expires. + ``hash_fn`` and ``padding`` may be NULL. + +.. cpp:enum:: botan_x509_crl_reason_code + + CRL revocation reason codes. Allowed values: `BOTAN_CRL_ENTRY_UNSPECIFIED`, + `BOTAN_CRL_ENTRY_KEY_COMPROMISE`, `BOTAN_CRL_ENTRY_CA_COMPROMISE`, `BOTAN_CRL_ENTRY_AFFILIATION_CHANGED`, + `BOTAN_CRL_ENTRY_SUPERSEDED`, `BOTAN_CRL_ENTRY_CESSATION_OF_OPERATION`, `BOTAN_CRL_ENTRY_CERTIFICATE_HOLD`, + `BOTAN_CRL_ENTRY_REMOVE_FROM_CRL`, `BOTAN_CRL_ENTRY_PRIVILEGE_WITHDRAWN`, `BOTAN_CRL_ENTRY_AA_COMPROMISE`. + +.. cpp:function:: int botan_x509_crl_entry_create(botan_x509_crl_entry_t* entry, botan_x509_cert_t cert, int reason_code) + + Create a new CRL entry to be added to a CRL later. + +.. cpp:function:: int botan_x509_crl_update(botan_x509_crl_t* crl_obj, \ + botan_x509_crl_t last_crl, \ + botan_rng_t rng, \ + botan_x509_cert_t ca_cert, \ + botan_privkey_t ca_key, \ + uint64_t issue_time, \ + uint32_t next_update, \ + const botan_x509_crl_entry_t* new_entries, \ + size_t new_entries_len, \ + const char* hash_fn, \ + const char* padding) + + Revoke some certificates. This does not update the given CRL in place. + ``issue_time`` is expected to be a UNIX timestamp, in seconds. + ``next_update`` is the number of seconds after ``issue_time`` until the CRL expires. + ``hash_fn`` and ``padding`` may be NULL. + ``new_entries`` is an array of ``botan_x509_crl_entry_t`` objects, ``new_entries_len`` is its length. + +.. cpp:function:: int botan_x509_crl_verify_signature(botan_x509_crl_t crl, botan_pubkey_t key) + + Verify the signature of a CRL. Returns 1 if the signature is valid, 0 otherwise. + .. cpp:function:: int botan_x509_crl_destroy(botan_x509_crl_t crl) Destroy the CRL object. +.. cpp:function:: int botan_x509_crl_this_update(botan_x509_crl_t crl, uint64_t* time_since_epoch) + + Return the time the CRL becomes valid, as seconds since epoch. + +.. cpp:function:: int botan_x509_crl_next_update(botan_x509_crl_t crl, uint64_t* time_since_epoch) + + Return the time the CRL expires, as seconds since epoch. Note that this field + is technically optional in CRLs, if the CRL does not specify a "next update" + timestamp, :cpp:enumerator:`BOTAN_FFI_ERROR_NO_VALUE` is returned. + +.. cpp:function:: int botan_x509_crl_view_binary_values(botan_x509_crl_t crl, \ + botan_x509_value_type value_type, \ + size_t index, \ + botan_view_ctx ctx, \ + botan_view_bin_fn view_fn) + + Access various binary fields of information contained in the CRL. + + Some of those may be multi-value fields, the `index` parameter may be used + to enumerate such values until :cpp:enumerator:`BOTAN_FFI_ERROR_OUT_OF_RANGE` + is returned. For singular values, an `index` of 0 must be used. + + See :ref:`x509_getter_function` for further information about the available + values. If a value does not exist :cpp:enumerator:`BOTAN_FFI_ERROR_NO_VALUE` + is returned. + +.. cpp:function:: int botan_x509_crl_view_binary_values_count(botan_x509_crl_t crl, \ + botan_x509_value_type value_type, \ + size_t* count) + + Get the number of entries for multi-value binary fields of information + contained in the CRL. + +.. cpp:function:: int botan_x509_crl_view_string_values(botan_x509_crl_t crl, \ + botan_x509_value_type value_type, \ + size_t index, \ + botan_view_ctx ctx, \ + botan_view_str_fn view_fn) + + Access various string fields of information contained in the CRL. + + Some of those may be multi-value fields, the `index` parameter may be used + to enumerate such values until :cpp:enumerator:`BOTAN_FFI_ERROR_OUT_OF_RANGE` + is returned. For singular values, an `index` of 0 must be used. + + See :ref:`x509_getter_function` for further information about the available + values. If a value does not exist :cpp:enumerator:`BOTAN_FFI_ERROR_NO_VALUE` + is returned. + +.. cpp:function:: int botan_x509_crl_view_string_values_count(botan_x509_crl_t crl, \ + botan_x509_value_type value_type, \ + size_t* count) + + Get the number of entries for multi-value string fields of information + contained in the CRL. + .. cpp:function:: int botan_x509_is_revoked(botan_x509_crl_t crl, botan_x509_cert_t cert) Check whether a given ``crl`` contains a given ``cert``. Return ``0`` when the certificate is revoked, ``-1`` otherwise. +.. cpp:function:: int botan_x509_crl_entries(botan_x509_crl_t crl, \ + size_t index, \ + botan_x509_crl_entry_t *entry) + + List the entries in the CRL. Using the `index` parameter applications can + enumerate all entries in the CRL. If the list of entries is exhausted, this + will return :cpp:enumerator:`BOTAN_FFI_ERROR_OUT_OF_RANGE`. + +.. cpp:function:: int botan_x509_crl_entries_count(botan_x509_crl_t crl, size_t* count) + + Get the number of entries in the CRL. + +.. cpp:function:: int botan_x509_crl_entry_reason(botan_x509_crl_entry_t entry, int* reason_code) + + Get the revocation reason code for the given CRL entry. The reason code is + according to `RFC 5280 - 5.3.1 `, see :cpp:enum:`botan_x509_crl_reason_code`. + +.. cpp:function:: int botan_x509_crl_entry_revocation_date(botan_x509_crl_entry_t entry, uint64_t* time_since_epoch) + + Get the revocation date for the given CRL entry, as seconds since epoch. + +.. cpp:function:: int botan_x509_crl_entry_serial_number(botan_x509_crl_entry_t entry, botan_mp_t* serial_number) + + Get the serial number for the given CRL entry as a multi-precision integer. + +.. cpp:function:: int botan_x509_crl_entry_view_serial_number(botan_x509_crl_entry_t entry, botan_view_ctx ctx, botan_view_bin_fn view) + + View the serial number for the given CRL entry, as big-endian encoded bytes. + +.. cpp:function:: int botan_x509_crl_entry_destroy(botan_x509_crl_entry_t entry) + + Destroy the CRL entry object. + +.. _x509_getter_function: + +X.509 Available Generic Getter Values +---------------------------------------- + +Most X.509 objects may contain various data fields that may be of interest for +using applications. Many of those values can be queried through a generic API +that is extensible without introducing ABI incompatibilities. + +All available value types of the generic X.509 object getters are: + +.. cpp:enumerator:: BOTAN_X509_SERIAL_NUMBER + + The binary big-endian encoded serial number of a certificate or CRL. + +.. cpp:enumerator:: BOTAN_X509_SUBJECT_DN_BITS + + The DER encoded subject distinguished name of the certificate. + +.. cpp:enumerator:: BOTAN_X509_ISSUER_DN_BITS + + The DER encoded issuer distinguished name of a certificate or CRL. + +.. cpp:enumerator:: BOTAN_X509_SUBJECT_KEY_IDENTIFIER + + The subject key identifier (usually a hash of the certificate's public key) + in binary format. + +.. cpp:enumerator:: BOTAN_X509_AUTHORITY_KEY_IDENTIFIER + + The issuer's key identifier (usually a hash of the issuer's public key) in + binary format. + +.. cpp:enumerator:: BOTAN_X509_PUBLIC_KEY_PKCS8_BITS + + The certificate's public key in PKCS#8 format (DER encoding). + +.. cpp:enumerator:: BOTAN_X509_TBS_DATA_BITS + + The "To-Be-Signed" data of a certificate or CRL (DER encoding). + +.. cpp:enumerator:: BOTAN_X509_SIGNATURE_SCHEME_BITS + + The signature scheme descriptor of a certificate or CRL (DER encoding). + +.. cpp:enumerator:: BOTAN_X509_SIGNATURE_BITS + + The raw signature data of a certificate or CRL. The encoding depends on the + signature algorithm but is always in binary format. + +.. cpp:enumerator:: BOTAN_X509_DER_ENCODING + + The binary DER encoding of the entire certificate or CRL object. + +.. cpp:enumerator:: BOTAN_X509_PEM_ENCODING + + The string-based PEM encoding of the entire certificate or CRL object. + +.. cpp:enumerator:: BOTAN_X509_CRL_DISTRIBUTION_URLS + + The CRL distribution points (URLs) noted in the certificate as a character + array. There might be more than one such URL defined in a certificate. + +.. cpp:enumerator:: BOTAN_X509_OCSP_RESPONDER_URLS + + The OCSP responders (URLs) noted in the certificate as a character array. + There might be more than one such URL defined in a certificate. + +.. cpp:enumerator:: BOTAN_X509_CA_ISSUERS_URLS + + The URLs of the issuing CA certificate of a certificate as a character array. + There might be more than one such URL defined in a certificate. + ZFEC (Forward Error Correction) ---------------------------------------- diff -Nru botan3-3.7.1+dfsg/doc/api_ref/filters.rst botan3-3.12.0+dfsg/doc/api_ref/filters.rst --- botan3-3.7.1+dfsg/doc/api_ref/filters.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/api_ref/filters.rst 2026-05-07 01:38:28.000000000 +0000 @@ -688,7 +688,7 @@ .. cpp:function:: std::string Filter::name() const - This should just return a useful decription of the filter object. + This should just return a useful description of the filter object. .. cpp:function:: void Filter::write(const uint8_t* input, size_t length) diff -Nru botan3-3.7.1+dfsg/doc/api_ref/footguns.rst botan3-3.12.0+dfsg/doc/api_ref/footguns.rst --- botan3-3.7.1+dfsg/doc/api_ref/footguns.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/api_ref/footguns.rst 2026-05-07 01:38:28.000000000 +0000 @@ -25,7 +25,7 @@ impossible or inconvenient, one option is to disable the pool, either at build time (disable the ``locking_allocator`` module) or at runtime. Unfortunately the runtime setting requires setting an environment variable (see :ref:`env_vars`), -and doing so consistently *prior to static intialization* is not trivial, due to +and doing so consistently *prior to static initialization* is not trivial, due to the previously mentioned fiasco. One option might be to use GCC's ``constructor`` function attribute. diff -Nru botan3-3.7.1+dfsg/doc/api_ref/fpe.rst botan3-3.12.0+dfsg/doc/api_ref/fpe.rst --- botan3-3.7.1+dfsg/doc/api_ref/fpe.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/api_ref/fpe.rst 2026-05-07 01:38:28.000000000 +0000 @@ -96,3 +96,7 @@ format, including a correct checksum. .. literalinclude:: ../../src/cli/cc_enc.cpp + +This example encrypts a string of dictionary words onto another string of dictionary words: + +.. literalinclude:: ../../src/examples/fpe_dictionary.cpp diff -Nru botan3-3.7.1+dfsg/doc/api_ref/hash.rst botan3-3.12.0+dfsg/doc/api_ref/hash.rst --- botan3-3.7.1+dfsg/doc/api_ref/hash.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/api_ref/hash.rst 2026-05-07 01:38:28.000000000 +0000 @@ -97,6 +97,44 @@ The following cryptographic hash functions are implemented. If in doubt, any of SHA-384, SHA-3, or BLAKE2b are fine choices. +Ascon-Hash256 +^^^^^^^^^^^^^ + +Available if ``BOTAN_HAS_ASCON_HASH256`` is defined. + +A hash function based on the Ascon permutation, specifically designed to allow +small footprint implementations. Its main use case is in constrained +environments, such as IoT devices where traditional cryptographic functions +may be too resource intensive. + +Unless you are interoperating with an existing device which due to resource +constraints can only use Ascon, prefer more typical hashes such as SHA-256, +SHA-512, or SHA-3. + +This hash function is standardized by NIST in SP.800-232. It is not compatible +with earlier versions of the Ascon specification. + +Algorithm specification name: ``Ascon-Hash256`` + +Ascon-XOF128 +^^^^^^^^^^^^ + +Available if ``BOTAN_HAS_ASCON_XOF128`` is defined. + +An eXtensible Output Functions (XOF) based on the Ascon permutation. Just like +the described Ascon-Hash above, its main use case is in constrained +environments, such as IoT devices where traditional cryptographic functions +may be too resource intensive. + +Unless you are interoperating with an existing device which due to resource +constraints can only use Ascon, prefer the more typical XOF SHAKE-128, or +SHAKE-512. + +This XOF is standardized by NIST in SP.800-232. It is not compatible +with earlier versions of the Ascon specification. + +Algorithm specification name: ``Ascon-XOF128`` + BLAKE2b ^^^^^^^^^ diff -Nru botan3-3.7.1+dfsg/doc/api_ref/message_auth_codes.rst botan3-3.12.0+dfsg/doc/api_ref/message_auth_codes.rst --- botan3-3.7.1+dfsg/doc/api_ref/message_auth_codes.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/api_ref/message_auth_codes.rst 2026-05-07 01:38:28.000000000 +0000 @@ -208,9 +208,14 @@ SipHash ~~~~~~~~~~~~ -A modern and very fast PRF. Produces only a 64-bit output. Defaults to -"SipHash(2,4)" which is the recommended configuration, using 2 rounds for each -input block and 4 rounds for finalization. +.. deprecated:: 3.8.0 + +SipHash is primarily designed for hash table randomization and, while not +known to be insecure for message authentication, is not advisable for this +use due to the small output size (just 64 bits). + +Defaults to "SipHash(2,4)" which is the recommended configuration, using 2 +rounds for each input block and 4 rounds for finalization. Available if ``BOTAN_HAS_SIPHASH`` is defined. @@ -224,6 +229,8 @@ X9.19-MAC ~~~~~~~~~~~~ +.. deprecated:: 3.7.0 + A CBC-MAC variant sometimes used in finance. Always uses DES. Sometimes called the "DES retail MAC", also standardized in ISO 9797-1. diff -Nru botan3-3.7.1+dfsg/doc/api_ref/otp.rst botan3-3.12.0+dfsg/doc/api_ref/otp.rst --- botan3-3.7.1+dfsg/doc/api_ref/otp.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/api_ref/otp.rst 2026-05-07 01:38:28.000000000 +0000 @@ -12,14 +12,14 @@ Botan implements the HOTP and TOTP schemes from RFC 4226 and 6238. -Since the range of possible OTPs is quite small, applications must rate limit +Since the range of possible OTP values is quite small, applications must rate limit OTP authentication attempts to some small number per second. Otherwise an attacker -could quickly try all 1000000 6-digit OTPs in a brief amount of time. +could quickly try all 1000000 6-digit values in a brief amount of time. HOTP ^^^^^^ -HOTP generates OTPs that are a short numeric sequence, between 6 and 8 digits +HOTP generates an OTP that is a short numeric sequence, between 6 and 8 digits (most applications use 6 digits), created using the HMAC of a 64-bit counter value. If the counter ever repeats the OTP will also repeat, thus both parties must assure the counter only increments and is never repeated or diff -Nru botan3-3.7.1+dfsg/doc/api_ref/pbkdf.rst botan3-3.12.0+dfsg/doc/api_ref/pbkdf.rst --- botan3-3.7.1+dfsg/doc/api_ref/pbkdf.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/api_ref/pbkdf.rst 2026-05-07 01:38:28.000000000 +0000 @@ -101,24 +101,32 @@ Create a default instance of the password hashing algorithm. Be warned the value returned here may change from release to release. - .. cpp:function:: std::unique_ptr tune( \ + .. cpp:function:: std::unique_ptr tune_params( \ size_t output_len, \ - std::chrono::milliseconds msec, \ - size_t max_memory_usage_mb = 0, \ - std::chrono::milliseconds tuning_msec = std::chrono::milliseconds(10)) const + uint64_t desired_msec, \ + std::optional max_memory_usage_mb = {}, \ + uint64_t tuning_msec = 10) const - Return a password hash instance tuned to run for approximately ``msec`` + Return a password hash instance tuned to run for approximately ``desired_msec`` milliseconds when producing an output of length ``output_len``. (Accuracy may vary, use the command line utility ``botan pbkdf_tune`` to check.) The parameters will be selected to use at most *max_memory_usage_mb* megabytes - of memory, or if left as zero any size is allowed. + of memory, or if left as nullopt any size is allowed. - This function works by runing a short tuning loop to estimate the + This function works by running a short tuning loop to estimate the performance of the algorithm, then scaling the parameters appropriately to hit the target size. The length of time the tuning loop runs can be controlled using the *tuning_msec* parameter. + .. cpp:function:: std::unique_ptr tune( \ + size_t output_len, \ + std::chrono::milliseconds msec, \ + size_t max_memory_usage_mb = 0, \ + std::chrono::milliseconds tuning_msec = std::chrono::milliseconds(10)) const + + A deprecated variant of tune_params. It will be removed in Botan4. + .. cpp:function:: std::unique_ptr from_params( \ size_t i1, size_t i2 = 0, size_t i3 = 0) const diff -Nru botan3-3.7.1+dfsg/doc/api_ref/pkcs11.rst botan3-3.12.0+dfsg/doc/api_ref/pkcs11.rst --- botan3-3.7.1+dfsg/doc/api_ref/pkcs11.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/api_ref/pkcs11.rst 2026-05-07 01:38:28.000000000 +0000 @@ -16,7 +16,8 @@ .. note:: - The Botan PKCS#11 interface is implemented against version v2.40 of the standard. + The Botan PKCS#11 interface is implemented against version v3.2 of the standard. + Versions 2.40 upto 3.2 are supported, but only the 3.2 headers are shipped with Botan. Botan wraps the C PKCS#11 API to provide a C++ PKCS#11 interface. This is done in two levels of abstraction: a low level API (see :ref:`pkcs11_low_level`) and @@ -39,7 +40,9 @@ The PKCS#11 standards committee provides header files (``pkcs11.h``, ``pkcs11f.h`` and ``pkcs11t.h``) which define the PKCS#11 API in the C programming language. These header files could be used directly to access PKCS#11 compatible smart cards or -HSMs. The external header files are shipped with Botan in version v2.4 of the standard. The PKCS#11 low +HSMs. A public domain variant of these header files is shipped with Botan in +version v3.2 (Draft wd13) of the standard. This variant is interchangeable with the original +v3.2 header files of OASIS. The PKCS#11 low level API wraps the original PKCS#11 API, but still allows to access all functions described in the standard and has the advantage that it is a C++ interface with features like RAII, exceptions and automatic memory management. @@ -777,7 +780,7 @@ Unlike the CardOS (4.4, 5.0, 5.3), the aforementioned SO-PIN/PUK is inappropriate for Gemalto (IDPrime MD 3840) cards, as it must be a byte array of length 24. For this reason some of the tests for Gemalto card involving - SO-PIN will fail. You run into a risk of exceding login attempts and as a + SO-PIN will fail. You run into a risk of exceeding login attempts and as a result locking your card! Currently, specifying pin via command-line option is not implemented, and therefore the desired PIN must be modified in the header src/tests/test_pkcs11.h: @@ -798,7 +801,7 @@ Test results +-------------------------------------+-------------------------------------------+---------------------------------------------------+---------------------------------------------------+---------------------------------------------------+---------------------------------------------------+ -| Smartcard | Status | OS | Midleware | Botan | Errors | +| Smartcard | Status | OS | Middleware | Botan | Errors | +=====================================+===========================================+===================================================+===================================================+===================================================+===================================================+ | CardOS 4.4 | mostly works | Windows 10, 64-bit, version 1709 | API Version 5.4.9.77 (Cryptoki v2.11) | 2.4.0, Cryptoki v2.40 | [50]_ | +-------------------------------------+-------------------------------------------+---------------------------------------------------+---------------------------------------------------+---------------------------------------------------+---------------------------------------------------+ @@ -887,7 +890,7 @@ - rng_add_entropy [5]_ -.. [53] Failing operations for CardOS 5.3 (middelware 5.5.1) +.. [53] Failing operations for CardOS 5.3 (middleware 5.5.1) - ecdh_privkey_export [2]_ - ecdh_generate_private_key [35]_ diff -Nru botan3-3.7.1+dfsg/doc/api_ref/pubkey.rst botan3-3.12.0+dfsg/doc/api_ref/pubkey.rst --- botan3-3.7.1+dfsg/doc/api_ref/pubkey.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/api_ref/pubkey.rst 2026-05-07 01:38:28.000000000 +0000 @@ -95,7 +95,7 @@ Return an object containing the public key corresponding to this private key. Prefer this over the (deprecated) implicit conversion of a private key to - a public key currently possible due to an inheritence relation. + a public key currently possible due to an inheritance relation. .. cpp:function:: secure_vector private_key_info() const @@ -107,7 +107,7 @@ .. cpp:function:: secure_vector private_key_bits() const - Return the serialization of the private key, cooresponding to the + Return the serialization of the private key, corresponding to the `PrivateKey` field of a PKCS #8 `PrivateKeyInfo` structure. See :rfc:`5208` for details. @@ -172,9 +172,12 @@ ~~~~~~~~~~~~~~~~~ Post-quantum key encapsulation scheme based on (structured) lattices. This -algorithm is standardized in FIPS 203. Decapsulation keys are always stored and -expanded from the 64-byte private random seeds (``d || z``), loading the -expanded key format specified in FIPS 203 is explicitly not supported. +algorithm is standardized in FIPS 203. New decapsulation keys are stored and +expanded from the 64-byte private random seeds (``d || z``). +Keys imported as seeds are always serialized as seeds, while keys imported in +expanded format (as specified in FIPS 203) are serialized in expanded format. +Exporting seeds as expanded keys is supported using ML-KEM private key-specific +methods. Support for ML-KEM is implemented in the module ``ml_kem``. @@ -206,6 +209,14 @@ signatures, then the whole scheme becomes insecure, and signatures can be forged. + .. warning:: + + Maintaining consistent state without replays is extremely difficult, + especially when multiple machines are involved. Even a single error will + compromise the entire signature scheme. XMSS should only be used in an + environment carefully designed to maintain consistent state. Prefer + the stateless SLH-DSA in new designs. + HSS-LMS ~~~~~~~ @@ -214,6 +225,14 @@ each signature. If the same state is ever used to generate two signatures, then the whole scheme becomes insecure, and signatures can be forged. + .. warning:: + + Maintaining consistent state without replays is extremely difficult, + especially when multiple machines are involved. Even a single error will + compromise the entire signature scheme. HSS-LMS should only be used in an + environment carefully designed to maintain consistent state. Prefer + the stateless SLH-DSA in new designs. + SLH-DSA (FIPS 205) ~~~~~~~~~~~~~~~~~~ @@ -349,7 +368,7 @@ Generate a new X448 private key -Others require additionally specfiying which curve to use. First create a +Others require additionally specifying which curve to use. First create a relevant :cpp:class:`EC_Group` using for example :cpp:func:`EC_Group::from_name` or :cpp:func:`EC_Group::from_OID`. Then pass it to the private key constructor. If the choice of group is not otherwise mandated by your @@ -890,7 +909,7 @@ .. cpp:function:: PK_Signer(const Private_Key& key, \ const std::string& padding, \ - Signature_Format format = Siganture_Format::Standard) + Signature_Format format = Signature_Format::Standard) Constructs a new signer object for the private key *key* using the hash/padding specified in *padding*. The key must support signature operations. In @@ -1003,7 +1022,7 @@ Botan implements the following signature algorithms: -1. RSA. Requires a :ref:`padding scheme ` as parameter. +1. RSA. Requires a :ref:`padding scheme ` as parameter. #. DSA. Requires a :ref:`hash function ` as parameter. #. ECDSA. Requires a :ref:`hash function ` as parameter. #. ECGDSA. Requires a :ref:`hash function ` as parameter. @@ -1038,7 +1057,7 @@ .. literalinclude:: /../src/examples/ecdsa.cpp :language: cpp -.. _emsa: +.. _rsa_padding: RSA signature padding schemes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1077,10 +1096,10 @@ ``PKCS1v15(Raw)``, ``PKCS1v15(Raw,MD5)``, -EMSA-PSS -"""""""" +Probabilistic signature scheme (PSS) +""""""""""""""""""""""""""""""""""""""" -Probabilistic signature scheme (PSS) (called EMSA4 in IEEE 1363). +Called EMSA4 in IEEE 1363. - Name: ``PSS`` - Deprecated aliases: ``EMSA-PSS``, ``PSSR``, ``PSS-MGF1``, ``EMSA4`` @@ -1140,28 +1159,28 @@ X9.31 """"" +Padding scheme from ANSI X9.31. Called EMSA2 in IEEE 1363. + .. deprecated:: 3.7.0 X9.31 signatures are obsolete, and support for it is deprecated -EMSA from X9.31 (EMSA2 in IEEE 1363). - - Name: ``X9.31`` - Deprecated aliases: ``EMSA2``, ``EMSA_X931`` - Parameters specification: ``()`` - Example: ``X9.31(SHA-256)`` -Raw EMSA +Raw """""""" Sign inputs directly with no hashing or padding .. warning:: - This exists as an escape hatch allowing an application to define - some protocol-specific padding scheme. Don't use this unless you - know what you are doing. + This exists as an escape hatch allowing an application to define some + protocol-specific padding scheme, and using it in a naive way is completely + insecure. Don't use this unless you know what you are doing. - Name: ``Raw`` - Parameters specification: @@ -1178,10 +1197,12 @@ For many signature schemes including ECDSA and DSA, simply naming a hash function like ``SHA-256`` is all that is required. -Previous versions of Botan required using a hash specifier -like ``EMSA1(SHA-256)`` when generating or verifying ECDSA/DSA signatures, -with the specified hash. -The ``EMSA1`` was a reference to a now obsolete IEEE standard. +.. note:: + + Previous versions of Botan required using a hash specifier like + ``EMSA1(SHA-256)`` when generating or verifying ECDSA/DSA signatures, with + the specified hash. The ``EMSA1`` was a reference to a now obsolete IEEE + standard. Parameters specification: @@ -1309,7 +1330,7 @@ The *peer_key* parameter must be the public key associated with the other party. - The shared key will be of length *key_len*. If the KDF cannot accomodate + The shared key will be of length *key_len*. If the KDF cannot accommodate outputs of this size (only likely for very large values, or if using KDF1), an exception will be thrown. If a KDF is not in use ("Raw" KDF), *key_len* is ignored and this function will always return directly what the agreement @@ -1566,6 +1587,15 @@ #. XMSS-SHAKE_10_512 #. XMSS-SHAKE_16_512 #. XMSS-SHAKE_20_512 +#. XMSS-SHA2_10_192 +#. XMSS-SHA2_16_192 +#. XMSS-SHA2_20_192 +#. XMSS-SHAKE256_10_256 +#. XMSS-SHAKE256_16_256 +#. XMSS-SHAKE256_20_256 +#. XMSS-SHAKE256_10_192 +#. XMSS-SHAKE256_16_192 +#. XMSS-SHAKE256_20_192 The algorithm name contains the hash function name, tree height and digest width defined by the corresponding parameter set. Choosing `XMSS-SHA2_10_256` diff -Nru botan3-3.7.1+dfsg/doc/api_ref/python.rst botan3-3.12.0+dfsg/doc/api_ref/python.rst --- botan3-3.7.1+dfsg/doc/api_ref/python.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/api_ref/python.rst 2026-05-07 01:38:28.000000000 +0000 @@ -46,6 +46,14 @@ no matter how many 'system' rng instances are created. Thus it is easy to use the RNG in a one-off way, with `botan.RandomNumberGenerator().get(32)`. + For some use cases it can be useful to provide a custom RNG implementation. + Use 'custom' as the rng_type and provide the ``get_callback=`` and + ``add_entropy_callback=`` arguments. The latter is optional. + ``get_callback`` takes an integer and is expected to return a bytes object + with the requested number of random bytes. + ``add_entropy_callback`` takes a bytes object containing entropy bytes and + is expected to add the given entropy to the RNG. + When Botan is configured with TPM 2.0 support, also 'tpm2' is allowed to instantiate a TPM-backed RNG. Note that this requires passing additional named arguments ``tpm2_context=`` with a ``TPM2Context`` and @@ -138,7 +146,7 @@ Previously ``cipher`` - The algorithm is spcified as a string (eg 'AES-128/GCM', + The algorithm is specified as a string (eg 'AES-128/GCM', 'Serpent/OCB(12)', 'Threefish-512/EAX'). Set the second param to False for decryption @@ -344,6 +352,10 @@ vary depending on the algorithm. For example RSA public modulus can be extracted with ``rsa_key.get_field("n")``. + .. py:method:: object_identifier() + + Returns the associated OID + .. py:method:: fingerprint(hash = 'SHA-256') Returns a hash of the public key @@ -373,6 +385,10 @@ "curve25519" and "x448" (which are actually completely distinct key types with a non-standard encoding). + .. py:classmethod:: create_ec(algo, ec_group, rng) + + Creates a new ec private key. + .. py:classmethod:: load(val, passphrase="") Return a private key (DER or PEM formats accepted) @@ -462,6 +478,17 @@ extracted with ``rsa_key.get_field("p")``. This function can also be used to extract the public parameters. + .. py:method:: object_identifier() + + Returns the associated OID + + .. py:method:: stateful_operation() + Return whether the key is stateful or not. + + .. py:method:: remaining_operations() + If the key is stateful, return the number of remaining operations. + Raises an exception if the key is not stateful. + Public Key Operations ---------------------------------------- @@ -563,6 +590,100 @@ Return the greatest common divisor of ``self`` and ``other`` +Object Identifiers (OID) +------------------------------------- +.. versionadded:: 3.8.0 + +.. py:class:: OID(object) + + .. py:classmethod:: from_string(value) + + Create a new OID from dot notation or from a known name + + .. py:method:: to_string() + + Export the OID in dot notation + + .. py:method:: to_name() + + Export the OID as a name if it has one, else in dot notation + + .. py:method:: register(name) + + Register the OID so that it may later be retrieved by the given name + + +EC Groups +------------------------------------- +.. versionadded:: 3.8.0 + +.. py:class:: ECGroup(object) + + .. py:classmethod:: supports_application_specific_group() + + Returns true if in this build configuration it is possible to register an application specific elliptic curve + + .. py:classmethod:: supports_named_group(name) + + Returns true if in this build configuration ECGroup.from_name(name) will succeed + + .. py:classmethod:: from_params(oid, p, a, b, base_x, base_y, order) + + Creates a new ECGroup from ec parameters + + .. py:classmethod:: from_ber(ber) + + Creates a new ECGroup from a BER blob + + .. py:classmethod:: from_pem(pem) + + Creates a new ECGroup from a pem encoding + + .. py:classmethod:: from_oid(oid) + + Creates a new ECGroup from a group named by an OID + + .. py:classmethod:: from_name(name) + + Creates a new ECGroup from a common group name + + .. py:method:: to_der() + + Export the group in DER encoding + + .. py:method:: to_pem() + + Export the group in PEM encoding + + .. py:method:: get_curve_oid() + + Get the curve OID + + .. py:method:: get_p() + + Get the prime modulus of the field + + .. py:method:: get_a() + + Get the a parameter of the elliptic curve equation + + .. py:method:: get_b() + + Get the b parameter of the elliptic curve equation + + .. py:method:: get_g_x() + + Get the x coordinate of the base point + + .. py:method:: get_g_y() + + Get the y coordinate of the base point + + .. py:method:: get_order() + + Get the order of the base point + + Format Preserving Encryption (FE1 scheme) ----------------------------------------- .. versionadded:: 2.8.0 @@ -688,7 +809,7 @@ crls=None) Verify a certificate. Returns 0 if validation was successful, returns a positive error code - if the validation was unsuccesful. + if the validation was unsuccessful. ``intermediates`` is a list of untrusted subauthorities. diff -Nru botan3-3.7.1+dfsg/doc/api_ref/tls.rst botan3-3.12.0+dfsg/doc/api_ref/tls.rst --- botan3-3.7.1+dfsg/doc/api_ref/tls.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/api_ref/tls.rst 2026-05-07 01:38:28.000000000 +0000 @@ -547,7 +547,7 @@ with full flexibility to handle session objects. More detail can be found in the API documentation inline. -.. cpp:class:: TLS::Session_Mananger +.. cpp:class:: TLS::Session_Manager .. cpp:function:: void store(const Session& session, const Session_Handle& handle) @@ -607,7 +607,7 @@ Limits the maximum number of saved sessions to *max_sessions*. -Noop Session Mananger +Noop Session Manager ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The ``TLS::Session_Manager_Noop`` implementation does not save @@ -695,19 +695,19 @@ No export key exchange mechanisms or ciphersuites are supported by botan. The null encryption ciphersuites (which provide only - authentication, sending data in cleartext) are also not supported - by the implementation and cannot be negotiated. + authentication, sending data in cleartext) are only supported if + they are explicitly enabled at build time by activating the + tls_null module. Cipher names without an explicit mode refers to CBC+HMAC ciphersuites. - Default value: "ChaCha20Poly1305", "AES-256/GCM", "AES-128/GCM" + Default value: "AES-256/GCM", "AES-128/GCM", "ChaCha20Poly1305" Also allowed: "AES-256", "AES-128", "AES-256/CCM", "AES-128/CCM", "AES-256/CCM(8)", "AES-128/CCM(8)", "Camellia-256/GCM", "Camellia-128/GCM", "ARIA-256/GCM", "ARIA-128/GCM" - Also allowed (though currently experimental): "AES-128/OCB(12)", - "AES-256/OCB(12)" + Also allowed (though currently experimental): "AES-256/OCB(12)" In versions up to 2.8.0, the CBC and CCM ciphersuites "AES-256", "AES-128", "AES-256/CCM" and "AES-128/CCM" were enabled by default. @@ -793,17 +793,22 @@ .. cpp:function:: std::vector key_exchange_groups() const Return a list of ECC curve and DH group TLS identifiers we are willing to use, in order of preference. - The default ordering puts the best performing ECC first. Default: - Group_Params::X25519, - Group_Params::SECP256R1, Group_Params::BRAINPOOL256R1, - Group_Params::SECP384R1, Group_Params::BRAINPOOL384R1, - Group_Params::SECP521R1, Group_Params::BRAINPOOL512R1, - Group_Params::FFDHE_2048, Group_Params::FFDHE_3072, Group_Params::FFDHE_4096, - Group_Params::FFDHE_6144, Group_Params::FFDHE_8192 - No other values are currently defined. + Group_Params::X25519, + Group_Params::SECP256R1, + Group_Params_Code::HYBRID_X25519_ML_KEM_768, + Group_Params_Code::HYBRID_SECP256R1_ML_KEM_768, + Group_Params_Code::HYBRID_SECP384R1_ML_KEM_1024, + Group_Params::X448, + Group_Params::SECP384R1, + Group_Params::SECP521R1, + Group_Params::BRAINPOOL256R1, + Group_Params::BRAINPOOL384R1, + Group_Params::BRAINPOOL512R1, + Group_Params::FFDHE_2048, + Group_Params::FFDHE_3072, .. cpp:function:: std::vector key_exchange_groups_to_offer() const @@ -1134,6 +1139,7 @@ to be in the future standardized by IETF * ``HYBRID_SECP256R1_ML_KEM_768`` ("secp256r1/ML-KEM-768") + * ``HYBRID_SECP384R1_ML_KEM_1024`` ("secp384r1/ML-KEM-1024") * ``HYBRID_X25519_ML_KEM_768`` ("x25519/ML-KEM-768") * Pure ML-KEM as documented in IETF draft ``draft-connolly-tls-mlkem-key-agreement`` @@ -1180,6 +1186,21 @@ .. literalinclude:: /../src/examples/tls_custom_curves_client.cpp :language: cpp +Special Case: Custom ECDH provider for TLS 1.2 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Users that wish to implement a custom ECDH provider for TLS 1.2 (e.g. to offload the implementation of +standard curves to some crypto hardware), must take the negotiated ECC point encoding (compressed vs. +uncompressed) into account. They should use the special callback ``tls12_generate_ephemeral_ecdh_key`` +which provides the desired ECC point encoding as an input parameter. + +This special callback is called *only for TLS 1.2* and *only for ECDH using standardized curves* that +Botan is aware of (for instance ``secp256r1``, ``brainpool256r1`` and such). Explicitly, that *does not +include X25519 and X448* as those algorithms have a well-defined point format. TLS 1.3 does not allow +negotiating the ECC point encoding (see `RFC 8446 Section 4.2.8.2 `_) +and thus does not call this callback either. Support for compressed points in TLS 1.2 is deprecated in +Botan and this callback will disappear when it is removed in a future release. + .. _tls_asio_stream: TLS Stream @@ -1335,9 +1356,9 @@ Aside of the modern coroutines-based approach, the ASIO stream may also be used in a more traditional way, using callback handler methods instead of coroutines. -Also, this example shows how to use a custom :cpp:class:`Credentials_Manager` -and pass it to the :cpp:class:`TLS::Stream` via a :cpp:class:`TLS::Context` -object. +Also, this example shows how to use custom :cpp:class:`Credentials_Manager` and +:cpp:class:`TLS::Policy` subclasses, passing them to the :cpp:class:`TLS::Stream` +via a :cpp:class:`TLS::Context` object. .. literalinclude:: /../src/examples/tls_stream_client.cpp :language: cpp @@ -1368,7 +1389,7 @@ random seed, and HMAC'ing it to produce a 256-bit value. This means for any one master key as many as 2\ :sup:`128` GCM keys can be created. This is done because NIST recommends that when using random nonces no one GCM key be used to -encrypt more than 2\ :sup:`32` messages (to avoid the possiblity of nonce +encrypt more than 2\ :sup:`32` messages (to avoid the possibility of nonce reuse). A random 96-bit nonce is created and included in the header. diff -Nru botan3-3.7.1+dfsg/doc/api_ref/tpm.rst botan3-3.12.0+dfsg/doc/api_ref/tpm.rst --- botan3-3.7.1+dfsg/doc/api_ref/tpm.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/api_ref/tpm.rst 2026-05-07 01:38:28.000000000 +0000 @@ -35,7 +35,7 @@ ~~~~~~~~~~~~~~~ The TPM context is the main entry point for all TPM operations. Also, it -provides authorative information about the TPM's capabilities and allows +provides authoritative information about the TPM's capabilities and allows persisting and evicting keys into the TPM's NVRAM. .. cpp:class:: Botan::TPM2::Context diff -Nru botan3-3.7.1+dfsg/doc/api_ref/versions.rst botan3-3.12.0+dfsg/doc/api_ref/versions.rst --- botan3-3.7.1+dfsg/doc/api_ref/versions.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/api_ref/versions.rst 2026-05-07 01:38:28.000000000 +0000 @@ -13,7 +13,7 @@ The library has functions for checking compile-time and runtime versions. -The build-time version information is defined in `botan/build.h` +The build-time version information is defined in ``botan/build.h`` .. c:macro:: BOTAN_VERSION_MAJOR @@ -30,8 +30,13 @@ .. c:macro:: BOTAN_VERSION_DATESTAMP Expands to an integer of the form YYYYMMDD if this is an official - release, or 0 otherwise. For instance, 1.10.1, which was released - on July 11, 2011, has a `BOTAN_VERSION_DATESTAMP` of 20110711. + release, or 0 otherwise. For instance, 3.6.1, which was released + on October 26, 2024, has a ``BOTAN_VERSION_DATESTAMP`` of 20241026. + + .. warning:: + + This macro is deprecated and will be removed in Botan4. Use + :cpp:func:`version_datestamp` .. c:macro:: BOTAN_DISTRIBUTION_INFO @@ -42,17 +47,26 @@ to specify any distribution-specific patches. If no value is given at build time, the value is the string "unspecified". + .. warning:: + + This macro is deprecated and will be removed in Botan4. Use + :cpp:func:`version_distribution_info` + .. c:macro:: BOTAN_VERSION_VC_REVISION .. versionadded:: 1.10.1 A macro expanding to a string that is set to a revision identifier corresponding to the source, or "unknown" if this could not be - determined. It is set for all official releases, and for builds that - originated from within a git checkout. + determined. It is set for all official releases. + + .. warning:: + + This macro is deprecated and will be removed in Botan4. Use + :cpp:func:`version_vc_revision` The runtime version information, and some helpers for compile time -version checks, are included in `botan/version.h` +version checks, are included in ``botan/version.h`` .. cpp:function:: std::string version_string() @@ -76,26 +90,31 @@ Return the datestamp of the release (or 0 if the current version is not an official release). -.. cpp:function:: std::string runtime_version_check(uint32_t major, uint32_t minor, uint32_t patch) +.. cpp:function:: std::optional version_vc_revision() + + .. versionadded:: 3.8 + + Returns a string that is set to a revision identifier corresponding to the + source, or ``nullopt`` if this could not be determined. It is set for all + official releases, and for builds that originated from within a git checkout. - Call this function with the compile-time version being built against, eg:: +.. cpp:function:: std::optional version_distribution_info() - Botan::runtime_version_check(BOTAN_VERSION_MAJOR, BOTAN_VERSION_MINOR, BOTAN_VERSION_PATCH) + .. versionadded:: 3.8 - It will return an empty string if the versions match, or otherwise - an error message indicating the discrepancy. This only is useful in - dynamic libraries, where it is possible to compile and run against - different versions. + Return any string that is set at build time using the ``--distribution-info`` + option. It allows a packager of the library to specify any distribution-specific + patches. If no value is given at build time, returns ``nullopt``. .. c:macro:: BOTAN_VERSION_CODE_FOR(maj,min,patch) Return a value that can be used to compare versions. The current (compile-time) version is available as the macro - `BOTAN_VERSION_CODE`. For instance, to choose one code path for - version 2.1.0 and later, and another code path for older releases:: + ``BOTAN_VERSION_CODE``. For instance, to choose one code path for + version 3.4.0 and later, and another code path for older releases:: - #if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(2,1,0) - // 2.1+ code path + #if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(3,4,0) + // 3.4+ code path #else // code path for older versions #endif diff -Nru botan3-3.7.1+dfsg/doc/api_ref/x509.rst botan3-3.12.0+dfsg/doc/api_ref/x509.rst --- botan3-3.7.1+dfsg/doc/api_ref/x509.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/api_ref/x509.rst 2026-05-07 01:38:28.000000000 +0000 @@ -166,6 +166,11 @@ Returns true if ``get_attribute`` or ``get_first_attribute`` will return a value. + .. cpp:function:: const std::vector>& dn_info() const + + Return the DN components as a vector of OID and ASN1_String pairs. Note that + the order of the components is preserved only when using the initializer list constructor. + .. cpp:function:: std::vector get_attribute(const std::string& attr) const Return all attributes associated with a certain attribute type. @@ -193,7 +198,10 @@ Add an attribute to a DN using an OID instead of string-valued attribute type. The ``X509_DN`` type also supports iostream extraction and insertion operators, -for formatted input and output. +for formatted input and output. Note that the class has deprecated constructors +taking a ``std::multimap``; use the initializer list constructor instead. When +using the deprecated constructors, the order of the DN components is not preserved, +which can violate RFC 5280 requirements. X.509v3 Extensions ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -376,6 +384,10 @@ Adds given certificate to the store + .. cpp:function:: Certificate_Store_In_Memory(const X509_Certificate& cert, const X509_CRL& crl) + + Adds given certificate and CRL to the store + .. cpp:function:: Certificate_Store_In_Memory() Create an empty store diff -Nru botan3-3.7.1+dfsg/doc/api_ref/zfec.rst botan3-3.12.0+dfsg/doc/api_ref/zfec.rst --- botan3-3.7.1+dfsg/doc/api_ref/zfec.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/api_ref/zfec.rst 2026-05-07 01:38:28.000000000 +0000 @@ -28,7 +28,7 @@ size. An example application that adds padding and a hash checksum is available -in ``src/cli/zfec.cpp`` and invokable using ``botan fec_encode`` and +in ``src/cli/zfec.cpp`` and invocable using ``botan fec_encode`` and ``botan fec_decode``. .. cpp:class:: ZFEC diff -Nru botan3-3.7.1+dfsg/doc/authors.txt botan3-3.12.0+dfsg/doc/authors.txt --- botan3-3.7.1+dfsg/doc/authors.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/authors.txt 2026-05-07 01:38:28.000000000 +0000 @@ -5,6 +5,7 @@ Allan L. Bazinet Alon Bar-Lev Amos Treiber (Rohde & Schwarz Cybersecurity) +André Schomburg (Volkswagen AG) Andrew Moon Antonio Coratelli Atanas Filyanov @@ -22,6 +23,7 @@ Daniel Neus (Rohde & Schwarz Cybersecurity) Daniel Seither (Kullo GmbH) Daniel Wyatt +Dirk Dobkowitz (Volkswagen AG) Elektrobit Automotive GmbH Eric Cornelius Erwan Chaussy @@ -35,6 +37,7 @@ Florent Le Coz Francis Dupont Frank Schoenmann +Frederik Dornemann (CARIAD SE) Google Inc Gustavo Serra Scalet guywithcrookedface @@ -51,10 +54,12 @@ Jose Luis Pereira (Fyde Inc.) Juraj Somorovsky (Hackmanit GmbH) Justin Karneges +Kagan Can Sit Kai Michaelis (Rohde & Schwarz Cybersecurity) Kirill A. Korinsky Konstantinos Kolelis Krzysztof Kwiatkowski +Lars Dürkop (CARIAD SE) Lauri Nurmi Luca Piccarreta Manuel Glaser (Rohde & Schwarz Cybersecurity) @@ -86,6 +91,7 @@ Robert Dailey Ryuhei Mori schregger +Sebastian Ahrens (Volkswagen AG) Sergii Cherkavskyi seu Shlomi Fish @@ -106,3 +112,4 @@ Yves Jerschow Zoltan Gyarmati 0xdefaced +polarnis diff -Nru botan3-3.7.1+dfsg/doc/building.rst botan3-3.12.0+dfsg/doc/building.rst --- botan3-3.7.1+dfsg/doc/building.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/building.rst 2026-05-07 01:38:28.000000000 +0000 @@ -111,7 +111,7 @@ Common Build Targets -------------------- -Build everthing that is configured:: +Build everything that is configured:: $ make all @@ -197,19 +197,48 @@ On macOS -------------- -A build on macOS works much like that on any other Unix-like system. +A standard build on macOS works much like that on any other Unix-like system. -To build a universal binary for macOS, for older macOs releases, -you need to set some additional build flags. -Do this with the `configure.py` flag `--cc-abi-flags`:: +One notable difference with macOS is the common usage of "universal binaries", +which is effectively a multiarch binary. This was used first for the PowerPC to +x86 transition, and more recently for the x86 to Aarch64 transition. + +Building a universal binary is a bit trickier for Botan compared with a standard +application, as the library makes use of many architecture specific extensions, +for example AES-NI and AVX2 on x86, and NEON and the ARMv8 crypto extensions on +Aarch64. Botan's build system also assumes that it is knowable at setup time +which files are to be compiled. + +Typically (for software with no architecture dependent code) a universal binary +is built by adding additional compilation flags that look something like +``-force_cpusubtype_ALL -arch x86_64 -arch arm64``. This effectively causes XCode +to compile each file twice, once for x86_64 and again for Aarch64. For most source +files this works fine, but for architecture-specific files it will result in errors +when code specific to one architecture is encountered when compiling for a different +architecture, resulting in errors like:: - --cc-abi-flags="-force_cpusubtype_ALL -mmacosx-version-min=10.4 -arch i386 -arch ppc" - - -for mac M1 on arm64, you can build the x86_64 arch version via Rosetta separately. -Do this with with `arch -x86_64 configure.py --library-suffix=-x86_64` -Then using lipo to create a fat binary. -`lipo -create libbotan-arm64.dylib libbotan-x86_64.dylib -o libbotan.dylib` + $ make + ... + error: unknown target CPU 'armv8.2-a+sha3' + note: valid target CPU values are: ... + +There are currently two ways of proceeding. + +The first is to use ``--cpu=generic``. This disables all architecture specific +code, which has performance implications, especially for algorithms with +dedicated hardware support like AES. This can be alleviated somewhat by making +sure the CommonCrypto provider (module ``commoncrypto``) is built, since then +Botan offloads many of these specific operations to CommonCrypto, which will be +able to use the CPU instructions. + +The second, and recommended, approach is to build twice and use ``lipo`` to +combine the two binaries. This looks something like:: + +$ ./configure.py --with-build-dir=botan_x86_64 --disable-cc-tests --build-targets=shared --cpu=x86_64 --extra-cxxflags='-arch x86_64' --ldflags='-arch x86_64' --library-suffix=-x86_64 +$ make -j8 -f botan_x86_64/Makefile +$ ./configure.py --with-build-dir=botan_aarch64 --disable-cc-tests --build-targets=shared --cpu=aarch64 --extra-cxxflags='-arch arm64' --ldflags='-arch arm64' --library-suffix=-aarch64 +$ make -j8 -f botan_aarch64/Makefile +$ lipo -create botan_aarch64/libbotan-3-aarch64.dylib botan_x86_64/libbotan-3-x86_64.dylib -o libbotan-3.dylib On Windows -------------- @@ -227,7 +256,7 @@ $ nmake check $ nmake install -Micosoft's ``nmake`` does not support building multiple jobs in parallel, which +Microsoft's ``nmake`` does not support building multiple jobs in parallel, which is unfortunate when building on modern multicore machines. It is possible to use the (somewhat unmaintained) `Jom `_ build tool, which is a ``nmake`` compatible build system that supports parallel builds. Alternately, @@ -318,19 +347,8 @@ $ ./configure.py --os=android --cc=clang --cpu=arm64 $ make -If you are building for mobile development consider restricting the build -to only what you need (see :ref:`minimized_builds`) - -Docker -^^^^^^^^^^^ - -To build android version, there is the possibility to use -the docker way:: - - sudo ANDROID_SDK_VER=29 ANDROID_ARCH=aarch64 src/scripts/docker-android.sh - -This will produce the docker-builds/android folder containing -each architecture compiled. +If you are building for mobile development, consider restricting the build +to only what you need (see :ref:`minimized_builds`) to minimize code size. Emscripten (WebAssembly) --------------------------- @@ -344,6 +362,10 @@ along with a static archive ``libbotan-3.a`` which can be linked with other modules. +To use the Wasm SIMD128 extension for improved performance of certain +algorithms (see ``hardware_acceleration.rst``), ensure that you pass the +``-msimd128`` compilation flag. + Supporting Older Distros -------------------------- @@ -385,6 +407,13 @@ would ordinarily use, along with the option ``--amalgamation``. This will create two (rather large) files, ``botan_all.h`` and ``botan_all.cpp``. +.. warning:: + + Compiling a single 120K+ line C++ file containing a variety of + carefully optimized SIMD and inline asm has a way of triggering + compiler bugs. When using an amalgamation build, be sure to build + and run the test suite! + .. note:: The library will as usual be configured to target some specific operating @@ -469,6 +498,10 @@ inserted into ``build/build.h`` which is (indirectly) included into every Botan header and source file. +.. warning:: + + This option is deprecated and is planned to be removed in 3.9.0 + Enabling or Disabling Use of Certain OS Features ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -496,28 +529,19 @@ .. note:: Disabling ``dyn_load`` module will also disable the PKCS #11 wrapper, which relies on dynamic loading. -Configuration Parameters +Feature Check Macros ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -There are some configuration parameters which you may want to tweak -before building the library. These can be found in ``build.h``. This -file is overwritten every time the configure script is run (and does -not exist until after you run the script for the first time). - -Also included in ``build/build.h`` are macros which let applications -check which features are included in the current version of the -library. All of them begin with ``BOTAN_HAS_``. For example, if -``BOTAN_HAS_RSA`` is defined, then an application knows that this -version of the library has RSA available. - -``BOTAN_MP_WORD_BITS``: This macro controls the size of the words used for -calculations with the MPI implementation in Botan. It must be set to either 32 -or 64 bits. The default is chosen based on the target processor. There is -normally no reason to change this. - -``BOTAN_DEFAULT_BUFFER_SIZE``: This constant is used as the size of -buffers throughout Botan. The default should be fine for most -purposes, reduce if you are very concerned about runtime memory usage. +When ``build.h`` is created, a set of macros are defined which can be used for +compile-time feature checks. + +Each of these macros has the form ``BOTAN_HAS_FOO``, for example +``BOTAN_HAS_RSA`` or ``BOTAN_HAS_TLS_13``. Each of these macros also has a +value, which corresponds to a YYYYMMDD date code integer. If a user-visible +change is made to a module (for example adding a particular feature) the date +code is set to a new value. This can be useful for applications if they need to +check that both a feature is enabled in general and that it supports some +specific feature that was added in a particular change. Building Applications ---------------------------------------- @@ -593,10 +617,13 @@ -------------------- Many developers wish to configure a minimized build which contains only the -specific features their application will use. In general this is straighforward: +specific features their application will use. In general this is straightforward: use ``--minimized-build`` plus ``--enable-modules=`` to enable the specific modules -you wish to use. Any such configurations should build and pass the tests; if you -encounter a case where it doesn't please file an issue. +you wish to use. It is possible to use an asterisk (``*``) as a wildcard for +related modules. For instance to enable all available AES implementations, use +``--enable-modules='aes*'`` which will enable ``aes_ni``, ``aes_power8``, etc. +Any such configurations should build and pass the tests; if you encounter a case +where it doesn't please file an issue. The only trick is knowing which features you want to enable. The most common difficulty comes with entropy sources. By default, none are enabled, which means @@ -707,14 +734,6 @@ Specify a compiler cache (like ccache) to use for each compiler invocation. -``--with-endian=ORDER`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The parameter should be either "little" or "big". If not used then if -the target architecture has a default, that is used. Otherwise left -unspecified, which causes less optimal codepaths to be used but will -work on either little or big endian. - ``--with-os-features=FEAT`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -760,75 +779,16 @@ Disable all deprecated modules and features. Note that individual deprecated modules can be explicitly disabled using ``--disable-modules=MODS``. -``--disable-sse2`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Disable use of SSE2 intrinsics - -``--disable-ssse3`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Disable use of SSSE3 intrinsics - -``--disable-sse4.1`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Disable use of SSE4.1 intrinsics - -``--disable-sse4.2`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Disable use of SSE4.2 intrinsics - -``--disable-avx2`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Disable use of AVX2 intrinsics - -``--disable-bmi2`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Disable use of BMI2 intrinsics - -``--disable-rdrand`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Disable use of RDRAND intrinsics - -``--disable-rdseed`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Disable use of RDSEED intrinsics - -``--disable-aes-ni`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Disable use of AES-NI intrinsics - -``--disable-sha-ni`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Disable use of SHA-NI intrinsics - -``--disable-altivec`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Disable use of AltiVec intrinsics - ``--disable-neon`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Disable use of NEON intrinsics - -``--disable-armv8crypto`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Disable use of ARMv8 Crypto intrinsics - -``--disable-powercrypto`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Disable use of POWER Crypto intrinsics +Disable use of ARM NEON intrinsics at compile time. This is needed to support +certain distributions which still support obsolete ARMv7 cores that don't +support NEON and which, for whatever reason, completely disable support for NEON +in their toolchains. For ordinary usage this is not necessary; the NEON using +code will be compiled and simply not used if at runtime NEON support cannot be +detected. This option is supported only for 32-bit ARM processors; Aarch64 +requires NEON and for such targets this option is ignored. ``--system-cert-bundle=PATH`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -857,6 +817,13 @@ Disable stack smashing protections. **not recommended** +``--enable-stack-scrubbing`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Enable scrubbing of stack frames that were used for cryptographic calculations +on potentially sensitive data. At the moment, this is supported exclusively on +GCC 14 and newer. + ``--with-coverage-info`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -1038,7 +1005,7 @@ Additional modules can be enabled if not prohibited by the policy. Currently available policies include ``bsi``, ``nist`` and ``modern``:: - $ ./configure.py --module-policy=bsi --enable-modules=tls,xts + $ ./configure.py --module-policy=bsi --enable-modules=tls13_pqc,xts ``--enable-modules=MODS`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -1053,7 +1020,7 @@ ``--minimized-build`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Start with the bare minimum. This is mostly useful in conjuction with +Start with the bare minimum. This is mostly useful in conjunction with ``--enable-modules`` to get a build that has just the features a particular application requires. @@ -1138,6 +1105,22 @@ Set the include file installation dir. +``--without-include-namespace`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +By default, the header files (e.g. ``botan/hex.h``) are installed into an +additional subdirectory named ``botan-``. This option causes them to be +installed directly into ```` instead. + +This option is not needed for normal usage and is only required in order to work +around limitations in certain package managers. + +``--cmakeconfigdir=DIR`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Set the CMake config (botan-config.cmake, botan-config-version.cmake) installation dir. +Defaults to ``/cmake/Botan-``. + ``--list-modules`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff -Nru botan3-3.7.1+dfsg/doc/cli.rst botan3-3.12.0+dfsg/doc/cli.rst --- botan3-3.7.1+dfsg/doc/cli.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/cli.rst 2026-05-07 01:38:28.000000000 +0000 @@ -173,33 +173,34 @@ X.509 ---------------------------------------------- -``gen_pkcs10 key CN --country= --organization= --ca --path-limit=1 --email= --dns= --ext-ku= --key-pass= --hash=SHA-256 --emsa=`` +``gen_pkcs10 key CN --country= --organization= --ca --path-limit=1 --email= --dns= --ext-ku= --key-pass= --hash=SHA-256 --padding=`` Generate a PKCS #10 certificate signing request (CSR) using the passed PKCS #8 private key *key*. If the private key is encrypted, the decryption passphrase - *key-pass* has to be passed.*emsa* specifies the padding scheme to be used - when calculating the signature. + *key-pass* has to be passed. - - For RSA keys EMSA4 (RSA-PSS) is the default scheme. - - For ECDSA, DSA, ECGDSA, ECKCDSA and GOST-34.10 keys *emsa* defaults to EMSA1. + The *padding* option specifies the padding scheme to be used when calculating + the signature. This is only used for RSA; for such keys PSS is used by default. -``gen_self_signed key CN --country= --dns= --organization= --email= --path-limit=1 --days=365 --key-pass= --ca --hash=SHA-256 --emsa= --der`` +``gen_self_signed key CN --country= --dns= --organization= --email= --path-limit=1 --days=365 --key-pass= --ca --hash=SHA-256 --padding= --der`` Generate a self signed X.509 certificate using the PKCS #8 private key *key*. If the private key is encrypted, the decryption passphrase *key-pass* has to be passed. If *ca* is passed, the certificate is marked for certificate - authority (CA) usage. *emsa* specifies the padding scheme to be used when - calculating the signature. + authority (CA) usage. - - For RSA keys EMSA4 (RSA-PSS) is the default scheme. - - For ECDSA, DSA, ECGDSA, ECKCDSA and GOST-34.10 keys *emsa* defaults to EMSA1. + The *padding* option specifies the padding scheme to be used when calculating + the signature. This is only used for RSA; for such keys PSS is used by default. -``sign_cert --ca-key-pass= --hash=SHA-256 --duration=365 --emsa= ca_cert ca_key pkcs10_req`` +``sign_cert --ca-key-pass= --hash=SHA-256 --duration=365 --padding= ca_cert ca_key pkcs10_req`` Create a CA signed X.509 certificate from the information contained in the PKCS #10 CSR *pkcs10_req*. The CA certificate is passed as *ca_cert* and the respective PKCS #8 private key as *ca_key*. If the private key is encrypted, the decryption passphrase *ca-key-pass* has to be passed. The created - certificate has a validity period of *duration* days. *emsa* specifies the - padding scheme to be used when calculating the signature. *emsa* defaults to - the padding scheme used in the CA certificate. + certificate has a validity period of *duration* days. + + The *padding* argument specifies the padding scheme to be used when + calculating the signature; this is only used for RSA. If not set then it will + defaults to the padding scheme used in the CA certificate, or otherwise + some suitable default. ``ocsp_check --timeout=3000 subject issuer`` Verify an X.509 certificate against the issuers OCSP responder. Pass the diff -Nru botan3-3.7.1+dfsg/doc/contents.rst botan3-3.12.0+dfsg/doc/contents.rst --- botan3-3.7.1+dfsg/doc/contents.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/contents.rst 2026-05-07 01:38:28.000000000 +0000 @@ -20,5 +20,6 @@ abi packaging security + threat_model side_channels dev_ref/contents diff -Nru botan3-3.7.1+dfsg/doc/credits.rst botan3-3.12.0+dfsg/doc/credits.rst --- botan3-3.7.1+dfsg/doc/credits.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/credits.rst 2026-05-07 01:38:28.000000000 +0000 @@ -41,7 +41,6 @@ N: Simon Cogliani E: simon.cogliani@tanker.io - W: https://www.tanker.io/ P: EA73 D0AF 5A81 A61A 8931 C2CA C9AB F2E4 3820 4F25 D: Getting keystream of ChaCha S: Paris, France @@ -94,6 +93,12 @@ N: Justin Karneges D: Qt support modules (mutexes and types), X.509 API design + N: Kagan Can Sit + E: kagancansit@hotmail.com + W: https://kagancansit.github.io + D: C++20 modernization, performance optimizations, code quality improvements + S: Turkey + N: Rostyslav Khudolii E: rhudoliy@gmail.com D: SRP6 FFI @@ -124,7 +129,6 @@ E: jack@randombit.net W: https://www.randombit.net/ P: 3F69 2E64 6D92 3BBE E7AE 9258 5C0F 96E8 4EC1 6D6B - B: 1DwxWb2J4vuX4vjsbzaCXW696rZfeamahz D: Original designer/author, maintainer 2001-current S: Vermont, USA @@ -186,7 +190,6 @@ N: Simon Warta E: simon@kullo.net - W: https://www.kullo.net D: Build system S: Germany diff -Nru botan3-3.7.1+dfsg/doc/deprecated.rst botan3-3.12.0+dfsg/doc/deprecated.rst --- botan3-3.7.1+dfsg/doc/deprecated.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/deprecated.rst 2026-05-07 01:38:28.000000000 +0000 @@ -23,6 +23,14 @@ * Support for building for Windows systems prior to Windows 10 is deprecated. +X509/PKIX Deprecations +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* Decoding or processing of certificates with negative serial numbers, or CRLs + containing any negative serial numbers in the revocation list, is + deprecated. In a future major release, any such certificate or CRL will be + rejected at parse time. + TLS Protocol Deprecations ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -103,6 +111,13 @@ elements are rarely if ever useful serialized into a protocol. Support for encoding or decoding EC identity elements is deprecated and will be removed. + +ASN.1 Deprecations +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Support for encoding or decoding TeletexString types is deprecated and will +be removed in a future major release. + Deprecated Modules ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -117,6 +132,10 @@ - Dilithium mode ``dilithium_aes``: Similar situation to Kyber 90s mode. +- Kyber R3 support: prefer ML-KEM + +- Dilithium R3 support: prefer ML-DSA + - Block cipher ``gost_28147``: This cipher was obsolete 20 years ago. - Block cipher ``noekeon``: An interesting design but not widely implemented. @@ -139,6 +158,9 @@ permutation, but rather the Keccak hash originally proposed during the SHA-3 competition. +- MAC ``siphash``: Only supports a 64-bit output length, and not really intended + for cryptography per se. + - MAC ``x919_mac``: Quite obsolete at this point - Signature scheme ``dsa``: Finite field DSA is slow, very rarely used anymore, @@ -165,14 +187,12 @@ This section lists other functionality which will be removed in a future major release, or where a backwards incompatible change is expected. -- Support for OtherNames in X.509 certificates is deprecated - - The ``PBKDF`` class is deprecated in favor of ``PasswordHash`` and ``PasswordHashFamily``. - Implicit conversion of a private key into a public key. Currently ``Private_Key`` derives from ``Public_Key`` (and likewise for each of the - algorithm specfic classes, eg ``RSA_PrivateKey`` derives from + algorithm specific classes, eg ``RSA_PrivateKey`` derives from ``RSA_PublicKey``). In a future release these derivations will not exist. To correctly extract the public key from a private key, use the function ``Private_Key::public_key()`` @@ -217,15 +237,18 @@ Deprecated Headers ^^^^^^^^^^^^^^^^^^^^^^ -These headers are currently publically available, but will be made +These headers are currently publicly available, but will be made internal to the library in the future. + System-specific certificate store headers: ``certstor_macos.h``, ``certstor_windows.h`` -- + use via ``Certificate_Store_System`` in ``certstor_system.h`` + PBKDF headers: ``bcrypt_pbkdf.h``, ``pbkdf2.h``, ``pgp_s2k.h``, ``scrypt.h``, and ``argon2.h``: Use the ``PasswordHash`` interface instead. Internal implementation headers - seemingly no reason for applications to use: + ``assert.h``, ``curve_gfp.h``, - ``numthry.h``, ``reducer.h``, ``tls_algos.h``, ``tls_magic.h`` @@ -234,4 +257,5 @@ the library API and most are just sufficient for what the library needs to implement other functionality. ``compiler.h``, + ``mem_ops.h``, ``uuid.h``, diff -Nru botan3-3.7.1+dfsg/doc/dev_ref/configure.rst botan3-3.12.0+dfsg/doc/dev_ref/configure.rst --- botan3-3.7.1+dfsg/doc/dev_ref/configure.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/dev_ref/configure.rst 2026-05-07 01:38:28.000000000 +0000 @@ -227,7 +227,7 @@ ``--disable-modules`` or ``--disable-deprecated-features``. * ``libs`` specifies additional libraries which should be linked if this module is - included. It maps from the OS name to a list of libraries (comma seperated). + included. It maps from the OS name to a list of libraries (comma separated). * ``frameworks`` is a macOS/iOS specific feature which maps from an OS name to a framework. @@ -412,7 +412,7 @@ takes this from the OS specific information. * ``ar_output_to`` gives the flag to pass to ``ar_command`` to specify where to output the static library. - * ``werror_flags`` gives the complier flags to treat warnings as errors. + * ``werror_flags`` gives the compiler flags to treat warnings as errors. Supporting a new OS --------------------------- diff -Nru botan3-3.7.1+dfsg/doc/dev_ref/contents.rst botan3-3.12.0+dfsg/doc/dev_ref/contents.rst --- botan3-3.7.1+dfsg/doc/dev_ref/contents.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/dev_ref/contents.rst 2026-05-07 01:38:28.000000000 +0000 @@ -16,6 +16,7 @@ todo os oids + pcurves next_major reading_list mistakes diff -Nru botan3-3.7.1+dfsg/doc/dev_ref/contributing.rst botan3-3.12.0+dfsg/doc/dev_ref/contributing.rst --- botan3-3.7.1+dfsg/doc/dev_ref/contributing.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/dev_ref/contributing.rst 2026-05-07 01:38:28.000000000 +0000 @@ -12,7 +12,7 @@ * ``tests`` contain what you would expect. Input files go under ``tests/data``. * ``python/botan3.py`` is the Python ctypes wrapper * ``bogo_shim`` contains the shim binary and configuration for - `BoringSSL's TLS test suite `_ + `BoringSSL's TLS test suite `_ * ``fuzzer`` contains fuzz targets for various modules of the library * ``ct_selftest`` has some tests to validate constant time checker tools (e.g. valgrind) * ``build-data`` contains files read by the configure script. For @@ -165,24 +165,32 @@ of total coverage. This coverage build requires the development headers for zlib, bzip2, liblzma, TrouSerS (libtspi), and Sqlite3. +Development Container +---------------------------------------- + +The repository root contains a .devcontainer configuration based on Ubuntu which +conveniently sets up a fully-functional build and test environment. This is the +recommended way for new contributors to start developing. + +Currently, the .devcontainer integrates best with Visual Studio Code, but other +integrations would be welcome. The container should also work decently using the +bare-metal devcontainer CLI. + Editor Integrations ---------------------------------------- The folder ``src/editors`` contains configuration files for a few editors. To make use of them, create symlinks of those into the root of your local -Botan repository. For example, to enable integration with VSCode and configure -the editor using editorconfig, you can do the following: +Botan repository. For instance, to enable editorconfig for any editor that +supports it, you can do the following: .. code-block:: bash cd /home/you/projects/botan - ln -s src/editors/vscode .vscode ln -s src/editors/editorconfig .editorconfig - code . - -With the recommended extensions installed, you should now have a good starting -point for working with Botan in VSCode. +If you are using VSCode with the development container, the right symlinks are +created automatically and you should be good to go off the bat. Copyright Notice ---------------------------------------- @@ -241,12 +249,13 @@ Use ``m_`` prefix on all member variables. ``clang-format`` is used for all C++ formatting. The configuration is -in ``.clang-format`` in the root directory. You can rerun the -formatter using ``make fmt``, by invoking the script -``src/scripts/dev_tools/run_clang_format.py`` or using an appropriate editor -configuration from ``src/editors``. If the output would be truly horrible, it is -allowed to disable formatting for a specific area using ``// clang-format off`` -annotations. +in ``src/configs/clang-format``. You can rerun the formatter using ``make fmt``, +by invoking the script ``src/scripts/dev_tools/run_clang_format.py`` or symlink +the configuration into the repo root as ``.clang-format`` and using an appropriate +editor configuration from ``src/editors``. Note that the dev-container shipped with +this repository sets this up properly when used with VSCode. If the output would be +truly horrible, it is allowed to disable formatting for a specific area using +``// clang-format off`` annotations. .. note:: @@ -285,6 +294,13 @@ should in any case be annotated (using ``CT::poison``) so it can be checked at runtime with tools. +SIMD Intrinsics +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Using intrinsics is the preferred method of invoking hardware specific instructions. +In doing so, prefer using (and extending if required) the wrapper types included in +``utils/simd``. + Operating System Dependencies ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff -Nru botan3-3.7.1+dfsg/doc/dev_ref/fuzzing.rst botan3-3.12.0+dfsg/doc/dev_ref/fuzzing.rst --- botan3-3.7.1+dfsg/doc/dev_ref/fuzzing.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/dev_ref/fuzzing.rst 2026-05-07 01:38:28.000000000 +0000 @@ -85,7 +85,6 @@ * https://github.com/randombit/crypto-corpus * https://github.com/mozilla/nss-fuzzing-corpus * https://github.com/google/boringssl/tree/master/fuzz -* https://github.com/openssl/openssl/tree/master/fuzz/corpora Adding new fuzzers --------------------- diff -Nru botan3-3.7.1+dfsg/doc/dev_ref/mistakes.rst botan3-3.12.0+dfsg/doc/dev_ref/mistakes.rst --- botan3-3.7.1+dfsg/doc/dev_ref/mistakes.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/dev_ref/mistakes.rst 2026-05-07 01:38:28.000000000 +0000 @@ -19,7 +19,7 @@ ------------------------------------ Classes like AES_128 and SHA_256 should never have been exposed to applications. -Intead such operations should have been accessible only via the higher level +Instead such operations should have been accessible only via the higher level interfaces (here BlockCipher and HashFunction). This would substantially reduce the overall API and ABI surface. diff -Nru botan3-3.7.1+dfsg/doc/dev_ref/next_major.rst botan3-3.12.0+dfsg/doc/dev_ref/next_major.rst --- botan3-3.7.1+dfsg/doc/dev_ref/next_major.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/dev_ref/next_major.rst 2026-05-07 01:38:28.000000000 +0000 @@ -16,21 +16,21 @@ into it... A number of operations currently defined on Public_Key can be -moved to Asymetric_Key, for example key_length and algorithm_identifier. +moved to Asymmetric_Key, for example key_length and algorithm_identifier. Due to Private_Key deriving from Public_Key, the fingerprint functions are oddly named. Otherwise we can't correctly disambiguate sk->fingerprint(); should this be the fingerprint of the public or private key. With the -split we can move this to Asymetric_Key::fingerprint and know that the +split we can move this to Asymmetric_Key::fingerprint and know that the correct thing happens. The public and private key encoding functions (pkcs8.h, x509_key.h) are also complicated by the combined keys. For example we have to use -PKCS8::PEM_encode(key) because key.PEM_encode() would be ambigious +PKCS8::PEM_encode(key) because key.PEM_encode() would be ambiguous (similar situation as with the fingerprint APIs currently). Once the key types are split, we can move all of this to the key types themselves, or again (for the shared cases, like unencrypted PEM) to -Asymetric_Key. +Asymmetric_Key. Decoding also can become simpler. We could consider moving to a model that doesn't use DataSource? Maybe just a span even? diff -Nru botan3-3.7.1+dfsg/doc/dev_ref/os.rst botan3-3.12.0+dfsg/doc/dev_ref/os.rst --- botan3-3.7.1+dfsg/doc/dev_ref/os.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/dev_ref/os.rst 2026-05-07 01:38:28.000000000 +0000 @@ -1,3 +1,6 @@ +.. This file was automatically generated by src/scripts/dev_tools/gen_os_features.py on 2026-04-24 +.. All manual changes will be lost. Edit the script instead. + OS Features ======================================== @@ -35,7 +38,6 @@ "apple_keychain", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", "X", " ", " ", " ", " ", " ", " ", " ", " " "arc4random", " ", "X", " ", "X", " ", "X", " ", " ", " ", " ", "X", " ", " ", "X", " ", "X", " ", "X", " ", " ", " ", " " "atomics", "X", "X", "X", "X", "X", "X", "X", "X", "X", "X", "X", "X", "X", "X", "X", "X", " ", "X", "X", "X", "X", "X" - "auxinfo", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", "X", " ", " ", " ", " ", " ", " " "cap_enter", " ", " ", " ", " ", " ", "X", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " " "ccrandom", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", "X", " ", " ", " ", " ", " ", " ", " ", " " "certificate_store", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", "X", " ", " ", " ", " ", " ", " ", "X" @@ -44,27 +46,24 @@ "crypto_ng", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", "X", " " "dev_random", "X", "X", "X", "X", "X", "X", " ", "X", "X", "X", " ", "X", " ", "X", " ", "X", " ", "X", "X", "X", " ", " " "elf_aux_info", " ", " ", " ", " ", " ", "X", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", "X", " ", " ", " ", " " - "explicit_bzero", " ", " ", " ", "X", " ", "X", " ", " ", " ", " ", " ", "X", " ", " ", " ", " ", " ", "X", " ", " ", " ", " " + "explicit_bzero", " ", " ", " ", "X", " ", "X", " ", " ", " ", "X", " ", "X", " ", " ", " ", " ", " ", "X", " ", " ", " ", " " "explicit_memset", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", "X", " ", " ", " ", " ", " ", " " "filesystem", "X", "X", "X", "X", "X", "X", "X", "X", "X", "X", "X", "X", "X", "X", "X", "X", " ", "X", "X", "X", "X", "X" "getauxval", " ", "X", " ", " ", " ", " ", " ", " ", " ", " ", " ", "X", " ", " ", " ", " ", " ", " ", " ", " ", " ", " " - "getentropy", " ", " ", " ", " ", " ", "X", " ", " ", " ", " ", " ", "X", " ", "X", " ", " ", " ", "X", " ", "X", " ", " " - "getrandom", " ", " ", " ", "X", " ", " ", " ", " ", " ", " ", " ", "X", " ", " ", " ", " ", " ", " ", " ", " ", " ", " " - "pledge", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", "X", " ", " ", " ", " " + "getentropy", " ", " ", " ", " ", " ", "X", " ", " ", " ", "X", " ", "X", " ", "X", " ", " ", " ", "X", " ", "X", " ", " " + "getrandom", " ", " ", " ", "X", " ", "X", " ", " ", " ", "X", " ", "X", " ", " ", " ", " ", " ", " ", " ", " ", " ", " " "posix1", "X", "X", "X", "X", "X", "X", " ", "X", "X", "X", "X", "X", " ", "X", " ", "X", " ", "X", "X", "X", " ", " " "posix_mlock", "X", "X", " ", "X", " ", "X", " ", " ", "X", "X", "X", "X", " ", "X", " ", "X", " ", "X", "X", "X", " ", " " "prctl", " ", "X", " ", " ", " ", " ", " ", " ", " ", " ", " ", "X", " ", " ", " ", " ", " ", " ", " ", " ", " ", " " - "proc_fs", "X", " ", " ", "X", " ", " ", " ", " ", " ", " ", " ", "X", " ", " ", " ", " ", " ", " ", " ", "X", " ", " " "rtlgenrandom", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", "X", " ", " ", " ", " ", " ", " ", "X" "rtlsecurezeromemory", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", "X", "X" "sandbox_proc", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", "X", " ", " ", " ", " ", " ", " ", " ", " " "setppriv", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", "X", " ", " " "sockets", "X", "X", "X", "X", " ", "X", " ", "X", "X", "X", "X", "X", " ", "X", " ", "X", " ", "X", "X", "X", " ", " " + "sysctlbyname", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", "X", " ", " ", "X", " ", " ", " ", " ", " ", " ", " ", " " + "system_clock", "X", "X", "X", "X", "X", "X", "X", "X", "X", "X", "X", "X", "X", "X", "X", "X", "X", "X", "X", "X", "X", "X" "thread_local", "X", "X", "X", "X", " ", "X", "X", "X", "X", "X", "X", "X", " ", "X", "X", "X", " ", "X", "X", "X", "X", "X" "threads", "X", "X", "X", "X", " ", "X", "X", "X", "X", "X", "X", "X", " ", "X", "X", "X", " ", "X", "X", "X", "X", "X" "virtual_lock", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", "X", " ", " ", " ", " ", " ", " ", "X" "win32", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", "X", " ", " ", " ", " ", " ", "X", "X" "winsock2", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", "X", "X" - -.. note:: - This file is auto generated by ``src/scripts/gen_os_features.py``. Dont modify it manually. diff -Nru botan3-3.7.1+dfsg/doc/dev_ref/pcurves.rst botan3-3.12.0+dfsg/doc/dev_ref/pcurves.rst --- botan3-3.7.1+dfsg/doc/dev_ref/pcurves.rst 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/dev_ref/pcurves.rst 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,62 @@ +Custom Elliptic Curve +=================================== + +Some products or protocols use custom designed (or even classified) elliptic +curve parameters. + +The default way of supporting curves like this is to use the constructor of +``EC_Group`` which accepts the various parameters as integers. This uses the +generic elliptic curve logic, which is already reasonably fast. + +However in certain cases the best possible performance is required, perhaps +because the hardware it is being deployed on is old/underpowered. The library +provides an escape hatch to support this, where a custom curve is supported +using the same curve-specific logic as used to implement common curves like +P-256. + +.. warning:: + + This process is documented for convenience but NOT OFFICIALLY SUPPORTED. + If you need to use this, please consider the life choices that brought you + to this point. + +The groups supported by the library are specified in a file +``src/build-data/ec_groups.txt``, which contains entries like + +.. code-block:: text + + Name = secp256r1 + OID = 1.2.840.10045.3.1.7 + Impl = pcurve generic legacy + P = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF + A = -3 + B = 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B + X = 0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296 + Y = 0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5 + N = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551 + +.. note:: + + Not all curve parameters can be supported by this process. In particular, + it is required that + + 1) The prime field is between 192 and 512 bits, and a multiple of 32 bits. + 2) The prime must be congruent to 3 modulo 4. + 3) The group order must have the same bit length as the prime. + 4) The group must be prime order; no cofactors are allowed. + +To add a new curve with curve specific optimizations, do the following: + +1) Add a new block to ``ec_groups.txt`` specifying the parameters. The + important value is that ``Impl`` contains ``pcurve``. If you only want to + support the group using the new dedicated implementation that will be + generated in a later step, you can skip ``generic`` and ``legacy`` here. + +2) Add the OID to ``src/build-data/oids.txt`` in the ``[ecc_param]`` block - the + OID name should match the value of ``Name`` in ``ec_groups.txt`` + +3) Run ``./src/scripts/dev_tools/gen_ec_groups.py``. This script requires the + Jinja2 template library, and the program ``addchain`` from + https://github.com/mmcloughlin/addchain + +4) Run ``./src/scripts/dev_tools/gen_oids.py`` to regenerate the OID lookup table diff -Nru botan3-3.7.1+dfsg/doc/dev_ref/reading_list.rst botan3-3.12.0+dfsg/doc/dev_ref/reading_list.rst --- botan3-3.7.1+dfsg/doc/dev_ref/reading_list.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/dev_ref/reading_list.rst 2026-05-07 01:38:28.000000000 +0000 @@ -12,12 +12,12 @@ * "Randomizing the Montgomery Powering Ladder" Le, Tan, Tunstall https://eprint.iacr.org/2015/657 - A variant of Algorithm 7 is used for GF(p) point multplications when + A variant of Algorithm 7 is used for GF(p) point multiplications when BOTAN_POINTGFP_BLINDED_MULTIPLY_USE_MONTGOMERY_LADDER is set * "Accelerating AES with vector permute instructions" Mike Hamburg https://shiftleft.org/papers/vector_aes/ - His public doman assembly code was rewritten into SSS3 intrinsics + His public domain assembly code was rewritten into SSS3 intrinsics for aes_ssse3. * "Elliptic curves and their implementation" Langley diff -Nru botan3-3.7.1+dfsg/doc/dev_ref/release_process.rst botan3-3.12.0+dfsg/doc/dev_ref/release_process.rst --- botan3-3.7.1+dfsg/doc/dev_ref/release_process.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/dev_ref/release_process.rst 2026-05-07 01:38:28.000000000 +0000 @@ -12,27 +12,24 @@ This information is only useful if you are a developer of botan who is creating a new release of the library. -Pre Release Testing +Pre Release Checks ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -In the week prior to a release: - -- [ ] Update relevant third party test suites (eg Limbo and BoGo) -- [ ] Do maintainer-mode builds with Clang and GCC to catch any warnings -- [ ] Test build configurations using `src/scripts/test_all_configs.py` -- [ ] Test a few builds on platforms not in CI (eg OpenBSD, FreeBSD, Solaris) - -Final Changes -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -When it is time to make the release: +In the week prior to a release, after feature freeze goes into effect - [ ] Check that the version number in ``src/build-data/version.txt`` is correct. - [ ] Confirm that the release notes in ``news.rst`` are accurate and complete. +- [ ] Diff ffi.h vs the previous release; is a new FFI version required? +- [ ] Perform a full clang-tidy run with latest available Clang +- [ ] Test build configurations using `src/scripts/test_all_configs.py` +- [ ] Test a few builds on platforms not in CI (eg OpenBSD, FreeBSD, Solaris) +- [ ] Update relevant third party test suites (eg Limbo, BoGo, TLS-Anvil, ...) Tag the Release ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +At the time the release is created + - [ ] Update the release date in ``news.rst`` - [ ] Update ``readme.rst`` with the new release URL/date - [ ] Check in those changes then backport to the release branch:: diff -Nru botan3-3.7.1+dfsg/doc/dev_ref/test_framework.rst botan3-3.12.0+dfsg/doc/dev_ref/test_framework.rst --- botan3-3.7.1+dfsg/doc/dev_ref/test_framework.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/dev_ref/test_framework.rst 2026-05-07 01:38:28.000000000 +0000 @@ -8,13 +8,13 @@ The intent is that the test framework and the test suite evolve symbiotically; as a general rule of thumb if a new function would make -the implementation of just two distinct tests simpler, it is worth +the implementation of just a few distinct tests simpler, it is worth adding to the framework on the assumption it will prove useful again. Feel free to propose changes to the test system. When writing a new test, there are three key classes that are used, namely ``Test``, ``Test::Result``, and ``Text_Based_Test``. A ``Test`` -(or ``Test_Based_Test``) runs and returns one or more ``Test::Result``. +(or ``Text_Based_Test``) runs and returns one or more ``Test::Result``. Namespaces in Test ------------------- @@ -30,12 +30,14 @@ ----------- The test framework is heavily data driven. As of this writing, there -is about 1 Mib of test code and 17 MiB of test data. For most (though +is about 2.5 Mib of test code and 28 MiB of test data. For most (though certainly not all) tests, it is better to add a data file representing the input and outputs, and run the tests over it. Data driven tests make adding or editing tests easier, for example by writing scripts which produce new test data and output it in the expected format. +Test data lives in ``src/tests/data``. + Test -------- @@ -70,7 +72,7 @@ .. cpp:function:: static Botan::RandomNumberGenerator& rng() Returns a reference to a fast, not cryptographically secure - random number generator. It is deterministicly seeded with the + random number generator. It is deterministically seeded with the seed logged by the test runner, so it is possible to reproduce results in "random" tests. @@ -93,7 +95,7 @@ return true or false if the test was successful or not; this allows performing conditional blocks as a result of earlier tests:: - if(result.test_eq("first value", produced, expected)) + if(result.test_str_eq("first value", produced, expected)) { // further tests that rely on the initial test being correct } @@ -110,91 +112,110 @@ Report a test that was successful. - .. cpp:function:: bool test_success(const std::string& note) + .. cpp:function:: bool test_success(std::string_view note) Report a test that was successful, including some comment. - .. cpp:function:: bool test_failure(const std::string& err) + .. cpp:function:: bool test_failure(std::string_view err) Report a test failure of some kind. The error string will be logged. - .. cpp:function:: bool test_failure(const std::string& what, const std::string& error) + .. cpp:function:: bool test_failure(std::string_view what, std::string_view error) Report a test failure of some kind, with a description of what failed and what the error was. - .. cpp:function:: void test_failure(const std::string& what, const uint8_t buf[], size_t buf_len) + .. cpp:function:: void test_failure(std::string_view what, std::span context) Report a test failure due to some particular input, which is provided as - arguments. Normally this is only used if the test was using some + ``context``. Normally this is only used if the test was using some randomized input which unexpectedly failed, since if the input is hardcoded or from a file it is easier to just reference the test number. - .. cpp:function:: bool test_eq(const std::string& what, const std::string& produced, const std::string& expected) + .. cpp:function:: bool test_str_eq(std::string_view what, std::string_view produced, std::string_view expected) - Compare to strings for equality. + Compare two strings for equality. - .. cpp:function:: bool test_ne(const std::string& what, const std::string& produced, const std::string& expected) + .. cpp:function:: bool test_str_ne(std::string_view what, std::string_view produced, std::string_view expected) - Compare to strings for non-equality. + Compare two strings for non-equality. - .. cpp:function:: bool test_eq(const char* producer, const std::string& what, \ - const uint8_t produced[], size_t produced_len, \ - const uint8_t expected[], size_t expected_len) + .. cpp:function:: bool test_bin_eq(std::string_view what, \ + std::span produced, \ + std::span expected); Compare two arrays for equality. - .. cpp:function:: bool test_ne(const char* producer, const std::string& what, \ - const uint8_t produced[], size_t produced_len, \ - const uint8_t expected[], size_t expected_len) + .. cpp:function:: bool test_bin_eq(std::string_view what, \ + std::span produced, \ + std::string_view expected_hex); + + Compare two arrays for equality, with the expected value provided as a hex string. - Compare two arrays for non-equality. + .. cpp:function:: template bool test_not_null(std::string_view what, T* ptr) - .. cpp:function:: bool test_eq(const std::string& producer, const std::string& what, \ - const std::vector& produced, \ - const std::vector& expected) + Verify that the pointer is not null. - Compare two vectors for equality. + .. cpp:function:: bool test_u8_eq(std::string_view what, uint8_t produced, uint8_t expected) - .. cpp:function:: bool test_ne(const std::string& producer, const std::string& what, \ - const std::vector& produced, \ - const std::vector& expected) + Test that ``produced`` == ``expected``. - Compare two vectors for non-equality. + .. cpp:function:: bool test_u16_eq(std::string_view what, uint16_t produced, uint16_t expected) - .. cpp:function:: bool confirm(const std::string& what, bool expr) + Test that ``produced`` == ``expected``. - Test that some expression evaluates to ``true``. + .. cpp:function:: bool test_u32_eq(std::string_view what, uint32_t produced, uint32_t expected) - .. cpp:function:: template bool test_not_null(const std::string& what, T* ptr) + Test that ``produced`` == ``expected``. - Verify that the pointer is not null. + .. cpp:function:: bool test_u64_eq(std::string_view what, uint64_t produced, uint64_t expected) - .. cpp:function:: bool test_lt(const std::string& what, size_t produced, size_t expected) + Test that ``produced`` == ``expected``. + + .. cpp:function:: bool test_sz_eq(std::string_view what, size_t produced, size_t expected) + + Test that ``produced`` == ``expected``. + + .. cpp:function:: bool test_sz_lt(std::string_view what, size_t produced, size_t expected) Test that ``produced`` < ``expected``. - .. cpp:function:: bool test_lte(const std::string& what, size_t produced, size_t expected) + .. cpp:function:: bool test_sz_lte(std::string_view what, size_t produced, size_t expected) Test that ``produced`` <= ``expected``. - .. cpp:function:: bool test_gt(const std::string& what, size_t produced, size_t expected) + .. cpp:function:: bool test_sz_gt(std::string_view what, size_t produced, size_t expected) Test that ``produced`` > ``expected``. - .. cpp:function:: bool test_gte(const std::string& what, size_t produced, size_t expected) + .. cpp:function:: bool test_sz_gte(std::string_view what, size_t produced, size_t expected) Test that ``produced`` >= ``expected``. - .. cpp:function:: bool test_throws(const std::string& what, std::function fn) + .. cpp:function:: bool test_throws(std::string_view what, std::function fn) Call a function and verify it throws an exception of some kind. - .. cpp:function:: bool test_throws(const std::string& what, const std::string& expected, std::function fn) + .. cpp:function:: bool test_throws(std::string_view what, std::string_view expected, std::function fn) Call a function and verify it throws an exception of some kind and that the exception message exactly equals ``expected``. +There is also a comparison function for arbitrary types, which is defined in the +separate header ``test_arb_eq.h`` because it drags in some additional headers. + +.. cpp:function:: template \ + bool test_arb_eq(Test::Result& result, std::string_view what, const T& produced, const T& expected) + + Compare some arbitrary Ts for equality. + + It is required that ``T`` not be something that is handled by one of the + existing comparison functions (eg not a string, integer, or bytestring + type) and also that ``test_arb_eq`` is able to deduce some way of + printing values of ``T``. Depending on your ``T`` you may need to extend + the implementation of ``detail::to_string`` in that header. + + Text_Based_Test ----------------- @@ -221,37 +242,36 @@ the test provides a default value which is returned if the key was not set for this particular instance of the test. - .. cpp:function:: std::vector get_req_bin(const std::string& key) const + .. cpp:function:: std::vector get_req_bin(std::string_view key) const Return a required binary string. The input is assumed to be hex encoded. - .. cpp:function:: std::vector get_opt_bin(const std::string& key) const + .. cpp:function:: std::vector get_opt_bin(std::string_view key) const Return an optional binary string. The input is assumed to be hex encoded. + Returns empty if the value was not provided. - .. cpp:function:: std::vector> get_req_bin_list(const std::string& key) const - - .. cpp:function:: Botan::BigInt get_req_bn(const std::string& key) const + .. cpp:function:: Botan::BigInt get_req_bn(std::string_view key) const Return a required BigInt. The input can be decimal or (with "0x" prefix) hex encoded. - .. cpp:function:: Botan::BigInt get_opt_bn(const std::string& key, const Botan::BigInt& def_value) const + .. cpp:function:: Botan::BigInt get_opt_bn(std::string_view key, const Botan::BigInt& def_value) const Return an optional BigInt. The input can be decimal or (with "0x" prefix) hex encoded. - .. cpp:function:: std::string get_req_str(const std::string& key) const + .. cpp:function:: std::string get_req_str(std::string_view key) const Return a required text string. - .. cpp:function:: std::string get_opt_str(const std::string& key, const std::string& def_value) const + .. cpp:function:: std::string get_opt_str(std::string_view key, std::string_view def_value) const - Return an optional text string. + Return an optional text string, or the specified default value if not set. - .. cpp:function:: size_t get_req_sz(const std::string& key) const + .. cpp:function:: size_t get_req_sz(std::string_view key) const Return a required integer. The input should be decimal. - .. cpp:function:: size_t get_opt_sz(const std::string& key, const size_t def_value) const + .. cpp:function:: size_t get_opt_sz(std::string_view key, const size_t def_value) const Return an optional integer. The input should be decimal. @@ -261,8 +281,6 @@ const std::string& required_keys, \ const std::string& optional_keys = "") - This constructor is - .. note:: The final element of required_keys is the "output key", that is the key which signifies the boundary between one test and the next. @@ -291,24 +309,21 @@ If you are simply writing a new test there should be no need to modify the runner, however it can be useful to be aware of its abilities. -The runner can run tests concurrently across many cores. By default single -threaded execution is used, but you can use ``--test-threads`` option to -specify the number of threads to use. If you use ``--test-threads=0`` then -the runner will probe the number of active CPUs and use that (but limited -to at most 16). If you want to run across many cores on a large machine, -explicitly specify a thread count. The speedup is close to linear. +The runner can run tests concurrently across many cores, and does so by default +on most systems. If you want single-threaded testing for some reason, use the +option ``--test-threads=1``. If not specified then (as of this writing) at most +16 threads will be used; you can use eg ``--test-threads=128`` if running on +a large system. The RNG used in the tests is deterministic, and the seed is logged for each execution. You can cause the random sequence to repeat using ``--drbg-seed`` -option. - -.. note:: - Currently the RNG is seeded just once at the start of execution. So you - must run the exact same sequence of tests as the original test run in - order to get reproducible results. +option. It's not necessary that the same sequence of tests be executed in order +to replay the state; if you see a test ``foo`` fail with a specific DRBG seed, +for example in a CI run, you should be able to replicate that with just +``botan-test --drbg-seed= foo``. If you are trying to track down a bug that happens only occasionally, two very useful options are ``--test-runs`` and ``--abort-on-first-fail``. The first takes an integer and runs the specified test cases that many times. The second -causes abort to be called on the very first failed test. This is sometimes +causes ``abort`` to be called on the very first failed test. This is sometimes useful when tracing a memory corruption bug. diff -Nru botan3-3.7.1+dfsg/doc/dev_ref/todo.rst botan3-3.12.0+dfsg/doc/dev_ref/todo.rst --- botan3-3.7.1+dfsg/doc/dev_ref/todo.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/dev_ref/todo.rst 2026-05-07 01:38:28.000000000 +0000 @@ -16,35 +16,26 @@ * Threefish-1024 * Skein-MAC * FFX format preserving encryption (NIST 800-38G) -* Adiantum (https://eprint.iacr.org/2018/720) * HPKE (RFC 9180) * Blake3 Hardware Specific Optimizations ---------------------------------------- - -* Stiched AES/GCM mode for CPUs supporting both AES and CLMUL -* Combine AES-NI, ARMv8 and POWER AES implementations (as already done for CLMUL) -* GFNI implementations for: Camellia, SEED, ARIA -* NEON/VMX support for the SIMD based GHASH -* Vector permute AES only supports little-endian systems; fix for big-endian -* Poly1305 using AVX2 -* SHA-512 using BMI2+AVX2 and/or new Intel instructions -* SM3 using x86 SM3-NI -* SM4 using x86 SM4-NI -* Constant time bitsliced DES +* AVX512 IFMA optimized field arithmetic for P-256 and/or P-384 +* Stitched AES/GCM implementation +* GFNI implementations of ZFEC, others? +* NEON/VMX/LSX support for the SIMD based GHASH * SIMD evaluation of SHA-2 and SHA-3 compression functions -* Improved Salsa implementations (SIMD_4x32 and/or AVX2) -* Add CLMUL/PMULL implementations for CRC24/CRC32 -* Add support for ARMv8.4-A SHA-3, SM3 and RNG instructions -* POWER8 SHA-2 extensions (GH #1486 + #1487) -* Add support for VPSUM on big-endian PPC64 (GH #2252) -* Add support for RISC-V crypto extensions +* Improved Salsa implementations (SIMD_4x32, AVX2, AVX512, ...) +* Add CLMUL/PMULL implementations for CRC24 +* Add support for ARMv8.4-A SHA-3 instructions +* Support POWER8 SHA-2 extensions (GH #1486 + #1487) +* Add support for RISC-V vector and crypto extensions +* Add support for using Loongarch64 LASX (256-bit SIMD) Public Key Crypto, Math ---------------------------------------- -* Short vector optimization for BigInt * BLS12-381 pairing, BLS signatures * Identity based encryption * Paillier homomorphic cryptosystem @@ -54,16 +45,12 @@ Utility Functions ------------------ -* Constant time base64 and hex is optimized using SWAR; apply this to base32 and base58 * Make Memory_Pool more concurrent (currently uses a global lock) * Guarded integer type to prevent overflow bugs External Providers ---------------------------------------- -* /dev/crypto provider (ciphers, hashes) -* Windows CryptoNG provider (ciphers, hashes) -* Extend Apple CommonCrypto provider (HMAC, CMAC, RSA, ECDSA, ECDH) * Add support for iOS keychain access * Extend support for TPM 2.0 (PCR, NVRAM, Policies, etc) @@ -71,7 +58,6 @@ ---------------------------------------- * Make DTLS support optional at build time -* Make TLS 1.2 support optional at build time * Improve/optimize DTLS defragmentation and retransmission * Make RSA optional at build time * Make finite field DH optional at build time @@ -83,7 +69,6 @@ ---------------------------------------- * Further tests of validation API (see GH #785) -* Test suite for validation of 'real world' cert chains (GH #611) * X.509 policy constraints * OCSP responder logic @@ -93,9 +78,6 @@ * Noise protocol * ACME protocol (needs a story for JSON) * Cryptographic Message Syntax (RFC 5652) -* Fernet symmetric encryption (https://cryptography.io/en/latest/fernet/) -* RNCryptor format (https://github.com/RNCryptor/RNCryptor) -* Age format (https://age-encryption.org/v1) * Useful OpenPGP subset 1: symmetrically encrypted files. Not aiming to process arbitrary OpenPGP, but rather produce something that happens to be readable by `gpg` and is relatively @@ -109,12 +91,11 @@ * Unicode path support on Windows (GH #1615) * The X.509 path validation tests have much duplicated logic -New C APIs +FFI APIs ---------------------------------------- * PKCS10 requests * Certificate signing -* CRLs * Expose TLS * Expose secret sharing * Expose deterministic PRNG @@ -130,7 +111,6 @@ so it can run as a standalone item (copied to a device, etc) * Run iOS binary under simulator in CI * Run Android binary under simulator in CI -* Add support for vxWorks CLI ---------------------------------------- diff -Nru botan3-3.7.1+dfsg/doc/goals.rst botan3-3.12.0+dfsg/doc/goals.rst --- botan3-3.7.1+dfsg/doc/goals.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/goals.rst 2026-05-07 01:38:28.000000000 +0000 @@ -14,6 +14,12 @@ accounted for where necessary. The library should never crash, or invoke undefined behavior, regardless of circumstances. +* Constant time programming. The table stakes for a modern cryptographic library + include being immune to basic timing/cache based side channels. Botan includes + utilities to assist in writing and testing constant time code. A test suite + run nightly in CI verifies Botan's constant time behavior across a range of + compilers, compiler options, and CPU architectures. + * Implement schemes important in practice. It should be practical to implement any real-world crypto protocol using just what the library provides. It is worth some (limited) additional complexity in the library, in order to expand @@ -48,9 +54,9 @@ least the option of using a post-quantum scheme. Botan provides a conservative selection of algorithms thought to be post-quantum secure. -* Performance. Botan does not in every case strive to be faster than every other - software implementation, but performance should be competitive and over time - new optimizations are identified and applied. +* Performance. Botan aims to have the fastest possible implementation of all + algorithms it supports, subject to the constraints implicit with the other + project goals. * Support whatever I/O mechanism the application wants. Allow the application to control all aspects of how the network is contacted, and ensure the API makes diff -Nru botan3-3.7.1+dfsg/doc/hardware_acceleration.rst botan3-3.12.0+dfsg/doc/hardware_acceleration.rst --- botan3-3.7.1+dfsg/doc/hardware_acceleration.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/hardware_acceleration.rst 2026-05-07 01:38:28.000000000 +0000 @@ -6,182 +6,293 @@ that are not available on all platforms and either speed up the algorithm or improve security in terms of side channel resistance. -A “base” software implementation is always provided. For example, for the AES-128 -block cipher three implementations are available. All of the AES-128 implementations -are immune to common cache/timing based side channels. - -* If AES hardware support is available (AES-NI, POWER8, Aarch64) use that -* If 128-bit SIMD with byte shuffles are available (SSSE3, NEON, or Altivec), - use the vperm technique published by Mike Hamburg at CHES 2009 -* If no hardware or SIMD support, fall back to a constant time bitsliced implementation - The following sections list the platforms and algorithms for which hardware acceleration is available. If the CPU specific optimizations are available at runtime, they are automatically used if enabled in the build. If not, the base implementation is used. +It is possible to disable CPU-specific optimizations at runtime by setting the +environment variable ``BOTAN_CLEAR_CPUID``. For example +``BOTAN_CLEAR_CPUID=avx2`` will disable use of any AVX2 instructions. + x86 -------------- -On x86-64 and x86-32 platforms, the following CPU specific optimizations are available: +On x86-64 and x86-32 platforms, the following CPU specific optimizations are available. + +.. note:: + + AVX-512 codepaths are only used on x86-64 processors that support AVX-512 + extensions similar to Intel Ice Lake or AMD Zen4 (requires AVX-512 F, VL, BW, + DQ, VBMI, VBMI2, BITALG, IFMA) + ++-----------+--------------------------------------------+-------------------------+------------+ +| Algorithm | Extension | Module | Added in | ++===========+============================================+=========================+============+ +| AES | VAES-AVX2 | ``aes_vaes`` | 3.6.0 | +| | | | | +| | AES-NI | ``aes_ni`` | 1.9.3 | +| | | | | +| | SSSE3 | ``aes_vperm`` | 1.9.10 | ++-----------+--------------------------------------------+-------------------------+------------+ +| AES-GCM | AVX-512 + CLMUL | ``ghash_avx512_clmul`` | 3.11.0 | +| | | | | +| | CLMUL | ``ghash_cpu`` | 1.11.6 | +| | | | | +| | SSSE3 | ``ghash_vperm`` | 1.9.10 | ++-----------+--------------------------------------------+-------------------------+------------+ +| Argon2 | AVX-512 | ``argon2_avx512`` | 3.11.1 | +| | | | | +| | AVX2 | ``argon2_avx2`` | 3.0.0 | +| | | | | +| | SSSE3 | ``argon2_simd64`` | 2.19.2 | ++-----------+--------------------------------------------+-------------------------+------------+ +| ARIA | AVX-512 + GFNI | ``aria_avx512_gfni`` | 3.11.0 | +| | | | | +| | AES-NI | ``aria_hwaes`` | 3.11.1 | ++-----------+--------------------------------------------+-------------------------+------------+ +| Camellia | AVX-512 + GFNI | ``camellia_avx512_gfni``| 3.11.0 | +| | | | | +| | AVX2 + GFNI | ``camellia_avx2_gfni`` | 3.9.0 | +| | | | | +| | AES-NI | ``camellia_hwaes`` | 3.11.1 | ++-----------+--------------------------------------------+-------------------------+------------+ +| ChaCha | AVX-512 | ``chacha_avx512`` | 3.1.0 | +| | | | | +| | AVX2 | ``chacha_avx2`` | 2.8.0 | +| | | | | +| | SSSE3 | ``chacha_simd32`` | 1.11.32 | ++-----------+--------------------------------------------+-------------------------+------------+ +| CTR | AVX2 | ``ctr_avx2`` | 3.11.1 | +| | | | | +| | SSSE3 | ``ctr_simd32`` | 3.11.1 | ++-----------+--------------------------------------------+-------------------------+------------+ +| IDEA | AVX2 | ``idea_avx2`` | 3.11.0 | +| | | | | +| | SSE2 | ``idea_sse2`` | 1.9.4 | ++-----------+--------------------------------------------+-------------------------+------------+ +| NOEKEON | SSSE3 | ``noekeon_simd`` | 1.9.4 | ++-----------+--------------------------------------------+-------------------------+------------+ +| Poly1305 | AVX-512 | ``poly1305_avx512`` | 3.11.0 | +| | | | | +| | AVX2 | ``poly1305_avx2`` | 3.11.0 | ++-----------+--------------------------------------------+-------------------------+------------+ +| RDRAND | RDRAND | ``processor_rng`` | 1.11.31 | ++-----------+--------------------------------------------+-------------------------+------------+ +| RDSEED | RDSEED | ``rdseed`` | 1.11.36 | ++-----------+--------------------------------------------+-------------------------+------------+ +| SEED | AVX-512 + GFNI | ``seed_avx512_gfni`` | 3.11.1 | +| | | | | +| | AES-NI | ``seed_hwaes`` | 3.11.1 | ++-----------+--------------------------------------------+-------------------------+------------+ +| Serpent | AVX-512 | ``serpent_avx512`` | 3.1.0 | +| | | | | +| | AVX2 | ``serpent_avx2`` | 2.8.0 | +| | | | | +| | SSSE3 | ``serpent_simd`` | 1.9.0 | ++-----------+--------------------------------------------+-------------------------+------------+ +| SHACAL2 | Intel SHA Extensions | ``shacal2_x86`` | 2.3.0 | +| | | | | +| | AVX-512 | ``shacal2_avx512`` | 3.9.0 | +| | | | | +| | AVX2 | ``shacal2_avx2`` | 2.13.0 | ++-----------+--------------------------------------------+-------------------------+------------+ +| SHA-1 | Intel SHA Extensions | ``sha1_x86`` | 2.2.0 | +| | | | | +| | AVX2 + BMI2 | ``sha1_avx2`` | 3.9.0 | +| | | | | +| | SSSE3 | ``sha1_simd`` | 1.7.12 | ++-----------+--------------------------------------------+-------------------------+------------+ +| SHA-256 | Intel SHA Extensions | ``sha2_32_x86`` | 2.2.0 | +| | | | | +| | AVX2 + BMI2 | ``sha2_32_avx2`` | 3.8.0 | +| | | | | +| | SSSE3 | ``sha2_32_simd`` | 3.8.0 | ++-----------+--------------------------------------------+-------------------------+------------+ +| SHA-512 | Intel SHA Extensions | ``sha2_64_x86`` | 3.8.0 | +| | | | | +| | AVX-512 + BMI2 | ``sha2_64_avx512`` | 3.8.0 | +| | | | | +| | AVX2 + BMI2 | ``sha2_64_avx2`` | 3.8.0 | ++-----------+--------------------------------------------+-------------------------+------------+ +| SHA-3 / | BMI2 | ``keccak_perm_bmi2`` | 2.10.0 | +| SHAKE / | | | | +| KMAC | AVX-512 | ``keccak_perm_avx512`` | 3.11.0 | ++-----------+--------------------------------------------+-------------------------+------------+ +| SM3 | AVX2 + BMI2 | ``sm3_avx2_bmi2`` | 3.11.0 | +| | | | | +| | SM3-NI | ``sm3_x86`` | 3.11.0 | ++-----------+--------------------------------------------+-------------------------+------------+ +| SM4 | AVX-512 + GFNI | ``sm4_avx512`` | 3.11.0 | +| | | | | +| | AVX2 + GFNI | ``sm4_gfni`` | 3.6.0 | +| | | | | +| | SM4-NI | ``sm4_x86`` | 3.8.0 | +| | | | | +| | AES-NI | ``sm4_hwaes`` | 3.11.1 | ++-----------+--------------------------------------------+-------------------------+------------+ +| Twofish | AVX-512 + GFNI | ``twofish_avx512`` | 3.11.1 | ++-----------+--------------------------------------------+-------------------------+------------+ +| Whirlpool | AVX-512 | ``whirlpool_avx512`` | 3.11.1 | +| | | | | +| | AVX2 | ``whirlpool_avx2`` | 3.11.1 | ++-----------+--------------------------------------------+-------------------------+------------+ +| XTS | AVX-512 + CLMUL | ``xts_avx512_clmul`` | 3.11.0 | ++-----------+--------------------------------------------+-------------------------+------------+ +| ZFEC | SSSE3 | ``zfec_vperm`` | 3.0.0 | ++-----------+--------------------------------------------+-------------------------+------------+ + +ARM +-------------- + +On ARM platforms, the following CPU specific optimizations are available. + +.. note:: + + The ARMv8 cryptography extensions are only used on 64-bit aarch64 systems +-----------+--------------------------------------------+--------------------+------------+ | Algorithm | Extension | Module | Added in | +===========+============================================+====================+============+ -| AES | VAES-AVX2 | `aes_vaes` | 3.6.0 | -| | | | | -| | AES-NI | `aes_ni` | 1.9.3 | +| AES | ARMv8 Cryptography Extensions | ``aes_armv8`` | 2.3.0 | | | | | | -| | SSSE3 | `aes_vperm` | 1.9.10 | +| | NEON | ``aes_vperm`` | 2.12.0 | +-----------+--------------------------------------------+--------------------+------------+ -| AES-GCM | CLMUL | `ghash_cpu` | 1.11.6 | -| | | | | -| | SSSE3 | `ghash_vperm` | 1.9.10 | -+-----------+--------------------------------------------+--------------------+------------+ -| Argon2 | AVX2 | `argon2_avx2` | 3.0.0 | -| | | | | -| | SSSE3 | `argon2_ssse3` | 2.19.2 | +| AES-GCM | ARMv8 Cryptography Extensions | ``ghash_cpu`` | 2.3.0 | +-----------+--------------------------------------------+--------------------+------------+ -| ChaCha | AVX512 (x86-64 only) | `chacha_avx512` | 3.1.0 | -| | | | | -| | AVX2 | `chacha_avx2` | 2.8.0 | -| | | | | -| | SSE2 | `chacha_simd32` | 1.11.32 | +| ARIA | ARMv8 Cryptography Extensions | ``aria_hwaes`` | 3.11.1 | +-----------+--------------------------------------------+--------------------+------------+ -| IDEA | SSE2 | `idea_sse2` | 1.9.4 | +| Camellia | ARMv8 Cryptography Extensions | ``camellia_hwaes`` | 3.11.1 | +-----------+--------------------------------------------+--------------------+------------+ -| KMAC | BMI2 | `keccak_perm_bmi2` | 3.2.0 | +| ChaCha | NEON | ``chacha_simd32`` | 2.8.0 | +-----------+--------------------------------------------+--------------------+------------+ -| NOEKEON | SSE2 | `noekeon_simd` | 1.9.4 | +| NOEKEON | NEON | ``noekeon_simd`` | 1.9.4 | +-----------+--------------------------------------------+--------------------+------------+ -| RDRAND | RDRAND | `processor_rng` | 1.11.31 | +| SEED | ARMv8 Cryptography Extensions | ``seed_hwaes`` | 3.11.1 | +-----------+--------------------------------------------+--------------------+------------+ -| RDSEED | RDSEED | `rdseed` | 1.11.36 | +| Serpent | NEON | ``serpent_simd`` | 1.9.2 | +-----------+--------------------------------------------+--------------------+------------+ -| Serpent | AVX512 (x86-64 only) | `serpent_avx512` | 3.1.0 | +| SHACAL2 | NEON | ``shacal2_simd`` | 2.3.0 | | | | | | -| | AVX2 | `serpent_avx2` | 2.8.0 | +| | ARMv8 Cryptography Extensions | ``shacal2_armv8`` | 2.13.0 | ++-----------+--------------------------------------------+--------------------+------------+ +| SHA-1 | ARMv8 Cryptography Extensions | ``sha1_armv8`` | 2.2.0 | | | | | | -| | SSE2 | `serpent_simd` | 1.9.0 | +| | NEON | ``sha1_simd`` | 3.8.0 | +-----------+--------------------------------------------+--------------------+------------+ -| SHACAL2 | Intel SHA Extensions | `shacal2_x86` | 2.3.0 | +| SHA-256 | ARMv8 Cryptography Extensions | ``sha2_32_armv8`` | 2.2.0 | | | | | | -| | AVX2 | `shacal2_avx2` | 2.13.0 | +| | NEON | ``sha2_32_simd`` | 3.8.0 | +-----------+--------------------------------------------+--------------------+------------+ -| SHAKE | BMI2 | `keccak_perm_bmi2` | 2.13.0 | +| SHA-384 | ARMv8 Cryptography Extensions | ``sha2_64_armv8`` | 3.3.0 | +-----------+--------------------------------------------+--------------------+------------+ -| SHA-1 | Intel SHA Extensions | `sha1_x86` | 2.2.0 | -| | | | | -| | SSE2 | `sha1_sse2` | 1.7.12 | +| SHA-512 | ARMv8 Cryptography Extensions | ``sha2_64_armv8`` | 3.3.0 | +-----------+--------------------------------------------+--------------------+------------+ -| SHA-256 | Intel SHA Extensions | `sha2_32_x86` | 2.2.0 | -| | | | | -| | BMI2 | `sha2_32_bmi2` | 2.7.0 | +| SM3 | ARMv8 Cryptography Extensions | ``sm3_armv8`` | 3.11.0 | +-----------+--------------------------------------------+--------------------+------------+ -| SHA-3 | BMI2 | `keccak_perm_bmi2` | 2.10.0 | +| SM4 | ARMv8 Cryptography Extensions | ``sm4_armv8`` | 2.8.0 | +| | | | | +| | ARMv8 Cryptography Extensions | ``sm4_hwaes`` | 3.11.1 | +-----------+--------------------------------------------+--------------------+------------+ -| SM4 | GFNI | `sm4_gfni` | 3.6.0 | +| ZFEC | NEON | ``zfec_vperm`` | 3.0.0 | +-----------+--------------------------------------------+--------------------+------------+ -ARM +POWER/PowerPC -------------- -On arm64 and arm32 platforms, the following CPU specific optimizations are available: +On 64-bit POWER/PowerPC platforms, the following CPU specific optimizations are available: +-----------+--------------------------------------------+--------------------+------------+ | Algorithm | Extension | Module | Added in | +===========+============================================+====================+============+ -| AES | NEON | `aes_armv8` | 1.9.3 | -+-----------+--------------------------------------------+--------------------+------------+ -| AES-GCM | PMULL (arm64 only) | `ghash_cpu` | 2.3.0 | +| AES | POWER8/POWER9 | ``aes_power8`` | 2.14.0 | | | | | | -| | NEON | `ghash_vperm` | 2.12.0 | +| | AltiVec | ``aes_vperm`` | 2.12.0 | +-----------+--------------------------------------------+--------------------+------------+ -| ChaCha | NEON | `chacha_simd32` | 2.8.0 | +| ChaCha | AltiVec | ``chacha_simd32`` | 2.8.0 | +-----------+--------------------------------------------+--------------------+------------+ -| NOEKEON | NEON | `noekeon_simd` | 1.9.4 | +| DARN | POWER9 | ``processor_rng`` | 2.15.0 | +-----------+--------------------------------------------+--------------------+------------+ -| Serpent | NEON | `serpent_simd` | 1.9.2 | +| Serpent | AltiVec | ``serpent_simd`` | 1.9.2 | +-----------+--------------------------------------------+--------------------+------------+ -| SHACAL2 | NEON | `shacal2_simd` | 2.3.0 | -| | | | | -| | ARMv8 Cryptography Extensions (arm64 only) | `shacal2_armv8` | 2.13.0 | +| SHACAL2 | AltiVec | ``shacal2_simd`` | 2.3.0 | ++-----------+--------------------------------------------+--------------------+------------+ +| NOEKEON | AltiVec | ``noekeon_simd`` | 1.9.4 | ++-----------+--------------------------------------------+--------------------+------------+ + +Loongarch64 +-------------- + +On loongarch64, the LSX extensions are used. + +.. note:: + + Loongarch64 apparently supports a "crypto" extension, for which hwcaps exist + for Linux, and there are shipping processors which do support these + extensions. However no documentation has been so far located. If you are + aware of any such documentation please do contact the maintainers. + +-----------+--------------------------------------------+--------------------+------------+ -| SHA-1 | ARMv8 Cryptography Extensions (arm64 only) | `sha1_armv8` | 2.2.0 | +| Algorithm | Extension | Module | Added in | ++===========+============================================+====================+============+ +| AES | LSX | ``aes_vperm`` | 3.8.0 | ++-----------+--------------------------------------------+--------------------+------------+ +| ChaCha | LSX | ``chacha_simd32`` | 3.8.0 | +-----------+--------------------------------------------+--------------------+------------+ -| SHA-256 | ARMv8 Cryptography Extensions (arm64 only) | `sha2_32_armv8` | 2.2.0 | +| Serpent | LSX | ``serpent_simd`` | 3.8.0 | +-----------+--------------------------------------------+--------------------+------------+ -| SHA-384 | ARMv8 Cryptography Extensions (arm64 only) | `sha2_64_armv8` | 3.3.0 | +| SHA-1 | LSX | ``sha1_simd`` | 3.8.0 | +-----------+--------------------------------------------+--------------------+------------+ -| SHA-512 | ARMv8 Cryptography Extensions (arm64 only) | `sha2_64_armv8` | 3.3.0 | +| SHACAL2 | LSX | ``shacal2_simd`` | 3.8.0 | +-----------+--------------------------------------------+--------------------+------------+ -| SM4 | ARMv8 Cryptography Extensions (arm64 only) | `sm4_armv8` | 2.8.0 | +| NOEKEON | LSX | ``noekeon_simd`` | 3.8.0 | ++-----------+--------------------------------------------+--------------------+------------+ +| ZFEC | LSX | ``zfec_vperm`` | 3.8.0 | +-----------+--------------------------------------------+--------------------+------------+ -PowerPC +Wasm -------------- -On ppc64 and ppc32 platforms, the following CPU specific optimizations are available: +On Wasm, the SIMD128 extension is used. + +.. note:: + + To make use of SIMD128, ````simd128`` compilation flag is required. +-----------+--------------------------------------------+--------------------+------------+ | Algorithm | Extension | Module | Added in | +===========+============================================+====================+============+ -| AES | POWER8/POWER9 | `aes_power8` | 2.14.0 | -| | | | | -| | AltiVec | `aes_vperm` | 2.12.0 | +| AES | SIMD128 | ``aes_vperm`` | 3.11.0 | ++-----------+--------------------------------------------+--------------------+------------+ +| AES-GCM | SIMD128 | ``ghash_vperm`` | 3.11.0 | ++-----------+--------------------------------------------+--------------------+------------+ +| Argon2 | SIMD128 | ``argon2_simd64`` | 3.11.0 | ++-----------+--------------------------------------------+--------------------+------------+ +| ChaCha | SIMD128 | ``chacha_simd32`` | 3.11.0 | +-----------+--------------------------------------------+--------------------+------------+ -| AES-GCM | AltiVec | `ghash_vperm` | 2.12.0 | +| Serpent | SIMD128 | ``serpent_simd`` | 3.11.0 | +-----------+--------------------------------------------+--------------------+------------+ -| ChaCha | AltiVec | `chacha_simd32` | 2.8.0 | +| SHA-1 | SIMD128 | ``sha1_simd`` | 3.11.0 | +-----------+--------------------------------------------+--------------------+------------+ -| DARN | POWER9 | `processor_rng` | 2.15.0 | +| SHA-256 | SIMD128 | ``sha2_32_simd`` | 3.11.0 | +-----------+--------------------------------------------+--------------------+------------+ -| Serpent | AltiVec | `serpent_simd` | 1.9.2 | +| SHACAL2 | SIMD128 | ``shacal2_simd`` | 3.11.0 | +-----------+--------------------------------------------+--------------------+------------+ -| SHACAL2 | AltiVec | `shacal2_simd` | 2.3.0 | +| NOEKEON | SIMD128 | ``noekeon_simd`` | 3.11.0 | +-----------+--------------------------------------------+--------------------+------------+ -| NOEKEON | AltiVec | `noekeon_simd` | 1.9.4 | +| ZFEC | SIMD128 | ``zfec_vperm`` | 3.11.0 | +-----------+--------------------------------------------+--------------------+------------+ Configuring Acceleration ------------------------------ -Hardware acceleration can be disabled at during configuring the build -by passing certain ``--disable-*`` options to ``configure.py``. -This will cause the base software implementation to be used instead -of the hardware accelerated one. The following options are currently supported: - -``--disable-sse2`` - disable SSE2 intrinsics -``--disable-ssse3`` - disable SSSE3 intrinsics -``--disable-sse4.1`` - disable SSE4.1 intrinsics -``--disable-sse4.2`` - disable SSE4.2 intrinsics -``--disable-avx2`` - disable AVX2 intrinsics -``--disable-bmi2`` - disable BMI2 intrinsics -``--disable-rdrand`` - disable RDRAND intrinsics -``--disable-rdseed`` - disable RDSEED intrinsics -``--disable-aes-ni`` - disable AES-NI intrinsics -``--disable-sha-ni`` - disable SHA-NI intrinsics -``--disable-altivec`` - disable AltiVec intrinsics -``--disable-neon`` - disable NEON intrinsics -``--disable-armv8crypto`` - disable ARMv8 Crypto intrinsics -``--disable-powercrypto`` - disable POWER Crypto intrinsics - -Additionally, ``--disable-modules=MODS`` can be used to remove a certain module, -if desirable. - -Last but not least, the ``BOTAN_CLEAR_CPUID`` :doc:`environment variable ` -can be set to a non-empty value *at runtime* to cause Botan to clear the CPUID bits for the CPU -extensions it uses. +If it is desirable to avoid using some form of acceleration, this can be accomplished +*at build time* by using ``--disable-modules=``. For instance, to remove support +of ARMv8 intrinsics for AES, use ``--disable-modules=aes_armv8``. Note that this is rarely +if ever required; if support for the CPU extension is not available at runtime then the +code using that extension will simply be skipped over. The only reason to do this is when +the code is being deployed to a fixed target (eg the specific board used in your product) +and you know that target does not support such an extension, and you wish to minimize code size. + +It is also possible to disable acceleration *at runtime* using +``BOTAN_CLEAR_CPUID`` :doc:`environment variable `. This is the preferred +mode of disabling acceleration. diff -Nru botan3-3.7.1+dfsg/doc/migration_guide.rst botan3-3.12.0+dfsg/doc/migration_guide.rst --- botan3-3.7.1+dfsg/doc/migration_guide.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/migration_guide.rst 2026-05-07 01:38:28.000000000 +0000 @@ -45,7 +45,7 @@ Starting with Botan 3.0 TLS 1.3 is supported. This development required a number of backward-incompatible changes to -accomodate the protocol differences to TLS 1.2, which is still supported. +accommodate the protocol differences to TLS 1.2, which is still supported. Build modules ^^^^^^^^^^^^^ @@ -201,7 +201,7 @@ self-contained encrypted and authenticated tickets) and stateful (identified with unique database handles). -To accomodates this flexibility the `Session_Manager` base class API has changed +To accommodate this flexibility the `Session_Manager` base class API has changed drastically and is now responsible for creation, storage and management of both stateful sessions and stateless session tickets. Sub-classes therefore gain full control over the session ticket's structure and @@ -453,5 +453,5 @@ Applications that rely on a static seed for deterministic RNG output might observe a different byte stream in such cases. As a workaround, users are -advised to "mimick" the legacy behaviour by manually pulling from the RNG in +advised to "mimic" the legacy behaviour by manually pulling from the RNG in "byte limit"-sized chunks and provide the "input" with each invocation. diff -Nru botan3-3.7.1+dfsg/doc/news_2x.rst botan3-3.12.0+dfsg/doc/news_2x.rst --- botan3-3.7.1+dfsg/doc/news_2x.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/news_2x.rst 2026-05-07 01:38:28.000000000 +0000 @@ -1817,8 +1817,8 @@ using ``--disable-modules=pkcs11`` (GH #837) * Add ``OS::run_cpu_instruction_probe`` for runtime probing of ISA extensions. - Supporting this requires system-specific techniques, currently Windows SEH and - Unix signal handling are supported. + Supporting this requires system-specific techniques, currently Windows Structured + Exception Handling and Unix signal handling are supported. * Add support for ARM NEON in the SIMD_4x32 type diff -Nru botan3-3.7.1+dfsg/doc/old_news.rst botan3-3.12.0+dfsg/doc/old_news.rst --- botan3-3.7.1+dfsg/doc/old_news.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/old_news.rst 2026-05-07 01:38:28.000000000 +0000 @@ -82,7 +82,7 @@ * Fix TLS session resumption bugs which caused resumption failures if an application used a single session cache for both TLS and DTLS. (GH #688) -* Add SHAKE-128 and SHAKE-256 XOFs as hash functions supporting paramaterized +* Add SHAKE-128 and SHAKE-256 XOFs as hash functions supporting parameterized output lengths. * Add MessageAuthenticationCode::start_msg interface, for MACs which require or @@ -621,7 +621,7 @@ * Add support for processing X.509 name constraint extension during path validation. GH #454 -* Add X509_Certificate::v3_extensions which allows retreiving the +* Add X509_Certificate::v3_extensions which allows retrieving the raw binary of all certificate extensions, including those which are not known to the library. This allows processing of custom extensions. GH #437 @@ -638,7 +638,7 @@ * SRP6 support is now optional in TLS * Support for negotiating MD5 and SHA-224 signatures in TLS v1.2 has - been removed. MD5 signatures are demonstratably insecure in TLS, + been removed. MD5 signatures are demonstrably insecure in TLS, SHA-224 is rarely used. * Support for negotiating ECC curves secp160r1, secp160r2, secp160k1, @@ -689,7 +689,7 @@ the library itself. GH #430 * Remove use of TickCount64 introduced in 1.11.27 which caused problem - with downstream distributors/users building XP compatiable binaries + with downstream distributors/users building XP compatible binaries which is still an option even in VS 2015 * MCEIES requires KDF1 at runtime but did not require it be enabled @@ -767,7 +767,7 @@ * Use TickCount64 and MemoryStatusEx in the Windows entropy source. Note these calls are only available in Vista/Server 2008. No - accomodations are made for XP or Server 2003, both of which are + accommodations are made for XP or Server 2003, both of which are no longer patched by the vendor. GH #365 Version 1.11.26, 2016-01-04 @@ -796,7 +796,7 @@ extended with new features and options. * Correct an error in PointGFp multiplication when multiplying a point - by the scalar value 3. PointGFp::operator* would instead erronously + by the scalar value 3. PointGFp::operator* would instead erroneously compute it as if the scalar was 1 instead. * Enable RdRand entropy source on Windows/MSVC. GH #364 @@ -855,7 +855,7 @@ * Work around a problem with some antivirus programs which causes the ``shutil.rmtree`` and ``os.makedirs`` Python calls to occasionally - fail. The could prevent ``configure.py`` from running sucessfully + fail. The could prevent ``configure.py`` from running successfully on such systems. GH #353 * Let ``configure.py`` run under CPython 2.6. GH #362 @@ -893,7 +893,7 @@ * Fixed the signature of the FFI function botan_pubkey_destroy, which took the wrong type and was not usable. -* The TLS client would erronously reject any server key exchange packet smaller +* The TLS client would erroneously reject any server key exchange packet smaller than 6 bytes. This prevented negotiating a plain PSK TLS ciphersuite with an empty identity hint. ECDHE_PSK and DHE_PSK suites were not affected. @@ -922,7 +922,7 @@ even when using a deterministic PRNG with the same seed. * In `configure,py`, the flags for controlling use of debug, sanitizer, and - converage information have been split out into individual options + coverage information have been split out into individual options `--with-debug-info`, `--with-sanitizers`, and `--with-coverage`. These allow enabling more than one in a build in a controlled way. The `--build-mode` flag added in 1.11.17 has been removed. @@ -1147,7 +1147,7 @@ create a pointer offset of a ``std::vector``. This failed when x was set equal to ``vec.size()`` to create the one-past-the-end address. The pointer in question was never dereferenced, but it triggered - the iterator debugging checks which prevented using these valuble + the iterator debugging checks which prevented using these valuable analysis tools. From Simon Warta and Daniel Seither. GH #125 * Several incorrect or missing module dependencies have been fixed. These @@ -1207,7 +1207,7 @@ * Added global timeout to HMAC_RNG entropy reseed. The defaults are the values set in the build.h macros ``BOTAN_RNG_AUTO_RESEED_TIMEOUT`` - and ``BOTAN_RNG_RESEED_DEFAULT_TIMEOUT``, but can be overriden + and ``BOTAN_RNG_RESEED_DEFAULT_TIMEOUT``, but can be overridden on a specific poll with the new API call reseed_with_timeout. * Fixed Python cipher update_granularity() and default_nonce_length() @@ -1343,7 +1343,7 @@ * Add SHA-512/256 -* The format of serialized TLS sessions has changed. Additiionally, PEM +* The format of serialized TLS sessions has changed. Additionally, PEM formatted sessions now use the label of "TLS SESSION" instead of "SSL SESSION" * Serialized TLS sessions are now encrypted using AES-256/GCM instead of a @@ -1469,7 +1469,7 @@ * Fixed a bug in CCM mode which caused it to produce incorrect tags when used with a value of L other than 2. This affected CCM TLS ciphersuites, which - use L=3. Thanks to Manuel Pégourié-Gonnard for the anaylsis and patch. + use L=3. Thanks to Manuel Pégourié-Gonnard for the analysis and patch. Bugzilla 270. * DTLS now supports timeouts and handshake retransmits. Timeout checking @@ -1648,7 +1648,7 @@ ``bcrypt``, ``keygen``, ``speed``, and various others. As part of this change many obsolete, duplicated, or one-off examples were removed, while others were extended with new functionality. Contributions of - new subcommands, new bling for exising ones, or documentation in any + new subcommands, new bling for existing ones, or documentation in any form is welcome. * Fix a bug in Lion, which was broken by a change in 1.11.0. The @@ -1722,7 +1722,7 @@ name for the EGD socket. Found by Coverity Scanner. * In PK_Encryptor_EME, PK_Decryptor_EME, PK_Verifier, and PK_Key_Agreement, - avoid dereferencing an unitialized pointer if no engine supported operations + avoid dereferencing an uninitialized pointer if no engine supported operations on the key object given. Found by Coverity scanner. * Avoid leaking a file descriptor in the /dev/random and EGD entropy sources if @@ -2317,7 +2317,7 @@ ``BOTAN_TARGET_ARCH_IS_X86_32``. The classes calling assembly have also been renamed. -* Similiarly to the above change, the AES implemenations using the +* Similarly to the above change, the AES implementations using the AES-NI instruction set have been renamed from AES_XXX_Intel to AES_XXX_NI. @@ -2423,7 +2423,7 @@ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GOST 34.10 signatures were being formatted in a way that was not -compatible with other implemenations, and specifically how GOST is +compatible with other implementations, and specifically how GOST is used in DNSSEC. The Keccak hash function was updated to the tweaked variant proposed @@ -2870,7 +2870,7 @@ Version 1.7.21, 2008-11-11 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -* Make algorithm lookup much more configuable +* Make algorithm lookup much more configurable * Add facilities for runtime performance testing of algorithms * Drop use of entropy estimation in the PRNGs * Increase intervals between HMAC_RNG automatic reseeding @@ -3137,7 +3137,7 @@ Version 1.6.2, 2007-03-24 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -* Fix autodection on Athlon64s running Linux +* Fix autodetection on Athlon64s running Linux * Fix builds on QNX and compilers using STLport * Remove a call to abort() that crept into production @@ -3192,7 +3192,7 @@ * Initialization failures are dealt with somewhat better * Add an example implementing Pollard's Rho algorithm * Better option handling in the test/benchmark tool -* Expand the xor_ciph example to support longer keys +* Expand the example of how to add a stream cipher to support longer keys * Some updates to the documentation Version 1.5.9, 2006-07-12 @@ -3626,7 +3626,7 @@ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * Major improvements in ASN.1 string handling -* Added partial support for ASN.1 UTF8 STRINGs and BMP STRINGs +* Added partial support for ASN.1 UTF8 STRING and BMP STRING types * Added partial support for the X.509v3 certificate policies extension * Centralized the handling of character set information * Added FIPS 140-2 startup self tests @@ -3830,7 +3830,7 @@ * Renamed Rijndael to AES, created aes.h, deleted rijndael.h * Removed support for the 'no_timer' LibraryInitializer option * Removed 'es_pthr' module, pending further testing -* Cleaned up get_ciph.cpp +* Cleaned up cipher factory Version 1.1.12, 2003-04-15 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -3910,7 +3910,7 @@ * Initial support for X.509v3 certificates and CAs * Major redesign/rewrite of the ASN.1 encoding/decoding code -* Added handling for DSA/NR signatures encoded as DER SEQUENCEs +* Added handling for DSA/NR signatures encoded as a DER SEQUENCE * Documented the generic cipher lookup interface * Added an (untested) entropy source for BeOS * Various cleanups and bug fixes @@ -4215,7 +4215,7 @@ * Added ECB and CTS block cipher modes (ecb.h, cts.h) * Added a Mutex interface (mutex.h) * Added module pthr_mux, implementing the Mutex interface -* Added Threaded Filter interface (thr_filt.h) +* Added Threaded Filter interface * All algorithms can now by keyed with SymmetricKey objects * More testing occurs with --validate (expected failures) * Fixed two bugs reported by Hany Greiss, in Luby-Rackoff and RC6 diff -Nru botan3-3.7.1+dfsg/doc/openssl_migration_guide.rst botan3-3.12.0+dfsg/doc/openssl_migration_guide.rst --- botan3-3.7.1+dfsg/doc/openssl_migration_guide.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/openssl_migration_guide.rst 2026-05-07 01:38:28.000000000 +0000 @@ -457,7 +457,7 @@ :language: cpp This example uses the ``PK_Signer`` and ``PK_Verifier`` classes to sign and verify -a message using :ref:`api_ref/pubkey:ecdsa`. The private key is similary +a message using :ref:`api_ref/pubkey:ecdsa`. The private key is similarly :ref:`loaded from a file `. The :doc:`hash function ` is passed as a string parameter. ``PK_Verifier::check_signature()`` is used to diff -Nru botan3-3.7.1+dfsg/doc/packaging.rst botan3-3.12.0+dfsg/doc/packaging.rst --- botan3-3.7.1+dfsg/doc/packaging.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/packaging.rst 2026-05-07 01:38:28.000000000 +0000 @@ -38,11 +38,15 @@ ----------------- Starting in Botan 3.3.0, we ship ``botan-config.cmake`` files. While this config -file is somewhat relocatable, it assumes the default installation directory -structure as generated by ``make install``. If your distribution changes the -directory layout of the installed files you might want to either adapt the final -``botan-config.cmake`` file accordingly or leave it out entirely using -``--without-cmake-config``. +file is somewhat relocatable, it assumes that the installation directory layout +as generated by ``make install`` remains unchanged after installation. If your +distribution changes the directory layout of the installed files you might want +to either adapt the final ``botan-config.cmake`` file accordingly or leave it +out entirely using ``--without-cmake-config``. + +Note that you may change the installed location of these files using the +``--cmakeconfigdir`` option at configure time. However, the location must be a +subdirectory of the install prefix. Please don't hesitate to give your feedback on this new feature by opening a ticket on the upstream GitHub. diff -Nru botan3-3.7.1+dfsg/doc/roadmap.rst botan3-3.12.0+dfsg/doc/roadmap.rst --- botan3-3.7.1+dfsg/doc/roadmap.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/roadmap.rst 2026-05-07 01:38:28.000000000 +0000 @@ -5,37 +5,40 @@ Near Term Plans ---------------------------------------- -Here is an outline of the development plans over the next ~12 months, -as of December 2024. +Here is an outline of the development plans over the next ~12 months, as of +February 2026. Botan2 --------------- -Botan2 is still supported, but no further feature work is planned. -Only security issues and serious bugs will be addressed. - -Currently, Botan2 is scheduled to reach end of life at the end of 2024. +As of 2025-01-01, Botan2 has reached end of life. No further releases are planned. Botan3 --------------- -The following future work is currently planned for Botan3: - -* New ECC based password authenticated key exchanges, to replace SRP. - The most likely candidate algorithms are SPAKE2(+) and CPace. - -* Adding an implementation of BLS12-381 elliptic curve pairing. +The following major feature work is currently planned for Botan3: +* SPAKE2+ password authenticated key exchange +* BLS12-381 * HPKE (RFC 9180) +* XMSS^MT +* HQC, possibly implemented using Rust + +Along with the usual optimizations, bug fixes, and refinements. Botan4 --------------- -At this time there is no immediate plan for a new major version. When it occurs, -it will remove functionality currently marked as deprecated, and adopt a new C++ -version. This is unlikely to occur before 2027, at the earliest. +Botan4 is currently planned for release in 2027. + +See the current planning discussion in https://github.com/randombit/botan/issues/4666 + +Botan4 will continue using C++20 rather than adopting a more recent language version. + +Botan4 is expected to be largely a subtractive major release; +deprecated APIs and functionality will be removed, with few additions. -One major change already planned for Botan4 is that in that release, Public_Key +One notable change planned for Botan4 is that in that release, Public_Key will no longer derive from Private_Key. And similarly, specific private keys (for example RSA_PrivateKey) will no longer derive from their corresponding public key type. diff -Nru botan3-3.7.1+dfsg/doc/security.rst botan3-3.12.0+dfsg/doc/security.rst --- botan3-3.7.1+dfsg/doc/security.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/security.rst 2026-05-07 01:38:28.000000000 +0000 @@ -15,6 +15,79 @@ This key can be found in the file ``doc/pgpkey.txt`` or online at https://keybase.io/jacklloyd and on most PGP keyservers. +2026 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* 2026-05-06 (CVE-2026-44378): BER decoding denial of service + + Certain patterns of indefinite length encodings in BER data could cause + quadratic behavior in the parser, resulting in a denial of service. Up until + Botan 3.12.0, such BER encodings were accepted even in structures which are + required to be encoded as DER. Any party able to transmit any ASN.1 encoded + data, such as a certificate or OCSP response, can induce CPU based denial of + service. + + Fixed in 3.12.0, all prior versions affected + + Credit: yt3 + +* 2026-03-31 (CVE-2026-34582): TLS 1.3 client authentication bypass + + The TLS 1.3 implementation allowed ApplicationData records to be processed + prior to the Finished message being received. A server which is attempting to + enforce client authentication via certificates can by bypassed by a client + which entirely omits Certificate, CertificateVerify, and the Finished message + and instead sends application data records. + + Introduced in 3.0.0, fixed in 3.11.1 + + Credit: Ben Smyth + +* 2026-03-31 (CVE-2026-34580): Certificate verification bypass due to trust anchor confusion + + During path validation, an end-entity certificate whose DN collided with the + DN of a trust anchor would be accepted immediately without further validation. + This bug was introduced in 3.11.0; prior versions are not affected. + + Introduced in 3.11.0, fixed in 3.11.1 + + Credit: Nicholas Carlini with Claude, Anthropic + +* 2026-03-15 (CVE-2026-32883): OCSP Response Forgery + + During verification of X.509 paths involving OCSP responses, Botan omitted checking + that the response signature was itself valid. This would allow a MitM attacker to + insert forged responses. It would also allow a malicious TLS server to staple + forged OCSP responses. + + Introduced in 3.0.0, fixed in 3.11.0 + + Found by Haruto Kimura + +* 2026-03-15 (CVE-2026-32877): Heap Overread During SM2 Decryption + + Decryption of SM2 ciphertexts failed to account for the possibility that the enclosed + MAC was of an invalid length. An invalid ciphertext with a MAC of the wrong length would + cause a heap over-read when the computed MAC value was compared with the insufficiently + sized buffer. This could result in denial of service. + + Introduced in 2.3.0, fixed in 3.11.0 + + Found by Haruto Kimura + +* 2026-03-15 (CVE-2026-32884): Bypass of Name Constraint Exclusion in CN Fallback Case + + If DNS name constraints apply to a certificate, and the certificate does not + contain any Subject Alternative Name extension, Botan checks that the certificates + commonName field (CN) would not be prohibited by the name constraint. However it + failed to account for the possibility that the CN might be mixed case; a certificate + with a mixed case CN and omitted SAN would be accepted even if the DNS name in the + CN violated a name constraint imposed by the issuing chain. + + Introduced in 2.0.0, fixed in 3.11.0 + + Found by Haruto Kimura + 2024 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff -Nru botan3-3.7.1+dfsg/doc/sem_ver.rst botan3-3.12.0+dfsg/doc/sem_ver.rst --- botan3-3.7.1+dfsg/doc/sem_ver.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/sem_ver.rst 2026-05-07 01:38:28.000000000 +0000 @@ -1,7 +1,7 @@ Semantic Versioning ===================== -Starting with 2.0.0, Botan adopted semantic versioning. This means we endevour +Starting with 2.0.0, Botan adopted semantic versioning. This means we endeavour to make no change which will either break compilation of existing code, or cause different behavior in a way that will cause compatibility issues. Such changes are reserved for new major versions. diff -Nru botan3-3.7.1+dfsg/doc/side_channels.rst botan3-3.12.0+dfsg/doc/side_channels.rst --- botan3-3.7.1+dfsg/doc/side_channels.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/side_channels.rst 2026-05-07 01:38:28.000000000 +0000 @@ -1,3 +1,6 @@ + +.. _side_channels: + Side Channels ========================= @@ -78,7 +81,7 @@ For general ``e``, the inversion proceeds using a technique based on the CRT - ``phi(n)`` is factored to ``2**k * o`` for some ``k`` > 1 and some odd ``o``. Then ``e`` is inverted modulo ``2**k`` and also modulo ``o``. The -inversion modulo ``2**k`` is done via a specialized constant-time algoirthm +inversion modulo ``2**k`` is done via a specialized constant-time algorithm which only works for powers of 2. Then the two inversions are combined using the CRT. This process does leak the value of ``k``; when generating keys Botan chooses ``p`` and ``q`` so that ``k`` is always 1. @@ -169,7 +172,7 @@ -------------------------- Several elliptic curve scalar multiplication algorithms are implemented to -accomodate different use cases. The implementations can be found in +accommodate different use cases. The implementations can be found in pcurves_impl.h as PrecomputedBaseMulTable, WindowedMulTable, and WindowedMul2Table. @@ -271,7 +274,7 @@ On all other processors, a constant time bitsliced implementation is used. This is typically slower than the vector permute implementation, and additionally for -best performance multiple blocks must be processed in parellel. So modes such +best performance multiple blocks must be processed in parallel. So modes such as CTR, GCM or XTS are relatively fast, but others such as CBC encryption suffer. @@ -314,7 +317,8 @@ This algorithm uses table lookups with secret sboxes. No cache-based side channel attack on Twofish has ever been published, but it is possible nobody -sufficiently skilled has ever tried. +sufficiently skilled has ever tried. There is also an AVX-512 implementation +which avoids table lookups completely. ChaCha20, Serpent, Threefish, ... ----------------------------------- @@ -354,14 +358,22 @@ trick to zero out an array. If possible an OS provided routine (such as ``RtlSecureZeroMemory`` or ``explicit_bzero``) is used. -On other platforms, by default the trick of referencing memset through a +On other platforms, the trick of referencing memset through a volatile function pointer is used. This approach is not guaranteed to work on all platforms, and currently there is no systematic check of the resulting binary function that it is compiled as expected. But, it is the best approach currently known and has been verified to work as expected on common platforms. -If BOTAN_USE_VOLATILE_MEMSET_FOR_ZERO is set to 0 in build.h (not the default) a -byte at a time loop through a volatile pointer is used to overwrite the array. +Stack Scrubbing +---------------------- + +GCC 14 and newer can emit code that scrubs the stack frames of functions that +handle sensitive information [GCCstrub] after they returned to the caller. This +can reduce the time window for sniffing sensitive information from a process. + +Botan can apply this to certain core routines of fundamental algorithms. For now +this feature is an opt-in. Configure with `--enable-stack-scrubbing` to benefit +from this feature if you are using a compatible version of GCC. Memory allocation ---------------------- @@ -435,7 +447,10 @@ [CoronDpa] Coron, "Resistance against Differential Power Analysis for Elliptic Curve Cryptosystems" -(https://citeseer.ist.psu.edu/viewdoc/summary?doi=10.1.1.1.5695) +(https://citeseerx.ist.psu.edu/document?doi=4d5d6dfdb582c0d695953e92c408f2377a6c9039) + +[GCCstrub] GCC Stack Scrubbing +(https://gcc.gnu.org/onlinedocs/gcc-14.2.0/gcc/Common-Type-Attributes.html#index-strub-type-attribute) [GcdFree] Joye, Paillier "GCD-Free Algorithms for Computing Modular Inverses" (https://marcjoye.github.io/papers/JP03gcdfree.pdf) @@ -452,11 +467,11 @@ elliptic-curve cryptography. (https://safecurves.cr.yp.to) [Lucky13] AlFardan, Paterson "Lucky Thirteen: Breaking the TLS and DTLS Record Protocols" -(http://www.isg.rhul.ac.uk/tls/TLStiming.pdf) +(https://www.isg.rhul.ac.uk/tls/Lucky13.html) [MillionMsg] Bleichenbacher "Chosen Ciphertext Attacks Against Protocols Based on the RSA Encryption Standard PKCS1" -(https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.19.8543) +(https://archiv.infsec.ethz.ch/education/fs08/secsem/bleichenbacher98.pdf) [MillionMsgTiming] Meyer, Somorovsky, Weiss, Schwenk, Schinzel, Tews: Revisiting SSL/TLS Implementations: New Bleichenbacher Side Channels and Attacks @@ -468,7 +483,7 @@ [RsaFault] Boneh, Demillo, Lipton "On the importance of checking cryptographic protocols for faults" -(https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.48.9764) +(https://citeseerx.ist.psu.edu/document?repid=rep1&type=pdf&doi=7622200b9459a8c0e25e74ce7316c2402862e919) [RandomMonty] Le, Tan, Tunstall "Randomizing the Montgomery Powering Ladder" (https://eprint.iacr.org/2015/657) diff -Nru botan3-3.7.1+dfsg/doc/support.rst botan3-3.12.0+dfsg/doc/support.rst --- botan3-3.7.1+dfsg/doc/support.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/support.rst 2026-05-07 01:38:28.000000000 +0000 @@ -14,17 +14,13 @@ * Linux ppc64le, GCC 11.2 or later * Windows x86-64, Visual C++ 2022 or later -.. warning:: - - Starting in Botan 3.8, the minimum required version of Clang will change - to Clang 17. - These platforms are all tested by continuous integration, and the developers have access to hardware in order to test patches. Problems affecting these platforms are considered release blockers. For Botan 3, the tier-2 supported platforms are +* macOS aarch64, latest XCode Clang * macOS x86-64, latest XCode Clang * iOS aarch64, latest XCode Clang * Windows x86-64, latest MinGW GCC @@ -64,16 +60,16 @@ that platform. In theory any working C++20 compiler is fine but in practice, we only regularly -test with GCC, Clang, and Visual C++. Several other compilers (such as IBM XLC, -Intel C++, and Sun Studio) are supported by the build system but are not tested -by the developers and may have build or codegen problems. Patches to improve -support for these compilers is welcome. +test with GCC, Clang, and Visual C++. Several other compilers (such as IBM XLC +and Intel C++) are supported by the build system but are not tested by the +developers and may have build or codegen problems. Patches to improve support +for these compilers is welcome. Branch Support Status ------------------------- Following table provides the support status for Botan branches, as of -January 2025. +August 2025. "Active development" refers to adding new features and optimizations. At the conclusion of the active development phase, only bugfixes are applied. @@ -83,10 +79,9 @@ ============== ============== ========================== ============ Branch First Release End of Active Development End of Life ============== ============== ========================== ============ -Botan 1.8 2008-12-08 2010-08-31 2016-02-13 -Botan 1.10 2011-06-20 2012-07-10 2018-12-31 -Botan 2 2017-01-06 2020-11-05 2024-12-31 -Botan 3 2023-04-11 ? 2027-12-31 or later +Botan2 2017-01-06 2020-11-05 2024-12-31 +Botan3 2023-04-11 2027? 2028-12-31 or later +Botan4 2027? ? ? ============== ============== ========================== ============ Getting Help diff -Nru botan3-3.7.1+dfsg/doc/threat_model.rst botan3-3.12.0+dfsg/doc/threat_model.rst --- botan3-3.7.1+dfsg/doc/threat_model.rst 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/doc/threat_model.rst 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,66 @@ + +Threat Model +===================== + +It is somewhat difficult to fully articulate a threat model for any library since it may +be used in different contexts. However, this document attempts to clearly state which +attackers are considered in-scope (and thus which countermeasures are in place), and which +are not. + +The basic threat model Botan is written for is described well in "The Program Counter +Security Model" (Molnar, Piotrowski, Schultz, Wagner). + +We assume an attacker exists who is capable of colocating their attack code on the same +CPU (eg via SMT) and performing analysis based on side channels in cache, TLB or branch +predictor resources. A somewhat stronger model is in the context of SGX enclaves, where it +is practical for an attacker to cause code in an SGX enclave to single-step the execution +and precisely measure each conditional jump and memory access. + +This also covers the (weaker) threat model of an attacker on the same LAN who is +performing attacks based purely on timing of operations. + +Wherever possible, code that manipulates secret data (for example when generating an ECDSA +signature or decrypting an AES ciphertext) is written to be "constant time"; avoiding any +conditional jumps or memory accesses where the predicate is (derived from) secret +information. Botan uses extensive annotations (``CT::poison``) to indicate which values +are secret, and uses automated analysis (currently using ``valgrind`` similar to Adam +Langley's ``ctgrind`` idea, though support for other tools is welcome) to verify that the +assembly created by the compiler in fact avoids all conditional jumps or memory accesses +that might leak secrets. This testing step is essential as some compilers (notably Clang) +are excellent at performing range analysis of values and will sometimes generate +conditional jumps even when the code as written appears to avoid such operations. Botan's +CI runs these tests automatically against GCC and Clang on x86-64 and aarch64, with a +range of different optimization levels. + +Some algorithms have a structure which allows for very practical blinding/re-randomization +of the operations. This is used as an additional countermeasure in case some particular +combination of compiler, compiler options, and target architecture results in a +conditional jump being inserted in an unexpected place. For example during ECDSA signing, +the inversion of ``k``, the scalar multiplication of ``g*k`` and the recombination of +``x * r + m`` are all blinded, even though all of the relevant arithmetic operations are +written and tested to avoid side channels. + +For more about specific side channel countermeasures, see :ref:`side_channels`. + +Do keep in mind that side channels are intrinsically a property of the *hardware* computer +system which is executing the code. Thus while a variety of best-effort countermeasures +and analysis tools are in place, the absence of any kind of side channel cannot be +guaranteed by a software library on it's own. It can only be verified with a specific +compiled binary on a specific hardware platform. + +Out Of Scope +----------------- + +* Speculative execution attacks such as Spectre are out of scope since countermeasures are + incredibly costly, and there is currently no way to verify that any such countermeasures, + once applied, are effective. + +* Attacks based on ALU side channels (such as contention on the multiplication unit + leaking the Hamming weight of the multiplier) are currently out of scope, though + randomized blinding may be helpful in some circumstances. + +* Power analysis attacks and EM side channel attacks are considered out of scope. + Preventing these attacks requires hardware support and a system-wide view of how leakage + is handled. That said, blinding and rerandomization may provide some protection against + such attacks. Patches which make it easier to use Botan in a system which must address + these issues would be accepted. diff -Nru botan3-3.7.1+dfsg/license.txt botan3-3.12.0+dfsg/license.txt --- botan3-3.7.1+dfsg/license.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/license.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,4 +1,4 @@ -Copyright (C) 1999-2025 The Botan Authors +Copyright (C) 1999-2026 The Botan Authors All rights reserved. Redistribution and use in source and binary forms, with or without diff -Nru botan3-3.7.1+dfsg/news.rst botan3-3.12.0+dfsg/news.rst --- botan3-3.7.1+dfsg/news.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/news.rst 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,485 @@ Release Notes ======================================== +Version 3.12.0, 2026-05-06 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* CVE-2026-44378: Resolve a CPU based denial of service when decoding + BER encoded data. + +* Optimize and improve certificate store search operations (GH #5510) + +* Require strict DER when decoding PKIX types such as certificates (#5521) + +* Discard TLS handshake state once the handshake has completed, retaining + only the data needed for the active connection (GH #5517) + +* Various TLS conformance, hardening, and performance fixes. (GH #5550 + #5551 #5568 #5555) + +* Various X509/PKIX/OCSP optimizations and bug fixes (GH #5535 #5536 #5546 #5554 + #5561 #5562 #5569) + +* Skip OCSP/CRL revocation checks on certificate chains which were already + going to be rejected due to path validation errors (GH #5512) + +* Add ``BER_Decoder::Limits`` which allows controlling what DER/BER syntax is + accepted while decoding. (GH #5507 #5514) + +* Add support for IPv6 name constraints in X.509 certificate path validation, + and add IPv6 address parsing and formatting utilities (GH #5534 #5537) + +* Refactor the Windows system certificate store and add a cache of materialized + certificates to avoid repeated parsing. (GH #5539) + +* Improve handling of unknown X.509 certificate extensions (GH #5518) + +* Skip checking the self-signature of self-signed certificates during parsing (GH #5515) + +* Add an index to ``X509_CRL`` for fast revocation checks (GH #5511) + +* Add ``X509_Certificate::Tag`` for fast searching/indexing of certificates (GH #5509) + +* Change ``X509_Object`` to share immutable state between copies (GH #5504) + +* Fix bugs in handling of indefinite length BER data, including missing + EOC markers being silently accepted (GH #5545) + +* Make certificate path building DFS incremental (GH #5513 #5520 #5521) + +* Avoid sending the TLS ``certificate_type`` extension unless TLS 1.2 is + disabled, since raw public keys are not currently supported in 1.2 (GH #5523) + +* Add support for RFC 9258 PSK import in TLS 1.3 (GH #5523) + +* Avoid truncation of large handshake messages in DTLS (GH #5522) + +* Add ALPN support to the Boost ASIO TLS stream (GH #5428) + +* Upgrade TLS-Anvil and add client-side TLS-Anvil testing (GH #5503) + +* Upgrade BoGo tests (GH #5523 #5556) + +* Add a script for running the NIST ACVP test vectors (GH #5527) + +* Add ``BigInt::signum`` to simplify sign comparisons (GH #5519) + +* Fixes for compiling with GCC 16 (GH #5564) + +* Add DRBG helpers to the C89/FFI interface and Python binding (GH #5527) + +* Add EC scalar and point operations to the C89/FFI interface (GH #5404 #5565) + +* Add NIST key wrap with padding to the Python binding (GH #5521) + +* Enforce maximum input length limits for ChaCha20Poly1305 and GHASH/GCM (GH #5521) + +* Add ``configure.py --without-include-namespace`` to allow installing + headers without the ``botan-3/`` subdirectory (GH #5528) + +Version 3.11.1, 2026-03-31 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* CVE-2026-34580: Resolve certificate verification bypass bug introduced in 3.11.0 (GH #5500) + +* CVE-2026-34582: Resolve TLS 1.3 client authentication bypass (GH #5599) + +* Add optimized Argon2 implementation using AVX512 (GH #5471) + +* Add optimized and constant-time Twofish implementation using AVX512/GFNI (GH #5465) + +* Add optimized and constant-time SEED implementation using AVX512/GFNI (GH #5472) + +* Add optimized and constant-time Whirlpool implementations using AVX2 and AVX512 (GH #5453 #5473) + +* Add SSSE3/NEON and AVX2 optimized codepaths for CTR (GH #5474 #5480) + +* Add constant time implementations of Camellia, ARIA, SEED and SM4 using AES-NI + or ARMv8 AES instructions to implement sbox lookups (GH #5476 #5477 #5479 #5481 #5485 #5492) + +* Improve performance of the AVX512 implementation of SHA-512 especially for Clang (GH #5490) + +* Optimizations for the IDEA modular multiplication (GH #5484) + +* Fix various minor TLS conformance issues flagged by TLS-Anvil (GH #5494 #5498) + +* Fix bug in Ed25519 where an invalid signature checked with PK_Verifier might + cause a later valid signature to be rejected. (GH #5454) + +* Fix a bug in handling of ECDSA DER-encode signatures where an invalid signature + checked with PK_Verifier might cause a later valid signature to be rejected. + (GH #5455) + +* Fix a problem introduced in 3.11.0 which could cause crashes on processors + without SSSE3 support, particularly when compiled by GCC. (GH #5460 #5463 #5469) + +* Fix various new warnings from ``clang-tidy`` 22 (GH #5456) + +* Fix a compilation error introduced in 3.11.0 which prevented using ``ffi`` unless + ``bcrypt`` was also enabled. (GH #5462) + +* Avoid a macro collision with Microsoft headers that could cause a compilation + problem in amalgamation mode. (GH #5486) + +* Enable explicit_bzero, getentropy, getrandom on Hurd (GH #5488) + +Version 3.11.0, 2026-03-15 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* CVE-2026-32877: Fix a heap over-read during SM2 decryption (GH #5450) + +* CVE-2026-32883: Fix an OCSP response forgery vulnerability (GH #5449) + +* CVE-2026-32884: Fix a name constraints bypass for DNS names (GH #5448) + +* Upgrade PKCS #11 wrapper to support v3.2 of the standard (GH #4540) + +* Add support for verifying X509 certificate chains when the trust anchor is + not self signed. (GH #5047) + +* Add support for multiple OCSP responders in an Authority Information Acesss + extension. (GH #5231) + +* Many additions to the C89/FFI interface, especially regarding X.509 + certificates and CRLs, also XOF support (GH #5148 #5166 #5188 #5217 #5220 + #5221 #5222 #5225 #5230 #5232 #5234 #5235 #5236 #5252) + +* Avoid using ISA enabling flags (like `-mavx2` or `/arch:AVX`) in the build + anymore, as this conflicts with precompiled headers and has been known to cause + miscompilations in certain circumstances. (GH #5297 #5260) + +* Add optimized Keccak permutation implementation using AVX-512 (GH #5191) + +* Add optimized SM3 implementations using AVX2/BMI2 (GH #5178), SM3-NI (GH #5183), + and ARMv8 instructions (GH #5444) + +* Add optimized SM4 implementation using AVX-512/GFNI (GH #5192 #5333) + +* Add optimized Camellia implementations using AVX2/GFNI and AVX-512/GFNI (GH #5442) + +* Add optimized ARIA implementation using AVX-512/GFNI (GH #5440) + +* Add AVX2 implementation of IDEA (GH #5447) + +* Ed448 and X448 optimizations (GH #5383) + +* Add AVX-512/CLMUL optimized XTS mode (GH #5251) + +* GCM and GMAC optimizations including new AVX-512 codepaths (GH #5273 #5278 #5379 #5418) + +* Poly1305 optimizations, including AVX2 and AVX-512 implementations (GH #5227) + +* Add new DES implementation using bitslicing (GH #5433) + +* Rewrite Twofish key schedule to avoid use of large tables (GH #5432) + +* Generate Streebog and Whirlpool tables at compile time (GH #5427 #5430 #5434) + +* Various elliptic curve arithmetic optimizations (GH #5186 #5194 #5195 #5196 + #5275 #5387 #5393 #5394 #5400 #5403) + +* Add some inline asm for aarch64 improving multiprecision integer performance (GH #5407) + +* Certain signature and KEM schemes, including XMSS, LMS, FrodoKEM, + ML-KEM/Kyber, and ML-DSA/Dilithium, would fail or produce incorrect results if + multiple threads attempted operations on the same key object concurrently. In + a strict sense uncoordinated multithreaded use of the same object was never + supported, but this usage did work for RSA, ECDSA, and other schemes, and the + previous behavior is potentially quite surprising. Tests have been added to + ensure this usage works for all schemes going forward. (GH #5359 #5361 #5366 + #5367 #5371 #5376 #5380 #5382) + +* Improve handling of constant time and variable time divisions (GH #5176 #5177 #5180) + +* In finite-field Diffie-Hellman use exponent lengths as prescribed in NIST SP 800-56A + and SP 800-56B. (GH #5384) + +* Optimize ECDSA signature setup phase (GH #5173) + +* The R3 versions of Kyber and Dilithium are official deprecated (GH #5368) + +* Check for already known/validated groups when decoding explicit EC parameters (GH #5268) + +* Add support for WebAssembly SIMD, optimizing various algorithms including AES, GCM, + ChaCha, SHA-1, SHA-256, Argon2 and others. (GH #5155 #5163 #5201) + +* Emscripten/WebAssembly improvements including using the new Wasm exception mechanism + (GH #5202) and re-enabling testing in CI with Emscripten (GH #5209) + +* Allow building TLS 1.3 without TLS 1.2 (GH #5292 #5293 #5303 #5309 #5318) + +* Add optional callback to provide a user-defined TLS 1.2 key derivation function (GH #5107) + +* Add scripts to run Wycheproof tests every night in CI (GH #5269) + +* Support for AltiVec on 32-bit PowerPC platforms has been dropped (GH #5266) + +* Many changes to improve library build times (GH #5279 #5280 #5284 #5285 #5286 #5287 #5288 + #5289 #5291 #5294 #5295 #5296 #5300 #5302 #5304 #5314 #5315 #5321 #5323 #5343 #5344 #5345 + #5346 #5347 #5354) + +* Test suite infrastructure cleanups (GH #5327 #5328 #5329 #5330 #5334 #5337 #5338 #5340 + #5341 #5342 #5348 #5351 #5352 #5357) + +* Unroll loops to improve Montgomery reduction performance. (GH #5150) + +* Increase maximum HMAC key length to 8192 bytes. (GH #5156) + +* Python binding additions including custom RNG (GH #5271) and checks for explicit + EC parameter encoding (GH #5282) + +* On MSVC default to using embedded debug info rather than the PDB (GH #5349) + +* Fix various clang-tidy and cppcheck warnings (GH #5172 #5207 #5204 #5205) + +Version 3.10.0, 2025-11-06 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* Add support for Ascon AEAD, hash and XOF from NIST SP 800-232 (GH #5061 #5076 #5097) + +* Add support for building with clang-cl (GH #4255) + +* Optimizations for base58 encoding and decoding (GH #5051) + +* Optimizations for SHA-3/SHAKE (GH #5133) + +* Optimizations for SEED (GH #5147) + +* Optimizations and cleanups for BLAKE2s (GH #5117) + +* Optimizations for Streebog (GH #5111) + +* Add new interface to ``Certificate_Store`` allowing search by issuer DN + plus serial. (GH #5072) + +* Fix a bug preventing botan_srp6_server_session_step1 from being reinvoked + (GH #5112 #5135) + +* Modify some bit operation functions to reduce risk of compilers introducing + non-constant time behavior (GH #5066) + +* Add new FFI functions for loading elliptic curve keys in SEC1 format (GH #5083) + +* Add new FFI functions for viewing the value of a ``botan_mp_t`` (GH #5131) + +* New faster implementation of Jacobi function (GH #5057) + +* Add optimized integer division logic for various special cases (GH #5068 #5077) + +* Correct documentation/comments relating to the maximum output length + that ``botan_mp_to_hex`` might write (GH #5131 #5129) + +* Fix an issue when trying to use CMake older than 3.18 (GH #5098 #5099) + +* Add typing hints to the Python binding (GH #5086 #5092) + +* Fix various issues flagged by the ``ruff`` Python linter (GH #5089) + +* Fix a bug in the Python binding which prevented signing raw bytes with ``PKSign`` + (GH #5082) + +* Update configure to check for Fedora's new location for trust roots (GH #5052) + +* Remove various internal references to "EME", an obsolete term used for RSA + encryption padding that originates from IEEE 1363. (GH #5055) + +* Fix various typos in the source and documentation (GH #5071 #5075 #5114) + +* Add a ``.devcontainer`` setup (GH #5094) + +Version 3.9.0, 2025-08-05 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* Add SHA-1 implementation using AVX2/BMI2 (GH #4852) + +* Add Camellia implementation using GFNI/AVX2 (GH #4848) + +* Add SHACAL2 implementation using AVX512 (GH #4878) + +* The eFrodoKEM TLS 1.3 ciphersuites have changed the suite code to match changes + in OQS. (GH #4900) + +* Add support for TLS 1.2 NULL cipher suites. These suites are disabled in the + build by default, enable ``tls_null`` module to use. (GH #4776) + +* Add support for X.509 extensions from RFC 3779 (GH #4699 #4883 #4884 #4886) + +* Elliptic curve improvements (GH #4841 #4934 #4935 #4937 #4949 $4953 #4991) + +* Add ``EC_Scalar::hash`` following RFC 9380's hash_to_field (GH #4950) + +* Modify the OID lookup system to use a static switch for builtin OIDs. (GH #4896 #4888) + +* Optimizations for X448 and Ed448 (GH #5037) + +* Modify ``BOTAN_CLEAR_CPUID`` so that clearing ``ssse3`` also disables AVX2/AVX512 + (GH #4853) + +* Remove various internal references to "EMSA", an obsolete term used for RSA + signature padding that originates from IEEE 1363. (GH #5008 #5024) + +* Enable support for GCC's "strub" stack clearing. This is disabled by default, use + the ``--enable-stack-scrubbing`` option to turn on. (GH #4882 #4925) + +* Use ``std::span`` in the internal block cipher padding mode interfaces (GH #4873) + +* Properly check DNS label length restrictions when checking wildcards. (GH #4876 #4881) + +* Work around a GCC 13/14 miscompilation when LTO is used (GH #4863 #4862) + +* Fix a bug preventing building ``System_RNG`` with only ``getrandom`` enabled. (GH #4932 #4930) + +* Document the specific threat model the library uses (GH #4955) + +* Remove ``configure.py`` options to disable specific CPU instructions. (GH #4927) + +* Remove ``configure.py`` option ``--with-local-config`` (GH #4905) + +* Add a better interface for encoding optional ASN.1 elements using ``std::optional`` (GH #5001) + +* Internal cleanups relating to multiprecision integers (GH #5009 #5010 #5012 #5014 #5017) + +* Resolve many warnings from ``clang-tidy`` (GH #4907 #4908 #4910 #4912 #4913 #4919 #4920 #4923 + #4924 #4931 #4956 #4957 #4958 #4959 #4960 #4961 #4962 #4963 #4964 #4968 #4969 #4971 #4972 #4973 + #4974 #4975 #4976 #4977 #4978 #4979 #4980 #4981 #4982 #4983 #4984 #4985 #4986 #4987 #4988 #4989 + #4990 #4992 #4993 #4998 #5004 #5005 #5031 #5032 #5034 #5035 #5036) + +* CMake improvements (GH #5022 #5027) + +* CI improvements (GH #4920 #4294 #4926 #4929) + +Version 3.8.1, 2025-05-07 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* Fix a bug that prevented building using the ``fips140`` or ``modern`` module + policies. (GH #4854 #4856) + +* Fix a missing include that caused compilation failures with libc++20 + (GH #4855 #4857) + +Version 3.8.0, 2025-05-06 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* Discussion has started regarding plans for Botan4, current ETA 2027. Check the + tracking ticket in https://github.com/randombit/botan/issues/4666 for the + current plans. + +* Ongoing elliptic curve optimizations and cleanups (GH #4554 #4620 #4623 #4625 + #4627 #4632 #4634 #4686 #4687 #4688 #4689 #4690 #4692 #4695 #4703 #4706 #4708 + #4710 #4711 #4746 #4794) + +* Add support for extended private keys in ML-KEM to handle certain implementations + which do not use the seed encoding. (GH #4817) + +* Add support for SHA-512 instructions added in upcoming Intel processors (GH #4766) + +* Add support for SM4 instructions added in upcoming Intel processors (GH #4768) + +* The SHA-1 implementation using SSE2 has been extended to support NEON and LoongArch LSX. + (GH #4809) + +* Add SHA-256 and SHA-512 implementations using AVX2/BMI2 (GH #4818 #4821) + +* Add SHA-512 implementation using AVX-512/BMI2 (GH #4842 #4849) + +* Add SHA-256 implementation using SSSE3 or NEON for message expansion (GH #4819) + +* The default TLS policy now prefers AES/GCM over ChaCha20Poly1305 (GH #4843) + +* Add support for TLS 1.3 post-quantum KEM secp384r1/ML-KEM-1024 (GH #4752) + +* Fix bugs in the server-side implementation of TLS 1.3 post-quantum hybrid + encryption which affected ciphersuites using NIST curves. (GH #4752) + +* Previously ``build.h`` included various parameters which could be modified by + end users prior to compilation. These have been removed. (GH #4639) + +* Previously ``build.h`` had macros reflecting various information about the + target system, such as ``BOTAN_TARGET_OS_IS_LINUX``. Now all such macros have + been moved to a new internal header. This allows sharing all installed + headers, including ``build.h``, across multiple different builds of the + library, as long as they all have the same version and module selection. This + simplifies vendoring the library. (GH #4642 #4747) + +* Various headers have been modified to minimize the number of inclusions they + make. You may need to modify your application to directly include any headers + which up until now had been implicitly pulled in. (GH #4650) + +* Add an FFI example which also works as a test in CI that prevents accidentally + making changes to ``ffi.h`` or ``build.h`` that make them incompatible with C. (GH #4640) + +* Add new FFI functions regarding stateful private keys (GH #4700), OIDS (GH #4816), + and EC_Group (GH #4834) + +* Add missing checks for null pointer arguments in FFI (#4704) + +* Faster base32 encoding using SWAR technique (GH #4765) + +* Add support for X.509 CRLs with the ``nextUpdate`` field unset. Such CRLs + are prohibited by RFC 5280, but do unfortunately exist within the ecosystem. (GH #4732) + +* When encoding a RSASSA-PSS-Params struct, skip encoding the trailer field + default value, as required by RFC 4055 (GH #4731) + +* Extend vector permute AES to support big-endian AltiVec/VMX systems. (GH #4738) + +* Extend use of POWER VMULL instruction to also support big-endian systems. (GH #4743) + +* Fix encoding extended key usage in PKCS10 requests (GH #4725) + +* Add internal API for hybrid PQ combiner keys (GH #4067) + +* Internal refactorings of CPU feature detection. (GH #4718) + +* Add support for CPU feature detection on RISCV64 (GH #4800 #4815) + +* Add support for the LoongArch LSX SIMD extension in AES, SHA-1, ZFEC, ChaCha (GH #4799) + +* Various SIMD-enabled implementations which previously only required SSE2 now additionally + require SSSE3. Such optimizations will no longer be used on (now quite rare) CPUs which + support SSE2 but not SSSE3. (GH #4803) + +* Optimize parsing of large CRLs (GH #4789 #4790 #4792) + +* Improve performance of RSA public and private key parsing (GH #4793) + +* Add a couple examples of using format preserving encryption (GH #4758) + +* CI cleanups and improvements (GH #4756 #4761 #4762 #4767 #4770 #4812 #4813) + +* The ``Ed25519_PrivateKey`` constructor had behavior that varied based on the + input length. Add explicit ``from_seed`` and ``from_bytes`` functions which + make the two options explicit. (GH #4701 #4702) + +* Add a new cleaner interface for handling ECIES flags (GH #4691) + +* Reduce use of heap in GCM/GMAC (GH #4826) and hex/base64 (GH #4832) + +* New faster Barrett reduction implementation (GH #4835) + +* Internal RSA signature padding cleanups (GH #4635) + +* Cleanups to the implementations of SHA-1 and SHA-256 using SHA-NI (GH #4773 #4774) + +* Cleanups to reduce code size where possible (GH #4775 #4777 #4781 #4825) + +* Fix a bug that caused the tests to skip testing AES-NI if AES-VAES was supported. + (GH #4649) + +* Fix issues with CMake integration when built in Debian-style multiarch setups. + (GH #4839) + +* Now even for purely static library builds, ``-fPIC`` is used to compile the + library objects. This allows linking position independent executables (PIE) + against the static library. (GH #4716) + +* Remove support for NetBSD ``_dlauxinfo`` which did not provide the information + that the library had expected it to. (GH #4736) + +* Add a script for comparing the performance between versions (GH #4693 #4754) + +* Update GHA CodeQL actions (GH #4644) + Version 3.7.1, 2025-02-05 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -34,7 +513,7 @@ all elliptic curves. This is no longer the case. You can re-enable support for specific named curves by adding a ``pcurves`` module, for example ``pcurves_secp256r1`` or ``pcurves_brainpool384r1``. Also in 3.7.0, the old - BigInt based EC arithemtic implementation was moved to ``legacy_ec_point``, + BigInt based EC arithmetic implementation was moved to ``legacy_ec_point``, which is marked as deprecated. Disabling this module will disable support for certain (also deprecated) elliptic curves such as "x962_p239v1" and "secp224k1". It will also disable support for application specific @@ -201,7 +680,7 @@ * Fix certificate validation when the trust root is a self-signed MD2 cert. (GH #4247 #4248) -* Internal "strong types" improvments (GH #4170) +* Internal "strong types" improvements (GH #4170) * Refactor the ``speed`` cli utility (GH #4364 #4367 #4369) @@ -607,8 +1086,8 @@ * Add checks for invalid length AD in Argon2 (GH #3626) -* CI now uses Android NDK 26, and earlier NDKs are not supported - due to limitations of the C++ library in earlier NDKs (GH #3718) +* CI now uses Android NDK 26. Earlier NDK versions are no longer supported + due to limitations in their C++ library implementations. (GH #3718) * Improve support for IBM's XLC compiler (GH #3730) diff -Nru botan3-3.7.1+dfsg/readme.rst botan3-3.12.0+dfsg/readme.rst --- botan3-3.7.1+dfsg/readme.rst 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/readme.rst 2026-05-07 01:38:28.000000000 +0000 @@ -1,8 +1,8 @@ Botan ======================================== -Botan (Japanese for peony flower) is a cryptography library released under the -permissive `Simplified BSD `_ license. +Botan is a C++ cryptography library released under the permissive +`Simplified BSD `_ license. Botan's `goal `_ is to be the best option for production cryptography by offering the tools @@ -73,14 +73,14 @@ February, May, August, and November. The latest release in the Botan3 series is -`3.7.1 `_ -`(sig) `__, -released on 2025-02-05. +`3.12.0 `_ +`(sig) `__, +released on 2026-05-06. Botan2 -------- -Botan2 has, as of 2025-1-1, reached end of life. No further releases are expected. +Botan2 has, as of 2025-01-01, reached end of life. No further releases are expected. The latest release in the Botan2 series is `2.19.5 `_ @@ -129,8 +129,7 @@ * Stream ciphers (X)ChaCha20, (X)Salsa20, RC4 * Hash functions SHA-1, SHA-2, SHA-3, RIPEMD-160, BLAKE2b/BLAKE2s, Skein-512, SM3, Whirlpool * Password hashing schemes Argon2, Scrypt, bcrypt, and PBKDF2 -* Authentication codes HMAC, CMAC, Poly1305, KMAC, SipHash, GMAC -* Non-cryptographic checksums Adler32, CRC24, CRC32 +* Authentication codes HMAC, CMAC, Poly1305, KMAC, GMAC Other Useful Things ---------------------------------------- @@ -150,3 +149,4 @@ * Encoding schemes including hex, base32, base64 and base58 * NIST key wrapping * Boost.Asio compatible TLS client stream +* 24-bit OpenPGP CRC diff -Nru botan3-3.7.1+dfsg/src/.clang-tidy botan3-3.12.0+dfsg/src/.clang-tidy --- botan3-3.7.1+dfsg/src/.clang-tidy 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/.clang-tidy 2026-05-07 01:38:28.000000000 +0000 @@ -1,8 +1,6 @@ --- -# This file was automatically generated by ./src/scripts/dev_tools/run_clang_tidy.py --regenerate-inline-config-file -# -# All manual edits to this file will be lost. Edit the script -# then regenerate this configuration file. +# This file was automatically generated by ./src/scripts/dev_tools/run_clang_tidy.py --regenerate-inline-config-file on 2026-04-24 +# All manual changes will be lost. Edit the script instead. Checks: > bugprone-*, @@ -15,35 +13,9 @@ performance-*, portability-*, readability-*, - -*-named-parameter, - -*-member-init, - -bugprone-lambda-function-name, - -bugprone-unchecked-optional-access, - -bugprone-empty-catch, - -cert-err58-cpp, - -cppcoreguidelines-avoid-const-or-ref-data-members, - -cppcoreguidelines-init-variables, - -cppcoreguidelines-owning-memory, - -cppcoreguidelines-prefer-member-initializer, - -cppcoreguidelines-slicing, - -hicpp-explicit-conversions, - -misc-const-correctness, - -misc-include-cleaner, - -misc-redundant-expression, - -misc-misplaced-const, - -misc-confusable-identifiers, - -modernize-avoid-bind, - -modernize-pass-by-value, - -modernize-use-ranges, - -performance-avoid-endl, - -readability-convert-member-functions-to-static, - -readability-implicit-bool-conversion, - -readability-inconsistent-declaration-parameter-name, - -readability-qualified-auto, - -readability-simplify-boolean-expr, - -readability-static-accessed-through-instance, -*-array-to-pointer-decay, -*-avoid-c-arrays, + -*-deprecated-headers, -*-else-after-return, -*-function-size, -*-magic-numbers, @@ -51,41 +23,46 @@ -*-no-array-decay, -*-use-auto, -*-use-emplace, - -*-deprecated-headers, - -bugprone-argument-comment, - -bugprone-branch-clone, -bugprone-easily-swappable-parameters, + -bugprone-empty-catch, -bugprone-implicit-widening-of-multiplication-result, - -bugprone-suspicious-stringview-data-usage, - -cppcoreguidelines-avoid-do-while, - -cppcoreguidelines-non-private-member-variables-in-classes, - -cppcoreguidelines-pro-bounds-pointer-arithmetic, + -bugprone-unchecked-optional-access, + -cert-dcl21-cpp, + -cppcoreguidelines-avoid-const-or-ref-data-members, + -cppcoreguidelines-pro-bounds-avoid-unchecked-container-access, -cppcoreguidelines-pro-bounds-constant-array-index, + -cppcoreguidelines-pro-bounds-pointer-arithmetic, -cppcoreguidelines-pro-type-const-cast, -cppcoreguidelines-pro-type-reinterpret-cast, - -cppcoreguidelines-pro-type-vararg, - -hicpp-no-assembler, - -hicpp-vararg, + -cppcoreguidelines-use-default-member-init, -hicpp-signed-bitwise, + -misc-include-cleaner, + -misc-multiple-inheritance, -misc-no-recursion, - -modernize-loop-convert, - -modernize-raw-string-literal, - -modernize-use-trailing-return-type, + -misc-override-with-different-visibility, + -modernize-avoid-c-style-cast, + -modernize-pass-by-value, -modernize-return-braced-init-list, -modernize-use-default-member-init, -modernize-use-designated-initializers, -modernize-use-nodiscard, + -modernize-use-ranges, + -modernize-use-trailing-return-type, -modernize-use-using, - -portability-simd-intrinsics, + -performance-avoid-endl, -readability-avoid-return-with-void-value, - -readability-container-data-pointer, + -readability-convert-member-functions-to-static, -readability-function-cognitive-complexity, -readability-identifier-length, - -readability-isolate-declaration, + -readability-inconsistent-declaration-parameter-name, -readability-math-missing-parentheses, -readability-non-const-parameter, -readability-redundant-access-specifiers, - -readability-suspicious-call-argument, - -readability-use-std-min-max, + -readability-redundant-inline-specifier, + -readability-redundant-parentheses, + -readability-redundant-typename, + -readability-simplify-boolean-expr, + -readability-static-accessed-through-instance, -readability-use-anyofallof, + -readability-use-concise-preprocessor-directives, --- diff -Nru botan3-3.7.1+dfsg/src/bogo_shim/bogo_shim.cpp botan3-3.12.0+dfsg/src/bogo_shim/bogo_shim.cpp --- botan3-3.7.1+dfsg/src/bogo_shim/bogo_shim.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/bogo_shim/bogo_shim.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -11,28 +11,46 @@ */ #include +#include #include +#include #include +#include #include #include #include #include #include +#include #include #include +#include +#include #include +#include #include #include #include +#include #include #include #include #include +#include +#if defined(BOTAN_HAS_TLS_13) + #include +#endif + +#include +#include #include +#include +#include #include #include #include +#include #include #include #include @@ -56,41 +74,46 @@ return rc; } -void shim_log(const std::string& s) { - if(::getenv("BOTAN_BOGO_SHIM_LOG")) { - /* - FIXMEs: - - Rewrite this to use a std::ostream instead - - Allow using the env variable to point to where the log is written - - Avoid rechecking the env variable with each call (!) - */ - - // NOLINTNEXTLINE(*-avoid-non-const-global-variables) - static FILE* g_log = std::fopen("/tmp/bogo_shim.log", "w"); - struct timeval tv; - ::gettimeofday(&tv, nullptr); - static_cast(std::fprintf(g_log, - "%lld.%lu: %s\n", - static_cast(tv.tv_sec), - static_cast(tv.tv_usec), - s.c_str())); - static_cast(std::fflush(g_log)); +void shim_log(std::string_view s) { + static const auto log_path = []() -> std::string { + const char* env = ::getenv("BOTAN_BOGO_SHIM_LOG"); + if(env == nullptr) { + return {}; + } + + auto log_file_path = std::string(env); + if(log_file_path.empty() || log_file_path == "1") { + return "/tmp/bogo_shim.log"; + } + return env; + }(); + + if(!log_path.empty()) { + static std::ofstream g_log(log_path, std::ios::out | std::ios::trunc); + if(g_log.is_open() && g_log.good()) { + const auto duration = std::chrono::system_clock::now().time_since_epoch(); + const auto seconds = std::chrono::duration_cast>(duration); + + g_log << std::fixed << std::setprecision(6) << seconds.count() << ": " << s << std::endl; + } } } -[[noreturn]] void shim_exit_with_error(const std::string& s, int rc = 1) { +[[noreturn]] void shim_exit_with_error(const std::string& s, int rc = 1) noexcept { shim_log("Exiting with " + s); std::cerr << s << "\n"; std::exit(rc); } -std::string map_to_bogo_error(const std::string& e) { +std::string map_to_bogo_error(const std::string& e) noexcept { shim_log("Original error " + e); static const std::unordered_map err_map{ {"Application data before handshake done", ":APPLICATION_DATA_INSTEAD_OF_HANDSHAKE:"}, {"Bad Hello_Request, has non-zero size", ":BAD_HELLO_REQUEST:"}, {"Bad code for TLS alert level", ":UNKNOWN_ALERT_TYPE:"}, + {"Bad encoding of SNI extension", ":DECODE_ERROR:"}, + {"Server sent non-empty SNI extension", ":DECODE_ERROR:"}, {"Bad encoding on signature algorithms extension", ":DECODE_ERROR:"}, {"Bad extension size", ":DECODE_ERROR:"}, {"Bad length in hello verify request", ":DECODE_ERROR:"}, @@ -99,6 +122,8 @@ {"Server certificate verification failed", ":BAD_SIGNATURE:"}, {"compression is not supported in TLS 1.3", ":DECODE_ERROR:"}, {"Cookie length must be at least 1 byte", ":DECODE_ERROR:"}, + {"Empty certificate_authorities list is illegal", ":DECODE_ERROR:"}, + {"Empty cookie extension is illegal", ":DECODE_ERROR:"}, {"Bad size (1) for TLS alert message", ":BAD_ALERT:"}, {"Bad size (4) for TLS alert message", ":BAD_ALERT:"}, {"CERTIFICATE decoding failed with PEM: No PEM header found", ":CANNOT_PARSE_LEAF_CERT:"}, @@ -120,6 +145,7 @@ {"Client did not comply with the requested key exchange group", ":WRONG_CURVE:"}, {"Client Hello must either contain both key_share and supported_groups extensions or neither", ":MISSING_KEY_SHARE:"}, + {"Server Hello did not contain a key share extension", ":MISSING_KEY_SHARE:"}, {"Client Hello offered a PSK without a psk_key_exchange_modes extension", ":MISSING_EXTENSION:"}, {"Client offered DTLS version with major version 0xFF", ":UNSUPPORTED_PROTOCOL:"}, {"Client offered SSLv3 which is not supported", ":UNSUPPORTED_PROTOCOL:"}, @@ -148,6 +174,7 @@ {"Empty PSK binders list", ":DECODE_ERROR: "}, {"Encoding error: Cannot encode PSS string, output length too small", ":NO_COMMON_SIGNATURE_ALGORITHMS:"}, {"Expected TLS but got a record with DTLS version", ":WRONG_VERSION_NUMBER:"}, + {"Expected ChangeCipherSpec but got a handshake message", ":UNEXPECTED_RECORD:"}, {"Extension removed in updated Client Hello", ":INCONSISTENT_CLIENT_HELLO:"}, {"Failed to agree on a signature algorithm", ":NO_COMMON_SIGNATURE_ALGORITHMS:"}, {"Failed to agree on any signature algorithm", ":NO_COMMON_SIGNATURE_ALGORITHMS:"}, @@ -156,17 +183,20 @@ ":NO_COMMON_SIGNATURE_ALGORITHMS:"}, {"PSK extension was not at the very end of the Client Hello", ":PRE_SHARED_KEY_MUST_BE_LAST:"}, {"Finished message didn't verify", ":DIGEST_CHECK_FAILED:"}, + {"Handshake message is 2293760 bytes, policy maximum is 65536", ":BAD_HANDSHAKE_RECORD:"}, {"Have data remaining in buffer after ClientHello", ":EXCESS_HANDSHAKE_DATA:"}, {"Have data remaining in buffer after Finished", ":EXCESS_HANDSHAKE_DATA:"}, {"Have data remaining in buffer after ServerHelloDone", ":EXCESS_HANDSHAKE_DATA:"}, {"Hello Retry Request does not request any changes to Client Hello", ":EMPTY_HELLO_RETRY_REQUEST:"}, {"Unexpected additional handshake message data found in record", ":EXCESS_HANDSHAKE_DATA:"}, {"Inconsistent length in certificate request", ":DECODE_ERROR:"}, + {"Inconsistent length in certificate_authorities extension", ":DECODE_ERROR:"}, {"unexpected key_update parameter", ":DECODE_ERROR:"}, {"Inconsistent values in fragmented DTLS handshake header", ":FRAGMENT_MISMATCH:"}, {"Invalid CertificateRequest: Length field outside parameters", ":DECODE_ERROR:"}, {"Invalid ServerHello: Length field outside parameters", ":DECODE_ERROR:"}, {"Invalid CertificateVerify: Extra bytes at end of message", ":DECODE_ERROR:"}, + {"Invalid Certificate_Status message: too small", ":DECODE_ERROR:"}, {"Invalid Certificate_Status: invalid length field", ":DECODE_ERROR:"}, {"Invalid ChangeCipherSpec", ":BAD_CHANGE_CIPHER_SPEC:"}, {"Invalid ClientHello: Length field outside parameters", ":DECODE_ERROR:"}, @@ -176,6 +206,7 @@ {"Invalid authentication tag: ChaCha20Poly1305 tag check failed", ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:"}, {"Invalid authentication tag: GCM tag check failed", ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:"}, {"Invalid encapsulated key length", ":BAD_ECPOINT:"}, + {"Invalid handshake message type", ":UNEXPECTED_RECORD:"}, {"Invalid hybrid KEM ciphertext", ":BAD_ECPOINT:"}, {"Invalid size 31 for X25519 public key", ":BAD_ECPOINT:"}, {"Invalid size 33 for X25519 public key", ":BAD_ECPOINT:"}, @@ -185,13 +216,17 @@ {"No shared TLS version", ":UNSUPPORTED_PROTOCOL:"}, {"OS2ECP: Unknown format type 251", ":BAD_ECPOINT:"}, {"Peer sent signature algorithm that is not suitable for TLS 1.3", ":WRONG_SIGNATURE_TYPE:"}, + {"Public key does not have the correct byte count", ":BAD_ECPOINT:"}, {"Policy forbids all available DTLS version", ":NO_SUPPORTED_VERSIONS_ENABLED:"}, {"Policy forbids all available TLS version", ":NO_SUPPORTED_VERSIONS_ENABLED:"}, {"Policy refuses to accept signing with any hash supported by peer", ":NO_COMMON_SIGNATURE_ALGORITHMS:"}, + {"Could not agree on a signature scheme with peer for RSA key", ":NO_COMMON_SIGNATURE_ALGORITHMS:"}, + {"Could not agree on a signature scheme with peer for ECDSA key", ":NO_COMMON_SIGNATURE_ALGORITHMS:"}, {"Policy requires client send a certificate, but it did not", ":PEER_DID_NOT_RETURN_A_CERTIFICATE:"}, {"PSK binder does not check out", ":DIGEST_CHECK_FAILED:"}, {"PSK identity selected by server is out of bounds", ":PSK_IDENTITY_NOT_FOUND:"}, {"PSK and ciphersuite selected by server are not compatible", ":OLD_SESSION_PRF_HASH_MISMATCH:"}, + {"Received an unexpectedly non-empty Certificate_Status_Request", ":DECODE_ERROR:"}, {"Received a record that exceeds maximum size", ":ENCRYPTED_LENGTH_TOO_LONG:"}, {"Received an encrypted record that exceeds maximum size", ":ENCRYPTED_LENGTH_TOO_LONG:"}, {"received an illegal handshake message", ":UNEXPECTED_MESSAGE:"}, @@ -224,6 +259,7 @@ {"Server replied with unsupported extensions: 0", ":UNEXPECTED_EXTENSION:"}, {"Server replied with unsupported extensions: 1234", ":UNEXPECTED_EXTENSION:"}, {"Server replied with unsupported extensions: 16", ":UNEXPECTED_EXTENSION:"}, + {"Server replied with unsupported extensions: 20", ":UNEXPECTED_EXTENSION:"}, {"Server replied with unsupported extensions: 43", ":UNEXPECTED_EXTENSION:"}, {"Server replied with unsupported extensions: 5", ":UNEXPECTED_EXTENSION:"}, {"Server resumed session and removed extended master secret", ":RESUMED_EMS_SESSION_WITHOUT_EMS_EXTENSION:"}, @@ -231,10 +267,12 @@ {"Server resumed session but with wrong version", ":OLD_SESSION_VERSION_NOT_RETURNED:"}, {"Server selected a group that is not compatible with the negotiated ciphersuite", ":WRONG_CURVE:"}, {"Server sent ECC curve prohibited by policy", ":WRONG_CURVE:"}, + {"Server selected a curve we did not offer", ":WRONG_CURVE:"}, {"group was not advertised as supported", ":WRONG_CURVE:"}, {"group was already offered", ":WRONG_CURVE:"}, {"Server selected a key exchange group we didn't offer.", ":WRONG_CURVE:"}, {"TLS 1.3 Server Hello selected a different version", ":SECOND_SERVERHELLO_VERSION_MISMATCH:"}, + {"TLS signature extension did not allow for RSA_PSS_SHA256 signature", ":WRONG_SIGNATURE_TYPE:"}, {"Version downgrade received after Hello Retry", ":SECOND_SERVERHELLO_VERSION_MISMATCH:"}, {"protected change cipher spec received", ":UNEXPECTED_RECORD:"}, {"Server sent an unsupported extension", ":UNEXPECTED_EXTENSION:"}, @@ -257,7 +295,7 @@ {"TLS record version had unexpected value", ":WRONG_VERSION_NUMBER:"}, {"Test requires rejecting cert", ":CERTIFICATE_VERIFY_FAILED:"}, {"Too many PSK binders", ":PSK_IDENTITY_BINDER_COUNT_MISMATCH:"}, - {"Unexpected ALPN protocol", ":INVALID_ALPN_PROTOCOL:"}, + {"Server selected an ALPN protocol not offered by the client", ":INVALID_ALPN_PROTOCOL:"}, {"Unexpected record type 42 from counterparty", ":UNEXPECTED_RECORD:"}, {"Unexpected state transition in handshake got a certificate_request expected server_hello_done seen server_hello+server_key_exchange", ":UNEXPECTED_MESSAGE:"}, @@ -300,6 +338,10 @@ ":UNEXPECTED_MESSAGE:"}, {"Unexpected state transition in handshake got a server_key_exchange not expecting messages", ":BAD_HELLO_REQUEST:"}, + {"Unexpected state transition in handshake got a certificate_request expected finished seen server_hello+encrypted_extensions", + ":UNEXPECTED_MESSAGE:"}, + {"Unexpected state transition in handshake got a certificate expected finished seen server_hello+encrypted_extensions", + ":UNEXPECTED_MESSAGE:"}, {"Unexpected state transition in handshake got a finished expected certificate_verify seen server_hello+certificate+encrypted_extensions", ":BAD_HELLO_REQUEST:"}, {"Unknown TLS handshake message type 43", ":UNEXPECTED_MESSAGE:"}, @@ -331,6 +373,12 @@ {"Peer sent unknown signature scheme", ":WRONG_SIGNATURE_TYPE:"}, {"We did not offer the usage of RSA_PSS_SHA256 as a signature scheme", ":WRONG_SIGNATURE_TYPE:"}, {"X25519 public point appears to be of low order", ":BAD_ECPOINT:"}, + {"TLS signature extension did not allow for RSA/SHA-256 signature", ":WRONG_SIGNATURE_TYPE:"}, + {"No sufficient server certificate available", ":PSK_IDENTITY_NOT_FOUND:"}, + {"Failed to agree on certificate_type", ":UNSUPPORTED_CERTIFICATE:"}, + {"Selected certificate type was not offered: X509", ":UNSUPPORTED_CERTIFICATE:"}, + {"Certificate type extension contains no types", ":DECODE_ERROR:"}, + {"Application did not provide a means to validate the raw public key", ":CERTIFICATE_VERIFY_FAILED:"}, }; auto err_map_i = err_map.find(e); @@ -343,7 +391,7 @@ class Shim_Exception final : public std::exception { public: - Shim_Exception(std::string_view msg, int rc = 1) : m_msg(msg), m_rc(rc) {} + explicit Shim_Exception(std::string_view msg, int rc = 1) : m_msg(msg), m_rc(rc) {} const char* what() const noexcept override { return m_msg.c_str(); } @@ -365,24 +413,23 @@ static std::string get_last_socket_error() { return ::strerror(errno); } - using unique_addrinfo_t = std::unique_ptr; + using unique_addr_info_ptr = std::unique_ptr; public: Shim_Socket(const std::string& hostname, int port, const bool ipv6) : m_socket(-1) { - addrinfo hints; - std::memset(&hints, 0, sizeof(hints)); + addrinfo hints{}; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_NUMERICSERV; const std::string service = std::to_string(port); - // TODO: C++23 will introduce std::out_ptr() that should replace the - // temporary variable for the call to ::getaddrinfo() and - // std::unique_ptr<>::reset(). - unique_addrinfo_t::pointer res_tmp; - int rc = ::getaddrinfo(hostname.c_str(), service.c_str(), &hints, &res_tmp); - unique_addrinfo_t res(res_tmp, &::freeaddrinfo); + unique_addr_info_ptr res = nullptr; + const int rc = ::getaddrinfo(hostname.c_str(), service.c_str(), &hints, Botan::out_ptr(res)); shim_log("Connecting " + hostname + ":" + service); @@ -390,7 +437,7 @@ throw Shim_Exception("Name resolution failed for " + hostname); } - for(addrinfo* rp = res.get(); (m_socket == -1) && (rp != nullptr); rp = rp->ai_next) { + for(const addrinfo* rp = res.get(); (m_socket == -1) && (rp != nullptr); rp = rp->ai_next) { if((!ipv6 && rp->ai_family != AF_INET) || (ipv6 && rp->ai_family != AF_INET6)) { continue; } @@ -402,7 +449,7 @@ continue; } - int err = ::connect(m_socket, rp->ai_addr, rp->ai_addrlen); + const int err = ::connect(m_socket, rp->ai_addr, rp->ai_addrlen); if(err != 0) { ::close(m_socket); @@ -422,8 +469,18 @@ Shim_Socket& operator=(Shim_Socket&&) = delete; ~Shim_Socket() { - ::close(m_socket); - m_socket = -1; + if(m_socket >= 0) { + // Signal that we are done writing so pending alert records + // are delivered with a FIN rather than lost to a RST. + ::shutdown(m_socket, SHUT_WR); + // Drain unread incoming data; if the receive buffer is + // non-empty when we close(), the kernel sends RST which + // discards our outgoing data (including any alert we sent). + char buf[256]; + while(::read(m_socket, buf, sizeof(buf)) > 0) {} + ::close(m_socket); + m_socket = -1; + } } void write(const uint8_t buf[], size_t len) const { @@ -433,7 +490,7 @@ size_t sent_so_far = 0; while(sent_so_far != len) { const size_t left = len - sent_so_far; - socket_op_ret_type sent = + const socket_op_ret_type sent = ::send(m_socket, Botan::cast_uint8_ptr_to_char(&buf[sent_so_far]), left, MSG_NOSIGNAL); if(sent < 0) { if(errno == EPIPE) { @@ -451,7 +508,7 @@ if(m_socket < 0) { throw Shim_Exception("Socket was bad on read"); } - socket_op_ret_type got = ::read(m_socket, Botan::cast_uint8_ptr_to_char(buf), len); + const socket_op_ret_type got = ::read(m_socket, Botan::cast_uint8_ptr_to_char(buf), len); if(got < 0) { if(errno == ECONNRESET) { @@ -469,7 +526,7 @@ } while(len > 0) { - socket_op_ret_type got = ::read(m_socket, Botan::cast_uint8_ptr_to_char(buf), len); + const socket_op_ret_type got = ::read(m_socket, Botan::cast_uint8_ptr_to_char(buf), len); if(got == 0) { throw Shim_Exception("Socket read EOF"); @@ -593,7 +650,7 @@ std::vector get_alpn_string_vec_opt(const std::string& option) const { // hack used for alpn list (relies on all ALPNs being 3 chars long...) - char delim = 0x03; + const char delim = 0x03; if(option_used(option)) { return Botan::split_on(get_string_opt(option), delim); @@ -606,15 +663,17 @@ if(!m_all_options.contains(key)) { throw Shim_Exception("Invalid option " + key); } - if(m_parsed_opts.find(key) != m_parsed_opts.end()) { + if(m_parsed_opts.contains(key)) { return true; } - if(m_parsed_int_vec_opts.find(key) != m_parsed_int_vec_opts.end()) { + if(m_parsed_int_vec_opts.contains(key)) { return true; } return false; } + const std::vector& raw_argv() const { return m_raw_argv; } + private: std::string get_opt(const std::string& key) const { auto i = m_parsed_opts.find(key); @@ -634,9 +693,113 @@ std::set m_parsed_flags; std::map m_parsed_opts; std::map> m_parsed_int_vec_opts; + std::vector m_raw_argv; }; +// A credential block parsed from a `-new-{x509,rpk,psk}-credential` argv segment. +// X509 and RPK blocks share cert/key file fields; the public key for an RPK +// block is derived from the loaded private key. +struct Shim_Credential { + enum class Kind : uint8_t { X509, RPK, PSK }; + + Kind kind = Kind::X509; + std::string cert_file; + std::string key_file; + Botan::secure_vector psk_key; + std::vector psk_identity; + std::vector psk_context; + std::string psk_hash; + + std::shared_ptr key; + std::shared_ptr raw_public_key; + std::vector cert_chain; +}; + +// Walk raw argv and extract `-new-{x509,rpk,psk}-credential` blocks. Each block +// captures the per-credential flags that follow until the next `-new-*-credential` +// or end of args. `-on-resume-*` blocks are ignored (we do not differentiate +// initial vs resume credentials). +std::vector parse_credential_blocks(const std::vector& argv) { + std::vector creds; + std::optional current; + + auto flush = [&]() { + if(current.has_value()) { + creds.push_back(std::move(*current)); + current.reset(); + } + }; + + for(size_t i = 1; i < argv.size(); ++i) { + const auto& arg = argv[i]; + + auto start_block = [&](Shim_Credential::Kind k) { + flush(); + Shim_Credential block; + block.kind = k; + current = std::move(block); + }; + + if(arg == "-new-x509-credential") { + start_block(Shim_Credential::Kind::X509); + } else if(arg == "-new-rpk-credential") { + start_block(Shim_Credential::Kind::RPK); + } else if(arg == "-new-psk-credential") { + start_block(Shim_Credential::Kind::PSK); + } else if(arg.starts_with("-on-resume-new-") || arg.starts_with("-new-")) { + // Unsupported credential block kind (resume, SPAKE2+, delegated, etc.). + flush(); + } else if(current.has_value()) { + auto take_arg = [&]() -> std::optional { + if(i + 1 < argv.size()) { + return argv[++i]; + } + return std::nullopt; + }; + + if(arg == "-cert-file") { + if(auto v = take_arg()) { + current->cert_file = *v; + } + } else if(arg == "-key-file") { + if(auto v = take_arg()) { + current->key_file = *v; + } + } else if(arg == "-psk-importer-key") { + if(auto v = take_arg()) { + current->psk_key = Botan::base64_decode(*v); + } + } else if(arg == "-psk-importer-identity") { + if(auto v = take_arg()) { + auto decoded = Botan::base64_decode(*v); + current->psk_identity.assign(decoded.begin(), decoded.end()); + } + } else if(arg == "-psk-importer-context") { + if(auto v = take_arg()) { + auto decoded = Botan::base64_decode(*v); + current->psk_context.assign(decoded.begin(), decoded.end()); + } + } else if(arg == "-psk-importer-sha256") { + current->psk_hash = "SHA-256"; + } else if(arg == "-psk-importer-sha384") { + current->psk_hash = "SHA-384"; + } + // Other per-credential fields (ocsp-response, signing-prefs, must-match-issuer, + // signed-cert-timestamps, trust-anchor-id, delegated-credential, pake-*) are + // accepted by the global parser but their semantics are not enforced here. + } + } + + flush(); + return creds; +} + void Shim_Arguments::parse_args(char* argv[]) { + // Store raw argv for later credential parsing + for(int j = 0; argv[j] != nullptr; ++j) { + m_raw_argv.emplace_back(argv[j]); + } + int i = 1; // skip argv[0] while(argv[i] != nullptr) { @@ -653,7 +816,7 @@ if(argv[i + 1] == nullptr) { throw Shim_Exception("Expected argument following " + param); } - std::string val(argv[i + 1]); + const std::string val(argv[i + 1]); shim_log(Botan::fmt("param {}={}", flag_name, val)); if(m_int_vec_opts.contains(flag_name)) { @@ -695,6 +858,7 @@ //"expect-accept-early-data", "expect-extended-master-secret", "expect-no-offer-early-data", + "expect-no-peer-cert", "expect-no-secure-renegotiation", "expect-no-session", "expect-no-session-id", @@ -729,6 +893,11 @@ "is-handshaker-supported", //"jdk11-workaround", "key-update", + "no-key-shares", + "new-psk-credential", + "new-rpk-credential", + "new-x509-credential", + "must-match-issuer", "no-check-client-certificate-type", "no-check-ecdsa-curve", "no-op-extra-handshake", @@ -738,10 +907,16 @@ "no-tls11", "no-tls12", "no-tls13", + "on-resume-expect-no-session", + //"on-resume-new-psk-credential", "on-resume-no-ticket", + //"on-resume-psk-importer-sha256", + //"on-resume-psk-importer-sha384", //"on-resume-verify-fail", //"partial-write", //"peek-then-read", + "psk-importer-sha256", + "psk-importer-sha384", //"read-with-unfinished-write", "reject-alpn", "renegotiate-freely", @@ -809,9 +984,19 @@ "expect-certificate-types", //"expect-channel-id", "expect-ocsp-response", + "expect-peer-rpk-sha256", + "delegated-credential", + "signed-cert-timestamps", + "trust-anchor-id", //"expect-quic-transport-params", //"expect-signed-cert-timestamps", "ocsp-response", + "on-resume-psk-importer-context", + "on-resume-psk-importer-identity", + "on-resume-psk-importer-key", + "psk-importer-context", + "psk-importer-identity", + "psk-importer-key", //"quic-transport-params", //"signed-cert-timestamps", //"ticket-key", /* we use a different ticket format from Boring */ @@ -821,7 +1006,12 @@ const std::set bogo_shim_int_opts{ "expect-cipher-aes", "expect-cipher-no-aes", + "expect-client-certificate-type", "expect-curve-id", + "expect-selected-credential", + "on-initial-expect-selected-credential", + "on-resume-expect-selected-credential", + "expect-peer-certificate-type", "expect-peer-signature-algorithm", "expect-ticket-age-skew", "expect-token-binding-param", @@ -845,8 +1035,10 @@ }; const std::set bogo_shim_int_vec_opts{ + "accepted-peer-cert-types", "curves", "expect-peer-verify-pref", + "key-shares", "signing-prefs", "verify-prefs", }; @@ -862,7 +1054,7 @@ class Shim_Policy final : public Botan::TLS::Policy { public: - Shim_Policy(const Shim_Arguments& args) : m_args(args), m_sessions(0) {} + explicit Shim_Policy(const Shim_Arguments& args) : m_args(args), m_sessions(0) {} void incr_session_established() { m_sessions += 1; } @@ -913,7 +1105,7 @@ std::vector allowed_signature_hashes() const override { if(m_args.option_used("signing-prefs")) { std::vector pref_hash; - for(size_t pref : m_args.get_int_vec_opt("signing-prefs")) { + for(const size_t pref : m_args.get_int_vec_opt("signing-prefs")) { const Botan::TLS::Signature_Scheme scheme(pref); if(!scheme.is_available()) { shim_log("skipping inavailable but preferred signature scheme: " + std::to_string(pref)); @@ -944,7 +1136,7 @@ std::vector acceptable_signature_schemes() const override { if(m_args.option_used("verify-prefs")) { std::vector schemes; - for(size_t pref : m_args.get_int_vec_opt("verify-prefs")) { + for(const size_t pref : m_args.get_int_vec_opt("verify-prefs")) { schemes.emplace_back(static_cast(pref)); } @@ -957,7 +1149,7 @@ std::vector allowed_signature_schemes() const override { if(m_args.option_used("signing-prefs")) { std::vector schemes; - for(size_t pref : m_args.get_int_vec_opt("signing-prefs")) { + for(const size_t pref : m_args.get_int_vec_opt("signing-prefs")) { schemes.emplace_back(static_cast(pref)); } @@ -988,13 +1180,9 @@ if(m_args.option_used("curves")) { std::vector groups; - // upcall to base class to find the groups actually supported by - // this Botan build - const auto supported_groups = Botan::TLS::Policy::key_exchange_groups(); - - for(size_t pref : m_args.get_int_vec_opt("curves")) { + for(const size_t pref : m_args.get_int_vec_opt("curves")) { const auto group = static_cast(pref); - if(std::find(supported_groups.cbegin(), supported_groups.cend(), group) != supported_groups.end()) { + if(group.to_string().has_value() && group.is_available()) { groups.push_back(group); } } @@ -1005,6 +1193,53 @@ return Botan::TLS::Policy::key_exchange_groups(); } + std::vector key_exchange_groups_to_offer() const override { + if(m_args.flag_set("no-key-shares")) { + return {}; + } + + const auto groups = key_exchange_groups(); + + if(m_args.option_used("key-shares")) { + // BoGo's -key-shares specifies an explicit subset of -curves to + // pre-emptively send key_share entries for. The list must be in + // the same relative order as key_exchange_groups(). + std::vector to_offer; + for(const size_t pref : m_args.get_int_vec_opt("key-shares")) { + const auto group = static_cast(pref); + if(group.to_string().has_value() && group.is_available()) { + to_offer.push_back(group); + } + } + return to_offer; + } + + // Default: offer key shares for the first classical group and the + // first post-quantum group, matching BoringSSL's default heuristic. + std::vector to_offer; + bool have_classical = false; + bool have_pq = false; + + for(auto g : groups) { + if(g.is_post_quantum()) { + if(!have_pq) { + to_offer.push_back(g); + have_pq = true; + } + } else { + if(!have_classical) { + to_offer.push_back(g); + have_classical = true; + } + } + if(have_classical && have_pq) { + break; + } + } + + return to_offer; + } + bool use_ecc_point_compression() const override { return false; } // BoGo expects this Botan::TLS::Group_Params choose_key_exchange_group( @@ -1033,16 +1268,12 @@ require_client_certificate_authentication(); } - bool allow_insecure_renegotiation() const override { - if(m_args.flag_set("expect-no-secure-renegotiation")) { - return true; - } else { - return false; - } - } + bool allow_insecure_renegotiation() const override { return m_args.flag_set("expect-no-secure-renegotiation"); } //bool include_time_in_hello_random() const override; + uint64_t minimum_key_update_interval_ms() const override { return 0; } + bool allow_client_initiated_renegotiation() const override { if(m_args.flag_set("renegotiate-freely")) { return true; @@ -1062,7 +1293,7 @@ bool allow_version(Botan::TLS::Protocol_Version version) const { if(m_args.option_used("min-version")) { const uint16_t min_version_16 = static_cast(m_args.get_int_opt("min-version")); - Botan::TLS::Protocol_Version min_version(min_version_16 >> 8, min_version_16 & 0xFF); + const Botan::TLS::Protocol_Version min_version(min_version_16 >> 8, min_version_16 & 0xFF); if(min_version > version) { return false; } @@ -1070,7 +1301,7 @@ if(m_args.option_used("max-version")) { const uint16_t max_version_16 = static_cast(m_args.get_int_opt("max-version")); - Botan::TLS::Protocol_Version max_version(max_version_16 >> 8, max_version_16 & 0xFF); + const Botan::TLS::Protocol_Version max_version(max_version_16 >> 8, max_version_16 & 0xFF); if(version > max_version) { return false; } @@ -1080,6 +1311,26 @@ } bool allow_tls12() const override { + // Botan implements RFC 7250 raw public keys only in its TLS 1.3 code + // path. When a test configures RawPublicKey as an accepted peer + // certificate type on the client side, disable TLS 1.2 so the TLS 1.3 + // ClientHello builder does not filter RawPublicKey out of the + // advertised certificate_type extension. + if(m_args.option_used("accepted-peer-cert-types") && !m_args.flag_set("server")) { + for(const size_t t : m_args.get_int_vec_opt("accepted-peer-cert-types")) { + if(static_cast(t) == Botan::TLS::Certificate_Type::RawPublicKey) { + return false; + } + } + } + // Likewise, when we have an RPK credential configured (server side or + // when the client uses -new-rpk-credential), disable TLS 1.2 so the + // ClientHello builder emits the certificate_type extensions. + for(const auto& a : m_args.raw_argv()) { + if(a == "-new-rpk-credential") { + return false; + } + } return !m_args.flag_set("dtls") && !m_args.flag_set("no-tls12") && allow_version(Botan::TLS::Protocol_Version::TLS_V12); } @@ -1118,7 +1369,7 @@ std::vector srtp_profiles() const override { if(m_args.option_used("srtp-profiles")) { - std::string srtp = m_args.get_string_opt("srtp-profiles"); + const std::string srtp = m_args.get_string_opt("srtp-profiles"); if(srtp == "SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32") { return {1, 2}; @@ -1138,6 +1389,8 @@ //bool negotiate_encrypt_then_mac() const override; + bool require_extended_master_secret() const override { return false; } + bool support_cert_status_message() const override { if(m_args.flag_set("server")) { if(!m_args.option_used("ocsp-response")) { @@ -1162,15 +1415,69 @@ //size_t dtls_maximum_timeout() const override; bool abort_connection_on_undesired_renegotiation() const override { - if(m_args.flag_set("renegotiate-ignore")) { - return false; - } else { - return true; - } + return !m_args.flag_set("renegotiate-ignore"); } size_t maximum_certificate_chain_size() const override { return m_args.get_int_opt_or_else("max-cert-list", 0); } + std::vector accepted_client_certificate_types() const override { + if(m_args.option_used("accepted-peer-cert-types") && m_args.flag_set("server")) { + std::vector types; + for(const size_t t : m_args.get_int_vec_opt("accepted-peer-cert-types")) { + types.push_back(static_cast(t)); + } + return types; + } + // As a client, advertise the cert types we have credentials for. + if(!m_args.flag_set("server")) { + if(auto types = configured_credential_types(); !types.empty()) { + return types; + } + } + return Botan::TLS::Policy::accepted_client_certificate_types(); + } + + std::vector accepted_server_certificate_types() const override { + if(m_args.option_used("accepted-peer-cert-types") && !m_args.flag_set("server")) { + std::vector types; + for(const size_t t : m_args.get_int_vec_opt("accepted-peer-cert-types")) { + types.push_back(static_cast(t)); + } + return types; + } + // As a server, advertise the cert types we have credentials for. + if(m_args.flag_set("server")) { + if(auto types = configured_credential_types(); !types.empty()) { + return types; + } + } + return Botan::TLS::Policy::accepted_server_certificate_types(); + } + + private: + // Scan raw argv for `-new-x509-credential` / `-new-rpk-credential` block markers + // and report which Certificate_Types are backed by available credentials, + // preserving the order in which credentials were configured (the first + // credential is the most-preferred one). + std::vector configured_credential_types() const { + std::vector types; + for(const auto& a : m_args.raw_argv()) { + Botan::TLS::Certificate_Type t = Botan::TLS::Certificate_Type::X509; + if(a == "-new-rpk-credential") { + t = Botan::TLS::Certificate_Type::RawPublicKey; + } else if(a == "-new-x509-credential") { + t = Botan::TLS::Certificate_Type::X509; + } else { + continue; + } + if(std::find(types.begin(), types.end(), t) == types.end()) { + types.push_back(t); + } + } + return types; + } + + public: bool tls_13_middlebox_compatibility_mode() const override { // These tests expect the client to send an alert in return of a malformed TLS 1.2 server hello. // However, our TLS 1.3 implementation produces an alert without downgrading to TLS 1.2 first. @@ -1185,11 +1492,7 @@ "MinimumVersion-Client-TLS13-TLS12-TLS", "MinimumVersion-Client2-TLS13-TLS12-TLS", }; - if(Botan::value_exists(alert_after_server_hello, m_args.test_name())) { - return false; - } - - return true; + return !Botan::value_exists(alert_after_server_hello, m_args.test_name()); } private: @@ -1203,7 +1506,7 @@ const std::string cipher_limit = m_args.get_string_opt_or_else("cipher", ""); if(cipher_limit == "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:[TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384|TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256|TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA]:TLS_RSA_WITH_AES_128_GCM_SHA256:TLS_RSA_WITH_AES_128_CBC_SHA:[TLS_RSA_WITH_AES_256_GCM_SHA384|TLS_RSA_WITH_AES_256_CBC_SHA]") { - std::vector suites = { + const std::vector suites = { "ECDHE_RSA_WITH_AES_128_GCM_SHA256", "ECDHE_RSA_WITH_AES_256_GCM_SHA384", "ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", @@ -1222,16 +1525,17 @@ } else { // Hack: go in reverse order to avoid preferring 3DES auto ciphersuites = Botan::TLS::Ciphersuite::all_known_ciphersuites(); + // TODO(Botan4) use std::ranges::reverse_view here once available (need newer Clang) + // NOLINTNEXTLINE(modernize-loop-convert) for(auto i = ciphersuites.rbegin(); i != ciphersuites.rend(); ++i) { const auto suite = *i; - // Can we use it? - if(suite.valid() == false || !suite.usable_in_version(version) || - !Botan::value_exists(allowed_ciphers(), suite.cipher_algo())) { - continue; - } + const bool usable = suite.valid() && suite.usable_in_version(version) && + Botan::value_exists(allowed_ciphers(), suite.cipher_algo()); - ciphersuite_codes.push_back(suite.ciphersuite_code()); + if(usable) { + ciphersuite_codes.push_back(suite.ciphersuite_code()); + } } } @@ -1240,7 +1544,7 @@ class Shim_Credentials final : public Botan::Credentials_Manager { public: - Shim_Credentials(const Shim_Arguments& args) : m_args(args) { + explicit Shim_Credentials(const Shim_Arguments& args) : m_args(args) { const auto psk_identity = m_args.get_string_opt_or_else("psk-identity", ""); const auto psk_str = m_args.get_string_opt_or_else("psk", ""); @@ -1254,17 +1558,23 @@ m_psk = Botan::SymmetricKey(reinterpret_cast(psk_str.data()), psk_str.size()); } - if(m_args.option_used("key-file") && m_args.option_used("cert-file")) { - Botan::DataSource_Stream key_stream(m_args.get_string_opt("key-file")); - m_key.reset(Botan::PKCS8::load_key(key_stream).release()); - - Botan::DataSource_Stream cert_stream(m_args.get_string_opt("cert-file")); - - while(!cert_stream.end_of_data()) { - try { - m_cert_chain.push_back(Botan::X509_Certificate(cert_stream)); - } catch(...) {} - } + m_credentials = parse_credential_blocks(m_args.raw_argv()); + for(auto& cred : m_credentials) { + load_credential(cred); + } + + // Legacy default cert/key (no `-new-*-credential` block) — only honored + // when no X509/RPK credential blocks were provided. + const bool has_block_cert = std::any_of(m_credentials.begin(), m_credentials.end(), [](const auto& c) { + return c.kind == Shim_Credential::Kind::X509 || c.kind == Shim_Credential::Kind::RPK; + }); + if(!has_block_cert && m_args.option_used("key-file") && m_args.option_used("cert-file")) { + Shim_Credential cred; + cred.kind = Shim_Credential::Kind::X509; + cred.key_file = m_args.get_string_opt("key-file"); + cred.cert_file = m_args.get_string_opt("cert-file"); + load_credential(cred); + m_credentials.push_back(std::move(cred)); } if(m_args.option_used("trust-cert") && !m_args.get_string_opt("trust-cert").empty()) { @@ -1277,6 +1587,31 @@ } } + private: + static void load_credential(Shim_Credential& cred) { + if(cred.kind == Shim_Credential::Kind::PSK) { + return; + } + if(cred.key_file.empty()) { + return; + } + Botan::DataSource_Stream key_stream(cred.key_file); + cred.key.reset(Botan::PKCS8::load_key(key_stream).release()); + + if(cred.kind == Shim_Credential::Kind::X509) { + Botan::DataSource_Stream cert_stream(cred.cert_file); + while(!cert_stream.end_of_data()) { + try { + cred.cert_chain.emplace_back(cert_stream); + } catch(...) {} + } + } else { + // RPK: derive the public key from the loaded private key. + cred.raw_public_key = cred.key->public_key(); + } + } + + public: std::vector trusted_certificate_authorities(const std::string& type, const std::string& context) override { if(m_args.flag_set("server") && type != "tls-server") { @@ -1317,6 +1652,41 @@ Botan::TLS::Connection_Side whoami, const std::vector& identities = {}, const std::optional& prf = std::nullopt) override { + std::vector psks; +#if defined(BOTAN_HAS_TLS_13) + // TLS 1.3 PSK credentials from -new-psk-credential blocks + const Botan::TLS::Protocol_Version target_version(Botan::TLS::Protocol_Version::TLS_V13); + bool any_psk_block = false; + + for(const auto& cred : m_credentials) { + if(cred.kind != Shim_Credential::Kind::PSK) { + continue; + } + any_psk_block = true; + const Botan::TLS::PSKImporter importer( + cred.psk_key, cred.psk_identity, cred.psk_context, cred.psk_hash.empty() ? "SHA-256" : cred.psk_hash); + + // Import each credential against both SHA-256 and SHA-384 cipher suites. + for(const auto& target_hash : {"SHA-256", "SHA-384"}) { + if(prf.has_value() && *prf != target_hash) { + continue; + } + + auto imported = importer.derive_imported_psk(target_version, target_hash); + + if(!identities.empty() && + std::find(identities.begin(), identities.end(), imported.identity()) == identities.end()) { + continue; + } + + psks.push_back(std::move(imported)); + } + } + if(any_psk_block) { + return psks; + } +#endif + // Legacy TLS 1.2 PSK from -psk / -psk-identity flags if(!m_psk_identity.has_value()) { return Botan::Credentials_Manager::find_preshared_keys(host, whoami, identities, prf); } @@ -1332,52 +1702,82 @@ throw Shim_Exception("PSK identified but not set"); } - std::vector psks; - - // Currently, BoGo tests PSK with TLS 1.2 only. In TLS 1.2 the PRF does not - // need to be specified for PSKs. - // - // TODO: Once BoGo has tests for TLS 1.3 with externally provided PSKs, this - // will need to be handled somehow. const std::string psk_prf = "SHA-256"; psks.emplace_back(m_psk_identity.value(), psk_prf, m_psk->bits_of()); return psks; } - std::vector cert_chain( + std::vector find_cert_chain( const std::vector& cert_key_types, const std::vector& /*cert_signature_schemes*/, + const std::vector& /*acceptable_CAs*/, const std::string& /*type*/, const std::string& /*context*/) override { if(m_args.flag_set("fail-cert-callback")) { throw std::runtime_error("Simulating cert verify callback failure"); } - if(m_key != nullptr && !m_cert_chain.empty()) { - for(const std::string& t : cert_key_types) { - if(t == m_key->algo_name()) { - return m_cert_chain; - } + for(const auto& cred : m_credentials) { + if(cred.kind != Shim_Credential::Kind::X509 || cred.key == nullptr || cred.cert_chain.empty()) { + continue; + } + if(cert_key_types.empty() || + std::find(cert_key_types.begin(), cert_key_types.end(), cred.key->algo_name()) != cert_key_types.end()) { + return cred.cert_chain; } } return {}; } - std::shared_ptr private_key_for(const Botan::X509_Certificate& /*cert*/, + std::shared_ptr find_raw_public_key(const std::vector& key_types, + const std::string& /*type*/, + const std::string& /*context*/) override { + for(const auto& cred : m_credentials) { + if(cred.kind != Shim_Credential::Kind::RPK || cred.raw_public_key == nullptr) { + continue; + } + if(key_types.empty() || + std::find(key_types.begin(), key_types.end(), cred.raw_public_key->algo_name()) != key_types.end()) { + return cred.raw_public_key; + } + } + return nullptr; + } + + std::shared_ptr private_key_for(const Botan::X509_Certificate& cert, const std::string& /*type*/, const std::string& /*context*/) override { - // assumes cert == m_cert - return m_key; + for(const auto& cred : m_credentials) { + if(cred.kind == Shim_Credential::Kind::X509 && !cred.cert_chain.empty() && + cred.cert_chain.front() == cert) { + return cred.key; + } + } + return nullptr; + } + + std::shared_ptr private_key_for(const Botan::Public_Key& raw_public_key, + const std::string& /*type*/, + const std::string& /*context*/) override { + const auto wanted = raw_public_key.public_key_bits(); + for(const auto& cred : m_credentials) { + if(cred.kind != Shim_Credential::Kind::RPK || cred.raw_public_key == nullptr) { + continue; + } + if(cred.raw_public_key->public_key_bits() == wanted) { + return cred.key; + } + } + return nullptr; } private: const Shim_Arguments& m_args; std::optional m_psk; std::optional m_psk_identity; - std::shared_ptr m_key; - std::vector m_cert_chain; Botan::Certificate_Store_In_Memory m_trust_roots; + std::vector m_credentials; }; class Shim_Callbacks final : public Botan::TLS::Callbacks { @@ -1426,8 +1826,8 @@ } } - std::vector tls_provide_cert_status(const std::vector&, - const Botan::TLS::Certificate_Status_Request&) override { + std::vector tls_provide_cert_status(const std::vector& /*certs*/, + const Botan::TLS::Certificate_Status_Request& /*status*/) override { if(m_args.flag_set("use-ocsp-callback") && m_args.flag_set("fail-ocsp-callback")) { throw std::runtime_error("Simulating failure from OCSP response callback"); } @@ -1508,8 +1908,8 @@ } if(!cert_chain.empty() && cert_chain.front().is_self_signed()) { - for(const auto roots : trusted_roots) { - if(roots->certificate_known(cert_chain.front())) { + for(auto* const roots : trusted_roots) { + if(roots->contains(cert_chain.front())) { shim_log("Trusting self-signed certificate"); return; } @@ -1522,6 +1922,31 @@ cert_chain, ocsp_responses, trusted_roots, usage, "" /* hostname */, policy); } + void tls_verify_raw_public_key(const Botan::Public_Key& raw_public_key, + Botan::Usage_Type /*usage*/, + std::string_view /*hostname*/, + const Botan::TLS::Policy& /*policy*/) override { + if(m_args.flag_set("verify-fail")) { + auto alert = Botan::TLS::Alert::HandshakeFailure; + if(m_args.flag_set("use-custom-verify-callback")) { + alert = Botan::TLS::Alert::CertificateUnknown; + } + throw Botan::TLS::TLS_Exception(alert, "Test requires rejecting cert"); + } + + if(m_args.option_used("expect-peer-rpk-sha256")) { + const auto expected = m_args.get_b64_opt("expect-peer-rpk-sha256"); + const auto spki = raw_public_key.subject_public_key(); + auto sha256 = Botan::HashFunction::create_or_throw("SHA-256"); + sha256->update(spki); + const auto digest = sha256->final_stdvec(); + if(digest != expected) { + throw Botan::TLS::TLS_Exception(Botan::TLS::Alert::CertificateUnknown, + "Raw public key SHA-256 did not match -expect-peer-rpk-sha256"); + } + } + } + std::optional tls_parse_ocsp_response(const std::vector& raw_response) override { if(m_args.option_used("expect-ocsp-response") && m_args.get_b64_opt("expect-ocsp-response") != raw_response) { shim_exit_with_error("unexpected OCSP response"); @@ -1607,7 +2032,7 @@ } if(alert.type() == Botan::TLS::Alert::CloseNotify) { - if(m_got_close == false && !m_args.flag_set("shim-shuts-down")) { + if(!m_got_close && !m_args.flag_set("shim-shuts-down")) { shim_log("Sending return close notify"); m_channel->send_alert(alert); } @@ -1619,8 +2044,8 @@ void tls_session_established(const Botan::TLS::Session_Summary& session) override { shim_log("Session established: " + Botan::hex_encode(session.session_id().get()) + " version " + - session.version().to_string() + " cipher " + session.ciphersuite().to_string() + " EMS " + - std::to_string(session.supports_extended_master_secret())); + session.version().to_string() + " cipher " + session.ciphersuite().to_string() + " " + + std::string((session.supports_extended_master_secret() ? "with EMS" : "without EMS"))); // probably need tests here? m_policy.incr_session_established(); @@ -1642,17 +2067,17 @@ } if(m_args.flag_set("expect-secure-renegotiation")) { - if(m_channel->secure_renegotiation_supported() == false) { + if(!m_channel->secure_renegotiation_supported()) { shim_exit_with_error("Expected secure renegotiation"); } } else if(m_args.flag_set("expect-no-secure-renegotiation")) { - if(m_channel->secure_renegotiation_supported() == true) { - shim_exit_with_error("Expected no secure renegotation"); + if(m_channel->secure_renegotiation_supported()) { + shim_exit_with_error("Expected no secure renegotiation"); } } if(m_args.flag_set("expect-extended-master-secret")) { - if(session.supports_extended_master_secret() == false) { + if(!session.supports_extended_master_secret()) { shim_exit_with_error("Expected extended maseter secret"); } } @@ -1664,7 +2089,7 @@ return; } - if(size_t length = m_args.get_int_opt_or_else("export-keying-material", 0)) { + if(const size_t length = m_args.get_int_opt_or_else("export-keying-material", 0)) { const std::string label = m_args.get_string_opt("export-label"); const std::string context = m_args.get_string_opt("export-context"); const auto exported = m_channel->key_material_export(label, context, length); @@ -1680,10 +2105,6 @@ } } - if(alpn == "baz" && !m_args.flag_set("allow-unknown-alpn-protos")) { - throw Botan::TLS::TLS_Exception(Botan::TLS::Alert::IllegalParameter, "Unexpected ALPN protocol"); - } - if(m_args.flag_set("shim-shuts-down")) { shim_log("Shim shutting down"); m_channel->close(); @@ -1694,7 +2115,7 @@ std::vector buf(32769, 0x42); - for(size_t sz : record_sizes) { + for(const size_t sz : record_sizes) { m_channel->send(buf.data(), sz); } @@ -1790,7 +2211,7 @@ // *before* any test data is transferred // See: https://github.com/google/boringssl/commit/50ee09552cde1c2019bef24520848d041920cfd4 shim_log("Sending ShimID: " + std::to_string(args->get_int_opt("shim-id"))); - std::array shim_id; + std::array shim_id{}; Botan::store_le(static_cast(args->get_int_opt("shim-id")), shim_id.data()); socket.write(shim_id.data(), shim_id.size()); @@ -1807,7 +2228,7 @@ if(is_server) { chan = std::make_unique(callbacks, session_manager, creds, policy, rng, is_datagram); } else { - Botan::TLS::Protocol_Version offer_version = policy->latest_supported_version(is_datagram); + const Botan::TLS::Protocol_Version offer_version = policy->latest_supported_version(is_datagram); shim_log("Offering " + offer_version.to_string()); std::string host_name = args->get_string_opt_or_else("host-name", hostname); @@ -1815,7 +2236,7 @@ host_name = ""; // avoid sending SNI for this test } - Botan::TLS::Server_Information server_info(host_name, port); + const Botan::TLS::Server_Information server_info(host_name, port); const std::vector next_protocols = args->get_alpn_string_vec_opt("advertise-alpn"); chan = std::make_unique( callbacks, session_manager, creds, policy, rng, server_info, offer_version, next_protocols); @@ -1827,8 +2248,8 @@ for(;;) { if(is_datagram) { - uint8_t opcode; - size_t got = socket.read(&opcode, 1); + uint8_t opcode = 0; + const size_t got = socket.read(&opcode, 1); if(got == 0) { shim_log("EOF on socket"); break; @@ -1838,7 +2259,7 @@ uint8_t len_bytes[4]; socket.read_exactly(len_bytes, sizeof(len_bytes)); - size_t packet_len = Botan::load_be(len_bytes, 0); + const size_t packet_len = Botan::load_be(len_bytes, 0); if(buf.size() < packet_len) { buf.resize(packet_len); @@ -1847,7 +2268,7 @@ chan->received_data(buf.data(), packet_len); } else if(opcode == 'T') { - uint8_t timeout_ack = 't'; + const uint8_t timeout_ack = 't'; uint8_t timeout_bytes[8]; socket.read_exactly(timeout_bytes, sizeof(timeout_bytes)); @@ -1863,7 +2284,7 @@ shim_exit_with_error("Unknown opcode " + std::to_string(opcode)); } } else { - size_t got = socket.read(buf.data(), buf.size()); + const size_t got = socket.read(buf.data(), buf.size()); if(got == 0) { shim_log("EOF on socket"); break; @@ -1883,7 +2304,7 @@ } const size_t needed = chan->received_data(buf.data(), got); - if(needed) { + if(needed > 0) { shim_log("Short read still need " + std::to_string(needed)); } } diff -Nru botan3-3.7.1+dfsg/src/bogo_shim/config.json botan3-3.12.0+dfsg/src/bogo_shim/config.json --- botan3-3.7.1+dfsg/src/bogo_shim/config.json 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/bogo_shim/config.json 2026-05-07 01:38:28.000000000 +0000 @@ -8,7 +8,6 @@ "InvalidECDHPoint-Server": "Unexpected error", "NoSharedCipher": "Unexpected error", "NoSharedCipher-TLS13": "Unexpected error", - "PartialFinishedWithServerHelloDone": "Unexpected record vs excess handshake data", "HelloRetryRequest-DuplicateCurve-TLS13": "expects 'illegal parameter' but we want to stick with 'decode error'", "HelloRetryRequest-DuplicateCookie-TLS13": "expects 'illegal parameter' but we want to stick with 'decode error'", @@ -16,13 +15,11 @@ "ClientSkipCertificateVerify-TLS13": "would require ambiguous error mapping", "Resume-Client-Mismatch-TLS13-TLS12-TLS": "server requests a downgrade to TLS 1.2, echoing the random session ID during a TLS 1.3 resumption. => error mapping conflict", "ServerAuth-NoFallback-TLS13": "would require ambiguous error mapping", - "TLS-TLS13-PSK_WITH_AES_128_CBC_SHA-server": "expects a different error for better coverage of Boring SSL's code base", "TLS-TLS13-PSK_WITH_AES_256_CBC_SHA-server": "expects a different error for better coverage of Boring SSL's code base", "TLS-TLS13-ECDHE_PSK_WITH_AES_128_CBC_SHA-server": "expects a different error for better coverage of Boring SSL's code base", "TLS-TLS13-ECDHE_PSK_WITH_AES_256_CBC_SHA-server": "expects a different error for better coverage of Boring SSL's code base", "TLS-TLS13-ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256-server": "expects a different error for better coverage of Boring SSL's code base", - "CertificateVerificationFail-Server-TLS12-TLS-Sync": "too picky TLS alert", "CertificateVerificationFail-Server-TLS12-CustomCallback-TLS-Sync": "too picky TLS alert", "CertificateVerificationFail-Server-TLS12-TLS-Sync-ImplicitHandshake": "too picky TLS alert", @@ -46,90 +43,104 @@ "CertificateVerificationFail-Server-TLS12-CustomCallback-DTLS-Sync-PackHandshake": "too picky TLS alert", "CertificateVerificationFail-Server-TLS12-DTLS-Sync-PackHandshake": "too picky TLS alert", "CertificateVerificationFail-Server-TLS13-DTLS-Sync-PackHandshake": "too picky TLS alert", - "CertificateVerificationFail-Server-TLS13-CustomCallback-DTLS-Sync-PackHandshake": "too picky TLS alert" + "CertificateVerificationFail-Server-TLS13-CustomCallback-DTLS-Sync-PackHandshake": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS13-TLS-Sync": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS13-CustomCallback-TLS-Sync": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS13-TLS-Sync-ImplicitHandshake": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS13-CustomCallback-TLS-Sync-ImplicitHandshake": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS13-TLS-Sync-SplitHandshakeRecords": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS13-CustomCallback-TLS-Sync-SplitHandshakeRecords": "too picky TLS alert", + "CertificateVerificationFail-Client-TLS12-DTLS-Sync-SplitHandshakeRecords": "too picky TLS alert", + "CertificateVerificationFail-Client-TLS12-CustomCallback-DTLS-Sync-SplitHandshakeRecords": "too picky TLS alert", + "CertificateVerificationFail-Client-TLS13-CustomCallback-DTLS-Sync-SplitHandshakeRecords": "too picky TLS alert", + "CertificateVerificationFail-Client-TLS13-DTLS-Sync-SplitHandshakeRecords": "too picky TLS alert", + "ClientOCSPCallback-FailNoStaple-TLS12-DTLS-Sync-SplitHandshakeRecords": "too picky TLS alert", + "ClientOCSPCallback-FailNoStaple-TLS13-DTLS-Sync-SplitHandshakeRecords": "too picky TLS alert", + "WrongMessageType-TLS13-ClientCertificate-TLS": "too picky TLS alert", + "TrailingMessageData-TLS13-ClientCertificate-TLS": "too picky TLS alert", + "GarbageCertificate-Server-TLS13": "too picky TLS alert", + "AppDataBeforeTLS13KeyChange": "too picky TLS alert", + "UnencryptedEncryptedExtensions": "Botan sends unexpected_message alert, BoGo expects bad_record_mac", + "TrustAnchors-Unsolicited-Certificate": "Botan sends illegal_parameter alert, BoGo expects unsupported_extension", + "Resume-Server-OmitAllPSKsOnSecondClientHello": "Botan reports inconsistent_client_hello, BoGo expects missing_extension", + "PSK-Server-OmitAllPSKsOnSecondClientHello-TLS": "Botan reports inconsistent_client_hello, BoGo expects missing_extension", + "PSK-Server-HRR-PSKMissing-TLS": "Botan sends handshake_failure alert, BoGo expects illegal_parameter", + "PSK-Server-MissingPSKMode-NoMatch-TLS": "Botan reports PSK_IDENTITY_NOT_FOUND, BoGo expects NO_SUPPORTED_PSK_MODE", + "ExtensionTrailingData-ServerName-Client-TLS-TLS12": "Botan sends illegal_parameter for malformed server SNI; BoGo expects decode_error", + "ExtensionTrailingData-ServerName-Client-TLS-TLS13": "Botan sends illegal_parameter for malformed server SNI; BoGo expects decode_error", + "ExtensionTrailingData-ServerName-Client-DTLS-TLS12": "Botan sends illegal_parameter for malformed server SNI; BoGo expects decode_error", + "ClientCertificateType-Server-RejectsInvalidEmptyExtension-VerifyPeer-TLS13": "Botan sends decode_error, BoGo expects illegal_parameter", + "ClientCertificateType-Server-RejectsInvalidEmptyExtension-FailIfNoClientCert-TLS13": "Botan sends decode_error, BoGo expects illegal_parameter", + "ClientCertificateType-Server-RejectsInvalidDefaultOnly-FailIfNoClientCert-TLS13": "Botan reports PEER_DID_NOT_RETURN_A_CERTIFICATE, BoGo expects DECODE_ERROR", + "ClientCertificateType-Server-NoClientHelloCertTypes-NoSharedType-FailIfNoClientCert-TLS13": "Botan reports PEER_DID_NOT_RETURN_A_CERTIFICATE, BoGo expects UNSUPPORTED_CERTIFICATE", + "ServerCertificateType-Client-RequestsRPKOnly-NegotiatedRPK-ServerIncorrectlySentX509-TLS13": "Botan parses RPK as X.509 and fails with BER decode error rather than DECODE_ERROR cleanly", + "ServerCertificateType-Client-RequestsRPKOnly-ServerSentEmptyRPK-TLS13": "Empty RPK rejected with generic ASN.1 error rather than INVALID_RAW_PUBLIC_KEY", + "ClientCertificateType-Server-RPKReceived-RPKAccepted-ClientSentX509InError-VerifyPeer-TLS13": "Botan parses RPK as X.509 and fails with BER decode error rather than DECODE_ERROR cleanly", + "ClientCertificateType-Server-RPKReceived-RPKAccepted-ClientSentX509InError-FailIfNoClientCert-TLS13": "Botan parses RPK as X.509 and fails with BER decode error rather than DECODE_ERROR cleanly", + "ClientCertificateType-Server-ClientSentEmptyRPK-TLS13": "Empty RPK rejected with generic ASN.1 error rather than INVALID_RAW_PUBLIC_KEY" }, - "DisabledTests": { + "*ML-DSA*": "Need support for the LAMPS tagged seed keys", "*TLS1": "No TLS 1.0", "*-TLS1-*": "No TLS 1.0", "*-TLS10-*": "No TLS 1.0", "TLS1-*": "No TLS 1.0", "VersionNegotiation*-TLS": "No TLS 1.0", "VersionNegotiation*-DTLS": "No DTLS 1.0", - "*TLS11": "No TLS 1.1", "*-TLS11-*": "No TLS 1.1", "TLS11-*": "No TLS 1.1", - "*DTLS13*": "No DTLS 1.3", "DTLS-TLS13*": "No DTLS 1.3", "*TLS13-DTLS": "No DTLS 1.3", "*DTLS-TLS13": "No DTLS 1.3", "TLS13*-DTLS-*": "No DTLS 1.3", "MinimumVersion-*-TLS13-*DTLS": "No DTLS 1.3", - "*RSA_PKCS1_MD5_SHA1": "We do not implement MD5/SHA1 concatenation anyway", "*RSA_PKCS1_SHA1*": "We do not implement PKCS1 SHA-1", "*-ECDSA_SHA1-*": "We do not implement ECDSA SHA-1", "*RSA_PKCS1_SHA256_LEGACY-TLS13": "We do allow for PKCS1 in TLS 1.3", - "Compliance-fips202205-*": "We do not have explicit support for a FIPS TLS policy", "Compliance-fips-202205-*": "We do not have explicit support for a FIPS TLS policy", "Compliance-wpa-202304-*": "We do not have explicit support for the WPA Enterprise mode", "Compliance-cnsa202407-*": "We do not have explicit support for CNSA", - "CBCRecordSplitting*": "No need to split CBC records in TLS 1.2", "DelegatedCredentials*": "No support of -delegated-cerdential", - "*SCSV*": "SCSV is meaningless without TLS 1.0/1.1 support", - "AllExtensions-*": "Not all extensions are implemented", - "VersionTolerance-TLS13": "We are not tolerating 0x0400 as Client Hello legacy_version", - "Server-JDK11-*": "We don't implement JDK-specific workarounds", "Client-RejectJDK11DowngradeRandom": "We don't implement this workaround", "ExportTrafficSecrets-*": "Exporting traffic secrets is not implemented", "TooManyChangeCipherSpec-Client-TLS13": "Limits on the number of CCS are not implemented", "TooManyChangeCipherSpec-Server-TLS13": "Limits on the number of CCS are not implemented", - "TooManyKeyUpdates": "Limits on the number of KeyUpdates are not implemented", - "PostQuantumNotEnabledByDefaultInClients": "Oh yes it is", - "TLS12SessionID-TLS13": "We don't offer TLS 1.3 when a TLS 1.2 session was found", "Ticket-Forbidden-TLS13": "We don't offer TLS 1.3 when a TLS 1.2 session was found", "Resume-Client-NoResume-TLS12-TLS13-TLS": "We don't offer TLS 1.3 when a TLS 1.2 session was found", "Resume-Client-Mismatch-TLS12-TLS13-TLS": "We don't offer TLS 1.3 when a TLS 1.2 session was found", "Resume-Server-UnofferedCipher-TLS13": "BoringSSL will not allow switching ciphers during TLS 1.3 resumption, we do, though.", - "HttpGET": "TLS 1.3 server does not detect HTTP", "HttpPOST": "TLS 1.3 server does not detect HTTP", "HttpPUT": "TLS 1.3 server does not detect HTTP", "HttpHEAD": "TLS 1.3 server does not detect HTTP", "HttpCONNECT": "TLS 1.3 server does not detect HTTP", - "*EarlyData*": "No TLS 1.3 Early Data, yet", "TLS13-TicketAgeSkew-*": "No TLS 1.3 Early Data, yet", "ExportKeyingMaterial-Server-HalfRTT-TLS13": "No TLS 1.3 Early Data, yet", "EarlyDataEnabled*": "No TLS 1.3 Early Data, yet", "EarlyData-Reject0RTT*": "No TLS 1.3 Early Data, yet", "PartialEndOfEarlyDataWithClientHello": "No TLS 1.3 Early Data, yet", - "SendNoClientCertificateExtensions-TLS13": "-signed-cert-timestamps currently not supported in the shim", "KeyUpdate-RequestACK-UnfinishedWrite": "-read-with-unfinished-write currently not supported in the shim", - "TLS-ECH*": "No ECH support", "ECH*": "No ECH support", - "DuplicateCertCompressionExt*": "No support for 1.3 cert compression extension", "CertCompression*-TLS13": "No support for 1.3 cert compression extension", - "SupportedVersionSelection-TLS12": "We just ignore the version extension in this case", "NoCommonSignatureAlgorithms-TLS12-Fallback": "Fallback behaviour not implemented by shim", "CheckClientCertificateTypes": "Client certificate type check is a library-user responsibility", - "Downgrade-*-Client-Ignore": "Not possible to ignore downgrade indicator", - "Agree-Digest-SHA1": "No SHA-1 in TLS 1.2", "ServerAuth-SHA1-Fallback-*": "No SHA-1 in TLS 1.2", "*-InvalidSignature-*_SHA1-TLS12": "No SHA-1 in TLS 1.2", @@ -137,24 +148,20 @@ "*-Sign-Negotiate-*_SHA1-TLS12": "No SHA-1 in TLS 1.2", "*-VerifyDefault-*_SHA1-TLS12": "No SHA-1 in TLS 1.2", "*-Verify-*_SHA1-TLS12": "No SHA-1 in TLS 1.2", - "*QUIC*": "No QUIC", "ALPS*": "No ALPS", "ExtraClientEncryptedExtension-*": "No ALPS", - "*NPN*": "No support for NPN", "ALPNServer-Preferred-*": "No support for NPN", "*-NextProtocol*": "No support for NPN", - + "TooManyKeyUpdates": "BoringSSL's approach to KeyUpdate throttling is ineffective and pointless", "*SignedCertificateTimestamp*": "No support for SCT", "*SCT*": "No support for SCT", "Renegotiation-ChangeAuthProperties": "No support for SCT", "UnsolicitedCertificateExtensions-*": "No support for SCT", "IgnoreExtensionsOnIntermediates-TLS13": "No support for SCT", "SendNoExtensionsOnIntermediate-TLS13": "No support for SCT", - - "CertificateVerificationSoftFail*": "Fail, but don't fail... wtf?", - + "CertificateVerificationSoftFail*": "Fail, but don't fail... wtf?", "*NULL-SHA*": "No support for NULL ciphers", "*WITH_NULL*": "No support for NULL ciphers", "*GREASE*": "No support for GREASE", @@ -170,107 +177,144 @@ "*FalseStart*": "Botan doesn't do false start", "MaxSendFragment*": "Maximum fragment extension not supported", "ExportKeyingMaterial-EmptyContext*": "No support for empty context", - "Peek-*": "No peek API", "*OldCallback*": "BoringSSL specific API test", - "*Renegotiate-Client-Explicit*": "BoringSSL specific API test", + "*Renegotiate-Client-Explicit*": "BoringSSL specific API test", "CBCRecordSplittingPartialWrite*": "BoringSSL specific API test", "TicketCallback*": "BoringSSL specific API test", "Server-DDoS*": "BoringSSL specific API test", "RetainOnlySHA256-*": "BoringSSL specific API test", "Renegotiate-Client-UnfinishedWrite": "BoringSSL specific API test", "FailEarlyCallback": "BoringSSL specific API test", - - "MLKEMKeyShareIncludedSecond": "BoringSSL specific policy test (we may offer solo PQ/T groups)", - "NotJustMLKEMKeyShare": "BoringSSL specific policy test (we may offer solo PQ/T groups)", - "MLKEMKeyShareIncludedThird": "BoringSSL specific policy test (we may offer solo PQ/T groups)", - "NotJustKyberKeyShare": "BoringSSL specific policy test (we may offer solo PQ/T groups)", - "KyberKeyShareIncludedSecond": "BoringSSL specific policy test (we may offer solo PQ/T groups)", - "KyberKeyShareIncludedThird": "BoringSSL specific policy test (we may offer solo PQ/T groups)", "CurveTest-*Kyber*": "We no longer support Kyber r3 key exchange", - "ShimTicketRewritable": "Botan has a different ticket format", "Resume-Server-DeclineCrossVersion*": "Botan has a different ticket format", "Resume-Server-DeclineBadCipher*": "Botan has a different ticket format", "Resume-Server-CipherNotPreferred*": "Botan has a different ticket format", - "TLS*-NoTicket-NoAccept": "BoGo expects that if ticket is issued stateful resumption is impossible", - "CheckLeafCurve": "Botan doesn't care what curve an ECDSA cert uses", "CheckECDSACurve-TLS12": "Botan doesn't care what curve an ECDSA cert uses", - "CertificateVerificationDoesNotFailOnResume*": "Botan doesn't support reverify on resume", "CertificateVerificationFailsOnResume*": "Botan doesn't support reverify on resume", "CertificateVerificationPassesOnResume*": "Botan doesn't support reverify on resume", - "CipherNegotiation-2": "No support for cipher equivalence classes", "CipherNegotiation-3": "No support for cipher equivalence classes", "CipherNegotiation-4": "No support for cipher equivalence classes", "CipherNegotiation-5": "No support for cipher equivalence classes", "CipherNegotiation-8": "No support for cipher equivalence classes", - "ALPNServer-SelectEmpty-*": "Botan treats empty ALPN from callback as a decline", - "AppDataAfterChangeCipherSpec-DTLS*": "BoringSSL DTLS drops out of order AppData, we reject", - "Resume-Client-NoResume-TLS1-TLS11-TLS": "BoGo expects resumption attempt sends latest version", "Resume-Client-NoResume-TLS1-TLS12-TLS": "BoGo expects resumption attempt sends latest version", "Resume-Client-NoResume-TLS11-TLS12-TLS": "BoGo expects resumption attempt sends latest version", "Resume-Client-NoResume-TLS1-TLS12-DTLS": "BoGo expects resumption attempt sends latest version", - "Resume-Client-Mismatch-TLS1-TLS11-TLS": "BoGo expects resumption attempt sends latest version", "Resume-Client-Mismatch-TLS1-TLS12-TLS": "BoGo expects resumption attempt sends latest version", "Resume-Client-Mismatch-TLS11-TLS12-TLS": "BoGo expects resumption attempt sends latest version", "Resume-Client-Mismatch-TLS1-TLS12-DTLS": "BoGo expects resumption attempt sends latest version", - "LooseInitialRecordVersion-TLS12": "Botan is somewhat strict about the record version number", - "CurveTest-*-Compressed*": "Point compression is supported, which BoGo doesn't expect", "PointFormat-*-MissingUncompressed": "Point compression is supported, which BoGo doesn't expect", - "RSAPSSSupport-ConfigPSS-NoCerts-TLS12-*": "Needs investigation", "RSAPSSSupport-Default-NoCerts-TLS12-*": "Needs investigation", - "DTLS-Retransmit*": "Shim needs timeout support", - "DTLS-StrayRetransmitFinished-ClientFull": "Needs investigation", "DTLS-StrayRetransmitFinished-ServerResume": "Needs investigation", - "DTLS-Replay-NonMonotonic": "Needs investigation, started failing after https://github.com/google/boringssl/commit/f94f3ed3965ea033001fb9ae006084eee408b861", - "SRTP-Server-IgnoreMKI-*": "Non-empty MKI is rejected (bug)", - "Renegotiate-Client-Packed": "Packing HelloRequest with Finished loses the HelloRequest (bug)", "SendHalfHelloRequest*PackHandshake": "Packing HelloRequest with Finished loses the HelloRequest (bug)", - "PartialClientFinishedWithClientHello": "Need to check for buffered messages when CCS (bug)", "SendUnencryptedFinished-DTLS": "Need to check for buffered messages when CCS (bug)", - "RSAKeyUsage-*-TLS12": "We always enforce key usage", "RSAKeyUsage-Client-WantSignature-GotEncipherment-AlwaysEnforced-TLS13": "We always enforce key usage", - - "AllExtensions-Client-Permute-TLS-TLS12" : "Requires new shim flags that are NYI (as of March 2022)", - "AllExtensions-Client-Permute-DTLS-TLS12" : "Requires new shim flags that are NYI (as of March 2022)", - "EarlyData-WriteAfterEncryptedExtensions" : "Requires new shim flags that are NYI (as of March 2022)", - "EarlyData-WriteAfterServerHello" : "Requires new shim flags that are NYI (as of March 2022)", - "TLS-HintMismatch-Certificate-*" : "Requires new shim flags that are NYI (as of May 2024)", - "TLS-HintMismatch-CipherMismatch1" : "Requires new shim flags that are NYI (as of March 2023)", - "TLS-HintMismatch-CipherMismatch2" : "Requires new shim flags that are NYI (as of March 2023)", - "TLS-HintMismatch-ECDHE-Group" : "Requires new shim flags that are NYI (as of March 2023)", - "TLS-HintMismatch-SignatureInput" : "Requires new shim flags that are NYI (as of March 2022)", - "TLS-HintMismatch-KeyShare" : "Requires new shim flags that are NYI (as of March 2022)", - "TLS-HintMismatch-HandshakerHelloRetryRequest" : "Requires new shim flags that are NYI (as of March 2022)", - "TLS-HintMismatch-ShimHelloRetryRequest" : "Requires new shim flags that are NYI (as of March 2022)", - "TLS-HintMismatch-SignatureAlgorithm-TLS*" : "Requires new shim flags that are NYI (as of March 2022)", - "TLS-HintMismatch-NoTickets1-TLS*" : "Requires new shim flags that are NYI (as of March 2022)", - "TLS-HintMismatch-NoTickets2-TLS*" : "Requires new shim flags that are NYI (as of March 2022)", - "TLS-HintMismatch-Version2" : "Requires new shim flags that are NYI (as of March 2022)", - "TLS-HintMismatch-CertificateRequest" : "Requires new shim flags that are NYI (as of March 2022)", - "TLS-HintMismatch-CertificateCompression-HandshakerOnly" : "Requires new shim flags that are NYI (as of March 2022)", - "TLS-HintMismatch-CertificateCompression-ShimOnly" : "Requires new shim flags that are NYI (as of March 2022)", - "TLS-HintMismatch-CertificateCompression-AlgorithmMismatch" : "Requires new shim flags that are NYI (as of March 2022)", - "TLS-HintMismatch-CertificateCompression-InputMismatch" : "Requires new shim flags that are NYI (as of March 2022)", - "TLS-HintMismatch-Version1" : "Requires new shim flags that are NYI (as of March 2022)", - - "CertificateSelection-*" : "Certificate selection is a library-user responsibility" - } + "AllExtensions-Client-Permute-TLS-TLS12": "Requires new shim flags that are NYI (as of March 2022)", + "AllExtensions-Client-Permute-DTLS-TLS12": "Requires new shim flags that are NYI (as of March 2022)", + "EarlyData-WriteAfterEncryptedExtensions": "Requires new shim flags that are NYI (as of March 2022)", + "EarlyData-WriteAfterServerHello": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-Certificate-*": "Requires new shim flags that are NYI (as of May 2024)", + "TLS-HintMismatch-CipherMismatch1": "Requires new shim flags that are NYI (as of March 2023)", + "TLS-HintMismatch-CipherMismatch2": "Requires new shim flags that are NYI (as of March 2023)", + "TLS-HintMismatch-ECDHE-Group": "Requires new shim flags that are NYI (as of March 2023)", + "TLS-HintMismatch-SignatureInput": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-KeyShare": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-HandshakerHelloRetryRequest": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-ShimHelloRetryRequest": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-SignatureAlgorithm-TLS*": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-NoTickets1-TLS*": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-NoTickets2-TLS*": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-Version2": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-CertificateRequest": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-CertificateCompression-HandshakerOnly": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-CertificateCompression-ShimOnly": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-CertificateCompression-AlgorithmMismatch": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-CertificateCompression-InputMismatch": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-Version1": "Requires new shim flags that are NYI (as of March 2022)", + "CertificateSelection-*": "Certificate selection is a library-user responsibility", + "ALPNClient-AllowUnknown-*": "Botan always validates server ALPN selection against offered list", + "SendEmptySessionTicket-TLS13": "Botan sends internal_error instead of decode_error - empty ticket is caught later than the spec expects", + "DTLS-ECH*": "No ECH support", + "KeyUpdate-*-DTLS": "No DTLS 1.3", + "AppDataBeforeTLS13KeyChange-DTLS*": "No DTLS 1.3", + "UnencryptedEncryptedExtensions-DTLS": "No DTLS 1.3", + "TLS13-OnlyPadding-DTLS": "No DTLS 1.3", + "Resume-*-TLS13-TLS12-DTLS": "No DTLS 1.3", + "Downgrade-TLS12-*-DTLS": "No DTLS 1.3", + "WrongMessageType-TLS13-*-DTLS": "No DTLS 1.3", + "TrailingMessageData-TLS13-EncryptedExtensions-DTLS": "No DTLS 1.3", + "TrailingMessageData-TLS13-ServerCertificateVerify-DTLS": "No DTLS 1.3", + "KeyChangeWithBufferedMessages-DTLS": "No DTLS 1.3", + "Renegotiate-DTLS-Server-Forbidden": "Botan tolerates DTLS renegotiation", + "Renegotiate-DTLS-Client-Forbidden": "Botan tolerates DTLS renegotiation", + "DTLS12-SendExtraFinished-*": "Botan tolerates extra Finished messages in DTLS", + "MixCompleteMessageWithFragments-DTLS-TLS12": "DTLS fragment handling difference", + "RejectPSSKeyType-*": "Botan does not reject RSA-PSS key type", + "CertificateCipherMismatch-PSS": "Botan does not reject PSS cipher mismatch", + "ServerNameExtensionServer-*-TLS12": "Botan does not echo server_name in TLS 1.2 ServerHello", + "IgnoreLegacyVersion-TLS13": "Botan strictly validates legacy_version in TLS 1.3 ClientHello", + "MTU-DTLS12-3DES-CBC": "No 3DES support", + "TLS13-Client-*TicketFlags": "Botan does not strictly validate ticket flags encoding", + "TLS12-NoTicket-NoOffer": "Different session ticket/ID handling", + "PAKE-*": "No PAKE support", + "TrustAnchors-EmptyID-*": "No TrustAnchors extension support", + "TrustAnchors-ServerSelect-*": "No TrustAnchors extension support", + "TrustAnchors-ServerReceiveEmptyRequest": "No TrustAnchors extension support", + "PSK-Server-CertOrPSK-Cert-*": "Botan shim does not honor credential ordering between PSK and X.509", + "ClientCertificateType-Client-OffersRPKOnly-ServerOmitsExtension-TLS13": "Botan does not reject when server omits cert_type extension and client only offers RPK", + "TLS13-EmptyRecords-DTLS": "No DTLS 1.3", + "TLS13-RecordPadding-DTLS": "No DTLS 1.3", + "ClientCertificateType-Server-*-TLS12": "Botan does not support RPK certificate type negotiation in TLS 1.2", + "ClientCertificateType-Client-*-TLS12*": "Botan does not support RPK certificate type negotiation in TLS 1.2", + "ServerCertificateType-Server-*-TLS12*": "Botan does not support RPK certificate type negotiation in TLS 1.2", + "ServerCertificateType-Client-RequestsRPKOnly-*-TLS12": "Botan does not support RPK certificate type negotiation in TLS 1.2", + "ServerCertificateType-Client-RequestsRPKX509-*-TLS12": "Botan does not support RPK certificate type negotiation in TLS 1.2", + "ServerCertificateType-Client-RequestsX509RPK-*-TLS12": "Botan does not support RPK certificate type negotiation in TLS 1.2", + "ServerCertificateType-Client-RPKVerifyFail-TLS12": "Botan does not support RPK certificate type negotiation in TLS 1.2", + "ServerCertificateType-Client-RequestsDefaultOnly-ServerPickedRPKInError-TLS12": "Botan does not support RPK certificate type negotiation in TLS 1.2", + "ClientCertificateType-Server-RejectsInvalidDefaultOnly-VerifyPeer-TLS13": "Botan does not reject X509-only cert type extension", + "ClientCertificateType-Server-NoClientHelloCertTypes-NoSharedType-VerifyPeer-TLS13": "Botan does not reject when no shared cert type exists", + "ServerCertificateType-Server-RejectsDefaultOnly-TLS12": "Botan does not reject X509-only cert type extension as server", + "ServerCertificateType-Server-RejectsDefaultOnly-TLS13": "Botan does not reject X509-only cert type extension as server", + "ServerCertificateType-Client-RequestsRPKOnly-ServerPickedX509ByDefaultInError-TLS13": "Botan does not enforce client-side cert type rejection of unsolicited X509", + "ExtensionTrailingData-TrustAnchors-ClientHello-Server-TLS-TLS13": "No TrustAnchors extension support", + "PSK-Client-PSKRequired-TLS": "Botan client does not enforce PSK-only mode", + "PSK-Client-PSKRequired-TLS12-TLS": "Botan client does not enforce PSK-only mode", + "PSK-*-DTLS": "No DTLS 1.3", + "NotJustKyberKeyShare*": "We no longer support Kyber r3 key exchange", + "KyberKeyShareIncludedSecond*": "We no longer support Kyber r3 key exchange", + "KyberKeyShareIncludedThird*": "We no longer support Kyber r3 key exchange", + "CustomKeyShares-All-TLS13": "We no longer support Kyber r3 key exchange", + "DTLS-Replay-NonMonotonic*": "Needs investigation, started failing after https://github.com/google/boringssl/commit/f94f3ed3965ea033001fb9ae006084eee408b861" + }, + "ErrorMap": { + ":CLIENTHELLO_PARSE_FAILED:": [ + ":DECODE_ERROR:" + ], + ":BAD_DECRYPT:": [ + ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:" + ], + ":ERROR_PARSING_EXTENSION:": [ + ":ERROR_PARSING_EXTENSION:", + ":DECODE_ERROR:" + ] + } } diff -Nru botan3-3.7.1+dfsg/src/bogo_shim/config_no_tls12.json botan3-3.12.0+dfsg/src/bogo_shim/config_no_tls12.json --- botan3-3.7.1+dfsg/src/bogo_shim/config_no_tls12.json 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/bogo_shim/config_no_tls12.json 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,334 @@ +{ + "LooseErrorTests": { + "AppDataBeforeHandshake": "BoGo expects different error before vs after CCS", + "AppDataBeforeHandshake-Empty": "Invalid record message", + "ServerHelloBogusCipher": "Unexpected error", + "Garbage": "Decoding error", + "Resume-Client-CipherMismatch": "Unexpected error", + "InvalidECDHPoint-Server": "Unexpected error", + "NoSharedCipher": "Unexpected error", + "NoSharedCipher-TLS13": "Unexpected error", + "PartialFinishedWithServerHelloDone": "Unexpected record vs excess handshake data", + "HelloRetryRequest-DuplicateCurve-TLS13": "expects 'illegal parameter' but we want to stick with 'decode error'", + "HelloRetryRequest-DuplicateCookie-TLS13": "expects 'illegal parameter' but we want to stick with 'decode error'", + "EncryptedExtensionsWithKeyShare-TLS13": "expects 'unsupported extension' but RFC requires 'illegal parameter'", + "ClientSkipCertificateVerify-TLS13": "would require ambiguous error mapping", + "Resume-Client-Mismatch-TLS13-TLS12-TLS": "server requests a downgrade to TLS 1.2, echoing the random session ID during a TLS 1.3 resumption. => error mapping conflict", + "ServerAuth-NoFallback-TLS13": "would require ambiguous error mapping", + "TLS-TLS13-PSK_WITH_AES_128_CBC_SHA-server": "expects a different error for better coverage of Boring SSL's code base", + "TLS-TLS13-PSK_WITH_AES_256_CBC_SHA-server": "expects a different error for better coverage of Boring SSL's code base", + "TLS-TLS13-ECDHE_PSK_WITH_AES_128_CBC_SHA-server": "expects a different error for better coverage of Boring SSL's code base", + "TLS-TLS13-ECDHE_PSK_WITH_AES_256_CBC_SHA-server": "expects a different error for better coverage of Boring SSL's code base", + "TLS-TLS13-ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256-server": "expects a different error for better coverage of Boring SSL's code base", + "CertificateVerificationFail-Server-TLS12-TLS-Sync": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS12-CustomCallback-TLS-Sync": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS12-TLS-Sync-ImplicitHandshake": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS12-CustomCallback-TLS-Sync-ImplicitHandshake": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS12-TLS-Sync-SplitHandshakeRecords": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS12-CustomCallback-TLS-Sync-SplitHandshakeRecords": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS12-TLS-Sync-PackHandshake": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS12-CustomCallback-TLS-Sync-PackHandshake": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS12-DTLS-Sync": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS13-DTLS-Sync": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS12-CustomCallback-DTLS-Sync": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS13-CustomCallback-DTLS-Sync": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS12-DTLS-Sync-ImplicitHandshake": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS12-CustomCallback-DTLS-Sync-ImplicitHandshake": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS13-CustomCallback-DTLS-Sync-ImplicitHandshake": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS13-DTLS-Sync-ImplicitHandshake": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS12-DTLS-Sync-SplitHandshakeRecords": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS12-CustomCallback-DTLS-Sync-SplitHandshakeRecords": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS13-DTLS-Sync-SplitHandshakeRecords": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS13-CustomCallback-DTLS-Sync-SplitHandshakeRecords": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS12-CustomCallback-DTLS-Sync-PackHandshake": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS12-DTLS-Sync-PackHandshake": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS13-DTLS-Sync-PackHandshake": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS13-CustomCallback-DTLS-Sync-PackHandshake": "too picky TLS alert", + "NoSSL3-Client-Unsolicited": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS13-TLS-Sync": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS13-CustomCallback-TLS-Sync": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS13-TLS-Sync-ImplicitHandshake": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS13-CustomCallback-TLS-Sync-ImplicitHandshake": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS13-TLS-Sync-SplitHandshakeRecords": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS13-CustomCallback-TLS-Sync-SplitHandshakeRecords": "too picky TLS alert", + "CertificateVerificationFail-Client-TLS12-DTLS-Sync-SplitHandshakeRecords": "too picky TLS alert", + "CertificateVerificationFail-Client-TLS12-CustomCallback-DTLS-Sync-SplitHandshakeRecords": "too picky TLS alert", + "CertificateVerificationFail-Client-TLS13-CustomCallback-DTLS-Sync-SplitHandshakeRecords": "too picky TLS alert", + "CertificateVerificationFail-Client-TLS13-DTLS-Sync-SplitHandshakeRecords": "too picky TLS alert", + "ClientOCSPCallback-FailNoStaple-TLS12-DTLS-Sync-SplitHandshakeRecords": "too picky TLS alert", + "ClientOCSPCallback-FailNoStaple-TLS13-DTLS-Sync-SplitHandshakeRecords": "too picky TLS alert", + "WrongMessageType-TLS13-ClientCertificate-TLS": "too picky TLS alert", + "TrailingMessageData-TLS13-ClientCertificate-TLS": "too picky TLS alert", + "GarbageCertificate-Server-TLS13": "too picky TLS alert", + "AppDataBeforeTLS13KeyChange": "too picky TLS alert", + "ExtensionTrailingData-ServerName-Client-TLS-TLS12": "Botan sends illegal_parameter for malformed server SNI; BoGo expects decode_error", + "ExtensionTrailingData-ServerName-Client-TLS-TLS13": "Botan sends illegal_parameter for malformed server SNI; BoGo expects decode_error", + "ExtensionTrailingData-ServerName-Client-DTLS-TLS12": "Botan sends illegal_parameter for malformed server SNI; BoGo expects decode_error", + "ClientCertificateType-Server-RejectsInvalidEmptyExtension-VerifyPeer-TLS13": "Botan sends decode_error, BoGo expects illegal_parameter", + "ClientCertificateType-Server-RejectsInvalidEmptyExtension-FailIfNoClientCert-TLS13": "Botan sends decode_error, BoGo expects illegal_parameter", + "ClientCertificateType-Server-RejectsInvalidDefaultOnly-FailIfNoClientCert-TLS13": "Botan reports PEER_DID_NOT_RETURN_A_CERTIFICATE, BoGo expects DECODE_ERROR", + "ClientCertificateType-Server-NoClientHelloCertTypes-NoSharedType-FailIfNoClientCert-TLS13": "Botan reports PEER_DID_NOT_RETURN_A_CERTIFICATE, BoGo expects UNSUPPORTED_CERTIFICATE", + "ServerCertificateType-Client-RequestsRPKOnly-NegotiatedRPK-ServerIncorrectlySentX509-TLS13": "Botan parses RPK as X.509 and fails with BER decode error rather than DECODE_ERROR cleanly", + "ServerCertificateType-Client-RequestsRPKOnly-ServerSentEmptyRPK-TLS13": "Empty RPK rejected with generic ASN.1 error rather than INVALID_RAW_PUBLIC_KEY", + "ClientCertificateType-Server-RPKReceived-RPKAccepted-ClientSentX509InError-VerifyPeer-TLS13": "Botan parses RPK as X.509 and fails with BER decode error rather than DECODE_ERROR cleanly", + "ClientCertificateType-Server-RPKReceived-RPKAccepted-ClientSentX509InError-FailIfNoClientCert-TLS13": "Botan parses RPK as X.509 and fails with BER decode error rather than DECODE_ERROR cleanly", + "ClientCertificateType-Server-ClientSentEmptyRPK-TLS13": "Empty RPK rejected with generic ASN.1 error rather than INVALID_RAW_PUBLIC_KEY" + }, + "DisabledTests": { + "*TLS1": "No TLS 1.0", + "*-TLS1-*": "No TLS 1.0", + "*-TLS10-*": "No TLS 1.0", + "TLS1-*": "No TLS 1.0", + "VersionNegotiation*-TLS": "No TLS 1.0", + "VersionNegotiation*-DTLS": "No DTLS 1.0", + "*TLS11": "No TLS 1.1", + "*-TLS11-*": "No TLS 1.1", + "TLS11-*": "No TLS 1.1", + "SendClientVersion-RSA": "No TLS 1.2", + "TLS12ClientShouldNotOffer-*": "No TLS 1.2", + "TLS12ServerShouldNotSelect-*": "No TLS 1.2", + "TLS12NoSessionID-TLS13": "No TLS 1.2", + "EMS-Forbidden-TLS13": "If we don't implement TLS 1.2, we won't offer Extended Master Secret", + "RenegotiationInfo-Forbidden-TLS13": "If we don't implement TLS 1.2, we won't offer Renegotiation Info", + "PointFormat-EncryptedExtensions-TLS13": "If we don't implement TLS 1.2, we won't offer Point Format in Encrypted Extensions", + "*DTLS13*": "No DTLS 1.3", + "DTLS-TLS13*": "No DTLS 1.3", + "*TLS13-DTLS": "No DTLS 1.3", + "*DTLS-TLS13": "No DTLS 1.3", + "TLS13*-DTLS-*": "No DTLS 1.3", + "MinimumVersion-*-TLS13-*DTLS": "No DTLS 1.3", + "*RSA_PKCS1_MD5_SHA1": "We do not implement MD5/SHA1 concatenation anyway", + "*RSA_PKCS1_SHA1*": "We do not implement PKCS1 SHA-1", + "*-ECDSA_SHA1-*": "We do not implement ECDSA SHA-1", + "*RSA_PKCS1_SHA256_LEGACY-TLS13": "We do allow for PKCS1 in TLS 1.3", + "Compliance-fips202205-*": "We do not have explicit support for a FIPS TLS policy", + "Compliance-fips-202205-*": "We do not have explicit support for a FIPS TLS policy", + "Compliance-wpa-202304-*": "We do not have explicit support for the WPA Enterprise mode", + "Compliance-cnsa202407-*": "We do not have explicit support for CNSA", + "CBCRecordSplitting*": "No need to split CBC records in TLS 1.2", + "DelegatedCredentials*": "No support of -delegated-cerdential", + "*SCSV*": "SCSV is meaningless without TLS 1.0/1.1 support", + "AllExtensions-*": "Not all extensions are implemented", + "VersionTolerance-TLS13": "We are not tolerating 0x0400 as Client Hello legacy_version", + "Server-JDK11-*": "We don't implement JDK-specific workarounds", + "Client-RejectJDK11DowngradeRandom": "We don't implement this workaround", + "ExportTrafficSecrets-*": "Exporting traffic secrets is not implemented", + "TooManyChangeCipherSpec-Client-TLS13": "Limits on the number of CCS are not implemented", + "TooManyChangeCipherSpec-Server-TLS13": "Limits on the number of CCS are not implemented", + "PostQuantumNotEnabledByDefaultInClients": "Oh yes it is", + "TLS12SessionID-TLS13": "We don't offer TLS 1.3 when a TLS 1.2 session was found", + "Ticket-Forbidden-TLS13": "We don't offer TLS 1.3 when a TLS 1.2 session was found", + "Resume-Client-NoResume-TLS12-TLS13-TLS": "We don't offer TLS 1.3 when a TLS 1.2 session was found", + "Resume-Client-Mismatch-TLS12-TLS13-TLS": "We don't offer TLS 1.3 when a TLS 1.2 session was found", + "Resume-Server-UnofferedCipher-TLS13": "BoringSSL will not allow switching ciphers during TLS 1.3 resumption, we do, though.", + "HttpGET": "TLS 1.3 server does not detect HTTP", + "HttpPOST": "TLS 1.3 server does not detect HTTP", + "HttpPUT": "TLS 1.3 server does not detect HTTP", + "HttpHEAD": "TLS 1.3 server does not detect HTTP", + "HttpCONNECT": "TLS 1.3 server does not detect HTTP", + "*EarlyData*": "No TLS 1.3 Early Data, yet", + "TLS13-TicketAgeSkew-*": "No TLS 1.3 Early Data, yet", + "ExportKeyingMaterial-Server-HalfRTT-TLS13": "No TLS 1.3 Early Data, yet", + "EarlyDataEnabled*": "No TLS 1.3 Early Data, yet", + "EarlyData-Reject0RTT*": "No TLS 1.3 Early Data, yet", + "PartialEndOfEarlyDataWithClientHello": "No TLS 1.3 Early Data, yet", + "SendNoClientCertificateExtensions-TLS13": "-signed-cert-timestamps currently not supported in the shim", + "KeyUpdate-RequestACK-UnfinishedWrite": "-read-with-unfinished-write currently not supported in the shim", + "TLS-ECH*": "No ECH support", + "ECH*": "No ECH support", + "DuplicateCertCompressionExt*": "No support for 1.3 cert compression extension", + "CertCompression*-TLS13": "No support for 1.3 cert compression extension", + "SupportedVersionSelection-TLS12": "We just ignore the version extension in this case", + "NoCommonSignatureAlgorithms-TLS12-Fallback": "Fallback behaviour not implemented by shim", + "CheckClientCertificateTypes": "Client certificate type check is a library-user responsibility", + "Downgrade-*-Client-Ignore": "Not possible to ignore downgrade indicator", + "Agree-Digest-SHA1": "No SHA-1 in TLS 1.2", + "ServerAuth-SHA1-Fallback-*": "No SHA-1 in TLS 1.2", + "*-InvalidSignature-*_SHA1-TLS12": "No SHA-1 in TLS 1.2", + "*-Sign-*_SHA1-TLS12": "No SHA-1 in TLS 1.2", + "*-Sign-Negotiate-*_SHA1-TLS12": "No SHA-1 in TLS 1.2", + "*-VerifyDefault-*_SHA1-TLS12": "No SHA-1 in TLS 1.2", + "*-Verify-*_SHA1-TLS12": "No SHA-1 in TLS 1.2", + "*QUIC*": "No QUIC", + "ALPS*": "No ALPS", + "ExtraClientEncryptedExtension-*": "No ALPS", + "*NPN*": "No support for NPN", + "ALPNServer-Preferred-*": "No support for NPN", + "*-NextProtocol*": "No support for NPN", + "TooManyKeyUpdates": "BoringSSL's approach to KeyUpdate throttling is ineffective and pointless", + "*SignedCertificateTimestamp*": "No support for SCT", + "*SCT*": "No support for SCT", + "Renegotiation-ChangeAuthProperties": "No support for SCT", + "UnsolicitedCertificateExtensions-*": "No support for SCT", + "IgnoreExtensionsOnIntermediates-TLS13": "No support for SCT", + "SendNoExtensionsOnIntermediate-TLS13": "No support for SCT", + "CertificateVerificationSoftFail*": "Fail, but don't fail... wtf?", + "*NULL-SHA*": "No support for NULL ciphers", + "*WITH_NULL*": "No support for NULL ciphers", + "*GREASE*": "No support for GREASE", + "*ChannelID*": "No support for ChannelID", + "*TokenBinding*": "No support for Token Binding", + "ClientHelloPadding": "No support for client hello padding extension", + "TLSUnique*": "Not supported", + "*CECPQ2*": "Not implemented", + "PQExperimentSignal*": "Not implemented", + "*P-224*": "P-224 not supported in TLS", + "*V2ClientHello*": "No support for SSLv2 client hellos", + "*Ed25519*": "Ed25519 not implemented in TLS", + "*FalseStart*": "Botan doesn't do false start", + "MaxSendFragment*": "Maximum fragment extension not supported", + "ExportKeyingMaterial-EmptyContext*": "No support for empty context", + "Peek-*": "No peek API", + "*OldCallback*": "BoringSSL specific API test", + "*Renegotiate-Client-Explicit*": "BoringSSL specific API test", + "CBCRecordSplittingPartialWrite*": "BoringSSL specific API test", + "TicketCallback*": "BoringSSL specific API test", + "Server-DDoS*": "BoringSSL specific API test", + "RetainOnlySHA256-*": "BoringSSL specific API test", + "Renegotiate-Client-UnfinishedWrite": "BoringSSL specific API test", + "FailEarlyCallback": "BoringSSL specific API test", + "CurveTest-*Kyber*": "We no longer support Kyber r3 key exchange", + "ShimTicketRewritable": "Botan has a different ticket format", + "Resume-Server-DeclineCrossVersion*": "Botan has a different ticket format", + "Resume-Server-DeclineBadCipher*": "Botan has a different ticket format", + "Resume-Server-CipherNotPreferred*": "Botan has a different ticket format", + "TLS*-NoTicket-NoAccept": "BoGo expects that if ticket is issued stateful resumption is impossible", + "CheckLeafCurve": "Botan doesn't care what curve an ECDSA cert uses", + "CheckECDSACurve-TLS12": "Botan doesn't care what curve an ECDSA cert uses", + "CertificateVerificationDoesNotFailOnResume*": "Botan doesn't support reverify on resume", + "CertificateVerificationFailsOnResume*": "Botan doesn't support reverify on resume", + "CertificateVerificationPassesOnResume*": "Botan doesn't support reverify on resume", + "CipherNegotiation-2": "No support for cipher equivalence classes", + "CipherNegotiation-3": "No support for cipher equivalence classes", + "CipherNegotiation-4": "No support for cipher equivalence classes", + "CipherNegotiation-5": "No support for cipher equivalence classes", + "CipherNegotiation-8": "No support for cipher equivalence classes", + "ALPNServer-SelectEmpty-*": "Botan treats empty ALPN from callback as a decline", + "AppDataAfterChangeCipherSpec-DTLS*": "BoringSSL DTLS drops out of order AppData, we reject", + "Resume-Client-NoResume-TLS1-TLS11-TLS": "BoGo expects resumption attempt sends latest version", + "Resume-Client-NoResume-TLS1-TLS12-TLS": "BoGo expects resumption attempt sends latest version", + "Resume-Client-NoResume-TLS11-TLS12-TLS": "BoGo expects resumption attempt sends latest version", + "Resume-Client-NoResume-TLS1-TLS12-DTLS": "BoGo expects resumption attempt sends latest version", + "Resume-Client-Mismatch-TLS1-TLS11-TLS": "BoGo expects resumption attempt sends latest version", + "Resume-Client-Mismatch-TLS1-TLS12-TLS": "BoGo expects resumption attempt sends latest version", + "Resume-Client-Mismatch-TLS11-TLS12-TLS": "BoGo expects resumption attempt sends latest version", + "Resume-Client-Mismatch-TLS1-TLS12-DTLS": "BoGo expects resumption attempt sends latest version", + "LooseInitialRecordVersion-TLS12": "Botan is somewhat strict about the record version number", + "CurveTest-*-Compressed*": "Point compression is supported, which BoGo doesn't expect", + "PointFormat-*-MissingUncompressed": "Point compression is supported, which BoGo doesn't expect", + "RSAPSSSupport-ConfigPSS-NoCerts-TLS12-*": "Needs investigation", + "RSAPSSSupport-Default-NoCerts-TLS12-*": "Needs investigation", + "DTLS-Retransmit*": "Shim needs timeout support", + "DTLS-StrayRetransmitFinished-ClientFull": "Needs investigation", + "DTLS-StrayRetransmitFinished-ServerResume": "Needs investigation", + "SRTP-Server-IgnoreMKI-*": "Non-empty MKI is rejected (bug)", + "Renegotiate-Client-Packed": "Packing HelloRequest with Finished loses the HelloRequest (bug)", + "SendHalfHelloRequest*PackHandshake": "Packing HelloRequest with Finished loses the HelloRequest (bug)", + "PartialClientFinishedWithClientHello": "Need to check for buffered messages when CCS (bug)", + "SendUnencryptedFinished-DTLS": "Need to check for buffered messages when CCS (bug)", + "RSAKeyUsage-*-TLS12": "We always enforce key usage", + "RSAKeyUsage-Client-WantSignature-GotEncipherment-AlwaysEnforced-TLS13": "We always enforce key usage", + "AllExtensions-Client-Permute-TLS-TLS12": "Requires new shim flags that are NYI (as of March 2022)", + "AllExtensions-Client-Permute-DTLS-TLS12": "Requires new shim flags that are NYI (as of March 2022)", + "EarlyData-WriteAfterEncryptedExtensions": "Requires new shim flags that are NYI (as of March 2022)", + "EarlyData-WriteAfterServerHello": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-Certificate-*": "Requires new shim flags that are NYI (as of May 2024)", + "TLS-HintMismatch-CipherMismatch1": "Requires new shim flags that are NYI (as of March 2023)", + "TLS-HintMismatch-CipherMismatch2": "Requires new shim flags that are NYI (as of March 2023)", + "TLS-HintMismatch-ECDHE-Group": "Requires new shim flags that are NYI (as of March 2023)", + "TLS-HintMismatch-SignatureInput": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-KeyShare": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-HandshakerHelloRetryRequest": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-ShimHelloRetryRequest": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-SignatureAlgorithm-TLS*": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-NoTickets1-TLS*": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-NoTickets2-TLS*": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-Version2": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-CertificateRequest": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-CertificateCompression-HandshakerOnly": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-CertificateCompression-ShimOnly": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-CertificateCompression-AlgorithmMismatch": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-CertificateCompression-InputMismatch": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-Version1": "Requires new shim flags that are NYI (as of March 2022)", + "CertificateSelection-*": "Certificate selection is a library-user responsibility", + "ALPNClient-AllowUnknown-*": "Botan always validates server ALPN selection against offered list", + "RejectEmptyOCSPResponse-TLS-TLS12": "Unmapped error string for empty OCSP response", + "RejectEmptyOCSPResponse-TLS-TLS13": "Unmapped error string for empty OCSP response", + "RejectEmptyOCSPResponse-DTLS-TLS12": "Unmapped error string for empty OCSP response", + "Resume-Server-OmitAllPSKsOnSecondClientHello": "Different error code for inconsistent ClientHello", + "SendEmptySessionTicket-TLS13": "Different error handling for empty session ticket", + "CertificateInResumption-TLS13": "Unmapped error string", + "CertificateRequestInResumption-TLS13": "Unmapped error string", + "AppDataBeforeTLS13KeyChange-Empty": "Different error code for bad decrypt", + "UnencryptedEncryptedExtensions": "Different error for unencrypted record", + "TrustAnchors-Unsolicited-Certificate": "Different alert code for unsolicited extension", + "PSK-Server-OmitAllPSKsOnSecondClientHello-TLS": "Botan reports INCONSISTENT_CLIENT_HELLO instead of MISSING_EXTENSION", + "PSK-Server-HRR-PSKMissing-TLS": "Botan sends handshake_failure alert instead of illegal_parameter", + "PSK-Server-MissingPSKMode-NoMatch-TLS": "Botan reports PSK_IDENTITY_NOT_FOUND instead of NO_SUPPORTED_PSK_MODE", + "DTLS-ECH*": "No ECH support", + "KeyUpdate-*-DTLS": "No DTLS 1.3", + "AppDataBeforeTLS13KeyChange-DTLS*": "No DTLS 1.3", + "UnencryptedEncryptedExtensions-DTLS": "No DTLS 1.3", + "TLS13-OnlyPadding-DTLS": "No DTLS 1.3", + "Resume-*-TLS13-TLS12-DTLS": "No DTLS 1.3", + "Downgrade-TLS12-*-DTLS": "No DTLS 1.3", + "WrongMessageType-TLS13-*-DTLS": "No DTLS 1.3", + "TrailingMessageData-TLS13-EncryptedExtensions-DTLS": "No DTLS 1.3", + "TrailingMessageData-TLS13-ServerCertificateVerify-DTLS": "No DTLS 1.3", + "KeyChangeWithBufferedMessages-DTLS": "No DTLS 1.3", + "Renegotiate-DTLS-Server-Forbidden": "Botan tolerates DTLS renegotiation", + "Renegotiate-DTLS-Client-Forbidden": "Botan tolerates DTLS renegotiation", + "DTLS12-SendExtraFinished-*": "Botan tolerates extra Finished messages in DTLS", + "MixCompleteMessageWithFragments-DTLS-TLS12": "DTLS fragment handling difference", + "RejectPSSKeyType-*": "Botan does not reject RSA-PSS key type", + "CertificateCipherMismatch-PSS": "Botan does not reject PSS cipher mismatch", + "ServerNameExtensionServer-*-TLS12": "Botan does not echo server_name in TLS 1.2 ServerHello", + "IgnoreLegacyVersion-TLS13": "Botan strictly validates legacy_version in TLS 1.3 ClientHello", + "MTU-DTLS12-3DES-CBC": "No 3DES support", + "TLS13-Client-*TicketFlags": "Botan does not strictly validate ticket flags encoding", + "TLS12-NoTicket-NoOffer": "Different session ticket/ID handling", + "PAKE-*": "No PAKE support", + "TrustAnchors-EmptyID-*": "No TrustAnchors extension support", + "TrustAnchors-ServerSelect-*": "No TrustAnchors extension support", + "TrustAnchors-ServerReceiveEmptyRequest": "No TrustAnchors extension support", + "ExtensionTrailingData-TrustAnchors-ClientHello-Server-TLS-TLS13": "No TrustAnchors extension support", + "PSK-Server-CertOrPSK-Cert-*": "Botan shim does not honor credential ordering between PSK and X.509", + "ClientCertificateType-Client-OffersRPKOnly-ServerOmitsExtension-TLS13": "Botan does not reject when server omits cert_type extension and client only offers RPK", + "*ML-DSA*": "Need support for the LAMPS tagged seed keys", + "TLS13-EmptyRecords-DTLS": "No DTLS 1.3", + "TLS13-RecordPadding-DTLS": "No DTLS 1.3", + "ClientCertificateType-Server-*-TLS12": "Botan does not support RPK certificate type negotiation in TLS 1.2", + "ClientCertificateType-Client-*-TLS12*": "Botan does not support RPK certificate type negotiation in TLS 1.2", + "ServerCertificateType-Server-*-TLS12*": "Botan does not support RPK certificate type negotiation in TLS 1.2", + "ServerCertificateType-Client-RequestsRPKOnly-*-TLS12": "Botan does not support RPK certificate type negotiation in TLS 1.2", + "ServerCertificateType-Client-RequestsRPKX509-*-TLS12": "Botan does not support RPK certificate type negotiation in TLS 1.2", + "ServerCertificateType-Client-RequestsX509RPK-*-TLS12": "Botan does not support RPK certificate type negotiation in TLS 1.2", + "ServerCertificateType-Client-RPKVerifyFail-TLS12": "Botan does not support RPK certificate type negotiation in TLS 1.2", + "ServerCertificateType-Client-RequestsDefaultOnly-ServerPickedRPKInError-TLS12": "Botan does not support RPK certificate type negotiation in TLS 1.2", + "ClientCertificateType-Server-RejectsInvalidDefaultOnly-VerifyPeer-TLS13": "Botan does not reject X509-only cert type extension", + "ClientCertificateType-Server-NoClientHelloCertTypes-NoSharedType-VerifyPeer-TLS13": "Botan does not reject when no shared cert type exists", + "ServerCertificateType-Server-RejectsDefaultOnly-TLS12": "Botan does not reject X509-only cert type extension as server", + "ServerCertificateType-Server-RejectsDefaultOnly-TLS13": "Botan does not reject X509-only cert type extension as server", + "ServerCertificateType-Client-RequestsRPKOnly-ServerPickedX509ByDefaultInError-TLS13": "Botan does not enforce client-side cert type rejection of unsolicited X509", + "PSK-Client-PSKRequired-TLS": "Botan client does not enforce PSK-only mode", + "PSK-Client-PSKRequired-TLS12-TLS": "Botan client does not enforce PSK-only mode", + "PSK-*-DTLS": "No DTLS 1.3", + "NotJustKyberKeyShare*": "We no longer support Kyber r3 key exchange", + "KyberKeyShareIncludedSecond*": "We no longer support Kyber r3 key exchange", + "KyberKeyShareIncludedThird*": "We no longer support Kyber r3 key exchange", + "CustomKeyShares-All-TLS13": "We no longer support Kyber r3 key exchange", + "DTLS-Replay-NonMonotonic*": "Needs investigation, started failing after https://github.com/google/boringssl/commit/f94f3ed3965ea033001fb9ae006084eee408b861" + }, + "ErrorMap": { + ":CLIENTHELLO_PARSE_FAILED:": [ + ":DECODE_ERROR:" + ], + ":BAD_DECRYPT:": [ + ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:" + ], + ":ERROR_PARSING_EXTENSION:": [ + ":ERROR_PARSING_EXTENSION:", + ":DECODE_ERROR:" + ] + } +} diff -Nru botan3-3.7.1+dfsg/src/bogo_shim/config_no_tls13.json botan3-3.12.0+dfsg/src/bogo_shim/config_no_tls13.json --- botan3-3.7.1+dfsg/src/bogo_shim/config_no_tls13.json 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/bogo_shim/config_no_tls13.json 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,332 @@ +{ + "LooseErrorTests": { + "AppDataBeforeHandshake": "BoGo expects different error before vs after CCS", + "AppDataBeforeHandshake-Empty": "Invalid record message", + "ServerHelloBogusCipher": "Unexpected error", + "Garbage": "Decoding error", + "Resume-Client-CipherMismatch": "Unexpected error", + "InvalidECDHPoint-Server": "Unexpected error", + "NoSharedCipher": "Unexpected error", + "NoSharedCipher-TLS13": "Unexpected error", + "PartialFinishedWithServerHelloDone": "Unexpected record vs excess handshake data", + "HelloRetryRequest-DuplicateCurve-TLS13": "expects 'illegal parameter' but we want to stick with 'decode error'", + "HelloRetryRequest-DuplicateCookie-TLS13": "expects 'illegal parameter' but we want to stick with 'decode error'", + "EncryptedExtensionsWithKeyShare-TLS13": "expects 'unsupported extension' but RFC requires 'illegal parameter'", + "ClientSkipCertificateVerify-TLS13": "would require ambiguous error mapping", + "Resume-Client-Mismatch-TLS13-TLS12-TLS": "server requests a downgrade to TLS 1.2, echoing the random session ID during a TLS 1.3 resumption. => error mapping conflict", + "ServerAuth-NoFallback-TLS13": "would require ambiguous error mapping", + "TLS-TLS13-PSK_WITH_AES_128_CBC_SHA-server": "expects a different error for better coverage of Boring SSL's code base", + "TLS-TLS13-PSK_WITH_AES_256_CBC_SHA-server": "expects a different error for better coverage of Boring SSL's code base", + "TLS-TLS13-ECDHE_PSK_WITH_AES_128_CBC_SHA-server": "expects a different error for better coverage of Boring SSL's code base", + "TLS-TLS13-ECDHE_PSK_WITH_AES_256_CBC_SHA-server": "expects a different error for better coverage of Boring SSL's code base", + "TLS-TLS13-ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256-server": "expects a different error for better coverage of Boring SSL's code base", + "CertificateVerificationFail-Server-TLS12-TLS-Sync": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS12-CustomCallback-TLS-Sync": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS12-TLS-Sync-ImplicitHandshake": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS12-CustomCallback-TLS-Sync-ImplicitHandshake": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS12-TLS-Sync-SplitHandshakeRecords": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS12-CustomCallback-TLS-Sync-SplitHandshakeRecords": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS12-TLS-Sync-PackHandshake": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS12-CustomCallback-TLS-Sync-PackHandshake": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS12-DTLS-Sync": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS13-DTLS-Sync": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS12-CustomCallback-DTLS-Sync": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS13-CustomCallback-DTLS-Sync": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS12-DTLS-Sync-ImplicitHandshake": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS12-CustomCallback-DTLS-Sync-ImplicitHandshake": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS13-CustomCallback-DTLS-Sync-ImplicitHandshake": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS13-DTLS-Sync-ImplicitHandshake": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS12-DTLS-Sync-SplitHandshakeRecords": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS12-CustomCallback-DTLS-Sync-SplitHandshakeRecords": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS13-DTLS-Sync-SplitHandshakeRecords": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS13-CustomCallback-DTLS-Sync-SplitHandshakeRecords": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS12-CustomCallback-DTLS-Sync-PackHandshake": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS12-DTLS-Sync-PackHandshake": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS13-DTLS-Sync-PackHandshake": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS13-CustomCallback-DTLS-Sync-PackHandshake": "too picky TLS alert", + "NoSSL3-Client-Unsolicited": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS13-TLS-Sync": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS13-CustomCallback-TLS-Sync": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS13-TLS-Sync-ImplicitHandshake": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS13-CustomCallback-TLS-Sync-ImplicitHandshake": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS13-TLS-Sync-SplitHandshakeRecords": "too picky TLS alert", + "CertificateVerificationFail-Server-TLS13-CustomCallback-TLS-Sync-SplitHandshakeRecords": "too picky TLS alert", + "CertificateVerificationFail-Client-TLS12-DTLS-Sync-SplitHandshakeRecords": "too picky TLS alert", + "CertificateVerificationFail-Client-TLS12-CustomCallback-DTLS-Sync-SplitHandshakeRecords": "too picky TLS alert", + "CertificateVerificationFail-Client-TLS13-CustomCallback-DTLS-Sync-SplitHandshakeRecords": "too picky TLS alert", + "CertificateVerificationFail-Client-TLS13-DTLS-Sync-SplitHandshakeRecords": "too picky TLS alert", + "ClientOCSPCallback-FailNoStaple-TLS12-DTLS-Sync-SplitHandshakeRecords": "too picky TLS alert", + "ClientOCSPCallback-FailNoStaple-TLS13-DTLS-Sync-SplitHandshakeRecords": "too picky TLS alert", + "WrongMessageType-TLS13-ClientCertificate-TLS": "too picky TLS alert", + "TrailingMessageData-TLS13-ClientCertificate-TLS": "too picky TLS alert", + "GarbageCertificate-Server-TLS13": "too picky TLS alert", + "AppDataBeforeTLS13KeyChange": "too picky TLS alert", + "ExtensionTrailingData-ServerName-Client-TLS-TLS12": "Botan sends illegal_parameter for malformed server SNI; BoGo expects decode_error", + "ExtensionTrailingData-ServerName-Client-TLS-TLS13": "Botan sends illegal_parameter for malformed server SNI; BoGo expects decode_error", + "ExtensionTrailingData-ServerName-Client-DTLS-TLS12": "Botan sends illegal_parameter for malformed server SNI; BoGo expects decode_error", + "ClientCertificateType-Server-RejectsInvalidEmptyExtension-VerifyPeer-TLS13": "Botan sends decode_error, BoGo expects illegal_parameter", + "ClientCertificateType-Server-RejectsInvalidEmptyExtension-FailIfNoClientCert-TLS13": "Botan sends decode_error, BoGo expects illegal_parameter", + "ClientCertificateType-Server-RejectsInvalidDefaultOnly-FailIfNoClientCert-TLS13": "Botan reports PEER_DID_NOT_RETURN_A_CERTIFICATE, BoGo expects DECODE_ERROR", + "ClientCertificateType-Server-NoClientHelloCertTypes-NoSharedType-FailIfNoClientCert-TLS13": "Botan reports PEER_DID_NOT_RETURN_A_CERTIFICATE, BoGo expects UNSUPPORTED_CERTIFICATE", + "ServerCertificateType-Client-RequestsRPKOnly-NegotiatedRPK-ServerIncorrectlySentX509-TLS13": "Botan parses RPK as X.509 and fails with BER decode error rather than DECODE_ERROR cleanly", + "ServerCertificateType-Client-RequestsRPKOnly-ServerSentEmptyRPK-TLS13": "Empty RPK rejected with generic ASN.1 error rather than INVALID_RAW_PUBLIC_KEY", + "ClientCertificateType-Server-RPKReceived-RPKAccepted-ClientSentX509InError-VerifyPeer-TLS13": "Botan parses RPK as X.509 and fails with BER decode error rather than DECODE_ERROR cleanly", + "ClientCertificateType-Server-RPKReceived-RPKAccepted-ClientSentX509InError-FailIfNoClientCert-TLS13": "Botan parses RPK as X.509 and fails with BER decode error rather than DECODE_ERROR cleanly", + "ClientCertificateType-Server-ClientSentEmptyRPK-TLS13": "Empty RPK rejected with generic ASN.1 error rather than INVALID_RAW_PUBLIC_KEY" + }, + "DisabledTests": { + "*TLS1": "No TLS 1.0", + "*-TLS1-*": "No TLS 1.0", + "*-TLS10-*": "No TLS 1.0", + "TLS1-*": "No TLS 1.0", + "VersionNegotiation*-TLS": "No TLS 1.0", + "VersionNegotiation*-DTLS": "No DTLS 1.0", + "*TLS11": "No TLS 1.1", + "*-TLS11-*": "No TLS 1.1", + "TLS11-*": "No TLS 1.1", + "EchoTLS13CompatibilitySessionID": "If we don't implement TLS 1.3, we won't set session ID accordingly", + "Downgrade-TLS12-Client-*": "If we don't implement TLS 1.3, we won't enforce the downgrade sentinel value", + "Downgrade-TLS12-Server-*": "If we don't implement TLS 1.3, we won't emit the downgrade sentinel value", + "MinimumVersion-Client2-TLS13-*": "We don't implement TLS 1.3, so we cannot offer TLS 1.3", + "MinimumVersion-Client-TLS13-*": "We don't implement TLS 1.3, so we cannot offer TLS 1.3", + "MinimumVersion-Server-TLS13-*": "We don't implement TLS 1.3, so we cannot expect TLS 1.3", + "MinimumVersion-Server2-TLS13-*": "We don't implement TLS 1.3, so we cannot expect TLS 1.3", + "*DTLS13*": "No DTLS 1.3", + "DTLS-TLS13*": "No DTLS 1.3", + "*TLS13-DTLS": "No DTLS 1.3", + "*DTLS-TLS13": "No DTLS 1.3", + "TLS13*-DTLS-*": "No DTLS 1.3", + "MinimumVersion-*-TLS13-*DTLS": "No DTLS 1.3", + "*RSA_PKCS1_MD5_SHA1": "We do not implement MD5/SHA1 concatenation anyway", + "*RSA_PKCS1_SHA1*": "We do not implement PKCS1 SHA-1", + "*-ECDSA_SHA1-*": "We do not implement ECDSA SHA-1", + "*RSA_PKCS1_SHA256_LEGACY-TLS13": "We do allow for PKCS1 in TLS 1.3", + "Compliance-fips202205-*": "We do not have explicit support for a FIPS TLS policy", + "Compliance-fips-202205-*": "We do not have explicit support for a FIPS TLS policy", + "Compliance-wpa-202304-*": "We do not have explicit support for the WPA Enterprise mode", + "Compliance-cnsa202407-*": "We do not have explicit support for CNSA", + "CBCRecordSplitting*": "No need to split CBC records in TLS 1.2", + "DelegatedCredentials*": "No support of -delegated-cerdential", + "*SCSV*": "SCSV is meaningless without TLS 1.0/1.1 support", + "AllExtensions-*": "Not all extensions are implemented", + "VersionTolerance-TLS13": "We are not tolerating 0x0400 as Client Hello legacy_version", + "Server-JDK11-*": "We don't implement JDK-specific workarounds", + "Client-RejectJDK11DowngradeRandom": "We don't implement this workaround", + "ExportTrafficSecrets-*": "Exporting traffic secrets is not implemented", + "TooManyChangeCipherSpec-Client-TLS13": "Limits on the number of CCS are not implemented", + "TooManyChangeCipherSpec-Server-TLS13": "Limits on the number of CCS are not implemented", + "PostQuantumNotEnabledByDefaultInClients": "Oh yes it is", + "TLS12SessionID-TLS13": "We don't offer TLS 1.3 when a TLS 1.2 session was found", + "Ticket-Forbidden-TLS13": "We don't offer TLS 1.3 when a TLS 1.2 session was found", + "Resume-Client-NoResume-TLS12-TLS13-TLS": "We don't offer TLS 1.3 when a TLS 1.2 session was found", + "Resume-Client-Mismatch-TLS12-TLS13-TLS": "We don't offer TLS 1.3 when a TLS 1.2 session was found", + "Resume-Server-UnofferedCipher-TLS13": "BoringSSL will not allow switching ciphers during TLS 1.3 resumption, we do, though.", + "HttpGET": "TLS 1.3 server does not detect HTTP", + "HttpPOST": "TLS 1.3 server does not detect HTTP", + "HttpPUT": "TLS 1.3 server does not detect HTTP", + "HttpHEAD": "TLS 1.3 server does not detect HTTP", + "HttpCONNECT": "TLS 1.3 server does not detect HTTP", + "*EarlyData*": "No TLS 1.3 Early Data, yet", + "TLS13-TicketAgeSkew-*": "No TLS 1.3 Early Data, yet", + "ExportKeyingMaterial-Server-HalfRTT-TLS13": "No TLS 1.3 Early Data, yet", + "EarlyDataEnabled*": "No TLS 1.3 Early Data, yet", + "EarlyData-Reject0RTT*": "No TLS 1.3 Early Data, yet", + "PartialEndOfEarlyDataWithClientHello": "No TLS 1.3 Early Data, yet", + "SendNoClientCertificateExtensions-TLS13": "-signed-cert-timestamps currently not supported in the shim", + "KeyUpdate-RequestACK-UnfinishedWrite": "-read-with-unfinished-write currently not supported in the shim", + "TLS-ECH*": "No ECH support", + "ECH*": "No ECH support", + "DuplicateCertCompressionExt*": "No support for 1.3 cert compression extension", + "CertCompression*-TLS13": "No support for 1.3 cert compression extension", + "SupportedVersionSelection-TLS12": "We just ignore the version extension in this case", + "NoCommonSignatureAlgorithms-TLS12-Fallback": "Fallback behaviour not implemented by shim", + "CheckClientCertificateTypes": "Client certificate type check is a library-user responsibility", + "Downgrade-*-Client-Ignore": "Not possible to ignore downgrade indicator", + "Agree-Digest-SHA1": "No SHA-1 in TLS 1.2", + "ServerAuth-SHA1-Fallback-*": "No SHA-1 in TLS 1.2", + "*-InvalidSignature-*_SHA1-TLS12": "No SHA-1 in TLS 1.2", + "*-Sign-*_SHA1-TLS12": "No SHA-1 in TLS 1.2", + "*-Sign-Negotiate-*_SHA1-TLS12": "No SHA-1 in TLS 1.2", + "*-VerifyDefault-*_SHA1-TLS12": "No SHA-1 in TLS 1.2", + "*-Verify-*_SHA1-TLS12": "No SHA-1 in TLS 1.2", + "*QUIC*": "No QUIC", + "ALPS*": "No ALPS", + "ExtraClientEncryptedExtension-*": "No ALPS", + "*NPN*": "No support for NPN", + "ALPNServer-Preferred-*": "No support for NPN", + "*-NextProtocol*": "No support for NPN", + "*SignedCertificateTimestamp*": "No support for SCT", + "*SCT*": "No support for SCT", + "Renegotiation-ChangeAuthProperties": "No support for SCT", + "UnsolicitedCertificateExtensions-*": "No support for SCT", + "IgnoreExtensionsOnIntermediates-TLS13": "No support for SCT", + "SendNoExtensionsOnIntermediate-TLS13": "No support for SCT", + "CertificateVerificationSoftFail*": "Fail, but don't fail... wtf?", + "*NULL-SHA*": "No support for NULL ciphers", + "*WITH_NULL*": "No support for NULL ciphers", + "*GREASE*": "No support for GREASE", + "*ChannelID*": "No support for ChannelID", + "*TokenBinding*": "No support for Token Binding", + "ClientHelloPadding": "No support for client hello padding extension", + "TLSUnique*": "Not supported", + "*CECPQ2*": "Not implemented", + "PQExperimentSignal*": "Not implemented", + "*P-224*": "P-224 not supported in TLS", + "*V2ClientHello*": "No support for SSLv2 client hellos", + "*Ed25519*": "Ed25519 not implemented in TLS", + "*FalseStart*": "Botan doesn't do false start", + "MaxSendFragment*": "Maximum fragment extension not supported", + "ExportKeyingMaterial-EmptyContext*": "No support for empty context", + "Peek-*": "No peek API", + "*OldCallback*": "BoringSSL specific API test", + "*Renegotiate-Client-Explicit*": "BoringSSL specific API test", + "CBCRecordSplittingPartialWrite*": "BoringSSL specific API test", + "TicketCallback*": "BoringSSL specific API test", + "Server-DDoS*": "BoringSSL specific API test", + "RetainOnlySHA256-*": "BoringSSL specific API test", + "Renegotiate-Client-UnfinishedWrite": "BoringSSL specific API test", + "FailEarlyCallback": "BoringSSL specific API test", + "CurveTest-*Kyber*": "We no longer support Kyber r3 key exchange", + "ShimTicketRewritable": "Botan has a different ticket format", + "Resume-Server-DeclineCrossVersion*": "Botan has a different ticket format", + "Resume-Server-DeclineBadCipher*": "Botan has a different ticket format", + "Resume-Server-CipherNotPreferred*": "Botan has a different ticket format", + "TLS*-NoTicket-NoAccept": "BoGo expects that if ticket is issued stateful resumption is impossible", + "CheckLeafCurve": "Botan doesn't care what curve an ECDSA cert uses", + "CheckECDSACurve-TLS12": "Botan doesn't care what curve an ECDSA cert uses", + "CertificateVerificationDoesNotFailOnResume*": "Botan doesn't support reverify on resume", + "CertificateVerificationFailsOnResume*": "Botan doesn't support reverify on resume", + "CertificateVerificationPassesOnResume*": "Botan doesn't support reverify on resume", + "CipherNegotiation-2": "No support for cipher equivalence classes", + "CipherNegotiation-3": "No support for cipher equivalence classes", + "CipherNegotiation-4": "No support for cipher equivalence classes", + "CipherNegotiation-5": "No support for cipher equivalence classes", + "CipherNegotiation-8": "No support for cipher equivalence classes", + "ALPNServer-SelectEmpty-*": "Botan treats empty ALPN from callback as a decline", + "AppDataAfterChangeCipherSpec-DTLS*": "BoringSSL DTLS drops out of order AppData, we reject", + "Resume-Client-NoResume-TLS1-TLS11-TLS": "BoGo expects resumption attempt sends latest version", + "Resume-Client-NoResume-TLS1-TLS12-TLS": "BoGo expects resumption attempt sends latest version", + "Resume-Client-NoResume-TLS11-TLS12-TLS": "BoGo expects resumption attempt sends latest version", + "Resume-Client-NoResume-TLS1-TLS12-DTLS": "BoGo expects resumption attempt sends latest version", + "Resume-Client-Mismatch-TLS1-TLS11-TLS": "BoGo expects resumption attempt sends latest version", + "Resume-Client-Mismatch-TLS1-TLS12-TLS": "BoGo expects resumption attempt sends latest version", + "Resume-Client-Mismatch-TLS11-TLS12-TLS": "BoGo expects resumption attempt sends latest version", + "Resume-Client-Mismatch-TLS1-TLS12-DTLS": "BoGo expects resumption attempt sends latest version", + "LooseInitialRecordVersion-TLS12": "Botan is somewhat strict about the record version number", + "CurveTest-*-Compressed*": "Point compression is supported, which BoGo doesn't expect", + "PointFormat-*-MissingUncompressed": "Point compression is supported, which BoGo doesn't expect", + "RSAPSSSupport-ConfigPSS-NoCerts-TLS12-*": "Needs investigation", + "RSAPSSSupport-Default-NoCerts-TLS12-*": "Needs investigation", + "DTLS-Retransmit*": "Shim needs timeout support", + "DTLS-StrayRetransmitFinished-ClientFull": "Needs investigation", + "DTLS-StrayRetransmitFinished-ServerResume": "Needs investigation", + "SRTP-Server-IgnoreMKI-*": "Non-empty MKI is rejected (bug)", + "Renegotiate-Client-Packed": "Packing HelloRequest with Finished loses the HelloRequest (bug)", + "SendHalfHelloRequest*PackHandshake": "Packing HelloRequest with Finished loses the HelloRequest (bug)", + "PartialClientFinishedWithClientHello": "Need to check for buffered messages when CCS (bug)", + "SendUnencryptedFinished-DTLS": "Need to check for buffered messages when CCS (bug)", + "RSAKeyUsage-*-TLS12": "We always enforce key usage", + "RSAKeyUsage-Client-WantSignature-GotEncipherment-AlwaysEnforced-TLS13": "We always enforce key usage", + "AllExtensions-Client-Permute-TLS-TLS12": "Requires new shim flags that are NYI (as of March 2022)", + "AllExtensions-Client-Permute-DTLS-TLS12": "Requires new shim flags that are NYI (as of March 2022)", + "EarlyData-WriteAfterEncryptedExtensions": "Requires new shim flags that are NYI (as of March 2022)", + "EarlyData-WriteAfterServerHello": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-Certificate-*": "Requires new shim flags that are NYI (as of May 2024)", + "TLS-HintMismatch-CipherMismatch1": "Requires new shim flags that are NYI (as of March 2023)", + "TLS-HintMismatch-CipherMismatch2": "Requires new shim flags that are NYI (as of March 2023)", + "TLS-HintMismatch-ECDHE-Group": "Requires new shim flags that are NYI (as of March 2023)", + "TLS-HintMismatch-SignatureInput": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-KeyShare": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-HandshakerHelloRetryRequest": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-ShimHelloRetryRequest": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-SignatureAlgorithm-TLS*": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-NoTickets1-TLS*": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-NoTickets2-TLS*": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-Version2": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-CertificateRequest": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-CertificateCompression-HandshakerOnly": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-CertificateCompression-ShimOnly": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-CertificateCompression-AlgorithmMismatch": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-CertificateCompression-InputMismatch": "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-Version1": "Requires new shim flags that are NYI (as of March 2022)", + "CertificateSelection-*": "Certificate selection is a library-user responsibility", + "ALPNClient-AllowUnknown-*": "Botan always validates server ALPN selection against offered list", + "Resume-Server-OmitAllPSKsOnSecondClientHello": "Different error code for inconsistent ClientHello", + "SendEmptySessionTicket-TLS13": "Different error handling for empty session ticket", + "CertificateInResumption-TLS13": "Unmapped error string", + "CertificateRequestInResumption-TLS13": "Unmapped error string", + "AppDataBeforeTLS13KeyChange-Empty": "Different error code for bad decrypt", + "UnencryptedEncryptedExtensions": "Different error for unencrypted record", + "TrustAnchors-Unsolicited-Certificate": "Different alert code for unsolicited extension", + "PSK-Server-OmitAllPSKsOnSecondClientHello-TLS": "Botan reports INCONSISTENT_CLIENT_HELLO instead of MISSING_EXTENSION", + "PSK-Server-HRR-PSKMissing-TLS": "Botan sends handshake_failure alert instead of illegal_parameter", + "PSK-Server-MissingPSKMode-NoMatch-TLS": "Botan reports PSK_IDENTITY_NOT_FOUND instead of NO_SUPPORTED_PSK_MODE", + "DTLS-ECH*": "No ECH support", + "KeyUpdate-*-DTLS": "No DTLS 1.3", + "AppDataBeforeTLS13KeyChange-DTLS*": "No DTLS 1.3", + "UnencryptedEncryptedExtensions-DTLS": "No DTLS 1.3", + "TLS13-OnlyPadding-DTLS": "No DTLS 1.3", + "Resume-*-TLS13-TLS12-DTLS": "No DTLS 1.3", + "Downgrade-TLS12-*-DTLS": "No DTLS 1.3", + "WrongMessageType-TLS13-*-DTLS": "No DTLS 1.3", + "TrailingMessageData-TLS13-EncryptedExtensions-DTLS": "No DTLS 1.3", + "TrailingMessageData-TLS13-ServerCertificateVerify-DTLS": "No DTLS 1.3", + "KeyChangeWithBufferedMessages-DTLS": "No DTLS 1.3", + "Renegotiate-DTLS-Server-Forbidden": "Botan tolerates DTLS renegotiation", + "Renegotiate-DTLS-Client-Forbidden": "Botan tolerates DTLS renegotiation", + "DTLS12-SendExtraFinished-*": "Botan tolerates extra Finished messages in DTLS", + "MixCompleteMessageWithFragments-DTLS-TLS12": "DTLS fragment handling difference", + "RejectPSSKeyType-*": "Botan does not reject RSA-PSS key type", + "CertificateCipherMismatch-PSS": "Botan does not reject PSS cipher mismatch", + "ServerNameExtensionServer-*-TLS12": "Botan does not echo server_name in TLS 1.2 ServerHello", + "IgnoreLegacyVersion-TLS13": "Botan strictly validates legacy_version in TLS 1.3 ClientHello", + "MTU-DTLS12-3DES-CBC": "No 3DES support", + "TLS13-Client-*TicketFlags": "Botan does not strictly validate ticket flags encoding", + "TLS12-NoTicket-NoOffer": "Different session ticket/ID handling", + "PAKE-*": "No PAKE support", + "TrustAnchors-EmptyID-*": "No TrustAnchors extension support", + "TrustAnchors-ServerSelect-*": "No TrustAnchors extension support", + "TrustAnchors-ServerReceiveEmptyRequest": "No TrustAnchors extension support", + "ExtensionTrailingData-TrustAnchors-ClientHello-Server-TLS-TLS13": "No TrustAnchors extension support", + "PSK-Server-CertOrPSK-Cert-*": "Botan shim does not honor credential ordering between PSK and X.509", + "ClientCertificateType-Client-OffersRPKOnly-ServerOmitsExtension-TLS13": "Botan does not reject when server omits cert_type extension and client only offers RPK", + "TooManyKeyUpdates": "BoringSSL's approach to KeyUpdate throttling is ineffective and pointless", + "*ML-DSA*": "Need support for the LAMPS tagged seed keys", + "TLS13-EmptyRecords-DTLS": "No DTLS 1.3", + "TLS13-RecordPadding-DTLS": "No DTLS 1.3", + "ClientCertificateType-Server-*-TLS12": "Botan does not support RPK certificate type negotiation in TLS 1.2", + "ClientCertificateType-Client-*-TLS12*": "Botan does not support RPK certificate type negotiation in TLS 1.2", + "ServerCertificateType-Server-*-TLS12*": "Botan does not support RPK certificate type negotiation in TLS 1.2", + "ServerCertificateType-Client-*-TLS12": "Botan does not support RPK certificate type negotiation in TLS 1.2", + "ServerCertificateType-Client-RequestsRPKOnly-*-TLS12": "Botan does not support RPK certificate type negotiation in TLS 1.2", + "ServerCertificateType-Client-RequestsRPKX509-*-TLS12": "Botan does not support RPK certificate type negotiation in TLS 1.2", + "ServerCertificateType-Client-RequestsX509RPK-*-TLS12": "Botan does not support RPK certificate type negotiation in TLS 1.2", + "ServerCertificateType-Client-RPKVerifyFail-TLS12": "Botan does not support RPK certificate type negotiation in TLS 1.2", + "ServerCertificateType-Client-RequestsDefaultOnly-ServerPickedRPKInError-TLS12": "Botan does not support RPK certificate type negotiation in TLS 1.2", + "ClientCertificateType-Server-RejectsInvalidDefaultOnly-VerifyPeer-TLS13": "Botan does not reject X509-only cert type extension", + "ClientCertificateType-Server-NoClientHelloCertTypes-NoSharedType-VerifyPeer-TLS13": "Botan does not reject when no shared cert type exists", + "ServerCertificateType-Server-RejectsDefaultOnly-TLS12": "Botan does not reject X509-only cert type extension as server", + "ServerCertificateType-Server-RejectsDefaultOnly-TLS13": "Botan does not reject X509-only cert type extension as server", + "ServerCertificateType-Client-RequestsRPKOnly-ServerPickedX509ByDefaultInError-TLS13": "Botan does not enforce client-side cert type rejection of unsolicited X509", + "PSK-Client-PSKRequired-TLS": "Botan client does not enforce PSK-only mode", + "PSK-Client-PSKRequired-TLS12-TLS": "Botan client does not enforce PSK-only mode", + "PSK-*-DTLS": "No DTLS 1.3", + "NotJustKyberKeyShare*": "We no longer support Kyber r3 key exchange", + "KyberKeyShareIncludedSecond*": "We no longer support Kyber r3 key exchange", + "KyberKeyShareIncludedThird*": "We no longer support Kyber r3 key exchange", + "CustomKeyShares-All-TLS13": "We no longer support Kyber r3 key exchange", + "DTLS-Replay-NonMonotonic*": "Needs investigation, started failing after https://github.com/google/boringssl/commit/f94f3ed3965ea033001fb9ae006084eee408b861" + }, + "ErrorMap": { + ":CLIENTHELLO_PARSE_FAILED:": [ + ":DECODE_ERROR:" + ], + ":BAD_DECRYPT:": [ + ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:" + ], + ":ERROR_PARSING_EXTENSION:": [ + ":ERROR_PARSING_EXTENSION:", + ":DECODE_ERROR:" + ] + } +} diff -Nru botan3-3.7.1+dfsg/src/build-data/arch/alpha.txt botan3-3.12.0+dfsg/src/build-data/arch/alpha.txt --- botan3-3.7.1+dfsg/src/build-data/arch/alpha.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/arch/alpha.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,5 +1,3 @@ -endian little -wordsize 64 axp diff -Nru botan3-3.7.1+dfsg/src/build-data/arch/arm32.txt botan3-3.12.0+dfsg/src/build-data/arch/arm32.txt --- botan3-3.7.1+dfsg/src/build-data/arch/arm32.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/arch/arm32.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,4 +1,3 @@ -endian little family arm diff -Nru botan3-3.7.1+dfsg/src/build-data/arch/arm64.txt botan3-3.12.0+dfsg/src/build-data/arch/arm64.txt --- botan3-3.7.1+dfsg/src/build-data/arch/arm64.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/arch/arm64.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,5 +1,3 @@ -endian little -wordsize 64 family arm diff -Nru botan3-3.7.1+dfsg/src/build-data/arch/generic.txt botan3-3.12.0+dfsg/src/build-data/arch/generic.txt --- botan3-3.7.1+dfsg/src/build-data/arch/generic.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/arch/generic.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,4 +1,4 @@ # This target can be used when building an amalgamation which must -# be built on multiple architectures, or when targetting a CPU +# be built on multiple architectures, or when targeting a CPU # which the build system doesn't know about. diff -Nru botan3-3.7.1+dfsg/src/build-data/arch/ia64.txt botan3-3.12.0+dfsg/src/build-data/arch/ia64.txt --- botan3-3.7.1+dfsg/src/build-data/arch/ia64.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/arch/ia64.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,4 +1,3 @@ -wordsize 64 itanium diff -Nru botan3-3.7.1+dfsg/src/build-data/arch/llvm.txt botan3-3.12.0+dfsg/src/build-data/arch/llvm.txt --- botan3-3.7.1+dfsg/src/build-data/arch/llvm.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/arch/llvm.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1 +0,0 @@ -wordsize 64 diff -Nru botan3-3.7.1+dfsg/src/build-data/arch/loongarch64.txt botan3-3.12.0+dfsg/src/build-data/arch/loongarch64.txt --- botan3-3.7.1+dfsg/src/build-data/arch/loongarch64.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/arch/loongarch64.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,3 +1,6 @@ family loongarch -endian little -wordsize 64 + + +lsx +lasx + diff -Nru botan3-3.7.1+dfsg/src/build-data/arch/m68k.txt botan3-3.12.0+dfsg/src/build-data/arch/m68k.txt --- botan3-3.7.1+dfsg/src/build-data/arch/m68k.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/arch/m68k.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,4 +1,3 @@ -endian big 680x0 diff -Nru botan3-3.7.1+dfsg/src/build-data/arch/mips64.txt botan3-3.12.0+dfsg/src/build-data/arch/mips64.txt --- botan3-3.7.1+dfsg/src/build-data/arch/mips64.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/arch/mips64.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,4 +1,3 @@ -wordsize 64 mips64el diff -Nru botan3-3.7.1+dfsg/src/build-data/arch/powerpcspe.txt botan3-3.12.0+dfsg/src/build-data/arch/powerpcspe.txt --- botan3-3.7.1+dfsg/src/build-data/arch/powerpcspe.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/arch/powerpcspe.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,3 +1,2 @@ -endian big family ppc diff -Nru botan3-3.7.1+dfsg/src/build-data/arch/ppc32.txt botan3-3.12.0+dfsg/src/build-data/arch/ppc32.txt --- botan3-3.7.1+dfsg/src/build-data/arch/ppc32.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/arch/ppc32.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,4 +1,3 @@ -endian big family ppc @@ -6,7 +5,3 @@ powerpc ppc - - -altivec - diff -Nru botan3-3.7.1+dfsg/src/build-data/arch/ppc64.txt botan3-3.12.0+dfsg/src/build-data/arch/ppc64.txt --- botan3-3.7.1+dfsg/src/build-data/arch/ppc64.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/arch/ppc64.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,7 +1,5 @@ -endian big family ppc -wordsize 64 powerpc64 diff -Nru botan3-3.7.1+dfsg/src/build-data/arch/riscv32.txt botan3-3.12.0+dfsg/src/build-data/arch/riscv32.txt --- botan3-3.7.1+dfsg/src/build-data/arch/riscv32.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/arch/riscv32.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,2 +1 @@ family riscv -endian little diff -Nru botan3-3.7.1+dfsg/src/build-data/arch/riscv64.txt botan3-3.12.0+dfsg/src/build-data/arch/riscv64.txt --- botan3-3.7.1+dfsg/src/build-data/arch/riscv64.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/arch/riscv64.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,3 +1 @@ family riscv -endian little -wordsize 64 diff -Nru botan3-3.7.1+dfsg/src/build-data/arch/s390.txt botan3-3.12.0+dfsg/src/build-data/arch/s390.txt --- botan3-3.7.1+dfsg/src/build-data/arch/s390.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/arch/s390.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1 +0,0 @@ -endian big diff -Nru botan3-3.7.1+dfsg/src/build-data/arch/s390x.txt botan3-3.12.0+dfsg/src/build-data/arch/s390x.txt --- botan3-3.7.1+dfsg/src/build-data/arch/s390x.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/arch/s390x.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,2 +0,0 @@ -endian big -wordsize 64 diff -Nru botan3-3.7.1+dfsg/src/build-data/arch/sparc32.txt botan3-3.12.0+dfsg/src/build-data/arch/sparc32.txt --- botan3-3.7.1+dfsg/src/build-data/arch/sparc32.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/arch/sparc32.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,4 +1,3 @@ -endian big family sparc diff -Nru botan3-3.7.1+dfsg/src/build-data/arch/sparc64.txt botan3-3.12.0+dfsg/src/build-data/arch/sparc64.txt --- botan3-3.7.1+dfsg/src/build-data/arch/sparc64.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/arch/sparc64.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,3 +1 @@ family sparc -wordsize 64 -endian big diff -Nru botan3-3.7.1+dfsg/src/build-data/arch/wasm.txt botan3-3.12.0+dfsg/src/build-data/arch/wasm.txt --- botan3-3.7.1+dfsg/src/build-data/arch/wasm.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/arch/wasm.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,2 +1,3 @@ -endian little -wordsize 32 + +simd128 + diff -Nru botan3-3.7.1+dfsg/src/build-data/arch/x32.txt botan3-3.12.0+dfsg/src/build-data/arch/x32.txt --- botan3-3.7.1+dfsg/src/build-data/arch/x32.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/arch/x32.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,4 +1,3 @@ -endian little family x86 diff -Nru botan3-3.7.1+dfsg/src/build-data/arch/x86_32.txt botan3-3.12.0+dfsg/src/build-data/arch/x86_32.txt --- botan3-3.7.1+dfsg/src/build-data/arch/x86_32.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/arch/x86_32.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,4 +1,3 @@ -endian little family x86 diff -Nru botan3-3.7.1+dfsg/src/build-data/arch/x86_64.txt botan3-3.12.0+dfsg/src/build-data/arch/x86_64.txt --- botan3-3.7.1+dfsg/src/build-data/arch/x86_64.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/arch/x86_64.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,5 +1,3 @@ -endian little -wordsize 64 family x86 @@ -23,6 +21,7 @@ sse41 ssse3 avx512 +avx512_clmul vaes sha512 sm3 diff -Nru botan3-3.7.1+dfsg/src/build-data/botan-config-version.cmake.in botan3-3.12.0+dfsg/src/build-data/botan-config-version.cmake.in --- botan3-3.7.1+dfsg/src/build-data/botan-config-version.cmake.in 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/botan-config-version.cmake.in 2026-05-07 01:38:28.000000000 +0000 @@ -1,4 +1,25 @@ -set(PACKAGE_VERSION %{version}) +# Copyright (c) 2023-present The Botan Authors (see doc/authors.txt) +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +set(PACKAGE_VERSION %{version_major}.%{version_minor}.%{version_patch}) # Botan follows semver: # * the requested version should be less or equal to the installed version, however diff -Nru botan3-3.7.1+dfsg/src/build-data/botan-config.cmake.in botan3-3.12.0+dfsg/src/build-data/botan-config.cmake.in --- botan3-3.7.1+dfsg/src/build-data/botan-config.cmake.in 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/botan-config.cmake.in 2026-05-07 01:38:28.000000000 +0000 @@ -1,3 +1,24 @@ +# Copyright (c) 2023-present The Botan Authors (see doc/authors.txt) +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + #.rst: # botan-config.cmake # ----------- @@ -19,11 +40,18 @@ # # This module defines :prop_tgt:`IMPORTED` targets: # -# ``Botan::Botan`` +# ``botan::botan`` # The botan shared library, if found. -# ``Botan::Botan-static`` +# ``botan::botan-static`` # The botan static library, if found. # +# Previous versions of this CMake module defined the targets in uppercase, +# such as ``Botan::Botan``, for backward-compatibility we define those as +# aliases (if CMake is 3.18 or newer, see GH #5098): +# +# ``Botan::Botan`` as an alias for ``botan::botan`` +# ``Botan::Botan-static`` as an alias for ``botan::botan-static`` +# # Result variables # ^^^^^^^^^^^^^^^^ # @@ -65,51 +93,88 @@ return() endif() -# botan-config.cmake lives in "${_Botan_PREFIX}/lib/cmake/Botan-X": traverse up to $_Botan_PREFIX -set(_Botan_PREFIX "${CMAKE_CURRENT_LIST_DIR}") -get_filename_component(_Botan_PREFIX "${_Botan_PREFIX}" DIRECTORY) -get_filename_component(_Botan_PREFIX "${_Botan_PREFIX}" DIRECTORY) -get_filename_component(_Botan_PREFIX "${_Botan_PREFIX}" DIRECTORY) +# botan-config.cmake lives N levels below the install prefix in the file system. +# Traverse up to the prefix, but first snap to the known original install path +# if loaded from there -- this handles cross-prefix symbolic links such as +# /lib -> /usr/lib that would otherwise make the traversal land at the wrong root. +get_filename_component(_realCurr "${CMAKE_CURRENT_LIST_DIR}" REALPATH) +get_filename_component(_realOrig "%{cmake_install_dir}" REALPATH) +if(_realCurr STREQUAL _realOrig) + set(_Botan_PREFIX "%{cmake_install_dir}") +else() + set(_Botan_PREFIX "${CMAKE_CURRENT_LIST_DIR}") +endif() +unset(_realCurr) +unset(_realOrig) + +# Traverse from the cmake config directory up to the install prefix. +# The exact number of steps is fixed at configure time from the known layout. +%{for cmake_relpath_components} +get_filename_component(_Botan_PREFIX "${_Botan_PREFIX}" PATH) +%{endfor} +if(_Botan_PREFIX STREQUAL "/") + set(_Botan_PREFIX "") +endif() + +set(_Botan_INCLUDE_DIR "${_Botan_PREFIX}/%{namespaced_includedir_rel}") +set(_Botan_LIB_PREFIX "${_Botan_PREFIX}/%{libdir_rel}") + +%{if bindir_rel} +set(_Botan_BIN_DIR "${_Botan_PREFIX}/%{bindir_rel}") +%{endif} +%{unless bindir_rel} +set(_Botan_BIN_DIR "%{bindir}") +%{endif} %{if build_static_lib} -if(NOT TARGET Botan::Botan-static) - add_library(Botan::Botan-static STATIC IMPORTED) - set_target_properties(Botan::Botan-static +if(NOT TARGET botan::botan-static) + add_library(botan::botan-static STATIC IMPORTED) + set_target_properties(botan::botan-static PROPERTIES - IMPORTED_LOCATION "${_Botan_PREFIX}/lib/%{static_lib_name}" - INTERFACE_INCLUDE_DIRECTORIES "${_Botan_PREFIX}/include/botan-%{version_major}" + IMPORTED_LOCATION "${_Botan_LIB_PREFIX}/%{static_lib_name}" + INTERFACE_INCLUDE_DIRECTORIES "${_Botan_INCLUDE_DIR}" IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" INTERFACE_LINK_OPTIONS "SHELL:%{cxx_abi_flags}") + + # TODO(Botan4): Remove this alias + if(NOT ${CMAKE_VERSION} VERSION_LESS "3.18.0") # 3.18 allows creating ALIAS targets to non-GLOBAL targets + add_library(Botan::Botan-static ALIAS botan::botan-static) + endif() endif() %{endif} %{if implib_name} -set(_Botan_implib "${_Botan_PREFIX}/lib/%{implib_name}") -set(_Botan_shared_lib "${_Botan_PREFIX}/bin/%{shared_lib_name}") +set(_Botan_implib "${_Botan_LIB_PREFIX}/%{implib_name}") +set(_Botan_shared_lib "${_Botan_BIN_DIR}/%{shared_lib_name}") %{endif} %{unless implib_name} set(_Botan_implib "") %{endif} %{if build_shared_lib} -if(NOT TARGET Botan::Botan) +if(NOT TARGET botan::botan) if(NOT DEFINED _Botan_shared_lib) - set(_Botan_shared_lib "${_Botan_PREFIX}/lib/%{shared_lib_name}") + set(_Botan_shared_lib "${_Botan_LIB_PREFIX}/%{shared_lib_name}") endif() - add_library(Botan::Botan SHARED IMPORTED) - set_target_properties(Botan::Botan + add_library(botan::botan SHARED IMPORTED) + set_target_properties(botan::botan PROPERTIES IMPORTED_LOCATION "${_Botan_shared_lib}" IMPORTED_IMPLIB "${_Botan_implib}" - INTERFACE_INCLUDE_DIRECTORIES "${_Botan_PREFIX}/include/botan-%{version_major}" + INTERFACE_INCLUDE_DIRECTORIES "${_Botan_INCLUDE_DIR}" INTERFACE_LINK_OPTIONS "SHELL:%{cxx_abi_flags}") - set_property(TARGET Botan::Botan APPEND PROPERTY IMPORTED_CONFIGURATIONS NOCONFIG) - set_target_properties(Botan::Botan + set_property(TARGET botan::botan APPEND PROPERTY IMPORTED_CONFIGURATIONS NOCONFIG) + set_target_properties(botan::botan PROPERTIES - IMPORTED_LOCATION_NOCONFIG "${_Botan_PREFIX}/lib/%{shared_lib_name}" + IMPORTED_LOCATION_NOCONFIG "${_Botan_LIB_PREFIX}/%{shared_lib_name}" IMPORTED_SONAME_NOCONFIG "%{shared_lib_name}" IMPORTED_IMPLIB_NOCONFIG "${_Botan_implib}") + + # TODO(Botan4): Remove this alias + if(NOT ${CMAKE_VERSION} VERSION_LESS "3.18.0") # 3.18 allows creating ALIAS targets to non-GLOBAL targets + add_library(Botan::Botan ALIAS botan::botan) + endif() endif() %{endif} diff -Nru botan3-3.7.1+dfsg/src/build-data/botan.pc.in botan3-3.12.0+dfsg/src/build-data/botan.pc.in --- botan3-3.7.1+dfsg/src/build-data/botan.pc.in 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/botan.pc.in 2026-05-07 01:38:28.000000000 +0000 @@ -1,7 +1,7 @@ prefix=%{prefix} exec_prefix=${prefix} -libdir=%{libdir} -includedir=${prefix}/include/botan-%{version_major} +libdir=${prefix}/%{libdir_rel} +includedir=${prefix}/%{namespaced_includedir_rel} Name: Botan Description: Crypto and TLS for Modern C++ diff -Nru botan3-3.7.1+dfsg/src/build-data/buildh.in botan3-3.12.0+dfsg/src/build-data/buildh.in --- botan3-3.7.1+dfsg/src/build-data/buildh.in 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/buildh.in 2026-05-07 01:38:28.000000000 +0000 @@ -1,19 +1,13 @@ -#ifndef BOTAN_BUILD_CONFIG_H_ -#define BOTAN_BUILD_CONFIG_H_ +#ifndef BOTAN_BUILD_INFO_H_ +#define BOTAN_BUILD_INFO_H_ /** * @file build.h * @brief Build configuration for Botan %{version} -* -* Automatically generated from -* '%{command_line}' -* -* Target -* - Compiler: %{cxx} %{cxx_abi_flags} %{cc_lang_flags} %{cc_compile_flags} -* - Arch: %{arch} -* - OS: %{os} */ +/* NOLINTBEGIN(*-macro-usage,*-macro-to-enum) */ + /** * @defgroup buildinfo Build Information */ @@ -24,42 +18,63 @@ * @{ */ -/// The major version of the release +/** +* The major version of the release +*/ #define BOTAN_VERSION_MAJOR %{version_major} -/// The minor version of the release + +/** +* The minor version of the release +*/ #define BOTAN_VERSION_MINOR %{version_minor} -/// The patch version of the release + +/** +* The patch version of the release +*/ #define BOTAN_VERSION_PATCH %{version_patch} /** * Expands to an integer of the form YYYYMMDD if this is an official * release, or 0 otherwise. For instance, 2.19.0, which was released * on January 19, 2022, has a `BOTAN_VERSION_DATESTAMP` of 20220119. + * + * This macro is deprecated; use version_datestamp from version.h + * + * TODO(Botan4) remove this */ #define BOTAN_VERSION_DATESTAMP %{version_datestamp} -%{if version_suffix} -#define BOTAN_VERSION_SUFFIX %{version_suffix} -#define BOTAN_VERSION_SUFFIX_STR "%{version_suffix}" -%{endif} - +/** + * A string set to the release type + * + * This macro is deprecated + * + * TODO(Botan4) remove this + */ #define BOTAN_VERSION_RELEASE_TYPE "%{release_type}" /** * A macro expanding to a string that is set to a revision identifier * corresponding to the source, or "unknown" if this could not be - * determined. It is set for all official releases, and for builds that - * originated from within a git checkout. + * determined. It is set for all official releases. + * + * This macro is deprecated; use version_vc_revision from version.h + * + * TODO(Botan4) remove this */ -#define BOTAN_VERSION_VC_REVISION "%{version_vc_rev}" +#define BOTAN_VERSION_VC_REVISION "%{version_vc_rev_or_unknown}" /** * A macro expanding to a string that is set at build time using the * `--distribution-info` option. It allows a packager of the library * to specify any distribution-specific patches. If no value is given * at build time, the value is the string "unspecified". + * + * This macro is deprecated; use version_distribution_info from version.h + * + * TODO(Botan4) remove this */ -#define BOTAN_DISTRIBUTION_INFO "%{distribution_info}" +#define BOTAN_DISTRIBUTION_INFO "%{distribution_info_or_unspecified}" /** * @} @@ -71,97 +86,42 @@ * @{ */ -/** How many bits per limb in a BigInt */ -#define BOTAN_MP_WORD_BITS %{mp_bits} - -%{if fuzzer_mode} -/** Disables certain validation checks to ease fuzzability of the library - * @warning This causes the library build to be insecure, hence, it must not be - * used in a production environment! - */ -#define BOTAN_UNSAFE_FUZZER_MODE -%{endif} %{if fuzzer_type} #define BOTAN_FUZZERS_ARE_BEING_BUILT -#define BOTAN_FUZZER_IS_%{fuzzer_type} %{endif} %{if disable_deprecated_features} +/** + * Indicates that deprecated features have been disabled + */ #define BOTAN_DISABLE_DEPRECATED_FEATURES %{endif} %{if enable_experimental_features} +/** + * Indicates that experimental features have been enabled + */ #define BOTAN_ENABLE_EXPERIMENTAL_FEATURES %{endif} -#define BOTAN_INSTALL_PREFIX R"(%{prefix})" -#define BOTAN_INSTALL_HEADER_DIR R"(%{includedir}/botan-%{version_major})" -#define BOTAN_INSTALL_LIB_DIR R"(%{libdir})" -#define BOTAN_LIB_LINK "%{link_to}" -#define BOTAN_LINK_FLAGS "%{cxx_abi_flags}" - -%{if system_cert_bundle} -#define BOTAN_SYSTEM_CERT_BUNDLE "%{system_cert_bundle}" -%{endif} - #ifndef BOTAN_DLL #define BOTAN_DLL %{visibility_attribute} #endif /* Target identification and feature test macros */ -#define BOTAN_TARGET_OS_IS_%{os_name|upper} - -%{for os_features} +%{for os_features_public} #define BOTAN_TARGET_OS_HAS_%{i|upper} %{endfor} -#define BOTAN_BUILD_COMPILER_IS_%{cc_macro} - -%{if cxx_supports_gcc_inline_asm} -#define BOTAN_USE_GCC_INLINE_ASM -%{endif} - -%{if cxx_ct_value_barrier_type} -#define BOTAN_CT_VALUE_BARRIER_USE_%{cxx_ct_value_barrier_type|upper} -%{endif} - -%{for sanitizer_types} -#define BOTAN_HAS_SANITIZER_%{i|upper} -%{endfor} - -#define BOTAN_TARGET_ARCH "%{arch}" -#define BOTAN_TARGET_ARCH_IS_%{arch|upper} -%{if endian} -#define BOTAN_TARGET_CPU_IS_%{endian|upper}_ENDIAN -%{endif} -%{if cpu_family} -#define BOTAN_TARGET_CPU_IS_%{cpu_family|upper}_FAMILY -%{endif} -%{if cpu_is_64bit} -#define BOTAN_TARGET_CPU_HAS_NATIVE_64BIT -%{endif} - -%{for cpu_features} -#define BOTAN_TARGET_SUPPORTS_%{i|upper} -%{endfor} - -%{if with_valgrind} -#define BOTAN_HAS_VALGRIND -%{endif} - %{if with_debug_asserts} +/** + * Has to be public due to use in assert.h + * TODO(Botan4) move this to target_info.h once assert.h is internal + */ #define BOTAN_ENABLE_DEBUG_ASSERTS %{endif} -%{if terminate_on_asserts} -#define BOTAN_TERMINATE_ON_ASSERTS -%{endif} - -%{if optimize_for_size} -#define BOTAN_OPTIMIZE_FOR_SIZE -%{endif} - /** * @} */ @@ -179,110 +139,21 @@ #define BOTAN_HAS_%{i} %{endfor} -/** - * @} - */ - -/** - * @addtogroup buildinfo_configuration - * @{ - */ - -/** Local/misc configuration options (if any) follow */ -%{local_config} - /* -* Things you can edit (but probably shouldn't) -*/ - -/** How much to allocate for a buffer of no particular size */ -#define BOTAN_DEFAULT_BUFFER_SIZE 4096 - -#if defined(BOTAN_HAS_VALGRIND) || defined(BOTAN_ENABLE_DEBUG_ASSERTS) - /** - * @brief Prohibits access to unused memory pages in Botan's memory pool - * - * If BOTAN_MEM_POOL_USE_MMU_PROTECTIONS is defined, the Memory_Pool - * class used for mlock'ed memory will use OS calls to set page - * permissions so as to prohibit access to pages on the free list, then - * enable read/write access when the page is set to be used. This will - * turn (some) use after free bugs into a crash. - * - * The additional syscalls have a substantial performance impact, which - * is why this option is not enabled by default. It is used when built for - * running in valgrind or debug assertions are enabled. - */ - #define BOTAN_MEM_POOL_USE_MMU_PROTECTIONS -#endif - -#if defined(BOTAN_HAS_VALGRIND) - /** - * If `BOTAN_CT_POISON_ENABLED` is defined, then the `CT::poison` and - * `CT::unpoison` functions have an effect and do not just compile to no-ops. - * - * At the moment that is only the case when building with valgrind support. We - * could potentially add support for other tools in the future. - */ - #define BOTAN_CT_POISON_ENABLED -#endif - -/** -* If enabled uses memset via volatile function pointer to zero memory, -* otherwise does a byte at a time write via a volatile pointer. -*/ -#define BOTAN_USE_VOLATILE_MEMSET_FOR_ZERO 1 - -/** -* Normally blinding is performed by choosing a random starting point (plus -* its inverse, of a form appropriate to the algorithm being blinded), and -* then choosing new blinding operands by successive squaring of both -* values. This is much faster than computing a new starting point but -* introduces some possible corelation +* Internal module feature definitions * -* To avoid possible leakage problems in long-running processes, the blinder -* periodically reinitializes the sequence. This value specifies how often -* a new sequence should be started. -*/ -#define BOTAN_BLINDING_REINIT_INTERVAL 64 - -/** -* Userspace RNGs like HMAC_DRBG will reseed after a specified number -* of outputs are generated. Set to zero to disable automatic reseeding. -*/ -#define BOTAN_RNG_DEFAULT_RESEED_INTERVAL 1024 - -/** Number of entropy bits polled for reseeding userspace RNGs like HMAC_DRBG */ -#define BOTAN_RNG_RESEED_POLL_BITS 256 - -#define BOTAN_RNG_RESEED_DEFAULT_TIMEOUT std::chrono::milliseconds(50) - -/** -* Specifies (in order) the list of entropy sources that will be used -* to seed an in-memory RNG. +* These macros have been in the past visible in build.h as feature macros +* but in the future these will be only visible in an internal header. +* Applications should not rely on or check for these macros. */ -#define BOTAN_ENTROPY_DEFAULT_SOURCES \ - { "rdseed", "hwrng", "getentropy", "system_rng", "system_stats" } - -/** Multiplier on a block cipher's native parallelism */ -#define BOTAN_BLOCK_CIPHER_PAR_MULT 4 +%{for module_internal_defines} +#define BOTAN_HAS_%{i} +%{endfor} /** * @} */ -/* Check for a common build problem */ - -#if defined(BOTAN_TARGET_ARCH_IS_X86_64) && ((defined(_MSC_VER) && !defined(_WIN64)) || \ - (defined(__clang__) && !defined(__x86_64__)) || \ - (defined(__GNUG__) && !defined(__x86_64__))) - #error "Trying to compile Botan configured as x86_64 with non-x86_64 compiler." -#endif - -#if defined(BOTAN_TARGET_ARCH_IS_X86_32) && ((defined(_MSC_VER) && defined(_WIN64)) || \ - (defined(__clang__) && !defined(__i386__)) || \ - (defined(__GNUG__) && !defined(__i386__))) - - #error "Trying to compile Botan configured as x86_32 with non-x86_32 compiler." -#endif +/* NOLINTEND(*-macro-usage,*-macro-to-enum) */ #endif diff -Nru botan3-3.7.1+dfsg/src/build-data/cc/clang.txt botan3-3.12.0+dfsg/src/build-data/cc/clang.txt --- botan3-3.7.1+dfsg/src/build-data/cc/clang.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/cc/clang.txt 2026-05-07 01:38:28.000000000 +0000 @@ -69,6 +69,8 @@ avx2 -> "-mavx2" avx512 -> "-mavx512f -mavx512bw -mavx512dq -mavx512vbmi -mavx512vbmi2 -mavx512bitalg -mavx512vl -mavx512ifma" +avx512_clmul -> "-mavx512f -mavx512bw -mavx512dq -mavx512vbmi -mavx512vbmi2 -mavx512bitalg -mavx512vl -mavx512ifma -mvpclmulqdq" + vaes -> "-mvaes -mavx2" sha512 -> "-msha512 -mavx2" sm3 -> "-msm3" @@ -88,6 +90,11 @@ arm64:armv8crypto -> "-march=armv8+crypto" arm64:armv8sha512 -> "-march=armv8.2-a+sha3" +arm64:armv8sm3 -> "-march=armv8.2-a+sm4" +arm64:armv8sm4 -> "-march=armv8.2-a+sm4" + +loongarch64:lsx -> "-mlsx" +loongarch64:lasx -> "-mlasx" arm32:neon -> "-mfpu=neon" arm64:neon -> "" diff -Nru botan3-3.7.1+dfsg/src/build-data/cc/clangcl.txt botan3-3.12.0+dfsg/src/build-data/cc/clangcl.txt --- botan3-3.7.1+dfsg/src/build-data/cc/clangcl.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/cc/clangcl.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,106 @@ +macro_name CLANGCL + +minimum_supported_version 14.0 + +binary_name clang-cl +linker_name lld-link + +output_to_object "/Fo" +output_to_exe "/OUT:" + +add_include_dir_option "/I" +add_system_include_dir_option "/external:W0 /external:I" +add_lib_dir_option "/LIBPATH:" +add_compile_definition_option "/D" +add_lib_option "%s.lib" + +compile_flags "/nologo /c" + +supports_gcc_inline_asm yes + +optimization_flags "/O2 /Oi" +size_optimization_flags "/O1 /Os" + +# for debug info in the object file (required if using sccache): +#debug_info_flags "/Z7" + +# for using a PDB file: +debug_info_flags "/Zi /FS" + +preproc_flags "/nologo /EP" + +# clang-cl has /Zc:preprocessor behavior by default, and does not accept the flag +lang_flags "/Zc:inline /std:c++20 /EHs /GR" + +# 4251: STL types used in DLL interface +# 4275: ??? +# 5072: ASan without debug info +warning_flags "/W4 /wd4251 /wd4275 /wd5072" + +werror_flags "/WX" + +visibility_build_flags "/DBOTAN_DLL=__declspec(dllexport)" +visibility_attribute "__declspec(dllimport)" + +# Include dependency tracking for Ninja +# See: https://ninja-build.org/manual.html#ref_headers +ninja_header_deps_style 'msvc' +header_deps_flag '/showIncludes' + +ar_command lib +ar_options "/nologo" +ar_output_to "/OUT:" + + +default -> address + +iterator -> "/D_ITERATOR_DEBUG_LEVEL=1" +address -> "/fsanitize=address" + + + +sse2 -> "-msse2" +ssse3 -> "-mssse3" +sse41 -> "-msse4.1" +avx2 -> "-mavx2" +avx512 -> "-mavx512f -mavx512bw -mavx512dq -mavx512vbmi -mavx512vbmi2 -mavx512bitalg -mavx512vl -mavx512ifma" + +bmi2 -> "-mbmi -mbmi2" +aesni -> "-maes -mpclmul" +rdrand -> "-mrdrnd" +rdseed -> "-mrdseed" +sha -> "-msha" +altivec -> "-maltivec" + +arm64:armv8crypto -> "-march=armv8+crypto" +arm64:armv8sha512 -> "-march=armv8.2-a+sha3" + +arm32:neon -> "-mfpu=neon" +arm64:neon -> "" + + + +debug -> "/Fd%{build_dir}/%{libname}.pdb" + + + +default -> "{linker} /DLL" +default-debug -> "{linker} /DLL /DEBUG" + + + +default -> "{linker}" +default-debug -> "{linker} /DEBUG" + + + +all -> "/bigobj" + +# These can be overridden with --msvc-runtime option +rt -> "/MD" +rt-debug -> "/MDd" + + + +default -> asm + diff -Nru botan3-3.7.1+dfsg/src/build-data/cc/emcc.txt botan3-3.12.0+dfsg/src/build-data/cc/emcc.txt --- botan3-3.7.1+dfsg/src/build-data/cc/emcc.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/cc/emcc.txt 2026-05-07 01:38:28.000000000 +0000 @@ -2,8 +2,8 @@ binary_name em++ -lang_flags "-s DISABLE_EXCEPTION_CATCHING=0 -std=c++20 -D_REENTRANT" -lang_binary_linker_flags "-s ALLOW_MEMORY_GROWTH=1 -s WASM=1 -s NO_DISABLE_EXCEPTION_CATCHING" +lang_flags "-fwasm-exceptions -std=c++20 -D_REENTRANT" +lang_binary_linker_flags "-s ALLOW_MEMORY_GROWTH=1 -s WASM=1 -fwasm-exceptions" warning_flags "-Wall -Wextra -Wpedantic -Wshadow -Wstrict-aliasing -Wstrict-overflow=5 -Wcast-align -Wmissing-declarations -Wpointer-arith -Wcast-qual -Wshorten-64-to-32" @@ -22,6 +22,10 @@ default -> "false" + +simd128 -> "-msimd128" + + default -> "{cxx}" diff -Nru botan3-3.7.1+dfsg/src/build-data/cc/gcc.txt botan3-3.12.0+dfsg/src/build-data/cc/gcc.txt --- botan3-3.7.1+dfsg/src/build-data/cc/gcc.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/cc/gcc.txt 2026-05-07 01:38:28.000000000 +0000 @@ -36,6 +36,7 @@ iterator -> "-D_GLIBCXX_DEBUG" address -> "-fsanitize=address" undefined -> "-fsanitize=undefined -fno-sanitize-recover=undefined" +thread -> "-fsanitize=thread" visibility_build_flags "-fvisibility=hidden" @@ -70,6 +71,8 @@ avx2 -> "-mavx2" avx512 -> "-mavx512f -mavx512bw -mavx512dq -mavx512vbmi -mavx512vbmi2 -mavx512bitalg -mavx512vl -mavx512ifma" +avx512_clmul -> "-mavx512f -mavx512bw -mavx512dq -mavx512vbmi -mavx512vbmi2 -mavx512bitalg -mavx512vl -mavx512ifma -mvpclmulqdq" + vaes -> "-mvaes -mavx2" sha512 -> "-msha512 -mavx2" sm3 -> "-msm3" @@ -93,6 +96,9 @@ arm64:armv8sha512 -> "-march=armv8.2-a+sha3" arm64:armv8sha3 -> "-march=armv8.2-a+sha3" +loongarch64:lsx -> "-mlsx" +loongarch64:lasx -> "-mlasx" + # For Aarch32 -mfpu=neon is required # For Aarch64 NEON is enabled by default arm32:neon -> "-mfpu=neon" diff -Nru botan3-3.7.1+dfsg/src/build-data/cc/msvc.txt botan3-3.12.0+dfsg/src/build-data/cc/msvc.txt --- botan3-3.7.1+dfsg/src/build-data/cc/msvc.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/cc/msvc.txt 2026-05-07 01:38:28.000000000 +0000 @@ -20,14 +20,14 @@ size_optimization_flags "/O1 /Os" # for debug info in the object file (required if using sccache): -#debug_info_flags "/Z7" +debug_info_flags "/Z7" # for using a PDB file: -debug_info_flags "/Zi /FS" +#debug_info_flags "/Zi /FS" preproc_flags "/nologo /EP /Zc:preprocessor" -lang_flags "/Zc:preprocessor /std:c++20 /EHs /GR" +lang_flags "/Zc:preprocessor /Zc:inline /std:c++20 /EHs /GR" # 4251: STL types used in DLL interface # 4275: ??? @@ -61,11 +61,14 @@ sse41 -> "" x86_64:avx2 -> "/arch:AVX" x86_64:avx512 -> "/arch:AVX512" +x86_64:avx512_clmul -> "/arch:AVX512" aesni -> "" clmul -> "" rdrand -> "" rdseed -> "" sha -> "" +bmi2 -> "" +gfni -> "" diff -Nru botan3-3.7.1+dfsg/src/build-data/cc/sunstudio.txt botan3-3.12.0+dfsg/src/build-data/cc/sunstudio.txt --- botan3-3.7.1+dfsg/src/build-data/cc/sunstudio.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/cc/sunstudio.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,43 +0,0 @@ -macro_name SUN_STUDIO - -binary_name CC - -optimization_flags "-xO2" - -shared_flags "-KPIC" -warning_flags "+w -erroff=truncwarn,wnoretvalue,wlessrestrictedthrow" -lang_flags "-std=c++20 +p -features=extensions" - -ar_command CC -ar_options "-xar -o" - -supports_gcc_inline_asm yes - - -default -> "{cxx} -G -h{soname_abi}" - - - -# Needed on some Linux distros -linux -> "-library=stlport4" - -sparc64 -> "-m64 -xarch=sparc" -x86_64 -> "-m64" - - - -# Botan needs C++11, and that requires Sun Studio 12.4 or above. -# Sun Studio 12.4 supports upto -xarch=avx2, but the processor must support it -# AESNI requires -xarch=aes, and RDRAND requires -xarch=avx_i. -# https://docs.oracle.com/cd/E37069_01/html/E37074/bjapp.html#OSSCGbkazd -sse2 -> "-xarch=sse2" -ssse3 -> "-xarch=ssse3" -sse41 -> "-xarch=sse4.1" -aesni -> "-xarch=aes" -rdrand -> "-xarch=avx_i" -avx2 -> "-xarch=avx2" - - - -default -> none - diff -Nru botan3-3.7.1+dfsg/src/build-data/cc/xcode.txt botan3-3.12.0+dfsg/src/build-data/cc/xcode.txt --- botan3-3.7.1+dfsg/src/build-data/cc/xcode.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/cc/xcode.txt 2026-05-07 01:38:28.000000000 +0000 @@ -60,6 +60,8 @@ avx2 -> "-mavx2" avx512 -> "-mavx512f -mavx512bw -mavx512dq -mavx512vbmi -mavx512vbmi2 -mavx512bitalg -mavx512vl -mavx512ifma" +avx512_clmul -> "-mavx512f -mavx512bw -mavx512dq -mavx512vbmi -mavx512vbmi2 -mavx512bitalg -mavx512vl -mavx512ifma -mvpclmulqdq" + bmi2 -> "-mbmi -mbmi2" aesni -> "-maes -mpclmul" rdrand -> "-mrdrnd" diff -Nru botan3-3.7.1+dfsg/src/build-data/compile_commands.json.in botan3-3.12.0+dfsg/src/build-data/compile_commands.json.in --- botan3-3.7.1+dfsg/src/build-data/compile_commands.json.in 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/compile_commands.json.in 2026-05-07 01:38:28.000000000 +0000 @@ -1,28 +1,28 @@ [ %{for lib_build_info} { "directory": "%{abs_root_dir}", - "command": "%{cxx} %{lib_flags} %{cc_sysroot} %{cxx_abi_flags} %{cc_lang_flags} %{os_feature_macros} %{cc_compile_flags} -DBOTAN_IS_BEING_BUILT %{cc_warning_flags} %{isa_flags} %{public_include_flags} %{internal_include_flags} %{external_include_flags} %{dash_c} %{src} %{dash_o}%{obj}", + "command": "%{cxx} %{lib_flags} %{cc_sysroot} %{cxx_abi_flags} %{cc_lang_flags} %{os_feature_macros} %{cc_compile_flags} -DBOTAN_IS_BEING_BUILT %{cc_warning_flags} %{public_include_flags} %{internal_include_flags} %{external_include_flags} %{dash_c} %{src} %{dash_o}%{obj}", "file": "%{src}" }, %{endfor} %{for test_build_info} { "directory": "%{abs_root_dir}", - "command": "%{cxx} %{cc_sysroot} %{cxx_abi_flags} %{cc_lang_flags} %{os_feature_macros} %{cc_compile_flags} -DBOTAN_IS_BEING_BUILT %{cc_warning_flags} %{isa_flags} %{public_include_flags} %{internal_include_flags} %{external_include_flags} %{dash_c} %{src} %{dash_o}%{obj}", + "command": "%{cxx} %{cc_sysroot} %{cxx_abi_flags} %{cc_lang_flags} %{os_feature_macros} %{cc_compile_flags} -DBOTAN_IS_BEING_BUILT %{cc_warning_flags} %{public_include_flags} %{internal_include_flags} %{external_include_flags} %{dash_c} %{src} %{dash_o}%{obj}", "file": "%{src}" }, %{endfor} %{for examples_build_info} { "directory": "%{abs_root_dir}", - "command": "%{cxx} %{cc_sysroot} %{cxx_abi_flags} %{cc_lang_flags} %{os_feature_macros} %{cc_compile_flags} %{cc_warning_flags} %{isa_flags} %{public_include_flags} %{external_include_flags} %{dash_c} %{src} %{dash_o}%{obj}", + "command": "%{cxx} %{cc_sysroot} %{cxx_abi_flags} %{cc_lang_flags} %{os_feature_macros} %{cc_compile_flags} %{cc_warning_flags} %{public_include_flags} %{external_include_flags} %{dash_c} %{src} %{dash_o}%{obj}", "file": "%{src}" }, %{endfor} %{for fuzzer_build_info} { "directory": "%{abs_root_dir}", - "command": "%{cxx} %{cc_sysroot} %{cxx_abi_flags} %{cc_lang_flags} %{os_feature_macros} %{cc_compile_flags} -DBOTAN_IS_BEING_BUILT %{cc_warning_flags} %{isa_flags} %{public_include_flags} %{internal_include_flags} %{external_include_flags} %{dash_c} %{src} %{dash_o}%{obj}", + "command": "%{cxx} %{cc_sysroot} %{cxx_abi_flags} %{cc_lang_flags} %{os_feature_macros} %{cc_compile_flags} -DBOTAN_IS_BEING_BUILT %{cc_warning_flags} %{public_include_flags} %{internal_include_flags} %{external_include_flags} %{dash_c} %{src} %{dash_o}%{obj}", "file": "%{src}" }, %{endfor} @@ -37,7 +37,7 @@ %{for cli_build_info} { "directory": "%{abs_root_dir}", - "command": "%{cxx} %{cc_sysroot} %{cxx_abi_flags} %{cc_lang_flags} %{os_feature_macros} %{cc_compile_flags} -DBOTAN_IS_BEING_BUILT %{cc_warning_flags} %{isa_flags} %{public_include_flags} %{internal_include_flags} %{external_include_flags} %{dash_c} %{src} %{dash_o}%{obj}", + "command": "%{cxx} %{cc_sysroot} %{cxx_abi_flags} %{cc_lang_flags} %{os_feature_macros} %{cc_compile_flags} -DBOTAN_IS_BEING_BUILT %{cc_warning_flags} %{public_include_flags} %{internal_include_flags} %{external_include_flags} %{dash_c} %{src} %{dash_o}%{obj}", "file": "%{src}" }%{omitlast ,} %{endfor} diff -Nru botan3-3.7.1+dfsg/src/build-data/detect_version.cpp botan3-3.12.0+dfsg/src/build-data/detect_version.cpp --- botan3-3.7.1+dfsg/src/build-data/detect_version.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/detect_version.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -3,7 +3,7 @@ * configure.py to determine the compilers version number. */ -#if defined(_MSC_VER) +#if defined(_MSC_VER) and !defined(__clang__) /* _MSC_VER Defined as an integer literal that encodes the major and diff -Nru botan3-3.7.1+dfsg/src/build-data/ec_groups.txt botan3-3.12.0+dfsg/src/build-data/ec_groups.txt --- botan3-3.7.1+dfsg/src/build-data/ec_groups.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/ec_groups.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,5 +1,6 @@ Name = secp256r1 OID = 1.2.840.10045.3.1.7 +Impl = pcurve generic legacy P = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF A = -3 B = 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B @@ -9,6 +10,7 @@ Name = secp384r1 OID = 1.3.132.0.34 +Impl = pcurve generic legacy P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF A = -3 B = 0xB3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF @@ -18,6 +20,7 @@ Name = secp521r1 OID = 1.3.132.0.35 +Impl = pcurve generic legacy P = 0x1FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF A = -3 B = 0x51953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00 @@ -27,6 +30,7 @@ Name = brainpool160r1 OID = 1.3.36.3.3.2.8.1.1.1 +Impl = legacy P = 0xE95E4A5F737059DC60DFC7AD95B3D8139515620F A = 0x340E7BE2A280EB74E2BE61BADA745D97E8F7C300 B = 0x1E589A8595423412134FAA2DBDEC95C8D8675E58 @@ -36,6 +40,7 @@ Name = brainpool192r1 OID = 1.3.36.3.3.2.8.1.1.3 +Impl = generic legacy P = 0xC302F41D932A36CDA7A3463093D18DB78FCE476DE1A86297 A = 0x6A91174076B1E0E19C39C031FE8685C1CAE040E5C69A28EF B = 0x469A28EF7C28CCA3DC721D044F4496BCCA7EF4146FBF25C9 @@ -45,6 +50,7 @@ Name = brainpool224r1 OID = 1.3.36.3.3.2.8.1.1.5 +Impl = generic legacy P = 0xD7C134AA264366862A18302575D1D787B09F075797DA89F57EC8C0FF A = 0x68A5E62CA9CE6C1C299803A6C1530B514E182AD8B0042A59CAD29F43 B = 0x2580F63CCFE44138870713B1A92369E33E2135D266DBB372386C400B @@ -54,6 +60,7 @@ Name = brainpool256r1 OID = 1.3.36.3.3.2.8.1.1.7 +Impl = pcurve generic legacy P = 0xA9FB57DBA1EEA9BC3E660A909D838D726E3BF623D52620282013481D1F6E5377 A = 0x7D5A0975FC2C3057EEF67530417AFFE7FB8055C126DC5C6CE94A4B44F330B5D9 B = 0x26DC5C6CE94A4B44F330B5D9BBD77CBF958416295CF7E1CE6BCCDC18FF8C07B6 @@ -63,6 +70,7 @@ Name = brainpool320r1 OID = 1.3.36.3.3.2.8.1.1.9 +Impl = generic legacy P = 0xD35E472036BC4FB7E13C785ED201E065F98FCFA6F6F40DEF4F92B9EC7893EC28FCD412B1F1B32E27 A = 0x3EE30B568FBAB0F883CCEBD46D3F3BB8A2A73513F5EB79DA66190EB085FFA9F492F375A97D860EB4 B = 0x520883949DFDBC42D3AD198640688A6FE13F41349554B49ACC31DCCD884539816F5EB4AC8FB1F1A6 @@ -72,6 +80,7 @@ Name = brainpool384r1 OID = 1.3.36.3.3.2.8.1.1.11 +Impl = pcurve generic legacy P = 0x8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B412B1DA197FB71123ACD3A729901D1A71874700133107EC53 A = 0x7BC382C63D8C150C3C72080ACE05AFA0C2BEA28E4FB22787139165EFBA91F90F8AA5814A503AD4EB04A8C7DD22CE2826 B = 0x4A8C7DD22CE28268B39B55416F0447C2FB77DE107DCD2A62E880EA53EEB62D57CB4390295DBC9943AB78696FA504C11 @@ -81,6 +90,7 @@ Name = brainpool512r1 OID = 1.3.36.3.3.2.8.1.1.13 +Impl = pcurve generic legacy P = 0xAADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA703308717D4D9B009BC66842AECDA12AE6A380E62881FF2F2D82C68528AA6056583A48F3 A = 0x7830A3318B603B89E2327145AC234CC594CBDD8D3DF91610A83441CAEA9863BC2DED5D5AA8253AA10A2EF1C98B9AC8B57F1117A72BF2C7B9E7C1AC4D77FC94CA B = 0x3DF91610A83441CAEA9863BC2DED5D5AA8253AA10A2EF1C98B9AC8B57F1117A72BF2C7B9E7C1AC4D77FC94CADC083E67984050B75EBAE5DD2809BD638016F723 @@ -90,6 +100,7 @@ Name = frp256v1 OID = 1.2.250.1.223.101.256.1 +Impl = pcurve generic legacy P = 0xF1FD178C0B3AD58F10126DE8CE42435B3961ADBCABC8CA6DE8FCF353D86E9C03 A = -3 B = 0xEE353FCA5428A9300D4ABA754A44C00FDFEC0C9AE4B1A1803075ED967B7BB73F @@ -98,6 +109,7 @@ N = 0xF1FD178C0B3AD58F10126DE8CE42435B53DC67E140D2BF941FFDD459C6D655E1 Name = gost_256A +Impl = generic legacy OID = 1.2.643.7.1.2.1.1.1 1.2.643.2.2.35.1 1.2.643.2.2.36.0 P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD97 A = -3 @@ -107,6 +119,7 @@ N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6C611070995AD10045841B09B761B893 Name = gost_512A +Impl = generic legacy OID = 1.2.643.7.1.2.1.2.1 P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDC7 A = -3 @@ -116,6 +129,7 @@ N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF27E69532F48D89116FF22B8D4E0560609B4B38ABFAD2B85DCACDB1411F10B275 Name = secp160k1 +Impl = legacy OID = 1.3.132.0.9 P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFAC73 A = 0x0 @@ -125,6 +139,7 @@ N = 0x100000000000000000001B8FA16DFAB9ACA16B6B3 Name = secp160r1 +Impl = legacy OID = 1.3.132.0.8 P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7FFFFFFF A = -3 @@ -134,6 +149,7 @@ N = 0x100000000000000000001F4C8F927AED3CA752257 Name = secp160r2 +Impl = legacy OID = 1.3.132.0.30 P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFAC73 A = -3 @@ -143,6 +159,7 @@ N = 0x100000000000000000000351EE786A818F3A1A16B Name = secp192k1 +Impl = generic legacy OID = 1.3.132.0.31 P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFEE37 A = 0x0 @@ -152,6 +169,7 @@ N = 0xFFFFFFFFFFFFFFFFFFFFFFFE26F2FC170F69466A74DEFD8D Name = secp192r1 +Impl = pcurve generic legacy OID = 1.2.840.10045.3.1.1 P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFF A = -3 @@ -161,6 +179,7 @@ N = 0xFFFFFFFFFFFFFFFFFFFFFFFF99DEF836146BC9B1B4D22831 Name = secp224k1 +Impl = legacy OID = 1.3.132.0.32 P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFE56D A = 0x0 @@ -170,6 +189,7 @@ N = 0x10000000000000000000000000001DCE8D2EC6184CAF0A971769FB1F7 Name = secp224r1 +Impl = pcurve legacy OID = 1.3.132.0.33 P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000001 A = -3 @@ -179,6 +199,7 @@ N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF16A2E0B8F03E13DD29455C5C2A3D Name = secp256k1 +Impl = pcurve generic legacy OID = 1.3.132.0.10 P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F A = 0x0 @@ -188,6 +209,7 @@ N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 Name = sm2p256v1 +Impl = pcurve generic legacy OID = 1.2.156.10197.1.301 P = 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF A = -3 @@ -197,6 +219,7 @@ N = 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123 Name = x962_p192v2 +Impl = generic legacy OID = 1.2.840.10045.3.1.2 P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFF A = -3 @@ -206,6 +229,7 @@ N = 0xFFFFFFFFFFFFFFFFFFFFFFFE5FB1A724DC80418648D8DD31 Name = x962_p192v3 +Impl = generic legacy OID = 1.2.840.10045.3.1.3 P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFF A = -3 @@ -215,6 +239,7 @@ N = 0xFFFFFFFFFFFFFFFFFFFFFFFF7A62D031C83F4294F640EC13 Name = x962_p239v1 +Impl = generic legacy OID = 1.2.840.10045.3.1.4 P = 0x7FFFFFFFFFFFFFFFFFFFFFFF7FFFFFFFFFFF8000000000007FFFFFFFFFFF A = -3 @@ -224,6 +249,7 @@ N = 0x7FFFFFFFFFFFFFFFFFFFFFFF7FFFFF9E5E9A9F5D9071FBD1522688909D0B Name = x962_p239v2 +Impl = generic legacy OID = 1.2.840.10045.3.1.5 P = 0x7FFFFFFFFFFFFFFFFFFFFFFF7FFFFFFFFFFF8000000000007FFFFFFFFFFF A = -3 @@ -233,6 +259,7 @@ N = 0x7FFFFFFFFFFFFFFFFFFFFFFF800000CFA7E8594377D414C03821BC582063 Name = x962_p239v3 +Impl = generic legacy OID = 1.2.840.10045.3.1.6 P = 0x7FFFFFFFFFFFFFFFFFFFFFFF7FFFFFFFFFFF8000000000007FFFFFFFFFFF A = -3 @@ -240,3 +267,13 @@ X = 0x6768AE8E18BB92CFCF005C949AA2C6D94853D0E660BBF854B1C9505FE95A Y = 0x1607E6898F390C06BC1D552BAD226F3B6FCFE48B6E818499AF18E3ED6CF3 N = 0x7FFFFFFFFFFFFFFFFFFFFFFF7FFFFF975DEB41B3A6057C3C432146526551 + +Name = numsp512d1 +Impl = pcurve generic legacy +OID = 1.3.6.1.4.1.25258.4.3 +P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDC7 +A = -3 +B = 0x01D99B +X = 0x02 +Y = 0x1C282EB23327F9711952C250EA61AD53FCC13031CF6DD336E0B9328433AFBDD8CC5A1C1F0C716FDC724DDE537C2B0ADB00BB3D08DC83755B205CC30D7F83CF28 +N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5B3CA4FB94E7831B4FC258ED97D0BDC63B568B36607CD243CE153F390433555D diff -Nru botan3-3.7.1+dfsg/src/build-data/ec_named.cpp.in botan3-3.12.0+dfsg/src/build-data/ec_named.cpp.in --- botan3-3.7.1+dfsg/src/build-data/ec_named.cpp.in 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/ec_named.cpp.in 1970-01-01 00:00:00.000000000 +0000 @@ -1,39 +0,0 @@ -/* -* ECC Group Info -* This file was automatically generated by %s on %s -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include - -namespace Botan { - -// clang-format off - -//static -std::shared_ptr EC_Group::EC_group_info(const OID& oid) { -%s - return std::shared_ptr(); -} - -//static -OID EC_Group::EC_group_identity_from_order(const BigInt& order) - { - const uint32_t low_bits = static_cast(order.word_at(0)); - -%s - return OID(); -} - -//static -const std::set& EC_Group::known_named_groups() { - static const std::set named_groups = { -%s - }; - return named_groups; -} - -} // namespace Botan - -// clang-format on diff -Nru botan3-3.7.1+dfsg/src/build-data/makefile.in botan3-3.12.0+dfsg/src/build-data/makefile.in --- botan3-3.7.1+dfsg/src/build-data/makefile.in 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/makefile.in 2026-05-07 01:38:28.000000000 +0000 @@ -151,22 +151,22 @@ %{for lib_build_info} %{obj}: %{src} - $(CXX) $(LIB_FLAGS) $(BUILD_FLAGS) -DBOTAN_IS_BEING_BUILT %{isa_flags} %{public_include_flags} %{internal_include_flags} %{external_include_flags} %{dash_c} %{src} %{dash_o}$@ + $(CXX) $(LIB_FLAGS) $(BUILD_FLAGS) -DBOTAN_IS_BEING_BUILT %{public_include_flags} %{internal_include_flags} %{external_include_flags} %{dash_c} %{src} %{dash_o}$@ %{endfor} %{for cli_build_info} %{obj}: %{src} - $(CXX) $(BUILD_FLAGS) -DBOTAN_IS_BEING_BUILT %{isa_flags} %{public_include_flags} %{internal_include_flags} %{external_include_flags} %{dash_c} %{src} %{dash_o}$@ + $(CXX) $(BUILD_FLAGS) -DBOTAN_IS_BEING_BUILT %{public_include_flags} %{internal_include_flags} %{external_include_flags} %{dash_c} %{src} %{dash_o}$@ %{endfor} %{for test_build_info} %{obj}: %{src} - $(CXX) $(BUILD_FLAGS) -DBOTAN_IS_BEING_BUILT %{isa_flags} %{public_include_flags} %{internal_include_flags} %{external_include_flags} %{dash_c} %{src} %{dash_o}$@ + $(CXX) $(BUILD_FLAGS) -DBOTAN_IS_BEING_BUILT %{public_include_flags} %{internal_include_flags} %{external_include_flags} %{dash_c} %{src} %{dash_o}$@ %{endfor} %{for fuzzer_build_info} %{obj}: %{src} - $(CXX) $(BUILD_FLAGS) -DBOTAN_IS_BEING_BUILT %{isa_flags} %{public_include_flags} %{internal_include_flags} %{external_include_flags} %{dash_c} %{src} %{dash_o}$@ + $(CXX) $(BUILD_FLAGS) -DBOTAN_IS_BEING_BUILT %{public_include_flags} %{internal_include_flags} %{external_include_flags} %{dash_c} %{src} %{dash_o}$@ %{exe}: %{obj} $(LIBRARIES) $(EXE_LINK_CMD) $(ABI_FLAGS) %{obj} $(BUILD_DIR_LINK_PATH) $(LANG_EXE_FLAGS) $(LDFLAGS) $(EXE_LINKS_TO) %{fuzzer_lib} %{output_to_exe}$@ @@ -174,7 +174,7 @@ %{for examples_build_info} %{obj}: %{src} - $(CXX) $(BUILD_FLAGS) %{isa_flags} %{public_include_flags} %{external_include_flags} %{dash_c} %{src} %{dash_o}$@ + $(CXX) $(BUILD_FLAGS) %{public_include_flags} %{external_include_flags} %{dash_c} %{src} %{dash_o}$@ %{exe}: %{obj} $(LIBRARIES) $(EXE_LINK_CMD) $(ABI_FLAGS) %{obj} $(BUILD_DIR_LINK_PATH) $(LANG_EXE_FLAGS) $(LDFLAGS) $(EXE_LINKS_TO) %{fuzzer_lib} %{output_to_exe}$@ diff -Nru botan3-3.7.1+dfsg/src/build-data/ninja.in botan3-3.12.0+dfsg/src/build-data/ninja.in --- botan3-3.7.1+dfsg/src/build-data/ninja.in 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/ninja.in 2026-05-07 01:38:28.000000000 +0000 @@ -28,7 +28,7 @@ %{if ninja_header_deps_style} deps = %{ninja_header_deps_style} %{endif} - command = %{cxx} %{lib_flags} ${ABI_FLAGS} ${LANG_FLAGS} ${CXXFLAGS} -DBOTAN_IS_BEING_BUILT ${WARN_FLAGS} ${isa_flags} %{public_include_flags} %{internal_include_flags} %{external_include_flags} %{header_deps_flag} %{header_deps_out|concat: $out.d} %{dash_c} $in %{dash_o}$out + command = %{cxx} %{lib_flags} ${ABI_FLAGS} ${LANG_FLAGS} ${CXXFLAGS} -DBOTAN_IS_BEING_BUILT ${WARN_FLAGS} %{public_include_flags} %{internal_include_flags} %{external_include_flags} %{header_deps_flag} %{header_deps_out|concat: $out.d} %{dash_c} $in %{dash_o}$out rule compile_exe %{if header_deps_out} @@ -37,7 +37,7 @@ %{if ninja_header_deps_style} deps = %{ninja_header_deps_style} %{endif} - command = %{cxx} ${ABI_FLAGS} ${LANG_FLAGS} ${CXXFLAGS} -DBOTAN_IS_BEING_BUILT ${WARN_FLAGS} ${isa_flags} %{public_include_flags} %{internal_include_flags} %{external_include_flags} %{header_deps_flag} %{header_deps_out|concat: $out.d} %{dash_c} $in %{dash_o}$out + command = %{cxx} ${ABI_FLAGS} ${LANG_FLAGS} ${CXXFLAGS} -DBOTAN_IS_BEING_BUILT ${WARN_FLAGS} %{public_include_flags} %{internal_include_flags} %{external_include_flags} %{header_deps_flag} %{header_deps_out|concat: $out.d} %{dash_c} $in %{dash_o}$out rule compile_example_exe %{if header_deps_out} @@ -46,7 +46,7 @@ %{if ninja_header_deps_style} deps = %{ninja_header_deps_style} %{endif} - command = %{cxx} ${ABI_FLAGS} ${LANG_FLAGS} ${CXXFLAGS} ${WARN_FLAGS} ${isa_flags} %{public_include_flags} %{external_include_flags} %{header_deps_flag} %{header_deps_out|concat: $out.d} %{dash_c} $in %{dash_o}$out + command = %{cxx} ${ABI_FLAGS} ${LANG_FLAGS} ${CXXFLAGS} ${WARN_FLAGS} %{public_include_flags} %{external_include_flags} %{header_deps_flag} %{header_deps_out|concat: $out.d} %{dash_c} $in %{dash_o}$out # The primary target build all: phony %{all_targets} @@ -54,16 +54,24 @@ # Library targets -%{if build_static_lib} +%{if build_static_lib_using_response_file} rule link_static rspfile = %{response_file_dir}/static.txt rspfile_content = $in command = %{ar_command} %{ar_options} %{ar_output_to}$out @%{response_file_dir}/static.txt -build %{out_dir}/%{static_lib_name}: link_static %{join lib_objs} +%{endif} +%{if build_static_lib_using_cmdline_args} + +rule link_static + command = %{ar_command} %{ar_options} %{ar_output_to}$out $in %{endif} +%{if build_static_lib} +build %{out_dir}/%{static_lib_name}: link_static %{join lib_objs} +%{endif} + %{if build_shared_lib} rule link_shared @@ -204,7 +212,6 @@ %{for lib_build_info} build %{obj}: compile_lib %{src} - isa_flags = %{isa_flags} %{endfor} %{for cli_build_info} diff -Nru botan3-3.7.1+dfsg/src/build-data/oids.txt botan3-3.12.0+dfsg/src/build-data/oids.txt --- botan3-3.7.1+dfsg/src/build-data/oids.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/oids.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,22 +1,26 @@ -# Regenerate with ./src/scripts/dev_tools/gen_oids.py oids > src/lib/asn1/oid_maps.cpp -# AND ./src/scripts/dev_tools/gen_oids.py dn_ub > src/lib/x509/x509_dn_ub.cpp -# (if you modified something under [dn]) +# Static OID data +# +# If you modify this data you must run ./src/scripts/dev_tools/gen_oids.py +# to regenerate the relevant source files # Public key types [pubkey] 1.2.840.113549.1.1.1 = RSA -2.5.8.1.1 = RSA 1.2.840.10040.4.1 = DSA 1.2.840.10046.2.1 = DH 1.3.6.1.4.1.3029.1.2.1 = ElGamal 1.3.6.1.4.1.25258.1.3 = McEliece -# Deprecated alias for X25519 -1.3.101.110 = Curve25519 1.3.101.110 = X25519 1.3.101.111 = X448 1.3.101.112 = Ed25519 1.3.101.113 = Ed448 +# Deprecated alternate RSA OID +2.5.8.1.1 = RSA + +# Deprecated alias for X25519 TODO(Botan4) remove this +1.3.101.110 = Curve25519 + # From NIST: 2.16.840.1.101.3.4.4.1 = ML-KEM-512 2.16.840.1.101.3.4.4.2 = ML-KEM-768 @@ -128,8 +132,8 @@ 1.2.840.10045.2.1 = ECDSA 1.3.132.1.12 = ECDH -1.2.156.10197.1.301.1 = SM2_Sig 1.2.156.10197.1.301.1 = SM2 +1.2.156.10197.1.301.1 = SM2_Sig 1.2.156.10197.1.301.2 = SM2_Kex 1.2.156.10197.1.301.3 = SM2_Enc @@ -260,6 +264,22 @@ 2.16.840.1.101.3.4.3.7 = DSA/SHA-3(384) 2.16.840.1.101.3.4.3.8 = DSA/SHA-3(512) +1.2.840.113549.1.1.2 = RSA/PKCS1v15(MD2) +1.2.840.113549.1.1.4 = RSA/PKCS1v15(MD5) +1.2.840.113549.1.1.5 = RSA/PKCS1v15(SHA-1) +1.2.840.113549.1.1.11 = RSA/PKCS1v15(SHA-256) +1.2.840.113549.1.1.12 = RSA/PKCS1v15(SHA-384) +1.2.840.113549.1.1.13 = RSA/PKCS1v15(SHA-512) +1.2.840.113549.1.1.14 = RSA/PKCS1v15(SHA-224) +1.2.840.113549.1.1.16 = RSA/PKCS1v15(SHA-512-256) +2.16.840.1.101.3.4.3.13 = RSA/PKCS1v15(SHA-3(224)) +2.16.840.1.101.3.4.3.14 = RSA/PKCS1v15(SHA-3(256)) +2.16.840.1.101.3.4.3.15 = RSA/PKCS1v15(SHA-3(384)) +2.16.840.1.101.3.4.3.16 = RSA/PKCS1v15(SHA-3(512)) +1.2.156.10197.1.504 = RSA/PKCS1v15(SM3) +1.3.36.3.3.1.2 = RSA/PKCS1v15(RIPEMD-160) +1.2.840.113549.1.1.10 = RSA/PSS + # TODO(Botan4) remove these EMSA3/EMSA4 aliases 1.2.840.113549.1.1.2 = RSA/EMSA3(MD2) 1.2.840.113549.1.1.4 = RSA/EMSA3(MD5) @@ -277,22 +297,6 @@ 1.3.36.3.3.1.2 = RSA/EMSA3(RIPEMD-160) 1.2.840.113549.1.1.10 = RSA/EMSA4 -1.2.840.113549.1.1.2 = RSA/PKCS1v15(MD2) -1.2.840.113549.1.1.4 = RSA/PKCS1v15(MD5) -1.2.840.113549.1.1.5 = RSA/PKCS1v15(SHA-1) -1.2.840.113549.1.1.11 = RSA/PKCS1v15(SHA-256) -1.2.840.113549.1.1.12 = RSA/PKCS1v15(SHA-384) -1.2.840.113549.1.1.13 = RSA/PKCS1v15(SHA-512) -1.2.840.113549.1.1.14 = RSA/PKCS1v15(SHA-224) -1.2.840.113549.1.1.16 = RSA/PKCS1v15(SHA-512-256) -2.16.840.1.101.3.4.3.13 = RSA/PKCS1v15(SHA-3(224)) -2.16.840.1.101.3.4.3.14 = RSA/PKCS1v15(SHA-3(256)) -2.16.840.1.101.3.4.3.15 = RSA/PKCS1v15(SHA-3(384)) -2.16.840.1.101.3.4.3.16 = RSA/PKCS1v15(SHA-3(512)) -1.2.156.10197.1.504 = RSA/PKCS1v15(SM3) -1.3.36.3.3.1.2 = RSA/PKCS1v15(RIPEMD-160) -1.2.840.113549.1.1.10 = RSA/PSS - 1.2.840.10045.4.1 = ECDSA/SHA-1 1.2.840.10045.4.3.1 = ECDSA/SHA-224 1.2.840.10045.4.3.2 = ECDSA/SHA-256 @@ -326,32 +330,47 @@ 1.2.840.113549.1.1.7 = RSA/OAEP 1.2.840.113549.1.1.8 = MGF1 -# DN with upper bounds from RFC 5280, Appendix A [dn] -2.5.4.3 = X520.CommonName = 64 -2.5.4.4 = X520.Surname = 40 -2.5.4.5 = X520.SerialNumber = 64 -2.5.4.6 = X520.Country = 3 -2.5.4.7 = X520.Locality = 128 -2.5.4.8 = X520.State = 128 -2.5.4.9 = X520.StreetAddress = 128 -2.5.4.10 = X520.Organization = 64 -2.5.4.11 = X520.OrganizationalUnit = 64 -2.5.4.12 = X520.Title = 64 -# the following three types are naming attributes of type "X520name" and inherit its bound -2.5.4.42 = X520.GivenName = 32768 -2.5.4.43 = X520.Initials = 32768 -2.5.4.44 = X520.GenerationalQualifier = 32768 -2.5.4.46 = X520.DNQualifier = 64 -2.5.4.65 = X520.Pseudonym = 128 +2.5.4.3 = X520.CommonName +2.5.4.4 = X520.Surname +2.5.4.5 = X520.SerialNumber +2.5.4.6 = X520.Country +2.5.4.7 = X520.Locality +2.5.4.8 = X520.State +2.5.4.9 = X520.StreetAddress +2.5.4.10 = X520.Organization +2.5.4.11 = X520.OrganizationalUnit +2.5.4.12 = X520.Title +2.5.4.42 = X520.GivenName +2.5.4.43 = X520.Initials +2.5.4.44 = X520.GenerationalQualifier +2.5.4.46 = X520.DNQualifier +2.5.4.65 = X520.Pseudonym [pbe] 1.2.840.113549.1.5.12 = PKCS5.PBKDF2 -1.2.840.113549.1.5.13 = PBES2 1.2.840.113549.1.5.13 = PBE-PKCS5v20 +1.2.840.113549.1.5.13 = PBES2 1.3.6.1.4.1.11591.4.11 = Scrypt +# PKCS#12 PBE algorithms (password-based encryption) +1.2.840.113549.1.12.1.3 = PBE-SHA1-3DES +1.2.840.113549.1.12.1.4 = PBE-SHA1-2DES + +[pkcs12] +# PKCS#12 bag types +1.2.840.113549.1.12.10.1.1 = PKCS12.KeyBag +1.2.840.113549.1.12.10.1.2 = PKCS12.PKCS8ShroudedKeyBag +1.2.840.113549.1.12.10.1.3 = PKCS12.CertBag +1.2.840.113549.1.12.10.1.4 = PKCS12.CRLBag +1.2.840.113549.1.12.10.1.5 = PKCS12.SecretBag +1.2.840.113549.1.12.10.1.6 = PKCS12.SafeContentsBag + +# PKCS#7 content types (used by PKCS#12) +1.2.840.113549.1.7.1 = PKCS7.Data +1.2.840.113549.1.7.6 = PKCS7.EncryptedData + [pkcs9] 1.2.840.113549.1.9.1 = PKCS9.EmailAddress 1.2.840.113549.1.9.2 = PKCS9.UnstructuredName @@ -360,6 +379,15 @@ 1.2.840.113549.1.9.7 = PKCS9.ChallengePassword 1.2.840.113549.1.9.14 = PKCS9.ExtensionRequest +# PKCS#12 cert/CRL bag types +1.2.840.113549.1.9.22.1 = PKCS9.X509Certificate +1.2.840.113549.1.9.22.2 = PKCS9.SDSICertificate +1.2.840.113549.1.9.23.1 = PKCS9.X509CRL + +# PKCS#12 attributes +1.2.840.113549.1.9.20 = PKCS9.FriendlyName +1.2.840.113549.1.9.21 = PKCS9.LocalKeyId + [pkix] 2.5.29.14 = X509v3.SubjectKeyIdentifier 2.5.29.15 = X509v3.KeyUsage @@ -378,7 +406,11 @@ 2.5.29.35 = X509v3.AuthorityKeyIdentifier 2.5.29.36 = X509v3.PolicyConstraints 2.5.29.37 = X509v3.ExtendedKeyUsage +2.5.29.37.0 = X509v3.AnyExtendedKeyUsage 1.3.6.1.5.5.7.1.1 = PKIX.AuthorityInformationAccess +# the following are taken from RFC 3779 https://www.rfc-editor.org/rfc/rfc3779.html +1.3.6.1.5.5.7.1.7 = PKIX.IpAddrBlocks +1.3.6.1.5.5.7.1.8 = PKIX.AutonomousSysIds 1.3.6.1.5.5.7.1.26 = PKIX.TNAuthList 2.5.29.32.0 = X509v3.AnyPolicy @@ -422,6 +454,11 @@ 1.3.132.0.33 = secp224r1 1.3.132.0.34 = secp384r1 1.3.132.0.35 = secp521r1 + +# TODO(Botan4) remove this OID assignment +# +# This was assigned by TU-Darmstadt to "primeCurve 38" which may or may not be +# secp521r1. In any case this OID is not used by any other implementation. 1.3.6.1.4.1.8301.3.1.2.9.0.38 = secp521r1 1.2.840.10045.3.1.1 = secp192r1 diff -Nru botan3-3.7.1+dfsg/src/build-data/os/hurd.txt botan3-3.12.0+dfsg/src/build-data/os/hurd.txt --- botan3-3.7.1+dfsg/src/build-data/os/hurd.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/os/hurd.txt 2026-05-07 01:38:28.000000000 +0000 @@ -7,6 +7,10 @@ dev_random clock_gettime +explicit_bzero +getrandom +getentropy + atomics sockets system_clock diff -Nru botan3-3.7.1+dfsg/src/build-data/os/netbsd.txt botan3-3.12.0+dfsg/src/build-data/os/netbsd.txt --- botan3-3.7.1+dfsg/src/build-data/os/netbsd.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/os/netbsd.txt 2026-05-07 01:38:28.000000000 +0000 @@ -15,7 +15,6 @@ threads thread_local filesystem -auxinfo diff -Nru botan3-3.7.1+dfsg/src/build-data/os/openbsd.txt botan3-3.12.0+dfsg/src/build-data/os/openbsd.txt --- botan3-3.7.1+dfsg/src/build-data/os/openbsd.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/os/openbsd.txt 2026-05-07 01:38:28.000000000 +0000 @@ -16,7 +16,6 @@ elf_aux_info getentropy explicit_bzero -pledge alloc_conceal atomics diff -Nru botan3-3.7.1+dfsg/src/build-data/policy/bsi.txt botan3-3.12.0+dfsg/src/build-data/policy/bsi.txt --- botan3-3.7.1+dfsg/src/build-data/policy/bsi.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/policy/bsi.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,9 +1,9 @@ -# For reference see BSI TR-02102-1 (2024-01): -# https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Publications/TechGuidelines/TG02102/BSI-TR-02102-1.pdf?__blob=publicationFile&v=7 +# For reference see BSI TR-02102-1 (2025-01): +# https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Publications/TechGuidelines/TG02102/BSI-TR-02102-1.pdf?__blob=publicationFile&v=9 # === 2. Asymmetric Encryption Schemes and Key Agreement === -# Table 2.1: Recommended classical asymmetric encryption and key derivation schemes +# Table 2.2: Recommended classical asymmetric encryption and key derivation schemes rsa # dlies (deprecated) ecies @@ -13,15 +13,18 @@ # Allowed KDF for ECIES (see 2.3.4) kdf1_iso18033 -# Table 2.3: Recommended formatting method for the RSA encryption algorithm +# Table 2.4: Recommended formatting method for the RSA encryption algorithm eme_oaep -# Table 2.4: Recommended parameters for FrodoKEM +# Table 2.5: Recommended parameters for FrodoKEM frodokem -# Table 2.5: Recommended parameters for ClassicMcEliece-KEM. +# Table 2.6: Recommended parameters for ClassicMcEliece-KEM. classic_mceliece +# Table 2.7: Recommended parameters for ML-KEM +ml_kem + # === 3. Symmetric Encryption Schemes === # Table 3.1: Recommended block ciphers aes @@ -35,6 +38,9 @@ # Table 3.3: Recommended padding schemes for block ciphers mode_pad # contains various paddings +# Section 3.3: Protection of Key Material +nist_keywrap + # === 4. Hash Functions === # Table 4.1: Recommended hash functions sha2_32 @@ -54,6 +60,9 @@ ecdsa ecgdsa eckcdsa +ml_dsa +slh_dsa_shake +slh_dsa_sha2 xmss hss_lms @@ -70,26 +79,13 @@ sp800_56c # (Two-Step KDF) hkdf -# B.1.3. Password-Based Key Derivation +# B.1.2. Password-Based Key Derivation argon2 argon2fmt + -# Addition: ML-KEM, ML-DSA, and SLH-DSA -# We expect the BSI to approve the new FIPS (203,204,205) PQC algorithms -# in the upcoming TR (see Section 2.4.3 and Remark 5.5). We believe it is in -# the BSI's interest to allow them here so applications can migrate to -# post-quantum security as soon as possible. -ml_kem -ml_dsa -slh_dsa_shake -slh_dsa_sha2 - - -# Optimization: PCurves -# To benefit from the speedup of pcurves, we activate all pcurve modules. We -# activate all curves regardless of the recommendation in the TR since they -# would be accessible anyway in a worse generic implementation. - + +# pcurves pcurves_brainpool256r1 pcurves_brainpool384r1 pcurves_brainpool512r1 @@ -105,14 +101,14 @@ pcurves_numsp512d1 pcurves_sm2p256v1 - +pcurves_generic - # block aes_ni aes_vperm aes_armv8 aes_power8 +aes_vaes # modes ghash_cpu @@ -121,8 +117,12 @@ # hash sha2_32_x86 sha2_32_armv8 -sha2_32_bmi2 -sha2_64_bmi2 +sha2_32_simd +sha2_32_avx2 +sha2_64_x86 +sha2_64_armv8 +sha2_64_avx2 +sha2_64_avx512 keccak_perm_bmi2 # entropy sources @@ -132,7 +132,7 @@ # pbkdf argon2_avx2 -argon2_ssse3 +argon2_simd64 # rng processor_rng @@ -141,7 +141,6 @@ # utils http_util # needed by x509 for OCSP online checks locking_allocator -simd diff -Nru botan3-3.7.1+dfsg/src/build-data/policy/fips140.txt botan3-3.12.0+dfsg/src/build-data/policy/fips140.txt --- botan3-3.7.1+dfsg/src/build-data/policy/fips140.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/policy/fips140.txt 2026-05-07 01:38:28.000000000 +0000 @@ -60,8 +60,9 @@ # hash sha2_32_x86 sha2_32_armv8 -sha2_32_bmi2 -sha2_64_bmi2 +sha2_32_simd +sha2_32_avx2 +sha2_64_avx2 keccak_perm_bmi2 # modes @@ -82,7 +83,6 @@ # utils http_util # needed by x509 for OCSP online checks locking_allocator -simd @@ -115,6 +115,7 @@ # mac blake2mac +poly1305 # modes chacha20poly1305 @@ -133,7 +134,6 @@ salsa20 # kdf -hkdf kdf1 kdf2 prf_x942 @@ -194,7 +194,6 @@ # misc bcrypt srp6 -sodium # tls tls_cbc diff -Nru botan3-3.7.1+dfsg/src/build-data/policy/modern.txt botan3-3.12.0+dfsg/src/build-data/policy/modern.txt --- botan3-3.7.1+dfsg/src/build-data/policy/modern.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/policy/modern.txt 2026-05-07 01:38:28.000000000 +0000 @@ -8,9 +8,11 @@ sha2_64 blake2 skein -keccak +shake sha3 +shake_xof + gcm ocb chacha20poly1305 @@ -20,7 +22,6 @@ cmac hmac poly1305 -siphash pbkdf2 bcrypt @@ -50,6 +51,13 @@ tls prf_tls +# pcurves +pcurves_secp256r1 +pcurves_secp384r1 +pcurves_secp521r1 + +pcurves_generic + ghash_cpu ghash_vperm @@ -65,17 +73,16 @@ chacha_simd32 chacha_avx2 -sha1_sse2 +sha1_simd sha1_x86 sha1_armv8 sha2_32_x86 +sha2_32_simd sha2_32_armv8 -sha2_32_bmi2 -sha2_64_bmi2 +sha2_32_avx2 +sha2_64_avx2 keccak_perm_bmi2 -simd - sessions_sql certstor_sql diff -Nru botan3-3.7.1+dfsg/src/build-data/target_info.h.in botan3-3.12.0+dfsg/src/build-data/target_info.h.in --- botan3-3.7.1+dfsg/src/build-data/target_info.h.in 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/target_info.h.in 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,110 @@ +#ifndef BOTAN_TARGET_INFO_H_ +#define BOTAN_TARGET_INFO_H_ + +#include + +/** +* @file target_info.h +* +* Automatically generated from +* '%{command_line}' +* +* Target +* - Compiler: %{cxx} %{cxx_abi_flags} %{cc_lang_flags} %{cc_compile_flags} +* - Arch: %{arch} +* - OS: %{os} +*/ + +/* NOLINTBEGIN(*-macro-usage,*-macro-to-enum) */ + +/* +* Configuration +*/ +%{if cxx_ct_value_barrier_type} +#define BOTAN_CT_VALUE_BARRIER_USE_%{cxx_ct_value_barrier_type|upper} +%{endif} + +[[maybe_unused]] static constexpr bool OptimizeForSize = %{optimize_for_size|as_bool}; + +%{if terminate_on_asserts} +#define BOTAN_TERMINATE_ON_ASSERTS +%{endif} + +%{if fuzzer_mode} +/** Disables certain validation checks to ease fuzzability of the library + * @warning This causes the library build to be insecure, hence, it must not be + * used in a production environment! + */ +#define BOTAN_UNSAFE_FUZZER_MODE +%{endif} + +/* +* Compiler Information +*/ +#define BOTAN_BUILD_COMPILER_IS_%{cc_macro} + +#define BOTAN_COMPILER_INVOCATION_STRING "%{cxx} %{cxx_abi_flags} %{cc_compile_flags}" + +%{if cxx_supports_gcc_inline_asm} +#define BOTAN_USE_GCC_INLINE_ASM +%{endif} + +%{if compiler_assisted_stack_scrubbing} +#define BOTAN_USE_COMPILER_ASSISTED_STACK_SCRUBBING +%{endif} + +/* +* External tool settings +*/ +%{if with_valgrind} +#define BOTAN_HAS_VALGRIND +%{endif} + +%{if fuzzer_type} +#define BOTAN_FUZZER_IS_%{fuzzer_type} +%{endif} + +%{for sanitizer_types} +#define BOTAN_HAS_SANITIZER_%{i|upper} +%{endfor} + +/* +* CPU feature information +*/ +#define BOTAN_TARGET_ARCH "%{arch}" + +#define BOTAN_TARGET_ARCH_IS_%{arch|upper} + +%{if cpu_family} +#define BOTAN_TARGET_ARCH_IS_%{cpu_family|upper}_FAMILY +%{endif} + +%{for cpu_features} +#define BOTAN_TARGET_ARCH_SUPPORTS_%{i|upper} +%{endfor} + +/* +* Operating system information +*/ +#define BOTAN_TARGET_OS_IS_%{os_name|upper} + +%{for os_features} +#define BOTAN_TARGET_OS_HAS_%{i|upper} +%{endfor} + +/* +* System paths +*/ +#define BOTAN_INSTALL_PREFIX R"(%{prefix})" +#define BOTAN_INSTALL_HEADER_DIR R"(%{namespaced_includedir_rel})" +#define BOTAN_INSTALL_LIB_DIR R"(%{libdir})" +#define BOTAN_LIB_LINK "%{link_to}" +#define BOTAN_LINK_FLAGS "%{cxx_abi_flags}" + +%{if system_cert_bundle} +#define BOTAN_SYSTEM_CERT_BUNDLE "%{system_cert_bundle}" +%{endif} + +/* NOLINTEND(*-macro-usage,*-macro-to-enum) */ + +#endif diff -Nru botan3-3.7.1+dfsg/src/build-data/templates/ec_named.cpp.in botan3-3.12.0+dfsg/src/build-data/templates/ec_named.cpp.in --- botan3-3.7.1+dfsg/src/build-data/templates/ec_named.cpp.in 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/templates/ec_named.cpp.in 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,56 @@ +/* +* ECC Group Info +* This file was automatically generated by {{ script }} on {{ date }} +* All manual changes will be lost. Edit the script instead. +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +namespace Botan { + +// clang-format off + +//static +std::shared_ptr EC_Group::EC_group_info(const OID& oid) { +{%- for curve in curves %} + // {{ curve["Name"] }} + if({% for oid in curve["OIDExpr"] %}oid == {{ oid }}{% if not loop.last %} || {% endif %}{% endfor %}) { + return load_EC_group_info( + "0x{{ "%X" % curve["P"] }}", + "0x{{ "%X" % curve["A"] }}", + "0x{{ "%X" % curve["B"] }}", + "0x{{ "%X" % curve["X"] }}", + "0x{{ "%X" % curve["Y"] }}", + "0x{{ "%X" % curve["N"] }}", + {% if curve["OIDExpr"] | length == 1 %}oid{% else %}{{ curve["OIDExpr"][0] }}{% endif %}); + } +{% endfor %} + return std::shared_ptr(); +} + +//static +OID EC_Group::EC_group_identity_from_order(const BigInt& order) + { + const uint32_t low_bits = static_cast(order.word_at(0)); +{% for curve in curves %} + if(low_bits == 0x{{ "%08X" % curve["N32"]}} && order == BigInt("0x{{ "%X" % curve["N"] }}")) { + return {{ curve["OIDExpr"][0] }}; + } +{% endfor %} + return OID(); +} + +//static +const std::set& EC_Group::known_named_groups() { + static const std::set named_groups = { +{{named_groups}} + }; + + return named_groups; +} + +} // namespace Botan + +// clang-format on diff -Nru botan3-3.7.1+dfsg/src/build-data/templates/pcurves.cpp.in botan3-3.12.0+dfsg/src/build-data/templates/pcurves.cpp.in --- botan3-3.7.1+dfsg/src/build-data/templates/pcurves.cpp.in 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/templates/pcurves.cpp.in 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,44 @@ +/* +* This file was automatically generated by {{ script }} on {{ date }} +* All manual changes will be lost. Edit the script instead. +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include + +namespace Botan::PCurve { + +void PrimeOrderCurve::Scalar::_zeroize() { + secure_zeroize_buffer(m_value.data(), m_value.size() * sizeof(word)); +} + +//static +std::shared_ptr PrimeOrderCurve::from_params( + const BigInt& p, const BigInt& a, const BigInt& b, const BigInt& base_x, const BigInt& base_y, const BigInt& order) { +#if defined(BOTAN_HAS_PCURVES_GENERIC) + return PCurveInstance::from_params(p, a, b, base_x, base_y, order); +#endif + + BOTAN_UNUSED(p, a, b, base_x, base_y, order); + return {}; +} + +//static +std::shared_ptr PrimeOrderCurve::for_named_curve(std::string_view name) { +{%- for curve in pcurves %} +#if defined(BOTAN_HAS_PCURVES_{{curve["Name"] | upper}}) + if(name == "{{curve["Name"]}}") { + return PCurveInstance::{{curve["Name"]}}(); + } +#endif +{% endfor %} + BOTAN_UNUSED(name); + return {}; +} + +} // namespace Botan::PCurve diff -Nru botan3-3.7.1+dfsg/src/build-data/templates/pcurves_instance.h.in botan3-3.12.0+dfsg/src/build-data/templates/pcurves_instance.h.in --- botan3-3.7.1+dfsg/src/build-data/templates/pcurves_instance.h.in 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/templates/pcurves_instance.h.in 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,42 @@ +/* +* This file was automatically generated by {{ script }} on {{ date }} +* All manual changes will be lost. Edit the script instead. +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_PCURVES_INSTANCE_H_ +#define BOTAN_PCURVES_INSTANCE_H_ + +#include +#include + +namespace Botan { + +class BigInt; + +} + +namespace Botan::PCurve { + +class PrimeOrderCurve; + +class PCurveInstance final { + public:{% for curve in pcurves %} +#if defined(BOTAN_HAS_PCURVES_{{ curve["Name"] | upper }}) + static std::shared_ptr {{ curve["Name"] }}(); +#endif +{% endfor %} +#if defined(BOTAN_HAS_PCURVES_GENERIC) + static std::shared_ptr from_params(const BigInt& p, + const BigInt& a, + const BigInt& b, + const BigInt& base_x, + const BigInt& base_y, + const BigInt& order); +#endif +}; + +} // namespace Botan::PCurve + +#endif diff -Nru botan3-3.7.1+dfsg/src/build-data/templates/pcurves_stub.cpp.in botan3-3.12.0+dfsg/src/build-data/templates/pcurves_stub.cpp.in --- botan3-3.7.1+dfsg/src/build-data/templates/pcurves_stub.cpp.in 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/templates/pcurves_stub.cpp.in 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,80 @@ +/* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include + +namespace Botan::PCurve { + +namespace { + +namespace {{ curve["Name"] }} { + +{% if crandall > 0 -%} +template +class {{ curve["Name"] | capitalize }}Rep final { + public: + static constexpr auto P = Params::P; + static constexpr size_t N = Params::N; + typedef typename Params::W W; + + static constexpr W C = {{ crandall }}; + + constexpr static std::array one() { return std::array{1}; } + + constexpr static std::array redc(const std::array& z) { + return redc_crandall(std::span{z}); + } + + constexpr static std::array to_rep(const std::array& x) { return x; } + + constexpr static std::array wide_to_rep(const std::array& x) { return redc(x); } + + constexpr static std::array from_rep(const std::array& z) { return z; } +}; +{% endif %} +// clang-format off +class Params final : public EllipticCurveParameters< + "{{ "%X" % curve['P'] }}", + "{{ "%X" % curve['A'] }}", + "{{ "%X" % curve['B'] }}", + "{{ "%X" % curve['N'] }}", + "{{ "%X" % curve['X'] }}", + "{{ "%X" % curve['Y'] }}"> { +}; +// clang-format on + +class Curve final : public EllipticCurve 0 %}, {{ curve["Name"] | capitalize}}Rep{% endif %}> { + public: + // Return the square of the inverse of x + static constexpr FieldElement fe_invert2(const FieldElement& x) { + // Generated using https://github.com/mmcloughlin/addchain + {{ addchain_fe2 }} + } + + {% if addchain_fe_sqrt != None -%} + // Return the square root of this field element (if it is a quadratic residue) + static constexpr FieldElement fe_sqrt(const FieldElement& x) { + // Generated using https://github.com/mmcloughlin/addchain + {{ addchain_fe_sqrt }} + } + {%- endif %} + + // Return the inverse of an integer modulo the order + static constexpr Scalar scalar_invert(const Scalar& x) { + // Generated using https://github.com/mmcloughlin/addchain + {{ addchain_scalar }} + } +}; + +} // namespace {{ curve["Name"] }} + +} // namespace + +std::shared_ptr PCurveInstance::{{ curve["Name"] }}() { + return PrimeOrderCurveImpl<{{ curve["Name"] }}::Curve>::instance(); +} + +} // namespace Botan::PCurve diff -Nru botan3-3.7.1+dfsg/src/build-data/templates/static_oids.cpp.in botan3-3.12.0+dfsg/src/build-data/templates/static_oids.cpp.in --- botan3-3.7.1+dfsg/src/build-data/templates/static_oids.cpp.in 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/templates/static_oids.cpp.in 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,91 @@ +/* +* This file was automatically generated by {{ script }} on {{ date }} +* All manual changes will be lost. Edit the script instead. +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include + +namespace Botan { + +namespace { + +// The hash can collide so we must verify the actual value matches before returning +std::optional if_match(const OID& oid, std::initializer_list val, std::string_view name) { + if(oid.matches(val)) { + return name; + } else { + return {}; + } +} + +std::optional if_match(std::string_view req, std::string_view actual, std::initializer_list oid) { + if(req == actual) { + return OID(oid); + } else { + return {}; + } +} + +uint32_t hash_oid_name(std::string_view s) { + uint64_t hash = 0x8188B31879A4879A; + + for(const char c : s) { + hash *= 251; + hash += c; + } + + return static_cast(hash % 805289); +} + +} // namespace + +//static +std::optional OID_Map::lookup_static_oid(const OID& oid) { + const uint32_t hc = static_cast(oid.hash_code() % 858701); + + switch(hc) { +{%- for match in static_oid_data|sort(attribute="oid_hash") %} + case 0x{{ "%05X" % (match.oid_hash) }}: + return if_match(oid, {{ match.oid }}, "{{match.name}}"); +{%- endfor %} + default: + return {}; + } +} + +//static +std::optional OID_Map::lookup_static_oid_name(std::string_view req) { + const uint32_t hc = hash_oid_name(req); + + switch(hc) { +{%- for match in static_oid_data|sort(attribute="name_hash") %} + case 0x{{ "%05X" % (match.name_hash) }}: + return if_match(req, "{{match.name}}", {{ match.oid }}); +{%- endfor %} + default: + return {}; + } +} + +std::unordered_map OID_Map::load_oid2str_map() { + return { +{%- for oid in dup_oids %} + {OID{{ oid.oid }}, "{{ oid.name }}"}, +{%- endfor %} + }; +} + +std::unordered_map OID_Map::load_str2oid_map() { + return { +{%- for oid in aliases %} + {"{{ oid.name }}", OID{{ oid.oid }}}, +{%- endfor %} + }; +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/build-data/templates/tls_suite_info.cpp.in botan3-3.12.0+dfsg/src/build-data/templates/tls_suite_info.cpp.in --- botan3-3.7.1+dfsg/src/build-data/templates/tls_suite_info.cpp.in 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/templates/tls_suite_info.cpp.in 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,58 @@ +/* +* TLS cipher suite information +* +* This file was automatically generated by {{ script }} on {{ date }} +* using the IANA assignments (tls-parameters.txt sha256 {{ contents_hash }}) +* All manual changes will be lost. Edit the script instead. +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include + +namespace Botan::TLS { + +namespace { + +consteval auto available_ciphersuites() { + // clang-format off + auto codes = std::array { +{%- for suite in suites %} +#if {{ suite.gates_expr }} + uint16_t{0x{{ suite.code }}}, // {{ suite.name }} +#endif +{%- endfor %} + }; + // clang-format on + + return codes; +} + +} // namespace + +//static +bool Ciphersuite::is_known_usable(uint16_t code) { + static constexpr auto codes = available_ciphersuites(); + return std::binary_search(codes.begin(), codes.end(), code); +} + +//static +const std::vector& Ciphersuite::all_known_ciphersuites() { + // clang-format off + + // Note that this list of ciphersuites is ordered by id! + static const std::vector g_ciphersuite_list = { +{%- for suite in suites %} + Ciphersuite(0x{{ suite.code }}, "{{ suite.name }}", Auth_Method::{{ suite.sig_algo }}, Kex_Algo::{{ suite.kex_algo }}, "{{ suite.cipher_algo }}", {{ suite.cipher_keylen }}, "{{ suite.mac_algo }}", {{ suite.mac_keylen }}, KDF_Algo::{{ suite.kdf_algo }}, Nonce_Format::{{ suite.nonce_format }}), +{%- endfor %} + }; + + // clang-format on + + return g_ciphersuite_list; +} + +} // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/build-data/version.txt botan3-3.12.0+dfsg/src/build-data/version.txt --- botan3-3.7.1+dfsg/src/build-data/version.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/version.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,13 +1,13 @@ release_major = 3 -release_minor = 7 -release_patch = 1 +release_minor = 12 +release_patch = 0 -release_so_abi_rev = 7 +release_so_abi_rev = 12 release_suffix = '' # These are set by the distribution script -release_vc_rev = 'git:09cc7f97ceb828c19461b2a63f820d3226bb921b' -release_datestamp = 20250205 -release_type = 'release' +release_vc_rev = None +release_datestamp = 0 +release_type = 'unreleased' diff -Nru botan3-3.7.1+dfsg/src/build-data/version_info.h.in botan3-3.12.0+dfsg/src/build-data/version_info.h.in --- botan3-3.7.1+dfsg/src/build-data/version_info.h.in 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/build-data/version_info.h.in 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,20 @@ +#ifndef BOTAN_VERSION_INFO_H_ +#define BOTAN_VERSION_INFO_H_ + +/* NOLINTBEGIN(*-macro-usage) */ + +#define BOTAN_FULL_VERSION_STRING "%{full_version_string}" + +#define BOTAN_SHORT_VERSION_STRING "%{short_version_string}" + +%{if version_vc_rev} +#define BOTAN_VC_REVISION "%{version_vc_rev}" +%{endif} + +%{if distribution_info} +#define BOTAN_DISTRIBUTION_INFO_STRING "%{distribution_info}" +%{endif} + +/* NOLINTEND(*-macro-usage) */ + +#endif diff -Nru botan3-3.7.1+dfsg/src/cli/argon2.cpp botan3-3.12.0+dfsg/src/cli/argon2.cpp --- botan3-3.7.1+dfsg/src/cli/argon2.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/argon2.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -12,6 +12,8 @@ namespace Botan_CLI { +namespace { + #if defined(BOTAN_HAS_ARGON2_FMT) class Generate_Argon2 final : public Command { @@ -48,9 +50,9 @@ const bool ok = Botan::argon2_check_pwhash(password.data(), password.size(), hash); - output() << "Password is " << (ok ? "valid" : "NOT valid") << std::endl; + output() << "Password is " << (ok ? "valid" : "NOT valid") << "\n"; - if(ok == false) { + if(!ok) { set_return_code(1); } } @@ -60,4 +62,6 @@ #endif // argon2 +} // namespace + } // namespace Botan_CLI diff -Nru botan3-3.7.1+dfsg/src/cli/argparse.h botan3-3.12.0+dfsg/src/cli/argparse.h --- botan3-3.7.1+dfsg/src/cli/argparse.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/argparse.h 2026-05-07 01:38:28.000000000 +0000 @@ -17,9 +17,9 @@ class Argument_Parser final { public: - Argument_Parser(const std::string& spec, - const std::vector& extra_flags = {}, - const std::vector& extra_opts = {}); + explicit Argument_Parser(const std::string& spec, + const std::vector& extra_flags = {}, + const std::vector& extra_opts = {}); void parse_args(const std::vector& params); @@ -51,21 +51,21 @@ std::vector m_user_rest; }; -std::vector Argument_Parser::split_on(const std::string& str, char delim) { +inline std::vector Argument_Parser::split_on(const std::string& str, char delim) { std::vector elems; if(str.empty()) { return elems; } std::string substr; - for(auto i = str.begin(); i != str.end(); ++i) { - if(*i == delim) { + for(const char c : str) { + if(c == delim) { if(!substr.empty()) { elems.push_back(substr); } substr.clear(); } else { - substr += *i; + substr += c; } } @@ -77,15 +77,15 @@ return elems; } -bool Argument_Parser::flag_set(const std::string& flag_name) const { +inline bool Argument_Parser::flag_set(const std::string& flag_name) const { return m_user_flags.contains(flag_name); } -bool Argument_Parser::has_arg(const std::string& opt_name) const { +inline bool Argument_Parser::has_arg(const std::string& opt_name) const { return m_user_args.contains(opt_name); } -std::string Argument_Parser::get_arg(const std::string& opt_name) const { +inline std::string Argument_Parser::get_arg(const std::string& opt_name) const { auto i = m_user_args.find(opt_name); if(i == m_user_args.end()) { // this shouldn't occur unless you passed the wrong thing to get_arg @@ -94,7 +94,7 @@ return i->second; } -std::string Argument_Parser::get_arg_or(const std::string& opt_name, const std::string& otherwise) const { +inline std::string Argument_Parser::get_arg_or(const std::string& opt_name, const std::string& otherwise) const { auto i = m_user_args.find(opt_name); if(i == m_user_args.end() || i->second.empty()) { return otherwise; @@ -102,7 +102,7 @@ return i->second; } -size_t Argument_Parser::get_arg_sz(const std::string& opt_name) const { +inline size_t Argument_Parser::get_arg_sz(const std::string& opt_name) const { const std::string s = get_arg(opt_name); try { @@ -112,7 +112,7 @@ } } -size_t Argument_Parser::get_arg_hex_sz_or(const std::string& opt_name, const std::string& otherwise) const { +inline size_t Argument_Parser::get_arg_hex_sz_or(const std::string& opt_name, const std::string& otherwise) const { const std::string s = get_arg_or(opt_name, otherwise); try { @@ -122,7 +122,7 @@ } } -std::vector Argument_Parser::get_arg_list(const std::string& what) const { +inline std::vector Argument_Parser::get_arg_list(const std::string& what) const { if(what == m_spec_rest) { return m_user_rest; } @@ -130,10 +130,10 @@ return split_on(get_arg(what), ','); } -void Argument_Parser::parse_args(const std::vector& params) { +inline void Argument_Parser::parse_args(const std::vector& params) { std::vector args; for(const auto& param : params) { - if(param.find("--") == 0) { + if(param.starts_with("--")) { // option const auto eq = param.find('='); @@ -209,9 +209,9 @@ } } -Argument_Parser::Argument_Parser(const std::string& spec, - const std::vector& extra_flags, - const std::vector& extra_opts) { +inline Argument_Parser::Argument_Parser(const std::string& spec, + const std::vector& extra_flags, + const std::vector& extra_opts) { class CLI_Error_Invalid_Spec final : public CLI_Error { public: explicit CLI_Error_Invalid_Spec(const std::string& bad_spec) : @@ -251,8 +251,8 @@ } } else { // named argument - if(!m_spec_rest.empty()) // rest arg wasn't last - { + if(!m_spec_rest.empty()) { + // rest arg wasn't last throw CLI_Error_Invalid_Spec(spec); } diff -Nru botan3-3.7.1+dfsg/src/cli/asn1.cpp botan3-3.12.0+dfsg/src/cli/asn1.cpp --- botan3-3.7.1+dfsg/src/cli/asn1.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/asn1.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -17,6 +17,8 @@ namespace Botan_CLI { +namespace { + class ASN1_Printer final : public Command { public: ASN1_Printer() : @@ -45,7 +47,7 @@ const std::string input = get_arg("file"); const size_t print_limit = get_arg_sz("print-limit"); const size_t bin_limit = get_arg_sz("bin-limit"); - const bool print_context_specific = flag_set("skip-context-specific") == false; + const bool print_context_specific = !flag_set("skip-context-specific"); const size_t max_depth = get_arg_sz("max-depth"); const size_t value_column = 60; @@ -67,7 +69,7 @@ data.swap(file_contents); } - Botan::ASN1_Pretty_Printer printer( + const Botan::ASN1_Pretty_Printer printer( print_limit, bin_limit, print_context_specific, initial_level, value_column, max_depth); printer.print_to_stream(output(), data.data(), data.size()); @@ -92,9 +94,9 @@ } try { - Botan::OID oid(oid_str); + const Botan::OID oid(oid_str); - std::string name = oid.human_name_or_empty(); + const std::string name = oid.human_name_or_empty(); if(name.empty()) { output() << "OID " << oid_str << " is not recognized\n"; } else { @@ -105,13 +107,15 @@ } catch(Botan::Exception&) {} // This throws if the string is not known - Botan::OID oid = Botan::OID::from_string(oid_str); + const Botan::OID oid = Botan::OID::from_string(oid_str); output() << "The string '" << oid_str << "' is associated with OID " << oid.to_string() << "\n"; } }; BOTAN_REGISTER_COMMAND("oid_info", OID_Info); +} // namespace + } // namespace Botan_CLI #endif // BOTAN_HAS_ASN1 diff -Nru botan3-3.7.1+dfsg/src/cli/bcrypt.cpp botan3-3.12.0+dfsg/src/cli/bcrypt.cpp --- botan3-3.7.1+dfsg/src/cli/bcrypt.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/bcrypt.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -12,6 +12,8 @@ namespace Botan_CLI { +namespace { + #if defined(BOTAN_HAS_BCRYPT) class Generate_Bcrypt final : public Command { @@ -55,9 +57,9 @@ const bool ok = Botan::check_bcrypt(password, hash); - output() << "Password is " << (ok ? "valid" : "NOT valid") << std::endl; + output() << "Password is " << (ok ? "valid" : "NOT valid") << "\n"; - if(ok == false) { + if(!ok) { set_return_code(1); } } @@ -67,4 +69,6 @@ #endif // bcrypt +} // namespace + } // namespace Botan_CLI diff -Nru botan3-3.7.1+dfsg/src/cli/cc_enc.cpp botan3-3.12.0+dfsg/src/cli/cc_enc.cpp --- botan3-3.7.1+dfsg/src/cli/cc_enc.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/cc_enc.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,8 +9,10 @@ #if defined(BOTAN_HAS_FPE_FE1) && defined(BOTAN_HAS_PBKDF) + #include #include #include + #include namespace Botan_CLI { @@ -20,7 +22,7 @@ uint8_t sum = 0; bool alt = false; - while(cc_number) { + while(cc_number > 0) { uint8_t digit = cc_number % 10; if(alt) { digit *= 2; @@ -58,11 +60,9 @@ } uint64_t encrypt_cc_number(uint64_t cc_number, const Botan::SymmetricKey& key, const std::vector& tweak) { - const Botan::BigInt n = 1000000000000000; + const Botan::BigInt n(1000000000000000); - const uint64_t cc_ranked = cc_rank(cc_number); - - const Botan::BigInt c = Botan::FPE::fe1_encrypt(n, cc_ranked, key, tweak); + const Botan::BigInt c = Botan::FPE::fe1_encrypt(n, Botan::BigInt::from_u64(cc_rank(cc_number)), key, tweak); if(c.bits() > 50) { throw Botan::Internal_Error("FPE produced a number too large"); @@ -76,11 +76,9 @@ } uint64_t decrypt_cc_number(uint64_t enc_cc, const Botan::SymmetricKey& key, const std::vector& tweak) { - const Botan::BigInt n = 1000000000000000; - - const uint64_t cc_ranked = cc_rank(enc_cc); + const Botan::BigInt n(1000000000000000); - const Botan::BigInt c = Botan::FPE::fe1_decrypt(n, cc_ranked, key, tweak); + const Botan::BigInt c = Botan::FPE::fe1_decrypt(n, Botan::BigInt::from_u64(cc_rank(enc_cc)), key, tweak); if(c.bits() > 50) { throw CLI_Error("FPE produced a number too large"); @@ -93,8 +91,6 @@ return cc_derank(dec_cc); } -} // namespace - class CC_Encrypt final : public Command { public: CC_Encrypt() : Command("cc_encrypt CC passphrase --tweak=") {} @@ -151,6 +147,8 @@ BOTAN_REGISTER_COMMAND("cc_decrypt", CC_Decrypt); +} // namespace + } // namespace Botan_CLI #endif // FPE && PBKDF diff -Nru botan3-3.7.1+dfsg/src/cli/cipher.cpp botan3-3.12.0+dfsg/src/cli/cipher.cpp --- botan3-3.7.1+dfsg/src/cli/cipher.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/cipher.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -11,7 +11,7 @@ #include #include - #include + #include #if defined(BOTAN_HAS_AEAD_MODES) #include @@ -19,6 +19,8 @@ namespace Botan_CLI { +namespace { + class Cipher final : public Command { public: Cipher() : Command("cipher --cipher=AES-256/GCM --decrypt --key= --nonce= --ad= --buf-size=4096 input-file") {} @@ -75,6 +77,8 @@ BOTAN_REGISTER_COMMAND("cipher", Cipher); +} // namespace + } // namespace Botan_CLI #endif diff -Nru botan3-3.7.1+dfsg/src/cli/cli.cpp botan3-3.12.0+dfsg/src/cli/cli.cpp --- botan3-3.7.1+dfsg/src/cli/cli.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/cli.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -176,14 +176,16 @@ return std::cerr; } -std::vector Command::slurp_file(const std::string& input_file, size_t buf_size) const { +//static +std::vector Command::slurp_file(const std::string& input_file, size_t buf_size) { std::vector buf; auto insert_fn = [&](const uint8_t b[], size_t l) { buf.insert(buf.end(), b, b + l); }; Command::read_file(input_file, insert_fn, buf_size); return buf; } -std::string Command::slurp_file_as_str(const std::string& input_file, size_t buf_size) const { +//static +std::string Command::slurp_file_as_str(const std::string& input_file, size_t buf_size) { std::string str; auto insert_fn = [&](const uint8_t b[], size_t l) { str.append(reinterpret_cast(b), l); }; Command::read_file(input_file, insert_fn, buf_size); @@ -255,7 +257,7 @@ } // namespace std::string Command::get_passphrase(const std::string& prompt) { - if(echo_suppression_supported() == false) { + if(!echo_suppression_supported()) { error_output() << "Warning: terminal echo suppression not enabled for this platform\n"; } diff -Nru botan3-3.7.1+dfsg/src/cli/cli.h botan3-3.12.0+dfsg/src/cli/cli.h --- botan3-3.7.1+dfsg/src/cli/cli.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/cli.h 2026-05-07 01:38:28.000000000 +0000 @@ -31,7 +31,7 @@ std::shared_ptr cli_make_rng(const std::string& type = "", const std::string& hex_drbg_seed = ""); -class Command { +class Command /* NOLINT(*special-member-functions) */ { public: /** * Get a registered command @@ -160,9 +160,9 @@ /* * Read an entire file into memory and return the contents */ - std::vector slurp_file(const std::string& input_file, size_t buf_size = 0) const; + static std::vector slurp_file(const std::string& input_file, size_t buf_size = 0); - std::string slurp_file_as_str(const std::string& input_file, size_t buf_size = 0) const; + static std::string slurp_file_as_str(const std::string& input_file, size_t buf_size = 0); /* * Read a file calling consumer_fn() with the inputs @@ -218,8 +218,10 @@ }; }; -#define BOTAN_REGISTER_COMMAND(name, CLI_Class) \ - const Botan_CLI::Command::Registration reg_cmd_##CLI_Class( \ +// NOLINTNEXTLINE(*-macro-usage) +#define BOTAN_REGISTER_COMMAND(name, CLI_Class) \ + /* NOLINTNEXTLINE(cert-err58-cpp,*-throwing-static-initialization) */ \ + const Botan_CLI::Command::Registration reg_cmd_##CLI_Class( \ name, []() -> std::unique_ptr { return std::make_unique(); }) } // namespace Botan_CLI diff -Nru botan3-3.7.1+dfsg/src/cli/cli_exceptions.h botan3-3.12.0+dfsg/src/cli/cli_exceptions.h --- botan3-3.7.1+dfsg/src/cli/cli_exceptions.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/cli_exceptions.h 2026-05-07 01:38:28.000000000 +0000 @@ -32,7 +32,7 @@ */ class CLI_Error_Unsupported final : public CLI_Error { public: - CLI_Error_Unsupported(const std::string& msg) : CLI_Error(msg) {} + explicit CLI_Error_Unsupported(const std::string& msg) : CLI_Error(msg) {} CLI_Error_Unsupported(const std::string& what, const std::string& who) : CLI_Error(what + " with '" + who + "' unsupported or not available") {} diff -Nru botan3-3.7.1+dfsg/src/cli/cli_rng.cpp botan3-3.12.0+dfsg/src/cli/cli_rng.cpp --- botan3-3.7.1+dfsg/src/cli/cli_rng.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/cli_rng.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -18,6 +18,10 @@ #include #endif +#if defined(BOTAN_HAS_JITTER_RNG) + #include +#endif + #if defined(BOTAN_HAS_ESDM_RNG) #include #endif @@ -32,6 +36,7 @@ #if defined(BOTAN_HAS_HMAC_DRBG) #include + #include #endif namespace Botan_CLI { @@ -53,6 +58,12 @@ } #endif +#if defined(BOTAN_HAS_JITTER_RNG) + if(rng_type == "jitter") { + return std::make_shared(); + } +#endif + const std::vector drbg_seed = Botan::hex_decode(hex_drbg_seed); #if defined(BOTAN_HAS_AUTO_SEEDING_RNG) @@ -108,11 +119,13 @@ } } +namespace { + class RNG final : public Command { public: RNG() : Command( - "rng --format=hex --system --esdm-full --esdm-pr --rdrand --auto --entropy --drbg --drbg-seed= *bytes") { + "rng --format=hex --system --esdm-full --esdm-pr --jitter --rdrand --auto --entropy --drbg --drbg-seed= *bytes") { } std::string group() const override { return "misc"; } @@ -124,7 +137,9 @@ std::string type = get_arg("rng-type"); if(type.empty()) { - for(std::string flag : {"system", "rdrand", "auto", "entropy", "drbg", "esdm-full", "esdm-pr"}) { + const std::vector known_rng_types = { + "system", "rdrand", "auto", "entropy", "drbg", "esdm-full", "esdm-pr", "jitter"}; + for(const auto& flag : known_rng_types) { if(flag_set(flag)) { type = flag; break; @@ -150,4 +165,6 @@ BOTAN_REGISTER_COMMAND("rng", RNG); +} // namespace + } // namespace Botan_CLI diff -Nru botan3-3.7.1+dfsg/src/cli/codec.cpp botan3-3.12.0+dfsg/src/cli/codec.cpp --- botan3-3.7.1+dfsg/src/cli/codec.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/codec.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -24,6 +24,8 @@ namespace Botan_CLI { +namespace { + #if defined(BOTAN_HAS_HEX_CODEC) class Hex_Encode final : public Command { @@ -52,8 +54,7 @@ void go() override { auto hex_dec_f = [&](const uint8_t b[], size_t l) { - std::vector bin = Botan::hex_decode(reinterpret_cast(b), l); - write_output(bin); + write_output(Botan::hex_decode(reinterpret_cast(b), l)); }; Command::read_file(get_arg("file"), hex_dec_f, 2); @@ -142,8 +143,7 @@ void go() override { auto write_bin = [&](const uint8_t b[], size_t l) { - Botan::secure_vector bin = Botan::base32_decode(reinterpret_cast(b), l); - write_output(bin); + write_output(Botan::base32_decode(reinterpret_cast(b), l)); }; Command::read_file(get_arg("file"), write_bin, 1024); @@ -182,8 +182,7 @@ void go() override { auto write_bin = [&](const uint8_t b[], size_t l) { - Botan::secure_vector bin = Botan::base64_decode(reinterpret_cast(b), l); - write_output(bin); + write_output(Botan::base64_decode(reinterpret_cast(b), l)); }; Command::read_file(get_arg("file"), write_bin, 1024); @@ -194,4 +193,6 @@ #endif // base64 +} // namespace + } // namespace Botan_CLI diff -Nru botan3-3.7.1+dfsg/src/cli/compress.cpp botan3-3.12.0+dfsg/src/cli/compress.cpp --- botan3-3.7.1+dfsg/src/cli/compress.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/compress.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -19,7 +19,7 @@ public: Compress() : Command("compress --type=gzip --level=6 --buf-size=8192 file") {} - std::string output_filename(const std::string& input_fsname, const std::string& comp_type) { + static std::string output_filename(const std::string& input_fsname, const std::string& comp_type) { const std::map suffixes = { {"zlib", "zlib"}, {"gzip", "gz"}, @@ -89,7 +89,7 @@ public: Decompress() : Command("decompress --buf-size=8192 file") {} - void parse_extension(const std::string& in_file, std::string& out_file, std::string& suffix) { + static void parse_extension(const std::string& in_file, std::string& out_file, std::string& suffix) { auto last_dot = in_file.find_last_of('.'); if(last_dot == std::string::npos || last_dot == 0) { throw CLI_Error("No extension detected in filename '" + in_file + "'"); @@ -106,7 +106,8 @@ void go() override { const size_t buf_size = get_arg_sz("buf-size"); const std::string in_file = get_arg("file"); - std::string out_file, suffix; + std::string out_file; + std::string suffix; parse_extension(in_file, out_file, suffix); std::ifstream in(in_file, std::ios::binary); diff -Nru botan3-3.7.1+dfsg/src/cli/entropy.cpp botan3-3.12.0+dfsg/src/cli/entropy.cpp --- botan3-3.7.1+dfsg/src/cli/entropy.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/entropy.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -4,11 +4,12 @@ * Botan is released under the Simplified BSD License (see license.txt) */ -#include "../tests/test_rng.h" // FIXME #include "cli.h" #if defined(BOTAN_HAS_ENTROPY_SOURCE) #include + #include + #include #endif #if defined(BOTAN_HAS_COMPRESSION) @@ -17,8 +18,39 @@ namespace Botan_CLI { +namespace { + #if defined(BOTAN_HAS_ENTROPY_SOURCE) +class SeedCapturing_RNG final : public Botan::RandomNumberGenerator { + public: + bool accepts_input() const override { return true; } + + void clear() override {} + + bool is_seeded() const override { return false; } + + std::string name() const override { return "SeedCapturing"; } + + size_t samples() const { return m_samples; } + + const std::vector& seed_material() const { return m_seed; } + + private: + void fill_bytes_with_input(std::span output, std::span input) override { + if(!output.empty()) { + throw CLI_Error("SeedCapturing_RNG has no output"); + } + + m_samples++; + m_seed.insert(m_seed.end(), input.begin(), input.end()); + } + + private: + std::vector m_seed; + size_t m_samples = 0; +}; + class Entropy final : public Command { public: Entropy() : Command("entropy --truncate-at=128 source") {} @@ -41,7 +73,7 @@ } for(const std::string& source : sources) { - Botan_Tests::SeedCapturing_RNG rng; + SeedCapturing_RNG rng; const size_t entropy_estimate = entropy_sources.poll_just(rng, source); if(rng.samples() == 0) { @@ -78,7 +110,7 @@ if(sample.size() <= truncate_sample) { output() << Botan::hex_encode(sample) << "\n"; } else if(truncate_sample > 0) { - output() << Botan::hex_encode(&sample[0], truncate_sample) << "...\n"; + output() << Botan::hex_encode(sample.data(), truncate_sample) << "...\n"; } } } @@ -88,4 +120,6 @@ #endif +} // namespace + } // namespace Botan_CLI diff -Nru botan3-3.7.1+dfsg/src/cli/hash.cpp botan3-3.12.0+dfsg/src/cli/hash.cpp --- botan3-3.7.1+dfsg/src/cli/hash.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/hash.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -12,6 +12,8 @@ namespace Botan_CLI { +namespace { + #if defined(BOTAN_HAS_HASH) class Hash final : public Command { @@ -62,4 +64,6 @@ #endif +} // namespace + } // namespace Botan_CLI diff -Nru botan3-3.7.1+dfsg/src/cli/hmac.cpp botan3-3.12.0+dfsg/src/cli/hmac.cpp --- botan3-3.7.1+dfsg/src/cli/hmac.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/hmac.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -15,6 +15,8 @@ namespace Botan_CLI { +namespace { + #if defined(BOTAN_HAS_HMAC) class HMAC final : public Command { @@ -50,7 +52,7 @@ read_file(fsname, update_hmac, buf_size); output() << Botan::hex_encode(hmac->final()); - if(no_fsname == false) { + if(!no_fsname) { output() << " " << fsname; } @@ -66,4 +68,6 @@ #endif // hmac +} // namespace + } // namespace Botan_CLI diff -Nru botan3-3.7.1+dfsg/src/cli/main.cpp botan3-3.12.0+dfsg/src/cli/main.cpp --- botan3-3.7.1+dfsg/src/cli/main.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/main.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -31,6 +31,6 @@ return 1; } - std::vector args(argv + std::min(argc, 2), argv + argc); + const std::vector args(argv + std::min(argc, 2), argv + argc); return cmd->run(args); } diff -Nru botan3-3.7.1+dfsg/src/cli/math.cpp botan3-3.12.0+dfsg/src/cli/math.cpp --- botan3-3.7.1+dfsg/src/cli/math.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/math.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -11,10 +11,12 @@ #include #include #include - #include + #include namespace Botan_CLI { +namespace { + class Modular_Inverse final : public Command { public: Modular_Inverse() : Command("mod_inverse n mod") {} @@ -54,7 +56,7 @@ const Botan::BigInt p = Botan::random_prime(rng(), bits); if(hex) { - output() << "0x" << std::hex << p << "\n"; + output() << std::hex << p << "\n"; } else { output() << p << "\n"; } @@ -73,7 +75,7 @@ std::string description() const override { return "Test if the integer n is composite or prime"; } void go() override { - Botan::BigInt n(get_arg("n")); + const Botan::BigInt n(get_arg("n")); const size_t prob = get_arg_sz("prob"); const bool prime = Botan::is_prime(n, rng(), prob); @@ -96,7 +98,7 @@ std::string description() const override { return "Factor a given integer"; } void go() override { - Botan::BigInt n(get_arg("n")); + const Botan::BigInt n(get_arg("n")); std::vector factors = factorize(n, rng()); std::sort(factors.begin(), factors.end()); @@ -119,7 +121,7 @@ break; } - Botan::BigInt a_factor = 0; + Botan::BigInt a_factor; while(a_factor == 0) { a_factor = rho(n, rng); } @@ -140,11 +142,13 @@ * Uses Brent's cycle finding */ static Botan::BigInt rho(const Botan::BigInt& n, Botan::RandomNumberGenerator& rng) { - auto monty_n = std::make_shared(n); + const Botan::Montgomery_Params monty_n(n); - const Botan::Montgomery_Int one(monty_n, monty_n->R1(), false); + const auto one = Botan::Montgomery_Int::one(monty_n); - Botan::Montgomery_Int x(monty_n, Botan::BigInt::random_integer(rng, 2, n - 3), false); + const auto two = Botan::BigInt::from_s32(2); + const auto three = Botan::BigInt::from_s32(3); + Botan::Montgomery_Int x(monty_n, Botan::BigInt::random_integer(rng, two, n - three), false); Botan::Montgomery_Int y = x; Botan::Montgomery_Int z = one; Botan::Montgomery_Int t(monty_n); @@ -152,7 +156,8 @@ Botan::secure_vector ws; - size_t i = 1, k = 2; + size_t i = 1; + size_t k = 2; while(true) { i++; @@ -162,11 +167,10 @@ break; } - x.square_this(ws); // x = x^2 - x.add(one, ws); + x.square_this_n_times(ws, 1); // x = x^2 + x = x + one; - t = y; - t.sub(x, ws); + t = y - x; z.mul_by(t, ws); @@ -191,7 +195,7 @@ } // failed - return 0; + return Botan::BigInt::zero(); } // Remove (and return) any small (< 2^16) factors @@ -199,12 +203,12 @@ std::vector factors; while(n.is_even()) { - factors.push_back(2); - n /= 2; + factors.push_back(Botan::BigInt::from_s32(2)); + n >>= 1; } for(size_t j = 0; j != Botan::PRIME_TABLE_SIZE; j++) { - uint16_t prime = Botan::PRIMES[j]; + auto prime = Botan::BigInt::from_s32(Botan::PRIMES[j]); if(n < prime) { break; } @@ -227,6 +231,8 @@ BOTAN_REGISTER_COMMAND("factor", Factor); +} // namespace + } // namespace Botan_CLI #endif diff -Nru botan3-3.7.1+dfsg/src/cli/pbkdf.cpp botan3-3.12.0+dfsg/src/cli/pbkdf.cpp --- botan3-3.7.1+dfsg/src/cli/pbkdf.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/pbkdf.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -16,6 +16,8 @@ namespace Botan_CLI { +namespace { + #if defined(BOTAN_HAS_PASSWORD_HASHING) class PBKDF_Tune final : public Command { @@ -29,7 +31,7 @@ void go() override { const size_t output_len = get_arg_sz("output-len"); const size_t max_mem = get_arg_sz("max-mem"); - const auto tune_msec = std::chrono::milliseconds(get_arg_sz("tune-msec")); + const size_t tune_msec = get_arg_sz("tune-msec"); const std::string algo = get_arg("algo"); const bool check_time = flag_set("check"); @@ -45,14 +47,14 @@ if(time == "default") { pwhash = pwdhash_fam->default_params(); } else { - size_t msec = 0; + size_t desired_runtime_msec = 0; try { - msec = std::stoul(time); + desired_runtime_msec = std::stoul(time); } catch(std::exception&) { throw CLI_Usage_Error("Unknown time value '" + time + "' for pbkdf_tune"); } - pwhash = pwdhash_fam->tune(output_len, std::chrono::milliseconds(msec), max_mem, tune_msec); + pwhash = pwdhash_fam->tune_params(output_len, desired_runtime_msec, max_mem, tune_msec); } output() << "For " << time << " ms selected " << pwhash->to_string(); @@ -71,7 +73,7 @@ const uint64_t end_ns = Botan::OS::get_system_timestamp_ns(); const uint64_t dur_ns = end_ns - start_ns; - output() << " took " << (dur_ns / 1000000.0) << " msec to compute"; + output() << " took " << (static_cast(dur_ns) / 1000000.0) << " msec to compute"; #else output() << "No system clock"; #endif @@ -86,4 +88,6 @@ #endif +} // namespace + } // namespace Botan_CLI diff -Nru botan3-3.7.1+dfsg/src/cli/perf.cpp botan3-3.12.0+dfsg/src/cli/perf.cpp --- botan3-3.7.1+dfsg/src/cli/perf.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/perf.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -5,6 +5,7 @@ */ #include "perf.h" + #include "cli_exceptions.h" #include @@ -45,7 +46,7 @@ if(param.starts_with(alg)) { return param; } - return Botan::fmt("{}-{}", alg, param); + return alg + "-" + param; } } // namespace Botan_CLI diff -Nru botan3-3.7.1+dfsg/src/cli/perf.h botan3-3.12.0+dfsg/src/cli/perf.h --- botan3-3.7.1+dfsg/src/cli/perf.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/perf.h 2026-05-07 01:38:28.000000000 +0000 @@ -7,16 +7,21 @@ #ifndef BOTAN_CLI_PERF_H_ #define BOTAN_CLI_PERF_H_ -#include -#include -#include #include #include #include +#include #include +#include #include "timer.h" +namespace Botan { + +class RandomNumberGenerator; + +} + namespace Botan_CLI { class PerfConfig final { @@ -24,7 +29,7 @@ PerfConfig(std::function record_result, size_t clock_speed, double clock_cycle_ratio, - std::chrono::milliseconds runtime, + uint64_t runtime, const std::vector& ecc_groups, const std::vector& buffer_sizes, std::ostream& error_output, @@ -42,7 +47,7 @@ const std::vector& ecc_groups() const { return m_ecc_groups; } - std::chrono::milliseconds runtime() const { return m_runtime; } + uint64_t runtime() const { return m_runtime; } std::ostream& error_output() const { return m_error_output; } @@ -62,14 +67,14 @@ std::function m_record_result; size_t m_clock_speed = 0; double m_clock_cycle_ratio = 0.0; - std::chrono::milliseconds m_runtime; + uint64_t m_runtime; std::vector m_ecc_groups; std::vector m_buffer_sizes; std::ostream& m_error_output; Botan::RandomNumberGenerator& m_rng; }; -class PerfTest { +class PerfTest /* NOLINT(*-special-member-functions) */ { public: virtual ~PerfTest() = default; @@ -93,8 +98,10 @@ static std::map& global_registry(); }; -#define BOTAN_REGISTER_PERF_TEST(name, Perf_Class) \ - const Botan_CLI::PerfTest::Registration reg_perf_##Perf_Class( \ +// NOLINTNEXTLINE(*-macro-usage) +#define BOTAN_REGISTER_PERF_TEST(name, Perf_Class) \ + /* NOLINTNEXTLINE(cert-err58-cpp,bugprone-throwing-static-initialization) */ \ + const Botan_CLI::PerfTest::Registration reg_perf_##Perf_Class( \ name, []() -> std::unique_ptr { return std::make_unique(); }) } // namespace Botan_CLI diff -Nru botan3-3.7.1+dfsg/src/cli/perf_ec.cpp botan3-3.12.0+dfsg/src/cli/perf_ec.cpp --- botan3-3.7.1+dfsg/src/cli/perf_ec.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/perf_ec.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,102 +7,258 @@ #include "perf.h" #if defined(BOTAN_HAS_ECC_GROUP) + #include #include + #include #endif namespace Botan_CLI { +namespace { + #if defined(BOTAN_HAS_ECC_GROUP) -class PerfTest_EllipticCurve final : public PerfTest { +class PerfTest_EllipticCurve_Mul final : public PerfTest { public: void go(const PerfConfig& config) override { const auto run = config.runtime(); auto& rng = config.rng(); for(const auto& group_name : config.ecc_groups()) { - auto init_timer = config.make_timer(group_name + " initialization"); + const auto group = Botan::EC_Group::from_name(group_name); - while(init_timer->under(run)) { - Botan::EC_Group::clear_registered_curve_data(); - init_timer->run([&]() { Botan::EC_Group::from_name(group_name); }); + auto bp_timer = config.make_timer(group_name + " blinded base point mul"); + auto bp_nb_timer = config.make_timer(group_name + " unblinded base point mul"); + + auto vp_timer = config.make_timer(group_name + " blinded variable point mul"); + auto vp_nb_timer = config.make_timer(group_name + " unblinded variable point mul"); + + auto g = Botan::EC_AffinePoint::generator(group); + + Botan::Null_RNG null_rng; + + while(bp_timer->under(run) && vp_timer->under(run)) { + const auto k = Botan::EC_Scalar::random(group, rng); + + const auto r1 = bp_timer->run([&]() { return Botan::EC_AffinePoint::g_mul(k, rng); }); + const auto r2 = vp_timer->run([&]() { return g.mul(k, rng); }); + const auto r3 = bp_nb_timer->run([&]() { return Botan::EC_AffinePoint::g_mul(k, null_rng); }); + const auto r4 = vp_nb_timer->run([&]() { return g.mul(k, null_rng); }); + + BOTAN_ASSERT_NOMSG(r1 == r2); + BOTAN_ASSERT_NOMSG(r1 == r3); + BOTAN_ASSERT_NOMSG(r1 == r4); } - config.record_result(*init_timer); + config.record_result(*bp_timer); + config.record_result(*bp_nb_timer); + config.record_result(*vp_timer); + config.record_result(*vp_nb_timer); + } + } +}; + +BOTAN_REGISTER_PERF_TEST("ecc_mul", PerfTest_EllipticCurve_Mul); + +class PerfTest_EllipticCurve_Mul2 final : public PerfTest { + public: + void go(const PerfConfig& config) override { + const auto run = config.runtime(); + auto& rng = config.rng(); + for(const auto& group_name : config.ecc_groups()) { const auto group = Botan::EC_Group::from_name(group_name); - auto bp_timer = config.make_timer(group_name + " base point mul"); - auto vp_timer = config.make_timer(group_name + " variable point mul"); - auto add_timer = config.make_timer(group_name + " point addition"); - auto der_uc_timer = config.make_timer(group_name + " point deserialize (uncompressed)"); - auto der_c_timer = config.make_timer(group_name + " point deserialize (compressed)"); - auto mul2_setup_timer = config.make_timer(group_name + " mul2 setup"); - auto mul2_timer = config.make_timer(group_name + " mul2"); - auto scalar_inv_timer = config.make_timer(group_name + " scalar inversion"); - auto h2c_nu_timer = config.make_timer(group_name + " hash to curve (NU)"); - auto h2c_ro_timer = config.make_timer(group_name + " hash to curve (RO)"); + auto mul2_setup_timer = config.make_timer(group_name + " mul2_vartime setup"); + auto mul2_vt_timer = config.make_timer(group_name + " mul2_vartime"); + auto mul2_ct_timer = config.make_timer(group_name + " blinded mul2"); + auto mul2_ct_nb_timer = config.make_timer(group_name + " unblinded mul2"); - std::vector ws; + Botan::Null_RNG null_rng; auto g = Botan::EC_AffinePoint::generator(group); + while(mul2_setup_timer->under(run) && mul2_ct_timer->under(run)) { + const auto k = Botan::EC_Scalar::random(group, rng); + const auto k2 = Botan::EC_Scalar::random(group, rng); + + const auto y = Botan::EC_AffinePoint::g_mul(Botan::EC_Scalar::random(group, rng), rng); + + auto mul2 = mul2_setup_timer->run([&]() { return Botan::EC_Group::Mul2Table(y); }); + + auto pt = mul2_vt_timer->run([&]() { return mul2.mul2_vartime(k, k2); }); + + auto pt2 = mul2_ct_timer->run([&]() { return Botan::EC_AffinePoint::mul_px_qy(g, k, y, k2, rng); }); + + auto pt3 = + mul2_ct_nb_timer->run([&]() { return Botan::EC_AffinePoint::mul_px_qy(g, k, y, k2, null_rng); }); + + BOTAN_ASSERT_NOMSG(pt == pt2); + BOTAN_ASSERT_NOMSG(pt == pt3); + } + + config.record_result(*mul2_setup_timer); + config.record_result(*mul2_vt_timer); + config.record_result(*mul2_ct_timer); + config.record_result(*mul2_ct_nb_timer); + } + } +}; + +BOTAN_REGISTER_PERF_TEST("ecc_mul2", PerfTest_EllipticCurve_Mul2); + +class PerfTest_EllipticCurve_H2C final : public PerfTest { + public: + void go(const PerfConfig& config) override { + const auto run = config.runtime(); + auto& rng = config.rng(); + + for(const auto& group_name : config.ecc_groups()) { + const auto group = Botan::EC_Group::from_name(group_name); + const bool h2c_supported = [&]() { try { - Botan::EC_AffinePoint::hash_to_curve_nu(group, "SHA-256", {}, {}); + Botan::EC_AffinePoint::hash_to_curve_nu(group, "SHA-256", {}, ""); } catch(Botan::Not_Implemented&) { return false; } return true; }(); - while(bp_timer->under(run) && vp_timer->under(run)) { - const auto k = Botan::EC_Scalar::random(group, rng); - const auto r1 = bp_timer->run([&]() { return Botan::EC_AffinePoint::g_mul(k, rng, ws); }); - const auto r2 = vp_timer->run([&]() { return g.mul(k, rng, ws); }); + if(!h2c_supported) { + continue; + } + + auto h2c_nu_timer = config.make_timer(group_name + " hash to curve (NU)"); + auto h2c_ro_timer = config.make_timer(group_name + " hash to curve (RO)"); + + std::vector input(32); + + while(h2c_ro_timer->under(run)) { + rng.randomize(input); + h2c_nu_timer->run([&]() { Botan::EC_AffinePoint::hash_to_curve_nu(group, "SHA-256", input, "domain"); }); + h2c_ro_timer->run([&]() { Botan::EC_AffinePoint::hash_to_curve_ro(group, "SHA-256", input, "domain"); }); + } + + config.record_result(*h2c_nu_timer); + config.record_result(*h2c_ro_timer); + } + } +}; + +BOTAN_REGISTER_PERF_TEST("ecc_h2c", PerfTest_EllipticCurve_H2C); + +class PerfTest_EllipticCurve_Misc final : public PerfTest { + public: + void go(const PerfConfig& config) override { + const auto run = config.runtime(); + auto& rng = config.rng(); + + for(const auto& group_name : config.ecc_groups()) { + auto init_timer = config.make_timer(group_name + " initialization"); + + while(init_timer->under(run)) { + Botan::EC_Group::clear_registered_curve_data(); + init_timer->run([&]() { Botan::EC_Group::from_name(group_name); }); + } + + config.record_result(*init_timer); + + const auto group = Botan::EC_Group::from_name(group_name); + + auto pt_add_timer = config.make_timer(group_name + " point addition"); + auto pt_neg_timer = config.make_timer(group_name + " point negation"); + auto der_uc_timer = config.make_timer(group_name + " point deserialize (uncompressed)"); + auto der_c_timer = config.make_timer(group_name + " point deserialize (compressed)"); + + while(pt_add_timer->under(run) && der_c_timer->under(run)) { + const auto r1 = Botan::EC_AffinePoint::g_mul(Botan::EC_Scalar::random(group, rng), rng); + const auto r2 = Botan::EC_AffinePoint::g_mul(Botan::EC_Scalar::random(group, rng), rng); const auto r1_bytes = r1.serialize_uncompressed(); const auto r2_bytes = r2.serialize_uncompressed(); - BOTAN_ASSERT_EQUAL(r1_bytes, r2_bytes, "Same result for multiplication"); - add_timer->run([&]() { r1.add(r2); }); + pt_add_timer->run([&]() { r1.add(r2); }); + pt_neg_timer->run([&]() { return r1.negate(); }); der_uc_timer->run([&]() { Botan::EC_AffinePoint::deserialize(group, r1_bytes); }); + der_uc_timer->run([&]() { Botan::EC_AffinePoint::deserialize(group, r2_bytes); }); const auto r1_cbytes = r1.serialize_compressed(); + const auto r2_cbytes = r2.serialize_compressed(); der_c_timer->run([&]() { Botan::EC_AffinePoint::deserialize(group, r1_cbytes); }); + der_c_timer->run([&]() { Botan::EC_AffinePoint::deserialize(group, r2_cbytes); }); + } + + config.record_result(*pt_add_timer); + config.record_result(*pt_neg_timer); + config.record_result(*der_uc_timer); + config.record_result(*der_c_timer); + } + } +}; - auto mul2 = mul2_setup_timer->run([&]() { return Botan::EC_Group::Mul2Table(r1); }); +BOTAN_REGISTER_PERF_TEST("ecc_misc", PerfTest_EllipticCurve_Misc); + +class PerfTest_EllipticCurve_Scalar final : public PerfTest { + public: + void go(const PerfConfig& config) override { + const auto run = config.runtime(); + auto& rng = config.rng(); - auto k_inv = scalar_inv_timer->run([&]() { return k.invert(); }); + for(const auto& group_name : config.ecc_groups()) { + const auto group = Botan::EC_Group::from_name(group_name); - auto pt = mul2_timer->run([&]() { return mul2.mul2_vartime(k, k_inv); }); + auto scalar_add_timer = config.make_timer(group_name + " scalar add"); + auto scalar_mul_timer = config.make_timer(group_name + " scalar mul"); + auto scalar_redc_timer = config.make_timer(group_name + " scalar redc"); + auto scalar_inv_timer = config.make_timer(group_name + " scalar inversion"); + auto scalar_inv_vt_timer = config.make_timer(group_name + " scalar inversion vartime"); - if(h2c_supported) { - h2c_nu_timer->run([&]() { Botan::EC_AffinePoint::hash_to_curve_nu(group, "SHA-256", r1_bytes, {}); }); - h2c_ro_timer->run([&]() { Botan::EC_AffinePoint::hash_to_curve_ro(group, "SHA-256", r1_bytes, {}); }); - } + while(scalar_inv_timer->under(run)) { + const auto rnd1 = rng.random_vec(group.get_order_bytes() * 2); + const auto rnd2 = rng.random_vec(group.get_order_bytes() * 2); + + const auto s1 = + scalar_redc_timer->run([&]() { return Botan::EC_Scalar::from_bytes_mod_order(group, rnd1); }); + const auto s2 = + scalar_redc_timer->run([&]() { return Botan::EC_Scalar::from_bytes_mod_order(group, rnd2); }); + + const auto sum1 = scalar_add_timer->run([&]() { return s1 + s2; }); + const auto sum2 = scalar_add_timer->run([&]() { return s2 + s1; }); + BOTAN_ASSERT_NOMSG(sum1 == sum2); + + const auto s1_inv = scalar_inv_timer->run([&]() { return s1.invert(); }); + const auto s1_inv_vt = scalar_inv_vt_timer->run([&]() { return s1.invert_vartime(); }); + BOTAN_ASSERT_NOMSG(s1_inv == s1_inv_vt); + + const auto s2_inv = scalar_inv_timer->run([&]() { return s2.invert(); }); + const auto s2_inv_vt = scalar_inv_vt_timer->run([&]() { return s2.invert_vartime(); }); + BOTAN_ASSERT_NOMSG(s2_inv == s2_inv_vt); + + const auto p1 = scalar_mul_timer->run([&]() { return s1 * s2; }); + const auto p2 = scalar_mul_timer->run([&]() { return s2 * s1; }); + BOTAN_ASSERT_NOMSG(p1 == p2); + + const auto c1 = scalar_mul_timer->run([&]() { return p1 * s1_inv; }); + BOTAN_ASSERT_NOMSG(c1 == s2); + const auto c2 = scalar_mul_timer->run([&]() { return p2 * s1_inv_vt; }); + BOTAN_ASSERT_NOMSG(c2 == s2); } - config.record_result(*add_timer); - config.record_result(*bp_timer); - config.record_result(*vp_timer); - config.record_result(*mul2_setup_timer); - config.record_result(*mul2_timer); + config.record_result(*scalar_add_timer); + config.record_result(*scalar_mul_timer); + config.record_result(*scalar_redc_timer); config.record_result(*scalar_inv_timer); - config.record_result(*der_uc_timer); - config.record_result(*der_c_timer); - - if(h2c_supported) { - config.record_result(*h2c_nu_timer); - config.record_result(*h2c_ro_timer); - } + config.record_result(*scalar_inv_vt_timer); } } }; -BOTAN_REGISTER_PERF_TEST("ecc", PerfTest_EllipticCurve); +BOTAN_REGISTER_PERF_TEST("ecc_scalar", PerfTest_EllipticCurve_Scalar); #endif +} // namespace + } // namespace Botan_CLI diff -Nru botan3-3.7.1+dfsg/src/cli/perf_math.cpp botan3-3.12.0+dfsg/src/cli/perf_math.cpp --- botan3-3.7.1+dfsg/src/cli/perf_math.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/perf_math.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,14 +6,18 @@ #include "perf.h" +#include + #if defined(BOTAN_HAS_BIGINT) + #include #include #include #endif #if defined(BOTAN_HAS_NUMBERTHEORY) #include - #include + #include + #include #include #endif @@ -23,14 +27,16 @@ namespace Botan_CLI { +namespace { + #if defined(BOTAN_HAS_BIGINT) class PerfTest_MpMul final : public PerfTest { public: void go(const PerfConfig& config) override { - std::chrono::milliseconds runtime_per_size = config.runtime(); + const auto runtime_per_size = config.runtime(); - for(size_t bits : {256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096}) { + for(const size_t bits : {256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096}) { auto mul_timer = config.make_timer("BigInt mul " + std::to_string(bits)); auto sqr_timer = config.make_timer("BigInt sqr " + std::to_string(bits)); @@ -62,9 +68,9 @@ class PerfTest_MpDiv final : public PerfTest { public: void go(const PerfConfig& config) override { - std::chrono::milliseconds runtime_per_size = config.runtime(); + const auto runtime_per_size = config.runtime(); - for(size_t n_bits : {256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096}) { + for(const size_t n_bits : {256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096}) { const size_t q_bits = n_bits / 2; const std::string bit_descr = std::to_string(n_bits) + "/" + std::to_string(q_bits); @@ -73,9 +79,12 @@ Botan::BigInt y; Botan::BigInt x; - Botan::secure_vector ws; + const Botan::secure_vector ws; - Botan::BigInt q1, r1, q2, r2; + Botan::BigInt q1; + Botan::BigInt r1; + Botan::BigInt q2; + Botan::BigInt r2; while(ct_div_timer->under(runtime_per_size)) { x.randomize(config.rng(), n_bits); @@ -104,20 +113,22 @@ class PerfTest_MpDiv10 final : public PerfTest { public: void go(const PerfConfig& config) override { - std::chrono::milliseconds runtime_per_size = config.runtime(); + const auto runtime_per_size = config.runtime(); - for(size_t n_bits : {256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096}) { + for(const size_t n_bits : {256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096}) { const std::string bit_descr = std::to_string(n_bits) + "/10"; auto div_timer = config.make_timer("BigInt div " + bit_descr); auto ct_div_timer = config.make_timer("BigInt ct_div " + bit_descr); Botan::BigInt x; - Botan::secure_vector ws; + const Botan::secure_vector ws; const auto ten = Botan::BigInt::from_word(10); - Botan::BigInt q1, r1, q2; - Botan::word r2; + Botan::BigInt q1; + Botan::BigInt r1; + Botan::BigInt q2; + Botan::word r2 = 0; while(ct_div_timer->under(runtime_per_size)) { x.randomize(config.rng(), n_bits); @@ -151,22 +162,22 @@ void go(const PerfConfig& config) override { const auto runtime = config.runtime(); - for(size_t bitsize : {512, 1024, 2048, 4096}) { + for(const size_t bitsize : {256, 512, 1024, 2048, 4096}) { Botan::BigInt p(config.rng(), bitsize); - std::string bit_str = std::to_string(bitsize) + " bit "; + const std::string bit_str = std::to_string(bitsize) + " bit "; auto barrett_setup_pub_timer = config.make_timer(bit_str + "Barrett setup public"); auto barrett_setup_sec_timer = config.make_timer(bit_str + "Barrett setup secret"); while(barrett_setup_sec_timer->under(runtime)) { - barrett_setup_sec_timer->run([&]() { Botan::Modular_Reducer::for_secret_modulus(p); }); - barrett_setup_pub_timer->run([&]() { Botan::Modular_Reducer::for_public_modulus(p); }); + barrett_setup_sec_timer->run([&]() { Botan::Barrett_Reduction::for_secret_modulus(p); }); + barrett_setup_pub_timer->run([&]() { Botan::Barrett_Reduction::for_public_modulus(p); }); } config.record_result(*barrett_setup_pub_timer); config.record_result(*barrett_setup_sec_timer); - auto mod_p = Botan::Modular_Reducer::for_public_modulus(p); + auto mod_p = Botan::Barrett_Reduction::for_public_modulus(p); auto barrett_timer = config.make_timer(bit_str + "Barrett redc"); auto knuth_timer = config.make_timer(bit_str + "Knuth redc"); @@ -197,7 +208,7 @@ void go(const PerfConfig& config) override { const auto runtime = config.runtime(); - for(size_t bits : {256, 384, 512, 1024, 2048}) { + for(const size_t bits : {256, 384, 512, 1024, 2048}) { const std::string bit_str = std::to_string(bits); auto timer = config.make_timer("inverse_mod-" + bit_str); @@ -233,7 +244,7 @@ void go(const PerfConfig& config) override { const auto runtime = config.runtime(); - for(size_t bits : {256, 512, 1024}) { + for(const size_t bits : {256, 512, 1024}) { auto mr_timer = config.make_timer("Miller-Rabin-" + std::to_string(bits)); auto bpsw_timer = config.make_timer("Bailie-PSW-" + std::to_string(bits)); auto lucas_timer = config.make_timer("Lucas-" + std::to_string(bits)); @@ -241,9 +252,11 @@ Botan::BigInt n = Botan::random_prime(config.rng(), bits); while(lucas_timer->under(runtime)) { - auto mod_n = Botan::Modular_Reducer::for_public_modulus(n); + auto mod_n = Botan::Barrett_Reduction::for_public_modulus(n); + const Botan::Montgomery_Params monty_n(n, mod_n); - mr_timer->run([&]() { return Botan::is_miller_rabin_probable_prime(n, mod_n, config.rng(), 2); }); + mr_timer->run( + [&]() { return Botan::is_miller_rabin_probable_prime(n, mod_n, monty_n, config.rng(), 2); }); bpsw_timer->run([&]() { return Botan::is_bailie_psw_probable_prime(n, mod_n); }); @@ -271,26 +284,15 @@ for(size_t bits : {256, 384, 512, 768, 1024, 1536}) { auto genprime_timer = config.make_timer("random_prime " + std::to_string(bits)); - auto gensafe_timer = config.make_timer("random_safe_prime " + std::to_string(bits)); auto is_prime_timer = config.make_timer("is_prime " + std::to_string(bits)); - while(gensafe_timer->under(runtime)) { + while(genprime_timer->under(runtime) && is_prime_timer->under(runtime)) { const Botan::BigInt p = genprime_timer->run([&] { return Botan::random_prime(rng, bits, coprime); }); if(!is_prime_timer->run([&] { return Botan::is_prime(p, rng, 64, true); })) { config.error_output() << "Generated prime " << p << " which failed a primality test"; } - const Botan::BigInt sg = gensafe_timer->run([&] { return Botan::random_safe_prime(rng, bits); }); - - if(!is_prime_timer->run([&] { return Botan::is_prime(sg, rng, 64, true); })) { - config.error_output() << "Generated safe prime " << sg << " which failed a primality test"; - } - - if(!is_prime_timer->run([&] { return Botan::is_prime(sg / 2, rng, 64, true); })) { - config.error_output() << "Generated prime " << sg / 2 << " which failed a primality test"; - } - // Now test p+2, p+4, ... which may or may not be prime for(size_t i = 2; i <= 64; i += 2) { is_prime_timer->run([&]() { Botan::is_prime(p + i, rng, 64, true); }); @@ -298,7 +300,6 @@ } config.record_result(*genprime_timer); - config.record_result(*gensafe_timer); config.record_result(*is_prime_timer); } } @@ -313,7 +314,7 @@ class PerfTest_ModExp final : public PerfTest { public: void go(const PerfConfig& config) override { - for(size_t group_bits : {1024, 1536, 2048, 3072, 4096, 6144, 8192}) { + for(const size_t group_bits : {1024, 1536, 2048, 3072, 4096, 6144, 8192}) { const std::string group_name = "modp/ietf/" + std::to_string(group_bits); auto group = Botan::DL_Group::from_name(group_name); @@ -341,4 +342,6 @@ #endif +} // namespace + } // namespace Botan_CLI diff -Nru botan3-3.7.1+dfsg/src/cli/perf_misc.cpp botan3-3.12.0+dfsg/src/cli/perf_misc.cpp --- botan3-3.7.1+dfsg/src/cli/perf_misc.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/perf_misc.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,7 +8,18 @@ #include // Always available: +#include #include +#include +#include + +#if defined(BOTAN_HAS_BASE32_CODEC) + #include +#endif + +#if defined(BOTAN_HAS_BASE58_CODEC) + #include +#endif #if defined(BOTAN_HAS_BASE64_CODEC) #include @@ -28,10 +39,12 @@ namespace Botan_CLI { +namespace { + class PerfTest_Hex final : public PerfTest { public: void go(const PerfConfig& config) override { - for(size_t buf_size : config.buffer_sizes()) { + for(const size_t buf_size : config.buffer_sizes()) { std::vector ibuf(buf_size); std::vector rbuf(buf_size); const size_t olen = 2 * buf_size; @@ -59,11 +72,45 @@ BOTAN_REGISTER_PERF_TEST("hex", PerfTest_Hex); +#if defined(BOTAN_HAS_BASE32_CODEC) +class PerfTest_Base32 final : public PerfTest { + public: + void go(const PerfConfig& config) override { + for(const size_t buf_size : config.buffer_sizes()) { + std::vector ibuf(buf_size); + std::vector rbuf(buf_size); + const size_t olen = Botan::base32_encode_max_output(ibuf.size()); + + auto enc_timer = config.make_timer("base32", ibuf.size(), "encode", "", ibuf.size()); + + auto dec_timer = config.make_timer("base32", olen, "decode", "", olen); + + const auto msec = config.runtime(); + + while(enc_timer->under(msec) && dec_timer->under(msec)) { + config.rng().randomize(ibuf); + + std::string b32 = enc_timer->run([&]() { return Botan::base32_encode(ibuf); }); + + dec_timer->run([&]() { Botan::base32_decode(rbuf.data(), b32); }); + BOTAN_ASSERT(rbuf == ibuf, "Encode/decode round trip ok"); + } + + config.record_result(*enc_timer); + config.record_result(*dec_timer); + } + } +}; + +BOTAN_REGISTER_PERF_TEST("base32", PerfTest_Base32); + +#endif + #if defined(BOTAN_HAS_BASE64_CODEC) class PerfTest_Base64 final : public PerfTest { public: void go(const PerfConfig& config) override { - for(size_t buf_size : config.buffer_sizes()) { + for(const size_t buf_size : config.buffer_sizes()) { std::vector ibuf(buf_size); std::vector rbuf(buf_size); const size_t olen = Botan::base64_encode_max_output(ibuf.size()); @@ -93,6 +140,36 @@ #endif +#if defined(BOTAN_HAS_BASE58_CODEC) +class PerfTest_Base58 final : public PerfTest { + public: + void go(const PerfConfig& config) override { + for(const size_t buf_size : config.buffer_sizes()) { + std::vector ibuf(buf_size); + + auto enc_timer = config.make_timer("base58", ibuf.size(), "encode", "", ibuf.size()); + auto dec_timer = config.make_timer("base58", ibuf.size(), "decode", "", ibuf.size()); + + const auto msec = config.runtime(); + + while(enc_timer->under(msec) && dec_timer->under(msec)) { + config.rng().randomize(ibuf); + + const std::string b58 = enc_timer->run([&]() { return Botan::base58_encode(ibuf); }); + const auto rbuf = dec_timer->run([&] { return Botan::base58_decode(b58); }); + BOTAN_ASSERT(rbuf == ibuf, "Encode/decode round trip ok"); + } + + config.record_result(*enc_timer); + config.record_result(*dec_timer); + } + } +}; + +BOTAN_REGISTER_PERF_TEST("base58", PerfTest_Base58); + +#endif + #if defined(BOTAN_HAS_FPE_FE1) class PerfTest_FpeFe1 final : public PerfTest { @@ -230,4 +307,6 @@ #endif +} // namespace + } // namespace Botan_CLI diff -Nru botan3-3.7.1+dfsg/src/cli/perf_pk_enc.cpp botan3-3.12.0+dfsg/src/cli/perf_pk_enc.cpp --- botan3-3.7.1+dfsg/src/cli/perf_pk_enc.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/perf_pk_enc.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,20 +9,21 @@ #if defined(BOTAN_HAS_PUBLIC_KEY_CRYPTO) #include #include + #include + #include #endif namespace Botan_CLI { +namespace { + #if defined(BOTAN_HAS_PUBLIC_KEY_CRYPTO) class PerfTest_PKEnc : public PerfTest { public: virtual std::string algo() const = 0; - virtual std::vector keygen_params(const PerfConfig& config) const { - BOTAN_UNUSED(config); - return {""}; - } + virtual std::vector keygen_params(const PerfConfig& /*config*/) const { return {""}; } void go(const PerfConfig& config) override { const std::string alg = this->algo(); @@ -35,15 +36,16 @@ } } - void bench_pk_ka(const PerfConfig& config, - const std::string& nm, - const std::string& algo, - const std::string& params, - const std::string& provider = "") { + static void bench_pk_ka(const PerfConfig& config, + const std::string& nm, + const std::string& algo, + const std::string& params, + const std::string& provider = "") { auto& rng = config.rng(); const auto msec = config.runtime(); - std::vector plaintext, ciphertext; + std::vector plaintext; + std::vector ciphertext; auto keygen_timer = config.make_timer(nm, 1, "keygen"); @@ -97,8 +99,7 @@ public: std::string algo() const override { return "ElGamal"; } - std::vector keygen_params(const PerfConfig& config) const override { - BOTAN_UNUSED(config); + std::vector keygen_params(const PerfConfig& /*config*/) const override { return { "modp/ietf/1024", "modp/ietf/2048", @@ -116,4 +117,6 @@ #endif +} // namespace + } // namespace Botan_CLI diff -Nru botan3-3.7.1+dfsg/src/cli/perf_pk_ka.cpp botan3-3.12.0+dfsg/src/cli/perf_pk_ka.cpp --- botan3-3.7.1+dfsg/src/cli/perf_pk_ka.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/perf_pk_ka.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,20 +9,20 @@ #if defined(BOTAN_HAS_PUBLIC_KEY_CRYPTO) #include #include + #include #endif namespace Botan_CLI { +namespace { + #if defined(BOTAN_HAS_PUBLIC_KEY_CRYPTO) class PerfTest_PKKa : public PerfTest { public: virtual std::string algo() const = 0; - virtual std::vector keygen_params(const PerfConfig& config) const { - BOTAN_UNUSED(config); - return {""}; - } + virtual std::vector keygen_params(const PerfConfig& /*config*/) const { return {""}; } void go(const PerfConfig& config) override { const std::string alg = this->algo(); @@ -35,11 +35,11 @@ } } - void bench_pk_ka(const PerfConfig& config, - const std::string& nm, - const std::string& algo, - const std::string& params, - const std::string& provider = "") { + static void bench_pk_ka(const PerfConfig& config, + const std::string& nm, + const std::string& algo, + const std::string& params, + const std::string& provider = "") { const auto msec = config.runtime(); const std::string kdf = "KDF2(SHA-256)"; // arbitrary choice @@ -91,8 +91,7 @@ public: std::string algo() const override { return "DH"; } - std::vector keygen_params(const PerfConfig& config) const override { - BOTAN_UNUSED(config); + std::vector keygen_params(const PerfConfig& /*config*/) const override { return { "ffdhe/ietf/2048", "ffdhe/ietf/3072", @@ -146,4 +145,6 @@ #endif +} // namespace + } // namespace Botan_CLI diff -Nru botan3-3.7.1+dfsg/src/cli/perf_pk_kem.cpp botan3-3.12.0+dfsg/src/cli/perf_pk_kem.cpp --- botan3-3.7.1+dfsg/src/cli/perf_pk_kem.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/perf_pk_kem.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,23 +6,25 @@ #include "perf.h" +#include + #if defined(BOTAN_HAS_PUBLIC_KEY_CRYPTO) #include #include + #include #endif namespace Botan_CLI { +namespace { + #if defined(BOTAN_HAS_PUBLIC_KEY_CRYPTO) class PerfTest_PK_KEM : public PerfTest { public: virtual std::string algo() const = 0; - virtual std::vector keygen_params(const PerfConfig& config) const { - BOTAN_UNUSED(config); - return {""}; - } + virtual std::vector keygen_params(const PerfConfig& /*config*/) const { return {""}; } void go(const PerfConfig& config) override { const std::string alg = this->algo(); @@ -35,11 +37,11 @@ } } - void bench_pk_kem(const PerfConfig& config, - const std::string& nm, - const std::string& algo, - const std::string& params, - const std::string& provider = "") { + static void bench_pk_kem(const PerfConfig& config, + const std::string& nm, + const std::string& algo, + const std::string& params, + const std::string& provider = "") { const auto msec = config.runtime(); auto& rng = config.rng(); @@ -70,7 +72,7 @@ kem_enc_timer->stop(); kem_dec_timer->start(); - Botan::secure_vector dec_shared_key = + const Botan::secure_vector dec_shared_key = dec.decrypt(kem_result.encapsulated_shared_key(), 64, salt); kem_dec_timer->stop(); @@ -93,8 +95,7 @@ public: std::string algo() const override { return "Kyber"; } - std::vector keygen_params(const PerfConfig& config) const override { - BOTAN_UNUSED(config); + std::vector keygen_params(const PerfConfig& /*config*/) const override { return { "Kyber-512-r3", "Kyber-512-90s-r3", @@ -116,8 +117,7 @@ public: std::string algo() const override { return "ML-KEM"; } - std::vector keygen_params(const PerfConfig& config) const override { - BOTAN_UNUSED(config); + std::vector keygen_params(const PerfConfig& /*config*/) const override { return { "ML-KEM-512", "ML-KEM-768", @@ -136,8 +136,7 @@ public: std::string algo() const override { return "FrodoKEM"; } - std::vector keygen_params(const PerfConfig& config) const override { - BOTAN_UNUSED(config); + std::vector keygen_params(const PerfConfig& /*config*/) const override { return { "FrodoKEM-640-SHAKE", "FrodoKEM-640-AES", @@ -165,25 +164,24 @@ public: std::string algo() const override { return "ClassicMcEliece"; } - std::vector keygen_params(const PerfConfig& config) const override { - BOTAN_UNUSED(config); + std::vector keygen_params(const PerfConfig& /*config*/) const override { return { - "mceliece348864", - "mceliece348864f", - "mceliece460896", - "mceliece460896f", - "mceliece6688128", - "mceliece6688128f", - "mceliece6688128pc", - "mceliece6688128pcf", - "mceliece6960119", - "mceliece6960119f", - "mceliece6960119pc", - "mceliece6960119pcf", - "mceliece8192128", - "mceliece8192128f", - "mceliece8192128pc", - "mceliece8192128pcf", + "348864", + "348864f", + "460896", + "460896f", + "6688128", + "6688128f", + "6688128pc", + "6688128pcf", + "6960119", + "6960119f", + "6960119pc", + "6960119pcf", + "8192128", + "8192128f", + "8192128pc", + "8192128pcf", }; } }; @@ -192,4 +190,6 @@ #endif +} // namespace + } // namespace Botan_CLI diff -Nru botan3-3.7.1+dfsg/src/cli/perf_pk_misc.cpp botan3-3.12.0+dfsg/src/cli/perf_pk_misc.cpp --- botan3-3.7.1+dfsg/src/cli/perf_pk_misc.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/perf_pk_misc.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,16 +7,71 @@ #include "perf.h" #if defined(BOTAN_HAS_PUBLIC_KEY_CRYPTO) + #include #include + #include + #include + #include + #include #endif #if defined(BOTAN_HAS_ECDSA) + #include #include #include #endif namespace Botan_CLI { +namespace { + +#if defined(BOTAN_HAS_PUBLIC_KEY_CRYPTO) + +class PerfTest_PKKeyParsing final : public PerfTest { + public: + void go(const PerfConfig& config) override { + const auto runtime = config.runtime(); + auto& rng = config.rng(); + + const std::pair keygen_algos[] = { + {"RSA", "2048"}, + {"ECDSA", "secp256r1"}, + {"ECDSA", "brainpool512r1"}, + {"DH", "modp/ietf/2048"}, + {"X25519", ""}, + {"Ed25519", ""}, + {"ML-DSA", "ML-DSA-6x5"}, + {"ML-KEM", "ML-KEM-768"}, + }; + + for(const auto& [algo, params] : keygen_algos) { + auto sk = Botan::create_private_key(algo, rng, params); + + if(!sk) { + continue; + } + + const auto pk = sk->public_key(); + + const std::string nm = params.empty() ? algo : Botan::fmt("{} {}", algo, params); + + auto pk_parse = config.make_timer(nm, 1, "public key parse"); + const auto pk_bytes = pk->subject_public_key(); + pk_parse->run_until_elapsed(runtime, [&]() { Botan::X509::load_key(pk_bytes); }); + config.record_result(*pk_parse); + + auto sk_parse = config.make_timer(nm, 1, "private key parse"); + const auto sk_bytes = sk->private_key_info(); + sk_parse->run_until_elapsed(runtime, [&]() { Botan::PKCS8::load_key(sk_bytes); }); + config.record_result(*sk_parse); + } + } +}; + +BOTAN_REGISTER_PERF_TEST("key_parsing", PerfTest_PKKeyParsing); + +#endif + #if defined(BOTAN_HAS_RSA) class PerfTest_RSAKeyGen final : public PerfTest { @@ -77,7 +132,7 @@ const uint8_t v = key.recovery_param(message, r, s); recovery_timer->run([&]() { - Botan::ECDSA_PublicKey recovered_key(group, message, r, s, v); + const Botan::ECDSA_PublicKey recovered_key(group, message, r, s, v); BOTAN_ASSERT(recovered_key.public_key_bits() == key.public_key_bits(), "Recovered public key correctly"); }); @@ -92,4 +147,6 @@ #endif +} // namespace + } // namespace Botan_CLI diff -Nru botan3-3.7.1+dfsg/src/cli/perf_pk_sig.cpp botan3-3.12.0+dfsg/src/cli/perf_pk_sig.cpp --- botan3-3.7.1+dfsg/src/cli/perf_pk_sig.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/perf_pk_sig.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,10 +9,14 @@ #if defined(BOTAN_HAS_PUBLIC_KEY_CRYPTO) #include #include + #include + #include #endif namespace Botan_CLI { +namespace { + #if defined(BOTAN_HAS_PUBLIC_KEY_CRYPTO) class PerfTest_PKSig : public PerfTest { @@ -21,10 +25,7 @@ virtual std::string hash() const { return "SHA-256"; } - virtual std::vector keygen_params(const PerfConfig& config) const { - BOTAN_UNUSED(config); - return {""}; - } + virtual std::vector keygen_params(const PerfConfig& /*config*/) const { return {""}; } void go(const PerfConfig& config) override { const std::string alg = this->algo(); @@ -38,12 +39,12 @@ } } - void bench_pk_sig(const PerfConfig& config, - const std::string& nm, - const std::string& alg, - const std::string& param, - const std::string& padding, - const std::string& provider = "") { + static void bench_pk_sig(const PerfConfig& config, + const std::string& nm, + const std::string& alg, + const std::string& param, + const std::string& padding, + const std::string& provider = "") { auto& rng = config.rng(); const auto msec = config.runtime(); @@ -60,7 +61,9 @@ auto pk = sk->public_key(); - std::vector message, signature, bad_signature; + std::vector message; + std::vector signature; + std::vector bad_signature; Botan::PK_Signer sig(*sk, rng, padding, Botan::Signature_Format::Standard, provider); Botan::PK_Verifier ver(*pk, padding, Botan::Signature_Format::Standard, provider); @@ -117,8 +120,7 @@ public: std::string algo() const override { return "DSA"; } - std::vector keygen_params(const PerfConfig& config) const override { - BOTAN_UNUSED(config); + std::vector keygen_params(const PerfConfig& /*config*/) const override { return {"dsa/jce/1024", "dsa/botan/2048", "dsa/botan/3072"}; } @@ -139,8 +141,7 @@ std::string hash() const override { return "PKCS1v15(SHA-256)"; } - std::vector keygen_params(const PerfConfig& config) const override { - BOTAN_UNUSED(config); + std::vector keygen_params(const PerfConfig& /*config*/) const override { return {"1024", "2048", "3072", "4096"}; } }; @@ -196,10 +197,7 @@ std::string hash() const override { return "GOST-34.11"; } - std::vector keygen_params(const PerfConfig& config) const override { - BOTAN_UNUSED(config); - return {"gost_256A"}; - } + std::vector keygen_params(const PerfConfig& /*config*/) const override { return {"gost_256A"}; } }; BOTAN_REGISTER_PERF_TEST("GOST-34.10", PerfTest_Gost3410); @@ -214,10 +212,7 @@ std::string hash() const override { return "SM3"; } - std::vector keygen_params(const PerfConfig& config) const override { - BOTAN_UNUSED(config); - return {"sm2p256v1"}; - } + std::vector keygen_params(const PerfConfig& /*config*/) const override { return {"sm2p256v1"}; } }; BOTAN_REGISTER_PERF_TEST("SM2", PerfTest_SM2); @@ -258,9 +253,7 @@ std::string hash() const override { return ""; } - std::vector keygen_params(const PerfConfig& config) const override { - BOTAN_UNUSED(config); - + std::vector keygen_params(const PerfConfig& /*config*/) const override { /* We only test H10 signatures here since already they are quite slow (a few seconds per signature). On a fast machine, H16 signatures take 1-2 @@ -287,9 +280,7 @@ std::string hash() const override { return ""; } - std::vector keygen_params(const PerfConfig& config) const override { - BOTAN_UNUSED(config); - + std::vector keygen_params(const PerfConfig& /*config*/) const override { // At first we compare instances with multiple hash functions. LMS trees with // height 10 are suitable, since they can be used for enough signatures and are // fast enough for speed testing. @@ -319,9 +310,7 @@ return alg + param.substr(11); } - std::vector keygen_params(const PerfConfig& config) const override { - BOTAN_UNUSED(config); - + std::vector keygen_params(const PerfConfig& /*config*/) const override { return {"SphincsPlus-sha2-128s-r3.1", "SphincsPlus-sha2-128f-r3.1", "SphincsPlus-sha2-192s-r3.1", @@ -349,9 +338,7 @@ std::string hash() const override { return ""; } - std::vector keygen_params(const PerfConfig& config) const override { - BOTAN_UNUSED(config); - + std::vector keygen_params(const PerfConfig& /*config*/) const override { return {"SLH-DSA-SHA2-128s", "SLH-DSA-SHA2-128f", "SLH-DSA-SHA2-192s", @@ -379,9 +366,7 @@ std::string hash() const override { return ""; } - std::vector keygen_params(const PerfConfig& config) const override { - BOTAN_UNUSED(config); - + std::vector keygen_params(const PerfConfig& /*config*/) const override { return { "Dilithium-4x4-r3", "Dilithium-4x4-AES-r3", @@ -405,9 +390,7 @@ std::string hash() const override { return ""; } - std::vector keygen_params(const PerfConfig& config) const override { - BOTAN_UNUSED(config); - + std::vector keygen_params(const PerfConfig& /*config*/) const override { return { "ML-DSA-4x4", "ML-DSA-6x5", @@ -420,4 +403,6 @@ #endif +} // namespace + } // namespace Botan_CLI diff -Nru botan3-3.7.1+dfsg/src/cli/perf_pwdhash.cpp botan3-3.12.0+dfsg/src/cli/perf_pwdhash.cpp --- botan3-3.7.1+dfsg/src/cli/perf_pwdhash.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/perf_pwdhash.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,8 +6,13 @@ #include "perf.h" +#include +#include + #if defined(BOTAN_HAS_PASSWORD_HASHING) #include + #include + #include #endif #if defined(BOTAN_HAS_BCRYPT) @@ -20,6 +25,8 @@ namespace Botan_CLI { +namespace { + #if defined(BOTAN_HAS_BCRYPT) class PerfTest_Bcrypt final : public PerfTest { @@ -49,7 +56,7 @@ const std::string password = "not a very good password"; for(uint8_t alg = 0; alg <= 4; ++alg) { - if(Botan::is_passhash9_alg_supported(alg) == false) { + if(!Botan::is_passhash9_alg_supported(alg)) { continue; } @@ -76,9 +83,9 @@ void go(const PerfConfig& config) override { auto pwdhash_fam = Botan::PasswordHashFamily::create_or_throw("Scrypt"); - for(size_t N : {8192, 16384, 32768, 65536}) { - for(size_t r : {1, 8, 16}) { - for(size_t p : {1}) { + for(const size_t N : {8192, 16384, 32768, 65536}) { + for(const size_t r : {1, 8, 16}) { + for(const size_t p : {1}) { auto pwdhash = pwdhash_fam->from_params(N, r, p); const size_t mem_usage = pwdhash->total_memory_usage() / (1024 * 1024); @@ -112,6 +119,45 @@ #endif +#if defined(BOTAN_HAS_PBKDF2) && defined(BOTAN_HAS_PASSWORD_HASHING) + +class PerfTest_PBKDF2 final : public PerfTest { + public: + void go(const PerfConfig& config) override { + const std::string hash = "SHA-256"; + auto pwdhash_fam = Botan::PasswordHashFamily::create(Botan::fmt("PBKDF2({})", hash)); + + if(pwdhash_fam != nullptr) { + for(const size_t iter : {10000, 100000}) { + auto pwdhash = pwdhash_fam->from_params(iter); + + auto pbkdf2_timer = config.make_timer(Botan::fmt("PBKDF2({},{})", hash, iter)); + + std::array salt{}; + config.rng().randomize(salt); + + const std::string password = "password"; + auto runtime = config.runtime(); + + std::array out{}; + + while(pbkdf2_timer->under(runtime)) { + pbkdf2_timer->run([&] { + pwdhash->hash(out, password, salt); + std::memcpy(salt.data(), out.data(), 8); + }); + } + + config.record_result(*pbkdf2_timer); + } + } + } +}; + +BOTAN_REGISTER_PERF_TEST("pbkdf2", PerfTest_PBKDF2); + +#endif + #if defined(BOTAN_HAS_ARGON2) class PerfTest_Argon2 final : public PerfTest { @@ -121,9 +167,9 @@ const auto msec = config.runtime(); - for(size_t M : {8 * 1024, 64 * 1024, 256 * 1024}) { - for(size_t t : {1, 4}) { - for(size_t p : {1, 4}) { + for(const size_t M : {8 * 1024, 64 * 1024, 256 * 1024}) { + for(const size_t t : {1, 4}) { + for(const size_t p : {1, 4}) { auto pwhash = pwhash_fam->from_params(M, t, p); auto timer = config.make_timer(pwhash->to_string()); @@ -146,4 +192,6 @@ #endif +} // namespace + } // namespace Botan_CLI diff -Nru botan3-3.7.1+dfsg/src/cli/perf_rng.cpp botan3-3.12.0+dfsg/src/cli/perf_rng.cpp --- botan3-3.7.1+dfsg/src/cli/perf_rng.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/perf_rng.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,6 +6,7 @@ #include "perf.h" +#include #include #if defined(BOTAN_HAS_COMPRESSION) @@ -34,11 +35,13 @@ namespace Botan_CLI { +namespace { + class PerfTest_Rng final : public PerfTest { public: void go(const PerfConfig& config) override { #if defined(BOTAN_HAS_HMAC_DRBG) - for(std::string hash : {"SHA-256", "SHA-384", "SHA-512"}) { + for(const std::string hash : {"SHA-256", "SHA-384", "SHA-512"}) { Botan::HMAC_DRBG hmac_drbg(hash); bench_rng(config, hmac_drbg, hmac_drbg.name()); } @@ -95,4 +98,6 @@ BOTAN_REGISTER_PERF_TEST("RNG", PerfTest_Rng); +} // namespace + } // namespace Botan_CLI diff -Nru botan3-3.7.1+dfsg/src/cli/perf_sym.cpp botan3-3.12.0+dfsg/src/cli/perf_sym.cpp --- botan3-3.7.1+dfsg/src/cli/perf_sym.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/perf_sym.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -5,8 +5,13 @@ */ #include "perf.h" + +#include #include +#include +#include + #if defined(BOTAN_HAS_BLOCK_CIPHER) #include #endif @@ -33,15 +38,17 @@ namespace Botan_CLI { +namespace { + #if defined(BOTAN_HAS_BLOCK_CIPHER) class PerfTest_BlockCipher final : public PerfTest { public: - PerfTest_BlockCipher(std::string_view alg) : m_alg(alg) {} + explicit PerfTest_BlockCipher(std::string_view alg) : m_alg(alg) {} void go(const PerfConfig& config) override { for(const auto& provider : Botan::BlockCipher::providers(m_alg)) { if(auto cipher = Botan::BlockCipher::create(m_alg, provider)) { - bench_stream_cipher(config, *cipher); + bench_block_cipher(config, *cipher); } } } @@ -49,7 +56,7 @@ static bool has_impl_for(std::string_view alg) { return !Botan::BlockCipher::providers(alg).empty(); } private: - void bench_stream_cipher(const PerfConfig& config, Botan::BlockCipher& cipher) { + static void bench_block_cipher(const PerfConfig& config, Botan::BlockCipher& cipher) { auto& rng = config.rng(); const auto runtime = config.runtime(); const auto provider = cipher.provider(); @@ -61,7 +68,7 @@ const size_t bs = cipher.block_size(); std::set buf_sizes_in_blocks; - for(size_t buf_size : config.buffer_sizes()) { + for(const size_t buf_size : config.buffer_sizes()) { if(buf_size % bs == 0) { buf_sizes_in_blocks.insert(buf_size); } else { @@ -69,7 +76,7 @@ } } - for(size_t buf_size : buf_sizes_in_blocks) { + for(const size_t buf_size : buf_sizes_in_blocks) { std::vector buffer(buf_size); const size_t mult = std::max(1, 65536 / buf_size); const size_t blocks = buf_size / bs; @@ -79,14 +86,14 @@ encrypt_timer->run_until_elapsed(runtime, [&]() { for(size_t i = 0; i != mult; ++i) { - cipher.encrypt_n(&buffer[0], &buffer[0], blocks); + cipher.encrypt_n(buffer.data(), buffer.data(), blocks); } }); config.record_result(*encrypt_timer); decrypt_timer->run_until_elapsed(runtime, [&]() { for(size_t i = 0; i != mult; ++i) { - cipher.decrypt_n(&buffer[0], &buffer[0], blocks); + cipher.decrypt_n(buffer.data(), buffer.data(), blocks); } }); config.record_result(*decrypt_timer); @@ -100,7 +107,7 @@ #if defined(BOTAN_HAS_CIPHER_MODES) class PerfTest_CipherMode final : public PerfTest { public: - PerfTest_CipherMode(std::string_view alg) : m_alg(alg) {} + explicit PerfTest_CipherMode(std::string_view alg) : m_alg(alg) {} void go(const PerfConfig& config) override { for(const auto& provider : Botan::Cipher_Mode::providers(m_alg)) { @@ -114,7 +121,7 @@ static bool has_impl_for(std::string_view alg) { return !Botan::Cipher_Mode::providers(alg).empty(); } private: - void bench_cipher_mode(const PerfConfig& config, Botan::Cipher_Mode& enc, Botan::Cipher_Mode& dec) { + static void bench_cipher_mode(const PerfConfig& config, Botan::Cipher_Mode& enc, Botan::Cipher_Mode& dec) { auto& rng = config.rng(); const auto runtime = config.runtime(); const auto provider = enc.provider(); @@ -146,17 +153,23 @@ } }); + Botan::secure_vector dbuffer; + + size_t iter = 0; + while(decrypt_timer->under(runtime)) { - if(!iv.empty()) { - iv[iv.size() - 1] += 1; - } + if(iter == 0 || iter % 128 == 0) { + if(!iv.empty()) { + iv[iv.size() - 1] += 1; + } - // Create a valid ciphertext/tag for decryption to run on - buffer.resize(buf_size); - enc.start(iv); - enc.finish(buffer); + // Create a valid ciphertext/tag for decryption to run on + buffer.resize(buf_size); + enc.start(iv); + enc.finish(buffer); + } - Botan::secure_vector dbuffer; + ++iter; decrypt_timer->run([&]() { for(size_t i = 0; i != mult; ++i) { @@ -180,7 +193,7 @@ #if defined(BOTAN_HAS_STREAM_CIPHER) class PerfTest_StreamCipher final : public PerfTest { public: - PerfTest_StreamCipher(std::string_view alg) : m_alg(alg) {} + explicit PerfTest_StreamCipher(std::string_view alg) : m_alg(alg) {} void go(const PerfConfig& config) override { for(const auto& provider : Botan::StreamCipher::providers(m_alg)) { @@ -193,7 +206,7 @@ static bool has_impl_for(std::string_view alg) { return !Botan::StreamCipher::providers(alg).empty(); } private: - void bench_stream_cipher(const PerfConfig& config, Botan::StreamCipher& cipher) { + static void bench_stream_cipher(const PerfConfig& config, Botan::StreamCipher& cipher) { auto& rng = config.rng(); const auto runtime = config.runtime(); const auto provider = cipher.provider(); @@ -243,7 +256,7 @@ #if defined(BOTAN_HAS_HASH) class PerfTest_HashFunction final : public PerfTest { public: - PerfTest_HashFunction(std::string_view alg) : m_alg(alg) {} + explicit PerfTest_HashFunction(std::string_view alg) : m_alg(alg) {} void go(const PerfConfig& config) override { for(const auto& provider : Botan::HashFunction::providers(m_alg)) { @@ -256,7 +269,7 @@ static bool has_impl_for(std::string_view alg) { return !Botan::HashFunction::providers(alg).empty(); } private: - void bench_hash_fn(const PerfConfig& config, Botan::HashFunction& hash) { + static void bench_hash_fn(const PerfConfig& config, Botan::HashFunction& hash) { std::vector output(hash.output_length()); const auto provider = hash.provider(); const auto runtime = config.runtime(); @@ -284,7 +297,7 @@ #if defined(BOTAN_HAS_MAC) class PerfTest_MessageAuthenticationCode final : public PerfTest { public: - PerfTest_MessageAuthenticationCode(std::string_view alg) : m_alg(alg) {} + explicit PerfTest_MessageAuthenticationCode(std::string_view alg) : m_alg(alg) {} void go(const PerfConfig& config) override { for(const auto& provider : Botan::MessageAuthenticationCode::providers(m_alg)) { @@ -299,7 +312,7 @@ } private: - void bench_mac_fn(const PerfConfig& config, Botan::MessageAuthenticationCode& mac) { + static void bench_mac_fn(const PerfConfig& config, Botan::MessageAuthenticationCode& mac) { std::vector output(mac.output_length()); const auto provider = mac.provider(); const auto runtime = config.runtime(); @@ -335,7 +348,7 @@ #if defined(BOTAN_HAS_XOF) class PerfTest_XOF final : public PerfTest { public: - PerfTest_XOF(std::string_view alg) : m_alg(alg) {} + explicit PerfTest_XOF(std::string_view alg) : m_alg(alg) {} void go(const PerfConfig& config) override { for(const auto& provider : Botan::XOF::providers(m_alg)) { @@ -348,11 +361,11 @@ static bool has_impl_for(std::string_view alg) { return !Botan::XOF::providers(alg).empty(); } private: - void bench_xof_fn(const PerfConfig& config, Botan::XOF& xof) { + static void bench_xof_fn(const PerfConfig& config, Botan::XOF& xof) { const auto runtime = config.runtime(); const auto provider = xof.provider(); - for(size_t buf_size : config.buffer_sizes()) { + for(const size_t buf_size : config.buffer_sizes()) { auto in = config.rng().random_vec(buf_size); Botan::secure_vector out(buf_size); @@ -364,6 +377,9 @@ config.record_result(*in_timer); config.record_result(*out_timer); + + // Our XOFs don't want to consume inputs after producing output, so reset the state + xof.clear(); } } @@ -371,6 +387,8 @@ }; #endif +} // namespace + //static std::unique_ptr PerfTest::get_sym(const std::string& alg) { #if defined(BOTAN_HAS_XOF) diff -Nru botan3-3.7.1+dfsg/src/cli/perf_x509.cpp botan3-3.12.0+dfsg/src/cli/perf_x509.cpp --- botan3-3.7.1+dfsg/src/cli/perf_x509.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/perf_x509.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,157 @@ +/* +* (C) 2025 Jack Lloyd +* 2025 René Meusel - Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "perf.h" +#include + +// Always available: +#include + +#if defined(BOTAN_HAS_X509) + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif + +namespace Botan_CLI { + +namespace { + +#if defined(BOTAN_HAS_X509) && defined(BOTAN_HAS_ML_DSA) + +class PerfTest_ASN1_Parsing final : public PerfTest { + private: + struct CA { + std::unique_ptr root_key; + Botan::X509_CA ca; + }; + + private: + static std::string_view get_hash_function() { return "SHAKE-256(512)"; } + + static std::unique_ptr create_private_key(Botan::RandomNumberGenerator& rng) { + return Botan::create_private_key("ML-DSA", rng, "ML-DSA-6x5"); + } + + static CA create_ca(Botan::RandomNumberGenerator& rng) { + auto root_cert_options = Botan::X509_Cert_Options("Benchmark Root/DE/RS/CS"); + root_cert_options.dns = "unobtainium.example.com"; + root_cert_options.email = "idont@exist.com"; + root_cert_options.is_CA = true; + + auto root_key = create_private_key(rng); + BOTAN_ASSERT_NONNULL(root_key); + auto root_cert = Botan::X509::create_self_signed_cert(root_cert_options, *root_key, get_hash_function(), rng); + auto ca = Botan::X509_CA(root_cert, *root_key, get_hash_function(), rng); + + return CA{ + std::move(root_key), + std::move(ca), + }; + } + + static Botan::X509_Certificate make_certificate(std::string_view common_name, + CA& ca, + Botan::RandomNumberGenerator& rng) { + Botan::X509_DN subject; + subject.add_attribute("X520.CommonName", common_name); + subject.add_attribute("X520.Country", "DE"); + subject.add_attribute("X520.State", "Berlin"); + subject.add_attribute("X520.Organization", "RS"); + subject.add_attribute("X520.OrganizationalUnit", "CS"); + + Botan::AlternativeName an; + an.add_dns("gibtsnicht.example.com"); + an.add_email("not.available@anywhere.com"); + + Botan::Extensions exts; + exts.add(std::make_unique(an)); + + const auto cert_key = create_private_key(rng); + BOTAN_ASSERT_NONNULL(cert_key); + const auto cert_req = Botan::PKCS10_Request::create(*cert_key, subject, exts, get_hash_function(), rng); + + const auto now = std::chrono::system_clock::now(); + using namespace std::chrono_literals; + return ca.ca.sign_request(cert_req, rng, Botan::X509_Time(now), Botan::X509_Time(now + 24h * 365)); + } + + static Botan::X509_CRL make_revocation_list(size_t entries, CA& ca, Botan::RandomNumberGenerator& rng) { + const auto empty_crl = ca.ca.new_crl(rng); + + std::vector crl_entries(entries); + std::generate(crl_entries.begin(), crl_entries.end(), [&] { + std::vector crl_entry_buffer; + + // Generating the CRL entries through their ASN.1 structure because + // our public API does not allow creating them without the actual + // certificate that is supposed to be revoked. + Botan::Extensions exts; + exts.add(std::make_unique(Botan::CRL_Code::KeyCompromise)); + Botan::DER_Encoder(crl_entry_buffer) + .start_sequence() + .encode(Botan::BigInt::from_bytes(rng.random_array<16>())) + .encode(Botan::X509_Time(std::chrono::system_clock::now())) + .start_sequence() + .encode(exts) + .end_cons() + .end_cons(); + + Botan::BER_Decoder ber(crl_entry_buffer, Botan::BER_Decoder::Limits::DER()); + + Botan::CRL_Entry entry; + entry.decode_from(ber); + return entry; + }); + + return ca.ca.update_crl(empty_crl, crl_entries, rng); + } + + public: + void go(const PerfConfig& config) override { + auto ca = create_ca(config.rng()); + auto cert = make_certificate("Test Certificate", ca, config.rng()); + auto crl = make_revocation_list(500, ca, config.rng()); + + const auto cert_encoded = cert.BER_encode(); + const auto crl_encoded = crl.BER_encode(); + + auto cert_timer = config.make_timer("X509 Certificate Parsing"); + auto crl_timer = config.make_timer("X509 CRL Parsing"); + + const auto runtime = config.runtime(); + + while(cert_timer->under(runtime)) { + cert_timer->start(); + std::ignore = Botan::X509_Certificate(cert_encoded); + cert_timer->stop(); + } + + while(crl_timer->under(runtime)) { + crl_timer->start(); + std::ignore = Botan::X509_CRL(crl_encoded); + crl_timer->stop(); + } + + config.record_result(*cert_timer); + config.record_result(*crl_timer); + } +}; + +BOTAN_REGISTER_PERF_TEST("asn1_parsing", PerfTest_ASN1_Parsing); + +#endif + +} // namespace + +} // namespace Botan_CLI diff -Nru botan3-3.7.1+dfsg/src/cli/pk_crypt.cpp botan3-3.12.0+dfsg/src/cli/pk_crypt.cpp --- botan3-3.7.1+dfsg/src/cli/pk_crypt.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/pk_crypt.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -61,7 +61,7 @@ const Botan::AlgorithmIdentifier hash_id(OAEP_HASH, Botan::AlgorithmIdentifier::USE_EMPTY_PARAM); const Botan::AlgorithmIdentifier pk_alg_id("RSA/OAEP", hash_id.BER_encode()); - Botan::PK_Encryptor_EME enc(*key, rng(), "OAEP(" + OAEP_HASH + ")"); + const Botan::PK_Encryptor_EME enc(*key, rng(), "OAEP(" + OAEP_HASH + ")"); const Botan::secure_vector file_key = rng().random_vec(aead->key_spec().maximum_keylength()); @@ -121,7 +121,8 @@ try { Botan::DataSource_Stream input(get_arg("datafile")); - Botan::BER_Decoder(Botan::PEM_Code::decode_check_label(input, "PUBKEY ENCRYPTED MESSAGE")) + Botan::BER_Decoder(Botan::PEM_Code::decode_check_label(input, "PUBKEY ENCRYPTED MESSAGE"), + Botan::BER_Decoder::Limits::DER()) .start_sequence() .decode(pk_alg_id) .decode(encrypted_key, Botan::ASN1_Type::OctetString) @@ -146,7 +147,7 @@ } Botan::AlgorithmIdentifier oaep_hash_id; - Botan::BER_Decoder(pk_alg_id.parameters()).decode(oaep_hash_id); + Botan::BER_Decoder(pk_alg_id.parameters(), Botan::BER_Decoder::Limits::DER()).decode(oaep_hash_id); const std::string oaep_hash = oaep_hash_id.oid().human_name_or_empty(); @@ -155,7 +156,7 @@ return set_return_code(1); } - if(oaep_hash_id.parameters().empty() == false) { + if(!oaep_hash_id.parameters().empty()) { error_output() << "Unknown OAEP parameters used\n"; return set_return_code(1); } @@ -165,7 +166,7 @@ const size_t expected_keylen = aead->key_spec().maximum_keylength(); - Botan::PK_Decryptor_EME dec(*key, rng(), "OAEP(" + oaep_hash + ")"); + const Botan::PK_Decryptor_EME dec(*key, rng(), "OAEP(" + oaep_hash + ")"); const Botan::secure_vector file_key = dec.decrypt_or_random(encrypted_key.data(), encrypted_key.size(), expected_keylen, rng()); diff -Nru botan3-3.7.1+dfsg/src/cli/psk.cpp botan3-3.12.0+dfsg/src/cli/psk.cpp --- botan3-3.7.1+dfsg/src/cli/psk.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/psk.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -16,8 +16,6 @@ class PSK_Tool_Base : public Command { public: - PSK_Tool_Base(const std::string& spec) : Command(spec) {} - std::string group() const override { return "psk"; } void go() override { @@ -25,12 +23,15 @@ const Botan::secure_vector db_key = Botan::hex_decode_locked(get_passphrase_arg("Database key", "db_key")); - std::shared_ptr db = std::make_shared(db_filename); + const std::shared_ptr db = std::make_shared(db_filename); Botan::Encrypted_PSK_Database_SQL psk(db_key, db, "psk"); psk_operation(psk); } + protected: + explicit PSK_Tool_Base(const std::string& spec) : Command(spec) {} + private: virtual void psk_operation(Botan::PSK_Database& db) = 0; }; diff -Nru botan3-3.7.1+dfsg/src/cli/pubkey.cpp botan3-3.12.0+dfsg/src/cli/pubkey.cpp --- botan3-3.7.1+dfsg/src/cli/pubkey.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/pubkey.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -11,18 +11,15 @@ #if defined(BOTAN_HAS_PUBLIC_KEY_CRYPTO) #include - #include - #include - #include #include + #include #include #include #include #include #include #include - #include #include @@ -36,11 +33,13 @@ namespace Botan_CLI { +namespace { + class PK_Keygen final : public Command { public: PK_Keygen() : Command( - "keygen --algo=RSA --params= --passphrase= --cipher= --pbkdf= --pbkdf-ms=300 --pbkdf-iter= --provider= --der-out") { + "keygen --algo=RSA --params= --passphrase= --cipher= --pbkdf= --pbkdf-ms=300 --pbkdf-iter= --provider= --rng-type= --drbg-seed= --der-out") { } std::string group() const override { return "pubkey"; } @@ -52,7 +51,7 @@ const std::string params = get_arg("params"); const std::string provider = get_arg("provider"); - std::unique_ptr key = Botan::create_private_key(algo, rng(), params, provider); + const std::unique_ptr key = Botan::create_private_key(algo, rng(), params, provider); if(!key) { throw CLI_Error_Unsupported("keygen", algo); @@ -178,7 +177,10 @@ class PK_Sign final : public Command { public: - PK_Sign() : Command("sign --der-format --passphrase= --hash=SHA-256 --padding= --provider= key file") {} + PK_Sign() : + Command( + "sign --der-format --passphrase= --hash=SHA-256 --padding= --provider= --rng-type= --drbg-seed= key file") { + } std::string group() const override { return "pubkey"; } @@ -284,7 +286,7 @@ public: PKCS8_Tool() : Command( - "pkcs8 --pass-in= --pub-out --der-out --pass-out= --cipher= --pbkdf= --pbkdf-ms=300 --pbkdf-iter= key") { + "pkcs8 --pass-in= --pub-out --der-out --pass-out= --cipher= --pbkdf= --pbkdf-ms=300 --pbkdf-iter= --rng-type= --drbg-seed= key") { } std::string group() const override { return "pubkey"; } @@ -366,7 +368,7 @@ const auto ec_group = Botan::EC_Group::from_name(get_arg("name")); if(flag_set("pem")) { - output() << ec_group.PEM_encode(); + output() << ec_group.PEM_encode(Botan::EC_Group_Encoding::NamedCurve); } else { output() << "P = " << std::hex << ec_group.get_p() << "\n" << "A = " << std::hex << ec_group.get_a() << "\n" @@ -435,7 +437,8 @@ class Gen_DL_Group final : public Command { public: - Gen_DL_Group() : Command("gen_dl_group --pbits=2048 --qbits=0 --seed= --type=subgroup") {} + Gen_DL_Group() : + Command("gen_dl_group --pbits=2048 --qbits=0 --seed= --type=subgroup --rng-type= --drbg-seed=") {} std::string group() const override { return "pubkey"; } @@ -452,13 +455,13 @@ if(!seed_str.empty()) { throw CLI_Usage_Error("Seed only supported for DSA param gen"); } - Botan::DL_Group grp(rng(), Botan::DL_Group::Strong, pbits); + const Botan::DL_Group grp(rng(), Botan::DL_Group::Strong, pbits); output() << grp.PEM_encode(Botan::DL_Group_Format::ANSI_X9_42); } else if(type == "subgroup") { if(!seed_str.empty()) { throw CLI_Usage_Error("Seed only supported for DSA param gen"); } - Botan::DL_Group grp(rng(), Botan::DL_Group::Prime_Subgroup, pbits, qbits); + const Botan::DL_Group grp(rng(), Botan::DL_Group::Prime_Subgroup, pbits, qbits); output() << grp.PEM_encode(Botan::DL_Group_Format::ANSI_X9_42); } else if(type == "dsa") { size_t dsa_qbits = qbits; @@ -473,11 +476,11 @@ } if(seed_str.empty()) { - Botan::DL_Group grp(rng(), Botan::DL_Group::DSA_Kosherizer, pbits, dsa_qbits); + const Botan::DL_Group grp(rng(), Botan::DL_Group::DSA_Kosherizer, pbits, dsa_qbits); output() << grp.PEM_encode(Botan::DL_Group_Format::ANSI_X9_57); } else { const std::vector seed = Botan::hex_decode(seed_str); - Botan::DL_Group grp(rng(), seed, pbits, dsa_qbits); + const Botan::DL_Group grp(rng(), seed, pbits, dsa_qbits); output() << grp.PEM_encode(Botan::DL_Group_Format::ANSI_X9_57); } @@ -491,6 +494,8 @@ #endif +} // namespace + } // namespace Botan_CLI #endif diff -Nru botan3-3.7.1+dfsg/src/cli/roughtime.cpp botan3-3.12.0+dfsg/src/cli/roughtime.cpp --- botan3-3.7.1+dfsg/src/cli/roughtime.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/roughtime.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -11,9 +11,6 @@ #include #include - #include - #include - #include #include #include @@ -22,6 +19,8 @@ namespace Botan_CLI { +namespace { + class RoughtimeCheck final : public Command { public: RoughtimeCheck() : Command("roughtime_check --raw-time chain-file") {} @@ -69,7 +68,7 @@ Google-Sandbox-Roughtime ed25519 etPaaIxcBMY1oUeGpwvPMCJMwlRVNxv51KK/tktoJTQ= udp roughtime.sandbox.google.com:2002 --chain-file= - Succesfull queries are appended to this file. + Successful queries are appended to this file. If limit of --max-chain-size records is reached, the oldest records are truncated. This queries records can be replayed using command roughtime_check . @@ -86,8 +85,8 @@ const size_t max_chain_size, const std::string& address, const Botan::Ed25519_PublicKey& public_key) { - Botan::Roughtime::Nonce nonce; - Botan::Roughtime::Nonce blind; + Botan::Roughtime::Nonce nonce{}; + Botan::Roughtime::Nonce blind{}; if(chain) { blind = Botan::Roughtime::Nonce(rng()); nonce = chain->next_nonce(blind); @@ -110,7 +109,7 @@ return; } const auto tolerance = get_arg_sz("check-local-clock"); - if(tolerance) { + if(tolerance > 0) { const auto now = std::chrono::system_clock::now(); const auto diff_abs = now >= response.utc_midpoint() ? now - response.utc_midpoint() : response.utc_midpoint() - now; @@ -181,6 +180,8 @@ BOTAN_REGISTER_COMMAND("roughtime", Roughtime); +} // namespace + } // namespace Botan_CLI #endif diff -Nru botan3-3.7.1+dfsg/src/cli/sandbox.cpp botan3-3.12.0+dfsg/src/cli/sandbox.cpp --- botan3-3.7.1+dfsg/src/cli/sandbox.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/sandbox.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -5,14 +5,15 @@ */ #include "sandbox.h" + #include +#include -#if defined(BOTAN_TARGET_OS_HAS_PLEDGE) - #include -#elif defined(BOTAN_TARGET_OS_HAS_CAP_ENTER) +#if defined(BOTAN_TARGET_OS_HAS_CAP_ENTER) #include #include #elif defined(BOTAN_TARGET_OS_HAS_SETPPRIV) + #include #include #elif defined(BOTAN_TARGET_OS_HAS_SANDBOX_PROC) #include @@ -29,27 +30,28 @@ }; #endif -Sandbox::Sandbox() { -#if defined(BOTAN_TARGET_OS_HAS_PLEDGE) - m_name = "pledge"; -#elif defined(BOTAN_TARGET_OS_HAS_CAP_ENTER) - m_name = "capsicum"; +namespace { + +std::string sandbox_impl_name() { +#if defined(BOTAN_TARGET_OS_HAS_CAP_ENTER) + return "capsicum"; #elif defined(BOTAN_TARGET_OS_HAS_SETPPRIV) - m_name = "privilege"; + return "privilege"; #elif defined(BOTAN_TARGET_OS_HAS_SANDBOX_PROC) - m_name = "sandbox"; + return "sandbox"; #else - m_name = ""; + return ""; #endif } +} // namespace + +Sandbox::Sandbox() : m_name(sandbox_impl_name()) {} + bool Sandbox::init() { Botan::initialize_allocator(); -#if defined(BOTAN_TARGET_OS_HAS_PLEDGE) - const static char* opts = "stdio rpath inet error"; - return (::pledge(opts, nullptr) == 0); -#elif defined(BOTAN_TARGET_OS_HAS_CAP_ENTER) +#if defined(BOTAN_TARGET_OS_HAS_CAP_ENTER) cap_rights_t wt, rd; if(::cap_rights_init(&wt, CAP_READ, CAP_WRITE) == nullptr) { diff -Nru botan3-3.7.1+dfsg/src/cli/sandbox.h botan3-3.12.0+dfsg/src/cli/sandbox.h --- botan3-3.7.1+dfsg/src/cli/sandbox.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/sandbox.h 2026-05-07 01:38:28.000000000 +0000 @@ -16,6 +16,11 @@ explicit Sandbox(); virtual ~Sandbox(); + Sandbox(const Sandbox& other) = delete; + Sandbox(Sandbox&& other) = delete; + Sandbox& operator=(const Sandbox& other) = delete; + Sandbox& operator=(Sandbox&& other) = delete; + static bool init(); const std::string& name() const { return m_name; } diff -Nru botan3-3.7.1+dfsg/src/cli/socket_utils.h botan3-3.12.0+dfsg/src/cli/socket_utils.h --- botan3-3.7.1+dfsg/src/cli/socket_utils.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/socket_utils.h 2026-05-07 01:38:28.000000000 +0000 @@ -10,6 +10,7 @@ #include "cli_exceptions.h" #include +#include #include #if defined(BOTAN_TARGET_OS_HAS_WINSOCK2) @@ -51,8 +52,16 @@ } inline std::string err_to_string(int e) { - // TODO use strerror_s here - return "Error code " + std::to_string(e); + /* + * MS documentation specifies 94 character max for user messages. + * strerror_s truncates to buffer size - 1 and guarantees null termination. + * Using 100 bytes yo ensure sufficient space with safety margin. + * https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/strerror-s-strerror-s-wcserror-s-wcserror-s + */ + std::array buf{}; + const auto res = strerror_s(buf.data(), buf.size() - 1, e); + const std::string_view msg = (res == 0) ? buf.data() : "failed to map error with strerror_s()"; + return Botan::fmt("Error: {} - {}", e, msg); } inline int close(int fd) { diff -Nru botan3-3.7.1+dfsg/src/cli/speed.cpp botan3-3.12.0+dfsg/src/cli/speed.cpp --- botan3-3.7.1+dfsg/src/cli/speed.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/speed.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,7 +9,6 @@ #include "perf.h" #include -#include #include #include #include @@ -17,9 +16,11 @@ // Always available: #include -#include -#include -#include +#include + +#if defined(BOTAN_HAS_CPUID) + #include +#endif #if defined(BOTAN_HAS_OS_UTILS) #include @@ -42,12 +43,23 @@ out << "[\n"; + out << "{" + << R"("arch": ")" << BOTAN_TARGET_ARCH << "\", " + << R"("version": ")" << Botan::short_version_cstr() << "\", "; + + if(auto vc_revision = Botan::version_vc_revision()) { + out << R"("git": ")" << *vc_revision << "\", "; + } + + out << R"("compiler": ")" << BOTAN_COMPILER_INVOCATION_STRING << "\"" + << "},\n"; + for(size_t i = 0; i != m_results.size(); ++i) { const Timer& t = m_results[i]; out << "{" - << "\"algo\": \"" << t.get_name() << "\", " - << "\"op\": \"" << t.doing() << "\", " + << R"("algo": ")" << t.get_name() << "\", " + << R"("op": ")" << t.doing() << "\", " << "\"events\": " << t.events() << ", "; if(t.cycles_consumed() > 0) { @@ -55,7 +67,7 @@ } if(t.buf_size() > 0) { - out << "\"bps\": " << static_cast(t.events() / (t.value() / 1000000000.0)) << ", "; + out << "\"bps\": " << static_cast(t.events() / (t.nanoseconds() / 1000000000.0)) << ", "; out << "\"buf_size\": " << t.buf_size() << ", "; } @@ -256,7 +268,45 @@ return oss.str(); } -} // namespace +std::vector interpret_ecc_groups(const std::string& arg) { + if(arg.empty()) { + return {"secp256r1", "secp384r1", "secp521r1", "brainpool256r1", "brainpool384r1", "brainpool512r1"}; + } + if(arg == "nist") { + return {"secp224r1", "secp256r1", "secp384r1", "secp521r1"}; + } + +#if defined(BOTAN_HAS_ECC_GROUP) + if(arg == "all") { + const auto& all = Botan::EC_Group::known_named_groups(); + return std::vector(all.begin(), all.end()); + } + + if(arg == "generic") { + std::vector groups; + for(const auto& group_name : Botan::EC_Group::known_named_groups()) { + const Botan::EC_Group group(group_name); + if(group.engine() == Botan::EC_Group_Engine::Generic) { + groups.push_back(group_name); + } + } + return groups; + } + + if(arg == "pcurves") { + std::vector groups; + for(const auto& group_name : Botan::EC_Group::known_named_groups()) { + const Botan::EC_Group group(group_name); + if(group.engine() == Botan::EC_Group_Engine::Optimized) { + groups.push_back(group_name); + } + } + return groups; + } +#endif + + return Command::split_on(arg, ','); +} class Speed final : public Command { public: @@ -303,6 +353,7 @@ "AES-128/GCM", "AES-128/XTS", "AES-128/SIV", + "Ascon-AEAD128", "Serpent/CBC", "Serpent/CTR-BE", @@ -325,6 +376,7 @@ "SHA-512", "SHA-3(256)", "SHA-3(512)", + "Ascon-Hash256", "RIPEMD-160", "Skein-512", "Blake2b", @@ -333,6 +385,7 @@ /* XOFs */ "SHAKE-128", "SHAKE-256", + "Ascon-XOF128", /* MACs */ "CMAC(AES-128)", @@ -361,8 +414,8 @@ std::string description() const override { return "Measures the speed of algorithms"; } void go() override { - std::chrono::milliseconds msec(get_arg_sz("msec")); - std::vector ecc_groups = Command::split_on(get_arg("ecc-groups"), ','); + const uint64_t milliseconds = get_arg_sz("msec"); + const std::string ecc_groups_arg = get_arg("ecc-groups"); const std::string format = get_arg("format"); const std::string clock_ratio = get_arg("cpu-clock-ratio"); @@ -410,33 +463,29 @@ throw CLI_Usage_Error("Unknown --format type '" + format + "'"); } -#if defined(BOTAN_HAS_ECC_GROUP) - if(ecc_groups.empty()) { - ecc_groups = {"secp256r1", "secp384r1", "secp521r1", "brainpool256r1", "brainpool384r1", "brainpool512r1"}; - } else if(ecc_groups.size() == 1 && ecc_groups[0] == "all") { - auto all = Botan::EC_Group::known_named_groups(); - ecc_groups.assign(all.begin(), all.end()); - } -#endif + const auto ecc_groups = interpret_ecc_groups(ecc_groups_arg); std::vector algos = get_arg_list("algos"); const std::vector buf_sizes = unique_buffer_sizes(get_arg("buf-size")); +#if defined(BOTAN_HAS_CPUID) for(const std::string& cpuid_to_clear : Command::split_on(get_arg("clear-cpuid"), ',')) { - auto bits = Botan::CPUID::bit_from_string(cpuid_to_clear); - if(bits.empty()) { + if(auto bit = Botan::CPUID::bit_from_string(cpuid_to_clear)) { + Botan::CPUID::clear_cpuid_bit(*bit); + } else { error_output() << "Warning don't know CPUID flag '" << cpuid_to_clear << "'\n"; } - - for(auto bit : bits) { - Botan::CPUID::clear_cpuid_bit(bit); - } } +#endif if(verbose() || m_summary) { +#if defined(BOTAN_HAS_CPUID) output() << Botan::version_string() << "\n" << "CPUID: " << Botan::CPUID::to_string() << "\n\n"; +#else + output() << Botan::version_string() << "\n\n"; +#endif } const bool using_defaults = (algos.empty()); @@ -444,14 +493,14 @@ algos = default_benchmark_list(); } - PerfConfig perf_config([&](const Timer& t) { this->record_result(t); }, - clock_speed, - clock_cycle_ratio, - msec, - ecc_groups, - buf_sizes, - this->error_output(), - this->rng()); + const PerfConfig perf_config([&](const Timer& t) { this->record_result(t); }, + clock_speed, + clock_cycle_ratio, + milliseconds, + ecc_groups, + buf_sizes, + this->error_output(), + this->rng()); for(const auto& algo : algos) { if(auto perf = PerfTest::get(algo)) { @@ -489,7 +538,7 @@ if(m_json) { m_json->add(t); } else { - output() << format_timer(t, m_time_unit) << std::endl; + output() << format_timer(t, m_time_unit) << "\n" << std::flush; if(m_summary) { m_summary->add(t); @@ -500,4 +549,6 @@ BOTAN_REGISTER_COMMAND("speed", Speed); +} // namespace + } // namespace Botan_CLI diff -Nru botan3-3.7.1+dfsg/src/cli/timer.cpp botan3-3.12.0+dfsg/src/cli/timer.cpp --- botan3-3.7.1+dfsg/src/cli/timer.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/timer.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,7 +7,6 @@ #include "timer.h" #include -#include #include #if defined(BOTAN_HAS_OS_UTILS) @@ -64,7 +63,7 @@ } void Timer::stop() { - if(m_timer_start) { + if(m_timer_start != 0) { const uint64_t now = timestamp_ns(); if(now > m_timer_start) { diff -Nru botan3-3.7.1+dfsg/src/cli/timer.h botan3-3.12.0+dfsg/src/cli/timer.h --- botan3-3.7.1+dfsg/src/cli/timer.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/timer.h 2026-05-07 01:38:28.000000000 +0000 @@ -8,7 +8,6 @@ #define BOTAN_CLI_TIMER_H_ #include -#include #include namespace Botan_CLI { @@ -23,18 +22,18 @@ double clock_cycle_ratio, uint64_t clock_speed); - Timer(std::string_view name) : Timer(name, "", "", 1, 0, 0.0, 0) {} + explicit Timer(std::string_view name) : Timer(name, "", "", 1, 0, 0.0, 0) {} Timer(std::string_view name, size_t buf_size) : Timer(name, "", "", buf_size, buf_size, 0.0, 0) {} - Timer(const Timer& other) = default; - Timer& operator=(const Timer& other) = default; - void start(); void stop(); - bool under(std::chrono::milliseconds msec) const { return (milliseconds() < msec.count()); } + bool under(uint64_t msec) const { + const uint64_t nano = msec * 1000000; + return value() < nano; + } class Timer_Scope final { public: @@ -46,18 +45,23 @@ } catch(...) {} } + Timer_Scope(const Timer_Scope& other) = delete; + Timer_Scope(Timer_Scope&& other) = delete; + Timer_Scope& operator=(const Timer_Scope& other) = delete; + Timer_Scope& operator=(Timer_Scope&& other) = delete; + private: Timer& m_timer; }; template auto run(F f) -> decltype(f()) { - Timer_Scope timer(*this); + const Timer_Scope timer(*this); return f(); } template - void run_until_elapsed(std::chrono::milliseconds msec, F f) { + void run_until_elapsed(uint64_t msec, F f) { while(this->under(msec)) { run(f); } @@ -65,19 +69,17 @@ uint64_t value() const { return m_time_used; } - double seconds() const { return value() / 1000000000.0; } + double seconds() const { return nanoseconds() / 1000000000.0; } - double milliseconds() const { return value() / 1000000.0; } + double milliseconds() const { return nanoseconds() / 1000000.0; } - double microseconds() const { return value() / 1000.0; } + double microseconds() const { return nanoseconds() / 1000.0; } double nanoseconds() const { return static_cast(value()); } - double ms_per_event() const { return milliseconds() / events(); } - uint64_t cycles_consumed() const { if(m_clock_speed != 0) { - return static_cast((m_clock_speed * value()) / 1000.0); + return (m_clock_speed * value()) / 1000; } return m_cpu_cycles_used; } @@ -90,11 +92,23 @@ size_t buf_size() const { return m_buf_size; } - double bytes_per_second() const { return seconds() > 0.0 ? events() / seconds() : 0.0; } + double bytes_per_second() const { return events_per_second(); } - double events_per_second() const { return seconds() > 0.0 ? events() / seconds() : 0.0; } + double events_per_second() const { + if(seconds() > 0.0 && events() > 0) { + return static_cast(events()) / seconds(); + } else { + return 0.0; + } + } - double seconds_per_event() const { return events() > 0 ? seconds() / events() : 0.0; } + double seconds_per_event() const { + if(seconds() > 0.0 && events() > 0) { + return seconds() / static_cast(events()); + } else { + return 0.0; + } + } bool operator<(const Timer& other) const; diff -Nru botan3-3.7.1+dfsg/src/cli/timing_tests.cpp botan3-3.12.0+dfsg/src/cli/timing_tests.cpp --- botan3-3.7.1+dfsg/src/cli/timing_tests.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/timing_tests.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -21,12 +21,16 @@ #include #include +#include #include #include #include #include +#include #include #include +#include +#include #include #if defined(BOTAN_HAS_BIGINT) @@ -56,21 +60,44 @@ #endif #if defined(BOTAN_HAS_TLS_CBC) + #include + #include #include + #include #include #endif -#if defined(BOTAN_HAS_ECDSA) - #include +#if defined(BOTAN_HAS_SYSTEM_RNG) + #include +#endif + +#if defined(BOTAN_HAS_CHACHA_RNG) + #include +#endif + +#if defined(BOTAN_TARGET_OS_HAS_POSIX1) + #include + #include #endif namespace Botan_CLI { namespace { -class TimingTestTimer { +void shuffle_idx(std::vector& vec, Botan::RandomNumberGenerator& rng) { + const size_t n = vec.size(); + for(size_t i = 0; i != n; ++i) { + uint8_t jb[sizeof(uint64_t)]; + rng.randomize(jb, sizeof(jb)); + const uint64_t j8 = Botan::load_le(jb, 0); + const size_t j = i + static_cast(j8) % (n - i); + std::swap(vec[i], vec[j]); + } +} + +class TimingTestTimer final { public: - TimingTestTimer() { m_start = get_high_resolution_clock(); } + TimingTestTimer() : m_start(get_high_resolution_clock()) {} uint64_t complete() const { return get_high_resolution_clock() - m_start; } @@ -84,8 +111,6 @@ uint64_t m_start; }; -} // namespace - class Timing_Test { public: Timing_Test() { @@ -93,8 +118,14 @@ A constant seed is ok here since the timing test rng just needs to be "random" but not cryptographically secure - even std::rand() would be ok. */ - const std::string drbg_seed(64, 'A'); - m_rng = cli_make_rng("", drbg_seed); // throws if it can't find anything to use + +#if defined(BOTAN_HAS_CHACHA_RNG) + m_rng = std::make_unique(std::vector(64, 0)); +#elif defined(BOTAN_HAS_SYSTEM_RNG) + m_rng = std::make_unique(); +#else + throw Botan::Not_Implemented("Missing RNG for timing_test"); +#endif } virtual ~Timing_Test() = default; @@ -116,7 +147,7 @@ Botan::RandomNumberGenerator& timing_test_rng() { return (*m_rng); } private: - std::shared_ptr m_rng; + std::unique_ptr m_rng; }; #if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EME_PKCS1) && defined(BOTAN_HAS_EME_RAW) @@ -125,8 +156,8 @@ public: explicit Bleichenbacker_Timing_Test(size_t keysize) : m_privkey(timing_test_rng(), keysize), - m_pubkey(m_privkey), - m_enc(m_pubkey, timing_test_rng(), "Raw"), + m_pubkey(m_privkey.public_key()), + m_enc(*m_pubkey, timing_test_rng(), "Raw"), m_dec(m_privkey, timing_test_rng(), "PKCS1v15") {} std::vector prepare_input(const std::string& input) override { @@ -135,7 +166,7 @@ } uint64_t measure_critical_function(const std::vector& input) override { - TimingTestTimer timer; + const TimingTestTimer timer; m_dec.decrypt_or_random(input.data(), m_ctext_length, m_expected_content_size, timing_test_rng()); return timer.complete(); } @@ -144,7 +175,7 @@ const size_t m_expected_content_size = 48; const size_t m_ctext_length = 256; Botan::RSA_PrivateKey m_privkey; - Botan::RSA_PublicKey m_pubkey; + std::unique_ptr m_pubkey; Botan::PK_Encryptor_EME m_enc; Botan::PK_Decryptor_EME m_dec; }; @@ -164,8 +195,8 @@ public: explicit Manger_Timing_Test(size_t keysize) : m_privkey(timing_test_rng(), keysize), - m_pubkey(m_privkey), - m_enc(m_pubkey, timing_test_rng(), m_encrypt_padding), + m_pubkey(m_privkey.public_key()), + m_enc(*m_pubkey, timing_test_rng(), m_encrypt_padding), m_dec(m_privkey, timing_test_rng(), m_decrypt_padding) {} std::vector prepare_input(const std::string& input) override { @@ -174,7 +205,7 @@ } uint64_t measure_critical_function(const std::vector& input) override { - TimingTestTimer timer; + const TimingTestTimer timer; try { m_dec.decrypt(input.data(), m_ctext_length); } catch(Botan::Decoding_Error&) {} @@ -186,7 +217,7 @@ const std::string m_decrypt_padding = "EME1(SHA-256)"; const size_t m_ctext_length = 256; Botan::RSA_PrivateKey m_privkey; - Botan::RSA_PublicKey m_pubkey; + std::unique_ptr m_pubkey; Botan::PK_Encryptor_EME m_enc; Botan::PK_Decryptor_EME m_dec; }; @@ -237,13 +268,13 @@ Botan::secure_vector data(input.begin(), input.end()); Botan::secure_vector aad(13); const Botan::secure_vector iv(16); - Botan::secure_vector key(16 + m_mac_keylen); + const Botan::secure_vector key(16 + m_mac_keylen); m_dec.set_key(unlock(key)); m_dec.set_associated_data(aad); m_dec.start(unlock(iv)); - TimingTestTimer timer; + const TimingTestTimer timer; try { m_dec.finish(data); } catch(Botan::TLS::TLS_Exception&) {} @@ -262,34 +293,35 @@ private: const Botan::EC_Group m_group; - const Botan::ECDSA_PrivateKey m_privkey; const Botan::EC_Scalar m_x; Botan::EC_Scalar m_b; - Botan::EC_Scalar m_b_inv; - std::vector m_ws; }; ECDSA_Timing_Test::ECDSA_Timing_Test(const std::string& ecgroup) : m_group(Botan::EC_Group::from_name(ecgroup)), - m_privkey(timing_test_rng(), m_group), - m_x(m_privkey._private_key()), - m_b(Botan::EC_Scalar::random(m_group, timing_test_rng())), - m_b_inv(m_b.invert()) {} + m_x(Botan::EC_Scalar::random(m_group, timing_test_rng())), + m_b(Botan::EC_Scalar::random(m_group, timing_test_rng())) {} uint64_t ECDSA_Timing_Test::measure_critical_function(const std::vector& input) { const auto k = Botan::EC_Scalar::from_bytes_with_trunc(m_group, input); // fixed message to minimize noise const auto m = Botan::EC_Scalar::from_bytes_with_trunc(m_group, std::vector{5}); - TimingTestTimer timer; + const TimingTestTimer timer; // the following ECDSA operations involve and should not leak any information about k - const auto r = Botan::EC_Scalar::gk_x_mod_order(k, timing_test_rng(), m_ws); - const auto k_inv = k.invert(); - m_b.square_self(); - m_b_inv.square_self(); + + const auto r = Botan::EC_Scalar::gk_x_mod_order(k, timing_test_rng()); + + const auto k_inv = (m_b * k).invert(); + const auto xr_m = ((m_x * m_b) * r) + (m * m_b); - const auto s = (k_inv * xr_m) * m_b_inv; + + const auto s = (k_inv * xr_m); + + // Generate the next blinding value via modular squaring + m_b.square_self(); + BOTAN_UNUSED(r, s); return timer.complete(); @@ -307,14 +339,13 @@ private: const Botan::EC_Group m_group; - std::vector m_ws; }; uint64_t ECC_Mul_Timing_Test::measure_critical_function(const std::vector& input) { const auto k = Botan::EC_Scalar::from_bytes_with_trunc(m_group, input); - TimingTestTimer timer; - const auto kG = Botan::EC_AffinePoint::g_mul(k, timing_test_rng(), m_ws); + const TimingTestTimer timer; + const auto kG = Botan::EC_AffinePoint::g_mul(k, timing_test_rng()); return timer.complete(); } @@ -336,7 +367,7 @@ const Botan::BigInt x(input.data(), input.size()); const size_t max_x_bits = m_group.p_bits(); - TimingTestTimer timer; + const TimingTestTimer timer; const Botan::BigInt g_x_p = m_group.power_g_p(x, max_x_bits); @@ -360,7 +391,7 @@ uint64_t Invmod_Timing_Test::measure_critical_function(const std::vector& input) { const Botan::BigInt k(input.data(), input.size()); - TimingTestTimer timer; + const TimingTestTimer timer; const Botan::BigInt inv = Botan::inverse_mod_secret_prime(k, m_p); return timer.complete(); } @@ -389,9 +420,14 @@ size_t total_runs = 0; std::vector results(inputs.size()); + std::vector indexes(inputs.size()); + std::iota(indexes.begin(), indexes.end(), size_t{0}); + while(total_runs < (warmup_runs + measurement_runs)) { - for(size_t i = 0; i != inputs.size(); ++i) { - results[i] = measure_critical_function(inputs[i]); + shuffle_idx(indexes, *m_rng); + + for(const size_t testcase : indexes) { + results[testcase] = measure_critical_function(inputs[testcase]); } total_runs++; @@ -435,7 +471,7 @@ filename = test_data_dir + "/" + test_type + ".vec"; } - std::vector lines = read_testdata(filename); + const std::vector lines = read_testdata(filename); std::vector> results = test->execute_evaluation(lines, warmup_runs, measurement_runs); @@ -454,7 +490,7 @@ static std::vector read_testdata(const std::string& filename) { std::vector lines; std::ifstream infile(filename); - if(infile.good() == false) { + if(!infile.good()) { throw CLI_Error("Error reading test data from '" + filename + "'"); } std::string line; @@ -541,23 +577,47 @@ BOTAN_REGISTER_COMMAND("timing_test", Timing_Test_Command); -#if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EME_PKCS1) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) +#if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EME_PKCS1) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) && \ + defined(BOTAN_HAS_SYSTEM_RNG) class MARVIN_Test_Command final : public Command { public: - MARVIN_Test_Command() : Command("marvin_test key_file ctext_dir --runs=10 --output-nsec --expect-pt-len=0") {} + MARVIN_Test_Command() : + Command("marvin_test key_file ctext_dir --runs=1K --report-every=0 --output-nsec --expect-pt-len=0") {} std::string group() const override { return "testing"; } std::string description() const override { return "Run a test for MARVIN attack"; } + #if defined(BOTAN_TARGET_OS_HAS_POSIX1) + static inline volatile sig_atomic_t g_sigint_recv = 0; + + static void marvin_sigint_handler(int /*signal*/) { g_sigint_recv = 1; } + #endif + void go() override { const std::string key_file = get_arg("key_file"); const std::string ctext_dir = get_arg("ctext_dir"); - const size_t measurement_runs = get_arg_sz("runs"); + const size_t measurement_runs = parse_runs_arg(get_arg("runs")); const size_t expect_pt_len = get_arg_sz("expect-pt-len"); + const size_t report_every = get_arg_sz("report-every"); const bool output_nsec = flag_set("output-nsec"); + #if defined(BOTAN_TARGET_OS_HAS_POSIX1) + ::setenv("BOTAN_THREAD_POOL_SIZE", "none", /*overwrite?*/ 1); + + struct sigaction sigaction {}; + + sigaction.sa_handler = marvin_sigint_handler; + sigemptyset(&sigaction.sa_mask); + sigaction.sa_flags = 0; + + const int rc = ::sigaction(SIGINT, &sigaction, nullptr); + if(rc != 0) { + throw CLI_Error("Failed to set SIGINT handler"); + } + #endif + Botan::DataSource_Stream key_src(key_file); const auto key = Botan::PKCS8::load_key(key_src); @@ -591,71 +651,113 @@ throw CLI_Usage_Error("Empty ciphertext directory for MARVIN test"); } - Botan::PK_Decryptor_EME op(*key, rng(), "PKCS1v15"); + auto& test_results_file = output(); - std::vector indexes; - for(size_t i = 0; i != names.size(); ++i) { - indexes.push_back(i); - } + const size_t testcases = names.size(); - std::vector> measurements(names.size()); + #if defined(BOTAN_HAS_CHACHA_RNG) + auto rng = Botan::ChaCha_RNG(Botan::system_rng()); + #else + auto& rng = Botan::system_rng(); + #endif + + const Botan::PK_Decryptor_EME op(*key, rng, "PKCS1v15"); + + std::vector indexes(testcases); + std::iota(indexes.begin(), indexes.end(), size_t{0}); + + std::vector> measurements(testcases); for(auto& m : measurements) { m.reserve(measurement_runs); } + // This is only set differently if we exit early from the loop + size_t runs_completed = measurement_runs; + + std::vector ciphertext(modulus_bytes); + for(size_t r = 0; r != measurement_runs; ++r) { - shuffle(indexes, rng()); + if(r > 0 && report_every > 0 && (r % report_every) == 0) { + std::cerr << "Gathering sample # " << r << "\n"; + } - std::vector ciphertext(modulus_bytes); - for(size_t i = 0; i != indexes.size(); ++i) { - const size_t testcase = indexes[i]; + shuffle_idx(indexes, rng); - // FIXME should this load be constant time? - Botan::copy_mem(&ciphertext[0], &ciphertext_data[testcase * modulus_bytes], modulus_bytes); + for(const size_t testcase : indexes) { + // Load the test ciphertext in constant time to avoid cache pollution + for(size_t j = 0; j != testcases; ++j) { + const auto j_eq_testcase = Botan::CT::Mask::is_equal(j, testcase).as_choice(); + const auto* testcase_j = &ciphertext_data[j * modulus_bytes]; + Botan::CT::conditional_assign_mem(j_eq_testcase, ciphertext.data(), testcase_j, modulus_bytes); + } - TimingTestTimer timer; - op.decrypt_or_random(ciphertext.data(), modulus_bytes, expect_pt_len, rng()); + const TimingTestTimer timer; + op.decrypt_or_random(ciphertext.data(), modulus_bytes, expect_pt_len, rng); const uint64_t duration = timer.complete(); BOTAN_ASSERT_NOMSG(measurements[testcase].size() == r); measurements[testcase].push_back(duration); } + + #if defined(BOTAN_TARGET_OS_HAS_POSIX1) + // Early exit check + if(g_sigint_recv != 0) { + std::cerr << "Exiting early after " << r << " measurements\n"; + runs_completed = r; + break; + } + #endif } + report_results(test_results_file, names, measurements, runs_completed, output_nsec); + } + + private: + static void report_results(std::ostream& output, + std::span names, + std::span> measurements, + size_t runs_completed, + bool output_nsec) { for(size_t t = 0; t != names.size(); ++t) { if(t > 0) { - output() << ","; + output << ","; } - output() << names[t]; + output << names[t]; } - output() << "\n"; + output << "\n"; - for(size_t r = 0; r != measurement_runs; ++r) { + for(size_t r = 0; r != runs_completed; ++r) { for(size_t t = 0; t != names.size(); ++t) { if(t > 0) { - output() << ","; + output << ","; } const uint64_t dur_nsec = measurements[t][r]; if(output_nsec) { - output() << dur_nsec; + output << dur_nsec; } else { const double dur_s = static_cast(dur_nsec) / 1000000000.0; - output() << dur_s; + output << dur_s; } } - output() << "\n"; + output << "\n"; } } - template - void shuffle(std::vector& vec, Botan::RandomNumberGenerator& rng) { - const size_t n = vec.size(); - for(size_t i = 0; i != n; ++i) { - uint8_t jb[sizeof(uint64_t)]; - rng.randomize(jb, sizeof(jb)); - uint64_t j8 = Botan::load_le(jb, 0); - size_t j = i + static_cast(j8) % (n - i); - std::swap(vec[i], vec[j]); + static size_t parse_runs_arg(const std::string& param) { + if(param.starts_with("-")) { + throw CLI_Usage_Error("Cannot have a negative run count"); + } + + if(param.ends_with("m") || param.ends_with("M")) { + return parse_runs_arg(param.substr(0, param.size() - 1)) * 1'000'000; + } else if(param.ends_with("k") || param.ends_with("K")) { + return parse_runs_arg(param.substr(0, param.size() - 1)) * 1'000; + } else { + try { + return static_cast(std::stoul(param)); + } catch(std::exception&) { + throw CLI_Usage_Error("Unexpected syntax for --runs option (try 1000, 1K, or 2M)"); + } } } }; @@ -664,4 +766,6 @@ #endif +} // namespace + } // namespace Botan_CLI diff -Nru botan3-3.7.1+dfsg/src/cli/tls_client.cpp botan3-3.12.0+dfsg/src/cli/tls_client.cpp --- botan3-3.7.1+dfsg/src/cli/tls_client.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/tls_client.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -10,6 +10,9 @@ #include "cli.h" +#include +#include + #if defined(BOTAN_HAS_TLS) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) && defined(BOTAN_TARGET_OS_HAS_SOCKETS) #include @@ -20,7 +23,6 @@ #include #include #include - #include #if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER) #include @@ -40,14 +42,14 @@ class Callbacks : public Botan::TLS::Callbacks { public: - Callbacks(TLS_Client& client_command) : m_client_command(client_command), m_peer_closed(false) {} + explicit Callbacks(TLS_Client& client_command) : m_client_command(client_command), m_peer_closed(false) {} std::ostream& output(); bool flag_set(const std::string& flag_name) const; std::string get_arg(const std::string& arg_name) const; void send(std::span buffer); - int peer_closed() const { return m_peer_closed; } + bool peer_closed() const { return m_peer_closed; } void tls_verify_cert_chain(const std::vector& cert_chain, const std::vector>& ocsp, @@ -59,14 +61,14 @@ throw Botan::Invalid_Argument("Certificate chain was empty"); } - Botan::Path_Validation_Restrictions restrictions(policy.require_cert_revocation_info(), - policy.minimum_signature_strength()); + const Botan::Path_Validation_Restrictions restrictions(policy.require_cert_revocation_info(), + policy.minimum_signature_strength()); auto ocsp_timeout = std::chrono::milliseconds(1000); const std::string checked_name = flag_set("skip-hostname-check") ? "" : std::string(hostname); - Botan::Path_Validation_Result result = Botan::x509_path_validate( + const Botan::Path_Validation_Result result = Botan::x509_path_validate( cert_chain, restrictions, trusted_roots, checked_name, usage, tls_current_timestamp(), ocsp_timeout, ocsp); if(result.successful_validation()) { @@ -203,7 +205,7 @@ const uint16_t port = get_arg_u16("port"); const std::string transport = get_arg("type"); const std::string next_protos = get_arg("next-protocols"); - const bool use_system_cert_store = flag_set("skip-system-cert-store") == false; + const bool use_system_cert_store = !flag_set("skip-system-cert-store"); const std::string trusted_CAs = get_arg("trusted-cas"); const auto tls_version = get_arg("tls-version"); @@ -290,7 +292,7 @@ if(client.is_active()) { FD_SET(STDIN_FILENO, &readfds); if(first_active && !protocols_to_offer.empty()) { - std::string app = client.application_protocol(); + const std::string app = client.application_protocol(); if(!app.empty()) { output() << "Server choose protocol: " << client.application_protocol() << "\n"; } @@ -305,7 +307,7 @@ if(FD_ISSET(m_sockfd, &readfds)) { uint8_t buf[4 * 1024] = {0}; - ssize_t got = ::read(m_sockfd, buf, sizeof(buf)); + const ssize_t got = ::read(m_sockfd, buf, sizeof(buf)); if(got == 0) { output() << "EOF on socket\n"; @@ -324,7 +326,7 @@ if(FD_ISSET(STDIN_FILENO, &readfds)) { uint8_t buf[1024] = {0}; - ssize_t got = read(STDIN_FILENO, buf, sizeof(buf)); + const ssize_t got = read(STDIN_FILENO, buf, sizeof(buf)); if(got == 0) { output() << "EOF on stdin\n"; @@ -337,7 +339,7 @@ } if(got == 2 && buf[1] == '\n') { - char cmd = buf[0]; + const char cmd = buf[0]; if(cmd == 'R' || cmd == 'r') { output() << "Client initiated renegotiation\n"; @@ -385,19 +387,20 @@ private: static socket_type connect_to_host(const std::string& host, uint16_t port, bool tcp) { - addrinfo hints; - Botan::clear_mem(&hints, 1); + addrinfo hints{}; hints.ai_family = AF_UNSPEC; hints.ai_socktype = tcp ? SOCK_STREAM : SOCK_DGRAM; - addrinfo *res, *rp = nullptr; - if(::getaddrinfo(host.c_str(), std::to_string(port).c_str(), &hints, &res) != 0) { + unique_addr_info_ptr res = nullptr; + + if(::getaddrinfo(host.c_str(), std::to_string(port).c_str(), &hints, Botan::out_ptr(res)) != 0) { throw CLI_Error("getaddrinfo failed for " + host); } socket_type fd = 0; + bool success = false; - for(rp = res; rp != nullptr; rp = rp->ai_next) { + for(const addrinfo* rp = res.get(); rp != nullptr; rp = rp->ai_next) { fd = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if(fd == invalid_socket()) { @@ -409,14 +412,13 @@ continue; } + success = true; break; } - ::freeaddrinfo(res); - - if(rp == nullptr) // no address succeeded - { - throw CLI_Error("connect failed"); + if(!success) { + // no address succeeded + throw CLI_Error("Connecting to host failed"); } return fd; @@ -431,6 +433,12 @@ } socket_type m_sockfd = invalid_socket(); + + using unique_addr_info_ptr = std::unique_ptr; }; namespace { diff -Nru botan3-3.7.1+dfsg/src/cli/tls_helpers.h botan3-3.12.0+dfsg/src/cli/tls_helpers.h --- botan3-3.7.1+dfsg/src/cli/tls_helpers.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/tls_helpers.h 2026-05-07 01:38:28.000000000 +0000 @@ -8,15 +8,19 @@ #ifndef BOTAN_CLI_TLS_HELPERS_H_ #define BOTAN_CLI_TLS_HELPERS_H_ +#include +#include #include #include #include #include +#include #include #include #include #include #include +#include #include "cli_exceptions.h" @@ -25,8 +29,8 @@ #endif inline bool value_exists(const std::vector& vec, const std::string& val) { - for(size_t i = 0; i != vec.size(); ++i) { - if(vec[i] == val) { + for(const auto& v : vec) { + if(v == val) { return true; } } @@ -35,9 +39,13 @@ inline std::string maybe_hex_encode(std::string_view v) { auto is_printable_char = [](uint8_t c) { return c >= 32 && c < 127; }; - if(!std::all_of(v.begin(), v.end(), is_printable_char)) { - return Botan::hex_encode(std::span(reinterpret_cast(v.data()), v.size())); + + for(const char c : v) { + if(!is_printable_char(c)) { + return Botan::hex_encode(std::span(reinterpret_cast(v.data()), v.size())); + } } + return std::string(v); } @@ -88,7 +96,7 @@ // the Hash algorithm MUST be set when the PSK is established or // default to SHA-256 if no such algorithm is defined. m_psk_prf(psk_prf.value_or("SHA-256")) { - if(ca_path.empty() == false) { + if(!ca_path.empty()) { m_certstores.push_back(std::make_shared(ca_path)); } @@ -252,11 +260,10 @@ class TLS_All_Policy final : public Botan::TLS::Policy { public: std::vector allowed_ciphers() const override { - return std::vector{"ChaCha20Poly1305", - "AES-256/OCB(12)", - "AES-128/OCB(12)", - "AES-256/GCM", + return std::vector{"AES-256/GCM", "AES-128/GCM", + "ChaCha20Poly1305", + "AES-256/OCB(12)", "AES-256/CCM", "AES-128/CCM", "AES-256/CCM(8)", @@ -270,7 +277,8 @@ "Camellia-256", "Camellia-128", "SEED", - "3DES"}; + "3DES", + "NULL"}; } std::vector allowed_key_exchange_methods() const override { @@ -296,7 +304,7 @@ } else if(policy_type == "bsi") { return std::make_shared(); } else if(policy_type == "datagram") { - return std::make_shared(); + return std::make_shared(); } else if(policy_type == "all" || policy_type == "everything") { return std::make_shared(); } diff -Nru botan3-3.7.1+dfsg/src/cli/tls_http_server.cpp botan3-3.12.0+dfsg/src/cli/tls_http_server.cpp --- botan3-3.7.1+dfsg/src/cli/tls_http_server.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/tls_http_server.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -37,6 +37,7 @@ #include #include + #include #include #include #include @@ -78,17 +79,17 @@ Logger(std::ostream& out, std::ostream& err) : m_out(out), m_err(err) {} void log(std::string_view out) { - std::scoped_lock lk(m_mutex); + const std::scoped_lock lk(m_mutex); m_out << Botan::fmt("[{}] {}", timestamp(), out) << "\n"; } void error(std::string_view err) { - std::scoped_lock lk(m_mutex); + const std::scoped_lock lk(m_mutex); m_err << Botan::fmt("[{}] {}", timestamp(), err) << "\n"; } void flush() { - std::scoped_lock lk(m_mutex); + const std::scoped_lock lk(m_mutex); m_out.flush(); m_err.flush(); } @@ -133,7 +134,7 @@ strm << "Client random: " << Botan::hex_encode(client_hello.random()) << "\n"; strm << "Client offered following ciphersuites:\n"; - for(uint16_t suite_id : client_hello.ciphersuites()) { + for(const uint16_t suite_id : client_hello.ciphersuites()) { const auto ciphersuite = Botan::TLS::Ciphersuite::by_id(suite_id); strm << " - 0x" << std::hex << std::setfill('0') << std::setw(4) << suite_id << std::dec @@ -337,11 +338,11 @@ std::string description() const override { return "Provides a simple HTTP server"; } size_t thread_count() const { - if(size_t t = get_arg_sz("threads")) { + if(const size_t t = get_arg_sz("threads")) { return t; } #if defined(BOTAN_HAS_OS_UTILS) - if(size_t t = Botan::OS::get_cpu_available()) { + if(const size_t t = Botan::OS::get_cpu_available()) { return t; } #endif @@ -400,8 +401,8 @@ io.run(); - for(size_t i = 0; i < threads.size(); ++i) { - threads[i]->join(); + for(auto& thread : threads) { + thread->join(); } } }; diff -Nru botan3-3.7.1+dfsg/src/cli/tls_proxy.cpp botan3-3.12.0+dfsg/src/cli/tls_proxy.cpp --- botan3-3.7.1+dfsg/src/cli/tls_proxy.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/tls_proxy.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -19,11 +19,12 @@ #include #include - #include + #include #include #include #include + #include #include #include #include @@ -42,6 +43,8 @@ namespace { +// NOLINTBEGIN(*-avoid-endl,*-avoid-bind) + using boost::asio::ip::tcp; template @@ -83,7 +86,7 @@ class ServerStatus { public: - ServerStatus(size_t max_clients) : m_max_clients(max_clients), m_clients_serviced(0) {} + explicit ServerStatus(size_t max_clients) : m_max_clients(max_clients), m_clients_serviced(0) {} bool should_exit() const { if(m_max_clients == 0) { @@ -131,7 +134,7 @@ } void stop() { - if(m_is_closed == false) { + if(!m_is_closed) { /* Don't need to talk to the server anymore Client socket is closed during write callback @@ -175,20 +178,21 @@ try { if(!m_tls->is_active()) { - log_binary_message("From client", &m_c2p[0], bytes_transferred); + log_binary_message("From client", m_c2p.data(), bytes_transferred); } - m_tls->received_data(&m_c2p[0], bytes_transferred); + m_tls->received_data(m_c2p.data(), bytes_transferred); } catch(Botan::Exception& e) { log_exception("TLS connection failed", e); stop(); return; } - m_client_socket.async_read_some(boost::asio::buffer(&m_c2p[0], m_c2p.size()), - m_strand.wrap(boost::bind(&tls_proxy_session::client_read, - shared_from_this(), - boost::asio::placeholders::error, - boost::asio::placeholders::bytes_transferred))); + m_client_socket.async_read_some( + boost::asio::buffer(m_c2p), + boost::asio::bind_executor( + m_strand, [self = shared_from_this()](const boost::system::error_code& ec, std::size_t bytes) { + self->client_read(ec, bytes); + })); } void handle_client_write_completion(const boost::system::error_code& error) { @@ -231,13 +235,15 @@ if(m_p2c.empty() && !m_p2c_pending.empty()) { std::swap(m_p2c_pending, m_p2c); - log_binary_message("To Client", &m_p2c[0], m_p2c.size()); + log_binary_message("To Client", m_p2c.data(), m_p2c.size()); - boost::asio::async_write(m_client_socket, - boost::asio::buffer(&m_p2c[0], m_p2c.size()), - m_strand.wrap(boost::bind(&tls_proxy_session::handle_client_write_completion, - shared_from_this(), - boost::asio::placeholders::error))); + boost::asio::async_write( + m_client_socket, + boost::asio::buffer(m_p2c), + boost::asio::bind_executor( + m_strand, [self = shared_from_this()](const boost::system::error_code& ec, std::size_t /*bytes*/) { + self->handle_client_write_completion(ec); + })); } } @@ -250,13 +256,15 @@ if(m_p2s.empty() && !m_p2s_pending.empty()) { std::swap(m_p2s_pending, m_p2s); - log_text_message("To Server", &m_p2s[0], m_p2s.size()); + log_text_message("To Server", m_p2s.data(), m_p2s.size()); - boost::asio::async_write(m_server_socket, - boost::asio::buffer(&m_p2s[0], m_p2s.size()), - m_strand.wrap(boost::bind(&tls_proxy_session::handle_server_write_completion, - shared_from_this(), - boost::asio::placeholders::error))); + boost::asio::async_write( + m_server_socket, + boost::asio::buffer(m_p2s), + boost::asio::bind_executor( + m_strand, [self = shared_from_this()](const boost::system::error_code& ec, std::size_t /*bytes*/) { + self->handle_server_write_completion(ec); + })); } } @@ -268,10 +276,10 @@ } try { - if(bytes_transferred) { - log_text_message("Server to client", &m_s2p[0], m_s2p.size()); - log_binary_message("Server to client", &m_s2p[0], m_s2p.size()); - m_tls->send(&m_s2p[0], bytes_transferred); + if(bytes_transferred > 0) { + log_text_message("Server to client", m_s2p.data(), m_s2p.size()); + log_binary_message("Server to client", m_s2p.data(), m_s2p.size()); + m_tls->send(m_s2p.data(), bytes_transferred); } } catch(Botan::Exception& e) { log_exception("TLS connection failed", e); @@ -281,11 +289,12 @@ m_s2p.resize(readbuf_size); - m_server_socket.async_read_some(boost::asio::buffer(&m_s2p[0], m_s2p.size()), - m_strand.wrap(boost::bind(&tls_proxy_session::server_read, - shared_from_this(), - boost::asio::placeholders::error, - boost::asio::placeholders::bytes_transferred))); + m_server_socket.async_read_some( + boost::asio::buffer(m_s2p), + boost::asio::bind_executor( + m_strand, [self = shared_from_this()](const boost::system::error_code& ec, std::size_t bytes) { + self->server_read(ec, bytes); + })); } void tls_session_activated() override { @@ -367,7 +376,7 @@ } void serve_one_session() { - session::pointer new_session = make_session(); + const session::pointer new_session = make_session(); m_acceptor.async_accept( new_session->client_socket(), @@ -408,11 +417,11 @@ std::string description() const override { return "Proxies requests between a TLS client and a TLS server"; } size_t thread_count() const { - if(size_t t = get_arg_sz("threads")) { + if(const size_t t = get_arg_sz("threads")) { return t; } #if defined(BOTAN_HAS_OS_UTILS) - if(size_t t = Botan::OS::get_cpu_available()) { + if(const size_t t = Botan::OS::get_cpu_available()) { return t; } #endif @@ -454,7 +463,8 @@ session_mgr = std::make_shared(rng_as_shared()); } - tls_proxy_server server(io, listen_port, server_endpoint_iterator, creds, policy, session_mgr, max_clients); + const tls_proxy_server server( + io, listen_port, server_endpoint_iterator, creds, policy, session_mgr, max_clients); std::vector> threads; @@ -465,12 +475,14 @@ io.run(); - for(size_t i = 0; i < threads.size(); ++i) { - threads[i]->join(); + for(auto& thread : threads) { + thread->join(); } } }; +// NOLINTEND(*-avoid-endl,*-avoid-bind) + BOTAN_REGISTER_COMMAND("tls_proxy", TLS_Proxy); } // namespace Botan_CLI diff -Nru botan3-3.7.1+dfsg/src/cli/tls_server.cpp botan3-3.12.0+dfsg/src/cli/tls_server.cpp --- botan3-3.7.1+dfsg/src/cli/tls_server.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/tls_server.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -10,6 +10,8 @@ #include "cli.h" #include "sandbox.h" +#include + #if defined(BOTAN_TARGET_OS_HAS_SOCKETS) #include #endif @@ -49,7 +51,7 @@ class Callbacks : public Botan::TLS::Callbacks { public: - Callbacks(TLS_Server& server_command) : m_server_command(server_command) {} + explicit Callbacks(TLS_Server& server_command) : m_server_command(server_command) {} std::ostream& output(); void send(std::span buffer); @@ -75,8 +77,8 @@ } void tls_record_received(uint64_t /*seq_no*/, std::span input) override { - for(size_t i = 0; i != input.size(); ++i) { - const char c = static_cast(input[i]); + for(auto uc : input) { + const char c = static_cast(uc); m_line_buf += c; if(c == '\n') { push_pending_output(std::exchange(m_line_buf, {})); @@ -174,7 +176,7 @@ return; } - socket_type server_fd = make_server_socket(port); + const socket_type server_fd = make_server_socket(port); size_t clients_served = 0; output() << "Listening for new connections on " << transport << " port " << port << std::endl; @@ -187,11 +189,12 @@ if(m_is_tcp) { m_socket = ::accept(server_fd, nullptr, nullptr); } else { - struct sockaddr_in from; + struct sockaddr_in from {}; + socklen_t from_len = sizeof(sockaddr_in); void* peek_buf = nullptr; - size_t peek_len = 0; + size_t peek_len = 0; // NOLINT(*-const-correctness) #if defined(BOTAN_TARGET_OS_IS_MACOS) // macOS handles zero size buffers differently - it will return 0 even if there's no incoming data, @@ -224,7 +227,7 @@ if(!dump_traces_to.empty()) { auto now = std::chrono::system_clock::now().time_since_epoch(); - uint64_t timestamp = std::chrono::duration_cast(now).count(); + const uint64_t timestamp = std::chrono::duration_cast(now).count(); const std::string dump_file = dump_traces_to + "/tls_" + std::to_string(timestamp) + ".bin"; dump_stream = std::make_unique(dump_file.c_str()); } @@ -233,7 +236,7 @@ while(!server.is_closed()) { try { uint8_t buf[4 * 1024] = {0}; - ssize_t got = ::recv(m_socket, Botan::cast_uint8_ptr_to_char(buf), sizeof(buf), 0); + const ssize_t got = ::recv(m_socket, Botan::cast_uint8_ptr_to_char(buf), sizeof(buf), 0); if(got == -1) { error_output() << "Error in socket read - " << err_to_string(errno) << std::endl; @@ -252,7 +255,7 @@ server.received_data(buf, got); while(server.is_active() && !m_pending_output.empty()) { - std::string output = m_pending_output.front(); + const std::string output = m_pending_output.front(); m_pending_output.pop_front(); server.send(output); @@ -287,11 +290,11 @@ void send(std::span buf) { if(m_is_tcp) { - ssize_t sent = ::send(m_socket, buf.data(), static_cast(buf.size()), MSG_NOSIGNAL); + const ssize_t sent = ::send(m_socket, buf.data(), static_cast(buf.size()), MSG_NOSIGNAL); if(sent == -1) { error_output() << "Error writing to socket - " << err_to_string(errno) << std::endl; - } else if(sent != static_cast(buf.size())) { + } else if(sent >= 0 && static_cast(sent) != buf.size()) { error_output() << "Packet of length " << buf.size() << " truncated to " << sent << std::endl; } } else { @@ -317,12 +320,12 @@ socket_type make_server_socket(uint16_t port) { const int type = m_is_tcp ? SOCK_STREAM : SOCK_DGRAM; - socket_type fd = ::socket(PF_INET, type, 0); + const socket_type fd = ::socket(PF_INET, type, 0); if(fd == invalid_socket()) { throw CLI_Error("Unable to acquire socket"); } - sockaddr_in socket_info; + sockaddr_in socket_info{}; Botan::clear_mem(&socket_info, 1); socket_info.sin_family = AF_INET; socket_info.sin_port = htons(port); diff -Nru botan3-3.7.1+dfsg/src/cli/tls_utils.cpp botan3-3.12.0+dfsg/src/cli/tls_utils.cpp --- botan3-3.7.1+dfsg/src/cli/tls_utils.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/tls_utils.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,18 +9,26 @@ #if defined(BOTAN_HAS_TLS) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) #include + #include #include - #include #include #include #include #include #include + #if defined(BOTAN_HAS_TLS_12) && defined(BOTAN_HAS_TLS_13) + #include + #include + #include + #endif + #include "tls_helpers.h" namespace Botan_CLI { +namespace { + class TLS_Ciphersuites final : public Command { public: TLS_Ciphersuites() : Command("tls_ciphers --policy=default --version=tls1.2") {} @@ -46,12 +54,12 @@ auto policy = load_tls_policy(policy_type); - if(policy->acceptable_protocol_version(version) == false) { + if(!policy->acceptable_protocol_version(version)) { error_output() << "Error: the policy specified does not allow the given TLS version\n"; return; } - for(uint16_t suite_id : policy->ciphersuite_list(version)) { + for(const uint16_t suite_id : policy->ciphersuite_list(version)) { const auto s = Botan::TLS::Ciphersuite::by_id(suite_id); output() << ((s) ? s->to_string() : "unknown cipher suite") << "\n"; } @@ -60,7 +68,7 @@ BOTAN_REGISTER_COMMAND("tls_ciphers", TLS_Ciphersuites); - #if defined(BOTAN_HAS_TLS_13) + #if defined(BOTAN_HAS_TLS_12) && defined(BOTAN_HAS_TLS_13) class TLS_Client_Hello_Reader final : public Command { public: @@ -110,9 +118,14 @@ } try { - auto hello = Botan::TLS::Client_Hello_13::parse(input); - - output() << format_hello(hello); + output() << format_hello([&]() -> std::variant { + auto data = Botan::TLS::Client_Hello_13::parse(input); + if(std::holds_alternative(data)) { + return std::get(std::move(data)); + } else { + return Botan::TLS::Client_Hello_12(input); + } + }()); } catch(std::exception& e) { error_output() << "Parsing client hello failed: " << e.what() << "\n"; } @@ -126,11 +139,11 @@ const auto* hello_base = std::visit([](const auto& ch) -> const Botan::TLS::Client_Hello* { return &ch; }, hello); - const auto version = std::visit(Botan::overloaded{ - [](const Botan::TLS::Client_Hello_12&) { return "1.2"; }, - [](const Botan::TLS::Client_Hello_13&) { return "1.3"; }, - }, - hello); + const std::string version = std::visit(Botan::overloaded{ + [](const Botan::TLS::Client_Hello_12&) { return "1.2"; }, + [](const Botan::TLS::Client_Hello_13&) { return "1.3"; }, + }, + hello); oss << "Version: " << version << "\n" << "Random: " << Botan::hex_encode(hello_base->random()) << "\n"; @@ -138,7 +151,7 @@ if(!hello_base->session_id().empty()) { oss << "SessionID: " << Botan::hex_encode(hello_base->session_id().get()) << "\n"; } - for(uint16_t csuite_id : hello_base->ciphersuites()) { + for(const uint16_t csuite_id : hello_base->ciphersuites()) { const auto csuite = Botan::TLS::Ciphersuite::by_id(csuite_id); if(csuite && csuite->valid()) { oss << "Cipher: " << csuite->to_string() << "\n"; @@ -154,7 +167,7 @@ if(hello_base->signature_schemes().empty()) { oss << "Did not send signature_algorithms extension\n"; } else { - for(Botan::TLS::Signature_Scheme scheme : hello_base->signature_schemes()) { + for(const Botan::TLS::Signature_Scheme scheme : hello_base->signature_schemes()) { try { auto s = scheme.to_string(); oss << s << " "; @@ -165,7 +178,7 @@ oss << "\n"; } - if(auto sg = hello_base->extensions().get()) { + if(auto* sg = hello_base->extensions().get()) { oss << "Supported Groups: "; for(const auto group : sg->groups()) { oss << group.to_string().value_or(Botan::fmt("Unknown group: {}", group.wire_code())) << " "; @@ -183,7 +196,7 @@ hello_flags["Session Ticket"] = ch12.supports_session_ticket(); }, [&](const Botan::TLS::Client_Hello_13& ch13) { - if(auto ks = ch13.extensions().get()) { + if(auto* ks = ch13.extensions().get()) { oss << "Key Shares: "; for(const auto group : ks->offered_groups()) { oss << group.to_string().value_or(Botan::fmt("Unknown group: {}", group.wire_code())) @@ -207,6 +220,8 @@ #endif +} // namespace + } // namespace Botan_CLI #endif diff -Nru botan3-3.7.1+dfsg/src/cli/tss.cpp botan3-3.12.0+dfsg/src/cli/tss.cpp --- botan3-3.7.1+dfsg/src/cli/tss.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/tss.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -17,6 +17,8 @@ namespace Botan_CLI { +namespace { + #if defined(BOTAN_HAS_THRESHOLD_SECRET_SHARING) class TSS_Split final : public Command { @@ -73,7 +75,7 @@ } private: - Botan::secure_vector slurp_file_lvec(const std::string& input_file) { + static Botan::secure_vector slurp_file_lvec(const std::string& input_file) { Botan::secure_vector buf; auto insert_fn = [&](const uint8_t b[], size_t l) { buf.insert(buf.end(), b, b + l); }; Command::read_file(input_file, insert_fn, 4096); @@ -117,4 +119,6 @@ #endif +} // namespace + } // namespace Botan_CLI diff -Nru botan3-3.7.1+dfsg/src/cli/utils.cpp botan3-3.12.0+dfsg/src/cli/utils.cpp --- botan3-3.7.1+dfsg/src/cli/utils.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/utils.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,11 +8,14 @@ #include "cli.h" #include -#include -#include +#include #include #include +#if defined(BOTAN_HAS_CPUID) + #include +#endif + #if defined(BOTAN_HAS_HTTP_UTIL) #include #endif @@ -27,6 +30,8 @@ namespace Botan_CLI { +namespace { + class Print_Help final : public Command { public: Print_Help() : Command("help") {} @@ -126,7 +131,7 @@ output() << "Command '" << cmd << "' is " << (exists ? "" : "not ") << "available\n"; } - if(exists == false) { + if(!exists) { this->set_return_code(1); } } @@ -158,7 +163,7 @@ } else if(arg == "cflags") { output() << "-I" << BOTAN_INSTALL_PREFIX << "/" << BOTAN_INSTALL_HEADER_DIR << "\n"; } else if(arg == "ldflags") { - if(*BOTAN_LINK_FLAGS) { + if(*BOTAN_LINK_FLAGS != 0) { output() << BOTAN_LINK_FLAGS << ' '; } output() << "-L" << BOTAN_INSTALL_LIB_DIR << "\n"; @@ -191,6 +196,8 @@ BOTAN_REGISTER_COMMAND("version", Version_Info); +#if defined(BOTAN_HAS_CPUID) + class Print_Cpuid final : public Command { public: Print_Cpuid() : Command("cpuid") {} @@ -206,6 +213,8 @@ BOTAN_REGISTER_COMMAND("cpuid", Print_Cpuid); +#endif + #if defined(BOTAN_HAS_OS_UTILS) class Cycle_Counter final : public Command { @@ -280,7 +289,7 @@ std::string description() const override { return "Print a random UUID"; } void go() override { - Botan::UUID uuid(rng()); + const Botan::UUID uuid(rng()); output() << uuid.to_string() << "\n"; } }; @@ -312,4 +321,6 @@ #endif // http_util +} // namespace + } // namespace Botan_CLI diff -Nru botan3-3.7.1+dfsg/src/cli/x509.cpp botan3-3.12.0+dfsg/src/cli/x509.cpp --- botan3-3.7.1+dfsg/src/cli/x509.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/x509.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -59,8 +59,6 @@ } } -} // namespace - #if defined(BOTAN_HAS_CERTSTOR_SYSTEM) class Trust_Root_Info final : public Command { @@ -72,7 +70,7 @@ std::string description() const override { return "List certs in the system trust store"; } void go() override { - Botan::System_Certificate_Store trust_roots; + const Botan::System_Certificate_Store trust_roots; const auto dn_list = trust_roots.all_subjects(); @@ -106,37 +104,48 @@ class Sign_Cert final : public Command { public: Sign_Cert() : - Command( - "sign_cert --ca-key-pass= --hash= " - "--duration=365 --emsa= ca_cert ca_key pkcs10_req") {} + Command("sign_cert --ca-key-pass= --hash= --padding= --emsa= --duration=365 ca_cert ca_key pkcs10_req") {} std::string group() const override { return "x509"; } std::string description() const override { return "Create a CA-signed X.509 certificate from a PKCS #10 CSR"; } void go() override { - Botan::X509_Certificate ca_cert(get_arg("ca_cert")); + const Botan::X509_Certificate ca_cert(get_arg("ca_cert")); const std::string key_file = get_arg("ca_key"); const std::string pass = get_passphrase_arg("Password for " + key_file, "ca-key-pass"); - const std::string emsa = get_arg("emsa"); + + // TODO(Botan4) remove --emsa option and this logic + const std::string padding = [&]() { + auto p = get_arg("padding"); + auto e = get_arg("emsa"); + if(e.empty() || p == e) { + return p; + } else if(p.empty()) { + return e; + } else { + throw CLI_Usage_Error("Use either --padding or --emsa not both"); + } + }(); + const std::string hash = get_arg("hash"); auto key = load_private_key(key_file, pass); - Botan::X509_CA ca(ca_cert, *key, hash, emsa, rng()); + const Botan::X509_CA ca(ca_cert, *key, hash, padding, rng()); - Botan::PKCS10_Request req(get_arg("pkcs10_req")); + const Botan::PKCS10_Request req(get_arg("pkcs10_req")); auto now = std::chrono::system_clock::now(); - Botan::X509_Time start_time(now); + const Botan::X509_Time start_time(now); typedef std::chrono::duration> days; - Botan::X509_Time end_time(now + days(get_arg_sz("duration"))); + const Botan::X509_Time end_time(now + days(get_arg_sz("duration"))); - Botan::X509_Certificate new_cert = ca.sign_request(req, rng(), start_time, end_time); + const Botan::X509_Certificate new_cert = ca.sign_request(req, rng(), start_time, end_time); update_stateful_private_key(*key, rng(), key_file, pass); output() << new_cert.PEM_encode(); @@ -154,23 +163,23 @@ std::string description() const override { return "Parse X.509 certificate and display data fields"; } void go() override { - std::vector data = slurp_file(get_arg("file")); + const std::vector data = slurp_file(get_arg("file")); Botan::DataSource_Memory in(data); while(!in.end_of_data()) { try { - Botan::X509_Certificate cert(in); + const Botan::X509_Certificate cert(in); try { - output() << cert.to_string() << std::endl; + output() << cert.to_string() << "\n"; } catch(Botan::Exception& e) { // to_string failed - report the exception and continue output() << "X509_Certificate::to_string failed: " << e.what() << "\n"; } if(flag_set("fingerprint")) { - output() << "Fingerprint: " << cert.fingerprint("SHA-256") << std::endl; + output() << "Fingerprint: " << cert.fingerprint("SHA-256") << "\n"; } } catch(Botan::Exception& e) { if(!in.end_of_data()) { @@ -196,13 +205,13 @@ } void go() override { - Botan::X509_Certificate subject(get_arg("subject")); - Botan::X509_Certificate issuer(get_arg("issuer")); - std::chrono::milliseconds timeout(get_arg_sz("timeout")); + const Botan::X509_Certificate subject(get_arg("subject")); + const Botan::X509_Certificate issuer(get_arg("issuer")); + const std::chrono::milliseconds timeout(get_arg_sz("timeout")); Botan::Certificate_Store_In_Memory cas; cas.add_certificate(issuer); - Botan::OCSP::Response resp = Botan::OCSP::online_check(issuer, subject, timeout); + const Botan::OCSP::Response resp = Botan::OCSP::online_check(issuer, subject, timeout); auto status = resp.status_for(issuer, subject, std::chrono::system_clock::now()); @@ -229,16 +238,16 @@ } void go() override { - Botan::X509_Certificate subject_cert(get_arg("subject")); + const Botan::X509_Certificate subject_cert(get_arg("subject")); Botan::Certificate_Store_In_Memory trusted; for(const auto& certfile : get_arg_list("ca_certs")) { trusted.add_certificate(Botan::X509_Certificate(certfile)); } - Botan::Path_Validation_Restrictions restrictions; + const Botan::Path_Validation_Restrictions restrictions; - Botan::Path_Validation_Result result = Botan::x509_path_validate(subject_cert, restrictions, trusted); + const Botan::Path_Validation_Result result = Botan::x509_path_validate(subject_cert, restrictions, trusted); if(result.successful_validation()) { output() << "Certificate passes validation checks\n"; @@ -255,7 +264,8 @@ Gen_Self_Signed() : Command( "gen_self_signed key CN --country= --dns= " - "--organization= --email= --path-limit=1 --days=365 --key-pass= --ca --hash= --emsa= --der") {} + "--organization= --email= --path-limit=1 --days=365 --key-pass= --ca --hash= --padding= --emsa= --der") { + } std::string group() const override { return "x509"; } @@ -277,17 +287,28 @@ opts.more_dns = Command::split_on(get_arg("dns"), ','); const bool der_format = flag_set("der"); - std::string emsa = get_arg("emsa"); + // TODO(Botan4) remove --emsa option and this logic + const std::string padding = [&]() { + auto p = get_arg("padding"); + auto e = get_arg("emsa"); + if(e.empty() || p == e) { + return p; + } else if(p.empty()) { + return e; + } else { + throw CLI_Usage_Error("Use either --padding or --emsa not both"); + } + }(); - if(emsa.empty() == false) { - opts.set_padding_scheme(emsa); + if(padding.empty() == false) { + opts.set_padding_scheme(padding); } if(flag_set("ca")) { opts.CA_key(get_arg_sz("path-limit")); } - Botan::X509_Certificate cert = Botan::X509::create_self_signed_cert(opts, *key, get_arg("hash"), rng()); + const Botan::X509_Certificate cert = Botan::X509::create_self_signed_cert(opts, *key, get_arg("hash"), rng()); update_stateful_private_key(*key, rng(), key_file, passphrase); if(der_format) { @@ -306,7 +327,7 @@ Generate_PKCS10() : Command( "gen_pkcs10 key CN --country= --organization= " - "--ca --path-limit=1 --email= --dns= --ext-ku= --key-pass= --hash= --emsa=") {} + "--ca --path-limit=1 --email= --dns= --ext-ku= --key-pass= --hash= --padding= --emsa=") {} std::string group() const override { return "x509"; } @@ -334,13 +355,24 @@ opts.add_ex_constraint(ext_ku); } - std::string emsa = get_arg("emsa"); + // TODO(Botan4) remove --emsa option and this logic + const std::string padding = [&]() { + auto p = get_arg("padding"); + auto e = get_arg("emsa"); + if(e.empty() || p == e) { + return p; + } else if(p.empty()) { + return e; + } else { + throw CLI_Usage_Error("Use either --padding or --emsa not both"); + } + }(); - if(emsa.empty() == false) { - opts.set_padding_scheme(emsa); + if(padding.empty() == false) { + opts.set_padding_scheme(padding); } - Botan::PKCS10_Request req = Botan::X509::create_cert_req(opts, *key, get_arg("hash"), rng()); + const Botan::PKCS10_Request req = Botan::X509::create_cert_req(opts, *key, get_arg("hash"), rng()); update_stateful_private_key(*key, rng(), key_file, passphrase); output() << req.PEM_encode(); @@ -349,6 +381,8 @@ BOTAN_REGISTER_COMMAND("gen_pkcs10", Generate_PKCS10); +} // namespace + } // namespace Botan_CLI #endif diff -Nru botan3-3.7.1+dfsg/src/cli/zfec.cpp botan3-3.12.0+dfsg/src/cli/zfec.cpp --- botan3-3.7.1+dfsg/src/cli/zfec.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/cli/zfec.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -17,14 +17,16 @@ namespace Botan_CLI { +namespace { + #if defined(BOTAN_HAS_ZFEC) && defined(BOTAN_HAS_SHA2_64) -static const uint32_t FEC_MAGIC = 0xFECC0DEC; +constexpr uint32_t FEC_MAGIC = 0xFECC0DEC; const char* const FEC_SHARE_HASH = "SHA-512-256"; class FEC_Share final { public: - FEC_Share() : m_share(0), m_k(0), m_n(0), m_padding(0), m_bits() {} + FEC_Share() : m_share(0), m_k(0), m_n(0), m_padding(0) {} FEC_Share(size_t share, size_t k, size_t n, size_t padding, const uint8_t bits[], size_t len) : m_share(share), m_k(k), m_n(n), m_padding(padding), m_bits(bits, bits + len) {} @@ -59,10 +61,10 @@ } } - size_t share_id = bits[4]; - size_t k = bits[5]; - size_t n = bits[6]; - size_t padding = bits[7]; + const size_t share_id = bits[4]; + const size_t k = bits[5]; + const size_t n = bits[6]; + const size_t padding = bits[7]; if(share_id >= n || k >= n || padding >= k) { throw CLI_Error("FEC share has invalid k/n/padding fields"); @@ -128,7 +130,7 @@ const std::string input = get_arg("input"); const std::string output_dir = get_arg("output-dir"); - Botan::ZFEC fec(k, n); // checks k/n for validity + const Botan::ZFEC fec(k, n); // checks k/n for validity auto hash = Botan::HashFunction::create_or_throw(FEC_SHARE_HASH); @@ -167,7 +169,7 @@ std::ofstream output(output_fsname.str(), std::ios::binary); - FEC_Share fec_share(share, k, n, padding, bits, len); + const FEC_Share fec_share(share, k, n, padding, bits, len); fec_share.serialize_to(*hash, output); }; @@ -234,7 +236,7 @@ return; } - Botan::ZFEC fec(k, n); + const Botan::ZFEC fec(k, n); std::vector decoded(share_size * k); @@ -297,4 +299,6 @@ #endif +} // namespace + } // namespace Botan_CLI diff -Nru botan3-3.7.1+dfsg/src/configs/ci_deps.conf botan3-3.12.0+dfsg/src/configs/ci_deps.conf --- botan3-3.7.1+dfsg/src/configs/ci_deps.conf 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/configs/ci_deps.conf 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,28 @@ + +[android_ndk] +url=https://dl.google.com/android/repository/android-ndk-r29-linux.zip +sha256=4abbbcdc842f3d4879206e9695d52709603e52dd68d3c1fff04b3b5e7a308ecf + +[intel_sde] +url=https://downloadmirror.intel.com/915934/sde-external-10.8.0-2026-03-15-lin.tar.xz +sha256=50b320cd226acef7a491f5b321fc1be3c3c7984f9e27a456e64894b5b0979dd3 + +[limbo] +url=https://raw.githubusercontent.com/C2SP/x509-limbo/f47fd1ae26eebaee24116039a4e28d85840b79a5/limbo.json +sha256=12bde89c688edd921ba8e892b314317aa04f98bbcd3d62e985bde2bebe099357 + +[coveralls] +url=https://github.com/coverallsapp/coverage-reporter/releases/download/v0.6.17/coveralls-linux.tar.gz +sha256=f3c837413f66a6402953eee9ba3486366bd3de536100adb4a670bbd83c0382e0 + +[esdm] +url=https://github.com/smuellerDD/esdm/archive/refs/tags/v1.2.0.tar.gz +sha256=83e5f0539ab8688661f099f8fa380289cb1d24a942ce93306766bb2edb1bb19d + +[jitterentropy] +url=https://github.com/smuellerDD/jitterentropy-library/archive/refs/tags/v3.6.2.tar.gz +sha256=33825562f62e599d402e9106a5626090572fcb2cb41d157736f236455d1c3fdb + +[sccache_windows] +url=https://github.com/mozilla/sccache/releases/download/v0.15.0/sccache-v0.15.0-x86_64-pc-windows-msvc.tar.gz +sha256=b0b257a164bf438b2dea134ca7ded41c100f59a64b3bf275a202f1e8102ab217 diff -Nru botan3-3.7.1+dfsg/src/configs/clang-format botan3-3.12.0+dfsg/src/configs/clang-format --- botan3-3.7.1+dfsg/src/configs/clang-format 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/configs/clang-format 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,60 @@ +Language: Cpp +Standard: c++20 + +BasedOnStyle: Chromium + +ColumnLimit: 120 +AccessModifierOffset: -3 +IndentWidth: 3 +ContinuationIndentWidth: 3 +ConstructorInitializerIndentWidth: 6 + +PointerAlignment: Left +ReferenceAlignment: Left +QualifierAlignment: Left + +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^' + Priority: 3 + CaseSensitive: false + - Regex: '^' + Priority: 2 + CaseSensitive: false + - Regex: '^<.*' + Priority: 4 + CaseSensitive: false + - Regex: '^<.*\.h>' + Priority: 3 + CaseSensitive: false + - Regex: '.*' + Priority: 1 + CaseSensitive: false + +AttributeMacros: ['BOTAN_FUNC_ISA', + 'BOTAN_FUNC_ISA_INLINE', + 'BOTAN_FORCE_INLINE', + 'BOTAN_DEPRECATED', + 'BOTAN_DEPRECATED_API'] + +BinPackArguments: false +BreakStringLiterals: false +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +ConstructorInitializerAllOnOneLineOrOnePerLine: true +EmptyLineBeforeAccessModifier: Always + +BreakConstructorInitializers: AfterColon +BreakInheritanceList: AfterComma +AllowShortBlocksOnASingleLine: Empty +AllowShortFunctionsOnASingleLine: Inline +SpaceBeforeParens: Never +IndentPPDirectives: BeforeHash +FixNamespaceComments: true +SeparateDefinitionBlocks: Always +KeepEmptyLinesAtTheStartOfBlocks: false +IndentAccessModifiers: true +ReflowComments: false +RequiresClausePosition: OwnLine +IndentRequiresClause: true +InsertNewlineAtEOF: True diff -Nru botan3-3.7.1+dfsg/src/configs/pylint.rc botan3-3.12.0+dfsg/src/configs/pylint.rc --- botan3-3.7.1+dfsg/src/configs/pylint.rc 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/configs/pylint.rc 2026-05-07 01:38:28.000000000 +0000 @@ -41,6 +41,7 @@ too-many-instance-attributes, too-many-public-methods, too-many-return-statements, +# too-many-positional-arguments, missing-docstring, # nice to have, but not necessary in every script fixme, # is it better to omit the note that something is worth improving??? @@ -283,7 +284,9 @@ [DESIGN] # Maximum number of arguments for function / method -max-args=8 +max-args = 10 + +#max-positional-arguments = 10 # Argument names that match this expression will be ignored. Default to name # with leading underscore diff -Nru botan3-3.7.1+dfsg/src/configs/repo_config.env botan3-3.12.0+dfsg/src/configs/repo_config.env --- botan3-3.7.1+dfsg/src/configs/repo_config.env 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/configs/repo_config.env 2026-05-07 01:38:28.000000000 +0000 @@ -16,24 +16,21 @@ BORINGSSL_REPO="randombit/boringssl" # The branch in our fork of boringssl that should be used for BoGo tests -BORINGSSL_BRANCH="rene/runner-20241016" +BORINGSSL_BRANCH="botan-bogo-20260429" # The Android NDK to for the cross platform builds to Android -ANDROID_NDK="android-ndk-r26" - -# Jitterentropy library version to be used for testing the 'jitter_rng' module -JITTERENTROPY_VERSION="3.6.0" - -# Entropy Source and DRNG Manager (ESDM) bundle version used to test the ESDM adapter -ESDM_VERSION="1.2.0" +ANDROID_NDK="android-ndk-r29" # The version of the Intel SDE tool to use for running the Intel SDE tests -INTEL_SDE_VERSION="sde-external-9.38.0-2024-04-18-lin" +INTEL_SDE_VERSION="sde-external-10.8.0-2026-03-15-lin" + +# Git repository URL for Wycheproof test vectors +WYCHEPROOF_GIT_URL=https://github.com/C2SP/wycheproof.git -# Limbo test suite revision to be used in run_limbo_tests.py -LIMBO_TEST_SUITE_REVISION="ec604cf2b1eebe22c6ffc40e380517c6d49c78cc" +# Git repository URL for NIST ACVP test vectors +ACVP_SERVER_GIT_URL=https://github.com/usnistgov/ACVP-Server.git # The maximum size of the compiler cache in CI # Those variables are directly consumed by ccache and sccache respectively -CCACHE_MAXSIZE="200M" -SCCACHE_CACHE_SIZE="200M" +CCACHE_MAXSIZE="300M" +SCCACHE_CACHE_SIZE="300M" diff -Nru botan3-3.7.1+dfsg/src/configs/sphinx/conf.py botan3-3.12.0+dfsg/src/configs/sphinx/conf.py --- botan3-3.7.1+dfsg/src/configs/sphinx/conf.py 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/configs/sphinx/conf.py 2026-05-07 01:38:28.000000000 +0000 @@ -101,7 +101,7 @@ try: # On Arch this is python-sphinx-furo - import furo + import furo # noqa: F401 html_theme = "furo" # Add a small edit button to each document to allow visitors to easily @@ -111,7 +111,7 @@ 'source_branch': 'master', 'source_directory': 'doc/', } -except ImportError as e: +except ImportError: print("Could not import furo theme; falling back to agago") html_theme = 'agogo' html_theme_path = [] @@ -234,3 +234,9 @@ # Make sure the target is unique autosectionlabel_prefix_document = True + +# -- Options for texinfo output -------------------------------------------------- +authors = 'The Botan Authors' + +# Show URL addresses after external links, options are 'inline', 'footnote' and 'no' +texinfo_show_urls = 'inline' diff -Nru botan3-3.7.1+dfsg/src/configs/typos.toml botan3-3.12.0+dfsg/src/configs/typos.toml --- botan3-3.7.1+dfsg/src/configs/typos.toml 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/configs/typos.toml 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,61 @@ +[files] +extend-exclude = [ + "src/lib/prov/pkcs11/pkcs11.h", + "src/build-data/cc/msvc.txt", + "src/build-data/cc/clangcl.txt", + "doc/authors.txt", +] + +[default] +extend-ignore-re = [ + "\\b[0-9A-Za-z+/]{20,80}(=|==)?\\b", + "\\b[0-9A-Fa-f]{9,80}\\b", +] + +[default.extend-words] +# This is a hack for dealing with hex +BA = "BA" +ba = "ba" + +# These should be removed if possible +Probablistic = "Probablistic" # pkcs11.h typo +divisable = "divisable" # Typo in Limbo test data + +# Weird names only used in a few modules +Lik = "Lik" # TODO(Botan4) remove when mce is removed +Projet = "Projet" # TODO(Botan4) remove when mce is removed + +# Names that typos doesn't know about +EMAC = "EMAC" +GOST = "GOST" +EDE = "EDE" +vor = "vor" + +# clang-analyzer +optin = "optin" + +# Abbreviations used locally, some not ideal +chello = "chello" +issu = "issu" +parm = "parm" +ser = "ser" +stuf = "stuf" +typ = "typ" +indx = "indx" +ACI = "ACI" + +[default.extend-identifiers] +countr_zero = "countr_zero" +Tru64 = "Tru64" + +# ARIA "FO" rounds makes typos upset +ARIA_FO = "ARIA_FO" +FO = "FO" +apply_fo_sbox = "apply_fo_sbox" +aria_fo_sbox = "aria_fo_sbox" +aria_fo = "aria_fo" +aria_fo_m = "aria_fo_m" +fo_pre_const = "fo_pre_const" +fo_post_const = "fo_post_const" +fo_pre_mat = "fo_pre_mat" +fo_post_mat = "fo_post_mat" diff -Nru botan3-3.7.1+dfsg/src/configs/zizmor.yml botan3-3.12.0+dfsg/src/configs/zizmor.yml --- botan3-3.7.1+dfsg/src/configs/zizmor.yml 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/configs/zizmor.yml 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,8 @@ +rules: + unpinned-uses: + config: + policies: + actions/*: ref-pin + google/oss-fuzz/*: ref-pin + github/codeql-action/*: ref-pin + "*": hash-pin diff -Nru botan3-3.7.1+dfsg/src/ct_selftest/ct_selftest.cpp botan3-3.12.0+dfsg/src/ct_selftest/ct_selftest.cpp --- botan3-3.7.1+dfsg/src/ct_selftest/ct_selftest.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/ct_selftest/ct_selftest.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -16,7 +16,9 @@ #include #include +#include +#include #include #include @@ -135,7 +137,7 @@ std::array output_bytes; std::memset(output_bytes.data(), 0x42, sizeof(output_bytes)); - // This mimicks what went wrong in Kyber's secret message expansion + // This mimics what went wrong in Kyber's secret message expansion // that was found by PQShield in Kyber's reference implementation and // was fixed in https://github.com/randombit/botan/pull/4107. // @@ -332,7 +334,7 @@ return 1; } -#if !defined(BOTAN_CT_POISON_ENABLED) +#if !defined(BOTAN_HAS_VALGRIND) std::cout << "The CT::poison API is disabled in this build, this test won't do anything useful\n" << "Configure with a compatible checker (e.g. --with-valgrind) to make the magic happen." << std::endl; return 1; diff -Nru botan3-3.7.1+dfsg/src/editors/vscode/extensions.json botan3-3.12.0+dfsg/src/editors/vscode/extensions.json --- botan3-3.7.1+dfsg/src/editors/vscode/extensions.json 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/editors/vscode/extensions.json 2026-05-07 01:38:28.000000000 +0000 @@ -1,14 +1,9 @@ { - // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. - // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp - // List of extensions which should be recommended for users of this workspace. "recommendations": [ + "llvm-vs-code-extensions.vscode-clangd", "ms-vscode.cpptools", + "ms-vscode.cpptools-themes", "ms-python.python", - "ms-python.pylint", - "xaver.clang-format", "EditorConfig.EditorConfig" - ], - // List of extensions recommended by VS Code that should not be recommended for users of this workspace. - "unwantedRecommendations": [] + ] } diff -Nru botan3-3.7.1+dfsg/src/editors/vscode/launch.json botan3-3.12.0+dfsg/src/editors/vscode/launch.json --- botan3-3.7.1+dfsg/src/editors/vscode/launch.json 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/editors/vscode/launch.json 2026-05-07 01:38:28.000000000 +0000 @@ -1,19 +1,30 @@ { + "inputs": [ + { + "id": "testSelection", + "type": "promptString", + "description": "Which test would you like to run? (see './botan-test --list-tests')", + "args": { + "prompt": "Test Selection" + } + } + ], "configurations": [ { - "name": "Debug Unittests", "name": "Debug Unittests (gdb)", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/botan-test", "args": [ - "--test-threads=1" + "--test-threads=1", + "${input:testSelection}" ], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": false, "MIMode": "gdb", + "preLaunchTask": "Build Unittests", "setupCommands": [ { "description": "Enable pretty-printing for gdb", @@ -33,12 +44,48 @@ "request": "launch", "program": "${workspaceFolder}/botan-test.exe", "args": [ - "--test-threads=1" + "--test-threads=1", + "${input:testSelection}" ], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], - "console": "externalTerminal" + "console": "externalTerminal", + "preLaunchTask": "Build Unittests" + }, + { + // Debugging of BoGo tests: + // + // 1. Run the BoGo tests with the --wait-for-debugger option + // $> src/editors/vscode/scripts/bogo.py --wait-for-debugger '' + // 2. Start this debugger configuration from VS Code and + // select the 'botan_bogo_shim' process in the process picker + // 3. Wait for the BoGo test to start the test + // + // Note that attaching might fail due to missing privileges. + // In that case, you can try to first run the following command: + // $> sudo sysctl kernel.yama.ptrace_scope=0 + "name": "Debug BoGo (gdb)", + "type": "cppdbg", + "request": "attach", + "processId":"${command:pickProcess}", + "program": "${workspaceFolder}/botan_bogo_shim", + "stopAtEntry": true, + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "description": "Set Disassembly Flavor to Intel", + "text": "-gdb-set disassembly-flavor intel", + "ignoreFailures": true + } + ] } ] } diff -Nru botan3-3.7.1+dfsg/src/editors/vscode/scripts/bogo.py botan3-3.12.0+dfsg/src/editors/vscode/scripts/bogo.py --- botan3-3.7.1+dfsg/src/editors/vscode/scripts/bogo.py 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/editors/vscode/scripts/bogo.py 2026-05-07 01:38:28.000000000 +0000 @@ -1,7 +1,7 @@ #!/usr/bin/env python3 +import argparse import os -import sys from common import run_cmd, get_concurrency @@ -12,10 +12,30 @@ BOGO_PATH = os.path.join(BORING_PATH, "ssl", "test", "runner") SHIM_PATH = "./botan_bogo_shim" +SHIM_CONFIG_NO_TLS13 = "src/bogo_shim/config_no_tls13.json" +SHIM_CONFIG_NO_TLS12 = "src/bogo_shim/config_no_tls12.json" SHIM_CONFIG = "src/bogo_shim/config.json" def main(): + parser = argparse.ArgumentParser(description='Run BoringSSL Bogo tests with Botan shim') + parser.add_argument('--without-tls-12', action='store_true', + help='Use shim config that disables TLS 1.2') + parser.add_argument('--without-tls-13', action='store_true', + help='Use shim config that disables TLS 1.3') + parser.add_argument('--wait-for-debugger', action='store_true', + help='BoGo waits for some seconds so that we can attach a debugger to the shim') + parser.add_argument('bogo_args', nargs=argparse.REMAINDER, help='Extra args for the bogo runner') + args = parser.parse_args() + + # Select config depending on the option + if args.without_tls_13: + config_path = SHIM_CONFIG_NO_TLS13 + elif args.without_tls_12: + config_path = SHIM_CONFIG_NO_TLS12 + else: + config_path = SHIM_CONFIG + if not os.path.isdir(BORING_PATH): # check out our fork of boring ssl run_cmd("git clone --depth 1 --branch %s %s %s" % @@ -24,12 +44,15 @@ # make doubly sure we're on the correct branch run_cmd("git -C %s checkout %s" % (BORING_PATH, BORING_BRANCH)) - extra_args = "-debug -test '%s'" % ';'.join( - sys.argv[1:]) if len(sys.argv) > 1 else '' + bogo_args = ';'.join(args.bogo_args) if args.bogo_args else '' + extra_args = "-wait-for-debugger " if args.wait_for_debugger else "" + extra_args += "-skip-tls12 -skip-dtls " if args.without_tls_12 else "" + extra_args += "-skip-tls13 " if args.without_tls_13 else "" + extra_args += "-debug -test '%s'" % bogo_args if bogo_args else '' run_cmd("go test -pipe -num-workers %d -shim-path %s -shim-config %s %s" % - (get_concurrency(), os.path.abspath(SHIM_PATH), os.path.abspath(SHIM_CONFIG), extra_args), BOGO_PATH) - + (get_concurrency(), os.path.abspath(SHIM_PATH), os.path.abspath(config_path), extra_args), + BOGO_PATH) if __name__ == '__main__': main() diff -Nru botan3-3.7.1+dfsg/src/editors/vscode/scripts/test.py botan3-3.12.0+dfsg/src/editors/vscode/scripts/test.py --- botan3-3.7.1+dfsg/src/editors/vscode/scripts/test.py 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/editors/vscode/scripts/test.py 2026-05-07 01:38:28.000000000 +0000 @@ -2,43 +2,57 @@ import os import re -import sys +import argparse +import glob import common - TESTS_DIR = "src/tests" -def get_test_names_from(test_file): +def discover_tests_in_file(test_file): if not os.path.dirname(test_file) == TESTS_DIR: - raise common.BuildError( - 'Given file path is not a Botan unit test: ' + test_file) + return [] with open(test_file, 'r', encoding='utf-8') as f: find_test_registration = \ re.compile( - r'BOTAN_REGISTER_TEST(_FN)?\s*\(\s*\"(.+)\",\s*\"(.+)\",[^)]+\)') + r'BOTAN_REGISTER_[A-Z_]*TEST(_FN)?\s*\(\s*\"(.+)\",\s*\"(.+)\",[^)]+\)') matches = find_test_registration.findall(f.read()) - tests = [match[-1] for match in matches] - - if not tests: - raise common.BuildError( - 'Failed to find a BOTAN_REGISTER_TEST in the given test file: ' + test_file) + return [match[-1] for match in matches] - return tests +def discover_tests(args): + tests = [] + if args.test_src_file: + tests = discover_tests_in_file(args.test_src_file) + + if args.list and not tests: + # Apparently 'test_src_file' didn't contain any tests, lets + # go ahead and discover all unit tests in the src/tests dir. + test_files = glob.glob(os.path.join(TESTS_DIR, '*.cpp'), recursive=False) + for test_file in test_files: + tests += discover_tests_in_file(test_file) + return sorted(set(tests)) def main(): test_binary = os.path.join('.', common.get_test_binary_name()) + args = argparse.ArgumentParser(description='Run Botan tests') + args.add_argument('--list', action='store_true', default=False, help='List all available tests') + args.add_argument('test_src_file', nargs='?', help='Path to the test source file') + parsed_args = args.parse_args() + + discovered_tests = discover_tests(parsed_args) + + if parsed_args.list: + print("\n".join(discovered_tests)) + return + + if not parsed_args.test_src_file: + discovered_tests.clear() - if len(sys.argv) == 2: - test_src_file = sys.argv[1] - test_names = get_test_names_from(test_src_file) - common.run_cmd("%s %s" % (test_binary, ' '.join(test_names))) - else: - common.run_cmd(test_binary) + common.run_cmd(" ".join([test_binary, *discovered_tests])) if __name__ == '__main__': diff -Nru botan3-3.7.1+dfsg/src/editors/vscode/settings.json botan3-3.12.0+dfsg/src/editors/vscode/settings.json --- botan3-3.7.1+dfsg/src/editors/vscode/settings.json 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/editors/vscode/settings.json 2026-05-07 01:38:28.000000000 +0000 @@ -1,16 +1,11 @@ { "[cpp]": { "editor.formatOnSave": true, - "editor.defaultFormatter": "xaver.clang-format" + "editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd" }, - "C_Cpp.codeAnalysis.clangTidy.enabled": true, - "C_Cpp.default.cppStandard": "c++20", - "C_Cpp.default.cStandard": "c17", - "C_Cpp.formatting": "disabled", "clangd.arguments": [ "--header-insertion=never" ], "clang-format.executable": "clang-format-17", - "pylint.args": [ - "--rcfile=src/configs/pylint.rc"], + "pylint.args": ["--rcfile=src/configs/pylint.rc"] } diff -Nru botan3-3.7.1+dfsg/src/editors/vscode/tasks.json botan3-3.12.0+dfsg/src/editors/vscode/tasks.json --- botan3-3.7.1+dfsg/src/editors/vscode/tasks.json 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/editors/vscode/tasks.json 2026-05-07 01:38:28.000000000 +0000 @@ -2,27 +2,19 @@ "version": "2.0.0", "tasks": [ { - "label": "Configure (gcc)", - "detail": "Default ./configure.py invocation for gcc. Run your own if you want.", + "label": "Configure", + "detail": "Default ./configure.py invocation. Run your own if you want.", "group": "build", "type": "shell", - "command": "python3 ./configure.py --cc gcc --compiler-cache=ccache --build-tool=ninja --without-documentation --debug-mode --build-targets=\"static,tests,bogo_shim\"", - "presentation": { - "reveal": "always", - "panel": "shared", - "close": false - } - }, - { - "label": "Configure (msvc)", - "detail": "Default ./configure.py invocation for msvc. Run your own if you want.", - "group": "build", - "type": "shell", - "shell": { - "args": [ - ] + "linux": { + "command": "./configure.py --compiler-cache=ccache --build-tool=ninja --without-documentation --debug-mode --build-targets=\"static,tests,cli,bogo_shim\"" + }, + "osx": { + "command": "./configure.py --compiler-cache=ccache --build-tool=ninja --without-documentation --debug-mode --build-targets=\"static,tests,cli\"" + }, + "windows": { + "command": "./configure.py --compiler-cache=sccache.exe --build-tool=ninja --link-method=hardlink --without-documentation --debug-mode --build-targets=\"static,tests,cli\"", }, - "command": "python ./configure.py --cc msvc --compiler-cache=sccache.exe --build-tool=ninja --link-method=hardlink --without-documentation --debug-mode --build-targets=\"static,tests\"", "presentation": { "reveal": "always", "panel": "shared", @@ -31,14 +23,18 @@ }, { "label": "Build All", - "group": "build", + "group": { + "kind": "build", + "isDefault": true + }, "type": "shell", + "dependsOn": "Configure", "linux": { - "command": "make -j $(nproc)", + "command": "ninja", "problemMatcher": "$gcc" }, "osx": { - "command": "make -j $(sysctl -n hw.logicalcpu)", + "command": "ninja", "problemMatcher": "$gcc" }, "windows": { @@ -55,90 +51,53 @@ "label": "Build Unittests", "group": "build", "type": "shell", + "dependsOn": "Configure", "linux": { - "command": "make -j $(nproc) tests" + "command": "ninja tests", + "problemMatcher": "$gcc" }, "osx": { - "command": "make -j $(sysctl -n hw.logicalcpu) tests" + "command": "ninja tests", + "problemMatcher": "$gcc" + }, + "windows": { + "command": "ninja tests", + "problemMatcher": "$msCompile" }, "presentation": { "reveal": "always", "panel": "shared", "close": false }, - "problemMatcher": "$gcc" }, { "label": "Build BoGo Shim", "group": "build", "type": "shell", + "dependsOn": "Configure", "linux": { - "command": "make -j $(nproc) bogo_shim" + "command": "ninja bogo_shim", + "problemMatcher": "$gcc" }, "osx": { - "command": "make -j $(sysctl -n hw.logicalcpu) bogo_shim" + "command": "ninja bogo_shim", + "problemMatcher": "$gcc" }, "presentation": { "reveal": "always", "panel": "shared", "close": true }, - "problemMatcher": "$gcc" }, { "label": "Run Unittests", - "detail": "run all unittests", - "group": "test", - "type": "shell", - "command": "python3 ${workspaceFolder}/src/editors/vscode/scripts/test.py", - "dependsOn": "Build Unittests", - "presentation": { - "reveal": "always", - "panel": "shared", - "close": false + "detail": "opportunistically runs the currently open unit test file or all unit tests", + "group": { + "kind": "test", + "isDefault": true }, - "problemMatcher": [ - { - "owner": "cpp", - "pattern": { - "regexp": "^Failure \\d+: (.+Internal error: False assertion .*) @(.*):(.*)$", - "message": 1, - "file": 2, - "line": 3, - "column": 0, - "endColumn": 0 - } - }, - { - "owner": "cpp", - "pattern": { - "regexp": "Failure \\d+: (.*) \\(at ([^:]+):(\\d+)\\)", - "message": 1, - "file": 2, - "line": 3, - "column": 0, - "endColumn": 0 - } - }, - { - "owner": "cpp", - "pattern": { - "regexp": "Failure \\d+: (.*)", - "message": 1, - "file": 0, - "line": 0, - "column": 0, - "endColumn": 0 - } - } - ] - }, - { - "label": "Run Current Unittest File", - "detail": "run the unittest file that is currently in focus", - "group": "test", "type": "shell", - "command": "python3 ${workspaceFolder}/src/editors/vscode/scripts/test.py ${relativeFile}", + "command": "${workspaceFolder}/src/editors/vscode/scripts/test.py ${relativeFile}", "dependsOn": "Build Unittests", "presentation": { "reveal": "always", @@ -185,7 +144,7 @@ "label": "Run BoGo Tests", "group": "test", "type": "shell", - "command": "python3 ${workspaceFolder}/src/editors/vscode/scripts/bogo.py", + "command": "${workspaceFolder}/src/editors/vscode/scripts/bogo.py", "dependsOn": "Build BoGo Shim", "presentation": { "reveal": "always", @@ -206,6 +165,18 @@ } ] } - } + }, + { + "label": "List Unittests", + "detail": "Lists all available unit tests", + "group": "test", + "type": "shell", + "command": "${workspaceFolder}/src/editors/vscode/scripts/test.py --list ${relativeFile}", + "presentation": { + "reveal": "always", + "panel": "shared", + "close": false + } + }, ] } diff -Nru botan3-3.7.1+dfsg/src/examples/check_key.cpp botan3-3.12.0+dfsg/src/examples/check_key.cpp --- botan3-3.7.1+dfsg/src/examples/check_key.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/check_key.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -5,7 +5,7 @@ #include int main() { - Botan::X509_Certificate cert("cert.pem"); + const Botan::X509_Certificate cert("cert.pem"); Botan::AutoSeeded_RNG rng; auto key = cert.subject_public_key(); if(!key->check_key(rng, false)) { diff -Nru botan3-3.7.1+dfsg/src/examples/custom_system_rng.cpp botan3-3.12.0+dfsg/src/examples/custom_system_rng.cpp --- botan3-3.7.1+dfsg/src/examples/custom_system_rng.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/custom_system_rng.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -13,6 +13,7 @@ */ #include +#include class MySoC_RandomNumberGenerator final : public Botan::Hardware_RNG { public: @@ -80,12 +81,8 @@ */ }; -#include - int main() { MySoC_RandomNumberGenerator my_rng; - printf("%d\n", my_rng.next_byte()); - - return 0; + return my_rng.next_byte(); } diff -Nru botan3-3.7.1+dfsg/src/examples/ecc_raw_private_key.cpp botan3-3.12.0+dfsg/src/examples/ecc_raw_private_key.cpp --- botan3-3.7.1+dfsg/src/examples/ecc_raw_private_key.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/ecc_raw_private_key.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,4 +1,5 @@ #include +#include #include #include #include diff -Nru botan3-3.7.1+dfsg/src/examples/ecc_raw_public_key.cpp botan3-3.12.0+dfsg/src/examples/ecc_raw_public_key.cpp --- botan3-3.7.1+dfsg/src/examples/ecc_raw_public_key.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/ecc_raw_public_key.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,3 +1,4 @@ +#include #include #include #include diff -Nru botan3-3.7.1+dfsg/src/examples/ecdh.cpp botan3-3.12.0+dfsg/src/examples/ecdh.cpp --- botan3-3.7.1+dfsg/src/examples/ecdh.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/ecdh.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -14,18 +14,18 @@ const std::string kdf = "KDF2(SHA-256)"; // the two parties generate ECDH keys - Botan::ECDH_PrivateKey key_a(rng, domain); - Botan::ECDH_PrivateKey key_b(rng, domain); + const Botan::ECDH_PrivateKey key_a(rng, domain); + const Botan::ECDH_PrivateKey key_b(rng, domain); // now they exchange their public values const auto key_apub = key_a.public_value(); const auto key_bpub = key_b.public_value(); // Construct key agreements and agree on a shared secret - Botan::PK_Key_Agreement ka_a(key_a, rng, kdf); + const Botan::PK_Key_Agreement ka_a(key_a, rng, kdf); const auto sA = ka_a.derive_key(32, key_bpub).bits_of(); - Botan::PK_Key_Agreement ka_b(key_b, rng, kdf); + const Botan::PK_Key_Agreement ka_b(key_b, rng, kdf); const auto sB = ka_b.derive_key(32, key_apub).bits_of(); if(sA != sB) { diff -Nru botan3-3.7.1+dfsg/src/examples/ecdsa.cpp botan3-3.12.0+dfsg/src/examples/ecdsa.cpp --- botan3-3.7.1+dfsg/src/examples/ecdsa.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/ecdsa.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -10,7 +10,7 @@ Botan::AutoSeeded_RNG rng; // Generate ECDSA keypair const auto group = Botan::EC_Group::from_name("secp521r1"); - Botan::ECDSA_PrivateKey key(rng, group); + const Botan::ECDSA_PrivateKey key(rng, group); const std::string message("This is a tasty burger!"); diff -Nru botan3-3.7.1+dfsg/src/examples/entropy.cpp botan3-3.12.0+dfsg/src/examples/entropy.cpp --- botan3-3.7.1+dfsg/src/examples/entropy.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/entropy.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -14,8 +14,8 @@ // includes needed for example Entropy_Source implementations #include #include -#include #include +#include // includes needed for main #include @@ -103,7 +103,7 @@ */ for(size_t i = 0; i != poll_goal; ++i) { - uint64_t timer = high_resolution_timer(); + const uint64_t timer = high_resolution_timer(); // If the timer is fast, this loop will almost always exit immediately and // the counter will just be zero. diff -Nru botan3-3.7.1+dfsg/src/examples/ffi.c botan3-3.12.0+dfsg/src/examples/ffi.c --- botan3-3.7.1+dfsg/src/examples/ffi.c 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/ffi.c 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,53 @@ +/* The two headers we guarantee to be parseable as C are ffi.h and build.h */ +#include + +#if defined(BOTAN_HAS_FFI) + #include +#else + #error "The C89 interface is not available in this build" +#endif + +#include +#include + +#define CHECK_RC(rc) \ + do { \ + if(rc != BOTAN_FFI_SUCCESS) { \ + printf("Call failed rc=%d (%s)\n", rc, botan_error_description(rc)); \ + return 1; \ + } \ + } while(0) + +int main() { + uint8_t digest[32]; + char hex[64 + 1] = {0}; + const char* str_to_hash = "Hello world"; + int rc = 0; + + printf("This is %s\n", botan_version_string()); + +#if defined(BOTAN_HAS_SHA_256) + botan_hash_t hash; + rc = botan_hash_init(&hash, "SHA-256", 0); + CHECK_RC(rc); + + rc = botan_hash_update(hash, (const uint8_t*)str_to_hash, strlen(str_to_hash)); + CHECK_RC(rc); + + rc = botan_hash_final(hash, digest); + CHECK_RC(rc); + + rc = botan_hash_destroy(hash); + CHECK_RC(rc); + + rc = botan_hex_encode(digest, sizeof(digest), hex, sizeof(hex)); + CHECK_RC(rc); + + printf("SHA-256(\"%s\") = %s\n", str_to_hash, hex); + +#else + printf("SHA-256 not included in the build\n"); +#endif + + return 0; +} diff -Nru botan3-3.7.1+dfsg/src/examples/fpe_alnum.cpp botan3-3.12.0+dfsg/src/examples/fpe_alnum.cpp --- botan3-3.7.1+dfsg/src/examples/fpe_alnum.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/fpe_alnum.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,122 @@ +#include +#include +#include +#include +#include + +namespace { + +constexpr size_t power(size_t b, size_t e) { + size_t p = 1; + + for(size_t i = 0; i != e; ++i) { + p *= b; + } + + return p; +} + +/* +* This example FPE encrypts strings of length 10 which +* are in [A-Z0-9], ie radix 36. +*/ +constexpr size_t LEN = 10; +constexpr size_t RADIX = 26 + 10; +constexpr size_t POWER = power(RADIX, LEN); + +size_t to_radix(char c) { + if(c >= '0' && c <= '9') { + return c - '0'; + } else if(c >= 'A' && c <= 'Z') { + return c - 'A' + 10; + } else { + throw std::invalid_argument("String contains unexpected character"); + } +} + +// Map from the string to an integer in [0,RADIX**LEN) +Botan::BigInt rank(std::string_view s) { + if(s.size() != LEN) { + throw std::invalid_argument("Cannot FPE encrypt string of incorrect length"); + } + + Botan::BigInt z = 0; + + for(size_t i = 0; i != LEN; ++i) { + z = z * RADIX + to_radix(s[i]); + } + + return z; +} + +char from_radix(size_t c) { + if(c <= 9) { + return static_cast(c + '0'); + } else if(c <= 35) { + return static_cast(c + 'A' - 10); + } else { + throw std::invalid_argument("Output contains unexpected character"); + } +} + +// Map from an integer in [0,RADIX**LEN) to the string +std::string derank(Botan::BigInt z) { + std::string s; + + for(size_t i = 0; i != LEN; ++i) { + const auto zi = z % RADIX; + s.push_back(from_radix(zi)); + z /= RADIX; + } + + std::reverse(s.begin(), s.end()); + + return s; +} + +} // namespace + +int main(int argc, char* argv[]) { + if(argc <= 3) { + std::cerr << "Usage: " << argv[0] << " \n"; + return 1; + } + + try { + const bool encrypt = [=]() { + const std::string arg1(argv[1]); + if(arg1 == "encrypt") { + return true; + } else if(arg1 == "decrypt") { + return false; + } else { + throw std::invalid_argument("Expected 'encrypt' or 'decrypt' not " + arg1); + } + }(); + + const auto key = Botan::hex_decode(argv[2]); + + Botan::FPE_FE1 fpe(Botan::BigInt::from_u64(POWER)); + fpe.set_key(key); + + for(size_t i = 3; argv[i] != nullptr; ++i) { + /* + * The tweak ensures that even if the same input is encrypted more than + * once it produces a different output. The same tweak must be used for + * decryption. Commonly this is available, eg a database row id. If not + * available then the tweak can be set to a constant. + */ + const uint64_t tweak = static_cast(i - 3); + + auto z = rank(std::string(argv[i])); + auto enc_z = encrypt ? fpe.encrypt(z, tweak) : fpe.decrypt(z, tweak); + auto enc_word = derank(enc_z); + std::cout << enc_word << " "; + } + std::cout << "\n"; + return 0; + } catch(std::exception& e) { + std::cout << e.what() << "\n"; + return 2; + } +} diff -Nru botan3-3.7.1+dfsg/src/examples/fpe_dictionary.cpp botan3-3.12.0+dfsg/src/examples/fpe_dictionary.cpp --- botan3-3.7.1+dfsg/src/examples/fpe_dictionary.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/fpe_dictionary.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,84 @@ +#include +#include +#include +#include +#include + +class Dictionary { + public: + explicit Dictionary(const std::string& filename) { + std::ifstream in(filename); + + while(in.good()) { + std::string word; + std::getline(in, word); + m_dict.push_back(word); + } + + std::sort(m_dict.begin(), m_dict.end()); + } + + size_t rank(const std::string& word) const { + auto i = std::lower_bound(m_dict.begin(), m_dict.end(), word); + + const size_t r = i - m_dict.begin(); + + if(m_dict[r] != word) { + throw std::runtime_error("The word " + word + " does not appear in the dictionary"); + } + + return r; + } + + std::string derank(size_t rank) const { return m_dict.at(rank); } + + size_t size() const { return m_dict.size(); } + + private: + std::vector m_dict; +}; + +int main(int argc, char* argv[]) { + if(argc <= 4) { + std::cerr << "Usage: " << argv[0] << " words...\n"; + return 1; + } + + try { + const bool encrypt = [=]() { + const std::string arg1(argv[1]); + if(arg1 == "encrypt") { + return true; + } else if(arg1 == "decrypt") { + return false; + } else { + throw std::invalid_argument("Expected 'encrypt' or 'decrypt' not " + arg1); + } + }(); + const Dictionary dict(argv[2]); + const auto key = Botan::hex_decode(argv[3]); + + Botan::FPE_FE1 fpe(Botan::BigInt::from_u64(dict.size())); + fpe.set_key(key); + + for(size_t i = 4; argv[i] != nullptr; ++i) { + /* + * The tweak ensures that even if the same input is encrypted more than + * once it produces a different output. The same tweak must be used for + * decryption. Commonly this is available, eg a database row id. If not + * available then the tweak can be set to a constant. + */ + const uint64_t tweak = static_cast(i - 4); + + auto z = Botan::BigInt(dict.rank(std::string(argv[i]))); + auto enc_z = encrypt ? fpe.encrypt(z, tweak) : fpe.decrypt(z, tweak); + auto enc_word = dict.derank(enc_z.word_at(0)); + std::cout << enc_word << " "; + } + std::cout << "\n"; + return 0; + } catch(std::exception& e) { + std::cout << e.what() << "\n"; + return 2; + } +} diff -Nru botan3-3.7.1+dfsg/src/examples/hash.cpp botan3-3.12.0+dfsg/src/examples/hash.cpp --- botan3-3.7.1+dfsg/src/examples/hash.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/hash.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -11,8 +11,8 @@ while(std::cin.good()) { // read STDIN to buffer - std::cin.read(reinterpret_cast(buf.data()), buf.size()); - size_t readcount = std::cin.gcount(); + std::cin.read(reinterpret_cast(buf.data()), static_cast(buf.size())); + const auto readcount = static_cast(std::cin.gcount()); // update hash computations with read data hash1->update(std::span{buf}.first(readcount)); hash2->update(std::span{buf}.first(readcount)); diff -Nru botan3-3.7.1+dfsg/src/examples/hmac.cpp botan3-3.12.0+dfsg/src/examples/hmac.cpp --- botan3-3.7.1+dfsg/src/examples/hmac.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/hmac.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -23,12 +23,12 @@ const auto key = rng.random_vec(32); // 256 bit random key // "Message" != "Mussage" so tags will also not match - std::string tag1 = compute_mac("Message", key); - std::string tag2 = compute_mac("Mussage", key); + const std::string tag1 = compute_mac("Message", key); + const std::string tag2 = compute_mac("Mussage", key); assert(tag1 != tag2); // Recomputing with original input message results in identical tag - std::string tag3 = compute_mac("Message", key); + const std::string tag3 = compute_mac("Message", key); assert(tag1 == tag3); return 0; diff -Nru botan3-3.7.1+dfsg/src/examples/hybrid_encryption.cpp botan3-3.12.0+dfsg/src/examples/hybrid_encryption.cpp --- botan3-3.7.1+dfsg/src/examples/hybrid_encryption.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/hybrid_encryption.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -36,7 +36,7 @@ sym_cipher->finish(d.ciphertext); // encrypt the symmetric key using RSA with a secure padding scheme - Botan::PK_Encryptor_EME asym_cipher(*pubkey, rng, "EME-OAEP(SHA-256,MGF1)"); + const Botan::PK_Encryptor_EME asym_cipher(*pubkey, rng, "EME-OAEP(SHA-256,MGF1)"); d.encryptedKey = asym_cipher.encrypt(key, rng); return d; @@ -48,7 +48,7 @@ Botan::secure_vector plaintext = encdata.ciphertext; // decrypt the symmetric key - Botan::PK_Decryptor_EME asym_cipher(privkey, rng, "EME-OAEP(SHA-256,MGF1)"); + const Botan::PK_Decryptor_EME asym_cipher(privkey, rng, "EME-OAEP(SHA-256,MGF1)"); const auto key = asym_cipher.decrypt(encdata.encryptedKey); // decrypt the data symmetrically diff -Nru botan3-3.7.1+dfsg/src/examples/hybrid_key_encapsulation.cpp botan3-3.12.0+dfsg/src/examples/hybrid_key_encapsulation.cpp --- botan3-3.7.1+dfsg/src/examples/hybrid_key_encapsulation.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/hybrid_key_encapsulation.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -27,10 +27,16 @@ public: explicit Hybrid_PublicKey(std::unique_ptr kex, std::unique_ptr kem) : m_kex_pk(std::move(kex)), m_kem_pk(std::move(kem)) { - BOTAN_ASSERT_NONNULL(m_kex_pk); - BOTAN_ASSERT_NONNULL(m_kem_pk); - BOTAN_ASSERT_NOMSG(m_kex_pk->supports_operation(Botan::PublicKeyOperation::KeyAgreement)); - BOTAN_ASSERT_NOMSG(m_kem_pk->supports_operation(Botan::PublicKeyOperation::KeyEncapsulation)); + if(m_kem_pk == nullptr || m_kex_pk == nullptr) { + throw std::runtime_error("Null arguments not allowed"); + } + + if(m_kex_pk->supports_operation(Botan::PublicKeyOperation::KeyAgreement)) { + throw std::runtime_error("The kex key must support key agreement"); + } + if(m_kex_pk->supports_operation(Botan::PublicKeyOperation::KeyEncapsulation)) { + throw std::runtime_error("The kem key must support key encapsulation"); + } } std::string algo_name() const override { @@ -165,9 +171,7 @@ Hybrid_Encryption_Operation(const Hybrid_PublicKey& hybrid_pk, std::string_view kdf) : m_hybrid_pk(hybrid_pk), m_kem_encryptor(hybrid_pk.kem_public_key(), "Raw"), - m_kdf(Botan::KDF::create_or_throw(kdf)) { - BOTAN_ASSERT_NONNULL(m_kdf); - } + m_kdf(Botan::KDF::create_or_throw(kdf)) {} /** * This returns the length of the encapsulated key in bytes. For such a @@ -214,7 +218,7 @@ // // TODO: fix this upstream by harmonizing the constructors of the // PK_Key_Agreement and PK_KEM_Encryptor classes. - Botan::PK_Key_Agreement kex(*ephemeral_keypair, rng, "Raw"); + const Botan::PK_Key_Agreement kex(*ephemeral_keypair, rng, "Raw"); // 2. KEX: Agree on a shared secret using the public key of the other // party and our ephemeral private key. The ephemeral public @@ -236,7 +240,11 @@ // 4. Hybrid: Concatenate the ephemeral public key and the KEM's // encapsulation to form a combined "hybrid encapsulation". - BOTAN_ASSERT_NOMSG(out_encapsed_key.size() == kex_encapsed_key.size() + kem_encapsed_key.size()); + + if(out_encapsed_key.size() != kex_encapsed_key.size() + kem_encapsed_key.size()) { + throw std::runtime_error("The output span is not the expected size"); + } + std::copy(kex_encapsed_key.begin(), kex_encapsed_key.end(), out_encapsed_key.begin()); std::copy( kem_encapsed_key.begin(), kem_encapsed_key.end(), out_encapsed_key.begin() + kex_encapsed_key.size()); @@ -252,7 +260,10 @@ concat_shared_key.insert(concat_shared_key.end(), kem_encapsed_key.begin(), kem_encapsed_key.end()); concat_shared_key.insert(concat_shared_key.end(), kem_shared_key.begin(), kem_shared_key.end()); - BOTAN_ASSERT_NOMSG(out_shared_key.size() >= desired_shared_key_length); + if(out_shared_key.size() < desired_shared_key_length) { + throw std::runtime_error("The output span is smaller than the requested length"); + } + m_kdf->derive_key(out_shared_key.first(desired_shared_key_length), concat_shared_key, salt, {}); } @@ -276,9 +287,7 @@ m_hybrid_sk(hybrid_sk), m_key_agreement(hybrid_sk.kex_private_key(), rng, "Raw"), m_kem_decryptor(hybrid_sk.kem_private_key(), rng, "Raw"), - m_kdf(Botan::KDF::create_or_throw(kdf)) { - BOTAN_ASSERT_NONNULL(m_kdf); - } + m_kdf(Botan::KDF::create_or_throw(kdf)) {} /** * This returns the length of the encapsulated key in bytes. For such a @@ -303,7 +312,9 @@ std::span encapsulated_key, size_t desired_shared_key_length, std::span salt) override { - BOTAN_ASSERT_NOMSG(encapsulated_key.size() == encapsulated_key_length()); + if(encapsulated_key.size() != encapsulated_key_length()) { + throw std::runtime_error("The provided encapsulated key is not of the expected length"); + } // The basic idea of the hybrid operation: // 1. Extract the ephemeral public key and the KEM's encapsulation @@ -340,7 +351,9 @@ concat_shared_key.insert(concat_shared_key.end(), kem_encapsed_key.begin(), kem_encapsed_key.end()); concat_shared_key.insert(concat_shared_key.end(), kem_shared_key.begin(), kem_shared_key.end()); - BOTAN_ASSERT_NOMSG(out_shared_key.size() >= desired_shared_key_length); + if(out_shared_key.size() < desired_shared_key_length) { + throw std::runtime_error("The output buffer is smaller than the requested key length"); + } m_kdf->derive_key(out_shared_key.first(desired_shared_key_length), concat_shared_key, salt, {}); } @@ -353,13 +366,13 @@ } // namespace -std::unique_ptr Hybrid_PublicKey::create_kem_encryption_op(std::string_view params, - std::string_view) const { +std::unique_ptr Hybrid_PublicKey::create_kem_encryption_op( + std::string_view params, std::string_view /*provider*/) const { return std::make_unique(*this, params); } std::unique_ptr Hybrid_PrivateKey::create_kem_decryption_op( - Botan::RandomNumberGenerator& rng, std::string_view params, std::string_view) const { + Botan::RandomNumberGenerator& rng, std::string_view params, std::string_view /*provider*/) const { return std::make_unique(*this, rng, params); } diff -Nru botan3-3.7.1+dfsg/src/examples/ml_kem.cpp botan3-3.12.0+dfsg/src/examples/ml_kem.cpp --- botan3-3.7.1+dfsg/src/examples/ml_kem.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/ml_kem.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -12,7 +12,7 @@ const auto salt = rng.random_array<16>(); - Botan::ML_KEM_PrivateKey priv_key(rng, Botan::ML_KEM_Mode::ML_KEM_768); + const Botan::ML_KEM_PrivateKey priv_key(rng, Botan::ML_KEM_Mode::ML_KEM_768); auto pub_key = priv_key.public_key(); Botan::PK_KEM_Encryptor enc(*pub_key, kdf); diff -Nru botan3-3.7.1+dfsg/src/examples/password_encryption.cpp botan3-3.12.0+dfsg/src/examples/password_encryption.cpp --- botan3-3.7.1+dfsg/src/examples/password_encryption.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/password_encryption.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ #include -#include #include +#include #include #include @@ -27,7 +27,7 @@ std::span salt, size_t output_length) { // Here, we use statically defined password hash parameters. Alternatively - // you could use Botan::PasswordHashFamily::tune() to automatically select + // you could use Botan::PasswordHashFamily::tune_params() to automatically select // parameters based on your desired runtime and memory usage. // // Defining those parameters highly depends on your use case and the @@ -38,7 +38,7 @@ constexpr size_t p = 2; // parallelism auto pbkdf = Botan::PasswordHashFamily::create_or_throw(pbkdf_algo)->from_params(M, t, p); - BOTAN_ASSERT_NONNULL(pbkdf); + // create_or_throw always either throws or returns a non-null pointer Botan::secure_vector key(output_length); pbkdf->hash(key, password, salt); @@ -57,7 +57,12 @@ // Stretch the password into enough cryptographically strong key material // to initialize the AEAD with a key and nonce (aka. initialization vector). const auto keydata = derive_key_material(password, salt, key_length + nonce_length); - BOTAN_ASSERT_NOMSG(keydata.size() == key_length + nonce_length); + + // The function always returns the requested length but lets check to make sure + if(keydata.size() != key_length + nonce_length) { + throw std::runtime_error("Unexpected output from derive_key_material"); + } + const auto key = std::span{keydata}.first(key_length); const auto nonce = std::span{keydata}.last(nonce_length); @@ -122,17 +127,14 @@ // Note: For simplicity we omit the authentication of any associated data. // If your use case would benefit from it, you should add it. Perhaps // to both the password hashing and the AEAD. - std::string_view password = "geheimnis"; - std::string_view message = "Attack at dawn!"; + const std::string_view password = "geheimnis"; + const std::string_view message = "Attack at dawn!"; try { const auto ciphertext = encrypt_by_password(password, rng, as>(message)); std::cout << "Ciphertext: " << Botan::hex_encode(ciphertext) << "\n"; const auto decrypted_message = decrypt_by_password(password, ciphertext); - BOTAN_ASSERT_NOMSG(message.size() == decrypted_message.size() && - std::equal(message.begin(), message.end(), decrypted_message.begin())); - std::cout << "Decrypted message: " << as(decrypted_message) << "\n"; } catch(const std::exception& ex) { std::cerr << "Something went wrong: " << ex.what() << "\n"; diff -Nru botan3-3.7.1+dfsg/src/examples/pkcs10_csr_on_tpm2.cpp botan3-3.12.0+dfsg/src/examples/pkcs10_csr_on_tpm2.cpp --- botan3-3.7.1+dfsg/src/examples/pkcs10_csr_on_tpm2.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/pkcs10_csr_on_tpm2.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -17,10 +17,14 @@ #include #include +namespace { + std::span as_byteview(std::string_view str) { return {reinterpret_cast(str.data()), str.size()}; } +} // namespace + int main() { // This TCTI configuration is just an example, adjust as needed! constexpr auto tcti_nameconf = "tabrmd:bus_name=net.randombit.botan.tabrmd,bus_type=session"; diff -Nru botan3-3.7.1+dfsg/src/examples/pkcs11_ecdh.cpp botan3-3.12.0+dfsg/src/examples/pkcs11_ecdh.cpp --- botan3-3.7.1+dfsg/src/examples/pkcs11_ecdh.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/pkcs11_ecdh.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -20,7 +20,7 @@ Botan::PKCS11::Slot slot(module, slots.at(0)); Botan::PKCS11::Session session(slot, false); - Botan::PKCS11::secure_string pin = {'1', '2', '3', '4', '5', '6'}; + const Botan::PKCS11::secure_string pin = {'1', '2', '3', '4', '5', '6'}; session.login(Botan::PKCS11::UserType::User, pin); /************ import ECDH private key *************/ @@ -28,7 +28,7 @@ Botan::AutoSeeded_RNG rng; // create private key in software - Botan::ECDH_PrivateKey priv_key_sw(rng, Botan::EC_Group::from_name("secp256r1")); + const Botan::ECDH_PrivateKey priv_key_sw(rng, Botan::EC_Group::from_name("secp256r1")); // set import properties Botan::PKCS11::EC_PrivateKeyImportProperties priv_import_props(priv_key_sw.DER_domain(), @@ -44,10 +44,10 @@ priv_import_props.set_label(label); // import to card - Botan::PKCS11::PKCS11_ECDH_PrivateKey priv_key(session, priv_import_props); + const Botan::PKCS11::PKCS11_ECDH_PrivateKey priv_key(session, priv_import_props); /************ export ECDH private key *************/ - Botan::ECDH_PrivateKey exported = priv_key.export_key(); + const Botan::ECDH_PrivateKey exported = priv_key.export_key(); /************ import ECDH public key *************/ @@ -65,10 +65,10 @@ pub_import_props.set_label(label); // import - Botan::PKCS11::PKCS11_ECDH_PublicKey pub_key(session, pub_import_props); + const Botan::PKCS11::PKCS11_ECDH_PublicKey pub_key(session, pub_import_props); /************ export ECDH private key *************/ - Botan::ECDH_PublicKey exported_pub = pub_key.export_key(); + const Botan::ECDH_PublicKey exported_pub = pub_key.export_key(); /************ generate ECDH private key *************/ @@ -77,7 +77,7 @@ priv_generate_props.set_private(true); priv_generate_props.set_derive(true); - Botan::PKCS11::PKCS11_ECDH_PrivateKey priv_key2( + const Botan::PKCS11::PKCS11_ECDH_PrivateKey priv_key2( session, Botan::EC_Group::from_name("secp256r1").DER_encode(), priv_generate_props); /************ generate ECDH key pair *************/ @@ -91,22 +91,22 @@ pub_generate_props.set_private(false); pub_generate_props.set_modifiable(true); - Botan::PKCS11::PKCS11_ECDH_KeyPair key_pair = + const Botan::PKCS11::PKCS11_ECDH_KeyPair key_pair = Botan::PKCS11::generate_ecdh_keypair(session, pub_generate_props, priv_generate_props); /************ ECDH derive *************/ - Botan::PKCS11::PKCS11_ECDH_KeyPair key_pair_other = + const Botan::PKCS11::PKCS11_ECDH_KeyPair key_pair_other = Botan::PKCS11::generate_ecdh_keypair(session, pub_generate_props, priv_generate_props); - Botan::PK_Key_Agreement ka(key_pair.second, rng, "Raw", "pkcs11"); - Botan::PK_Key_Agreement kb(key_pair_other.second, rng, "Raw", "pkcs11"); + const Botan::PK_Key_Agreement ka(key_pair.second, rng, "Raw", "pkcs11"); + const Botan::PK_Key_Agreement kb(key_pair_other.second, rng, "Raw", "pkcs11"); - Botan::SymmetricKey alice_key = ka.derive_key(32, key_pair_other.first.raw_public_key_bits()); + const Botan::SymmetricKey alice_key = ka.derive_key(32, key_pair_other.first.raw_public_key_bits()); - Botan::SymmetricKey bob_key = kb.derive_key(32, key_pair.first.raw_public_key_bits()); + const Botan::SymmetricKey bob_key = kb.derive_key(32, key_pair.first.raw_public_key_bits()); - bool eq = alice_key == bob_key; + const bool eq = alice_key == bob_key; return eq ? 0 : 1; } diff -Nru botan3-3.7.1+dfsg/src/examples/pkcs11_ecdsa.cpp botan3-3.12.0+dfsg/src/examples/pkcs11_ecdsa.cpp --- botan3-3.7.1+dfsg/src/examples/pkcs11_ecdsa.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/pkcs11_ecdsa.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -19,7 +19,7 @@ Botan::PKCS11::Slot slot(module, slots.at(0)); Botan::PKCS11::Session session(slot, false); - Botan::PKCS11::secure_string pin = {'1', '2', '3', '4', '5', '6'}; + const Botan::PKCS11::secure_string pin = {'1', '2', '3', '4', '5', '6'}; session.login(Botan::PKCS11::UserType::User, pin); /************ import ECDSA private key *************/ @@ -27,7 +27,7 @@ // create private key in software Botan::AutoSeeded_RNG rng; - Botan::ECDSA_PrivateKey priv_key_sw(rng, Botan::EC_Group::from_name("secp256r1")); + const Botan::ECDSA_PrivateKey priv_key_sw(rng, Botan::EC_Group::from_name("secp256r1")); // set the private key import properties Botan::PKCS11::EC_PrivateKeyImportProperties priv_import_props(priv_key_sw.DER_domain(), @@ -43,10 +43,10 @@ priv_import_props.set_label(label); // import to card - Botan::PKCS11::PKCS11_ECDSA_PrivateKey priv_key(session, priv_import_props); + const Botan::PKCS11::PKCS11_ECDSA_PrivateKey priv_key(session, priv_import_props); /************ export PKCS#11 ECDSA private key *************/ - Botan::ECDSA_PrivateKey priv_exported = priv_key.export_key(); + const Botan::ECDSA_PrivateKey priv_exported = priv_key.export_key(); /************ import ECDSA public key *************/ @@ -63,10 +63,10 @@ label = "test ECDSA pub key"; pub_import_props.set_label(label); - Botan::PKCS11::PKCS11_ECDSA_PublicKey public_key(session, pub_import_props); + const Botan::PKCS11::PKCS11_ECDSA_PublicKey public_key(session, pub_import_props); /************ export PKCS#11 ECDSA public key *************/ - Botan::ECDSA_PublicKey pub_exported = public_key.export_key(); + const Botan::ECDSA_PublicKey pub_exported = public_key.export_key(); /************ generate PKCS#11 ECDSA private key *************/ Botan::PKCS11::EC_PrivateKeyGenerationProperties priv_generate_props; @@ -74,7 +74,7 @@ priv_generate_props.set_private(true); priv_generate_props.set_sign(true); - Botan::PKCS11::PKCS11_ECDSA_PrivateKey pk( + const Botan::PKCS11::PKCS11_ECDSA_PrivateKey pk( session, Botan::EC_Group::from_name("secp256r1").DER_encode(), priv_generate_props); /************ generate PKCS#11 ECDSA key pair *************/ @@ -88,7 +88,7 @@ pub_generate_props.set_private(false); pub_generate_props.set_modifiable(true); - Botan::PKCS11::PKCS11_ECDSA_KeyPair key_pair = + const Botan::PKCS11::PKCS11_ECDSA_KeyPair key_pair = Botan::PKCS11::generate_ecdsa_keypair(session, pub_generate_props, priv_generate_props); /************ PKCS#11 ECDSA sign and verify *************/ @@ -99,7 +99,7 @@ auto signature = signer.sign_message(plaintext, rng); Botan::PK_Verifier token_verifier(key_pair.first, "Raw", Botan::Signature_Format::Standard, "pkcs11"); - bool ecdsa_ok = token_verifier.verify_message(plaintext, signature); + const bool ecdsa_ok = token_verifier.verify_message(plaintext, signature); return ecdsa_ok ? 0 : 1; } diff -Nru botan3-3.7.1+dfsg/src/examples/pkcs11_low_level.cpp botan3-3.12.0+dfsg/src/examples/pkcs11_low_level.cpp --- botan3-3.7.1+dfsg/src/examples/pkcs11_low_level.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/pkcs11_low_level.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -4,14 +4,14 @@ #include int main() { - Botan::PKCS11::Module module("C:\\pkcs11-middleware\\library.dll"); + const Botan::PKCS11::Module module("C:\\pkcs11-middleware\\library.dll"); // C_Initialize is automatically called by the constructor of the Module // work with the token std::vector slot_ids; - [[maybe_unused]] bool success = module->C_GetSlotList(true, slot_ids); + [[maybe_unused]] const bool success = module->C_GetSlotList(true, slot_ids); // C_Finalize is automatically called by the destructor of the Module diff -Nru botan3-3.7.1+dfsg/src/examples/pkcs11_module.cpp botan3-3.12.0+dfsg/src/examples/pkcs11_module.cpp --- botan3-3.7.1+dfsg/src/examples/pkcs11_module.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/pkcs11_module.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -10,7 +10,7 @@ // Sometimes useful if a newly connected token is not detected by the PKCS#11 module module.reload(); - Botan::PKCS11::Info info = module.get_info(); + const Botan::PKCS11::Info info = module.get_info(); // print library version std::cout << std::to_string(info.libraryVersion.major) << "." << std::to_string(info.libraryVersion.minor) << '\n'; diff -Nru botan3-3.7.1+dfsg/src/examples/pkcs11_objects.cpp botan3-3.12.0+dfsg/src/examples/pkcs11_objects.cpp --- botan3-3.7.1+dfsg/src/examples/pkcs11_objects.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/pkcs11_objects.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -16,9 +16,9 @@ Botan::PKCS11::Session session(slot, false); // create an simple data object - Botan::secure_vector value = {0x00, 0x01, 0x02, 0x03}; - std::size_t id = 1337; - std::string label = "test data object"; + const Botan::secure_vector value = {0x00, 0x01, 0x02, 0x03}; + const std::size_t id = 1337; + const std::string label = "test data object"; // set properties of the new object Botan::PKCS11::DataObjectProperties data_obj_props; @@ -31,19 +31,20 @@ data_obj_props.set_object_id(encoded_id); // create the object - Botan::PKCS11::Object data_obj(session, data_obj_props); + const Botan::PKCS11::Object data_obj(session, data_obj_props); // get label of this object - Botan::PKCS11::secure_string retrieved_label = data_obj.get_attribute_value(Botan::PKCS11::AttributeType::Label); + const Botan::PKCS11::secure_string retrieved_label = + data_obj.get_attribute_value(Botan::PKCS11::AttributeType::Label); // set a new label - Botan::PKCS11::secure_string new_label = {'B', 'o', 't', 'a', 'n'}; + const Botan::PKCS11::secure_string new_label = {'B', 'o', 't', 'a', 'n'}; data_obj.set_attribute_value(Botan::PKCS11::AttributeType::Label, new_label); // copy the object Botan::PKCS11::AttributeContainer copy_attributes; copy_attributes.add_string(Botan::PKCS11::AttributeType::Label, "copied object"); - [[maybe_unused]] Botan::PKCS11::ObjectHandle copied_obj_handle = data_obj.copy(copy_attributes); + [[maybe_unused]] const Botan::PKCS11::ObjectHandle copied_obj_handle = data_obj.copy(copy_attributes); // search for an object Botan::PKCS11::AttributeContainer search_template; diff -Nru botan3-3.7.1+dfsg/src/examples/pkcs11_rsa.cpp botan3-3.12.0+dfsg/src/examples/pkcs11_rsa.cpp --- botan3-3.7.1+dfsg/src/examples/pkcs11_rsa.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/pkcs11_rsa.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -14,14 +14,14 @@ Botan::PKCS11::Slot slot(module, slots.at(0)); Botan::PKCS11::Session session(slot, false); - Botan::PKCS11::secure_string pin = {'1', '2', '3', '4', '5', '6'}; + const Botan::PKCS11::secure_string pin = {'1', '2', '3', '4', '5', '6'}; session.login(Botan::PKCS11::UserType::User, pin); /************ import RSA private key *************/ // create private key in software Botan::AutoSeeded_RNG rng; - Botan::RSA_PrivateKey priv_key_sw(rng, 2048); + const Botan::RSA_PrivateKey priv_key_sw(rng, 2048); // set the private key import properties Botan::PKCS11::RSA_PrivateKeyImportProperties priv_import_props(priv_key_sw.get_n(), priv_key_sw.get_d()); @@ -39,10 +39,10 @@ priv_import_props.set_sign(true); // import - Botan::PKCS11::PKCS11_RSA_PrivateKey priv_key(session, priv_import_props); + const Botan::PKCS11::PKCS11_RSA_PrivateKey priv_key(session, priv_import_props); /************ export PKCS#11 RSA private key *************/ - Botan::RSA_PrivateKey exported = priv_key.export_key(); + const Botan::RSA_PrivateKey exported = priv_key.export_key(); /************ import RSA public key *************/ @@ -53,7 +53,7 @@ pub_import_props.set_private(false); // import - Botan::PKCS11::PKCS11_RSA_PublicKey public_key(session, pub_import_props); + const Botan::PKCS11::PKCS11_RSA_PublicKey public_key(session, pub_import_props); /************ generate RSA private key *************/ @@ -64,7 +64,7 @@ priv_generate_props.set_decrypt(true); priv_generate_props.set_label("BOTAN_TEST_RSA_PRIV_KEY"); - Botan::PKCS11::PKCS11_RSA_PrivateKey private_key2(session, 2048, priv_generate_props); + const Botan::PKCS11::PKCS11_RSA_PrivateKey private_key2(session, 2048, priv_generate_props); /************ generate RSA key pair *************/ @@ -76,28 +76,28 @@ pub_generate_props.set_verify(true); pub_generate_props.set_private(false); - Botan::PKCS11::PKCS11_RSA_KeyPair rsa_keypair = + const Botan::PKCS11::PKCS11_RSA_KeyPair rsa_keypair = Botan::PKCS11::generate_rsa_keypair(session, pub_generate_props, priv_generate_props); /************ RSA encrypt *************/ Botan::secure_vector plaintext = {0x00, 0x01, 0x02, 0x03}; - Botan::PK_Encryptor_EME encryptor(rsa_keypair.first, rng, "Raw"); + const Botan::PK_Encryptor_EME encryptor(rsa_keypair.first, rng, "Raw"); auto ciphertext = encryptor.encrypt(plaintext, rng); /************ RSA decrypt *************/ - Botan::PK_Decryptor_EME decryptor(rsa_keypair.second, rng, "Raw"); + const Botan::PK_Decryptor_EME decryptor(rsa_keypair.second, rng, "Raw"); plaintext = decryptor.decrypt(ciphertext); /************ RSA sign *************/ - Botan::PK_Signer signer(rsa_keypair.second, rng, "EMSA4(SHA-256)", Botan::Signature_Format::Standard); + Botan::PK_Signer signer(rsa_keypair.second, rng, "PSS(SHA-256)", Botan::Signature_Format::Standard); auto signature = signer.sign_message(plaintext, rng); /************ RSA verify *************/ - Botan::PK_Verifier verifier(rsa_keypair.first, "EMSA4(SHA-256)", Botan::Signature_Format::Standard); + Botan::PK_Verifier verifier(rsa_keypair.first, "PSS(SHA-256)", Botan::Signature_Format::Standard); auto ok = verifier.verify_message(plaintext, signature); return ok ? 0 : 1; diff -Nru botan3-3.7.1+dfsg/src/examples/pkcs11_session.cpp botan3-3.12.0+dfsg/src/examples/pkcs11_session.cpp --- botan3-3.7.1+dfsg/src/examples/pkcs11_session.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/pkcs11_session.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -11,46 +11,46 @@ Botan::PKCS11::Slot slot(module, slots.at(0)); // open read only session - { Botan::PKCS11::Session read_only_session(slot, true); } + { const Botan::PKCS11::Session read_only_session(slot, true); } // open read write session - { Botan::PKCS11::Session read_write_session(slot, false); } + { const Botan::PKCS11::Session read_write_session(slot, false); } // open read write session by passing flags { - Botan::PKCS11::Flags flags = + const Botan::PKCS11::Flags flags = Botan::PKCS11::flags(Botan::PKCS11::Flag::SerialSession | Botan::PKCS11::Flag::RwSession); - Botan::PKCS11::Session read_write_session(slot, flags, nullptr, nullptr); + const Botan::PKCS11::Session read_write_session(slot, flags, nullptr, nullptr); } // move ownership of a session { Botan::PKCS11::Session session(slot, false); - Botan::PKCS11::SessionHandle handle = session.release(); + const Botan::PKCS11::SessionHandle handle = session.release(); - Botan::PKCS11::Session session2(slot, handle); + const Botan::PKCS11::Session session2(slot, handle); } Botan::PKCS11::Session session(slot, false); // get session info - Botan::PKCS11::SessionInfo info = session.get_info(); + const Botan::PKCS11::SessionInfo info = session.get_info(); std::cout << info.slotID << '\n'; // login - Botan::PKCS11::secure_string pin = {'1', '2', '3', '4', '5', '6'}; + const Botan::PKCS11::secure_string pin = {'1', '2', '3', '4', '5', '6'}; session.login(Botan::PKCS11::UserType::User, pin); // set pin - Botan::PKCS11::secure_string new_pin = {'6', '5', '4', '3', '2', '1'}; + const Botan::PKCS11::secure_string new_pin = {'6', '5', '4', '3', '2', '1'}; session.set_pin(pin, new_pin); // logoff session.logoff(); // log in as security officer - Botan::PKCS11::secure_string so_pin = {'0', '0', '0', '0', '0', '0', '0', '0'}; + const Botan::PKCS11::secure_string so_pin = {'0', '0', '0', '0', '0', '0', '0', '0'}; session.login(Botan::PKCS11::UserType::SO, so_pin); // change pin to old pin diff -Nru botan3-3.7.1+dfsg/src/examples/pkcs11_slot.cpp botan3-3.12.0+dfsg/src/examples/pkcs11_slot.cpp --- botan3-3.7.1+dfsg/src/examples/pkcs11_slot.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/pkcs11_slot.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,32 +9,32 @@ Botan::PKCS11::Module module("C:\\pkcs11-middleware\\library.dll"); // only slots with connected token - std::vector slots = Botan::PKCS11::Slot::get_available_slots(module, true); + const std::vector slots = Botan::PKCS11::Slot::get_available_slots(module, true); // use first slot - Botan::PKCS11::Slot slot(module, slots.at(0)); + const Botan::PKCS11::Slot slot(module, slots.at(0)); // print firmware version of the slot - Botan::PKCS11::SlotInfo slot_info = slot.get_slot_info(); + const Botan::PKCS11::SlotInfo slot_info = slot.get_slot_info(); std::cout << std::to_string(slot_info.firmwareVersion.major) << "." << std::to_string(slot_info.firmwareVersion.minor) << '\n'; // print firmware version of the token - Botan::PKCS11::TokenInfo token_info = slot.get_token_info(); + const Botan::PKCS11::TokenInfo token_info = slot.get_token_info(); std::cout << std::to_string(token_info.firmwareVersion.major) << "." << std::to_string(token_info.firmwareVersion.minor) << '\n'; // retrieve all mechanisms supported by the token - std::vector mechanisms = slot.get_mechanism_list(); + const std::vector mechanisms = slot.get_mechanism_list(); // retrieve information about a particular mechanism - Botan::PKCS11::MechanismInfo mech_info = slot.get_mechanism_info(Botan::PKCS11::MechanismType::RsaPkcsOaep); + const Botan::PKCS11::MechanismInfo mech_info = slot.get_mechanism_info(Botan::PKCS11::MechanismType::RsaPkcsOaep); // maximum RSA key length supported: std::cout << mech_info.ulMaxKeySize << '\n'; // initialize the token - Botan::PKCS11::secure_string so_pin(8, '0'); + const Botan::PKCS11::secure_string so_pin(8, '0'); slot.initialize("Botan PKCS11 documentation test label", so_pin); return 0; diff -Nru botan3-3.7.1+dfsg/src/examples/pkcs11_token_management.cpp botan3-3.12.0+dfsg/src/examples/pkcs11_token_management.cpp --- botan3-3.7.1+dfsg/src/examples/pkcs11_token_management.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/pkcs11_token_management.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -14,9 +14,9 @@ // use first slot Botan::PKCS11::Slot slot(module, slots.at(0)); - Botan::PKCS11::secure_string so_pin = {'1', '2', '3', '4', '5', '6', '7', '8'}; - Botan::PKCS11::secure_string pin = {'1', '2', '3', '4', '5', '6'}; - Botan::PKCS11::secure_string test_pin = {'6', '5', '4', '3', '2', '1'}; + const Botan::PKCS11::secure_string so_pin = {'1', '2', '3', '4', '5', '6', '7', '8'}; + const Botan::PKCS11::secure_string pin = {'1', '2', '3', '4', '5', '6'}; + const Botan::PKCS11::secure_string test_pin = {'6', '5', '4', '3', '2', '1'}; // set pin Botan::PKCS11::set_pin(slot, so_pin, test_pin); diff -Nru botan3-3.7.1+dfsg/src/examples/pkcs11_x509.cpp botan3-3.12.0+dfsg/src/examples/pkcs11_x509.cpp --- botan3-3.7.1+dfsg/src/examples/pkcs11_x509.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/pkcs11_x509.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -14,7 +14,7 @@ Botan::PKCS11::Session session(slot, false); // load existing certificate - Botan::X509_Certificate root("test.crt"); + const Botan::X509_Certificate root("test.crt"); // set props Botan::PKCS11::X509_CertificateProperties props(root.subject_dn().DER_encode(), root.BER_encode()); @@ -24,10 +24,10 @@ props.set_token(true); // import - Botan::PKCS11::PKCS11_X509_Certificate pkcs11_cert(session, props); + const Botan::PKCS11::PKCS11_X509_Certificate pkcs11_cert(session, props); // load by handle - Botan::PKCS11::PKCS11_X509_Certificate pkcs11_cert2(session, pkcs11_cert.handle()); + const Botan::PKCS11::PKCS11_X509_Certificate pkcs11_cert2(session, pkcs11_cert.handle()); return 0; } diff -Nru botan3-3.7.1+dfsg/src/examples/pwdhash.cpp botan3-3.12.0+dfsg/src/examples/pwdhash.cpp --- botan3-3.7.1+dfsg/src/examples/pwdhash.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/pwdhash.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,23 +6,23 @@ int main() { // You can change this to "PBKDF2(SHA-512)" or "Scrypt" or "Argon2id" or ... - std::string_view pbkdf_algo = "Argon2i"; - auto pbkdf_runtime = std::chrono::milliseconds(300); + const std::string_view pbkdf_algo = "Argon2i"; + constexpr uint64_t pbkdf_runtime = 300; // milliseconds constexpr size_t output_hash = 32; constexpr size_t salt_len = 32; constexpr size_t max_pbkdf_mb = 128; auto pwd_fam = Botan::PasswordHashFamily::create_or_throw(pbkdf_algo); - auto pwdhash = pwd_fam->tune(output_hash, pbkdf_runtime, max_pbkdf_mb); + auto pwdhash = pwd_fam->tune_params(output_hash, pbkdf_runtime, max_pbkdf_mb); std::cout << "Using params " << pwdhash->to_string() << '\n'; const auto salt = Botan::system_rng().random_array(); - std::string_view password = "tell no one"; + const std::string_view password = "tell no one"; - std::array key; + std::array key{}; pwdhash->hash(key, password, salt); std::cout << Botan::hex_encode(key) << '\n'; diff -Nru botan3-3.7.1+dfsg/src/examples/rsa_encrypt.cpp botan3-3.12.0+dfsg/src/examples/rsa_encrypt.cpp --- botan3-3.7.1+dfsg/src/examples/rsa_encrypt.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/rsa_encrypt.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -11,7 +11,7 @@ if(argc != 2) { return 1; } - std::string_view plaintext( + const std::string_view plaintext( "Your great-grandfather gave this watch to your granddad for good luck. " "Unfortunately, Dane's luck wasn't as good as his old man's."); const Botan::secure_vector pt(plaintext.data(), plaintext.data() + plaintext.length()); @@ -22,11 +22,11 @@ auto kp = Botan::PKCS8::load_key(in); // encrypt with pk - Botan::PK_Encryptor_EME enc(*kp, rng, "OAEP(SHA-256)"); + const Botan::PK_Encryptor_EME enc(*kp, rng, "OAEP(SHA-256)"); const auto ct = enc.encrypt(pt, rng); // decrypt with sk - Botan::PK_Decryptor_EME dec(*kp, rng, "OAEP(SHA-256)"); + const Botan::PK_Decryptor_EME dec(*kp, rng, "OAEP(SHA-256)"); const auto pt2 = dec.decrypt(ct); std::cout << "\nenc: " << Botan::hex_encode(ct) << "\ndec: " << Botan::hex_encode(pt2); diff -Nru botan3-3.7.1+dfsg/src/examples/tls_13_hybrid_key_exchange_client.cpp botan3-3.12.0+dfsg/src/examples/tls_13_hybrid_key_exchange_client.cpp --- botan3-3.7.1+dfsg/src/examples/tls_13_hybrid_key_exchange_client.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/tls_13_hybrid_key_exchange_client.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -11,19 +11,17 @@ */ class Callbacks : public Botan::TLS::Callbacks { public: - void tls_emit_data(std::span data) override { - BOTAN_UNUSED(data); + void tls_emit_data([[maybe_unused]] std::span data) override { // send data to tls server, e.g., using BSD sockets or boost asio } - void tls_record_received(uint64_t seq_no, std::span data) override { - BOTAN_UNUSED(seq_no, data); + void tls_record_received([[maybe_unused]] uint64_t seq_no, + [[maybe_unused]] std::span data) override { // process full TLS record received by tls server, e.g., // by passing it to the application } - void tls_alert(Botan::TLS::Alert alert) override { - BOTAN_UNUSED(alert); + void tls_alert([[maybe_unused]] Botan::TLS::Alert alert) override { // handle a tls alert received from the tls server } }; @@ -36,9 +34,8 @@ */ class Client_Credentials : public Botan::Credentials_Manager { public: - std::vector trusted_certificate_authorities(const std::string& type, - const std::string& context) override { - BOTAN_UNUSED(type, context); + std::vector trusted_certificate_authorities( + [[maybe_unused]] const std::string& type, [[maybe_unused]] const std::string& context) override { // return a list of certificates of CAs we trust for tls server certificates, // e.g., all the certificates in the local directory "cas" return {&m_cert_store}; @@ -56,6 +53,7 @@ auto groups = Botan::TLS::Default_Policy::key_exchange_groups(); groups.push_back(Botan::TLS::Group_Params::HYBRID_X25519_ML_KEM_768); groups.push_back(Botan::TLS::Group_Params::HYBRID_SECP256R1_ML_KEM_768); + groups.push_back(Botan::TLS::Group_Params::HYBRID_SECP384R1_ML_KEM_1024); return groups; } @@ -75,13 +73,13 @@ auto policy = std::make_shared(); // open the tls connection - Botan::TLS::Client client(callbacks, - session_mgr, - creds, - policy, - rng, - Botan::TLS::Server_Information("botan.randombit.net", 443), - Botan::TLS::Protocol_Version::TLS_V12); + const Botan::TLS::Client client(callbacks, + session_mgr, + creds, + policy, + rng, + Botan::TLS::Server_Information("botan.randombit.net", 443), + Botan::TLS::Protocol_Version::TLS_V12); while(!client.is_closed()) { // read data received from the tls server, e.g., using BSD sockets or boost asio diff -Nru botan3-3.7.1+dfsg/src/examples/tls_client.cpp botan3-3.12.0+dfsg/src/examples/tls_client.cpp --- botan3-3.7.1+dfsg/src/examples/tls_client.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/tls_client.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -12,20 +12,18 @@ */ class Callbacks : public Botan::TLS::Callbacks { public: - void tls_emit_data(std::span data) override { + void tls_emit_data([[maybe_unused]] std::span data) override { // send data to tls server, e.g., using BSD sockets or boost asio - BOTAN_UNUSED(data); } - void tls_record_received(uint64_t seq_no, std::span data) override { + void tls_record_received([[maybe_unused]] uint64_t seq_no, + [[maybe_unused]] std::span data) override { // process full TLS record received by tls server, e.g., // by passing it to the application - BOTAN_UNUSED(seq_no, data); } - void tls_alert(Botan::TLS::Alert alert) override { + void tls_alert([[maybe_unused]] Botan::TLS::Alert alert) override { // handle a tls alert received from the tls server - BOTAN_UNUSED(alert); } }; @@ -38,31 +36,27 @@ */ class Client_Credentials : public Botan::Credentials_Manager { public: - std::vector trusted_certificate_authorities(const std::string& type, - const std::string& context) override { - BOTAN_UNUSED(type, context); + std::vector trusted_certificate_authorities( + [[maybe_unused]] const std::string& type, [[maybe_unused]] const std::string& context) override { // return a list of certificates of CAs we trust for tls server certificates // ownership of the pointers remains with Credentials_Manager return {&m_cert_store}; } std::vector cert_chain( - const std::vector& cert_key_types, - const std::vector& cert_signature_schemes, - const std::string& type, - const std::string& context) override { - BOTAN_UNUSED(cert_key_types, cert_signature_schemes, type, context); - + [[maybe_unused]] const std::vector& cert_key_types, + [[maybe_unused]] const std::vector& cert_signature_schemes, + [[maybe_unused]] const std::string& type, + [[maybe_unused]] const std::string& context) override { // when using tls client authentication (optional), return // a certificate chain being sent to the tls server, // else an empty list return {}; } - std::shared_ptr private_key_for(const Botan::X509_Certificate& cert, - const std::string& type, - const std::string& context) override { - BOTAN_UNUSED(cert, type, context); + std::shared_ptr private_key_for([[maybe_unused]] const Botan::X509_Certificate& cert, + [[maybe_unused]] const std::string& type, + [[maybe_unused]] const std::string& context) override { // when returning a chain in cert_chain(), return the private key // associated with the leaf certificate here return nullptr; @@ -81,13 +75,13 @@ auto policy = std::make_shared(); // open the tls connection - Botan::TLS::Client client(callbacks, - session_mgr, - creds, - policy, - rng, - Botan::TLS::Server_Information("botan.randombit.net", 443), - Botan::TLS::Protocol_Version::TLS_V12); + const Botan::TLS::Client client(callbacks, + session_mgr, + creds, + policy, + rng, + Botan::TLS::Server_Information("botan.randombit.net", 443), + Botan::TLS::Protocol_Version::TLS_V12); while(!client.is_closed()) { // read data received from the tls server, e.g., using BSD sockets or boost asio diff -Nru botan3-3.7.1+dfsg/src/examples/tls_custom_curves_client.cpp botan3-3.12.0+dfsg/src/examples/tls_custom_curves_client.cpp --- botan3-3.7.1+dfsg/src/examples/tls_custom_curves_client.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/tls_custom_curves_client.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,5 +1,7 @@ #include #include +#include +#include #include #include @@ -12,19 +14,17 @@ */ class Callbacks : public Botan::TLS::Callbacks { public: - void tls_emit_data(std::span data) override { - BOTAN_UNUSED(data); + void tls_emit_data([[maybe_unused]] std::span data) override { // send data to tls server, e.g., using BSD sockets or boost asio } - void tls_record_received(uint64_t seq_no, std::span data) override { - BOTAN_UNUSED(seq_no, data); + void tls_record_received([[maybe_unused]] uint64_t seq_no, + [[maybe_unused]] std::span data) override { // process full TLS record received by tls server, e.g., // by passing it to the application } - void tls_alert(Botan::TLS::Alert alert) override { - BOTAN_UNUSED(alert); + void tls_alert([[maybe_unused]] Botan::TLS::Alert alert) override { // handle a tls alert received from the tls server } @@ -65,9 +65,8 @@ */ class Client_Credentials : public Botan::Credentials_Manager { public: - std::vector trusted_certificate_authorities(const std::string& type, - const std::string& context) override { - BOTAN_UNUSED(type, context); + std::vector trusted_certificate_authorities( + [[maybe_unused]] const std::string& type, [[maybe_unused]] const std::string& context) override { // return a list of certificates of CAs we trust for tls server certificates, // e.g., all the certificates in the local directory "cas" return {&m_cert_store}; @@ -111,7 +110,7 @@ const Botan::OID oid("1.3.6.1.4.1.25258.4.1"); // create EC_Group object to register the curve - Botan::EC_Group numsp256d1(oid, p, a, b, g_x, g_y, n); + const Botan::EC_Group numsp256d1(oid, p, a, b, g_x, g_y, n); if(!numsp256d1.verify_group(*rng)) { return 1; @@ -128,13 +127,13 @@ auto policy = std::make_shared(); // open the tls connection - Botan::TLS::Client client(callbacks, - session_mgr, - creds, - policy, - rng, - Botan::TLS::Server_Information("botan.randombit.net", 443), - Botan::TLS::Protocol_Version::TLS_V12); + const Botan::TLS::Client client(callbacks, + session_mgr, + creds, + policy, + rng, + Botan::TLS::Server_Information("botan.randombit.net", 443), + Botan::TLS::Protocol_Version::TLS_V12); while(!client.is_closed()) { // read data received from the tls server, e.g., using BSD sockets or boost asio diff -Nru botan3-3.7.1+dfsg/src/examples/tls_proxy.cpp botan3-3.12.0+dfsg/src/examples/tls_proxy.cpp --- botan3-3.7.1+dfsg/src/examples/tls_proxy.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/tls_proxy.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -15,20 +15,18 @@ */ class Callbacks : public Botan::TLS::Callbacks { public: - void tls_emit_data(std::span data) override { + void tls_emit_data([[maybe_unused]] std::span data) override { // send data to tls client, e.g., using BSD sockets or boost asio - BOTAN_UNUSED(data); } - void tls_record_received(uint64_t seq_no, std::span data) override { + void tls_record_received([[maybe_unused]] uint64_t seq_no, + [[maybe_unused]] std::span data) override { // process full TLS record received by tls client, e.g., // by passing it to the application - BOTAN_UNUSED(seq_no, data); } - void tls_alert(Botan::TLS::Alert alert) override { + void tls_alert([[maybe_unused]] Botan::TLS::Alert alert) override { // handle a tls alert received from the tls server - BOTAN_UNUSED(alert); } }; @@ -47,9 +45,8 @@ m_key.reset(Botan::PKCS8::load_key(in).release()); } - std::vector trusted_certificate_authorities(const std::string& type, - const std::string& context) override { - BOTAN_UNUSED(type, context); + std::vector trusted_certificate_authorities( + [[maybe_unused]] const std::string& type, [[maybe_unused]] const std::string& context) override { // if client authentication is required, this function // shall return a list of certificates of CAs we trust // for tls client certificates, otherwise return an empty list @@ -57,21 +54,18 @@ } std::vector cert_chain( - const std::vector& cert_key_types, - const std::vector& cert_signature_schemes, - const std::string& type, - const std::string& context) override { - BOTAN_UNUSED(cert_key_types, cert_signature_schemes, type, context); - + [[maybe_unused]] const std::vector& cert_key_types, + [[maybe_unused]] const std::vector& cert_signature_schemes, + [[maybe_unused]] const std::string& type, + [[maybe_unused]] const std::string& context) override { // return the certificate chain being sent to the tls client // e.g., the certificate file "botan.randombit.net.crt" return {Botan::X509_Certificate("botan.randombit.net.crt")}; } - std::shared_ptr private_key_for(const Botan::X509_Certificate& cert, - const std::string& type, - const std::string& context) override { - BOTAN_UNUSED(cert, type, context); + std::shared_ptr private_key_for([[maybe_unused]] const Botan::X509_Certificate& cert, + [[maybe_unused]] const std::string& type, + [[maybe_unused]] const std::string& context) override { // return the private key associated with the leaf certificate, // in this case the one associated with "botan.randombit.net.crt" return m_key; @@ -90,7 +84,7 @@ auto policy = std::make_shared(); // accept tls connection from client - Botan::TLS::Server server(callbacks, session_mgr, creds, policy, rng); + const Botan::TLS::Server server(callbacks, session_mgr, creds, policy, rng); // read data received from the tls client, e.g., using BSD sockets or boost asio // and pass it to server.received_data(). diff -Nru botan3-3.7.1+dfsg/src/examples/tls_ssl_key_log_file.cpp botan3-3.12.0+dfsg/src/examples/tls_ssl_key_log_file.cpp --- botan3-3.7.1+dfsg/src/examples/tls_ssl_key_log_file.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/tls_ssl_key_log_file.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -17,14 +17,14 @@ #include #include -#if defined(BOTAN_TARGET_OS_HAS_SOCKETS) +#if __has_include() #include #include #include #include -#endif -#if defined(BOTAN_TARGET_OS_HAS_POSIX1) #include + + #define HAS_BSD_SOCKETS #endif namespace { @@ -36,8 +36,8 @@ public: Client_Credential() = default; - std::vector trusted_certificate_authorities(const std::string&, - const std::string&) override { + std::vector trusted_certificate_authorities(const std::string& /*type*/, + const std::string& /*context*/) override { return {&m_cert_store}; } @@ -64,24 +64,22 @@ } } - std::vector trusted_certificate_authorities(const std::string&, - const std::string&) override { + std::vector trusted_certificate_authorities(const std::string& /*type*/, + const std::string& /*context*/) override { return {&m_cert_store}; } std::vector cert_chain( - const std::vector& cert_key_types, - const std::vector& cert_signature_schemes, - const std::string& type, - const std::string& context) override { - BOTAN_UNUSED(cert_signature_schemes, type, context); - + [[maybe_unused]] const std::vector& cert_key_types, + [[maybe_unused]] const std::vector& cert_signature_schemes, + [[maybe_unused]] const std::string& type, + [[maybe_unused]] const std::string& context) override { // return the certificate chain being sent to the tls client // e.g., the certificate file "botan.randombit.net.crt" std::vector certs; for(auto& cert : certificates) { - std::string algorithm = cert.subject_public_key()->algo_name(); - for(auto& key : cert_key_types) { + const std::string algorithm = cert.subject_public_key()->algo_name(); + for(const auto& key : cert_key_types) { if(algorithm == key) { certs.push_back(cert); } @@ -90,10 +88,9 @@ return certs; } - std::shared_ptr private_key_for(const Botan::X509_Certificate& cert, - const std::string& type, - const std::string& context) override { - BOTAN_UNUSED(cert, type, context); + std::shared_ptr private_key_for([[maybe_unused]] const Botan::X509_Certificate& cert, + [[maybe_unused]] const std::string& type, + [[maybe_unused]] const std::string& context) override { // return the private key associated with the leaf certificate, // in this case the one associated with "botan.randombit.net.crt" return m_key; @@ -114,13 +111,14 @@ Botan::TLS::Callbacks& parent; public: - BotanTLSCallbacksProxy(Botan::TLS::Callbacks& callbacks) : parent(callbacks) {} + explicit BotanTLSCallbacksProxy(Botan::TLS::Callbacks& callbacks) : parent(callbacks) {} void tls_emit_data(std::span data) override { parent.tls_emit_data(data); } - void tls_record_received(uint64_t seq_no, std::span data) override { BOTAN_UNUSED(seq_no, data); } + void tls_record_received([[maybe_unused]] uint64_t seq_no, + [[maybe_unused]] std::span data) override {} - void tls_alert(Botan::TLS::Alert alert) override { BOTAN_UNUSED(alert); } + void tls_alert([[maybe_unused]] Botan::TLS::Alert alert) override {} void tls_ssl_key_log_data(std::string_view label, std::span client_random, @@ -133,20 +131,22 @@ class DtlsConnection : public Botan::TLS::Callbacks { int fd; -#if defined(BOTAN_TARGET_OS_HAS_SOCKETS) - sockaddr_in remote_addr; +#if defined(HAS_BSD_SOCKETS) + sockaddr_in remote_addr{}; #endif std::unique_ptr dtls_channel; std::function activated_callback; public: - DtlsConnection(const std::string& r_addr, int r_port, int socket, bool is_server) : fd(socket) { -#if defined(BOTAN_TARGET_OS_HAS_SOCKETS) + DtlsConnection([[maybe_unused]] const std::string& r_addr, + [[maybe_unused]] int r_port, + int socket, + bool is_server) : + fd(socket) { +#if defined(HAS_BSD_SOCKETS) remote_addr.sin_family = AF_INET; inet_aton(r_addr.c_str(), &remote_addr.sin_addr); remote_addr.sin_port = htons(r_port); -#else - BOTAN_UNUSED(r_addr, r_port); #endif auto tls_callbacks_proxy = std::make_shared(*this); auto rng = std::make_shared(); @@ -170,19 +170,18 @@ } } - void tls_emit_data(std::span data) override { -#if defined(BOTAN_TARGET_OS_HAS_SOCKETS) - sendto(fd, data.data(), data.size(), 0, reinterpret_cast(&remote_addr), sizeof(sockaddr_in)); -#else - BOTAN_UNUSED(data); + void tls_emit_data([[maybe_unused]] std::span data) override { +#if defined(HAS_BSD_SOCKETS) // send data to the other side // ... + sendto(fd, data.data(), data.size(), 0, reinterpret_cast(&remote_addr), sizeof(sockaddr_in)); #endif } - void tls_record_received(uint64_t seq_no, std::span data) override { BOTAN_UNUSED(seq_no, data); } + void tls_record_received([[maybe_unused]] uint64_t seq_no, + [[maybe_unused]] std::span data) override {} - void tls_alert(Botan::TLS::Alert alert) override { BOTAN_UNUSED(alert); } + void tls_alert([[maybe_unused]] Botan::TLS::Alert alert) override {} void tls_session_activated() override { std::cout << "************ on_dtls_connect() ***********" << std::endl; @@ -204,12 +203,10 @@ void set_activated_callback(std::function callback) { activated_callback = std::move(callback); } void close() const { - if(fd) { -#if defined(BOTAN_TARGET_OS_HAS_SOCKETS) + if(fd >= 0) { +#if defined(HAS_BSD_SOCKETS) shutdown(fd, SHUT_RDWR); - #if defined(BOTAN_TARGET_OS_HAS_POSIX1) ::close(fd); - #endif #endif } } @@ -219,7 +216,7 @@ std::cout << "Start Server" << std::endl; int fd = 0; -#if defined(BOTAN_TARGET_OS_HAS_SOCKETS) +#if defined(HAS_BSD_SOCKETS) fd = socket(AF_INET, SOCK_DGRAM, 0); if(fd == -1) { return; @@ -228,14 +225,14 @@ if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, static_cast(&true_opt), sizeof(true_opt)) == -1) { return; } - sockaddr_in addr; + sockaddr_in addr{}; addr.sin_family = AF_INET; addr.sin_port = htons(SERVER_PORT); inet_aton("127.0.0.1", &addr.sin_addr); if(bind(fd, reinterpret_cast(&addr), sizeof(sockaddr_in)) == -1) { return; } - sockaddr_in fromaddr; + sockaddr_in fromaddr{}; fromaddr.sin_family = AF_INET; socklen_t len = sizeof(sockaddr_in); #else @@ -246,7 +243,7 @@ auto connection = std::make_shared("127.0.0.1", CLIENT_PORT, fd, true); conn_callback(connection); -#if defined(BOTAN_TARGET_OS_HAS_SOCKETS) +#if defined(HAS_BSD_SOCKETS) static uint8_t data[8192]; ssize_t recvlen = 0; while((recvlen = recvfrom(fd, data, sizeof(data), 0, reinterpret_cast(&fromaddr), &len)) > 0) { @@ -265,7 +262,7 @@ std::cout << "Start Client" << std::endl; int fd = 0; -#if defined(BOTAN_TARGET_OS_HAS_SOCKETS) +#if defined(HAS_BSD_SOCKETS) fd = socket(AF_INET, SOCK_DGRAM, 0); if(fd == -1) { return; @@ -274,14 +271,14 @@ if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, static_cast(&true_opt), sizeof(true_opt)) == -1) { return; } - sockaddr_in addr; + sockaddr_in addr{}; addr.sin_family = AF_INET; addr.sin_port = htons(CLIENT_PORT); inet_aton("127.0.0.1", &addr.sin_addr); if(bind(fd, reinterpret_cast(&addr), sizeof(sockaddr_in)) == -1) { return; } - sockaddr_in fromaddr; + sockaddr_in fromaddr{}; fromaddr.sin_family = AF_INET; socklen_t len = sizeof(sockaddr_in); #else @@ -291,7 +288,7 @@ auto connection = std::make_shared("127.0.0.1", SERVER_PORT, fd, false); conn_callback(connection); -#if defined(BOTAN_TARGET_OS_HAS_SOCKETS) +#if defined(HAS_BSD_SOCKETS) static uint8_t data[8192]; ssize_t recvlen = 0; while((recvlen = recvfrom(fd, data, sizeof(data), 0, reinterpret_cast(&fromaddr), &len)) > 0) { @@ -313,14 +310,14 @@ std::condition_variable conn_cond; std::vector> connections; std::thread server(server_proc, [&](std::shared_ptr conn) { - std::lock_guard lk(m); + const std::scoped_lock lk(m); connections.push_back(std::move(conn)); if(connections.size() == 2) { conn_cond.notify_one(); } }); std::thread client(client_proc, [&](std::shared_ptr conn) { - std::lock_guard lk(m); + const std::scoped_lock lk(m); connections.push_back(std::move(conn)); if(connections.size() == 2) { conn_cond.notify_one(); diff -Nru botan3-3.7.1+dfsg/src/examples/tls_stream_client.cpp botan3-3.12.0+dfsg/src/examples/tls_stream_client.cpp --- botan3-3.7.1+dfsg/src/examples/tls_stream_client.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/tls_stream_client.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -11,8 +11,7 @@ #include #include - #include - #include + #include namespace http = boost::beast::http; namespace ap = boost::asio::placeholders; @@ -22,8 +21,8 @@ public: Credentials_Manager() = default; - std::vector trusted_certificate_authorities(const std::string&, - const std::string&) override { + std::vector trusted_certificate_authorities(const std::string& /*type*/, + const std::string& /*context*/) override { return {&m_cert_store}; } @@ -31,6 +30,16 @@ Botan::System_Certificate_Store m_cert_store; }; +// Custom TLS policy that relaxes the certificate revocation info requirement. +// Often this setting causes frustration for new users. However, applications +// should carefully consider whether or not to enable revocation checks. +class Example_Policy : public Botan::TLS::Policy { + public: + bool require_cert_revocation_info() const override { return false; } +}; + +// NOLINTBEGIN(*-avoid-bind) + // a simple https client based on TLS::Stream class client { public: @@ -42,8 +51,8 @@ m_ctx(std::make_shared(std::make_shared(), std::make_shared(), std::make_shared(), - std::make_shared(), - host)), + std::make_shared(), + Botan::TLS::Server_Information(host))), m_stream(io_context, m_ctx) { boost::asio::async_connect(m_stream.lowest_layer(), endpoints.begin(), @@ -69,7 +78,7 @@ m_stream, m_request, boost::bind(&client::handle_write, this, ap::error, ap::bytes_transferred)); } - void handle_write(const boost::system::error_code& error, size_t) { + void handle_write(const boost::system::error_code& error, size_t /*unused*/) { if(error) { std::cout << "Write failed: " << error.message() << '\n'; return; @@ -78,7 +87,7 @@ m_stream, m_reply, m_response, boost::bind(&client::handle_read, this, ap::error, ap::bytes_transferred)); } - void handle_read(const boost::system::error_code& error, size_t) { + void handle_read(const boost::system::error_code& error, size_t /*unused*/) { if(!error) { std::cout << "Reply: "; std::cout << m_response.body() << '\n'; @@ -96,6 +105,8 @@ Botan::TLS::Stream m_stream; }; +// NOLINTEND(*-avoid-bind) + int main(int argc, char* argv[]) { if(argc != 4) { std::cerr << "Usage: tls_stream_client \n" @@ -104,15 +115,15 @@ return 1; } - const auto host = argv[1]; - const auto port = argv[2]; - const auto target = argv[3]; + auto* const host = argv[1]; + auto* const port = argv[2]; + auto* const target = argv[3]; try { boost::asio::io_context io_context; boost::asio::ip::tcp::resolver resolver(io_context); - boost::asio::ip::tcp::resolver::results_type endpoints = resolver.resolve(host, port); + const boost::asio::ip::tcp::resolver::results_type endpoints = resolver.resolve(host, port); http::request req; req.version(11); @@ -121,7 +132,7 @@ req.set(http::field::host, host); req.set(http::field::user_agent, Botan::version_string()); - client c(io_context, endpoints, host, req); + const client c(io_context, endpoints, host, req); io_context.run(); } catch(std::exception& e) { diff -Nru botan3-3.7.1+dfsg/src/examples/tls_stream_coroutine_client.cpp botan3-3.12.0+dfsg/src/examples/tls_stream_coroutine_client.cpp --- botan3-3.7.1+dfsg/src/examples/tls_stream_coroutine_client.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/tls_stream_coroutine_client.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -10,7 +10,7 @@ #define BOOST_VERSION_IS_COMPATIBLE #endif -#if defined(BOOST_VERSION_IS_COMPATIBLE) && defined(BOTAN_HAS_HAS_DEFAULT_TLS_CONTEXT) +#if defined(BOOST_VERSION_IS_COMPATIBLE) && defined(BOTAN_HAS_DEFAULT_TLS_CONTEXT) #include #include @@ -114,7 +114,7 @@ std::cout << "Your boost version is too old, sorry.\n" << "Or did you compile Botan without --with-boost?\n"; #endif - #if !defined(BOTAN_HAS_HAS_DEFAULT_TLS_CONTEXT) + #if !defined(BOTAN_HAS_DEFAULT_TLS_CONTEXT) std::cout << "Your system needs an auto seeded RNG and a certificate store.\n"; #endif return 1; diff -Nru botan3-3.7.1+dfsg/src/examples/x509_path.cpp botan3-3.12.0+dfsg/src/examples/x509_path.cpp --- botan3-3.7.1+dfsg/src/examples/x509_path.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/x509_path.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,7 +9,7 @@ // Additionally trust all system-specific CA certificates Botan::System_Certificate_Store systemStore; - std::vector trusted_roots{&customStore, &systemStore}; + const std::vector trusted_roots{&customStore, &systemStore}; // Load the end entity certificate and two untrusted intermediate CAs from file std::vector end_certs; @@ -18,15 +18,15 @@ end_certs.emplace_back(Botan::X509_Certificate("int1.crt")); // intermediate 1 // Optional: Set up restrictions, e.g. min. key strength, maximum age of OCSP responses - Botan::Path_Validation_Restrictions restrictions; + const Botan::Path_Validation_Restrictions restrictions; // Optional: Specify usage type, compared against the key usage in end_certs[0] - Botan::Usage_Type usage = Botan::Usage_Type::UNSPECIFIED; + const Botan::Usage_Type usage = Botan::Usage_Type::UNSPECIFIED; // Optional: Specify hostname, if not empty, compared against the DNS name in end_certs[0] - std::string hostname; + const std::string hostname; - Botan::Path_Validation_Result validationResult = + const Botan::Path_Validation_Result validationResult = Botan::x509_path_validate(end_certs, restrictions, trusted_roots, hostname, usage); if(!validationResult.successful_validation()) { diff -Nru botan3-3.7.1+dfsg/src/examples/xmss.cpp botan3-3.12.0+dfsg/src/examples/xmss.cpp --- botan3-3.7.1+dfsg/src/examples/xmss.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/examples/xmss.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -12,7 +12,7 @@ // create a new public/private key pair using SHA2 256 as hash // function and a tree height of 10. - Botan::XMSS_PrivateKey private_key(Botan::XMSS_Parameters::xmss_algorithm_t::XMSS_SHA2_10_256, rng); + const Botan::XMSS_PrivateKey private_key(Botan::XMSS_Parameters::xmss_algorithm_t::XMSS_SHA2_10_256, rng); const Botan::XMSS_PublicKey& public_key(private_key); // create Public Key Signer using the private key. diff -Nru botan3-3.7.1+dfsg/src/fuzzer/asn1.cpp botan3-3.12.0+dfsg/src/fuzzer/asn1.cpp --- botan3-3.7.1+dfsg/src/fuzzer/asn1.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/asn1.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,6 +7,7 @@ #include "fuzzers.h" #include +#include #include class ASN1_Parser final : public Botan::ASN1_Formatter { @@ -14,15 +15,26 @@ ASN1_Parser() : Botan::ASN1_Formatter(true, 64) {} protected: - std::string format(Botan::ASN1_Type, Botan::ASN1_Class, size_t, size_t, std::string_view) const override { + std::string format(Botan::ASN1_Type type, + Botan::ASN1_Class klass, + size_t level, + size_t length, + std::string_view value) const override { + BOTAN_UNUSED(type, klass, level, length, value); return ""; } - std::string format_bin(Botan::ASN1_Type, Botan::ASN1_Class, const std::vector&) const override { + std::string format_bin(Botan::ASN1_Type type, + Botan::ASN1_Class klass, + const std::vector& value) const override { + BOTAN_UNUSED(type, klass, value); return ""; } - std::string format_bn(const Botan::BigInt&) const override { return ""; } + std::string format_bn(const Botan::BigInt& bn) const override { + BOTAN_UNUSED(bn); + return ""; + } }; void fuzz(std::span in) { @@ -32,7 +44,7 @@ * on actual output formatting, no memory is allocated, etc. */ std::ofstream out; - ASN1_Parser printer; + const ASN1_Parser printer; printer.print_to_stream(out, in.data(), in.size()); - } catch(Botan::Exception& e) {} + } catch(const Botan::Exception& e) {} } diff -Nru botan3-3.7.1+dfsg/src/fuzzer/barrett.cpp botan3-3.12.0+dfsg/src/fuzzer/barrett.cpp --- botan3-3.7.1+dfsg/src/fuzzer/barrett.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/barrett.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,7 +7,7 @@ #include "fuzzers.h" #include -#include +#include #include void fuzz(std::span in) { @@ -21,32 +21,28 @@ return; } - const size_t x_len = 2 * ((in.size() + 2) / 3); + const size_t x_len = 2 * in.size() / 3; - Botan::BigInt x = Botan::BigInt::from_bytes(in.subspan(0, x_len)); + const Botan::BigInt x = Botan::BigInt::from_bytes(in.subspan(0, x_len)); const Botan::BigInt p = Botan::BigInt::from_bytes(in.subspan(x_len, in.size() - x_len)); if(p.is_zero()) { return; } - const size_t x_bits = x.bits(); - if(x_bits % 8 == 0 && x_bits / 8 == x_len) { - x.flip_sign(); - } - - const Botan::BigInt ref = x % p; - - const Botan::Modular_Reducer mod_p(p); - const Botan::BigInt z = mod_p.reduce(x); - - const Botan::BigInt ct = ct_modulo(x, p); - - if(ref != z || ref != ct) { - FUZZER_WRITE_AND_CRASH("X = " << x.to_hex_string() << "\n" - << "P = " << p.to_hex_string() << "\n" - << "Barrett = " << z.to_hex_string() << "\n" - << "Ct = " << ct.to_hex_string() << "\n" - << "Ref = " << ref.to_hex_string() << "\n"); - } + try { + const auto mod_p = Botan::Barrett_Reduction::for_public_modulus(p); + const Botan::BigInt z = mod_p.reduce(x); + + const Botan::BigInt ref = x % p; + const Botan::BigInt ct = ct_modulo(x, p); + + if(ref != z || ref != ct) { + FUZZER_WRITE_AND_CRASH("X = " << x.to_hex_string() << "\n" + << "P = " << p.to_hex_string() << "\n" + << "Barrett = " << z.to_hex_string() << "\n" + << "Ct = " << ct.to_hex_string() << "\n" + << "Ref = " << ref.to_hex_string() << "\n"); + } + } catch(const Botan::Invalid_Argument&) {} } diff -Nru botan3-3.7.1+dfsg/src/fuzzer/bn_cmp.cpp botan3-3.12.0+dfsg/src/fuzzer/bn_cmp.cpp --- botan3-3.7.1+dfsg/src/fuzzer/bn_cmp.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/bn_cmp.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -21,10 +21,10 @@ Botan::BigInt x = Botan::BigInt::from_bytes(in.subspan(1, x_len)); Botan::BigInt y = Botan::BigInt::from_bytes(in.subspan(1 + x_len, in.size() - x_len - 1)); - if(signs & 1) { + if((signs & 1) != 0) { x.flip_sign(); } - if(signs & 2) { + if((signs & 2) != 0) { y.flip_sign(); } @@ -54,17 +54,17 @@ if(is_lt) { FUZZER_ASSERT_TRUE(!is_gt); - FUZZER_ASSERT_TRUE(d1.is_nonzero()); - FUZZER_ASSERT_TRUE(d2.is_nonzero()); - FUZZER_ASSERT_TRUE(d1.is_negative()); - FUZZER_ASSERT_TRUE(d2.is_positive()); + FUZZER_ASSERT_TRUE(d1.signum() != 0); + FUZZER_ASSERT_TRUE(d2.signum() != 0); + FUZZER_ASSERT_TRUE(d1.signum() < 0); + FUZZER_ASSERT_TRUE(d2.signum() > 0); } if(is_gt) { FUZZER_ASSERT_TRUE(!is_lt); - FUZZER_ASSERT_TRUE(d1.is_nonzero()); - FUZZER_ASSERT_TRUE(d2.is_nonzero()); - FUZZER_ASSERT_TRUE(d1.is_positive()); - FUZZER_ASSERT_TRUE(d2.is_negative()); + FUZZER_ASSERT_TRUE(d1.signum() != 0); + FUZZER_ASSERT_TRUE(d2.signum() != 0); + FUZZER_ASSERT_TRUE(d1.signum() > 0); + FUZZER_ASSERT_TRUE(d2.signum() < 0); } } diff -Nru botan3-3.7.1+dfsg/src/fuzzer/bn_sqr.cpp botan3-3.12.0+dfsg/src/fuzzer/bn_sqr.cpp --- botan3-3.7.1+dfsg/src/fuzzer/bn_sqr.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/bn_sqr.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -14,10 +14,10 @@ return; } - Botan::BigInt x = Botan::BigInt::from_bytes(in); + const Botan::BigInt x = Botan::BigInt::from_bytes(in); - Botan::BigInt x_sqr = square(x); - Botan::BigInt x_mul = x * x; + const Botan::BigInt x_sqr = square(x); + const Botan::BigInt x_mul = x * x; FUZZER_ASSERT_EQUAL(x_sqr, x_mul); } diff -Nru botan3-3.7.1+dfsg/src/fuzzer/cert.cpp botan3-3.12.0+dfsg/src/fuzzer/cert.cpp --- botan3-3.7.1+dfsg/src/fuzzer/cert.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/cert.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -16,6 +16,6 @@ try { Botan::DataSource_Memory input(in); - Botan::X509_Certificate cert(input); - } catch(Botan::Exception& e) {} + const Botan::X509_Certificate cert(input); + } catch(const Botan::Exception& e) {} } diff -Nru botan3-3.7.1+dfsg/src/fuzzer/crl.cpp botan3-3.12.0+dfsg/src/fuzzer/crl.cpp --- botan3-3.7.1+dfsg/src/fuzzer/crl.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/crl.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -12,6 +12,6 @@ void fuzz(std::span in) { try { Botan::DataSource_Memory input(in); - Botan::X509_CRL crl(input); - } catch(Botan::Exception& e) {} + const Botan::X509_CRL crl(input); + } catch(const Botan::Exception& e) {} } diff -Nru botan3-3.7.1+dfsg/src/fuzzer/divide.cpp botan3-3.12.0+dfsg/src/fuzzer/divide.cpp --- botan3-3.7.1+dfsg/src/fuzzer/divide.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/divide.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -13,7 +13,13 @@ } // Save on allocations by making these static - static Botan::BigInt x, y, q, r, ct_q, ct_r, z; + static Botan::BigInt x; + static Botan::BigInt y; + static Botan::BigInt q; + static Botan::BigInt r; + static Botan::BigInt ct_q; + static Botan::BigInt ct_r; + static Botan::BigInt z; x = Botan::BigInt::from_bytes(in.subspan(0, in.size() / 2)); y = Botan::BigInt::from_bytes(in.subspan(in.size() / 2, in.size() - in.size() / 2)); @@ -48,7 +54,7 @@ z = q * y + r; FUZZER_ASSERT_EQUAL(z, x); - Botan::word rw; + Botan::word rw = 0; Botan::ct_divide_word(x, y.word_at(0), ct_q, rw); FUZZER_ASSERT_EQUAL(ct_q, q); FUZZER_ASSERT_EQUAL(rw, r.word_at(0)); diff -Nru botan3-3.7.1+dfsg/src/fuzzer/ec_scalar.cpp botan3-3.12.0+dfsg/src/fuzzer/ec_scalar.cpp --- botan3-3.7.1+dfsg/src/fuzzer/ec_scalar.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/ec_scalar.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,138 @@ +/* +* (C) 2026 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ +#include "fuzzers.h" + +#include +#include +#include + +namespace { + +void check_scalar_arith(const Botan::EC_Group& group, std::span in) { + // Need at least 2 scalars worth of input + const size_t scalar_bytes = group.get_order_bytes(); + + if(in.size() < 2 * scalar_bytes || in.size() > 2 * 2 * scalar_bytes) { + return; + } + + const auto a = Botan::EC_Scalar::from_bytes_mod_order(group, in.first(in.size() / 2)); + const auto b = Botan::EC_Scalar::from_bytes_mod_order(group, in.last(in.size() / 2)); + + const auto one = Botan::EC_Scalar::one(group); + + // a - a == 0 + FUZZER_ASSERT_TRUE((a - a).is_zero()); + + // a + (-a) == 0 + FUZZER_ASSERT_TRUE((a + a.negate()).is_zero()); + + // a * 1 == a + FUZZER_ASSERT_TRUE((a * one) == a); + + // a + b == b + a (commutativity) + FUZZER_ASSERT_TRUE((a + b) == (b + a)); + + // a * b == b * a (commutativity) + FUZZER_ASSERT_TRUE((a * b) == (b * a)); + + if(!a.is_zero()) { + const auto a_inv = a.invert(); + const auto a_inv_vt = a.invert_vartime(); + + // invert and invert_vartime agree + FUZZER_ASSERT_TRUE(a_inv == a_inv_vt); + + // a * a^-1 == 1 + FUZZER_ASSERT_TRUE((a * a_inv) == one); + + // (a^-1)^-1 == a + FUZZER_ASSERT_TRUE(a_inv.invert() == a); + } + + if(!b.is_zero()) { + const auto b_inv = b.invert(); + const auto b_inv_vt = b.invert_vartime(); + + FUZZER_ASSERT_TRUE(b_inv == b_inv_vt); + FUZZER_ASSERT_TRUE((b * b_inv) == one); + } + + // (a + b) * c == a*c + b*c for c = a (distributivity, reusing a as c) + FUZZER_ASSERT_TRUE((a + b) * a == (a * a + b * a)); + + // square_self: a^2 == a * a + auto a_sq = Botan::EC_Scalar(a); + a_sq.square_self(); + FUZZER_ASSERT_TRUE(a_sq == (a * a)); + + /* + Serialization round-trip tests + + The value of zero can be serialized but *not* deserialized + */ + if(!a.is_zero()) { + std::vector a_bytes(scalar_bytes); + a.serialize_to(a_bytes); + const auto a_rt = Botan::EC_Scalar::deserialize(group, a_bytes); + FUZZER_ASSERT_TRUE(a_rt.has_value()); + FUZZER_ASSERT_TRUE(a_rt.value() == a); + } + + if(!b.is_zero()) { + std::vector b_bytes(scalar_bytes); + b.serialize_to(b_bytes); + const auto b_rt = Botan::EC_Scalar::deserialize(group, b_bytes); + FUZZER_ASSERT_TRUE(b_rt.has_value()); + FUZZER_ASSERT_TRUE(b_rt.value() == b); + } +} + +} // namespace + +void fuzz(std::span in) { + // First byte selects the curve + if(in.empty()) { + return; + } + + const uint8_t curve_id = in[0]; + const auto data = in.subspan(1); + + static const Botan::EC_Group p192 = Botan::EC_Group::from_name("secp192r1"); + static const Botan::EC_Group p224 = Botan::EC_Group::from_name("secp224r1"); + static const Botan::EC_Group p256 = Botan::EC_Group::from_name("secp256r1"); + static const Botan::EC_Group p384 = Botan::EC_Group::from_name("secp384r1"); + static const Botan::EC_Group p521 = Botan::EC_Group::from_name("secp521r1"); + static const Botan::EC_Group bp256 = Botan::EC_Group::from_name("brainpool256r1"); + static const Botan::EC_Group bp384 = Botan::EC_Group::from_name("brainpool384r1"); + static const Botan::EC_Group bp512 = Botan::EC_Group::from_name("brainpool512r1"); + static const Botan::EC_Group k256 = Botan::EC_Group::from_name("secp256k1"); + static const Botan::EC_Group frp256 = Botan::EC_Group::from_name("frp256v1"); + static const Botan::EC_Group sm2 = Botan::EC_Group::from_name("sm2p256v1"); + static const Botan::EC_Group numsp512 = Botan::EC_Group::from_name("numsp512d1"); + + constexpr size_t total_curves = 12; + + // NOLINTNEXTLINE(*-avoid-c-arrays) + std::array curves{ + &p192, + &p224, + &p256, + &p384, + &p521, + &bp256, + &bp384, + &bp512, + &k256, + &frp256, + &sm2, + &numsp512, + }; + + const auto& group = *curves[curve_id % total_curves]; + check_scalar_arith(group, data); +} diff -Nru botan3-3.7.1+dfsg/src/fuzzer/ecc_bp256.cpp botan3-3.12.0+dfsg/src/fuzzer/ecc_bp256.cpp --- botan3-3.7.1+dfsg/src/fuzzer/ecc_bp256.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/ecc_bp256.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -12,6 +12,6 @@ return; } - static Botan::EC_Group bp256("brainpool256r1"); + static const Botan::EC_Group bp256("brainpool256r1"); return check_ecc_math(bp256, in); } diff -Nru botan3-3.7.1+dfsg/src/fuzzer/ecc_bp384.cpp botan3-3.12.0+dfsg/src/fuzzer/ecc_bp384.cpp --- botan3-3.7.1+dfsg/src/fuzzer/ecc_bp384.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/ecc_bp384.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,16 @@ +/* +* (C) 2015,2016 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ +#include "fuzzers.h" + +#include "ecc_helper.h" + +void fuzz(std::span in) { + if(in.size() > 2 * 384 / 8) { + return; + } + static const Botan::EC_Group bp384("brainpool384r1"); + return check_ecc_math(bp384, in); +} diff -Nru botan3-3.7.1+dfsg/src/fuzzer/ecc_bp512.cpp botan3-3.12.0+dfsg/src/fuzzer/ecc_bp512.cpp --- botan3-3.7.1+dfsg/src/fuzzer/ecc_bp512.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/ecc_bp512.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,16 @@ +/* +* (C) 2015,2016 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ +#include "fuzzers.h" + +#include "ecc_helper.h" + +void fuzz(std::span in) { + if(in.size() > 2 * 512 / 8) { + return; + } + static const Botan::EC_Group bp512("brainpool512r1"); + return check_ecc_math(bp512, in); +} diff -Nru botan3-3.7.1+dfsg/src/fuzzer/ecc_frp256.cpp botan3-3.12.0+dfsg/src/fuzzer/ecc_frp256.cpp --- botan3-3.7.1+dfsg/src/fuzzer/ecc_frp256.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/ecc_frp256.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,16 @@ +/* +* (C) 2015,2016 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ +#include "fuzzers.h" + +#include "ecc_helper.h" + +void fuzz(std::span in) { + if(in.size() > 2 * 256 / 8) { + return; + } + static const Botan::EC_Group frp256("frp256v1"); + return check_ecc_math(frp256, in); +} diff -Nru botan3-3.7.1+dfsg/src/fuzzer/ecc_helper.h botan3-3.12.0+dfsg/src/fuzzer/ecc_helper.h --- botan3-3.7.1+dfsg/src/fuzzer/ecc_helper.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/ecc_helper.h 2026-05-07 01:38:28.000000000 +0000 @@ -12,9 +12,7 @@ #include #include #include -#include - -namespace { +#include inline std::ostream& operator<<(std::ostream& o, const Botan::EC_AffinePoint& point) { o << Botan::hex_encode(point.serialize_uncompressed()) << "\n"; @@ -26,7 +24,7 @@ const Botan::BigInt& curve_p, const Botan::BigInt& curve_a, const Botan::BigInt& curve_b) { - Botan::BigInt xpow3 = x * x * x; + const Botan::BigInt xpow3 = x * x * x; Botan::BigInt g = curve_a * x; g += xpow3; @@ -83,6 +81,4 @@ FUZZER_ASSERT_EQUAL(T2, R2); } -} // namespace - #endif diff -Nru botan3-3.7.1+dfsg/src/fuzzer/ecc_numsp512.cpp botan3-3.12.0+dfsg/src/fuzzer/ecc_numsp512.cpp --- botan3-3.7.1+dfsg/src/fuzzer/ecc_numsp512.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/ecc_numsp512.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,16 @@ +/* +* (C) 2015,2016 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ +#include "fuzzers.h" + +#include "ecc_helper.h" + +void fuzz(std::span in) { + if(in.size() > 2 * 512 / 8) { + return; + } + static const Botan::EC_Group numsp512("numsp512d1"); + return check_ecc_math(numsp512, in); +} diff -Nru botan3-3.7.1+dfsg/src/fuzzer/ecc_p224.cpp botan3-3.12.0+dfsg/src/fuzzer/ecc_p224.cpp --- botan3-3.7.1+dfsg/src/fuzzer/ecc_p224.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/ecc_p224.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,16 @@ +/* +* (C) 2015,2016 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ +#include "fuzzers.h" + +#include "ecc_helper.h" + +void fuzz(std::span in) { + if(in.size() > 2 * 224 / 8) { + return; + } + static const Botan::EC_Group p224("secp224r1"); + return check_ecc_math(p224, in); +} diff -Nru botan3-3.7.1+dfsg/src/fuzzer/ecc_p256.cpp botan3-3.12.0+dfsg/src/fuzzer/ecc_p256.cpp --- botan3-3.7.1+dfsg/src/fuzzer/ecc_p256.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/ecc_p256.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -11,6 +11,6 @@ if(in.size() > 2 * 256 / 8) { return; } - static Botan::EC_Group p256("secp256r1"); + static const Botan::EC_Group p256("secp256r1"); return check_ecc_math(p256, in); } diff -Nru botan3-3.7.1+dfsg/src/fuzzer/ecc_p384.cpp botan3-3.12.0+dfsg/src/fuzzer/ecc_p384.cpp --- botan3-3.7.1+dfsg/src/fuzzer/ecc_p384.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/ecc_p384.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -11,6 +11,6 @@ if(in.size() > 2 * 384 / 8) { return; } - static Botan::EC_Group p384("secp384r1"); + static const Botan::EC_Group p384("secp384r1"); return check_ecc_math(p384, in); } diff -Nru botan3-3.7.1+dfsg/src/fuzzer/ecc_p521.cpp botan3-3.12.0+dfsg/src/fuzzer/ecc_p521.cpp --- botan3-3.7.1+dfsg/src/fuzzer/ecc_p521.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/ecc_p521.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -11,6 +11,6 @@ if(in.size() > 2 * (521 + 7) / 8) { return; } - static Botan::EC_Group p521("secp521r1"); + static const Botan::EC_Group p521("secp521r1"); return check_ecc_math(p521, in); } diff -Nru botan3-3.7.1+dfsg/src/fuzzer/ecc_secp256k1.cpp botan3-3.12.0+dfsg/src/fuzzer/ecc_secp256k1.cpp --- botan3-3.7.1+dfsg/src/fuzzer/ecc_secp256k1.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/ecc_secp256k1.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,16 @@ +/* +* (C) 2015,2016 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ +#include "fuzzers.h" + +#include "ecc_helper.h" + +void fuzz(std::span in) { + if(in.size() > 2 * 256 / 8) { + return; + } + static const Botan::EC_Group secp256k1("secp256k1"); + return check_ecc_math(secp256k1, in); +} diff -Nru botan3-3.7.1+dfsg/src/fuzzer/ecc_sm2p256.cpp botan3-3.12.0+dfsg/src/fuzzer/ecc_sm2p256.cpp --- botan3-3.7.1+dfsg/src/fuzzer/ecc_sm2p256.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/ecc_sm2p256.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,16 @@ +/* +* (C) 2015,2016 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ +#include "fuzzers.h" + +#include "ecc_helper.h" + +void fuzz(std::span in) { + if(in.size() > 2 * 256 / 8) { + return; + } + static const Botan::EC_Group sm2("sm2p256v1"); + return check_ecc_math(sm2, in); +} diff -Nru botan3-3.7.1+dfsg/src/fuzzer/fuzzers.h botan3-3.12.0+dfsg/src/fuzzer/fuzzers.h --- botan3-3.7.1+dfsg/src/fuzzer/fuzzers.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/fuzzers.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,19 +9,23 @@ #include #include +#include +#include #include #include #include // for setenv #include -static const size_t max_fuzzer_input_size = 8192; +static constexpr size_t max_fuzzer_input_size = 8192; extern void fuzz(std::span in); +// Need to declare these before defining them; extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv); extern "C" int LLVMFuzzerTestOneInput(const uint8_t in[], size_t len); -extern "C" int LLVMFuzzerInitialize(int*, char***) { +// NOLINTNEXTLINE(*-definitions-in-headers) +extern "C" int LLVMFuzzerInitialize(int* /*argc*/, char*** /*argv*/) { /* * This disables the mlock pool, as overwrites within the pool are * opaque to ASan or other instrumentation. @@ -31,9 +35,18 @@ } // Called by main() in libFuzzer or in main for AFL below +// NOLINTNEXTLINE(*-definitions-in-headers) extern "C" int LLVMFuzzerTestOneInput(const uint8_t in[], size_t len) { if(len <= max_fuzzer_input_size) { - fuzz(std::span(in, len)); + try { + fuzz(std::span(in, len)); + } catch(const std::exception& e) { + std::cerr << "Uncaught exception from fuzzer driver " << e.what() << "\n"; + abort(); + } catch(...) { + std::cerr << "Uncaught exception from fuzzer driver (unknown type)\n"; + abort(); + } } return 0; } @@ -41,7 +54,7 @@ // Some helpers for the fuzzer jigs inline std::shared_ptr fuzzer_rng_as_shared() { - static std::shared_ptr rng = + static const std::shared_ptr rng = std::make_shared(Botan::secure_vector(32)); return rng; } @@ -50,47 +63,45 @@ return *fuzzer_rng_as_shared(); } -#define FUZZER_WRITE_AND_CRASH(expr) \ - do { \ - std::cerr << expr << " @ Line " << __LINE__ << " in " << __FILE__ << "\n"; \ - abort(); \ +// TODO use a constexpr function with std::source_location +// NOLINTNEXTLINE(*-macro-usage) +#define FUZZER_WRITE_AND_CRASH(expr) \ + /* NOLINTNEXTLINE(*-avoid-do-while) */ \ + do { \ + std::cerr << expr << " @ Line " << __LINE__ << " in " << __FILE__ << "\n"; /* NOLINT(*-macro-paren*) */ \ + abort(); \ } while(0) -#define FUZZER_ASSERT_EQUAL(x, y) \ - do { \ - if(x != y) { \ - FUZZER_WRITE_AND_CRASH(#x << " = " << x << " != " << #y << " = " << y << "\n"); \ - } \ +// TODO use a constexpr function with std::source_location +// NOLINTNEXTLINE(*-macro-usage) +#define FUZZER_ASSERT_EQUAL(x, y) \ + /* NOLINTNEXTLINE(*-avoid-do-while) */ \ + do { \ + if((x) != (y)) { \ + FUZZER_WRITE_AND_CRASH(#x << " = " << (x) << " != " << #y << " = " << (y) << "\n"); \ + } \ } while(0) +// TODO use a constexpr function with std::source_location +// NOLINTNEXTLINE(*-macro-usage) #define FUZZER_ASSERT_TRUE(e) \ + /* NOLINTNEXTLINE(*-avoid-do-while) */ \ do { \ + /* NOLINTNEXTLINE(*-simplify-boolean-expr) */ \ if(!(e)) { \ FUZZER_WRITE_AND_CRASH("Expression " << #e << " was false"); \ } \ } while(0) -#if defined(BOTAN_FUZZER_IS_AFL) || defined(BOTAN_FUZZER_IS_TEST) - - /* Stub for AFL */ - - #if defined(BOTAN_FUZZER_IS_AFL) && !defined(__AFL_COMPILER) - #error "Build configured for AFL but not being compiled by AFL compiler" - #endif - - #if defined(BOTAN_FUZZER_IS_TEST) - - #include - -namespace { +#if defined(BOTAN_FUZZER_IS_TEST) -int fuzz_files(char* files[]) { - for(size_t i = 0; files[i]; ++i) { +inline int fuzz_files(char* files[]) { + for(size_t i = 0; files[i] != nullptr; ++i) { std::ifstream in(files[i]); if(in.good()) { std::vector buf(max_fuzzer_input_size); - in.read(reinterpret_cast(buf.data()), buf.size()); + in.read(reinterpret_cast(buf.data()), static_cast(buf.size())); const size_t got = in.gcount(); buf.resize(got); buf.shrink_to_fit(); @@ -102,10 +113,17 @@ return 0; } -} // namespace +#endif + +#if defined(BOTAN_FUZZER_IS_AFL) || defined(BOTAN_FUZZER_IS_TEST) + /* Stub for AFL */ + + #if defined(BOTAN_FUZZER_IS_AFL) && !defined(__AFL_COMPILER) + #error "Build configured for AFL but not being compiled by AFL compiler" #endif +// NOLINTNEXTLINE(*-definitions-in-headers) int main(int argc, char* argv[]) { LLVMFuzzerInitialize(&argc, &argv); @@ -120,7 +138,7 @@ #endif { std::vector buf(max_fuzzer_input_size); - std::cin.read(reinterpret_cast(buf.data()), buf.size()); + std::cin.read(reinterpret_cast(buf.data()), static_cast(buf.size())); const size_t got = std::cin.gcount(); buf.resize(got); @@ -134,6 +152,7 @@ #include +// NOLINTNEXTLINE(*-definitions-in-headers) int main(int argc, char* argv[]) { LLVMFuzzerInitialize(&argc, &argv); diff -Nru botan3-3.7.1+dfsg/src/fuzzer/gcd.cpp botan3-3.12.0+dfsg/src/fuzzer/gcd.cpp --- botan3-3.7.1+dfsg/src/fuzzer/gcd.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/gcd.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -29,8 +29,9 @@ return; } - const Botan::BigInt x = Botan::BigInt::from_bytes(in.subspan(in.size() / 2)); - const Botan::BigInt y = Botan::BigInt::from_bytes(in.subspan(in.size() / 2, in.size() - (in.size() / 2))); + const size_t half = in.size() / 2; + const Botan::BigInt x = Botan::BigInt::from_bytes(in.subspan(0, half)); + const Botan::BigInt y = Botan::BigInt::from_bytes(in.subspan(half, in.size() - half)); const Botan::BigInt ref = ref_gcd(x, y); const Botan::BigInt lib = Botan::gcd(x, y); diff -Nru botan3-3.7.1+dfsg/src/fuzzer/invert.cpp botan3-3.12.0+dfsg/src/fuzzer/invert.cpp --- botan3-3.7.1+dfsg/src/fuzzer/invert.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/invert.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -16,10 +16,14 @@ if(n.is_even() && mod.is_even()) { return 0; } - Botan::BigInt u = mod, v = n; - Botan::BigInt A = 1, B = 0, C = 0, D = 1; + Botan::BigInt u = mod; + Botan::BigInt v = n; + Botan::BigInt A = 1; + Botan::BigInt B = 0; + Botan::BigInt C = 0; + Botan::BigInt D = 1; - while(u.is_nonzero()) { + while(!u.is_zero()) { const size_t u_zero_bits = Botan::low_zero_bits(u); u >>= u_zero_bits; for(size_t i = 0; i != u_zero_bits; ++i) { @@ -57,7 +61,7 @@ return 0; // no modular inverse } - while(D.is_negative()) { + while(D.signum() < 0) { D += mod; } while(D >= mod) { @@ -77,7 +81,7 @@ } const Botan::BigInt x = Botan::BigInt::from_bytes(in.subspan(0, in.size() / 2)); - Botan::BigInt mod = Botan::BigInt::from_bytes(in.subspan(in.size() / 2, in.size() - in.size() / 2)); + const Botan::BigInt mod = Botan::BigInt::from_bytes(in.subspan(in.size() / 2, in.size() - in.size() / 2)); if(mod < 2) { return; diff -Nru botan3-3.7.1+dfsg/src/fuzzer/ipv4.cpp botan3-3.12.0+dfsg/src/fuzzer/ipv4.cpp --- botan3-3.7.1+dfsg/src/fuzzer/ipv4.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/ipv4.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -10,16 +10,16 @@ #include void fuzz(std::span in) { - std::string_view str(reinterpret_cast(in.data()), in.size()); + const std::string_view str(reinterpret_cast(in.data()), in.size()); if(auto ipv4 = Botan::string_to_ipv4(str)) { const auto rt = Botan::ipv4_to_string(*ipv4); FUZZER_ASSERT_EQUAL(str, rt); } if(in.size() == 4) { - uint32_t ip = Botan::load_be(in.data(), 0); - auto s = Botan::ipv4_to_string(ip); - auto rt = Botan::string_to_ipv4(s); + const uint32_t ip = Botan::load_be(in.data(), 0); + const auto s = Botan::ipv4_to_string(ip); + const auto rt = Botan::string_to_ipv4(s); FUZZER_ASSERT_TRUE(rt.has_value()); FUZZER_ASSERT_EQUAL(rt.value(), ip); } diff -Nru botan3-3.7.1+dfsg/src/fuzzer/mem_pool.cpp botan3-3.12.0+dfsg/src/fuzzer/mem_pool.cpp --- botan3-3.7.1+dfsg/src/fuzzer/mem_pool.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/mem_pool.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -26,10 +27,10 @@ struct RawPage { public: - RawPage(void* p) : m_p(p) {} + explicit RawPage(void* p) : m_p(p) {} ~RawPage() { - // NOLINTNEXTLINE(*-no-malloc) + // NOLINTNEXTLINE(*-no-malloc,*-owning-memory) std::free(m_p); } @@ -58,10 +59,10 @@ for(size_t i = 0; i != count; ++i) { void* ptr = nullptr; - int rc = ::posix_memalign(&ptr, page_size, page_size); + const int rc = ::posix_memalign(&ptr, page_size, page_size); FUZZER_ASSERT_EQUAL(rc, 0); - if(ptr) { + if(ptr != nullptr) { pages.push_back(RawPage(ptr)); } } @@ -76,19 +77,19 @@ const size_t page_size = 4096; // static to avoid repeated allocations - static std::vector raw_mem = allocate_raw_pages(page_count, page_size); + static const std::vector raw_mem = allocate_raw_pages(page_count, page_size); std::vector mem_pages; mem_pages.reserve(raw_mem.size()); - for(size_t i = 0; i != raw_mem.size(); ++i) { - mem_pages.push_back(raw_mem[i].ptr()); + for(const auto& rm : raw_mem) { + mem_pages.push_back(rm.ptr()); } Botan::Memory_Pool pool(mem_pages, page_size); std::map ptrs; size_t in_len = in.size(); - auto x = in.data(); + const auto* x = in.data(); while(in_len > 0) { const uint8_t op = in[0] % 2; size_t idx = (in[0] >> 1); @@ -105,7 +106,7 @@ const size_t plen = idx + 1; // ensure non-zero uint8_t* p = static_cast(pool.allocate(plen)); - if(p) { + if(p != nullptr) { const size_t expected_alignment = compute_expected_alignment(plen); const size_t alignment = reinterpret_cast(p) % expected_alignment; if(alignment != 0) { @@ -126,7 +127,7 @@ std::memset(p, static_cast(idx), plen); auto insert = ptrs.insert(std::make_pair(p, plen)); - if(insert.second == false) { + if(!insert.second) { FUZZER_WRITE_AND_CRASH("Pointer " << static_cast(p) << " already existed\n"); } diff -Nru botan3-3.7.1+dfsg/src/fuzzer/mode_padding.cpp botan3-3.12.0+dfsg/src/fuzzer/mode_padding.cpp --- botan3-3.7.1+dfsg/src/fuzzer/mode_padding.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/mode_padding.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -15,7 +15,7 @@ if(in.size() <= 2) { return in.size(); } - size_t len = in.size(); + const size_t len = in.size(); const size_t padding_length = in[len - 1]; @@ -35,7 +35,7 @@ } size_t ref_x923_unpad(std::span in) { - size_t len = in.size(); + const size_t len = in.size(); if(len <= 2) { return len; } @@ -57,7 +57,7 @@ } size_t ref_oneandzero_unpad(std::span in) { - size_t len = in.size(); + const size_t len = in.size(); if(len <= 2) { return len; } @@ -82,7 +82,7 @@ } size_t ref_esp_unpad(std::span in) { - size_t len = in.size(); + const size_t len = in.size(); if(len <= 2) { return len; } @@ -104,7 +104,7 @@ } uint16_t ref_tls_cbc_unpad(std::span in) { - size_t len = in.size(); + const size_t len = in.size(); if(len == 0) { return 0; } @@ -130,33 +130,33 @@ } // namespace void fuzz(std::span in) { - static Botan::PKCS7_Padding pkcs7; - static Botan::ANSI_X923_Padding x923; - static Botan::OneAndZeros_Padding oneandzero; - static Botan::ESP_Padding esp; + static const Botan::PKCS7_Padding pkcs7; + static const Botan::ANSI_X923_Padding x923; + static const Botan::OneAndZeros_Padding oneandzero; + static const Botan::ESP_Padding esp; - size_t len = in.size(); + const size_t len = in.size(); if(pkcs7.valid_blocksize(len)) { - const size_t ct_pkcs7 = pkcs7.unpad(in.data(), len); + const size_t ct_pkcs7 = pkcs7.unpad(in); const size_t ref_pkcs7 = ref_pkcs7_unpad(in); FUZZER_ASSERT_EQUAL(ct_pkcs7, ref_pkcs7); } if(x923.valid_blocksize(len)) { - const size_t ct_x923 = x923.unpad(in.data(), len); + const size_t ct_x923 = x923.unpad(in); const size_t ref_x923 = ref_x923_unpad(in); FUZZER_ASSERT_EQUAL(ct_x923, ref_x923); } if(oneandzero.valid_blocksize(len)) { - const size_t ct_oneandzero = oneandzero.unpad(in.data(), len); + const size_t ct_oneandzero = oneandzero.unpad(in); const size_t ref_oneandzero = ref_oneandzero_unpad(in); FUZZER_ASSERT_EQUAL(ct_oneandzero, ref_oneandzero); } if(esp.valid_blocksize(len)) { - const size_t ct_esp = esp.unpad(in.data(), len); + const size_t ct_esp = esp.unpad(in); const size_t ref_esp = ref_esp_unpad(in); FUZZER_ASSERT_EQUAL(ct_esp, ref_esp); } diff -Nru botan3-3.7.1+dfsg/src/fuzzer/mp_comba_mul.cpp botan3-3.12.0+dfsg/src/fuzzer/mp_comba_mul.cpp --- botan3-3.7.1+dfsg/src/fuzzer/mp_comba_mul.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/mp_comba_mul.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -12,7 +12,7 @@ if(in.empty() || words > 2 * 16) { return; } - size_t in_len = in.size(); + const size_t in_len = in.size(); word x[24] = {0}; word y[24] = {0}; diff -Nru botan3-3.7.1+dfsg/src/fuzzer/mp_fuzzers.h botan3-3.12.0+dfsg/src/fuzzer/mp_fuzzers.h --- botan3-3.7.1+dfsg/src/fuzzer/mp_fuzzers.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/mp_fuzzers.h 2026-05-07 01:38:28.000000000 +0000 @@ -10,24 +10,28 @@ #include "fuzzers.h" #include - -#if BOTAN_MP_WORD_BITS == 64 - #define WORD_FORMAT_STRING "%016lX" -#else - #define WORD_FORMAT_STRING "%08X" -#endif +#include +#include +#include using Botan::word; -namespace { +inline std::string format_word_vec(std::string_view name, const word x[], size_t x_len) { + std::ostringstream oss; + oss << name << " = "; + + constexpr size_t width = 2 * sizeof(word); -inline void dump_word_vec(const char* name, const word x[], size_t x_len) { - fprintf(stderr, "%s = ", name); for(size_t i = 0; i != x_len; ++i) { - fprintf(stderr, WORD_FORMAT_STRING, x[i]); - fprintf(stderr, " "); + oss << std::uppercase << std::setw(width) << std::setfill('0') << std::hex << x[i] << " "; } - fprintf(stderr, "\n"); + + oss << "\n"; + return oss.str(); +} + +inline void dump_word_vec(std::string_view name, const word x[], size_t x_len) { + std::cerr << format_word_vec(name, x, x_len); } inline void compare_word_vec(const word x[], size_t x_len, const word y[], size_t y_len, const char* comparing) { @@ -58,6 +62,4 @@ } } -} // namespace - #endif diff -Nru botan3-3.7.1+dfsg/src/fuzzer/mp_redc.cpp botan3-3.12.0+dfsg/src/fuzzer/mp_redc.cpp --- botan3-3.7.1+dfsg/src/fuzzer/mp_redc.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/mp_redc.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -14,8 +14,8 @@ word z[2 * N] = {0}; - word z_script[2 * N] = {0}; - word z_ref[2 * N] = {0}; + word r_script[N] = {0}; + word r_ref[N] = {0}; word p[N] = {0}; word p_dash = 0; @@ -25,39 +25,35 @@ std::memcpy(p, in.data() + sizeof(z), sizeof(p)); std::memcpy(&p_dash, in.data() + sizeof(z) + sizeof(p), sizeof(p_dash)); - for(size_t i = 0; i != 2 * N; ++i) { - z_script[i] = z_ref[i] = z[i]; - } - if(N == 4) { - Botan::bigint_monty_redc_4(z_script, p, p_dash, ws); + Botan::bigint_monty_redc_4(r_script, z, p, p_dash, ws); } else if(N == 6) { - Botan::bigint_monty_redc_6(z_script, p, p_dash, ws); + Botan::bigint_monty_redc_6(r_script, z, p, p_dash, ws); } else if(N == 8) { - Botan::bigint_monty_redc_8(z_script, p, p_dash, ws); + Botan::bigint_monty_redc_8(r_script, z, p, p_dash, ws); } else if(N == 16) { - Botan::bigint_monty_redc_16(z_script, p, p_dash, ws); + Botan::bigint_monty_redc_16(r_script, z, p, p_dash, ws); } else if(N == 24) { - Botan::bigint_monty_redc_24(z_script, p, p_dash, ws); + Botan::bigint_monty_redc_24(r_script, z, p, p_dash, ws); } else if(N == 32) { - Botan::bigint_monty_redc_32(z_script, p, p_dash, ws); + Botan::bigint_monty_redc_32(r_script, z, p, p_dash, ws); } else { std::abort(); } - Botan::bigint_monty_redc_generic(z_ref, 2 * N, p, N, p_dash, ws); + Botan::bigint_monty_redc_generic(r_ref, z, 2 * N, p, N, p_dash, ws); - for(size_t i = 0; i != 2 * N; ++i) { - if(z_script[i] != z_ref[i]) { + for(size_t i = 0; i != N; ++i) { + if(r_script[i] != r_ref[i]) { dump_word_vec("input", z, 2 * N); - dump_word_vec("z_script", z_script, 2 * N); - dump_word_vec("z_ref", z_ref, 2 * N); + dump_word_vec("r_script", r_script, 2 * N); + dump_word_vec("r_ref", r_ref, 2 * N); dump_word_vec("p", p, N); dump_word_vec("p_dash", &p_dash, 1); std::abort(); } } - compare_word_vec(z_script, 2 * N, z_ref, 2 * N, "redc generic vs specialized"); + compare_word_vec(r_script, N, r_ref, N, "redc generic vs specialized"); } } // namespace diff -Nru botan3-3.7.1+dfsg/src/fuzzer/mp_redc_crandall.cpp botan3-3.12.0+dfsg/src/fuzzer/mp_redc_crandall.cpp --- botan3-3.7.1+dfsg/src/fuzzer/mp_redc_crandall.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/mp_redc_crandall.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,20 +9,28 @@ #include #include +namespace { + +consteval word crandall_C() { + if(sizeof(word) == 8) { + // secp256k1 modulus + return static_cast(0x1000003d1); + } else { + // 128 bit prime with largest possible C + return 0xffffffe1; + } +} + +} // namespace + void fuzz(std::span in) { if(in.size() != 8 * sizeof(word)) { return; } -#if BOTAN_MP_WORD_BITS == 64 - // secp256k1 modulus - const word C = 0x1000003d1; -#else - // 128 bit prime with largest possible C - const word C = 0xffffffe1; -#endif + constexpr word C = crandall_C(); - static const Botan::BigInt refp = Botan::BigInt::power_of_2(4 * BOTAN_MP_WORD_BITS) - C; + static const Botan::BigInt refp = Botan::BigInt::power_of_2(4 * 8 * sizeof(C)) - C; static const Botan::BigInt refp2 = refp * refp; const auto refz = Botan::BigInt::from_bytes(in); diff -Nru botan3-3.7.1+dfsg/src/fuzzer/ocsp.cpp botan3-3.12.0+dfsg/src/fuzzer/ocsp.cpp --- botan3-3.7.1+dfsg/src/fuzzer/ocsp.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/ocsp.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -10,6 +10,6 @@ void fuzz(std::span in) { try { - Botan::OCSP::Response response(in.data(), in.size()); - } catch(Botan::Exception& e) {} + const Botan::OCSP::Response response(in.data(), in.size()); + } catch(const Botan::Exception& e) {} } diff -Nru botan3-3.7.1+dfsg/src/fuzzer/os2ecp.cpp botan3-3.12.0+dfsg/src/fuzzer/os2ecp.cpp --- botan3-3.7.1+dfsg/src/fuzzer/os2ecp.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/os2ecp.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -13,7 +13,7 @@ void check_os2ecp(const Botan::EC_Group& group, std::span in) { try { Botan::EC_AffinePoint(group, in); - } catch(Botan::Exception& e) {} + } catch(const Botan::Exception& e) {} } } // namespace @@ -23,13 +23,13 @@ return; } - static Botan::EC_Group p192 = Botan::EC_Group::from_name("secp192r1"); - static Botan::EC_Group p224 = Botan::EC_Group::from_name("secp224r1"); - static Botan::EC_Group p256 = Botan::EC_Group::from_name("secp256r1"); - static Botan::EC_Group p384 = Botan::EC_Group::from_name("secp384r1"); - static Botan::EC_Group p521 = Botan::EC_Group::from_name("secp521r1"); - static Botan::EC_Group bp256 = Botan::EC_Group::from_name("brainpool256r1"); - static Botan::EC_Group bp512 = Botan::EC_Group::from_name("brainpool512r1"); + static const Botan::EC_Group p192 = Botan::EC_Group::from_name("secp192r1"); + static const Botan::EC_Group p224 = Botan::EC_Group::from_name("secp224r1"); + static const Botan::EC_Group p256 = Botan::EC_Group::from_name("secp256r1"); + static const Botan::EC_Group p384 = Botan::EC_Group::from_name("secp384r1"); + static const Botan::EC_Group p521 = Botan::EC_Group::from_name("secp521r1"); + static const Botan::EC_Group bp256 = Botan::EC_Group::from_name("brainpool256r1"); + static const Botan::EC_Group bp512 = Botan::EC_Group::from_name("brainpool512r1"); check_os2ecp(p192, in); check_os2ecp(p224, in); diff -Nru botan3-3.7.1+dfsg/src/fuzzer/pkcs1.cpp botan3-3.12.0+dfsg/src/fuzzer/pkcs1.cpp --- botan3-3.7.1+dfsg/src/fuzzer/pkcs1.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/pkcs1.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -39,21 +39,22 @@ std::vector lib_result; std::vector ref_result; - bool lib_rejected = false, ref_rejected = false; + bool lib_rejected = false; + bool ref_rejected = false; try { lib_result.resize(in.size()); - auto written = (static_cast(&pkcs1))->unpad(lib_result, in); + auto written = (static_cast(&pkcs1))->unpad(lib_result, in); lib_rejected = !written.has_value().as_bool(); lib_result.resize(written.value_or(0)); - } catch(Botan::Decoding_Error&) { + } catch(const Botan::Decoding_Error&) { lib_rejected = true; } try { ref_result = simple_pkcs1_unpad(in.data(), in.size()); - } catch(Botan::Decoding_Error& e) { + } catch(const Botan::Decoding_Error& e) { ref_rejected = true; } diff -Nru botan3-3.7.1+dfsg/src/fuzzer/pkcs8.cpp botan3-3.12.0+dfsg/src/fuzzer/pkcs8.cpp --- botan3-3.7.1+dfsg/src/fuzzer/pkcs8.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/pkcs8.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -14,12 +14,15 @@ void fuzz(std::span in) { try { Botan::DataSource_Memory input(in); - std::unique_ptr key = Botan::PKCS8::load_key(input); - } catch(Botan::Exception& e) {} + Botan::PKCS8::load_key(input); + } catch(const Botan::Exception& e) {} /* * This avoids OOMs in OSS-Fuzz caused by storing precomputations * for thousands of curves randomly generated by the fuzzer. + * + * TODO(Botan4) we can remove this call once support for explicit curves + * is removed */ Botan::EC_Group::clear_registered_curve_data(); } diff -Nru botan3-3.7.1+dfsg/src/fuzzer/pow_mod.cpp botan3-3.12.0+dfsg/src/fuzzer/pow_mod.cpp --- botan3-3.7.1+dfsg/src/fuzzer/pow_mod.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/pow_mod.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,7 +7,7 @@ #include "fuzzers.h" #include -#include +#include namespace { @@ -19,7 +19,7 @@ return 1; } - Botan::Modular_Reducer mod_p(p); + auto mod_p = Botan::Barrett_Reduction::for_public_modulus(p); Botan::BigInt y = 1; while(n > 1) { @@ -62,5 +62,5 @@ << "Z = " << z.to_hex_string() << "\n" << "R = " << ref.to_hex_string() << "\n"); } - } catch(Botan::Exception& e) {} + } catch(const Botan::Exception& e) {} } diff -Nru botan3-3.7.1+dfsg/src/fuzzer/ressol.cpp botan3-3.12.0+dfsg/src/fuzzer/ressol.cpp --- botan3-3.7.1+dfsg/src/fuzzer/ressol.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/ressol.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,13 +7,14 @@ #include "fuzzers.h" #include -#include +#include void fuzz(std::span in) { // Ressol is mostly used for ECC point decompression so best to test smaller sizes static const size_t p_bits = 256; - static const Botan::BigInt p = random_prime(fuzzer_rng(), p_bits); - static const Botan::Modular_Reducer mod_p(p); + // Use p == 1 mod 4 since sqrt modulo p == 3 mod 4 is a fast case + static const Botan::BigInt p = random_prime(fuzzer_rng(), p_bits, 0, 1, 4); + static auto mod_p = Botan::Barrett_Reduction::for_public_modulus(p); if(in.size() > p_bits / 8) { return; @@ -21,7 +22,7 @@ try { const Botan::BigInt a = Botan::BigInt::from_bytes(in); - Botan::BigInt a_sqrt = Botan::sqrt_modulo_prime(a, p); + const Botan::BigInt a_sqrt = Botan::sqrt_modulo_prime(a, p); if(a_sqrt > 0) { const Botan::BigInt a_redc = mod_p.reduce(a); @@ -34,5 +35,5 @@ << "Z = " << z.to_hex_string() << "\n"); } } - } catch(Botan::Exception& e) {} + } catch(const Botan::Exception& e) {} } diff -Nru botan3-3.7.1+dfsg/src/fuzzer/tls_13_handshake_layer.cpp botan3-3.12.0+dfsg/src/fuzzer/tls_13_handshake_layer.cpp --- botan3-3.7.1+dfsg/src/fuzzer/tls_13_handshake_layer.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/tls_13_handshake_layer.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,6 +8,7 @@ #include "fuzzers.h" +#include #include #include @@ -22,14 +23,14 @@ } // namespace void fuzz(std::span in) { - static Botan::TLS::Default_Policy policy; + static const Botan::TLS::Default_Policy policy; try { auto hl1 = prepare(in); - Botan::TLS::Transcript_Hash_State ths("SHA-256"); - while(hl1.next_message(policy, ths).has_value()) {}; + Botan::TLS::Transcript_Hash_State transcript_hash("SHA-256"); + while(hl1.next_message(policy, transcript_hash).has_value()) {}; auto hl2 = prepare(in); while(hl2.next_post_handshake_message(policy).has_value()) {}; - } catch(Botan::Exception& e) {} + } catch(const Botan::Exception& e) {} } diff -Nru botan3-3.7.1+dfsg/src/fuzzer/tls_client.cpp botan3-3.12.0+dfsg/src/fuzzer/tls_client.cpp --- botan3-3.7.1+dfsg/src/fuzzer/tls_client.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/tls_client.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,14 +7,24 @@ #include "fuzzers.h" #include +#include +#include #include +#include +#include #include class Fuzzer_TLS_Client_Creds : public Botan::Credentials_Manager { public: - std::string psk_identity_hint(const std::string&, const std::string&) override { return "psk_hint"; } + std::string psk_identity_hint(const std::string& /*type*/, const std::string& /*context*/) override { + return "psk_hint"; + } - std::string psk_identity(const std::string&, const std::string&, const std::string&) override { return "psk_id"; } + std::string psk_identity(const std::string& /*type*/, + const std::string& /*context*/, + const std::string& /*hint*/) override { + return "psk_id"; + } Botan::secure_vector session_ticket_key() override { return Botan::hex_decode_locked("AABBCCDDEEFF00112233445566778899"); @@ -41,11 +51,11 @@ class Fuzzer_TLS_Policy : public Botan::TLS::Policy { public: - std::vector ciphersuite_list(Botan::TLS::Protocol_Version) const override { + std::vector ciphersuite_list(Botan::TLS::Protocol_Version version) const override { std::vector ciphersuites; for(auto&& suite : Botan::TLS::Ciphersuite::all_known_ciphersuites()) { - if(suite.valid() == false) { + if(suite.valid() && suite.usable_in_version(version)) { ciphersuites.push_back(suite.ciphersuite_code()); } } @@ -56,15 +66,15 @@ class Fuzzer_TLS_Client_Callbacks : public Botan::TLS::Callbacks { public: - void tls_emit_data(std::span) override { + void tls_emit_data(std::span /*data*/) override { // discard } - void tls_record_received(uint64_t, std::span) override { + void tls_record_received(uint64_t /*rec*/, std::span /*data*/) override { // ignore peer data } - void tls_alert(Botan::TLS::Alert) override { + void tls_alert(Botan::TLS::Alert /*alert*/) override { // ignore alert } @@ -91,8 +101,8 @@ auto session_manager = std::make_shared(); auto policy = std::make_shared(); - Botan::TLS::Protocol_Version client_offer = Botan::TLS::Protocol_Version::TLS_V12; - Botan::TLS::Server_Information info("server.name", 443); + const Botan::TLS::Protocol_Version client_offer = Botan::TLS::Protocol_Version::TLS_V12; + const Botan::TLS::Server_Information info("server.name", 443); auto callbacks = std::make_shared(); auto creds = std::make_shared(); @@ -100,5 +110,5 @@ try { client.received_data(in); - } catch(std::exception& e) {} + } catch(const std::exception& e) {} } diff -Nru botan3-3.7.1+dfsg/src/fuzzer/tls_client_hello.cpp botan3-3.12.0+dfsg/src/fuzzer/tls_client_hello.cpp --- botan3-3.7.1+dfsg/src/fuzzer/tls_client_hello.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/tls_client_hello.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,11 +6,11 @@ #include "fuzzers.h" -#include +#include void fuzz(std::span in) { try { - std::vector v(in.begin(), in.end()); - Botan::TLS::Client_Hello_12 ch(v); // TODO: We might want to do that for TLS 1.3 as well - } catch(Botan::Exception& e) {} + const std::vector v(in.begin(), in.end()); + const Botan::TLS::Client_Hello_12 ch(v); // TODO: We might want to do that for TLS 1.3 as well + } catch(const Botan::Exception& e) {} } diff -Nru botan3-3.7.1+dfsg/src/fuzzer/tls_server.cpp botan3-3.12.0+dfsg/src/fuzzer/tls_server.cpp --- botan3-3.7.1+dfsg/src/fuzzer/tls_server.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/tls_server.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,8 +9,14 @@ #include #include #include +#include +#include +#include +#include #include +#include #include +#include #include @@ -82,9 +88,15 @@ return Botan::hex_decode_locked("AABBCCDDEEFF00112233445566778899"); } - std::string psk_identity_hint(const std::string&, const std::string&) override { return "psk_hint"; } + std::string psk_identity_hint(const std::string& /*type*/, const std::string& /*context*/) override { + return "psk_hint"; + } - std::string psk_identity(const std::string&, const std::string&, const std::string&) override { return "psk_id"; } + std::string psk_identity(const std::string& /*type*/, + const std::string& /*context*/, + const std::string& /*hint*/) override { + return "psk_id"; + } std::vector find_preshared_keys( std::string_view host, @@ -107,11 +119,11 @@ class Fuzzer_TLS_Policy : public Botan::TLS::Policy { public: - std::vector ciphersuite_list(Botan::TLS::Protocol_Version) const override { + std::vector ciphersuite_list(Botan::TLS::Protocol_Version version) const override { std::vector ciphersuites; for(auto&& suite : Botan::TLS::Ciphersuite::all_known_ciphersuites()) { - if(suite.valid()) { + if(suite.valid() and suite.usable_in_version(version)) { ciphersuites.push_back(suite.ciphersuite_code()); } } @@ -122,15 +134,15 @@ class Fuzzer_TLS_Server_Callbacks : public Botan::TLS::Callbacks { public: - void tls_emit_data(std::span) override { + void tls_emit_data(std::span /*data*/) override { // discard } - void tls_record_received(uint64_t, std::span) override { + void tls_record_received(uint64_t /*rec*/, std::span /*data*/) override { // ignore peer data } - void tls_alert(Botan::TLS::Alert) override { + void tls_alert(Botan::TLS::Alert /*alert*/) override { // ignore alert } @@ -167,15 +179,15 @@ auto session_manager = std::make_shared(); auto policy = std::make_shared(); - Botan::TLS::Server_Information info("server.name", 443); + const Botan::TLS::Server_Information info("server.name", 443); auto creds = std::make_shared(); auto callbacks = std::make_shared(); - const bool is_datagram = in[0] & 1; + const bool is_datagram = (in[0] & 1) == 1; Botan::TLS::Server server(callbacks, session_manager, creds, policy, fuzzer_rng_as_shared(), is_datagram); try { server.received_data(in.subspan(1, in.size() - 1)); - } catch(std::exception& e) {} + } catch(const std::exception& e) {} } diff -Nru botan3-3.7.1+dfsg/src/fuzzer/uri.cpp botan3-3.12.0+dfsg/src/fuzzer/uri.cpp --- botan3-3.7.1+dfsg/src/fuzzer/uri.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/uri.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -15,5 +15,5 @@ try { Botan::URI::from_any(std::string(reinterpret_cast(input.data()), input.size())); - } catch(Botan::Exception& e) {} + } catch(const Botan::Exception& e) {} } diff -Nru botan3-3.7.1+dfsg/src/fuzzer/x509_path.cpp botan3-3.12.0+dfsg/src/fuzzer/x509_path.cpp --- botan3-3.7.1+dfsg/src/fuzzer/x509_path.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/fuzzer/x509_path.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -14,15 +14,15 @@ Botan::DataSource_Memory input(in); try { - Botan::X509_Certificate subject(input); - Botan::X509_Certificate issuer(input); + const Botan::X509_Certificate subject(input); + const Botan::X509_Certificate issuer(input); std::vector roots; - std::unique_ptr root_store(new Botan::Certificate_Store_In_Memory(issuer)); + const std::unique_ptr root_store(new Botan::Certificate_Store_In_Memory(issuer)); roots.push_back(root_store.get()); - Botan::Path_Validation_Restrictions restrictions; + const Botan::Path_Validation_Restrictions restrictions; x509_path_validate({subject}, restrictions, roots); - } catch(Botan::Exception& e) {} + } catch(const Botan::Exception& e) {} } diff -Nru botan3-3.7.1+dfsg/src/lib/asn1/alg_id.cpp botan3-3.12.0+dfsg/src/lib/asn1/alg_id.cpp --- botan3-3.7.1+dfsg/src/lib/asn1/alg_id.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/asn1/alg_id.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -27,8 +27,8 @@ /* * Create an AlgorithmIdentifier */ -AlgorithmIdentifier::AlgorithmIdentifier(const OID& oid, Encoding_Option option) : m_oid(oid), m_parameters() { - const uint8_t DER_NULL[] = {0x05, 0x00}; +AlgorithmIdentifier::AlgorithmIdentifier(const OID& oid, Encoding_Option option) : m_oid(oid) { + constexpr uint8_t DER_NULL[] = {0x05, 0x00}; if(option == USE_NULL_PARAM) { m_parameters.assign(DER_NULL, DER_NULL + 2); @@ -38,9 +38,8 @@ /* * Create an AlgorithmIdentifier */ -AlgorithmIdentifier::AlgorithmIdentifier(std::string_view oid, Encoding_Option option) : - m_oid(OID::from_string(oid)), m_parameters() { - const uint8_t DER_NULL[] = {0x05, 0x00}; +AlgorithmIdentifier::AlgorithmIdentifier(std::string_view oid, Encoding_Option option) : m_oid(OID::from_string(oid)) { + constexpr uint8_t DER_NULL[2] = {0x05, 0x00}; if(option == USE_NULL_PARAM) { m_parameters.assign(DER_NULL, DER_NULL + 2); diff -Nru botan3-3.7.1+dfsg/src/lib/asn1/asn1_obj.cpp botan3-3.12.0+dfsg/src/lib/asn1/asn1_obj.cpp --- botan3-3.7.1+dfsg/src/lib/asn1/asn1_obj.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/asn1/asn1_obj.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,11 +7,12 @@ #include +#include #include #include #include #include -#include +#include #include namespace Botan { @@ -23,11 +24,15 @@ return output; } +BER_Object::~BER_Object() { + secure_scrub_memory(m_value); +} + /* * Check a type invariant on BER data */ void BER_Object::assert_is_a(ASN1_Type expected_type_tag, ASN1_Class expected_class_tag, std::string_view descr) const { - if(this->is_a(expected_type_tag, expected_class_tag) == false) { + if(!this->is_a(expected_type_tag, expected_class_tag)) { std::stringstream msg; msg << "Tag mismatch when decoding " << descr << " got "; @@ -160,7 +165,7 @@ /* * BER Decoding Exceptions */ -BER_Decoding_Error::BER_Decoding_Error(std::string_view str) : Decoding_Error(fmt("BER: {}", str)) {} +BER_Decoding_Error::BER_Decoding_Error(std::string_view err) : Decoding_Error(fmt("BER: {}", err)) {} BER_Bad_Tag::BER_Bad_Tag(std::string_view str, uint32_t tagging) : BER_Decoding_Error(fmt("{}: {}", str, tagging)) {} @@ -183,24 +188,21 @@ * Convert a BER object into a string object */ std::string to_string(const BER_Object& obj) { - return std::string(cast_uint8_ptr_to_char(obj.bits()), obj.length()); + return bytes_to_string(obj.data()); } /* * Do heuristic tests for BER data */ bool maybe_BER(DataSource& source) { - uint8_t first_u8; - if(!source.peek_byte(first_u8)) { + uint8_t first_u8 = 0; + if(source.peek_byte(first_u8) == 0) { BOTAN_ASSERT_EQUAL(source.read_byte(first_u8), 0, "Expected EOF"); throw Stream_IO_Error("ASN1::maybe_BER: Source was empty"); } const auto cons_seq = static_cast(ASN1_Class::Constructed) | static_cast(ASN1_Type::Sequence); - if(first_u8 == cons_seq) { - return true; - } - return false; + return first_u8 == cons_seq; } } // namespace ASN1 diff -Nru botan3-3.7.1+dfsg/src/lib/asn1/asn1_obj.h botan3-3.12.0+dfsg/src/lib/asn1/asn1_obj.h --- botan3-3.7.1+dfsg/src/lib/asn1/asn1_obj.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/asn1/asn1_obj.h 2026-05-07 01:38:28.000000000 +0000 @@ -8,25 +8,24 @@ #define BOTAN_ASN1_OBJECT_TYPES_H_ #include -#include -#include #include #include #include #include #include -#include #include namespace Botan { class BER_Decoder; class DER_Encoder; +class ASN1_Time; // in asn1_time.h +typedef ASN1_Time X509_Time; /** * ASN.1 Class Tags */ -enum class ASN1_Class : uint32_t { +enum class ASN1_Class : uint32_t /* NOLINT(performance-enum-size) */ { Universal = 0b0000'0000, Application = 0b0100'0000, ContextSpecific = 0b1000'0000, @@ -41,7 +40,7 @@ /** * ASN.1 Type Tags */ -enum class ASN1_Type : uint32_t { +enum class ASN1_Type : uint32_t /* NOLINT(performance-enum-size) */ { Eoc = 0x00, Boolean = 0x01, Integer = 0x02, @@ -69,7 +68,7 @@ }; inline bool intersects(ASN1_Class x, ASN1_Class y) { - return static_cast(x) & static_cast(y); + return (static_cast(x) & static_cast(y)) != 0; } inline ASN1_Type operator|(ASN1_Type x, ASN1_Type y) { @@ -118,6 +117,8 @@ ASN1_Object() = default; ASN1_Object(const ASN1_Object&) = default; ASN1_Object& operator=(const ASN1_Object&) = default; + ASN1_Object(ASN1_Object&&) = default; + ASN1_Object& operator=(ASN1_Object&&) = default; virtual ~ASN1_Object() = default; }; @@ -126,15 +127,13 @@ */ class BOTAN_PUBLIC_API(2, 0) BER_Object final { public: - BER_Object() : m_type_tag(ASN1_Type::NoObject), m_class_tag(ASN1_Class::Universal) {} + BER_Object() = default; BER_Object(const BER_Object& other) = default; - - BER_Object& operator=(const BER_Object& other) = default; - BER_Object(BER_Object&& other) = default; - + BER_Object& operator=(const BER_Object& other) = default; BER_Object& operator=(BER_Object&& other) = default; + ~BER_Object(); bool is_set() const { return m_type_tag != ASN1_Type::NoObject; } @@ -161,9 +160,9 @@ bool is_a(int type_tag, ASN1_Class class_tag) const; private: - ASN1_Type m_type_tag; - ASN1_Class m_class_tag; - secure_vector m_value; + ASN1_Type m_type_tag = ASN1_Type::NoObject; + ASN1_Class m_class_tag = ASN1_Class::Universal; + std::vector m_value; friend class BER_Decoder; @@ -199,7 +198,7 @@ */ class BOTAN_PUBLIC_API(2, 0) BER_Decoding_Error : public Decoding_Error { public: - explicit BER_Decoding_Error(std::string_view); + explicit BER_Decoding_Error(std::string_view err); }; /** @@ -216,7 +215,7 @@ class BOTAN_PUBLIC_API(2, 0) OID final : public ASN1_Object { public: /** - * Create an uninitialied OID object + * Create an uninitialised OID object */ explicit OID() = default; @@ -231,12 +230,12 @@ /** * Initialize an OID from a sequence of integer values */ - explicit OID(std::initializer_list init); + OID(std::initializer_list init); /** * Initialize an OID from a vector of integer values */ - BOTAN_DEPRECATED("Use another contructor") explicit OID(std::vector&& init); + explicit OID(std::vector&& init); /** * Construct an OID from a string. @@ -256,8 +255,8 @@ */ static void register_oid(const OID& oid, std::string_view name); - void encode_into(DER_Encoder&) const override; - void decode_from(BER_Decoder&) override; + void encode_into(DER_Encoder& to) const override; + void decode_from(BER_Decoder& from) override; /** * Find out whether this OID is empty @@ -304,10 +303,15 @@ /** * Return a hash code for this OID * - * This value is only meant as a std::unsorted_map hash and + * This value is only meant as a std::unordered_map hash and * can change value from release to release. */ - size_t hash_code() const; + uint64_t hash_code() const; + + /** + * Check if this OID matches the provided value + */ + bool matches(std::initializer_list other) const; /** * Get this OID as list (vector) of its components. @@ -327,10 +331,7 @@ std::vector m_id; }; -inline std::ostream& operator<<(std::ostream& out, const OID& oid) { - out << oid.to_string(); - return out; -} +BOTAN_PUBLIC_API(3, 0) std::ostream& operator<<(std::ostream& out, const OID& oid); /** * Compare two OIDs. @@ -351,79 +352,13 @@ BOTAN_PUBLIC_API(2, 0) bool operator<(const OID& a, const OID& b); /** -* Time (GeneralizedTime/UniversalTime) -*/ -class BOTAN_PUBLIC_API(2, 0) ASN1_Time final : public ASN1_Object { - public: - /// DER encode a ASN1_Time - void encode_into(DER_Encoder&) const override; - - // Decode a BER encoded ASN1_Time - void decode_from(BER_Decoder&) override; - - /// Return an internal string representation of the time - std::string to_string() const; - - /// Returns a human friendly string replesentation of no particular formatting - std::string readable_string() const; - - /// Return if the time has been set somehow - bool time_is_set() const; - - /// Compare this time against another - int32_t cmp(const ASN1_Time& other) const; - - /// Create an invalid ASN1_Time - ASN1_Time() = default; - - /// Create a ASN1_Time from a time point - explicit ASN1_Time(const std::chrono::system_clock::time_point& time); - - /// Create an ASN1_Time from string - ASN1_Time(std::string_view t_spec); - - /// Create an ASN1_Time from string and a specified tagging (Utc or Generalized) - ASN1_Time(std::string_view t_spec, ASN1_Type tag); - - /// Returns a STL timepoint object - std::chrono::system_clock::time_point to_std_timepoint() const; - - /// Return time since epoch - uint64_t time_since_epoch() const; - - private: - void set_to(std::string_view t_spec, ASN1_Type type); - bool passes_sanity_check() const; - - uint32_t m_year = 0; - uint32_t m_month = 0; - uint32_t m_day = 0; - uint32_t m_hour = 0; - uint32_t m_minute = 0; - uint32_t m_second = 0; - ASN1_Type m_tag = ASN1_Type::NoObject; -}; - -/* -* Comparison Operations -*/ -BOTAN_PUBLIC_API(2, 0) bool operator==(const ASN1_Time&, const ASN1_Time&); -BOTAN_PUBLIC_API(2, 0) bool operator!=(const ASN1_Time&, const ASN1_Time&); -BOTAN_PUBLIC_API(2, 0) bool operator<=(const ASN1_Time&, const ASN1_Time&); -BOTAN_PUBLIC_API(2, 0) bool operator>=(const ASN1_Time&, const ASN1_Time&); -BOTAN_PUBLIC_API(2, 0) bool operator<(const ASN1_Time&, const ASN1_Time&); -BOTAN_PUBLIC_API(2, 0) bool operator>(const ASN1_Time&, const ASN1_Time&); - -typedef ASN1_Time X509_Time; - -/** * ASN.1 string type * This class normalizes all inputs to a UTF-8 std::string */ class BOTAN_PUBLIC_API(2, 0) ASN1_String final : public ASN1_Object { public: - void encode_into(DER_Encoder&) const override; - void decode_from(BER_Decoder&) override; + void encode_into(DER_Encoder& to) const override; + void decode_from(BER_Decoder& from) override; ASN1_Type tagging() const { return m_tag; } @@ -456,10 +391,10 @@ */ class BOTAN_PUBLIC_API(2, 0) AlgorithmIdentifier final : public ASN1_Object { public: - enum Encoding_Option { USE_NULL_PARAM, USE_EMPTY_PARAM }; + enum Encoding_Option : uint8_t { USE_NULL_PARAM, USE_EMPTY_PARAM }; /* NOLINT(*-use-enum-class) */ - void encode_into(DER_Encoder&) const override; - void decode_from(BER_Decoder&) override; + void encode_into(DER_Encoder& to) const override; + void decode_from(BER_Decoder& from) override; AlgorithmIdentifier() = default; @@ -495,15 +430,26 @@ /* * Comparison Operations */ -BOTAN_PUBLIC_API(2, 0) bool operator==(const AlgorithmIdentifier&, const AlgorithmIdentifier&); -BOTAN_PUBLIC_API(2, 0) bool operator!=(const AlgorithmIdentifier&, const AlgorithmIdentifier&); +BOTAN_PUBLIC_API(2, 0) bool operator==(const AlgorithmIdentifier& x, const AlgorithmIdentifier& y); +BOTAN_PUBLIC_API(2, 0) bool operator!=(const AlgorithmIdentifier& x, const AlgorithmIdentifier& y); } // namespace Botan template <> class std::hash { public: - size_t operator()(const Botan::OID& oid) const noexcept { return oid.hash_code(); } + size_t operator()(const Botan::OID& oid) const noexcept { return static_cast(oid.hash_code()); } }; +/* +In 3.11 ASN1_Time was split out to its own header as is huge in C++20 +However we continue to include this header (when not building the library), +to avoid breaking applications which would expect it to still be available. + +TODO(Botan4) remove this +*/ +#if defined(BOTAN_AMALGAMATION_H_) || (!defined(BOTAN_IS_BEING_BUILT) && !defined(__clang_analyzer__)) + #include +#endif + #endif diff -Nru botan3-3.7.1+dfsg/src/lib/asn1/asn1_oid.cpp botan3-3.12.0+dfsg/src/lib/asn1/asn1_oid.cpp --- botan3-3.7.1+dfsg/src/lib/asn1/asn1_oid.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/asn1/asn1_oid.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -10,11 +10,11 @@ #include #include #include +#include #include #include #include #include -#include #include #include #include @@ -38,7 +38,7 @@ std::string elem; std::vector oid_elems; - for(char c : oid) { + for(const char c : oid) { if(c == '.') { if(elem.empty()) { return std::vector(); @@ -152,13 +152,19 @@ return !human_name_or_empty().empty(); } -size_t OID::hash_code() const { - constexpr uint64_t mod = 0xffffffffffffffc5; - uint64_t hash = 0; +bool OID::matches(std::initializer_list other) const { + // TODO: once all target compilers support it, use std::ranges::equal + return std::equal(m_id.begin(), m_id.end(), other.begin(), other.end()); +} + +uint64_t OID::hash_code() const { + // If this is changed also update gen_oids.py to match + uint64_t hash = 0x621F302327D9A49A; for(auto id : m_id) { - hash = (hash * 257 + id) % mod; + hash *= 193; + hash += id; } - return static_cast(hash); + return hash; } /* @@ -183,7 +189,7 @@ if(z <= 0x7F) { encoding.push_back(static_cast(z)); } else { - size_t z7 = (high_bit(z) + 7 - 1) / 7; + const size_t z7 = (high_bit(z) + 7 - 1) / 7; for(size_t j = 0; j != z7; ++j) { uint8_t zp = static_cast(z >> (7 * (z7 - j - 1)) & 0x7F); @@ -200,9 +206,10 @@ std::vector encoding; // We know 40 * root can't overflow because root is between 0 and 2 - auto first = BOTAN_ASSERT_IS_SOME(checked_add(40 * m_id[0], m_id[1])); + auto first = checked_add(40 * m_id[0], m_id[1]); + BOTAN_ASSERT_NOMSG(first.has_value()); - append(encoding, first); + append(encoding, *first); for(size_t i = 2; i != m_id.size(); ++i) { append(encoding, m_id[i]); @@ -214,7 +221,7 @@ * Decode a BER encoded OBJECT IDENTIFIER */ void OID::decode_from(BER_Decoder& decoder) { - BER_Object obj = decoder.get_next_object(); + const BER_Object obj = decoder.get_next_object(); if(obj.tagging() != (ASN1_Class::Universal | ASN1_Type::ObjectId)) { throw BER_Bad_Tag("Error decoding OID, unknown tag", obj.tagging()); } @@ -243,7 +250,7 @@ } const uint8_t next = data.take_byte(); - const bool more = (next & 0x80); + const bool more = (next & 0x80) == 0x80; const uint8_t value = next & 0x7F; if((b >> (32 - 7)) != 0) { @@ -290,4 +297,9 @@ m_id = parts; } +std::ostream& operator<<(std::ostream& out, const OID& oid) { + out << oid.to_string(); + return out; +} + } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/asn1/asn1_print.cpp botan3-3.12.0+dfsg/src/lib/asn1/asn1_print.cpp --- botan3-3.7.1+dfsg/src/lib/asn1/asn1_print.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/asn1/asn1_print.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,12 +6,13 @@ #include +#include #include #include #include #include #include -#include +#include #include #include @@ -19,14 +20,30 @@ namespace { +// Printable here means fits into an ASN.1 "PRINTABLE STRING" type +bool is_printable_char(char c) { + if(c >= 'a' && c <= 'z') { + return true; + } + + if(c >= 'A' && c <= 'Z') { + return true; + } + + if(c >= '0' && c <= '9') { + return true; + } + + if(c == '.' || c == ':' || c == '/' || c == '-') { + return true; + } + + return false; +} + bool all_printable_chars(const uint8_t bits[], size_t bits_len) { for(size_t i = 0; i != bits_len; ++i) { - int c = bits[i]; - if(c > 127) { - return false; - } - - if((std::isalnum(c) || c == '.' || c == ':' || c == '/' || c == '-') == false) { + if(!is_printable_char(bits[i])) { return false; } } @@ -49,7 +66,7 @@ return false; } - if(all_printable_chars(bits + 2, bits_len - 2) == false) { + if(!all_printable_chars(bits + 2, bits_len - 2)) { return false; } @@ -65,7 +82,8 @@ } void ASN1_Formatter::print_to_stream(std::ostream& output, const uint8_t in[], size_t len) const { - BER_Decoder dec(in, len); + const auto decoder_limits = m_require_der ? BER_Decoder::Limits::DER() : BER_Decoder::Limits::BER(); + BER_Decoder dec(std::span{in, len}, decoder_limits); decode(output, dec, 0); } @@ -84,10 +102,10 @@ std::vector bits; DER_Encoder(bits).add_object(type_tag, class_tag, obj.bits(), obj.length()); - BER_Decoder data(bits); + BER_Decoder data(bits, decoder.limits()); if(intersects(class_tag, ASN1_Class::Constructed)) { - BER_Decoder cons_info(obj.bits(), obj.length()); + BER_Decoder cons_info(obj, decoder.limits()); if(recurse_deeper) { output << format(type_tag, class_tag, level, length, ""); @@ -101,14 +119,13 @@ if(m_print_context_specific) { try { if(possibly_a_general_name(bits.data(), bits.size())) { - output << format( - type_tag, class_tag, level, level, std::string(cast_uint8_ptr_to_char(&bits[2]), bits.size() - 2)); + output << format(type_tag, class_tag, level, level, bytes_to_string(std::span{bits}.subspan(2))); success_parsing_cs = true; } else if(recurse_deeper) { std::vector inner_bits; data.decode(inner_bits, type_tag); - BER_Decoder inner(inner_bits); + BER_Decoder inner(inner_bits, decoder.limits()); std::ostringstream inner_data; decode(inner_data, inner, level + 1); // recurse output << inner_data.str(); @@ -117,7 +134,7 @@ } catch(...) {} } - if(success_parsing_cs == false) { + if(!success_parsing_cs) { output << format(type_tag, class_tag, level, length, format_bin(type_tag, class_tag, bits)); } } else if(type_tag == ASN1_Type::ObjectId) { @@ -143,7 +160,7 @@ output << format(type_tag, class_tag, level, length, format_bn(number)); } else if(type_tag == ASN1_Type::Boolean) { - bool boolean; + bool boolean = false; data.decode(boolean); output << format(type_tag, class_tag, level, length, (boolean ? "true" : "false")); } else if(type_tag == ASN1_Type::Null) { @@ -155,7 +172,7 @@ if(recurse_deeper) { try { - BER_Decoder inner(decoded_bits); + BER_Decoder inner(decoded_bits, decoder.limits()); std::ostringstream inner_data; decode(inner_data, inner, level + 1); // recurse @@ -253,7 +270,7 @@ ASN1_Class /*class_tag*/, const std::vector& vec) const { if(all_printable_chars(vec.data(), vec.size())) { - return std::string(cast_uint8_ptr_to_char(vec.data()), vec.size()); + return bytes_to_string(vec); } else { return hex_encode(vec); } diff -Nru botan3-3.7.1+dfsg/src/lib/asn1/asn1_print.h botan3-3.12.0+dfsg/src/lib/asn1/asn1_print.h --- botan3-3.7.1+dfsg/src/lib/asn1/asn1_print.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/asn1/asn1_print.h 2026-05-07 01:38:28.000000000 +0000 @@ -20,7 +20,7 @@ /** * Format ASN.1 data and call a virtual to format */ -class BOTAN_PUBLIC_API(2, 4) ASN1_Formatter { +class BOTAN_PUBLIC_API(2, 4) ASN1_Formatter /* NOLINT(*-special-member-functions) */ { public: virtual ~ASN1_Formatter() = default; @@ -28,9 +28,10 @@ * @param print_context_specific if true, try to parse nested context specific data. * @param max_depth do not recurse more than this many times. If zero, recursion * is unbounded. + * @param require_der if true then non-canonical BER data is rejected */ - ASN1_Formatter(bool print_context_specific, size_t max_depth) : - m_print_context_specific(print_context_specific), m_max_depth(max_depth) {} + ASN1_Formatter(bool print_context_specific, size_t max_depth, bool require_der = false) : + m_print_context_specific(print_context_specific), m_max_depth(max_depth), m_require_der(require_der) {} void print_to_stream(std::ostream& out, const uint8_t in[], size_t len) const; @@ -52,6 +53,8 @@ * This is called to format binary elements that we don't know how to * convert to a string. The result will be passed as value to format; the * tags are included as a hint to aid decoding. + * + * TODO(Botan4) change the vector to a span */ virtual std::string format_bin(ASN1_Type type_tag, ASN1_Class class_tag, @@ -67,6 +70,7 @@ const bool m_print_context_specific; const size_t m_max_depth; + const bool m_require_der; }; /** @@ -83,14 +87,16 @@ * @param value_column ASN.1 values are lined up at this column in output * @param max_depth do not recurse more than this many times. If zero, recursion * is unbounded. + * @param require_der if true then non-canonical BER data is rejected */ - ASN1_Pretty_Printer(size_t print_limit = 4096, - size_t print_binary_limit = 2048, - bool print_context_specific = true, - size_t initial_level = 0, - size_t value_column = 60, - size_t max_depth = 64) : - ASN1_Formatter(print_context_specific, max_depth), + explicit ASN1_Pretty_Printer(size_t print_limit = 4096, + size_t print_binary_limit = 2048, + bool print_context_specific = true, + size_t initial_level = 0, + size_t value_column = 60, + size_t max_depth = 64, + bool require_der = false) : + ASN1_Formatter(print_context_specific, max_depth, require_der), m_print_limit(print_limit), m_print_binary_limit(print_binary_limit), m_initial_level(initial_level), diff -Nru botan3-3.7.1+dfsg/src/lib/asn1/asn1_str.cpp botan3-3.12.0+dfsg/src/lib/asn1/asn1_str.cpp --- botan3-3.7.1+dfsg/src/lib/asn1/asn1_str.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/asn1/asn1_str.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,42 +7,89 @@ #include +#include #include #include #include -#include #include +#include namespace Botan { namespace { -/* -* Choose an encoding for the string -*/ -ASN1_Type choose_encoding(std::string_view str) { - auto all_printable = CT::Mask::set(); - - for(size_t i = 0; i != str.size(); ++i) { - const uint8_t c = static_cast(str[i]); +class ASN1_String_Codepoint_Validator final { + public: + constexpr ASN1_String_Codepoint_Validator() : m_table(make_table()) {} + + constexpr bool valid_encoding(std::string_view str, ASN1_Type tag) const { + const uint8_t mask = mask_for(tag); + for(const char c : str) { + const uint8_t codepoint = static_cast(c); + const bool is_valid = (m_table[codepoint] & mask) != 0; + + if(!is_valid) { + return false; + } + } + + return true; + } + + private: + static constexpr uint8_t Numeric_String = 0x01; + static constexpr uint8_t Printable_String = 0x02; + static constexpr uint8_t IA5_String = 0x04; + static constexpr uint8_t Visible_String = 0x08; + + static constexpr uint8_t mask_for(ASN1_Type tag) { + switch(tag) { + case ASN1_Type::NumericString: + return Numeric_String; + case ASN1_Type::PrintableString: + return Printable_String; + case ASN1_Type::Ia5String: + return IA5_String; + case ASN1_Type::VisibleString: + return Visible_String; + default: + return 0; + } + } + + static constexpr std::array make_table() { + std::array table = {}; + + for(size_t i = 0; i != table.size(); ++i) { + const auto c = static_cast(i); + + // Don't allow embedded null in IA5 even if technically valid + if(c >= 1 && c <= 0x7F) { + table[i] |= IA5_String; + } + + if(c >= 0x20 && c <= 0x7E) { + table[i] |= Visible_String; + } + + if(c == ' ' || (c >= '0' && c <= '9')) { + table[i] |= Numeric_String; + } + + if((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == ' ' || c == '\'' || + c == '(' || c == ')' || c == '+' || c == ',' || c == '-' || c == '.' || c == '/' || c == ':' || + c == '=' || c == '?') { + table[i] |= Printable_String; + } + } - auto is_alpha_lower = CT::Mask::is_within_range(c, 'a', 'z'); - auto is_alpha_upper = CT::Mask::is_within_range(c, 'A', 'Z'); - auto is_decimal = CT::Mask::is_within_range(c, '0', '9'); + return table; + } - auto is_print_punc = CT::Mask::is_any_of(c, {' ', '(', ')', '+', ',', '-', '.', '/', ':', '=', '?'}); + std::array m_table; +}; - auto is_printable = is_alpha_lower | is_alpha_upper | is_decimal | is_print_punc; - - all_printable &= is_printable; - } - - if(all_printable.as_bool()) { - return ASN1_Type::PrintableString; - } else { - return ASN1_Type::Utf8String; - } -} +constexpr ASN1_String_Codepoint_Validator g_char_validator; bool is_utf8_subset_string_type(ASN1_Type tag) { return (tag == ASN1_Type::NumericString || tag == ASN1_Type::PrintableString || tag == ASN1_Type::VisibleString || @@ -54,6 +101,30 @@ tag == ASN1_Type::UniversalString); } +bool is_valid_asn1_string_content(const std::string& str, ASN1_Type tag) { + BOTAN_ASSERT_NOMSG(is_utf8_subset_string_type(tag)); + + switch(tag) { + case ASN1_Type::Utf8String: + return is_valid_utf8(str); + case ASN1_Type::NumericString: + case ASN1_Type::PrintableString: + case ASN1_Type::Ia5String: + case ASN1_Type::VisibleString: + return g_char_validator.valid_encoding(str, tag); + default: + return false; + } +} + +ASN1_Type choose_encoding(std::string_view str) { + if(g_char_validator.valid_encoding(str, ASN1_Type::PrintableString)) { + return ASN1_Type::PrintableString; + } else { + return ASN1_Type::Utf8String; + } +} + } // namespace //static @@ -65,6 +136,10 @@ if(!is_utf8_subset_string_type(m_tag)) { throw Invalid_Argument("ASN1_String only supports encoding to UTF-8 or a UTF-8 subset"); } + + if(!is_valid_asn1_string_content(m_utf8_str, m_tag)) { + throw Invalid_Argument(fmt("ASN1_String: Invalid {} encoding", asn1_tag_to_string(m_tag))); + } } ASN1_String::ASN1_String(std::string_view str) : ASN1_String(str, choose_encoding(str)) {} @@ -86,11 +161,12 @@ * Decode a BER encoded ASN1_String */ void ASN1_String::decode_from(BER_Decoder& source) { - BER_Object obj = source.get_next_object(); + const BER_Object obj = source.get_next_object(); - if(!is_asn1_string_type(obj.type())) { + if(obj.get_class() != ASN1_Class::Universal || !is_asn1_string_type(obj.type())) { auto typ = static_cast(obj.type()); - throw Decoding_Error(fmt("ASN1_String: Unknown string type {}", typ)); + auto cls = static_cast(obj.get_class()); + throw Decoding_Error(fmt("ASN1_String: Unknown string type {}/{}", typ, cls)); } m_tag = obj.type(); @@ -109,6 +185,10 @@ } else { // All other supported string types are UTF-8 or some subset thereof m_utf8_str = ASN1::to_string(obj); + + if(!is_valid_asn1_string_content(m_utf8_str, m_tag)) { + throw Decoding_Error(fmt("ASN1_String: Invalid {} encoding", asn1_tag_to_string(m_tag))); + } } } diff -Nru botan3-3.7.1+dfsg/src/lib/asn1/asn1_time.cpp botan3-3.12.0+dfsg/src/lib/asn1/asn1_time.cpp --- botan3-3.7.1+dfsg/src/lib/asn1/asn1_time.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/asn1/asn1_time.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -5,8 +5,9 @@ * Botan is released under the Simplified BSD License (see license.txt) */ -#include +#include +#include #include #include #include @@ -17,8 +18,12 @@ namespace Botan { +ASN1_Time ASN1_Time::from_seconds_since_epoch(uint64_t time_since_epoch) { + return ASN1_Time(std::chrono::system_clock::time_point(std::chrono::seconds(time_since_epoch))); +} + ASN1_Time::ASN1_Time(const std::chrono::system_clock::time_point& time) { - calendar_point cal(time); + const calendar_point cal(time); m_year = cal.year(); m_month = cal.month(); @@ -27,6 +32,7 @@ m_minute = cal.minutes(); m_second = cal.seconds(); + // NOLINTNEXTLINE(*-prefer-member-initializer) m_tag = (m_year >= 2050) ? ASN1_Type::GeneralizedTime : ASN1_Type::UtcTime; } @@ -51,13 +57,24 @@ } void ASN1_Time::decode_from(BER_Decoder& source) { - BER_Object ber_time = source.get_next_object(); + const BER_Object ber_time = source.get_next_object(); - set_to(ASN1::to_string(ber_time), ber_time.type()); + if(ber_time.get_class() != ASN1_Class::Universal || + (ber_time.type() != ASN1_Type::UtcTime && ber_time.type() != ASN1_Type::GeneralizedTime)) { + throw Decoding_Error(fmt("ASN1_Time: Unexpected tag {}/{}", + static_cast(ber_time.type()), + static_cast(ber_time.get_class()))); + } + + try { + set_to(ASN1::to_string(ber_time), ber_time.type()); + } catch(Invalid_Argument& e) { + throw Decoding_Error(fmt("Invalid ASN1_Time encoding: {}", e.what())); + } } std::string ASN1_Time::to_string() const { - if(time_is_set() == false) { + if(!time_is_set()) { throw Invalid_State("ASN1_Time::to_string: No time set"); } @@ -80,7 +97,7 @@ const uint64_t int_repr = year_factor * full_year + mon_factor * m_month + day_factor * m_day + hour_factor * m_hour + min_factor * m_minute + m_second; - std::string repr = std::to_string(int_repr) + "Z"; + const std::string repr = std::to_string(int_repr) + "Z"; const size_t desired_size = (m_tag == ASN1_Type::UtcTime) ? 13 : 15; @@ -90,7 +107,7 @@ } std::string ASN1_Time::readable_string() const { - if(time_is_set() == false) { + if(!time_is_set()) { throw Invalid_State("ASN1_Time::readable_string: No time set"); } @@ -112,7 +129,9 @@ throw Invalid_State("ASN1_Time::cmp: Cannot compare empty times"); } - const int32_t EARLIER = -1, LATER = 1, SAME_TIME = 0; + constexpr int32_t EARLIER = -1; + constexpr int32_t LATER = 1; + constexpr int32_t SAME_TIME = 0; if(m_year < other.m_year) { return EARLIER; diff -Nru botan3-3.7.1+dfsg/src/lib/asn1/asn1_time.h botan3-3.12.0+dfsg/src/lib/asn1/asn1_time.h --- botan3-3.7.1+dfsg/src/lib/asn1/asn1_time.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/asn1/asn1_time.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,84 @@ +/* +* (C) 1999-2007,2018,2020,2026 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_ASN1_TIME_TYPE_H_ +#define BOTAN_ASN1_TIME_TYPE_H_ + +#include +#include + +namespace Botan { + +/** +* Time (GeneralizedTime/UniversalTime) +*/ +class BOTAN_PUBLIC_API(2, 0) ASN1_Time final : public ASN1_Object { + public: + /// DER encode a ASN1_Time + void encode_into(DER_Encoder& to) const override; + + // Decode a BER encoded ASN1_Time + void decode_from(BER_Decoder& from) override; + + /// Return an internal string representation of the time + std::string to_string() const; + + /// Returns a human friendly string representation of no particular formatting + std::string readable_string() const; + + /// Return if the time has been set somehow + bool time_is_set() const; + + /// Compare this time against another + int32_t cmp(const ASN1_Time& other) const; + + /// Create an invalid ASN1_Time + ASN1_Time() = default; + + /// Create a ASN1_Time from a time point + explicit ASN1_Time(const std::chrono::system_clock::time_point& time); + + /// Create an ASN1_Time from seconds since epoch + static ASN1_Time from_seconds_since_epoch(uint64_t seconds); + + /// Create an ASN1_Time from string + BOTAN_FUTURE_EXPLICIT ASN1_Time(std::string_view t_spec); + + /// Create an ASN1_Time from string and a specified tagging (Utc or Generalized) + ASN1_Time(std::string_view t_spec, ASN1_Type tag); + + /// Returns a STL timepoint object + std::chrono::system_clock::time_point to_std_timepoint() const; + + /// Return time since epoch + uint64_t time_since_epoch() const; + + private: + void set_to(std::string_view t_spec, ASN1_Type type); + bool passes_sanity_check() const; + + uint32_t m_year = 0; + uint32_t m_month = 0; + uint32_t m_day = 0; + uint32_t m_hour = 0; + uint32_t m_minute = 0; + uint32_t m_second = 0; + ASN1_Type m_tag = ASN1_Type::NoObject; +}; + +/* +* Comparison Operations +*/ +BOTAN_PUBLIC_API(2, 0) bool operator==(const ASN1_Time& x, const ASN1_Time& y); +BOTAN_PUBLIC_API(2, 0) bool operator!=(const ASN1_Time& x, const ASN1_Time& y); +BOTAN_PUBLIC_API(2, 0) bool operator<=(const ASN1_Time& x, const ASN1_Time& y); +BOTAN_PUBLIC_API(2, 0) bool operator>=(const ASN1_Time& x, const ASN1_Time& y); +BOTAN_PUBLIC_API(2, 0) bool operator<(const ASN1_Time& x, const ASN1_Time& y); +BOTAN_PUBLIC_API(2, 0) bool operator>(const ASN1_Time& x, const ASN1_Time& y); + +} // namespace Botan + +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/asn1/ber_dec.cpp botan3-3.12.0+dfsg/src/lib/asn1/ber_dec.cpp --- botan3-3.7.1+dfsg/src/lib/asn1/ber_dec.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/asn1/ber_dec.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ /* * BER Decoder -* (C) 1999-2008,2015,2017,2018 Jack Lloyd +* (C) 1999-2008,2015,2017,2018,2026 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -16,142 +17,309 @@ namespace { -/* -* This value is somewhat arbitrary. OpenSSL allows up to 128 nested -* indefinite length sequences. If you increase this, also increase the -* limit in the test in test_asn1.cpp -*/ -const size_t ALLOWED_EOC_NESTINGS = 16; +bool is_constructed(ASN1_Class class_tag) { + return (static_cast(class_tag) & static_cast(ASN1_Class::Constructed)) != 0; +} /* * BER decode an ASN.1 type tag */ size_t decode_tag(DataSource* ber, ASN1_Type& type_tag, ASN1_Class& class_tag) { - uint8_t b; - if(!ber->read_byte(b)) { + auto b = ber->read_byte(); + + if(!b) { type_tag = ASN1_Type::NoObject; class_tag = ASN1_Class::NoObject; return 0; } - if((b & 0x1F) != 0x1F) { - type_tag = ASN1_Type(b & 0x1F); - class_tag = ASN1_Class(b & 0xE0); + if((*b & 0x1F) != 0x1F) { + type_tag = ASN1_Type(*b & 0x1F); + class_tag = ASN1_Class(*b & 0xE0); return 1; } size_t tag_bytes = 1; - class_tag = ASN1_Class(b & 0xE0); + class_tag = ASN1_Class(*b & 0xE0); - size_t tag_buf = 0; + uint32_t tag_buf = 0; while(true) { - if(!ber->read_byte(b)) { + b = ber->read_byte(); + if(!b) { throw BER_Decoding_Error("Long-form tag truncated"); } - if(tag_buf & 0xFF000000) { + if((tag_buf >> 24) != 0) { throw BER_Decoding_Error("Long-form tag overflowed 32 bits"); } - // This is required even by BER (see X.690 section 8.1.2.4.2 sentence c) - if(tag_bytes == 0 && b == 0x80) { + // This is required even by BER (see X.690 section 8.1.2.4.2 sentence c). + // Bits 7-1 of the first subsequent octet must not be all zero; this rules + // out both 0x80 (continuation with no data) and 0x00 (a long-form encoding + // of tag value 0, which collides with the EOC marker). + if(tag_bytes == 1 && (*b & 0x7F) == 0) { throw BER_Decoding_Error("Long form tag with leading zero"); } ++tag_bytes; - tag_buf = (tag_buf << 7) | (b & 0x7F); - if((b & 0x80) == 0) { + tag_buf = (tag_buf << 7) | (*b & 0x7F); + if((*b & 0x80) == 0) { break; } } + // Per X.690 8.1.2.2, tag values 0-30 shall be encoded in the short form. + // Long-form encoding is reserved for tag values >= 31 (X.690 8.1.2.3). + // This is unconditional and applies to BER as well as DER. + if(tag_buf <= 30) { + throw BER_Decoding_Error("Long-form tag encoding used for small tag value"); + } + + if(tag_buf == static_cast(ASN1_Type::NoObject)) { + throw BER_Decoding_Error("Tag value collides with internal sentinel"); + } + + // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) type_tag = ASN1_Type(tag_buf); return tag_bytes; } /* -* Find the EOC marker +* Find the EOC marker by scanning TLVs via peek, without buffering. +* Returns the number of bytes before and including the EOC marker. */ -size_t find_eoc(DataSource* src, size_t allow_indef); +size_t find_eoc(DataSource* src, size_t base_offset, size_t allow_indef); + +/* +* Result of decoding a BER length field. +* +* If indefinite is true, indefinite-length encoding was used: content_length +* is the number of content bytes (excluding the 2-byte EOC marker) and the +* caller must consume the EOC bytes after reading the content. +*/ +class BerDecodedLength final { + public: + BerDecodedLength(size_t content_length, size_t field_length) : + BerDecodedLength(content_length, field_length, false) {} + + static BerDecodedLength indefinite(size_t content_length, size_t field_length) { + return BerDecodedLength(content_length, field_length, true); + } + + size_t content_length() const { return m_content_length; } + + // Length plus the EOC bytes if an indefinite length field + size_t total_length() const { return m_indefinite ? m_content_length + 2 : m_content_length; } + + size_t field_length() const { return m_field_length; } + + bool indefinite_length() const { return m_indefinite; } + + private: + BerDecodedLength(size_t content_length, size_t field_length, bool indefinite) : + m_content_length(content_length), m_field_length(field_length), m_indefinite(indefinite) {} + + size_t m_content_length; + size_t m_field_length; + bool m_indefinite; +}; /* * BER decode an ASN.1 length field */ -size_t decode_length(DataSource* ber, size_t& field_size, size_t allow_indef) { - uint8_t b; - if(!ber->read_byte(b)) { +BerDecodedLength decode_length(DataSource* ber, size_t allow_indef, bool der_mode, bool constructed) { + uint8_t b = 0; + if(ber->read_byte(b) == 0) { throw BER_Decoding_Error("Length field not found"); } - field_size = 1; if((b & 0x80) == 0) { - return b; + return BerDecodedLength(b, 1); } - field_size += (b & 0x7F); - if(field_size > 5) { + const size_t num_length_bytes = (b & 0x7F); + if(num_length_bytes > 4) { throw BER_Decoding_Error("Length field is too large"); } - if(field_size == 1) { - if(allow_indef == 0) { + const size_t field_size = 1 + num_length_bytes; + + if(num_length_bytes == 0) { + if(der_mode) { + throw BER_Decoding_Error("Detected indefinite-length encoding in DER structure"); + } else if(!constructed) { + // Indefinite length is only valid for constructed types (X.690 8.1.3.2) + throw BER_Decoding_Error("Indefinite-length encoding used with non-constructed type"); + } else if(allow_indef == 0) { throw BER_Decoding_Error("Nested EOC markers too deep, rejecting to avoid stack exhaustion"); } else { - return find_eoc(ber, allow_indef - 1); + // find_eoc returns bytes up to and including the EOC marker. + // Return the content length; the caller consumes the EOC separately. + const size_t eoc_len = find_eoc(ber, /*base_offset=*/0, allow_indef - 1); + if(eoc_len < 2) { + throw BER_Decoding_Error("Invalid EOC encoding"); + } + return BerDecodedLength::indefinite(eoc_len - 2, field_size); } } size_t length = 0; - for(size_t i = 0; i != field_size - 1; ++i) { - if(get_byte<0>(length) != 0) { - throw BER_Decoding_Error("Field length overflow"); - } - if(!ber->read_byte(b)) { + for(size_t i = 0; i != num_length_bytes; ++i) { + if(ber->read_byte(b) == 0) { throw BER_Decoding_Error("Corrupted length field"); } + // Can't overflow since we already checked that num_length_bytes <= 4 length = (length << 8) | b; } - return length; + + // DER requires shortest possible length encoding + if(der_mode) { + if(length < 128) { + throw BER_Decoding_Error("Detected non-canonical length encoding in DER structure"); + } + if(num_length_bytes > 1 && length < (size_t(1) << ((num_length_bytes - 1) * 8))) { + throw BER_Decoding_Error("Detected non-canonical length encoding in DER structure"); + } + } + + return BerDecodedLength(length, field_size); } /* -* Find the EOC marker +* Peek a tag from the source at the given offset without consuming any data. +* Returns the number of bytes consumed by the tag, or 0 on EOF. */ -size_t find_eoc(DataSource* ber, size_t allow_indef) { - secure_vector buffer(BOTAN_DEFAULT_BUFFER_SIZE), data; +size_t peek_tag(DataSource* src, size_t offset, ASN1_Type& type_tag, ASN1_Class& class_tag) { + uint8_t b = 0; + if(src->peek(&b, 1, offset) == 0) { + type_tag = ASN1_Type::NoObject; + class_tag = ASN1_Class::NoObject; + return 0; + } + + if((b & 0x1F) != 0x1F) { + type_tag = ASN1_Type(b & 0x1F); + class_tag = ASN1_Class(b & 0xE0); + return 1; + } + + class_tag = ASN1_Class(b & 0xE0); + size_t tag_bytes = 1; + uint32_t tag_buf = 0; while(true) { - const size_t got = ber->peek(buffer.data(), buffer.size(), data.size()); - if(got == 0) { + if(src->peek(&b, 1, offset + tag_bytes) == 0) { + throw BER_Decoding_Error("Long-form tag truncated"); + } + if((tag_buf >> 24) != 0) { + throw BER_Decoding_Error("Long-form tag overflowed 32 bits"); + } + // Required even by BER (X.690 section 8.1.2.4.2 sentence c). + // Bits 7-1 of the first subsequent octet must not be all zero; this rules + // out both 0x80 (continuation with no data) and 0x00 (a long-form encoding + // of tag value 0, which collides with the EOC marker). + if(tag_bytes == 1 && (b & 0x7F) == 0) { + throw BER_Decoding_Error("Long form tag with leading zero"); + } + ++tag_bytes; + tag_buf = (tag_buf << 7) | (b & 0x7F); + if((b & 0x80) == 0) { break; } + } + + // Per X.690 8.1.2.2, tag values 0-30 shall be encoded in the short form. + // Long-form encoding is reserved for tag values >= 31 (X.690 8.1.2.3). + // This is unconditional and applies to BER as well as DER. + if(tag_buf <= 30) { + throw BER_Decoding_Error("Long-form tag encoding used for small tag value"); + } + + if(tag_buf == static_cast(ASN1_Type::NoObject)) { + throw BER_Decoding_Error("Tag value collides with internal sentinel"); + } + + // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) + type_tag = ASN1_Type(tag_buf); + return tag_bytes; +} + +/* +* Peek a length from the source at the given offset without consuming any data. +* Returns the decoded length and sets field_size to the number of bytes consumed. +* For indefinite-length encoding, recursively scans ahead to find the EOC marker. +*/ +size_t peek_length(DataSource* src, size_t offset, size_t& field_size, size_t allow_indef, bool constructed) { + uint8_t b = 0; + if(src->peek(&b, 1, offset) == 0) { + throw BER_Decoding_Error("Length field not found"); + } - data += std::make_pair(buffer.data(), got); + field_size = 1; + if((b & 0x80) == 0) { + return b; } - DataSource_Memory source(data); - data.clear(); + const size_t num_length_bytes = (b & 0x7F); + field_size += num_length_bytes; + if(field_size > 5) { + throw BER_Decoding_Error("Length field is too large"); + } + + if(num_length_bytes == 0) { + // Indefinite length is only valid for constructed types (X.690 8.1.3.2) + if(!constructed) { + throw BER_Decoding_Error("Indefinite-length encoding used with non-constructed type"); + } + if(allow_indef == 0) { + throw BER_Decoding_Error("Nested EOC markers too deep, rejecting to avoid stack exhaustion"); + } + return find_eoc(src, offset + 1, allow_indef - 1); + } size_t length = 0; + for(size_t i = 0; i < num_length_bytes; ++i) { + if(src->peek(&b, 1, offset + 1 + i) == 0) { + throw BER_Decoding_Error("Corrupted length field"); + } + if(get_byte<0>(length) != 0) { + throw BER_Decoding_Error("Field length overflow"); + } + length = (length << 8) | b; + } + return length; +} + +/* +* Find the EOC marker by scanning TLVs via peek, without buffering. +* Returns the number of bytes before and including the EOC marker. +*/ +size_t find_eoc(DataSource* src, size_t base_offset, size_t allow_indef) { + size_t offset = base_offset; + while(true) { - ASN1_Type type_tag; - ASN1_Class class_tag; - const size_t tag_size = decode_tag(&source, type_tag, class_tag); + ASN1_Type type_tag = ASN1_Type::NoObject; + ASN1_Class class_tag = ASN1_Class::NoObject; + const size_t tag_size = peek_tag(src, offset, type_tag, class_tag); if(type_tag == ASN1_Type::NoObject) { - break; + throw BER_Decoding_Error("Missing EOC marker in indefinite-length encoding"); } size_t length_size = 0; - const size_t item_size = decode_length(&source, length_size, allow_indef); - source.discard_next(item_size); + const size_t item_size = peek_length(src, offset + tag_size, length_size, allow_indef, is_constructed(class_tag)); - if(auto new_len = checked_add(length, item_size, tag_size, length_size)) { - length = new_len.value(); + if(auto new_offset = checked_add(offset, tag_size, length_size, item_size)) { + offset = new_offset.value(); } else { - throw Decoding_Error("Integer overflow while decoding DER"); + throw Decoding_Error("Integer overflow while scanning for EOC"); } if(type_tag == ASN1_Type::Eoc && class_tag == ASN1_Class::Universal) { + // Per X.690 8.1.5 the EOC marker is exactly two zero octets + if(length_size != 1 || item_size != 0) { + throw BER_Decoding_Error("EOC marker with non-zero length"); + } break; } } - return length; + + return offset - base_offset; } class DataSource_BERObject final : public DataSource { @@ -186,15 +354,17 @@ size_t get_bytes_read() const override { return m_offset; } - explicit DataSource_BERObject(BER_Object&& obj) : m_obj(std::move(obj)), m_offset(0) {} + explicit DataSource_BERObject(BER_Object&& obj) : m_obj(std::move(obj)) {} private: BER_Object m_obj; - size_t m_offset; + size_t m_offset = 0; }; } // namespace +BER_Decoder::~BER_Decoder() = default; + /* * Check if more objects are there */ @@ -226,11 +396,22 @@ * Discard all the bytes remaining in the source */ BER_Decoder& BER_Decoder::discard_remaining() { - uint8_t buf; - while(m_source->read_byte(buf)) {} + m_pushed = BER_Object(); + uint8_t buf = 0; + while(m_source->read_byte(buf) != 0) {} return (*this); } +std::optional BER_Decoder::read_next_byte() { + BOTAN_ASSERT_NOMSG(m_source != nullptr); + uint8_t b = 0; + if(m_source->read_byte(b) != 0) { + return b; + } else { + return {}; + } +} + const BER_Object& BER_Decoder::peek_next_object() { if(!m_pushed.is_set()) { m_pushed = get_next_object(); @@ -251,26 +432,47 @@ } for(;;) { - ASN1_Type type_tag; - ASN1_Class class_tag; + ASN1_Type type_tag = ASN1_Type::NoObject; + ASN1_Class class_tag = ASN1_Class::NoObject; decode_tag(m_source, type_tag, class_tag); next.set_tagging(type_tag, class_tag); if(next.is_set() == false) { // no more objects return next; } - size_t field_size; - const size_t length = decode_length(m_source, field_size, ALLOWED_EOC_NESTINGS); - if(!m_source->check_available(length)) { + const size_t allow_indef = m_limits.allow_ber_encoding() ? m_limits.max_nested_indefinite_length() : 0; + const bool der_mode = m_limits.require_der_encoding(); + const auto dl = decode_length(m_source, allow_indef, der_mode, is_constructed(class_tag)); + + // Per X.690 8.1.5 the only valid EOC encoding is the two-octet + // sequence 0x00 0x00. Reject any other length encoding on a tag of + // (Eoc, Universal) before we consume the "content" bytes. + if(type_tag == ASN1_Type::Eoc && class_tag == ASN1_Class::Universal && + (dl.content_length() != 0 || dl.indefinite_length())) { + throw BER_Decoding_Error("EOC marker with non-zero length"); + } + + if(!m_source->check_available(dl.total_length())) { throw BER_Decoding_Error("Value truncated"); } - uint8_t* out = next.mutable_bits(length); - if(m_source->read(out, length) != length) { + uint8_t* out = next.mutable_bits(dl.content_length()); + if(m_source->read(out, dl.content_length()) != dl.content_length()) { throw BER_Decoding_Error("Value truncated"); } + if(dl.indefinite_length()) { + // After reading the data consume the 2-byte EOC + uint8_t eoc[2] = {0xFF, 0xFF}; + if(m_source->read(eoc, 2) != 2 || eoc[0] != 0x00 || eoc[1] != 0x00) { + throw BER_Decoding_Error("Missing or malformed EOC marker"); + } + } + if(next.tagging() == static_cast(ASN1_Type::Eoc)) { + if(m_limits.require_der_encoding()) { + throw BER_Decoding_Error("Detected EOC marker in DER structure"); + } continue; } else { break; @@ -280,6 +482,18 @@ return next; } +BER_Object BER_Decoder::get_next_value(size_t sizeofT, ASN1_Type type_tag, ASN1_Class class_tag) { + const BER_Object obj = get_next_object(); + obj.assert_is_a(type_tag, class_tag); + + if(obj.length() != sizeofT) { + throw BER_Decoding_Error("Size mismatch. Object value size is " + std::to_string(obj.length()) + + "; Output type size is " + std::to_string(sizeofT)); + } + + return obj; +} + /* * Push a object back into the stream */ @@ -300,69 +514,45 @@ BER_Decoder BER_Decoder::start_cons(ASN1_Type type_tag, ASN1_Class class_tag) { BER_Object obj = get_next_object(); obj.assert_is_a(type_tag, class_tag | ASN1_Class::Constructed); - return BER_Decoder(std::move(obj), this); + BER_Decoder child(std::move(obj), this); + return child; } /* * Finish decoding a CONSTRUCTED type */ BER_Decoder& BER_Decoder::end_cons() { - if(!m_parent) { + if(m_parent == nullptr) { throw Invalid_State("BER_Decoder::end_cons called with null parent"); } - if(!m_source->end_of_data()) { + if(!m_source->end_of_data() || m_pushed.is_set()) { throw Decoding_Error("BER_Decoder::end_cons called with data left"); } return (*m_parent); } -BER_Decoder::BER_Decoder(BER_Object&& obj, BER_Decoder* parent) { +BER_Decoder::BER_Decoder(BER_Object&& obj, BER_Decoder* parent) : + m_limits(parent != nullptr ? parent->limits() : BER_Decoder::Limits::BER()), m_parent(parent) { m_data_src = std::make_unique(std::move(obj)); m_source = m_data_src.get(); - m_parent = parent; } /* * BER_Decoder Constructor */ -BER_Decoder::BER_Decoder(DataSource& src) { - m_source = &src; -} +BER_Decoder::BER_Decoder(DataSource& src, Limits limits) : m_limits(limits), m_source(&src) {} /* * BER_Decoder Constructor */ -BER_Decoder::BER_Decoder(const uint8_t data[], size_t length) { - m_data_src = std::make_unique(data, length); +BER_Decoder::BER_Decoder(std::span buf, Limits limits) : m_limits(limits) { + m_data_src = std::make_unique(buf); m_source = m_data_src.get(); } -/* -* BER_Decoder Constructor -*/ -BER_Decoder::BER_Decoder(const secure_vector& data) { - m_data_src = std::make_unique(data); - m_source = m_data_src.get(); -} +BER_Decoder::BER_Decoder(BER_Decoder&& other) noexcept = default; -/* -* BER_Decoder Constructor -*/ -BER_Decoder::BER_Decoder(const std::vector& data) { - m_data_src = std::make_unique(data.data(), data.size()); - m_source = m_data_src.get(); -} - -/* -* BER_Decoder Copy Constructor -*/ -BER_Decoder::BER_Decoder(const BER_Decoder& other) { - m_source = other.m_source; - - // take ownership - std::swap(m_data_src, other.m_data_src); - m_parent = other.m_parent; -} +BER_Decoder& BER_Decoder::operator=(BER_Decoder&&) noexcept = default; /* * Request for an object to decode itself @@ -376,7 +566,7 @@ * Decode a BER encoded NULL */ BER_Decoder& BER_Decoder::decode_null() { - BER_Object obj = get_next_object(); + const BER_Object obj = get_next_object(); obj.assert_is_a(ASN1_Type::Null, ASN1_Class::Universal); if(obj.length() > 0) { throw BER_Decoding_Error("NULL object had nonzero size"); @@ -395,14 +585,22 @@ * Decode a BER encoded BOOLEAN */ BER_Decoder& BER_Decoder::decode(bool& out, ASN1_Type type_tag, ASN1_Class class_tag) { - BER_Object obj = get_next_object(); + const BER_Object obj = get_next_object(); obj.assert_is_a(type_tag, class_tag); if(obj.length() != 1) { throw BER_Decoding_Error("BER boolean value had invalid size"); } - out = (obj.bits()[0]) ? true : false; + const uint8_t val = obj.bits()[0]; + + // DER requires boolean values to be exactly 0x00 or 0xFF + if(m_limits.require_der_encoding() && val != 0x00 && val != 0xFF) { + throw BER_Decoding_Error("Detected non-canonical boolean encoding in DER structure"); + } + + out = (val != 0) ? true : false; + return (*this); } @@ -413,7 +611,7 @@ BigInt integer; decode(integer, type_tag, class_tag); - if(integer.is_negative()) { + if(integer.signum() < 0) { throw BER_Decoding_Error("Decoded small integer value was negative"); } @@ -440,6 +638,10 @@ BigInt integer; decode(integer, type_tag, class_tag); + if(integer.is_negative()) { + throw BER_Decoding_Error("Decoded small integer value was negative"); + } + if(integer.bits() > 8 * T_bytes) { throw BER_Decoding_Error("Decoded integer value larger than expected"); } @@ -456,18 +658,36 @@ * Decode a BER encoded INTEGER */ BER_Decoder& BER_Decoder::decode(BigInt& out, ASN1_Type type_tag, ASN1_Class class_tag) { - BER_Object obj = get_next_object(); + const BER_Object obj = get_next_object(); obj.assert_is_a(type_tag, class_tag); + // DER requires minimal INTEGER encoding (X.690 section 8.3.2) + if(m_limits.require_der_encoding()) { + if(obj.length() == 0) { + throw BER_Decoding_Error("Detected empty INTEGER encoding in DER structure"); + } + if(obj.length() > 1) { + if(obj.bits()[0] == 0x00 && (obj.bits()[1] & 0x80) == 0) { + throw BER_Decoding_Error("Detected non-minimal INTEGER encoding in DER structure"); + } + if(obj.bits()[0] == 0xFF && (obj.bits()[1] & 0x80) != 0) { + throw BER_Decoding_Error("Detected non-minimal INTEGER encoding in DER structure"); + } + } + } + if(obj.length() == 0) { out.clear(); } else { - const bool negative = (obj.bits()[0] & 0x80) ? true : false; + const uint8_t first = obj.bits()[0]; + const bool negative = (first & 0x80) == 0x80; if(negative) { secure_vector vec(obj.bits(), obj.bits() + obj.length()); for(size_t i = obj.length(); i > 0; --i) { - if(vec[i - 1]--) { + const bool gt0 = (vec[i - 1] > 0); + vec[i - 1] -= 1; + if(gt0) { break; } } @@ -486,24 +706,50 @@ namespace { +bool is_constructed(const BER_Object& obj) { + return is_constructed(obj.class_tag()); +} + template void asn1_decode_binary_string(std::vector& buffer, const BER_Object& obj, ASN1_Type real_type, ASN1_Type type_tag, - ASN1_Class class_tag) { + ASN1_Class class_tag, + bool require_der) { obj.assert_is_a(type_tag, class_tag); + // DER requires BIT STRING and OCTET STRING to use primitive encoding + if(require_der && is_constructed(obj)) { + throw BER_Decoding_Error("Detected constructed string encoding in DER structure"); + } + if(real_type == ASN1_Type::OctetString) { buffer.assign(obj.bits(), obj.bits() + obj.length()); } else { if(obj.length() == 0) { throw BER_Decoding_Error("Invalid BIT STRING"); } - if(obj.bits()[0] >= 8) { + + const uint8_t unused_bits = obj.bits()[0]; + + if(unused_bits >= 8) { throw BER_Decoding_Error("Bad number of unused bits in BIT STRING"); } + // Empty BIT STRING with unused bits > 0 ... + if(unused_bits > 0 && obj.length() < 2) { + throw BER_Decoding_Error("Invalid BIT STRING"); + } + + // DER requires unused bits in BIT STRING to be zero (X.690 section 11.2.2) + if(require_der && unused_bits > 0) { + const uint8_t last_byte = obj.bits()[obj.length() - 1]; + if((last_byte & ((1 << unused_bits) - 1)) != 0) { + throw BER_Decoding_Error("Detected non-zero padding bits in BIT STRING in DER structure"); + } + } + buffer.resize(obj.length() - 1); if(obj.length() > 1) { @@ -525,7 +771,8 @@ throw BER_Bad_Tag("Bad tag for {BIT,OCTET} STRING", static_cast(real_type)); } - asn1_decode_binary_string(buffer, get_next_object(), real_type, type_tag, class_tag); + asn1_decode_binary_string( + buffer, get_next_object(), real_type, type_tag, class_tag, m_limits.require_der_encoding()); return (*this); } @@ -537,7 +784,8 @@ throw BER_Bad_Tag("Bad tag for {BIT,OCTET} STRING", static_cast(real_type)); } - asn1_decode_binary_string(buffer, get_next_object(), real_type, type_tag, class_tag); + asn1_decode_binary_string( + buffer, get_next_object(), real_type, type_tag, class_tag, m_limits.require_der_encoding()); return (*this); } diff -Nru botan3-3.7.1+dfsg/src/lib/asn1/ber_dec.h botan3-3.12.0+dfsg/src/lib/asn1/ber_dec.h --- botan3-3.7.1+dfsg/src/lib/asn1/ber_dec.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/asn1/ber_dec.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,12 +9,15 @@ #define BOTAN_BER_DECODER_H_ #include -#include -#include +#include +#include +#include +#include namespace Botan { class BigInt; +class DataSource; /** * BER Decoding Object @@ -22,43 +25,77 @@ class BOTAN_PUBLIC_API(2, 0) BER_Decoder final { public: /** - * Set up to BER decode the data in buf of length len + * Controls what encoding rules the decoder accepts. */ - BER_Decoder(const uint8_t buf[], size_t len); + class BOTAN_PUBLIC_API(3, 12) Limits final { + public: + /** + * Accept only DER encodings + */ + static Limits DER() { return Limits(false, 0); } - /** - * Set up to BER decode the data in buf of length len - */ - BER_Decoder(std::span buf) : BER_Decoder(buf.data(), buf.size()) {} + /** + * Accept non-canonical BER encodings. + * + * @param max_nested_indef maximum number of nested indefinite-length encodings accepted + */ + static Limits BER(size_t max_nested_indef = 16) { return Limits(true, max_nested_indef); } + + bool allow_ber_encoding() const { return m_allow_ber; } + + bool require_der_encoding() const { return !allow_ber_encoding(); } + + size_t max_nested_indefinite_length() const { return m_max_nested_indef; } + + bool operator==(const Limits&) const = default; + + private: + Limits(bool allow_ber, size_t max_nested_indef) : + m_allow_ber(allow_ber), m_max_nested_indef(max_nested_indef) {} + + bool m_allow_ber; + size_t m_max_nested_indef; + }; /** - * Set up to BER decode the data in vec + * Set up to BER decode the data in buf of length len */ - explicit BER_Decoder(const secure_vector& vec); + BOTAN_DEPRECATED("Use BER_Decoder(span) constructor") + BER_Decoder(const uint8_t buf[], size_t len, Limits limits = Limits::BER()) : + BER_Decoder(std::span{buf, len}, limits) {} /** - * Set up to BER decode the data in vec + * Set up to BER decode the data in buf */ - explicit BER_Decoder(const std::vector& vec); + explicit BER_Decoder(std::span buf, Limits limits = Limits::BER()); /** * Set up to BER decode the data in src */ - explicit BER_Decoder(DataSource& src); + explicit BER_Decoder(DataSource& src, Limits limits = Limits::BER()); /** * Set up to BER decode the data in obj */ - BER_Decoder(const BER_Object& obj) : BER_Decoder(obj.bits(), obj.length()) {} + BOTAN_FUTURE_EXPLICIT BER_Decoder(const BER_Object& obj, Limits limits = Limits::BER()) : + BER_Decoder(obj.data(), limits) {} /** * Set up to BER decode the data in obj + * TODO(Botan4) remove this? */ - BER_Decoder(BER_Object&& obj) : BER_Decoder(std::move(obj), nullptr) {} + BOTAN_FUTURE_EXPLICIT BER_Decoder(BER_Object&& obj) : BER_Decoder(std::move(obj), nullptr) {} - BER_Decoder(const BER_Decoder& other); + BER_Decoder(const BER_Decoder& other) = delete; + BER_Decoder(BER_Decoder&& other) noexcept; BER_Decoder& operator=(const BER_Decoder&) = delete; + BER_Decoder& operator=(BER_Decoder&&) noexcept; + + /** + * Returns the limits currently applied to this decoder + */ + Limits limits() const { return m_limits; } /** * Get the next object in the data stream. @@ -150,17 +187,11 @@ */ template BER_Decoder& get_next_value(T& out, ASN1_Type type_tag, ASN1_Class class_tag = ASN1_Class::ContextSpecific) - requires std::is_standard_layout::value && std::is_trivial::value + requires std::is_standard_layout_v && std::is_trivial_v { - BER_Object obj = get_next_object(); - obj.assert_is_a(type_tag, class_tag); - - if(obj.length() != sizeof(T)) { - throw BER_Decoding_Error("Size mismatch. Object value size is " + std::to_string(obj.length()) + - "; Output type size is " + std::to_string(sizeof(T))); - } + const BER_Object obj = get_next_value(sizeof(T), type_tag, class_tag); - copy_mem(reinterpret_cast(&out), obj.bits(), obj.length()); + std::memcpy(reinterpret_cast(&out), obj.bits(), obj.length()); return (*this); } @@ -171,9 +202,12 @@ template BER_Decoder& raw_bytes(std::vector& out) { out.clear(); - uint8_t buf; - while(m_source->read_byte(buf)) { - out.push_back(buf); + for(;;) { + if(auto next = this->read_next_byte()) { + out.push_back(*next); + } else { + break; + } } return (*this); } @@ -248,7 +282,15 @@ } template - BER_Decoder& decode_optional(T& out, ASN1_Type type_tag, ASN1_Class class_tag, const T& default_value = T()); + BER_Decoder& decode_optional(T& out, ASN1_Type type_tag, ASN1_Class class_tag, const T& default_value = T()) { + std::optional optval; + this->decode_optional(optval, type_tag, class_tag); + out = optval ? *optval : default_value; + return (*this); + } + + template + BER_Decoder& decode_optional(std::optional& out, ASN1_Type type_tag, ASN1_Class class_tag); template BER_Decoder& decode_optional_implicit(T& out, @@ -290,11 +332,11 @@ ASN1_Class class_tag = ASN1_Class::ContextSpecific) { BER_Object obj = get_next_object(); - ASN1_Type type_tag = static_cast(expected_tag); + const ASN1_Type type_tag = static_cast(expected_tag); if(obj.is_a(type_tag, class_tag)) { if(class_tag == ASN1_Class::ExplicitContextSpecific) { - BER_Decoder(std::move(obj)).decode(out, real_type).verify_end(); + BER_Decoder(obj, m_limits).decode(out, real_type).verify_end(); } else { push_back(std::move(obj)); decode(out, real_type, type_tag, class_tag); @@ -315,33 +357,42 @@ return decode_optional_string(out, real_type, static_cast(expected_tag), class_tag); } + ~BER_Decoder(); + private: BER_Decoder(BER_Object&& obj, BER_Decoder* parent); + std::optional read_next_byte(); + + BER_Object get_next_value(size_t sizeofT, ASN1_Type type_tag, ASN1_Class class_tag); + + Limits m_limits; BER_Decoder* m_parent = nullptr; BER_Object m_pushed; // either m_data_src.get() or an unowned pointer DataSource* m_source; - mutable std::unique_ptr m_data_src; + std::unique_ptr m_data_src; }; /* * Decode an OPTIONAL or DEFAULT element */ template -BER_Decoder& BER_Decoder::decode_optional(T& out, ASN1_Type type_tag, ASN1_Class class_tag, const T& default_value) { +BER_Decoder& BER_Decoder::decode_optional(std::optional& optval, ASN1_Type type_tag, ASN1_Class class_tag) { BER_Object obj = get_next_object(); if(obj.is_a(type_tag, class_tag)) { + T out{}; if(class_tag == ASN1_Class::ExplicitContextSpecific) { - BER_Decoder(std::move(obj)).decode(out).verify_end(); + BER_Decoder(obj, m_limits).decode(out).verify_end(); } else { - push_back(std::move(obj)); - decode(out, type_tag, class_tag); + this->push_back(std::move(obj)); + this->decode(out, type_tag, class_tag); } + optval = std::move(out); } else { - out = default_value; - push_back(std::move(obj)); + this->push_back(std::move(obj)); + optval = std::nullopt; } return (*this); @@ -373,7 +424,7 @@ } /* -* Decode a list of homogenously typed values +* Decode a list of homogeneously typed values */ template BER_Decoder& BER_Decoder::decode_list(std::vector& vec, ASN1_Type type_tag, ASN1_Class class_tag) { @@ -391,7 +442,7 @@ } /* -* Decode an optional list of homogenously typed values +* Decode an optional list of homogeneously typed values */ template bool BER_Decoder::decode_optional_list(std::vector& vec, ASN1_Type type_tag, ASN1_Class class_tag) { diff -Nru botan3-3.7.1+dfsg/src/lib/asn1/der_enc.cpp botan3-3.12.0+dfsg/src/lib/asn1/der_enc.cpp --- botan3-3.7.1+dfsg/src/lib/asn1/der_enc.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/asn1/der_enc.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -12,6 +12,7 @@ #include #include #include +#include #include namespace Botan { @@ -78,7 +79,7 @@ void DER_Encoder::DER_Sequence::push_contents(DER_Encoder& der) { const auto real_class_tag = m_class_tag | ASN1_Class::Constructed; - if(m_type_tag == ASN1_Type::Set) { + if(m_type_tag == ASN1_Type::Set && m_class_tag == ASN1_Class::Universal) { std::sort(m_set_contents.begin(), m_set_contents.end()); for(const auto& set_elem : m_set_contents) { m_contents += set_elem; @@ -94,7 +95,7 @@ * Add an encoded value to the SEQUENCE/SET */ void DER_Encoder::DER_Sequence::add_bytes(const uint8_t data[], size_t length) { - if(m_type_tag == ASN1_Type::Set) { + if(m_type_tag == ASN1_Type::Set && m_class_tag == ASN1_Class::Universal) { m_set_contents.push_back(secure_vector(data, data + length)); } else { m_contents += std::make_pair(data, length); @@ -102,7 +103,7 @@ } void DER_Encoder::DER_Sequence::add_bytes(const uint8_t hdr[], size_t hdr_len, const uint8_t val[], size_t val_len) { - if(m_type_tag == ASN1_Type::Set) { + if(m_type_tag == ASN1_Type::Set && m_class_tag == ASN1_Class::Universal) { secure_vector m; m.reserve(hdr_len + val_len); m += std::make_pair(hdr, hdr_len); @@ -124,7 +125,8 @@ /* * DER_Sequence Constructor */ -DER_Encoder::DER_Sequence::DER_Sequence(ASN1_Type t1, ASN1_Class t2) : m_type_tag(t1), m_class_tag(t2) {} +DER_Encoder::DER_Sequence::DER_Sequence(ASN1_Type type_tag, ASN1_Class class_tag) : + m_type_tag(type_tag), m_class_tag(class_tag) {} /* * Return the encoded contents @@ -184,14 +186,7 @@ * Start a new ASN.1 EXPLICIT encoding */ DER_Encoder& DER_Encoder::start_explicit(uint16_t type_no) { - ASN1_Type type_tag = static_cast(type_no); - - // This would confuse DER_Sequence - if(type_tag == ASN1_Type::Set) { - throw Internal_Error("DER_Encoder.start_explicit(SET) not supported"); - } - - return start_cons(type_tag, ASN1_Class::ContextSpecific); + return start_cons(static_cast(type_no), ASN1_Class::ContextSpecific); } /* @@ -276,7 +271,7 @@ * DER encode a BOOLEAN */ DER_Encoder& DER_Encoder::encode(bool is_true, ASN1_Type type_tag, ASN1_Class class_tag) { - uint8_t val = is_true ? 0xFF : 0x00; + const uint8_t val = is_true ? 0xFF : 0x00; return add_object(type_tag, class_tag, &val, 1); } @@ -295,21 +290,30 @@ return add_object(type_tag, class_tag, 0); } - const size_t extra_zero = (n.bits() % 8 == 0) ? 1 : 0; + // Serialize magnitude with one extra leading byte + auto contents = n.serialize(n.bytes() + 1); - auto contents = n.serialize(n.bytes() + extra_zero); - if(n < 0) { - for(unsigned char& content : contents) { - content = ~content; + if(n.signum() < 0) { + // Two's complement: bitwise NOT then increment + for(auto& byte : contents) { + byte = ~byte; } for(size_t i = contents.size(); i > 0; --i) { - if(++contents[i - 1]) { + if(++contents[i - 1] != 0) { break; } } } - return add_object(type_tag, class_tag, contents); + /* + * DER requires the leading byte be emitted only if it required + */ + BOTAN_ASSERT_NOMSG(contents.size() >= 2); + const bool leading_byte_redundant = + (contents[0] == 0x00 && (contents[1] & 0x80) == 0) || (contents[0] == 0xFF && (contents[1] & 0x80) != 0); + auto encoding = std::span{contents}.subspan(leading_byte_redundant ? 1 : 0); + + return add_object(type_tag, class_tag, encoding); } /* @@ -340,16 +344,14 @@ * Write the encoding of the byte(s) */ DER_Encoder& DER_Encoder::add_object(ASN1_Type type_tag, ASN1_Class class_tag, std::string_view rep_str) { - const uint8_t* rep = cast_char_ptr_to_uint8(rep_str.data()); - const size_t rep_len = rep_str.size(); - return add_object(type_tag, class_tag, rep, rep_len); + return add_object(type_tag, class_tag, as_span_of_bytes(rep_str)); } /* * Write the encoding of the byte */ DER_Encoder& DER_Encoder::add_object(ASN1_Type type_tag, ASN1_Class class_tag, uint8_t rep) { - return add_object(type_tag, class_tag, &rep, 1); + return add_object(type_tag, class_tag, std::span{&rep, 1}); } } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/asn1/der_enc.h botan3-3.12.0+dfsg/src/lib/asn1/der_enc.h --- botan3-3.7.1+dfsg/src/lib/asn1/der_enc.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/asn1/der_enc.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,7 +9,10 @@ #define BOTAN_DER_ENCODER_H_ #include +#include #include +#include +#include #include namespace Botan { @@ -34,19 +37,19 @@ * DER encode, writing to @param vec * If this constructor is used, get_contents* may not be called. */ - DER_Encoder(secure_vector& vec); + BOTAN_FUTURE_EXPLICIT DER_Encoder(secure_vector& vec); /** * DER encode, writing to @param vec * If this constructor is used, get_contents* may not be called. */ - DER_Encoder(std::vector& vec); + BOTAN_FUTURE_EXPLICIT DER_Encoder(std::vector& vec); /** * DER encode, calling append to write output * If this constructor is used, get_contents* may not be called. */ - DER_Encoder(append_fn append) : m_append_output(std::move(append)) {} + BOTAN_FUTURE_EXPLICIT DER_Encoder(append_fn append) : m_append_output(std::move(append)) {} secure_vector get_contents(); @@ -54,8 +57,8 @@ * Return the encoded contents as a std::vector * * If using this function, instead pass a std::vector to the - * contructor of DER_Encoder where the output will be placed. This - * avoids several unecessary copies. + * constructor of DER_Encoder where the output will be placed. This + * avoids several unnecessary copies. */ BOTAN_DEPRECATED("Use DER_Encoder(vector) instead") std::vector get_contents_unlocked(); @@ -83,10 +86,7 @@ */ DER_Encoder& raw_bytes(const uint8_t val[], size_t len); - template - DER_Encoder& raw_bytes(const std::vector& val) { - return raw_bytes(val.data(), val.size()); - } + DER_Encoder& raw_bytes(std::span val) { return raw_bytes(val.data(), val.size()); } DER_Encoder& encode_null(); DER_Encoder& encode(bool b); @@ -120,6 +120,7 @@ } template + BOTAN_DEPRECATED("Use the version that takes a std::optional") DER_Encoder& encode_optional(const T& value, const T& default_value) { if(value != default_value) { encode(value); @@ -128,6 +129,14 @@ } template + DER_Encoder& encode_optional(const std::optional& value) { + if(value) { + encode(*value); + } + return (*this); + } + + template DER_Encoder& encode_list(const std::vector& values) { for(size_t i = 0; i != values.size(); ++i) { encode(values[i]); @@ -164,14 +173,25 @@ return (*this); } + DER_Encoder& encode_if(bool pred, bool num) { + if(pred) { + encode(num); + } + return (*this); + } + DER_Encoder& add_object(ASN1_Type type_tag, ASN1_Class class_tag, const uint8_t rep[], size_t length); - DER_Encoder& add_object(ASN1_Type type_tag, ASN1_Class class_tag, const std::vector& rep) { + DER_Encoder& add_object(ASN1_Type type_tag, ASN1_Class class_tag, std::span rep) { return add_object(type_tag, class_tag, rep.data(), rep.size()); } + DER_Encoder& add_object(ASN1_Type type_tag, ASN1_Class class_tag, const std::vector& rep) { + return add_object(type_tag, class_tag, std::span{rep}); + } + DER_Encoder& add_object(ASN1_Type type_tag, ASN1_Class class_tag, const secure_vector& rep) { - return add_object(type_tag, class_tag, rep.data(), rep.size()); + return add_object(type_tag, class_tag, std::span{rep}); } DER_Encoder& add_object(ASN1_Type type_tag, ASN1_Class class_tag, std::string_view str); @@ -189,11 +209,11 @@ void add_bytes(const uint8_t hdr[], size_t hdr_len, const uint8_t val[], size_t val_len); - DER_Sequence(ASN1_Type, ASN1_Class); + DER_Sequence(ASN1_Type type_tag, ASN1_Class class_tag); DER_Sequence(DER_Sequence&& seq) noexcept : - m_type_tag(std::move(seq.m_type_tag)), - m_class_tag(std::move(seq.m_class_tag)), + m_type_tag(seq.m_type_tag), + m_class_tag(seq.m_class_tag), m_contents(std::move(seq.m_contents)), m_set_contents(std::move(seq.m_set_contents)) {} @@ -206,8 +226,8 @@ } DER_Sequence(const DER_Sequence& seq) = default; - DER_Sequence& operator=(const DER_Sequence& seq) = default; + ~DER_Sequence() = default; private: ASN1_Type m_type_tag; diff -Nru botan3-3.7.1+dfsg/src/lib/asn1/info.txt botan3-3.12.0+dfsg/src/lib/asn1/info.txt --- botan3-3.7.1+dfsg/src/lib/asn1/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/asn1/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -12,6 +12,7 @@ +asn1_time.h asn1_print.h asn1_obj.h der_enc.h diff -Nru botan3-3.7.1+dfsg/src/lib/asn1/oid_map.cpp botan3-3.12.0+dfsg/src/lib/asn1/oid_map.cpp --- botan3-3.7.1+dfsg/src/lib/asn1/oid_map.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/asn1/oid_map.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -19,7 +19,15 @@ } void OID_Map::add_oid(const OID& oid, std::string_view str) { - lock_guard_type lock(m_mutex); + if(auto name = lookup_static_oid(oid)) { + if(*name != str) { + throw Invalid_State("Cannot register two different names to a single OID"); + } else { + return; + } + } + + const lock_guard_type lock(m_mutex); auto o2s = m_oid2str.find(oid); @@ -37,21 +45,33 @@ } void OID_Map::add_str2oid(const OID& oid, std::string_view str) { - lock_guard_type lock(m_mutex); + if(lookup_static_oid_name(str).has_value()) { + return; + } + + const lock_guard_type lock(m_mutex); if(!m_str2oid.contains(std::string(str))) { m_str2oid.insert(std::make_pair(str, oid)); } } void OID_Map::add_oid2str(const OID& oid, std::string_view str) { - lock_guard_type lock(m_mutex); + if(lookup_static_oid(oid).has_value()) { + return; + } + + const lock_guard_type lock(m_mutex); if(!m_oid2str.contains(oid)) { m_oid2str.insert(std::make_pair(oid, str)); } } std::string OID_Map::oid2str(const OID& oid) { - lock_guard_type lock(m_mutex); + if(auto name = lookup_static_oid(oid)) { + return std::string(*name); + } + + const lock_guard_type lock(m_mutex); auto i = m_oid2str.find(oid); if(i != m_oid2str.end()) { @@ -62,7 +82,11 @@ } OID OID_Map::str2oid(std::string_view str) { - lock_guard_type lock(m_mutex); + if(auto oid = lookup_static_oid_name(str)) { + return std::move(*oid); + } + + const lock_guard_type lock(m_mutex); auto i = m_str2oid.find(std::string(str)); if(i != m_str2oid.end()) { return i->second; diff -Nru botan3-3.7.1+dfsg/src/lib/asn1/oid_map.h botan3-3.12.0+dfsg/src/lib/asn1/oid_map.h --- botan3-3.7.1+dfsg/src/lib/asn1/oid_map.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/asn1/oid_map.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -19,8 +20,10 @@ public: void add_oid(const OID& oid, std::string_view str); + // TODO(Botan4) remove this function when oids.h is removed void add_str2oid(const OID& oid, std::string_view str); + // TODO(Botan4) remove this function when oids.h is removed void add_oid2str(const OID& oid, std::string_view str); std::string oid2str(const OID& oid); @@ -30,6 +33,9 @@ static OID_Map& global_registry(); private: + static std::optional lookup_static_oid(const OID& oid); + static std::optional lookup_static_oid_name(std::string_view name); + static std::unordered_map load_oid2str_map(); static std::unordered_map load_str2oid_map(); diff -Nru botan3-3.7.1+dfsg/src/lib/asn1/oid_maps.cpp botan3-3.12.0+dfsg/src/lib/asn1/oid_maps.cpp --- botan3-3.7.1+dfsg/src/lib/asn1/oid_maps.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/asn1/oid_maps.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,693 +0,0 @@ -/* -* OID maps -* -* This file was automatically generated by ./src/scripts/dev_tools/gen_oids.py on 2025-01-26 -* -* All manual edits to this file will be lost. Edit the script -* then regenerate this source file. -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include -#include - -namespace Botan { - -std::unordered_map OID_Map::load_oid2str_map() { - return std::unordered_map{ - - {OID({0, 3, 4401, 5, 3, 1, 9, 26}), "Camellia-192/GCM"}, - {OID({0, 3, 4401, 5, 3, 1, 9, 46}), "Camellia-256/GCM"}, - {OID({0, 3, 4401, 5, 3, 1, 9, 6}), "Camellia-128/GCM"}, - {OID({0, 4, 0, 127, 0, 15, 1, 1, 13, 0}), "XMSS"}, - {OID({1, 0, 14888, 3, 0, 5}), "ECKCDSA"}, - {OID({1, 2, 156, 10197, 1, 104, 100}), "SM4/OCB"}, - {OID({1, 2, 156, 10197, 1, 104, 2}), "SM4/CBC"}, - {OID({1, 2, 156, 10197, 1, 104, 8}), "SM4/GCM"}, - {OID({1, 2, 156, 10197, 1, 301}), "sm2p256v1"}, - {OID({1, 2, 156, 10197, 1, 301, 1}), "SM2"}, - {OID({1, 2, 156, 10197, 1, 301, 2}), "SM2_Kex"}, - {OID({1, 2, 156, 10197, 1, 301, 3}), "SM2_Enc"}, - {OID({1, 2, 156, 10197, 1, 401}), "SM3"}, - {OID({1, 2, 156, 10197, 1, 501}), "SM2_Sig/SM3"}, - {OID({1, 2, 156, 10197, 1, 504}), "RSA/PKCS1v15(SM3)"}, - {OID({1, 2, 250, 1, 223, 101, 256, 1}), "frp256v1"}, - {OID({1, 2, 392, 200011, 61, 1, 1, 1, 2}), "Camellia-128/CBC"}, - {OID({1, 2, 392, 200011, 61, 1, 1, 1, 3}), "Camellia-192/CBC"}, - {OID({1, 2, 392, 200011, 61, 1, 1, 1, 4}), "Camellia-256/CBC"}, - {OID({1, 2, 410, 200004, 1, 100, 4, 3}), "ECKCDSA/SHA-1"}, - {OID({1, 2, 410, 200004, 1, 100, 4, 4}), "ECKCDSA/SHA-224"}, - {OID({1, 2, 410, 200004, 1, 100, 4, 5}), "ECKCDSA/SHA-256"}, - {OID({1, 2, 410, 200004, 1, 4}), "SEED/CBC"}, - {OID({1, 2, 643, 100, 1}), "GOST.OGRN"}, - {OID({1, 2, 643, 100, 111}), "GOST.SubjectSigningTool"}, - {OID({1, 2, 643, 100, 112}), "GOST.IssuerSigningTool"}, - {OID({1, 2, 643, 2, 2, 19}), "GOST-34.10"}, - {OID({1, 2, 643, 2, 2, 3}), "GOST-34.10/GOST-R-34.11-94"}, - {OID({1, 2, 643, 2, 2, 35, 1}), "gost_256A"}, - {OID({1, 2, 643, 2, 2, 36, 0}), "gost_256A"}, - {OID({1, 2, 643, 3, 131, 1, 1}), "GOST.INN"}, - {OID({1, 2, 643, 7, 1, 1, 1, 1}), "GOST-34.10-2012-256"}, - {OID({1, 2, 643, 7, 1, 1, 1, 2}), "GOST-34.10-2012-512"}, - {OID({1, 2, 643, 7, 1, 1, 2, 2}), "Streebog-256"}, - {OID({1, 2, 643, 7, 1, 1, 2, 3}), "Streebog-512"}, - {OID({1, 2, 643, 7, 1, 1, 3, 2}), "GOST-34.10-2012-256/Streebog-256"}, - {OID({1, 2, 643, 7, 1, 1, 3, 3}), "GOST-34.10-2012-512/Streebog-512"}, - {OID({1, 2, 643, 7, 1, 2, 1, 1, 1}), "gost_256A"}, - {OID({1, 2, 643, 7, 1, 2, 1, 1, 2}), "gost_256B"}, - {OID({1, 2, 643, 7, 1, 2, 1, 2, 1}), "gost_512A"}, - {OID({1, 2, 643, 7, 1, 2, 1, 2, 2}), "gost_512B"}, - {OID({1, 2, 840, 10040, 4, 1}), "DSA"}, - {OID({1, 2, 840, 10040, 4, 3}), "DSA/SHA-1"}, - {OID({1, 2, 840, 10045, 2, 1}), "ECDSA"}, - {OID({1, 2, 840, 10045, 3, 1, 1}), "secp192r1"}, - {OID({1, 2, 840, 10045, 3, 1, 2}), "x962_p192v2"}, - {OID({1, 2, 840, 10045, 3, 1, 3}), "x962_p192v3"}, - {OID({1, 2, 840, 10045, 3, 1, 4}), "x962_p239v1"}, - {OID({1, 2, 840, 10045, 3, 1, 5}), "x962_p239v2"}, - {OID({1, 2, 840, 10045, 3, 1, 6}), "x962_p239v3"}, - {OID({1, 2, 840, 10045, 3, 1, 7}), "secp256r1"}, - {OID({1, 2, 840, 10045, 4, 1}), "ECDSA/SHA-1"}, - {OID({1, 2, 840, 10045, 4, 3, 1}), "ECDSA/SHA-224"}, - {OID({1, 2, 840, 10045, 4, 3, 2}), "ECDSA/SHA-256"}, - {OID({1, 2, 840, 10045, 4, 3, 3}), "ECDSA/SHA-384"}, - {OID({1, 2, 840, 10045, 4, 3, 4}), "ECDSA/SHA-512"}, - {OID({1, 2, 840, 10046, 2, 1}), "DH"}, - {OID({1, 2, 840, 113533, 7, 66, 10}), "CAST-128/CBC"}, - {OID({1, 2, 840, 113533, 7, 66, 15}), "KeyWrap.CAST-128"}, - {OID({1, 2, 840, 113549, 1, 1, 1}), "RSA"}, - {OID({1, 2, 840, 113549, 1, 1, 10}), "RSA/PSS"}, - {OID({1, 2, 840, 113549, 1, 1, 11}), "RSA/PKCS1v15(SHA-256)"}, - {OID({1, 2, 840, 113549, 1, 1, 12}), "RSA/PKCS1v15(SHA-384)"}, - {OID({1, 2, 840, 113549, 1, 1, 13}), "RSA/PKCS1v15(SHA-512)"}, - {OID({1, 2, 840, 113549, 1, 1, 14}), "RSA/PKCS1v15(SHA-224)"}, - {OID({1, 2, 840, 113549, 1, 1, 16}), "RSA/PKCS1v15(SHA-512-256)"}, - {OID({1, 2, 840, 113549, 1, 1, 2}), "RSA/PKCS1v15(MD2)"}, - {OID({1, 2, 840, 113549, 1, 1, 4}), "RSA/PKCS1v15(MD5)"}, - {OID({1, 2, 840, 113549, 1, 1, 5}), "RSA/PKCS1v15(SHA-1)"}, - {OID({1, 2, 840, 113549, 1, 1, 7}), "RSA/OAEP"}, - {OID({1, 2, 840, 113549, 1, 1, 8}), "MGF1"}, - {OID({1, 2, 840, 113549, 1, 5, 12}), "PKCS5.PBKDF2"}, - {OID({1, 2, 840, 113549, 1, 5, 13}), "PBE-PKCS5v20"}, - {OID({1, 2, 840, 113549, 1, 9, 1}), "PKCS9.EmailAddress"}, - {OID({1, 2, 840, 113549, 1, 9, 14}), "PKCS9.ExtensionRequest"}, - {OID({1, 2, 840, 113549, 1, 9, 16, 3, 17}), "HSS-LMS"}, - {OID({1, 2, 840, 113549, 1, 9, 16, 3, 18}), "ChaCha20Poly1305"}, - {OID({1, 2, 840, 113549, 1, 9, 16, 3, 6}), "KeyWrap.TripleDES"}, - {OID({1, 2, 840, 113549, 1, 9, 16, 3, 8}), "Compression.Zlib"}, - {OID({1, 2, 840, 113549, 1, 9, 2}), "PKCS9.UnstructuredName"}, - {OID({1, 2, 840, 113549, 1, 9, 3}), "PKCS9.ContentType"}, - {OID({1, 2, 840, 113549, 1, 9, 4}), "PKCS9.MessageDigest"}, - {OID({1, 2, 840, 113549, 1, 9, 7}), "PKCS9.ChallengePassword"}, - {OID({1, 2, 840, 113549, 2, 10}), "HMAC(SHA-384)"}, - {OID({1, 2, 840, 113549, 2, 11}), "HMAC(SHA-512)"}, - {OID({1, 2, 840, 113549, 2, 13}), "HMAC(SHA-512-256)"}, - {OID({1, 2, 840, 113549, 2, 5}), "MD5"}, - {OID({1, 2, 840, 113549, 2, 7}), "HMAC(SHA-1)"}, - {OID({1, 2, 840, 113549, 2, 8}), "HMAC(SHA-224)"}, - {OID({1, 2, 840, 113549, 2, 9}), "HMAC(SHA-256)"}, - {OID({1, 2, 840, 113549, 3, 7}), "TripleDES/CBC"}, - {OID({1, 3, 101, 110}), "X25519"}, - {OID({1, 3, 101, 111}), "X448"}, - {OID({1, 3, 101, 112}), "Ed25519"}, - {OID({1, 3, 101, 113}), "Ed448"}, - {OID({1, 3, 132, 0, 10}), "secp256k1"}, - {OID({1, 3, 132, 0, 30}), "secp160r2"}, - {OID({1, 3, 132, 0, 31}), "secp192k1"}, - {OID({1, 3, 132, 0, 32}), "secp224k1"}, - {OID({1, 3, 132, 0, 33}), "secp224r1"}, - {OID({1, 3, 132, 0, 34}), "secp384r1"}, - {OID({1, 3, 132, 0, 35}), "secp521r1"}, - {OID({1, 3, 132, 0, 8}), "secp160r1"}, - {OID({1, 3, 132, 0, 9}), "secp160k1"}, - {OID({1, 3, 132, 1, 12}), "ECDH"}, - {OID({1, 3, 14, 3, 2, 26}), "SHA-1"}, - {OID({1, 3, 14, 3, 2, 7}), "DES/CBC"}, - {OID({1, 3, 36, 3, 2, 1}), "RIPEMD-160"}, - {OID({1, 3, 36, 3, 3, 1, 2}), "RSA/PKCS1v15(RIPEMD-160)"}, - {OID({1, 3, 36, 3, 3, 2, 5, 2, 1}), "ECGDSA"}, - {OID({1, 3, 36, 3, 3, 2, 5, 4, 1}), "ECGDSA/RIPEMD-160"}, - {OID({1, 3, 36, 3, 3, 2, 5, 4, 2}), "ECGDSA/SHA-1"}, - {OID({1, 3, 36, 3, 3, 2, 5, 4, 3}), "ECGDSA/SHA-224"}, - {OID({1, 3, 36, 3, 3, 2, 5, 4, 4}), "ECGDSA/SHA-256"}, - {OID({1, 3, 36, 3, 3, 2, 5, 4, 5}), "ECGDSA/SHA-384"}, - {OID({1, 3, 36, 3, 3, 2, 5, 4, 6}), "ECGDSA/SHA-512"}, - {OID({1, 3, 36, 3, 3, 2, 8, 1, 1, 1}), "brainpool160r1"}, - {OID({1, 3, 36, 3, 3, 2, 8, 1, 1, 11}), "brainpool384r1"}, - {OID({1, 3, 36, 3, 3, 2, 8, 1, 1, 13}), "brainpool512r1"}, - {OID({1, 3, 36, 3, 3, 2, 8, 1, 1, 3}), "brainpool192r1"}, - {OID({1, 3, 36, 3, 3, 2, 8, 1, 1, 5}), "brainpool224r1"}, - {OID({1, 3, 36, 3, 3, 2, 8, 1, 1, 7}), "brainpool256r1"}, - {OID({1, 3, 36, 3, 3, 2, 8, 1, 1, 9}), "brainpool320r1"}, - {OID({1, 3, 6, 1, 4, 1, 11591, 15, 1}), "OpenPGP.Ed25519"}, - {OID({1, 3, 6, 1, 4, 1, 11591, 4, 11}), "Scrypt"}, - {OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 1}), "ClassicMcEliece_348864"}, - {OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 10}), "ClassicMcEliece_8192128f"}, - {OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 2}), "ClassicMcEliece_348864f"}, - {OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 3}), "ClassicMcEliece_460896"}, - {OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 4}), "ClassicMcEliece_460896f"}, - {OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 5}), "ClassicMcEliece_6688128"}, - {OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 6}), "ClassicMcEliece_6688128f"}, - {OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 7}), "ClassicMcEliece_6960119"}, - {OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 8}), "ClassicMcEliece_6960119f"}, - {OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 9}), "ClassicMcEliece_8192128"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 10, 1}), "Dilithium-4x4-AES-r3"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 10, 2}), "Dilithium-6x5-AES-r3"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 10, 3}), "Dilithium-8x7-AES-r3"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 11, 1}), "Kyber-512-90s-r3"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 11, 2}), "Kyber-768-90s-r3"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 11, 3}), "Kyber-1024-90s-r3"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 12, 1, 1}), "SphincsPlus-shake-128s-r3.1"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 12, 1, 2}), "SphincsPlus-shake-128f-r3.1"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 12, 1, 3}), "SphincsPlus-shake-192s-r3.1"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 12, 1, 4}), "SphincsPlus-shake-192f-r3.1"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 12, 1, 5}), "SphincsPlus-shake-256s-r3.1"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 12, 1, 6}), "SphincsPlus-shake-256f-r3.1"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 12, 2, 1}), "SphincsPlus-sha2-128s-r3.1"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 12, 2, 2}), "SphincsPlus-sha2-128f-r3.1"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 12, 2, 3}), "SphincsPlus-sha2-192s-r3.1"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 12, 2, 4}), "SphincsPlus-sha2-192f-r3.1"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 12, 2, 5}), "SphincsPlus-sha2-256s-r3.1"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 12, 2, 6}), "SphincsPlus-sha2-256f-r3.1"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 12, 3, 1}), "SphincsPlus-haraka-128s-r3.1"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 12, 3, 2}), "SphincsPlus-haraka-128f-r3.1"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 12, 3, 3}), "SphincsPlus-haraka-192s-r3.1"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 12, 3, 4}), "SphincsPlus-haraka-192f-r3.1"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 12, 3, 5}), "SphincsPlus-haraka-256s-r3.1"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 12, 3, 6}), "SphincsPlus-haraka-256f-r3.1"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 13}), "HSS-LMS-Private-Key"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 14, 1}), "FrodoKEM-640-SHAKE"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 14, 2}), "FrodoKEM-976-SHAKE"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 14, 3}), "FrodoKEM-1344-SHAKE"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 15, 1}), "FrodoKEM-640-AES"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 15, 2}), "FrodoKEM-976-AES"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 15, 3}), "FrodoKEM-1344-AES"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 16, 1}), "eFrodoKEM-640-SHAKE"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 16, 2}), "eFrodoKEM-976-SHAKE"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 16, 3}), "eFrodoKEM-1344-SHAKE"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 17, 1}), "eFrodoKEM-640-AES"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 17, 2}), "eFrodoKEM-976-AES"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 17, 3}), "eFrodoKEM-1344-AES"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 1}), "ClassicMcEliece_6688128pc"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 2}), "ClassicMcEliece_6688128pcf"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 3}), "ClassicMcEliece_6960119pc"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 4}), "ClassicMcEliece_6960119pcf"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 5}), "ClassicMcEliece_8192128pc"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 6}), "ClassicMcEliece_8192128pcf"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 3}), "McEliece"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 5}), "XMSS-draft6"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 6, 1}), "GOST-34.10-2012-256/SHA-256"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 7, 1}), "Kyber-512-r3"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 7, 2}), "Kyber-768-r3"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 7, 3}), "Kyber-1024-r3"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 8}), "XMSS-draft12"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 9, 1}), "Dilithium-4x4-r3"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 9, 2}), "Dilithium-6x5-r3"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 1, 9, 3}), "Dilithium-8x7-r3"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 3, 1}), "Serpent/CBC"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 3, 101}), "Serpent/GCM"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 3, 102}), "Twofish/GCM"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 3, 2}), "Threefish-512/CBC"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 3, 2, 1}), "AES-128/OCB"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 3, 2, 2}), "AES-192/OCB"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 3, 2, 3}), "AES-256/OCB"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 3, 2, 4}), "Serpent/OCB"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 3, 2, 5}), "Twofish/OCB"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 3, 2, 6}), "Camellia-128/OCB"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 3, 2, 7}), "Camellia-192/OCB"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 3, 2, 8}), "Camellia-256/OCB"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 3, 3}), "Twofish/CBC"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 3, 4, 1}), "AES-128/SIV"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 3, 4, 2}), "AES-192/SIV"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 3, 4, 3}), "AES-256/SIV"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 3, 4, 4}), "Serpent/SIV"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 3, 4, 5}), "Twofish/SIV"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 3, 4, 6}), "Camellia-128/SIV"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 3, 4, 7}), "Camellia-192/SIV"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 3, 4, 8}), "Camellia-256/SIV"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 3, 4, 9}), "SM4/SIV"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 4, 1}), "numsp256d1"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 4, 2}), "numsp384d1"}, - {OID({1, 3, 6, 1, 4, 1, 25258, 4, 3}), "numsp512d1"}, - {OID({1, 3, 6, 1, 4, 1, 3029, 1, 2, 1}), "ElGamal"}, - {OID({1, 3, 6, 1, 4, 1, 3029, 1, 5, 1}), "OpenPGP.Curve25519"}, - {OID({1, 3, 6, 1, 4, 1, 311, 20, 2, 2}), "Microsoft SmartcardLogon"}, - {OID({1, 3, 6, 1, 4, 1, 311, 20, 2, 3}), "Microsoft UPN"}, - {OID({1, 3, 6, 1, 4, 1, 8301, 3, 1, 2, 9, 0, 38}), "secp521r1"}, - {OID({1, 3, 6, 1, 5, 5, 7, 1, 1}), "PKIX.AuthorityInformationAccess"}, - {OID({1, 3, 6, 1, 5, 5, 7, 1, 26}), "PKIX.TNAuthList"}, - {OID({1, 3, 6, 1, 5, 5, 7, 3, 1}), "PKIX.ServerAuth"}, - {OID({1, 3, 6, 1, 5, 5, 7, 3, 2}), "PKIX.ClientAuth"}, - {OID({1, 3, 6, 1, 5, 5, 7, 3, 3}), "PKIX.CodeSigning"}, - {OID({1, 3, 6, 1, 5, 5, 7, 3, 4}), "PKIX.EmailProtection"}, - {OID({1, 3, 6, 1, 5, 5, 7, 3, 5}), "PKIX.IPsecEndSystem"}, - {OID({1, 3, 6, 1, 5, 5, 7, 3, 6}), "PKIX.IPsecTunnel"}, - {OID({1, 3, 6, 1, 5, 5, 7, 3, 7}), "PKIX.IPsecUser"}, - {OID({1, 3, 6, 1, 5, 5, 7, 3, 8}), "PKIX.TimeStamping"}, - {OID({1, 3, 6, 1, 5, 5, 7, 3, 9}), "PKIX.OCSPSigning"}, - {OID({1, 3, 6, 1, 5, 5, 7, 48, 1}), "PKIX.OCSP"}, - {OID({1, 3, 6, 1, 5, 5, 7, 48, 1, 1}), "PKIX.OCSP.BasicResponse"}, - {OID({1, 3, 6, 1, 5, 5, 7, 48, 1, 5}), "PKIX.OCSP.NoCheck"}, - {OID({1, 3, 6, 1, 5, 5, 7, 48, 2}), "PKIX.CertificateAuthorityIssuers"}, - {OID({1, 3, 6, 1, 5, 5, 7, 8, 5}), "PKIX.XMPPAddr"}, - {OID({2, 16, 840, 1, 101, 3, 4, 1, 2}), "AES-128/CBC"}, - {OID({2, 16, 840, 1, 101, 3, 4, 1, 22}), "AES-192/CBC"}, - {OID({2, 16, 840, 1, 101, 3, 4, 1, 25}), "KeyWrap.AES-192"}, - {OID({2, 16, 840, 1, 101, 3, 4, 1, 26}), "AES-192/GCM"}, - {OID({2, 16, 840, 1, 101, 3, 4, 1, 27}), "AES-192/CCM"}, - {OID({2, 16, 840, 1, 101, 3, 4, 1, 42}), "AES-256/CBC"}, - {OID({2, 16, 840, 1, 101, 3, 4, 1, 45}), "KeyWrap.AES-256"}, - {OID({2, 16, 840, 1, 101, 3, 4, 1, 46}), "AES-256/GCM"}, - {OID({2, 16, 840, 1, 101, 3, 4, 1, 47}), "AES-256/CCM"}, - {OID({2, 16, 840, 1, 101, 3, 4, 1, 5}), "KeyWrap.AES-128"}, - {OID({2, 16, 840, 1, 101, 3, 4, 1, 6}), "AES-128/GCM"}, - {OID({2, 16, 840, 1, 101, 3, 4, 1, 7}), "AES-128/CCM"}, - {OID({2, 16, 840, 1, 101, 3, 4, 2, 1}), "SHA-256"}, - {OID({2, 16, 840, 1, 101, 3, 4, 2, 10}), "SHA-3(512)"}, - {OID({2, 16, 840, 1, 101, 3, 4, 2, 11}), "SHAKE-128"}, - {OID({2, 16, 840, 1, 101, 3, 4, 2, 12}), "SHAKE-256"}, - {OID({2, 16, 840, 1, 101, 3, 4, 2, 2}), "SHA-384"}, - {OID({2, 16, 840, 1, 101, 3, 4, 2, 3}), "SHA-512"}, - {OID({2, 16, 840, 1, 101, 3, 4, 2, 4}), "SHA-224"}, - {OID({2, 16, 840, 1, 101, 3, 4, 2, 6}), "SHA-512-256"}, - {OID({2, 16, 840, 1, 101, 3, 4, 2, 7}), "SHA-3(224)"}, - {OID({2, 16, 840, 1, 101, 3, 4, 2, 8}), "SHA-3(256)"}, - {OID({2, 16, 840, 1, 101, 3, 4, 2, 9}), "SHA-3(384)"}, - {OID({2, 16, 840, 1, 101, 3, 4, 3, 1}), "DSA/SHA-224"}, - {OID({2, 16, 840, 1, 101, 3, 4, 3, 10}), "ECDSA/SHA-3(256)"}, - {OID({2, 16, 840, 1, 101, 3, 4, 3, 11}), "ECDSA/SHA-3(384)"}, - {OID({2, 16, 840, 1, 101, 3, 4, 3, 12}), "ECDSA/SHA-3(512)"}, - {OID({2, 16, 840, 1, 101, 3, 4, 3, 13}), "RSA/PKCS1v15(SHA-3(224))"}, - {OID({2, 16, 840, 1, 101, 3, 4, 3, 14}), "RSA/PKCS1v15(SHA-3(256))"}, - {OID({2, 16, 840, 1, 101, 3, 4, 3, 15}), "RSA/PKCS1v15(SHA-3(384))"}, - {OID({2, 16, 840, 1, 101, 3, 4, 3, 16}), "RSA/PKCS1v15(SHA-3(512))"}, - {OID({2, 16, 840, 1, 101, 3, 4, 3, 17}), "ML-DSA-4x4"}, - {OID({2, 16, 840, 1, 101, 3, 4, 3, 18}), "ML-DSA-6x5"}, - {OID({2, 16, 840, 1, 101, 3, 4, 3, 19}), "ML-DSA-8x7"}, - {OID({2, 16, 840, 1, 101, 3, 4, 3, 2}), "DSA/SHA-256"}, - {OID({2, 16, 840, 1, 101, 3, 4, 3, 20}), "SLH-DSA-SHA2-128s"}, - {OID({2, 16, 840, 1, 101, 3, 4, 3, 21}), "SLH-DSA-SHA2-128f"}, - {OID({2, 16, 840, 1, 101, 3, 4, 3, 22}), "SLH-DSA-SHA2-192s"}, - {OID({2, 16, 840, 1, 101, 3, 4, 3, 23}), "SLH-DSA-SHA2-192f"}, - {OID({2, 16, 840, 1, 101, 3, 4, 3, 24}), "SLH-DSA-SHA2-256s"}, - {OID({2, 16, 840, 1, 101, 3, 4, 3, 25}), "SLH-DSA-SHA2-256f"}, - {OID({2, 16, 840, 1, 101, 3, 4, 3, 26}), "SLH-DSA-SHAKE-128s"}, - {OID({2, 16, 840, 1, 101, 3, 4, 3, 27}), "SLH-DSA-SHAKE-128f"}, - {OID({2, 16, 840, 1, 101, 3, 4, 3, 28}), "SLH-DSA-SHAKE-192s"}, - {OID({2, 16, 840, 1, 101, 3, 4, 3, 29}), "SLH-DSA-SHAKE-192f"}, - {OID({2, 16, 840, 1, 101, 3, 4, 3, 3}), "DSA/SHA-384"}, - {OID({2, 16, 840, 1, 101, 3, 4, 3, 30}), "SLH-DSA-SHAKE-256s"}, - {OID({2, 16, 840, 1, 101, 3, 4, 3, 31}), "SLH-DSA-SHAKE-256f"}, - {OID({2, 16, 840, 1, 101, 3, 4, 3, 4}), "DSA/SHA-512"}, - {OID({2, 16, 840, 1, 101, 3, 4, 3, 5}), "DSA/SHA-3(224)"}, - {OID({2, 16, 840, 1, 101, 3, 4, 3, 6}), "DSA/SHA-3(256)"}, - {OID({2, 16, 840, 1, 101, 3, 4, 3, 7}), "DSA/SHA-3(384)"}, - {OID({2, 16, 840, 1, 101, 3, 4, 3, 8}), "DSA/SHA-3(512)"}, - {OID({2, 16, 840, 1, 101, 3, 4, 3, 9}), "ECDSA/SHA-3(224)"}, - {OID({2, 16, 840, 1, 101, 3, 4, 4, 1}), "ML-KEM-512"}, - {OID({2, 16, 840, 1, 101, 3, 4, 4, 2}), "ML-KEM-768"}, - {OID({2, 16, 840, 1, 101, 3, 4, 4, 3}), "ML-KEM-1024"}, - {OID({2, 16, 840, 1, 113730, 1, 13}), "Certificate Comment"}, - {OID({2, 5, 29, 14}), "X509v3.SubjectKeyIdentifier"}, - {OID({2, 5, 29, 15}), "X509v3.KeyUsage"}, - {OID({2, 5, 29, 16}), "X509v3.PrivateKeyUsagePeriod"}, - {OID({2, 5, 29, 17}), "X509v3.SubjectAlternativeName"}, - {OID({2, 5, 29, 18}), "X509v3.IssuerAlternativeName"}, - {OID({2, 5, 29, 19}), "X509v3.BasicConstraints"}, - {OID({2, 5, 29, 20}), "X509v3.CRLNumber"}, - {OID({2, 5, 29, 21}), "X509v3.ReasonCode"}, - {OID({2, 5, 29, 23}), "X509v3.HoldInstructionCode"}, - {OID({2, 5, 29, 24}), "X509v3.InvalidityDate"}, - {OID({2, 5, 29, 28}), "X509v3.CRLIssuingDistributionPoint"}, - {OID({2, 5, 29, 30}), "X509v3.NameConstraints"}, - {OID({2, 5, 29, 31}), "X509v3.CRLDistributionPoints"}, - {OID({2, 5, 29, 32}), "X509v3.CertificatePolicies"}, - {OID({2, 5, 29, 32, 0}), "X509v3.AnyPolicy"}, - {OID({2, 5, 29, 35}), "X509v3.AuthorityKeyIdentifier"}, - {OID({2, 5, 29, 36}), "X509v3.PolicyConstraints"}, - {OID({2, 5, 29, 37}), "X509v3.ExtendedKeyUsage"}, - {OID({2, 5, 4, 10}), "X520.Organization"}, - {OID({2, 5, 4, 11}), "X520.OrganizationalUnit"}, - {OID({2, 5, 4, 12}), "X520.Title"}, - {OID({2, 5, 4, 3}), "X520.CommonName"}, - {OID({2, 5, 4, 4}), "X520.Surname"}, - {OID({2, 5, 4, 42}), "X520.GivenName"}, - {OID({2, 5, 4, 43}), "X520.Initials"}, - {OID({2, 5, 4, 44}), "X520.GenerationalQualifier"}, - {OID({2, 5, 4, 46}), "X520.DNQualifier"}, - {OID({2, 5, 4, 5}), "X520.SerialNumber"}, - {OID({2, 5, 4, 6}), "X520.Country"}, - {OID({2, 5, 4, 65}), "X520.Pseudonym"}, - {OID({2, 5, 4, 7}), "X520.Locality"}, - {OID({2, 5, 4, 8}), "X520.State"}, - {OID({2, 5, 4, 9}), "X520.StreetAddress"}, - {OID({2, 5, 8, 1, 1}), "RSA"}}; -} - -std::unordered_map OID_Map::load_str2oid_map() { - return std::unordered_map{ - - {"AES-128/CBC", OID({2, 16, 840, 1, 101, 3, 4, 1, 2})}, - {"AES-128/CCM", OID({2, 16, 840, 1, 101, 3, 4, 1, 7})}, - {"AES-128/GCM", OID({2, 16, 840, 1, 101, 3, 4, 1, 6})}, - {"AES-128/OCB", OID({1, 3, 6, 1, 4, 1, 25258, 3, 2, 1})}, - {"AES-128/SIV", OID({1, 3, 6, 1, 4, 1, 25258, 3, 4, 1})}, - {"AES-192/CBC", OID({2, 16, 840, 1, 101, 3, 4, 1, 22})}, - {"AES-192/CCM", OID({2, 16, 840, 1, 101, 3, 4, 1, 27})}, - {"AES-192/GCM", OID({2, 16, 840, 1, 101, 3, 4, 1, 26})}, - {"AES-192/OCB", OID({1, 3, 6, 1, 4, 1, 25258, 3, 2, 2})}, - {"AES-192/SIV", OID({1, 3, 6, 1, 4, 1, 25258, 3, 4, 2})}, - {"AES-256/CBC", OID({2, 16, 840, 1, 101, 3, 4, 1, 42})}, - {"AES-256/CCM", OID({2, 16, 840, 1, 101, 3, 4, 1, 47})}, - {"AES-256/GCM", OID({2, 16, 840, 1, 101, 3, 4, 1, 46})}, - {"AES-256/OCB", OID({1, 3, 6, 1, 4, 1, 25258, 3, 2, 3})}, - {"AES-256/SIV", OID({1, 3, 6, 1, 4, 1, 25258, 3, 4, 3})}, - {"CAST-128/CBC", OID({1, 2, 840, 113533, 7, 66, 10})}, - {"Camellia-128/CBC", OID({1, 2, 392, 200011, 61, 1, 1, 1, 2})}, - {"Camellia-128/GCM", OID({0, 3, 4401, 5, 3, 1, 9, 6})}, - {"Camellia-128/OCB", OID({1, 3, 6, 1, 4, 1, 25258, 3, 2, 6})}, - {"Camellia-128/SIV", OID({1, 3, 6, 1, 4, 1, 25258, 3, 4, 6})}, - {"Camellia-192/CBC", OID({1, 2, 392, 200011, 61, 1, 1, 1, 3})}, - {"Camellia-192/GCM", OID({0, 3, 4401, 5, 3, 1, 9, 26})}, - {"Camellia-192/OCB", OID({1, 3, 6, 1, 4, 1, 25258, 3, 2, 7})}, - {"Camellia-192/SIV", OID({1, 3, 6, 1, 4, 1, 25258, 3, 4, 7})}, - {"Camellia-256/CBC", OID({1, 2, 392, 200011, 61, 1, 1, 1, 4})}, - {"Camellia-256/GCM", OID({0, 3, 4401, 5, 3, 1, 9, 46})}, - {"Camellia-256/OCB", OID({1, 3, 6, 1, 4, 1, 25258, 3, 2, 8})}, - {"Camellia-256/SIV", OID({1, 3, 6, 1, 4, 1, 25258, 3, 4, 8})}, - {"Certificate Comment", OID({2, 16, 840, 1, 113730, 1, 13})}, - {"ChaCha20Poly1305", OID({1, 2, 840, 113549, 1, 9, 16, 3, 18})}, - {"ClassicMcEliece_348864", OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 1})}, - {"ClassicMcEliece_348864f", OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 2})}, - {"ClassicMcEliece_460896", OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 3})}, - {"ClassicMcEliece_460896f", OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 4})}, - {"ClassicMcEliece_6688128", OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 5})}, - {"ClassicMcEliece_6688128f", OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 6})}, - {"ClassicMcEliece_6688128pc", OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 1})}, - {"ClassicMcEliece_6688128pcf", OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 2})}, - {"ClassicMcEliece_6960119", OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 7})}, - {"ClassicMcEliece_6960119f", OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 8})}, - {"ClassicMcEliece_6960119pc", OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 3})}, - {"ClassicMcEliece_6960119pcf", OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 4})}, - {"ClassicMcEliece_8192128", OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 9})}, - {"ClassicMcEliece_8192128f", OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 10})}, - {"ClassicMcEliece_8192128pc", OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 5})}, - {"ClassicMcEliece_8192128pcf", OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 6})}, - {"Compression.Zlib", OID({1, 2, 840, 113549, 1, 9, 16, 3, 8})}, - {"Curve25519", OID({1, 3, 101, 110})}, - {"DES/CBC", OID({1, 3, 14, 3, 2, 7})}, - {"DH", OID({1, 2, 840, 10046, 2, 1})}, - {"DSA", OID({1, 2, 840, 10040, 4, 1})}, - {"DSA/SHA-1", OID({1, 2, 840, 10040, 4, 3})}, - {"DSA/SHA-224", OID({2, 16, 840, 1, 101, 3, 4, 3, 1})}, - {"DSA/SHA-256", OID({2, 16, 840, 1, 101, 3, 4, 3, 2})}, - {"DSA/SHA-3(224)", OID({2, 16, 840, 1, 101, 3, 4, 3, 5})}, - {"DSA/SHA-3(256)", OID({2, 16, 840, 1, 101, 3, 4, 3, 6})}, - {"DSA/SHA-3(384)", OID({2, 16, 840, 1, 101, 3, 4, 3, 7})}, - {"DSA/SHA-3(512)", OID({2, 16, 840, 1, 101, 3, 4, 3, 8})}, - {"DSA/SHA-384", OID({2, 16, 840, 1, 101, 3, 4, 3, 3})}, - {"DSA/SHA-512", OID({2, 16, 840, 1, 101, 3, 4, 3, 4})}, - {"Dilithium-4x4-AES-r3", OID({1, 3, 6, 1, 4, 1, 25258, 1, 10, 1})}, - {"Dilithium-4x4-r3", OID({1, 3, 6, 1, 4, 1, 25258, 1, 9, 1})}, - {"Dilithium-6x5-AES-r3", OID({1, 3, 6, 1, 4, 1, 25258, 1, 10, 2})}, - {"Dilithium-6x5-r3", OID({1, 3, 6, 1, 4, 1, 25258, 1, 9, 2})}, - {"Dilithium-8x7-AES-r3", OID({1, 3, 6, 1, 4, 1, 25258, 1, 10, 3})}, - {"Dilithium-8x7-r3", OID({1, 3, 6, 1, 4, 1, 25258, 1, 9, 3})}, - {"ECDH", OID({1, 3, 132, 1, 12})}, - {"ECDSA", OID({1, 2, 840, 10045, 2, 1})}, - {"ECDSA/SHA-1", OID({1, 2, 840, 10045, 4, 1})}, - {"ECDSA/SHA-224", OID({1, 2, 840, 10045, 4, 3, 1})}, - {"ECDSA/SHA-256", OID({1, 2, 840, 10045, 4, 3, 2})}, - {"ECDSA/SHA-3(224)", OID({2, 16, 840, 1, 101, 3, 4, 3, 9})}, - {"ECDSA/SHA-3(256)", OID({2, 16, 840, 1, 101, 3, 4, 3, 10})}, - {"ECDSA/SHA-3(384)", OID({2, 16, 840, 1, 101, 3, 4, 3, 11})}, - {"ECDSA/SHA-3(512)", OID({2, 16, 840, 1, 101, 3, 4, 3, 12})}, - {"ECDSA/SHA-384", OID({1, 2, 840, 10045, 4, 3, 3})}, - {"ECDSA/SHA-512", OID({1, 2, 840, 10045, 4, 3, 4})}, - {"ECGDSA", OID({1, 3, 36, 3, 3, 2, 5, 2, 1})}, - {"ECGDSA/RIPEMD-160", OID({1, 3, 36, 3, 3, 2, 5, 4, 1})}, - {"ECGDSA/SHA-1", OID({1, 3, 36, 3, 3, 2, 5, 4, 2})}, - {"ECGDSA/SHA-224", OID({1, 3, 36, 3, 3, 2, 5, 4, 3})}, - {"ECGDSA/SHA-256", OID({1, 3, 36, 3, 3, 2, 5, 4, 4})}, - {"ECGDSA/SHA-384", OID({1, 3, 36, 3, 3, 2, 5, 4, 5})}, - {"ECGDSA/SHA-512", OID({1, 3, 36, 3, 3, 2, 5, 4, 6})}, - {"ECKCDSA", OID({1, 0, 14888, 3, 0, 5})}, - {"ECKCDSA/SHA-1", OID({1, 2, 410, 200004, 1, 100, 4, 3})}, - {"ECKCDSA/SHA-224", OID({1, 2, 410, 200004, 1, 100, 4, 4})}, - {"ECKCDSA/SHA-256", OID({1, 2, 410, 200004, 1, 100, 4, 5})}, - {"Ed25519", OID({1, 3, 101, 112})}, - {"Ed448", OID({1, 3, 101, 113})}, - {"ElGamal", OID({1, 3, 6, 1, 4, 1, 3029, 1, 2, 1})}, - {"FrodoKEM-1344-AES", OID({1, 3, 6, 1, 4, 1, 25258, 1, 15, 3})}, - {"FrodoKEM-1344-SHAKE", OID({1, 3, 6, 1, 4, 1, 25258, 1, 14, 3})}, - {"FrodoKEM-640-AES", OID({1, 3, 6, 1, 4, 1, 25258, 1, 15, 1})}, - {"FrodoKEM-640-SHAKE", OID({1, 3, 6, 1, 4, 1, 25258, 1, 14, 1})}, - {"FrodoKEM-976-AES", OID({1, 3, 6, 1, 4, 1, 25258, 1, 15, 2})}, - {"FrodoKEM-976-SHAKE", OID({1, 3, 6, 1, 4, 1, 25258, 1, 14, 2})}, - {"GOST-34.10", OID({1, 2, 643, 2, 2, 19})}, - {"GOST-34.10-2012-256", OID({1, 2, 643, 7, 1, 1, 1, 1})}, - {"GOST-34.10-2012-256/SHA-256", OID({1, 3, 6, 1, 4, 1, 25258, 1, 6, 1})}, - {"GOST-34.10-2012-256/Streebog-256", OID({1, 2, 643, 7, 1, 1, 3, 2})}, - {"GOST-34.10-2012-512", OID({1, 2, 643, 7, 1, 1, 1, 2})}, - {"GOST-34.10-2012-512/Streebog-512", OID({1, 2, 643, 7, 1, 1, 3, 3})}, - {"GOST-34.10/GOST-R-34.11-94", OID({1, 2, 643, 2, 2, 3})}, - {"GOST.INN", OID({1, 2, 643, 3, 131, 1, 1})}, - {"GOST.IssuerSigningTool", OID({1, 2, 643, 100, 112})}, - {"GOST.OGRN", OID({1, 2, 643, 100, 1})}, - {"GOST.SubjectSigningTool", OID({1, 2, 643, 100, 111})}, - {"HMAC(SHA-1)", OID({1, 2, 840, 113549, 2, 7})}, - {"HMAC(SHA-224)", OID({1, 2, 840, 113549, 2, 8})}, - {"HMAC(SHA-256)", OID({1, 2, 840, 113549, 2, 9})}, - {"HMAC(SHA-384)", OID({1, 2, 840, 113549, 2, 10})}, - {"HMAC(SHA-512)", OID({1, 2, 840, 113549, 2, 11})}, - {"HMAC(SHA-512-256)", OID({1, 2, 840, 113549, 2, 13})}, - {"HSS-LMS", OID({1, 2, 840, 113549, 1, 9, 16, 3, 17})}, - {"HSS-LMS-Private-Key", OID({1, 3, 6, 1, 4, 1, 25258, 1, 13})}, - {"KeyWrap.AES-128", OID({2, 16, 840, 1, 101, 3, 4, 1, 5})}, - {"KeyWrap.AES-192", OID({2, 16, 840, 1, 101, 3, 4, 1, 25})}, - {"KeyWrap.AES-256", OID({2, 16, 840, 1, 101, 3, 4, 1, 45})}, - {"KeyWrap.CAST-128", OID({1, 2, 840, 113533, 7, 66, 15})}, - {"KeyWrap.TripleDES", OID({1, 2, 840, 113549, 1, 9, 16, 3, 6})}, - {"Kyber-1024-90s-r3", OID({1, 3, 6, 1, 4, 1, 25258, 1, 11, 3})}, - {"Kyber-1024-r3", OID({1, 3, 6, 1, 4, 1, 25258, 1, 7, 3})}, - {"Kyber-512-90s-r3", OID({1, 3, 6, 1, 4, 1, 25258, 1, 11, 1})}, - {"Kyber-512-r3", OID({1, 3, 6, 1, 4, 1, 25258, 1, 7, 1})}, - {"Kyber-768-90s-r3", OID({1, 3, 6, 1, 4, 1, 25258, 1, 11, 2})}, - {"Kyber-768-r3", OID({1, 3, 6, 1, 4, 1, 25258, 1, 7, 2})}, - {"MD5", OID({1, 2, 840, 113549, 2, 5})}, - {"MGF1", OID({1, 2, 840, 113549, 1, 1, 8})}, - {"ML-DSA-4x4", OID({2, 16, 840, 1, 101, 3, 4, 3, 17})}, - {"ML-DSA-6x5", OID({2, 16, 840, 1, 101, 3, 4, 3, 18})}, - {"ML-DSA-8x7", OID({2, 16, 840, 1, 101, 3, 4, 3, 19})}, - {"ML-KEM-1024", OID({2, 16, 840, 1, 101, 3, 4, 4, 3})}, - {"ML-KEM-512", OID({2, 16, 840, 1, 101, 3, 4, 4, 1})}, - {"ML-KEM-768", OID({2, 16, 840, 1, 101, 3, 4, 4, 2})}, - {"McEliece", OID({1, 3, 6, 1, 4, 1, 25258, 1, 3})}, - {"Microsoft SmartcardLogon", OID({1, 3, 6, 1, 4, 1, 311, 20, 2, 2})}, - {"Microsoft UPN", OID({1, 3, 6, 1, 4, 1, 311, 20, 2, 3})}, - {"OpenPGP.Curve25519", OID({1, 3, 6, 1, 4, 1, 3029, 1, 5, 1})}, - {"OpenPGP.Ed25519", OID({1, 3, 6, 1, 4, 1, 11591, 15, 1})}, - {"PBE-PKCS5v20", OID({1, 2, 840, 113549, 1, 5, 13})}, - {"PBES2", OID({1, 2, 840, 113549, 1, 5, 13})}, - {"PKCS5.PBKDF2", OID({1, 2, 840, 113549, 1, 5, 12})}, - {"PKCS9.ChallengePassword", OID({1, 2, 840, 113549, 1, 9, 7})}, - {"PKCS9.ContentType", OID({1, 2, 840, 113549, 1, 9, 3})}, - {"PKCS9.EmailAddress", OID({1, 2, 840, 113549, 1, 9, 1})}, - {"PKCS9.ExtensionRequest", OID({1, 2, 840, 113549, 1, 9, 14})}, - {"PKCS9.MessageDigest", OID({1, 2, 840, 113549, 1, 9, 4})}, - {"PKCS9.UnstructuredName", OID({1, 2, 840, 113549, 1, 9, 2})}, - {"PKIX.AuthorityInformationAccess", OID({1, 3, 6, 1, 5, 5, 7, 1, 1})}, - {"PKIX.CertificateAuthorityIssuers", OID({1, 3, 6, 1, 5, 5, 7, 48, 2})}, - {"PKIX.ClientAuth", OID({1, 3, 6, 1, 5, 5, 7, 3, 2})}, - {"PKIX.CodeSigning", OID({1, 3, 6, 1, 5, 5, 7, 3, 3})}, - {"PKIX.EmailProtection", OID({1, 3, 6, 1, 5, 5, 7, 3, 4})}, - {"PKIX.IPsecEndSystem", OID({1, 3, 6, 1, 5, 5, 7, 3, 5})}, - {"PKIX.IPsecTunnel", OID({1, 3, 6, 1, 5, 5, 7, 3, 6})}, - {"PKIX.IPsecUser", OID({1, 3, 6, 1, 5, 5, 7, 3, 7})}, - {"PKIX.OCSP", OID({1, 3, 6, 1, 5, 5, 7, 48, 1})}, - {"PKIX.OCSP.BasicResponse", OID({1, 3, 6, 1, 5, 5, 7, 48, 1, 1})}, - {"PKIX.OCSP.NoCheck", OID({1, 3, 6, 1, 5, 5, 7, 48, 1, 5})}, - {"PKIX.OCSPSigning", OID({1, 3, 6, 1, 5, 5, 7, 3, 9})}, - {"PKIX.ServerAuth", OID({1, 3, 6, 1, 5, 5, 7, 3, 1})}, - {"PKIX.TNAuthList", OID({1, 3, 6, 1, 5, 5, 7, 1, 26})}, - {"PKIX.TimeStamping", OID({1, 3, 6, 1, 5, 5, 7, 3, 8})}, - {"PKIX.XMPPAddr", OID({1, 3, 6, 1, 5, 5, 7, 8, 5})}, - {"RIPEMD-160", OID({1, 3, 36, 3, 2, 1})}, - {"RSA", OID({1, 2, 840, 113549, 1, 1, 1})}, - {"RSA/EMSA3(MD2)", OID({1, 2, 840, 113549, 1, 1, 2})}, - {"RSA/EMSA3(MD5)", OID({1, 2, 840, 113549, 1, 1, 4})}, - {"RSA/EMSA3(RIPEMD-160)", OID({1, 3, 36, 3, 3, 1, 2})}, - {"RSA/EMSA3(SHA-1)", OID({1, 2, 840, 113549, 1, 1, 5})}, - {"RSA/EMSA3(SHA-224)", OID({1, 2, 840, 113549, 1, 1, 14})}, - {"RSA/EMSA3(SHA-256)", OID({1, 2, 840, 113549, 1, 1, 11})}, - {"RSA/EMSA3(SHA-3(224))", OID({2, 16, 840, 1, 101, 3, 4, 3, 13})}, - {"RSA/EMSA3(SHA-3(256))", OID({2, 16, 840, 1, 101, 3, 4, 3, 14})}, - {"RSA/EMSA3(SHA-3(384))", OID({2, 16, 840, 1, 101, 3, 4, 3, 15})}, - {"RSA/EMSA3(SHA-3(512))", OID({2, 16, 840, 1, 101, 3, 4, 3, 16})}, - {"RSA/EMSA3(SHA-384)", OID({1, 2, 840, 113549, 1, 1, 12})}, - {"RSA/EMSA3(SHA-512)", OID({1, 2, 840, 113549, 1, 1, 13})}, - {"RSA/EMSA3(SHA-512-256)", OID({1, 2, 840, 113549, 1, 1, 16})}, - {"RSA/EMSA3(SM3)", OID({1, 2, 156, 10197, 1, 504})}, - {"RSA/EMSA4", OID({1, 2, 840, 113549, 1, 1, 10})}, - {"RSA/OAEP", OID({1, 2, 840, 113549, 1, 1, 7})}, - {"RSA/PKCS1v15(MD2)", OID({1, 2, 840, 113549, 1, 1, 2})}, - {"RSA/PKCS1v15(MD5)", OID({1, 2, 840, 113549, 1, 1, 4})}, - {"RSA/PKCS1v15(RIPEMD-160)", OID({1, 3, 36, 3, 3, 1, 2})}, - {"RSA/PKCS1v15(SHA-1)", OID({1, 2, 840, 113549, 1, 1, 5})}, - {"RSA/PKCS1v15(SHA-224)", OID({1, 2, 840, 113549, 1, 1, 14})}, - {"RSA/PKCS1v15(SHA-256)", OID({1, 2, 840, 113549, 1, 1, 11})}, - {"RSA/PKCS1v15(SHA-3(224))", OID({2, 16, 840, 1, 101, 3, 4, 3, 13})}, - {"RSA/PKCS1v15(SHA-3(256))", OID({2, 16, 840, 1, 101, 3, 4, 3, 14})}, - {"RSA/PKCS1v15(SHA-3(384))", OID({2, 16, 840, 1, 101, 3, 4, 3, 15})}, - {"RSA/PKCS1v15(SHA-3(512))", OID({2, 16, 840, 1, 101, 3, 4, 3, 16})}, - {"RSA/PKCS1v15(SHA-384)", OID({1, 2, 840, 113549, 1, 1, 12})}, - {"RSA/PKCS1v15(SHA-512)", OID({1, 2, 840, 113549, 1, 1, 13})}, - {"RSA/PKCS1v15(SHA-512-256)", OID({1, 2, 840, 113549, 1, 1, 16})}, - {"RSA/PKCS1v15(SM3)", OID({1, 2, 156, 10197, 1, 504})}, - {"RSA/PSS", OID({1, 2, 840, 113549, 1, 1, 10})}, - {"SEED/CBC", OID({1, 2, 410, 200004, 1, 4})}, - {"SHA-1", OID({1, 3, 14, 3, 2, 26})}, - {"SHA-224", OID({2, 16, 840, 1, 101, 3, 4, 2, 4})}, - {"SHA-256", OID({2, 16, 840, 1, 101, 3, 4, 2, 1})}, - {"SHA-3(224)", OID({2, 16, 840, 1, 101, 3, 4, 2, 7})}, - {"SHA-3(256)", OID({2, 16, 840, 1, 101, 3, 4, 2, 8})}, - {"SHA-3(384)", OID({2, 16, 840, 1, 101, 3, 4, 2, 9})}, - {"SHA-3(512)", OID({2, 16, 840, 1, 101, 3, 4, 2, 10})}, - {"SHA-384", OID({2, 16, 840, 1, 101, 3, 4, 2, 2})}, - {"SHA-512", OID({2, 16, 840, 1, 101, 3, 4, 2, 3})}, - {"SHA-512-256", OID({2, 16, 840, 1, 101, 3, 4, 2, 6})}, - {"SHAKE-128", OID({2, 16, 840, 1, 101, 3, 4, 2, 11})}, - {"SHAKE-256", OID({2, 16, 840, 1, 101, 3, 4, 2, 12})}, - {"SLH-DSA-SHA2-128f", OID({2, 16, 840, 1, 101, 3, 4, 3, 21})}, - {"SLH-DSA-SHA2-128s", OID({2, 16, 840, 1, 101, 3, 4, 3, 20})}, - {"SLH-DSA-SHA2-192f", OID({2, 16, 840, 1, 101, 3, 4, 3, 23})}, - {"SLH-DSA-SHA2-192s", OID({2, 16, 840, 1, 101, 3, 4, 3, 22})}, - {"SLH-DSA-SHA2-256f", OID({2, 16, 840, 1, 101, 3, 4, 3, 25})}, - {"SLH-DSA-SHA2-256s", OID({2, 16, 840, 1, 101, 3, 4, 3, 24})}, - {"SLH-DSA-SHAKE-128f", OID({2, 16, 840, 1, 101, 3, 4, 3, 27})}, - {"SLH-DSA-SHAKE-128s", OID({2, 16, 840, 1, 101, 3, 4, 3, 26})}, - {"SLH-DSA-SHAKE-192f", OID({2, 16, 840, 1, 101, 3, 4, 3, 29})}, - {"SLH-DSA-SHAKE-192s", OID({2, 16, 840, 1, 101, 3, 4, 3, 28})}, - {"SLH-DSA-SHAKE-256f", OID({2, 16, 840, 1, 101, 3, 4, 3, 31})}, - {"SLH-DSA-SHAKE-256s", OID({2, 16, 840, 1, 101, 3, 4, 3, 30})}, - {"SM2", OID({1, 2, 156, 10197, 1, 301, 1})}, - {"SM2_Enc", OID({1, 2, 156, 10197, 1, 301, 3})}, - {"SM2_Kex", OID({1, 2, 156, 10197, 1, 301, 2})}, - {"SM2_Sig", OID({1, 2, 156, 10197, 1, 301, 1})}, - {"SM2_Sig/SM3", OID({1, 2, 156, 10197, 1, 501})}, - {"SM3", OID({1, 2, 156, 10197, 1, 401})}, - {"SM4/CBC", OID({1, 2, 156, 10197, 1, 104, 2})}, - {"SM4/GCM", OID({1, 2, 156, 10197, 1, 104, 8})}, - {"SM4/OCB", OID({1, 2, 156, 10197, 1, 104, 100})}, - {"SM4/SIV", OID({1, 3, 6, 1, 4, 1, 25258, 3, 4, 9})}, - {"Scrypt", OID({1, 3, 6, 1, 4, 1, 11591, 4, 11})}, - {"Serpent/CBC", OID({1, 3, 6, 1, 4, 1, 25258, 3, 1})}, - {"Serpent/GCM", OID({1, 3, 6, 1, 4, 1, 25258, 3, 101})}, - {"Serpent/OCB", OID({1, 3, 6, 1, 4, 1, 25258, 3, 2, 4})}, - {"Serpent/SIV", OID({1, 3, 6, 1, 4, 1, 25258, 3, 4, 4})}, - {"SphincsPlus-haraka-128f-r3.1", OID({1, 3, 6, 1, 4, 1, 25258, 1, 12, 3, 2})}, - {"SphincsPlus-haraka-128s-r3.1", OID({1, 3, 6, 1, 4, 1, 25258, 1, 12, 3, 1})}, - {"SphincsPlus-haraka-192f-r3.1", OID({1, 3, 6, 1, 4, 1, 25258, 1, 12, 3, 4})}, - {"SphincsPlus-haraka-192s-r3.1", OID({1, 3, 6, 1, 4, 1, 25258, 1, 12, 3, 3})}, - {"SphincsPlus-haraka-256f-r3.1", OID({1, 3, 6, 1, 4, 1, 25258, 1, 12, 3, 6})}, - {"SphincsPlus-haraka-256s-r3.1", OID({1, 3, 6, 1, 4, 1, 25258, 1, 12, 3, 5})}, - {"SphincsPlus-sha2-128f-r3.1", OID({1, 3, 6, 1, 4, 1, 25258, 1, 12, 2, 2})}, - {"SphincsPlus-sha2-128s-r3.1", OID({1, 3, 6, 1, 4, 1, 25258, 1, 12, 2, 1})}, - {"SphincsPlus-sha2-192f-r3.1", OID({1, 3, 6, 1, 4, 1, 25258, 1, 12, 2, 4})}, - {"SphincsPlus-sha2-192s-r3.1", OID({1, 3, 6, 1, 4, 1, 25258, 1, 12, 2, 3})}, - {"SphincsPlus-sha2-256f-r3.1", OID({1, 3, 6, 1, 4, 1, 25258, 1, 12, 2, 6})}, - {"SphincsPlus-sha2-256s-r3.1", OID({1, 3, 6, 1, 4, 1, 25258, 1, 12, 2, 5})}, - {"SphincsPlus-shake-128f-r3.1", OID({1, 3, 6, 1, 4, 1, 25258, 1, 12, 1, 2})}, - {"SphincsPlus-shake-128s-r3.1", OID({1, 3, 6, 1, 4, 1, 25258, 1, 12, 1, 1})}, - {"SphincsPlus-shake-192f-r3.1", OID({1, 3, 6, 1, 4, 1, 25258, 1, 12, 1, 4})}, - {"SphincsPlus-shake-192s-r3.1", OID({1, 3, 6, 1, 4, 1, 25258, 1, 12, 1, 3})}, - {"SphincsPlus-shake-256f-r3.1", OID({1, 3, 6, 1, 4, 1, 25258, 1, 12, 1, 6})}, - {"SphincsPlus-shake-256s-r3.1", OID({1, 3, 6, 1, 4, 1, 25258, 1, 12, 1, 5})}, - {"Streebog-256", OID({1, 2, 643, 7, 1, 1, 2, 2})}, - {"Streebog-512", OID({1, 2, 643, 7, 1, 1, 2, 3})}, - {"Threefish-512/CBC", OID({1, 3, 6, 1, 4, 1, 25258, 3, 2})}, - {"TripleDES/CBC", OID({1, 2, 840, 113549, 3, 7})}, - {"Twofish/CBC", OID({1, 3, 6, 1, 4, 1, 25258, 3, 3})}, - {"Twofish/GCM", OID({1, 3, 6, 1, 4, 1, 25258, 3, 102})}, - {"Twofish/OCB", OID({1, 3, 6, 1, 4, 1, 25258, 3, 2, 5})}, - {"Twofish/SIV", OID({1, 3, 6, 1, 4, 1, 25258, 3, 4, 5})}, - {"X25519", OID({1, 3, 101, 110})}, - {"X448", OID({1, 3, 101, 111})}, - {"X509v3.AnyPolicy", OID({2, 5, 29, 32, 0})}, - {"X509v3.AuthorityKeyIdentifier", OID({2, 5, 29, 35})}, - {"X509v3.BasicConstraints", OID({2, 5, 29, 19})}, - {"X509v3.CRLDistributionPoints", OID({2, 5, 29, 31})}, - {"X509v3.CRLIssuingDistributionPoint", OID({2, 5, 29, 28})}, - {"X509v3.CRLNumber", OID({2, 5, 29, 20})}, - {"X509v3.CertificatePolicies", OID({2, 5, 29, 32})}, - {"X509v3.ExtendedKeyUsage", OID({2, 5, 29, 37})}, - {"X509v3.HoldInstructionCode", OID({2, 5, 29, 23})}, - {"X509v3.InvalidityDate", OID({2, 5, 29, 24})}, - {"X509v3.IssuerAlternativeName", OID({2, 5, 29, 18})}, - {"X509v3.KeyUsage", OID({2, 5, 29, 15})}, - {"X509v3.NameConstraints", OID({2, 5, 29, 30})}, - {"X509v3.PolicyConstraints", OID({2, 5, 29, 36})}, - {"X509v3.PrivateKeyUsagePeriod", OID({2, 5, 29, 16})}, - {"X509v3.ReasonCode", OID({2, 5, 29, 21})}, - {"X509v3.SubjectAlternativeName", OID({2, 5, 29, 17})}, - {"X509v3.SubjectKeyIdentifier", OID({2, 5, 29, 14})}, - {"X520.CommonName", OID({2, 5, 4, 3})}, - {"X520.Country", OID({2, 5, 4, 6})}, - {"X520.DNQualifier", OID({2, 5, 4, 46})}, - {"X520.GenerationalQualifier", OID({2, 5, 4, 44})}, - {"X520.GivenName", OID({2, 5, 4, 42})}, - {"X520.Initials", OID({2, 5, 4, 43})}, - {"X520.Locality", OID({2, 5, 4, 7})}, - {"X520.Organization", OID({2, 5, 4, 10})}, - {"X520.OrganizationalUnit", OID({2, 5, 4, 11})}, - {"X520.Pseudonym", OID({2, 5, 4, 65})}, - {"X520.SerialNumber", OID({2, 5, 4, 5})}, - {"X520.State", OID({2, 5, 4, 8})}, - {"X520.StreetAddress", OID({2, 5, 4, 9})}, - {"X520.Surname", OID({2, 5, 4, 4})}, - {"X520.Title", OID({2, 5, 4, 12})}, - {"XMSS", OID({0, 4, 0, 127, 0, 15, 1, 1, 13, 0})}, - {"XMSS-draft12", OID({1, 3, 6, 1, 4, 1, 25258, 1, 8})}, - {"XMSS-draft6", OID({1, 3, 6, 1, 4, 1, 25258, 1, 5})}, - {"brainpool160r1", OID({1, 3, 36, 3, 3, 2, 8, 1, 1, 1})}, - {"brainpool192r1", OID({1, 3, 36, 3, 3, 2, 8, 1, 1, 3})}, - {"brainpool224r1", OID({1, 3, 36, 3, 3, 2, 8, 1, 1, 5})}, - {"brainpool256r1", OID({1, 3, 36, 3, 3, 2, 8, 1, 1, 7})}, - {"brainpool320r1", OID({1, 3, 36, 3, 3, 2, 8, 1, 1, 9})}, - {"brainpool384r1", OID({1, 3, 36, 3, 3, 2, 8, 1, 1, 11})}, - {"brainpool512r1", OID({1, 3, 36, 3, 3, 2, 8, 1, 1, 13})}, - {"eFrodoKEM-1344-AES", OID({1, 3, 6, 1, 4, 1, 25258, 1, 17, 3})}, - {"eFrodoKEM-1344-SHAKE", OID({1, 3, 6, 1, 4, 1, 25258, 1, 16, 3})}, - {"eFrodoKEM-640-AES", OID({1, 3, 6, 1, 4, 1, 25258, 1, 17, 1})}, - {"eFrodoKEM-640-SHAKE", OID({1, 3, 6, 1, 4, 1, 25258, 1, 16, 1})}, - {"eFrodoKEM-976-AES", OID({1, 3, 6, 1, 4, 1, 25258, 1, 17, 2})}, - {"eFrodoKEM-976-SHAKE", OID({1, 3, 6, 1, 4, 1, 25258, 1, 16, 2})}, - {"frp256v1", OID({1, 2, 250, 1, 223, 101, 256, 1})}, - {"gost_256A", OID({1, 2, 643, 7, 1, 2, 1, 1, 1})}, - {"gost_256B", OID({1, 2, 643, 7, 1, 2, 1, 1, 2})}, - {"gost_512A", OID({1, 2, 643, 7, 1, 2, 1, 2, 1})}, - {"gost_512B", OID({1, 2, 643, 7, 1, 2, 1, 2, 2})}, - {"numsp256d1", OID({1, 3, 6, 1, 4, 1, 25258, 4, 1})}, - {"numsp384d1", OID({1, 3, 6, 1, 4, 1, 25258, 4, 2})}, - {"numsp512d1", OID({1, 3, 6, 1, 4, 1, 25258, 4, 3})}, - {"secp160k1", OID({1, 3, 132, 0, 9})}, - {"secp160r1", OID({1, 3, 132, 0, 8})}, - {"secp160r2", OID({1, 3, 132, 0, 30})}, - {"secp192k1", OID({1, 3, 132, 0, 31})}, - {"secp192r1", OID({1, 2, 840, 10045, 3, 1, 1})}, - {"secp224k1", OID({1, 3, 132, 0, 32})}, - {"secp224r1", OID({1, 3, 132, 0, 33})}, - {"secp256k1", OID({1, 3, 132, 0, 10})}, - {"secp256r1", OID({1, 2, 840, 10045, 3, 1, 7})}, - {"secp384r1", OID({1, 3, 132, 0, 34})}, - {"secp521r1", OID({1, 3, 132, 0, 35})}, - {"sm2p256v1", OID({1, 2, 156, 10197, 1, 301})}, - {"x962_p192v2", OID({1, 2, 840, 10045, 3, 1, 2})}, - {"x962_p192v3", OID({1, 2, 840, 10045, 3, 1, 3})}, - {"x962_p239v1", OID({1, 2, 840, 10045, 3, 1, 4})}, - {"x962_p239v2", OID({1, 2, 840, 10045, 3, 1, 5})}, - {"x962_p239v3", OID({1, 2, 840, 10045, 3, 1, 6})}}; -} - -} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/asn1/pss_params.cpp botan3-3.12.0+dfsg/src/lib/asn1/pss_params.cpp --- botan3-3.7.1+dfsg/src/lib/asn1/pss_params.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/asn1/pss_params.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,6 +7,7 @@ #include +#include #include #include #include @@ -15,15 +16,15 @@ namespace Botan { //static -PSS_Params PSS_Params::from_emsa_name(std::string_view emsa_name) { - SCAN_Name scanner(emsa_name); +PSS_Params PSS_Params::from_padding_name(std::string_view padding_name) { + const SCAN_Name scanner(padding_name); if((scanner.algo_name() != "PSS" && scanner.algo_name() != "PSS_Raw") || scanner.arg_count() != 3) { - throw Invalid_Argument(fmt("PSS_Params::from_emsa_name unexpected param '{}'", emsa_name)); + throw Invalid_Argument(fmt("PSS_Params::from_padding_name unexpected param '{}'", padding_name)); } const std::string hash_fn = scanner.arg(0); - BOTAN_ASSERT_NOMSG(scanner.arg(1) == "MGF1"); + BOTAN_ARG_CHECK(scanner.arg(1) == "MGF1", "PSS requires MGF1"); const size_t salt_len = scanner.arg_as_integer(2); return PSS_Params(hash_fn, salt_len); } @@ -32,11 +33,13 @@ m_hash(hash_fn, AlgorithmIdentifier::USE_NULL_PARAM), m_mgf("MGF1", m_hash.BER_encode()), m_mgf_hash(m_hash), - m_salt_len(salt_len) {} + m_salt_len(salt_len), + m_trailer_field(1) {} -PSS_Params::PSS_Params(std::span der) { - BER_Decoder decoder(der); +PSS_Params::PSS_Params(std::span der) : m_salt_len(0), m_trailer_field(1) { + BER_Decoder decoder(der, BER_Decoder::Limits::DER()); this->decode_from(decoder); + decoder.verify_end(); } std::vector PSS_Params::serialize() const { @@ -46,8 +49,6 @@ } void PSS_Params::encode_into(DER_Encoder& to) const { - const size_t trailer_field = 1; - to.start_sequence() .start_context_specific(0) .encode(m_hash) @@ -58,9 +59,6 @@ .start_context_specific(2) .encode(m_salt_len) .end_cons() - .start_context_specific(3) - .encode(trailer_field) - .end_cons() .end_cons(); } @@ -77,7 +75,7 @@ .decode_optional(m_trailer_field, ASN1_Type(3), ASN1_Class::ExplicitContextSpecific, default_trailer) .end_cons(); - BER_Decoder(m_mgf.parameters()).decode(m_mgf_hash); + BER_Decoder(m_mgf.parameters(), from.limits()).decode(m_mgf_hash); } } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/asn1/pss_params.h botan3-3.12.0+dfsg/src/lib/asn1/pss_params.h --- botan3-3.7.1+dfsg/src/lib/asn1/pss_params.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/asn1/pss_params.h 2026-05-07 01:38:28.000000000 +0000 @@ -25,17 +25,27 @@ public: /** * Note that the only valid strings you can pass to this function - * are values returned by EMSA::name() and these may change in a - * minor release. + * are values returned by SignaturePaddingScheme::name() and + * these may change in a minor release. */ - static PSS_Params from_emsa_name(std::string_view emsa_name); + static PSS_Params from_padding_name(std::string_view padding_name); + + /** + * Note that the only valid strings you can pass to this function + * are values returned by SignaturePaddingScheme::name() and + * these may change in a minor release. + */ + BOTAN_DEPRECATED("Use PSS_Params::from_padding_name") + static PSS_Params from_emsa_name(std::string_view padding_name) { + return PSS_Params::from_padding_name(padding_name); + } PSS_Params(std::string_view hash_fn, size_t salt_len); /** * Decode an encoded RSASSA-PSS-params */ - PSS_Params(std::span der); + BOTAN_FUTURE_EXPLICIT PSS_Params(std::span der); const AlgorithmIdentifier& hash_algid() const { return m_hash; } diff -Nru botan3-3.7.1+dfsg/src/lib/asn1/static_oids.cpp botan3-3.12.0+dfsg/src/lib/asn1/static_oids.cpp --- botan3-3.7.1+dfsg/src/lib/asn1/static_oids.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/asn1/static_oids.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,1459 @@ +/* +* This file was automatically generated by ./src/scripts/dev_tools/gen_oids.py on 2026-04-24 +* All manual changes will be lost. Edit the script instead. +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include + +namespace Botan { + +namespace { + +// The hash can collide so we must verify the actual value matches before returning +std::optional if_match(const OID& oid, std::initializer_list val, std::string_view name) { + if(oid.matches(val)) { + return name; + } else { + return {}; + } +} + +std::optional if_match(std::string_view req, std::string_view actual, std::initializer_list oid) { + if(req == actual) { + return OID(oid); + } else { + return {}; + } +} + +uint32_t hash_oid_name(std::string_view s) { + uint64_t hash = 0x8188B31879A4879A; + + for(const char c : s) { + hash *= 251; + hash += c; + } + + return static_cast(hash % 805289); +} + +} // namespace + +//static +std::optional OID_Map::lookup_static_oid(const OID& oid) { + const uint32_t hc = static_cast(oid.hash_code() % 858701); + + switch(hc) { + case 0x01506: + return if_match(oid, {1, 2, 840, 10045, 4, 3, 1}, "ECDSA/SHA-224"); + case 0x01507: + return if_match(oid, {1, 2, 840, 10045, 4, 3, 2}, "ECDSA/SHA-256"); + case 0x01508: + return if_match(oid, {1, 2, 840, 10045, 4, 3, 3}, "ECDSA/SHA-384"); + case 0x01509: + return if_match(oid, {1, 2, 840, 10045, 4, 3, 4}, "ECDSA/SHA-512"); + case 0x04C1E: + return if_match(oid, {1, 3, 6, 1, 4, 1, 3029, 1, 2, 1}, "ElGamal"); + case 0x04E61: + return if_match(oid, {1, 3, 6, 1, 4, 1, 3029, 1, 5, 1}, "OpenPGP.Curve25519"); + case 0x0779B: + return if_match(oid, {1, 2, 840, 113549, 2, 5}, "MD5"); + case 0x0779D: + return if_match(oid, {1, 2, 840, 113549, 2, 7}, "HMAC(SHA-1)"); + case 0x0779E: + return if_match(oid, {1, 2, 840, 113549, 2, 8}, "HMAC(SHA-224)"); + case 0x0779F: + return if_match(oid, {1, 2, 840, 113549, 2, 9}, "HMAC(SHA-256)"); + case 0x077A0: + return if_match(oid, {1, 2, 840, 113549, 2, 10}, "HMAC(SHA-384)"); + case 0x077A1: + return if_match(oid, {1, 2, 840, 113549, 2, 11}, "HMAC(SHA-512)"); + case 0x077A3: + return if_match(oid, {1, 2, 840, 113549, 2, 13}, "HMAC(SHA-512-256)"); + case 0x0785E: + return if_match(oid, {1, 2, 840, 113549, 3, 7}, "TripleDES/CBC"); + case 0x0C904: + return if_match(oid, {1, 0, 14888, 3, 0, 5}, "ECKCDSA"); + case 0x11547: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 12, 1, 1}, "SphincsPlus-shake-128s-r3.1"); + case 0x11548: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 12, 1, 2}, "SphincsPlus-shake-128f-r3.1"); + case 0x11549: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 12, 1, 3}, "SphincsPlus-shake-192s-r3.1"); + case 0x1154A: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 12, 1, 4}, "SphincsPlus-shake-192f-r3.1"); + case 0x1154B: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 12, 1, 5}, "SphincsPlus-shake-256s-r3.1"); + case 0x1154C: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 12, 1, 6}, "SphincsPlus-shake-256f-r3.1"); + case 0x11608: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 12, 2, 1}, "SphincsPlus-sha2-128s-r3.1"); + case 0x11609: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 12, 2, 2}, "SphincsPlus-sha2-128f-r3.1"); + case 0x1160A: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 12, 2, 3}, "SphincsPlus-sha2-192s-r3.1"); + case 0x1160B: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 12, 2, 4}, "SphincsPlus-sha2-192f-r3.1"); + case 0x1160C: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 12, 2, 5}, "SphincsPlus-sha2-256s-r3.1"); + case 0x1160D: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 12, 2, 6}, "SphincsPlus-sha2-256f-r3.1"); + case 0x116C9: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 12, 3, 1}, "SphincsPlus-haraka-128s-r3.1"); + case 0x116CA: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 12, 3, 2}, "SphincsPlus-haraka-128f-r3.1"); + case 0x116CB: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 12, 3, 3}, "SphincsPlus-haraka-192s-r3.1"); + case 0x116CC: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 12, 3, 4}, "SphincsPlus-haraka-192f-r3.1"); + case 0x116CD: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 12, 3, 5}, "SphincsPlus-haraka-256s-r3.1"); + case 0x116CE: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 12, 3, 6}, "SphincsPlus-haraka-256f-r3.1"); + case 0x1533B: + return if_match(oid, {1, 2, 156, 10197, 1, 104, 2}, "SM4/CBC"); + case 0x15341: + return if_match(oid, {1, 2, 156, 10197, 1, 104, 8}, "SM4/GCM"); + case 0x1539D: + return if_match(oid, {1, 2, 156, 10197, 1, 104, 100}, "SM4/OCB"); + case 0x187D7: + return if_match(oid, {1, 3, 14, 3, 2, 7}, "DES/CBC"); + case 0x187EA: + return if_match(oid, {1, 3, 14, 3, 2, 26}, "SHA-1"); + case 0x19933: + return if_match(oid, {1, 3, 132, 0, 8}, "secp160r1"); + case 0x19934: + return if_match(oid, {1, 3, 132, 0, 9}, "secp160k1"); + case 0x19935: + return if_match(oid, {1, 3, 132, 0, 10}, "secp256k1"); + case 0x19949: + return if_match(oid, {1, 3, 132, 0, 30}, "secp160r2"); + case 0x1994A: + return if_match(oid, {1, 3, 132, 0, 31}, "secp192k1"); + case 0x1994B: + return if_match(oid, {1, 3, 132, 0, 32}, "secp224k1"); + case 0x1994C: + return if_match(oid, {1, 3, 132, 0, 33}, "secp224r1"); + case 0x1994D: + return if_match(oid, {1, 3, 132, 0, 34}, "secp384r1"); + case 0x1994E: + return if_match(oid, {1, 3, 132, 0, 35}, "secp521r1"); + case 0x199F8: + return if_match(oid, {1, 3, 132, 1, 12}, "ECDH"); + case 0x1E7BF: + return if_match(oid, {1, 2, 156, 10197, 1, 301, 1}, "SM2"); + case 0x1E7C0: + return if_match(oid, {1, 2, 156, 10197, 1, 301, 2}, "SM2_Kex"); + case 0x1E7C1: + return if_match(oid, {1, 2, 156, 10197, 1, 301, 3}, "SM2_Enc"); + case 0x21960: + return if_match(oid, {1, 3, 36, 3, 3, 1, 2}, "RSA/PKCS1v15(RIPEMD-160)"); + case 0x2198A: + return if_match(oid, {1, 2, 840, 113533, 7, 66, 10}, "CAST-128/CBC"); + case 0x2198F: + return if_match(oid, {1, 2, 840, 113533, 7, 66, 15}, "KeyWrap.CAST-128"); + case 0x227C0: + return if_match(oid, {1, 3, 101, 110}, "X25519"); + case 0x227C1: + return if_match(oid, {1, 3, 101, 111}, "X448"); + case 0x227C2: + return if_match(oid, {1, 3, 101, 112}, "Ed25519"); + case 0x227C3: + return if_match(oid, {1, 3, 101, 113}, "Ed448"); + case 0x27565: + return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 48, 1, 1}, "PKIX.OCSP.BasicResponse"); + case 0x27569: + return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 48, 1, 5}, "PKIX.OCSP.NoCheck"); + case 0x29F7C: + return if_match(oid, {1, 2, 410, 200004, 1, 100, 4, 3}, "ECKCDSA/SHA-1"); + case 0x29F7D: + return if_match(oid, {1, 2, 410, 200004, 1, 100, 4, 4}, "ECKCDSA/SHA-224"); + case 0x29F7E: + return if_match(oid, {1, 2, 410, 200004, 1, 100, 4, 5}, "ECKCDSA/SHA-256"); + case 0x2AC3B: + return if_match(oid, {2, 5, 29, 32, 0}, "X509v3.AnyPolicy"); + case 0x2B000: + return if_match(oid, {2, 5, 29, 37, 0}, "X509v3.AnyExtendedKeyUsage"); + case 0x2B5C9: + return if_match(oid, {1, 2, 840, 10045, 2, 1}, "ECDSA"); + case 0x2B74B: + return if_match(oid, {1, 2, 840, 10045, 4, 1}, "ECDSA/SHA-1"); + case 0x3474A: + return if_match(oid, {1, 2, 840, 10046, 2, 1}, "DH"); + case 0x38D6D: + return if_match(oid, {1, 2, 643, 7, 1, 2, 1, 1, 1}, "gost_256A"); + case 0x38D6E: + return if_match(oid, {1, 2, 643, 7, 1, 2, 1, 1, 2}, "gost_256B"); + case 0x38E2E: + return if_match(oid, {1, 2, 643, 7, 1, 2, 1, 2, 1}, "gost_512A"); + case 0x38E2F: + return if_match(oid, {1, 2, 643, 7, 1, 2, 1, 2, 2}, "gost_512B"); + case 0x38F2C: + return if_match(oid, {1, 2, 643, 2, 2, 3}, "GOST-34.10/GOST-R-34.11-94"); + case 0x38F3C: + return if_match(oid, {1, 2, 643, 2, 2, 19}, "GOST-34.10"); + case 0x3D7B8: + return if_match(oid, {0, 3, 4401, 5, 3, 1, 9, 6}, "Camellia-128/GCM"); + case 0x3D7CC: + return if_match(oid, {0, 3, 4401, 5, 3, 1, 9, 26}, "Camellia-192/GCM"); + case 0x3D7E0: + return if_match(oid, {0, 3, 4401, 5, 3, 1, 9, 46}, "Camellia-256/GCM"); + case 0x3F20F: + return if_match(oid, {1, 3, 36, 3, 2, 1}, "RIPEMD-160"); + case 0x4266E: + return if_match(oid, {0, 4, 0, 127, 0, 15, 1, 1, 13, 0}, "XMSS"); + case 0x478C4: + return if_match(oid, {1, 2, 410, 200004, 1, 4}, "SEED/CBC"); + case 0x47D98: + return if_match(oid, {1, 2, 156, 10197, 1, 301}, "sm2p256v1"); + case 0x47DFC: + return if_match(oid, {1, 2, 156, 10197, 1, 401}, "SM3"); + case 0x47E60: + return if_match(oid, {1, 2, 156, 10197, 1, 501}, "SM2_Sig/SM3"); + case 0x47E63: + return if_match(oid, {1, 2, 156, 10197, 1, 504}, "RSA/PKCS1v15(SM3)"); + case 0x52B13: + return if_match(oid, {1, 2, 643, 3, 131, 1, 1}, "GOST.INN"); + case 0x635AE: + return if_match(oid, {1, 2, 250, 1, 223, 101, 256, 1}, "frp256v1"); + case 0x6A784: + return if_match(oid, {1, 2, 840, 113549, 1, 12, 10, 1, 1}, "PKCS12.KeyBag"); + case 0x6A785: + return if_match(oid, {1, 2, 840, 113549, 1, 12, 10, 1, 2}, "PKCS12.PKCS8ShroudedKeyBag"); + case 0x6A786: + return if_match(oid, {1, 2, 840, 113549, 1, 12, 10, 1, 3}, "PKCS12.CertBag"); + case 0x6A787: + return if_match(oid, {1, 2, 840, 113549, 1, 12, 10, 1, 4}, "PKCS12.CRLBag"); + case 0x6A788: + return if_match(oid, {1, 2, 840, 113549, 1, 12, 10, 1, 5}, "PKCS12.SecretBag"); + case 0x6A789: + return if_match(oid, {1, 2, 840, 113549, 1, 12, 10, 1, 6}, "PKCS12.SafeContentsBag"); + case 0x6EB86: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 6, 1}, "GOST-34.10-2012-256/SHA-256"); + case 0x6EC47: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 7, 1}, "Kyber-512-r3"); + case 0x6EC48: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 7, 2}, "Kyber-768-r3"); + case 0x6EC49: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 7, 3}, "Kyber-1024-r3"); + case 0x6EDC9: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 9, 1}, "Dilithium-4x4-r3"); + case 0x6EDCA: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 9, 2}, "Dilithium-6x5-r3"); + case 0x6EDCB: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 9, 3}, "Dilithium-8x7-r3"); + case 0x6EE8A: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 10, 1}, "Dilithium-4x4-AES-r3"); + case 0x6EE8B: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 10, 2}, "Dilithium-6x5-AES-r3"); + case 0x6EE8C: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 10, 3}, "Dilithium-8x7-AES-r3"); + case 0x6EF4B: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 11, 1}, "Kyber-512-90s-r3"); + case 0x6EF4C: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 11, 2}, "Kyber-768-90s-r3"); + case 0x6EF4D: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 11, 3}, "Kyber-1024-90s-r3"); + case 0x6F18E: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 14, 1}, "FrodoKEM-640-SHAKE"); + case 0x6F18F: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 14, 2}, "FrodoKEM-976-SHAKE"); + case 0x6F190: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 14, 3}, "FrodoKEM-1344-SHAKE"); + case 0x6F24F: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 15, 1}, "FrodoKEM-640-AES"); + case 0x6F250: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 15, 2}, "FrodoKEM-976-AES"); + case 0x6F251: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 15, 3}, "FrodoKEM-1344-AES"); + case 0x6F310: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 16, 1}, "eFrodoKEM-640-SHAKE"); + case 0x6F311: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 16, 2}, "eFrodoKEM-976-SHAKE"); + case 0x6F312: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 16, 3}, "eFrodoKEM-1344-SHAKE"); + case 0x6F3D1: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 17, 1}, "eFrodoKEM-640-AES"); + case 0x6F3D2: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 17, 2}, "eFrodoKEM-976-AES"); + case 0x6F3D3: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 17, 3}, "eFrodoKEM-1344-AES"); + case 0x6F492: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 18, 1}, "ClassicMcEliece_6688128pc"); + case 0x6F493: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 18, 2}, "ClassicMcEliece_6688128pcf"); + case 0x6F494: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 18, 3}, "ClassicMcEliece_6960119pc"); + case 0x6F495: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 18, 4}, "ClassicMcEliece_6960119pcf"); + case 0x6F496: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 18, 5}, "ClassicMcEliece_8192128pc"); + case 0x6F497: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 18, 6}, "ClassicMcEliece_8192128pcf"); + case 0x6F79D: + return if_match(oid, {2, 16, 840, 1, 113730, 1, 13}, "Certificate Comment"); + case 0x701A0: + return if_match(oid, {1, 3, 36, 3, 3, 2, 5, 2, 1}, "ECGDSA"); + case 0x70322: + return if_match(oid, {1, 3, 36, 3, 3, 2, 5, 4, 1}, "ECGDSA/RIPEMD-160"); + case 0x70323: + return if_match(oid, {1, 3, 36, 3, 3, 2, 5, 4, 2}, "ECGDSA/SHA-1"); + case 0x70324: + return if_match(oid, {1, 3, 36, 3, 3, 2, 5, 4, 3}, "ECGDSA/SHA-224"); + case 0x70325: + return if_match(oid, {1, 3, 36, 3, 3, 2, 5, 4, 4}, "ECGDSA/SHA-256"); + case 0x70326: + return if_match(oid, {1, 3, 36, 3, 3, 2, 5, 4, 5}, "ECGDSA/SHA-384"); + case 0x70327: + return if_match(oid, {1, 3, 36, 3, 3, 2, 5, 4, 6}, "ECGDSA/SHA-512"); + case 0x72B21: + return if_match(oid, {1, 2, 643, 7, 1, 1, 1, 1}, "GOST-34.10-2012-256"); + case 0x72B22: + return if_match(oid, {1, 2, 643, 7, 1, 1, 1, 2}, "GOST-34.10-2012-512"); + case 0x72BE3: + return if_match(oid, {1, 2, 643, 7, 1, 1, 2, 2}, "Streebog-256"); + case 0x72BE4: + return if_match(oid, {1, 2, 643, 7, 1, 1, 2, 3}, "Streebog-512"); + case 0x72CA4: + return if_match(oid, {1, 2, 643, 7, 1, 1, 3, 2}, "GOST-34.10-2012-256/Streebog-256"); + case 0x72CA5: + return if_match(oid, {1, 2, 643, 7, 1, 1, 3, 3}, "GOST-34.10-2012-512/Streebog-512"); + case 0x7C7C7: + return if_match(oid, {1, 2, 840, 113549, 1, 9, 22, 1}, "PKCS9.X509Certificate"); + case 0x7C7C8: + return if_match(oid, {1, 2, 840, 113549, 1, 9, 22, 2}, "PKCS9.SDSICertificate"); + case 0x7C888: + return if_match(oid, {1, 2, 840, 113549, 1, 9, 23, 1}, "PKCS9.X509CRL"); + case 0x7E10F: + return if_match(oid, {2, 5, 4, 3}, "X520.CommonName"); + case 0x7E110: + return if_match(oid, {2, 5, 4, 4}, "X520.Surname"); + case 0x7E111: + return if_match(oid, {2, 5, 4, 5}, "X520.SerialNumber"); + case 0x7E112: + return if_match(oid, {2, 5, 4, 6}, "X520.Country"); + case 0x7E113: + return if_match(oid, {2, 5, 4, 7}, "X520.Locality"); + case 0x7E114: + return if_match(oid, {2, 5, 4, 8}, "X520.State"); + case 0x7E115: + return if_match(oid, {2, 5, 4, 9}, "X520.StreetAddress"); + case 0x7E116: + return if_match(oid, {2, 5, 4, 10}, "X520.Organization"); + case 0x7E117: + return if_match(oid, {2, 5, 4, 11}, "X520.OrganizationalUnit"); + case 0x7E118: + return if_match(oid, {2, 5, 4, 12}, "X520.Title"); + case 0x7E136: + return if_match(oid, {2, 5, 4, 42}, "X520.GivenName"); + case 0x7E137: + return if_match(oid, {2, 5, 4, 43}, "X520.Initials"); + case 0x7E138: + return if_match(oid, {2, 5, 4, 44}, "X520.GenerationalQualifier"); + case 0x7E13A: + return if_match(oid, {2, 5, 4, 46}, "X520.DNQualifier"); + case 0x7E14D: + return if_match(oid, {2, 5, 4, 65}, "X520.Pseudonym"); + case 0x7F3F3: + return if_match(oid, {2, 5, 29, 14}, "X509v3.SubjectKeyIdentifier"); + case 0x7F3F4: + return if_match(oid, {2, 5, 29, 15}, "X509v3.KeyUsage"); + case 0x7F3F5: + return if_match(oid, {2, 5, 29, 16}, "X509v3.PrivateKeyUsagePeriod"); + case 0x7F3F6: + return if_match(oid, {2, 5, 29, 17}, "X509v3.SubjectAlternativeName"); + case 0x7F3F7: + return if_match(oid, {2, 5, 29, 18}, "X509v3.IssuerAlternativeName"); + case 0x7F3F8: + return if_match(oid, {2, 5, 29, 19}, "X509v3.BasicConstraints"); + case 0x7F3F9: + return if_match(oid, {2, 5, 29, 20}, "X509v3.CRLNumber"); + case 0x7F3FA: + return if_match(oid, {2, 5, 29, 21}, "X509v3.ReasonCode"); + case 0x7F3FC: + return if_match(oid, {2, 5, 29, 23}, "X509v3.HoldInstructionCode"); + case 0x7F3FD: + return if_match(oid, {2, 5, 29, 24}, "X509v3.InvalidityDate"); + case 0x7F401: + return if_match(oid, {2, 5, 29, 28}, "X509v3.CRLIssuingDistributionPoint"); + case 0x7F403: + return if_match(oid, {2, 5, 29, 30}, "X509v3.NameConstraints"); + case 0x7F404: + return if_match(oid, {2, 5, 29, 31}, "X509v3.CRLDistributionPoints"); + case 0x7F405: + return if_match(oid, {2, 5, 29, 32}, "X509v3.CertificatePolicies"); + case 0x7F408: + return if_match(oid, {2, 5, 29, 35}, "X509v3.AuthorityKeyIdentifier"); + case 0x7F409: + return if_match(oid, {2, 5, 29, 36}, "X509v3.PolicyConstraints"); + case 0x7F40A: + return if_match(oid, {2, 5, 29, 37}, "X509v3.ExtendedKeyUsage"); + case 0x80B84: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 3, 2, 1}, "AES-128/OCB"); + case 0x80B85: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 3, 2, 2}, "AES-192/OCB"); + case 0x80B86: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 3, 2, 3}, "AES-256/OCB"); + case 0x80B87: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 3, 2, 4}, "Serpent/OCB"); + case 0x80B88: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 3, 2, 5}, "Twofish/OCB"); + case 0x80B89: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 3, 2, 6}, "Camellia-128/OCB"); + case 0x80B8A: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 3, 2, 7}, "Camellia-192/OCB"); + case 0x80B8B: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 3, 2, 8}, "Camellia-256/OCB"); + case 0x80D06: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 3, 4, 1}, "AES-128/SIV"); + case 0x80D07: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 3, 4, 2}, "AES-192/SIV"); + case 0x80D08: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 3, 4, 3}, "AES-256/SIV"); + case 0x80D09: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 3, 4, 4}, "Serpent/SIV"); + case 0x80D0A: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 3, 4, 5}, "Twofish/SIV"); + case 0x80D0B: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 3, 4, 6}, "Camellia-128/SIV"); + case 0x80D0C: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 3, 4, 7}, "Camellia-192/SIV"); + case 0x80D0D: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 3, 4, 8}, "Camellia-256/SIV"); + case 0x80D0E: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 3, 4, 9}, "SM4/SIV"); + case 0x84C6A: + return if_match(oid, {1, 2, 392, 200011, 61, 1, 1, 1, 2}, "Camellia-128/CBC"); + case 0x84C6B: + return if_match(oid, {1, 2, 392, 200011, 61, 1, 1, 1, 3}, "Camellia-192/CBC"); + case 0x84C6C: + return if_match(oid, {1, 2, 392, 200011, 61, 1, 1, 1, 4}, "Camellia-256/CBC"); + case 0x88CD3: + return if_match(oid, {1, 2, 840, 113549, 1, 9, 16, 3, 6}, "KeyWrap.TripleDES"); + case 0x88CD5: + return if_match(oid, {1, 2, 840, 113549, 1, 9, 16, 3, 8}, "Compression.Zlib"); + case 0x88CDE: + return if_match(oid, {1, 2, 840, 113549, 1, 9, 16, 3, 17}, "HSS-LMS"); + case 0x88CDF: + return if_match(oid, {1, 2, 840, 113549, 1, 9, 16, 3, 18}, "ChaCha20Poly1305"); + case 0x92296: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 1, 2}, "AES-128/CBC"); + case 0x92299: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 1, 5}, "KeyWrap.AES-128"); + case 0x9229A: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 1, 6}, "AES-128/GCM"); + case 0x9229B: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 1, 7}, "AES-128/CCM"); + case 0x922AA: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 1, 22}, "AES-192/CBC"); + case 0x922AD: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 1, 25}, "KeyWrap.AES-192"); + case 0x922AE: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 1, 26}, "AES-192/GCM"); + case 0x922AF: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 1, 27}, "AES-192/CCM"); + case 0x922BE: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 1, 42}, "AES-256/CBC"); + case 0x922C1: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 1, 45}, "KeyWrap.AES-256"); + case 0x922C2: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 1, 46}, "AES-256/GCM"); + case 0x922C3: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 1, 47}, "AES-256/CCM"); + case 0x92356: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 2, 1}, "SHA-256"); + case 0x92357: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 2, 2}, "SHA-384"); + case 0x92358: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 2, 3}, "SHA-512"); + case 0x92359: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 2, 4}, "SHA-224"); + case 0x9235B: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 2, 6}, "SHA-512-256"); + case 0x9235C: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 2, 7}, "SHA-3(224)"); + case 0x9235D: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 2, 8}, "SHA-3(256)"); + case 0x9235E: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 2, 9}, "SHA-3(384)"); + case 0x9235F: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 2, 10}, "SHA-3(512)"); + case 0x92360: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 2, 11}, "SHAKE-128"); + case 0x92361: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 2, 12}, "SHAKE-256"); + case 0x92417: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 3, 1}, "DSA/SHA-224"); + case 0x92418: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 3, 2}, "DSA/SHA-256"); + case 0x92419: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 3, 3}, "DSA/SHA-384"); + case 0x9241A: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 3, 4}, "DSA/SHA-512"); + case 0x9241B: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 3, 5}, "DSA/SHA-3(224)"); + case 0x9241C: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 3, 6}, "DSA/SHA-3(256)"); + case 0x9241D: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 3, 7}, "DSA/SHA-3(384)"); + case 0x9241E: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 3, 8}, "DSA/SHA-3(512)"); + case 0x9241F: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 3, 9}, "ECDSA/SHA-3(224)"); + case 0x92420: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 3, 10}, "ECDSA/SHA-3(256)"); + case 0x92421: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 3, 11}, "ECDSA/SHA-3(384)"); + case 0x92422: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 3, 12}, "ECDSA/SHA-3(512)"); + case 0x92423: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 3, 13}, "RSA/PKCS1v15(SHA-3(224))"); + case 0x92424: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 3, 14}, "RSA/PKCS1v15(SHA-3(256))"); + case 0x92425: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 3, 15}, "RSA/PKCS1v15(SHA-3(384))"); + case 0x92426: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 3, 16}, "RSA/PKCS1v15(SHA-3(512))"); + case 0x92427: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 3, 17}, "ML-DSA-4x4"); + case 0x92428: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 3, 18}, "ML-DSA-6x5"); + case 0x92429: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 3, 19}, "ML-DSA-8x7"); + case 0x9242A: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 3, 20}, "SLH-DSA-SHA2-128s"); + case 0x9242B: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 3, 21}, "SLH-DSA-SHA2-128f"); + case 0x9242C: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 3, 22}, "SLH-DSA-SHA2-192s"); + case 0x9242D: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 3, 23}, "SLH-DSA-SHA2-192f"); + case 0x9242E: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 3, 24}, "SLH-DSA-SHA2-256s"); + case 0x9242F: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 3, 25}, "SLH-DSA-SHA2-256f"); + case 0x92430: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 3, 26}, "SLH-DSA-SHAKE-128s"); + case 0x92431: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 3, 27}, "SLH-DSA-SHAKE-128f"); + case 0x92432: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 3, 28}, "SLH-DSA-SHAKE-192s"); + case 0x92433: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 3, 29}, "SLH-DSA-SHAKE-192f"); + case 0x92434: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 3, 30}, "SLH-DSA-SHAKE-256s"); + case 0x92435: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 3, 31}, "SLH-DSA-SHAKE-256f"); + case 0x924D8: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 4, 1}, "ML-KEM-512"); + case 0x924D9: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 4, 2}, "ML-KEM-768"); + case 0x924DA: + return if_match(oid, {2, 16, 840, 1, 101, 3, 4, 4, 3}, "ML-KEM-1024"); + case 0x9479F: + return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 1, 1}, "PKIX.AuthorityInformationAccess"); + case 0x947A5: + return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 1, 7}, "PKIX.IpAddrBlocks"); + case 0x947A6: + return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 1, 8}, "PKIX.AutonomousSysIds"); + case 0x947B8: + return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 1, 26}, "PKIX.TNAuthList"); + case 0x94921: + return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 3, 1}, "PKIX.ServerAuth"); + case 0x94922: + return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 3, 2}, "PKIX.ClientAuth"); + case 0x94923: + return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 3, 3}, "PKIX.CodeSigning"); + case 0x94924: + return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 3, 4}, "PKIX.EmailProtection"); + case 0x94925: + return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 3, 5}, "PKIX.IPsecEndSystem"); + case 0x94926: + return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 3, 6}, "PKIX.IPsecTunnel"); + case 0x94927: + return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 3, 7}, "PKIX.IPsecUser"); + case 0x94928: + return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 3, 8}, "PKIX.TimeStamping"); + case 0x94929: + return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 3, 9}, "PKIX.OCSPSigning"); + case 0x94CEA: + return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 8, 5}, "PKIX.XMPPAddr"); + case 0x954DB: + return if_match(oid, {1, 3, 6, 1, 4, 1, 311, 20, 2, 2}, "Microsoft SmartcardLogon"); + case 0x954DC: + return if_match(oid, {1, 3, 6, 1, 4, 1, 311, 20, 2, 3}, "Microsoft UPN"); + case 0x96B0E: + return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 48, 1}, "PKIX.OCSP"); + case 0x96B0F: + return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 48, 2}, "PKIX.CertificateAuthorityIssuers"); + case 0x96C77: + return if_match(oid, {1, 2, 840, 113549, 1, 12, 1, 3}, "PBE-SHA1-3DES"); + case 0x96C78: + return if_match(oid, {1, 2, 840, 113549, 1, 12, 1, 4}, "PBE-SHA1-2DES"); + case 0x9A008: + return if_match(oid, {1, 3, 36, 3, 3, 2, 8, 1, 1, 1}, "brainpool160r1"); + case 0x9A00A: + return if_match(oid, {1, 3, 36, 3, 3, 2, 8, 1, 1, 3}, "brainpool192r1"); + case 0x9A00C: + return if_match(oid, {1, 3, 36, 3, 3, 2, 8, 1, 1, 5}, "brainpool224r1"); + case 0x9A00E: + return if_match(oid, {1, 3, 36, 3, 3, 2, 8, 1, 1, 7}, "brainpool256r1"); + case 0x9A010: + return if_match(oid, {1, 3, 36, 3, 3, 2, 8, 1, 1, 9}, "brainpool320r1"); + case 0x9A012: + return if_match(oid, {1, 3, 36, 3, 3, 2, 8, 1, 1, 11}, "brainpool384r1"); + case 0x9A014: + return if_match(oid, {1, 3, 36, 3, 3, 2, 8, 1, 1, 13}, "brainpool512r1"); + case 0xA0D61: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 3}, "McEliece"); + case 0xA0D63: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 5}, "XMSS-draft6"); + case 0xA0D66: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 8}, "XMSS-draft12"); + case 0xA0D6B: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 1, 13}, "HSS-LMS-Private-Key"); + case 0xA0EE1: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 3, 1}, "Serpent/CBC"); + case 0xA0EE2: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 3, 2}, "Threefish-512/CBC"); + case 0xA0EE3: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 3, 3}, "Twofish/CBC"); + case 0xA0F45: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 3, 101}, "Serpent/GCM"); + case 0xA0F46: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 3, 102}, "Twofish/GCM"); + case 0xA0FA2: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 4, 1}, "numsp256d1"); + case 0xA0FA3: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 4, 2}, "numsp384d1"); + case 0xA0FA4: + return if_match(oid, {1, 3, 6, 1, 4, 1, 25258, 4, 3}, "numsp512d1"); + case 0xA244B: + return if_match(oid, {1, 3, 6, 1, 4, 1, 22554, 5, 1, 1}, "ClassicMcEliece_348864"); + case 0xA244C: + return if_match(oid, {1, 3, 6, 1, 4, 1, 22554, 5, 1, 2}, "ClassicMcEliece_348864f"); + case 0xA244D: + return if_match(oid, {1, 3, 6, 1, 4, 1, 22554, 5, 1, 3}, "ClassicMcEliece_460896"); + case 0xA244E: + return if_match(oid, {1, 3, 6, 1, 4, 1, 22554, 5, 1, 4}, "ClassicMcEliece_460896f"); + case 0xA244F: + return if_match(oid, {1, 3, 6, 1, 4, 1, 22554, 5, 1, 5}, "ClassicMcEliece_6688128"); + case 0xA2450: + return if_match(oid, {1, 3, 6, 1, 4, 1, 22554, 5, 1, 6}, "ClassicMcEliece_6688128f"); + case 0xA2451: + return if_match(oid, {1, 3, 6, 1, 4, 1, 22554, 5, 1, 7}, "ClassicMcEliece_6960119"); + case 0xA2452: + return if_match(oid, {1, 3, 6, 1, 4, 1, 22554, 5, 1, 8}, "ClassicMcEliece_6960119f"); + case 0xA2453: + return if_match(oid, {1, 3, 6, 1, 4, 1, 22554, 5, 1, 9}, "ClassicMcEliece_8192128"); + case 0xA2454: + return if_match(oid, {1, 3, 6, 1, 4, 1, 22554, 5, 1, 10}, "ClassicMcEliece_8192128f"); + case 0xAF989: + return if_match(oid, {1, 2, 840, 113549, 1, 1, 1}, "RSA"); + case 0xAF98A: + return if_match(oid, {1, 2, 840, 113549, 1, 1, 2}, "RSA/PKCS1v15(MD2)"); + case 0xAF98C: + return if_match(oid, {1, 2, 840, 113549, 1, 1, 4}, "RSA/PKCS1v15(MD5)"); + case 0xAF98D: + return if_match(oid, {1, 2, 840, 113549, 1, 1, 5}, "RSA/PKCS1v15(SHA-1)"); + case 0xAF98F: + return if_match(oid, {1, 2, 840, 113549, 1, 1, 7}, "RSA/OAEP"); + case 0xAF990: + return if_match(oid, {1, 2, 840, 113549, 1, 1, 8}, "MGF1"); + case 0xAF992: + return if_match(oid, {1, 2, 840, 113549, 1, 1, 10}, "RSA/PSS"); + case 0xAF993: + return if_match(oid, {1, 2, 840, 113549, 1, 1, 11}, "RSA/PKCS1v15(SHA-256)"); + case 0xAF994: + return if_match(oid, {1, 2, 840, 113549, 1, 1, 12}, "RSA/PKCS1v15(SHA-384)"); + case 0xAF995: + return if_match(oid, {1, 2, 840, 113549, 1, 1, 13}, "RSA/PKCS1v15(SHA-512)"); + case 0xAF996: + return if_match(oid, {1, 2, 840, 113549, 1, 1, 14}, "RSA/PKCS1v15(SHA-224)"); + case 0xAF998: + return if_match(oid, {1, 2, 840, 113549, 1, 1, 16}, "RSA/PKCS1v15(SHA-512-256)"); + case 0xAFC98: + return if_match(oid, {1, 2, 840, 113549, 1, 5, 12}, "PKCS5.PBKDF2"); + case 0xAFC99: + return if_match(oid, {1, 2, 840, 113549, 1, 5, 13}, "PBE-PKCS5v20"); + case 0xAFE0F: + return if_match(oid, {1, 2, 840, 113549, 1, 7, 1}, "PKCS7.Data"); + case 0xAFE14: + return if_match(oid, {1, 2, 840, 113549, 1, 7, 6}, "PKCS7.EncryptedData"); + case 0xAFF91: + return if_match(oid, {1, 2, 840, 113549, 1, 9, 1}, "PKCS9.EmailAddress"); + case 0xAFF92: + return if_match(oid, {1, 2, 840, 113549, 1, 9, 2}, "PKCS9.UnstructuredName"); + case 0xAFF93: + return if_match(oid, {1, 2, 840, 113549, 1, 9, 3}, "PKCS9.ContentType"); + case 0xAFF94: + return if_match(oid, {1, 2, 840, 113549, 1, 9, 4}, "PKCS9.MessageDigest"); + case 0xAFF97: + return if_match(oid, {1, 2, 840, 113549, 1, 9, 7}, "PKCS9.ChallengePassword"); + case 0xAFF9E: + return if_match(oid, {1, 2, 840, 113549, 1, 9, 14}, "PKCS9.ExtensionRequest"); + case 0xAFFA4: + return if_match(oid, {1, 2, 840, 113549, 1, 9, 20}, "PKCS9.FriendlyName"); + case 0xAFFA5: + return if_match(oid, {1, 2, 840, 113549, 1, 9, 21}, "PKCS9.LocalKeyId"); + case 0xC0226: + return if_match(oid, {1, 3, 6, 1, 4, 1, 11591, 4, 11}, "Scrypt"); + case 0xC0A67: + return if_match(oid, {1, 3, 6, 1, 4, 1, 11591, 15, 1}, "OpenPGP.Ed25519"); + case 0xC4CE5: + return if_match(oid, {1, 2, 643, 100, 1}, "GOST.OGRN"); + case 0xC4D53: + return if_match(oid, {1, 2, 643, 100, 111}, "GOST.SubjectSigningTool"); + case 0xC4D54: + return if_match(oid, {1, 2, 643, 100, 112}, "GOST.IssuerSigningTool"); + case 0xC9C50: + return if_match(oid, {1, 2, 840, 10045, 3, 1, 1}, "secp192r1"); + case 0xC9C51: + return if_match(oid, {1, 2, 840, 10045, 3, 1, 2}, "x962_p192v2"); + case 0xC9C52: + return if_match(oid, {1, 2, 840, 10045, 3, 1, 3}, "x962_p192v3"); + case 0xC9C53: + return if_match(oid, {1, 2, 840, 10045, 3, 1, 4}, "x962_p239v1"); + case 0xC9C54: + return if_match(oid, {1, 2, 840, 10045, 3, 1, 5}, "x962_p239v2"); + case 0xC9C55: + return if_match(oid, {1, 2, 840, 10045, 3, 1, 6}, "x962_p239v3"); + case 0xC9C56: + return if_match(oid, {1, 2, 840, 10045, 3, 1, 7}, "secp256r1"); + case 0xCFA13: + return if_match(oid, {1, 2, 840, 10040, 4, 1}, "DSA"); + case 0xCFA15: + return if_match(oid, {1, 2, 840, 10040, 4, 3}, "DSA/SHA-1"); + default: + return {}; + } +} + +//static +std::optional OID_Map::lookup_static_oid_name(std::string_view req) { + const uint32_t hc = hash_oid_name(req); + + switch(hc) { + case 0x00545: + return if_match(req, "Twofish/GCM", {1, 3, 6, 1, 4, 1, 25258, 3, 102}); + case 0x00CF3: + return if_match(req, "SphincsPlus-sha2-192f-r3.1", {1, 3, 6, 1, 4, 1, 25258, 1, 12, 2, 4}); + case 0x015FE: + return if_match(req, "FrodoKEM-640-SHAKE", {1, 3, 6, 1, 4, 1, 25258, 1, 14, 1}); + case 0x01F9E: + return if_match(req, "MD5", {1, 2, 840, 113549, 2, 5}); + case 0x02293: + return if_match(req, "SphincsPlus-shake-192f-r3.1", {1, 3, 6, 1, 4, 1, 25258, 1, 12, 1, 4}); + case 0x02B93: + return if_match(req, "Microsoft SmartcardLogon", {1, 3, 6, 1, 4, 1, 311, 20, 2, 2}); + case 0x041D5: + return if_match(req, "secp160k1", {1, 3, 132, 0, 9}); + case 0x044B3: + return if_match(req, "Camellia-256/SIV", {1, 3, 6, 1, 4, 1, 25258, 3, 4, 8}); + case 0x048B2: + return if_match(req, "secp160r1", {1, 3, 132, 0, 8}); + case 0x048B3: + return if_match(req, "secp160r2", {1, 3, 132, 0, 30}); + case 0x05CDA: + return if_match(req, "X520.Country", {2, 5, 4, 6}); + case 0x07783: + return if_match(req, "PKIX.ServerAuth", {1, 3, 6, 1, 5, 5, 7, 3, 1}); + case 0x086C7: + return if_match(req, "numsp384d1", {1, 3, 6, 1, 4, 1, 25258, 4, 2}); + case 0x08A92: + return if_match(req, "RSA/PKCS1v15(SHA-1)", {1, 2, 840, 113549, 1, 1, 5}); + case 0x09EA0: + return if_match(req, "DES/CBC", {1, 3, 14, 3, 2, 7}); + case 0x0B2D6: + return if_match(req, "ECDSA/SHA-3(512)", {2, 16, 840, 1, 101, 3, 4, 3, 12}); + case 0x0BA72: + return if_match(req, "SphincsPlus-sha2-128s-r3.1", {1, 3, 6, 1, 4, 1, 25258, 1, 12, 2, 1}); + case 0x0BE23: + return if_match(req, "ECGDSA", {1, 3, 36, 3, 3, 2, 5, 2, 1}); + case 0x0C109: + return if_match(req, "PKCS9.FriendlyName", {1, 2, 840, 113549, 1, 9, 20}); + case 0x0D012: + return if_match(req, "SphincsPlus-shake-128s-r3.1", {1, 3, 6, 1, 4, 1, 25258, 1, 12, 1, 1}); + case 0x0DCE9: + return if_match(req, "ClassicMcEliece_8192128f", {1, 3, 6, 1, 4, 1, 22554, 5, 1, 10}); + case 0x0E52A: + return if_match(req, "numsp512d1", {1, 3, 6, 1, 4, 1, 25258, 4, 3}); + case 0x0F9CC: + return if_match(req, "PKCS9.UnstructuredName", {1, 2, 840, 113549, 1, 9, 2}); + case 0x0FF45: + return if_match(req, "Camellia-256/GCM", {0, 3, 4401, 5, 3, 1, 9, 46}); + case 0x1033D: + return if_match(req, "DSA/SHA-3(384)", {2, 16, 840, 1, 101, 3, 4, 3, 7}); + case 0x1139D: + return if_match(req, "secp192k1", {1, 3, 132, 0, 31}); + case 0x113D6: + return if_match(req, "X520.DNQualifier", {2, 5, 4, 46}); + case 0x11A7A: + return if_match(req, "secp192r1", {1, 2, 840, 10045, 3, 1, 1}); + case 0x12096: + return if_match(req, "SM2_Kex", {1, 2, 156, 10197, 1, 301, 2}); + case 0x13FC1: + return if_match(req, "X520.GenerationalQualifier", {2, 5, 4, 44}); + case 0x1445B: + return if_match(req, "PKCS5.PBKDF2", {1, 2, 840, 113549, 1, 5, 12}); + case 0x1495D: + return if_match(req, "eFrodoKEM-1344-AES", {1, 3, 6, 1, 4, 1, 25258, 1, 17, 3}); + case 0x14E30: + return if_match(req, "ClassicMcEliece_460896", {1, 3, 6, 1, 4, 1, 22554, 5, 1, 3}); + case 0x14FB1: + return if_match(req, "XMSS-draft12", {1, 3, 6, 1, 4, 1, 25258, 1, 8}); + case 0x156E3: + return if_match(req, "Compression.Zlib", {1, 2, 840, 113549, 1, 9, 16, 3, 8}); + case 0x1579E: + return if_match(req, "Streebog-512", {1, 2, 643, 7, 1, 1, 2, 3}); + case 0x1701A: + return if_match(req, "X509v3.AnyExtendedKeyUsage", {2, 5, 29, 37, 0}); + case 0x175EF: + return if_match(req, "Kyber-1024-90s-r3", {1, 3, 6, 1, 4, 1, 25258, 1, 11, 3}); + case 0x17709: + return if_match(req, "X520.GivenName", {2, 5, 4, 42}); + case 0x17AD9: + return if_match(req, "RSA/PKCS1v15(SM3)", {1, 2, 156, 10197, 1, 504}); + case 0x17CE2: + return if_match(req, "SLH-DSA-SHA2-256f", {2, 16, 840, 1, 101, 3, 4, 3, 25}); + case 0x17CEF: + return if_match(req, "SLH-DSA-SHA2-256s", {2, 16, 840, 1, 101, 3, 4, 3, 24}); + case 0x18618: + return if_match(req, "FrodoKEM-976-AES", {1, 3, 6, 1, 4, 1, 25258, 1, 15, 2}); + case 0x19480: + return if_match(req, "eFrodoKEM-1344-SHAKE", {1, 3, 6, 1, 4, 1, 25258, 1, 16, 3}); + case 0x1958A: + return if_match(req, "X509v3.InvalidityDate", {2, 5, 29, 24}); + case 0x19851: + return if_match(req, "DSA/SHA-1", {1, 2, 840, 10040, 4, 3}); + case 0x1B2E7: + return if_match(req, "KeyWrap.AES-128", {2, 16, 840, 1, 101, 3, 4, 1, 5}); + case 0x1B9BE: + return if_match(req, "KeyWrap.AES-192", {2, 16, 840, 1, 101, 3, 4, 1, 25}); + case 0x1D439: + return if_match(req, "SphincsPlus-haraka-192f-r3.1", {1, 3, 6, 1, 4, 1, 25258, 1, 12, 3, 4}); + case 0x2065B: + return if_match(req, "KeyWrap.CAST-128", {1, 2, 840, 113533, 7, 66, 15}); + case 0x216A0: + return if_match(req, "ML-KEM-512", {2, 16, 840, 1, 101, 3, 4, 4, 1}); + case 0x2216B: + return if_match(req, "GOST-34.10-2012-512", {1, 2, 643, 7, 1, 1, 1, 2}); + case 0x22C2C: + return if_match(req, "ElGamal", {1, 3, 6, 1, 4, 1, 3029, 1, 2, 1}); + case 0x2559A: + return if_match(req, "X520.Initials", {2, 5, 4, 43}); + case 0x271AC: + return if_match(req, "PKIX.AutonomousSysIds", {1, 3, 6, 1, 5, 5, 7, 1, 8}); + case 0x2808B: + return if_match(req, "PKCS7.Data", {1, 2, 840, 113549, 1, 7, 1}); + case 0x281B8: + return if_match(req, "SphincsPlus-haraka-128s-r3.1", {1, 3, 6, 1, 4, 1, 25258, 1, 12, 3, 1}); + case 0x29999: + return if_match(req, "DSA/SHA-3(256)", {2, 16, 840, 1, 101, 3, 4, 3, 6}); + case 0x2A83D: + return if_match(req, "SHA-224", {2, 16, 840, 1, 101, 3, 4, 2, 4}); + case 0x2AB30: + return if_match(req, "SHA-256", {2, 16, 840, 1, 101, 3, 4, 2, 1}); + case 0x2ABEF: + return if_match(req, "KeyWrap.AES-256", {2, 16, 840, 1, 101, 3, 4, 1, 45}); + case 0x2BAEF: + return if_match(req, "SM2_Sig/SM3", {1, 2, 156, 10197, 1, 501}); + case 0x2C39A: + return if_match(req, "ECGDSA/RIPEMD-160", {1, 3, 36, 3, 3, 2, 5, 4, 1}); + case 0x2C54F: + return if_match(req, "ECDSA/SHA-3(224)", {2, 16, 840, 1, 101, 3, 4, 3, 9}); + case 0x2EEA6: + return if_match(req, "RSA/PKCS1v15(RIPEMD-160)", {1, 3, 36, 3, 3, 1, 2}); + case 0x2EFBA: + return if_match(req, "Kyber-512-r3", {1, 3, 6, 1, 4, 1, 25258, 1, 7, 1}); + case 0x2F0AD: + return if_match(req, "PKCS7.EncryptedData", {1, 2, 840, 113549, 1, 7, 6}); + case 0x2F219: + return if_match(req, "PBE-SHA1-2DES", {1, 2, 840, 113549, 1, 12, 1, 4}); + case 0x3133E: + return if_match(req, "SLH-DSA-SHA2-128f", {2, 16, 840, 1, 101, 3, 4, 3, 21}); + case 0x3134B: + return if_match(req, "SLH-DSA-SHA2-128s", {2, 16, 840, 1, 101, 3, 4, 3, 20}); + case 0x3160D: + return if_match(req, "RSA/PKCS1v15(SHA-3(224))", {2, 16, 840, 1, 101, 3, 4, 3, 13}); + case 0x319E0: + return if_match(req, "GOST-34.10-2012-256/Streebog-256", {1, 2, 643, 7, 1, 1, 3, 2}); + case 0x31B3D: + return if_match(req, "HMAC(SHA-512)", {1, 2, 840, 113549, 2, 11}); + case 0x31C6D: + return if_match(req, "secp384r1", {1, 3, 132, 0, 34}); + case 0x32899: + return if_match(req, "TripleDES/CBC", {1, 2, 840, 113549, 3, 7}); + case 0x33D04: + return if_match(req, "PKCS12.SecretBag", {1, 2, 840, 113549, 1, 12, 10, 1, 5}); + case 0x3615D: + return if_match(req, "FrodoKEM-976-SHAKE", {1, 3, 6, 1, 4, 1, 25258, 1, 14, 2}); + case 0x361B8: + return if_match(req, "Ed25519", {1, 3, 101, 112}); + case 0x3649D: + return if_match(req, "SHAKE-128", {2, 16, 840, 1, 101, 3, 4, 2, 11}); + case 0x36693: + return if_match(req, "ClassicMcEliece_348864", {1, 3, 6, 1, 4, 1, 22554, 5, 1, 1}); + case 0x373C7: + return if_match(req, "ML-DSA-4x4", {2, 16, 840, 1, 101, 3, 4, 3, 17}); + case 0x3750B: + return if_match(req, "ClassicMcEliece_8192128", {1, 3, 6, 1, 4, 1, 22554, 5, 1, 9}); + case 0x39890: + return if_match(req, "Ed448", {1, 3, 101, 113}); + case 0x3A438: + return if_match(req, "SHA-384", {2, 16, 840, 1, 101, 3, 4, 2, 2}); + case 0x3A963: + return if_match(req, "DH", {1, 2, 840, 10046, 2, 1}); + case 0x3AC83: + return if_match(req, "MGF1", {1, 2, 840, 113549, 1, 1, 8}); + case 0x3ACBA: + return if_match(req, "X509v3.IssuerAlternativeName", {2, 5, 29, 18}); + case 0x3B273: + return if_match(req, "KeyWrap.TripleDES", {1, 2, 840, 113549, 1, 9, 16, 3, 6}); + case 0x3B91E: + return if_match(req, "X509v3.PrivateKeyUsagePeriod", {2, 5, 29, 16}); + case 0x3BC8A: + return if_match(req, "SLH-DSA-SHAKE-192f", {2, 16, 840, 1, 101, 3, 4, 3, 29}); + case 0x3BC97: + return if_match(req, "SLH-DSA-SHAKE-192s", {2, 16, 840, 1, 101, 3, 4, 3, 28}); + case 0x3D127: + return if_match(req, "DSA", {1, 2, 840, 10040, 4, 1}); + case 0x3E249: + return if_match(req, "HSS-LMS", {1, 2, 840, 113549, 1, 9, 16, 3, 17}); + case 0x3E7D5: + return if_match(req, "RSA/PKCS1v15(SHA-3(256))", {2, 16, 840, 1, 101, 3, 4, 3, 14}); + case 0x3F748: + return if_match(req, "GOST.OGRN", {1, 2, 643, 100, 1}); + case 0x3F99F: + return if_match(req, "X509v3.BasicConstraints", {2, 5, 29, 19}); + case 0x40726: + return if_match(req, "SHA-3(512)", {2, 16, 840, 1, 101, 3, 4, 2, 10}); + case 0x407BF: + return if_match(req, "ML-KEM-768", {2, 16, 840, 1, 101, 3, 4, 4, 2}); + case 0x41334: + return if_match(req, "ECDSA/SHA-3(384)", {2, 16, 840, 1, 101, 3, 4, 3, 11}); + case 0x42DF3: + return if_match(req, "X509v3.CRLDistributionPoints", {2, 5, 29, 31}); + case 0x437FB: + return if_match(req, "brainpool160r1", {1, 3, 36, 3, 3, 2, 8, 1, 1, 1}); + case 0x441F5: + return if_match(req, "gost_256A", {1, 2, 643, 7, 1, 2, 1, 1, 1}); + case 0x441F6: + return if_match(req, "gost_256B", {1, 2, 643, 7, 1, 2, 1, 1, 2}); + case 0x44221: + return if_match(req, "GOST-34.10-2012-512/Streebog-512", {1, 2, 643, 7, 1, 1, 3, 3}); + case 0x44322: + return if_match(req, "ClassicMcEliece_6960119pc", {1, 3, 6, 1, 4, 1, 25258, 1, 18, 3}); + case 0x44973: + return if_match(req, "Kyber-512-90s-r3", {1, 3, 6, 1, 4, 1, 25258, 1, 11, 1}); + case 0x45C27: + return if_match(req, "RSA/PKCS1v15(SHA-512-256)", {1, 2, 840, 113549, 1, 1, 16}); + case 0x45C85: + return if_match(req, "X509v3.ReasonCode", {2, 5, 29, 21}); + case 0x45DA5: + return if_match(req, "SHAKE-256", {2, 16, 840, 1, 101, 3, 4, 2, 12}); + case 0x4663C: + return if_match(req, "X509v3.PolicyConstraints", {2, 5, 29, 36}); + case 0x480F7: + return if_match(req, "Serpent/OCB", {1, 3, 6, 1, 4, 1, 25258, 3, 2, 4}); + case 0x48627: + return if_match(req, "Dilithium-4x4-AES-r3", {1, 3, 6, 1, 4, 1, 25258, 1, 10, 1}); + case 0x48861: + return if_match(req, "ChaCha20Poly1305", {1, 2, 840, 113549, 1, 9, 16, 3, 18}); + case 0x4A292: + return if_match(req, "frp256v1", {1, 2, 250, 1, 223, 101, 256, 1}); + case 0x4A9EE: + return if_match(req, "ClassicMcEliece_6960119f", {1, 3, 6, 1, 4, 1, 22554, 5, 1, 8}); + case 0x4BF87: + return if_match(req, "PKIX.TNAuthList", {1, 3, 6, 1, 5, 5, 7, 1, 26}); + case 0x4C088: + return if_match(req, "eFrodoKEM-976-AES", {1, 3, 6, 1, 4, 1, 25258, 1, 17, 2}); + case 0x4C513: + return if_match(req, "DSA/SHA-224", {2, 16, 840, 1, 101, 3, 4, 3, 1}); + case 0x4C806: + return if_match(req, "DSA/SHA-256", {2, 16, 840, 1, 101, 3, 4, 3, 2}); + case 0x4D740: + return if_match(req, "X509v3.AnyPolicy", {2, 5, 29, 32, 0}); + case 0x4DE49: + return if_match(req, "RSA/PKCS1v15(SHA-512)", {1, 2, 840, 113549, 1, 1, 13}); + case 0x4ED5D: + return if_match(req, "CAST-128/CBC", {1, 2, 840, 113533, 7, 66, 10}); + case 0x4FCDC: + return if_match(req, "RSA", {1, 2, 840, 113549, 1, 1, 1}); + case 0x501CB: + return if_match(req, "ECDSA/SHA-224", {1, 2, 840, 10045, 4, 3, 1}); + case 0x50395: + return if_match(req, "GOST-34.10/GOST-R-34.11-94", {1, 2, 643, 2, 2, 3}); + case 0x504BE: + return if_match(req, "ECDSA/SHA-256", {1, 2, 840, 10045, 4, 3, 2}); + case 0x509C3: + return if_match(req, "brainpool192r1", {1, 3, 36, 3, 3, 2, 8, 1, 1, 3}); + case 0x509F9: + return if_match(req, "PKCS9.ContentType", {1, 2, 840, 113549, 1, 9, 3}); + case 0x50B26: + return if_match(req, "FrodoKEM-640-AES", {1, 3, 6, 1, 4, 1, 25258, 1, 15, 1}); + case 0x50D78: + return if_match(req, "x962_p192v2", {1, 2, 840, 10045, 3, 1, 2}); + case 0x50D79: + return if_match(req, "x962_p192v3", {1, 2, 840, 10045, 3, 1, 3}); + case 0x51DC6: + return if_match(req, "AES-128/OCB", {1, 3, 6, 1, 4, 1, 25258, 3, 2, 1}); + case 0x52DB6: + return if_match(req, "HMAC(SHA-224)", {1, 2, 840, 113549, 2, 8}); + case 0x53E11: + return if_match(req, "FrodoKEM-1344-SHAKE", {1, 3, 6, 1, 4, 1, 25258, 1, 14, 3}); + case 0x54012: + return if_match(req, "PKIX.TimeStamping", {1, 3, 6, 1, 5, 5, 7, 3, 8}); + case 0x5407A: + return if_match(req, "Serpent/CBC", {1, 3, 6, 1, 4, 1, 25258, 3, 1}); + case 0x5576D: + return if_match(req, "SphincsPlus-sha2-128f-r3.1", {1, 3, 6, 1, 4, 1, 25258, 1, 12, 2, 2}); + case 0x55EF6: + return if_match(req, "AES-192/OCB", {1, 3, 6, 1, 4, 1, 25258, 3, 2, 2}); + case 0x55FFA: + return if_match(req, "ML-DSA-6x5", {2, 16, 840, 1, 101, 3, 4, 3, 18}); + case 0x56826: + return if_match(req, "brainpool320r1", {1, 3, 36, 3, 3, 2, 8, 1, 1, 9}); + case 0x56D0D: + return if_match(req, "SphincsPlus-shake-128f-r3.1", {1, 3, 6, 1, 4, 1, 25258, 1, 12, 1, 2}); + case 0x57077: + return if_match(req, "XMSS-draft6", {1, 3, 6, 1, 4, 1, 25258, 1, 5}); + case 0x5818B: + return if_match(req, "ECGDSA/SHA-224", {1, 3, 36, 3, 3, 2, 5, 4, 3}); + case 0x5847E: + return if_match(req, "ECGDSA/SHA-256", {1, 3, 36, 3, 3, 2, 5, 4, 4}); + case 0x5898B: + return if_match(req, "SHA-512", {2, 16, 840, 1, 101, 3, 4, 2, 3}); + case 0x58991: + return if_match(req, "PKIX.OCSP.NoCheck", {1, 3, 6, 1, 5, 5, 7, 48, 1, 5}); + case 0x59717: + return if_match(req, "X509v3.SubjectKeyIdentifier", {2, 5, 29, 14}); + case 0x5A1E1: + return if_match(req, "PKCS12.KeyBag", {1, 2, 840, 113549, 1, 12, 10, 1, 1}); + case 0x5A570: + return if_match(req, "X520.CommonName", {2, 5, 4, 3}); + case 0x5A990: + return if_match(req, "ECDSA/SHA-3(256)", {2, 16, 840, 1, 101, 3, 4, 3, 10}); + case 0x5AB0E: + return if_match(req, "SphincsPlus-sha2-256s-r3.1", {1, 3, 6, 1, 4, 1, 25258, 1, 12, 2, 5}); + case 0x5AC4A: + return if_match(req, "X520.Surname", {2, 5, 4, 4}); + case 0x5AF2C: + return if_match(req, "ClassicMcEliece_8192128pc", {1, 3, 6, 1, 4, 1, 25258, 1, 18, 5}); + case 0x5BC39: + return if_match(req, "X509v3.KeyUsage", {2, 5, 29, 15}); + case 0x5BDDB: + return if_match(req, "numsp256d1", {1, 3, 6, 1, 4, 1, 25258, 4, 1}); + case 0x5C0AE: + return if_match(req, "SphincsPlus-shake-256s-r3.1", {1, 3, 6, 1, 4, 1, 25258, 1, 12, 1, 5}); + case 0x5C10E: + return if_match(req, "DSA/SHA-384", {2, 16, 840, 1, 101, 3, 4, 3, 3}); + case 0x5CFE5: + return if_match(req, "PKCS9.X509Certificate", {1, 2, 840, 113549, 1, 9, 22, 1}); + case 0x5D1CF: + return if_match(req, "X520.SerialNumber", {2, 5, 4, 5}); + case 0x5D375: + return if_match(req, "SM4/OCB", {1, 2, 156, 10197, 1, 104, 100}); + case 0x5DD49: + return if_match(req, "AES-128/CBC", {2, 16, 840, 1, 101, 3, 4, 1, 2}); + case 0x5DE4E: + return if_match(req, "AES-128/CCM", {2, 16, 840, 1, 101, 3, 4, 1, 7}); + case 0x5DF23: + return if_match(req, "HMAC(SHA-512-256)", {1, 2, 840, 113549, 2, 13}); + case 0x5ED04: + return if_match(req, "SM2", {1, 2, 156, 10197, 1, 301, 1}); + case 0x5ED05: + return if_match(req, "SM3", {1, 2, 156, 10197, 1, 401}); + case 0x5FDC6: + return if_match(req, "ECDSA/SHA-384", {1, 2, 840, 10045, 4, 3, 3}); + case 0x6199F: + return if_match(req, "SHA-3(224)", {2, 16, 840, 1, 101, 3, 4, 2, 7}); + case 0x61E79: + return if_match(req, "AES-192/CBC", {2, 16, 840, 1, 101, 3, 4, 1, 22}); + case 0x61F7E: + return if_match(req, "AES-192/CCM", {2, 16, 840, 1, 101, 3, 4, 1, 27}); + case 0x64947: + return if_match(req, "OpenPGP.Ed25519", {1, 3, 6, 1, 4, 1, 11591, 15, 1}); + case 0x652E7: + return if_match(req, "sm2p256v1", {1, 2, 156, 10197, 1, 301}); + case 0x6697B: + return if_match(req, "FrodoKEM-1344-AES", {1, 3, 6, 1, 4, 1, 25258, 1, 15, 3}); + case 0x67B2C: + return if_match(req, "X520.State", {2, 5, 4, 8}); + case 0x67B9B: + return if_match(req, "HMAC(SHA-384)", {1, 2, 840, 113549, 2, 10}); + case 0x67D86: + return if_match(req, "ECGDSA/SHA-384", {1, 3, 36, 3, 3, 2, 5, 4, 5}); + case 0x68A0B: + return if_match(req, "Camellia-128/OCB", {1, 3, 6, 1, 4, 1, 25258, 3, 2, 6}); + case 0x68E33: + return if_match(req, "PKCS9.ExtensionRequest", {1, 2, 840, 113549, 1, 9, 14}); + case 0x69126: + return if_match(req, "X509v3.SubjectAlternativeName", {2, 5, 29, 17}); + case 0x692F8: + return if_match(req, "SM4/CBC", {1, 2, 156, 10197, 1, 104, 2}); + case 0x695E1: + return if_match(req, "Dilithium-4x4-r3", {1, 3, 6, 1, 4, 1, 25258, 1, 9, 1}); + case 0x696DC: + return if_match(req, "PKIX.IpAddrBlocks", {1, 3, 6, 1, 5, 5, 7, 1, 7}); + case 0x6A7CA: + return if_match(req, "ECDSA", {1, 2, 840, 10045, 2, 1}); + case 0x6BD26: + return if_match(req, "GOST.INN", {1, 2, 643, 3, 131, 1, 1}); + case 0x6CB3B: + return if_match(req, "Camellia-192/OCB", {1, 3, 6, 1, 4, 1, 25258, 3, 2, 7}); + case 0x6E602: + return if_match(req, "Dilithium-8x7-r3", {1, 3, 6, 1, 4, 1, 25258, 1, 9, 3}); + case 0x6F0C2: + return if_match(req, "RSA/PKCS1v15(SHA-224)", {1, 2, 840, 113549, 1, 1, 14}); + case 0x6F9F8: + return if_match(req, "PKCS12.SafeContentsBag", {1, 2, 840, 113549, 1, 12, 10, 1, 6}); + case 0x6FB26: + return if_match(req, "PKIX.AuthorityInformationAccess", {1, 3, 6, 1, 5, 5, 7, 1, 1}); + case 0x70BB6: + return if_match(req, "brainpool384r1", {1, 3, 36, 3, 3, 2, 8, 1, 1, 11}); + case 0x70EA6: + return if_match(req, "PKCS12.PKCS8ShroudedKeyBag", {1, 2, 840, 113549, 1, 12, 10, 1, 2}); + case 0x71EB3: + return if_match(req, "SphincsPlus-haraka-128f-r3.1", {1, 3, 6, 1, 4, 1, 25258, 1, 12, 3, 2}); + case 0x7382C: + return if_match(req, "ML-KEM-1024", {2, 16, 840, 1, 101, 3, 4, 4, 3}); + case 0x743BD: + return if_match(req, "AES-256/OCB", {1, 3, 6, 1, 4, 1, 25258, 3, 2, 3}); + case 0x7498E: + return if_match(req, "Camellia-128/CBC", {1, 2, 392, 200011, 61, 1, 1, 1, 2}); + case 0x74C2E: + return if_match(req, "ML-DSA-8x7", {2, 16, 840, 1, 101, 3, 4, 3, 19}); + case 0x7505F: + return if_match(req, "PKIX.XMPPAddr", {1, 3, 6, 1, 5, 5, 7, 8, 5}); + case 0x7517A: + return if_match(req, "RSA/PKCS1v15(MD2)", {1, 2, 840, 113549, 1, 1, 2}); + case 0x7546B: + return if_match(req, "RSA/PKCS1v15(MD5)", {1, 2, 840, 113549, 1, 1, 4}); + case 0x75921: + return if_match(req, "ClassicMcEliece_348864f", {1, 3, 6, 1, 4, 1, 22554, 5, 1, 2}); + case 0x76784: + return if_match(req, "SHA-3(384)", {2, 16, 840, 1, 101, 3, 4, 2, 9}); + case 0x768FD: + return if_match(req, "PKCS9.LocalKeyId", {1, 2, 840, 113549, 1, 9, 21}); + case 0x76A19: + return if_match(req, "brainpool512r1", {1, 3, 36, 3, 3, 2, 8, 1, 1, 13}); + case 0x77254: + return if_match(req, "SphincsPlus-haraka-256s-r3.1", {1, 3, 6, 1, 4, 1, 25258, 1, 12, 3, 5}); + case 0x77ADC: + return if_match(req, "secp224k1", {1, 3, 132, 0, 32}); + case 0x781B9: + return if_match(req, "secp224r1", {1, 3, 132, 0, 33}); + case 0x78ABE: + return if_match(req, "Camellia-192/CBC", {1, 2, 392, 200011, 61, 1, 1, 1, 3}); + case 0x792F2: + return if_match(req, "ClassicMcEliece_6688128pc", {1, 3, 6, 1, 4, 1, 25258, 1, 18, 1}); + case 0x7A661: + return if_match(req, "DSA/SHA-512", {2, 16, 840, 1, 101, 3, 4, 3, 4}); + case 0x7A977: + return if_match(req, "X509v3.ExtendedKeyUsage", {2, 5, 29, 37}); + case 0x7AE67: + return if_match(req, "SM2_Enc", {1, 2, 156, 10197, 1, 301, 3}); + case 0x7B602: + return if_match(req, "Twofish/OCB", {1, 3, 6, 1, 4, 1, 25258, 3, 2, 5}); + case 0x7B9A1: + return if_match(req, "SphincsPlus-sha2-192s-r3.1", {1, 3, 6, 1, 4, 1, 25258, 1, 12, 2, 3}); + case 0x7BB0A: + return if_match(req, "SLH-DSA-SHAKE-256f", {2, 16, 840, 1, 101, 3, 4, 3, 31}); + case 0x7BB17: + return if_match(req, "SLH-DSA-SHAKE-256s", {2, 16, 840, 1, 101, 3, 4, 3, 30}); + case 0x7BCF3: + return if_match(req, "PKIX.EmailProtection", {1, 3, 6, 1, 5, 5, 7, 3, 4}); + case 0x7CC2C: + return if_match(req, "SHA-512-256", {2, 16, 840, 1, 101, 3, 4, 2, 6}); + case 0x7CF41: + return if_match(req, "SphincsPlus-shake-192s-r3.1", {1, 3, 6, 1, 4, 1, 25258, 1, 12, 1, 3}); + case 0x7DB91: + return if_match(req, "GOST-34.10", {1, 2, 643, 2, 2, 19}); + case 0x7E319: + return if_match(req, "ECDSA/SHA-512", {1, 2, 840, 10045, 4, 3, 4}); + case 0x7E874: + return if_match(req, "ClassicMcEliece_6688128f", {1, 3, 6, 1, 4, 1, 22554, 5, 1, 6}); + case 0x7EAAF: + return if_match(req, "eFrodoKEM-640-SHAKE", {1, 3, 6, 1, 4, 1, 25258, 1, 16, 1}); + case 0x7F51F: + return if_match(req, "PKIX.IPsecTunnel", {1, 3, 6, 1, 5, 5, 7, 3, 6}); + case 0x80272: + return if_match(req, "X520.Organization", {2, 5, 4, 10}); + case 0x80340: + return if_match(req, "AES-256/CBC", {2, 16, 840, 1, 101, 3, 4, 1, 42}); + case 0x80445: + return if_match(req, "AES-256/CCM", {2, 16, 840, 1, 101, 3, 4, 1, 47}); + case 0x811F7: + return if_match(req, "HMAC(SHA-256)", {1, 2, 840, 113549, 2, 9}); + case 0x82434: + return if_match(req, "PKCS9.X509CRL", {1, 2, 840, 113549, 1, 9, 23, 1}); + case 0x82B47: + return if_match(req, "Threefish-512/CBC", {1, 3, 6, 1, 4, 1, 25258, 3, 2}); + case 0x83EA7: + return if_match(req, "RSA/PKCS1v15(SHA-384)", {1, 2, 840, 113549, 1, 1, 12}); + case 0x84596: + return if_match(req, "eFrodoKEM-640-AES", {1, 3, 6, 1, 4, 1, 25258, 1, 17, 1}); + case 0x8469F: + return if_match(req, "ClassicMcEliece_6960119pcf", {1, 3, 6, 1, 4, 1, 25258, 1, 18, 4}); + case 0x84CA4: + return if_match(req, "secp256k1", {1, 3, 132, 0, 10}); + case 0x85381: + return if_match(req, "secp256r1", {1, 2, 840, 10045, 3, 1, 7}); + case 0x854FC: + return if_match(req, "PKIX.IPsecUser", {1, 3, 6, 1, 5, 5, 7, 3, 7}); + case 0x85F51: + return if_match(req, "Serpent/SIV", {1, 3, 6, 1, 4, 1, 25258, 3, 4, 4}); + case 0x862D9: + return if_match(req, "ECGDSA/SHA-512", {1, 3, 36, 3, 3, 2, 5, 4, 6}); + case 0x87585: + return if_match(req, "Twofish/CBC", {1, 3, 6, 1, 4, 1, 25258, 3, 3}); + case 0x877D1: + return if_match(req, "PKCS9.EmailAddress", {1, 2, 840, 113549, 1, 9, 1}); + case 0x87D27: + return if_match(req, "PKIX.CertificateAuthorityIssuers", {1, 3, 6, 1, 5, 5, 7, 48, 2}); + case 0x87E42: + return if_match(req, "X509v3.AuthorityKeyIdentifier", {2, 5, 29, 35}); + case 0x889B1: + return if_match(req, "ECDSA/SHA-1", {1, 2, 840, 10045, 4, 1}); + case 0x89658: + return if_match(req, "PBE-PKCS5v20", {1, 2, 840, 113549, 1, 5, 13}); + case 0x8976D: + return if_match(req, "PKCS9.MessageDigest", {1, 2, 840, 113549, 1, 9, 4}); + case 0x8B002: + return if_match(req, "Camellia-256/OCB", {1, 3, 6, 1, 4, 1, 25258, 3, 2, 8}); + case 0x8B935: + return if_match(req, "ClassicMcEliece_6688128", {1, 3, 6, 1, 4, 1, 22554, 5, 1, 5}); + case 0x8CE3D: + return if_match(req, "PKCS9.ChallengePassword", {1, 2, 840, 113549, 1, 9, 7}); + case 0x8D45C: + return if_match(req, "ECKCDSA", {1, 0, 14888, 3, 0, 5}); + case 0x8E0C1: + return if_match(req, "X509v3.CertificatePolicies", {2, 5, 29, 32}); + case 0x8E39A: + return if_match(req, "HSS-LMS-Private-Key", {1, 3, 6, 1, 4, 1, 25258, 1, 13}); + case 0x8EC51: + return if_match(req, "Kyber-768-r3", {1, 3, 6, 1, 4, 1, 25258, 1, 7, 2}); + case 0x8F94A: + return if_match(req, "Dilithium-6x5-r3", {1, 3, 6, 1, 4, 1, 25258, 1, 9, 2}); + case 0x8FC20: + return if_match(req, "AES-128/SIV", {1, 3, 6, 1, 4, 1, 25258, 3, 4, 1}); + case 0x8FDE0: + return if_match(req, "SHA-3(256)", {2, 16, 840, 1, 101, 3, 4, 2, 8}); + case 0x919E3: + return if_match(req, "Serpent/GCM", {1, 3, 6, 1, 4, 1, 25258, 3, 101}); + case 0x91C1A: + return if_match(req, "X25519", {1, 3, 101, 110}); + case 0x91DC4: + return if_match(req, "McEliece", {1, 3, 6, 1, 4, 1, 25258, 1, 3}); + case 0x93467: + return if_match(req, "Dilithium-6x5-AES-r3", {1, 3, 6, 1, 4, 1, 25258, 1, 10, 2}); + case 0x93D50: + return if_match(req, "AES-192/SIV", {1, 3, 6, 1, 4, 1, 25258, 3, 4, 2}); + case 0x95166: + return if_match(req, "SLH-DSA-SHAKE-128f", {2, 16, 840, 1, 101, 3, 4, 3, 27}); + case 0x95173: + return if_match(req, "SLH-DSA-SHAKE-128s", {2, 16, 840, 1, 101, 3, 4, 3, 26}); + case 0x952D6: + return if_match(req, "PKIX.OCSP", {1, 3, 6, 1, 5, 5, 7, 48, 1}); + case 0x959B9: + return if_match(req, "PKIX.IPsecEndSystem", {1, 3, 6, 1, 5, 5, 7, 3, 5}); + case 0x96F85: + return if_match(req, "Camellia-256/CBC", {1, 2, 392, 200011, 61, 1, 1, 1, 4}); + case 0x97D5E: + return if_match(req, "HMAC(SHA-1)", {1, 2, 840, 113549, 2, 7}); + case 0x9805C: + return if_match(req, "SEED/CBC", {1, 2, 410, 200004, 1, 4}); + case 0x980E7: + return if_match(req, "SphincsPlus-haraka-192s-r3.1", {1, 3, 6, 1, 4, 1, 25258, 1, 12, 3, 3}); + case 0x980F5: + return if_match(req, "GOST.SubjectSigningTool", {1, 2, 643, 100, 111}); + case 0x98B03: + return if_match(req, "XMSS", {0, 4, 0, 127, 0, 15, 1, 1, 13, 0}); + case 0x9A6B2: + return if_match(req, "ECKCDSA/SHA-1", {1, 2, 410, 200004, 1, 100, 4, 3}); + case 0x9B1CF: + return if_match(req, "SM4/SIV", {1, 3, 6, 1, 4, 1, 25258, 3, 4, 9}); + case 0x9B6B2: + return if_match(req, "AES-128/GCM", {2, 16, 840, 1, 101, 3, 4, 1, 6}); + case 0x9B6BB: + return if_match(req, "X520.OrganizationalUnit", {2, 5, 4, 11}); + case 0x9B851: + return if_match(req, "OpenPGP.Curve25519", {1, 3, 6, 1, 4, 1, 3029, 1, 5, 1}); + case 0x9C80B: + return if_match(req, "SLH-DSA-SHA2-192f", {2, 16, 840, 1, 101, 3, 4, 3, 23}); + case 0x9C818: + return if_match(req, "SLH-DSA-SHA2-192s", {2, 16, 840, 1, 101, 3, 4, 3, 22}); + case 0x9CD2B: + return if_match(req, "Scrypt", {1, 3, 6, 1, 4, 1, 11591, 4, 11}); + case 0x9CDE1: + return if_match(req, "GOST-34.10-2012-256/SHA-256", {1, 3, 6, 1, 4, 1, 25258, 1, 6, 1}); + case 0x9CF73: + return if_match(req, "ClassicMcEliece_460896f", {1, 3, 6, 1, 4, 1, 22554, 5, 1, 4}); + case 0x9D354: + return if_match(req, "RIPEMD-160", {1, 3, 36, 3, 2, 1}); + case 0x9D503: + return if_match(req, "RSA/PKCS1v15(SHA-256)", {1, 2, 840, 113549, 1, 1, 11}); + case 0x9EC88: + return if_match(req, "DSA/SHA-3(512)", {2, 16, 840, 1, 101, 3, 4, 3, 8}); + case 0x9EF36: + return if_match(req, "ClassicMcEliece_6960119", {1, 3, 6, 1, 4, 1, 22554, 5, 1, 7}); + case 0x9F764: + return if_match(req, "X448", {1, 3, 101, 111}); + case 0x9F7E2: + return if_match(req, "AES-192/GCM", {2, 16, 840, 1, 101, 3, 4, 1, 26}); + case 0x9F9C5: + return if_match(req, "ClassicMcEliece_6688128pcf", {1, 3, 6, 1, 4, 1, 25258, 1, 18, 2}); + case 0xA0805: + return if_match(req, "PKCS9.SDSICertificate", {1, 2, 840, 113549, 1, 9, 22, 2}); + case 0xA2B5B: + return if_match(req, "X509v3.CRLNumber", {2, 5, 29, 20}); + case 0xA3005: + return if_match(req, "X520.Title", {2, 5, 4, 12}); + case 0xA323F: + return if_match(req, "X509v3.NameConstraints", {2, 5, 29, 30}); + case 0xA3C55: + return if_match(req, "X520.Pseudonym", {2, 5, 4, 65}); + case 0xA4809: + return if_match(req, "SphincsPlus-sha2-256f-r3.1", {1, 3, 6, 1, 4, 1, 25258, 1, 12, 2, 6}); + case 0xA57AF: + return if_match(req, "secp521r1", {1, 3, 132, 0, 35}); + case 0xA5DA9: + return if_match(req, "SphincsPlus-shake-256f-r3.1", {1, 3, 6, 1, 4, 1, 25258, 1, 12, 1, 6}); + case 0xA6865: + return if_match(req, "Camellia-128/SIV", {1, 3, 6, 1, 4, 1, 25258, 3, 4, 6}); + case 0xA6C61: + return if_match(req, "SM4/GCM", {1, 2, 156, 10197, 1, 104, 8}); + case 0xA8439: + return if_match(req, "PKCS12.CertBag", {1, 2, 840, 113549, 1, 12, 10, 1, 3}); + case 0xA9061: + return if_match(req, "Kyber-768-90s-r3", {1, 3, 6, 1, 4, 1, 25258, 1, 11, 2}); + case 0xAA995: + return if_match(req, "Camellia-192/SIV", {1, 3, 6, 1, 4, 1, 25258, 3, 4, 7}); + case 0xAAE2B: + return if_match(req, "Dilithium-8x7-AES-r3", {1, 3, 6, 1, 4, 1, 25258, 1, 10, 3}); + case 0xABCED: + return if_match(req, "GOST.IssuerSigningTool", {1, 2, 643, 100, 112}); + case 0xABD24: + return if_match(req, "RSA/OAEP", {1, 2, 840, 113549, 1, 1, 7}); + case 0xAC2EC: + return if_match(req, "Streebog-256", {1, 2, 643, 7, 1, 1, 2, 2}); + case 0xAC3DD: + return if_match(req, "Certificate Comment", {2, 16, 840, 1, 113730, 1, 13}); + case 0xAC511: + return if_match(req, "PBE-SHA1-3DES", {1, 2, 840, 113549, 1, 12, 1, 3}); + case 0xAE6FE: + return if_match(req, "PKIX.ClientAuth", {1, 3, 6, 1, 5, 5, 7, 3, 2}); + case 0xAE8D3: + return if_match(req, "ClassicMcEliece_8192128pcf", {1, 3, 6, 1, 4, 1, 25258, 1, 18, 6}); + case 0xAF476: + return if_match(req, "ECDH", {1, 3, 132, 1, 12}); + case 0xAFA6A: + return if_match(req, "RSA/PKCS1v15(SHA-3(384))", {2, 16, 840, 1, 101, 3, 4, 3, 15}); + case 0xB2217: + return if_match(req, "AES-256/SIV", {1, 3, 6, 1, 4, 1, 25258, 3, 4, 3}); + case 0xB22F7: + return if_match(req, "Camellia-128/GCM", {0, 3, 4401, 5, 3, 1, 9, 6}); + case 0xB23DE: + return if_match(req, "X520.Locality", {2, 5, 4, 7}); + case 0xB2FBD: + return if_match(req, "ECKCDSA/SHA-224", {1, 2, 410, 200004, 1, 100, 4, 4}); + case 0xB32B0: + return if_match(req, "ECKCDSA/SHA-256", {1, 2, 410, 200004, 1, 100, 4, 5}); + case 0xB360E: + return if_match(req, "eFrodoKEM-976-SHAKE", {1, 3, 6, 1, 4, 1, 25258, 1, 16, 2}); + case 0xB4368: + return if_match(req, "ECGDSA/SHA-1", {1, 3, 36, 3, 3, 2, 5, 4, 2}); + case 0xB58CD: + return if_match(req, "RSA/PKCS1v15(SHA-3(512))", {2, 16, 840, 1, 101, 3, 4, 3, 16}); + case 0xB6427: + return if_match(req, "Camellia-192/GCM", {0, 3, 4401, 5, 3, 1, 9, 26}); + case 0xB7102: + return if_match(req, "brainpool224r1", {1, 3, 36, 3, 3, 2, 8, 1, 1, 5}); + case 0xB710D: + return if_match(req, "X509v3.CRLIssuingDistributionPoint", {2, 5, 29, 28}); + case 0xB72D4: + return if_match(req, "Microsoft UPN", {1, 3, 6, 1, 4, 1, 311, 20, 2, 3}); + case 0xB73A5: + return if_match(req, "RSA/PSS", {1, 2, 840, 113549, 1, 1, 10}); + case 0xB84B3: + return if_match(req, "PKIX.CodeSigning", {1, 3, 6, 1, 5, 5, 7, 3, 3}); + case 0xB8CB9: + return if_match(req, "GOST-34.10-2012-256", {1, 2, 643, 7, 1, 1, 1, 1}); + case 0xB945C: + return if_match(req, "Twofish/SIV", {1, 3, 6, 1, 4, 1, 25258, 3, 4, 5}); + case 0xB94E4: + return if_match(req, "gost_512A", {1, 2, 643, 7, 1, 2, 1, 2, 1}); + case 0xB94E5: + return if_match(req, "gost_512B", {1, 2, 643, 7, 1, 2, 1, 2, 2}); + case 0xBA1D8: + return if_match(req, "X520.StreetAddress", {2, 5, 4, 9}); + case 0xBCB45: + return if_match(req, "PKCS12.CRLBag", {1, 2, 840, 113549, 1, 12, 10, 1, 4}); + case 0xBCC82: + return if_match(req, "x962_p239v1", {1, 2, 840, 10045, 3, 1, 4}); + case 0xBCC83: + return if_match(req, "x962_p239v2", {1, 2, 840, 10045, 3, 1, 5}); + case 0xBCC84: + return if_match(req, "x962_p239v3", {1, 2, 840, 10045, 3, 1, 6}); + case 0xBD92B: + return if_match(req, "X509v3.HoldInstructionCode", {2, 5, 29, 23}); + case 0xBDCA9: + return if_match(req, "AES-256/GCM", {2, 16, 840, 1, 101, 3, 4, 1, 46}); + case 0xBE48D: + return if_match(req, "PKIX.OCSP.BasicResponse", {1, 3, 6, 1, 5, 5, 7, 48, 1, 1}); + case 0xBF71E: + return if_match(req, "Kyber-1024-r3", {1, 3, 6, 1, 4, 1, 25258, 1, 7, 3}); + case 0xBFF01: + return if_match(req, "DSA/SHA-3(224)", {2, 16, 840, 1, 101, 3, 4, 3, 5}); + case 0xC0F4F: + return if_match(req, "SphincsPlus-haraka-256f-r3.1", {1, 3, 6, 1, 4, 1, 25258, 1, 12, 3, 6}); + case 0xC1875: + return if_match(req, "SHA-1", {1, 3, 14, 3, 2, 26}); + case 0xC28D1: + return if_match(req, "PKIX.OCSPSigning", {1, 3, 6, 1, 5, 5, 7, 3, 9}); + case 0xC42CA: + return if_match(req, "brainpool256r1", {1, 3, 36, 3, 3, 2, 8, 1, 1, 7}); + default: + return {}; + } +} + +std::unordered_map OID_Map::load_oid2str_map() { + return { + {OID{2, 5, 8, 1, 1}, "RSA"}, + {OID{1, 3, 6, 1, 4, 1, 8301, 3, 1, 2, 9, 0, 38}, "secp521r1"}, + {OID{1, 2, 643, 2, 2, 35, 1}, "gost_256A"}, + {OID{1, 2, 643, 2, 2, 36, 0}, "gost_256A"}, + }; +} + +std::unordered_map OID_Map::load_str2oid_map() { + return { + {"Curve25519", OID{1, 3, 101, 110}}, + {"SM2_Sig", OID{1, 2, 156, 10197, 1, 301, 1}}, + {"RSA/EMSA3(MD2)", OID{1, 2, 840, 113549, 1, 1, 2}}, + {"RSA/EMSA3(MD5)", OID{1, 2, 840, 113549, 1, 1, 4}}, + {"RSA/EMSA3(SHA-1)", OID{1, 2, 840, 113549, 1, 1, 5}}, + {"RSA/EMSA3(SHA-256)", OID{1, 2, 840, 113549, 1, 1, 11}}, + {"RSA/EMSA3(SHA-384)", OID{1, 2, 840, 113549, 1, 1, 12}}, + {"RSA/EMSA3(SHA-512)", OID{1, 2, 840, 113549, 1, 1, 13}}, + {"RSA/EMSA3(SHA-224)", OID{1, 2, 840, 113549, 1, 1, 14}}, + {"RSA/EMSA3(SHA-512-256)", OID{1, 2, 840, 113549, 1, 1, 16}}, + {"RSA/EMSA3(SHA-3(224))", OID{2, 16, 840, 1, 101, 3, 4, 3, 13}}, + {"RSA/EMSA3(SHA-3(256))", OID{2, 16, 840, 1, 101, 3, 4, 3, 14}}, + {"RSA/EMSA3(SHA-3(384))", OID{2, 16, 840, 1, 101, 3, 4, 3, 15}}, + {"RSA/EMSA3(SHA-3(512))", OID{2, 16, 840, 1, 101, 3, 4, 3, 16}}, + {"RSA/EMSA3(SM3)", OID{1, 2, 156, 10197, 1, 504}}, + {"RSA/EMSA3(RIPEMD-160)", OID{1, 3, 36, 3, 3, 1, 2}}, + {"RSA/EMSA4", OID{1, 2, 840, 113549, 1, 1, 10}}, + {"PBES2", OID{1, 2, 840, 113549, 1, 5, 13}}, + }; +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/base/buf_comp.cpp botan3-3.12.0+dfsg/src/lib/base/buf_comp.cpp --- botan3-3.7.1+dfsg/src/lib/base/buf_comp.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/base/buf_comp.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,9 +7,14 @@ #include #include +#include namespace Botan { +void Buffered_Computation::update(std::string_view str) { + add_data(as_span_of_bytes(str)); +} + void Buffered_Computation::update_be(uint16_t val) { uint8_t inb[sizeof(val)]; store_be(val, inb); @@ -46,4 +51,9 @@ add_data({inb, sizeof(inb)}); } +void Buffered_Computation::final(std::span out) { + BOTAN_ARG_CHECK(out.size() >= output_length(), "provided output buffer has insufficient capacity"); + final_result(out); +} + } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/base/buf_comp.h botan3-3.12.0+dfsg/src/lib/base/buf_comp.h --- botan3-3.7.1+dfsg/src/lib/base/buf_comp.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/base/buf_comp.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,7 +9,6 @@ #define BOTAN_BUFFERED_COMPUTATION_H_ #include -#include #include #include #include @@ -20,7 +19,7 @@ * This class represents any kind of computation which uses an internal * state, such as hash functions or MACs */ -class BOTAN_PUBLIC_API(2, 0) Buffered_Computation { +class BOTAN_PUBLIC_API(2, 0) Buffered_Computation /* NOLINT(*special-member-functions) */ { public: /** * @return length of the output of this function in bytes @@ -53,7 +52,7 @@ * @param str the input to process as a std::string_view. Will be interpreted * as a byte array based on the strings encoding. */ - void update(std::string_view str) { add_data({cast_char_ptr_to_uint8(str.data()), str.size()}); } + void update(std::string_view str); /** * Process a single byte. @@ -83,10 +82,7 @@ std::vector final_stdvec() { return final>(); } - void final(std::span out) { - BOTAN_ARG_CHECK(out.size() >= output_length(), "provided output buffer has insufficient capacity"); - final_result(out); - } + void final(std::span out); template void final(T& out) { diff -Nru botan3-3.7.1+dfsg/src/lib/base/secmem.h botan3-3.12.0+dfsg/src/lib/base/secmem.h --- botan3-3.7.1+dfsg/src/lib/base/secmem.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/base/secmem.h 2026-05-07 01:38:28.000000000 +0000 @@ -10,23 +10,27 @@ #include #include // IWYU pragma: export -#include -#include +#include #include #include // IWYU pragma: export +#if !defined(BOTAN_IS_BEING_BUILT) && !defined(BOTAN_DISABLE_DEPRECATED_FEATURES) + // TODO(Botan4) remove this + #include +#endif + namespace Botan { template #if !defined(_ITERATOR_DEBUG_LEVEL) || _ITERATOR_DEBUG_LEVEL == 0 /* - * Assert exists to prevent someone from doing something that will + * Check exists to prevent someone from doing something that will * probably crash anyway (like secure_vector where ~non_POD_t * deletes a member pointer which was zeroed before it ran). * MSVC in debug mode uses non-integral proxy types in container types * like std::vector, thus we disable the check there. */ - requires std::is_integral::value || std::is_enum::value + requires std::is_integral_v || std::is_enum_v #endif class secure_allocator { @@ -37,10 +41,13 @@ secure_allocator() noexcept = default; secure_allocator(const secure_allocator&) noexcept = default; secure_allocator& operator=(const secure_allocator&) noexcept = default; + secure_allocator(secure_allocator&&) noexcept = default; + secure_allocator& operator=(secure_allocator&&) noexcept = default; + ~secure_allocator() noexcept = default; template - secure_allocator(const secure_allocator&) noexcept {} + explicit secure_allocator(const secure_allocator& /*other*/) noexcept {} T* allocate(std::size_t n) { return static_cast(allocate_memory(n, sizeof(T))); } @@ -48,19 +55,22 @@ }; template -inline bool operator==(const secure_allocator&, const secure_allocator&) { +inline bool operator==(const secure_allocator& /*a*/, const secure_allocator& /*b*/) { return true; } template -inline bool operator!=(const secure_allocator&, const secure_allocator&) { +inline bool operator!=(const secure_allocator& /*a*/, const secure_allocator& /*b*/) { return false; } template using secure_vector = std::vector>; + +#if !defined(BOTAN_IS_BEING_BUILT) && !defined(BOTAN_DISABLE_DEPRECATED_FEATURES) template using secure_deque = std::deque>; +#endif // For better compatibility with 1.10 API template @@ -76,6 +86,8 @@ return std::vector(in.begin(), in.end()); } +// TODO(Botan4) remove these += operators entirely + template std::vector& operator+=(std::vector& out, const std::vector& in) { out.insert(out.end(), in.begin(), in.end()); @@ -83,6 +95,12 @@ } template +std::vector& operator+=(std::vector& out, std::span in) { + out.insert(out.end(), in.begin(), in.end()); + return out; +} + +template std::vector& operator+=(std::vector& out, T in) { out.push_back(in); return out; @@ -102,15 +120,37 @@ /** * Zeroise the values; length remains unchanged +* +* Note this is not intended for cases where the compiler might elide +* the writes as being without side-effects; use secure_scrub_memory +* for that. +* +* TODO(Botan4): make these not-inlined and only for secure_vector, eg declare +* void zeroize(secure_vector& v); +* void zeroize(secure_vector& v); +* void zeroize(secure_vector& v); +* void zeroize(secure_vector& v); +* * @param vec the vector to zeroise */ template void zeroise(std::vector& vec) { - std::fill(vec.begin(), vec.end(), static_cast(0)); + for(size_t i = 0; i != vec.size(); ++i) { + vec[i] = static_cast(0); + } } /** * Zeroise the values then free the memory +* +* TODO(Botan4): make these not-inlined and only for secure_vector, eg declare +* void zap(secure_vector& v); +* void zap(secure_vector& v); +* void zap(secure_vector& v); +* void zap(secure_vector& v); +* +* [And maybe rename as well] +* * @param vec the vector to zeroise and free */ template diff -Nru botan3-3.7.1+dfsg/src/lib/base/sym_algo.cpp botan3-3.12.0+dfsg/src/lib/base/sym_algo.cpp --- botan3-3.7.1+dfsg/src/lib/base/sym_algo.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/base/sym_algo.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,9 +7,14 @@ #include #include +#include namespace Botan { +void SymmetricAlgorithm::set_key(const OctetString& key) { + set_key(std::span{key.begin(), key.length()}); +} + void SymmetricAlgorithm::throw_key_not_set_error() const { throw Key_Not_Set(name()); } diff -Nru botan3-3.7.1+dfsg/src/lib/base/sym_algo.h botan3-3.12.0+dfsg/src/lib/base/sym_algo.h --- botan3-3.7.1+dfsg/src/lib/base/sym_algo.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/base/sym_algo.h 2026-05-07 01:38:28.000000000 +0000 @@ -8,13 +8,14 @@ #ifndef BOTAN_SYMMETRIC_ALGORITHM_H_ #define BOTAN_SYMMETRIC_ALGORITHM_H_ -#include #include - #include +#include namespace Botan { +class OctetString; + /** * Represents the length requirements on an algorithm key */ @@ -33,7 +34,7 @@ * @param k_mod the number of bytes the key must be a multiple of */ Key_Length_Specification(size_t min_k, size_t max_k, size_t k_mod = 1) : - m_min_keylen(min_k), m_max_keylen(max_k ? max_k : min_k), m_keylen_mod(k_mod) {} + m_min_keylen(min_k), m_max_keylen(max_k > 0 ? max_k : min_k), m_keylen_mod(k_mod) {} /** * @param length is a key length in bytes @@ -76,7 +77,12 @@ */ class BOTAN_PUBLIC_API(2, 0) SymmetricAlgorithm { public: + SymmetricAlgorithm() = default; virtual ~SymmetricAlgorithm() = default; + SymmetricAlgorithm(const SymmetricAlgorithm& other) = default; + SymmetricAlgorithm(SymmetricAlgorithm&& other) = default; + SymmetricAlgorithm& operator=(const SymmetricAlgorithm& other) = default; + SymmetricAlgorithm& operator=(SymmetricAlgorithm&& other) = default; /** * Reset the internal state. This includes not just the key, but @@ -110,7 +116,7 @@ * Set the symmetric key of this object. * @param key the SymmetricKey to be set. */ - void set_key(const SymmetricKey& key) { set_key(std::span{key.begin(), key.length()}); } + void set_key(const OctetString& key); /** * Set the symmetric key of this object. diff -Nru botan3-3.7.1+dfsg/src/lib/base/symkey.cpp botan3-3.12.0+dfsg/src/lib/base/symkey.cpp --- botan3-3.7.1+dfsg/src/lib/base/symkey.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/base/symkey.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -55,8 +55,8 @@ * Set the parity of each key byte to odd */ void OctetString::set_odd_parity() { - for(size_t j = 0; j != m_data.size(); ++j) { - m_data[j] = odd_parity_of(m_data[j]); + for(auto& b : m_data) { + b = odd_parity_of(b); } } @@ -87,7 +87,7 @@ } /* -* Unequality Operation for OctetStrings +* Inequality Operation for OctetStrings */ bool operator!=(const OctetString& s1, const OctetString& s2) { return !(s1 == s2); diff -Nru botan3-3.7.1+dfsg/src/lib/block/aes/aes.cpp botan3-3.12.0+dfsg/src/lib/block/aes/aes.cpp --- botan3-3.7.1+dfsg/src/lib/block/aes/aes.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/aes/aes.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,17 +8,24 @@ #include #include -#include #include #include #include -namespace Botan { +#if defined(BOTAN_HAS_CPUID) + #include +#endif #if defined(BOTAN_HAS_AES_POWER8) || defined(BOTAN_HAS_AES_ARMV8) || defined(BOTAN_HAS_AES_NI) #define BOTAN_HAS_HW_AES_SUPPORT #endif +#if defined(BOTAN_HAS_HW_AES_SUPPORT) + #include +#endif + +namespace Botan { + /* * One of three AES implementation strategies are used to get a constant time * implementation which is immune to common cache/timing based side channels: @@ -298,7 +305,7 @@ const uint32_t d3 = tinv12 ^ tinv13; const uint32_t sd1 = d1 ^ d3; const uint32_t sd0 = d0 ^ d2; - const uint32_t dl = d0 ^ d1; + const uint32_t dl = d0 ^ d1; // NOLINT(misc-confusable-identifiers) const uint32_t dh = d2 ^ d3; const uint32_t dd = sd0 ^ sd1; const uint32_t abcd3 = dh & bh; @@ -408,43 +415,43 @@ inline void shift_rows(uint32_t B[8]) { // 3 0 1 2 7 4 5 6 10 11 8 9 14 15 12 13 17 18 19 16 21 22 23 20 24 25 26 27 28 29 30 31 -#if defined(BOTAN_TARGET_CPU_HAS_NATIVE_64BIT) - for(size_t i = 0; i != 8; i += 2) { - uint64_t x = (static_cast(B[i]) << 32) | B[i + 1]; - x = bit_permute_step(x, 0x0022331100223311, 2); - x = bit_permute_step(x, 0x0055005500550055, 1); - B[i] = static_cast(x >> 32); - B[i + 1] = static_cast(x); - } -#else - for(size_t i = 0; i != 8; ++i) { - uint32_t x = B[i]; - x = bit_permute_step(x, 0x00223311, 2); - x = bit_permute_step(x, 0x00550055, 1); - B[i] = x; + if constexpr(HasNative64BitRegisters) { + for(size_t i = 0; i != 8; i += 2) { + uint64_t x = (static_cast(B[i]) << 32) | B[i + 1]; + x = bit_permute_step(x, 0x0022331100223311, 2); + x = bit_permute_step(x, 0x0055005500550055, 1); + B[i] = static_cast(x >> 32); + B[i + 1] = static_cast(x); + } + } else { + for(size_t i = 0; i != 8; ++i) { + uint32_t x = B[i]; + x = bit_permute_step(x, 0x00223311, 2); + x = bit_permute_step(x, 0x00550055, 1); + B[i] = x; + } } -#endif } inline void inv_shift_rows(uint32_t B[8]) { // Inverse of shift_rows, just inverting the steps -#if defined(BOTAN_TARGET_CPU_HAS_NATIVE_64BIT) - for(size_t i = 0; i != 8; i += 2) { - uint64_t x = (static_cast(B[i]) << 32) | B[i + 1]; - x = bit_permute_step(x, 0x0055005500550055, 1); - x = bit_permute_step(x, 0x0022331100223311, 2); - B[i] = static_cast(x >> 32); - B[i + 1] = static_cast(x); - } -#else - for(size_t i = 0; i != 8; ++i) { - uint32_t x = B[i]; - x = bit_permute_step(x, 0x00550055, 1); - x = bit_permute_step(x, 0x00223311, 2); - B[i] = x; + if constexpr(HasNative64BitRegisters) { + for(size_t i = 0; i != 8; i += 2) { + uint64_t x = (static_cast(B[i]) << 32) | B[i + 1]; + x = bit_permute_step(x, 0x0055005500550055, 1); + x = bit_permute_step(x, 0x0022331100223311, 2); + B[i] = static_cast(x >> 32); + B[i + 1] = static_cast(x); + } + } else { + for(size_t i = 0; i != 8; ++i) { + uint32_t x = B[i]; + x = bit_permute_step(x, 0x00550055, 1); + x = bit_permute_step(x, 0x00223311, 2); + B[i] = x; + } } -#endif } inline void mix_columns(uint32_t B[8]) { @@ -580,10 +587,10 @@ uint32_t B[8] = {0}; - CT::poison(B, 8); - load_be(B, in, this_loop * 4); + CT::poison(B, 8); + for(size_t i = 0; i != 8; ++i) { B[i] ^= DK[i % 4]; } @@ -692,8 +699,9 @@ CT::poison(key, length); - EK.resize(length + 28); - DK.resize(length + 28); + const size_t KS_len = length + 28; + EK.resize(KS_len); + DK.resize(KS_len); for(size_t i = 0; i != X; ++i) { EK[i] = load_be(key, i); @@ -726,10 +734,8 @@ if(bswap_keys) { // HW AES on little endian needs the subkeys to be byte reversed - for(size_t i = 0; i != EK.size(); ++i) { + for(size_t i = 0; i != KS_len; ++i) { EK[i] = reverse_bytes(EK[i]); - } - for(size_t i = 0; i != DK.size(); ++i) { DK[i] = reverse_bytes(DK[i]); } } @@ -741,19 +747,19 @@ size_t aes_parallelism() { #if defined(BOTAN_HAS_AES_VAES) - if(CPUID::has_avx2_vaes()) { + if(CPUID::has(CPUID::Feature::AVX2_AES)) { return 8; // pipelined } #endif #if defined(BOTAN_HAS_HW_AES_SUPPORT) - if(CPUID::has_hw_aes()) { + if(CPUID::has(CPUID::Feature::HW_AES)) { return 4; // pipelined } #endif #if defined(BOTAN_HAS_AES_VPERM) - if(CPUID::has_vperm()) { + if(CPUID::has(CPUID::Feature::SIMD_4X32)) { return 2; // pipelined } #endif @@ -762,22 +768,22 @@ return 2; } -const char* aes_provider() { +std::string aes_provider() { #if defined(BOTAN_HAS_AES_VAES) - if(CPUID::has_avx2_vaes()) { - return "vaes"; + if(auto feat = CPUID::check(CPUID::Feature::AVX2_AES)) { + return *feat; } #endif #if defined(BOTAN_HAS_HW_AES_SUPPORT) - if(CPUID::has_hw_aes()) { - return "cpu"; + if(auto feat = CPUID::check(CPUID::Feature::HW_AES)) { + return *feat; } #endif #if defined(BOTAN_HAS_AES_VPERM) - if(CPUID::has_vperm()) { - return "vperm"; + if(auto feat = CPUID::check(CPUID::Feature::SIMD_4X32)) { + return *feat; } #endif @@ -826,19 +832,19 @@ assert_key_material_set(); #if defined(BOTAN_HAS_AES_VAES) - if(CPUID::has_avx2_vaes()) { + if(CPUID::has(CPUID::Feature::AVX2_AES)) { return x86_vaes_encrypt_n(in, out, blocks); } #endif #if defined(BOTAN_HAS_HW_AES_SUPPORT) - if(CPUID::has_hw_aes()) { + if(CPUID::has(CPUID::Feature::HW_AES)) { return hw_aes_encrypt_n(in, out, blocks); } #endif #if defined(BOTAN_HAS_AES_VPERM) - if(CPUID::has_vperm()) { + if(CPUID::has(CPUID::Feature::SIMD_4X32)) { return vperm_encrypt_n(in, out, blocks); } #endif @@ -850,19 +856,19 @@ assert_key_material_set(); #if defined(BOTAN_HAS_AES_VAES) - if(CPUID::has_avx2_vaes()) { + if(CPUID::has(CPUID::Feature::AVX2_AES)) { return x86_vaes_decrypt_n(in, out, blocks); } #endif #if defined(BOTAN_HAS_HW_AES_SUPPORT) - if(CPUID::has_hw_aes()) { + if(CPUID::has(CPUID::Feature::HW_AES)) { return hw_aes_decrypt_n(in, out, blocks); } #endif #if defined(BOTAN_HAS_AES_VPERM) - if(CPUID::has_vperm()) { + if(CPUID::has(CPUID::Feature::SIMD_4X32)) { return vperm_decrypt_n(in, out, blocks); } #endif @@ -872,25 +878,26 @@ void AES_128::key_schedule(std::span key) { #if defined(BOTAN_HAS_AES_NI) - if(CPUID::has_aes_ni()) { + if(CPUID::has(CPUID::Feature::AESNI)) { return aesni_key_schedule(key.data(), key.size()); } #endif #if defined(BOTAN_HAS_AES_VAES) - if(CPUID::has_avx2_vaes()) { - return aes_key_schedule(key.data(), key.size(), m_EK, m_DK, CPUID::is_little_endian()); + if(CPUID::has(CPUID::Feature::AVX2_AES)) { + return aes_key_schedule(key.data(), key.size(), m_EK, m_DK, true); } #endif #if defined(BOTAN_HAS_HW_AES_SUPPORT) - if(CPUID::has_hw_aes()) { - return aes_key_schedule(key.data(), key.size(), m_EK, m_DK, CPUID::is_little_endian()); + if(CPUID::has(CPUID::Feature::HW_AES)) { + constexpr bool is_little_endian = std::endian::native == std::endian::little; + return aes_key_schedule(key.data(), key.size(), m_EK, m_DK, is_little_endian); } #endif #if defined(BOTAN_HAS_AES_VPERM) - if(CPUID::has_vperm()) { + if(CPUID::has(CPUID::Feature::SIMD_4X32)) { return vperm_key_schedule(key.data(), key.size()); } #endif @@ -907,19 +914,19 @@ assert_key_material_set(); #if defined(BOTAN_HAS_AES_VAES) - if(CPUID::has_avx2_vaes()) { + if(CPUID::has(CPUID::Feature::AVX2_AES)) { return x86_vaes_encrypt_n(in, out, blocks); } #endif #if defined(BOTAN_HAS_HW_AES_SUPPORT) - if(CPUID::has_hw_aes()) { + if(CPUID::has(CPUID::Feature::HW_AES)) { return hw_aes_encrypt_n(in, out, blocks); } #endif #if defined(BOTAN_HAS_AES_VPERM) - if(CPUID::has_vperm()) { + if(CPUID::has(CPUID::Feature::SIMD_4X32)) { return vperm_encrypt_n(in, out, blocks); } #endif @@ -931,19 +938,19 @@ assert_key_material_set(); #if defined(BOTAN_HAS_AES_VAES) - if(CPUID::has_avx2_vaes()) { + if(CPUID::has(CPUID::Feature::AVX2_AES)) { return x86_vaes_decrypt_n(in, out, blocks); } #endif #if defined(BOTAN_HAS_HW_AES_SUPPORT) - if(CPUID::has_hw_aes()) { + if(CPUID::has(CPUID::Feature::HW_AES)) { return hw_aes_decrypt_n(in, out, blocks); } #endif #if defined(BOTAN_HAS_AES_VPERM) - if(CPUID::has_vperm()) { + if(CPUID::has(CPUID::Feature::SIMD_4X32)) { return vperm_decrypt_n(in, out, blocks); } #endif @@ -953,25 +960,26 @@ void AES_192::key_schedule(std::span key) { #if defined(BOTAN_HAS_AES_NI) - if(CPUID::has_aes_ni()) { + if(CPUID::has(CPUID::Feature::AESNI)) { return aesni_key_schedule(key.data(), key.size()); } #endif #if defined(BOTAN_HAS_AES_VAES) - if(CPUID::has_avx2_vaes()) { - return aes_key_schedule(key.data(), key.size(), m_EK, m_DK, CPUID::is_little_endian()); + if(CPUID::has(CPUID::Feature::AVX2_AES)) { + return aes_key_schedule(key.data(), key.size(), m_EK, m_DK, true); } #endif #if defined(BOTAN_HAS_HW_AES_SUPPORT) - if(CPUID::has_hw_aes()) { - return aes_key_schedule(key.data(), key.size(), m_EK, m_DK, CPUID::is_little_endian()); + if(CPUID::has(CPUID::Feature::HW_AES)) { + constexpr bool is_little_endian = std::endian::native == std::endian::little; + return aes_key_schedule(key.data(), key.size(), m_EK, m_DK, is_little_endian); } #endif #if defined(BOTAN_HAS_AES_VPERM) - if(CPUID::has_vperm()) { + if(CPUID::has(CPUID::Feature::SIMD_4X32)) { return vperm_key_schedule(key.data(), key.size()); } #endif @@ -988,19 +996,19 @@ assert_key_material_set(); #if defined(BOTAN_HAS_AES_VAES) - if(CPUID::has_avx2_vaes()) { + if(CPUID::has(CPUID::Feature::AVX2_AES)) { return x86_vaes_encrypt_n(in, out, blocks); } #endif #if defined(BOTAN_HAS_HW_AES_SUPPORT) - if(CPUID::has_hw_aes()) { + if(CPUID::has(CPUID::Feature::HW_AES)) { return hw_aes_encrypt_n(in, out, blocks); } #endif #if defined(BOTAN_HAS_AES_VPERM) - if(CPUID::has_vperm()) { + if(CPUID::has(CPUID::Feature::SIMD_4X32)) { return vperm_encrypt_n(in, out, blocks); } #endif @@ -1012,19 +1020,19 @@ assert_key_material_set(); #if defined(BOTAN_HAS_AES_VAES) - if(CPUID::has_avx2_vaes()) { + if(CPUID::has(CPUID::Feature::AVX2_AES)) { return x86_vaes_decrypt_n(in, out, blocks); } #endif #if defined(BOTAN_HAS_HW_AES_SUPPORT) - if(CPUID::has_hw_aes()) { + if(CPUID::has(CPUID::Feature::HW_AES)) { return hw_aes_decrypt_n(in, out, blocks); } #endif #if defined(BOTAN_HAS_AES_VPERM) - if(CPUID::has_vperm()) { + if(CPUID::has(CPUID::Feature::SIMD_4X32)) { return vperm_decrypt_n(in, out, blocks); } #endif @@ -1034,25 +1042,26 @@ void AES_256::key_schedule(std::span key) { #if defined(BOTAN_HAS_AES_NI) - if(CPUID::has_aes_ni()) { + if(CPUID::has(CPUID::Feature::AESNI)) { return aesni_key_schedule(key.data(), key.size()); } #endif #if defined(BOTAN_HAS_AES_VAES) - if(CPUID::has_avx2_vaes()) { - return aes_key_schedule(key.data(), key.size(), m_EK, m_DK, CPUID::is_little_endian()); + if(CPUID::has(CPUID::Feature::AVX2_AES)) { + return aes_key_schedule(key.data(), key.size(), m_EK, m_DK, true); } #endif #if defined(BOTAN_HAS_HW_AES_SUPPORT) - if(CPUID::has_hw_aes()) { - return aes_key_schedule(key.data(), key.size(), m_EK, m_DK, CPUID::is_little_endian()); + if(CPUID::has(CPUID::Feature::HW_AES)) { + constexpr bool is_little_endian = std::endian::native == std::endian::little; + return aes_key_schedule(key.data(), key.size(), m_EK, m_DK, is_little_endian); } #endif #if defined(BOTAN_HAS_AES_VPERM) - if(CPUID::has_vperm()) { + if(CPUID::has(CPUID::Feature::SIMD_4X32)) { return vperm_key_schedule(key.data(), key.size()); } #endif diff -Nru botan3-3.7.1+dfsg/src/lib/block/aes/aes.h botan3-3.12.0+dfsg/src/lib/block/aes/aes.h --- botan3-3.7.1+dfsg/src/lib/block/aes/aes.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/aes/aes.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,6 +9,7 @@ #define BOTAN_AES_H_ #include +#include namespace Botan { diff -Nru botan3-3.7.1+dfsg/src/lib/block/aes/aes_armv8/aes_armv8.cpp botan3-3.12.0+dfsg/src/lib/block/aes/aes_armv8/aes_armv8.cpp --- botan3-3.7.1+dfsg/src/lib/block/aes/aes_armv8/aes_armv8.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/aes/aes_armv8/aes_armv8.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -10,6 +10,7 @@ #include +#include #include #include @@ -17,60 +18,64 @@ namespace AES_AARCH64 { -BOTAN_FUNC_ISA_INLINE("+crypto+aes") void enc(uint8x16_t& B, uint8x16_t K) { +namespace { + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AES void enc(uint8x16_t& B, uint8x16_t K) { B = vaesmcq_u8(vaeseq_u8(B, K)); } -BOTAN_FUNC_ISA_INLINE("+crypto+aes") -void enc4(uint8x16_t& B0, uint8x16_t& B1, uint8x16_t& B2, uint8x16_t& B3, uint8x16_t K) { +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AES void enc4( + uint8x16_t& B0, uint8x16_t& B1, uint8x16_t& B2, uint8x16_t& B3, uint8x16_t K) { B0 = vaesmcq_u8(vaeseq_u8(B0, K)); B1 = vaesmcq_u8(vaeseq_u8(B1, K)); B2 = vaesmcq_u8(vaeseq_u8(B2, K)); B3 = vaesmcq_u8(vaeseq_u8(B3, K)); } -BOTAN_FUNC_ISA_INLINE("+crypto+aes") void enc_last(uint8x16_t& B, uint8x16_t K, uint8x16_t K2) { +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AES void enc_last(uint8x16_t& B, uint8x16_t K, uint8x16_t K2) { B = veorq_u8(vaeseq_u8(B, K), K2); } -BOTAN_FUNC_ISA_INLINE("+crypto+aes") -void enc4_last(uint8x16_t& B0, uint8x16_t& B1, uint8x16_t& B2, uint8x16_t& B3, uint8x16_t K, uint8x16_t K2) { +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AES void enc4_last( + uint8x16_t& B0, uint8x16_t& B1, uint8x16_t& B2, uint8x16_t& B3, uint8x16_t K, uint8x16_t K2) { B0 = veorq_u8(vaeseq_u8(B0, K), K2); B1 = veorq_u8(vaeseq_u8(B1, K), K2); B2 = veorq_u8(vaeseq_u8(B2, K), K2); B3 = veorq_u8(vaeseq_u8(B3, K), K2); } -BOTAN_FUNC_ISA_INLINE("+crypto+aes") void dec(uint8x16_t& B, uint8x16_t K) { +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AES void dec(uint8x16_t& B, uint8x16_t K) { B = vaesimcq_u8(vaesdq_u8(B, K)); } -BOTAN_FUNC_ISA_INLINE("+crypto+aes") -void dec4(uint8x16_t& B0, uint8x16_t& B1, uint8x16_t& B2, uint8x16_t& B3, uint8x16_t K) { +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AES void dec4( + uint8x16_t& B0, uint8x16_t& B1, uint8x16_t& B2, uint8x16_t& B3, uint8x16_t K) { B0 = vaesimcq_u8(vaesdq_u8(B0, K)); B1 = vaesimcq_u8(vaesdq_u8(B1, K)); B2 = vaesimcq_u8(vaesdq_u8(B2, K)); B3 = vaesimcq_u8(vaesdq_u8(B3, K)); } -BOTAN_FUNC_ISA_INLINE("+crypto+aes") void dec_last(uint8x16_t& B, uint8x16_t K, uint8x16_t K2) { +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AES void dec_last(uint8x16_t& B, uint8x16_t K, uint8x16_t K2) { B = veorq_u8(vaesdq_u8(B, K), K2); } -BOTAN_FUNC_ISA_INLINE("+crypto+aes") -void dec4_last(uint8x16_t& B0, uint8x16_t& B1, uint8x16_t& B2, uint8x16_t& B3, uint8x16_t K, uint8x16_t K2) { +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AES void dec4_last( + uint8x16_t& B0, uint8x16_t& B1, uint8x16_t& B2, uint8x16_t& B3, uint8x16_t K, uint8x16_t K2) { B0 = veorq_u8(vaesdq_u8(B0, K), K2); B1 = veorq_u8(vaesdq_u8(B1, K), K2); B2 = veorq_u8(vaesdq_u8(B2, K), K2); B3 = veorq_u8(vaesdq_u8(B3, K), K2); } +} // namespace + } // namespace AES_AARCH64 /* * AES-128 Encryption */ -BOTAN_FUNC_ISA("+crypto+aes") void AES_128::hw_aes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { +BOTAN_FN_ISA_AES void AES_128::hw_aes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { const uint8_t* skey = reinterpret_cast(m_EK.data()); const uint8x16_t K0 = vld1q_u8(skey + 0 * 16); @@ -133,7 +138,7 @@ /* * AES-128 Decryption */ -BOTAN_FUNC_ISA("+crypto+aes") void AES_128::hw_aes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { +BOTAN_FN_ISA_AES void AES_128::hw_aes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { const uint8_t* skey = reinterpret_cast(m_DK.data()); const uint8x16_t K0 = vld1q_u8(skey + 0 * 16); @@ -188,7 +193,7 @@ dec(B, K6); dec(B, K7); dec(B, K8); - B = veorq_u8(vaesdq_u8(B, K9), K10); + dec_last(B, K9, K10); vst1q_u8(out + 16 * i, B); } } @@ -196,7 +201,7 @@ /* * AES-192 Encryption */ -BOTAN_FUNC_ISA("+crypto+aes") void AES_192::hw_aes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { +BOTAN_FN_ISA_AES void AES_192::hw_aes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { const uint8_t* skey = reinterpret_cast(m_EK.data()); const uint8x16_t K0 = vld1q_u8(skey + 0 * 16); @@ -257,7 +262,7 @@ enc(B, K8); enc(B, K9); enc(B, K10); - B = veorq_u8(vaeseq_u8(B, K11), K12); + enc_last(B, K11, K12); vst1q_u8(out + 16 * i, B); } } @@ -265,7 +270,7 @@ /* * AES-192 Decryption */ -BOTAN_FUNC_ISA("+crypto+aes") void AES_192::hw_aes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { +BOTAN_FN_ISA_AES void AES_192::hw_aes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { const uint8_t* skey = reinterpret_cast(m_DK.data()); const uint8x16_t K0 = vld1q_u8(skey + 0 * 16); @@ -326,7 +331,7 @@ dec(B, K8); dec(B, K9); dec(B, K10); - B = veorq_u8(vaesdq_u8(B, K11), K12); + dec_last(B, K11, K12); vst1q_u8(out + 16 * i, B); } } @@ -334,7 +339,7 @@ /* * AES-256 Encryption */ -BOTAN_FUNC_ISA("+crypto+aes") void AES_256::hw_aes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { +BOTAN_FN_ISA_AES void AES_256::hw_aes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { const uint8_t* skey = reinterpret_cast(m_EK.data()); const uint8x16_t K0 = vld1q_u8(skey + 0 * 16); @@ -403,7 +408,7 @@ enc(B, K10); enc(B, K11); enc(B, K12); - B = veorq_u8(vaeseq_u8(B, K13), K14); + enc_last(B, K13, K14); vst1q_u8(out + 16 * i, B); } } @@ -411,7 +416,7 @@ /* * AES-256 Decryption */ -BOTAN_FUNC_ISA("+crypto+aes") void AES_256::hw_aes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { +BOTAN_FN_ISA_AES void AES_256::hw_aes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { const uint8_t* skey = reinterpret_cast(m_DK.data()); const uint8x16_t K0 = vld1q_u8(skey + 0 * 16); @@ -478,7 +483,7 @@ dec(B, K10); dec(B, K11); dec(B, K12); - B = veorq_u8(vaesdq_u8(B, K13), K14); + dec_last(B, K13, K14); vst1q_u8(out + 16 * i, B); } } diff -Nru botan3-3.7.1+dfsg/src/lib/block/aes/aes_armv8/info.txt botan3-3.12.0+dfsg/src/lib/block/aes/aes_armv8/info.txt --- botan3-3.7.1+dfsg/src/lib/block/aes/aes_armv8/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/aes/aes_armv8/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + AES_ARMV8 -> 20170903 - + name -> "AES ARMv8" @@ -10,3 +10,7 @@ armv8crypto + + +cpuid + diff -Nru botan3-3.7.1+dfsg/src/lib/block/aes/aes_ni/aes_ni.cpp botan3-3.12.0+dfsg/src/lib/block/aes/aes_ni/aes_ni.cpp --- botan3-3.7.1+dfsg/src/lib/block/aes/aes_ni/aes_ni.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/aes/aes_ni/aes_ni.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,17 +7,19 @@ #include +#include #include -#include +#include #include namespace Botan { namespace { +// NOLINTBEGIN(portability-simd-intrinsics) + template -BOTAN_FUNC_ISA("ssse3,aes") -inline __m128i aes_128_key_expansion(__m128i key, __m128i key_getting_rcon) { +BOTAN_FN_ISA_AESNI inline __m128i aes_128_key_expansion(__m128i key, __m128i key_getting_rcon) { __m128i key_with_rcon = _mm_aeskeygenassist_si128(key_getting_rcon, RC); key_with_rcon = _mm_shuffle_epi32(key_with_rcon, _MM_SHUFFLE(3, 3, 3, 3)); key = _mm_xor_si128(key, _mm_slli_si128(key, 4)); @@ -26,7 +28,7 @@ return _mm_xor_si128(key, key_with_rcon); } -BOTAN_FUNC_ISA("ssse3") +BOTAN_FN_ISA_AESNI void aes_192_key_expansion( __m128i* K1, __m128i* K2, __m128i key2_with_rcon, secure_vector& out, size_t offset) { __m128i key1 = *K1; @@ -56,7 +58,7 @@ /* * The second half of the AES-256 key expansion (other half same as AES-128) */ -BOTAN_FUNC_ISA("ssse3,aes") __m128i aes_256_key_expansion(__m128i key, __m128i key2) { +BOTAN_FN_ISA_AESNI __m128i aes_256_key_expansion(__m128i key, __m128i key2) { __m128i key_with_rcon = _mm_aeskeygenassist_si128(key2, 0x00); key_with_rcon = _mm_shuffle_epi32(key_with_rcon, _MM_SHUFFLE(2, 2, 2, 2)); @@ -66,63 +68,70 @@ return _mm_xor_si128(key, key_with_rcon); } -BOTAN_FORCE_INLINE void keyxor(SIMD_4x32 K, SIMD_4x32& B0, SIMD_4x32& B1, SIMD_4x32& B2, SIMD_4x32& B3) { +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AESNI void keyxor( + SIMD_4x32 K, SIMD_4x32& B0, SIMD_4x32& B1, SIMD_4x32& B2, SIMD_4x32& B3) { B0 ^= K; B1 ^= K; B2 ^= K; B3 ^= K; } -BOTAN_FUNC_ISA_INLINE("aes") void aesenc(SIMD_4x32 K, SIMD_4x32& B) { +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AESNI void aesenc(SIMD_4x32 K, SIMD_4x32& B) { B = SIMD_4x32(_mm_aesenc_si128(B.raw(), K.raw())); } -BOTAN_FUNC_ISA_INLINE("aes") void aesenc(SIMD_4x32 K, SIMD_4x32& B0, SIMD_4x32& B1, SIMD_4x32& B2, SIMD_4x32& B3) { +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AESNI void aesenc( + SIMD_4x32 K, SIMD_4x32& B0, SIMD_4x32& B1, SIMD_4x32& B2, SIMD_4x32& B3) { B0 = SIMD_4x32(_mm_aesenc_si128(B0.raw(), K.raw())); B1 = SIMD_4x32(_mm_aesenc_si128(B1.raw(), K.raw())); B2 = SIMD_4x32(_mm_aesenc_si128(B2.raw(), K.raw())); B3 = SIMD_4x32(_mm_aesenc_si128(B3.raw(), K.raw())); } -BOTAN_FUNC_ISA_INLINE("aes") void aesenclast(SIMD_4x32 K, SIMD_4x32& B) { +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AESNI void aesenclast(SIMD_4x32 K, SIMD_4x32& B) { B = SIMD_4x32(_mm_aesenclast_si128(B.raw(), K.raw())); } -BOTAN_FUNC_ISA_INLINE("aes") void aesenclast(SIMD_4x32 K, SIMD_4x32& B0, SIMD_4x32& B1, SIMD_4x32& B2, SIMD_4x32& B3) { +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AESNI void aesenclast( + SIMD_4x32 K, SIMD_4x32& B0, SIMD_4x32& B1, SIMD_4x32& B2, SIMD_4x32& B3) { B0 = SIMD_4x32(_mm_aesenclast_si128(B0.raw(), K.raw())); B1 = SIMD_4x32(_mm_aesenclast_si128(B1.raw(), K.raw())); B2 = SIMD_4x32(_mm_aesenclast_si128(B2.raw(), K.raw())); B3 = SIMD_4x32(_mm_aesenclast_si128(B3.raw(), K.raw())); } -BOTAN_FUNC_ISA_INLINE("aes") void aesdec(SIMD_4x32 K, SIMD_4x32& B) { +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AESNI void aesdec(SIMD_4x32 K, SIMD_4x32& B) { B = SIMD_4x32(_mm_aesdec_si128(B.raw(), K.raw())); } -BOTAN_FUNC_ISA_INLINE("aes") void aesdec(SIMD_4x32 K, SIMD_4x32& B0, SIMD_4x32& B1, SIMD_4x32& B2, SIMD_4x32& B3) { +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AESNI void aesdec( + SIMD_4x32 K, SIMD_4x32& B0, SIMD_4x32& B1, SIMD_4x32& B2, SIMD_4x32& B3) { B0 = SIMD_4x32(_mm_aesdec_si128(B0.raw(), K.raw())); B1 = SIMD_4x32(_mm_aesdec_si128(B1.raw(), K.raw())); B2 = SIMD_4x32(_mm_aesdec_si128(B2.raw(), K.raw())); B3 = SIMD_4x32(_mm_aesdec_si128(B3.raw(), K.raw())); } -BOTAN_FUNC_ISA_INLINE("aes") void aesdeclast(SIMD_4x32 K, SIMD_4x32& B) { +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AESNI void aesdeclast(SIMD_4x32 K, SIMD_4x32& B) { B = SIMD_4x32(_mm_aesdeclast_si128(B.raw(), K.raw())); } -BOTAN_FUNC_ISA_INLINE("aes") void aesdeclast(SIMD_4x32 K, SIMD_4x32& B0, SIMD_4x32& B1, SIMD_4x32& B2, SIMD_4x32& B3) { +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AESNI void aesdeclast( + SIMD_4x32 K, SIMD_4x32& B0, SIMD_4x32& B1, SIMD_4x32& B2, SIMD_4x32& B3) { B0 = SIMD_4x32(_mm_aesdeclast_si128(B0.raw(), K.raw())); B1 = SIMD_4x32(_mm_aesdeclast_si128(B1.raw(), K.raw())); B2 = SIMD_4x32(_mm_aesdeclast_si128(B2.raw(), K.raw())); B3 = SIMD_4x32(_mm_aesdeclast_si128(B3.raw(), K.raw())); } +// NOLINTEND(portability-simd-intrinsics) + } // namespace /* * AES-128 Encryption */ -BOTAN_FUNC_ISA("ssse3,aes") void AES_128::hw_aes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { +BOTAN_FN_ISA_AESNI void AES_128::hw_aes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { const SIMD_4x32 K0 = SIMD_4x32::load_le(&m_EK[4 * 0]); const SIMD_4x32 K1 = SIMD_4x32::load_le(&m_EK[4 * 1]); const SIMD_4x32 K2 = SIMD_4x32::load_le(&m_EK[4 * 2]); @@ -185,7 +194,7 @@ /* * AES-128 Decryption */ -BOTAN_FUNC_ISA("ssse3,aes") void AES_128::hw_aes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { +BOTAN_FN_ISA_AESNI void AES_128::hw_aes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { const SIMD_4x32 K0 = SIMD_4x32::load_le(&m_DK[4 * 0]); const SIMD_4x32 K1 = SIMD_4x32::load_le(&m_DK[4 * 1]); const SIMD_4x32 K2 = SIMD_4x32::load_le(&m_DK[4 * 2]); @@ -248,10 +257,12 @@ /* * AES-128 Key Schedule */ -BOTAN_FUNC_ISA("ssse3,aes") void AES_128::aesni_key_schedule(const uint8_t key[], size_t /*length*/) { +BOTAN_FN_ISA_AESNI void AES_128::aesni_key_schedule(const uint8_t key[], size_t /*length*/) { m_EK.resize(44); m_DK.resize(44); + // NOLINTBEGIN(portability-simd-intrinsics) TODO convert to using SIMD_4x32 + const __m128i K0 = _mm_loadu_si128(reinterpret_cast(key)); const __m128i K1 = aes_128_key_expansion<0x01>(K0, K0); const __m128i K2 = aes_128_key_expansion<0x02>(K1, K1); @@ -291,12 +302,14 @@ _mm_storeu_si128(DK_mm + 8, _mm_aesimc_si128(K2)); _mm_storeu_si128(DK_mm + 9, _mm_aesimc_si128(K1)); _mm_storeu_si128(DK_mm + 10, K0); + + // NOLINTEND(portability-simd-intrinsics) } /* * AES-192 Encryption */ -BOTAN_FUNC_ISA("ssse3,aes") void AES_192::hw_aes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { +BOTAN_FN_ISA_AESNI void AES_192::hw_aes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { const SIMD_4x32 K0 = SIMD_4x32::load_le(&m_EK[4 * 0]); const SIMD_4x32 K1 = SIMD_4x32::load_le(&m_EK[4 * 1]); const SIMD_4x32 K2 = SIMD_4x32::load_le(&m_EK[4 * 2]); @@ -366,7 +379,7 @@ /* * AES-192 Decryption */ -BOTAN_FUNC_ISA("ssse3,aes") void AES_192::hw_aes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { +BOTAN_FN_ISA_AESNI void AES_192::hw_aes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { const SIMD_4x32 K0 = SIMD_4x32::load_le(&m_DK[4 * 0]); const SIMD_4x32 K1 = SIMD_4x32::load_le(&m_DK[4 * 1]); const SIMD_4x32 K2 = SIMD_4x32::load_le(&m_DK[4 * 2]); @@ -436,10 +449,12 @@ /* * AES-192 Key Schedule */ -BOTAN_FUNC_ISA("ssse3,aes") void AES_192::aesni_key_schedule(const uint8_t key[], size_t /*length*/) { +BOTAN_FN_ISA_AESNI void AES_192::aesni_key_schedule(const uint8_t key[], size_t /*length*/) { m_EK.resize(52); m_DK.resize(52); + // NOLINTBEGIN(portability-simd-intrinsics) TODO convert to using SIMD_4x32 + __m128i K0 = _mm_loadu_si128(reinterpret_cast(key)); __m128i K1 = _mm_loadu_si128(reinterpret_cast(key + 8)); K1 = _mm_srli_si128(K1, 8); @@ -472,12 +487,14 @@ _mm_storeu_si128(DK_mm + 10, _mm_aesimc_si128(_mm_loadu_si128(EK_mm + 2))); _mm_storeu_si128(DK_mm + 11, _mm_aesimc_si128(_mm_loadu_si128(EK_mm + 1))); _mm_storeu_si128(DK_mm + 12, _mm_loadu_si128(EK_mm + 0)); + + // NOLINTEND(portability-simd-intrinsics) } /* * AES-256 Encryption */ -BOTAN_FUNC_ISA("ssse3,aes") void AES_256::hw_aes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { +BOTAN_FN_ISA_AESNI void AES_256::hw_aes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { const SIMD_4x32 K0 = SIMD_4x32::load_le(&m_EK[4 * 0]); const SIMD_4x32 K1 = SIMD_4x32::load_le(&m_EK[4 * 1]); const SIMD_4x32 K2 = SIMD_4x32::load_le(&m_EK[4 * 2]); @@ -553,7 +570,7 @@ /* * AES-256 Decryption */ -BOTAN_FUNC_ISA("ssse3,aes") void AES_256::hw_aes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { +BOTAN_FN_ISA_AESNI void AES_256::hw_aes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { const SIMD_4x32 K0 = SIMD_4x32::load_le(&m_DK[4 * 0]); const SIMD_4x32 K1 = SIMD_4x32::load_le(&m_DK[4 * 1]); const SIMD_4x32 K2 = SIMD_4x32::load_le(&m_DK[4 * 2]); @@ -629,10 +646,12 @@ /* * AES-256 Key Schedule */ -BOTAN_FUNC_ISA("ssse3,aes") void AES_256::aesni_key_schedule(const uint8_t key[], size_t /*length*/) { +BOTAN_FN_ISA_AESNI void AES_256::aesni_key_schedule(const uint8_t key[], size_t /*length*/) { m_EK.resize(60); m_DK.resize(60); + // NOLINTBEGIN(portability-simd-intrinsics) TODO convert to using SIMD_4x32 + const __m128i K0 = _mm_loadu_si128(reinterpret_cast(key)); const __m128i K1 = _mm_loadu_si128(reinterpret_cast(key + 16)); @@ -690,6 +709,8 @@ _mm_storeu_si128(DK_mm + 12, _mm_aesimc_si128(K2)); _mm_storeu_si128(DK_mm + 13, _mm_aesimc_si128(K1)); _mm_storeu_si128(DK_mm + 14, K0); + + // NOLINTEND(portability-simd-intrinsics) } } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/block/aes/aes_ni/info.txt botan3-3.12.0+dfsg/src/lib/block/aes/aes_ni/info.txt --- botan3-3.7.1+dfsg/src/lib/block/aes/aes_ni/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/aes/aes_ni/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + AES_NI -> 20131128 - + name -> "AES-NI" @@ -8,7 +8,8 @@ -simd +cpuid +simd_4x32 diff -Nru botan3-3.7.1+dfsg/src/lib/block/aes/aes_power8/aes_power8.cpp botan3-3.12.0+dfsg/src/lib/block/aes/aes_power8/aes_power8.cpp --- botan3-3.7.1+dfsg/src/lib/block/aes/aes_power8/aes_power8.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/aes/aes_power8/aes_power8.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -11,8 +11,8 @@ #include -#include -#include +#include +#include #include #undef vector @@ -20,14 +20,18 @@ namespace Botan { +// NOLINTBEGIN(readability-container-data-pointer) + typedef __vector unsigned long long Altivec64x2; typedef __vector unsigned int Altivec32x4; typedef __vector unsigned char Altivec8x16; namespace { -inline Altivec8x16 reverse_vec(Altivec8x16 src) { - if(CPUID::is_little_endian()) { +static_assert(std::endian::native == std::endian::big || std::endian::native == std::endian::little); + +BOTAN_FORCE_INLINE Altivec8x16 reverse_vec(Altivec8x16 src) { + if constexpr(std::endian::native == std::endian::little) { const Altivec8x16 mask = {15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}; const Altivec8x16 zero = {0}; return vec_perm(src, zero, mask); @@ -36,68 +40,104 @@ } } -BOTAN_FUNC_ISA("vsx") inline Altivec64x2 load_key(const uint32_t key[]) { +BOTAN_FN_ISA_AES BOTAN_FORCE_INLINE Altivec64x2 load_key(const uint32_t key[]) { return reinterpret_cast(reverse_vec(reinterpret_cast(vec_vsx_ld(0, key)))); } -BOTAN_FUNC_ISA("vsx") inline Altivec64x2 load_block(const uint8_t src[]) { +BOTAN_FN_ISA_AES BOTAN_FORCE_INLINE Altivec64x2 load_block(const uint8_t src[]) { return reinterpret_cast(reverse_vec(vec_vsx_ld(0, src))); } -BOTAN_FUNC_ISA("vsx") inline void store_block(Altivec64x2 src, uint8_t dest[]) { +BOTAN_FN_ISA_AES BOTAN_FORCE_INLINE void store_block(Altivec64x2 src, uint8_t dest[]) { vec_vsx_st(reverse_vec(reinterpret_cast(src)), 0, dest); } -inline void store_blocks(Altivec64x2 B0, Altivec64x2 B1, Altivec64x2 B2, Altivec64x2 B3, uint8_t out[]) { +BOTAN_FORCE_INLINE void store_blocks(Altivec64x2 B0, Altivec64x2 B1, Altivec64x2 B2, Altivec64x2 B3, uint8_t out[]) { store_block(B0, out); store_block(B1, out + 16); store_block(B2, out + 16 * 2); store_block(B3, out + 16 * 3); } -#define AES_XOR_4(B0, B1, B2, B3, K) \ - do { \ - B0 = vec_xor(B0, K); \ - B1 = vec_xor(B1, K); \ - B2 = vec_xor(B2, K); \ - B3 = vec_xor(B3, K); \ - } while(0) - -#define AES_ENCRYPT_4(B0, B1, B2, B3, K) \ - do { \ - B0 = __builtin_crypto_vcipher(B0, K); \ - B1 = __builtin_crypto_vcipher(B1, K); \ - B2 = __builtin_crypto_vcipher(B2, K); \ - B3 = __builtin_crypto_vcipher(B3, K); \ - } while(0) - -#define AES_ENCRYPT_4_LAST(B0, B1, B2, B3, K) \ - do { \ - B0 = __builtin_crypto_vcipherlast(B0, K); \ - B1 = __builtin_crypto_vcipherlast(B1, K); \ - B2 = __builtin_crypto_vcipherlast(B2, K); \ - B3 = __builtin_crypto_vcipherlast(B3, K); \ - } while(0) - -#define AES_DECRYPT_4(B0, B1, B2, B3, K) \ - do { \ - B0 = __builtin_crypto_vncipher(B0, K); \ - B1 = __builtin_crypto_vncipher(B1, K); \ - B2 = __builtin_crypto_vncipher(B2, K); \ - B3 = __builtin_crypto_vncipher(B3, K); \ - } while(0) - -#define AES_DECRYPT_4_LAST(B0, B1, B2, B3, K) \ - do { \ - B0 = __builtin_crypto_vncipherlast(B0, K); \ - B1 = __builtin_crypto_vncipherlast(B1, K); \ - B2 = __builtin_crypto_vncipherlast(B2, K); \ - B3 = __builtin_crypto_vncipherlast(B3, K); \ - } while(0) +BOTAN_FN_ISA_AES BOTAN_FORCE_INLINE void xor_blocks( + Altivec64x2& B0, Altivec64x2& B1, Altivec64x2& B2, Altivec64x2& B3, Altivec64x2 K) { + B0 = vec_xor(B0, K); + B1 = vec_xor(B1, K); + B2 = vec_xor(B2, K); + B3 = vec_xor(B3, K); +} + +BOTAN_FN_ISA_AES BOTAN_FORCE_INLINE void aes_vcipher(Altivec64x2& B, Altivec64x2 K) { +#if defined(__clang__) + B = reinterpret_cast( + __builtin_crypto_vcipher(reinterpret_cast(B), reinterpret_cast(K))); +#else + B = __builtin_crypto_vcipher(B, K); +#endif +} + +BOTAN_FN_ISA_AES BOTAN_FORCE_INLINE void aes_vcipherlast(Altivec64x2& B, Altivec64x2 K) { +#if defined(__clang__) + B = reinterpret_cast( + __builtin_crypto_vcipherlast(reinterpret_cast(B), reinterpret_cast(K))); +#else + B = __builtin_crypto_vcipherlast(B, K); +#endif +} + +BOTAN_FN_ISA_AES BOTAN_FORCE_INLINE void aes_vncipher(Altivec64x2& B, Altivec64x2 K) { +#if defined(__clang__) + B = reinterpret_cast( + __builtin_crypto_vncipher(reinterpret_cast(B), reinterpret_cast(K))); +#else + B = __builtin_crypto_vncipher(B, K); +#endif +} + +BOTAN_FN_ISA_AES BOTAN_FORCE_INLINE void aes_vncipherlast(Altivec64x2& B, Altivec64x2 K) { +#if defined(__clang__) + B = reinterpret_cast( + __builtin_crypto_vncipherlast(reinterpret_cast(B), reinterpret_cast(K))); +#else + B = __builtin_crypto_vncipherlast(B, K); +#endif +} + +BOTAN_FN_ISA_AES BOTAN_FORCE_INLINE void aes_vcipher( + Altivec64x2& B0, Altivec64x2& B1, Altivec64x2& B2, Altivec64x2& B3, Altivec64x2 K) { + aes_vcipher(B0, K); + aes_vcipher(B1, K); + aes_vcipher(B2, K); + aes_vcipher(B3, K); +} + +BOTAN_FN_ISA_AES BOTAN_FORCE_INLINE void aes_vcipherlast( + Altivec64x2& B0, Altivec64x2& B1, Altivec64x2& B2, Altivec64x2& B3, Altivec64x2 K) { + aes_vcipherlast(B0, K); + aes_vcipherlast(B1, K); + aes_vcipherlast(B2, K); + aes_vcipherlast(B3, K); +} + +BOTAN_FN_ISA_AES BOTAN_FORCE_INLINE void aes_vncipher( + Altivec64x2& B0, Altivec64x2& B1, Altivec64x2& B2, Altivec64x2& B3, Altivec64x2 K) { + aes_vncipher(B0, K); + aes_vncipher(B1, K); + aes_vncipher(B2, K); + aes_vncipher(B3, K); +} + +BOTAN_FN_ISA_AES BOTAN_FORCE_INLINE void aes_vncipherlast( + Altivec64x2& B0, Altivec64x2& B1, Altivec64x2& B2, Altivec64x2& B3, Altivec64x2 K) { + aes_vncipherlast(B0, K); + aes_vncipherlast(B1, K); + aes_vncipherlast(B2, K); + aes_vncipherlast(B3, K); +} } // namespace -BOTAN_FUNC_ISA("crypto,vsx") void AES_128::hw_aes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { +BOTAN_FN_ISA_AES void AES_128::hw_aes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { const Altivec64x2 K0 = load_key(&m_EK[0]); const Altivec64x2 K1 = load_key(&m_EK[4]); const Altivec64x2 K2 = load_key(&m_EK[8]); @@ -116,17 +156,17 @@ Altivec64x2 B2 = load_block(in + 16 * 2); Altivec64x2 B3 = load_block(in + 16 * 3); - AES_XOR_4(B0, B1, B2, B3, K0); - AES_ENCRYPT_4(B0, B1, B2, B3, K1); - AES_ENCRYPT_4(B0, B1, B2, B3, K2); - AES_ENCRYPT_4(B0, B1, B2, B3, K3); - AES_ENCRYPT_4(B0, B1, B2, B3, K4); - AES_ENCRYPT_4(B0, B1, B2, B3, K5); - AES_ENCRYPT_4(B0, B1, B2, B3, K6); - AES_ENCRYPT_4(B0, B1, B2, B3, K7); - AES_ENCRYPT_4(B0, B1, B2, B3, K8); - AES_ENCRYPT_4(B0, B1, B2, B3, K9); - AES_ENCRYPT_4_LAST(B0, B1, B2, B3, K10); + xor_blocks(B0, B1, B2, B3, K0); + aes_vcipher(B0, B1, B2, B3, K1); + aes_vcipher(B0, B1, B2, B3, K2); + aes_vcipher(B0, B1, B2, B3, K3); + aes_vcipher(B0, B1, B2, B3, K4); + aes_vcipher(B0, B1, B2, B3, K5); + aes_vcipher(B0, B1, B2, B3, K6); + aes_vcipher(B0, B1, B2, B3, K7); + aes_vcipher(B0, B1, B2, B3, K8); + aes_vcipher(B0, B1, B2, B3, K9); + aes_vcipherlast(B0, B1, B2, B3, K10); store_blocks(B0, B1, B2, B3, out); @@ -139,16 +179,16 @@ Altivec64x2 B = load_block(in); B = vec_xor(B, K0); - B = __builtin_crypto_vcipher(B, K1); - B = __builtin_crypto_vcipher(B, K2); - B = __builtin_crypto_vcipher(B, K3); - B = __builtin_crypto_vcipher(B, K4); - B = __builtin_crypto_vcipher(B, K5); - B = __builtin_crypto_vcipher(B, K6); - B = __builtin_crypto_vcipher(B, K7); - B = __builtin_crypto_vcipher(B, K8); - B = __builtin_crypto_vcipher(B, K9); - B = __builtin_crypto_vcipherlast(B, K10); + aes_vcipher(B, K1); + aes_vcipher(B, K2); + aes_vcipher(B, K3); + aes_vcipher(B, K4); + aes_vcipher(B, K5); + aes_vcipher(B, K6); + aes_vcipher(B, K7); + aes_vcipher(B, K8); + aes_vcipher(B, K9); + aes_vcipherlast(B, K10); store_block(B, out); @@ -157,7 +197,7 @@ } } -BOTAN_FUNC_ISA("crypto,vsx") void AES_128::hw_aes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { +BOTAN_FN_ISA_AES void AES_128::hw_aes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { const Altivec64x2 K0 = load_key(&m_EK[40]); const Altivec64x2 K1 = load_key(&m_EK[36]); const Altivec64x2 K2 = load_key(&m_EK[32]); @@ -176,17 +216,17 @@ Altivec64x2 B2 = load_block(in + 16 * 2); Altivec64x2 B3 = load_block(in + 16 * 3); - AES_XOR_4(B0, B1, B2, B3, K0); - AES_DECRYPT_4(B0, B1, B2, B3, K1); - AES_DECRYPT_4(B0, B1, B2, B3, K2); - AES_DECRYPT_4(B0, B1, B2, B3, K3); - AES_DECRYPT_4(B0, B1, B2, B3, K4); - AES_DECRYPT_4(B0, B1, B2, B3, K5); - AES_DECRYPT_4(B0, B1, B2, B3, K6); - AES_DECRYPT_4(B0, B1, B2, B3, K7); - AES_DECRYPT_4(B0, B1, B2, B3, K8); - AES_DECRYPT_4(B0, B1, B2, B3, K9); - AES_DECRYPT_4_LAST(B0, B1, B2, B3, K10); + xor_blocks(B0, B1, B2, B3, K0); + aes_vncipher(B0, B1, B2, B3, K1); + aes_vncipher(B0, B1, B2, B3, K2); + aes_vncipher(B0, B1, B2, B3, K3); + aes_vncipher(B0, B1, B2, B3, K4); + aes_vncipher(B0, B1, B2, B3, K5); + aes_vncipher(B0, B1, B2, B3, K6); + aes_vncipher(B0, B1, B2, B3, K7); + aes_vncipher(B0, B1, B2, B3, K8); + aes_vncipher(B0, B1, B2, B3, K9); + aes_vncipherlast(B0, B1, B2, B3, K10); store_blocks(B0, B1, B2, B3, out); @@ -199,16 +239,16 @@ Altivec64x2 B = load_block(in); B = vec_xor(B, K0); - B = __builtin_crypto_vncipher(B, K1); - B = __builtin_crypto_vncipher(B, K2); - B = __builtin_crypto_vncipher(B, K3); - B = __builtin_crypto_vncipher(B, K4); - B = __builtin_crypto_vncipher(B, K5); - B = __builtin_crypto_vncipher(B, K6); - B = __builtin_crypto_vncipher(B, K7); - B = __builtin_crypto_vncipher(B, K8); - B = __builtin_crypto_vncipher(B, K9); - B = __builtin_crypto_vncipherlast(B, K10); + aes_vncipher(B, K1); + aes_vncipher(B, K2); + aes_vncipher(B, K3); + aes_vncipher(B, K4); + aes_vncipher(B, K5); + aes_vncipher(B, K6); + aes_vncipher(B, K7); + aes_vncipher(B, K8); + aes_vncipher(B, K9); + aes_vncipherlast(B, K10); store_block(B, out); @@ -217,7 +257,7 @@ } } -BOTAN_FUNC_ISA("crypto,vsx") void AES_192::hw_aes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { +BOTAN_FN_ISA_AES void AES_192::hw_aes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { const Altivec64x2 K0 = load_key(&m_EK[0]); const Altivec64x2 K1 = load_key(&m_EK[4]); const Altivec64x2 K2 = load_key(&m_EK[8]); @@ -238,19 +278,19 @@ Altivec64x2 B2 = load_block(in + 16 * 2); Altivec64x2 B3 = load_block(in + 16 * 3); - AES_XOR_4(B0, B1, B2, B3, K0); - AES_ENCRYPT_4(B0, B1, B2, B3, K1); - AES_ENCRYPT_4(B0, B1, B2, B3, K2); - AES_ENCRYPT_4(B0, B1, B2, B3, K3); - AES_ENCRYPT_4(B0, B1, B2, B3, K4); - AES_ENCRYPT_4(B0, B1, B2, B3, K5); - AES_ENCRYPT_4(B0, B1, B2, B3, K6); - AES_ENCRYPT_4(B0, B1, B2, B3, K7); - AES_ENCRYPT_4(B0, B1, B2, B3, K8); - AES_ENCRYPT_4(B0, B1, B2, B3, K9); - AES_ENCRYPT_4(B0, B1, B2, B3, K10); - AES_ENCRYPT_4(B0, B1, B2, B3, K11); - AES_ENCRYPT_4_LAST(B0, B1, B2, B3, K12); + xor_blocks(B0, B1, B2, B3, K0); + aes_vcipher(B0, B1, B2, B3, K1); + aes_vcipher(B0, B1, B2, B3, K2); + aes_vcipher(B0, B1, B2, B3, K3); + aes_vcipher(B0, B1, B2, B3, K4); + aes_vcipher(B0, B1, B2, B3, K5); + aes_vcipher(B0, B1, B2, B3, K6); + aes_vcipher(B0, B1, B2, B3, K7); + aes_vcipher(B0, B1, B2, B3, K8); + aes_vcipher(B0, B1, B2, B3, K9); + aes_vcipher(B0, B1, B2, B3, K10); + aes_vcipher(B0, B1, B2, B3, K11); + aes_vcipherlast(B0, B1, B2, B3, K12); store_blocks(B0, B1, B2, B3, out); @@ -263,18 +303,18 @@ Altivec64x2 B = load_block(in); B = vec_xor(B, K0); - B = __builtin_crypto_vcipher(B, K1); - B = __builtin_crypto_vcipher(B, K2); - B = __builtin_crypto_vcipher(B, K3); - B = __builtin_crypto_vcipher(B, K4); - B = __builtin_crypto_vcipher(B, K5); - B = __builtin_crypto_vcipher(B, K6); - B = __builtin_crypto_vcipher(B, K7); - B = __builtin_crypto_vcipher(B, K8); - B = __builtin_crypto_vcipher(B, K9); - B = __builtin_crypto_vcipher(B, K10); - B = __builtin_crypto_vcipher(B, K11); - B = __builtin_crypto_vcipherlast(B, K12); + aes_vcipher(B, K1); + aes_vcipher(B, K2); + aes_vcipher(B, K3); + aes_vcipher(B, K4); + aes_vcipher(B, K5); + aes_vcipher(B, K6); + aes_vcipher(B, K7); + aes_vcipher(B, K8); + aes_vcipher(B, K9); + aes_vcipher(B, K10); + aes_vcipher(B, K11); + aes_vcipherlast(B, K12); store_block(B, out); @@ -283,7 +323,7 @@ } } -BOTAN_FUNC_ISA("crypto,vsx") void AES_192::hw_aes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { +BOTAN_FN_ISA_AES void AES_192::hw_aes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { const Altivec64x2 K0 = load_key(&m_EK[48]); const Altivec64x2 K1 = load_key(&m_EK[44]); const Altivec64x2 K2 = load_key(&m_EK[40]); @@ -304,19 +344,19 @@ Altivec64x2 B2 = load_block(in + 16 * 2); Altivec64x2 B3 = load_block(in + 16 * 3); - AES_XOR_4(B0, B1, B2, B3, K0); - AES_DECRYPT_4(B0, B1, B2, B3, K1); - AES_DECRYPT_4(B0, B1, B2, B3, K2); - AES_DECRYPT_4(B0, B1, B2, B3, K3); - AES_DECRYPT_4(B0, B1, B2, B3, K4); - AES_DECRYPT_4(B0, B1, B2, B3, K5); - AES_DECRYPT_4(B0, B1, B2, B3, K6); - AES_DECRYPT_4(B0, B1, B2, B3, K7); - AES_DECRYPT_4(B0, B1, B2, B3, K8); - AES_DECRYPT_4(B0, B1, B2, B3, K9); - AES_DECRYPT_4(B0, B1, B2, B3, K10); - AES_DECRYPT_4(B0, B1, B2, B3, K11); - AES_DECRYPT_4_LAST(B0, B1, B2, B3, K12); + xor_blocks(B0, B1, B2, B3, K0); + aes_vncipher(B0, B1, B2, B3, K1); + aes_vncipher(B0, B1, B2, B3, K2); + aes_vncipher(B0, B1, B2, B3, K3); + aes_vncipher(B0, B1, B2, B3, K4); + aes_vncipher(B0, B1, B2, B3, K5); + aes_vncipher(B0, B1, B2, B3, K6); + aes_vncipher(B0, B1, B2, B3, K7); + aes_vncipher(B0, B1, B2, B3, K8); + aes_vncipher(B0, B1, B2, B3, K9); + aes_vncipher(B0, B1, B2, B3, K10); + aes_vncipher(B0, B1, B2, B3, K11); + aes_vncipherlast(B0, B1, B2, B3, K12); store_blocks(B0, B1, B2, B3, out); @@ -329,18 +369,18 @@ Altivec64x2 B = load_block(in); B = vec_xor(B, K0); - B = __builtin_crypto_vncipher(B, K1); - B = __builtin_crypto_vncipher(B, K2); - B = __builtin_crypto_vncipher(B, K3); - B = __builtin_crypto_vncipher(B, K4); - B = __builtin_crypto_vncipher(B, K5); - B = __builtin_crypto_vncipher(B, K6); - B = __builtin_crypto_vncipher(B, K7); - B = __builtin_crypto_vncipher(B, K8); - B = __builtin_crypto_vncipher(B, K9); - B = __builtin_crypto_vncipher(B, K10); - B = __builtin_crypto_vncipher(B, K11); - B = __builtin_crypto_vncipherlast(B, K12); + aes_vncipher(B, K1); + aes_vncipher(B, K2); + aes_vncipher(B, K3); + aes_vncipher(B, K4); + aes_vncipher(B, K5); + aes_vncipher(B, K6); + aes_vncipher(B, K7); + aes_vncipher(B, K8); + aes_vncipher(B, K9); + aes_vncipher(B, K10); + aes_vncipher(B, K11); + aes_vncipherlast(B, K12); store_block(B, out); @@ -349,7 +389,7 @@ } } -BOTAN_FUNC_ISA("crypto,vsx") void AES_256::hw_aes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { +BOTAN_FN_ISA_AES void AES_256::hw_aes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { const Altivec64x2 K0 = load_key(&m_EK[0]); const Altivec64x2 K1 = load_key(&m_EK[4]); const Altivec64x2 K2 = load_key(&m_EK[8]); @@ -372,21 +412,21 @@ Altivec64x2 B2 = load_block(in + 16 * 2); Altivec64x2 B3 = load_block(in + 16 * 3); - AES_XOR_4(B0, B1, B2, B3, K0); - AES_ENCRYPT_4(B0, B1, B2, B3, K1); - AES_ENCRYPT_4(B0, B1, B2, B3, K2); - AES_ENCRYPT_4(B0, B1, B2, B3, K3); - AES_ENCRYPT_4(B0, B1, B2, B3, K4); - AES_ENCRYPT_4(B0, B1, B2, B3, K5); - AES_ENCRYPT_4(B0, B1, B2, B3, K6); - AES_ENCRYPT_4(B0, B1, B2, B3, K7); - AES_ENCRYPT_4(B0, B1, B2, B3, K8); - AES_ENCRYPT_4(B0, B1, B2, B3, K9); - AES_ENCRYPT_4(B0, B1, B2, B3, K10); - AES_ENCRYPT_4(B0, B1, B2, B3, K11); - AES_ENCRYPT_4(B0, B1, B2, B3, K12); - AES_ENCRYPT_4(B0, B1, B2, B3, K13); - AES_ENCRYPT_4_LAST(B0, B1, B2, B3, K14); + xor_blocks(B0, B1, B2, B3, K0); + aes_vcipher(B0, B1, B2, B3, K1); + aes_vcipher(B0, B1, B2, B3, K2); + aes_vcipher(B0, B1, B2, B3, K3); + aes_vcipher(B0, B1, B2, B3, K4); + aes_vcipher(B0, B1, B2, B3, K5); + aes_vcipher(B0, B1, B2, B3, K6); + aes_vcipher(B0, B1, B2, B3, K7); + aes_vcipher(B0, B1, B2, B3, K8); + aes_vcipher(B0, B1, B2, B3, K9); + aes_vcipher(B0, B1, B2, B3, K10); + aes_vcipher(B0, B1, B2, B3, K11); + aes_vcipher(B0, B1, B2, B3, K12); + aes_vcipher(B0, B1, B2, B3, K13); + aes_vcipherlast(B0, B1, B2, B3, K14); store_blocks(B0, B1, B2, B3, out); @@ -399,20 +439,20 @@ Altivec64x2 B = load_block(in); B = vec_xor(B, K0); - B = __builtin_crypto_vcipher(B, K1); - B = __builtin_crypto_vcipher(B, K2); - B = __builtin_crypto_vcipher(B, K3); - B = __builtin_crypto_vcipher(B, K4); - B = __builtin_crypto_vcipher(B, K5); - B = __builtin_crypto_vcipher(B, K6); - B = __builtin_crypto_vcipher(B, K7); - B = __builtin_crypto_vcipher(B, K8); - B = __builtin_crypto_vcipher(B, K9); - B = __builtin_crypto_vcipher(B, K10); - B = __builtin_crypto_vcipher(B, K11); - B = __builtin_crypto_vcipher(B, K12); - B = __builtin_crypto_vcipher(B, K13); - B = __builtin_crypto_vcipherlast(B, K14); + aes_vcipher(B, K1); + aes_vcipher(B, K2); + aes_vcipher(B, K3); + aes_vcipher(B, K4); + aes_vcipher(B, K5); + aes_vcipher(B, K6); + aes_vcipher(B, K7); + aes_vcipher(B, K8); + aes_vcipher(B, K9); + aes_vcipher(B, K10); + aes_vcipher(B, K11); + aes_vcipher(B, K12); + aes_vcipher(B, K13); + aes_vcipherlast(B, K14); store_block(B, out); @@ -421,7 +461,7 @@ } } -BOTAN_FUNC_ISA("crypto,vsx") void AES_256::hw_aes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { +BOTAN_FN_ISA_AES void AES_256::hw_aes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { const Altivec64x2 K0 = load_key(&m_EK[56]); const Altivec64x2 K1 = load_key(&m_EK[52]); const Altivec64x2 K2 = load_key(&m_EK[48]); @@ -444,21 +484,21 @@ Altivec64x2 B2 = load_block(in + 16 * 2); Altivec64x2 B3 = load_block(in + 16 * 3); - AES_XOR_4(B0, B1, B2, B3, K0); - AES_DECRYPT_4(B0, B1, B2, B3, K1); - AES_DECRYPT_4(B0, B1, B2, B3, K2); - AES_DECRYPT_4(B0, B1, B2, B3, K3); - AES_DECRYPT_4(B0, B1, B2, B3, K4); - AES_DECRYPT_4(B0, B1, B2, B3, K5); - AES_DECRYPT_4(B0, B1, B2, B3, K6); - AES_DECRYPT_4(B0, B1, B2, B3, K7); - AES_DECRYPT_4(B0, B1, B2, B3, K8); - AES_DECRYPT_4(B0, B1, B2, B3, K9); - AES_DECRYPT_4(B0, B1, B2, B3, K10); - AES_DECRYPT_4(B0, B1, B2, B3, K11); - AES_DECRYPT_4(B0, B1, B2, B3, K12); - AES_DECRYPT_4(B0, B1, B2, B3, K13); - AES_DECRYPT_4_LAST(B0, B1, B2, B3, K14); + xor_blocks(B0, B1, B2, B3, K0); + aes_vncipher(B0, B1, B2, B3, K1); + aes_vncipher(B0, B1, B2, B3, K2); + aes_vncipher(B0, B1, B2, B3, K3); + aes_vncipher(B0, B1, B2, B3, K4); + aes_vncipher(B0, B1, B2, B3, K5); + aes_vncipher(B0, B1, B2, B3, K6); + aes_vncipher(B0, B1, B2, B3, K7); + aes_vncipher(B0, B1, B2, B3, K8); + aes_vncipher(B0, B1, B2, B3, K9); + aes_vncipher(B0, B1, B2, B3, K10); + aes_vncipher(B0, B1, B2, B3, K11); + aes_vncipher(B0, B1, B2, B3, K12); + aes_vncipher(B0, B1, B2, B3, K13); + aes_vncipherlast(B0, B1, B2, B3, K14); store_blocks(B0, B1, B2, B3, out); @@ -471,20 +511,20 @@ Altivec64x2 B = load_block(in); B = vec_xor(B, K0); - B = __builtin_crypto_vncipher(B, K1); - B = __builtin_crypto_vncipher(B, K2); - B = __builtin_crypto_vncipher(B, K3); - B = __builtin_crypto_vncipher(B, K4); - B = __builtin_crypto_vncipher(B, K5); - B = __builtin_crypto_vncipher(B, K6); - B = __builtin_crypto_vncipher(B, K7); - B = __builtin_crypto_vncipher(B, K8); - B = __builtin_crypto_vncipher(B, K9); - B = __builtin_crypto_vncipher(B, K10); - B = __builtin_crypto_vncipher(B, K11); - B = __builtin_crypto_vncipher(B, K12); - B = __builtin_crypto_vncipher(B, K13); - B = __builtin_crypto_vncipherlast(B, K14); + aes_vncipher(B, K1); + aes_vncipher(B, K2); + aes_vncipher(B, K3); + aes_vncipher(B, K4); + aes_vncipher(B, K5); + aes_vncipher(B, K6); + aes_vncipher(B, K7); + aes_vncipher(B, K8); + aes_vncipher(B, K9); + aes_vncipher(B, K10); + aes_vncipher(B, K11); + aes_vncipher(B, K12); + aes_vncipher(B, K13); + aes_vncipherlast(B, K14); store_block(B, out); @@ -493,10 +533,6 @@ } } -#undef AES_XOR_4 -#undef AES_ENCRYPT_4 -#undef AES_ENCRYPT_4_LAST -#undef AES_DECRYPT_4 -#undef AES_DECRYPT_4_LAST +// NOLINTEND(readability-container-data-pointer) } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/block/aes/aes_power8/info.txt botan3-3.12.0+dfsg/src/lib/block/aes/aes_power8/info.txt --- botan3-3.7.1+dfsg/src/lib/block/aes/aes_power8/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/aes/aes_power8/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,12 +1,16 @@ - + AES_POWER8 -> 20180223 - + name -> "AES Power8" brief -> "AES using Power8 instructions" + +cpuid + + ppc64 diff -Nru botan3-3.7.1+dfsg/src/lib/block/aes/aes_vaes/aes_vaes.cpp botan3-3.12.0+dfsg/src/lib/block/aes/aes_vaes/aes_vaes.cpp --- botan3-3.7.1+dfsg/src/lib/block/aes/aes_vaes/aes_vaes.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/aes/aes_vaes/aes_vaes.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,75 +6,80 @@ #include -#include +#include #include -#include +#include namespace Botan { namespace { -BOTAN_FORCE_INLINE void keyxor(SIMD_8x32 K, SIMD_8x32& B0, SIMD_8x32& B1, SIMD_8x32& B2, SIMD_8x32& B3) { +BOTAN_FORCE_INLINE void BOTAN_FN_ISA_AVX2_VAES +keyxor(SIMD_8x32 K, SIMD_8x32& B0, SIMD_8x32& B1, SIMD_8x32& B2, SIMD_8x32& B3) { B0 ^= K; B1 ^= K; B2 ^= K; B3 ^= K; } -BOTAN_FUNC_ISA_INLINE("vaes,avx2") void aesenc(SIMD_8x32 K, SIMD_8x32& B) { +// NOLINTBEGIN(portability-simd-intrinsics) + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX2_VAES void aesenc(SIMD_8x32 K, SIMD_8x32& B) { B = SIMD_8x32(_mm256_aesenc_epi128(B.raw(), K.raw())); } -BOTAN_FUNC_ISA_INLINE("vaes,avx2") -void aesenc(SIMD_8x32 K, SIMD_8x32& B0, SIMD_8x32& B1, SIMD_8x32& B2, SIMD_8x32& B3) { +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX2_VAES void aesenc( + SIMD_8x32 K, SIMD_8x32& B0, SIMD_8x32& B1, SIMD_8x32& B2, SIMD_8x32& B3) { B0 = SIMD_8x32(_mm256_aesenc_epi128(B0.raw(), K.raw())); B1 = SIMD_8x32(_mm256_aesenc_epi128(B1.raw(), K.raw())); B2 = SIMD_8x32(_mm256_aesenc_epi128(B2.raw(), K.raw())); B3 = SIMD_8x32(_mm256_aesenc_epi128(B3.raw(), K.raw())); } -BOTAN_FUNC_ISA_INLINE("vaes,avx2") void aesenclast(SIMD_8x32 K, SIMD_8x32& B) { +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX2_VAES void aesenclast(SIMD_8x32 K, SIMD_8x32& B) { B = SIMD_8x32(_mm256_aesenclast_epi128(B.raw(), K.raw())); } -BOTAN_FUNC_ISA_INLINE("vaes,avx2") -void aesenclast(SIMD_8x32 K, SIMD_8x32& B0, SIMD_8x32& B1, SIMD_8x32& B2, SIMD_8x32& B3) { +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX2_VAES void aesenclast( + SIMD_8x32 K, SIMD_8x32& B0, SIMD_8x32& B1, SIMD_8x32& B2, SIMD_8x32& B3) { B0 = SIMD_8x32(_mm256_aesenclast_epi128(B0.raw(), K.raw())); B1 = SIMD_8x32(_mm256_aesenclast_epi128(B1.raw(), K.raw())); B2 = SIMD_8x32(_mm256_aesenclast_epi128(B2.raw(), K.raw())); B3 = SIMD_8x32(_mm256_aesenclast_epi128(B3.raw(), K.raw())); } -BOTAN_FUNC_ISA_INLINE("vaes,avx2") void aesdec(SIMD_8x32 K, SIMD_8x32& B) { +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX2_VAES void aesdec(SIMD_8x32 K, SIMD_8x32& B) { B = SIMD_8x32(_mm256_aesdec_epi128(B.raw(), K.raw())); } -BOTAN_FUNC_ISA_INLINE("vaes,avx2") -void aesdec(SIMD_8x32 K, SIMD_8x32& B0, SIMD_8x32& B1, SIMD_8x32& B2, SIMD_8x32& B3) { +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX2_VAES void aesdec( + SIMD_8x32 K, SIMD_8x32& B0, SIMD_8x32& B1, SIMD_8x32& B2, SIMD_8x32& B3) { B0 = SIMD_8x32(_mm256_aesdec_epi128(B0.raw(), K.raw())); B1 = SIMD_8x32(_mm256_aesdec_epi128(B1.raw(), K.raw())); B2 = SIMD_8x32(_mm256_aesdec_epi128(B2.raw(), K.raw())); B3 = SIMD_8x32(_mm256_aesdec_epi128(B3.raw(), K.raw())); } -BOTAN_FUNC_ISA_INLINE("vaes,avx2") void aesdeclast(SIMD_8x32 K, SIMD_8x32& B) { +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX2_VAES void aesdeclast(SIMD_8x32 K, SIMD_8x32& B) { B = SIMD_8x32(_mm256_aesdeclast_epi128(B.raw(), K.raw())); } -BOTAN_FUNC_ISA_INLINE("vaes,avx2") -void aesdeclast(SIMD_8x32 K, SIMD_8x32& B0, SIMD_8x32& B1, SIMD_8x32& B2, SIMD_8x32& B3) { +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX2_VAES void aesdeclast( + SIMD_8x32 K, SIMD_8x32& B0, SIMD_8x32& B1, SIMD_8x32& B2, SIMD_8x32& B3) { B0 = SIMD_8x32(_mm256_aesdeclast_epi128(B0.raw(), K.raw())); B1 = SIMD_8x32(_mm256_aesdeclast_epi128(B1.raw(), K.raw())); B2 = SIMD_8x32(_mm256_aesdeclast_epi128(B2.raw(), K.raw())); B3 = SIMD_8x32(_mm256_aesdeclast_epi128(B3.raw(), K.raw())); } +// NOLINTEND(portability-simd-intrinsics) + } // namespace /* * AES-128 Encryption */ -BOTAN_FUNC_ISA("vaes,avx2") void AES_128::x86_vaes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { +BOTAN_FN_ISA_AVX2_VAES void AES_128::x86_vaes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { const SIMD_8x32 K0 = SIMD_8x32::load_le128(&m_EK[4 * 0]); const SIMD_8x32 K1 = SIMD_8x32::load_le128(&m_EK[4 * 1]); const SIMD_8x32 K2 = SIMD_8x32::load_le128(&m_EK[4 * 2]); @@ -159,7 +164,7 @@ /* * AES-128 Decryption */ -BOTAN_FUNC_ISA("vaes,avx2") void AES_128::x86_vaes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { +BOTAN_FN_ISA_AVX2_VAES void AES_128::x86_vaes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { const SIMD_8x32 K0 = SIMD_8x32::load_le128(&m_DK[4 * 0]); const SIMD_8x32 K1 = SIMD_8x32::load_le128(&m_DK[4 * 1]); const SIMD_8x32 K2 = SIMD_8x32::load_le128(&m_DK[4 * 2]); @@ -244,7 +249,7 @@ /* * AES-192 Encryption */ -BOTAN_FUNC_ISA("vaes,avx2") void AES_192::x86_vaes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { +BOTAN_FN_ISA_AVX2_VAES void AES_192::x86_vaes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { const SIMD_8x32 K0 = SIMD_8x32::load_le128(&m_EK[4 * 0]); const SIMD_8x32 K1 = SIMD_8x32::load_le128(&m_EK[4 * 1]); const SIMD_8x32 K2 = SIMD_8x32::load_le128(&m_EK[4 * 2]); @@ -337,7 +342,7 @@ /* * AES-192 Decryption */ -BOTAN_FUNC_ISA("vaes,avx2") void AES_192::x86_vaes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { +BOTAN_FN_ISA_AVX2_VAES void AES_192::x86_vaes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { const SIMD_8x32 K0 = SIMD_8x32::load_le128(&m_DK[4 * 0]); const SIMD_8x32 K1 = SIMD_8x32::load_le128(&m_DK[4 * 1]); const SIMD_8x32 K2 = SIMD_8x32::load_le128(&m_DK[4 * 2]); @@ -427,7 +432,7 @@ } } -BOTAN_FUNC_ISA("vaes,avx2") void AES_256::x86_vaes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { +BOTAN_FN_ISA_AVX2_VAES void AES_256::x86_vaes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { const SIMD_8x32 K0 = SIMD_8x32::load_le128(&m_EK[4 * 0]); const SIMD_8x32 K1 = SIMD_8x32::load_le128(&m_EK[4 * 1]); const SIMD_8x32 K2 = SIMD_8x32::load_le128(&m_EK[4 * 2]); @@ -528,7 +533,7 @@ /* * AES-256 Decryption */ -BOTAN_FUNC_ISA("vaes,avx2") void AES_256::x86_vaes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { +BOTAN_FN_ISA_AVX2_VAES void AES_256::x86_vaes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { const SIMD_8x32 K0 = SIMD_8x32::load_le128(&m_DK[4 * 0]); const SIMD_8x32 K1 = SIMD_8x32::load_le128(&m_DK[4 * 1]); const SIMD_8x32 K2 = SIMD_8x32::load_le128(&m_DK[4 * 2]); diff -Nru botan3-3.7.1+dfsg/src/lib/block/aes/aes_vaes/info.txt botan3-3.12.0+dfsg/src/lib/block/aes/aes_vaes/info.txt --- botan3-3.7.1+dfsg/src/lib/block/aes/aes_vaes/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/aes/aes_vaes/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + AES_VAES -> 20240803 - + name -> "AES-VAES" @@ -8,6 +8,7 @@ +cpuid simd_avx2 diff -Nru botan3-3.7.1+dfsg/src/lib/block/aes/aes_vperm/aes_vperm.cpp botan3-3.12.0+dfsg/src/lib/block/aes/aes_vperm/aes_vperm.cpp --- botan3-3.7.1+dfsg/src/lib/block/aes/aes_vperm/aes_vperm.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/aes/aes_vperm/aes_vperm.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -13,212 +13,156 @@ #include #include -#include - -#if defined(BOTAN_SIMD_USE_SSE2) - #include -#endif +#include +#include +#include +#include namespace Botan { namespace { -inline SIMD_4x32 BOTAN_FUNC_ISA(BOTAN_VPERM_ISA) shuffle(SIMD_4x32 a, SIMD_4x32 b) { -#if defined(BOTAN_SIMD_USE_SSE2) - return SIMD_4x32(_mm_shuffle_epi8(a.raw(), b.raw())); -#elif defined(BOTAN_SIMD_USE_NEON) - const uint8x16_t tbl = vreinterpretq_u8_u32(a.raw()); - const uint8x16_t idx = vreinterpretq_u8_u32(b.raw()); - - #if defined(BOTAN_TARGET_ARCH_IS_ARM32) - const uint8x8x2_t tbl2 = {vget_low_u8(tbl), vget_high_u8(tbl)}; - - return SIMD_4x32( - vreinterpretq_u32_u8(vcombine_u8(vtbl2_u8(tbl2, vget_low_u8(idx)), vtbl2_u8(tbl2, vget_high_u8(idx))))); - - #else - return SIMD_4x32(vreinterpretq_u32_u8(vqtbl1q_u8(tbl, idx))); - #endif - -#elif defined(BOTAN_SIMD_USE_ALTIVEC) - - const auto zero = vec_splat_s8(0x00); - const auto mask = vec_cmplt(reinterpret_cast<__vector signed char>(b.raw()), zero); - const auto r = vec_perm(reinterpret_cast<__vector signed char>(a.raw()), - reinterpret_cast<__vector signed char>(a.raw()), - reinterpret_cast<__vector unsigned char>(b.raw())); - return SIMD_4x32(reinterpret_cast<__vector unsigned int>(vec_sel(r, zero, mask))); - -#else - #error "No shuffle implementation available" -#endif -} - -inline SIMD_4x32 BOTAN_FUNC_ISA(BOTAN_VPERM_ISA) alignr8(SIMD_4x32 a, SIMD_4x32 b) { -#if defined(BOTAN_SIMD_USE_SSE2) - return SIMD_4x32(_mm_alignr_epi8(a.raw(), b.raw(), 8)); -#elif defined(BOTAN_SIMD_USE_NEON) - return SIMD_4x32(vextq_u32(b.raw(), a.raw(), 2)); -#elif defined(BOTAN_SIMD_USE_ALTIVEC) - const __vector unsigned char mask = {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; - return SIMD_4x32(vec_perm(b.raw(), a.raw(), mask)); -#else - #error "No alignr8 implementation available" -#endif -} - -const SIMD_4x32 k_ipt1 = SIMD_4x32(0x5A2A7000, 0xC2B2E898, 0x52227808, 0xCABAE090); -const SIMD_4x32 k_ipt2 = SIMD_4x32(0x317C4D00, 0x4C01307D, 0xB0FDCC81, 0xCD80B1FC); - -const SIMD_4x32 k_inv1 = SIMD_4x32(0x0D080180, 0x0E05060F, 0x0A0B0C02, 0x04070309); -const SIMD_4x32 k_inv2 = SIMD_4x32(0x0F0B0780, 0x01040A06, 0x02050809, 0x030D0E0C); - -const SIMD_4x32 sb1u = SIMD_4x32(0xCB503E00, 0xB19BE18F, 0x142AF544, 0xA5DF7A6E); -const SIMD_4x32 sb1t = SIMD_4x32(0xFAE22300, 0x3618D415, 0x0D2ED9EF, 0x3BF7CCC1); -const SIMD_4x32 sbou = SIMD_4x32(0x6FBDC700, 0xD0D26D17, 0xC502A878, 0x15AABF7A); -const SIMD_4x32 sbot = SIMD_4x32(0x5FBB6A00, 0xCFE474A5, 0x412B35FA, 0x8E1E90D1); - -const SIMD_4x32 sboud = SIMD_4x32(0x7EF94000, 0x1387EA53, 0xD4943E2D, 0xC7AA6DB9); -const SIMD_4x32 sbotd = SIMD_4x32(0x93441D00, 0x12D7560F, 0xD8C58E9C, 0xCA4B8159); - -const SIMD_4x32 mc_forward[4] = {SIMD_4x32(0x00030201, 0x04070605, 0x080B0A09, 0x0C0F0E0D), - SIMD_4x32(0x04070605, 0x080B0A09, 0x0C0F0E0D, 0x00030201), - SIMD_4x32(0x080B0A09, 0x0C0F0E0D, 0x00030201, 0x04070605), - SIMD_4x32(0x0C0F0E0D, 0x00030201, 0x04070605, 0x080B0A09)}; - -const SIMD_4x32 vperm_sr[4] = { - SIMD_4x32(0x03020100, 0x07060504, 0x0B0A0908, 0x0F0E0D0C), - SIMD_4x32(0x0F0A0500, 0x030E0904, 0x07020D08, 0x0B06010C), - SIMD_4x32(0x0B020900, 0x0F060D04, 0x030A0108, 0x070E050C), - SIMD_4x32(0x070A0D00, 0x0B0E0104, 0x0F020508, 0x0306090C), -}; - -const SIMD_4x32 rcon[10] = { - SIMD_4x32(0x00000070, 0x00000000, 0x00000000, 0x00000000), - SIMD_4x32(0x0000002A, 0x00000000, 0x00000000, 0x00000000), - SIMD_4x32(0x00000098, 0x00000000, 0x00000000, 0x00000000), - SIMD_4x32(0x00000008, 0x00000000, 0x00000000, 0x00000000), - SIMD_4x32(0x0000004D, 0x00000000, 0x00000000, 0x00000000), - SIMD_4x32(0x0000007C, 0x00000000, 0x00000000, 0x00000000), - SIMD_4x32(0x0000007D, 0x00000000, 0x00000000, 0x00000000), - SIMD_4x32(0x00000081, 0x00000000, 0x00000000, 0x00000000), - SIMD_4x32(0x0000001F, 0x00000000, 0x00000000, 0x00000000), - SIMD_4x32(0x00000083, 0x00000000, 0x00000000, 0x00000000), -}; - -const SIMD_4x32 sb2u = SIMD_4x32(0x0B712400, 0xE27A93C6, 0xBC982FCD, 0x5EB7E955); -const SIMD_4x32 sb2t = SIMD_4x32(0x0AE12900, 0x69EB8840, 0xAB82234A, 0xC2A163C8); - -const SIMD_4x32 k_dipt1 = SIMD_4x32(0x0B545F00, 0x0F505B04, 0x114E451A, 0x154A411E); -const SIMD_4x32 k_dipt2 = SIMD_4x32(0x60056500, 0x86E383E6, 0xF491F194, 0x12771772); - -const SIMD_4x32 sb9u = SIMD_4x32(0x9A86D600, 0x851C0353, 0x4F994CC9, 0xCAD51F50); -const SIMD_4x32 sb9t = SIMD_4x32(0xECD74900, 0xC03B1789, 0xB2FBA565, 0x725E2C9E); - -const SIMD_4x32 sbeu = SIMD_4x32(0x26D4D000, 0x46F29296, 0x64B4F6B0, 0x22426004); -const SIMD_4x32 sbet = SIMD_4x32(0xFFAAC100, 0x0C55A6CD, 0x98593E32, 0x9467F36B); - -const SIMD_4x32 sbdu = SIMD_4x32(0xE6B1A200, 0x7D57CCDF, 0x882A4439, 0xF56E9B13); -const SIMD_4x32 sbdt = SIMD_4x32(0x24C6CB00, 0x3CE2FAF7, 0x15DEEFD3, 0x2931180D); - -const SIMD_4x32 sbbu = SIMD_4x32(0x96B44200, 0xD0226492, 0xB0F2D404, 0x602646F6); -const SIMD_4x32 sbbt = SIMD_4x32(0xCD596700, 0xC19498A6, 0x3255AA6B, 0xF3FF0C3E); - -const SIMD_4x32 mcx[4] = { - SIMD_4x32(0x0C0F0E0D, 0x00030201, 0x04070605, 0x080B0A09), - SIMD_4x32(0x080B0A09, 0x0C0F0E0D, 0x00030201, 0x04070605), - SIMD_4x32(0x04070605, 0x080B0A09, 0x0C0F0E0D, 0x00030201), - SIMD_4x32(0x00030201, 0x04070605, 0x080B0A09, 0x0C0F0E0D), -}; - -const SIMD_4x32 mc_backward[4] = { - SIMD_4x32(0x02010003, 0x06050407, 0x0A09080B, 0x0E0D0C0F), - SIMD_4x32(0x0E0D0C0F, 0x02010003, 0x06050407, 0x0A09080B), - SIMD_4x32(0x0A09080B, 0x0E0D0C0F, 0x02010003, 0x06050407), - SIMD_4x32(0x06050407, 0x0A09080B, 0x0E0D0C0F, 0x02010003), -}; +inline SIMD_4x32 BOTAN_FN_ISA_SIMD_4X32 shuffle(SIMD_4x32 tbl, SIMD_4x32 idx) { + if constexpr(std::endian::native == std::endian::little) { + return SIMD_4x32::byte_shuffle(tbl, idx); + } else { + return SIMD_4x32::byte_shuffle(tbl.bswap(), idx.bswap()).bswap(); + } +} -const SIMD_4x32 lo_nibs_mask = SIMD_4x32::splat_u8(0x0F); +inline SIMD_4x32 BOTAN_FN_ISA_SIMD_4X32 masked_shuffle(SIMD_4x32 tbl, SIMD_4x32 idx) { + if constexpr(std::endian::native == std::endian::little) { + return SIMD_4x32::masked_byte_shuffle(tbl, idx); + } else { + return SIMD_4x32::masked_byte_shuffle(tbl.bswap(), idx.bswap()).bswap(); + } +} -inline SIMD_4x32 low_nibs(SIMD_4x32 x) { +inline SIMD_4x32 BOTAN_FN_ISA_SIMD_4X32 shiftrows(SIMD_4x32 x, size_t r) { + const SIMD_4x32 vperm_sr[4] = { + SIMD_4x32(0x03020100, 0x07060504, 0x0B0A0908, 0x0F0E0D0C), + SIMD_4x32(0x0F0A0500, 0x030E0904, 0x07020D08, 0x0B06010C), + SIMD_4x32(0x0B020900, 0x0F060D04, 0x030A0108, 0x070E050C), + SIMD_4x32(0x070A0D00, 0x0B0E0104, 0x0F020508, 0x0306090C), + }; + + return shuffle(x, vperm_sr[r]); +} + +inline SIMD_4x32 BOTAN_FN_ISA_SIMD_4X32 low_nibs(SIMD_4x32 x) { + const SIMD_4x32 lo_nibs_mask = SIMD_4x32::splat_u8(0x0F); return lo_nibs_mask & x; } -inline SIMD_4x32 high_nibs(SIMD_4x32 x) { +inline SIMD_4x32 BOTAN_FN_ISA_SIMD_4X32 high_nibs(SIMD_4x32 x) { + const SIMD_4x32 lo_nibs_mask = SIMD_4x32::splat_u8(0x0F); return (x.shr<4>() & lo_nibs_mask); } -inline SIMD_4x32 BOTAN_FUNC_ISA(BOTAN_VPERM_ISA) aes_enc_first_round(SIMD_4x32 B, SIMD_4x32 K) { +inline SIMD_4x32 BOTAN_FN_ISA_SIMD_4X32 aes_enc_first_round(SIMD_4x32 B, SIMD_4x32 K) { + const SIMD_4x32 k_ipt1 = SIMD_4x32(0x5A2A7000, 0xC2B2E898, 0x52227808, 0xCABAE090); + const SIMD_4x32 k_ipt2 = SIMD_4x32(0x317C4D00, 0x4C01307D, 0xB0FDCC81, 0xCD80B1FC); + return shuffle(k_ipt1, low_nibs(B)) ^ shuffle(k_ipt2, high_nibs(B)) ^ K; } -inline SIMD_4x32 BOTAN_FUNC_ISA(BOTAN_VPERM_ISA) aes_enc_round(SIMD_4x32 B, SIMD_4x32 K, size_t r) { +BOTAN_FORCE_INLINE BOTAN_FN_ISA_SIMD_4X32 std::pair aes_decompose_kinv(const SIMD_4x32 B) { + const SIMD_4x32 k_inv1 = SIMD_4x32(0x0D080180, 0x0E05060F, 0x0A0B0C02, 0x04070309); + const SIMD_4x32 k_inv2 = SIMD_4x32(0x0F0B0780, 0x01040A06, 0x02050809, 0x030D0E0C); + const SIMD_4x32 Bh = high_nibs(B); SIMD_4x32 Bl = low_nibs(B); const SIMD_4x32 t2 = shuffle(k_inv2, Bl); Bl ^= Bh; - const SIMD_4x32 t5 = Bl ^ shuffle(k_inv1, t2 ^ shuffle(k_inv1, Bh)); - const SIMD_4x32 t6 = Bh ^ shuffle(k_inv1, t2 ^ shuffle(k_inv1, Bl)); + const SIMD_4x32 t5 = Bl ^ masked_shuffle(k_inv1, t2 ^ shuffle(k_inv1, Bh)); + const SIMD_4x32 t6 = Bh ^ masked_shuffle(k_inv1, t2 ^ shuffle(k_inv1, Bl)); - const SIMD_4x32 t7 = shuffle(sb1t, t6) ^ shuffle(sb1u, t5) ^ K; - const SIMD_4x32 t8 = shuffle(sb2t, t6) ^ shuffle(sb2u, t5) ^ shuffle(t7, mc_forward[r % 4]); + return std::make_pair(t5, t6); +} + +inline SIMD_4x32 BOTAN_FN_ISA_SIMD_4X32 aes_enc_round(SIMD_4x32 B, SIMD_4x32 K, size_t r) { + const SIMD_4x32 sb2u = SIMD_4x32(0x0B712400, 0xE27A93C6, 0xBC982FCD, 0x5EB7E955); + const SIMD_4x32 sb2t = SIMD_4x32(0x0AE12900, 0x69EB8840, 0xAB82234A, 0xC2A163C8); + + const SIMD_4x32 mc_forward[4] = {SIMD_4x32(0x00030201, 0x04070605, 0x080B0A09, 0x0C0F0E0D), + SIMD_4x32(0x04070605, 0x080B0A09, 0x0C0F0E0D, 0x00030201), + SIMD_4x32(0x080B0A09, 0x0C0F0E0D, 0x00030201, 0x04070605), + SIMD_4x32(0x0C0F0E0D, 0x00030201, 0x04070605, 0x080B0A09)}; + const SIMD_4x32 mc_backward[4] = { + SIMD_4x32(0x02010003, 0x06050407, 0x0A09080B, 0x0E0D0C0F), + SIMD_4x32(0x0E0D0C0F, 0x02010003, 0x06050407, 0x0A09080B), + SIMD_4x32(0x0A09080B, 0x0E0D0C0F, 0x02010003, 0x06050407), + SIMD_4x32(0x06050407, 0x0A09080B, 0x0E0D0C0F, 0x02010003), + }; + const SIMD_4x32 sb1u = SIMD_4x32(0xCB503E00, 0xB19BE18F, 0x142AF544, 0xA5DF7A6E); + const SIMD_4x32 sb1t = SIMD_4x32(0xFAE22300, 0x3618D415, 0x0D2ED9EF, 0x3BF7CCC1); + + const auto [t5, t6] = aes_decompose_kinv(B); + + const SIMD_4x32 t7 = masked_shuffle(sb1t, t6) ^ masked_shuffle(sb1u, t5) ^ K; + const SIMD_4x32 t8 = masked_shuffle(sb2t, t6) ^ masked_shuffle(sb2u, t5) ^ shuffle(t7, mc_forward[r % 4]); return shuffle(t8, mc_forward[r % 4]) ^ shuffle(t7, mc_backward[r % 4]) ^ t8; } -inline SIMD_4x32 BOTAN_FUNC_ISA(BOTAN_VPERM_ISA) aes_enc_last_round(SIMD_4x32 B, SIMD_4x32 K, size_t r) { - const SIMD_4x32 Bh = high_nibs(B); - SIMD_4x32 Bl = low_nibs(B); - const SIMD_4x32 t2 = shuffle(k_inv2, Bl); - Bl ^= Bh; +inline SIMD_4x32 BOTAN_FN_ISA_SIMD_4X32 aes_enc_last_round(SIMD_4x32 B, SIMD_4x32 K, size_t r) { + const SIMD_4x32 sbou = SIMD_4x32(0x6FBDC700, 0xD0D26D17, 0xC502A878, 0x15AABF7A); + const SIMD_4x32 sbot = SIMD_4x32(0x5FBB6A00, 0xCFE474A5, 0x412B35FA, 0x8E1E90D1); - const SIMD_4x32 t5 = Bl ^ shuffle(k_inv1, t2 ^ shuffle(k_inv1, Bh)); - const SIMD_4x32 t6 = Bh ^ shuffle(k_inv1, t2 ^ shuffle(k_inv1, Bl)); + const auto [t5, t6] = aes_decompose_kinv(B); - return shuffle(shuffle(sbou, t5) ^ shuffle(sbot, t6) ^ K, vperm_sr[r % 4]); + return shiftrows(masked_shuffle(sbou, t5) ^ masked_shuffle(sbot, t6) ^ K, r % 4); } -inline SIMD_4x32 BOTAN_FUNC_ISA(BOTAN_VPERM_ISA) aes_dec_first_round(SIMD_4x32 B, SIMD_4x32 K) { +inline SIMD_4x32 BOTAN_FN_ISA_SIMD_4X32 aes_dec_first_round(SIMD_4x32 B, SIMD_4x32 K) { + const SIMD_4x32 k_dipt1 = SIMD_4x32(0x0B545F00, 0x0F505B04, 0x114E451A, 0x154A411E); + const SIMD_4x32 k_dipt2 = SIMD_4x32(0x60056500, 0x86E383E6, 0xF491F194, 0x12771772); + return shuffle(k_dipt1, low_nibs(B)) ^ shuffle(k_dipt2, high_nibs(B)) ^ K; } -inline SIMD_4x32 BOTAN_FUNC_ISA(BOTAN_VPERM_ISA) aes_dec_round(SIMD_4x32 B, SIMD_4x32 K, size_t r) { - const SIMD_4x32 Bh = high_nibs(B); - B = low_nibs(B); - const SIMD_4x32 t2 = shuffle(k_inv2, B); +inline SIMD_4x32 BOTAN_FN_ISA_SIMD_4X32 aes_dec_round(SIMD_4x32 B, SIMD_4x32 K, size_t r) { + const SIMD_4x32 mcx[4] = { + SIMD_4x32(0x0C0F0E0D, 0x00030201, 0x04070605, 0x080B0A09), + SIMD_4x32(0x080B0A09, 0x0C0F0E0D, 0x00030201, 0x04070605), + SIMD_4x32(0x04070605, 0x080B0A09, 0x0C0F0E0D, 0x00030201), + SIMD_4x32(0x00030201, 0x04070605, 0x080B0A09, 0x0C0F0E0D), + }; - B ^= Bh; + const SIMD_4x32 sbbu = SIMD_4x32(0x96B44200, 0xD0226492, 0xB0F2D404, 0x602646F6); + const SIMD_4x32 sbbt = SIMD_4x32(0xCD596700, 0xC19498A6, 0x3255AA6B, 0xF3FF0C3E); + const SIMD_4x32 sbdu = SIMD_4x32(0xE6B1A200, 0x7D57CCDF, 0x882A4439, 0xF56E9B13); + const SIMD_4x32 sbdt = SIMD_4x32(0x24C6CB00, 0x3CE2FAF7, 0x15DEEFD3, 0x2931180D); + const SIMD_4x32 sbeu = SIMD_4x32(0x26D4D000, 0x46F29296, 0x64B4F6B0, 0x22426004); + const SIMD_4x32 sbet = SIMD_4x32(0xFFAAC100, 0x0C55A6CD, 0x98593E32, 0x9467F36B); + const SIMD_4x32 sb9u = SIMD_4x32(0x9A86D600, 0x851C0353, 0x4F994CC9, 0xCAD51F50); + const SIMD_4x32 sb9t = SIMD_4x32(0xECD74900, 0xC03B1789, 0xB2FBA565, 0x725E2C9E); - const SIMD_4x32 t5 = B ^ shuffle(k_inv1, t2 ^ shuffle(k_inv1, Bh)); - const SIMD_4x32 t6 = Bh ^ shuffle(k_inv1, t2 ^ shuffle(k_inv1, B)); + const auto [t5, t6] = aes_decompose_kinv(B); const SIMD_4x32 mc = mcx[(r - 1) % 4]; - const SIMD_4x32 t8 = shuffle(sb9t, t6) ^ shuffle(sb9u, t5) ^ K; - const SIMD_4x32 t9 = shuffle(t8, mc) ^ shuffle(sbdu, t5) ^ shuffle(sbdt, t6); - const SIMD_4x32 t12 = shuffle(t9, mc) ^ shuffle(sbbu, t5) ^ shuffle(sbbt, t6); - return shuffle(t12, mc) ^ shuffle(sbeu, t5) ^ shuffle(sbet, t6); + const SIMD_4x32 t8 = masked_shuffle(sb9t, t6) ^ masked_shuffle(sb9u, t5) ^ K; + const SIMD_4x32 t9 = shuffle(t8, mc) ^ masked_shuffle(sbdu, t5) ^ masked_shuffle(sbdt, t6); + const SIMD_4x32 t12 = shuffle(t9, mc) ^ masked_shuffle(sbbu, t5) ^ masked_shuffle(sbbt, t6); + return shuffle(t12, mc) ^ masked_shuffle(sbeu, t5) ^ masked_shuffle(sbet, t6); } -inline SIMD_4x32 BOTAN_FUNC_ISA(BOTAN_VPERM_ISA) aes_dec_last_round(SIMD_4x32 B, SIMD_4x32 K, size_t r) { - const uint32_t which_sr = ((((r - 1) << 4) ^ 48) & 48) / 16; - - const SIMD_4x32 Bh = high_nibs(B); - B = low_nibs(B); - const SIMD_4x32 t2 = shuffle(k_inv2, B); +inline SIMD_4x32 BOTAN_FN_ISA_SIMD_4X32 aes_dec_last_round(SIMD_4x32 B, SIMD_4x32 K, size_t r) { + const SIMD_4x32 sboud = SIMD_4x32(0x7EF94000, 0x1387EA53, 0xD4943E2D, 0xC7AA6DB9); + const SIMD_4x32 sbotd = SIMD_4x32(0x93441D00, 0x12D7560F, 0xD8C58E9C, 0xCA4B8159); - B ^= Bh; + const uint32_t which_sr = ((((r - 1) << 4) ^ 48) & 48) / 16; - const SIMD_4x32 t5 = B ^ shuffle(k_inv1, t2 ^ shuffle(k_inv1, Bh)); - const SIMD_4x32 t6 = Bh ^ shuffle(k_inv1, t2 ^ shuffle(k_inv1, B)); + const auto [t5, t6] = aes_decompose_kinv(B); - const SIMD_4x32 x = shuffle(sboud, t5) ^ shuffle(sbotd, t6) ^ K; - return shuffle(x, vperm_sr[which_sr]); + const SIMD_4x32 x = masked_shuffle(sboud, t5) ^ masked_shuffle(sbotd, t6) ^ K; + return shiftrows(x, which_sr); } -void BOTAN_FUNC_ISA(BOTAN_VPERM_ISA) - vperm_encrypt_blocks(const uint8_t in[], uint8_t out[], size_t blocks, const SIMD_4x32 K[], size_t rounds) { +void BOTAN_FN_ISA_SIMD_4X32 +vperm_encrypt_blocks(const uint8_t in[], uint8_t out[], size_t blocks, const SIMD_4x32 K[], size_t rounds) { CT::poison(in, blocks * 16); const size_t blocks2 = blocks - (blocks % 2); @@ -259,8 +203,8 @@ CT::unpoison(out, blocks * 16); } -void BOTAN_FUNC_ISA(BOTAN_VPERM_ISA) - vperm_decrypt_blocks(const uint8_t in[], uint8_t out[], size_t blocks, const SIMD_4x32 K[], size_t rounds) { +void BOTAN_FN_ISA_SIMD_4X32 +vperm_decrypt_blocks(const uint8_t in[], uint8_t out[], size_t blocks, const SIMD_4x32 K[], size_t rounds) { CT::poison(in, blocks * 16); const size_t blocks2 = blocks - (blocks % 2); @@ -303,121 +247,121 @@ } // namespace -void AES_128::vperm_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { +void BOTAN_FN_ISA_SIMD_4X32 AES_128::vperm_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { const SIMD_4x32 K[11] = { - SIMD_4x32(&m_EK[4 * 0]), - SIMD_4x32(&m_EK[4 * 1]), - SIMD_4x32(&m_EK[4 * 2]), - SIMD_4x32(&m_EK[4 * 3]), - SIMD_4x32(&m_EK[4 * 4]), - SIMD_4x32(&m_EK[4 * 5]), - SIMD_4x32(&m_EK[4 * 6]), - SIMD_4x32(&m_EK[4 * 7]), - SIMD_4x32(&m_EK[4 * 8]), - SIMD_4x32(&m_EK[4 * 9]), - SIMD_4x32(&m_EK[4 * 10]), + SIMD_4x32::load_le(&m_EK[4 * 0]), + SIMD_4x32::load_le(&m_EK[4 * 1]), + SIMD_4x32::load_le(&m_EK[4 * 2]), + SIMD_4x32::load_le(&m_EK[4 * 3]), + SIMD_4x32::load_le(&m_EK[4 * 4]), + SIMD_4x32::load_le(&m_EK[4 * 5]), + SIMD_4x32::load_le(&m_EK[4 * 6]), + SIMD_4x32::load_le(&m_EK[4 * 7]), + SIMD_4x32::load_le(&m_EK[4 * 8]), + SIMD_4x32::load_le(&m_EK[4 * 9]), + SIMD_4x32::load_le(&m_EK[4 * 10]), }; return vperm_encrypt_blocks(in, out, blocks, K, 10); } -void AES_128::vperm_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { +void BOTAN_FN_ISA_SIMD_4X32 AES_128::vperm_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { const SIMD_4x32 K[11] = { - SIMD_4x32(&m_DK[4 * 0]), - SIMD_4x32(&m_DK[4 * 1]), - SIMD_4x32(&m_DK[4 * 2]), - SIMD_4x32(&m_DK[4 * 3]), - SIMD_4x32(&m_DK[4 * 4]), - SIMD_4x32(&m_DK[4 * 5]), - SIMD_4x32(&m_DK[4 * 6]), - SIMD_4x32(&m_DK[4 * 7]), - SIMD_4x32(&m_DK[4 * 8]), - SIMD_4x32(&m_DK[4 * 9]), - SIMD_4x32(&m_DK[4 * 10]), + SIMD_4x32::load_le(&m_DK[4 * 0]), + SIMD_4x32::load_le(&m_DK[4 * 1]), + SIMD_4x32::load_le(&m_DK[4 * 2]), + SIMD_4x32::load_le(&m_DK[4 * 3]), + SIMD_4x32::load_le(&m_DK[4 * 4]), + SIMD_4x32::load_le(&m_DK[4 * 5]), + SIMD_4x32::load_le(&m_DK[4 * 6]), + SIMD_4x32::load_le(&m_DK[4 * 7]), + SIMD_4x32::load_le(&m_DK[4 * 8]), + SIMD_4x32::load_le(&m_DK[4 * 9]), + SIMD_4x32::load_le(&m_DK[4 * 10]), }; return vperm_decrypt_blocks(in, out, blocks, K, 10); } -void AES_192::vperm_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { +void BOTAN_FN_ISA_SIMD_4X32 AES_192::vperm_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { const SIMD_4x32 K[13] = { - SIMD_4x32(&m_EK[4 * 0]), - SIMD_4x32(&m_EK[4 * 1]), - SIMD_4x32(&m_EK[4 * 2]), - SIMD_4x32(&m_EK[4 * 3]), - SIMD_4x32(&m_EK[4 * 4]), - SIMD_4x32(&m_EK[4 * 5]), - SIMD_4x32(&m_EK[4 * 6]), - SIMD_4x32(&m_EK[4 * 7]), - SIMD_4x32(&m_EK[4 * 8]), - SIMD_4x32(&m_EK[4 * 9]), - SIMD_4x32(&m_EK[4 * 10]), - SIMD_4x32(&m_EK[4 * 11]), - SIMD_4x32(&m_EK[4 * 12]), + SIMD_4x32::load_le(&m_EK[4 * 0]), + SIMD_4x32::load_le(&m_EK[4 * 1]), + SIMD_4x32::load_le(&m_EK[4 * 2]), + SIMD_4x32::load_le(&m_EK[4 * 3]), + SIMD_4x32::load_le(&m_EK[4 * 4]), + SIMD_4x32::load_le(&m_EK[4 * 5]), + SIMD_4x32::load_le(&m_EK[4 * 6]), + SIMD_4x32::load_le(&m_EK[4 * 7]), + SIMD_4x32::load_le(&m_EK[4 * 8]), + SIMD_4x32::load_le(&m_EK[4 * 9]), + SIMD_4x32::load_le(&m_EK[4 * 10]), + SIMD_4x32::load_le(&m_EK[4 * 11]), + SIMD_4x32::load_le(&m_EK[4 * 12]), }; return vperm_encrypt_blocks(in, out, blocks, K, 12); } -void AES_192::vperm_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { +void BOTAN_FN_ISA_SIMD_4X32 AES_192::vperm_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { const SIMD_4x32 K[13] = { - SIMD_4x32(&m_DK[4 * 0]), - SIMD_4x32(&m_DK[4 * 1]), - SIMD_4x32(&m_DK[4 * 2]), - SIMD_4x32(&m_DK[4 * 3]), - SIMD_4x32(&m_DK[4 * 4]), - SIMD_4x32(&m_DK[4 * 5]), - SIMD_4x32(&m_DK[4 * 6]), - SIMD_4x32(&m_DK[4 * 7]), - SIMD_4x32(&m_DK[4 * 8]), - SIMD_4x32(&m_DK[4 * 9]), - SIMD_4x32(&m_DK[4 * 10]), - SIMD_4x32(&m_DK[4 * 11]), - SIMD_4x32(&m_DK[4 * 12]), + SIMD_4x32::load_le(&m_DK[4 * 0]), + SIMD_4x32::load_le(&m_DK[4 * 1]), + SIMD_4x32::load_le(&m_DK[4 * 2]), + SIMD_4x32::load_le(&m_DK[4 * 3]), + SIMD_4x32::load_le(&m_DK[4 * 4]), + SIMD_4x32::load_le(&m_DK[4 * 5]), + SIMD_4x32::load_le(&m_DK[4 * 6]), + SIMD_4x32::load_le(&m_DK[4 * 7]), + SIMD_4x32::load_le(&m_DK[4 * 8]), + SIMD_4x32::load_le(&m_DK[4 * 9]), + SIMD_4x32::load_le(&m_DK[4 * 10]), + SIMD_4x32::load_le(&m_DK[4 * 11]), + SIMD_4x32::load_le(&m_DK[4 * 12]), }; return vperm_decrypt_blocks(in, out, blocks, K, 12); } -void AES_256::vperm_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { +void BOTAN_FN_ISA_SIMD_4X32 AES_256::vperm_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { const SIMD_4x32 K[15] = { - SIMD_4x32(&m_EK[4 * 0]), - SIMD_4x32(&m_EK[4 * 1]), - SIMD_4x32(&m_EK[4 * 2]), - SIMD_4x32(&m_EK[4 * 3]), - SIMD_4x32(&m_EK[4 * 4]), - SIMD_4x32(&m_EK[4 * 5]), - SIMD_4x32(&m_EK[4 * 6]), - SIMD_4x32(&m_EK[4 * 7]), - SIMD_4x32(&m_EK[4 * 8]), - SIMD_4x32(&m_EK[4 * 9]), - SIMD_4x32(&m_EK[4 * 10]), - SIMD_4x32(&m_EK[4 * 11]), - SIMD_4x32(&m_EK[4 * 12]), - SIMD_4x32(&m_EK[4 * 13]), - SIMD_4x32(&m_EK[4 * 14]), + SIMD_4x32::load_le(&m_EK[4 * 0]), + SIMD_4x32::load_le(&m_EK[4 * 1]), + SIMD_4x32::load_le(&m_EK[4 * 2]), + SIMD_4x32::load_le(&m_EK[4 * 3]), + SIMD_4x32::load_le(&m_EK[4 * 4]), + SIMD_4x32::load_le(&m_EK[4 * 5]), + SIMD_4x32::load_le(&m_EK[4 * 6]), + SIMD_4x32::load_le(&m_EK[4 * 7]), + SIMD_4x32::load_le(&m_EK[4 * 8]), + SIMD_4x32::load_le(&m_EK[4 * 9]), + SIMD_4x32::load_le(&m_EK[4 * 10]), + SIMD_4x32::load_le(&m_EK[4 * 11]), + SIMD_4x32::load_le(&m_EK[4 * 12]), + SIMD_4x32::load_le(&m_EK[4 * 13]), + SIMD_4x32::load_le(&m_EK[4 * 14]), }; return vperm_encrypt_blocks(in, out, blocks, K, 14); } -void AES_256::vperm_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { +void BOTAN_FN_ISA_SIMD_4X32 AES_256::vperm_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { const SIMD_4x32 K[15] = { - SIMD_4x32(&m_DK[4 * 0]), - SIMD_4x32(&m_DK[4 * 1]), - SIMD_4x32(&m_DK[4 * 2]), - SIMD_4x32(&m_DK[4 * 3]), - SIMD_4x32(&m_DK[4 * 4]), - SIMD_4x32(&m_DK[4 * 5]), - SIMD_4x32(&m_DK[4 * 6]), - SIMD_4x32(&m_DK[4 * 7]), - SIMD_4x32(&m_DK[4 * 8]), - SIMD_4x32(&m_DK[4 * 9]), - SIMD_4x32(&m_DK[4 * 10]), - SIMD_4x32(&m_DK[4 * 11]), - SIMD_4x32(&m_DK[4 * 12]), - SIMD_4x32(&m_DK[4 * 13]), - SIMD_4x32(&m_DK[4 * 14]), + SIMD_4x32::load_le(&m_DK[4 * 0]), + SIMD_4x32::load_le(&m_DK[4 * 1]), + SIMD_4x32::load_le(&m_DK[4 * 2]), + SIMD_4x32::load_le(&m_DK[4 * 3]), + SIMD_4x32::load_le(&m_DK[4 * 4]), + SIMD_4x32::load_le(&m_DK[4 * 5]), + SIMD_4x32::load_le(&m_DK[4 * 6]), + SIMD_4x32::load_le(&m_DK[4 * 7]), + SIMD_4x32::load_le(&m_DK[4 * 8]), + SIMD_4x32::load_le(&m_DK[4 * 9]), + SIMD_4x32::load_le(&m_DK[4 * 10]), + SIMD_4x32::load_le(&m_DK[4 * 11]), + SIMD_4x32::load_le(&m_DK[4 * 12]), + SIMD_4x32::load_le(&m_DK[4 * 13]), + SIMD_4x32::load_le(&m_DK[4 * 14]), }; return vperm_decrypt_blocks(in, out, blocks, K, 14); @@ -425,22 +369,25 @@ namespace { -inline SIMD_4x32 BOTAN_FUNC_ISA(BOTAN_VPERM_ISA) - aes_schedule_transform(SIMD_4x32 input, SIMD_4x32 table_1, SIMD_4x32 table_2) { +inline SIMD_4x32 BOTAN_FN_ISA_SIMD_4X32 aes_schedule_transform(SIMD_4x32 input, SIMD_4x32 table_1, SIMD_4x32 table_2) { return shuffle(table_1, low_nibs(input)) ^ shuffle(table_2, high_nibs(input)); } -SIMD_4x32 BOTAN_FUNC_ISA(BOTAN_VPERM_ISA) aes_schedule_mangle(SIMD_4x32 k, uint8_t round_no) { +inline SIMD_4x32 BOTAN_FN_ISA_SIMD_4X32 aes_schedule_transform_init(SIMD_4x32 input) { + return aes_enc_first_round(input, SIMD_4x32()); +} + +SIMD_4x32 BOTAN_FN_ISA_SIMD_4X32 aes_schedule_mangle(SIMD_4x32 k, uint8_t round_no) { const SIMD_4x32 mc_forward0(0x00030201, 0x04070605, 0x080B0A09, 0x0C0F0E0D); SIMD_4x32 t = shuffle(k ^ SIMD_4x32::splat_u8(0x5B), mc_forward0); SIMD_4x32 t2 = t; t = shuffle(t, mc_forward0); t2 = t ^ t2 ^ shuffle(t, mc_forward0); - return shuffle(t2, vperm_sr[round_no % 4]); + return shiftrows(t2, round_no % 4); } -SIMD_4x32 BOTAN_FUNC_ISA(BOTAN_VPERM_ISA) aes_schedule_mangle_dec(SIMD_4x32 k, uint8_t round_no) { +SIMD_4x32 BOTAN_FN_ISA_SIMD_4X32 aes_schedule_mangle_dec(SIMD_4x32 k, uint8_t round_no) { const SIMD_4x32 mc_forward0(0x00030201, 0x04070605, 0x080B0A09, 0x0C0F0E0D); const SIMD_4x32 dsk[8] = { @@ -466,19 +413,18 @@ t = aes_schedule_transform(t, dsk[6], dsk[7]); output = shuffle(t ^ output, mc_forward0); - return shuffle(output, vperm_sr[round_no % 4]); + return shiftrows(output, round_no % 4); } -SIMD_4x32 BOTAN_FUNC_ISA(BOTAN_VPERM_ISA) aes_schedule_mangle_last(SIMD_4x32 k, uint8_t round_no) { +SIMD_4x32 BOTAN_FN_ISA_SIMD_4X32 aes_schedule_mangle_last(SIMD_4x32 k, uint8_t round_no) { const SIMD_4x32 out_tr1(0xD6B66000, 0xFF9F4929, 0xDEBE6808, 0xF7974121); const SIMD_4x32 out_tr2(0x50BCEC00, 0x01EDBD51, 0xB05C0CE0, 0xE10D5DB1); - k = shuffle(k, vperm_sr[round_no % 4]); - k ^= SIMD_4x32::splat_u8(0x5B); + k = shiftrows(k, round_no % 4) ^ SIMD_4x32::splat_u8(0x5B); return aes_schedule_transform(k, out_tr1, out_tr2); } -SIMD_4x32 BOTAN_FUNC_ISA(BOTAN_VPERM_ISA) aes_schedule_mangle_last_dec(SIMD_4x32 k) { +SIMD_4x32 BOTAN_FN_ISA_SIMD_4X32 aes_schedule_mangle_last_dec(SIMD_4x32 k) { const SIMD_4x32 deskew1(0x47A4E300, 0x07E4A340, 0x5DBEF91A, 0x1DFEB95A); const SIMD_4x32 deskew2(0x83EA6900, 0x5F36B5DC, 0xF49D1E77, 0x2841C2AB); @@ -486,31 +432,39 @@ return aes_schedule_transform(k, deskew1, deskew2); } -SIMD_4x32 BOTAN_FUNC_ISA(BOTAN_VPERM_ISA) aes_schedule_round(SIMD_4x32 input1, SIMD_4x32 input2) { +SIMD_4x32 BOTAN_FN_ISA_SIMD_4X32 aes_schedule_round(SIMD_4x32 input1, SIMD_4x32 input2) { + const SIMD_4x32 sb1u = SIMD_4x32(0xCB503E00, 0xB19BE18F, 0x142AF544, 0xA5DF7A6E); + const SIMD_4x32 sb1t = SIMD_4x32(0xFAE22300, 0x3618D415, 0x0D2ED9EF, 0x3BF7CCC1); + SIMD_4x32 smeared = input2 ^ input2.shift_elems_left<1>(); smeared ^= smeared.shift_elems_left<2>(); smeared ^= SIMD_4x32::splat_u8(0x5B); - const SIMD_4x32 Bh = high_nibs(input1); - SIMD_4x32 Bl = low_nibs(input1); + const auto [t5, t6] = aes_decompose_kinv(input1); - const SIMD_4x32 t2 = shuffle(k_inv2, Bl); - - Bl ^= Bh; - - SIMD_4x32 t5 = Bl ^ shuffle(k_inv1, t2 ^ shuffle(k_inv1, Bh)); - SIMD_4x32 t6 = Bh ^ shuffle(k_inv1, t2 ^ shuffle(k_inv1, Bl)); - - return smeared ^ shuffle(sb1u, t5) ^ shuffle(sb1t, t6); + return smeared ^ masked_shuffle(sb1u, t5) ^ masked_shuffle(sb1t, t6); } -SIMD_4x32 BOTAN_FUNC_ISA(BOTAN_VPERM_ISA) aes_schedule_round(SIMD_4x32 rc, SIMD_4x32 input1, SIMD_4x32 input2) { +SIMD_4x32 BOTAN_FN_ISA_SIMD_4X32 aes_schedule_round_rcon(size_t rc, SIMD_4x32 input1, SIMD_4x32 input2) { + const SIMD_4x32 rcon[10] = { + SIMD_4x32(0x00000070, 0x00000000, 0x00000000, 0x00000000), + SIMD_4x32(0x0000002A, 0x00000000, 0x00000000, 0x00000000), + SIMD_4x32(0x00000098, 0x00000000, 0x00000000, 0x00000000), + SIMD_4x32(0x00000008, 0x00000000, 0x00000000, 0x00000000), + SIMD_4x32(0x0000004D, 0x00000000, 0x00000000, 0x00000000), + SIMD_4x32(0x0000007C, 0x00000000, 0x00000000, 0x00000000), + SIMD_4x32(0x0000007D, 0x00000000, 0x00000000, 0x00000000), + SIMD_4x32(0x00000081, 0x00000000, 0x00000000, 0x00000000), + SIMD_4x32(0x0000001F, 0x00000000, 0x00000000, 0x00000000), + SIMD_4x32(0x00000083, 0x00000000, 0x00000000, 0x00000000), + }; + // This byte shuffle is equivalent to alignr<1>(shuffle32(input1, (3,3,3,3))); const SIMD_4x32 shuffle3333_15 = SIMD_4x32::splat(0x0C0F0E0D); - return aes_schedule_round(shuffle(input1, shuffle3333_15), input2 ^ rc); + return aes_schedule_round(shuffle(input1, shuffle3333_15), input2 ^ rcon[rc]); } -SIMD_4x32 BOTAN_FUNC_ISA(BOTAN_VPERM_ISA) aes_schedule_192_smear(SIMD_4x32 x, SIMD_4x32 y) { +SIMD_4x32 BOTAN_FN_ISA_SIMD_4X32 aes_schedule_192_smear(SIMD_4x32 x, SIMD_4x32 y) { const SIMD_4x32 shuffle3332 = SIMD_4x32(0x0B0A0908, 0x0F0E0D0C, 0x0F0E0D0C, 0x0F0E0D0C); const SIMD_4x32 shuffle2000 = SIMD_4x32(0x03020100, 0x03020100, 0x03020100, 0x0B0A0908); @@ -521,49 +475,52 @@ } // namespace -void AES_128::vperm_key_schedule(const uint8_t keyb[], size_t /*unused*/) { +// NOLINTBEGIN(readability-container-data-pointer) + +void BOTAN_FN_ISA_SIMD_4X32 AES_128::vperm_key_schedule(const uint8_t keyb[], size_t /*unused*/) { m_EK.resize(11 * 4); m_DK.resize(11 * 4); SIMD_4x32 key = SIMD_4x32::load_le(keyb); - shuffle(key, vperm_sr[2]).store_le(&m_DK[4 * 10]); + shiftrows(key, 2).store_le(&m_DK[4 * 10]); - key = aes_schedule_transform(key, k_ipt1, k_ipt2); + key = aes_schedule_transform_init(key); key.store_le(&m_EK[0]); for(size_t i = 1; i != 10; ++i) { - key = aes_schedule_round(rcon[i - 1], key, key); + key = aes_schedule_round_rcon(i - 1, key, key); aes_schedule_mangle(key, (12 - i) % 4).store_le(&m_EK[4 * i]); aes_schedule_mangle_dec(key, (10 - i) % 4).store_le(&m_DK[4 * (10 - i)]); } - key = aes_schedule_round(rcon[9], key, key); + key = aes_schedule_round_rcon(9, key, key); aes_schedule_mangle_last(key, 2).store_le(&m_EK[4 * 10]); aes_schedule_mangle_last_dec(key).store_le(&m_DK[0]); } -void AES_192::vperm_key_schedule(const uint8_t keyb[], size_t /*unused*/) { +void BOTAN_FN_ISA_SIMD_4X32 AES_192::vperm_key_schedule(const uint8_t keyb[], size_t /*unused*/) { m_EK.resize(13 * 4); m_DK.resize(13 * 4); SIMD_4x32 key1 = SIMD_4x32::load_le(keyb); SIMD_4x32 key2 = SIMD_4x32::load_le(keyb + 8); - shuffle(key1, vperm_sr[0]).store_le(&m_DK[12 * 4]); + shiftrows(key1, 0).store_le(&m_DK[12 * 4]); - key1 = aes_schedule_transform(key1, k_ipt1, k_ipt2); - key2 = aes_schedule_transform(key2, k_ipt1, k_ipt2); + key1 = aes_schedule_transform_init(key1); + key2 = aes_schedule_transform_init(key2); key1.store_le(&m_EK[0]); for(size_t i = 0; i != 4; ++i) { // key2 with 8 high bytes masked off SIMD_4x32 t = key2; - key2 = aes_schedule_round(rcon[2 * i], key2, key1); - const SIMD_4x32 key2t = alignr8(key2, t); + key2 = aes_schedule_round_rcon(2 * i, key2, key1); + const auto key2t = SIMD_4x32::alignr8(key2, t); + aes_schedule_mangle(key2t, (i + 3) % 4).store_le(&m_EK[4 * (3 * i + 1)]); aes_schedule_mangle_dec(key2t, (i + 3) % 4).store_le(&m_DK[4 * (11 - 3 * i)]); @@ -572,7 +529,7 @@ aes_schedule_mangle(t, (i + 2) % 4).store_le(&m_EK[4 * (3 * i + 2)]); aes_schedule_mangle_dec(t, (i + 2) % 4).store_le(&m_DK[4 * (10 - 3 * i)]); - key2 = aes_schedule_round(rcon[2 * i + 1], t, key2); + key2 = aes_schedule_round_rcon(2 * i + 1, t, key2); if(i == 3) { aes_schedule_mangle_last(key2, (i + 1) % 4).store_le(&m_EK[4 * (3 * i + 3)]); @@ -587,17 +544,17 @@ } } -void AES_256::vperm_key_schedule(const uint8_t keyb[], size_t /*unused*/) { +void BOTAN_FN_ISA_SIMD_4X32 AES_256::vperm_key_schedule(const uint8_t keyb[], size_t /*unused*/) { m_EK.resize(15 * 4); m_DK.resize(15 * 4); SIMD_4x32 key1 = SIMD_4x32::load_le(keyb); SIMD_4x32 key2 = SIMD_4x32::load_le(keyb + 16); - shuffle(key1, vperm_sr[2]).store_le(&m_DK[4 * 14]); + shiftrows(key1, 2).store_le(&m_DK[4 * 14]); - key1 = aes_schedule_transform(key1, k_ipt1, k_ipt2); - key2 = aes_schedule_transform(key2, k_ipt1, k_ipt2); + key1 = aes_schedule_transform_init(key1); + key2 = aes_schedule_transform_init(key2); key1.store_le(&m_EK[0]); aes_schedule_mangle(key2, 3).store_le(&m_EK[4]); @@ -608,7 +565,7 @@ for(size_t i = 2; i != 14; i += 2) { const SIMD_4x32 k_t = key2; - key1 = key2 = aes_schedule_round(rcon[(i / 2) - 1], key2, key1); + key1 = key2 = aes_schedule_round_rcon((i / 2) - 1, key2, key1); aes_schedule_mangle(key2, i % 4).store_le(&m_EK[4 * i]); aes_schedule_mangle_dec(key2, (i + 2) % 4).store_le(&m_DK[4 * (14 - i)]); @@ -619,10 +576,12 @@ aes_schedule_mangle_dec(key2, (i + 1) % 4).store_le(&m_DK[4 * (13 - i)]); } - key2 = aes_schedule_round(rcon[6], key2, key1); + key2 = aes_schedule_round_rcon(6, key2, key1); aes_schedule_mangle_last(key2, 2).store_le(&m_EK[4 * 14]); aes_schedule_mangle_last_dec(key2).store_le(&m_DK[0]); } +// NOLINTEND(readability-container-data-pointer) + } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/block/aes/aes_vperm/info.txt botan3-3.12.0+dfsg/src/lib/block/aes/aes_vperm/info.txt --- botan3-3.7.1+dfsg/src/lib/block/aes/aes_vperm/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/aes/aes_vperm/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,34 +1,35 @@ - + AES_VPERM -> 20190901 - + name -> "AES Vector Permutation" brief -> "AES using Vector Permutation Instructions" -endian little - -x86_32:sse2 -x86_64:sse2 x86_32:ssse3 x86_64:ssse3 +x32:ssse3 arm32:neon arm64:neon -ppc32:altivec ppc64:altivec +loongarch64:lsx +wasm:simd128 x86_32 x86_64 +x32 arm32 arm64 -ppc32 ppc64 +loongarch64 +wasm -simd +cpuid +simd_4x32 diff -Nru botan3-3.7.1+dfsg/src/lib/block/aria/aria.cpp botan3-3.12.0+dfsg/src/lib/block/aria/aria.cpp --- botan3-3.7.1+dfsg/src/lib/block/aria/aria.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/aria/aria.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -23,6 +23,10 @@ #include #include +#if defined(BOTAN_HAS_CPUID) + #include +#endif + namespace Botan { namespace { @@ -170,7 +174,10 @@ const size_t ROUNDS = (KS.size() / 4) - 1; for(size_t i = 0; i != blocks; ++i) { - uint32_t t0, t1, t2, t3; + uint32_t t0 = 0; + uint32_t t1 = 0; + uint32_t t2 = 0; + uint32_t t3 = 0; load_be(in + 16 * i, t0, t1, t2, t3); for(size_t r = 0; r < ROUNDS; r += 2) { @@ -317,7 +324,7 @@ ERK.resize(4 * 17); } - ARIA_ROL128<19>(w0, w1, &ERK[0]); + ARIA_ROL128<19>(w0, w1, &ERK[0]); // NOLINT(*-container-data-pointer) ARIA_ROL128<19>(w1, w2, &ERK[4]); ARIA_ROL128<19>(w2, w3, &ERK[8]); ARIA_ROL128<19>(w3, w0, &ERK[12]); @@ -362,31 +369,109 @@ void ARIA_128::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { assert_key_material_set(); + +#if defined(BOTAN_HAS_ARIA_AVX512_GFNI) + if(CPUID::has(CPUID::Feature::AVX512, CPUID::Feature::GFNI)) { + return aria_avx512_gfni_encrypt(in, out, blocks); + } +#endif + +#if defined(BOTAN_HAS_ARIA_HWAES) + if(CPUID::has(CPUID::Feature::HW_AES)) { + return aria_hwaes_encrypt(in, out, blocks); + } +#endif + ARIA_F::transform(in, out, blocks, m_ERK); } void ARIA_192::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { assert_key_material_set(); + +#if defined(BOTAN_HAS_ARIA_AVX512_GFNI) + if(CPUID::has(CPUID::Feature::AVX512, CPUID::Feature::GFNI)) { + return aria_avx512_gfni_encrypt(in, out, blocks); + } +#endif + +#if defined(BOTAN_HAS_ARIA_HWAES) + if(CPUID::has(CPUID::Feature::HW_AES)) { + return aria_hwaes_encrypt(in, out, blocks); + } +#endif + ARIA_F::transform(in, out, blocks, m_ERK); } void ARIA_256::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { assert_key_material_set(); + +#if defined(BOTAN_HAS_ARIA_AVX512_GFNI) + if(CPUID::has(CPUID::Feature::AVX512, CPUID::Feature::GFNI)) { + return aria_avx512_gfni_encrypt(in, out, blocks); + } +#endif + +#if defined(BOTAN_HAS_ARIA_HWAES) + if(CPUID::has(CPUID::Feature::HW_AES)) { + return aria_hwaes_encrypt(in, out, blocks); + } +#endif + ARIA_F::transform(in, out, blocks, m_ERK); } void ARIA_128::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { assert_key_material_set(); + +#if defined(BOTAN_HAS_ARIA_AVX512_GFNI) + if(CPUID::has(CPUID::Feature::AVX512, CPUID::Feature::GFNI)) { + return aria_avx512_gfni_decrypt(in, out, blocks); + } +#endif + +#if defined(BOTAN_HAS_ARIA_HWAES) + if(CPUID::has(CPUID::Feature::HW_AES)) { + return aria_hwaes_decrypt(in, out, blocks); + } +#endif + ARIA_F::transform(in, out, blocks, m_DRK); } void ARIA_192::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { assert_key_material_set(); + +#if defined(BOTAN_HAS_ARIA_AVX512_GFNI) + if(CPUID::has(CPUID::Feature::AVX512, CPUID::Feature::GFNI)) { + return aria_avx512_gfni_decrypt(in, out, blocks); + } +#endif + +#if defined(BOTAN_HAS_ARIA_HWAES) + if(CPUID::has(CPUID::Feature::HW_AES)) { + return aria_hwaes_decrypt(in, out, blocks); + } +#endif + ARIA_F::transform(in, out, blocks, m_DRK); } void ARIA_256::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { assert_key_material_set(); + +#if defined(BOTAN_HAS_ARIA_AVX512_GFNI) + if(CPUID::has(CPUID::Feature::AVX512, CPUID::Feature::GFNI)) { + return aria_avx512_gfni_decrypt(in, out, blocks); + } +#endif + +#if defined(BOTAN_HAS_ARIA_HWAES) + if(CPUID::has(CPUID::Feature::HW_AES)) { + return aria_hwaes_decrypt(in, out, blocks); + } +#endif + ARIA_F::transform(in, out, blocks, m_DRK); } @@ -402,6 +487,66 @@ return !m_ERK.empty(); } +namespace { + +size_t aria_parallelism() { +#if defined(BOTAN_HAS_ARIA_AVX512_GFNI) + if(CPUID::has(CPUID::Feature::AVX512, CPUID::Feature::GFNI)) { + return 16; + } +#endif + +#if defined(BOTAN_HAS_ARIA_HWAES) + if(CPUID::has(CPUID::Feature::HW_AES)) { + return 4; + } +#endif + + return 1; +} + +std::string aria_provider() { +#if defined(BOTAN_HAS_ARIA_AVX512_GFNI) + if(auto feat = CPUID::check(CPUID::Feature::AVX512, CPUID::Feature::GFNI)) { + return *feat; + } +#endif + +#if defined(BOTAN_HAS_ARIA_HWAES) + if(auto feat = CPUID::check(CPUID::Feature::HW_AES)) { + return *feat; + } +#endif + + return "base"; +} + +} // namespace + +size_t ARIA_128::parallelism() const { + return aria_parallelism(); +} + +std::string ARIA_128::provider() const { + return aria_provider(); +} + +size_t ARIA_192::parallelism() const { + return aria_parallelism(); +} + +std::string ARIA_192::provider() const { + return aria_provider(); +} + +size_t ARIA_256::parallelism() const { + return aria_parallelism(); +} + +std::string ARIA_256::provider() const { + return aria_provider(); +} + void ARIA_128::key_schedule(std::span key) { ARIA_F::key_schedule(m_ERK, m_DRK, key); } diff -Nru botan3-3.7.1+dfsg/src/lib/block/aria/aria.h botan3-3.12.0+dfsg/src/lib/block/aria/aria.h --- botan3-3.7.1+dfsg/src/lib/block/aria/aria.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/aria/aria.h 2026-05-07 01:38:28.000000000 +0000 @@ -17,6 +17,7 @@ #define BOTAN_ARIA_H_ #include +#include namespace Botan { @@ -34,11 +35,23 @@ std::unique_ptr new_object() const override { return std::make_unique(); } + std::string provider() const override; + size_t parallelism() const override; bool has_keying_material() const override; private: void key_schedule(std::span key) override; +#if defined(BOTAN_HAS_ARIA_AVX512_GFNI) + void aria_avx512_gfni_encrypt(const uint8_t in[], uint8_t out[], size_t blocks) const; + void aria_avx512_gfni_decrypt(const uint8_t in[], uint8_t out[], size_t blocks) const; +#endif + +#if defined(BOTAN_HAS_ARIA_HWAES) + void aria_hwaes_encrypt(const uint8_t in[], uint8_t out[], size_t blocks) const; + void aria_hwaes_decrypt(const uint8_t in[], uint8_t out[], size_t blocks) const; +#endif + // Encryption and Decryption round keys. secure_vector m_ERK, m_DRK; }; @@ -57,11 +70,23 @@ std::unique_ptr new_object() const override { return std::make_unique(); } + std::string provider() const override; + size_t parallelism() const override; bool has_keying_material() const override; private: void key_schedule(std::span key) override; +#if defined(BOTAN_HAS_ARIA_AVX512_GFNI) + void aria_avx512_gfni_encrypt(const uint8_t in[], uint8_t out[], size_t blocks) const; + void aria_avx512_gfni_decrypt(const uint8_t in[], uint8_t out[], size_t blocks) const; +#endif + +#if defined(BOTAN_HAS_ARIA_HWAES) + void aria_hwaes_encrypt(const uint8_t in[], uint8_t out[], size_t blocks) const; + void aria_hwaes_decrypt(const uint8_t in[], uint8_t out[], size_t blocks) const; +#endif + // Encryption and Decryption round keys. secure_vector m_ERK, m_DRK; }; @@ -80,11 +105,23 @@ std::unique_ptr new_object() const override { return std::make_unique(); } + std::string provider() const override; + size_t parallelism() const override; bool has_keying_material() const override; private: void key_schedule(std::span key) override; +#if defined(BOTAN_HAS_ARIA_AVX512_GFNI) + void aria_avx512_gfni_encrypt(const uint8_t in[], uint8_t out[], size_t blocks) const; + void aria_avx512_gfni_decrypt(const uint8_t in[], uint8_t out[], size_t blocks) const; +#endif + +#if defined(BOTAN_HAS_ARIA_HWAES) + void aria_hwaes_encrypt(const uint8_t in[], uint8_t out[], size_t blocks) const; + void aria_hwaes_decrypt(const uint8_t in[], uint8_t out[], size_t blocks) const; +#endif + // Encryption and Decryption round keys. secure_vector m_ERK, m_DRK; }; diff -Nru botan3-3.7.1+dfsg/src/lib/block/aria/aria_avx512_gfni/aria_avx512_gfni.cpp botan3-3.12.0+dfsg/src/lib/block/aria/aria_avx512_gfni/aria_avx512_gfni.cpp --- botan3-3.7.1+dfsg/src/lib/block/aria/aria_avx512_gfni/aria_avx512_gfni.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/aria/aria_avx512_gfni/aria_avx512_gfni.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,390 @@ +/* +* (C) 2026 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include + +namespace Botan { + +namespace ARIA_AVX512 { + +namespace { + +/* +* ARIA has two S-boxes pairs S1/X1 (the Rijndael sbox and its inverse) +* and S2/X2 (another sbox and its inverse), all of which can be described +* as an affine transformation applied to an inversion in GF(2^8) +* +* A very helpful reference for this implementation was +* +* "AVX-Based Acceleration of ARIA Block Cipher Algorithm" +* by Yoo, Kivilinna, Cho. +* IEEE Access, Vol. 11, 2023 (DOI: 10.1109/ACCESS.2023.3298026) +* +* +* The paper describes the sbox decompositions (Section IV. A. 1.) +* +* S1(x) = A_S1(inv(x)) -> affineinv(AFF_S1, x, 0x63) +* S2(x) = A_S2(inv(x)) -> affineinv(AFF_S2, x, 0xE2) +* X1(x) = inv(A_{S1^-1}(x)) -> affine(AFF_X1, x, 0x05) then affineinv(I, y, 0) +* X2(x) = inv(A_{S2^-1}(x)) -> affine(AFF_X2, x, 0x2C) then affineinv(I, y, 0) +* +* where inv(x) = x^-1 in GF(2^8), implemented by the GFNI affineinv instruction +* and the AFF_* matrixes are the constants following. +* +* The approach used here diverges from the implementation described in the +* paper; they used AVX-512 to compute 64 blocks in parallel. This implementation +* instead takes advantage of the fact that AVX-512/GFNI can use 4 different GFNI +* affine constants in a single call, and so needs only 16 block chunks. This +* leads to less register pressure and (imo) a simpler implementation, albeit likely +* giving up some performance with larger input sizes. +*/ + +constexpr uint64_t AFF_S1 = gfni_matrix(R"( + 1 0 0 0 1 1 1 1 + 1 1 0 0 0 1 1 1 + 1 1 1 0 0 0 1 1 + 1 1 1 1 0 0 0 1 + 1 1 1 1 1 0 0 0 + 0 1 1 1 1 1 0 0 + 0 0 1 1 1 1 1 0 + 0 0 0 1 1 1 1 1)"); + +constexpr uint64_t AFF_S2 = gfni_matrix(R"( + 0 1 0 1 0 1 1 1 + 0 0 1 1 1 1 1 1 + 1 1 1 0 1 1 0 1 + 1 1 0 0 0 0 1 1 + 0 1 0 0 0 0 1 1 + 1 1 0 0 1 1 1 0 + 0 1 1 0 0 0 1 1 + 1 1 1 1 0 1 1 0)"); + +constexpr uint64_t AFF_X1 = gfni_matrix(R"( + 0 0 1 0 0 1 0 1 + 1 0 0 1 0 0 1 0 + 0 1 0 0 1 0 0 1 + 1 0 1 0 0 1 0 0 + 0 1 0 1 0 0 1 0 + 0 0 1 0 1 0 0 1 + 1 0 0 1 0 1 0 0 + 0 1 0 0 1 0 1 0)"); + +constexpr uint64_t AFF_X2 = gfni_matrix(R"( + 0 0 0 1 1 0 0 0 + 0 0 1 0 0 1 1 0 + 0 0 0 0 1 0 1 0 + 1 1 1 0 0 0 1 1 + 1 1 1 0 1 1 0 0 + 0 1 1 0 1 0 1 1 + 1 0 1 1 1 1 0 1 + 1 0 0 1 0 0 1 1)"); + +// GFNI identity matrix +constexpr uint64_t IDENTITY = gfni_matrix(R"( + 1 0 0 0 0 0 0 0 + 0 1 0 0 0 0 0 0 + 0 0 1 0 0 0 0 0 + 0 0 0 1 0 0 0 0 + 0 0 0 0 1 0 0 0 + 0 0 0 0 0 1 0 0 + 0 0 0 0 0 0 1 0 + 0 0 0 0 0 0 0 1)"); + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512_GFNI SIMD_16x32 +apply_aria_sbox(SIMD_16x32 x, __m512i pre_mat, __m512i pre_const, __m512i post_mat, __m512i post_const) { + /* + * After transposing the blocks, we have 4 16-word registers where register 0 contains + * all of the first words of the block, etc. + * + * However ARIA wants to send adjacent bytes of each word through the 4 different + * sboxes (either S1||S2||X1||X2 for "FE rounds" or X1||X2||S1||S2 for "FO rounds"). + * This is handled here by using a permutation to send the 16 first bytes into the + * first zmm lane, the 16 second bytes in the second zmm lane, etc. GFNI lets you + * specify different affine matrices for each lane so we can then compute all 4 sboxes + * with a single sequence. We cannot make use of GFNI's builtin XOR/add instruction, + * since we need to use different constants for each lane, but this just requires an + * extra XOR instruction after the GFNI instructions. + */ + + const __m512i fwd_perm = _mm512_set_epi64(0x3F3B37332F2B2723, + 0x1F1B17130F0B0703, + 0x3E3A36322E2A2622, + 0x1E1A16120E0A0602, + 0x3D3935312D292521, + 0x1D1915110D090501, + 0x3C3834302C282420, + 0x1C1814100C080400); + + const __m512i inv_perm = _mm512_set_epi64(0x3F2F1F0F3E2E1E0E, + 0x3D2D1D0D3C2C1C0C, + 0x3B2B1B0B3A2A1A0A, + 0x3929190938281808, + 0x3727170736261606, + 0x3525150534241404, + 0x3323130332221202, + 0x3121110130201000); + + // Permute to align bytes into the 128-bit sbox lanes + __m512i v = _mm512_permutexvar_epi8(fwd_perm, x.raw()); + + // The sbox magic + v = _mm512_xor_si512(_mm512_gf2p8affine_epi64_epi8(v, pre_mat, 0), pre_const); + v = _mm512_xor_si512(_mm512_gf2p8affineinv_epi64_epi8(v, post_mat, 0), post_const); + + // Permute back to standard ordering + v = _mm512_permutexvar_epi8(inv_perm, v); + return SIMD_16x32(v); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512_GFNI SIMD_16x32 apply_fo_sbox(SIMD_16x32 x) { + /* + * FO is S1 || S2 || X1 || X2 + * + * S1/S2 requires the affine transformation after the inversion, likewise X1/X2 requires + * the affine transformation before the inversion. So half of the matrices in use for + * each instruction are the identity. + */ + const __m512i fo_pre_mat = _mm512_set_epi64(IDENTITY, IDENTITY, IDENTITY, IDENTITY, AFF_X1, AFF_X1, AFF_X2, AFF_X2); + + const __m512i fo_post_mat = _mm512_set_epi64(AFF_S1, AFF_S1, AFF_S2, AFF_S2, IDENTITY, IDENTITY, IDENTITY, IDENTITY); + + const __m512i fo_pre_const = _mm512_set_epi64(0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0505050505050505, + 0x0505050505050505, + 0x2C2C2C2C2C2C2C2C, + 0x2C2C2C2C2C2C2C2C); + + const __m512i fo_post_const = _mm512_set_epi64(0x6363636363636363, + 0x6363636363636363, + 0xE2E2E2E2E2E2E2E2, + 0xE2E2E2E2E2E2E2E2, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000); + + return apply_aria_sbox(x, fo_pre_mat, fo_pre_const, fo_post_mat, fo_post_const); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512_GFNI SIMD_16x32 apply_fe_sbox(SIMD_16x32 x) { + const __m512i fe_pre_mat = _mm512_set_epi64(AFF_X1, AFF_X1, AFF_X2, AFF_X2, IDENTITY, IDENTITY, IDENTITY, IDENTITY); + + const __m512i fe_post_mat = _mm512_set_epi64(IDENTITY, IDENTITY, IDENTITY, IDENTITY, AFF_S1, AFF_S1, AFF_S2, AFF_S2); + + const __m512i fe_pre_const = _mm512_set_epi64(0x0505050505050505, + 0x0505050505050505, + 0x2C2C2C2C2C2C2C2C, + 0x2C2C2C2C2C2C2C2C, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000); + + const __m512i fe_post_const = _mm512_set_epi64(0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x6363636363636363, + 0x6363636363636363, + 0xE2E2E2E2E2E2E2E2, + 0xE2E2E2E2E2E2E2E2); + + return apply_aria_sbox(x, fe_pre_mat, fe_pre_const, fe_post_mat, fe_post_const); +} + +BOTAN_FN_ISA_AVX512 BOTAN_FORCE_INLINE SIMD_16x32 swap_abcd_badc(SIMD_16x32 x) { + // Why you no 16-bit rotate Intel? + + const __m512i rol16 = _mm512_set_epi64(0x0E0F0C0D0A0B0809, + 0x0607040502030001, + 0x0E0F0C0D0A0B0809, + 0x0607040502030001, + 0x0E0F0C0D0A0B0809, + 0x0607040502030001, + 0x0E0F0C0D0A0B0809, + 0x0607040502030001); + + return SIMD_16x32(_mm512_shuffle_epi8(x.raw(), rol16)); +} + +/* +* This applies mixing in much the same way as the M1/M2/M3/M4 constants in the +* scalar/table version in aria.cpp (ARIA_F1/ARIA_F2) +* +* Notice that the constants are rotational and each has the property that it +* maps the byte into all 3 of the other bytes, ie byte 0 goes into bytes 1,2,3, +* then byte 1 goes into bytes 0,2,3, .... +* +* This is neatly handled by XORing together rotations of the words +*/ +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512 SIMD_16x32 aria_fo_m(SIMD_16x32 x) { + return x.rotl<8>() ^ x.rotl<16>() ^ x.rotl<24>(); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512 SIMD_16x32 aria_fe_m(SIMD_16x32 x) { + return x ^ x.rotl<8>() ^ x.rotl<24>(); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512 void aria_mix(SIMD_16x32& B0, SIMD_16x32& B1, SIMD_16x32& B2, SIMD_16x32& B3) { + B1 ^= B2; + B2 ^= B3; + B0 ^= B1; + B3 ^= B1; + B2 ^= B0; + B1 ^= B2; +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512_GFNI void aria_fo(SIMD_16x32& B0, + SIMD_16x32& B1, + SIMD_16x32& B2, + SIMD_16x32& B3) { + B0 = aria_fo_m(apply_fo_sbox(B0)); + B1 = aria_fo_m(apply_fo_sbox(B1)); + B2 = aria_fo_m(apply_fo_sbox(B2)); + B3 = aria_fo_m(apply_fo_sbox(B3)); + + aria_mix(B0, B1, B2, B3); + + B1 = swap_abcd_badc(B1); + B2 = B2.rotl<16>(); + B3 = B3.bswap(); + + aria_mix(B0, B1, B2, B3); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512_GFNI void aria_fe(SIMD_16x32& B0, + SIMD_16x32& B1, + SIMD_16x32& B2, + SIMD_16x32& B3) { + B0 = aria_fe_m(apply_fe_sbox(B0)); + B1 = aria_fe_m(apply_fe_sbox(B1)); + B2 = aria_fe_m(apply_fe_sbox(B2)); + B3 = aria_fe_m(apply_fe_sbox(B3)); + + aria_mix(B0, B1, B2, B3); + + B3 = swap_abcd_badc(B3); + B0 = B0.rotl<16>(); + B1 = B1.bswap(); + + aria_mix(B0, B1, B2, B3); +} + +/* +* 16-wide ARIA block processing +*/ +BOTAN_FN_ISA_AVX512_GFNI +void transform_16(const uint8_t in[], uint8_t out[], std::span KS) { + const size_t ROUNDS = (KS.size() / 4) - 1; + + BOTAN_ASSERT_NOMSG(ROUNDS == 12 || ROUNDS == 14 || ROUNDS == 16); + + SIMD_16x32 B0 = SIMD_16x32::load_be(in); + SIMD_16x32 B1 = SIMD_16x32::load_be(in + 64); + SIMD_16x32 B2 = SIMD_16x32::load_be(in + 128); + SIMD_16x32 B3 = SIMD_16x32::load_be(in + 192); + + SIMD_16x32::transpose(B0, B1, B2, B3); + + for(size_t r = 0; r != ROUNDS; r += 2) { + B0 ^= SIMD_16x32::splat(KS[4 * r]); + B1 ^= SIMD_16x32::splat(KS[4 * r + 1]); + B2 ^= SIMD_16x32::splat(KS[4 * r + 2]); + B3 ^= SIMD_16x32::splat(KS[4 * r + 3]); + aria_fo(B0, B1, B2, B3); + + B0 ^= SIMD_16x32::splat(KS[4 * r + 4]); + B1 ^= SIMD_16x32::splat(KS[4 * r + 5]); + B2 ^= SIMD_16x32::splat(KS[4 * r + 6]); + B3 ^= SIMD_16x32::splat(KS[4 * r + 7]); + + if(r != ROUNDS - 2) { + aria_fe(B0, B1, B2, B3); + } + } + + B0 = apply_fe_sbox(B0) ^ SIMD_16x32::splat(KS[4 * ROUNDS]); + B1 = apply_fe_sbox(B1) ^ SIMD_16x32::splat(KS[4 * ROUNDS + 1]); + B2 = apply_fe_sbox(B2) ^ SIMD_16x32::splat(KS[4 * ROUNDS + 2]); + B3 = apply_fe_sbox(B3) ^ SIMD_16x32::splat(KS[4 * ROUNDS + 3]); + + SIMD_16x32::transpose(B0, B1, B2, B3); + + B0.store_be(out); + B1.store_be(out + 64); + B2.store_be(out + 128); + B3.store_be(out + 192); +} + +void BOTAN_FN_ISA_AVX512_GFNI aria_transform(const uint8_t in[], + uint8_t out[], + size_t blocks, + std::span KS) { + while(blocks >= 16) { + ARIA_AVX512::transform_16(in, out, KS); + in += 16 * 16; + out += 16 * 16; + blocks -= 16; + } + + if(blocks > 0) { + uint8_t ibuf[16 * 16] = {0}; + uint8_t obuf[16 * 16] = {0}; + copy_mem(ibuf, in, blocks * 16); + ARIA_AVX512::transform_16(ibuf, obuf, KS); + copy_mem(out, obuf, blocks * 16); + } +} + +} // namespace + +} // namespace ARIA_AVX512 + +void BOTAN_FN_ISA_AVX512_GFNI ARIA_128::aria_avx512_gfni_encrypt(const uint8_t in[], + uint8_t out[], + size_t blocks) const { + ARIA_AVX512::aria_transform(in, out, blocks, m_ERK); +} + +void BOTAN_FN_ISA_AVX512_GFNI ARIA_128::aria_avx512_gfni_decrypt(const uint8_t in[], + uint8_t out[], + size_t blocks) const { + ARIA_AVX512::aria_transform(in, out, blocks, m_DRK); +} + +void BOTAN_FN_ISA_AVX512_GFNI ARIA_192::aria_avx512_gfni_encrypt(const uint8_t in[], + uint8_t out[], + size_t blocks) const { + ARIA_AVX512::aria_transform(in, out, blocks, m_ERK); +} + +void BOTAN_FN_ISA_AVX512_GFNI ARIA_192::aria_avx512_gfni_decrypt(const uint8_t in[], + uint8_t out[], + size_t blocks) const { + ARIA_AVX512::aria_transform(in, out, blocks, m_DRK); +} + +void BOTAN_FN_ISA_AVX512_GFNI ARIA_256::aria_avx512_gfni_encrypt(const uint8_t in[], + uint8_t out[], + size_t blocks) const { + ARIA_AVX512::aria_transform(in, out, blocks, m_ERK); +} + +void BOTAN_FN_ISA_AVX512_GFNI ARIA_256::aria_avx512_gfni_decrypt(const uint8_t in[], + uint8_t out[], + size_t blocks) const { + ARIA_AVX512::aria_transform(in, out, blocks, m_DRK); +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/block/aria/aria_avx512_gfni/info.txt botan3-3.12.0+dfsg/src/lib/block/aria/aria_avx512_gfni/info.txt --- botan3-3.7.1+dfsg/src/lib/block/aria/aria_avx512_gfni/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/aria/aria_avx512_gfni/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,18 @@ + +ARIA_AVX512_GFNI -> 20260303 + + + +name -> "ARIA AVX-512/GFNI" + + + +cpuid +simd_avx2 +simd_avx512 + + + +gfni +avx512 + diff -Nru botan3-3.7.1+dfsg/src/lib/block/aria/aria_hwaes/aria_hwaes.cpp botan3-3.12.0+dfsg/src/lib/block/aria/aria_hwaes/aria_hwaes.cpp --- botan3-3.7.1+dfsg/src/lib/block/aria/aria_hwaes/aria_hwaes.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/aria/aria_hwaes/aria_hwaes.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,248 @@ +/* +* (C) 2026 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include + +namespace Botan { + +namespace ARIA_HWAES { + +namespace { + +// ARIA S1 is just the AES sbox +BOTAN_FORCE_INLINE BOTAN_FN_ISA_HWAES SIMD_4x32 aria_s1(SIMD_4x32 v) { + return hw_aes_sbox(v); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_HWAES SIMD_4x32 aria_s2(SIMD_4x32 v) { + constexpr uint64_t AFF_S2 = gfni_matrix(R"( + 0 1 0 1 0 1 1 1 + 0 0 1 1 1 1 1 1 + 1 1 1 0 1 1 0 1 + 1 1 0 0 0 0 1 1 + 0 1 0 0 0 0 1 1 + 1 1 0 0 1 1 1 0 + 0 1 1 0 0 0 1 1 + 1 1 1 1 0 1 1 0)"); + + constexpr auto POST_S2 = Gf2AffineTransformation::post_sbox(AFF_S2, 0xE2); + return POST_S2.affine_transform(hw_aes_sbox(v)); +} + +// ARIA X1 is just the AES inverse sbox +BOTAN_FORCE_INLINE BOTAN_FN_ISA_HWAES SIMD_4x32 aria_x1(SIMD_4x32 v) { + return hw_aes_inv_sbox(v); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_HWAES SIMD_4x32 aria_x2(SIMD_4x32 v) { + constexpr uint64_t AFF_X2 = gfni_matrix(R"( + 0 0 0 1 1 0 0 0 + 0 0 1 0 0 1 1 0 + 0 0 0 0 1 0 1 0 + 1 1 1 0 0 0 1 1 + 1 1 1 0 1 1 0 0 + 0 1 1 0 1 0 1 1 + 1 0 1 1 1 1 0 1 + 1 0 0 1 0 0 1 1)"); + constexpr auto PRE_X2D = Gf2AffineTransformation::post_inv_sbox(AFF_X2, 0x2C); + + return hw_aes_inv_sbox(PRE_X2D.affine_transform(v)); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_HWAES SIMD_4x32 aria_fo_m(SIMD_4x32 x) { + return x.rotl<8>() ^ x.rotl<16>() ^ x.rotl<24>(); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_HWAES SIMD_4x32 aria_fe_m(SIMD_4x32 x) { + return x ^ x.rotl<8>() ^ x.rotl<24>(); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_HWAES void aria_mix(SIMD_4x32& B0, SIMD_4x32& B1, SIMD_4x32& B2, SIMD_4x32& B3) { + B1 ^= B2; + B2 ^= B3; + B0 ^= B1; + B3 ^= B1; + B2 ^= B0; + B1 ^= B2; +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_HWAES SIMD_4x32 swap_abcd_badc(SIMD_4x32 x) { + const auto shuf = SIMD_4x32(0x02030001, 0x06070405, 0x0A0B0809, 0x0E0F0C0D); + return SIMD_4x32::byte_shuffle(x, shuf); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_HWAES SIMD_4x32 byte_transpose(SIMD_4x32 v) { + const SIMD_4x32 tbl(0x0C080400, 0x0D090501, 0x0E0A0602, 0x0F0B0703); + return SIMD_4x32::byte_shuffle(v, tbl); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_HWAES void aria_fo_sbox(SIMD_4x32& B0, SIMD_4x32& B1, SIMD_4x32& B2, SIMD_4x32& B3) { + B0 = byte_transpose(B0); + B1 = byte_transpose(B1); + B2 = byte_transpose(B2); + B3 = byte_transpose(B3); + SIMD_4x32::transpose(B0, B1, B2, B3); + + B3 = aria_s1(B3); + B2 = aria_s2(B2); + B1 = aria_x1(B1); + B0 = aria_x2(B0); + + SIMD_4x32::transpose(B0, B1, B2, B3); + B0 = byte_transpose(B0); + B1 = byte_transpose(B1); + B2 = byte_transpose(B2); + B3 = byte_transpose(B3); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_HWAES void aria_fe_sbox(SIMD_4x32& B0, SIMD_4x32& B1, SIMD_4x32& B2, SIMD_4x32& B3) { + B0 = byte_transpose(B0); + B1 = byte_transpose(B1); + B2 = byte_transpose(B2); + B3 = byte_transpose(B3); + SIMD_4x32::transpose(B0, B1, B2, B3); + + B3 = aria_x1(B3); + B2 = aria_x2(B2); + B1 = aria_s1(B1); + B0 = aria_s2(B0); + + SIMD_4x32::transpose(B0, B1, B2, B3); + B0 = byte_transpose(B0); + B1 = byte_transpose(B1); + B2 = byte_transpose(B2); + B3 = byte_transpose(B3); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_HWAES void aria_fo(SIMD_4x32& B0, SIMD_4x32& B1, SIMD_4x32& B2, SIMD_4x32& B3) { + aria_fo_sbox(B0, B1, B2, B3); + + B0 = aria_fo_m(B0); + B1 = aria_fo_m(B1); + B2 = aria_fo_m(B2); + B3 = aria_fo_m(B3); + + aria_mix(B0, B1, B2, B3); + + B1 = swap_abcd_badc(B1); + B2 = B2.rotl<16>(); + B3 = B3.bswap(); + + aria_mix(B0, B1, B2, B3); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_HWAES void aria_fe(SIMD_4x32& B0, SIMD_4x32& B1, SIMD_4x32& B2, SIMD_4x32& B3) { + aria_fe_sbox(B0, B1, B2, B3); + + B0 = aria_fe_m(B0); + B1 = aria_fe_m(B1); + B2 = aria_fe_m(B2); + B3 = aria_fe_m(B3); + + aria_mix(B0, B1, B2, B3); + + B3 = swap_abcd_badc(B3); + B0 = B0.rotl<16>(); + B1 = B1.bswap(); + + aria_mix(B0, B1, B2, B3); +} + +BOTAN_FN_ISA_HWAES void transform_4(const uint8_t in[], uint8_t out[], std::span KS) { + const size_t ROUNDS = (KS.size() / 4) - 1; + + auto B0 = SIMD_4x32::load_be(in); + auto B1 = SIMD_4x32::load_be(in + 16); + auto B2 = SIMD_4x32::load_be(in + 32); + auto B3 = SIMD_4x32::load_be(in + 48); + + SIMD_4x32::transpose(B0, B1, B2, B3); + + for(size_t r = 0; r != ROUNDS; r += 2) { + B0 ^= SIMD_4x32::splat(KS[4 * r]); + B1 ^= SIMD_4x32::splat(KS[4 * r + 1]); + B2 ^= SIMD_4x32::splat(KS[4 * r + 2]); + B3 ^= SIMD_4x32::splat(KS[4 * r + 3]); + + aria_fo(B0, B1, B2, B3); + + B0 ^= SIMD_4x32::splat(KS[4 * r + 4]); + B1 ^= SIMD_4x32::splat(KS[4 * r + 5]); + B2 ^= SIMD_4x32::splat(KS[4 * r + 6]); + B3 ^= SIMD_4x32::splat(KS[4 * r + 7]); + + if(r != ROUNDS - 2) { + aria_fe(B0, B1, B2, B3); + } + } + + // Last half-round: FE sbox only + aria_fe_sbox(B0, B1, B2, B3); + + B0 ^= SIMD_4x32::splat(KS[4 * ROUNDS]); + B1 ^= SIMD_4x32::splat(KS[4 * ROUNDS + 1]); + B2 ^= SIMD_4x32::splat(KS[4 * ROUNDS + 2]); + B3 ^= SIMD_4x32::splat(KS[4 * ROUNDS + 3]); + + SIMD_4x32::transpose(B0, B1, B2, B3); + + B0.store_be(out); + B1.store_be(out + 16); + B2.store_be(out + 32); + B3.store_be(out + 48); +} + +void BOTAN_FN_ISA_HWAES aria_transform(const uint8_t in[], uint8_t out[], size_t blocks, std::span KS) { + while(blocks >= 4) { + transform_4(in, out, KS); + in += 4 * 16; + out += 4 * 16; + blocks -= 4; + } + + if(blocks > 0) { + uint8_t ibuf[4 * 16] = {0}; + uint8_t obuf[4 * 16] = {0}; + copy_mem(ibuf, in, blocks * 16); + transform_4(ibuf, obuf, KS); + copy_mem(out, obuf, blocks * 16); + } +} + +} // namespace + +} // namespace ARIA_HWAES + +void BOTAN_FN_ISA_HWAES ARIA_128::aria_hwaes_encrypt(const uint8_t in[], uint8_t out[], size_t blocks) const { + ARIA_HWAES::aria_transform(in, out, blocks, m_ERK); +} + +void BOTAN_FN_ISA_HWAES ARIA_128::aria_hwaes_decrypt(const uint8_t in[], uint8_t out[], size_t blocks) const { + ARIA_HWAES::aria_transform(in, out, blocks, m_DRK); +} + +void BOTAN_FN_ISA_HWAES ARIA_192::aria_hwaes_encrypt(const uint8_t in[], uint8_t out[], size_t blocks) const { + ARIA_HWAES::aria_transform(in, out, blocks, m_ERK); +} + +void BOTAN_FN_ISA_HWAES ARIA_192::aria_hwaes_decrypt(const uint8_t in[], uint8_t out[], size_t blocks) const { + ARIA_HWAES::aria_transform(in, out, blocks, m_DRK); +} + +void BOTAN_FN_ISA_HWAES ARIA_256::aria_hwaes_encrypt(const uint8_t in[], uint8_t out[], size_t blocks) const { + ARIA_HWAES::aria_transform(in, out, blocks, m_ERK); +} + +void BOTAN_FN_ISA_HWAES ARIA_256::aria_hwaes_decrypt(const uint8_t in[], uint8_t out[], size_t blocks) const { + ARIA_HWAES::aria_transform(in, out, blocks, m_DRK); +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/block/aria/aria_hwaes/info.txt botan3-3.12.0+dfsg/src/lib/block/aria/aria_hwaes/info.txt --- botan3-3.7.1+dfsg/src/lib/block/aria/aria_hwaes/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/aria/aria_hwaes/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,12 @@ + +ARIA_HWAES -> 20260323 + + + +name -> "ARIA using hardware AES instructions" + + + +cpuid +simd_hwaes + diff -Nru botan3-3.7.1+dfsg/src/lib/block/block_cipher.cpp botan3-3.12.0+dfsg/src/lib/block/block_cipher.cpp --- botan3-3.7.1+dfsg/src/lib/block/block_cipher.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/block_cipher.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,8 +7,11 @@ #include +#include #include #include +#include +#include #if defined(BOTAN_HAS_AES) #include @@ -51,6 +54,8 @@ #endif #if defined(BOTAN_HAS_LION) + #include + #include #include #endif diff -Nru botan3-3.7.1+dfsg/src/lib/block/block_cipher.h botan3-3.12.0+dfsg/src/lib/block/block_cipher.h --- botan3-3.7.1+dfsg/src/lib/block/block_cipher.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/block_cipher.h 2026-05-07 01:38:28.000000000 +0000 @@ -8,7 +8,6 @@ #ifndef BOTAN_BLOCK_CIPHER_H_ #define BOTAN_BLOCK_CIPHER_H_ -#include #include #include #include @@ -45,6 +44,14 @@ static std::vector providers(std::string_view algo_spec); /** + * Multiplier on a block cipher's native parallelism + * + * Usually notable performance gains come from further loop blocking, + * at least for 2 or 4x + */ + static constexpr size_t ParallelismMult = 4; + + /** * @return block size of this algorithm */ virtual size_t block_size() const = 0; @@ -55,9 +62,9 @@ virtual size_t parallelism() const { return 1; } /** - * @return prefererred parallelism of this cipher in bytes + * @return preferred parallelism of this cipher in bytes */ - size_t parallel_bytes() const { return parallelism() * block_size() * BOTAN_BLOCK_CIPHER_PAR_MULT; } + size_t parallel_bytes() const { return parallelism() * block_size() * BlockCipher::ParallelismMult; } /** * @return provider information about this implementation. Default is "base", @@ -76,7 +83,7 @@ /** * Decrypt a block. - * @param in The ciphertext block to be decypted as a byte array. + * @param in The ciphertext block to be decrypted as a byte array. * Must be of length block_size(). * @param out The byte array designated to hold the decrypted block. * Must be of length block_size(). @@ -149,18 +156,28 @@ */ virtual void decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const = 0; - virtual void encrypt_n_xex(uint8_t data[], const uint8_t mask[], size_t blocks) const { + BOTAN_DEPRECATED("Deprecated no replacement") + void encrypt_n_xex(uint8_t data[], const uint8_t mask[], size_t blocks) const { const size_t BS = block_size(); - xor_buf(data, mask, blocks * BS); + for(size_t i = 0; i != blocks * BS; ++i) { + data[i] ^= mask[i]; + } encrypt_n(data, data, blocks); - xor_buf(data, mask, blocks * BS); + for(size_t i = 0; i != blocks * BS; ++i) { + data[i] ^= mask[i]; + } } - virtual void decrypt_n_xex(uint8_t data[], const uint8_t mask[], size_t blocks) const { + BOTAN_DEPRECATED("Deprecated no replacement") + void decrypt_n_xex(uint8_t data[], const uint8_t mask[], size_t blocks) const { const size_t BS = block_size(); - xor_buf(data, mask, blocks * BS); + for(size_t i = 0; i != blocks * BS; ++i) { + data[i] ^= mask[i]; + } decrypt_n(data, data, blocks); - xor_buf(data, mask, blocks * BS); + for(size_t i = 0; i != blocks * BS; ++i) { + data[i] ^= mask[i]; + } } /** @@ -169,8 +186,6 @@ virtual std::unique_ptr new_object() const = 0; BlockCipher* clone() const { return this->new_object().release(); } - - ~BlockCipher() override = default; }; /** @@ -194,7 +209,7 @@ template class Block_Cipher_Fixed_Params : public BaseClass { public: - enum { BLOCK_SIZE = BS }; + enum { BLOCK_SIZE = BS }; /* NOLINT(*-enum-size,*-use-enum-class) */ size_t block_size() const final { return BS; } diff -Nru botan3-3.7.1+dfsg/src/lib/block/blowfish/blowfish.cpp botan3-3.12.0+dfsg/src/lib/block/blowfish/blowfish.cpp --- botan3-3.7.1+dfsg/src/lib/block/blowfish/blowfish.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/blowfish/blowfish.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -157,8 +157,14 @@ assert_key_material_set(); while(blocks >= 4) { - uint32_t L0, R0, L1, R1, L2, R2, L3, R3; - load_be(in, L0, R0, L1, R1, L2, R2, L3, R3); + uint32_t L0 = load_be(in, 0); + uint32_t R0 = load_be(in, 1); + uint32_t L1 = load_be(in, 2); + uint32_t R1 = load_be(in, 3); + uint32_t L2 = load_be(in, 4); + uint32_t R2 = load_be(in, 5); + uint32_t L3 = load_be(in, 6); + uint32_t R3 = load_be(in, 7); for(size_t r = 0; r != 16; r += 2) { L0 ^= m_P[r]; @@ -196,9 +202,9 @@ blocks -= 4; } - while(blocks) { - uint32_t L, R; - load_be(in, L, R); + while(blocks > 0) { + uint32_t L = load_be(in, 0); + uint32_t R = load_be(in, 1); for(size_t r = 0; r != 16; r += 2) { L ^= m_P[r]; @@ -226,8 +232,14 @@ assert_key_material_set(); while(blocks >= 4) { - uint32_t L0, R0, L1, R1, L2, R2, L3, R3; - load_be(in, L0, R0, L1, R1, L2, R2, L3, R3); + uint32_t L0 = load_be(in, 0); + uint32_t R0 = load_be(in, 1); + uint32_t L1 = load_be(in, 2); + uint32_t R1 = load_be(in, 3); + uint32_t L2 = load_be(in, 4); + uint32_t R2 = load_be(in, 5); + uint32_t L3 = load_be(in, 6); + uint32_t R3 = load_be(in, 7); for(size_t r = 17; r != 1; r -= 2) { L0 ^= m_P[r]; @@ -266,9 +278,9 @@ blocks -= 4; } - while(blocks) { - uint32_t L, R; - load_be(in, L, R); + while(blocks > 0) { + uint32_t L = load_be(in, 0); + uint32_t R = load_be(in, 1); for(size_t r = 17; r != 1; r -= 2) { L ^= m_P[r]; @@ -307,6 +319,7 @@ } void Blowfish::key_expansion(const uint8_t key[], size_t length, const uint8_t salt[], size_t salt_length) { + BOTAN_ASSERT_NOMSG(length > 0); BOTAN_ASSERT_NOMSG(salt_length % 4 == 0); for(size_t i = 0, j = 0; i != 18; ++i, j += 4) { @@ -315,7 +328,8 @@ const size_t P_salt_offset = (salt_length > 0) ? 18 % (salt_length / 4) : 0; - uint32_t L = 0, R = 0; + uint32_t L = 0; + uint32_t R = 0; generate_sbox(m_P, L, R, salt, salt_length, 0); generate_sbox(m_S, L, R, salt, salt_length, P_salt_offset); } @@ -327,10 +341,8 @@ const uint8_t key[], size_t length, const uint8_t salt[], size_t salt_length, size_t workfactor, bool salt_first) { BOTAN_ARG_CHECK(salt_length > 0 && salt_length % 4 == 0, "Invalid salt length for Blowfish salted key schedule"); - if(length > 72) { - // Truncate longer passwords to the 72 char bcrypt limit - length = 72; - } + // Truncate longer passwords to the 72 char bcrypt limit + length = std::min(length, 72); m_P.resize(18); copy_mem(m_P.data(), P_INIT, 18); @@ -377,7 +389,8 @@ L ^= BFF(R, m_S); } - uint32_t T = R; + // Must read-then-write since sometimes sbox parameter is m_P + const uint32_t T = R; R = L ^ m_P[16]; L = T ^ m_P[17]; box[i] = L; diff -Nru botan3-3.7.1+dfsg/src/lib/block/blowfish/blowfish.h botan3-3.12.0+dfsg/src/lib/block/blowfish/blowfish.h --- botan3-3.7.1+dfsg/src/lib/block/blowfish/blowfish.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/blowfish/blowfish.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,6 +9,7 @@ #define BOTAN_BLOWFISH_H_ #include +#include namespace Botan { diff -Nru botan3-3.7.1+dfsg/src/lib/block/camellia/camellia.cpp botan3-3.12.0+dfsg/src/lib/block/camellia/camellia.cpp --- botan3-3.7.1+dfsg/src/lib/block/camellia/camellia.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/camellia/camellia.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -11,6 +11,10 @@ #include #include +#if defined(BOTAN_HAS_CPUID) + #include +#endif + namespace Botan { namespace { @@ -137,12 +141,12 @@ /* * Camellia Encryption */ -void encrypt(const uint8_t in[], uint8_t out[], size_t blocks, const secure_vector& SK, const size_t rounds) { +void encrypt(const uint8_t in[], uint8_t out[], size_t blocks, const secure_vector& SK, size_t rounds) { prefetch_arrays(SBOX1, SBOX2, SBOX3, SBOX4); for(size_t i = 0; i < blocks; ++i) { - uint64_t D1, D2; - load_be(in + 16 * i, D1, D2); + uint64_t D1 = load_be(in, 2 * i + 0); + uint64_t D2 = load_be(in, 2 * i + 1); const uint64_t* K = SK.data(); @@ -175,12 +179,12 @@ /* * Camellia Decryption */ -void decrypt(const uint8_t in[], uint8_t out[], size_t blocks, const secure_vector& SK, const size_t rounds) { +void decrypt(const uint8_t in[], uint8_t out[], size_t blocks, const secure_vector& SK, size_t rounds) { prefetch_arrays(SBOX1, SBOX2, SBOX3, SBOX4); for(size_t i = 0; i < blocks; ++i) { - uint64_t D1, D2; - load_be(in + 16 * i, D1, D2); + uint64_t D1 = load_be(in, 2 * i + 0); + uint64_t D2 = load_be(in, 2 * i + 1); const uint64_t* K = &SK[SK.size() - 1]; @@ -345,37 +349,195 @@ } } +std::string provider() { +#if defined(BOTAN_HAS_CAMELLIA_AVX512_GFNI) + if(auto feat = CPUID::check(CPUID::Feature::AVX512, CPUID::Feature::GFNI)) { + return *feat; + } +#endif + +#if defined(BOTAN_HAS_CAMELLIA_AVX2_GFNI) + if(auto feat = CPUID::check(CPUID::Feature::GFNI)) { + return *feat; + } +#endif + +#if defined(BOTAN_HAS_CAMELLIA_HWAES) + if(auto feat = CPUID::check(CPUID::Feature::HW_AES)) { + return *feat; + } +#endif + + return "base"; +} + +size_t parallelism() { +#if defined(BOTAN_HAS_CAMELLIA_AVX512_GFNI) + if(CPUID::has(CPUID::Feature::AVX512, CPUID::Feature::GFNI)) { + return 16; + } +#endif + +#if defined(BOTAN_HAS_CAMELLIA_AVX2_GFNI) + if(CPUID::has(CPUID::Feature::GFNI)) { + return 4; + } +#endif + +#if defined(BOTAN_HAS_CAMELLIA_HWAES) + if(CPUID::has(CPUID::Feature::HW_AES)) { + return 2; + } +#endif + + return 1; +} + } // namespace Camellia_F } // namespace void Camellia_128::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { assert_key_material_set(); + +#if defined(BOTAN_HAS_CAMELLIA_AVX512_GFNI) + if(CPUID::has(CPUID::Feature::AVX512, CPUID::Feature::GFNI)) { + return avx512_gfni_encrypt(in, out, blocks, m_SK); + } +#endif + +#if defined(BOTAN_HAS_CAMELLIA_AVX2_GFNI) + if(CPUID::has(CPUID::Feature::GFNI)) { + return avx2_gfni_encrypt(in, out, blocks, m_SK); + } +#endif + +#if defined(BOTAN_HAS_CAMELLIA_HWAES) + if(CPUID::has(CPUID::Feature::HW_AES)) { + return hwaes_encrypt(in, out, blocks, m_SK); + } +#endif + Camellia_F::encrypt(in, out, blocks, m_SK, 9); } void Camellia_192::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { assert_key_material_set(); + +#if defined(BOTAN_HAS_CAMELLIA_AVX512_GFNI) + if(CPUID::has(CPUID::Feature::AVX512, CPUID::Feature::GFNI)) { + return avx512_gfni_encrypt(in, out, blocks, m_SK); + } +#endif + +#if defined(BOTAN_HAS_CAMELLIA_AVX2_GFNI) + if(CPUID::has(CPUID::Feature::GFNI)) { + return avx2_gfni_encrypt(in, out, blocks, m_SK); + } +#endif + +#if defined(BOTAN_HAS_CAMELLIA_HWAES) + if(CPUID::has(CPUID::Feature::HW_AES)) { + return hwaes_encrypt(in, out, blocks, m_SK); + } +#endif + Camellia_F::encrypt(in, out, blocks, m_SK, 12); } void Camellia_256::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { assert_key_material_set(); + +#if defined(BOTAN_HAS_CAMELLIA_AVX512_GFNI) + if(CPUID::has(CPUID::Feature::AVX512, CPUID::Feature::GFNI)) { + return avx512_gfni_encrypt(in, out, blocks, m_SK); + } +#endif + +#if defined(BOTAN_HAS_CAMELLIA_AVX2_GFNI) + if(CPUID::has(CPUID::Feature::GFNI)) { + return avx2_gfni_encrypt(in, out, blocks, m_SK); + } +#endif + +#if defined(BOTAN_HAS_CAMELLIA_HWAES) + if(CPUID::has(CPUID::Feature::HW_AES)) { + return hwaes_encrypt(in, out, blocks, m_SK); + } +#endif + Camellia_F::encrypt(in, out, blocks, m_SK, 12); } void Camellia_128::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { assert_key_material_set(); + +#if defined(BOTAN_HAS_CAMELLIA_AVX512_GFNI) + if(CPUID::has(CPUID::Feature::AVX512, CPUID::Feature::GFNI)) { + return avx512_gfni_decrypt(in, out, blocks, m_SK); + } +#endif + +#if defined(BOTAN_HAS_CAMELLIA_AVX2_GFNI) + if(CPUID::has(CPUID::Feature::GFNI)) { + return avx2_gfni_decrypt(in, out, blocks, m_SK); + } +#endif + +#if defined(BOTAN_HAS_CAMELLIA_HWAES) + if(CPUID::has(CPUID::Feature::HW_AES)) { + return hwaes_decrypt(in, out, blocks, m_SK); + } +#endif + Camellia_F::decrypt(in, out, blocks, m_SK, 9); } void Camellia_192::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { assert_key_material_set(); + +#if defined(BOTAN_HAS_CAMELLIA_AVX512_GFNI) + if(CPUID::has(CPUID::Feature::AVX512, CPUID::Feature::GFNI)) { + return avx512_gfni_decrypt(in, out, blocks, m_SK); + } +#endif + +#if defined(BOTAN_HAS_CAMELLIA_AVX2_GFNI) + if(CPUID::has(CPUID::Feature::GFNI)) { + return avx2_gfni_decrypt(in, out, blocks, m_SK); + } +#endif + +#if defined(BOTAN_HAS_CAMELLIA_HWAES) + if(CPUID::has(CPUID::Feature::HW_AES)) { + return hwaes_decrypt(in, out, blocks, m_SK); + } +#endif + Camellia_F::decrypt(in, out, blocks, m_SK, 12); } void Camellia_256::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { assert_key_material_set(); + +#if defined(BOTAN_HAS_CAMELLIA_AVX512_GFNI) + if(CPUID::has(CPUID::Feature::AVX512, CPUID::Feature::GFNI)) { + return avx512_gfni_decrypt(in, out, blocks, m_SK); + } +#endif + +#if defined(BOTAN_HAS_CAMELLIA_AVX2_GFNI) + if(CPUID::has(CPUID::Feature::GFNI)) { + return avx2_gfni_decrypt(in, out, blocks, m_SK); + } +#endif + +#if defined(BOTAN_HAS_CAMELLIA_HWAES) + if(CPUID::has(CPUID::Feature::HW_AES)) { + return hwaes_decrypt(in, out, blocks, m_SK); + } +#endif + Camellia_F::decrypt(in, out, blocks, m_SK, 12); } @@ -415,4 +577,28 @@ zap(m_SK); } +std::string Camellia_128::provider() const { + return Camellia_F::provider(); +} + +std::string Camellia_192::provider() const { + return Camellia_F::provider(); +} + +std::string Camellia_256::provider() const { + return Camellia_F::provider(); +} + +size_t Camellia_128::parallelism() const { + return Camellia_F::parallelism(); +} + +size_t Camellia_192::parallelism() const { + return Camellia_F::parallelism(); +} + +size_t Camellia_256::parallelism() const { + return Camellia_F::parallelism(); +} + } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/block/camellia/camellia.h botan3-3.12.0+dfsg/src/lib/block/camellia/camellia.h --- botan3-3.7.1+dfsg/src/lib/block/camellia/camellia.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/camellia/camellia.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,6 +9,7 @@ #define BOTAN_CAMELLIA_H_ #include +#include namespace Botan { @@ -24,6 +25,9 @@ std::string name() const override { return "Camellia-128"; } + std::string provider() const override; + size_t parallelism() const override; + std::unique_ptr new_object() const override { return std::make_unique(); } bool has_keying_material() const override; @@ -31,6 +35,21 @@ private: void key_schedule(std::span key) override; +#if defined(BOTAN_HAS_CAMELLIA_AVX2_GFNI) + static void avx2_gfni_encrypt(const uint8_t in[], uint8_t out[], size_t blocks, std::span SK); + static void avx2_gfni_decrypt(const uint8_t in[], uint8_t out[], size_t blocks, std::span SK); +#endif + +#if defined(BOTAN_HAS_CAMELLIA_AVX512_GFNI) + static void avx512_gfni_encrypt(const uint8_t in[], uint8_t out[], size_t blocks, std::span SK); + static void avx512_gfni_decrypt(const uint8_t in[], uint8_t out[], size_t blocks, std::span SK); +#endif + +#if defined(BOTAN_HAS_CAMELLIA_HWAES) + static void hwaes_encrypt(const uint8_t in[], uint8_t out[], size_t blocks, std::span SK); + static void hwaes_decrypt(const uint8_t in[], uint8_t out[], size_t blocks, std::span SK); +#endif + secure_vector m_SK; }; @@ -46,6 +65,9 @@ std::string name() const override { return "Camellia-192"; } + std::string provider() const override; + size_t parallelism() const override; + std::unique_ptr new_object() const override { return std::make_unique(); } bool has_keying_material() const override; @@ -53,6 +75,21 @@ private: void key_schedule(std::span key) override; +#if defined(BOTAN_HAS_CAMELLIA_AVX2_GFNI) + static void avx2_gfni_encrypt(const uint8_t in[], uint8_t out[], size_t blocks, std::span SK); + static void avx2_gfni_decrypt(const uint8_t in[], uint8_t out[], size_t blocks, std::span SK); +#endif + +#if defined(BOTAN_HAS_CAMELLIA_AVX512_GFNI) + static void avx512_gfni_encrypt(const uint8_t in[], uint8_t out[], size_t blocks, std::span SK); + static void avx512_gfni_decrypt(const uint8_t in[], uint8_t out[], size_t blocks, std::span SK); +#endif + +#if defined(BOTAN_HAS_CAMELLIA_HWAES) + static void hwaes_encrypt(const uint8_t in[], uint8_t out[], size_t blocks, std::span SK); + static void hwaes_decrypt(const uint8_t in[], uint8_t out[], size_t blocks, std::span SK); +#endif + secure_vector m_SK; }; @@ -68,6 +105,9 @@ std::string name() const override { return "Camellia-256"; } + std::string provider() const override; + size_t parallelism() const override; + std::unique_ptr new_object() const override { return std::make_unique(); } bool has_keying_material() const override; @@ -75,6 +115,21 @@ private: void key_schedule(std::span key) override; +#if defined(BOTAN_HAS_CAMELLIA_AVX2_GFNI) + static void avx2_gfni_encrypt(const uint8_t in[], uint8_t out[], size_t blocks, std::span SK); + static void avx2_gfni_decrypt(const uint8_t in[], uint8_t out[], size_t blocks, std::span SK); +#endif + +#if defined(BOTAN_HAS_CAMELLIA_AVX512_GFNI) + static void avx512_gfni_encrypt(const uint8_t in[], uint8_t out[], size_t blocks, std::span SK); + static void avx512_gfni_decrypt(const uint8_t in[], uint8_t out[], size_t blocks, std::span SK); +#endif + +#if defined(BOTAN_HAS_CAMELLIA_HWAES) + static void hwaes_encrypt(const uint8_t in[], uint8_t out[], size_t blocks, std::span SK); + static void hwaes_decrypt(const uint8_t in[], uint8_t out[], size_t blocks, std::span SK); +#endif + secure_vector m_SK; }; diff -Nru botan3-3.7.1+dfsg/src/lib/block/camellia/camellia_avx2_gfni/camellia_avx2_gfni.cpp botan3-3.12.0+dfsg/src/lib/block/camellia/camellia_avx2_gfni/camellia_avx2_gfni.cpp --- botan3-3.7.1+dfsg/src/lib/block/camellia/camellia_avx2_gfni/camellia_avx2_gfni.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/camellia/camellia_avx2_gfni/camellia_avx2_gfni.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,444 @@ +/* +* (C) 2025,2026 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include + +namespace Botan { + +namespace Camellia_AVX2_GFNI { + +/* +* This follows exactly the approach used in the AVX-512+GFNI implementation +* with only minor complications due to missing rotate and masked operations. +*/ + +namespace { + +constexpr uint64_t pre123_a = gfni_matrix(R"( + 1 1 1 0 1 1 0 1 + 0 0 1 1 0 0 1 0 + 1 1 0 1 0 0 0 0 + 1 0 1 1 0 0 1 1 + 0 0 0 0 1 1 0 0 + 1 0 1 0 0 1 0 0 + 0 0 1 0 1 1 0 0 + 1 0 0 0 0 1 1 0)"); + +constexpr uint64_t pre4_a = gfni_matrix(R"( + 1 1 0 1 1 0 1 1 + 0 1 1 0 0 1 0 0 + 1 0 1 0 0 0 0 1 + 0 1 1 0 0 1 1 1 + 0 0 0 1 1 0 0 0 + 0 1 0 0 1 0 0 1 + 0 1 0 1 1 0 0 0 + 0 0 0 0 1 1 0 1)"); + +constexpr uint8_t pre_c = 0b01000101; + +constexpr uint64_t post2_a = gfni_matrix(R"( + 0 0 0 1 1 1 0 0 + 0 0 0 0 0 0 0 1 + 0 1 1 0 0 1 1 0 + 1 0 1 1 1 1 1 0 + 0 0 0 1 1 0 1 1 + 1 0 0 0 1 1 1 0 + 0 1 0 1 1 1 1 0 + 0 1 1 1 1 1 1 1)"); + +constexpr uint64_t post3_a = gfni_matrix(R"( + 0 1 1 0 0 1 1 0 + 1 0 1 1 1 1 1 0 + 0 0 0 1 1 0 1 1 + 1 0 0 0 1 1 1 0 + 0 1 0 1 1 1 1 0 + 0 1 1 1 1 1 1 1 + 0 0 0 1 1 1 0 0 + 0 0 0 0 0 0 0 1)"); + +constexpr uint64_t post14_a = gfni_matrix(R"( + 0 0 0 0 0 0 0 1 + 0 1 1 0 0 1 1 0 + 1 0 1 1 1 1 1 0 + 0 0 0 1 1 0 1 1 + 1 0 0 0 1 1 1 0 + 0 1 0 1 1 1 1 0 + 0 1 1 1 1 1 1 1 + 0 0 0 1 1 1 0 0)"); + +// NOLINTBEGIN(portability-simd-intrinsics) + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX2_GFNI SIMD_4x64 camellia_f(SIMD_4x64 x) { + const __m256i xr = x.raw(); + + // Compute S1(x), S2(x), S3(x), S4(x) for all bytes + const auto y123 = _mm256_gf2p8affine_epi64_epi8(xr, _mm256_set1_epi64x(pre123_a), pre_c); + const auto y4 = _mm256_gf2p8affine_epi64_epi8(xr, _mm256_set1_epi64x(pre4_a), pre_c); + + const auto s1 = _mm256_gf2p8affineinv_epi64_epi8(y123, _mm256_set1_epi64x(post14_a), 0x6E); + const auto s2 = _mm256_gf2p8affineinv_epi64_epi8(y123, _mm256_set1_epi64x(post2_a), 0xDC); + const auto s3 = _mm256_gf2p8affineinv_epi64_epi8(y123, _mm256_set1_epi64x(post3_a), 0x37); + const auto s4 = _mm256_gf2p8affineinv_epi64_epi8(y4, _mm256_set1_epi64x(post14_a), 0x6E); + + // Blend to find correct S(x) for each byte position + + const auto mask_s2 = _mm256_set1_epi64x(0x00FF0000FF000000); + const auto mask_s3 = _mm256_set1_epi64x(0x0000FF0000FF0000); + const auto mask_s4 = _mm256_set1_epi64x(0x000000FF0000FF00); + + auto sx = s1; + sx = _mm256_blendv_epi8(sx, s2, mask_s2); + sx = _mm256_blendv_epi8(sx, s3, mask_s3); + sx = _mm256_blendv_epi8(sx, s4, mask_s4); + + // Linear mixing layer + const auto P1 = _mm256_set_epi64x(0x0808080908080809, 0x0000000100000001, 0x0808080908080809, 0x0000000100000001); + const auto P2 = _mm256_set_epi64x(0x09090A0A09090A0A, 0x0101020201010202, 0x09090A0A09090A0A, 0x0101020201010202); + const auto P3 = _mm256_set_epi64x(0x0A0B0B0B0A0B0B0B, 0x0203030302030303, 0x0A0B0B0B0A0B0B0B, 0x0203030302030303); + const auto P4 = _mm256_set_epi64x(0x0C0C0D0C0E0D0C0C, 0x0404050406050404, 0x0C0C0D0C0E0D0C0C, 0x0404050406050404); + const auto P5 = _mm256_set_epi64x(0x0D0E0E0D0F0E0D0F, 0x0506060507060507, 0x0D0E0E0D0F0E0D0F, 0x0506060507060507); + const auto P6 = _mm256_set_epi64x(0x0F0F0F0EFFFFFFFF, 0x07070706FFFFFFFF, 0x0F0F0F0EFFFFFFFF, 0x07070706FFFFFFFF); + + const auto t1 = SIMD_4x64(_mm256_shuffle_epi8(sx, P1)); + const auto t2 = SIMD_4x64(_mm256_shuffle_epi8(sx, P2)); + const auto t3 = SIMD_4x64(_mm256_shuffle_epi8(sx, P3)); + const auto t4 = SIMD_4x64(_mm256_shuffle_epi8(sx, P4)); + const auto t5 = SIMD_4x64(_mm256_shuffle_epi8(sx, P5)); + const auto t6 = SIMD_4x64(_mm256_shuffle_epi8(sx, P6)); + + return (t1 ^ t2 ^ t3 ^ t4 ^ t5 ^ t6); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX2 void load_and_deinterleave(const uint8_t in[], SIMD_4x64& L, SIMD_4x64& R) { + auto A = SIMD_4x64::load_be(in); + auto B = SIMD_4x64::load_be(in + 32); + + auto Ap = _mm256_permute4x64_epi64(A.raw(), 0b11'01'10'00); // [L[0], L[1], R[0], R[1]] + auto Bp = _mm256_permute4x64_epi64(B.raw(), 0b11'01'10'00); // [L[2], L[3], R[2], R[3]] + + L = SIMD_4x64(_mm256_permute2x128_si256(Ap, Bp, 0x20)); // [L[0], L[1], L[2], L[3]] + R = SIMD_4x64(_mm256_permute2x128_si256(Ap, Bp, 0x31)); // [R[0], R[1], R[2], R[3]] +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX2 void interleave_and_store(uint8_t out[], SIMD_4x64 L, SIMD_4x64 R) { + auto T1 = _mm256_permute2x128_si256(R.raw(), L.raw(), 0x20); // [R[0], R[1], L[0], L[1]] + auto T2 = _mm256_permute2x128_si256(R.raw(), L.raw(), 0x31); // [R[2], R[3], L[2], L[3]] + + auto A = SIMD_4x64(_mm256_permute4x64_epi64(T1, 0b11'01'10'00)); // [R[0], L[0], R[1], L[1]] + auto B = SIMD_4x64(_mm256_permute4x64_epi64(T2, 0b11'01'10'00)); // [R[2], L[2], R[3], L[3]] + + A.store_be(out); + B.store_be(out + 32); +} + +/* +* 32-bit rotate on SIMD_4x64 helper for FL/FLINV +*/ +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX2 SIMD_4x64 rotl32_1(SIMD_4x64 t) { + return SIMD_4x64(_mm256_or_si256(_mm256_slli_epi32(t.raw(), 1), _mm256_srli_epi32(t.raw(), 31))); +} + +// NOLINTEND(portability-simd-intrinsics) + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX2 SIMD_4x64 FL_4(SIMD_4x64 v, uint64_t K) { + const uint32_t k1 = static_cast(K >> 32); + const uint32_t k2 = static_cast(K & 0xFFFFFFFF); + + auto x1 = v.shr<32>(); + auto x2 = v & SIMD_4x64::splat(0xFFFFFFFF); + + x2 ^= rotl32_1(x1 & SIMD_4x64::splat(k1)); + x1 ^= (x2 | SIMD_4x64::splat(k2)); + + return x1.shl<32>() | x2; +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX2 SIMD_4x64 FLINV_4(SIMD_4x64 v, uint64_t K) { + const uint32_t k1 = static_cast(K >> 32); + const uint32_t k2 = static_cast(K & 0xFFFFFFFF); + + auto x1 = v.shr<32>(); + auto x2 = v & SIMD_4x64::splat(0xFFFFFFFF); + + x1 ^= (x2 | SIMD_4x64::splat(k2)); + x2 ^= rotl32_1(x1 & SIMD_4x64::splat(k1)); + + return x1.shl<32>() | x2; +} + +// Helpers for 6 round iterations + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX2_GFNI void six_e_rounds(SIMD_4x64& L, SIMD_4x64& R, std::span SK) { + R ^= camellia_f(L ^ SIMD_4x64::splat(SK[0])); + L ^= camellia_f(R ^ SIMD_4x64::splat(SK[1])); + R ^= camellia_f(L ^ SIMD_4x64::splat(SK[2])); + L ^= camellia_f(R ^ SIMD_4x64::splat(SK[3])); + R ^= camellia_f(L ^ SIMD_4x64::splat(SK[4])); + L ^= camellia_f(R ^ SIMD_4x64::splat(SK[5])); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX2_GFNI void six_d_rounds(SIMD_4x64& L, SIMD_4x64& R, std::span SK) { + R ^= camellia_f(L ^ SIMD_4x64::splat(SK[5])); + L ^= camellia_f(R ^ SIMD_4x64::splat(SK[4])); + R ^= camellia_f(L ^ SIMD_4x64::splat(SK[3])); + L ^= camellia_f(R ^ SIMD_4x64::splat(SK[2])); + R ^= camellia_f(L ^ SIMD_4x64::splat(SK[1])); + L ^= camellia_f(R ^ SIMD_4x64::splat(SK[0])); +} + +BOTAN_FN_ISA_AVX2_GFNI +void camellia_encrypt_x4_18r(const uint8_t in[], uint8_t out[], std::span SK) { + SIMD_4x64 L; + SIMD_4x64 R; + load_and_deinterleave(in, L, R); + + L ^= SIMD_4x64::splat(SK[0]); + R ^= SIMD_4x64::splat(SK[1]); + + six_e_rounds(L, R, SK.subspan(2)); + + L = FL_4(L, SK[8]); + R = FLINV_4(R, SK[9]); + + six_e_rounds(L, R, SK.subspan(10)); + + L = FL_4(L, SK[16]); + R = FLINV_4(R, SK[17]); + + six_e_rounds(L, R, SK.subspan(18)); + + R ^= SIMD_4x64::splat(SK[24]); + L ^= SIMD_4x64::splat(SK[25]); + + interleave_and_store(out, L, R); +} + +BOTAN_FN_ISA_AVX2_GFNI +void camellia_decrypt_x4_18r(const uint8_t in[], uint8_t out[], std::span SK) { + SIMD_4x64 L; + SIMD_4x64 R; + load_and_deinterleave(in, L, R); + + R ^= SIMD_4x64::splat(SK[25]); + L ^= SIMD_4x64::splat(SK[24]); + + six_d_rounds(L, R, SK.subspan(18)); + + L = FL_4(L, SK[17]); + R = FLINV_4(R, SK[16]); + + six_d_rounds(L, R, SK.subspan(10)); + + L = FL_4(L, SK[9]); + R = FLINV_4(R, SK[8]); + + six_d_rounds(L, R, SK.subspan(2)); + + L ^= SIMD_4x64::splat(SK[1]); + R ^= SIMD_4x64::splat(SK[0]); + + interleave_and_store(out, L, R); +} + +BOTAN_FN_ISA_AVX2_GFNI +void camellia_encrypt_x4_24r(const uint8_t in[], uint8_t out[], std::span SK) { + SIMD_4x64 L; + SIMD_4x64 R; + load_and_deinterleave(in, L, R); + + L ^= SIMD_4x64::splat(SK[0]); + R ^= SIMD_4x64::splat(SK[1]); + + six_e_rounds(L, R, SK.subspan(2)); + + L = FL_4(L, SK[8]); + R = FLINV_4(R, SK[9]); + + six_e_rounds(L, R, SK.subspan(10)); + + L = FL_4(L, SK[16]); + R = FLINV_4(R, SK[17]); + + six_e_rounds(L, R, SK.subspan(18)); + + L = FL_4(L, SK[24]); + R = FLINV_4(R, SK[25]); + + six_e_rounds(L, R, SK.subspan(26)); + + R ^= SIMD_4x64::splat(SK[32]); + L ^= SIMD_4x64::splat(SK[33]); + + interleave_and_store(out, L, R); +} + +BOTAN_FN_ISA_AVX2_GFNI +void camellia_decrypt_x4_24r(const uint8_t in[], uint8_t out[], std::span SK) { + SIMD_4x64 L; + SIMD_4x64 R; + load_and_deinterleave(in, L, R); + + R ^= SIMD_4x64::splat(SK[33]); + L ^= SIMD_4x64::splat(SK[32]); + + six_d_rounds(L, R, SK.subspan(26)); + + L = FL_4(L, SK[25]); + R = FLINV_4(R, SK[24]); + + six_d_rounds(L, R, SK.subspan(18)); + + L = FL_4(L, SK[17]); + R = FLINV_4(R, SK[16]); + + six_d_rounds(L, R, SK.subspan(10)); + + L = FL_4(L, SK[9]); + R = FLINV_4(R, SK[8]); + + six_d_rounds(L, R, SK.subspan(2)); + + L ^= SIMD_4x64::splat(SK[1]); + R ^= SIMD_4x64::splat(SK[0]); + + interleave_and_store(out, L, R); +} + +} // namespace + +} // namespace Camellia_AVX2_GFNI + +// static +void BOTAN_FN_ISA_AVX2_GFNI Camellia_128::avx2_gfni_encrypt(const uint8_t in[], + uint8_t out[], + size_t blocks, + std::span SK) { + while(blocks >= 4) { + Camellia_AVX2_GFNI::camellia_encrypt_x4_18r(in, out, SK); + in += 4 * 16; + out += 4 * 16; + blocks -= 4; + } + + if(blocks > 0) { + uint8_t ibuf[4 * 16] = {0}; + uint8_t obuf[4 * 16] = {0}; + copy_mem(ibuf, in, blocks * 16); + Camellia_AVX2_GFNI::camellia_encrypt_x4_18r(ibuf, obuf, SK); + copy_mem(out, obuf, blocks * 16); + } +} + +// static +void BOTAN_FN_ISA_AVX2_GFNI Camellia_128::avx2_gfni_decrypt(const uint8_t in[], + uint8_t out[], + size_t blocks, + std::span SK) { + while(blocks >= 4) { + Camellia_AVX2_GFNI::camellia_decrypt_x4_18r(in, out, SK); + in += 4 * 16; + out += 4 * 16; + blocks -= 4; + } + + if(blocks > 0) { + uint8_t ibuf[4 * 16] = {0}; + uint8_t obuf[4 * 16] = {0}; + copy_mem(ibuf, in, blocks * 16); + Camellia_AVX2_GFNI::camellia_decrypt_x4_18r(ibuf, obuf, SK); + copy_mem(out, obuf, blocks * 16); + } +} + +// static +void BOTAN_FN_ISA_AVX2_GFNI Camellia_192::avx2_gfni_encrypt(const uint8_t in[], + uint8_t out[], + size_t blocks, + std::span SK) { + while(blocks >= 4) { + Camellia_AVX2_GFNI::camellia_encrypt_x4_24r(in, out, SK); + in += 4 * 16; + out += 4 * 16; + blocks -= 4; + } + + if(blocks > 0) { + uint8_t ibuf[4 * 16] = {0}; + uint8_t obuf[4 * 16] = {0}; + copy_mem(ibuf, in, blocks * 16); + Camellia_AVX2_GFNI::camellia_encrypt_x4_24r(ibuf, obuf, SK); + copy_mem(out, obuf, blocks * 16); + } +} + +// static +void BOTAN_FN_ISA_AVX2_GFNI Camellia_192::avx2_gfni_decrypt(const uint8_t in[], + uint8_t out[], + size_t blocks, + std::span SK) { + while(blocks >= 4) { + Camellia_AVX2_GFNI::camellia_decrypt_x4_24r(in, out, SK); + in += 4 * 16; + out += 4 * 16; + blocks -= 4; + } + + if(blocks > 0) { + uint8_t ibuf[4 * 16] = {0}; + uint8_t obuf[4 * 16] = {0}; + copy_mem(ibuf, in, blocks * 16); + Camellia_AVX2_GFNI::camellia_decrypt_x4_24r(ibuf, obuf, SK); + copy_mem(out, obuf, blocks * 16); + } +} + +// static +void BOTAN_FN_ISA_AVX2_GFNI Camellia_256::avx2_gfni_encrypt(const uint8_t in[], + uint8_t out[], + size_t blocks, + std::span SK) { + while(blocks >= 4) { + Camellia_AVX2_GFNI::camellia_encrypt_x4_24r(in, out, SK); + in += 4 * 16; + out += 4 * 16; + blocks -= 4; + } + + if(blocks > 0) { + uint8_t ibuf[4 * 16] = {0}; + uint8_t obuf[4 * 16] = {0}; + copy_mem(ibuf, in, blocks * 16); + Camellia_AVX2_GFNI::camellia_encrypt_x4_24r(ibuf, obuf, SK); + copy_mem(out, obuf, blocks * 16); + } +} + +// static +void BOTAN_FN_ISA_AVX2_GFNI Camellia_256::avx2_gfni_decrypt(const uint8_t in[], + uint8_t out[], + size_t blocks, + std::span SK) { + while(blocks >= 4) { + Camellia_AVX2_GFNI::camellia_decrypt_x4_24r(in, out, SK); + in += 4 * 16; + out += 4 * 16; + blocks -= 4; + } + + if(blocks > 0) { + uint8_t ibuf[4 * 16] = {0}; + uint8_t obuf[4 * 16] = {0}; + copy_mem(ibuf, in, blocks * 16); + Camellia_AVX2_GFNI::camellia_decrypt_x4_24r(ibuf, obuf, SK); + copy_mem(out, obuf, blocks * 16); + } +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/block/camellia/camellia_avx2_gfni/info.txt botan3-3.12.0+dfsg/src/lib/block/camellia/camellia_avx2_gfni/info.txt --- botan3-3.7.1+dfsg/src/lib/block/camellia/camellia_avx2_gfni/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/camellia/camellia_avx2_gfni/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,18 @@ + +CAMELLIA_AVX2_GFNI -> 20250502 + + + +name -> "Camellia using GFNI/AVX2" + + + +avx2 +gfni + + + +simd_avx2 +simd_4x64 +cpuid + diff -Nru botan3-3.7.1+dfsg/src/lib/block/camellia/camellia_avx512_gfni/camellia_avx512_gfni.cpp botan3-3.12.0+dfsg/src/lib/block/camellia/camellia_avx512_gfni/camellia_avx512_gfni.cpp --- botan3-3.7.1+dfsg/src/lib/block/camellia/camellia_avx512_gfni/camellia_avx512_gfni.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/camellia/camellia_avx512_gfni/camellia_avx512_gfni.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,761 @@ +/* +* (C) 2026 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include + +namespace Botan { + +namespace Camellia_AVX512 { + +namespace { + +constexpr uint64_t pre123_a = gfni_matrix(R"( + 1 1 1 0 1 1 0 1 + 0 0 1 1 0 0 1 0 + 1 1 0 1 0 0 0 0 + 1 0 1 1 0 0 1 1 + 0 0 0 0 1 1 0 0 + 1 0 1 0 0 1 0 0 + 0 0 1 0 1 1 0 0 + 1 0 0 0 0 1 1 0)"); + +constexpr uint64_t pre4_a = gfni_matrix(R"( + 1 1 0 1 1 0 1 1 + 0 1 1 0 0 1 0 0 + 1 0 1 0 0 0 0 1 + 0 1 1 0 0 1 1 1 + 0 0 0 1 1 0 0 0 + 0 1 0 0 1 0 0 1 + 0 1 0 1 1 0 0 0 + 0 0 0 0 1 1 0 1)"); + +constexpr uint8_t pre_c = 0b01000101; + +constexpr uint64_t post2_a = gfni_matrix(R"( + 0 0 0 1 1 1 0 0 + 0 0 0 0 0 0 0 1 + 0 1 1 0 0 1 1 0 + 1 0 1 1 1 1 1 0 + 0 0 0 1 1 0 1 1 + 1 0 0 0 1 1 1 0 + 0 1 0 1 1 1 1 0 + 0 1 1 1 1 1 1 1)"); + +constexpr uint64_t post3_a = gfni_matrix(R"( + 0 1 1 0 0 1 1 0 + 1 0 1 1 1 1 1 0 + 0 0 0 1 1 0 1 1 + 1 0 0 0 1 1 1 0 + 0 1 0 1 1 1 1 0 + 0 1 1 1 1 1 1 1 + 0 0 0 1 1 1 0 0 + 0 0 0 0 0 0 0 1)"); + +constexpr uint64_t post14_a = gfni_matrix(R"( + 0 0 0 0 0 0 0 1 + 0 1 1 0 0 1 1 0 + 1 0 1 1 1 1 1 0 + 0 0 0 1 1 0 1 1 + 1 0 0 0 1 1 1 0 + 0 1 0 1 1 1 1 0 + 0 1 1 1 1 1 1 1 + 0 0 0 1 1 1 0 0)"); + +// NOLINTBEGIN(portability-simd-intrinsics) + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512_GFNI SIMD_8x64 camellia_f(SIMD_8x64 x) { + const __m512i xr = x.raw(); + + /* + * Camellia sends different bytes of each word through different sboxes; we + * resolve this like cavemen by computing all 4 S-box variants over the full + * vector in parallel, then blending the results. + */ + + // Compute S1(x), S2(x), S3(x), S4(x) for all bytes + const __m512i y123 = _mm512_gf2p8affine_epi64_epi8(xr, _mm512_set1_epi64(pre123_a), pre_c); + const __m512i y4 = _mm512_gf2p8affine_epi64_epi8(xr, _mm512_set1_epi64(pre4_a), pre_c); + + const __m512i s1 = _mm512_gf2p8affineinv_epi64_epi8(y123, _mm512_set1_epi64(post14_a), 0x6E); + const __m512i s2 = _mm512_gf2p8affineinv_epi64_epi8(y123, _mm512_set1_epi64(post2_a), 0xDC); + const __m512i s3 = _mm512_gf2p8affineinv_epi64_epi8(y123, _mm512_set1_epi64(post3_a), 0x37); + const __m512i s4 = _mm512_gf2p8affineinv_epi64_epi8(y4, _mm512_set1_epi64(post14_a), 0x6E); + + // Blend to find correct S(x) for each byte position + + auto sx = s1; + sx = _mm512_mask_blend_epi8(__mmask64(0x4848484848484848), sx, s2); // s2 at bytes {3,6} + sx = _mm512_mask_blend_epi8(__mmask64(0x2424242424242424), sx, s3); // s3 at bytes {2,5} + sx = _mm512_mask_blend_epi8(__mmask64(0x1212121212121212), sx, s4); // s4 at bytes {1,4} + + // Linear mixing layer + const auto P1 = _mm512_set_epi64(0x0808080908080809, + 0x0000000100000001, + 0x0808080908080809, + 0x0000000100000001, + 0x0808080908080809, + 0x0000000100000001, + 0x0808080908080809, + 0x0000000100000001); + const auto P2 = _mm512_set_epi64(0x09090A0A09090A0A, + 0x0101020201010202, + 0x09090A0A09090A0A, + 0x0101020201010202, + 0x09090A0A09090A0A, + 0x0101020201010202, + 0x09090A0A09090A0A, + 0x0101020201010202); + const auto P3 = _mm512_set_epi64(0x0A0B0B0B0A0B0B0B, + 0x0203030302030303, + 0x0A0B0B0B0A0B0B0B, + 0x0203030302030303, + 0x0A0B0B0B0A0B0B0B, + 0x0203030302030303, + 0x0A0B0B0B0A0B0B0B, + 0x0203030302030303); + const auto P4 = _mm512_set_epi64(0x0C0C0D0C0E0D0C0C, + 0x0404050406050404, + 0x0C0C0D0C0E0D0C0C, + 0x0404050406050404, + 0x0C0C0D0C0E0D0C0C, + 0x0404050406050404, + 0x0C0C0D0C0E0D0C0C, + 0x0404050406050404); + const auto P5 = _mm512_set_epi64(0x0D0E0E0D0F0E0D0F, + 0x0506060507060507, + 0x0D0E0E0D0F0E0D0F, + 0x0506060507060507, + 0x0D0E0E0D0F0E0D0F, + 0x0506060507060507, + 0x0D0E0E0D0F0E0D0F, + 0x0506060507060507); + const auto P6 = _mm512_set_epi64(0x0F0F0F0EFFFFFFFF, + 0x07070706FFFFFFFF, + 0x0F0F0F0EFFFFFFFF, + 0x07070706FFFFFFFF, + 0x0F0F0F0EFFFFFFFF, + 0x07070706FFFFFFFF, + 0x0F0F0F0EFFFFFFFF, + 0x07070706FFFFFFFF); + + const auto t1 = SIMD_8x64(_mm512_shuffle_epi8(sx, P1)); + const auto t2 = SIMD_8x64(_mm512_shuffle_epi8(sx, P2)); + const auto t3 = SIMD_8x64(_mm512_shuffle_epi8(sx, P3)); + const auto t4 = SIMD_8x64(_mm512_shuffle_epi8(sx, P4)); + const auto t5 = SIMD_8x64(_mm512_shuffle_epi8(sx, P5)); + const auto t6 = SIMD_8x64(_mm512_shuffle_epi8(sx, P6)); + + return (t1 ^ t2 ^ t3 ^ t4 ^ t5 ^ t6); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512 SIMD_8x64 FL_8(SIMD_8x64 v, uint64_t K) { + const auto Kv = _mm512_set1_epi64(K); + auto vr = v.raw(); + + // x2 ^= rotl<1>(x1 & k1): AND, rotate 32-bit elements, shift high->low, XOR + vr = _mm512_xor_si512(vr, _mm512_srli_epi64(_mm512_rol_epi32(_mm512_and_si512(vr, Kv), 1), 32)); + + // x1 ^= (x2 | k2): OR, shift low->high, XOR + vr = _mm512_xor_si512(vr, _mm512_slli_epi64(_mm512_or_si512(vr, Kv), 32)); + + return SIMD_8x64(vr); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512 SIMD_8x64 FLINV_8(SIMD_8x64 v, uint64_t K) { + const auto Kv = _mm512_set1_epi64(K); + auto vr = v.raw(); + + // x1 ^= (x2 | k2): OR, shift low->high, XOR + vr = _mm512_xor_si512(vr, _mm512_slli_epi64(_mm512_or_si512(vr, Kv), 32)); + + // x2 ^= rotl<1>(x1 & k1): AND, rotate 32-bit elements, shift high->low, XOR + vr = _mm512_xor_si512(vr, _mm512_srli_epi64(_mm512_rol_epi32(_mm512_and_si512(vr, Kv), 1), 32)); + + return SIMD_8x64(vr); +} + +/* +* Load 8 blocks, byte-swap, and deinterleave into L (even) and R (odd) halves +*/ +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512 void load_and_deinterleave(const uint8_t in[], SIMD_8x64& L, SIMD_8x64& R) { + const auto idx_l = _mm512_set_epi64(0x0E, 0x0C, 0x0A, 0x08, 0x06, 0x04, 0x02, 0x00); + const auto idx_r = _mm512_set_epi64(0x0F, 0x0D, 0x0B, 0x09, 0x07, 0x05, 0x03, 0x01); + + auto A = SIMD_8x64::load_be(in); + auto B = SIMD_8x64::load_be(in + 64); + + L = SIMD_8x64(_mm512_permutex2var_epi64(A.raw(), idx_l, B.raw())); + R = SIMD_8x64(_mm512_permutex2var_epi64(A.raw(), idx_r, B.raw())); +} + +/* +* Interleave R/L halves (note swap), byte-swap, and store 8 blocks +*/ +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512 void interleave_and_store(uint8_t out[], SIMD_8x64 L, SIMD_8x64 R) { + const auto idx_lo = _mm512_set_epi64(0x0B, 0x03, 0x0A, 0x02, 0x09, 0x01, 0x08, 0x00); + const auto idx_hi = _mm512_set_epi64(0x0F, 0x07, 0x0E, 0x06, 0x0D, 0x05, 0x0C, 0x04); + + auto A = SIMD_8x64(_mm512_permutex2var_epi64(R.raw(), idx_lo, L.raw())); + auto B = SIMD_8x64(_mm512_permutex2var_epi64(R.raw(), idx_hi, L.raw())); + + A.store_be(out); + B.store_be(out + 64); +} + +// NOLINTEND(portability-simd-intrinsics) + +// Helpers for 6 round iterations + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512_GFNI void six_e_rounds(SIMD_8x64& L, + SIMD_8x64& R, + std::span SK) { + R ^= camellia_f(L ^ SIMD_8x64::splat(SK[0])); + L ^= camellia_f(R ^ SIMD_8x64::splat(SK[1])); + R ^= camellia_f(L ^ SIMD_8x64::splat(SK[2])); + L ^= camellia_f(R ^ SIMD_8x64::splat(SK[3])); + R ^= camellia_f(L ^ SIMD_8x64::splat(SK[4])); + L ^= camellia_f(R ^ SIMD_8x64::splat(SK[5])); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512_GFNI void six_d_rounds(SIMD_8x64& L, + SIMD_8x64& R, + std::span SK) { + R ^= camellia_f(L ^ SIMD_8x64::splat(SK[5])); + L ^= camellia_f(R ^ SIMD_8x64::splat(SK[4])); + R ^= camellia_f(L ^ SIMD_8x64::splat(SK[3])); + L ^= camellia_f(R ^ SIMD_8x64::splat(SK[2])); + R ^= camellia_f(L ^ SIMD_8x64::splat(SK[1])); + L ^= camellia_f(R ^ SIMD_8x64::splat(SK[0])); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512_GFNI void six_e_rounds_x2( + SIMD_8x64& L1, SIMD_8x64& R1, SIMD_8x64& L2, SIMD_8x64& R2, std::span SK) { + const auto K0 = SIMD_8x64::splat(SK[0]); + const auto K1 = SIMD_8x64::splat(SK[1]); + const auto K2 = SIMD_8x64::splat(SK[2]); + const auto K3 = SIMD_8x64::splat(SK[3]); + const auto K4 = SIMD_8x64::splat(SK[4]); + const auto K5 = SIMD_8x64::splat(SK[5]); + + R1 ^= camellia_f(L1 ^ K0); + R2 ^= camellia_f(L2 ^ K0); + L1 ^= camellia_f(R1 ^ K1); + L2 ^= camellia_f(R2 ^ K1); + R1 ^= camellia_f(L1 ^ K2); + R2 ^= camellia_f(L2 ^ K2); + L1 ^= camellia_f(R1 ^ K3); + L2 ^= camellia_f(R2 ^ K3); + R1 ^= camellia_f(L1 ^ K4); + R2 ^= camellia_f(L2 ^ K4); + L1 ^= camellia_f(R1 ^ K5); + L2 ^= camellia_f(R2 ^ K5); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512_GFNI void six_d_rounds_x2( + SIMD_8x64& L1, SIMD_8x64& R1, SIMD_8x64& L2, SIMD_8x64& R2, std::span SK) { + const auto K0 = SIMD_8x64::splat(SK[0]); + const auto K1 = SIMD_8x64::splat(SK[1]); + const auto K2 = SIMD_8x64::splat(SK[2]); + const auto K3 = SIMD_8x64::splat(SK[3]); + const auto K4 = SIMD_8x64::splat(SK[4]); + const auto K5 = SIMD_8x64::splat(SK[5]); + + R1 ^= camellia_f(L1 ^ K5); + R2 ^= camellia_f(L2 ^ K5); + L1 ^= camellia_f(R1 ^ K4); + L2 ^= camellia_f(R2 ^ K4); + R1 ^= camellia_f(L1 ^ K3); + R2 ^= camellia_f(L2 ^ K3); + L1 ^= camellia_f(R1 ^ K2); + L2 ^= camellia_f(R2 ^ K2); + R1 ^= camellia_f(L1 ^ K1); + R2 ^= camellia_f(L2 ^ K1); + L1 ^= camellia_f(R1 ^ K0); + L2 ^= camellia_f(R2 ^ K0); +} + +BOTAN_FN_ISA_AVX512_GFNI +void camellia_encrypt_x16_18r(const uint8_t in[], uint8_t out[], std::span SK) { + SIMD_8x64 L1; + SIMD_8x64 R1; + SIMD_8x64 L2; + SIMD_8x64 R2; + load_and_deinterleave(in, L1, R1); + load_and_deinterleave(in + 128, L2, R2); + + const auto K0 = SIMD_8x64::splat(SK[0]); + const auto K1 = SIMD_8x64::splat(SK[1]); + L1 ^= K0; + L2 ^= K0; + R1 ^= K1; + R2 ^= K1; + + six_e_rounds_x2(L1, R1, L2, R2, SK.subspan(2)); + + L1 = FL_8(L1, SK[8]); + L2 = FL_8(L2, SK[8]); + R1 = FLINV_8(R1, SK[9]); + R2 = FLINV_8(R2, SK[9]); + + six_e_rounds_x2(L1, R1, L2, R2, SK.subspan(10)); + + L1 = FL_8(L1, SK[16]); + L2 = FL_8(L2, SK[16]); + R1 = FLINV_8(R1, SK[17]); + R2 = FLINV_8(R2, SK[17]); + + six_e_rounds_x2(L1, R1, L2, R2, SK.subspan(18)); + + const auto K24 = SIMD_8x64::splat(SK[24]); + const auto K25 = SIMD_8x64::splat(SK[25]); + R1 ^= K24; + R2 ^= K24; + L1 ^= K25; + L2 ^= K25; + + interleave_and_store(out, L1, R1); + interleave_and_store(out + 128, L2, R2); +} + +BOTAN_FN_ISA_AVX512_GFNI +void camellia_decrypt_x16_18r(const uint8_t in[], uint8_t out[], std::span SK) { + SIMD_8x64 L1; + SIMD_8x64 R1; + SIMD_8x64 L2; + SIMD_8x64 R2; + load_and_deinterleave(in, L1, R1); + load_and_deinterleave(in + 128, L2, R2); + + const auto K25 = SIMD_8x64::splat(SK[25]); + const auto K24 = SIMD_8x64::splat(SK[24]); + R1 ^= K25; + R2 ^= K25; + L1 ^= K24; + L2 ^= K24; + + six_d_rounds_x2(L1, R1, L2, R2, SK.subspan(18)); + + L1 = FL_8(L1, SK[17]); + L2 = FL_8(L2, SK[17]); + R1 = FLINV_8(R1, SK[16]); + R2 = FLINV_8(R2, SK[16]); + + six_d_rounds_x2(L1, R1, L2, R2, SK.subspan(10)); + + L1 = FL_8(L1, SK[9]); + L2 = FL_8(L2, SK[9]); + R1 = FLINV_8(R1, SK[8]); + R2 = FLINV_8(R2, SK[8]); + + six_d_rounds_x2(L1, R1, L2, R2, SK.subspan(2)); + + const auto K1 = SIMD_8x64::splat(SK[1]); + const auto K0 = SIMD_8x64::splat(SK[0]); + L1 ^= K1; + L2 ^= K1; + R1 ^= K0; + R2 ^= K0; + + interleave_and_store(out, L1, R1); + interleave_and_store(out + 128, L2, R2); +} + +BOTAN_FN_ISA_AVX512_GFNI +void camellia_encrypt_x16_24r(const uint8_t in[], uint8_t out[], std::span SK) { + SIMD_8x64 L1; + SIMD_8x64 R1; + SIMD_8x64 L2; + SIMD_8x64 R2; + load_and_deinterleave(in, L1, R1); + load_and_deinterleave(in + 128, L2, R2); + + const auto K0 = SIMD_8x64::splat(SK[0]); + const auto K1 = SIMD_8x64::splat(SK[1]); + L1 ^= K0; + L2 ^= K0; + R1 ^= K1; + R2 ^= K1; + + six_e_rounds_x2(L1, R1, L2, R2, SK.subspan(2)); + + L1 = FL_8(L1, SK[8]); + L2 = FL_8(L2, SK[8]); + R1 = FLINV_8(R1, SK[9]); + R2 = FLINV_8(R2, SK[9]); + + six_e_rounds_x2(L1, R1, L2, R2, SK.subspan(10)); + + L1 = FL_8(L1, SK[16]); + L2 = FL_8(L2, SK[16]); + R1 = FLINV_8(R1, SK[17]); + R2 = FLINV_8(R2, SK[17]); + + six_e_rounds_x2(L1, R1, L2, R2, SK.subspan(18)); + + L1 = FL_8(L1, SK[24]); + L2 = FL_8(L2, SK[24]); + R1 = FLINV_8(R1, SK[25]); + R2 = FLINV_8(R2, SK[25]); + + six_e_rounds_x2(L1, R1, L2, R2, SK.subspan(26)); + + const auto K32 = SIMD_8x64::splat(SK[32]); + const auto K33 = SIMD_8x64::splat(SK[33]); + R1 ^= K32; + R2 ^= K32; + L1 ^= K33; + L2 ^= K33; + + interleave_and_store(out, L1, R1); + interleave_and_store(out + 128, L2, R2); +} + +BOTAN_FN_ISA_AVX512_GFNI +void camellia_decrypt_x16_24r(const uint8_t in[], uint8_t out[], std::span SK) { + SIMD_8x64 L1; + SIMD_8x64 R1; + SIMD_8x64 L2; + SIMD_8x64 R2; + load_and_deinterleave(in, L1, R1); + load_and_deinterleave(in + 128, L2, R2); + + const auto K33 = SIMD_8x64::splat(SK[33]); + const auto K32 = SIMD_8x64::splat(SK[32]); + R1 ^= K33; + R2 ^= K33; + L1 ^= K32; + L2 ^= K32; + + six_d_rounds_x2(L1, R1, L2, R2, SK.subspan(26)); + + L1 = FL_8(L1, SK[25]); + L2 = FL_8(L2, SK[25]); + R1 = FLINV_8(R1, SK[24]); + R2 = FLINV_8(R2, SK[24]); + + six_d_rounds_x2(L1, R1, L2, R2, SK.subspan(18)); + + L1 = FL_8(L1, SK[17]); + L2 = FL_8(L2, SK[17]); + R1 = FLINV_8(R1, SK[16]); + R2 = FLINV_8(R2, SK[16]); + + six_d_rounds_x2(L1, R1, L2, R2, SK.subspan(10)); + + L1 = FL_8(L1, SK[9]); + L2 = FL_8(L2, SK[9]); + R1 = FLINV_8(R1, SK[8]); + R2 = FLINV_8(R2, SK[8]); + + six_d_rounds_x2(L1, R1, L2, R2, SK.subspan(2)); + + const auto K1 = SIMD_8x64::splat(SK[1]); + const auto K0 = SIMD_8x64::splat(SK[0]); + L1 ^= K1; + L2 ^= K1; + R1 ^= K0; + R2 ^= K0; + + interleave_and_store(out, L1, R1); + interleave_and_store(out + 128, L2, R2); +} + +BOTAN_FN_ISA_AVX512_GFNI +void camellia_encrypt_x8_18r(const uint8_t in[], uint8_t out[], std::span SK) { + SIMD_8x64 L; + SIMD_8x64 R; + load_and_deinterleave(in, L, R); + + L ^= SIMD_8x64::splat(SK[0]); + R ^= SIMD_8x64::splat(SK[1]); + + six_e_rounds(L, R, SK.subspan(2)); + + L = FL_8(L, SK[8]); + R = FLINV_8(R, SK[9]); + + six_e_rounds(L, R, SK.subspan(10)); + + L = FL_8(L, SK[16]); + R = FLINV_8(R, SK[17]); + + six_e_rounds(L, R, SK.subspan(18)); + + R ^= SIMD_8x64::splat(SK[24]); + L ^= SIMD_8x64::splat(SK[25]); + + interleave_and_store(out, L, R); +} + +BOTAN_FN_ISA_AVX512_GFNI +void camellia_decrypt_x8_18r(const uint8_t in[], uint8_t out[], std::span SK) { + SIMD_8x64 L; + SIMD_8x64 R; + load_and_deinterleave(in, L, R); + + R ^= SIMD_8x64::splat(SK[25]); + L ^= SIMD_8x64::splat(SK[24]); + + six_d_rounds(L, R, SK.subspan(18)); + + L = FL_8(L, SK[17]); + R = FLINV_8(R, SK[16]); + + six_d_rounds(L, R, SK.subspan(10)); + + L = FL_8(L, SK[9]); + R = FLINV_8(R, SK[8]); + + six_d_rounds(L, R, SK.subspan(2)); + + L ^= SIMD_8x64::splat(SK[1]); + R ^= SIMD_8x64::splat(SK[0]); + + interleave_and_store(out, L, R); +} + +BOTAN_FN_ISA_AVX512_GFNI +void camellia_encrypt_x8_24r(const uint8_t in[], uint8_t out[], std::span SK) { + SIMD_8x64 L; + SIMD_8x64 R; + load_and_deinterleave(in, L, R); + + L ^= SIMD_8x64::splat(SK[0]); + R ^= SIMD_8x64::splat(SK[1]); + + six_e_rounds(L, R, SK.subspan(2)); + + L = FL_8(L, SK[8]); + R = FLINV_8(R, SK[9]); + + six_e_rounds(L, R, SK.subspan(10)); + + L = FL_8(L, SK[16]); + R = FLINV_8(R, SK[17]); + + six_e_rounds(L, R, SK.subspan(18)); + + L = FL_8(L, SK[24]); + R = FLINV_8(R, SK[25]); + + six_e_rounds(L, R, SK.subspan(26)); + + R ^= SIMD_8x64::splat(SK[32]); + L ^= SIMD_8x64::splat(SK[33]); + + interleave_and_store(out, L, R); +} + +BOTAN_FN_ISA_AVX512_GFNI +void camellia_decrypt_x8_24r(const uint8_t in[], uint8_t out[], std::span SK) { + SIMD_8x64 L; + SIMD_8x64 R; + load_and_deinterleave(in, L, R); + + R ^= SIMD_8x64::splat(SK[33]); + L ^= SIMD_8x64::splat(SK[32]); + + six_d_rounds(L, R, SK.subspan(26)); + + L = FL_8(L, SK[25]); + R = FLINV_8(R, SK[24]); + + six_d_rounds(L, R, SK.subspan(18)); + + L = FL_8(L, SK[17]); + R = FLINV_8(R, SK[16]); + + six_d_rounds(L, R, SK.subspan(10)); + + L = FL_8(L, SK[9]); + R = FLINV_8(R, SK[8]); + + six_d_rounds(L, R, SK.subspan(2)); + + L ^= SIMD_8x64::splat(SK[1]); + R ^= SIMD_8x64::splat(SK[0]); + + interleave_and_store(out, L, R); +} + +} // namespace + +} // namespace Camellia_AVX512 + +// static +void BOTAN_FN_ISA_AVX512_GFNI Camellia_128::avx512_gfni_encrypt(const uint8_t in[], + uint8_t out[], + size_t blocks, + std::span SK) { + while(blocks >= 16) { + Camellia_AVX512::camellia_encrypt_x16_18r(in, out, SK); + in += 16 * 16; + out += 16 * 16; + blocks -= 16; + } + + while(blocks >= 8) { + Camellia_AVX512::camellia_encrypt_x8_18r(in, out, SK); + in += 8 * 16; + out += 8 * 16; + blocks -= 8; + } + + if(blocks > 0) { + uint8_t ibuf[8 * 16] = {0}; + uint8_t obuf[8 * 16] = {0}; + copy_mem(ibuf, in, blocks * 16); + Camellia_AVX512::camellia_encrypt_x8_18r(ibuf, obuf, SK); + copy_mem(out, obuf, blocks * 16); + } +} + +// static +void BOTAN_FN_ISA_AVX512_GFNI Camellia_128::avx512_gfni_decrypt(const uint8_t in[], + uint8_t out[], + size_t blocks, + std::span SK) { + while(blocks >= 16) { + Camellia_AVX512::camellia_decrypt_x16_18r(in, out, SK); + in += 16 * 16; + out += 16 * 16; + blocks -= 16; + } + + while(blocks >= 8) { + Camellia_AVX512::camellia_decrypt_x8_18r(in, out, SK); + in += 8 * 16; + out += 8 * 16; + blocks -= 8; + } + + if(blocks > 0) { + uint8_t ibuf[8 * 16] = {0}; + uint8_t obuf[8 * 16] = {0}; + copy_mem(ibuf, in, blocks * 16); + Camellia_AVX512::camellia_decrypt_x8_18r(ibuf, obuf, SK); + copy_mem(out, obuf, blocks * 16); + } +} + +// static +void BOTAN_FN_ISA_AVX512_GFNI Camellia_192::avx512_gfni_encrypt(const uint8_t in[], + uint8_t out[], + size_t blocks, + std::span SK) { + while(blocks >= 16) { + Camellia_AVX512::camellia_encrypt_x16_24r(in, out, SK); + in += 16 * 16; + out += 16 * 16; + blocks -= 16; + } + + while(blocks >= 8) { + Camellia_AVX512::camellia_encrypt_x8_24r(in, out, SK); + in += 8 * 16; + out += 8 * 16; + blocks -= 8; + } + + if(blocks > 0) { + uint8_t ibuf[8 * 16] = {0}; + uint8_t obuf[8 * 16] = {0}; + copy_mem(ibuf, in, blocks * 16); + Camellia_AVX512::camellia_encrypt_x8_24r(ibuf, obuf, SK); + copy_mem(out, obuf, blocks * 16); + } +} + +// static +void BOTAN_FN_ISA_AVX512_GFNI Camellia_192::avx512_gfni_decrypt(const uint8_t in[], + uint8_t out[], + size_t blocks, + std::span SK) { + while(blocks >= 16) { + Camellia_AVX512::camellia_decrypt_x16_24r(in, out, SK); + in += 16 * 16; + out += 16 * 16; + blocks -= 16; + } + + while(blocks >= 8) { + Camellia_AVX512::camellia_decrypt_x8_24r(in, out, SK); + in += 8 * 16; + out += 8 * 16; + blocks -= 8; + } + + if(blocks > 0) { + uint8_t ibuf[8 * 16] = {0}; + uint8_t obuf[8 * 16] = {0}; + copy_mem(ibuf, in, blocks * 16); + Camellia_AVX512::camellia_decrypt_x8_24r(ibuf, obuf, SK); + copy_mem(out, obuf, blocks * 16); + } +} + +// static +void BOTAN_FN_ISA_AVX512_GFNI Camellia_256::avx512_gfni_encrypt(const uint8_t in[], + uint8_t out[], + size_t blocks, + std::span SK) { + while(blocks >= 16) { + Camellia_AVX512::camellia_encrypt_x16_24r(in, out, SK); + in += 16 * 16; + out += 16 * 16; + blocks -= 16; + } + + while(blocks >= 8) { + Camellia_AVX512::camellia_encrypt_x8_24r(in, out, SK); + in += 8 * 16; + out += 8 * 16; + blocks -= 8; + } + + if(blocks > 0) { + uint8_t ibuf[8 * 16] = {0}; + uint8_t obuf[8 * 16] = {0}; + copy_mem(ibuf, in, blocks * 16); + Camellia_AVX512::camellia_encrypt_x8_24r(ibuf, obuf, SK); + copy_mem(out, obuf, blocks * 16); + } +} + +// static +void BOTAN_FN_ISA_AVX512_GFNI Camellia_256::avx512_gfni_decrypt(const uint8_t in[], + uint8_t out[], + size_t blocks, + std::span SK) { + while(blocks >= 16) { + Camellia_AVX512::camellia_decrypt_x16_24r(in, out, SK); + in += 16 * 16; + out += 16 * 16; + blocks -= 16; + } + + while(blocks >= 8) { + Camellia_AVX512::camellia_decrypt_x8_24r(in, out, SK); + in += 8 * 16; + out += 8 * 16; + blocks -= 8; + } + + if(blocks > 0) { + uint8_t ibuf[8 * 16] = {0}; + uint8_t obuf[8 * 16] = {0}; + copy_mem(ibuf, in, blocks * 16); + Camellia_AVX512::camellia_decrypt_x8_24r(ibuf, obuf, SK); + copy_mem(out, obuf, blocks * 16); + } +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/block/camellia/camellia_avx512_gfni/info.txt botan3-3.12.0+dfsg/src/lib/block/camellia/camellia_avx512_gfni/info.txt --- botan3-3.7.1+dfsg/src/lib/block/camellia/camellia_avx512_gfni/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/camellia/camellia_avx512_gfni/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,18 @@ + +CAMELLIA_AVX512_GFNI -> 20260313 + + + +name -> "Camellia AVX-512/GFNI" + + + +cpuid +simd_avx2 +simd_8x64 + + + +gfni +avx512 + diff -Nru botan3-3.7.1+dfsg/src/lib/block/camellia/camellia_hwaes/camellia_hwaes.cpp botan3-3.12.0+dfsg/src/lib/block/camellia/camellia_hwaes/camellia_hwaes.cpp --- botan3-3.7.1+dfsg/src/lib/block/camellia/camellia_hwaes/camellia_hwaes.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/camellia/camellia_hwaes/camellia_hwaes.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,437 @@ +/* +* (C) 2026 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include + +namespace Botan { + +namespace Camellia_HWAES { + +namespace { + +/* Helpers for 64-bit operations on SIMD_4x32 */ + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_HWAES SIMD_4x32 load_be64(const uint8_t* in) { + const auto bswap64 = SIMD_4x32(0x04050607, 0x00010203, 0x0C0D0E0F, 0x08090A0B); + return SIMD_4x32::byte_shuffle(SIMD_4x32::load_le(in), bswap64); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_HWAES void store_be64(uint8_t* out, SIMD_4x32 v) { + const auto bswap64 = SIMD_4x32(0x04050607, 0x00010203, 0x0C0D0E0F, 0x08090A0B); + SIMD_4x32::byte_shuffle(v, bswap64).store_le(out); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_HWAES SIMD_4x32 splat64(uint64_t v) { + const uint32_t lo = static_cast(v); + const uint32_t hi = static_cast(v >> 32); + return SIMD_4x32(lo, hi, lo, hi); +} + +/* The Camellia round function */ +BOTAN_FORCE_INLINE BOTAN_FN_ISA_HWAES SIMD_4x32 camellia_f(SIMD_4x32 x) { + // Pre-affine shared by S1/S2/S3 + constexpr uint64_t pre123_a = gfni_matrix(R"( + 1 1 1 0 1 1 0 1 + 0 0 1 1 0 0 1 0 + 1 1 0 1 0 0 0 0 + 1 0 1 1 0 0 1 1 + 0 0 0 0 1 1 0 0 + 1 0 1 0 0 1 0 0 + 0 0 1 0 1 1 0 0 + 1 0 0 0 0 1 1 0)"); + + // Pre-affine for S4 + constexpr uint64_t pre4_a = gfni_matrix(R"( + 1 1 0 1 1 0 1 1 + 0 1 1 0 0 1 0 0 + 1 0 1 0 0 0 0 1 + 0 1 1 0 0 1 1 1 + 0 0 0 1 1 0 0 0 + 0 1 0 0 1 0 0 1 + 0 1 0 1 1 0 0 0 + 0 0 0 0 1 1 0 1)"); + + constexpr uint8_t pre_c = 0x45; + + // Post-affine for S1 and S4 + constexpr uint64_t post14_a = gfni_matrix(R"( + 0 0 0 0 0 0 0 1 + 0 1 1 0 0 1 1 0 + 1 0 1 1 1 1 1 0 + 0 0 0 1 1 0 1 1 + 1 0 0 0 1 1 1 0 + 0 1 0 1 1 1 1 0 + 0 1 1 1 1 1 1 1 + 0 0 0 1 1 1 0 0)"); + constexpr uint8_t post14_c = 0x6E; + + // Post-affine for S2 + constexpr uint64_t post2_a = gfni_matrix(R"( + 0 0 0 1 1 1 0 0 + 0 0 0 0 0 0 0 1 + 0 1 1 0 0 1 1 0 + 1 0 1 1 1 1 1 0 + 0 0 0 1 1 0 1 1 + 1 0 0 0 1 1 1 0 + 0 1 0 1 1 1 1 0 + 0 1 1 1 1 1 1 1)"); + constexpr uint8_t post2_c = 0xDC; + + // Post-affine for S3 + constexpr uint64_t post3_a = gfni_matrix(R"( + 0 1 1 0 0 1 1 0 + 1 0 1 1 1 1 1 0 + 0 0 0 1 1 0 1 1 + 1 0 0 0 1 1 1 0 + 0 1 0 1 1 1 1 0 + 0 1 1 1 1 1 1 1 + 0 0 0 1 1 1 0 0 + 0 0 0 0 0 0 0 1)"); + constexpr uint8_t post3_c = 0x37; + + constexpr auto PRE123 = Gf2AffineTransformation(pre123_a, pre_c); + constexpr auto PRE4 = Gf2AffineTransformation(pre4_a, pre_c); + constexpr auto POST14 = Gf2AffineTransformation::post_sbox(post14_a, post14_c); + constexpr auto POST2 = Gf2AffineTransformation::post_sbox(post2_a, post2_c); + constexpr auto POST3 = Gf2AffineTransformation::post_sbox(post3_a, post3_c); + + const auto mask_s2 = SIMD_4x32(0xFF000000, 0x00FF0000, 0xFF000000, 0x00FF0000); + const auto mask_s3 = SIMD_4x32(0x00FF0000, 0x0000FF00, 0x00FF0000, 0x0000FF00); + const auto mask_s4 = SIMD_4x32(0x0000FF00, 0x000000FF, 0x0000FF00, 0x000000FF); + + const auto pre123 = PRE123.affine_transform(x); + const auto pre4 = PRE4.affine_transform(x); + + const auto sub = hw_aes_sbox(SIMD_4x32::byte_blend(mask_s4, pre4, pre123)); + + const auto s14 = POST14.affine_transform(sub); + const auto s2 = POST2.affine_transform(sub); + const auto s3 = POST3.affine_transform(sub); + + // Final merged Sbox output for all bytes + const auto sbox = SIMD_4x32::byte_blend(mask_s3, s3, SIMD_4x32::byte_blend(mask_s2, s2, s14)); + + // The linear mixing step + const auto P1 = SIMD_4x32(0x00000001, 0x00000001, 0x08080809, 0x08080809); + const auto P2 = SIMD_4x32(0x01010202, 0x01010202, 0x09090A0A, 0x09090A0A); + const auto P3 = SIMD_4x32(0x02030303, 0x02030303, 0x0A0B0B0B, 0x0A0B0B0B); + const auto P4 = SIMD_4x32(0x06050404, 0x04040504, 0x0E0D0C0C, 0x0C0C0D0C); + const auto P5 = SIMD_4x32(0x07060507, 0x05060605, 0x0F0E0D0F, 0x0D0E0E0D); + const auto P6 = SIMD_4x32(0xFFFFFFFF, 0x07070706, 0xFFFFFFFF, 0x0F0F0F0E); + + const auto sxp1 = SIMD_4x32::byte_shuffle(sbox, P1); + const auto sxp2 = SIMD_4x32::byte_shuffle(sbox, P2); + const auto sxp3 = SIMD_4x32::byte_shuffle(sbox, P3); + const auto sxp4 = SIMD_4x32::byte_shuffle(sbox, P4); + const auto sxp5 = SIMD_4x32::byte_shuffle(sbox, P5); + const auto sxp6 = SIMD_4x32::byte_shuffle(sbox, P6); + + return (sxp1 ^ sxp2 ^ sxp3 ^ sxp4 ^ sxp5 ^ sxp6); +} + +/* +* FL and FL-inverse operate on 32-bit sub-halves within each 64-bit element. +* We use byte_shuffle to broadcast each 32-bit half, then recombine with byte_blend. +*/ +BOTAN_FORCE_INLINE BOTAN_FN_ISA_HWAES SIMD_4x32 FL_2(SIMD_4x32 v, uint64_t K) { + const uint32_t k1 = static_cast(K >> 32); + const uint32_t k2 = static_cast(K); + + // Broadcast upper/lower 32-bit halves of each 64-bit element + const auto shuf_hi = SIMD_4x32(0x07060504, 0x07060504, 0x0F0E0D0C, 0x0F0E0D0C); + const auto shuf_lo = SIMD_4x32(0x03020100, 0x03020100, 0x0B0A0908, 0x0B0A0908); + + auto x1 = SIMD_4x32::byte_shuffle(v, shuf_hi); + auto x2 = SIMD_4x32::byte_shuffle(v, shuf_lo); + + x2 ^= (x1 & SIMD_4x32::splat(k1)).rotl<1>(); + x1 ^= x2 | SIMD_4x32::splat(k2); + + // Recombine: lo from x2, hi from x1 + const auto mask_hi = SIMD_4x32(0x00000000, 0xFFFFFFFF, 0x00000000, 0xFFFFFFFF); + return SIMD_4x32::byte_blend(mask_hi, x1, x2); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_HWAES SIMD_4x32 FLINV_2(SIMD_4x32 v, uint64_t K) { + const uint32_t k1 = static_cast(K >> 32); + const uint32_t k2 = static_cast(K); + + const auto shuf_hi = SIMD_4x32(0x07060504, 0x07060504, 0x0F0E0D0C, 0x0F0E0D0C); + const auto shuf_lo = SIMD_4x32(0x03020100, 0x03020100, 0x0B0A0908, 0x0B0A0908); + + auto x1 = SIMD_4x32::byte_shuffle(v, shuf_hi); + auto x2 = SIMD_4x32::byte_shuffle(v, shuf_lo); + + x1 ^= x2 | SIMD_4x32::splat(k2); + x2 ^= (x1 & SIMD_4x32::splat(k1)).rotl<1>(); + + const auto mask_hi = SIMD_4x32(0x00000000, 0xFFFFFFFF, 0x00000000, 0xFFFFFFFF); + return SIMD_4x32::byte_blend(mask_hi, x1, x2); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_HWAES void load_and_deinterleave(const uint8_t in[], SIMD_4x32& L, SIMD_4x32& R) { + auto A = load_be64(in); // block 0: [L0, R0] + auto B = load_be64(in + 16); // block 1: [L1, R1] + const auto mask_upper = SIMD_4x32(0x00000000, 0x00000000, 0xFFFFFFFF, 0xFFFFFFFF); + L = SIMD_4x32::byte_blend(mask_upper, B.swap_halves(), A); // [L0, L1] + R = SIMD_4x32::byte_blend(mask_upper, B, A.swap_halves()); // [R0, R1] +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_HWAES void interleave_and_store(uint8_t out[], SIMD_4x32 L, SIMD_4x32 R) { + // Camellia output swaps L and R + const auto mask_upper = SIMD_4x32(0x00000000, 0x00000000, 0xFFFFFFFF, 0xFFFFFFFF); + auto A = SIMD_4x32::byte_blend(mask_upper, L.swap_halves(), R); // [R0, L0] + auto B = SIMD_4x32::byte_blend(mask_upper, L, R.swap_halves()); // [R1, L1] + store_be64(out, A); + store_be64(out + 16, B); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_HWAES void six_e_rounds(SIMD_4x32& L, SIMD_4x32& R, const uint64_t SK[]) { + R ^= camellia_f(L ^ splat64(SK[0])); + L ^= camellia_f(R ^ splat64(SK[1])); + R ^= camellia_f(L ^ splat64(SK[2])); + L ^= camellia_f(R ^ splat64(SK[3])); + R ^= camellia_f(L ^ splat64(SK[4])); + L ^= camellia_f(R ^ splat64(SK[5])); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_HWAES void six_d_rounds(SIMD_4x32& L, SIMD_4x32& R, const uint64_t SK[]) { + R ^= camellia_f(L ^ splat64(SK[5])); + L ^= camellia_f(R ^ splat64(SK[4])); + R ^= camellia_f(L ^ splat64(SK[3])); + L ^= camellia_f(R ^ splat64(SK[2])); + R ^= camellia_f(L ^ splat64(SK[1])); + L ^= camellia_f(R ^ splat64(SK[0])); +} + +BOTAN_FN_ISA_HWAES void camellia_encrypt_x2_18r(const uint8_t in[], uint8_t out[], std::span SK) { + SIMD_4x32 L; + SIMD_4x32 R; + load_and_deinterleave(in, L, R); + + L ^= splat64(SK[0]); + R ^= splat64(SK[1]); + + six_e_rounds(L, R, &SK[2]); + L = FL_2(L, SK[8]); + R = FLINV_2(R, SK[9]); + six_e_rounds(L, R, &SK[10]); + L = FL_2(L, SK[16]); + R = FLINV_2(R, SK[17]); + six_e_rounds(L, R, &SK[18]); + + R ^= splat64(SK[24]); + L ^= splat64(SK[25]); + + interleave_and_store(out, L, R); +} + +BOTAN_FN_ISA_HWAES void camellia_decrypt_x2_18r(const uint8_t in[], uint8_t out[], std::span SK) { + SIMD_4x32 L; + SIMD_4x32 R; + load_and_deinterleave(in, L, R); + + R ^= splat64(SK[25]); + L ^= splat64(SK[24]); + + six_d_rounds(L, R, &SK[18]); + L = FL_2(L, SK[17]); + R = FLINV_2(R, SK[16]); + six_d_rounds(L, R, &SK[10]); + L = FL_2(L, SK[9]); + R = FLINV_2(R, SK[8]); + six_d_rounds(L, R, &SK[2]); + + L ^= splat64(SK[1]); + R ^= splat64(SK[0]); + + interleave_and_store(out, L, R); +} + +BOTAN_FN_ISA_HWAES void camellia_encrypt_x2_24r(const uint8_t in[], uint8_t out[], std::span SK) { + SIMD_4x32 L; + SIMD_4x32 R; + load_and_deinterleave(in, L, R); + + L ^= splat64(SK[0]); + R ^= splat64(SK[1]); + + six_e_rounds(L, R, &SK[2]); + L = FL_2(L, SK[8]); + R = FLINV_2(R, SK[9]); + six_e_rounds(L, R, &SK[10]); + L = FL_2(L, SK[16]); + R = FLINV_2(R, SK[17]); + six_e_rounds(L, R, &SK[18]); + L = FL_2(L, SK[24]); + R = FLINV_2(R, SK[25]); + six_e_rounds(L, R, &SK[26]); + + R ^= splat64(SK[32]); + L ^= splat64(SK[33]); + + interleave_and_store(out, L, R); +} + +BOTAN_FN_ISA_HWAES void camellia_decrypt_x2_24r(const uint8_t in[], uint8_t out[], std::span SK) { + SIMD_4x32 L; + SIMD_4x32 R; + load_and_deinterleave(in, L, R); + + R ^= splat64(SK[33]); + L ^= splat64(SK[32]); + + six_d_rounds(L, R, &SK[26]); + L = FL_2(L, SK[25]); + R = FLINV_2(R, SK[24]); + six_d_rounds(L, R, &SK[18]); + L = FL_2(L, SK[17]); + R = FLINV_2(R, SK[16]); + six_d_rounds(L, R, &SK[10]); + L = FL_2(L, SK[9]); + R = FLINV_2(R, SK[8]); + six_d_rounds(L, R, &SK[2]); + + L ^= splat64(SK[1]); + R ^= splat64(SK[0]); + + interleave_and_store(out, L, R); +} + +} // namespace + +} // namespace Camellia_HWAES + +// static +void BOTAN_FN_ISA_HWAES Camellia_128::hwaes_encrypt(const uint8_t in[], + uint8_t out[], + size_t blocks, + std::span SK) { + while(blocks >= 2) { + Camellia_HWAES::camellia_encrypt_x2_18r(in, out, SK); + in += 2 * 16; + out += 2 * 16; + blocks -= 2; + } + + if(blocks > 0) { + uint8_t ibuf[2 * 16] = {0}; + uint8_t obuf[2 * 16] = {0}; + copy_mem(ibuf, in, 16); + Camellia_HWAES::camellia_encrypt_x2_18r(ibuf, obuf, SK); + copy_mem(out, obuf, 16); + } +} + +// static +void BOTAN_FN_ISA_HWAES Camellia_128::hwaes_decrypt(const uint8_t in[], + uint8_t out[], + size_t blocks, + std::span SK) { + while(blocks >= 2) { + Camellia_HWAES::camellia_decrypt_x2_18r(in, out, SK); + in += 2 * 16; + out += 2 * 16; + blocks -= 2; + } + + if(blocks > 0) { + uint8_t ibuf[2 * 16] = {0}; + uint8_t obuf[2 * 16] = {0}; + copy_mem(ibuf, in, 16); + Camellia_HWAES::camellia_decrypt_x2_18r(ibuf, obuf, SK); + copy_mem(out, obuf, 16); + } +} + +// static +void BOTAN_FN_ISA_HWAES Camellia_192::hwaes_encrypt(const uint8_t in[], + uint8_t out[], + size_t blocks, + std::span SK) { + while(blocks >= 2) { + Camellia_HWAES::camellia_encrypt_x2_24r(in, out, SK); + in += 2 * 16; + out += 2 * 16; + blocks -= 2; + } + + if(blocks > 0) { + uint8_t ibuf[2 * 16] = {0}; + uint8_t obuf[2 * 16] = {0}; + copy_mem(ibuf, in, 16); + Camellia_HWAES::camellia_encrypt_x2_24r(ibuf, obuf, SK); + copy_mem(out, obuf, 16); + } +} + +// static +void BOTAN_FN_ISA_HWAES Camellia_192::hwaes_decrypt(const uint8_t in[], + uint8_t out[], + size_t blocks, + std::span SK) { + while(blocks >= 2) { + Camellia_HWAES::camellia_decrypt_x2_24r(in, out, SK); + in += 2 * 16; + out += 2 * 16; + blocks -= 2; + } + + if(blocks > 0) { + uint8_t ibuf[2 * 16] = {0}; + uint8_t obuf[2 * 16] = {0}; + copy_mem(ibuf, in, 16); + Camellia_HWAES::camellia_decrypt_x2_24r(ibuf, obuf, SK); + copy_mem(out, obuf, 16); + } +} + +// static +void BOTAN_FN_ISA_HWAES Camellia_256::hwaes_encrypt(const uint8_t in[], + uint8_t out[], + size_t blocks, + std::span SK) { + while(blocks >= 2) { + Camellia_HWAES::camellia_encrypt_x2_24r(in, out, SK); + in += 2 * 16; + out += 2 * 16; + blocks -= 2; + } + + if(blocks > 0) { + uint8_t ibuf[2 * 16] = {0}; + uint8_t obuf[2 * 16] = {0}; + copy_mem(ibuf, in, 16); + Camellia_HWAES::camellia_encrypt_x2_24r(ibuf, obuf, SK); + copy_mem(out, obuf, 16); + } +} + +// static +void BOTAN_FN_ISA_HWAES Camellia_256::hwaes_decrypt(const uint8_t in[], + uint8_t out[], + size_t blocks, + std::span SK) { + while(blocks >= 2) { + Camellia_HWAES::camellia_decrypt_x2_24r(in, out, SK); + in += 2 * 16; + out += 2 * 16; + blocks -= 2; + } + + if(blocks > 0) { + uint8_t ibuf[2 * 16] = {0}; + uint8_t obuf[2 * 16] = {0}; + copy_mem(ibuf, in, 16); + Camellia_HWAES::camellia_decrypt_x2_24r(ibuf, obuf, SK); + copy_mem(out, obuf, 16); + } +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/block/camellia/camellia_hwaes/info.txt botan3-3.12.0+dfsg/src/lib/block/camellia/camellia_hwaes/info.txt --- botan3-3.7.1+dfsg/src/lib/block/camellia/camellia_hwaes/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/camellia/camellia_hwaes/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,12 @@ + +CAMELLIA_HWAES -> 20260321 + + + +name -> "Camellia using hardware AES instructions" + + + +cpuid +simd_hwaes + diff -Nru botan3-3.7.1+dfsg/src/lib/block/cascade/cascade.cpp botan3-3.12.0+dfsg/src/lib/block/cascade/cascade.cpp --- botan3-3.7.1+dfsg/src/lib/block/cascade/cascade.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/cascade/cascade.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,23 +7,23 @@ #include +#include #include -#include #include namespace Botan { void Cascade_Cipher::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { - size_t c1_blocks = blocks * (block_size() / m_cipher1->block_size()); - size_t c2_blocks = blocks * (block_size() / m_cipher2->block_size()); + const size_t c1_blocks = blocks * (block_size() / m_cipher1->block_size()); + const size_t c2_blocks = blocks * (block_size() / m_cipher2->block_size()); m_cipher1->encrypt_n(in, out, c1_blocks); m_cipher2->encrypt_n(out, out, c2_blocks); } void Cascade_Cipher::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { - size_t c1_blocks = blocks * (block_size() / m_cipher1->block_size()); - size_t c2_blocks = blocks * (block_size() / m_cipher2->block_size()); + const size_t c1_blocks = blocks * (block_size() / m_cipher1->block_size()); + const size_t c2_blocks = blocks * (block_size() / m_cipher2->block_size()); m_cipher2->decrypt_n(in, out, c2_blocks); m_cipher1->decrypt_n(out, out, c1_blocks); diff -Nru botan3-3.7.1+dfsg/src/lib/block/cascade/cascade.h botan3-3.12.0+dfsg/src/lib/block/cascade/cascade.h --- botan3-3.7.1+dfsg/src/lib/block/cascade/cascade.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/cascade/cascade.h 2026-05-07 01:38:28.000000000 +0000 @@ -39,11 +39,8 @@ */ Cascade_Cipher(std::unique_ptr cipher1, std::unique_ptr cipher2); - Cascade_Cipher(const Cascade_Cipher&) = delete; - Cascade_Cipher& operator=(const Cascade_Cipher&) = delete; - private: - void key_schedule(std::span) override; + void key_schedule(std::span key) override; std::unique_ptr m_cipher1, m_cipher2; size_t m_block_size; diff -Nru botan3-3.7.1+dfsg/src/lib/block/cast128/cast128.cpp botan3-3.12.0+dfsg/src/lib/block/cast128/cast128.cpp --- botan3-3.7.1+dfsg/src/lib/block/cast128/cast128.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/cast128/cast128.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,6 +7,7 @@ #include +#include #include #include @@ -187,7 +188,10 @@ assert_key_material_set(); while(blocks >= 2) { - uint32_t L0, R0, L1, R1; + uint32_t L0 = 0; + uint32_t R0 = 0; + uint32_t L1 = 0; + uint32_t R1 = 0; load_be(in, L0, R0, L1, R1); L0 ^= F1(R0, m_MK[0], m_RK[0]); @@ -230,8 +234,9 @@ in += 2 * BLOCK_SIZE; } - if(blocks) { - uint32_t L, R; + if(blocks > 0) { + uint32_t L = 0; + uint32_t R = 0; load_be(in, L, R); L ^= F1(R, m_MK[0], m_RK[0]); @@ -262,7 +267,10 @@ assert_key_material_set(); while(blocks >= 2) { - uint32_t L0, R0, L1, R1; + uint32_t L0 = 0; + uint32_t R0 = 0; + uint32_t L1 = 0; + uint32_t R1 = 0; load_be(in, L0, R0, L1, R1); L0 ^= F1(R0, m_MK[15], m_RK[15]); @@ -305,8 +313,9 @@ in += 2 * BLOCK_SIZE; } - if(blocks) { - uint32_t L, R; + if(blocks > 0) { + uint32_t L = 0; + uint32_t R = 0; load_be(in, L, R); L ^= F1(R, m_MK[15], m_RK[15]); @@ -503,7 +512,8 @@ }; secure_vector Z(4); - ByteReader x(X.data()), z(Z.data()); + const ByteReader x(X.data()); + const ByteReader z(Z.data()); Z[0] = X[0] ^ S5[x(13)] ^ S6[x(15)] ^ S7[x(12)] ^ S8[x(14)] ^ S7[x(8)]; Z[1] = X[2] ^ S5[z(0)] ^ S6[z(2)] ^ S7[z(1)] ^ S8[z(3)] ^ S8[x(10)]; diff -Nru botan3-3.7.1+dfsg/src/lib/block/cast128/cast128.h botan3-3.12.0+dfsg/src/lib/block/cast128/cast128.h --- botan3-3.7.1+dfsg/src/lib/block/cast128/cast128.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/cast128/cast128.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,6 +9,7 @@ #define BOTAN_CAST128_H_ #include +#include namespace Botan { diff -Nru botan3-3.7.1+dfsg/src/lib/block/des/des.cpp botan3-3.12.0+dfsg/src/lib/block/des/des.cpp --- botan3-3.7.1+dfsg/src/lib/block/des/des.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/des/des.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,86 +1,659 @@ /* * DES -* (C) 1999-2008,2018,2020 Jack Lloyd -* -* Based on a public domain implemenation by Phil Karn (who in turn -* credited Richard Outerbridge and Jim Gillogly) +* (C) 1999-2008,2018,2020,2026 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ #include +#include +#include #include -#include namespace Botan { namespace { -alignas(256) const uint8_t SPBOX_CATS[64 * 8] = { - 0x54, 0x00, 0x10, 0x55, 0x51, 0x15, 0x01, 0x10, 0x04, 0x54, 0x55, 0x04, 0x45, 0x51, 0x40, 0x01, - 0x05, 0x44, 0x44, 0x14, 0x14, 0x50, 0x50, 0x45, 0x11, 0x41, 0x41, 0x11, 0x00, 0x05, 0x15, 0x40, - 0x10, 0x55, 0x01, 0x50, 0x54, 0x40, 0x40, 0x04, 0x51, 0x10, 0x14, 0x41, 0x04, 0x01, 0x45, 0x15, - 0x55, 0x11, 0x50, 0x45, 0x41, 0x05, 0x15, 0x54, 0x05, 0x44, 0x44, 0x00, 0x11, 0x14, 0x00, 0x51, - - 0x55, 0x44, 0x04, 0x15, 0x10, 0x01, 0x51, 0x45, 0x41, 0x55, 0x54, 0x40, 0x44, 0x10, 0x01, 0x51, - 0x14, 0x11, 0x45, 0x00, 0x40, 0x04, 0x15, 0x50, 0x11, 0x41, 0x00, 0x14, 0x05, 0x54, 0x50, 0x05, - 0x00, 0x15, 0x51, 0x10, 0x45, 0x50, 0x54, 0x04, 0x50, 0x44, 0x01, 0x55, 0x15, 0x01, 0x04, 0x40, - 0x05, 0x54, 0x10, 0x41, 0x11, 0x45, 0x41, 0x11, 0x14, 0x00, 0x44, 0x05, 0x40, 0x51, 0x55, 0x14, - - 0x09, 0xA8, 0x00, 0xA1, 0x88, 0x00, 0x29, 0x88, 0x21, 0x81, 0x81, 0x20, 0xA9, 0x21, 0xA0, 0x09, - 0x80, 0x01, 0xA8, 0x08, 0x28, 0xA0, 0xA1, 0x29, 0x89, 0x28, 0x20, 0x89, 0x01, 0xA9, 0x08, 0x80, - 0xA8, 0x80, 0x21, 0x09, 0x20, 0xA8, 0x88, 0x00, 0x08, 0x21, 0xA9, 0x88, 0x81, 0x08, 0x00, 0xA1, - 0x89, 0x20, 0x80, 0xA9, 0x01, 0x29, 0x28, 0x81, 0xA0, 0x89, 0x09, 0xA0, 0x29, 0x01, 0xA1, 0x28, - - 0x51, 0x15, 0x15, 0x04, 0x54, 0x45, 0x41, 0x11, 0x00, 0x50, 0x50, 0x55, 0x05, 0x00, 0x44, 0x41, - 0x01, 0x10, 0x40, 0x51, 0x04, 0x40, 0x11, 0x14, 0x45, 0x01, 0x14, 0x44, 0x10, 0x54, 0x55, 0x05, - 0x44, 0x41, 0x50, 0x55, 0x05, 0x00, 0x00, 0x50, 0x14, 0x44, 0x45, 0x01, 0x51, 0x15, 0x15, 0x04, - 0x55, 0x05, 0x01, 0x10, 0x41, 0x11, 0x54, 0x45, 0x11, 0x14, 0x40, 0x51, 0x04, 0x40, 0x10, 0x54, - - 0x01, 0x29, 0x28, 0xA1, 0x08, 0x01, 0x80, 0x28, 0x89, 0x08, 0x21, 0x89, 0xA1, 0xA8, 0x09, 0x80, - 0x20, 0x88, 0x88, 0x00, 0x81, 0xA9, 0xA9, 0x21, 0xA8, 0x81, 0x00, 0xA0, 0x29, 0x20, 0xA0, 0x09, - 0x08, 0xA1, 0x01, 0x20, 0x80, 0x28, 0xA1, 0x89, 0x21, 0x80, 0xA8, 0x29, 0x89, 0x01, 0x20, 0xA8, - 0xA9, 0x09, 0xA0, 0xA9, 0x28, 0x00, 0x88, 0xA0, 0x09, 0x21, 0x81, 0x08, 0x00, 0x88, 0x29, 0x81, - - 0x41, 0x50, 0x04, 0x55, 0x50, 0x01, 0x55, 0x10, 0x44, 0x15, 0x10, 0x41, 0x11, 0x44, 0x40, 0x05, - 0x00, 0x11, 0x45, 0x04, 0x14, 0x45, 0x01, 0x51, 0x51, 0x00, 0x15, 0x54, 0x05, 0x14, 0x54, 0x40, - 0x44, 0x01, 0x51, 0x14, 0x55, 0x10, 0x05, 0x41, 0x10, 0x44, 0x40, 0x05, 0x41, 0x55, 0x14, 0x50, - 0x15, 0x54, 0x00, 0x51, 0x01, 0x04, 0x50, 0x15, 0x04, 0x11, 0x45, 0x00, 0x54, 0x40, 0x11, 0x45, - - 0x10, 0x51, 0x45, 0x00, 0x04, 0x45, 0x15, 0x54, 0x55, 0x10, 0x00, 0x41, 0x01, 0x40, 0x51, 0x05, - 0x44, 0x15, 0x11, 0x44, 0x41, 0x50, 0x54, 0x11, 0x50, 0x04, 0x05, 0x55, 0x14, 0x01, 0x40, 0x14, - 0x40, 0x14, 0x10, 0x45, 0x45, 0x51, 0x51, 0x01, 0x11, 0x40, 0x44, 0x10, 0x54, 0x05, 0x15, 0x54, - 0x05, 0x41, 0x55, 0x50, 0x14, 0x00, 0x01, 0x55, 0x00, 0x15, 0x50, 0x04, 0x41, 0x44, 0x04, 0x11, - - 0x89, 0x08, 0x20, 0xA9, 0x80, 0x89, 0x01, 0x80, 0x21, 0xA0, 0xA9, 0x28, 0xA8, 0x29, 0x08, 0x01, - 0xA0, 0x81, 0x88, 0x09, 0x28, 0x21, 0xA1, 0xA8, 0x09, 0x00, 0x00, 0xA1, 0x81, 0x88, 0x29, 0x20, - 0x29, 0x20, 0xa8, 0x08, 0x01, 0xA1, 0x08, 0x29, 0x88, 0x01, 0x81, 0xA0, 0xA1, 0x80, 0x20, 0x89, - 0x00, 0xA9, 0x21, 0x81, 0xA0, 0x88, 0x89, 0x00, 0xA9, 0x28, 0x28, 0x09, 0x09, 0x21, 0x80, 0xA8, +template +concept BitsliceT = requires(T& a, const T& b) { + a ^= b; + a &= b; + a |= b; + ~a; }; -const uint32_t SPBOX_CAT_0_MUL = 0x70041106; -const uint32_t SPBOX_CAT_1_MUL = 0x02012020; -const uint32_t SPBOX_CAT_2_MUL = 0x00901048; -const uint32_t SPBOX_CAT_3_MUL = 0x8e060221; -const uint32_t SPBOX_CAT_4_MUL = 0x00912140; -const uint32_t SPBOX_CAT_5_MUL = 0x80841018; -const uint32_t SPBOX_CAT_6_MUL = 0xe0120202; -const uint32_t SPBOX_CAT_7_MUL = 0x00212240; - -const uint32_t SPBOX_CAT_0_MASK = 0x01010404; -const uint32_t SPBOX_CAT_1_MASK = 0x80108020; -const uint32_t SPBOX_CAT_2_MASK = 0x08020208; -const uint32_t SPBOX_CAT_3_MASK = 0x00802081; -const uint32_t SPBOX_CAT_4_MASK = 0x42080100; -const uint32_t SPBOX_CAT_5_MASK = 0x20404010; -const uint32_t SPBOX_CAT_6_MASK = 0x04200802; -const uint32_t SPBOX_CAT_7_MASK = 0x10041040; +/* +* The circuits for the DES sboxes used here were found by Roman Rusakov and +* Solar Designer for use in JtR. The designers explicitly disclaimed all +* copyright with regards to the circuits themselves ("Being mathematical +* formulas, they are not copyrighted and are free for reuse by anyone.") +* +* John The Ripper also contains Sbox circuit descriptions making use of select +* and ternlogd-style instruction sets which are significantly more compact than +* these circuits. Sadly, very few CPUs support such instructions on GPRs. +*/ + +template +BOTAN_FORCE_INLINE void SBox1(T a1, T a2, T a3, T a4, T a5, T a6, T& out1, T& out2, T& out3, T& out4) { + const T x1 = a1 & ~a5; + const T x2 = a4 ^ x1; + const T x3 = a3 | a6; + const T x4 = a1 ^ a3; + const T x5 = x3 & x4; + const T x6 = a4 ^ x5; + const T x7 = x6 & ~x2; + + const T x8 = a5 ^ a6; + const T x9 = a3 ^ x8; + const T x10 = x2 & ~x9; + const T x11 = a6 | x5; + const T x12 = x10 ^ x11; + const T x13 = x12 & ~x7; + + const T x14 = a1 | a6; + const T x15 = x12 | x14; + const T x16 = a5 & ~x6; + const T x17 = x15 ^ x16; + + const T x18 = a4 & ~x14; + const T x19 = x16 ^ x18; + const T x20 = x8 & ~x4; + const T x21 = x19 | x20; + + const T x22 = a3 & ~x1; + const T x23 = x2 ^ x15; + const T x24 = x23 & ~x22; + const T x25 = ~x24; + const T x26 = x3 & x12; + const T x27 = x25 ^ x26; + const T x28 = x17 & ~a2; + const T x29 = x28 ^ x27; + out3 ^= x29; + + const T x30 = x8 ^ x24; + const T x31 = x16 | x30; + const T x32 = x3 ^ x31; + const T x33 = a1 ^ x32; + const T x34 = x27 ^ x33; + const T x35 = x7 | a2; + const T x36 = x35 ^ x34; + out1 ^= x36; + + const T x37 = x2 & ~x21; + const T x38 = x30 ^ x37; + const T x39 = x16 ^ x32; + const T x40 = x34 & ~x39; + const T x41 = x38 ^ x40; + const T x42 = a2 & ~x13; + const T x43 = x42 ^ x41; + out2 ^= x43; + + const T x44 = x9 ^ x20; + const T x45 = x14 ^ x40; + const T x46 = x45 & ~x44; + const T x47 = x41 ^ x46; + const T x48 = x47 | a2; + const T x49 = x48 ^ x21; + out4 ^= x49; +} + +template +BOTAN_FORCE_INLINE void SBox2(T a1, T a2, T a3, T a4, T a5, T a6, T& out1, T& out2, T& out3, T& out4) { + const T x1 = a2 ^ a5; + + const T x2 = a1 & ~a6; + const T x3 = a5 & ~x2; + const T x4 = a2 | x3; + + const T x5 = x1 & ~a6; + const T x6 = a1 & x1; + const T x7 = a5 ^ x6; + const T x8 = x7 & ~x5; + + const T x9 = a3 & a6; + const T x10 = x3 ^ x5; + const T x11 = x4 & x10; + const T x12 = x11 & ~x9; + + const T x13 = a3 & x11; + const T x14 = ~a1; + const T x15 = x13 ^ x14; + const T x16 = a6 ^ x1; + const T x17 = x16 & ~x9; + const T x18 = x15 ^ x17; + const T x19 = a4 & ~x12; + const T x20 = x19 ^ x18; + out2 ^= x20; + + const T x21 = a2 & ~x17; + const T x22 = x7 ^ x21; + const T x23 = x15 & ~x22; + const T x24 = a3 ^ x16; + const T x25 = x23 ^ x24; + const T x26 = x4 & ~a4; + const T x27 = x26 ^ x25; + out1 ^= x27; + + const T x28 = a2 & ~x9; + const T x29 = x24 | x28; + const T x30 = x4 ^ x18; + const T x31 = x9 | x30; + const T x32 = x29 ^ x31; + + const T x33 = x11 ^ x18; + const T x34 = x25 ^ x33; + const T x35 = x31 & x34; + const T x36 = x1 & x29; + const T x37 = x35 ^ x36; + const T x38 = x37 | a4; + const T x39 = x38 ^ x32; + out3 ^= x39; + + const T x40 = x37 & ~x22; + const T x41 = x16 | x30; + const T x42 = x40 ^ x41; + const T x43 = x8 | a4; + const T x44 = x43 ^ x42; + out4 ^= x44; +} + +template +BOTAN_FORCE_INLINE void SBox3(T a1, T a2, T a3, T a4, T a5, T a6, T& out1, T& out2, T& out3, T& out4) { + const T x1 = a1 & ~a2; + const T x2 = a3 ^ a6; + const T x3 = x1 | x2; + const T x4 = a4 ^ a6; + const T x5 = x4 & ~a1; + const T x6 = x3 ^ x5; + + const T x7 = a2 ^ x2; + const T x8 = x7 & ~a6; + const T x9 = x3 ^ x8; + const T x10 = x6 & ~x9; + + const T x11 = a6 & x6; + const T x12 = a4 | x11; + const T x13 = a1 & x12; + const T x14 = x7 ^ x13; + const T x15 = x6 & ~a5; + const T x16 = x15 ^ x14; + out4 ^= x16; + + const T x17 = x2 & x4; + const T x18 = a1 ^ a4; + const T x19 = x9 ^ x18; + const T x20 = a3 | x19; + const T x21 = x20 & ~x17; + + const T x22 = x5 | x18; + const T x23 = x14 & ~x22; + const T x24 = a4 & a6; + const T x25 = x24 & ~a2; + const T x26 = x23 ^ x25; + + const T x27 = x9 & x26; + const T x28 = x7 | x24; + const T x29 = x28 & ~x27; + const T x30 = a1 ^ x29; + const T x31 = x21 & a5; + const T x32 = x31 ^ x30; + out2 ^= x32; + + const T x33 = x6 & ~a2; + const T x34 = x33 & ~a3; + const T x35 = ~x7; + const T x36 = x22 ^ x35; + const T x37 = x34 ^ x36; + const T x38 = a5 & ~x10; + const T x39 = x38 ^ x37; + out1 ^= x39; + + const T x40 = x34 | x36; + const T x41 = x5 | x33; + const T x42 = x40 ^ x41; + const T x43 = a4 & ~x6; + const T x44 = x42 | x43; + const T x45 = a5 & ~x26; + const T x46 = x45 ^ x44; + out3 ^= x46; +} + +template +BOTAN_FORCE_INLINE void SBox4(T a1, T a2, T a3, T a4, T a5, T a6, T& out1, T& out2, T& out3, T& out4) { + const T x1 = a1 ^ a3; + const T x2 = a3 ^ a5; + const T x3 = a2 | a4; + const T x4 = a5 ^ x3; + const T x5 = x2 & ~x4; + const T x6 = x2 & ~a2; + const T x7 = a4 ^ x6; + const T x8 = x1 | x7; + const T x9 = x8 & ~x5; + const T x10 = a2 ^ x9; + + const T x11 = x7 & x10; + const T x12 = x2 & ~x11; + const T x13 = x1 ^ x10; + const T x14 = x13 & ~x12; + const T x15 = x5 ^ x14; + + const T x16 = a2 ^ a4; + const T x17 = a5 | x6; + const T x18 = x13 ^ x17; + const T x19 = x18 & ~x16; + const T x20 = x9 ^ x19; + const T x21 = a6 & ~x15; + const T x22 = x21 ^ x20; + out1 ^= x22; + + const T x23 = ~x20; + const T x24 = x15 & ~a6; + const T x25 = x24 ^ x23; + out2 ^= x25; + + const T x26 = x15 ^ x23; + const T x27 = x26 & ~x16; + const T x28 = x11 | x27; + const T x29 = x18 ^ x28; + const T x30 = x10 | a6; + const T x31 = x30 ^ x29; + out3 ^= x31; + + const T x32 = a6 & x10; + const T x33 = x32 ^ x29; + out4 ^= x33; +} + +template +BOTAN_FORCE_INLINE void SBox5(T a1, T a2, T a3, T a4, T a5, T a6, T& out1, T& out2, T& out3, T& out4) { + const T x1 = a1 | a3; + const T x2 = x1 & ~a6; + const T x3 = a1 ^ x2; + const T x4 = a3 ^ x3; + const T x5 = a4 | x4; + + const T x6 = x2 & ~a4; + const T x7 = a3 ^ x6; + const T x8 = a5 & x7; + const T x9 = a1 | x4; + const T x10 = x8 ^ x9; + const T x11 = a4 ^ x10; + + const T x12 = a6 ^ x11; + const T x13 = x3 | x12; + const T x14 = a5 & x13; + const T x15 = x3 ^ x14; + const T x16 = a4 & x9; + const T x17 = x15 ^ x16; + + const T x18 = x13 & ~a1; + const T x19 = x7 ^ x18; + const T x20 = a5 ^ x5; + const T x21 = x20 & ~x19; + const T x22 = ~x21; + const T x23 = x22 & ~a2; + const T x24 = x23 ^ x11; + out3 ^= x24; + + const T x25 = x7 & ~x14; + const T x26 = x18 ^ x20; + const T x27 = x17 | x26; + const T x28 = x27 & ~x25; + const T x29 = x5 & ~x28; + + const T x30 = x12 & x28; + const T x31 = x20 ^ x30; + const T x32 = x7 & x9; + const T x33 = x31 | x32; + const T x34 = x14 ^ x33; + const T x35 = x34 & a2; + const T x36 = x35 ^ x17; + out4 ^= x36; + + const T x37 = x1 ^ x28; + const T x38 = a1 ^ x37; + const T x39 = a4 & x31; + const T x40 = x38 ^ x39; + const T x41 = x29 | a2; + const T x42 = x41 ^ x40; + out1 ^= x42; + + const T x43 = x5 ^ x7; + const T x44 = x43 & ~x40; + const T x45 = x3 ^ x31; + const T x46 = x44 ^ x45; + const T x47 = x5 & a2; + const T x48 = x47 ^ x46; + out2 ^= x48; +} + +template +BOTAN_FORCE_INLINE void SBox6(T a1, T a2, T a3, T a4, T a5, T a6, T& out1, T& out2, T& out3, T& out4) { + const T x1 = a2 ^ a5; + + const T x2 = a2 | a6; + const T x3 = a1 & x2; + const T x4 = x1 ^ x3; + const T x5 = a6 ^ x4; + const T x6 = a5 & ~x5; + + const T x7 = a1 & x5; + const T x8 = a2 ^ x7; + const T x9 = a1 ^ a3; + const T x10 = x8 | x9; + const T x11 = x4 ^ x10; + + const T x12 = a3 & x11; + const T x13 = x12 & ~a6; + const T x14 = x6 | x8; + const T x15 = x13 ^ x14; + const T x16 = x15 & a4; + const T x17 = x16 ^ x11; + out4 ^= x17; + + const T x18 = a2 ^ x10; + const T x19 = a6 & ~x18; + const T x20 = a3 ^ x19; + const T x21 = a5 & ~x12; + const T x22 = x20 | x21; + + const T x23 = a2 | x9; + const T x24 = x15 ^ x23; + const T x25 = x3 | x22; + const T x26 = x24 ^ x25; + + const T x27 = a1 | x11; + const T x28 = x14 & x27; + const T x29 = x20 ^ x28; + const T x30 = x29 & ~x13; + const T x31 = x6 | a4; + const T x32 = x31 ^ x30; + out3 ^= x32; + + const T x33 = x4 ^ x29; + const T x34 = a5 & ~x33; + const T x35 = ~x23; + const T x36 = x18 ^ x35; + const T x37 = x34 ^ x36; + const T x38 = x37 & ~a4; + const T x39 = x38 ^ x26; + out2 ^= x39; + + const T x40 = a6 ^ x7; + const T x41 = a1 ^ x20; + const T x42 = x40 & x41; + const T x43 = x12 ^ x36; + const T x44 = x42 ^ x43; + const T x45 = x22 & ~a4; + const T x46 = x45 ^ x44; + out1 ^= x46; +} + +template +BOTAN_FORCE_INLINE void SBox7(T a1, T a2, T a3, T a4, T a5, T a6, T& out1, T& out2, T& out3, T& out4) { + const T x1 = a4 ^ a5; + const T x2 = a3 ^ x1; + const T x3 = a6 & x2; + const T x4 = a4 & x1; + const T x5 = a2 ^ x4; + const T x6 = x3 & x5; + + const T x7 = a6 & x4; + const T x8 = a3 ^ x7; + const T x9 = x5 | x8; + const T x10 = a6 ^ x1; + const T x11 = x9 ^ x10; + const T x12 = a1 & ~x6; + const T x13 = x12 ^ x11; + out4 ^= x13; + + const T x14 = a5 & ~x2; + const T x15 = x5 | x14; + const T x16 = x3 ^ x8; + const T x17 = x15 ^ x16; + + const T x18 = x3 ^ x10; + const T x19 = a4 & ~x18; + const T x20 = x5 & ~x19; + const T x21 = a5 ^ x16; + const T x22 = x20 ^ x21; + + const T x23 = x18 & ~x7; + const T x24 = x19 | x23; + const T x25 = a2 ^ x9; + const T x26 = x22 & x25; + const T x27 = x24 ^ x26; + const T x28 = x27 & a1; + const T x29 = x28 ^ x22; + out3 ^= x29; + + const T x30 = x5 & ~a3; + const T x31 = x23 | x30; + const T x32 = x4 | x22; + const T x33 = x31 & x32; + const T x34 = x27 ^ x33; + + const T x35 = x17 | x24; + const T x36 = x14 ^ x35; + const T x37 = a6 & x36; + const T x38 = x33 ^ x37; + const T x39 = x38 & ~a1; + const T x40 = x39 ^ x17; + out1 ^= x40; + + const T x41 = ~x37; + const T x42 = a2 | x41; + const T x43 = x17 ^ x33; + const T x44 = x42 ^ x43; + const T x45 = x34 | a1; + const T x46 = x45 ^ x44; + out2 ^= x46; +} + +template +BOTAN_FORCE_INLINE void SBox8(T a1, T a2, T a3, T a4, T a5, T a6, T& out1, T& out2, T& out3, T& out4) { + const T x1 = a3 & ~a2; + const T x2 = a5 & ~a3; + const T x3 = a4 ^ x2; + const T x4 = a1 & x3; + const T x5 = x4 & ~x1; + + const T x6 = a2 & ~x3; + const T x7 = a1 | x6; + const T x8 = a2 & ~a3; + const T x9 = a5 ^ x8; + const T x10 = x7 & x9; + const T x11 = x4 | x10; + + const T x12 = ~x3; + const T x13 = x10 ^ x12; + const T x14 = a3 & ~x7; + const T x15 = x13 ^ x14; + const T x16 = x1 ^ x15; + const T x17 = x5 | a6; + const T x18 = x17 ^ x16; + out2 ^= x18; + + const T x19 = a1 ^ x16; + const T x20 = a5 & x19; + const T x21 = a2 ^ x15; + const T x22 = x20 ^ x21; + const T x23 = x6 ^ x22; + + const T x24 = x11 ^ x22; + const T x25 = a2 | x24; + const T x26 = a5 ^ x19; + const T x27 = x25 ^ x26; + const T x28 = x11 & a6; + const T x29 = x28 ^ x27; + out3 ^= x29; + + const T x30 = x9 ^ x23; + const T x31 = a4 | x21; + const T x32 = x30 ^ x31; + const T x33 = a1 ^ x32; + const T x34 = x33 & a6; + const T x35 = x34 ^ x23; + out4 ^= x35; + + const T x36 = x30 & ~a4; + const T x37 = x27 & x36; + const T x38 = x5 ^ x32; + const T x39 = x37 ^ x38; + const T x40 = x39 | a6; + const T x41 = x40 ^ x23; + out1 ^= x41; +} + +void des_transpose(uint64_t M[32]) { + for(size_t i = 0; i != 16; ++i) { + swap_bits(M[i], M[i + 16], 0x0000FFFF0000FFFF, 16); + } + + for(size_t i = 0; i != 32; i += 16) { + for(size_t j = 0; j != 8; ++j) { + swap_bits(M[i + j], M[i + j + 8], 0x00FF00FF00FF00FF, 8); + } + } + + for(size_t i = 0; i != 32; i += 8) { + for(size_t j = 0; j != 4; ++j) { + swap_bits(M[i + j + 0], M[i + j + 4], 0x0F0F0F0F0F0F0F0F, 4); + } + } + + for(size_t i = 0; i != 32; i += 4) { + for(size_t j = 0; j != 2; ++j) { + swap_bits(M[i + j + 0], M[i + j + 2], 0x3333333333333333, 2); + } + } + + for(size_t i = 0; i != 32; i += 2) { + swap_bits(M[i], M[i + 1], 0x5555555555555555, 1); + } +} + +void transpose_in(uint32_t B[64], const uint8_t in[], size_t n_blocks) { + uint64_t M[32] = {}; + + load_be(M, in, n_blocks); + + des_transpose(M); + + // clang-format off + static constexpr uint8_t IP[64] = { + 57, 49, 41, 33, 25, 17, 9, 1, + 59, 51, 43, 35, 27, 19, 11, 3, + 61, 53, 45, 37, 29, 21, 13, 5, + 63, 55, 47, 39, 31, 23, 15, 7, + 56, 48, 40, 32, 24, 16, 8, 0, + 58, 50, 42, 34, 26, 18, 10, 2, + 60, 52, 44, 36, 28, 20, 12, 4, + 62, 54, 46, 38, 30, 22, 14, 6 + }; + // clang-format on + + for(size_t i = 0; i < 64; ++i) { + const uint8_t src = IP[i]; + if(src < 32) { + B[i] = static_cast(M[31 - src] >> 32); + } else { + B[i] = static_cast(M[63 - src]); + } + } +} + +void transpose_out(uint8_t out[], const uint32_t B[64], size_t n_blocks) { + // clang-format off + static constexpr uint8_t FP[64] = { + 39, 7, 47, 15, 55, 23, 63, 31, + 38, 6, 46, 14, 54, 22, 62, 30, + 37, 5, 45, 13, 53, 21, 61, 29, + 36, 4, 44, 12, 52, 20, 60, 28, + 35, 3, 43, 11, 51, 19, 59, 27, + 34, 2, 42, 10, 50, 18, 58, 26, + 33, 1, 41, 9, 49, 17, 57, 25, + 32, 0, 40, 8, 48, 16, 56, 24 + }; + // clang-format on + + uint64_t M[32]; + for(size_t i = 0; i != 32; ++i) { + // XOR with 32 here absorbs the DES output swap into the FP + M[i] = (static_cast(B[FP[31 - i] ^ 32]) << 32) | B[FP[63 - i] ^ 32]; + } + + des_transpose(M); + + for(size_t i = 0; i != n_blocks; ++i) { + store_be(out + i * 8, M[i]); + } +} /* -* DES Key Schedule +* DES round - L ^= P(S(E(R) ^ K)) +* +* Each S-box takes 6 bits from E(R) XORed with 6 round key bits, +* and XORs 4 output bits into L at positions given by the P permutation. +* The E expansion, key XOR, S-box evaluation, and P permutation are +* all fused into the calls below. */ -void des_key_schedule(uint32_t round_key[32], const uint8_t key[8]) { +void des_round(uint32_t L[32], const uint32_t R[32], const uint32_t RK[48]) { + // clang-format off + SBox1(R[31] ^ RK[ 0], R[ 0] ^ RK[ 1], R[ 1] ^ RK[ 2], + R[ 2] ^ RK[ 3], R[ 3] ^ RK[ 4], R[ 4] ^ RK[ 5], + L[ 8], L[16], L[22], L[30]); + + SBox2(R[ 3] ^ RK[ 6], R[ 4] ^ RK[ 7], R[ 5] ^ RK[ 8], + R[ 6] ^ RK[ 9], R[ 7] ^ RK[10], R[ 8] ^ RK[11], + L[12], L[27], L[ 1], L[17]); + + SBox3(R[ 7] ^ RK[12], R[ 8] ^ RK[13], R[ 9] ^ RK[14], + R[10] ^ RK[15], R[11] ^ RK[16], R[12] ^ RK[17], + L[23], L[15], L[29], L[ 5]); + + SBox4(R[11] ^ RK[18], R[12] ^ RK[19], R[13] ^ RK[20], + R[14] ^ RK[21], R[15] ^ RK[22], R[16] ^ RK[23], + L[25], L[19], L[ 9], L[ 0]); + + SBox5(R[15] ^ RK[24], R[16] ^ RK[25], R[17] ^ RK[26], + R[18] ^ RK[27], R[19] ^ RK[28], R[20] ^ RK[29], + L[ 7], L[13], L[24], L[ 2]); + + SBox6(R[19] ^ RK[30], R[20] ^ RK[31], R[21] ^ RK[32], + R[22] ^ RK[33], R[23] ^ RK[34], R[24] ^ RK[35], + L[ 3], L[28], L[10], L[18]); + + SBox7(R[23] ^ RK[36], R[24] ^ RK[37], R[25] ^ RK[38], + R[26] ^ RK[39], R[27] ^ RK[40], R[28] ^ RK[41], + L[31], L[11], L[21], L[ 6]); + + SBox8(R[27] ^ RK[42], R[28] ^ RK[43], R[29] ^ RK[44], + R[30] ^ RK[45], R[31] ^ RK[46], R[ 0] ^ RK[47], + L[ 4], L[26], L[14], L[20]); + // clang-format on +} + +void des_encrypt(uint32_t L[32], uint32_t R[32], const uint32_t round_key[]) { + for(size_t round = 0; round < 16; round += 2) { + des_round(L, R, &round_key[round * 48]); + des_round(R, L, &round_key[(round + 1) * 48]); + } +} + +void des_decrypt(uint32_t L[32], uint32_t R[32], const uint32_t round_key[]) { + for(size_t round = 16; round > 0; round -= 2) { + des_round(L, R, &round_key[(round - 1) * 48]); + des_round(R, L, &round_key[(round - 2) * 48]); + } +} + +/* +* The usual DES key schedule except that each round key is instead of 48 bits, +* is 48 32-bit values which are either all-1 or all-0 +*/ +void des_key_schedule(uint32_t round_key[], const uint8_t key[8]) { static const uint8_t ROT[16] = {1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1}; uint32_t C = ((key[7] & 0x80) << 20) | ((key[6] & 0x80) << 19) | ((key[5] & 0x80) << 18) | ((key[4] & 0x80) << 17) | @@ -98,147 +671,28 @@ ((key[3] & 0x08) << 4) | ((key[2] & 0x08) << 3) | ((key[1] & 0x08) << 2) | ((key[0] & 0x08) << 1) | ((key[3] & 0x10) >> 1) | ((key[2] & 0x10) >> 2) | ((key[1] & 0x10) >> 3) | ((key[0] & 0x10) >> 4); + static const uint8_t PC2_C[24] = {13, 16, 10, 23, 0, 4, 2, 27, 14, 5, 20, 9, + 22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1}; + + static const uint8_t PC2_D[24] = {12, 23, 2, 8, 18, 26, 1, 11, 22, 16, 4, 19, + 15, 20, 10, 27, 5, 24, 17, 13, 21, 7, 0, 3}; + for(size_t i = 0; i != 16; ++i) { C = ((C << ROT[i]) | (C >> (28 - ROT[i]))) & 0x0FFFFFFF; D = ((D << ROT[i]) | (D >> (28 - ROT[i]))) & 0x0FFFFFFF; - round_key[2 * i] = ((C & 0x00000010) << 22) | ((C & 0x00000800) << 17) | ((C & 0x00000020) << 16) | - ((C & 0x00004004) << 15) | ((C & 0x00000200) << 11) | ((C & 0x00020000) << 10) | - ((C & 0x01000000) >> 6) | ((C & 0x00100000) >> 4) | ((C & 0x00010000) << 3) | - ((C & 0x08000000) >> 2) | ((C & 0x00800000) << 1) | ((D & 0x00000010) << 8) | - ((D & 0x00000002) << 7) | ((D & 0x00000001) << 2) | ((D & 0x00000200)) | - ((D & 0x00008000) >> 2) | ((D & 0x00000088) >> 3) | ((D & 0x00001000) >> 7) | - ((D & 0x00080000) >> 9) | ((D & 0x02020000) >> 14) | ((D & 0x00400000) >> 21); - round_key[2 * i + 1] = - ((C & 0x00000001) << 28) | ((C & 0x00000082) << 18) | ((C & 0x00002000) << 14) | ((C & 0x00000100) << 10) | - ((C & 0x00001000) << 9) | ((C & 0x00040000) << 6) | ((C & 0x02400000) << 4) | ((C & 0x00008000) << 2) | - ((C & 0x00200000) >> 1) | ((C & 0x04000000) >> 10) | ((D & 0x00000020) << 6) | ((D & 0x00000100)) | - ((D & 0x00000800) >> 1) | ((D & 0x00000040) >> 3) | ((D & 0x00010000) >> 4) | ((D & 0x00000400) >> 5) | - ((D & 0x00004000) >> 10) | ((D & 0x04000000) >> 13) | ((D & 0x00800000) >> 14) | ((D & 0x00100000) >> 18) | - ((D & 0x01000000) >> 24) | ((D & 0x08000000) >> 26); - } -} -inline uint32_t spbox(uint32_t T0, uint32_t T1) { - return ((SPBOX_CATS[0 * 64 + ((T0 >> 24) & 0x3F)] * SPBOX_CAT_0_MUL) & SPBOX_CAT_0_MASK) ^ - ((SPBOX_CATS[1 * 64 + ((T1 >> 24) & 0x3F)] * SPBOX_CAT_1_MUL) & SPBOX_CAT_1_MASK) ^ - ((SPBOX_CATS[2 * 64 + ((T0 >> 16) & 0x3F)] * SPBOX_CAT_2_MUL) & SPBOX_CAT_2_MASK) ^ - ((SPBOX_CATS[3 * 64 + ((T1 >> 16) & 0x3F)] * SPBOX_CAT_3_MUL) & SPBOX_CAT_3_MASK) ^ - ((SPBOX_CATS[4 * 64 + ((T0 >> 8) & 0x3F)] * SPBOX_CAT_4_MUL) & SPBOX_CAT_4_MASK) ^ - ((SPBOX_CATS[5 * 64 + ((T1 >> 8) & 0x3F)] * SPBOX_CAT_5_MUL) & SPBOX_CAT_5_MASK) ^ - ((SPBOX_CATS[6 * 64 + ((T0 >> 0) & 0x3F)] * SPBOX_CAT_6_MUL) & SPBOX_CAT_6_MASK) ^ - ((SPBOX_CATS[7 * 64 + ((T1 >> 0) & 0x3F)] * SPBOX_CAT_7_MUL) & SPBOX_CAT_7_MASK); -} + uint32_t* rk = &round_key[i * 48]; -/* -* DES Encryption -*/ -inline void des_encrypt(uint32_t& Lr, uint32_t& Rr, const uint32_t round_key[32]) { - uint32_t L = Lr; - uint32_t R = Rr; - for(size_t i = 0; i != 16; i += 2) { - L ^= spbox(rotr<4>(R) ^ round_key[2 * i], R ^ round_key[2 * i + 1]); - R ^= spbox(rotr<4>(L) ^ round_key[2 * i + 2], L ^ round_key[2 * i + 3]); + for(size_t j = 0; j < 24; ++j) { + const uint32_t bit = (C >> (27 - PC2_C[j])) & 1; + rk[j] = static_cast(0) - bit; + } + + for(size_t j = 0; j < 24; ++j) { + const uint32_t bit = (D >> (27 - PC2_D[j])) & 1; + rk[24 + j] = static_cast(0) - bit; + } } - - Lr = L; - Rr = R; -} - -inline void des_encrypt_x2(uint32_t& L0r, uint32_t& R0r, uint32_t& L1r, uint32_t& R1r, const uint32_t round_key[32]) { - uint32_t L0 = L0r; - uint32_t R0 = R0r; - uint32_t L1 = L1r; - uint32_t R1 = R1r; - - for(size_t i = 0; i != 16; i += 2) { - L0 ^= spbox(rotr<4>(R0) ^ round_key[2 * i], R0 ^ round_key[2 * i + 1]); - L1 ^= spbox(rotr<4>(R1) ^ round_key[2 * i], R1 ^ round_key[2 * i + 1]); - - R0 ^= spbox(rotr<4>(L0) ^ round_key[2 * i + 2], L0 ^ round_key[2 * i + 3]); - R1 ^= spbox(rotr<4>(L1) ^ round_key[2 * i + 2], L1 ^ round_key[2 * i + 3]); - } - - L0r = L0; - R0r = R0; - L1r = L1; - R1r = R1; -} - -/* -* DES Decryption -*/ -inline void des_decrypt(uint32_t& Lr, uint32_t& Rr, const uint32_t round_key[32]) { - uint32_t L = Lr; - uint32_t R = Rr; - for(size_t i = 16; i != 0; i -= 2) { - L ^= spbox(rotr<4>(R) ^ round_key[2 * i - 2], R ^ round_key[2 * i - 1]); - R ^= spbox(rotr<4>(L) ^ round_key[2 * i - 4], L ^ round_key[2 * i - 3]); - } - Lr = L; - Rr = R; -} - -inline void des_decrypt_x2(uint32_t& L0r, uint32_t& R0r, uint32_t& L1r, uint32_t& R1r, const uint32_t round_key[32]) { - uint32_t L0 = L0r; - uint32_t R0 = R0r; - uint32_t L1 = L1r; - uint32_t R1 = R1r; - - for(size_t i = 16; i != 0; i -= 2) { - L0 ^= spbox(rotr<4>(R0) ^ round_key[2 * i - 2], R0 ^ round_key[2 * i - 1]); - L1 ^= spbox(rotr<4>(R1) ^ round_key[2 * i - 2], R1 ^ round_key[2 * i - 1]); - - R0 ^= spbox(rotr<4>(L0) ^ round_key[2 * i - 4], L0 ^ round_key[2 * i - 3]); - R1 ^= spbox(rotr<4>(L1) ^ round_key[2 * i - 4], L1 ^ round_key[2 * i - 3]); - } - - L0r = L0; - R0r = R0; - L1r = L1; - R1r = R1; -} - -inline void des_IP(uint32_t& L, uint32_t& R) { - // IP sequence by Wei Dai, taken from public domain Crypto++ - uint32_t T; - R = rotl<4>(R); - T = (L ^ R) & 0xF0F0F0F0; - L ^= T; - R = rotr<20>(R ^ T); - T = (L ^ R) & 0xFFFF0000; - L ^= T; - R = rotr<18>(R ^ T); - T = (L ^ R) & 0x33333333; - L ^= T; - R = rotr<6>(R ^ T); - T = (L ^ R) & 0x00FF00FF; - L ^= T; - R = rotl<9>(R ^ T); - T = (L ^ R) & 0xAAAAAAAA; - L = rotl<1>(L ^ T); - R ^= T; -} - -inline void des_FP(uint32_t& L, uint32_t& R) { - // FP sequence by Wei Dai, taken from public domain Crypto++ - uint32_t T; - - R = rotr<1>(R); - T = (L ^ R) & 0xAAAAAAAA; - R ^= T; - L = rotr<9>(L ^ T); - T = (L ^ R) & 0x00FF00FF; - R ^= T; - L = rotl<6>(L ^ T); - T = (L ^ R) & 0x33333333; - R ^= T; - L = rotl<18>(L ^ T); - T = (L ^ R) & 0xFFFF0000; - R ^= T; - L = rotl<20>(L ^ T); - T = (L ^ R) & 0xF0F0F0F0; - R ^= T; - L = rotr<4>(L ^ T); } } // namespace @@ -249,38 +703,22 @@ void DES::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { assert_key_material_set(); - while(blocks >= 2) { - uint32_t L0 = load_be(in, 0); - uint32_t R0 = load_be(in, 1); - uint32_t L1 = load_be(in, 2); - uint32_t R1 = load_be(in, 3); - - des_IP(L0, R0); - des_IP(L1, R1); - - des_encrypt_x2(L0, R0, L1, R1, m_round_key.data()); - - des_FP(L0, R0); - des_FP(L1, R1); - - store_be(out, R0, L0, R1, L1); - - in += 2 * BLOCK_SIZE; - out += 2 * BLOCK_SIZE; - blocks -= 2; - } - - while(blocks > 0) { - uint32_t L0 = load_be(in, 0); - uint32_t R0 = load_be(in, 1); - des_IP(L0, R0); - des_encrypt(L0, R0, m_round_key.data()); - des_FP(L0, R0); - store_be(out, R0, L0); - - in += BLOCK_SIZE; - out += BLOCK_SIZE; - blocks -= 1; + uint32_t B[64]; + + while(blocks >= 32) { + transpose_in(B, in, 32); + des_encrypt(&B[0], &B[32], m_round_key.data()); + transpose_out(out, B, 32); + + in += 32 * BLOCK_SIZE; + out += 32 * BLOCK_SIZE; + blocks -= 32; + } + + if(blocks > 0) { + transpose_in(B, in, blocks); + des_encrypt(&B[0], &B[32], m_round_key.data()); + transpose_out(out, B, blocks); } } @@ -290,38 +728,22 @@ void DES::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { assert_key_material_set(); - while(blocks >= 2) { - uint32_t L0 = load_be(in, 0); - uint32_t R0 = load_be(in, 1); - uint32_t L1 = load_be(in, 2); - uint32_t R1 = load_be(in, 3); - - des_IP(L0, R0); - des_IP(L1, R1); - - des_decrypt_x2(L0, R0, L1, R1, m_round_key.data()); - - des_FP(L0, R0); - des_FP(L1, R1); - - store_be(out, R0, L0, R1, L1); - - in += 2 * BLOCK_SIZE; - out += 2 * BLOCK_SIZE; - blocks -= 2; - } - - while(blocks > 0) { - uint32_t L0 = load_be(in, 0); - uint32_t R0 = load_be(in, 1); - des_IP(L0, R0); - des_decrypt(L0, R0, m_round_key.data()); - des_FP(L0, R0); - store_be(out, R0, L0); - - in += BLOCK_SIZE; - out += BLOCK_SIZE; - blocks -= 1; + uint32_t B[64]; + + while(blocks >= 32) { + transpose_in(B, in, 32); + des_decrypt(&B[0], &B[32], m_round_key.data()); + transpose_out(out, B, 32); + + in += 32 * BLOCK_SIZE; + out += 32 * BLOCK_SIZE; + blocks -= 32; + } + + if(blocks > 0) { + transpose_in(B, in, blocks); + des_decrypt(&B[0], &B[32], m_round_key.data()); + transpose_out(out, B, blocks); } } @@ -333,7 +755,7 @@ * DES Key Schedule */ void DES::key_schedule(std::span key) { - m_round_key.resize(32); + m_round_key.resize(16 * 48); des_key_schedule(m_round_key.data(), key.data()); } @@ -347,44 +769,30 @@ void TripleDES::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { assert_key_material_set(); - while(blocks >= 2) { - uint32_t L0 = load_be(in, 0); - uint32_t R0 = load_be(in, 1); - uint32_t L1 = load_be(in, 2); - uint32_t R1 = load_be(in, 3); - - des_IP(L0, R0); - des_IP(L1, R1); - - des_encrypt_x2(L0, R0, L1, R1, &m_round_key[0]); - des_decrypt_x2(R0, L0, R1, L1, &m_round_key[32]); - des_encrypt_x2(L0, R0, L1, R1, &m_round_key[64]); - - des_FP(L0, R0); - des_FP(L1, R1); - - store_be(out, R0, L0, R1, L1); - - in += 2 * BLOCK_SIZE; - out += 2 * BLOCK_SIZE; - blocks -= 2; - } - - while(blocks > 0) { - uint32_t L0 = load_be(in, 0); - uint32_t R0 = load_be(in, 1); - - des_IP(L0, R0); - des_encrypt(L0, R0, &m_round_key[0]); - des_decrypt(R0, L0, &m_round_key[32]); - des_encrypt(L0, R0, &m_round_key[64]); - des_FP(L0, R0); - - store_be(out, R0, L0); - - in += BLOCK_SIZE; - out += BLOCK_SIZE; - blocks -= 1; + const uint32_t* k1 = m_round_key.data(); + const uint32_t* k2 = k1 + 16 * 48; + const uint32_t* k3 = k2 + 16 * 48; + + uint32_t B[64]; + + while(blocks >= 32) { + transpose_in(B, in, 32); + des_encrypt(&B[0], &B[32], k1); + des_decrypt(&B[32], &B[0], k2); + des_encrypt(&B[0], &B[32], k3); + transpose_out(out, B, 32); + + in += 32 * BLOCK_SIZE; + out += 32 * BLOCK_SIZE; + blocks -= 32; + } + + if(blocks > 0) { + transpose_in(B, in, blocks); + des_encrypt(&B[0], &B[32], k1); + des_decrypt(&B[32], &B[0], k2); + des_encrypt(&B[0], &B[32], k3); + transpose_out(out, B, blocks); } } @@ -394,44 +802,30 @@ void TripleDES::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { assert_key_material_set(); - while(blocks >= 2) { - uint32_t L0 = load_be(in, 0); - uint32_t R0 = load_be(in, 1); - uint32_t L1 = load_be(in, 2); - uint32_t R1 = load_be(in, 3); - - des_IP(L0, R0); - des_IP(L1, R1); - - des_decrypt_x2(L0, R0, L1, R1, &m_round_key[64]); - des_encrypt_x2(R0, L0, R1, L1, &m_round_key[32]); - des_decrypt_x2(L0, R0, L1, R1, &m_round_key[0]); - - des_FP(L0, R0); - des_FP(L1, R1); - - store_be(out, R0, L0, R1, L1); - - in += 2 * BLOCK_SIZE; - out += 2 * BLOCK_SIZE; - blocks -= 2; - } - - while(blocks > 0) { - uint32_t L0 = load_be(in, 0); - uint32_t R0 = load_be(in, 1); - - des_IP(L0, R0); - des_decrypt(L0, R0, &m_round_key[64]); - des_encrypt(R0, L0, &m_round_key[32]); - des_decrypt(L0, R0, &m_round_key[0]); - des_FP(L0, R0); - - store_be(out, R0, L0); - - in += BLOCK_SIZE; - out += BLOCK_SIZE; - blocks -= 1; + const uint32_t* k1 = m_round_key.data(); + const uint32_t* k2 = k1 + 16 * 48; + const uint32_t* k3 = k2 + 16 * 48; + + uint32_t B[64]; + + while(blocks >= 32) { + transpose_in(B, in, 32); + des_decrypt(&B[0], &B[32], k3); + des_encrypt(&B[32], &B[0], k2); + des_decrypt(&B[0], &B[32], k1); + transpose_out(out, B, 32); + + in += 32 * BLOCK_SIZE; + out += 32 * BLOCK_SIZE; + blocks -= 32; + } + + if(blocks > 0) { + transpose_in(B, in, blocks); + des_decrypt(&B[0], &B[32], k3); + des_encrypt(&B[32], &B[0], k2); + des_decrypt(&B[0], &B[32], k1); + transpose_out(out, B, blocks); } } @@ -443,14 +837,14 @@ * TripleDES Key Schedule */ void TripleDES::key_schedule(std::span key) { - m_round_key.resize(3 * 32); - des_key_schedule(&m_round_key[0], key.first(8).data()); - des_key_schedule(&m_round_key[32], key.subspan(8, 8).data()); + m_round_key.resize(3 * 16 * 48); + des_key_schedule(m_round_key.data(), key.first(8).data()); + des_key_schedule(m_round_key.data() + 16 * 48, key.subspan(8, 8).data()); if(key.size() == 24) { - des_key_schedule(&m_round_key[64], key.last(8).data()); + des_key_schedule(m_round_key.data() + 2 * 16 * 48, key.last(8).data()); } else { - copy_mem(&m_round_key[64], &m_round_key[0], 32); + copy_mem(m_round_key.data() + 2 * 16 * 48, m_round_key.data(), 16 * 48); } } diff -Nru botan3-3.7.1+dfsg/src/lib/block/des/des.h botan3-3.12.0+dfsg/src/lib/block/des/des.h --- botan3-3.7.1+dfsg/src/lib/block/des/des.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/des/des.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,6 +9,7 @@ #define BOTAN_DES_H_ #include +#include namespace Botan { @@ -26,10 +27,12 @@ std::unique_ptr new_object() const override { return std::make_unique(); } + size_t parallelism() const override { return 32; } + bool has_keying_material() const override; private: - void key_schedule(std::span) override; + void key_schedule(std::span key) override; secure_vector m_round_key; }; @@ -48,10 +51,12 @@ std::unique_ptr new_object() const override { return std::make_unique(); } + size_t parallelism() const override { return 32; } + bool has_keying_material() const override; private: - void key_schedule(std::span) override; + void key_schedule(std::span key) override; secure_vector m_round_key; }; diff -Nru botan3-3.7.1+dfsg/src/lib/block/gost_28147/gost_28147.cpp botan3-3.12.0+dfsg/src/lib/block/gost_28147/gost_28147.cpp --- botan3-3.7.1+dfsg/src/lib/block/gost_28147/gost_28147.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/gost_28147/gost_28147.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -26,7 +26,7 @@ } GOST_28147_89_Params::GOST_28147_89_Params(std::string_view n) : m_name(n) { - // Encoded in the packed fromat from RFC 4357 + // Encoded in the packed format from RFC 4357 // GostR3411_94_TestParamSet (OID 1.2.643.2.2.31.0) static const uint8_t GOST_R_3411_TEST_PARAMS[64] = { diff -Nru botan3-3.7.1+dfsg/src/lib/block/gost_28147/gost_28147.h botan3-3.12.0+dfsg/src/lib/block/gost_28147/gost_28147.h --- botan3-3.7.1+dfsg/src/lib/block/gost_28147/gost_28147.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/gost_28147/gost_28147.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,6 +9,7 @@ #define BOTAN_GOST_28147_89_H_ #include +#include namespace Botan { diff -Nru botan3-3.7.1+dfsg/src/lib/block/idea/idea.cpp botan3-3.12.0+dfsg/src/lib/block/idea/idea.cpp --- botan3-3.7.1+dfsg/src/lib/block/idea/idea.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/idea/idea.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,10 +7,13 @@ #include -#include #include #include +#if defined(BOTAN_HAS_CPUID) + #include +#endif + namespace Botan { namespace { @@ -19,17 +22,14 @@ * Multiplication modulo 65537 */ inline uint16_t mul(uint16_t x, uint16_t y) { - const uint32_t P = static_cast(x) * y; - const auto P_mask = CT::Mask(CT::Mask::is_zero(P)); - - const uint32_t P_hi = P >> 16; - const uint32_t P_lo = P & 0xFFFF; + uint32_t P = static_cast(x) * y; + const uint16_t P_is_zero = static_cast(ct_is_zero(P)); - const uint16_t carry = (P_lo < P_hi); - const uint16_t r_1 = static_cast((P_lo - P_hi) + carry); - const uint16_t r_2 = 1 - x - y; + P = (P & 0xFFFF) - (P >> 16); + const uint16_t R1 = static_cast(P - (P >> 16)); + const uint16_t R0 = 1 - x - y; - return P_mask.select(r_2, r_1); + return choose(P_is_zero, R0, R1); } /* @@ -65,7 +65,10 @@ CT::poison(K, 52); for(size_t i = 0; i < blocks; ++i) { - uint16_t X1, X2, X3, X4; + uint16_t X1 = 0; + uint16_t X2 = 0; + uint16_t X3 = 0; + uint16_t X4 = 0; load_be(in + BLOCK_SIZE * i, X1, X2, X3, X4); for(size_t j = 0; j != 8; ++j) { @@ -103,8 +106,14 @@ } // namespace size_t IDEA::parallelism() const { +#if defined(BOTAN_HAS_IDEA_AVX2) + if(CPUID::has(CPUID::Feature::AVX2)) { + return 16; + } +#endif + #if defined(BOTAN_HAS_IDEA_SSE2) - if(CPUID::has_sse2()) { + if(CPUID::has(CPUID::Feature::SSE2)) { return 8; } #endif @@ -113,9 +122,15 @@ } std::string IDEA::provider() const { +#if defined(BOTAN_HAS_IDEA_AVX2) + if(auto feat = CPUID::check(CPUID::Feature::AVX2)) { + return *feat; + } +#endif + #if defined(BOTAN_HAS_IDEA_SSE2) - if(CPUID::has_sse2()) { - return "sse2"; + if(auto feat = CPUID::check(CPUID::Feature::SSE2)) { + return *feat; } #endif @@ -128,8 +143,19 @@ void IDEA::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { assert_key_material_set(); +#if defined(BOTAN_HAS_IDEA_AVX2) + if(CPUID::has(CPUID::Feature::AVX2)) { + while(blocks >= 16) { + avx2_idea_op_16(in, out, m_EK.data()); + in += 16 * BLOCK_SIZE; + out += 16 * BLOCK_SIZE; + blocks -= 16; + } + } +#endif + #if defined(BOTAN_HAS_IDEA_SSE2) - if(CPUID::has_sse2()) { + if(CPUID::has(CPUID::Feature::SSE2)) { while(blocks >= 8) { sse2_idea_op_8(in, out, m_EK.data()); in += 8 * BLOCK_SIZE; @@ -148,8 +174,19 @@ void IDEA::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { assert_key_material_set(); +#if defined(BOTAN_HAS_IDEA_AVX2) + if(CPUID::has(CPUID::Feature::AVX2)) { + while(blocks >= 16) { + avx2_idea_op_16(in, out, m_DK.data()); + in += 16 * BLOCK_SIZE; + out += 16 * BLOCK_SIZE; + blocks -= 16; + } + } +#endif + #if defined(BOTAN_HAS_IDEA_SSE2) - if(CPUID::has_sse2()) { + if(CPUID::has(CPUID::Feature::SSE2)) { while(blocks >= 8) { sse2_idea_op_8(in, out, m_DK.data()); in += 8 * BLOCK_SIZE; diff -Nru botan3-3.7.1+dfsg/src/lib/block/idea/idea.h botan3-3.12.0+dfsg/src/lib/block/idea/idea.h --- botan3-3.7.1+dfsg/src/lib/block/idea/idea.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/idea/idea.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,6 +9,7 @@ #define BOTAN_IDEA_H_ #include +#include namespace Botan { @@ -32,6 +33,10 @@ bool has_keying_material() const override; private: +#if defined(BOTAN_HAS_IDEA_AVX2) + static void avx2_idea_op_16(const uint8_t in[128], uint8_t out[128], const uint16_t EK[52]); +#endif + #if defined(BOTAN_HAS_IDEA_SSE2) static void sse2_idea_op_8(const uint8_t in[64], uint8_t out[64], const uint16_t EK[52]); #endif diff -Nru botan3-3.7.1+dfsg/src/lib/block/idea/idea_avx2/idea_avx2.cpp botan3-3.12.0+dfsg/src/lib/block/idea/idea_avx2/idea_avx2.cpp --- botan3-3.7.1+dfsg/src/lib/block/idea/idea_avx2/idea_avx2.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/idea/idea_avx2/idea_avx2.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,219 @@ +/* +* (C) 2026 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include + +namespace Botan { + +namespace { + +// NOLINTBEGIN(portability-simd-intrinsics) + +/* +* SIMD type of 16 16-bit elements +*/ +class SIMD_16x16 final { + public: + using native_type = __m256i; + + SIMD_16x16(const SIMD_16x16&) = default; + SIMD_16x16& operator=(const SIMD_16x16&) = default; + SIMD_16x16(SIMD_16x16&&) = default; + SIMD_16x16& operator=(SIMD_16x16&&) = default; + ~SIMD_16x16() = default; + + BOTAN_FN_ISA_AVX2 explicit SIMD_16x16(native_type x) : m_simd(x) {} + + static SIMD_16x16 BOTAN_FN_ISA_AVX2 load_le(const uint8_t in[]) { + return SIMD_16x16(_mm256_loadu_si256(reinterpret_cast(in))); + } + + void BOTAN_FN_ISA_AVX2 store_le(uint8_t out[]) const { + _mm256_storeu_si256(reinterpret_cast<__m256i*>(out), m_simd); + } + + static SIMD_16x16 BOTAN_FN_ISA_AVX2 load_be(const uint8_t in[]) { return load_le(in).bswap(); } + + void BOTAN_FN_ISA_AVX2 store_be(uint8_t out[]) const { bswap().store_le(out); } + + SIMD_16x16 BOTAN_FN_ISA_AVX2 bswap() const { + // clang-format off + const auto bswap_tbl = _mm256_set_epi8( + 14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1, + 14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1); + // clang-format on + return SIMD_16x16(_mm256_shuffle_epi8(m_simd, bswap_tbl)); + } + + SIMD_16x16 BOTAN_FN_ISA_AVX2 operator-(const SIMD_16x16& o) const { + return SIMD_16x16(_mm256_sub_epi16(m_simd, o.m_simd)); + } + + SIMD_16x16 BOTAN_FN_ISA_AVX2 operator^(const SIMD_16x16& o) const { + return SIMD_16x16(_mm256_xor_si256(m_simd, o.m_simd)); + } + + void BOTAN_FN_ISA_AVX2 operator+=(const SIMD_16x16& o) { m_simd = _mm256_add_epi16(m_simd, o.m_simd); } + + void BOTAN_FN_ISA_AVX2 operator+=(uint16_t v) { m_simd = _mm256_add_epi16(m_simd, _mm256_set1_epi16(v)); } + + void BOTAN_FN_ISA_AVX2 operator^=(const SIMD_16x16& o) { m_simd = _mm256_xor_si256(m_simd, o.m_simd); } + + static inline BOTAN_FN_ISA_AVX2 SIMD_16x16 mul_mod_65537(SIMD_16x16 X, uint16_t K_16) { + const auto zeros = SIMD_16x16::splat(0); + const auto ones = SIMD_16x16::splat(1); + const auto K = SIMD_16x16::splat(K_16); + + // If X == 0 or K == 0 then P == X * K == 0 + const auto P_is_zero = SIMD_16x16( + _mm256_or_si256(_mm256_cmpeq_epi16(X.raw(), zeros.raw()), _mm256_cmpeq_epi16(K.raw(), zeros.raw()))); + + // Return value if P == 0: 1 - X - K + const auto R0 = ones - X - K; + + const auto mul_lo = SIMD_16x16(_mm256_mullo_epi16(X.raw(), K.raw())); + const auto mul_hi = SIMD_16x16(_mm256_mulhi_epu16(X.raw(), K.raw())); + + // AVX2 doesn't have unsigned comparisons so emulate with a signed compare by flipping the sign bit + const auto sign_bit = SIMD_16x16::splat(0x8000); + const auto borrow = SIMD_16x16(_mm256_cmpgt_epi16((mul_hi ^ sign_bit).raw(), (mul_lo ^ sign_bit).raw())); + + // R1 = mul_lo - mul_hi + (mul_hi > mul_lo ? 1 : 0) + const auto R1 = mul_lo - mul_hi - borrow; + + return SIMD_16x16(_mm256_blendv_epi8(R1.raw(), R0.raw(), P_is_zero.raw())); + } + + /* + * 4x16 matrix transpose + */ + static void BOTAN_FN_ISA_AVX2 transpose_in(SIMD_16x16& B0, SIMD_16x16& B1, SIMD_16x16& B2, SIMD_16x16& B3) { + auto B0r = _mm256_shuffle_epi32(B0.raw(), _MM_SHUFFLE(3, 1, 2, 0)); + auto B1r = _mm256_shuffle_epi32(B1.raw(), _MM_SHUFFLE(3, 1, 2, 0)); + auto B2r = _mm256_shuffle_epi32(B2.raw(), _MM_SHUFFLE(3, 1, 2, 0)); + auto B3r = _mm256_shuffle_epi32(B3.raw(), _MM_SHUFFLE(3, 1, 2, 0)); + + B0r = _mm256_shufflelo_epi16(B0r, _MM_SHUFFLE(3, 1, 2, 0)); + B1r = _mm256_shufflelo_epi16(B1r, _MM_SHUFFLE(3, 1, 2, 0)); + B2r = _mm256_shufflelo_epi16(B2r, _MM_SHUFFLE(3, 1, 2, 0)); + B3r = _mm256_shufflelo_epi16(B3r, _MM_SHUFFLE(3, 1, 2, 0)); + + B0r = _mm256_shufflehi_epi16(B0r, _MM_SHUFFLE(3, 1, 2, 0)); + B1r = _mm256_shufflehi_epi16(B1r, _MM_SHUFFLE(3, 1, 2, 0)); + B2r = _mm256_shufflehi_epi16(B2r, _MM_SHUFFLE(3, 1, 2, 0)); + B3r = _mm256_shufflehi_epi16(B3r, _MM_SHUFFLE(3, 1, 2, 0)); + + const auto T0 = _mm256_unpacklo_epi32(B0r, B1r); + const auto T1 = _mm256_unpackhi_epi32(B0r, B1r); + const auto T2 = _mm256_unpacklo_epi32(B2r, B3r); + const auto T3 = _mm256_unpackhi_epi32(B2r, B3r); + + B0 = SIMD_16x16(_mm256_unpacklo_epi64(T0, T2)); + B1 = SIMD_16x16(_mm256_unpackhi_epi64(T0, T2)); + B2 = SIMD_16x16(_mm256_unpacklo_epi64(T1, T3)); + B3 = SIMD_16x16(_mm256_unpackhi_epi64(T1, T3)); + } + + /* + * 4x16 matrix transpose (inverse) + */ + static void BOTAN_FN_ISA_AVX2 transpose_out(SIMD_16x16& B0, SIMD_16x16& B1, SIMD_16x16& B2, SIMD_16x16& B3) { + auto T0 = _mm256_unpacklo_epi64(B0.raw(), B1.raw()); + auto T1 = _mm256_unpacklo_epi64(B2.raw(), B3.raw()); + auto T2 = _mm256_unpackhi_epi64(B0.raw(), B1.raw()); + auto T3 = _mm256_unpackhi_epi64(B2.raw(), B3.raw()); + + T0 = _mm256_shuffle_epi32(T0, _MM_SHUFFLE(3, 1, 2, 0)); + T1 = _mm256_shuffle_epi32(T1, _MM_SHUFFLE(3, 1, 2, 0)); + T2 = _mm256_shuffle_epi32(T2, _MM_SHUFFLE(3, 1, 2, 0)); + T3 = _mm256_shuffle_epi32(T3, _MM_SHUFFLE(3, 1, 2, 0)); + + T0 = _mm256_shufflehi_epi16(T0, _MM_SHUFFLE(3, 1, 2, 0)); + T1 = _mm256_shufflehi_epi16(T1, _MM_SHUFFLE(3, 1, 2, 0)); + T2 = _mm256_shufflehi_epi16(T2, _MM_SHUFFLE(3, 1, 2, 0)); + T3 = _mm256_shufflehi_epi16(T3, _MM_SHUFFLE(3, 1, 2, 0)); + + T0 = _mm256_shufflelo_epi16(T0, _MM_SHUFFLE(3, 1, 2, 0)); + T1 = _mm256_shufflelo_epi16(T1, _MM_SHUFFLE(3, 1, 2, 0)); + T2 = _mm256_shufflelo_epi16(T2, _MM_SHUFFLE(3, 1, 2, 0)); + T3 = _mm256_shufflelo_epi16(T3, _MM_SHUFFLE(3, 1, 2, 0)); + + B0 = SIMD_16x16(_mm256_unpacklo_epi32(T0, T1)); + B1 = SIMD_16x16(_mm256_unpackhi_epi32(T0, T1)); + B2 = SIMD_16x16(_mm256_unpacklo_epi32(T2, T3)); + B3 = SIMD_16x16(_mm256_unpackhi_epi32(T2, T3)); + } + + native_type BOTAN_FN_ISA_AVX2 raw() const { return m_simd; } + + private: + static SIMD_16x16 BOTAN_FN_ISA_AVX2 splat(uint16_t v) { return SIMD_16x16(_mm256_set1_epi16(v)); } + + native_type m_simd; +}; + +// NOLINTEND(portability-simd-intrinsics) + +} // namespace + +BOTAN_FN_ISA_AVX2 void IDEA::avx2_idea_op_16(const uint8_t in[128], uint8_t out[128], const uint16_t EK[52]) { + CT::poison(in, 128); + CT::poison(out, 128); + CT::poison(EK, 52); + + auto B0 = SIMD_16x16::load_be(in + 0); + auto B1 = SIMD_16x16::load_be(in + 32); + auto B2 = SIMD_16x16::load_be(in + 64); + auto B3 = SIMD_16x16::load_be(in + 96); + + SIMD_16x16::transpose_in(B0, B1, B2, B3); + + for(size_t i = 0; i != 8; ++i) { + B0 = SIMD_16x16::mul_mod_65537(B0, EK[6 * i + 0]); + B1 += EK[6 * i + 1]; + B2 += EK[6 * i + 2]; + B3 = SIMD_16x16::mul_mod_65537(B3, EK[6 * i + 3]); + + const auto T0 = B2; + B2 ^= B0; + B2 = SIMD_16x16::mul_mod_65537(B2, EK[6 * i + 4]); + + const auto T1 = B1; + + B1 ^= B3; + B1 += B2; + B1 = SIMD_16x16::mul_mod_65537(B1, EK[6 * i + 5]); + + B2 += B1; + + B0 ^= B1; + B1 ^= T0; + B3 ^= B2; + B2 ^= T1; + } + + B0 = SIMD_16x16::mul_mod_65537(B0, EK[48]); + B1 += EK[50]; + B2 += EK[49]; + B3 = SIMD_16x16::mul_mod_65537(B3, EK[51]); + + SIMD_16x16::transpose_out(B0, B2, B1, B3); + + B0.store_be(out + 0); + B2.store_be(out + 32); + B1.store_be(out + 64); + B3.store_be(out + 96); + + CT::unpoison(in, 128); + CT::unpoison(out, 128); + CT::unpoison(EK, 52); +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/block/idea/idea_avx2/info.txt botan3-3.12.0+dfsg/src/lib/block/idea/idea_avx2/info.txt --- botan3-3.7.1+dfsg/src/lib/block/idea/idea_avx2/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/idea/idea_avx2/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,16 @@ + +IDEA_AVX2 -> 20260314 + + + +name -> "IDEA AVX2" +brief -> "IDEA using AVX2 SIMD instructions" + + + +avx2 + + + +cpuid + diff -Nru botan3-3.7.1+dfsg/src/lib/block/idea/idea_sse2/idea_sse2.cpp botan3-3.12.0+dfsg/src/lib/block/idea/idea_sse2/idea_sse2.cpp --- botan3-3.7.1+dfsg/src/lib/block/idea/idea_sse2/idea_sse2.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/idea/idea_sse2/idea_sse2.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,79 +8,65 @@ #include #include +#include #include namespace Botan { +// NOLINTBEGIN(portability-simd-intrinsics) TODO add various helper fns + namespace { -BOTAN_FUNC_ISA("sse2") inline __m128i mul(__m128i X, uint16_t K_16) { +BOTAN_FN_ISA_SSE2 inline __m128i mul(__m128i X, uint16_t K_16) { const __m128i zeros = _mm_set1_epi16(0); const __m128i ones = _mm_set1_epi16(1); - const __m128i K = _mm_set1_epi16(K_16); - const __m128i X_is_zero = _mm_cmpeq_epi16(X, zeros); - const __m128i K_is_zero = _mm_cmpeq_epi16(K, zeros); + // If X == 0 or K == 0 then P == X * K == 0 + const __m128i P_is_zero = _mm_or_si128(_mm_cmpeq_epi16(X, zeros), _mm_cmpeq_epi16(K, zeros)); + + // Return value if P == 0: 1 - X - K + const __m128i R0 = _mm_sub_epi16(_mm_sub_epi16(ones, X), K); const __m128i mul_lo = _mm_mullo_epi16(X, K); const __m128i mul_hi = _mm_mulhi_epu16(X, K); - __m128i T = _mm_sub_epi16(mul_lo, mul_hi); - - // Unsigned compare; cmp = 1 if mul_lo < mul_hi else 0 - const __m128i subs = _mm_subs_epu16(mul_hi, mul_lo); - const __m128i cmp = _mm_min_epu8(_mm_or_si128(subs, _mm_srli_epi16(subs, 8)), ones); - - T = _mm_add_epi16(T, cmp); - - /* Selection: if X[i] is zero then assign 1-K - if K is zero then assign 1-X[i] - - Could if() off value of K_16 for the second, but this gives a - constant time implementation which is a nice bonus. - */ + __m128i R1 = _mm_sub_epi16(mul_lo, mul_hi); - T = _mm_or_si128(_mm_andnot_si128(X_is_zero, T), _mm_and_si128(_mm_sub_epi16(ones, K), X_is_zero)); + // SSE doesn't have unsigned comparisons so emulate with a signed compare by flipping the sign bit + const __m128i sign_bit = _mm_set1_epi16(static_cast(0x8000)); + const __m128i borrow = _mm_cmpgt_epi16(_mm_xor_si128(mul_hi, sign_bit), _mm_xor_si128(mul_lo, sign_bit)); - T = _mm_or_si128(_mm_andnot_si128(K_is_zero, T), _mm_and_si128(_mm_sub_epi16(ones, X), K_is_zero)); + // R1 = mul_lo - mul_hi + (mul_hi > mul_lo ? 1 : 0) + R1 = _mm_sub_epi16(R1, borrow); - return T; + // Return either R1 or R0 (1-X-K) depending on if P == 0 or not + return _mm_or_si128(_mm_andnot_si128(P_is_zero, R1), _mm_and_si128(P_is_zero, R0)); } /* * 4x8 matrix transpose -* -* FIXME: why do I need the extra set of unpack_epi32 here? Inverse in -* transpose_out doesn't need it. Something with the shuffle? Removing -* that extra unpack could easily save 3-4 cycles per block, and would -* also help a lot with register pressure on 32-bit x86 */ -BOTAN_FUNC_ISA("sse2") void transpose_in(__m128i& B0, __m128i& B1, __m128i& B2, __m128i& B3) { - __m128i T0 = _mm_unpackhi_epi32(B0, B1); - __m128i T1 = _mm_unpacklo_epi32(B0, B1); - __m128i T2 = _mm_unpackhi_epi32(B2, B3); - __m128i T3 = _mm_unpacklo_epi32(B2, B3); - - __m128i T4 = _mm_unpacklo_epi32(T0, T1); - __m128i T5 = _mm_unpackhi_epi32(T0, T1); - __m128i T6 = _mm_unpacklo_epi32(T2, T3); - __m128i T7 = _mm_unpackhi_epi32(T2, T3); - - T0 = _mm_shufflehi_epi16(T4, _MM_SHUFFLE(1, 3, 0, 2)); - T1 = _mm_shufflehi_epi16(T5, _MM_SHUFFLE(1, 3, 0, 2)); - T2 = _mm_shufflehi_epi16(T6, _MM_SHUFFLE(1, 3, 0, 2)); - T3 = _mm_shufflehi_epi16(T7, _MM_SHUFFLE(1, 3, 0, 2)); - - T0 = _mm_shufflelo_epi16(T0, _MM_SHUFFLE(1, 3, 0, 2)); - T1 = _mm_shufflelo_epi16(T1, _MM_SHUFFLE(1, 3, 0, 2)); - T2 = _mm_shufflelo_epi16(T2, _MM_SHUFFLE(1, 3, 0, 2)); - T3 = _mm_shufflelo_epi16(T3, _MM_SHUFFLE(1, 3, 0, 2)); - - T0 = _mm_shuffle_epi32(T0, _MM_SHUFFLE(3, 1, 2, 0)); - T1 = _mm_shuffle_epi32(T1, _MM_SHUFFLE(3, 1, 2, 0)); - T2 = _mm_shuffle_epi32(T2, _MM_SHUFFLE(3, 1, 2, 0)); - T3 = _mm_shuffle_epi32(T3, _MM_SHUFFLE(3, 1, 2, 0)); +BOTAN_FN_ISA_SSE2 void transpose_in(__m128i& B0, __m128i& B1, __m128i& B2, __m128i& B3) { + B0 = _mm_shuffle_epi32(B0, _MM_SHUFFLE(3, 1, 2, 0)); + B1 = _mm_shuffle_epi32(B1, _MM_SHUFFLE(3, 1, 2, 0)); + B2 = _mm_shuffle_epi32(B2, _MM_SHUFFLE(3, 1, 2, 0)); + B3 = _mm_shuffle_epi32(B3, _MM_SHUFFLE(3, 1, 2, 0)); + + B0 = _mm_shufflelo_epi16(B0, _MM_SHUFFLE(3, 1, 2, 0)); + B1 = _mm_shufflelo_epi16(B1, _MM_SHUFFLE(3, 1, 2, 0)); + B2 = _mm_shufflelo_epi16(B2, _MM_SHUFFLE(3, 1, 2, 0)); + B3 = _mm_shufflelo_epi16(B3, _MM_SHUFFLE(3, 1, 2, 0)); + + B0 = _mm_shufflehi_epi16(B0, _MM_SHUFFLE(3, 1, 2, 0)); + B1 = _mm_shufflehi_epi16(B1, _MM_SHUFFLE(3, 1, 2, 0)); + B2 = _mm_shufflehi_epi16(B2, _MM_SHUFFLE(3, 1, 2, 0)); + B3 = _mm_shufflehi_epi16(B3, _MM_SHUFFLE(3, 1, 2, 0)); + + const __m128i T0 = _mm_unpacklo_epi32(B0, B1); + const __m128i T1 = _mm_unpackhi_epi32(B0, B1); + const __m128i T2 = _mm_unpacklo_epi32(B2, B3); + const __m128i T3 = _mm_unpackhi_epi32(B2, B3); B0 = _mm_unpacklo_epi64(T0, T2); B1 = _mm_unpackhi_epi64(T0, T2); @@ -91,7 +77,7 @@ /* * 4x8 matrix transpose (reverse) */ -BOTAN_FUNC_ISA("sse2") void transpose_out(__m128i& B0, __m128i& B1, __m128i& B2, __m128i& B3) { +BOTAN_FN_ISA_SSE2 void transpose_out(__m128i& B0, __m128i& B1, __m128i& B2, __m128i& B3) { __m128i T0 = _mm_unpacklo_epi64(B0, B1); __m128i T1 = _mm_unpacklo_epi64(B2, B3); __m128i T2 = _mm_unpackhi_epi64(B0, B1); @@ -123,7 +109,7 @@ /* * 8 wide IDEA encryption/decryption in SSE2 */ -BOTAN_FUNC_ISA("sse2") void IDEA::sse2_idea_op_8(const uint8_t in[64], uint8_t out[64], const uint16_t EK[52]) { +BOTAN_FN_ISA_SSE2 void IDEA::sse2_idea_op_8(const uint8_t in[64], uint8_t out[64], const uint16_t EK[52]) { CT::poison(in, 64); CT::poison(out, 64); CT::poison(EK, 52); @@ -149,11 +135,11 @@ B2 = _mm_add_epi16(B2, _mm_set1_epi16(EK[6 * i + 2])); B3 = mul(B3, EK[6 * i + 3]); - __m128i T0 = B2; + const __m128i T0 = B2; B2 = _mm_xor_si128(B2, B0); B2 = mul(B2, EK[6 * i + 4]); - __m128i T1 = B1; + const __m128i T1 = B1; B1 = _mm_xor_si128(B1, B3); B1 = _mm_add_epi16(B1, B2); @@ -192,4 +178,6 @@ CT::unpoison(EK, 52); } +// NOLINTEND(portability-simd-intrinsics) + } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/block/idea/idea_sse2/info.txt botan3-3.12.0+dfsg/src/lib/block/idea/idea_sse2/info.txt --- botan3-3.7.1+dfsg/src/lib/block/idea/idea_sse2/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/idea/idea_sse2/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + IDEA_SSE2 -> 20131128 - + name -> "IDEA SSE2" @@ -10,3 +10,7 @@ sse2 + + +cpuid + diff -Nru botan3-3.7.1+dfsg/src/lib/block/kuznyechik/kuznyechik.cpp botan3-3.12.0+dfsg/src/lib/block/kuznyechik/kuznyechik.cpp --- botan3-3.7.1+dfsg/src/lib/block/kuznyechik/kuznyechik.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/kuznyechik/kuznyechik.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -11,7 +11,7 @@ #include -#include +#include #include namespace Botan { @@ -53,37 +53,7 @@ const constexpr uint8_t LINEAR[16] = { 0x94, 0x20, 0x85, 0x10, 0xC2, 0xC0, 0x01, 0xFB, 0x01, 0xC0, 0xC2, 0x10, 0x85, 0x20, 0x94, 0x01}; -constexpr uint8_t poly_mul(uint8_t x, uint8_t y) { - const uint8_t poly = 0xC3; - - uint8_t r = 0; - while(x > 0 && y > 0) { - if(y & 1) { - r ^= x; - } - x = (x << 1) ^ ((x >> 7) * poly); - y >>= 1; - } - return r; -} - -constexpr uint64_t poly_mul(uint64_t x, uint8_t y) { - const uint64_t lo_bit = 0x0101010101010101; - const uint64_t mask = 0x7F7F7F7F7F7F7F7F; - const uint64_t poly = 0xC3; - - uint64_t r = 0; - while(x > 0 && y > 0) { - if(y & 1) { - r ^= x; - } - x = ((x & mask) << 1) ^ (((x >> 7) & lo_bit) * poly); - y >>= 1; - } - return r; -} - -consteval std::array T_table(bool forward) { +consteval std::array L_table(bool forward) noexcept { std::array L = {}; for(size_t i = 0; i != 16; ++i) { @@ -94,7 +64,10 @@ } if(!forward) { - std::reverse(L.begin(), L.end()); + // Reverse L + for(size_t i = 0; i != 128; ++i) { + std::swap(L[i], L[255 - i]); + } } auto sqr_matrix = [](std::span mat) { @@ -102,7 +75,7 @@ for(size_t i = 0; i != 16; ++i) { for(size_t j = 0; j != 16; ++j) { for(size_t k = 0; k != 16; ++k) { - res[16 * i + j] ^= poly_mul(mat[16 * i + k], mat[16 * k + j]); + res[16 * i + j] ^= poly_mul<0xC3>(mat[16 * i + k], mat[16 * k + j]); } } } @@ -113,8 +86,11 @@ L = sqr_matrix(L); } - const auto SB = forward ? S : IS; + return L; +} +consteval std::array T_table(std::span L, + std::span SB) noexcept { std::array T = {}; for(size_t i = 0; i != 16; ++i) { @@ -127,8 +103,8 @@ for(size_t j = 0; j != 256; ++j) { const uint8_t Sj = SB[j]; - T[512 * i + 2 * j] = poly_mul(L_stride_0, Sj); - T[512 * i + 2 * j + 1] = poly_mul(L_stride_1, Sj); + T[512 * i + 2 * j] = poly_mul<0xC3>(L_stride_0, Sj); + T[512 * i + 2 * j + 1] = poly_mul<0xC3>(L_stride_1, Sj); } } @@ -137,8 +113,13 @@ } // namespace Kuznyechik_T -const constinit auto T = Kuznyechik_T::T_table(true); -const constinit auto IT = Kuznyechik_T::T_table(false); +// TODO(Botan4) this indirection with L/IL is required to work around a problem +// with Clang 19, where suddenly T_table became too much for it to handle as constexpr. +// Check if it's possible to remove this. +constexpr auto L = Kuznyechik_T::L_table(true); +constexpr auto IL = Kuznyechik_T::L_table(false); +const constinit auto T = Kuznyechik_T::T_table(L, S); +const constinit auto IT = Kuznyechik_T::T_table(IL, IS); const uint64_t C[32][2] = {{0xb87a486c7276a26e, 0x019484dd10bd275d}, {0xb3f490d8e4ec87dc, 0x02ebcb7920b94eba}, {0x0b8ed8b4969a25b2, 0x037f4fa4300469e7}, {0xa52be3730b1bcd7b, 0x041555f240b19cb7}, @@ -207,18 +188,13 @@ } // namespace -Kuznyechik::~Kuznyechik() { - clear(); -} - void Kuznyechik::clear() { - secure_scrub_memory(m_rke, sizeof(m_rke)); - secure_scrub_memory(m_rkd, sizeof(m_rkd)); - m_has_keying_material = false; + zap(m_rke); + zap(m_rkd); } bool Kuznyechik::has_keying_material() const { - return m_has_keying_material; + return !m_rke.empty(); } void Kuznyechik::key_schedule(std::span key) { @@ -231,19 +207,19 @@ uint64_t k2 = load_le(key.data(), 2); uint64_t k3 = load_le(key.data(), 3); - m_rke[0][0] = k0; - m_rke[0][1] = k1; - m_rke[1][0] = k2; - m_rke[1][1] = k3; + m_rke.resize(20); + + m_rke[0] = k0; + m_rke[1] = k1; + m_rke[2] = k2; + m_rke[3] = k3; for(size_t i = 0; i != 4; ++i) { for(size_t r = 0; r != 8; r += 2) { - uint64_t t0, t1, t2, t3; - - t0 = k0 ^ C[8 * i + r][0]; - t1 = k1 ^ C[8 * i + r][1]; - t2 = k0; - t3 = k1; + uint64_t t0 = k0 ^ C[8 * i + r][0]; + uint64_t t1 = k1 ^ C[8 * i + r][1]; + const uint64_t t2 = k0; + const uint64_t t3 = k1; LS(t0, t1); t0 ^= k2; t1 ^= k3; @@ -257,15 +233,17 @@ k1 ^= t3; } - m_rke[2 * i + 2][0] = k0; - m_rke[2 * i + 2][1] = k1; - m_rke[2 * i + 3][0] = k2; - m_rke[2 * i + 3][1] = k3; + m_rke[4 * (i + 1) + 0] = k0; + m_rke[4 * (i + 1) + 1] = k1; + m_rke[4 * (i + 1) + 2] = k2; + m_rke[4 * (i + 1) + 3] = k3; } + m_rkd.resize(20); + for(size_t i = 0; i != 10; i++) { - uint64_t t0 = m_rke[i][0]; - uint64_t t1 = m_rke[i][1]; + uint64_t t0 = m_rke[2 * i + 0]; + uint64_t t1 = m_rke[2 * i + 1]; if(i > 0) { Kuznyechik_F::ILSS(t0, t1); @@ -273,57 +251,55 @@ const size_t dest = 9 - i; - m_rkd[dest][0] = t0; - m_rkd[dest][1] = t1; + m_rkd[2 * dest + 0] = t0; + m_rkd[2 * dest + 1] = t1; } - - m_has_keying_material = true; } void Kuznyechik::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { assert_key_material_set(); - while(blocks) { + while(blocks > 0) { uint64_t x1 = load_le(in, 0); uint64_t x2 = load_le(in, 1); - x1 ^= m_rke[0][0]; - x2 ^= m_rke[0][1]; + x1 ^= m_rke[0]; + x2 ^= m_rke[1]; Kuznyechik_F::LS(x1, x2); - x1 ^= m_rke[1][0]; - x2 ^= m_rke[1][1]; + x1 ^= m_rke[2]; + x2 ^= m_rke[3]; Kuznyechik_F::LS(x1, x2); - x1 ^= m_rke[2][0]; - x2 ^= m_rke[2][1]; + x1 ^= m_rke[4]; + x2 ^= m_rke[5]; Kuznyechik_F::LS(x1, x2); - x1 ^= m_rke[3][0]; - x2 ^= m_rke[3][1]; + x1 ^= m_rke[6]; + x2 ^= m_rke[7]; Kuznyechik_F::LS(x1, x2); - x1 ^= m_rke[4][0]; - x2 ^= m_rke[4][1]; + x1 ^= m_rke[8]; + x2 ^= m_rke[9]; Kuznyechik_F::LS(x1, x2); - x1 ^= m_rke[5][0]; - x2 ^= m_rke[5][1]; + x1 ^= m_rke[10]; + x2 ^= m_rke[11]; Kuznyechik_F::LS(x1, x2); - x1 ^= m_rke[6][0]; - x2 ^= m_rke[6][1]; + x1 ^= m_rke[12]; + x2 ^= m_rke[13]; Kuznyechik_F::LS(x1, x2); - x1 ^= m_rke[7][0]; - x2 ^= m_rke[7][1]; + x1 ^= m_rke[14]; + x2 ^= m_rke[15]; Kuznyechik_F::LS(x1, x2); - x1 ^= m_rke[8][0]; - x2 ^= m_rke[8][1]; + x1 ^= m_rke[16]; + x2 ^= m_rke[17]; Kuznyechik_F::LS(x1, x2); - x1 ^= m_rke[9][0]; - x2 ^= m_rke[9][1]; + x1 ^= m_rke[18]; + x2 ^= m_rke[19]; store_le(out, x1, x2); @@ -335,51 +311,51 @@ void Kuznyechik::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { assert_key_material_set(); - while(blocks) { + while(blocks > 0) { uint64_t x1 = load_le(in, 0); uint64_t x2 = load_le(in, 1); Kuznyechik_F::ILSS(x1, x2); - x1 ^= m_rkd[0][0]; - x2 ^= m_rkd[0][1]; + x1 ^= m_rkd[0]; + x2 ^= m_rkd[1]; Kuznyechik_F::ILS(x1, x2); - x1 ^= m_rkd[1][0]; - x2 ^= m_rkd[1][1]; + x1 ^= m_rkd[2]; + x2 ^= m_rkd[3]; Kuznyechik_F::ILS(x1, x2); - x1 ^= m_rkd[2][0]; - x2 ^= m_rkd[2][1]; + x1 ^= m_rkd[4]; + x2 ^= m_rkd[5]; Kuznyechik_F::ILS(x1, x2); - x1 ^= m_rkd[3][0]; - x2 ^= m_rkd[3][1]; + x1 ^= m_rkd[6]; + x2 ^= m_rkd[7]; Kuznyechik_F::ILS(x1, x2); - x1 ^= m_rkd[4][0]; - x2 ^= m_rkd[4][1]; + x1 ^= m_rkd[8]; + x2 ^= m_rkd[9]; Kuznyechik_F::ILS(x1, x2); - x1 ^= m_rkd[5][0]; - x2 ^= m_rkd[5][1]; + x1 ^= m_rkd[10]; + x2 ^= m_rkd[11]; Kuznyechik_F::ILS(x1, x2); - x1 ^= m_rkd[6][0]; - x2 ^= m_rkd[6][1]; + x1 ^= m_rkd[12]; + x2 ^= m_rkd[13]; Kuznyechik_F::ILS(x1, x2); - x1 ^= m_rkd[7][0]; - x2 ^= m_rkd[7][1]; + x1 ^= m_rkd[14]; + x2 ^= m_rkd[15]; Kuznyechik_F::ILS(x1, x2); - x1 ^= m_rkd[8][0]; - x2 ^= m_rkd[8][1]; + x1 ^= m_rkd[16]; + x2 ^= m_rkd[17]; x1 = Kuznyechik_F::ISI(x1); x2 = Kuznyechik_F::ISI(x2); - x1 ^= m_rkd[9][0]; - x2 ^= m_rkd[9][1]; + x1 ^= m_rkd[18]; + x2 ^= m_rkd[19]; store_le(out, x1, x2); diff -Nru botan3-3.7.1+dfsg/src/lib/block/kuznyechik/kuznyechik.h botan3-3.12.0+dfsg/src/lib/block/kuznyechik/kuznyechik.h --- botan3-3.7.1+dfsg/src/lib/block/kuznyechik/kuznyechik.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/kuznyechik/kuznyechik.h 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,7 @@ /* * Kuznyechik -* (C) 2012 Jack Lloyd +* (C) 2023 Richard Huveneers +* 2025 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -9,6 +10,7 @@ #define BOTAN_KUZNYECHIK_H_ #include +#include namespace Botan { @@ -27,13 +29,11 @@ std::unique_ptr new_object() const override { return std::make_unique(); } bool has_keying_material() const override; - ~Kuznyechik() override; private: void key_schedule(std::span key) override; - uint64_t m_rke[10][2]; - uint64_t m_rkd[10][2]; - bool m_has_keying_material; + secure_vector m_rke; + secure_vector m_rkd; }; } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/block/lion/lion.cpp botan3-3.12.0+dfsg/src/lib/block/lion/lion.cpp --- botan3-3.7.1+dfsg/src/lib/block/lion/lion.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/lion/lion.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,6 +8,7 @@ #include #include +#include #include namespace Botan { diff -Nru botan3-3.7.1+dfsg/src/lib/block/noekeon/noekeon.cpp botan3-3.12.0+dfsg/src/lib/block/noekeon/noekeon.cpp --- botan3-3.7.1+dfsg/src/lib/block/noekeon/noekeon.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/noekeon/noekeon.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,10 +7,13 @@ #include -#include #include #include +#if defined(BOTAN_HAS_CPUID) + #include +#endif + namespace Botan { namespace { @@ -57,7 +60,7 @@ A1 ^= ~(A2 | A3); A0 ^= A2 & A1; - uint32_t T = A3; + const uint32_t T = A3; A3 = A0; A0 = T; @@ -71,7 +74,7 @@ size_t Noekeon::parallelism() const { #if defined(BOTAN_HAS_NOEKEON_SIMD) - if(CPUID::has_simd_32()) { + if(CPUID::has(CPUID::Feature::SIMD_4X32)) { return 4; } #endif @@ -81,8 +84,8 @@ std::string Noekeon::provider() const { #if defined(BOTAN_HAS_NOEKEON_SIMD) - if(CPUID::has_simd_32()) { - return "simd"; + if(auto feat = CPUID::check(CPUID::Feature::SIMD_4X32)) { + return *feat; } #endif @@ -102,7 +105,7 @@ assert_key_material_set(); #if defined(BOTAN_HAS_NOEKEON_SIMD) - if(CPUID::has_simd_32()) { + if(CPUID::has(CPUID::Feature::SIMD_4X32)) { while(blocks >= 4) { simd_encrypt_4(in, out); in += 4 * BLOCK_SIZE; @@ -150,7 +153,7 @@ assert_key_material_set(); #if defined(BOTAN_HAS_NOEKEON_SIMD) - if(CPUID::has_simd_32()) { + if(CPUID::has(CPUID::Feature::SIMD_4X32)) { while(blocks >= 4) { simd_decrypt_4(in, out); in += 4 * BLOCK_SIZE; diff -Nru botan3-3.7.1+dfsg/src/lib/block/noekeon/noekeon.h botan3-3.12.0+dfsg/src/lib/block/noekeon/noekeon.h --- botan3-3.7.1+dfsg/src/lib/block/noekeon/noekeon.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/noekeon/noekeon.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,6 +9,7 @@ #define BOTAN_NOEKEON_H_ #include +#include namespace Botan { diff -Nru botan3-3.7.1+dfsg/src/lib/block/noekeon/noekeon_simd/info.txt botan3-3.12.0+dfsg/src/lib/block/noekeon/noekeon_simd/info.txt --- botan3-3.7.1+dfsg/src/lib/block/noekeon/noekeon_simd/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/noekeon/noekeon_simd/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + NOEKEON_SIMD -> 20160903 - + name -> "Noekeon SIMD" @@ -8,6 +8,18 @@ -noekeon -simd +cpuid +simd_4x32 + + +x86_32:ssse3 +x86_64:ssse3 +x32:ssse3 +arm32:neon +arm64:neon +ppc32:altivec +ppc64:altivec +loongarch64:lsx +wasm:simd128 + diff -Nru botan3-3.7.1+dfsg/src/lib/block/noekeon/noekeon_simd/noekeon_simd.cpp botan3-3.12.0+dfsg/src/lib/block/noekeon/noekeon_simd/noekeon_simd.cpp --- botan3-3.7.1+dfsg/src/lib/block/noekeon/noekeon_simd/noekeon_simd.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/noekeon/noekeon_simd/noekeon_simd.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,7 +7,7 @@ #include -#include +#include namespace Botan { @@ -16,14 +16,14 @@ /* * Noekeon's Theta Operation */ -inline void theta(SIMD_4x32& A0, - SIMD_4x32& A1, - SIMD_4x32& A2, - SIMD_4x32& A3, - const SIMD_4x32& K0, - const SIMD_4x32& K1, - const SIMD_4x32& K2, - const SIMD_4x32& K3) { +inline void BOTAN_FN_ISA_SIMD_4X32 theta(SIMD_4x32& A0, + SIMD_4x32& A1, + SIMD_4x32& A2, + SIMD_4x32& A3, + const SIMD_4x32& K0, + const SIMD_4x32& K1, + const SIMD_4x32& K2, + const SIMD_4x32& K3) { SIMD_4x32 T = A0 ^ A2; T ^= T.rotl<8>() ^ T.rotr<8>(); A1 ^= T; @@ -43,11 +43,11 @@ /* * Noekeon's Gamma S-Box Layer */ -inline void gamma(SIMD_4x32& A0, SIMD_4x32& A1, SIMD_4x32& A2, SIMD_4x32& A3) { +inline void BOTAN_FN_ISA_SIMD_4X32 gamma(SIMD_4x32& A0, SIMD_4x32& A1, SIMD_4x32& A2, SIMD_4x32& A3) { A1 ^= ~(A2 | A3); A0 ^= A2 & A1; - SIMD_4x32 T = A3; + const SIMD_4x32 T = A3; A3 = A0; A0 = T; @@ -62,7 +62,7 @@ /* * Noekeon Encryption */ -void Noekeon::simd_encrypt_4(const uint8_t in[], uint8_t out[]) const { +void BOTAN_FN_ISA_SIMD_4X32 Noekeon::simd_encrypt_4(const uint8_t in[], uint8_t out[]) const { const SIMD_4x32 K0 = SIMD_4x32::splat(m_EK[0]); const SIMD_4x32 K1 = SIMD_4x32::splat(m_EK[1]); const SIMD_4x32 K2 = SIMD_4x32::splat(m_EK[2]); @@ -105,7 +105,7 @@ /* * Noekeon Encryption */ -void Noekeon::simd_decrypt_4(const uint8_t in[], uint8_t out[]) const { +void BOTAN_FN_ISA_SIMD_4X32 Noekeon::simd_decrypt_4(const uint8_t in[], uint8_t out[]) const { const SIMD_4x32 K0 = SIMD_4x32::splat(m_DK[0]); const SIMD_4x32 K1 = SIMD_4x32::splat(m_DK[1]); const SIMD_4x32 K2 = SIMD_4x32::splat(m_DK[2]); diff -Nru botan3-3.7.1+dfsg/src/lib/block/seed/seed.cpp botan3-3.12.0+dfsg/src/lib/block/seed/seed.cpp --- botan3-3.7.1+dfsg/src/lib/block/seed/seed.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/seed/seed.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -10,6 +10,10 @@ #include #include +#if defined(BOTAN_HAS_CPUID) + #include +#endif + namespace Botan { namespace { @@ -51,7 +55,7 @@ /* * SEED G Function */ -inline uint32_t SEED_G(uint32_t X) { +BOTAN_FORCE_INLINE uint32_t SEED_G(uint32_t X) { const uint32_t M = 0x01010101; const uint32_t s0 = M * SEED_S0[get_byte<3>(X)]; const uint32_t s1 = M * SEED_S1[get_byte<2>(X)]; @@ -74,8 +78,66 @@ void SEED::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { assert_key_material_set(); +#if defined(BOTAN_HAS_SEED_AVX512_GFNI) + if(CPUID::has(CPUID::Feature::AVX512, CPUID::Feature::GFNI)) { + return avx512_gfni_encrypt(in, out, blocks); + } +#endif + +#if defined(BOTAN_HAS_SEED_HWAES) + if(CPUID::has(CPUID::Feature::HW_AES)) { + return hwaes_encrypt(in, out, blocks); + } +#endif + prefetch_arrays(SEED_S0, SEED_S1); + while(blocks >= 2) { + uint32_t B00 = load_be(in, 0); + uint32_t B01 = load_be(in, 1); + uint32_t B02 = load_be(in, 2); + uint32_t B03 = load_be(in, 3); + uint32_t B10 = load_be(in, 4); + uint32_t B11 = load_be(in, 5); + uint32_t B12 = load_be(in, 6); + uint32_t B13 = load_be(in, 7); + + for(size_t j = 0; j != 16; j += 2) { + uint32_t T00 = B02 ^ m_K[2 * j]; + uint32_t T10 = B12 ^ m_K[2 * j]; + uint32_t T01 = SEED_G(B02 ^ B03 ^ m_K[2 * j + 1]); + uint32_t T11 = SEED_G(B12 ^ B13 ^ m_K[2 * j + 1]); + T00 = SEED_G(T01 + T00); + T10 = SEED_G(T11 + T10); + T01 = SEED_G(T01 + T00); + T11 = SEED_G(T11 + T10); + B01 ^= T01; + B11 ^= T11; + B00 ^= T00 + T01; + B10 ^= T10 + T11; + + T00 = B00 ^ m_K[2 * j + 2]; + T10 = B10 ^ m_K[2 * j + 2]; + T01 = SEED_G(B00 ^ B01 ^ m_K[2 * j + 3]); + T11 = SEED_G(B10 ^ B11 ^ m_K[2 * j + 3]); + T10 = SEED_G(T11 + T10); + T00 = SEED_G(T01 + T00); + T01 = SEED_G(T01 + T00); + T11 = SEED_G(T11 + T10); + B03 ^= T01; + B13 ^= T11; + B02 ^= T00 + T01; + B12 ^= T10 + T11; + } + + store_be(out, B02, B03, B00, B01, B12, B13, B10, B11); + + in += 2 * BLOCK_SIZE; + out += 2 * BLOCK_SIZE; + + blocks -= 2; + } + for(size_t i = 0; i != blocks; ++i) { uint32_t B0 = load_be(in, 0); uint32_t B1 = load_be(in, 1); @@ -83,10 +145,8 @@ uint32_t B3 = load_be(in, 3); for(size_t j = 0; j != 16; j += 2) { - uint32_t T0, T1; - - T0 = B2 ^ m_K[2 * j]; - T1 = SEED_G(B2 ^ B3 ^ m_K[2 * j + 1]); + uint32_t T0 = B2 ^ m_K[2 * j]; + uint32_t T1 = SEED_G(B2 ^ B3 ^ m_K[2 * j + 1]); T0 = SEED_G(T1 + T0); T1 = SEED_G(T1 + T0); B1 ^= T1; @@ -113,8 +173,65 @@ void SEED::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { assert_key_material_set(); +#if defined(BOTAN_HAS_SEED_AVX512_GFNI) + if(CPUID::has(CPUID::Feature::AVX512, CPUID::Feature::GFNI)) { + return avx512_gfni_decrypt(in, out, blocks); + } +#endif + +#if defined(BOTAN_HAS_SEED_HWAES) + if(CPUID::has(CPUID::Feature::HW_AES)) { + return hwaes_decrypt(in, out, blocks); + } +#endif + prefetch_arrays(SEED_S0, SEED_S1); + while(blocks >= 2) { + uint32_t B00 = load_be(in, 0); + uint32_t B01 = load_be(in, 1); + uint32_t B02 = load_be(in, 2); + uint32_t B03 = load_be(in, 3); + uint32_t B10 = load_be(in, 4); + uint32_t B11 = load_be(in, 5); + uint32_t B12 = load_be(in, 6); + uint32_t B13 = load_be(in, 7); + + for(size_t j = 0; j != 16; j += 2) { + uint32_t T00 = B02 ^ m_K[30 - 2 * j]; + uint32_t T10 = B12 ^ m_K[30 - 2 * j]; + uint32_t T01 = SEED_G(B02 ^ B03 ^ m_K[31 - 2 * j]); + uint32_t T11 = SEED_G(B12 ^ B13 ^ m_K[31 - 2 * j]); + T00 = SEED_G(T01 + T00); + T10 = SEED_G(T11 + T10); + T01 = SEED_G(T01 + T00); + T11 = SEED_G(T11 + T10); + B01 ^= T01; + B11 ^= T11; + B00 ^= T00 + T01; + B10 ^= T10 + T11; + + T00 = B00 ^ m_K[28 - 2 * j]; + T10 = B10 ^ m_K[28 - 2 * j]; + T01 = SEED_G(B00 ^ B01 ^ m_K[29 - 2 * j]); + T11 = SEED_G(B10 ^ B11 ^ m_K[29 - 2 * j]); + T00 = SEED_G(T01 + T00); + T10 = SEED_G(T11 + T10); + T01 = SEED_G(T01 + T00); + T11 = SEED_G(T11 + T10); + B03 ^= T01; + B13 ^= T11; + B02 ^= T00 + T01; + B12 ^= T10 + T11; + } + + store_be(out, B02, B03, B00, B01, B12, B13, B10, B11); + + in += 2 * BLOCK_SIZE; + out += 2 * BLOCK_SIZE; + blocks -= 2; + } + for(size_t i = 0; i != blocks; ++i) { uint32_t B0 = load_be(in, 0); uint32_t B1 = load_be(in, 1); @@ -122,10 +239,8 @@ uint32_t B3 = load_be(in, 3); for(size_t j = 0; j != 16; j += 2) { - uint32_t T0, T1; - - T0 = B2 ^ m_K[30 - 2 * j]; - T1 = SEED_G(B2 ^ B3 ^ m_K[31 - 2 * j]); + uint32_t T0 = B2 ^ m_K[30 - 2 * j]; + uint32_t T1 = SEED_G(B2 ^ B3 ^ m_K[31 - 2 * j]); T0 = SEED_G(T1 + T0); T1 = SEED_G(T1 + T0); B1 ^= T1; @@ -200,4 +315,36 @@ zap(m_K); } +size_t SEED::parallelism() const { +#if defined(BOTAN_HAS_SEED_AVX512_GFNI) + if(CPUID::has(CPUID::Feature::AVX512, CPUID::Feature::GFNI)) { + return 16; + } +#endif + +#if defined(BOTAN_HAS_SEED_HWAES) + if(CPUID::has(CPUID::Feature::HW_AES)) { + return 4; + } +#endif + + return 1; +} + +std::string SEED::provider() const { +#if defined(BOTAN_HAS_SEED_AVX512_GFNI) + if(auto feat = CPUID::check(CPUID::Feature::AVX512, CPUID::Feature::GFNI)) { + return *feat; + } +#endif + +#if defined(BOTAN_HAS_SEED_HWAES) + if(auto feat = CPUID::check(CPUID::Feature::HW_AES)) { + return *feat; + } +#endif + + return "base"; +} + } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/block/seed/seed.h botan3-3.12.0+dfsg/src/lib/block/seed/seed.h --- botan3-3.7.1+dfsg/src/lib/block/seed/seed.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/seed/seed.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,6 +9,7 @@ #define BOTAN_SEED_H_ #include +#include namespace Botan { @@ -26,11 +27,23 @@ std::unique_ptr new_object() const override { return std::make_unique(); } + std::string provider() const override; + size_t parallelism() const override; bool has_keying_material() const override; private: void key_schedule(std::span key) override; +#if defined(BOTAN_HAS_SEED_AVX512_GFNI) + void avx512_gfni_encrypt(const uint8_t in[], uint8_t out[], size_t blocks) const; + void avx512_gfni_decrypt(const uint8_t in[], uint8_t out[], size_t blocks) const; +#endif + +#if defined(BOTAN_HAS_SEED_HWAES) + void hwaes_encrypt(const uint8_t in[], uint8_t out[], size_t blocks) const; + void hwaes_decrypt(const uint8_t in[], uint8_t out[], size_t blocks) const; +#endif + secure_vector m_K; }; diff -Nru botan3-3.7.1+dfsg/src/lib/block/seed/seed_avx512_gfni/info.txt botan3-3.12.0+dfsg/src/lib/block/seed/seed_avx512_gfni/info.txt --- botan3-3.7.1+dfsg/src/lib/block/seed/seed_avx512_gfni/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/seed/seed_avx512_gfni/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,17 @@ + +SEED_AVX512_GFNI -> 20260319 + + + +name -> "SEED AVX-512/GFNI" + + + +cpuid +simd_avx512 + + + +gfni +avx512 + diff -Nru botan3-3.7.1+dfsg/src/lib/block/seed/seed_avx512_gfni/seed_avx512_gfni.cpp botan3-3.12.0+dfsg/src/lib/block/seed/seed_avx512_gfni/seed_avx512_gfni.cpp --- botan3-3.7.1+dfsg/src/lib/block/seed/seed_avx512_gfni/seed_avx512_gfni.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/seed/seed_avx512_gfni/seed_avx512_gfni.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,331 @@ +/* +* (C) 2026 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include + +namespace Botan { + +namespace SEED_AVX512_GFNI { + +namespace { + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512_GFNI SIMD_16x32 seed_g(const SIMD_16x32& X) { + /* + * SEED's two sboxes are both based on inversions in GF(2^8) modulo the polynomial + * x^8+x^6+x^5+x+1 (0x163), followed by different affine transforms. + * + * GFNI uses AES's field (modulo 0x11B) so the pre-inversion matrix is a field isomorphism + * that maps the inputs into the AES field. The post-inversion matrices then apply map + * back to SEED's field and apply the appropriate linear transform. + */ + + // Field isomorphism from SEED's field to AES field + constexpr uint64_t seed_pre_a = gfni_matrix(R"( + 1 1 0 1 0 0 0 0 + 0 0 1 1 0 0 1 1 + 0 0 0 0 1 1 0 1 + 0 1 1 1 0 1 0 0 + 0 1 1 0 1 0 0 0 + 0 0 0 1 1 0 0 0 + 0 0 1 1 1 1 0 0 + 0 0 0 0 1 1 1 0 + )"); + + // Field isomorphism from AES->SEED multiplied by S0's affine matrix + constexpr uint64_t seed_s0_post_a = gfni_matrix(R"( + 0 1 0 1 1 0 0 1 + 0 0 1 1 1 0 1 0 + 1 0 0 0 1 1 1 0 + 1 1 0 0 1 0 0 1 + 0 1 0 1 1 0 1 1 + 1 1 1 1 1 0 1 1 + 0 0 1 1 0 1 0 1 + 0 0 0 1 0 1 1 1 + )"); + + // Field isomorphism from AES->SEED multiplied by S1's affine matrix + constexpr uint64_t seed_s1_post_a = gfni_matrix(R"( + 0 0 1 1 0 1 1 0 + 0 1 1 0 0 0 1 0 + 0 1 0 1 1 0 1 1 + 0 0 0 0 0 0 1 1 + 1 1 0 1 0 0 0 0 + 0 1 0 0 1 0 1 1 + 1 1 1 0 1 0 1 1 + 1 1 1 1 0 0 0 1 + )"); + + constexpr uint8_t seed_s0_post_c = 0xA9; + constexpr uint8_t seed_s1_post_c = 0x38; + + // Compute S0(x) and S1(x) for all bytes + const auto pre = gf2p8affine(X); + const auto s0 = gf2p8affineinv(pre); + const auto s1 = gf2p8affineinv(pre); + + // Blend S0/S1 outputs by alternating bytes + constexpr uint64_t blend_mask = 0xAAAAAAAAAAAAAAAA; // 0b1010.... + const auto sbox = SIMD_16x32(_mm512_mask_blend_epi8(blend_mask, s0.raw(), s1.raw())); + + // Linear mixing layer + const auto M0 = SIMD_16x32::splat(0x3FCFF3FC); + const auto M1 = SIMD_16x32::splat(0xFC3FCFF3); + const auto M2 = SIMD_16x32::splat(0xF3FC3FCF); + const auto M3 = SIMD_16x32::splat(0xCFF3FC3F); + + // Masks for broadcasting each byte across the 32 bit word that contains it + + // clang-format off + alignas(64) constexpr uint8_t SHUF_BYTE0[64] = { + 0, 0, 0, 0, 4, 4, 4, 4, 8, 8, 8, 8, 12, 12, 12, 12, + 0, 0, 0, 0, 4, 4, 4, 4, 8, 8, 8, 8, 12, 12, 12, 12, + 0, 0, 0, 0, 4, 4, 4, 4, 8, 8, 8, 8, 12, 12, 12, 12, + 0, 0, 0, 0, 4, 4, 4, 4, 8, 8, 8, 8, 12, 12, 12, 12, + }; + alignas(64) constexpr uint8_t SHUF_BYTE1[64] = { + 1, 1, 1, 1, 5, 5, 5, 5, 9, 9, 9, 9, 13, 13, 13, 13, + 1, 1, 1, 1, 5, 5, 5, 5, 9, 9, 9, 9, 13, 13, 13, 13, + 1, 1, 1, 1, 5, 5, 5, 5, 9, 9, 9, 9, 13, 13, 13, 13, + 1, 1, 1, 1, 5, 5, 5, 5, 9, 9, 9, 9, 13, 13, 13, 13, + }; + alignas(64) constexpr uint8_t SHUF_BYTE2[64] = { + 2, 2, 2, 2, 6, 6, 6, 6, 10, 10, 10, 10, 14, 14, 14, 14, + 2, 2, 2, 2, 6, 6, 6, 6, 10, 10, 10, 10, 14, 14, 14, 14, + 2, 2, 2, 2, 6, 6, 6, 6, 10, 10, 10, 10, 14, 14, 14, 14, + 2, 2, 2, 2, 6, 6, 6, 6, 10, 10, 10, 10, 14, 14, 14, 14, + }; + alignas(64) constexpr uint8_t SHUF_BYTE3[64] = { + 3, 3, 3, 3, 7, 7, 7, 7, 11, 11, 11, 11, 15, 15, 15, 15, + 3, 3, 3, 3, 7, 7, 7, 7, 11, 11, 11, 11, 15, 15, 15, 15, + 3, 3, 3, 3, 7, 7, 7, 7, 11, 11, 11, 11, 15, 15, 15, 15, + 3, 3, 3, 3, 7, 7, 7, 7, 11, 11, 11, 11, 15, 15, 15, 15, + }; + // clang-format on + + const auto b0 = SIMD_16x32(_mm512_shuffle_epi8(sbox.raw(), _mm512_load_si512(SHUF_BYTE0))); + const auto b1 = SIMD_16x32(_mm512_shuffle_epi8(sbox.raw(), _mm512_load_si512(SHUF_BYTE1))); + const auto b2 = SIMD_16x32(_mm512_shuffle_epi8(sbox.raw(), _mm512_load_si512(SHUF_BYTE2))); + const auto b3 = SIMD_16x32(_mm512_shuffle_epi8(sbox.raw(), _mm512_load_si512(SHUF_BYTE3))); + + // Return (b0 & M0) ^ (b1 & M1) ^ (b2 & M2) ^ (b3 & M3) + // ternlogd 0x78 is a ^ (b & c) + auto result = SIMD_16x32(b0) & M0; + result = SIMD_16x32::ternary_fn<0x78>(result, b1, M1); + result = SIMD_16x32::ternary_fn<0x78>(result, b2, M2); + result = SIMD_16x32::ternary_fn<0x78>(result, b3, M3); + + return SIMD_16x32(result); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512_GFNI void seed_round( + SIMD_16x32& B0, SIMD_16x32& B1, SIMD_16x32& B2, SIMD_16x32& B3, uint32_t K0, uint32_t K1, uint32_t K2, uint32_t K3) { + auto T0 = B2 ^ SIMD_16x32::splat(K0); + auto T1 = seed_g(B2 ^ B3 ^ SIMD_16x32::splat(K1)); + T0 = seed_g(T1 + T0); + T1 = seed_g(T1 + T0); + B1 ^= T1; + B0 ^= T0 + T1; + + T0 = B0 ^ SIMD_16x32::splat(K2); + T1 = seed_g(B0 ^ B1 ^ SIMD_16x32::splat(K3)); + T0 = seed_g(T1 + T0); + T1 = seed_g(T1 + T0); + B3 ^= T1; + B2 ^= T0 + T1; +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512_GFNI void encrypt(const uint8_t ptext[16 * 4 * 4], + uint8_t ctext[16 * 4 * 4], + std::span RK) { + SIMD_16x32 B0 = SIMD_16x32::load_be(ptext + 16 * 4 * 0); + SIMD_16x32 B1 = SIMD_16x32::load_be(ptext + 16 * 4 * 1); + SIMD_16x32 B2 = SIMD_16x32::load_be(ptext + 16 * 4 * 2); + SIMD_16x32 B3 = SIMD_16x32::load_be(ptext + 16 * 4 * 3); + + SIMD_16x32::transpose(B0, B1, B2, B3); + + for(size_t j = 0; j != 8; ++j) { + const uint32_t K0 = RK[4 * j]; + const uint32_t K1 = RK[4 * j + 1]; + const uint32_t K2 = RK[4 * j + 2]; + const uint32_t K3 = RK[4 * j + 3]; + + seed_round(B0, B1, B2, B3, K0, K1, K2, K3); + } + + // Output order is B2, B3, B0, B1 + SIMD_16x32::transpose(B2, B3, B0, B1); + B2.store_be(ctext + 16 * 4 * 0); + B3.store_be(ctext + 16 * 4 * 1); + B0.store_be(ctext + 16 * 4 * 2); + B1.store_be(ctext + 16 * 4 * 3); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512_GFNI void decrypt(const uint8_t ctext[16 * 4 * 4], + uint8_t ptext[16 * 4 * 4], + std::span RK) { + SIMD_16x32 B0 = SIMD_16x32::load_be(ctext + 16 * 4 * 0); + SIMD_16x32 B1 = SIMD_16x32::load_be(ctext + 16 * 4 * 1); + SIMD_16x32 B2 = SIMD_16x32::load_be(ctext + 16 * 4 * 2); + SIMD_16x32 B3 = SIMD_16x32::load_be(ctext + 16 * 4 * 3); + + SIMD_16x32::transpose(B0, B1, B2, B3); + + for(size_t j = 0; j != 8; ++j) { + const uint32_t K0 = RK[30 - 4 * j]; + const uint32_t K1 = RK[31 - 4 * j]; + const uint32_t K2 = RK[28 - 4 * j]; + const uint32_t K3 = RK[29 - 4 * j]; + + seed_round(B0, B1, B2, B3, K0, K1, K2, K3); + } + + SIMD_16x32::transpose(B2, B3, B0, B1); + B2.store_be(ptext + 16 * 4 * 0); + B3.store_be(ptext + 16 * 4 * 1); + B0.store_be(ptext + 16 * 4 * 2); + B1.store_be(ptext + 16 * 4 * 3); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512_GFNI void encrypt_x2(const uint8_t ptext[32 * 4 * 4], + uint8_t ctext[32 * 4 * 4], + std::span RK) { + SIMD_16x32 B0 = SIMD_16x32::load_be(ptext + 16 * 4 * 0); + SIMD_16x32 B1 = SIMD_16x32::load_be(ptext + 16 * 4 * 1); + SIMD_16x32 B2 = SIMD_16x32::load_be(ptext + 16 * 4 * 2); + SIMD_16x32 B3 = SIMD_16x32::load_be(ptext + 16 * 4 * 3); + + SIMD_16x32 B4 = SIMD_16x32::load_be(ptext + 16 * 4 * 4); + SIMD_16x32 B5 = SIMD_16x32::load_be(ptext + 16 * 4 * 5); + SIMD_16x32 B6 = SIMD_16x32::load_be(ptext + 16 * 4 * 6); + SIMD_16x32 B7 = SIMD_16x32::load_be(ptext + 16 * 4 * 7); + + SIMD_16x32::transpose(B0, B1, B2, B3); + SIMD_16x32::transpose(B4, B5, B6, B7); + + for(size_t j = 0; j != 8; ++j) { + const uint32_t K0 = RK[4 * j]; + const uint32_t K1 = RK[4 * j + 1]; + const uint32_t K2 = RK[4 * j + 2]; + const uint32_t K3 = RK[4 * j + 3]; + + seed_round(B0, B1, B2, B3, K0, K1, K2, K3); + seed_round(B4, B5, B6, B7, K0, K1, K2, K3); + } + + SIMD_16x32::transpose(B2, B3, B0, B1); + SIMD_16x32::transpose(B6, B7, B4, B5); + + B2.store_be(ctext + 16 * 4 * 0); + B3.store_be(ctext + 16 * 4 * 1); + B0.store_be(ctext + 16 * 4 * 2); + B1.store_be(ctext + 16 * 4 * 3); + + B6.store_be(ctext + 16 * 4 * 4); + B7.store_be(ctext + 16 * 4 * 5); + B4.store_be(ctext + 16 * 4 * 6); + B5.store_be(ctext + 16 * 4 * 7); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512_GFNI void decrypt_x2(const uint8_t ctext[32 * 4 * 4], + uint8_t ptext[32 * 4 * 4], + std::span RK) { + SIMD_16x32 B0 = SIMD_16x32::load_be(ctext + 16 * 4 * 0); + SIMD_16x32 B1 = SIMD_16x32::load_be(ctext + 16 * 4 * 1); + SIMD_16x32 B2 = SIMD_16x32::load_be(ctext + 16 * 4 * 2); + SIMD_16x32 B3 = SIMD_16x32::load_be(ctext + 16 * 4 * 3); + + SIMD_16x32 B4 = SIMD_16x32::load_be(ctext + 16 * 4 * 4); + SIMD_16x32 B5 = SIMD_16x32::load_be(ctext + 16 * 4 * 5); + SIMD_16x32 B6 = SIMD_16x32::load_be(ctext + 16 * 4 * 6); + SIMD_16x32 B7 = SIMD_16x32::load_be(ctext + 16 * 4 * 7); + + SIMD_16x32::transpose(B0, B1, B2, B3); + SIMD_16x32::transpose(B4, B5, B6, B7); + + for(size_t j = 0; j != 8; ++j) { + const uint32_t K0 = RK[30 - 4 * j]; + const uint32_t K1 = RK[31 - 4 * j]; + const uint32_t K2 = RK[28 - 4 * j]; + const uint32_t K3 = RK[29 - 4 * j]; + + seed_round(B0, B1, B2, B3, K0, K1, K2, K3); + seed_round(B4, B5, B6, B7, K0, K1, K2, K3); + } + + SIMD_16x32::transpose(B2, B3, B0, B1); + SIMD_16x32::transpose(B6, B7, B4, B5); + + B2.store_be(ptext + 16 * 4 * 0); + B3.store_be(ptext + 16 * 4 * 1); + B0.store_be(ptext + 16 * 4 * 2); + B1.store_be(ptext + 16 * 4 * 3); + + B6.store_be(ptext + 16 * 4 * 4); + B7.store_be(ptext + 16 * 4 * 5); + B4.store_be(ptext + 16 * 4 * 6); + B5.store_be(ptext + 16 * 4 * 7); +} + +} // namespace + +} // namespace SEED_AVX512_GFNI + +void BOTAN_FN_ISA_AVX512_GFNI SEED::avx512_gfni_encrypt(const uint8_t ptext[], uint8_t ctext[], size_t blocks) const { + while(blocks >= 32) { + SEED_AVX512_GFNI::encrypt_x2(ptext, ctext, m_K); + ptext += 16 * 32; + ctext += 16 * 32; + blocks -= 32; + } + + while(blocks >= 16) { + SEED_AVX512_GFNI::encrypt(ptext, ctext, m_K); + ptext += 16 * 16; + ctext += 16 * 16; + blocks -= 16; + } + + if(blocks > 0) { + BOTAN_ASSERT_NOMSG(blocks < 16); + uint8_t pbuf[16 * 16] = {0}; + uint8_t cbuf[16 * 16] = {0}; + copy_mem(pbuf, ptext, blocks * 16); + SEED_AVX512_GFNI::encrypt(pbuf, cbuf, m_K); + copy_mem(ctext, cbuf, blocks * 16); + } +} + +void BOTAN_FN_ISA_AVX512_GFNI SEED::avx512_gfni_decrypt(const uint8_t ctext[], uint8_t ptext[], size_t blocks) const { + while(blocks >= 32) { + SEED_AVX512_GFNI::decrypt_x2(ctext, ptext, m_K); + ptext += 16 * 32; + ctext += 16 * 32; + blocks -= 32; + } + + while(blocks >= 16) { + SEED_AVX512_GFNI::decrypt(ctext, ptext, m_K); + ptext += 16 * 16; + ctext += 16 * 16; + blocks -= 16; + } + + if(blocks > 0) { + BOTAN_ASSERT_NOMSG(blocks < 16); + uint8_t pbuf[16 * 16] = {0}; + uint8_t cbuf[16 * 16] = {0}; + copy_mem(cbuf, ctext, blocks * 16); + SEED_AVX512_GFNI::decrypt(cbuf, pbuf, m_K); + copy_mem(ptext, pbuf, blocks * 16); + } +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/block/seed/seed_hwaes/info.txt botan3-3.12.0+dfsg/src/lib/block/seed/seed_hwaes/info.txt --- botan3-3.7.1+dfsg/src/lib/block/seed/seed_hwaes/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/seed/seed_hwaes/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,12 @@ + +SEED_HWAES -> 20260322 + + + +name -> "SEED using hardware AES instructions" + + + +cpuid +simd_hwaes + diff -Nru botan3-3.7.1+dfsg/src/lib/block/seed/seed_hwaes/seed_hwaes.cpp botan3-3.12.0+dfsg/src/lib/block/seed/seed_hwaes/seed_hwaes.cpp --- botan3-3.7.1+dfsg/src/lib/block/seed/seed_hwaes/seed_hwaes.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/seed/seed_hwaes/seed_hwaes.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,196 @@ +/* +* (C) 2026 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include + +namespace Botan { + +namespace SEED_HWAES { + +namespace { + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_HWAES SIMD_4x32 seed_g(SIMD_4x32 X) { + // Field isomorphism from SEED's field (0x163) to AES field (0x11B) + constexpr uint64_t pre_a = gfni_matrix(R"( + 1 1 0 1 0 0 0 0 + 0 0 1 1 0 0 1 1 + 0 0 0 0 1 1 0 1 + 0 1 1 1 0 1 0 0 + 0 1 1 0 1 0 0 0 + 0 0 0 1 1 0 0 0 + 0 0 1 1 1 1 0 0 + 0 0 0 0 1 1 1 0)"); + + // AES->SEED field isomorphism composed with S0's affine + constexpr uint64_t s0_post_a = gfni_matrix(R"( + 0 1 0 1 1 0 0 1 + 0 0 1 1 1 0 1 0 + 1 0 0 0 1 1 1 0 + 1 1 0 0 1 0 0 1 + 0 1 0 1 1 0 1 1 + 1 1 1 1 1 0 1 1 + 0 0 1 1 0 1 0 1 + 0 0 0 1 0 1 1 1)"); + constexpr uint8_t s0_post_c = 0xA9; + + // AES->SEED field isomorphism composed with S1's affine + constexpr uint64_t s1_post_a = gfni_matrix(R"( + 0 0 1 1 0 1 1 0 + 0 1 1 0 0 0 1 0 + 0 1 0 1 1 0 1 1 + 0 0 0 0 0 0 1 1 + 1 1 0 1 0 0 0 0 + 0 1 0 0 1 0 1 1 + 1 1 1 0 1 0 1 1 + 1 1 1 1 0 0 0 1)"); + constexpr uint8_t s1_post_c = 0x38; + + constexpr auto pre = Gf2AffineTransformation(pre_a, 0x00); + constexpr auto post_s0 = Gf2AffineTransformation::post_sbox(s0_post_a, s0_post_c); + constexpr auto post_s1 = Gf2AffineTransformation::post_sbox(s1_post_a, s1_post_c); + + // Shared computation for S0(x) and S1(x) + const auto sub = hw_aes_sbox(pre.affine_transform(X)); + + // Compute S0(x) and S1(x) + const auto s0 = post_s0.affine_transform(sub); + const auto s1 = post_s1.affine_transform(sub); + + // Blend S0(x) and S1(x) outputs in alternating bytes + const auto sbox = SIMD_4x32::byte_blend(0x00FF00FF, s0, s1); + + // Linear mixing step + const auto M0 = SIMD_4x32::splat(0x3FCFF3FC); + const auto M1 = SIMD_4x32::splat(0xFC3FCFF3); + const auto M2 = SIMD_4x32::splat(0xF3FC3FCF); + const auto M3 = SIMD_4x32::splat(0xCFF3FC3F); + + // Broadcast each byte of a 32-bit word to all 4 positions + const auto SHUF0 = SIMD_4x32(0x00000000, 0x04040404, 0x08080808, 0x0C0C0C0C); + const auto SHUF1 = SIMD_4x32(0x01010101, 0x05050505, 0x09090909, 0x0D0D0D0D); + const auto SHUF2 = SIMD_4x32(0x02020202, 0x06060606, 0x0A0A0A0A, 0x0E0E0E0E); + const auto SHUF3 = SIMD_4x32(0x03030303, 0x07070707, 0x0B0B0B0B, 0x0F0F0F0F); + + auto b0 = SIMD_4x32::byte_shuffle(sbox, SHUF0); + auto b1 = SIMD_4x32::byte_shuffle(sbox, SHUF1); + auto b2 = SIMD_4x32::byte_shuffle(sbox, SHUF2); + auto b3 = SIMD_4x32::byte_shuffle(sbox, SHUF3); + + return (b0 & M0) ^ (b1 & M1) ^ (b2 & M2) ^ (b3 & M3); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_HWAES void seed_round( + SIMD_4x32& B0, SIMD_4x32& B1, SIMD_4x32& B2, SIMD_4x32& B3, uint32_t K0, uint32_t K1, uint32_t K2, uint32_t K3) { + auto T0 = B2 ^ SIMD_4x32::splat(K0); + auto T1 = seed_g(B2 ^ B3 ^ SIMD_4x32::splat(K1)); + T0 = seed_g(T1 + T0); + T1 = seed_g(T1 + T0); + B1 ^= T1; + B0 ^= T0 + T1; + + T0 = B0 ^ SIMD_4x32::splat(K2); + T1 = seed_g(B0 ^ B1 ^ SIMD_4x32::splat(K3)); + T0 = seed_g(T1 + T0); + T1 = seed_g(T1 + T0); + B3 ^= T1; + B2 ^= T0 + T1; +} + +BOTAN_FN_ISA_HWAES void encrypt_4(const uint8_t ptext[4 * 16], uint8_t ctext[4 * 16], std::span RK) { + auto B0 = SIMD_4x32::load_be(ptext); + auto B1 = SIMD_4x32::load_be(ptext + 16); + auto B2 = SIMD_4x32::load_be(ptext + 32); + auto B3 = SIMD_4x32::load_be(ptext + 48); + + SIMD_4x32::transpose(B0, B1, B2, B3); + + for(size_t j = 0; j != 8; ++j) { + const uint32_t K0 = RK[4 * j]; + const uint32_t K1 = RK[4 * j + 1]; + const uint32_t K2 = RK[4 * j + 2]; + const uint32_t K3 = RK[4 * j + 3]; + + seed_round(B0, B1, B2, B3, K0, K1, K2, K3); + } + + // Output order: B2, B3, B0, B1 + SIMD_4x32::transpose(B2, B3, B0, B1); + + B2.store_be(ctext); + B3.store_be(ctext + 16); + B0.store_be(ctext + 32); + B1.store_be(ctext + 48); +} + +BOTAN_FN_ISA_HWAES void decrypt_4(const uint8_t ctext[4 * 16], uint8_t ptext[4 * 16], std::span RK) { + auto B0 = SIMD_4x32::load_be(ctext); + auto B1 = SIMD_4x32::load_be(ctext + 16); + auto B2 = SIMD_4x32::load_be(ctext + 32); + auto B3 = SIMD_4x32::load_be(ctext + 48); + + SIMD_4x32::transpose(B0, B1, B2, B3); + + for(size_t j = 0; j != 8; ++j) { + const uint32_t K0 = RK[30 - 4 * j]; + const uint32_t K1 = RK[31 - 4 * j]; + const uint32_t K2 = RK[28 - 4 * j]; + const uint32_t K3 = RK[29 - 4 * j]; + + seed_round(B0, B1, B2, B3, K0, K1, K2, K3); + } + + SIMD_4x32::transpose(B2, B3, B0, B1); + + B2.store_be(ptext); + B3.store_be(ptext + 16); + B0.store_be(ptext + 32); + B1.store_be(ptext + 48); +} + +} // namespace + +} // namespace SEED_HWAES + +void BOTAN_FN_ISA_HWAES SEED::hwaes_encrypt(const uint8_t ptext[], uint8_t ctext[], size_t blocks) const { + while(blocks >= 4) { + SEED_HWAES::encrypt_4(ptext, ctext, m_K); + ptext += 4 * 16; + ctext += 4 * 16; + blocks -= 4; + } + + if(blocks > 0) { + uint8_t pbuf[4 * 16] = {0}; + uint8_t cbuf[4 * 16] = {0}; + copy_mem(pbuf, ptext, blocks * 16); + SEED_HWAES::encrypt_4(pbuf, cbuf, m_K); + copy_mem(ctext, cbuf, blocks * 16); + } +} + +void BOTAN_FN_ISA_HWAES SEED::hwaes_decrypt(const uint8_t ctext[], uint8_t ptext[], size_t blocks) const { + while(blocks >= 4) { + SEED_HWAES::decrypt_4(ctext, ptext, m_K); + ptext += 4 * 16; + ctext += 4 * 16; + blocks -= 4; + } + + if(blocks > 0) { + uint8_t cbuf[4 * 16] = {0}; + uint8_t pbuf[4 * 16] = {0}; + copy_mem(cbuf, ctext, blocks * 16); + SEED_HWAES::decrypt_4(cbuf, pbuf, m_K); + copy_mem(ptext, pbuf, blocks * 16); + } +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/block/serpent/serpent.cpp botan3-3.12.0+dfsg/src/lib/block/serpent/serpent.cpp --- botan3-3.7.1+dfsg/src/lib/block/serpent/serpent.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/serpent/serpent.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -11,7 +11,7 @@ #include #include -#if defined(BOTAN_HAS_SERPENT_SIMD) || defined(BOTAN_HAS_SERPENT_AVX2) || defined(BOTAN_HAS_SERPENT_AVX512) +#if defined(BOTAN_HAS_CPUID) #include #endif @@ -26,7 +26,7 @@ assert_key_material_set(); #if defined(BOTAN_HAS_SERPENT_AVX512) - if(CPUID::has_avx512()) { + if(CPUID::has(CPUID::Feature::AVX512)) { while(blocks >= 16) { avx512_encrypt_16(in, out); in += 16 * BLOCK_SIZE; @@ -37,7 +37,7 @@ #endif #if defined(BOTAN_HAS_SERPENT_AVX2) - if(CPUID::has_avx2()) { + if(CPUID::has(CPUID::Feature::AVX2)) { while(blocks >= 8) { avx2_encrypt_8(in, out); in += 8 * BLOCK_SIZE; @@ -48,7 +48,7 @@ #endif #if defined(BOTAN_HAS_SERPENT_SIMD) - if(CPUID::has_simd_32()) { + if(CPUID::has(CPUID::Feature::SIMD_4X32)) { while(blocks >= 4) { simd_encrypt_4(in, out); in += 4 * BLOCK_SIZE; @@ -61,7 +61,10 @@ const Key_Inserter key_xor(m_round_key.data()); for(size_t i = 0; i < blocks; ++i) { - uint32_t B0, B1, B2, B3; + uint32_t B0 = 0; + uint32_t B1 = 0; + uint32_t B2 = 0; + uint32_t B3 = 0; load_le(in + 16 * i, B0, B1, B2, B3); key_xor(0, B0, B1, B2, B3); @@ -174,7 +177,7 @@ assert_key_material_set(); #if defined(BOTAN_HAS_SERPENT_AVX512) - if(CPUID::has_avx512()) { + if(CPUID::has(CPUID::Feature::AVX512)) { while(blocks >= 16) { avx512_decrypt_16(in, out); in += 16 * BLOCK_SIZE; @@ -185,7 +188,7 @@ #endif #if defined(BOTAN_HAS_SERPENT_AVX2) - if(CPUID::has_avx2()) { + if(CPUID::has(CPUID::Feature::AVX2)) { while(blocks >= 8) { avx2_decrypt_8(in, out); in += 8 * BLOCK_SIZE; @@ -196,7 +199,7 @@ #endif #if defined(BOTAN_HAS_SERPENT_SIMD) - if(CPUID::has_simd_32()) { + if(CPUID::has(CPUID::Feature::SIMD_4X32)) { while(blocks >= 4) { simd_decrypt_4(in, out); in += 4 * BLOCK_SIZE; @@ -209,7 +212,10 @@ const Key_Inserter key_xor(m_round_key.data()); for(size_t i = 0; i < blocks; ++i) { - uint32_t B0, B1, B2, B3; + uint32_t B0 = 0; + uint32_t B1 = 0; + uint32_t B2 = 0; + uint32_t B3 = 0; load_le(in + 16 * i, B0, B1, B2, B3); key_xor(32, B0, B1, B2, B3); @@ -333,7 +339,7 @@ W[key.size() / 4] |= uint32_t(1) << ((key.size() % 4) * 8); for(size_t i = 8; i != 140; ++i) { - uint32_t wi = W[i - 8] ^ W[i - 5] ^ W[i - 3] ^ W[i - 1] ^ PHI ^ uint32_t(i - 8); + const uint32_t wi = W[i - 8] ^ W[i - 5] ^ W[i - 3] ^ W[i - 1] ^ PHI ^ uint32_t(i - 8); W[i] = rotl<11>(wi); } @@ -387,20 +393,20 @@ std::string Serpent::provider() const { #if defined(BOTAN_HAS_SERPENT_AVX512) - if(CPUID::has_avx512()) { - return "avx512"; + if(auto feat = CPUID::check(CPUID::Feature::AVX512)) { + return *feat; } #endif #if defined(BOTAN_HAS_SERPENT_AVX2) - if(CPUID::has_avx2()) { - return "avx2"; + if(auto feat = CPUID::check(CPUID::Feature::AVX2)) { + return *feat; } #endif #if defined(BOTAN_HAS_SERPENT_SIMD) - if(CPUID::has_simd_32()) { - return "simd"; + if(auto feat = CPUID::check(CPUID::Feature::SIMD_4X32)) { + return *feat; } #endif diff -Nru botan3-3.7.1+dfsg/src/lib/block/serpent/serpent.h botan3-3.12.0+dfsg/src/lib/block/serpent/serpent.h --- botan3-3.7.1+dfsg/src/lib/block/serpent/serpent.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/serpent/serpent.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,6 +9,7 @@ #define BOTAN_SERPENT_H_ #include +#include namespace Botan { diff -Nru botan3-3.7.1+dfsg/src/lib/block/serpent/serpent_avx2/info.txt botan3-3.12.0+dfsg/src/lib/block/serpent/serpent_avx2/info.txt --- botan3-3.7.1+dfsg/src/lib/block/serpent/serpent_avx2/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/serpent/serpent_avx2/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + SERPENT_AVX2 -> 20180824 - + name -> "Serpent AVX2" @@ -12,10 +12,6 @@ +cpuid simd_avx2 - -# MSVC 2019 miscompiles this code (see #2120) - -!msvc - diff -Nru botan3-3.7.1+dfsg/src/lib/block/serpent/serpent_avx2/serpent_avx2.cpp botan3-3.12.0+dfsg/src/lib/block/serpent/serpent_avx2/serpent_avx2.cpp --- botan3-3.7.1+dfsg/src/lib/block/serpent/serpent_avx2/serpent_avx2.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/serpent/serpent_avx2/serpent_avx2.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -11,7 +11,8 @@ namespace Botan { -#if defined(__GNUG__) && !defined(__clang__) +// TODO(Botan4) if minimum GCC is increased we can remove this +#if defined(__GNUG__) && !defined(__clang__) && (__GNUG__ < 13) // These macros are redundant with the versions in serpent_sbox.h // but unfortunately removing them seems to trigger a bug in GCC @@ -47,8 +48,7 @@ #endif -BOTAN_AVX2_FN -void Serpent::avx2_encrypt_8(const uint8_t in[128], uint8_t out[128]) const { +void BOTAN_FN_ISA_AVX2 Serpent::avx2_encrypt_8(const uint8_t in[128], uint8_t out[128]) const { using namespace Botan::Serpent_F; SIMD_8x32::reset_registers(); @@ -171,8 +171,7 @@ SIMD_8x32::zero_registers(); } -BOTAN_AVX2_FN -void Serpent::avx2_decrypt_8(const uint8_t in[128], uint8_t out[128]) const { +void BOTAN_FN_ISA_AVX2 Serpent::avx2_decrypt_8(const uint8_t in[128], uint8_t out[128]) const { using namespace Botan::Serpent_F; SIMD_8x32::reset_registers(); @@ -296,6 +295,7 @@ SIMD_8x32::zero_registers(); } +// TODO(Botan4) remove when compiler hack above is removed #undef transform #undef i_transform diff -Nru botan3-3.7.1+dfsg/src/lib/block/serpent/serpent_avx512/info.txt botan3-3.12.0+dfsg/src/lib/block/serpent/serpent_avx512/info.txt --- botan3-3.7.1+dfsg/src/lib/block/serpent/serpent_avx512/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/serpent/serpent_avx512/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + SERPENT_AVX512 -> 20230101 - + name -> "Serpent AVX512" @@ -12,10 +12,11 @@ +cpuid simd_avx512 -# MSVC miscompiles this code +# MSVC miscompiles this code !msvc diff -Nru botan3-3.7.1+dfsg/src/lib/block/serpent/serpent_avx512/serpent_avx512.cpp botan3-3.12.0+dfsg/src/lib/block/serpent/serpent_avx512/serpent_avx512.cpp --- botan3-3.7.1+dfsg/src/lib/block/serpent/serpent_avx512/serpent_avx512.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/serpent/serpent_avx512/serpent_avx512.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -5,11 +5,49 @@ */ #include -#include + +#include #include namespace Botan { +// TODO(Botan4) if minimum GCC is increased we can remove this +#if defined(__GNUG__) && !defined(__clang__) && (__GNUG__ < 14) + +// These macros are redundant with the versions in serpent_sbox.h +// but unfortunately removing them seems to trigger a bug in GCC +// when building in amalgamation mode + + #define transform(B0, B1, B2, B3) \ + do { \ + B0 = B0.rotl<13>(); \ + B2 = B2.rotl<3>(); \ + B1 ^= B0 ^ B2; \ + B3 ^= B2 ^ B0.shl<3>(); \ + B1 = B1.rotl<1>(); \ + B3 = B3.rotl<7>(); \ + B0 ^= B1 ^ B3; \ + B2 ^= B3 ^ B1.shl<7>(); \ + B0 = B0.rotl<5>(); \ + B2 = B2.rotl<22>(); \ + } while(0) + + #define i_transform(B0, B1, B2, B3) \ + do { \ + B2 = B2.rotr<22>(); \ + B0 = B0.rotr<5>(); \ + B2 ^= B3 ^ B1.shl<7>(); \ + B0 ^= B1 ^ B3; \ + B3 = B3.rotr<7>(); \ + B1 = B1.rotr<1>(); \ + B3 ^= B2 ^ B0.shl<3>(); \ + B1 ^= B0 ^ B2; \ + B2 = B2.rotr<3>(); \ + B0 = B0.rotr<13>(); \ + } while(0) + +#endif + namespace { BOTAN_FORCE_INLINE void SBoxE0(SIMD_16x32& a, SIMD_16x32& b, SIMD_16x32& c, SIMD_16x32& d) { @@ -265,8 +303,7 @@ } // namespace -BOTAN_AVX512_FN -void Serpent::avx512_encrypt_16(const uint8_t in[16 * 16], uint8_t out[16 * 16]) const { +void BOTAN_FN_ISA_AVX512 Serpent::avx512_encrypt_16(const uint8_t in[16 * 16], uint8_t out[16 * 16]) const { using namespace Botan::Serpent_F; SIMD_16x32 B0 = SIMD_16x32::load_le(in); @@ -387,8 +424,7 @@ SIMD_16x32::zero_registers(); } -BOTAN_AVX512_FN -void Serpent::avx512_decrypt_16(const uint8_t in[16 * 16], uint8_t out[16 * 16]) const { +void BOTAN_FN_ISA_AVX512 Serpent::avx512_decrypt_16(const uint8_t in[16 * 16], uint8_t out[16 * 16]) const { using namespace Botan::Serpent_F; SIMD_16x32 B0 = SIMD_16x32::load_le(in); @@ -510,4 +546,8 @@ SIMD_16x32::zero_registers(); } +// TODO(Botan4) remove when compiler hack above is removed +#undef transform +#undef i_transform + } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/block/serpent/serpent_fn.h botan3-3.12.0+dfsg/src/lib/block/serpent/serpent_fn.h --- botan3-3.7.1+dfsg/src/lib/block/serpent/serpent_fn.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/serpent/serpent_fn.h 2026-05-07 01:38:28.000000000 +0000 @@ -10,9 +10,19 @@ #include #include #include +#include namespace Botan::Serpent_F { +// Concept for types that support bitwise operations (unsigned integers or SIMD types) +template +concept BitsliceT = requires(T& a, const T& b) { + a ^= b; + a &= b; + a |= b; + ~a; +}; + template BOTAN_FORCE_INLINE uint32_t shl(uint32_t v) { return v << S; @@ -21,7 +31,7 @@ /* * Serpent's Linear Transform */ -template +template BOTAN_FORCE_INLINE void transform(T& B0, T& B1, T& B2, T& B3) { B0 = rotl<13>(B0); B2 = rotl<3>(B2); @@ -38,7 +48,7 @@ /* * Serpent's Inverse Linear Transform */ -template +template BOTAN_FORCE_INLINE void i_transform(T& B0, T& B1, T& B2, T& B3) { B2 = rotr<22>(B2); B0 = rotr<5>(B0); @@ -54,10 +64,10 @@ class Key_Inserter final { public: - Key_Inserter(const uint32_t* RK) : m_RK(RK) {} + explicit Key_Inserter(const uint32_t* RK) : m_RK(RK) {} - template - inline void operator()(size_t R, T& B0, T& B1, T& B2, T& B3) const { + template + BOTAN_FORCE_INLINE void operator()(size_t R, T& B0, T& B1, T& B2, T& B3) const { B0 ^= m_RK[4 * R]; B1 ^= m_RK[4 * R + 1]; B2 ^= m_RK[4 * R + 2]; diff -Nru botan3-3.7.1+dfsg/src/lib/block/serpent/serpent_sbox.h botan3-3.12.0+dfsg/src/lib/block/serpent/serpent_sbox.h --- botan3-3.7.1+dfsg/src/lib/block/serpent/serpent_sbox.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/serpent/serpent_sbox.h 2026-05-07 01:38:28.000000000 +0000 @@ -15,7 +15,7 @@ namespace Botan::Serpent_F { -template +template BOTAN_FORCE_INLINE void SBoxE0(T& a, T& b, T& c, T& d) { d ^= a; T t0 = b; @@ -40,7 +40,7 @@ b = t0; } -template +template BOTAN_FORCE_INLINE void SBoxE1(T& a, T& b, T& c, T& d) { a = ~a; c = ~c; @@ -66,7 +66,7 @@ b = t0; } -template +template BOTAN_FORCE_INLINE void SBoxE2(T& a, T& b, T& c, T& d) { T t0 = a; a &= c; @@ -89,7 +89,7 @@ d = ~t0; } -template +template BOTAN_FORCE_INLINE void SBoxE3(T& a, T& b, T& c, T& d) { T t0 = a; a |= d; @@ -115,7 +115,7 @@ d = t0; } -template +template BOTAN_FORCE_INLINE void SBoxE4(T& a, T& b, T& c, T& d) { b ^= d; d = ~d; @@ -142,7 +142,7 @@ b = t0; } -template +template BOTAN_FORCE_INLINE void SBoxE5(T& a, T& b, T& c, T& d) { a ^= b; b ^= d; @@ -169,7 +169,7 @@ d = t0; } -template +template BOTAN_FORCE_INLINE void SBoxE6(T& a, T& b, T& c, T& d) { c = ~c; T t0 = d; @@ -192,7 +192,7 @@ c = t0; } -template +template BOTAN_FORCE_INLINE void SBoxE7(T& a, T& b, T& c, T& d) { T t0 = b; b |= c; @@ -220,7 +220,7 @@ a = t0; } -template +template BOTAN_FORCE_INLINE void SBoxD0(T& a, T& b, T& c, T& d) { c = ~c; T t0 = b; @@ -245,7 +245,7 @@ b = t0; } -template +template BOTAN_FORCE_INLINE void SBoxD1(T& a, T& b, T& c, T& d) { T t0 = b; b ^= d; @@ -273,7 +273,7 @@ d = t0; } -template +template BOTAN_FORCE_INLINE void SBoxD2(T& a, T& b, T& c, T& d) { c ^= d; d ^= a; @@ -298,7 +298,7 @@ b = t0; } -template +template BOTAN_FORCE_INLINE void SBoxD3(T& a, T& b, T& c, T& d) { T t0 = c; c ^= b; @@ -324,7 +324,7 @@ d = t0; } -template +template BOTAN_FORCE_INLINE void SBoxD4(T& a, T& b, T& c, T& d) { T t0 = c; c &= d; @@ -350,7 +350,7 @@ d = t0; } -template +template BOTAN_FORCE_INLINE void SBoxD5(T& a, T& b, T& c, T& d) { b = ~b; T t0 = d; @@ -378,7 +378,7 @@ c = t0; } -template +template BOTAN_FORCE_INLINE void SBoxD6(T& a, T& b, T& c, T& d) { a ^= c; T t0 = c; @@ -402,7 +402,7 @@ c = t0; } -template +template BOTAN_FORCE_INLINE void SBoxD7(T& a, T& b, T& c, T& d) { T t0 = c; c ^= a; diff -Nru botan3-3.7.1+dfsg/src/lib/block/serpent/serpent_simd/info.txt botan3-3.12.0+dfsg/src/lib/block/serpent/serpent_simd/info.txt --- botan3-3.7.1+dfsg/src/lib/block/serpent/serpent_simd/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/serpent/serpent_simd/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + SERPENT_SIMD -> 20160903 - + name -> "Serpent SIMD" @@ -8,5 +8,18 @@ -simd +cpuid +simd_4x32 + + +x86_32:ssse3 +x86_64:ssse3 +x32:ssse3 +arm32:neon +arm64:neon +ppc32:altivec +ppc64:altivec +loongarch64:lsx +wasm:simd128 + diff -Nru botan3-3.7.1+dfsg/src/lib/block/serpent/serpent_simd/serpent_simd.cpp botan3-3.12.0+dfsg/src/lib/block/serpent/serpent_simd/serpent_simd.cpp --- botan3-3.7.1+dfsg/src/lib/block/serpent/serpent_simd/serpent_simd.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/serpent/serpent_simd/serpent_simd.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,14 +8,14 @@ #include #include -#include +#include namespace Botan { /* * SIMD Serpent Encryption of 4 blocks in parallel */ -void Serpent::simd_encrypt_4(const uint8_t in[64], uint8_t out[64]) const { +void BOTAN_FN_ISA_SIMD_4X32 Serpent::simd_encrypt_4(const uint8_t in[64], uint8_t out[64]) const { using namespace Botan::Serpent_F; SIMD_4x32 B0 = SIMD_4x32::load_le(in); @@ -138,7 +138,7 @@ /* * SIMD Serpent Decryption of 4 blocks in parallel */ -void Serpent::simd_decrypt_4(const uint8_t in[64], uint8_t out[64]) const { +void BOTAN_FN_ISA_SIMD_4X32 Serpent::simd_decrypt_4(const uint8_t in[64], uint8_t out[64]) const { using namespace Botan::Serpent_F; SIMD_4x32 B0 = SIMD_4x32::load_le(in); diff -Nru botan3-3.7.1+dfsg/src/lib/block/shacal2/shacal2.cpp botan3-3.12.0+dfsg/src/lib/block/shacal2/shacal2.cpp --- botan3-3.7.1+dfsg/src/lib/block/shacal2/shacal2.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/shacal2/shacal2.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,10 +8,13 @@ #include #include -#include #include #include +#if defined(BOTAN_HAS_CPUID) + #include +#endif + namespace Botan { namespace { @@ -44,20 +47,29 @@ void SHACAL2::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { assert_key_material_set(); +#if defined(BOTAN_HAS_SHACAL2_AVX512) + if(CPUID::has(CPUID::Feature::AVX512)) { + const size_t consumed = avx512_encrypt_blocks(in, out, blocks); + in += consumed * BLOCK_SIZE; + out += consumed * BLOCK_SIZE; + blocks -= consumed; + } +#endif + #if defined(BOTAN_HAS_SHACAL2_X86) - if(CPUID::has_intel_sha()) { + if(CPUID::has(CPUID::Feature::SHA)) { return x86_encrypt_blocks(in, out, blocks); } #endif #if defined(BOTAN_HAS_SHACAL2_ARMV8) - if(CPUID::has_arm_sha2()) { + if(CPUID::has(CPUID::Feature::SHA2)) { return armv8_encrypt_blocks(in, out, blocks); } #endif #if defined(BOTAN_HAS_SHACAL2_AVX2) - if(CPUID::has_avx2()) { + if(CPUID::has(CPUID::Feature::AVX2)) { while(blocks >= 8) { avx2_encrypt_8(in, out); in += 8 * BLOCK_SIZE; @@ -68,7 +80,7 @@ #endif #if defined(BOTAN_HAS_SHACAL2_SIMD) - if(CPUID::has_simd_32()) { + if(CPUID::has(CPUID::Feature::SIMD_4X32)) { while(blocks >= 4) { simd_encrypt_4(in, out); in += 4 * BLOCK_SIZE; @@ -112,8 +124,17 @@ void SHACAL2::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { assert_key_material_set(); +#if defined(BOTAN_HAS_SHACAL2_AVX512) + if(CPUID::has(CPUID::Feature::AVX512)) { + const size_t consumed = avx512_decrypt_blocks(in, out, blocks); + in += consumed * BLOCK_SIZE; + out += consumed * BLOCK_SIZE; + blocks -= consumed; + } +#endif + #if defined(BOTAN_HAS_SHACAL2_AVX2) - if(CPUID::has_avx2()) { + if(CPUID::has(CPUID::Feature::AVX2)) { while(blocks >= 8) { avx2_decrypt_8(in, out); in += 8 * BLOCK_SIZE; @@ -124,7 +145,7 @@ #endif #if defined(BOTAN_HAS_SHACAL2_SIMD) - if(CPUID::has_simd_32()) { + if(CPUID::has(CPUID::Feature::SIMD_4X32)) { while(blocks >= 4) { simd_decrypt_4(in, out); in += 4 * BLOCK_SIZE; @@ -200,26 +221,32 @@ } size_t SHACAL2::parallelism() const { +#if defined(BOTAN_HAS_SHACAL2_AVX512) + if(CPUID::has(CPUID::Feature::AVX512)) { + return 16; + } +#endif + #if defined(BOTAN_HAS_SHACAL2_X86) - if(CPUID::has_intel_sha()) { + if(CPUID::has(CPUID::Feature::SHA)) { return 2; } #endif #if defined(BOTAN_HAS_SHACAL2_ARMV8) - if(CPUID::has_arm_sha2()) { + if(CPUID::has(CPUID::Feature::SHA2)) { return 2; } #endif #if defined(BOTAN_HAS_SHACAL2_AVX2) - if(CPUID::has_avx2()) { + if(CPUID::has(CPUID::Feature::AVX2)) { return 8; } #endif #if defined(BOTAN_HAS_SHACAL2_SIMD) - if(CPUID::has_simd_32()) { + if(CPUID::has(CPUID::Feature::SIMD_4X32)) { return 4; } #endif @@ -228,27 +255,33 @@ } std::string SHACAL2::provider() const { +#if defined(BOTAN_HAS_SHACAL2_AVX512) + if(auto feat = CPUID::check(CPUID::Feature::AVX512)) { + return *feat; + } +#endif + #if defined(BOTAN_HAS_SHACAL2_X86) - if(CPUID::has_intel_sha()) { - return "intel_sha"; + if(auto feat = CPUID::check(CPUID::Feature::SHA)) { + return *feat; } #endif #if defined(BOTAN_HAS_SHACAL2_ARMV8) - if(CPUID::has_arm_sha2()) { - return "armv8_sha2"; + if(auto feat = CPUID::check(CPUID::Feature::SHA2)) { + return *feat; } #endif #if defined(BOTAN_HAS_SHACAL2_AVX2) - if(CPUID::has_avx2()) { - return "avx2"; + if(auto feat = CPUID::check(CPUID::Feature::AVX2)) { + return *feat; } #endif #if defined(BOTAN_HAS_SHACAL2_SIMD) - if(CPUID::has_simd_32()) { - return "simd"; + if(auto feat = CPUID::check(CPUID::Feature::SIMD_4X32)) { + return *feat; } #endif diff -Nru botan3-3.7.1+dfsg/src/lib/block/shacal2/shacal2.h botan3-3.12.0+dfsg/src/lib/block/shacal2/shacal2.h --- botan3-3.7.1+dfsg/src/lib/block/shacal2/shacal2.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/shacal2/shacal2.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,6 +9,7 @@ #define BOTAN_SHACAL2_H_ #include +#include namespace Botan { @@ -43,6 +44,11 @@ void avx2_decrypt_8(const uint8_t in[], uint8_t out[]) const; #endif +#if defined(BOTAN_HAS_SHACAL2_AVX512) + size_t avx512_encrypt_blocks(const uint8_t in[], uint8_t out[], size_t blocks) const; + size_t avx512_decrypt_blocks(const uint8_t in[], uint8_t out[], size_t blocks) const; +#endif + #if defined(BOTAN_HAS_SHACAL2_X86) void x86_encrypt_blocks(const uint8_t in[], uint8_t out[], size_t blocks) const; #endif diff -Nru botan3-3.7.1+dfsg/src/lib/block/shacal2/shacal2_armv8/info.txt botan3-3.12.0+dfsg/src/lib/block/shacal2/shacal2_armv8/info.txt --- botan3-3.7.1+dfsg/src/lib/block/shacal2/shacal2_armv8/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/shacal2/shacal2_armv8/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + SHACAL2_ARMV8 -> 20201221 - + name -> "SHACAL-2 ARMv8" @@ -8,7 +8,7 @@ -shacal2 +cpuid diff -Nru botan3-3.7.1+dfsg/src/lib/block/shacal2/shacal2_armv8/shacal2_arvm8.cpp botan3-3.12.0+dfsg/src/lib/block/shacal2/shacal2_armv8/shacal2_arvm8.cpp --- botan3-3.7.1+dfsg/src/lib/block/shacal2/shacal2_armv8/shacal2_arvm8.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/shacal2/shacal2_armv8/shacal2_arvm8.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,7 +6,7 @@ #include -#include +#include #include namespace Botan { @@ -15,7 +15,7 @@ Only encryption is supported since the inverse round function would require a different instruction */ -BOTAN_FUNC_ISA("+crypto+sha2") +BOTAN_FN_ISA_SHA2 void SHACAL2::armv8_encrypt_blocks(const uint8_t in[], uint8_t out[], size_t blocks) const { const uint32_t* input32 = reinterpret_cast(in); uint32_t* output32 = reinterpret_cast(out); diff -Nru botan3-3.7.1+dfsg/src/lib/block/shacal2/shacal2_avx2/info.txt botan3-3.12.0+dfsg/src/lib/block/shacal2/shacal2_avx2/info.txt --- botan3-3.7.1+dfsg/src/lib/block/shacal2/shacal2_avx2/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/shacal2/shacal2_avx2/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + SHACAL2_AVX2 -> 20180826 - + name -> "SHACAL-2 AVX2" @@ -12,5 +12,6 @@ +cpuid simd_avx2 diff -Nru botan3-3.7.1+dfsg/src/lib/block/shacal2/shacal2_avx2/shacal2_avx2.cpp botan3-3.12.0+dfsg/src/lib/block/shacal2/shacal2_avx2/shacal2_avx2.cpp --- botan3-3.7.1+dfsg/src/lib/block/shacal2/shacal2_avx2/shacal2_avx2.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/shacal2/shacal2_avx2/shacal2_avx2.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -10,31 +10,33 @@ namespace Botan { +namespace SHACAL2_AVX2_F { + namespace { -void BOTAN_FORCE_INLINE BOTAN_AVX2_FN SHACAL2_Fwd(const SIMD_8x32& A, - const SIMD_8x32& B, - const SIMD_8x32& C, - SIMD_8x32& D, - const SIMD_8x32& E, - const SIMD_8x32& F, - const SIMD_8x32& G, - SIMD_8x32& H, - uint32_t RK) { +void BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX2 SHACAL2_Fwd(const SIMD_8x32& A, + const SIMD_8x32& B, + const SIMD_8x32& C, + SIMD_8x32& D, + const SIMD_8x32& E, + const SIMD_8x32& F, + const SIMD_8x32& G, + SIMD_8x32& H, + uint32_t RK) { H += E.sigma1() + SIMD_8x32::choose(E, F, G) + SIMD_8x32::splat(RK); D += H; H += A.sigma0() + SIMD_8x32::majority(A, B, C); } -void BOTAN_FORCE_INLINE BOTAN_AVX2_FN SHACAL2_Rev(const SIMD_8x32& A, - const SIMD_8x32& B, - const SIMD_8x32& C, - SIMD_8x32& D, - const SIMD_8x32& E, - const SIMD_8x32& F, - const SIMD_8x32& G, - SIMD_8x32& H, - uint32_t RK) { +void BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX2 SHACAL2_Rev(const SIMD_8x32& A, + const SIMD_8x32& B, + const SIMD_8x32& C, + SIMD_8x32& D, + const SIMD_8x32& E, + const SIMD_8x32& F, + const SIMD_8x32& G, + SIMD_8x32& H, + uint32_t RK) { H -= A.sigma0() + SIMD_8x32::majority(A, B, C); D -= H; H -= E.sigma1() + SIMD_8x32::choose(E, F, G) + SIMD_8x32::splat(RK); @@ -42,7 +44,11 @@ } // namespace -void BOTAN_AVX2_FN SHACAL2::avx2_encrypt_8(const uint8_t in[], uint8_t out[]) const { +} // namespace SHACAL2_AVX2_F + +void BOTAN_FN_ISA_AVX2 SHACAL2::avx2_encrypt_8(const uint8_t in[], uint8_t out[]) const { + using namespace SHACAL2_AVX2_F; + SIMD_8x32::reset_registers(); SIMD_8x32 A = SIMD_8x32::load_be(in); @@ -83,7 +89,9 @@ SIMD_8x32::zero_registers(); } -BOTAN_AVX2_FN void SHACAL2::avx2_decrypt_8(const uint8_t in[], uint8_t out[]) const { +void BOTAN_FN_ISA_AVX2 SHACAL2::avx2_decrypt_8(const uint8_t in[], uint8_t out[]) const { + using namespace SHACAL2_AVX2_F; + SIMD_8x32::reset_registers(); SIMD_8x32 A = SIMD_8x32::load_be(in); diff -Nru botan3-3.7.1+dfsg/src/lib/block/shacal2/shacal2_avx512/info.txt botan3-3.12.0+dfsg/src/lib/block/shacal2/shacal2_avx512/info.txt --- botan3-3.7.1+dfsg/src/lib/block/shacal2/shacal2_avx512/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/shacal2/shacal2_avx512/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,18 @@ + +SHACAL2_AVX512 -> 20250516 + + + +name -> "SHACAL-2 AVX512" +brief -> "SHACAL-2 using AVX512 instructions" + + + +avx512 + + + +cpuid +simd_avx512 +simd_avx2 + diff -Nru botan3-3.7.1+dfsg/src/lib/block/shacal2/shacal2_avx512/shacal2_avx512.cpp botan3-3.12.0+dfsg/src/lib/block/shacal2/shacal2_avx512/shacal2_avx512.cpp --- botan3-3.7.1+dfsg/src/lib/block/shacal2/shacal2_avx512/shacal2_avx512.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/shacal2/shacal2_avx512/shacal2_avx512.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,337 @@ +/* +* (C) 2025 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include + +namespace Botan { + +namespace SHACAL2_AVX512_F { + +namespace { + +// NOLINTBEGIN(portability-simd-intrinsics) + +/* +* 8x16 Transpose +* +* Convert from +* +* A00 B00 C00 ... H00 +* A01 B01 C01 ... H01 +* .. +* A15 B15 C15 ... H15 +* +* with two blocks stored in each register, into +* +* A00 A01 ... A15 +* B00 B01 ... B15 +* ... +* H00 H01 ... H15 +*/ +BOTAN_FN_ISA_AVX512 +void transpose_in(SIMD_16x32& B0, + SIMD_16x32& B1, + SIMD_16x32& B2, + SIMD_16x32& B3, + SIMD_16x32& B4, + SIMD_16x32& B5, + SIMD_16x32& B6, + SIMD_16x32& B7) { + auto t0 = _mm512_unpacklo_epi32(B0.raw(), B1.raw()); + auto t1 = _mm512_unpackhi_epi32(B0.raw(), B1.raw()); + auto t2 = _mm512_unpacklo_epi32(B2.raw(), B3.raw()); + auto t3 = _mm512_unpackhi_epi32(B2.raw(), B3.raw()); + auto t4 = _mm512_unpacklo_epi32(B4.raw(), B5.raw()); + auto t5 = _mm512_unpackhi_epi32(B4.raw(), B5.raw()); + auto t6 = _mm512_unpacklo_epi32(B6.raw(), B7.raw()); + auto t7 = _mm512_unpackhi_epi32(B6.raw(), B7.raw()); + + auto r0 = _mm512_unpacklo_epi64(t0, t2); + auto r1 = _mm512_unpackhi_epi64(t0, t2); + auto r2 = _mm512_unpacklo_epi64(t1, t3); + auto r3 = _mm512_unpackhi_epi64(t1, t3); + auto r4 = _mm512_unpacklo_epi64(t4, t6); + auto r5 = _mm512_unpackhi_epi64(t4, t6); + auto r6 = _mm512_unpacklo_epi64(t5, t7); + auto r7 = _mm512_unpackhi_epi64(t5, t7); + + const __m512i tbl0 = _mm512_set_epi32(27, 19, 26, 18, 25, 17, 24, 16, 11, 3, 10, 2, 9, 1, 8, 0); + const __m512i tbl1 = _mm512_add_epi32(tbl0, _mm512_set1_epi32(4)); + B0 = SIMD_16x32(_mm512_permutex2var_epi32(r0, tbl0, r4)); + B1 = SIMD_16x32(_mm512_permutex2var_epi32(r1, tbl0, r5)); + B2 = SIMD_16x32(_mm512_permutex2var_epi32(r2, tbl0, r6)); + B3 = SIMD_16x32(_mm512_permutex2var_epi32(r3, tbl0, r7)); + B4 = SIMD_16x32(_mm512_permutex2var_epi32(r0, tbl1, r4)); + B5 = SIMD_16x32(_mm512_permutex2var_epi32(r1, tbl1, r5)); + B6 = SIMD_16x32(_mm512_permutex2var_epi32(r2, tbl1, r6)); + B7 = SIMD_16x32(_mm512_permutex2var_epi32(r3, tbl1, r7)); +} + +BOTAN_FN_ISA_AVX512 +void transpose_out(SIMD_16x32& B0, + SIMD_16x32& B1, + SIMD_16x32& B2, + SIMD_16x32& B3, + SIMD_16x32& B4, + SIMD_16x32& B5, + SIMD_16x32& B6, + SIMD_16x32& B7) { + auto t0 = _mm512_unpacklo_epi32(B0.raw(), B1.raw()); + auto t1 = _mm512_unpackhi_epi32(B0.raw(), B1.raw()); + auto t2 = _mm512_unpacklo_epi32(B2.raw(), B3.raw()); + auto t3 = _mm512_unpackhi_epi32(B2.raw(), B3.raw()); + auto t4 = _mm512_unpacklo_epi32(B4.raw(), B5.raw()); + auto t5 = _mm512_unpackhi_epi32(B4.raw(), B5.raw()); + auto t6 = _mm512_unpacklo_epi32(B6.raw(), B7.raw()); + auto t7 = _mm512_unpackhi_epi32(B6.raw(), B7.raw()); + + auto r0 = _mm512_unpacklo_epi64(t0, t2); + auto r1 = _mm512_unpackhi_epi64(t0, t2); + auto r2 = _mm512_unpacklo_epi64(t1, t3); + auto r3 = _mm512_unpackhi_epi64(t1, t3); + auto r4 = _mm512_unpacklo_epi64(t4, t6); + auto r5 = _mm512_unpackhi_epi64(t4, t6); + auto r6 = _mm512_unpacklo_epi64(t5, t7); + auto r7 = _mm512_unpackhi_epi64(t5, t7); + + const __m512i tbl0 = _mm512_set_epi32(23, 22, 21, 20, 7, 6, 5, 4, 19, 18, 17, 16, 3, 2, 1, 0); + const __m512i tbl1 = _mm512_add_epi32(tbl0, _mm512_set1_epi32(8)); + + auto s0 = _mm512_permutex2var_epi32(r0, tbl0, r4); + auto s1 = _mm512_permutex2var_epi32(r1, tbl0, r5); + auto s2 = _mm512_permutex2var_epi32(r2, tbl0, r6); + auto s3 = _mm512_permutex2var_epi32(r3, tbl0, r7); + auto s4 = _mm512_permutex2var_epi32(r0, tbl1, r4); + auto s5 = _mm512_permutex2var_epi32(r1, tbl1, r5); + auto s6 = _mm512_permutex2var_epi32(r2, tbl1, r6); + auto s7 = _mm512_permutex2var_epi32(r3, tbl1, r7); + + B0 = SIMD_16x32(_mm512_shuffle_i32x4(s0, s1, 0b01000100)); + B1 = SIMD_16x32(_mm512_shuffle_i32x4(s2, s3, 0b01000100)); + B2 = SIMD_16x32(_mm512_shuffle_i32x4(s0, s1, 0b11101110)); + B3 = SIMD_16x32(_mm512_shuffle_i32x4(s2, s3, 0b11101110)); + B4 = SIMD_16x32(_mm512_shuffle_i32x4(s4, s5, 0b01000100)); + B5 = SIMD_16x32(_mm512_shuffle_i32x4(s6, s7, 0b01000100)); + B6 = SIMD_16x32(_mm512_shuffle_i32x4(s4, s5, 0b11101110)); + B7 = SIMD_16x32(_mm512_shuffle_i32x4(s6, s7, 0b11101110)); +} + +// NOLINTEND(portability-simd-intrinsics) + +template +void BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512 SHACAL2_Fwd(const SimdT& A, + const SimdT& B, + const SimdT& C, + SimdT& D, + const SimdT& E, + const SimdT& F, + const SimdT& G, + SimdT& H, + uint32_t RK) { + H += E.sigma1() + SimdT::choose(E, F, G) + SimdT::splat(RK); + D += H; + H += A.sigma0() + SimdT::majority(A, B, C); +} + +template +void BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512 SHACAL2_Rev(const SimdT& A, + const SimdT& B, + const SimdT& C, + SimdT& D, + const SimdT& E, + const SimdT& F, + const SimdT& G, + SimdT& H, + uint32_t RK) { + H -= A.sigma0() + SimdT::majority(A, B, C); + D -= H; + H -= E.sigma1() + SimdT::choose(E, F, G) + SimdT::splat(RK); +} + +} // namespace + +} // namespace SHACAL2_AVX512_F + +size_t BOTAN_FN_ISA_AVX512 SHACAL2::avx512_encrypt_blocks(const uint8_t in[], uint8_t out[], size_t blocks) const { + using namespace SHACAL2_AVX512_F; + + size_t consumed = 0; + + while(blocks >= 16) { + SIMD_16x32 A = SIMD_16x32::load_be(in + 64 * 0); + SIMD_16x32 B = SIMD_16x32::load_be(in + 64 * 1); + SIMD_16x32 C = SIMD_16x32::load_be(in + 64 * 2); + SIMD_16x32 D = SIMD_16x32::load_be(in + 64 * 3); + SIMD_16x32 E = SIMD_16x32::load_be(in + 64 * 4); + SIMD_16x32 F = SIMD_16x32::load_be(in + 64 * 5); + SIMD_16x32 G = SIMD_16x32::load_be(in + 64 * 6); + SIMD_16x32 H = SIMD_16x32::load_be(in + 64 * 7); + + transpose_in(A, B, C, D, E, F, G, H); + + for(size_t r = 0; r != 64; r += 8) { + SHACAL2_Fwd(A, B, C, D, E, F, G, H, m_RK[r + 0]); + SHACAL2_Fwd(H, A, B, C, D, E, F, G, m_RK[r + 1]); + SHACAL2_Fwd(G, H, A, B, C, D, E, F, m_RK[r + 2]); + SHACAL2_Fwd(F, G, H, A, B, C, D, E, m_RK[r + 3]); + SHACAL2_Fwd(E, F, G, H, A, B, C, D, m_RK[r + 4]); + SHACAL2_Fwd(D, E, F, G, H, A, B, C, m_RK[r + 5]); + SHACAL2_Fwd(C, D, E, F, G, H, A, B, m_RK[r + 6]); + SHACAL2_Fwd(B, C, D, E, F, G, H, A, m_RK[r + 7]); + } + + transpose_out(A, B, C, D, E, F, G, H); + + A.store_be(out + 64 * 0); + B.store_be(out + 64 * 1); + C.store_be(out + 64 * 2); + D.store_be(out + 64 * 3); + E.store_be(out + 64 * 4); + F.store_be(out + 64 * 5); + G.store_be(out + 64 * 6); + H.store_be(out + 64 * 7); + + in += 16 * BLOCK_SIZE; + out += 16 * BLOCK_SIZE; + blocks -= 16; + consumed += 16; + } + + while(blocks >= 8) { + SIMD_8x32 A = SIMD_8x32::load_be(in + 32 * 0); + SIMD_8x32 B = SIMD_8x32::load_be(in + 32 * 1); + SIMD_8x32 C = SIMD_8x32::load_be(in + 32 * 2); + SIMD_8x32 D = SIMD_8x32::load_be(in + 32 * 3); + SIMD_8x32 E = SIMD_8x32::load_be(in + 32 * 4); + SIMD_8x32 F = SIMD_8x32::load_be(in + 32 * 5); + SIMD_8x32 G = SIMD_8x32::load_be(in + 32 * 6); + SIMD_8x32 H = SIMD_8x32::load_be(in + 32 * 7); + + SIMD_8x32::transpose(A, B, C, D, E, F, G, H); + + for(size_t r = 0; r != 64; r += 8) { + SHACAL2_Fwd(A, B, C, D, E, F, G, H, m_RK[r + 0]); + SHACAL2_Fwd(H, A, B, C, D, E, F, G, m_RK[r + 1]); + SHACAL2_Fwd(G, H, A, B, C, D, E, F, m_RK[r + 2]); + SHACAL2_Fwd(F, G, H, A, B, C, D, E, m_RK[r + 3]); + SHACAL2_Fwd(E, F, G, H, A, B, C, D, m_RK[r + 4]); + SHACAL2_Fwd(D, E, F, G, H, A, B, C, m_RK[r + 5]); + SHACAL2_Fwd(C, D, E, F, G, H, A, B, m_RK[r + 6]); + SHACAL2_Fwd(B, C, D, E, F, G, H, A, m_RK[r + 7]); + } + + SIMD_8x32::transpose(A, B, C, D, E, F, G, H); + + A.store_be(out + 32 * 0); + B.store_be(out + 32 * 1); + C.store_be(out + 32 * 2); + D.store_be(out + 32 * 3); + E.store_be(out + 32 * 4); + F.store_be(out + 32 * 5); + G.store_be(out + 32 * 6); + H.store_be(out + 32 * 7); + + in += 8 * BLOCK_SIZE; + out += 8 * BLOCK_SIZE; + blocks -= 8; + consumed += 8; + } + + return consumed; +} + +size_t BOTAN_FN_ISA_AVX512 SHACAL2::avx512_decrypt_blocks(const uint8_t in[], uint8_t out[], size_t blocks) const { + using namespace SHACAL2_AVX512_F; + + size_t consumed = 0; + + while(blocks >= 16) { + SIMD_16x32 A = SIMD_16x32::load_be(in + 64 * 0); + SIMD_16x32 B = SIMD_16x32::load_be(in + 64 * 1); + SIMD_16x32 C = SIMD_16x32::load_be(in + 64 * 2); + SIMD_16x32 D = SIMD_16x32::load_be(in + 64 * 3); + SIMD_16x32 E = SIMD_16x32::load_be(in + 64 * 4); + SIMD_16x32 F = SIMD_16x32::load_be(in + 64 * 5); + SIMD_16x32 G = SIMD_16x32::load_be(in + 64 * 6); + SIMD_16x32 H = SIMD_16x32::load_be(in + 64 * 7); + + transpose_in(A, B, C, D, E, F, G, H); + + for(size_t r = 0; r != 64; r += 8) { + SHACAL2_Rev(B, C, D, E, F, G, H, A, m_RK[63 - r]); + SHACAL2_Rev(C, D, E, F, G, H, A, B, m_RK[62 - r]); + SHACAL2_Rev(D, E, F, G, H, A, B, C, m_RK[61 - r]); + SHACAL2_Rev(E, F, G, H, A, B, C, D, m_RK[60 - r]); + SHACAL2_Rev(F, G, H, A, B, C, D, E, m_RK[59 - r]); + SHACAL2_Rev(G, H, A, B, C, D, E, F, m_RK[58 - r]); + SHACAL2_Rev(H, A, B, C, D, E, F, G, m_RK[57 - r]); + SHACAL2_Rev(A, B, C, D, E, F, G, H, m_RK[56 - r]); + } + + transpose_out(A, B, C, D, E, F, G, H); + + A.store_be(out + 64 * 0); + B.store_be(out + 64 * 1); + C.store_be(out + 64 * 2); + D.store_be(out + 64 * 3); + E.store_be(out + 64 * 4); + F.store_be(out + 64 * 5); + G.store_be(out + 64 * 6); + H.store_be(out + 64 * 7); + + in += 16 * BLOCK_SIZE; + out += 16 * BLOCK_SIZE; + blocks -= 16; + consumed += 16; + } + + while(blocks >= 8) { + SIMD_8x32 A = SIMD_8x32::load_be(in + 32 * 0); + SIMD_8x32 B = SIMD_8x32::load_be(in + 32 * 1); + SIMD_8x32 C = SIMD_8x32::load_be(in + 32 * 2); + SIMD_8x32 D = SIMD_8x32::load_be(in + 32 * 3); + SIMD_8x32 E = SIMD_8x32::load_be(in + 32 * 4); + SIMD_8x32 F = SIMD_8x32::load_be(in + 32 * 5); + SIMD_8x32 G = SIMD_8x32::load_be(in + 32 * 6); + SIMD_8x32 H = SIMD_8x32::load_be(in + 32 * 7); + + SIMD_8x32::transpose(A, B, C, D, E, F, G, H); + + for(size_t r = 0; r != 64; r += 8) { + SHACAL2_Rev(B, C, D, E, F, G, H, A, m_RK[63 - r]); + SHACAL2_Rev(C, D, E, F, G, H, A, B, m_RK[62 - r]); + SHACAL2_Rev(D, E, F, G, H, A, B, C, m_RK[61 - r]); + SHACAL2_Rev(E, F, G, H, A, B, C, D, m_RK[60 - r]); + SHACAL2_Rev(F, G, H, A, B, C, D, E, m_RK[59 - r]); + SHACAL2_Rev(G, H, A, B, C, D, E, F, m_RK[58 - r]); + SHACAL2_Rev(H, A, B, C, D, E, F, G, m_RK[57 - r]); + SHACAL2_Rev(A, B, C, D, E, F, G, H, m_RK[56 - r]); + } + + SIMD_8x32::transpose(A, B, C, D, E, F, G, H); + + A.store_be(out + 32 * 0); + B.store_be(out + 32 * 1); + C.store_be(out + 32 * 2); + D.store_be(out + 32 * 3); + E.store_be(out + 32 * 4); + F.store_be(out + 32 * 5); + G.store_be(out + 32 * 6); + H.store_be(out + 32 * 7); + + in += 8 * BLOCK_SIZE; + out += 8 * BLOCK_SIZE; + blocks -= 8; + consumed += 8; + } + + return consumed; +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/block/shacal2/shacal2_simd/info.txt botan3-3.12.0+dfsg/src/lib/block/shacal2/shacal2_simd/info.txt --- botan3-3.7.1+dfsg/src/lib/block/shacal2/shacal2_simd/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/shacal2/shacal2_simd/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + SHACAL2_SIMD -> 20170813 - + name -> "SHACAL-2 SIMD" @@ -8,6 +8,18 @@ -shacal2 -simd +cpuid +simd_4x32 + + +x86_32:ssse3 +x86_64:ssse3 +x32:ssse3 +arm32:neon +arm64:neon +ppc32:altivec +ppc64:altivec +loongarch64:lsx +wasm:simd128 + diff -Nru botan3-3.7.1+dfsg/src/lib/block/shacal2/shacal2_simd/shacal2_simd.cpp botan3-3.12.0+dfsg/src/lib/block/shacal2/shacal2_simd/shacal2_simd.cpp --- botan3-3.7.1+dfsg/src/lib/block/shacal2/shacal2_simd/shacal2_simd.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/shacal2/shacal2_simd/shacal2_simd.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,35 +7,35 @@ #include -#include +#include namespace Botan { namespace { -inline void SHACAL2_Fwd(const SIMD_4x32& A, - const SIMD_4x32& B, - const SIMD_4x32& C, - SIMD_4x32& D, - const SIMD_4x32& E, - const SIMD_4x32& F, - const SIMD_4x32& G, - SIMD_4x32& H, - uint32_t RK) { +inline void BOTAN_FN_ISA_SIMD_4X32 SHACAL2_Fwd(const SIMD_4x32& A, + const SIMD_4x32& B, + const SIMD_4x32& C, + SIMD_4x32& D, + const SIMD_4x32& E, + const SIMD_4x32& F, + const SIMD_4x32& G, + SIMD_4x32& H, + uint32_t RK) { H += E.sigma1() + SIMD_4x32::choose(E, F, G) + SIMD_4x32::splat(RK); D += H; H += A.sigma0() + SIMD_4x32::majority(A, B, C); } -inline void SHACAL2_Rev(const SIMD_4x32& A, - const SIMD_4x32& B, - const SIMD_4x32& C, - SIMD_4x32& D, - const SIMD_4x32& E, - const SIMD_4x32& F, - const SIMD_4x32& G, - SIMD_4x32& H, - uint32_t RK) { +inline void BOTAN_FN_ISA_SIMD_4X32 SHACAL2_Rev(const SIMD_4x32& A, + const SIMD_4x32& B, + const SIMD_4x32& C, + SIMD_4x32& D, + const SIMD_4x32& E, + const SIMD_4x32& F, + const SIMD_4x32& G, + SIMD_4x32& H, + uint32_t RK) { H -= A.sigma0() + SIMD_4x32::majority(A, B, C); D -= H; H -= E.sigma1() + SIMD_4x32::choose(E, F, G) + SIMD_4x32::splat(RK); @@ -43,7 +43,7 @@ } // namespace -void SHACAL2::simd_encrypt_4(const uint8_t in[], uint8_t out[]) const { +void BOTAN_FN_ISA_SIMD_4X32 SHACAL2::simd_encrypt_4(const uint8_t in[], uint8_t out[]) const { SIMD_4x32 A = SIMD_4x32::load_be(in); SIMD_4x32 E = SIMD_4x32::load_be(in + 16); SIMD_4x32 B = SIMD_4x32::load_be(in + 32); @@ -82,7 +82,7 @@ H.store_be(out + 112); } -void SHACAL2::simd_decrypt_4(const uint8_t in[], uint8_t out[]) const { +void BOTAN_FN_ISA_SIMD_4X32 SHACAL2::simd_decrypt_4(const uint8_t in[], uint8_t out[]) const { SIMD_4x32 A = SIMD_4x32::load_be(in); SIMD_4x32 E = SIMD_4x32::load_be(in + 16); SIMD_4x32 B = SIMD_4x32::load_be(in + 32); diff -Nru botan3-3.7.1+dfsg/src/lib/block/shacal2/shacal2_x86/info.txt botan3-3.12.0+dfsg/src/lib/block/shacal2/shacal2_x86/info.txt --- botan3-3.7.1+dfsg/src/lib/block/shacal2/shacal2_x86/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/shacal2/shacal2_x86/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + SHACAL2_X86 -> 20170814 - + name -> "SHACAL-2 X86" @@ -8,7 +8,7 @@ -shacal2 +cpuid diff -Nru botan3-3.7.1+dfsg/src/lib/block/shacal2/shacal2_x86/shacal2_x86.cpp botan3-3.12.0+dfsg/src/lib/block/shacal2/shacal2_x86/shacal2_x86.cpp --- botan3-3.7.1+dfsg/src/lib/block/shacal2/shacal2_x86/shacal2_x86.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/shacal2/shacal2_x86/shacal2_x86.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,7 +7,7 @@ #include -#include +#include #include namespace Botan { @@ -17,7 +17,9 @@ require a different instruction */ -BOTAN_FUNC_ISA("sha,ssse3") void SHACAL2::x86_encrypt_blocks(const uint8_t in[], uint8_t out[], size_t blocks) const { +void BOTAN_FN_ISA_SHANI SHACAL2::x86_encrypt_blocks(const uint8_t in[], uint8_t out[], size_t blocks) const { + // NOLINTBEGIN(portability-simd-intrinsics) TODO convert to SIMD_4x32 plus SHA-NI helpers + const __m128i MASK1 = _mm_set_epi8(8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7); const __m128i MASK2 = _mm_set_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); @@ -68,11 +70,11 @@ out_mm += 4; } - while(blocks) { + while(blocks > 0) { __m128i B0 = _mm_loadu_si128(in_mm); __m128i B1 = _mm_loadu_si128(in_mm + 1); - __m128i TMP = _mm_shuffle_epi8(_mm_unpacklo_epi64(B0, B1), MASK2); + const __m128i TMP = _mm_shuffle_epi8(_mm_unpacklo_epi64(B0, B1), MASK2); B1 = _mm_shuffle_epi8(_mm_unpackhi_epi64(B0, B1), MASK2); B0 = TMP; @@ -95,6 +97,8 @@ in_mm += 2; out_mm += 2; } + + // NOLINTEND(portability-simd-intrinsics) } } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/block/sm4/sm4.cpp botan3-3.12.0+dfsg/src/lib/block/sm4/sm4.cpp --- botan3-3.7.1+dfsg/src/lib/block/sm4/sm4.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/sm4/sm4.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,10 +8,13 @@ #include -#include #include #include +#if defined(BOTAN_HAS_CPUID) + #include +#endif + namespace Botan { namespace { @@ -162,17 +165,35 @@ assert_key_material_set(); #if defined(BOTAN_HAS_SM4_ARMV8) - if(CPUID::has_arm_sm4()) { + if(CPUID::has(CPUID::Feature::SM4)) { return sm4_armv8_encrypt(in, out, blocks); } #endif +#if defined(BOTAN_HAS_SM4_X86) + if(CPUID::has(CPUID::Feature::SM4)) { + return sm4_x86_encrypt(in, out, blocks); + } +#endif + +#if defined(BOTAN_HAS_SM4_AVX512_GFNI) + if(CPUID::has(CPUID::Feature::AVX512, CPUID::Feature::GFNI)) { + return sm4_avx512_gfni_encrypt(in, out, blocks); + } +#endif + #if defined(BOTAN_HAS_SM4_GFNI) - if(CPUID::has_gfni()) { + if(CPUID::has(CPUID::Feature::GFNI)) { return sm4_gfni_encrypt(in, out, blocks); } #endif +#if defined(BOTAN_HAS_SM4_HWAES) + if(CPUID::has(CPUID::Feature::HW_AES)) { + return sm4_hwaes_encrypt(in, out, blocks); + } +#endif + while(blocks >= 2) { uint32_t B0 = load_be(in, 0); uint32_t B1 = load_be(in, 1); @@ -229,17 +250,35 @@ assert_key_material_set(); #if defined(BOTAN_HAS_SM4_ARMV8) - if(CPUID::has_arm_sm4()) { + if(CPUID::has(CPUID::Feature::SM4)) { return sm4_armv8_decrypt(in, out, blocks); } #endif +#if defined(BOTAN_HAS_SM4_X86) + if(CPUID::has(CPUID::Feature::SM4)) { + return sm4_x86_decrypt(in, out, blocks); + } +#endif + +#if defined(BOTAN_HAS_SM4_AVX512_GFNI) + if(CPUID::has(CPUID::Feature::AVX512, CPUID::Feature::GFNI)) { + return sm4_avx512_gfni_decrypt(in, out, blocks); + } +#endif + #if defined(BOTAN_HAS_SM4_GFNI) - if(CPUID::has_gfni()) { + if(CPUID::has(CPUID::Feature::GFNI)) { return sm4_gfni_decrypt(in, out, blocks); } #endif +#if defined(BOTAN_HAS_SM4_HWAES) + if(CPUID::has(CPUID::Feature::HW_AES)) { + return sm4_hwaes_decrypt(in, out, blocks); + } +#endif + while(blocks >= 2) { uint32_t B0 = load_be(in, 0); uint32_t B1 = load_be(in, 1); @@ -325,30 +364,54 @@ size_t SM4::parallelism() const { #if defined(BOTAN_HAS_SM4_ARMV8) - if(CPUID::has_arm_sm4()) { + if(CPUID::has(CPUID::Feature::SM4)) { return 4; } #endif +#if defined(BOTAN_HAS_SM4_AVX512_GFNI) + if(CPUID::has(CPUID::Feature::AVX512, CPUID::Feature::GFNI)) { + return 16; + } +#endif + #if defined(BOTAN_HAS_SM4_GFNI) - if(CPUID::has_gfni()) { + if(CPUID::has(CPUID::Feature::GFNI)) { return 8; } #endif +#if defined(BOTAN_HAS_SM4_HWAES) + if(CPUID::has(CPUID::Feature::HW_AES)) { + return 4; + } +#endif + return 1; } std::string SM4::provider() const { #if defined(BOTAN_HAS_SM4_ARMV8) - if(CPUID::has_arm_sm4()) { - return "armv8"; + if(auto feat = CPUID::check(CPUID::Feature::SM4)) { + return *feat; + } +#endif + +#if defined(BOTAN_HAS_SM4_AVX512_GFNI) + if(auto feat = CPUID::check(CPUID::Feature::AVX512, CPUID::Feature::GFNI)) { + return *feat; } #endif #if defined(BOTAN_HAS_SM4_GFNI) - if(CPUID::has_gfni()) { - return "gfni"; + if(auto feat = CPUID::check(CPUID::Feature::GFNI)) { + return *feat; + } +#endif + +#if defined(BOTAN_HAS_SM4_HWAES) + if(auto feat = CPUID::check(CPUID::Feature::HW_AES)) { + return *feat; } #endif diff -Nru botan3-3.7.1+dfsg/src/lib/block/sm4/sm4.h botan3-3.12.0+dfsg/src/lib/block/sm4/sm4.h --- botan3-3.7.1+dfsg/src/lib/block/sm4/sm4.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/sm4/sm4.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,6 +9,7 @@ #define BOTAN_SM4_H_ #include +#include namespace Botan { @@ -38,11 +39,26 @@ void sm4_armv8_decrypt(const uint8_t in[], uint8_t out[], size_t blocks) const; #endif +#if defined(BOTAN_HAS_SM4_X86) + void sm4_x86_encrypt(const uint8_t in[], uint8_t out[], size_t blocks) const; + void sm4_x86_decrypt(const uint8_t in[], uint8_t out[], size_t blocks) const; +#endif + +#if defined(BOTAN_HAS_SM4_AVX512_GFNI) + void sm4_avx512_gfni_encrypt(const uint8_t in[], uint8_t out[], size_t blocks) const; + void sm4_avx512_gfni_decrypt(const uint8_t in[], uint8_t out[], size_t blocks) const; +#endif + #if defined(BOTAN_HAS_SM4_GFNI) void sm4_gfni_encrypt(const uint8_t in[], uint8_t out[], size_t blocks) const; void sm4_gfni_decrypt(const uint8_t in[], uint8_t out[], size_t blocks) const; #endif +#if defined(BOTAN_HAS_SM4_HWAES) + void sm4_hwaes_encrypt(const uint8_t in[], uint8_t out[], size_t blocks) const; + void sm4_hwaes_decrypt(const uint8_t in[], uint8_t out[], size_t blocks) const; +#endif + secure_vector m_RK; }; diff -Nru botan3-3.7.1+dfsg/src/lib/block/sm4/sm4_armv8/info.txt botan3-3.12.0+dfsg/src/lib/block/sm4/sm4_armv8/info.txt --- botan3-3.7.1+dfsg/src/lib/block/sm4/sm4_armv8/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/sm4/sm4_armv8/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + SM4_ARMV8 -> 20180709 - + name -> "SM4 ARMv8" @@ -10,3 +10,7 @@ armv8sm4 + + +cpuid + diff -Nru botan3-3.7.1+dfsg/src/lib/block/sm4/sm4_armv8/sm4_armv8.cpp botan3-3.12.0+dfsg/src/lib/block/sm4/sm4_armv8/sm4_armv8.cpp --- botan3-3.7.1+dfsg/src/lib/block/sm4/sm4_armv8/sm4_armv8.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/sm4/sm4_armv8/sm4_armv8.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,16 +6,16 @@ #include -#include +#include #include namespace Botan { namespace { -alignas(16) static const uint8_t qswap_tbl[16] = {12, 13, 14, 15, 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3}; +alignas(16) const uint8_t qswap_tbl[16] = {12, 13, 14, 15, 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3}; -alignas(16) static const uint8_t bswap_tbl[16] = {15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}; +alignas(16) const uint8_t bswap_tbl[16] = {15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}; inline uint32x4_t qswap_32(uint32x4_t B) { return vreinterpretq_u32_u8(vqtbl1q_u8(vreinterpretq_u8_u32(B), vld1q_u8(qswap_tbl))); @@ -33,8 +33,7 @@ return vreinterpretq_u32_u8(vqtbl1q_u8(vreinterpretq_u8_u32(B), vld1q_u8(bswap_tbl))); } -inline void BOTAN_FUNC_ISA("arch=armv8.2-a+sm4") - SM4_E(uint32x4_t& B0, uint32x4_t& B1, uint32x4_t& B2, uint32x4_t& B3, uint32x4_t K) { +inline void BOTAN_FN_ISA_SM4 SM4_E(uint32x4_t& B0, uint32x4_t& B1, uint32x4_t& B2, uint32x4_t& B3, uint32x4_t K) { B0 = vsm4eq_u32(B0, K); B1 = vsm4eq_u32(B1, K); B2 = vsm4eq_u32(B2, K); @@ -43,10 +42,8 @@ } // namespace -void BOTAN_FUNC_ISA("arch=armv8.2-a+sm4") SM4::sm4_armv8_encrypt(const uint8_t input8[], - uint8_t output8[], - size_t blocks) const { - const uint32x4_t K0 = vld1q_u32(&m_RK[0]); +void BOTAN_FN_ISA_SM4 SM4::sm4_armv8_encrypt(const uint8_t input8[], uint8_t output8[], size_t blocks) const { + const uint32x4_t K0 = vld1q_u32(&m_RK[0]); // NOLINT(*-container-data-pointer) const uint32x4_t K1 = vld1q_u32(&m_RK[4]); const uint32x4_t K2 = vld1q_u32(&m_RK[8]); const uint32x4_t K3 = vld1q_u32(&m_RK[12]); @@ -55,8 +52,8 @@ const uint32x4_t K6 = vld1q_u32(&m_RK[24]); const uint32x4_t K7 = vld1q_u32(&m_RK[28]); - const uint32_t* input32 = reinterpret_cast(reinterpret_cast(input8)); - uint32_t* output32 = reinterpret_cast(reinterpret_cast(output8)); + const uint32_t* input32 = reinterpret_cast(input8); + uint32_t* output32 = reinterpret_cast(output8); while(blocks >= 4) { uint32x4_t B0 = bswap_32(vld1q_u32(input32)); @@ -102,10 +99,8 @@ } } -void BOTAN_FUNC_ISA("arch=armv8.2-a+sm4") SM4::sm4_armv8_decrypt(const uint8_t input8[], - uint8_t output8[], - size_t blocks) const { - const uint32x4_t K0 = qswap_32(vld1q_u32(&m_RK[0])); +void BOTAN_FN_ISA_SM4 SM4::sm4_armv8_decrypt(const uint8_t input8[], uint8_t output8[], size_t blocks) const { + const uint32x4_t K0 = qswap_32(vld1q_u32(&m_RK[0])); // NOLINT(*-container-data-pointer) const uint32x4_t K1 = qswap_32(vld1q_u32(&m_RK[4])); const uint32x4_t K2 = qswap_32(vld1q_u32(&m_RK[8])); const uint32x4_t K3 = qswap_32(vld1q_u32(&m_RK[12])); @@ -114,8 +109,8 @@ const uint32x4_t K6 = qswap_32(vld1q_u32(&m_RK[24])); const uint32x4_t K7 = qswap_32(vld1q_u32(&m_RK[28])); - const uint32_t* input32 = reinterpret_cast(reinterpret_cast(input8)); - uint32_t* output32 = reinterpret_cast(reinterpret_cast(output8)); + const uint32_t* input32 = reinterpret_cast(input8); + uint32_t* output32 = reinterpret_cast(output8); while(blocks >= 4) { uint32x4_t B0 = bswap_32(vld1q_u32(input32)); diff -Nru botan3-3.7.1+dfsg/src/lib/block/sm4/sm4_avx512/info.txt botan3-3.12.0+dfsg/src/lib/block/sm4/sm4_avx512/info.txt --- botan3-3.7.1+dfsg/src/lib/block/sm4/sm4_avx512/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/sm4/sm4_avx512/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,19 @@ + +SM4_AVX512_GFNI -> 20251227 + + + +name -> "SM4 AVX-512/GFNI" + + + +cpuid +simd_4x32 +simd_avx2 +simd_avx512 + + + +gfni +avx512 + diff -Nru botan3-3.7.1+dfsg/src/lib/block/sm4/sm4_avx512/sm4_avx512.cpp botan3-3.12.0+dfsg/src/lib/block/sm4/sm4_avx512/sm4_avx512.cpp --- botan3-3.7.1+dfsg/src/lib/block/sm4/sm4_avx512/sm4_avx512.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/sm4/sm4_avx512/sm4_avx512.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,302 @@ +/* +* (C) 2025 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include + +namespace Botan { + +namespace SM4_AVX512_GFNI { + +namespace { + +template +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512_GFNI SIMD_T sm4_sbox(const SIMD_T& x) { + /* + * See https://eprint.iacr.org/2022/1154 section 3.3 for details on + * how this works + */ + constexpr uint64_t pre_a = gfni_matrix(R"( + 0 0 1 1 0 0 1 0 + 0 0 0 1 0 1 0 0 + 1 0 1 1 1 1 1 0 + 1 0 0 1 1 1 0 1 + 0 1 0 1 1 0 0 0 + 0 1 0 0 0 1 0 0 + 0 0 0 0 1 0 1 0 + 1 0 1 1 1 0 1 0)"); + + constexpr uint8_t pre_c = 0b00111110; + + constexpr uint64_t post_a = gfni_matrix(R"( + 1 1 0 0 1 1 1 1 + 1 1 0 1 0 1 0 1 + 0 0 1 0 1 1 0 0 + 1 0 0 1 0 1 0 1 + 0 0 1 0 1 1 1 0 + 0 1 1 0 0 1 0 1 + 1 0 1 0 1 1 0 1 + 1 0 0 1 0 0 0 1)"); + + constexpr uint8_t post_c = 0b11010011; + + auto y = gf2p8affine(x); + return gf2p8affineinv(y); +} + +template +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512_GFNI SIMD_T sm4_f(const SIMD_T& x) { + const auto sx = sm4_sbox(x); + return sx ^ sx.template rotl<2>() ^ sx.template rotl<10>() ^ sx.template rotl<18>() ^ sx.template rotl<24>(); +} + +template +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512_GFNI void encrypt(const uint8_t ptext[16 * 4 * M], + uint8_t ctext[16 * 4 * M], + std::span RK) { + SIMD_T B0 = SIMD_T::load_be(ptext); + SIMD_T B1 = SIMD_T::load_be(ptext + 16 * M); + SIMD_T B2 = SIMD_T::load_be(ptext + 16 * 2 * M); + SIMD_T B3 = SIMD_T::load_be(ptext + 16 * 3 * M); + + SIMD_T::transpose(B0, B1, B2, B3); + + B0 = B0.rev_words(); + B1 = B1.rev_words(); + B2 = B2.rev_words(); + B3 = B3.rev_words(); + + for(size_t j = 0; j != 8; ++j) { + B0 ^= sm4_f(B1 ^ B2 ^ B3 ^ SIMD_T::splat(RK[4 * j])); + B1 ^= sm4_f(B2 ^ B3 ^ B0 ^ SIMD_T::splat(RK[4 * j + 1])); + B2 ^= sm4_f(B3 ^ B0 ^ B1 ^ SIMD_T::splat(RK[4 * j + 2])); + B3 ^= sm4_f(B0 ^ B1 ^ B2 ^ SIMD_T::splat(RK[4 * j + 3])); + } + + SIMD_T::transpose(B0, B1, B2, B3); + + B3.rev_words().store_be(ctext); + B2.rev_words().store_be(ctext + 16 * M); + B1.rev_words().store_be(ctext + 16 * 2 * M); + B0.rev_words().store_be(ctext + 16 * 3 * M); +} + +template +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512_GFNI void encrypt_x2(const uint8_t ptext[32 * 4 * M], + uint8_t ctext[32 * 4 * M], + std::span RK) { + SIMD_T B0 = SIMD_T::load_be(ptext); + SIMD_T B1 = SIMD_T::load_be(ptext + 16 * M); + SIMD_T B2 = SIMD_T::load_be(ptext + 16 * 2 * M); + SIMD_T B3 = SIMD_T::load_be(ptext + 16 * 3 * M); + + SIMD_T B4 = SIMD_T::load_be(ptext + 16 * 4 * M); + SIMD_T B5 = SIMD_T::load_be(ptext + 16 * 5 * M); + SIMD_T B6 = SIMD_T::load_be(ptext + 16 * 6 * M); + SIMD_T B7 = SIMD_T::load_be(ptext + 16 * 7 * M); + + SIMD_T::transpose(B0, B1, B2, B3); + SIMD_T::transpose(B4, B5, B6, B7); + + B0 = B0.rev_words(); + B1 = B1.rev_words(); + B2 = B2.rev_words(); + B3 = B3.rev_words(); + + B4 = B4.rev_words(); + B5 = B5.rev_words(); + B6 = B6.rev_words(); + B7 = B7.rev_words(); + + for(size_t j = 0; j != 8; ++j) { + B0 ^= sm4_f(B1 ^ B2 ^ B3 ^ SIMD_T::splat(RK[4 * j])); + B4 ^= sm4_f(B5 ^ B6 ^ B7 ^ SIMD_T::splat(RK[4 * j])); + + B1 ^= sm4_f(B2 ^ B3 ^ B0 ^ SIMD_T::splat(RK[4 * j + 1])); + B5 ^= sm4_f(B6 ^ B7 ^ B4 ^ SIMD_T::splat(RK[4 * j + 1])); + + B2 ^= sm4_f(B3 ^ B0 ^ B1 ^ SIMD_T::splat(RK[4 * j + 2])); + B6 ^= sm4_f(B7 ^ B4 ^ B5 ^ SIMD_T::splat(RK[4 * j + 2])); + + B3 ^= sm4_f(B0 ^ B1 ^ B2 ^ SIMD_T::splat(RK[4 * j + 3])); + B7 ^= sm4_f(B4 ^ B5 ^ B6 ^ SIMD_T::splat(RK[4 * j + 3])); + } + + SIMD_T::transpose(B0, B1, B2, B3); + SIMD_T::transpose(B4, B5, B6, B7); + + B3.rev_words().store_be(ctext); + B2.rev_words().store_be(ctext + 16 * M); + B1.rev_words().store_be(ctext + 16 * 2 * M); + B0.rev_words().store_be(ctext + 16 * 3 * M); + + B7.rev_words().store_be(ctext + 16 * 4 * M); + B6.rev_words().store_be(ctext + 16 * 5 * M); + B5.rev_words().store_be(ctext + 16 * 6 * M); + B4.rev_words().store_be(ctext + 16 * 7 * M); +} + +template +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512_GFNI void decrypt(const uint8_t ctext[16 * 4 * M], + uint8_t ptext[16 * 4 * M], + std::span RK) { + SIMD_T B0 = SIMD_T::load_be(ctext); + SIMD_T B1 = SIMD_T::load_be(ctext + 16 * M); + SIMD_T B2 = SIMD_T::load_be(ctext + 16 * 2 * M); + SIMD_T B3 = SIMD_T::load_be(ctext + 16 * 3 * M); + + SIMD_T::transpose(B0, B1, B2, B3); + + B0 = B0.rev_words(); + B1 = B1.rev_words(); + B2 = B2.rev_words(); + B3 = B3.rev_words(); + + for(size_t j = 0; j != 8; ++j) { + B0 ^= sm4_f(B1 ^ B2 ^ B3 ^ SIMD_T::splat(RK[32 - (4 * j + 1)])); + B1 ^= sm4_f(B2 ^ B3 ^ B0 ^ SIMD_T::splat(RK[32 - (4 * j + 2)])); + B2 ^= sm4_f(B3 ^ B0 ^ B1 ^ SIMD_T::splat(RK[32 - (4 * j + 3)])); + B3 ^= sm4_f(B0 ^ B1 ^ B2 ^ SIMD_T::splat(RK[32 - (4 * j + 4)])); + } + + SIMD_T::transpose(B0, B1, B2, B3); + + B3.rev_words().store_be(ptext); + B2.rev_words().store_be(ptext + 16 * M); + B1.rev_words().store_be(ptext + 16 * 2 * M); + B0.rev_words().store_be(ptext + 16 * 3 * M); +} + +template +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512_GFNI void decrypt_x2(const uint8_t ctext[32 * 4 * M], + uint8_t ptext[32 * 4 * M], + std::span RK) { + SIMD_T B0 = SIMD_T::load_be(ctext); + SIMD_T B1 = SIMD_T::load_be(ctext + 16 * M); + SIMD_T B2 = SIMD_T::load_be(ctext + 16 * 2 * M); + SIMD_T B3 = SIMD_T::load_be(ctext + 16 * 3 * M); + + SIMD_T B4 = SIMD_T::load_be(ctext + 16 * 4 * M); + SIMD_T B5 = SIMD_T::load_be(ctext + 16 * 5 * M); + SIMD_T B6 = SIMD_T::load_be(ctext + 16 * 6 * M); + SIMD_T B7 = SIMD_T::load_be(ctext + 16 * 7 * M); + + SIMD_T::transpose(B0, B1, B2, B3); + SIMD_T::transpose(B4, B5, B6, B7); + + B0 = B0.rev_words(); + B1 = B1.rev_words(); + B2 = B2.rev_words(); + B3 = B3.rev_words(); + + B4 = B4.rev_words(); + B5 = B5.rev_words(); + B6 = B6.rev_words(); + B7 = B7.rev_words(); + + for(size_t j = 0; j != 8; ++j) { + B0 ^= sm4_f(B1 ^ B2 ^ B3 ^ SIMD_T::splat(RK[32 - (4 * j + 1)])); + B4 ^= sm4_f(B5 ^ B6 ^ B7 ^ SIMD_T::splat(RK[32 - (4 * j + 1)])); + + B1 ^= sm4_f(B2 ^ B3 ^ B0 ^ SIMD_T::splat(RK[32 - (4 * j + 2)])); + B5 ^= sm4_f(B6 ^ B7 ^ B4 ^ SIMD_T::splat(RK[32 - (4 * j + 2)])); + + B2 ^= sm4_f(B3 ^ B0 ^ B1 ^ SIMD_T::splat(RK[32 - (4 * j + 3)])); + B6 ^= sm4_f(B7 ^ B4 ^ B5 ^ SIMD_T::splat(RK[32 - (4 * j + 3)])); + + B3 ^= sm4_f(B0 ^ B1 ^ B2 ^ SIMD_T::splat(RK[32 - (4 * j + 4)])); + B7 ^= sm4_f(B4 ^ B5 ^ B6 ^ SIMD_T::splat(RK[32 - (4 * j + 4)])); + } + + SIMD_T::transpose(B0, B1, B2, B3); + SIMD_T::transpose(B4, B5, B6, B7); + + B3.rev_words().store_be(ptext); + B2.rev_words().store_be(ptext + 16 * M); + B1.rev_words().store_be(ptext + 16 * 2 * M); + B0.rev_words().store_be(ptext + 16 * 3 * M); + + B7.rev_words().store_be(ptext + 16 * 4 * M); + B6.rev_words().store_be(ptext + 16 * 5 * M); + B5.rev_words().store_be(ptext + 16 * 6 * M); + B4.rev_words().store_be(ptext + 16 * 7 * M); +} + +} // namespace + +} // namespace SM4_AVX512_GFNI + +void BOTAN_FN_ISA_AVX512_GFNI SM4::sm4_avx512_gfni_encrypt(const uint8_t ptext[], + uint8_t ctext[], + size_t blocks) const { + while(blocks >= 32) { + SM4_AVX512_GFNI::encrypt_x2(ptext, ctext, m_RK); + ptext += 16 * 32; + ctext += 16 * 32; + blocks -= 32; + } + + while(blocks >= 16) { + SM4_AVX512_GFNI::encrypt(ptext, ctext, m_RK); + ptext += 16 * 16; + ctext += 16 * 16; + blocks -= 16; + } + + while(blocks >= 8) { + SM4_AVX512_GFNI::encrypt(ptext, ctext, m_RK); + ptext += 16 * 8; + ctext += 16 * 8; + blocks -= 8; + } + + if(blocks > 0) { + uint8_t pbuf[16 * 8] = {0}; + uint8_t cbuf[16 * 8] = {0}; + copy_mem(pbuf, ptext, blocks * 16); + SM4_AVX512_GFNI::encrypt(pbuf, cbuf, m_RK); + copy_mem(ctext, cbuf, blocks * 16); + } +} + +void BOTAN_FN_ISA_AVX512_GFNI SM4::sm4_avx512_gfni_decrypt(const uint8_t ctext[], + uint8_t ptext[], + size_t blocks) const { + while(blocks >= 32) { + SM4_AVX512_GFNI::decrypt_x2(ctext, ptext, m_RK); + ptext += 16 * 32; + ctext += 16 * 32; + blocks -= 32; + } + + while(blocks >= 16) { + SM4_AVX512_GFNI::decrypt(ctext, ptext, m_RK); + ptext += 16 * 16; + ctext += 16 * 16; + blocks -= 16; + } + + while(blocks >= 8) { + SM4_AVX512_GFNI::decrypt(ctext, ptext, m_RK); + ptext += 16 * 8; + ctext += 16 * 8; + blocks -= 8; + } + + if(blocks > 0) { + uint8_t cbuf[16 * 8] = {0}; + uint8_t pbuf[16 * 8] = {0}; + copy_mem(cbuf, ctext, blocks * 16); + SM4_AVX512_GFNI::decrypt(cbuf, pbuf, m_RK); + copy_mem(ptext, pbuf, blocks * 16); + } +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/block/sm4/sm4_gfni/info.txt botan3-3.12.0+dfsg/src/lib/block/sm4/sm4_gfni/info.txt --- botan3-3.7.1+dfsg/src/lib/block/sm4/sm4_gfni/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/sm4/sm4_gfni/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + SM4_GFNI -> 20240803 - + name -> "SM4 GFNI" @@ -8,6 +8,7 @@ +cpuid simd_avx2 diff -Nru botan3-3.7.1+dfsg/src/lib/block/sm4/sm4_gfni/sm4_gfni.cpp botan3-3.12.0+dfsg/src/lib/block/sm4/sm4_gfni/sm4_gfni.cpp --- botan3-3.7.1+dfsg/src/lib/block/sm4/sm4_gfni/sm4_gfni.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/sm4/sm4_gfni/sm4_gfni.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,6 +6,8 @@ #include +#include +#include #include #include @@ -13,7 +15,7 @@ namespace { -BOTAN_FUNC_ISA_INLINE(BOTAN_GFNI_ISA) SIMD_8x32 sm4_sbox(const SIMD_8x32& x) { +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX2_GFNI SIMD_8x32 sm4_sbox(const SIMD_8x32& x) { /* * See https://eprint.iacr.org/2022/1154 section 3.3 for details on * how this works @@ -46,13 +48,14 @@ return gf2p8affineinv(y); } -BOTAN_FUNC_ISA_INLINE(BOTAN_GFNI_ISA) SIMD_8x32 sm4_f(const SIMD_8x32& x) { - SIMD_8x32 sx = sm4_sbox(x); +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX2_GFNI SIMD_8x32 sm4_f(const SIMD_8x32& x) { + const SIMD_8x32 sx = sm4_sbox(x); return sx ^ sx.rotl<2>() ^ sx.rotl<10>() ^ sx.rotl<18>() ^ sx.rotl<24>(); } -BOTAN_FUNC_ISA_INLINE(BOTAN_GFNI_ISA) -void sm4_gfni_encrypt_8(const uint8_t ptext[8 * 16], uint8_t ctext[8 * 16], std::span RK) { +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX2_GFNI void sm4_gfni_encrypt_8(const uint8_t ptext[8 * 16], + uint8_t ctext[8 * 16], + std::span RK) { SIMD_8x32 B0 = SIMD_8x32::load_be(ptext); SIMD_8x32 B1 = SIMD_8x32::load_be(ptext + 16 * 2); SIMD_8x32 B2 = SIMD_8x32::load_be(ptext + 16 * 4); @@ -80,8 +83,9 @@ B0.rev_words().store_be(ctext + 16 * 6); } -BOTAN_FUNC_ISA_INLINE(BOTAN_GFNI_ISA) -void sm4_gfni_decrypt_8(const uint8_t ctext[8 * 16], uint8_t ptext[8 * 16], std::span RK) { +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX2_GFNI void sm4_gfni_decrypt_8(const uint8_t ctext[8 * 16], + uint8_t ptext[8 * 16], + std::span RK) { SIMD_8x32 B0 = SIMD_8x32::load_be(ctext); SIMD_8x32 B1 = SIMD_8x32::load_be(ctext + 16 * 2); SIMD_8x32 B2 = SIMD_8x32::load_be(ctext + 16 * 4); @@ -111,7 +115,7 @@ } // namespace -void BOTAN_FUNC_ISA("gfni,avx2") SM4::sm4_gfni_encrypt(const uint8_t ptext[], uint8_t ctext[], size_t blocks) const { +void BOTAN_FN_ISA_AVX2_GFNI SM4::sm4_gfni_encrypt(const uint8_t ptext[], uint8_t ctext[], size_t blocks) const { while(blocks >= 8) { sm4_gfni_encrypt_8(ptext, ctext, m_RK); ptext += 16 * 8; @@ -128,7 +132,7 @@ } } -void BOTAN_FUNC_ISA("gfni,avx2") SM4::sm4_gfni_decrypt(const uint8_t ctext[], uint8_t ptext[], size_t blocks) const { +void BOTAN_FN_ISA_AVX2_GFNI SM4::sm4_gfni_decrypt(const uint8_t ctext[], uint8_t ptext[], size_t blocks) const { while(blocks >= 8) { sm4_gfni_decrypt_8(ctext, ptext, m_RK); ptext += 16 * 8; diff -Nru botan3-3.7.1+dfsg/src/lib/block/sm4/sm4_hwaes/info.txt botan3-3.12.0+dfsg/src/lib/block/sm4/sm4_hwaes/info.txt --- botan3-3.7.1+dfsg/src/lib/block/sm4/sm4_hwaes/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/sm4/sm4_hwaes/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,12 @@ + +SM4_HWAES -> 20260322 + + + +name -> "SM4 using hardware AES instructions" + + + +cpuid +simd_hwaes + diff -Nru botan3-3.7.1+dfsg/src/lib/block/sm4/sm4_hwaes/sm4_hwaes.cpp botan3-3.12.0+dfsg/src/lib/block/sm4/sm4_hwaes/sm4_hwaes.cpp --- botan3-3.7.1+dfsg/src/lib/block/sm4/sm4_hwaes/sm4_hwaes.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/sm4/sm4_hwaes/sm4_hwaes.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,274 @@ +/* +* (C) 2026 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include + +namespace Botan { + +namespace { + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_HWAES SIMD_4x32 sm4_sbox(const SIMD_4x32& x) { + /* + * The SM4 sbox is, like the AES sbox, based on inversion in GF(2^8) plus an + * affine transformation. + * + * See + * - sections 3.1 and 3.3 + * - + * - + * describes a similar approach for implementing Camellia in section 4.4 + */ + + constexpr uint64_t pre_a = gfni_matrix(R"( + 0 0 1 1 0 0 1 0 + 0 0 0 1 0 1 0 0 + 1 0 1 1 1 1 1 0 + 1 0 0 1 1 1 0 1 + 0 1 0 1 1 0 0 0 + 0 1 0 0 0 1 0 0 + 0 0 0 0 1 0 1 0 + 1 0 1 1 1 0 1 0)"); + constexpr uint8_t pre_c = 0b00111110; + + constexpr uint64_t post_a = gfni_matrix(R"( + 1 1 0 0 1 1 1 1 + 1 1 0 1 0 1 0 1 + 0 0 1 0 1 1 0 0 + 1 0 0 1 0 1 0 1 + 0 0 1 0 1 1 1 0 + 0 1 1 0 0 1 0 1 + 1 0 1 0 1 1 0 1 + 1 0 0 1 0 0 0 1)"); + constexpr uint8_t post_c = 0b11010011; + + constexpr auto pre = Gf2AffineTransformation(pre_a, pre_c); + constexpr auto post = Gf2AffineTransformation::post_sbox(post_a, post_c); + + return post.affine_transform(hw_aes_sbox(pre.affine_transform(x))); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_HWAES SIMD_4x32 sm4_f(const SIMD_4x32& x) { + const auto sx = sm4_sbox(x); + // L linear transform + return sx ^ sx.rotl<2>() ^ sx.rotl<10>() ^ sx.rotl<18>() ^ sx.rotl<24>(); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_HWAES void sm4_hwaes_encrypt_4(const uint8_t ptext[4 * 16], + uint8_t ctext[4 * 16], + std::span RK) { + auto B0 = SIMD_4x32::load_be(ptext + 16 * 0); + auto B1 = SIMD_4x32::load_be(ptext + 16 * 1); + auto B2 = SIMD_4x32::load_be(ptext + 16 * 2); + auto B3 = SIMD_4x32::load_be(ptext + 16 * 3); + + SIMD_4x32::transpose(B0, B1, B2, B3); + + for(size_t j = 0; j != 8; ++j) { + const auto K0 = SIMD_4x32::splat(RK[4 * j]); + const auto K1 = SIMD_4x32::splat(RK[4 * j + 1]); + const auto K2 = SIMD_4x32::splat(RK[4 * j + 2]); + const auto K3 = SIMD_4x32::splat(RK[4 * j + 3]); + B0 ^= sm4_f(B1 ^ B2 ^ B3 ^ K0); + B1 ^= sm4_f(B2 ^ B3 ^ B0 ^ K1); + B2 ^= sm4_f(B3 ^ B0 ^ B1 ^ K2); + B3 ^= sm4_f(B0 ^ B1 ^ B2 ^ K3); + } + + // SM4 reverses word order + SIMD_4x32::transpose(B3, B2, B1, B0); + + B3.store_be(ctext + 16 * 0); + B2.store_be(ctext + 16 * 1); + B1.store_be(ctext + 16 * 2); + B0.store_be(ctext + 16 * 3); +} + +// Same as sm4_hwaes_encrypt_4 except interleaved 2x +BOTAN_FORCE_INLINE BOTAN_FN_ISA_HWAES void sm4_hwaes_encrypt_8(const uint8_t ptext[8 * 16], + uint8_t ctext[8 * 16], + std::span RK) { + auto B0 = SIMD_4x32::load_be(ptext + 16 * 0); + auto B1 = SIMD_4x32::load_be(ptext + 16 * 1); + auto B2 = SIMD_4x32::load_be(ptext + 16 * 2); + auto B3 = SIMD_4x32::load_be(ptext + 16 * 3); + auto B4 = SIMD_4x32::load_be(ptext + 16 * 4); + auto B5 = SIMD_4x32::load_be(ptext + 16 * 5); + auto B6 = SIMD_4x32::load_be(ptext + 16 * 6); + auto B7 = SIMD_4x32::load_be(ptext + 16 * 7); + + SIMD_4x32::transpose(B0, B1, B2, B3); + SIMD_4x32::transpose(B4, B5, B6, B7); + + for(size_t j = 0; j != 8; ++j) { + const auto K0 = SIMD_4x32::splat(RK[4 * j]); + const auto K1 = SIMD_4x32::splat(RK[4 * j + 1]); + const auto K2 = SIMD_4x32::splat(RK[4 * j + 2]); + const auto K3 = SIMD_4x32::splat(RK[4 * j + 3]); + + B0 ^= sm4_f(B1 ^ B2 ^ B3 ^ K0); + B4 ^= sm4_f(B5 ^ B6 ^ B7 ^ K0); + + B1 ^= sm4_f(B2 ^ B3 ^ B0 ^ K1); + B5 ^= sm4_f(B6 ^ B7 ^ B4 ^ K1); + + B2 ^= sm4_f(B3 ^ B0 ^ B1 ^ K2); + B6 ^= sm4_f(B7 ^ B4 ^ B5 ^ K2); + + B3 ^= sm4_f(B0 ^ B1 ^ B2 ^ K3); + B7 ^= sm4_f(B4 ^ B5 ^ B6 ^ K3); + } + + // SM4 reverses word order + SIMD_4x32::transpose(B3, B2, B1, B0); + SIMD_4x32::transpose(B7, B6, B5, B4); + + B3.store_be(ctext + 16 * 0); + B2.store_be(ctext + 16 * 1); + B1.store_be(ctext + 16 * 2); + B0.store_be(ctext + 16 * 3); + + B7.store_be(ctext + 16 * 4); + B6.store_be(ctext + 16 * 5); + B5.store_be(ctext + 16 * 6); + B4.store_be(ctext + 16 * 7); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_HWAES void sm4_hwaes_decrypt_4(const uint8_t ctext[4 * 16], + uint8_t ptext[4 * 16], + std::span RK) { + auto B0 = SIMD_4x32::load_be(ctext + 16 * 0); + auto B1 = SIMD_4x32::load_be(ctext + 16 * 1); + auto B2 = SIMD_4x32::load_be(ctext + 16 * 2); + auto B3 = SIMD_4x32::load_be(ctext + 16 * 3); + + SIMD_4x32::transpose(B0, B1, B2, B3); + + for(size_t j = 0; j != 8; ++j) { + const auto K0 = SIMD_4x32::splat(RK[32 - (4 * j + 1)]); + const auto K1 = SIMD_4x32::splat(RK[32 - (4 * j + 2)]); + const auto K2 = SIMD_4x32::splat(RK[32 - (4 * j + 3)]); + const auto K3 = SIMD_4x32::splat(RK[32 - (4 * j + 4)]); + B0 ^= sm4_f(B1 ^ B2 ^ B3 ^ K0); + B1 ^= sm4_f(B2 ^ B3 ^ B0 ^ K1); + B2 ^= sm4_f(B3 ^ B0 ^ B1 ^ K2); + B3 ^= sm4_f(B0 ^ B1 ^ B2 ^ K3); + } + + // SM4 reverses word order + SIMD_4x32::transpose(B3, B2, B1, B0); + + B3.store_be(ptext + 16 * 0); + B2.store_be(ptext + 16 * 1); + B1.store_be(ptext + 16 * 2); + B0.store_be(ptext + 16 * 3); +} + +// Same as sm4_hwaes_decrypt_4 except interleaved 2x +BOTAN_FORCE_INLINE BOTAN_FN_ISA_HWAES void sm4_hwaes_decrypt_8(const uint8_t ctext[8 * 16], + uint8_t ptext[8 * 16], + std::span RK) { + auto B0 = SIMD_4x32::load_be(ctext + 16 * 0); + auto B1 = SIMD_4x32::load_be(ctext + 16 * 1); + auto B2 = SIMD_4x32::load_be(ctext + 16 * 2); + auto B3 = SIMD_4x32::load_be(ctext + 16 * 3); + auto B4 = SIMD_4x32::load_be(ctext + 16 * 4); + auto B5 = SIMD_4x32::load_be(ctext + 16 * 5); + auto B6 = SIMD_4x32::load_be(ctext + 16 * 6); + auto B7 = SIMD_4x32::load_be(ctext + 16 * 7); + + SIMD_4x32::transpose(B0, B1, B2, B3); + SIMD_4x32::transpose(B4, B5, B6, B7); + + for(size_t j = 0; j != 8; ++j) { + const auto K0 = SIMD_4x32::splat(RK[32 - (4 * j + 1)]); + const auto K1 = SIMD_4x32::splat(RK[32 - (4 * j + 2)]); + const auto K2 = SIMD_4x32::splat(RK[32 - (4 * j + 3)]); + const auto K3 = SIMD_4x32::splat(RK[32 - (4 * j + 4)]); + + B0 ^= sm4_f(B1 ^ B2 ^ B3 ^ K0); + B4 ^= sm4_f(B5 ^ B6 ^ B7 ^ K0); + + B1 ^= sm4_f(B2 ^ B3 ^ B0 ^ K1); + B5 ^= sm4_f(B6 ^ B7 ^ B4 ^ K1); + + B2 ^= sm4_f(B3 ^ B0 ^ B1 ^ K2); + B6 ^= sm4_f(B7 ^ B4 ^ B5 ^ K2); + + B3 ^= sm4_f(B0 ^ B1 ^ B2 ^ K3); + B7 ^= sm4_f(B4 ^ B5 ^ B6 ^ K3); + } + + // SM4 reverses word order + SIMD_4x32::transpose(B3, B2, B1, B0); + SIMD_4x32::transpose(B7, B6, B5, B4); + + B3.store_be(ptext + 16 * 0); + B2.store_be(ptext + 16 * 1); + B1.store_be(ptext + 16 * 2); + B0.store_be(ptext + 16 * 3); + + B7.store_be(ptext + 16 * 4); + B6.store_be(ptext + 16 * 5); + B5.store_be(ptext + 16 * 6); + B4.store_be(ptext + 16 * 7); +} + +} // namespace + +void BOTAN_FN_ISA_HWAES SM4::sm4_hwaes_encrypt(const uint8_t ptext[], uint8_t ctext[], size_t blocks) const { + while(blocks >= 8) { + sm4_hwaes_encrypt_8(ptext, ctext, m_RK); + ptext += 16 * 8; + ctext += 16 * 8; + blocks -= 8; + } + + while(blocks >= 4) { + sm4_hwaes_encrypt_4(ptext, ctext, m_RK); + ptext += 16 * 4; + ctext += 16 * 4; + blocks -= 4; + } + + if(blocks > 0) { + uint8_t pbuf[4 * 16] = {0}; + uint8_t cbuf[4 * 16] = {0}; + copy_mem(pbuf, ptext, blocks * 16); + sm4_hwaes_encrypt_4(pbuf, cbuf, m_RK); + copy_mem(ctext, cbuf, blocks * 16); + } +} + +void BOTAN_FN_ISA_HWAES SM4::sm4_hwaes_decrypt(const uint8_t ctext[], uint8_t ptext[], size_t blocks) const { + while(blocks >= 8) { + sm4_hwaes_decrypt_8(ctext, ptext, m_RK); + ptext += 16 * 8; + ctext += 16 * 8; + blocks -= 8; + } + + while(blocks >= 4) { + sm4_hwaes_decrypt_4(ctext, ptext, m_RK); + ptext += 16 * 4; + ctext += 16 * 4; + blocks -= 4; + } + + if(blocks > 0) { + uint8_t cbuf[4 * 16] = {0}; + uint8_t pbuf[4 * 16] = {0}; + copy_mem(cbuf, ctext, blocks * 16); + sm4_hwaes_decrypt_4(cbuf, pbuf, m_RK); + copy_mem(ptext, pbuf, blocks * 16); + } +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/block/sm4/sm4_x86/info.txt botan3-3.12.0+dfsg/src/lib/block/sm4/sm4_x86/info.txt --- botan3-3.7.1+dfsg/src/lib/block/sm4/sm4_x86/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/sm4/sm4_x86/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,23 @@ + +SM4_X86 -> 20250311 + + + +name -> "SM4 x86" +brief -> "SM4 using Intel SM4 extension" + + + +cpuid +simd_avx2 + + + +sm4 + + + +gcc:14 +clang:17 +msvc + diff -Nru botan3-3.7.1+dfsg/src/lib/block/sm4/sm4_x86/sm4_x86.cpp botan3-3.12.0+dfsg/src/lib/block/sm4/sm4_x86/sm4_x86.cpp --- botan3-3.7.1+dfsg/src/lib/block/sm4/sm4_x86/sm4_x86.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/sm4/sm4_x86/sm4_x86.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,142 @@ +/* +* (C) 2025 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include + +namespace Botan { + +namespace { + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX2_SM4 SIMD_8x32 sm4_x86_rnds4(const SIMD_8x32& b, const SIMD_8x32& k) { + // NOLINTNEXTLINE(portability-simd-intrinsics) + return SIMD_8x32(_mm256_sm4rnds4_epi32(b.raw(), k.raw())); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX2_SM4 void sm4_x86_encrypt_x2(uint8_t out[2 * 16], + const uint8_t inp[2 * 16], + std::span RK) { + auto B0 = SIMD_8x32::load_be(inp); + + for(size_t i = 0; i != 8; ++i) { + const auto RK_i = SIMD_8x32::load_le128(&RK[4 * i]); + B0 = sm4_x86_rnds4(B0, RK_i); + } + + B0.reverse().store_le(out); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX2_SM4 void sm4_x86_encrypt_x8(uint8_t out[8 * 16], + const uint8_t inp[8 * 16], + std::span RK) { + auto B0 = SIMD_8x32::load_be(inp); + auto B1 = SIMD_8x32::load_be(inp + 32); + auto B2 = SIMD_8x32::load_be(inp + 64); + auto B3 = SIMD_8x32::load_be(inp + 96); + + for(size_t i = 0; i != 8; ++i) { + auto RK_i = SIMD_8x32::load_le128(&RK[4 * i]); + B0 = sm4_x86_rnds4(B0, RK_i); + B1 = sm4_x86_rnds4(B1, RK_i); + B2 = sm4_x86_rnds4(B2, RK_i); + B3 = sm4_x86_rnds4(B3, RK_i); + } + + B0.reverse().store_le(out); + B1.reverse().store_le(out + 32); + B2.reverse().store_le(out + 64); + B3.reverse().store_le(out + 96); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX2_SM4 void sm4_x86_decrypt_x2(uint8_t out[2 * 16], + const uint8_t inp[2 * 16], + std::span RK) { + auto B0 = SIMD_8x32::load_be(inp); + + for(size_t i = 0; i != 8; ++i) { + auto RK_i = SIMD_8x32::load_le128(&RK[28 - 4 * i]).rev_words(); + B0 = sm4_x86_rnds4(B0, RK_i); + } + + B0.reverse().store_le(out); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX2_SM4 void sm4_x86_decrypt_x8(uint8_t out[8 * 16], + const uint8_t inp[8 * 16], + std::span RK) { + auto B0 = SIMD_8x32::load_be(inp); + auto B1 = SIMD_8x32::load_be(inp + 32); + auto B2 = SIMD_8x32::load_be(inp + 64); + auto B3 = SIMD_8x32::load_be(inp + 96); + + for(size_t i = 0; i != 8; ++i) { + auto RK_i = SIMD_8x32::load_le128(&RK[28 - 4 * i]).rev_words(); + B0 = sm4_x86_rnds4(B0, RK_i); + B1 = sm4_x86_rnds4(B1, RK_i); + B2 = sm4_x86_rnds4(B2, RK_i); + B3 = sm4_x86_rnds4(B3, RK_i); + } + + B0.reverse().store_le(out); + B1.reverse().store_le(out + 32); + B2.reverse().store_le(out + 64); + B3.reverse().store_le(out + 96); +} + +} // namespace + +void BOTAN_FN_ISA_AVX2_SM4 SM4::sm4_x86_encrypt(const uint8_t inp[], uint8_t out[], size_t blocks) const { + while(blocks >= 8) { + sm4_x86_encrypt_x8(out, inp, m_RK); + inp += 8 * 16; + out += 8 * 16; + blocks -= 8; + } + + while(blocks >= 2) { + sm4_x86_encrypt_x2(out, inp, m_RK); + inp += 2 * 16; + out += 2 * 16; + blocks -= 2; + } + + if(blocks > 0) { + uint8_t ibuf[2 * 16] = {0}; + uint8_t obuf[2 * 16] = {0}; + copy_mem(ibuf, inp, blocks * 16); + sm4_x86_encrypt_x2(obuf, ibuf, m_RK); + copy_mem(out, obuf, blocks * 16); + } +} + +void BOTAN_FN_ISA_AVX2_SM4 SM4::sm4_x86_decrypt(const uint8_t inp[], uint8_t out[], size_t blocks) const { + while(blocks >= 8) { + sm4_x86_decrypt_x8(out, inp, m_RK); + inp += 8 * 16; + out += 8 * 16; + blocks -= 8; + } + + while(blocks >= 2) { + sm4_x86_decrypt_x2(out, inp, m_RK); + inp += 2 * 16; + out += 2 * 16; + blocks -= 2; + } + + if(blocks > 0) { + uint8_t ibuf[2 * 16] = {0}; + uint8_t obuf[2 * 16] = {0}; + copy_mem(ibuf, inp, blocks * 16); + sm4_x86_decrypt_x2(obuf, ibuf, m_RK); + copy_mem(out, obuf, blocks * 16); + } +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/block/threefish_512/threefish_512.cpp botan3-3.12.0+dfsg/src/lib/block/threefish_512/threefish_512.cpp --- botan3-3.7.1+dfsg/src/lib/block/threefish_512/threefish_512.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/threefish_512/threefish_512.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,7 +7,6 @@ #include -#include #include #include @@ -51,7 +50,7 @@ X3 -= X7; } -class Key_Inserter { +class Key_Inserter final { public: Key_Inserter(const uint64_t* K, const uint64_t* T) : m_K(K), m_T(T) {} @@ -201,7 +200,14 @@ const Key_Inserter key(m_K.data(), m_T.data()); for(size_t i = 0; i < blocks; ++i) { - uint64_t X0, X1, X2, X3, X4, X5, X6, X7; + uint64_t X0 = 0; + uint64_t X1 = 0; + uint64_t X2 = 0; + uint64_t X3 = 0; + uint64_t X4 = 0; + uint64_t X5 = 0; + uint64_t X6 = 0; + uint64_t X7 = 0; load_le(in + BLOCK_SIZE * i, X0, X1, X2, X3, X4, X5, X6, X7); key.e_add(0, X0, X1, X2, X3, X4, X5, X6, X7); @@ -228,7 +234,14 @@ const Key_Inserter key(m_K.data(), m_T.data()); for(size_t i = 0; i < blocks; ++i) { - uint64_t X0, X1, X2, X3, X4, X5, X6, X7; + uint64_t X0 = 0; + uint64_t X1 = 0; + uint64_t X2 = 0; + uint64_t X3 = 0; + uint64_t X4 = 0; + uint64_t X5 = 0; + uint64_t X6 = 0; + uint64_t X7 = 0; load_le(in + BLOCK_SIZE * i, X0, X1, X2, X3, X4, X5, X6, X7); key.d_add(18, X0, X1, X2, X3, X4, X5, X6, X7); diff -Nru botan3-3.7.1+dfsg/src/lib/block/threefish_512/threefish_512.h botan3-3.12.0+dfsg/src/lib/block/threefish_512/threefish_512.h --- botan3-3.7.1+dfsg/src/lib/block/threefish_512/threefish_512.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/threefish_512/threefish_512.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,6 +9,7 @@ #define BOTAN_THREEFISH_512_H_ #include +#include namespace Botan { diff -Nru botan3-3.7.1+dfsg/src/lib/block/twofish/twofish.cpp botan3-3.12.0+dfsg/src/lib/block/twofish/twofish.cpp --- botan3-3.7.1+dfsg/src/lib/block/twofish/twofish.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/twofish/twofish.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,22 +1,140 @@ /* * Twofish -* (C) 1999-2007,2017 Jack Lloyd -* -* The key schedule implemenation is based on a public domain -* implementation by Matthew Skala +* (C) 1999-2007,2017,2026 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ #include +#include #include #include +#if defined(BOTAN_HAS_CPUID) + #include +#endif + namespace Botan { namespace { +namespace Twofish_KS { + +// Twofish q-permutation derived from four 4-bit sboxes +// ("Twofish: A 128-Bit Block Cipher", section 4.3.5) +consteval std::array twofish_q_perm(std::array t0, + std::array t1, + std::array t2, + std::array t3) noexcept { + std::array Q = {}; + for(size_t x = 0; x != 256; ++x) { + const uint8_t a0 = static_cast((x >> 4) & 0x0F); + const uint8_t b0 = static_cast(x & 0x0F); + + const uint8_t a1 = a0 ^ b0; + const uint8_t b1 = a0 ^ ((b0 >> 1) | ((b0 & 1) << 3)) ^ ((8 * a0) & 0x0F); + + const uint8_t a2 = t0[a1]; + const uint8_t b2 = t1[b1]; + + const uint8_t a3 = a2 ^ b2; + const uint8_t b3 = a2 ^ ((b2 >> 1) | ((b2 & 1) << 3)) ^ ((8 * a2) & 0x0F); + + const uint8_t a4 = t2[a3]; + const uint8_t b4 = t3[b3]; + + Q[x] = static_cast((b4 << 4) | a4); + } + return Q; +} + +// clang-format off +alignas(256) constexpr auto Q0 = twofish_q_perm( + {8, 1, 7, 13, 6, 15, 3, 2, 0, 11, 5, 9, 14, 12, 10, 4}, + {14, 12, 11, 8, 1, 2, 3, 5, 15, 4, 10, 6, 7, 0, 9, 13}, + {11, 10, 5, 14, 6, 13, 9, 0, 12, 8, 15, 3, 2, 4, 7, 1}, + {13, 7, 15, 4, 1, 2, 6, 14, 9, 11, 3, 0, 8, 5, 12, 10}); + +alignas(256) constexpr auto Q1 = twofish_q_perm( + {2, 8, 11, 13, 15, 7, 6, 14, 3, 1, 9, 4, 0, 10, 12, 5}, + {1, 14, 2, 11, 4, 12, 3, 7, 6, 13, 10, 5, 15, 9, 0, 8}, + {4, 12, 7, 5, 1, 6, 9, 10, 0, 14, 13, 8, 2, 11, 3, 15}, + {11, 9, 5, 1, 12, 3, 13, 14, 6, 4, 7, 15, 2, 0, 8, 10}); + +// clang-format on + +/* +* MDS matrix multiplication (Twofish paper Section 4.2) +* +* MDS = [01, EF, 5B, 5B] +* [5B, EF, EF, 01] +* [EF, 5B, 01, EF] +* [EF, 01, EF, 5B] +* +* The MDS coefficients are 01, 5B, and EF. These were chosen so that +* +* 5B = 1 + 1/x^2 +* EF = 1 + 1/x + 1/x^2 +* +* in GF(2^8) mod x^8+x^6+x^5+x^3+1, where 1/x is computed by shifting +* right and conditionally XORing with 0xB4 (which is itself just the +* irreducible polynomial 0x169 shifted right by 1). +* +* This property of the MDS constants is described (briefly) in Section 7.3 +* of the Twofish paper. +*/ + +inline uint8_t mds_div_x(uint8_t q) { + return (q >> 1) ^ (CT::value_barrier(q & 1) * 0xB4); +} + +inline uint32_t mds0(uint8_t q) { + const uint8_t q_div_x = mds_div_x(q); + const uint8_t q5b = q ^ mds_div_x(q_div_x); + const uint8_t qef = q5b ^ q_div_x; + return make_uint32(qef, qef, q5b, q); +} + +inline uint32_t mds1(uint8_t q) { + const uint8_t q_div_x = mds_div_x(q); + const uint8_t q5b = q ^ mds_div_x(q_div_x); + const uint8_t qef = q5b ^ q_div_x; + return make_uint32(q, q5b, qef, qef); +} + +inline uint32_t mds2(uint8_t q) { + const uint8_t q_div_x = mds_div_x(q); + const uint8_t q5b = q ^ mds_div_x(q_div_x); + const uint8_t qef = q5b ^ q_div_x; + return make_uint32(qef, q, qef, q5b); +} + +inline uint32_t mds3(uint8_t q) { + const uint8_t q_div_x = mds_div_x(q); + const uint8_t q5b = q ^ mds_div_x(q_div_x); + const uint8_t qef = q5b ^ q_div_x; + return make_uint32(q5b, qef, q, q5b); +} + +// Constant-time GF(2^8) multiply in the RS field (irreducible polynomial 0x14D) +inline uint32_t gf_mul_rs32(uint32_t rs, uint8_t k) { + constexpr uint32_t lo_bit = 0x01010101; + constexpr uint32_t mask = 0x7F7F7F7F; + constexpr uint32_t poly = 0x4D; + + uint32_t r = 0; + for(size_t i = 0; i != 8; ++i) { + const auto k_lo = CT::Mask::expand(k & 1); + r ^= k_lo.if_set_return(rs); + rs = ((rs & mask) << 1) ^ (((rs >> 7) & lo_bit) * poly); + k >>= 1; + } + return r; +} + +} // namespace Twofish_KS + inline void TF_E( uint32_t A, uint32_t B, uint32_t& C, uint32_t& D, uint32_t RK1, uint32_t RK2, const secure_vector& SB) { uint32_t X = SB[get_byte<3>(A)] ^ SB[256 + get_byte<2>(A)] ^ SB[512 + get_byte<1>(A)] ^ SB[768 + get_byte<0>(A)]; @@ -55,9 +173,26 @@ void Twofish::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { assert_key_material_set(); +#if defined(BOTAN_HAS_TWOFISH_AVX512) + if(!m_QS.empty()) { + while(blocks >= 16) { + avx512_encrypt_16(in, out); + in += 16 * BLOCK_SIZE; + out += 16 * BLOCK_SIZE; + blocks -= 16; + } + } +#endif + while(blocks >= 2) { - uint32_t A0, B0, C0, D0; - uint32_t A1, B1, C1, D1; + uint32_t A0 = 0; + uint32_t B0 = 0; + uint32_t C0 = 0; + uint32_t D0 = 0; + uint32_t A1 = 0; + uint32_t B1 = 0; + uint32_t C1 = 0; + uint32_t D1 = 0; load_le(in, A0, B0, C0, D0, A1, B1, C1, D1); A0 ^= m_RK[0]; @@ -93,8 +228,11 @@ in += 2 * BLOCK_SIZE; } - if(blocks) { - uint32_t A, B, C, D; + if(blocks > 0) { + uint32_t A = 0; + uint32_t B = 0; + uint32_t C = 0; + uint32_t D = 0; load_le(in, A, B, C, D); A ^= m_RK[0]; @@ -122,9 +260,26 @@ void Twofish::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const { assert_key_material_set(); +#if defined(BOTAN_HAS_TWOFISH_AVX512) + if(!m_QS.empty()) { + while(blocks >= 16) { + avx512_decrypt_16(in, out); + in += 16 * BLOCK_SIZE; + out += 16 * BLOCK_SIZE; + blocks -= 16; + } + } +#endif + while(blocks >= 2) { - uint32_t A0, B0, C0, D0; - uint32_t A1, B1, C1, D1; + uint32_t A0 = 0; + uint32_t B0 = 0; + uint32_t C0 = 0; + uint32_t D0 = 0; + uint32_t A1 = 0; + uint32_t B1 = 0; + uint32_t C1 = 0; + uint32_t D1 = 0; load_le(in, A0, B0, C0, D0, A1, B1, C1, D1); A0 ^= m_RK[4]; @@ -160,8 +315,11 @@ in += 2 * BLOCK_SIZE; } - if(blocks) { - uint32_t A, B, C, D; + if(blocks > 0) { + uint32_t A = 0; + uint32_t B = 0; + uint32_t C = 0; + uint32_t D = 0; load_le(in, A, B, C, D); A ^= m_RK[4]; @@ -187,47 +345,77 @@ return !m_SB.empty(); } +std::string Twofish::provider() const { +#if defined(BOTAN_HAS_TWOFISH_AVX512) + if(CPUID::has(CPUID::Feature::AVX512, CPUID::Feature::GFNI)) { + return "avx512"; + } +#endif + return "base"; +} + +size_t Twofish::parallelism() const { +#if defined(BOTAN_HAS_TWOFISH_AVX512) + if(CPUID::has(CPUID::Feature::AVX512, CPUID::Feature::GFNI)) { + return 16; + } +#endif + return 1; +} + /* * Twofish Key Schedule */ void Twofish::key_schedule(std::span key) { - m_SB.resize(1024); + using namespace Twofish_KS; + + // Reed-Solomon matrix for key schedule (Twofish paper Section 4.3) + // in column-major form + + // clang-format off + constexpr uint32_t RS32[8] = { + 0x01A402A4, + 0xA456A155, + 0x5582FC87, + 0x87F3C15A, + 0x5A1E4758, + 0x58C6AEDB, + 0xDB683D9E, + 0x9EE51903 + }; + // clang-format on + m_RK.resize(40); secure_vector S(16); for(size_t i = 0; i != key.size(); ++i) { - /* - * Do one column of the RS matrix multiplcation - */ - if(key[i]) { - uint8_t X = POLY_TO_EXP[key[i] - 1]; - - uint8_t RS1 = RS[(4 * i) % 32]; - uint8_t RS2 = RS[(4 * i + 1) % 32]; - uint8_t RS3 = RS[(4 * i + 2) % 32]; - uint8_t RS4 = RS[(4 * i + 3) % 32]; - - S[4 * (i / 8)] ^= EXP_TO_POLY[(X + POLY_TO_EXP[RS1 - 1]) % 255]; - S[4 * (i / 8) + 1] ^= EXP_TO_POLY[(X + POLY_TO_EXP[RS2 - 1]) % 255]; - S[4 * (i / 8) + 2] ^= EXP_TO_POLY[(X + POLY_TO_EXP[RS3 - 1]) % 255]; - S[4 * (i / 8) + 3] ^= EXP_TO_POLY[(X + POLY_TO_EXP[RS4 - 1]) % 255]; - } + const uint8_t ki = key[i]; + const size_t s_off = 4 * (i / 8); + + const uint32_t p = gf_mul_rs32(RS32[i % 8], ki); + + S[s_off + 0] ^= get_byte<0>(p); + S[s_off + 1] ^= get_byte<1>(p); + S[s_off + 2] ^= get_byte<2>(p); + S[s_off + 3] ^= get_byte<3>(p); } + secure_vector QS(1024); + if(key.size() == 16) { for(size_t i = 0; i != 256; ++i) { - m_SB[i] = MDS0[Q0[Q0[i] ^ S[0]] ^ S[4]]; - m_SB[256 + i] = MDS1[Q0[Q1[i] ^ S[1]] ^ S[5]]; - m_SB[512 + i] = MDS2[Q1[Q0[i] ^ S[2]] ^ S[6]]; - m_SB[768 + i] = MDS3[Q1[Q1[i] ^ S[3]] ^ S[7]]; + QS[i] = Q1[Q0[Q0[i] ^ S[0]] ^ S[4]]; + QS[256 + i] = Q0[Q0[Q1[i] ^ S[1]] ^ S[5]]; + QS[512 + i] = Q1[Q1[Q0[i] ^ S[2]] ^ S[6]]; + QS[768 + i] = Q0[Q1[Q1[i] ^ S[3]] ^ S[7]]; } for(size_t i = 0; i < 40; i += 2) { - uint32_t X = MDS0[Q0[Q0[i] ^ key[8]] ^ key[0]] ^ MDS1[Q0[Q1[i] ^ key[9]] ^ key[1]] ^ - MDS2[Q1[Q0[i] ^ key[10]] ^ key[2]] ^ MDS3[Q1[Q1[i] ^ key[11]] ^ key[3]]; - uint32_t Y = MDS0[Q0[Q0[i + 1] ^ key[12]] ^ key[4]] ^ MDS1[Q0[Q1[i + 1] ^ key[13]] ^ key[5]] ^ - MDS2[Q1[Q0[i + 1] ^ key[14]] ^ key[6]] ^ MDS3[Q1[Q1[i + 1] ^ key[15]] ^ key[7]]; + uint32_t X = mds0(Q1[Q0[Q0[i] ^ key[8]] ^ key[0]]) ^ mds1(Q0[Q0[Q1[i] ^ key[9]] ^ key[1]]) ^ + mds2(Q1[Q1[Q0[i] ^ key[10]] ^ key[2]]) ^ mds3(Q0[Q1[Q1[i] ^ key[11]] ^ key[3]]); + uint32_t Y = mds0(Q1[Q0[Q0[i + 1] ^ key[12]] ^ key[4]]) ^ mds1(Q0[Q0[Q1[i + 1] ^ key[13]] ^ key[5]]) ^ + mds2(Q1[Q1[Q0[i + 1] ^ key[14]] ^ key[6]]) ^ mds3(Q0[Q1[Q1[i + 1] ^ key[15]] ^ key[7]]); Y = rotl<8>(Y); X += Y; Y += X; @@ -237,20 +425,20 @@ } } else if(key.size() == 24) { for(size_t i = 0; i != 256; ++i) { - m_SB[i] = MDS0[Q0[Q0[Q1[i] ^ S[0]] ^ S[4]] ^ S[8]]; - m_SB[256 + i] = MDS1[Q0[Q1[Q1[i] ^ S[1]] ^ S[5]] ^ S[9]]; - m_SB[512 + i] = MDS2[Q1[Q0[Q0[i] ^ S[2]] ^ S[6]] ^ S[10]]; - m_SB[768 + i] = MDS3[Q1[Q1[Q0[i] ^ S[3]] ^ S[7]] ^ S[11]]; + QS[i] = Q1[Q0[Q0[Q1[i] ^ S[0]] ^ S[4]] ^ S[8]]; + QS[256 + i] = Q0[Q0[Q1[Q1[i] ^ S[1]] ^ S[5]] ^ S[9]]; + QS[512 + i] = Q1[Q1[Q0[Q0[i] ^ S[2]] ^ S[6]] ^ S[10]]; + QS[768 + i] = Q0[Q1[Q1[Q0[i] ^ S[3]] ^ S[7]] ^ S[11]]; } for(size_t i = 0; i < 40; i += 2) { uint32_t X = - MDS0[Q0[Q0[Q1[i] ^ key[16]] ^ key[8]] ^ key[0]] ^ MDS1[Q0[Q1[Q1[i] ^ key[17]] ^ key[9]] ^ key[1]] ^ - MDS2[Q1[Q0[Q0[i] ^ key[18]] ^ key[10]] ^ key[2]] ^ MDS3[Q1[Q1[Q0[i] ^ key[19]] ^ key[11]] ^ key[3]]; - uint32_t Y = MDS0[Q0[Q0[Q1[i + 1] ^ key[20]] ^ key[12]] ^ key[4]] ^ - MDS1[Q0[Q1[Q1[i + 1] ^ key[21]] ^ key[13]] ^ key[5]] ^ - MDS2[Q1[Q0[Q0[i + 1] ^ key[22]] ^ key[14]] ^ key[6]] ^ - MDS3[Q1[Q1[Q0[i + 1] ^ key[23]] ^ key[15]] ^ key[7]]; + mds0(Q1[Q0[Q0[Q1[i] ^ key[16]] ^ key[8]] ^ key[0]]) ^ mds1(Q0[Q0[Q1[Q1[i] ^ key[17]] ^ key[9]] ^ key[1]]) ^ + mds2(Q1[Q1[Q0[Q0[i] ^ key[18]] ^ key[10]] ^ key[2]]) ^ mds3(Q0[Q1[Q1[Q0[i] ^ key[19]] ^ key[11]] ^ key[3]]); + uint32_t Y = mds0(Q1[Q0[Q0[Q1[i + 1] ^ key[20]] ^ key[12]] ^ key[4]]) ^ + mds1(Q0[Q0[Q1[Q1[i + 1] ^ key[21]] ^ key[13]] ^ key[5]]) ^ + mds2(Q1[Q1[Q0[Q0[i + 1] ^ key[22]] ^ key[14]] ^ key[6]]) ^ + mds3(Q0[Q1[Q1[Q0[i + 1] ^ key[23]] ^ key[15]] ^ key[7]]); Y = rotl<8>(Y); X += Y; Y += X; @@ -260,21 +448,21 @@ } } else if(key.size() == 32) { for(size_t i = 0; i != 256; ++i) { - m_SB[i] = MDS0[Q0[Q0[Q1[Q1[i] ^ S[0]] ^ S[4]] ^ S[8]] ^ S[12]]; - m_SB[256 + i] = MDS1[Q0[Q1[Q1[Q0[i] ^ S[1]] ^ S[5]] ^ S[9]] ^ S[13]]; - m_SB[512 + i] = MDS2[Q1[Q0[Q0[Q0[i] ^ S[2]] ^ S[6]] ^ S[10]] ^ S[14]]; - m_SB[768 + i] = MDS3[Q1[Q1[Q0[Q1[i] ^ S[3]] ^ S[7]] ^ S[11]] ^ S[15]]; + QS[i] = Q1[Q0[Q0[Q1[Q1[i] ^ S[0]] ^ S[4]] ^ S[8]] ^ S[12]]; + QS[256 + i] = Q0[Q0[Q1[Q1[Q0[i] ^ S[1]] ^ S[5]] ^ S[9]] ^ S[13]]; + QS[512 + i] = Q1[Q1[Q0[Q0[Q0[i] ^ S[2]] ^ S[6]] ^ S[10]] ^ S[14]]; + QS[768 + i] = Q0[Q1[Q1[Q0[Q1[i] ^ S[3]] ^ S[7]] ^ S[11]] ^ S[15]]; } for(size_t i = 0; i < 40; i += 2) { - uint32_t X = MDS0[Q0[Q0[Q1[Q1[i] ^ key[24]] ^ key[16]] ^ key[8]] ^ key[0]] ^ - MDS1[Q0[Q1[Q1[Q0[i] ^ key[25]] ^ key[17]] ^ key[9]] ^ key[1]] ^ - MDS2[Q1[Q0[Q0[Q0[i] ^ key[26]] ^ key[18]] ^ key[10]] ^ key[2]] ^ - MDS3[Q1[Q1[Q0[Q1[i] ^ key[27]] ^ key[19]] ^ key[11]] ^ key[3]]; - uint32_t Y = MDS0[Q0[Q0[Q1[Q1[i + 1] ^ key[28]] ^ key[20]] ^ key[12]] ^ key[4]] ^ - MDS1[Q0[Q1[Q1[Q0[i + 1] ^ key[29]] ^ key[21]] ^ key[13]] ^ key[5]] ^ - MDS2[Q1[Q0[Q0[Q0[i + 1] ^ key[30]] ^ key[22]] ^ key[14]] ^ key[6]] ^ - MDS3[Q1[Q1[Q0[Q1[i + 1] ^ key[31]] ^ key[23]] ^ key[15]] ^ key[7]]; + uint32_t X = mds0(Q1[Q0[Q0[Q1[Q1[i] ^ key[24]] ^ key[16]] ^ key[8]] ^ key[0]]) ^ + mds1(Q0[Q0[Q1[Q1[Q0[i] ^ key[25]] ^ key[17]] ^ key[9]] ^ key[1]]) ^ + mds2(Q1[Q1[Q0[Q0[Q0[i] ^ key[26]] ^ key[18]] ^ key[10]] ^ key[2]]) ^ + mds3(Q0[Q1[Q1[Q0[Q1[i] ^ key[27]] ^ key[19]] ^ key[11]] ^ key[3]]); + uint32_t Y = mds0(Q1[Q0[Q0[Q1[Q1[i + 1] ^ key[28]] ^ key[20]] ^ key[12]] ^ key[4]]) ^ + mds1(Q0[Q0[Q1[Q1[Q0[i + 1] ^ key[29]] ^ key[21]] ^ key[13]] ^ key[5]]) ^ + mds2(Q1[Q1[Q0[Q0[Q0[i + 1] ^ key[30]] ^ key[22]] ^ key[14]] ^ key[6]]) ^ + mds3(Q0[Q1[Q1[Q0[Q1[i + 1] ^ key[31]] ^ key[23]] ^ key[15]] ^ key[7]]); Y = rotl<8>(Y); X += Y; Y += X; @@ -283,6 +471,20 @@ m_RK[i + 1] = rotl<9>(Y); } } + + m_SB.resize(1024); + for(size_t i = 0; i != 256; ++i) { + m_SB[i] = mds0(QS[i]); + m_SB[256 + i] = mds1(QS[256 + i]); + m_SB[512 + i] = mds2(QS[512 + i]); + m_SB[768 + i] = mds3(QS[768 + i]); + } + +#if defined(BOTAN_HAS_TWOFISH_AVX512) + if(CPUID::has(CPUID::Feature::AVX512, CPUID::Feature::GFNI)) { + m_QS = std::move(QS); + } +#endif } /* @@ -291,6 +493,7 @@ void Twofish::clear() { zap(m_SB); zap(m_RK); + zap(m_QS); } } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/block/twofish/twofish.h botan3-3.12.0+dfsg/src/lib/block/twofish/twofish.h --- botan3-3.7.1+dfsg/src/lib/block/twofish/twofish.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/twofish/twofish.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,6 +9,7 @@ #define BOTAN_TWOFISH_H_ #include +#include namespace Botan { @@ -21,27 +22,28 @@ void decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const override; void clear() override; + std::string provider() const override; std::string name() const override { return "Twofish"; } std::unique_ptr new_object() const override { return std::make_unique(); } + size_t parallelism() const override; + bool has_keying_material() const override; private: void key_schedule(std::span key) override; - static const uint32_t MDS0[256]; - static const uint32_t MDS1[256]; - static const uint32_t MDS2[256]; - static const uint32_t MDS3[256]; - static const uint8_t Q0[256]; - static const uint8_t Q1[256]; - static const uint8_t RS[32]; - static const uint8_t EXP_TO_POLY[255]; - static const uint8_t POLY_TO_EXP[255]; +#if defined(BOTAN_HAS_TWOFISH_AVX512) + void avx512_encrypt_16(const uint8_t in[16 * 16], uint8_t out[16 * 16]) const; + void avx512_decrypt_16(const uint8_t in[16 * 16], uint8_t out[16 * 16]) const; +#endif + + secure_vector m_SB; + secure_vector m_RK; - secure_vector m_SB, m_RK; + secure_vector m_QS; // Sboxes without MDS applied, only used for AVX-512 }; } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/block/twofish/twofish_avx512/info.txt botan3-3.12.0+dfsg/src/lib/block/twofish/twofish_avx512/info.txt --- botan3-3.7.1+dfsg/src/lib/block/twofish/twofish_avx512/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/twofish/twofish_avx512/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,22 @@ + +TWOFISH_AVX512 -> 20260318 + + + +name -> "Twofish AVX-512" +brief -> "Twofish using AVX-512 and GFNI instructions" + + + +avx512 +gfni + + + +cpuid +simd_avx512 + + + +!msvc + diff -Nru botan3-3.7.1+dfsg/src/lib/block/twofish/twofish_avx512/twofish_avx512.cpp botan3-3.12.0+dfsg/src/lib/block/twofish/twofish_avx512/twofish_avx512.cpp --- botan3-3.7.1+dfsg/src/lib/block/twofish/twofish_avx512/twofish_avx512.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/twofish/twofish_avx512/twofish_avx512.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,197 @@ +/* +* (C) 2026 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include + +namespace Botan { + +namespace { + +namespace Twofish_AVX512 { + +// NOLINTBEGIN(portability-simd-intrinsics) + +template +BOTAN_FN_ISA_AVX512_GFNI BOTAN_FORCE_INLINE __m512i lookup_sbox(const SIMD_16x32 W, const uint8_t* QS) { + static_assert(N < 4); + + // Parallel sbox lookup using permutations + blend + + const auto q0 = _mm512_loadu_si512(QS); + const auto q1 = _mm512_loadu_si512(QS + 64); + const auto q2 = _mm512_loadu_si512(QS + 128); + const auto q3 = _mm512_loadu_si512(QS + 192); + + const auto bytemask = _mm512_set1_epi32(0xFF); + const auto idx = _mm512_and_si512(_mm512_srli_epi32(W.raw(), N * 8), bytemask); + + // Select on both Q[0-128] and Q[128-256] using the low 7 bits + const __m512i lo = _mm512_permutex2var_epi8(q0, idx, q1); + const __m512i hi = _mm512_permutex2var_epi8(q2, idx, q3); + + // Then select between those results using the top bit + return _mm512_mask_blend_epi8(_mm512_movepi8_mask(idx), lo, hi); +} + +BOTAN_FN_ISA_AVX512_GFNI +BOTAN_FORCE_INLINE SIMD_16x32 apply_mds(__m512i q, __m512i mds_gfni) { + // clang-format off + alignas(64) constexpr uint8_t MDS_PRE_SHUFFLE[64] = { + 0, 4, 8, 12, 16, 20, 24, 28, 0, 4, 8, 12, 16, 20, 24, 28, + 0, 4, 8, 12, 16, 20, 24, 28, 0, 4, 8, 12, 16, 20, 24, 28, + 32, 36, 40, 44, 48, 52, 56, 60, 32, 36, 40, 44, 48, 52, 56, 60, + 32, 36, 40, 44, 48, 52, 56, 60, 32, 36, 40, 44, 48, 52, 56, 60, + }; + + alignas(64) constexpr uint8_t MDS_POST_SHUFFLE[64] = { + 0, 8, 16, 24, 1, 9, 17, 25, 2, 10, 18, 26, 3, 11, 19, 27, + 4, 12, 20, 28, 5, 13, 21, 29, 6, 14, 22, 30, 7, 15, 23, 31, + 32, 40, 48, 56, 33, 41, 49, 57, 34, 42, 50, 58, 35, 43, 51, 59, + 36, 44, 52, 60, 37, 45, 53, 61, 38, 46, 54, 62, 39, 47, 55, 63, + }; + // clang-format on + + const __m512i pre = _mm512_permutexvar_epi8(_mm512_load_si512(MDS_PRE_SHUFFLE), q); + const __m512i transformed = _mm512_gf2p8affine_epi64_epi8(pre, mds_gfni, 0); + return SIMD_16x32(_mm512_permutexvar_epi8(_mm512_load_si512(MDS_POST_SHUFFLE), transformed)); +} + +BOTAN_FN_ISA_AVX512_GFNI +BOTAN_FORCE_INLINE SIMD_16x32 g_func(SIMD_16x32 W, const uint8_t* QS) { + constexpr uint64_t GFNI_ID = 0x0102040810204080; + constexpr uint64_t GFNI_5B = 0x050B162953A24182; + constexpr uint64_t GFNI_EF = 0x070F1F3972E3C183; + + const __m512i MDS0 = _mm512_set_epi64(GFNI_EF, GFNI_EF, GFNI_5B, GFNI_ID, GFNI_EF, GFNI_EF, GFNI_5B, GFNI_ID); + const __m512i MDS1 = _mm512_set_epi64(GFNI_ID, GFNI_5B, GFNI_EF, GFNI_EF, GFNI_ID, GFNI_5B, GFNI_EF, GFNI_EF); + const __m512i MDS2 = _mm512_set_epi64(GFNI_EF, GFNI_ID, GFNI_EF, GFNI_5B, GFNI_EF, GFNI_ID, GFNI_EF, GFNI_5B); + const __m512i MDS3 = _mm512_set_epi64(GFNI_5B, GFNI_EF, GFNI_ID, GFNI_5B, GFNI_5B, GFNI_EF, GFNI_ID, GFNI_5B); + + const auto r0 = apply_mds(lookup_sbox<0>(W, QS), MDS0); + const auto r1 = apply_mds(lookup_sbox<1>(W, QS + 256), MDS1); + const auto r2 = apply_mds(lookup_sbox<2>(W, QS + 512), MDS2); + const auto r3 = apply_mds(lookup_sbox<3>(W, QS + 768), MDS3); + + return (r0 ^ r1 ^ r2 ^ r3); +} + +// NOLINTEND(portability-simd-intrinsics) + +BOTAN_FN_ISA_AVX512_GFNI +BOTAN_FORCE_INLINE void twofish_encrypt_round( + SIMD_16x32 A, SIMD_16x32 B, SIMD_16x32& C, SIMD_16x32& D, uint32_t rk1, uint32_t rk2, const uint8_t* QS) { + SIMD_16x32 X = g_func(A, QS); + SIMD_16x32 Y = g_func(B.rotl<8>(), QS); + + X += Y; + Y += X; + + X += SIMD_16x32::splat(rk1); + Y += SIMD_16x32::splat(rk2); + + C = (C ^ X).rotr<1>(); + D = D.rotl<1>() ^ Y; +} + +BOTAN_FN_ISA_AVX512_GFNI +BOTAN_FORCE_INLINE void twofish_decrypt_round( + SIMD_16x32 A, SIMD_16x32 B, SIMD_16x32& C, SIMD_16x32& D, uint32_t rk1, uint32_t rk2, const uint8_t* QS) { + SIMD_16x32 X = g_func(A, QS); + SIMD_16x32 Y = g_func(B.rotl<8>(), QS); + + X += Y; + Y += X; + + X += SIMD_16x32::splat(rk1); + Y += SIMD_16x32::splat(rk2); + + C = C.rotl<1>() ^ X; + D = (D ^ Y).rotr<1>(); +} + +} // namespace Twofish_AVX512 + +} // namespace + +void BOTAN_FN_ISA_AVX512_GFNI Twofish::avx512_encrypt_16(const uint8_t in[16 * 16], uint8_t out[16 * 16]) const { + using namespace Twofish_AVX512; + + SIMD_16x32 B0 = SIMD_16x32::load_le(in); + SIMD_16x32 B1 = SIMD_16x32::load_le(in + 64); + SIMD_16x32 B2 = SIMD_16x32::load_le(in + 128); + SIMD_16x32 B3 = SIMD_16x32::load_le(in + 192); + + SIMD_16x32::transpose(B0, B1, B2, B3); + + B0 ^= SIMD_16x32::splat(m_RK[0]); + B1 ^= SIMD_16x32::splat(m_RK[1]); + B2 ^= SIMD_16x32::splat(m_RK[2]); + B3 ^= SIMD_16x32::splat(m_RK[3]); + + const uint8_t* QS = m_QS.data(); + + for(size_t k = 8; k != 40; k += 4) { + twofish_encrypt_round(B0, B1, B2, B3, m_RK[k], m_RK[k + 1], QS); + twofish_encrypt_round(B2, B3, B0, B1, m_RK[k + 2], m_RK[k + 3], QS); + } + + B2 ^= SIMD_16x32::splat(m_RK[4]); + B3 ^= SIMD_16x32::splat(m_RK[5]); + B0 ^= SIMD_16x32::splat(m_RK[6]); + B1 ^= SIMD_16x32::splat(m_RK[7]); + + SIMD_16x32::transpose(B2, B3, B0, B1); + + B2.store_le(out); + B3.store_le(out + 64); + B0.store_le(out + 128); + B1.store_le(out + 192); + + SIMD_16x32::zero_registers(); +} + +void BOTAN_FN_ISA_AVX512_GFNI Twofish::avx512_decrypt_16(const uint8_t in[16 * 16], uint8_t out[16 * 16]) const { + using namespace Twofish_AVX512; + + SIMD_16x32 B0 = SIMD_16x32::load_le(in); + SIMD_16x32 B1 = SIMD_16x32::load_le(in + 64); + SIMD_16x32 B2 = SIMD_16x32::load_le(in + 128); + SIMD_16x32 B3 = SIMD_16x32::load_le(in + 192); + + SIMD_16x32::transpose(B0, B1, B2, B3); + + B0 ^= SIMD_16x32::splat(m_RK[4]); + B1 ^= SIMD_16x32::splat(m_RK[5]); + B2 ^= SIMD_16x32::splat(m_RK[6]); + B3 ^= SIMD_16x32::splat(m_RK[7]); + + const uint8_t* QS = m_QS.data(); + + for(size_t k = 40; k != 8; k -= 4) { + twofish_decrypt_round(B0, B1, B2, B3, m_RK[k - 2], m_RK[k - 1], QS); + twofish_decrypt_round(B2, B3, B0, B1, m_RK[k - 4], m_RK[k - 3], QS); + } + + B2 ^= SIMD_16x32::splat(m_RK[0]); + B3 ^= SIMD_16x32::splat(m_RK[1]); + B0 ^= SIMD_16x32::splat(m_RK[2]); + B1 ^= SIMD_16x32::splat(m_RK[3]); + + SIMD_16x32::transpose(B2, B3, B0, B1); + + B2.store_le(out); + B3.store_le(out + 64); + B0.store_le(out + 128); + B1.store_le(out + 192); + + SIMD_16x32::zero_registers(); +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/block/twofish/twofish_tab.cpp botan3-3.12.0+dfsg/src/lib/block/twofish/twofish_tab.cpp --- botan3-3.7.1+dfsg/src/lib/block/twofish/twofish_tab.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/block/twofish/twofish_tab.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,204 +0,0 @@ -/* -* S-Box and MDS Tables for Twofish -* (C) 1999-2007 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include - -namespace Botan { - -alignas(256) const uint8_t Twofish::Q0[256] = { - 0xA9, 0x67, 0xB3, 0xE8, 0x04, 0xFD, 0xA3, 0x76, 0x9A, 0x92, 0x80, 0x78, 0xE4, 0xDD, 0xD1, 0x38, 0x0D, 0xC6, 0x35, - 0x98, 0x18, 0xF7, 0xEC, 0x6C, 0x43, 0x75, 0x37, 0x26, 0xFA, 0x13, 0x94, 0x48, 0xF2, 0xD0, 0x8B, 0x30, 0x84, 0x54, - 0xDF, 0x23, 0x19, 0x5B, 0x3D, 0x59, 0xF3, 0xAE, 0xA2, 0x82, 0x63, 0x01, 0x83, 0x2E, 0xD9, 0x51, 0x9B, 0x7C, 0xA6, - 0xEB, 0xA5, 0xBE, 0x16, 0x0C, 0xE3, 0x61, 0xC0, 0x8C, 0x3A, 0xF5, 0x73, 0x2C, 0x25, 0x0B, 0xBB, 0x4E, 0x89, 0x6B, - 0x53, 0x6A, 0xB4, 0xF1, 0xE1, 0xE6, 0xBD, 0x45, 0xE2, 0xF4, 0xB6, 0x66, 0xCC, 0x95, 0x03, 0x56, 0xD4, 0x1C, 0x1E, - 0xD7, 0xFB, 0xC3, 0x8E, 0xB5, 0xE9, 0xCF, 0xBF, 0xBA, 0xEA, 0x77, 0x39, 0xAF, 0x33, 0xC9, 0x62, 0x71, 0x81, 0x79, - 0x09, 0xAD, 0x24, 0xCD, 0xF9, 0xD8, 0xE5, 0xC5, 0xB9, 0x4D, 0x44, 0x08, 0x86, 0xE7, 0xA1, 0x1D, 0xAA, 0xED, 0x06, - 0x70, 0xB2, 0xD2, 0x41, 0x7B, 0xA0, 0x11, 0x31, 0xC2, 0x27, 0x90, 0x20, 0xF6, 0x60, 0xFF, 0x96, 0x5C, 0xB1, 0xAB, - 0x9E, 0x9C, 0x52, 0x1B, 0x5F, 0x93, 0x0A, 0xEF, 0x91, 0x85, 0x49, 0xEE, 0x2D, 0x4F, 0x8F, 0x3B, 0x47, 0x87, 0x6D, - 0x46, 0xD6, 0x3E, 0x69, 0x64, 0x2A, 0xCE, 0xCB, 0x2F, 0xFC, 0x97, 0x05, 0x7A, 0xAC, 0x7F, 0xD5, 0x1A, 0x4B, 0x0E, - 0xA7, 0x5A, 0x28, 0x14, 0x3F, 0x29, 0x88, 0x3C, 0x4C, 0x02, 0xB8, 0xDA, 0xB0, 0x17, 0x55, 0x1F, 0x8A, 0x7D, 0x57, - 0xC7, 0x8D, 0x74, 0xB7, 0xC4, 0x9F, 0x72, 0x7E, 0x15, 0x22, 0x12, 0x58, 0x07, 0x99, 0x34, 0x6E, 0x50, 0xDE, 0x68, - 0x65, 0xBC, 0xDB, 0xF8, 0xC8, 0xA8, 0x2B, 0x40, 0xDC, 0xFE, 0x32, 0xA4, 0xCA, 0x10, 0x21, 0xF0, 0xD3, 0x5D, 0x0F, - 0x00, 0x6F, 0x9D, 0x36, 0x42, 0x4A, 0x5E, 0xC1, 0xE0}; - -alignas(256) const uint8_t Twofish::Q1[256] = { - 0x75, 0xF3, 0xC6, 0xF4, 0xDB, 0x7B, 0xFB, 0xC8, 0x4A, 0xD3, 0xE6, 0x6B, 0x45, 0x7D, 0xE8, 0x4B, 0xD6, 0x32, 0xD8, - 0xFD, 0x37, 0x71, 0xF1, 0xE1, 0x30, 0x0F, 0xF8, 0x1B, 0x87, 0xFA, 0x06, 0x3F, 0x5E, 0xBA, 0xAE, 0x5B, 0x8A, 0x00, - 0xBC, 0x9D, 0x6D, 0xC1, 0xB1, 0x0E, 0x80, 0x5D, 0xD2, 0xD5, 0xA0, 0x84, 0x07, 0x14, 0xB5, 0x90, 0x2C, 0xA3, 0xB2, - 0x73, 0x4C, 0x54, 0x92, 0x74, 0x36, 0x51, 0x38, 0xB0, 0xBD, 0x5A, 0xFC, 0x60, 0x62, 0x96, 0x6C, 0x42, 0xF7, 0x10, - 0x7C, 0x28, 0x27, 0x8C, 0x13, 0x95, 0x9C, 0xC7, 0x24, 0x46, 0x3B, 0x70, 0xCA, 0xE3, 0x85, 0xCB, 0x11, 0xD0, 0x93, - 0xB8, 0xA6, 0x83, 0x20, 0xFF, 0x9F, 0x77, 0xC3, 0xCC, 0x03, 0x6F, 0x08, 0xBF, 0x40, 0xE7, 0x2B, 0xE2, 0x79, 0x0C, - 0xAA, 0x82, 0x41, 0x3A, 0xEA, 0xB9, 0xE4, 0x9A, 0xA4, 0x97, 0x7E, 0xDA, 0x7A, 0x17, 0x66, 0x94, 0xA1, 0x1D, 0x3D, - 0xF0, 0xDE, 0xB3, 0x0B, 0x72, 0xA7, 0x1C, 0xEF, 0xD1, 0x53, 0x3E, 0x8F, 0x33, 0x26, 0x5F, 0xEC, 0x76, 0x2A, 0x49, - 0x81, 0x88, 0xEE, 0x21, 0xC4, 0x1A, 0xEB, 0xD9, 0xC5, 0x39, 0x99, 0xCD, 0xAD, 0x31, 0x8B, 0x01, 0x18, 0x23, 0xDD, - 0x1F, 0x4E, 0x2D, 0xF9, 0x48, 0x4F, 0xF2, 0x65, 0x8E, 0x78, 0x5C, 0x58, 0x19, 0x8D, 0xE5, 0x98, 0x57, 0x67, 0x7F, - 0x05, 0x64, 0xAF, 0x63, 0xB6, 0xFE, 0xF5, 0xB7, 0x3C, 0xA5, 0xCE, 0xE9, 0x68, 0x44, 0xE0, 0x4D, 0x43, 0x69, 0x29, - 0x2E, 0xAC, 0x15, 0x59, 0xA8, 0x0A, 0x9E, 0x6E, 0x47, 0xDF, 0x34, 0x35, 0x6A, 0xCF, 0xDC, 0x22, 0xC9, 0xC0, 0x9B, - 0x89, 0xD4, 0xED, 0xAB, 0x12, 0xA2, 0x0D, 0x52, 0xBB, 0x02, 0x2F, 0xA9, 0xD7, 0x61, 0x1E, 0xB4, 0x50, 0x04, 0xF6, - 0xC2, 0x16, 0x25, 0x86, 0x56, 0x55, 0x09, 0xBE, 0x91}; - -alignas(64) const uint8_t Twofish::RS[32] = {0x01, 0xA4, 0x02, 0xA4, 0xA4, 0x56, 0xA1, 0x55, 0x55, 0x82, 0xFC, - 0x87, 0x87, 0xF3, 0xC1, 0x5A, 0x5A, 0x1E, 0x47, 0x58, 0x58, 0xC6, - 0xAE, 0xDB, 0xDB, 0x68, 0x3D, 0x9E, 0x9E, 0xE5, 0x19, 0x03}; - -alignas(256) const uint8_t Twofish::EXP_TO_POLY[255] = { - 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x4D, 0x9A, 0x79, 0xF2, 0xA9, 0x1F, 0x3E, 0x7C, 0xF8, 0xBD, 0x37, - 0x6E, 0xDC, 0xF5, 0xA7, 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC0, 0xCD, 0xD7, 0xE3, 0x8B, 0x5B, 0xB6, 0x21, 0x42, - 0x84, 0x45, 0x8A, 0x59, 0xB2, 0x29, 0x52, 0xA4, 0x05, 0x0A, 0x14, 0x28, 0x50, 0xA0, 0x0D, 0x1A, 0x34, 0x68, 0xD0, - 0xED, 0x97, 0x63, 0xC6, 0xC1, 0xCF, 0xD3, 0xEB, 0x9B, 0x7B, 0xF6, 0xA1, 0x0F, 0x1E, 0x3C, 0x78, 0xF0, 0xAD, 0x17, - 0x2E, 0x5C, 0xB8, 0x3D, 0x7A, 0xF4, 0xA5, 0x07, 0x0E, 0x1C, 0x38, 0x70, 0xE0, 0x8D, 0x57, 0xAE, 0x11, 0x22, 0x44, - 0x88, 0x5D, 0xBA, 0x39, 0x72, 0xE4, 0x85, 0x47, 0x8E, 0x51, 0xA2, 0x09, 0x12, 0x24, 0x48, 0x90, 0x6D, 0xDA, 0xF9, - 0xBF, 0x33, 0x66, 0xCC, 0xD5, 0xE7, 0x83, 0x4B, 0x96, 0x61, 0xC2, 0xC9, 0xDF, 0xF3, 0xAB, 0x1B, 0x36, 0x6C, 0xD8, - 0xFD, 0xB7, 0x23, 0x46, 0x8C, 0x55, 0xAA, 0x19, 0x32, 0x64, 0xC8, 0xDD, 0xF7, 0xA3, 0x0B, 0x16, 0x2C, 0x58, 0xB0, - 0x2D, 0x5A, 0xB4, 0x25, 0x4A, 0x94, 0x65, 0xCA, 0xD9, 0xFF, 0xB3, 0x2B, 0x56, 0xAC, 0x15, 0x2A, 0x54, 0xA8, 0x1D, - 0x3A, 0x74, 0xE8, 0x9D, 0x77, 0xEE, 0x91, 0x6F, 0xDE, 0xF1, 0xAF, 0x13, 0x26, 0x4C, 0x98, 0x7D, 0xFA, 0xB9, 0x3F, - 0x7E, 0xFC, 0xB5, 0x27, 0x4E, 0x9C, 0x75, 0xEA, 0x99, 0x7F, 0xFE, 0xB1, 0x2F, 0x5E, 0xBC, 0x35, 0x6A, 0xD4, 0xE5, - 0x87, 0x43, 0x86, 0x41, 0x82, 0x49, 0x92, 0x69, 0xD2, 0xE9, 0x9F, 0x73, 0xE6, 0x81, 0x4F, 0x9E, 0x71, 0xE2, 0x89, - 0x5F, 0xBE, 0x31, 0x62, 0xC4, 0xC5, 0xC7, 0xC3, 0xCB, 0xDB, 0xFB, 0xBB, 0x3B, 0x76, 0xEC, 0x95, 0x67, 0xCE, 0xD1, - 0xEF, 0x93, 0x6B, 0xD6, 0xE1, 0x8F, 0x53, 0xA6}; - -alignas(256) const uint8_t Twofish::POLY_TO_EXP[255] = { - 0x00, 0x01, 0x17, 0x02, 0x2E, 0x18, 0x53, 0x03, 0x6A, 0x2F, 0x93, 0x19, 0x34, 0x54, 0x45, 0x04, 0x5C, 0x6B, 0xB6, - 0x30, 0xA6, 0x94, 0x4B, 0x1A, 0x8C, 0x35, 0x81, 0x55, 0xAA, 0x46, 0x0D, 0x05, 0x24, 0x5D, 0x87, 0x6C, 0x9B, 0xB7, - 0xC1, 0x31, 0x2B, 0xA7, 0xA3, 0x95, 0x98, 0x4C, 0xCA, 0x1B, 0xE6, 0x8D, 0x73, 0x36, 0xCD, 0x82, 0x12, 0x56, 0x62, - 0xAB, 0xF0, 0x47, 0x4F, 0x0E, 0xBD, 0x06, 0xD4, 0x25, 0xD2, 0x5E, 0x27, 0x88, 0x66, 0x6D, 0xD6, 0x9C, 0x79, 0xB8, - 0x08, 0xC2, 0xDF, 0x32, 0x68, 0x2C, 0xFD, 0xA8, 0x8A, 0xA4, 0x5A, 0x96, 0x29, 0x99, 0x22, 0x4D, 0x60, 0xCB, 0xE4, - 0x1C, 0x7B, 0xE7, 0x3B, 0x8E, 0x9E, 0x74, 0xF4, 0x37, 0xD8, 0xCE, 0xF9, 0x83, 0x6F, 0x13, 0xB2, 0x57, 0xE1, 0x63, - 0xDC, 0xAC, 0xC4, 0xF1, 0xAF, 0x48, 0x0A, 0x50, 0x42, 0x0F, 0xBA, 0xBE, 0xC7, 0x07, 0xDE, 0xD5, 0x78, 0x26, 0x65, - 0xD3, 0xD1, 0x5F, 0xE3, 0x28, 0x21, 0x89, 0x59, 0x67, 0xFC, 0x6E, 0xB1, 0xD7, 0xF8, 0x9D, 0xF3, 0x7A, 0x3A, 0xB9, - 0xC6, 0x09, 0x41, 0xC3, 0xAE, 0xE0, 0xDB, 0x33, 0x44, 0x69, 0x92, 0x2D, 0x52, 0xFE, 0x16, 0xA9, 0x0C, 0x8B, 0x80, - 0xA5, 0x4A, 0x5B, 0xB5, 0x97, 0xC9, 0x2A, 0xA2, 0x9A, 0xC0, 0x23, 0x86, 0x4E, 0xBC, 0x61, 0xEF, 0xCC, 0x11, 0xE5, - 0x72, 0x1D, 0x3D, 0x7C, 0xEB, 0xE8, 0xE9, 0x3C, 0xEA, 0x8F, 0x7D, 0x9F, 0xEC, 0x75, 0x1E, 0xF5, 0x3E, 0x38, 0xF6, - 0xD9, 0x3F, 0xCF, 0x76, 0xFA, 0x1F, 0x84, 0xA0, 0x70, 0xED, 0x14, 0x90, 0xB3, 0x7E, 0x58, 0xFB, 0xE2, 0x20, 0x64, - 0xD0, 0xDD, 0x77, 0xAD, 0xDA, 0xC5, 0x40, 0xF2, 0x39, 0xB0, 0xF7, 0x49, 0xB4, 0x0B, 0x7F, 0x51, 0x15, 0x43, 0x91, - 0x10, 0x71, 0xBB, 0xEE, 0xBF, 0x85, 0xC8, 0xA1}; - -alignas(256) const uint32_t Twofish::MDS0[256] = { - 0xBCBC3275, 0xECEC21F3, 0x202043C6, 0xB3B3C9F4, 0xDADA03DB, 0x02028B7B, 0xE2E22BFB, 0x9E9EFAC8, 0xC9C9EC4A, - 0xD4D409D3, 0x18186BE6, 0x1E1E9F6B, 0x98980E45, 0xB2B2387D, 0xA6A6D2E8, 0x2626B74B, 0x3C3C57D6, 0x93938A32, - 0x8282EED8, 0x525298FD, 0x7B7BD437, 0xBBBB3771, 0x5B5B97F1, 0x474783E1, 0x24243C30, 0x5151E20F, 0xBABAC6F8, - 0x4A4AF31B, 0xBFBF4887, 0x0D0D70FA, 0xB0B0B306, 0x7575DE3F, 0xD2D2FD5E, 0x7D7D20BA, 0x666631AE, 0x3A3AA35B, - 0x59591C8A, 0x00000000, 0xCDCD93BC, 0x1A1AE09D, 0xAEAE2C6D, 0x7F7FABC1, 0x2B2BC7B1, 0xBEBEB90E, 0xE0E0A080, - 0x8A8A105D, 0x3B3B52D2, 0x6464BAD5, 0xD8D888A0, 0xE7E7A584, 0x5F5FE807, 0x1B1B1114, 0x2C2CC2B5, 0xFCFCB490, - 0x3131272C, 0x808065A3, 0x73732AB2, 0x0C0C8173, 0x79795F4C, 0x6B6B4154, 0x4B4B0292, 0x53536974, 0x94948F36, - 0x83831F51, 0x2A2A3638, 0xC4C49CB0, 0x2222C8BD, 0xD5D5F85A, 0xBDBDC3FC, 0x48487860, 0xFFFFCE62, 0x4C4C0796, - 0x4141776C, 0xC7C7E642, 0xEBEB24F7, 0x1C1C1410, 0x5D5D637C, 0x36362228, 0x6767C027, 0xE9E9AF8C, 0x4444F913, - 0x1414EA95, 0xF5F5BB9C, 0xCFCF18C7, 0x3F3F2D24, 0xC0C0E346, 0x7272DB3B, 0x54546C70, 0x29294CCA, 0xF0F035E3, - 0x0808FE85, 0xC6C617CB, 0xF3F34F11, 0x8C8CE4D0, 0xA4A45993, 0xCACA96B8, 0x68683BA6, 0xB8B84D83, 0x38382820, - 0xE5E52EFF, 0xADAD569F, 0x0B0B8477, 0xC8C81DC3, 0x9999FFCC, 0x5858ED03, 0x19199A6F, 0x0E0E0A08, 0x95957EBF, - 0x70705040, 0xF7F730E7, 0x6E6ECF2B, 0x1F1F6EE2, 0xB5B53D79, 0x09090F0C, 0x616134AA, 0x57571682, 0x9F9F0B41, - 0x9D9D803A, 0x111164EA, 0x2525CDB9, 0xAFAFDDE4, 0x4545089A, 0xDFDF8DA4, 0xA3A35C97, 0xEAEAD57E, 0x353558DA, - 0xEDEDD07A, 0x4343FC17, 0xF8F8CB66, 0xFBFBB194, 0x3737D3A1, 0xFAFA401D, 0xC2C2683D, 0xB4B4CCF0, 0x32325DDE, - 0x9C9C71B3, 0x5656E70B, 0xE3E3DA72, 0x878760A7, 0x15151B1C, 0xF9F93AEF, 0x6363BFD1, 0x3434A953, 0x9A9A853E, - 0xB1B1428F, 0x7C7CD133, 0x88889B26, 0x3D3DA65F, 0xA1A1D7EC, 0xE4E4DF76, 0x8181942A, 0x91910149, 0x0F0FFB81, - 0xEEEEAA88, 0x161661EE, 0xD7D77321, 0x9797F5C4, 0xA5A5A81A, 0xFEFE3FEB, 0x6D6DB5D9, 0x7878AEC5, 0xC5C56D39, - 0x1D1DE599, 0x7676A4CD, 0x3E3EDCAD, 0xCBCB6731, 0xB6B6478B, 0xEFEF5B01, 0x12121E18, 0x6060C523, 0x6A6AB0DD, - 0x4D4DF61F, 0xCECEE94E, 0xDEDE7C2D, 0x55559DF9, 0x7E7E5A48, 0x2121B24F, 0x03037AF2, 0xA0A02665, 0x5E5E198E, - 0x5A5A6678, 0x65654B5C, 0x62624E58, 0xFDFD4519, 0x0606F48D, 0x404086E5, 0xF2F2BE98, 0x3333AC57, 0x17179067, - 0x05058E7F, 0xE8E85E05, 0x4F4F7D64, 0x89896AAF, 0x10109563, 0x74742FB6, 0x0A0A75FE, 0x5C5C92F5, 0x9B9B74B7, - 0x2D2D333C, 0x3030D6A5, 0x2E2E49CE, 0x494989E9, 0x46467268, 0x77775544, 0xA8A8D8E0, 0x9696044D, 0x2828BD43, - 0xA9A92969, 0xD9D97929, 0x8686912E, 0xD1D187AC, 0xF4F44A15, 0x8D8D1559, 0xD6D682A8, 0xB9B9BC0A, 0x42420D9E, - 0xF6F6C16E, 0x2F2FB847, 0xDDDD06DF, 0x23233934, 0xCCCC6235, 0xF1F1C46A, 0xC1C112CF, 0x8585EBDC, 0x8F8F9E22, - 0x7171A1C9, 0x9090F0C0, 0xAAAA539B, 0x0101F189, 0x8B8BE1D4, 0x4E4E8CED, 0x8E8E6FAB, 0xABABA212, 0x6F6F3EA2, - 0xE6E6540D, 0xDBDBF252, 0x92927BBB, 0xB7B7B602, 0x6969CA2F, 0x3939D9A9, 0xD3D30CD7, 0xA7A72361, 0xA2A2AD1E, - 0xC3C399B4, 0x6C6C4450, 0x07070504, 0x04047FF6, 0x272746C2, 0xACACA716, 0xD0D07625, 0x50501386, 0xDCDCF756, - 0x84841A55, 0xE1E15109, 0x7A7A25BE, 0x1313EF91}; - -alignas(256) const uint32_t Twofish::MDS1[256] = { - 0xA9D93939, 0x67901717, 0xB3719C9C, 0xE8D2A6A6, 0x04050707, 0xFD985252, 0xA3658080, 0x76DFE4E4, 0x9A084545, - 0x92024B4B, 0x80A0E0E0, 0x78665A5A, 0xE4DDAFAF, 0xDDB06A6A, 0xD1BF6363, 0x38362A2A, 0x0D54E6E6, 0xC6432020, - 0x3562CCCC, 0x98BEF2F2, 0x181E1212, 0xF724EBEB, 0xECD7A1A1, 0x6C774141, 0x43BD2828, 0x7532BCBC, 0x37D47B7B, - 0x269B8888, 0xFA700D0D, 0x13F94444, 0x94B1FBFB, 0x485A7E7E, 0xF27A0303, 0xD0E48C8C, 0x8B47B6B6, 0x303C2424, - 0x84A5E7E7, 0x54416B6B, 0xDF06DDDD, 0x23C56060, 0x1945FDFD, 0x5BA33A3A, 0x3D68C2C2, 0x59158D8D, 0xF321ECEC, - 0xAE316666, 0xA23E6F6F, 0x82165757, 0x63951010, 0x015BEFEF, 0x834DB8B8, 0x2E918686, 0xD9B56D6D, 0x511F8383, - 0x9B53AAAA, 0x7C635D5D, 0xA63B6868, 0xEB3FFEFE, 0xA5D63030, 0xBE257A7A, 0x16A7ACAC, 0x0C0F0909, 0xE335F0F0, - 0x6123A7A7, 0xC0F09090, 0x8CAFE9E9, 0x3A809D9D, 0xF5925C5C, 0x73810C0C, 0x2C273131, 0x2576D0D0, 0x0BE75656, - 0xBB7B9292, 0x4EE9CECE, 0x89F10101, 0x6B9F1E1E, 0x53A93434, 0x6AC4F1F1, 0xB499C3C3, 0xF1975B5B, 0xE1834747, - 0xE66B1818, 0xBDC82222, 0x450E9898, 0xE26E1F1F, 0xF4C9B3B3, 0xB62F7474, 0x66CBF8F8, 0xCCFF9999, 0x95EA1414, - 0x03ED5858, 0x56F7DCDC, 0xD4E18B8B, 0x1C1B1515, 0x1EADA2A2, 0xD70CD3D3, 0xFB2BE2E2, 0xC31DC8C8, 0x8E195E5E, - 0xB5C22C2C, 0xE9894949, 0xCF12C1C1, 0xBF7E9595, 0xBA207D7D, 0xEA641111, 0x77840B0B, 0x396DC5C5, 0xAF6A8989, - 0x33D17C7C, 0xC9A17171, 0x62CEFFFF, 0x7137BBBB, 0x81FB0F0F, 0x793DB5B5, 0x0951E1E1, 0xADDC3E3E, 0x242D3F3F, - 0xCDA47676, 0xF99D5555, 0xD8EE8282, 0xE5864040, 0xC5AE7878, 0xB9CD2525, 0x4D049696, 0x44557777, 0x080A0E0E, - 0x86135050, 0xE730F7F7, 0xA1D33737, 0x1D40FAFA, 0xAA346161, 0xED8C4E4E, 0x06B3B0B0, 0x706C5454, 0xB22A7373, - 0xD2523B3B, 0x410B9F9F, 0x7B8B0202, 0xA088D8D8, 0x114FF3F3, 0x3167CBCB, 0xC2462727, 0x27C06767, 0x90B4FCFC, - 0x20283838, 0xF67F0404, 0x60784848, 0xFF2EE5E5, 0x96074C4C, 0x5C4B6565, 0xB1C72B2B, 0xAB6F8E8E, 0x9E0D4242, - 0x9CBBF5F5, 0x52F2DBDB, 0x1BF34A4A, 0x5FA63D3D, 0x9359A4A4, 0x0ABCB9B9, 0xEF3AF9F9, 0x91EF1313, 0x85FE0808, - 0x49019191, 0xEE611616, 0x2D7CDEDE, 0x4FB22121, 0x8F42B1B1, 0x3BDB7272, 0x47B82F2F, 0x8748BFBF, 0x6D2CAEAE, - 0x46E3C0C0, 0xD6573C3C, 0x3E859A9A, 0x6929A9A9, 0x647D4F4F, 0x2A948181, 0xCE492E2E, 0xCB17C6C6, 0x2FCA6969, - 0xFCC3BDBD, 0x975CA3A3, 0x055EE8E8, 0x7AD0EDED, 0xAC87D1D1, 0x7F8E0505, 0xD5BA6464, 0x1AA8A5A5, 0x4BB72626, - 0x0EB9BEBE, 0xA7608787, 0x5AF8D5D5, 0x28223636, 0x14111B1B, 0x3FDE7575, 0x2979D9D9, 0x88AAEEEE, 0x3C332D2D, - 0x4C5F7979, 0x02B6B7B7, 0xB896CACA, 0xDA583535, 0xB09CC4C4, 0x17FC4343, 0x551A8484, 0x1FF64D4D, 0x8A1C5959, - 0x7D38B2B2, 0x57AC3333, 0xC718CFCF, 0x8DF40606, 0x74695353, 0xB7749B9B, 0xC4F59797, 0x9F56ADAD, 0x72DAE3E3, - 0x7ED5EAEA, 0x154AF4F4, 0x229E8F8F, 0x12A2ABAB, 0x584E6262, 0x07E85F5F, 0x99E51D1D, 0x34392323, 0x6EC1F6F6, - 0x50446C6C, 0xDE5D3232, 0x68724646, 0x6526A0A0, 0xBC93CDCD, 0xDB03DADA, 0xF8C6BABA, 0xC8FA9E9E, 0xA882D6D6, - 0x2BCF6E6E, 0x40507070, 0xDCEB8585, 0xFE750A0A, 0x328A9393, 0xA48DDFDF, 0xCA4C2929, 0x10141C1C, 0x2173D7D7, - 0xF0CCB4B4, 0xD309D4D4, 0x5D108A8A, 0x0FE25151, 0x00000000, 0x6F9A1919, 0x9DE01A1A, 0x368F9494, 0x42E6C7C7, - 0x4AECC9C9, 0x5EFDD2D2, 0xC1AB7F7F, 0xE0D8A8A8}; - -alignas(256) const uint32_t Twofish::MDS2[256] = { - 0xBC75BC32, 0xECF3EC21, 0x20C62043, 0xB3F4B3C9, 0xDADBDA03, 0x027B028B, 0xE2FBE22B, 0x9EC89EFA, 0xC94AC9EC, - 0xD4D3D409, 0x18E6186B, 0x1E6B1E9F, 0x9845980E, 0xB27DB238, 0xA6E8A6D2, 0x264B26B7, 0x3CD63C57, 0x9332938A, - 0x82D882EE, 0x52FD5298, 0x7B377BD4, 0xBB71BB37, 0x5BF15B97, 0x47E14783, 0x2430243C, 0x510F51E2, 0xBAF8BAC6, - 0x4A1B4AF3, 0xBF87BF48, 0x0DFA0D70, 0xB006B0B3, 0x753F75DE, 0xD25ED2FD, 0x7DBA7D20, 0x66AE6631, 0x3A5B3AA3, - 0x598A591C, 0x00000000, 0xCDBCCD93, 0x1A9D1AE0, 0xAE6DAE2C, 0x7FC17FAB, 0x2BB12BC7, 0xBE0EBEB9, 0xE080E0A0, - 0x8A5D8A10, 0x3BD23B52, 0x64D564BA, 0xD8A0D888, 0xE784E7A5, 0x5F075FE8, 0x1B141B11, 0x2CB52CC2, 0xFC90FCB4, - 0x312C3127, 0x80A38065, 0x73B2732A, 0x0C730C81, 0x794C795F, 0x6B546B41, 0x4B924B02, 0x53745369, 0x9436948F, - 0x8351831F, 0x2A382A36, 0xC4B0C49C, 0x22BD22C8, 0xD55AD5F8, 0xBDFCBDC3, 0x48604878, 0xFF62FFCE, 0x4C964C07, - 0x416C4177, 0xC742C7E6, 0xEBF7EB24, 0x1C101C14, 0x5D7C5D63, 0x36283622, 0x672767C0, 0xE98CE9AF, 0x441344F9, - 0x149514EA, 0xF59CF5BB, 0xCFC7CF18, 0x3F243F2D, 0xC046C0E3, 0x723B72DB, 0x5470546C, 0x29CA294C, 0xF0E3F035, - 0x088508FE, 0xC6CBC617, 0xF311F34F, 0x8CD08CE4, 0xA493A459, 0xCAB8CA96, 0x68A6683B, 0xB883B84D, 0x38203828, - 0xE5FFE52E, 0xAD9FAD56, 0x0B770B84, 0xC8C3C81D, 0x99CC99FF, 0x580358ED, 0x196F199A, 0x0E080E0A, 0x95BF957E, - 0x70407050, 0xF7E7F730, 0x6E2B6ECF, 0x1FE21F6E, 0xB579B53D, 0x090C090F, 0x61AA6134, 0x57825716, 0x9F419F0B, - 0x9D3A9D80, 0x11EA1164, 0x25B925CD, 0xAFE4AFDD, 0x459A4508, 0xDFA4DF8D, 0xA397A35C, 0xEA7EEAD5, 0x35DA3558, - 0xED7AEDD0, 0x431743FC, 0xF866F8CB, 0xFB94FBB1, 0x37A137D3, 0xFA1DFA40, 0xC23DC268, 0xB4F0B4CC, 0x32DE325D, - 0x9CB39C71, 0x560B56E7, 0xE372E3DA, 0x87A78760, 0x151C151B, 0xF9EFF93A, 0x63D163BF, 0x345334A9, 0x9A3E9A85, - 0xB18FB142, 0x7C337CD1, 0x8826889B, 0x3D5F3DA6, 0xA1ECA1D7, 0xE476E4DF, 0x812A8194, 0x91499101, 0x0F810FFB, - 0xEE88EEAA, 0x16EE1661, 0xD721D773, 0x97C497F5, 0xA51AA5A8, 0xFEEBFE3F, 0x6DD96DB5, 0x78C578AE, 0xC539C56D, - 0x1D991DE5, 0x76CD76A4, 0x3EAD3EDC, 0xCB31CB67, 0xB68BB647, 0xEF01EF5B, 0x1218121E, 0x602360C5, 0x6ADD6AB0, - 0x4D1F4DF6, 0xCE4ECEE9, 0xDE2DDE7C, 0x55F9559D, 0x7E487E5A, 0x214F21B2, 0x03F2037A, 0xA065A026, 0x5E8E5E19, - 0x5A785A66, 0x655C654B, 0x6258624E, 0xFD19FD45, 0x068D06F4, 0x40E54086, 0xF298F2BE, 0x335733AC, 0x17671790, - 0x057F058E, 0xE805E85E, 0x4F644F7D, 0x89AF896A, 0x10631095, 0x74B6742F, 0x0AFE0A75, 0x5CF55C92, 0x9BB79B74, - 0x2D3C2D33, 0x30A530D6, 0x2ECE2E49, 0x49E94989, 0x46684672, 0x77447755, 0xA8E0A8D8, 0x964D9604, 0x284328BD, - 0xA969A929, 0xD929D979, 0x862E8691, 0xD1ACD187, 0xF415F44A, 0x8D598D15, 0xD6A8D682, 0xB90AB9BC, 0x429E420D, - 0xF66EF6C1, 0x2F472FB8, 0xDDDFDD06, 0x23342339, 0xCC35CC62, 0xF16AF1C4, 0xC1CFC112, 0x85DC85EB, 0x8F228F9E, - 0x71C971A1, 0x90C090F0, 0xAA9BAA53, 0x018901F1, 0x8BD48BE1, 0x4EED4E8C, 0x8EAB8E6F, 0xAB12ABA2, 0x6FA26F3E, - 0xE60DE654, 0xDB52DBF2, 0x92BB927B, 0xB702B7B6, 0x692F69CA, 0x39A939D9, 0xD3D7D30C, 0xA761A723, 0xA21EA2AD, - 0xC3B4C399, 0x6C506C44, 0x07040705, 0x04F6047F, 0x27C22746, 0xAC16ACA7, 0xD025D076, 0x50865013, 0xDC56DCF7, - 0x8455841A, 0xE109E151, 0x7ABE7A25, 0x139113EF}; - -alignas(256) const uint32_t Twofish::MDS3[256] = { - 0xD939A9D9, 0x90176790, 0x719CB371, 0xD2A6E8D2, 0x05070405, 0x9852FD98, 0x6580A365, 0xDFE476DF, 0x08459A08, - 0x024B9202, 0xA0E080A0, 0x665A7866, 0xDDAFE4DD, 0xB06ADDB0, 0xBF63D1BF, 0x362A3836, 0x54E60D54, 0x4320C643, - 0x62CC3562, 0xBEF298BE, 0x1E12181E, 0x24EBF724, 0xD7A1ECD7, 0x77416C77, 0xBD2843BD, 0x32BC7532, 0xD47B37D4, - 0x9B88269B, 0x700DFA70, 0xF94413F9, 0xB1FB94B1, 0x5A7E485A, 0x7A03F27A, 0xE48CD0E4, 0x47B68B47, 0x3C24303C, - 0xA5E784A5, 0x416B5441, 0x06DDDF06, 0xC56023C5, 0x45FD1945, 0xA33A5BA3, 0x68C23D68, 0x158D5915, 0x21ECF321, - 0x3166AE31, 0x3E6FA23E, 0x16578216, 0x95106395, 0x5BEF015B, 0x4DB8834D, 0x91862E91, 0xB56DD9B5, 0x1F83511F, - 0x53AA9B53, 0x635D7C63, 0x3B68A63B, 0x3FFEEB3F, 0xD630A5D6, 0x257ABE25, 0xA7AC16A7, 0x0F090C0F, 0x35F0E335, - 0x23A76123, 0xF090C0F0, 0xAFE98CAF, 0x809D3A80, 0x925CF592, 0x810C7381, 0x27312C27, 0x76D02576, 0xE7560BE7, - 0x7B92BB7B, 0xE9CE4EE9, 0xF10189F1, 0x9F1E6B9F, 0xA93453A9, 0xC4F16AC4, 0x99C3B499, 0x975BF197, 0x8347E183, - 0x6B18E66B, 0xC822BDC8, 0x0E98450E, 0x6E1FE26E, 0xC9B3F4C9, 0x2F74B62F, 0xCBF866CB, 0xFF99CCFF, 0xEA1495EA, - 0xED5803ED, 0xF7DC56F7, 0xE18BD4E1, 0x1B151C1B, 0xADA21EAD, 0x0CD3D70C, 0x2BE2FB2B, 0x1DC8C31D, 0x195E8E19, - 0xC22CB5C2, 0x8949E989, 0x12C1CF12, 0x7E95BF7E, 0x207DBA20, 0x6411EA64, 0x840B7784, 0x6DC5396D, 0x6A89AF6A, - 0xD17C33D1, 0xA171C9A1, 0xCEFF62CE, 0x37BB7137, 0xFB0F81FB, 0x3DB5793D, 0x51E10951, 0xDC3EADDC, 0x2D3F242D, - 0xA476CDA4, 0x9D55F99D, 0xEE82D8EE, 0x8640E586, 0xAE78C5AE, 0xCD25B9CD, 0x04964D04, 0x55774455, 0x0A0E080A, - 0x13508613, 0x30F7E730, 0xD337A1D3, 0x40FA1D40, 0x3461AA34, 0x8C4EED8C, 0xB3B006B3, 0x6C54706C, 0x2A73B22A, - 0x523BD252, 0x0B9F410B, 0x8B027B8B, 0x88D8A088, 0x4FF3114F, 0x67CB3167, 0x4627C246, 0xC06727C0, 0xB4FC90B4, - 0x28382028, 0x7F04F67F, 0x78486078, 0x2EE5FF2E, 0x074C9607, 0x4B655C4B, 0xC72BB1C7, 0x6F8EAB6F, 0x0D429E0D, - 0xBBF59CBB, 0xF2DB52F2, 0xF34A1BF3, 0xA63D5FA6, 0x59A49359, 0xBCB90ABC, 0x3AF9EF3A, 0xEF1391EF, 0xFE0885FE, - 0x01914901, 0x6116EE61, 0x7CDE2D7C, 0xB2214FB2, 0x42B18F42, 0xDB723BDB, 0xB82F47B8, 0x48BF8748, 0x2CAE6D2C, - 0xE3C046E3, 0x573CD657, 0x859A3E85, 0x29A96929, 0x7D4F647D, 0x94812A94, 0x492ECE49, 0x17C6CB17, 0xCA692FCA, - 0xC3BDFCC3, 0x5CA3975C, 0x5EE8055E, 0xD0ED7AD0, 0x87D1AC87, 0x8E057F8E, 0xBA64D5BA, 0xA8A51AA8, 0xB7264BB7, - 0xB9BE0EB9, 0x6087A760, 0xF8D55AF8, 0x22362822, 0x111B1411, 0xDE753FDE, 0x79D92979, 0xAAEE88AA, 0x332D3C33, - 0x5F794C5F, 0xB6B702B6, 0x96CAB896, 0x5835DA58, 0x9CC4B09C, 0xFC4317FC, 0x1A84551A, 0xF64D1FF6, 0x1C598A1C, - 0x38B27D38, 0xAC3357AC, 0x18CFC718, 0xF4068DF4, 0x69537469, 0x749BB774, 0xF597C4F5, 0x56AD9F56, 0xDAE372DA, - 0xD5EA7ED5, 0x4AF4154A, 0x9E8F229E, 0xA2AB12A2, 0x4E62584E, 0xE85F07E8, 0xE51D99E5, 0x39233439, 0xC1F66EC1, - 0x446C5044, 0x5D32DE5D, 0x72466872, 0x26A06526, 0x93CDBC93, 0x03DADB03, 0xC6BAF8C6, 0xFA9EC8FA, 0x82D6A882, - 0xCF6E2BCF, 0x50704050, 0xEB85DCEB, 0x750AFE75, 0x8A93328A, 0x8DDFA48D, 0x4C29CA4C, 0x141C1014, 0x73D72173, - 0xCCB4F0CC, 0x09D4D309, 0x108A5D10, 0xE2510FE2, 0x00000000, 0x9A196F9A, 0xE01A9DE0, 0x8F94368F, 0xE6C742E6, - 0xECC94AEC, 0xFDD25EFD, 0xAB7FC1AB, 0xD8A8E0D8}; - -} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/codec/base32/base32.cpp botan3-3.12.0+dfsg/src/lib/codec/base32/base32.cpp --- botan3-3.7.1+dfsg/src/lib/codec/base32/base32.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/codec/base32/base32.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,7 +1,7 @@ /* * Base32 Encoding and Decoding * (C) 2018 Erwan Chaussy -* (C) 2018,2020 Jack Lloyd +* (C) 2018,2020,2025 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include namespace Botan { @@ -22,23 +24,23 @@ public: static std::string name() noexcept { return "base32"; } - static size_t encoding_bytes_in() noexcept { return m_encoding_bytes_in; } + static constexpr size_t encoding_bytes_in() noexcept { return m_encoding_bytes_in; } - static size_t encoding_bytes_out() noexcept { return m_encoding_bytes_out; } + static constexpr size_t encoding_bytes_out() noexcept { return m_encoding_bytes_out; } - static size_t decoding_bytes_in() noexcept { return m_encoding_bytes_out; } + static constexpr size_t decoding_bytes_in() noexcept { return m_encoding_bytes_out; } - static size_t decoding_bytes_out() noexcept { return m_encoding_bytes_in; } + static constexpr size_t decoding_bytes_out() noexcept { return m_encoding_bytes_in; } - static size_t bits_consumed() noexcept { return m_encoding_bits; } + static constexpr size_t bits_consumed() noexcept { return m_encoding_bits; } - static size_t remaining_bits_before_padding() noexcept { return m_remaining_bits_before_padding; } + static constexpr size_t remaining_bits_before_padding() noexcept { return m_remaining_bits_before_padding; } - static size_t encode_max_output(size_t input_length) { + static constexpr size_t encode_max_output(size_t input_length) { return (round_up(input_length, m_encoding_bytes_in) / m_encoding_bytes_in) * m_encoding_bytes_out; } - static size_t decode_max_output(size_t input_length) { + static constexpr size_t decode_max_output(size_t input_length) { return (round_up(input_length, m_encoding_bytes_out) * m_encoding_bytes_in) / m_encoding_bytes_out; } @@ -56,27 +58,26 @@ out_ptr[4] = (decode_buf[6] << 5) | decode_buf[7]; } - static size_t bytes_to_remove(size_t final_truncate) { return final_truncate ? (final_truncate / 2) + 1 : 0; } + static size_t bytes_to_remove(size_t final_truncate) { + return (final_truncate > 0) ? (final_truncate / 2) + 1 : 0; + } private: - static const size_t m_encoding_bits = 5; - static const size_t m_remaining_bits_before_padding = 6; + static constexpr size_t m_encoding_bits = 5; + static constexpr size_t m_remaining_bits_before_padding = 6; - static const size_t m_encoding_bytes_in = 5; - static const size_t m_encoding_bytes_out = 8; + static constexpr size_t m_encoding_bytes_in = 5; + static constexpr size_t m_encoding_bytes_out = 8; }; namespace { -char lookup_base32_char(uint8_t x) { - BOTAN_DEBUG_ASSERT(x < 32); - - const auto in_AZ = CT::Mask::is_lt(x, 26); +uint64_t lookup_base32_char(uint64_t x) { + uint64_t r = x; + r += swar_lt(x, 0x1a1a1a1a1a1a1a1a) & 0x2929292929292929; + r += 0x1818181818181818; - const char c_AZ = 'A' + x; - const char c_27 = '2' + (x - 26); - - return in_AZ.select(c_AZ, c_27); + return r; } } // namespace @@ -92,14 +93,16 @@ const uint8_t b6 = ((in[3] & 0x03) << 3) | (in[4] >> 5); const uint8_t b7 = in[4] & 0x1F; - out[0] = lookup_base32_char(b0); - out[1] = lookup_base32_char(b1); - out[2] = lookup_base32_char(b2); - out[3] = lookup_base32_char(b3); - out[4] = lookup_base32_char(b4); - out[5] = lookup_base32_char(b5); - out[6] = lookup_base32_char(b6); - out[7] = lookup_base32_char(b7); + auto b = lookup_base32_char(make_uint64(b0, b1, b2, b3, b4, b5, b6, b7)); + + out[0] = static_cast(get_byte<0>(b)); + out[1] = static_cast(get_byte<1>(b)); + out[2] = static_cast(get_byte<2>(b)); + out[3] = static_cast(get_byte<3>(b)); + out[4] = static_cast(get_byte<4>(b)); + out[5] = static_cast(get_byte<5>(b)); + out[6] = static_cast(get_byte<6>(b)); + out[7] = static_cast(get_byte<7>(b)); } //static @@ -167,4 +170,12 @@ return base32_decode(input.data(), input.size(), ignore_ws); } +size_t base32_encode_max_output(size_t input_length) { + return Base32::encode_max_output(input_length); +} + +size_t base32_decode_max_output(size_t input_length) { + return Base32::decode_max_output(input_length); +} + } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/codec/base32/base32.h botan3-3.12.0+dfsg/src/lib/codec/base32/base32.h --- botan3-3.7.1+dfsg/src/lib/codec/base32/base32.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/codec/base32/base32.h 2026-05-07 01:38:28.000000000 +0000 @@ -112,6 +112,20 @@ */ secure_vector BOTAN_PUBLIC_API(2, 7) base32_decode(std::string_view input, bool ignore_ws = true); +/** +* Calculate the size of output buffer for base32_encode +* @param input_length the length of input in bytes +* @return the size of output buffer in bytes +*/ +size_t BOTAN_PUBLIC_API(3, 8) base32_encode_max_output(size_t input_length); + +/** +* Calculate the size of output buffer for base32_decode +* @param input_length the length of input in bytes +* @return the size of output buffer in bytes +*/ +size_t BOTAN_PUBLIC_API(3, 8) base32_decode_max_output(size_t input_length); + } // namespace Botan #endif diff -Nru botan3-3.7.1+dfsg/src/lib/codec/base58/base58.cpp botan3-3.12.0+dfsg/src/lib/codec/base58/base58.cpp --- botan3-3.7.1+dfsg/src/lib/codec/base58/base58.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/codec/base58/base58.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,5 +1,5 @@ /* -* (C) 2018,2020 Jack Lloyd +* (C) 2018,2020,2026 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -11,7 +11,9 @@ #include #include #include +#include #include +#include namespace Botan { @@ -35,43 +37,92 @@ // "123456789 ABCDEFGH JKLMN PQRSTUVWXYZ abcdefghijk mnopqrstuvwxyz" BOTAN_DEBUG_ASSERT(x < 58); - const auto is_dec_19 = CT::Mask::is_lte(x, 8); - const auto is_alpha_AH = CT::Mask::is_within_range(x, 9, 16); - const auto is_alpha_JN = CT::Mask::is_within_range(x, 17, 21); - const auto is_alpha_PZ = CT::Mask::is_within_range(x, 22, 32); - const auto is_alpha_ak = CT::Mask::is_within_range(x, 33, 43); - // otherwise in 'm'-'z' - - const char c_19 = '1' + x; - const char c_AH = 'A' + (x - 9); - const char c_JN = 'J' + (x - 17); - const char c_PZ = 'P' + (x - 22); - const char c_ak = 'a' + (x - 33); - const char c_mz = 'm' + (x - 44); - - char ret = c_mz; - ret = is_dec_19.select(c_19, ret); - ret = is_alpha_AH.select(c_AH, ret); - ret = is_alpha_JN.select(c_JN, ret); - ret = is_alpha_PZ.select(c_PZ, ret); - ret = is_alpha_ak.select(c_ak, ret); + // This works by computing offset(x) such that x + offset(x) is equal to the + // desired character - return ret; + size_t offset = 49; + + offset += CT::Mask::is_gt(x, 8).if_set_return(7); + offset += CT::Mask::is_gt(x, 16).if_set_return(1); + offset += CT::Mask::is_gt(x, 21).if_set_return(1); + offset += CT::Mask::is_gt(x, 32).if_set_return(6); + offset += CT::Mask::is_gt(x, 43).if_set_return(1); + return static_cast(x + offset); +} + +consteval word base58_conversion_radix() { + if constexpr(sizeof(word) == 8) { + // 58^10 largest that fits into a 64 bit word + return 430804206899405824U; + } else { + // 58^5 largest that fits into a 32 bit word + return 656356768U; + } +} + +consteval size_t base58_conversion_radix_digits() { + if constexpr(sizeof(word) == 8) { + return 10; + } else { + return 5; + } +} + +constexpr std::pair divmod_58(word x) { + BOTAN_DEBUG_ASSERT(x < base58_conversion_radix()); + + word q = 0; + + // Division by constant 58 + // + // Compilers will *usually* convert an expression like `x / 58` into + // exactly this kind of operation, but not necessarily always... + if constexpr(sizeof(word) == 4) { + const uint64_t magic = 2369637129; // ceil(2**36 / 29) + const uint64_t z = magic * x; + q = z >> 37; + } else { + const uint64_t magic = 5088756985850910791; // ceil(2**67 / 29) + uint64_t lo = 0; // unused + uint64_t hi = 0; + mul64x64_128(magic, x >> 1, &lo, &hi); + q = static_cast(hi >> 3); + } + + const uint8_t r = static_cast(x - q * 58); + return std::make_pair(r, q); } std::string base58_encode(BigInt v, size_t leading_zeros) { - const word radix = 58; + constexpr word radix = base58_conversion_radix(); + constexpr size_t radix_digits = base58_conversion_radix_digits(); - std::string result; BigInt q; + std::vector digits; - while(v.is_nonzero()) { - word r; + while(!v.is_zero()) { + word r = 0; ct_divide_word(v, radix, q, r); - result.push_back(lookup_base58_char(static_cast(r))); + + for(size_t i = 0; i != radix_digits; ++i) { + const auto [r58, q58] = divmod_58(r); + digits.push_back(r58); + r = q58; + } v.swap(q); } + // remove leading zeros + while(!digits.empty() && digits.back() == 0) { + digits.pop_back(); + } + + std::string result; + + for(const uint8_t d : digits) { + result.push_back(lookup_base58_char(d)); + } + for(size_t i = 0; i != leading_zeros; ++i) { result.push_back('1'); // 'zero' byte } @@ -91,41 +142,44 @@ } uint8_t base58_value_of(char input) { - // "123456789 ABCDEFGH JKLMN PQRSTUVWXYZ abcdefghijk mnopqrstuvwxyz" + /* + * Alphabet: "123456789 ABCDEFGH JKLMN PQRSTUVWXYZ abcdefghijk mnopqrstuvwxyz" + * + * Valid input ranges are: + * + * '1'-'9' (length 9) + * 'A'-'H' (length 8) + * 'J'-'N' (length 5) + * 'P'-'Z' (length 11) + * 'a'-'k' (length 11) + * 'm'-'z' (length 14) + */ + constexpr uint64_t v_lo = make_uint64(0, '1', 'A', 'J', 'P', 'a', 'm', 0); + constexpr uint64_t v_range = make_uint64(0, 9, 8, 5, 11, 11, 14, 0); + + const uint8_t x = static_cast(input); + const uint64_t x8 = x * 0x0101010101010101; // replicate x to each byte + + // is x8 in any of the ranges? + const uint64_t v_mask = swar_in_range(x8, v_lo, v_range) ^ 0x8000000000000000; + + /* + * Offsets mapping from the character code x to the base58 value of x in each range + * + * For example '2' (50) + 0xCF == 1 + * + * Fallback byte 7 is set to 0xFF - x so that if used it results in 0xFF to indicate invalid. + */ + constexpr uint64_t val_v_const = make_uint64(0, 0xCF, 0xC8, 0xC7, 0xC6, 0xC0, 0xBF, 0); + const uint64_t val_v = val_v_const ^ (static_cast(0xFF - x) << 56); - const uint8_t c = static_cast(input); - - const auto is_dec_19 = CT::Mask::is_within_range(c, uint8_t('1'), uint8_t('9')); - const auto is_alpha_AH = CT::Mask::is_within_range(c, uint8_t('A'), uint8_t('H')); - const auto is_alpha_JN = CT::Mask::is_within_range(c, uint8_t('J'), uint8_t('N')); - const auto is_alpha_PZ = CT::Mask::is_within_range(c, uint8_t('P'), uint8_t('Z')); - - const auto is_alpha_ak = CT::Mask::is_within_range(c, uint8_t('a'), uint8_t('k')); - const auto is_alpha_mz = CT::Mask::is_within_range(c, uint8_t('m'), uint8_t('z')); - - const uint8_t c_dec_19 = c - uint8_t('1'); - const uint8_t c_AH = c - uint8_t('A') + 9; - const uint8_t c_JN = c - uint8_t('J') + 17; - const uint8_t c_PZ = c - uint8_t('P') + 22; - - const uint8_t c_ak = c - uint8_t('a') + 33; - const uint8_t c_mz = c - uint8_t('m') + 44; - - uint8_t ret = 0xFF; // default value - - ret = is_dec_19.select(c_dec_19, ret); - ret = is_alpha_AH.select(c_AH, ret); - ret = is_alpha_JN.select(c_JN, ret); - ret = is_alpha_PZ.select(c_PZ, ret); - ret = is_alpha_ak.select(c_ak, ret); - ret = is_alpha_mz.select(c_mz, ret); - return ret; + return x + static_cast(val_v >> (8 * index_of_first_set_byte(v_mask))); } } // namespace std::string base58_encode(const uint8_t input[], size_t input_length) { - BigInt v(input, input_length); + const BigInt v(input, input_length); return base58_encode(v, count_leading_zeros(input, input_length, 0)); } @@ -139,7 +193,7 @@ std::vector base58_decode(const char input[], size_t input_length) { const size_t leading_zeros = count_leading_zeros(input, input_length, '1'); - BigInt v; + std::vector digits; for(size_t i = leading_zeros; i != input_length; ++i) { const char c = input[i]; @@ -154,8 +208,29 @@ throw Decoding_Error("Invalid base58"); } + digits.push_back(idx); + } + + BigInt v; + + constexpr word radix1 = 58; + constexpr word radix2 = 58 * 58; + constexpr word radix3 = 58 * 58 * 58; + constexpr word radix4 = 58 * 58 * 58 * 58; + + std::span remaining{digits}; + + while(remaining.size() >= 4) { + const word accum = radix3 * remaining[0] + radix2 * remaining[1] + radix1 * remaining[2] + remaining[3]; + v *= radix4; + v += accum; + remaining = remaining.subspan(4); + } + + while(!remaining.empty()) { v *= 58; - v += idx; + v += remaining[0]; + remaining = remaining.subspan(1); } return v.serialize(v.bytes() + leading_zeros); diff -Nru botan3-3.7.1+dfsg/src/lib/codec/base58/base58.h botan3-3.12.0+dfsg/src/lib/codec/base58/base58.h --- botan3-3.7.1+dfsg/src/lib/codec/base58/base58.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/codec/base58/base58.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,7 +9,6 @@ #include -#include #include #include #include diff -Nru botan3-3.7.1+dfsg/src/lib/codec/base64/base64.cpp botan3-3.12.0+dfsg/src/lib/codec/base64/base64.cpp --- botan3-3.7.1+dfsg/src/lib/codec/base64/base64.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/codec/base64/base64.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -24,23 +23,23 @@ public: static std::string name() noexcept { return "base64"; } - static size_t encoding_bytes_in() noexcept { return m_encoding_bytes_in; } + static constexpr size_t encoding_bytes_in() noexcept { return m_encoding_bytes_in; } - static size_t encoding_bytes_out() noexcept { return m_encoding_bytes_out; } + static constexpr size_t encoding_bytes_out() noexcept { return m_encoding_bytes_out; } - static size_t decoding_bytes_in() noexcept { return m_encoding_bytes_out; } + static constexpr size_t decoding_bytes_in() noexcept { return m_encoding_bytes_out; } - static size_t decoding_bytes_out() noexcept { return m_encoding_bytes_in; } + static constexpr size_t decoding_bytes_out() noexcept { return m_encoding_bytes_in; } - static size_t bits_consumed() noexcept { return m_encoding_bits; } + static constexpr size_t bits_consumed() noexcept { return m_encoding_bits; } - static size_t remaining_bits_before_padding() noexcept { return m_remaining_bits_before_padding; } + static constexpr size_t remaining_bits_before_padding() noexcept { return m_remaining_bits_before_padding; } - static size_t encode_max_output(size_t input_length) { + static constexpr size_t encode_max_output(size_t input_length) { return (round_up(input_length, m_encoding_bytes_in) / m_encoding_bytes_in) * m_encoding_bytes_out; } - static size_t decode_max_output(size_t input_length) { + static constexpr size_t decode_max_output(size_t input_length) { return (round_up(input_length, m_encoding_bytes_out) * m_encoding_bytes_in) / m_encoding_bytes_out; } @@ -59,11 +58,11 @@ static size_t bytes_to_remove(size_t final_truncate) { return final_truncate; } private: - static const size_t m_encoding_bits = 6; - static const size_t m_remaining_bits_before_padding = 8; + static constexpr size_t m_encoding_bits = 6; + static constexpr size_t m_remaining_bits_before_padding = 8; - static const size_t m_encoding_bytes_in = 3; - static const size_t m_encoding_bytes_out = 4; + static constexpr size_t m_encoding_bytes_in = 3; + static constexpr size_t m_encoding_bytes_out = 4; }; uint32_t lookup_base64_chars(uint32_t x32) { @@ -133,7 +132,7 @@ // This is the offset added to x to get the value const uint64_t val_v = 0xbfb904 ^ (0xFF000000 - (x << 24)); - uint8_t z = x + static_cast(val_v >> (8 * index_of_first_set_byte(v_mask))); + const uint8_t z = x + static_cast(val_v >> (8 * index_of_first_set_byte(v_mask))); // Valid base64 special characters, and some whitespace chars constexpr uint64_t specials_i = make_uint64(0, '+', '/', '=', ' ', '\n', '\t', '\r'); diff -Nru botan3-3.7.1+dfsg/src/lib/codec/hex/hex.cpp botan3-3.12.0+dfsg/src/lib/codec/hex/hex.cpp --- botan3-3.7.1+dfsg/src/lib/codec/hex/hex.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/codec/hex/hex.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -43,7 +42,7 @@ std::string hex_encode(const uint8_t input[], size_t input_length, bool uppercase) { std::string output(2 * input_length, 0); - if(input_length) { + if(input_length > 0) { hex_encode(&output.front(), input, input_length, uppercase); } @@ -100,7 +99,7 @@ } input_consumed = input_length; - size_t written = (out_ptr - output); + const size_t written = (out_ptr - output); /* * We only got half of a uint8_t at the end; zap the half-written @@ -116,7 +115,7 @@ size_t hex_decode(uint8_t output[], const char input[], size_t input_length, bool ignore_ws) { size_t consumed = 0; - size_t written = hex_decode(output, input, input_length, consumed, ignore_ws); + const size_t written = hex_decode(output, input, input_length, consumed, ignore_ws); if(consumed != input_length) { throw Invalid_Argument("hex_decode: input did not have full bytes"); @@ -136,7 +135,7 @@ secure_vector hex_decode_locked(const char input[], size_t input_length, bool ignore_ws) { secure_vector bin(1 + input_length / 2); - size_t written = hex_decode(bin.data(), input, input_length, ignore_ws); + const size_t written = hex_decode(bin.data(), input, input_length, ignore_ws); bin.resize(written); return bin; @@ -149,7 +148,7 @@ std::vector hex_decode(const char input[], size_t input_length, bool ignore_ws) { std::vector bin(1 + input_length / 2); - size_t written = hex_decode(bin.data(), input, input_length, ignore_ws); + const size_t written = hex_decode(bin.data(), input, input_length, ignore_ws); bin.resize(written); return bin; diff -Nru botan3-3.7.1+dfsg/src/lib/compat/sodium/sodium.h botan3-3.12.0+dfsg/src/lib/compat/sodium/sodium.h --- botan3-3.7.1+dfsg/src/lib/compat/sodium/sodium.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/compat/sodium/sodium.h 2026-05-07 01:38:28.000000000 +0000 @@ -16,7 +16,7 @@ namespace Botan::Sodium { // sodium/randombytes.h -enum Sodium_Constants : size_t { +enum Sodium_Constants : uint32_t /* NOLINT(*-use-enum-class) */ { SODIUM_SIZE_MAX = 0xFFFFFFFF, crypto_aead_chacha20poly1305_ABYTES = 16, @@ -1138,7 +1138,7 @@ // sodium/crypto_stream_salsa20.h inline size_t crypto_stream_salsa20_keybytes() { - return crypto_stream_xsalsa20_KEYBYTES; + return crypto_stream_salsa20_KEYBYTES; } inline size_t crypto_stream_salsa20_noncebytes() { @@ -1254,7 +1254,7 @@ BOTAN_PUBLIC_API(2, 11) int crypto_sign_ed25519_detached( - uint8_t sig[], unsigned long long* sig_len, const uint8_t msg[], size_t msg_len, const uint8_t sk[32]); + uint8_t sig[], unsigned long long* sig_len, const uint8_t msg[], size_t msg_len, const uint8_t sk[64]); BOTAN_PUBLIC_API(2, 11) int crypto_sign_ed25519_verify_detached(const uint8_t sig[], const uint8_t msg[], size_t msg_len, const uint8_t pk[32]); @@ -1291,16 +1291,16 @@ return "ed25519"; } -inline int crypto_sign_seed_keypair(uint8_t pk[32], uint8_t sk[32], const uint8_t seed[]) { +inline int crypto_sign_seed_keypair(uint8_t pk[32], uint8_t sk[64], const uint8_t seed[]) { return crypto_sign_ed25519_seed_keypair(pk, sk, seed); } -inline int crypto_sign_keypair(uint8_t pk[32], uint8_t sk[32]) { +inline int crypto_sign_keypair(uint8_t pk[32], uint8_t sk[64]) { return crypto_sign_ed25519_keypair(pk, sk); } inline int crypto_sign_detached( - uint8_t sig[], unsigned long long* sig_len, const uint8_t msg[], size_t msg_len, const uint8_t sk[32]) { + uint8_t sig[], unsigned long long* sig_len, const uint8_t msg[], size_t msg_len, const uint8_t sk[64]) { return crypto_sign_ed25519_detached(sig, sig_len, msg, msg_len, sk); } diff -Nru botan3-3.7.1+dfsg/src/lib/compat/sodium/sodium_25519.cpp botan3-3.12.0+dfsg/src/lib/compat/sodium/sodium_25519.cpp --- botan3-3.7.1+dfsg/src/lib/compat/sodium/sodium_25519.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/compat/sodium/sodium_25519.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,12 +8,14 @@ #include #include +#include namespace Botan { int Sodium::crypto_scalarmult_curve25519(uint8_t out[32], const uint8_t scalar[32], const uint8_t point[32]) { curve25519_donna(out, scalar, point); - return 0; + // Return -1 if the result is the identity + return -static_cast(CT::all_zeros(out, 32).if_set_return(1)); } int Sodium::crypto_scalarmult_curve25519_base(uint8_t out[32], const uint8_t scalar[32]) { @@ -22,10 +24,10 @@ } int Sodium::crypto_sign_ed25519_detached( - uint8_t sig[], unsigned long long* sig_len, const uint8_t msg[], size_t msg_len, const uint8_t sk[32]) { + uint8_t sig[], unsigned long long* sig_len, const uint8_t msg[], size_t msg_len, const uint8_t sk[64]) { ed25519_sign(sig, msg, msg_len, sk, nullptr, 0); - if(sig_len) { + if(sig_len != nullptr) { *sig_len = 64; } return 0; diff -Nru botan3-3.7.1+dfsg/src/lib/compat/sodium/sodium_aead.cpp botan3-3.12.0+dfsg/src/lib/compat/sodium/sodium_aead.cpp --- botan3-3.7.1+dfsg/src/lib/compat/sodium/sodium_aead.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/compat/sodium/sodium_aead.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,6 +7,7 @@ #include #include +#include #include namespace Botan { @@ -36,7 +37,7 @@ chacha20poly1305->finish(buf); copy_mem(ctext, buf.data(), buf.size()); - if(ctext_len) { + if(ctext_len != nullptr) { *ctext_len = buf.size(); } return 0; @@ -55,7 +56,9 @@ return -1; } - *ptext_len = 0; + if(ptext_len != nullptr) { + *ptext_len = 0; + } auto chacha20poly1305 = AEAD_Mode::create_or_throw("ChaCha20Poly1305", Cipher_Dir::Decryption); @@ -73,7 +76,9 @@ return -1; } - *ptext_len = ctext_len - 16; + if(ptext_len != nullptr) { + *ptext_len = ctext_len - 16; + } copy_mem(ptext, buf.data(), buf.size()); return 0; @@ -181,7 +186,7 @@ const uint8_t key[]) { BOTAN_UNUSED(unused_secret_nonce); - if(mac_len) { + if(mac_len != nullptr) { *mac_len = 16; } @@ -243,7 +248,7 @@ const uint8_t nonce[], const uint8_t key[]) { BOTAN_UNUSED(unused_secret_nonce); - if(mac_len) { + if(mac_len != nullptr) { *mac_len = 16; } @@ -307,7 +312,7 @@ const uint8_t nonce[], const uint8_t key[]) { BOTAN_UNUSED(unused_secret_nonce); - if(mac_len) { + if(mac_len != nullptr) { *mac_len = 16; } diff -Nru botan3-3.7.1+dfsg/src/lib/compat/sodium/sodium_auth.cpp botan3-3.12.0+dfsg/src/lib/compat/sodium/sodium_auth.cpp --- botan3-3.7.1+dfsg/src/lib/compat/sodium/sodium_auth.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/compat/sodium/sodium_auth.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,6 +8,7 @@ #include #include +#include namespace Botan { @@ -47,7 +48,7 @@ const uint8_t key[]) { secure_vector computed(crypto_onetimeauth_poly1305_BYTES); crypto_onetimeauth_poly1305(computed.data(), in, in_len, key); - return crypto_verify_16(computed.data(), mac) ? 0 : -1; + return sodium_memcmp(computed.data(), mac, computed.size()); } int Sodium::crypto_auth_hmacsha512(uint8_t out[], const uint8_t in[], size_t in_len, const uint8_t key[]) { @@ -61,7 +62,7 @@ int Sodium::crypto_auth_hmacsha512_verify(const uint8_t mac[], const uint8_t in[], size_t in_len, const uint8_t key[]) { secure_vector computed(crypto_auth_hmacsha512_BYTES); crypto_auth_hmacsha512(computed.data(), in, in_len, key); - return crypto_verify_64(computed.data(), mac) ? 0 : -1; + return sodium_memcmp(computed.data(), mac, computed.size()); } int Sodium::crypto_auth_hmacsha512256(uint8_t out[], const uint8_t in[], size_t in_len, const uint8_t key[]) { @@ -82,7 +83,7 @@ const uint8_t key[]) { secure_vector computed(crypto_auth_hmacsha512256_BYTES); crypto_auth_hmacsha512256(computed.data(), in, in_len, key); - return crypto_verify_32(computed.data(), mac) ? 0 : -1; + return sodium_memcmp(computed.data(), mac, computed.size()); } int Sodium::crypto_auth_hmacsha256(uint8_t out[], const uint8_t in[], size_t in_len, const uint8_t key[]) { @@ -96,7 +97,7 @@ int Sodium::crypto_auth_hmacsha256_verify(const uint8_t mac[], const uint8_t in[], size_t in_len, const uint8_t key[]) { secure_vector computed(crypto_auth_hmacsha256_BYTES); crypto_auth_hmacsha256(computed.data(), in, in_len, key); - return crypto_verify_32(computed.data(), mac) ? 0 : -1; + return sodium_memcmp(computed.data(), mac, computed.size()); } } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/compat/sodium/sodium_chacha.cpp botan3-3.12.0+dfsg/src/lib/compat/sodium/sodium_chacha.cpp --- botan3-3.7.1+dfsg/src/lib/compat/sodium/sodium_chacha.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/compat/sodium/sodium_chacha.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -25,7 +25,7 @@ int Sodium::crypto_stream_chacha20_xor_ic( uint8_t out[], const uint8_t in[], size_t in_len, const uint8_t nonce[], uint64_t ic, const uint8_t key[]) { - if((ic >> 6) != 0) { // otherwise multiply overflows + if((ic >> 58) != 0) { // otherwise multiply overflows return -1; } @@ -75,7 +75,7 @@ int Sodium::crypto_stream_xchacha20_xor_ic( uint8_t out[], const uint8_t in[], size_t in_len, const uint8_t nonce[], uint64_t ic, const uint8_t key[]) { - if((ic >> 6) != 0) { // otherwise multiply overflows + if((ic >> 58) != 0) { // otherwise multiply overflows return -1; } diff -Nru botan3-3.7.1+dfsg/src/lib/compat/sodium/sodium_salsa.cpp botan3-3.12.0+dfsg/src/lib/compat/sodium/sodium_salsa.cpp --- botan3-3.7.1+dfsg/src/lib/compat/sodium/sodium_salsa.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/compat/sodium/sodium_salsa.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -64,7 +64,7 @@ int Sodium::crypto_stream_salsa20_xor_ic( uint8_t out[], const uint8_t in[], size_t in_len, const uint8_t nonce[], uint64_t ic, const uint8_t key[]) { - if((ic >> 6) != 0) { // otherwise multiply overflows + if((ic >> 58) != 0) { // otherwise multiply overflows return -1; } @@ -91,7 +91,7 @@ int Sodium::crypto_stream_xsalsa20_xor_ic( uint8_t out[], const uint8_t in[], size_t in_len, const uint8_t nonce[], uint64_t ic, const uint8_t key[]) { - if((ic >> 6) != 0) { // otherwise multiply overflows + if((ic >> 58) != 0) { // otherwise multiply overflows return -1; } diff -Nru botan3-3.7.1+dfsg/src/lib/compat/sodium/sodium_secretbox.cpp botan3-3.12.0+dfsg/src/lib/compat/sodium/sodium_secretbox.cpp --- botan3-3.7.1+dfsg/src/lib/compat/sodium/sodium_secretbox.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/compat/sodium/sodium_secretbox.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,6 +7,7 @@ #include #include +#include #include #include #include diff -Nru botan3-3.7.1+dfsg/src/lib/compat/sodium/sodium_utils.cpp botan3-3.12.0+dfsg/src/lib/compat/sodium/sodium_utils.cpp --- botan3-3.7.1+dfsg/src/lib/compat/sodium/sodium_utils.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/compat/sodium/sodium_utils.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -13,10 +13,6 @@ #include #include -#if defined(BOTAN_HAS_OS_UTILS) - #include -#endif - namespace Botan { void Sodium::randombytes_buf(void* buf, size_t len) { @@ -29,7 +25,7 @@ } // Not completely uniform - uint64_t x; + uint64_t x = 0; randombytes_buf(&x, sizeof(x)); return x % upper_bound; } @@ -44,15 +40,15 @@ } int Sodium::crypto_verify_16(const uint8_t x[16], const uint8_t y[16]) { - return static_cast(CT::is_equal(x, y, 16).select(1, 0)); + return static_cast(CT::is_equal(x, y, 16).select(1, 0)) - 1; } int Sodium::crypto_verify_32(const uint8_t x[32], const uint8_t y[32]) { - return static_cast(CT::is_equal(x, y, 32).select(1, 0)); + return static_cast(CT::is_equal(x, y, 32).select(1, 0)) - 1; } int Sodium::crypto_verify_64(const uint8_t x[64], const uint8_t y[64]) { - return static_cast(CT::is_equal(x, y, 64).select(1, 0)); + return static_cast(CT::is_equal(x, y, 64).select(1, 0)) - 1; } void Sodium::sodium_memzero(void* ptr, size_t len) { @@ -93,15 +89,16 @@ uint8_t carry = 1; for(size_t i = 0; i != len; ++i) { b[i] += carry; - carry &= (b[i] == 0); + carry &= CT::Mask::is_zero(b[i]).if_set_return(1); } } void Sodium::sodium_add(uint8_t a[], const uint8_t b[], size_t len) { - uint8_t carry = 0; + uint16_t carry = 0; for(size_t i = 0; i != len; ++i) { - a[i] += b[i] + carry; - carry = (a[i] < b[i]); + carry += static_cast(a[i]) + b[i]; + a[i] = static_cast(carry); + carry >>= 8; } } @@ -112,7 +109,7 @@ return nullptr; } - // NOLINTNEXTLINE(*-no-malloc) + // NOLINTNEXTLINE(*-no-malloc,*-owning-memory,*-const-correctness) uint8_t* p = static_cast(std::calloc(size + sizeof(len), 1)); store_le(len, p); return p + 8; @@ -126,36 +123,25 @@ uint8_t* p = static_cast(ptr) - 8; const uint64_t len = load_le(p, 0); secure_scrub_memory(ptr, static_cast(len)); - // NOLINTNEXTLINE(*-no-malloc) + // NOLINTNEXTLINE(*-no-malloc,*-owning-memory) std::free(p); } void* Sodium::sodium_allocarray(size_t count, size_t size) { - const size_t bytes = count * size; - if(bytes < count || bytes < size) { + if(count > 0 && size > SIZE_MAX / count) { return nullptr; } - return sodium_malloc(bytes); + return sodium_malloc(count * size); } int Sodium::sodium_mprotect_noaccess(void* ptr) { -#if defined(BOTAN_HAS_OS_UTILS) - OS::page_prohibit_access(ptr); - return 0; -#else BOTAN_UNUSED(ptr); return -1; -#endif } int Sodium::sodium_mprotect_readwrite(void* ptr) { -#if defined(BOTAN_HAS_OS_UTILS) - OS::page_allow_access(ptr); - return 0; -#else BOTAN_UNUSED(ptr); return -1; -#endif } } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/compression/bzip2/bzip2.cpp botan3-3.12.0+dfsg/src/lib/compression/bzip2/bzip2.cpp --- botan3-3.7.1+dfsg/src/lib/compression/bzip2/bzip2.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/compression/bzip2/bzip2.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -46,7 +46,7 @@ block_size = 9; } - int rc = BZ2_bzCompressInit(streamp(), static_cast(block_size), 0, 0); + const int rc = BZ2_bzCompressInit(streamp(), static_cast(block_size), 0, 0); if(rc != BZ_OK) { throw Compression_Error("BZ2_bzCompressInit", ErrorType::Bzip2Error, rc); @@ -61,7 +61,7 @@ ~Bzip2_Compression_Stream() override { BZ2_bzCompressEnd(streamp()); } bool run(uint32_t flags) override { - int rc = BZ2_bzCompress(streamp(), flags); + const int rc = BZ2_bzCompress(streamp(), flags); if(rc < 0) { throw Compression_Error("BZ2_bzCompress", ErrorType::Bzip2Error, rc); @@ -74,7 +74,7 @@ class Bzip2_Decompression_Stream final : public Bzip2_Stream { public: Bzip2_Decompression_Stream() { - int rc = BZ2_bzDecompressInit(streamp(), 0, 0); + const int rc = BZ2_bzDecompressInit(streamp(), 0, 0); if(rc != BZ_OK) { throw Compression_Error("BZ2_bzDecompressInit", ErrorType::Bzip2Error, rc); @@ -88,8 +88,8 @@ ~Bzip2_Decompression_Stream() override { BZ2_bzDecompressEnd(streamp()); } - bool run(uint32_t) override { - int rc = BZ2_bzDecompress(streamp()); + bool run(uint32_t /*flags*/) override { + const int rc = BZ2_bzDecompress(streamp()); if(rc != BZ_OK && rc != BZ_STREAM_END) { throw Compression_Error("BZ2_bzDecompress", ErrorType::Bzip2Error, rc); diff -Nru botan3-3.7.1+dfsg/src/lib/compression/compress_utils.cpp botan3-3.12.0+dfsg/src/lib/compression/compress_utils.cpp --- botan3-3.7.1+dfsg/src/lib/compression/compress_utils.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/compression/compress_utils.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -25,7 +25,7 @@ return nullptr; } - void* ptr = std::calloc(n, size); // NOLINT(*-no-malloc) + void* ptr = std::calloc(n, size); // NOLINT(*-no-malloc,*-owning-memory,*-const-correctness) /* * Return null rather than throwing here as we are being called by a @@ -35,7 +35,7 @@ * send upwards to the compression wrappers. */ - if(ptr) { + if(ptr != nullptr) { m_current_allocs[ptr] = n * size; } @@ -43,7 +43,7 @@ } void Compression_Alloc_Info::do_free(void* ptr) { - if(ptr) { + if(ptr != nullptr) { auto i = m_current_allocs.find(ptr); if(i == m_current_allocs.end()) { @@ -51,7 +51,7 @@ } secure_scrub_memory(ptr, i->second); - std::free(ptr); // NOLINT(*-no-malloc) + std::free(ptr); // NOLINT(*-no-malloc,*-owning-memory) m_current_allocs.erase(i); } } @@ -152,6 +152,9 @@ } // More data follows: try to process as a following stream + // Remove stream1's unused output space so stream2's output + // is placed immediately after stream1's data with no gap. + m_buffer.resize(m_buffer.size() - m_stream->avail_out()); const size_t read = (buf.size() - offset) - m_stream->avail_in(); start(); m_stream->next_in(buf.data() + offset + read, buf.size() - offset - read); @@ -172,14 +175,27 @@ } void Stream_Decompression::update(secure_vector& buf, size_t offset) { + if(!m_stream) { + if(buf.size() == offset) { + return; + } + // Previous stream ended cleanly; re-initialize for a concatenated stream + start(); + } process(buf, offset, m_stream->run_flag()); } void Stream_Decompression::finish(secure_vector& buf, size_t offset) { - if(buf.size() != offset || m_stream.get()) { - process(buf, offset, m_stream->finish_flag()); + if(!m_stream) { + if(buf.size() == offset) { + return; + } + // Previous stream ended cleanly; re-initialize for a concatenated stream + start(); } + process(buf, offset, m_stream->finish_flag()); + if(m_stream) { throw Invalid_State(fmt("{} finished but not at stream end", name())); } diff -Nru botan3-3.7.1+dfsg/src/lib/compression/compression.h botan3-3.12.0+dfsg/src/lib/compression/compression.h --- botan3-3.7.1+dfsg/src/lib/compression/compression.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/compression/compression.h 2026-05-07 01:38:28.000000000 +0000 @@ -10,6 +10,7 @@ #include #include +#include #include namespace Botan { @@ -17,7 +18,7 @@ /** * Interface for a compression algorithm. */ -class BOTAN_PUBLIC_API(2, 0) Compression_Algorithm { +class BOTAN_PUBLIC_API(2, 0) Compression_Algorithm /* NOLINT(*-special-member-functions) */ { public: /** * Create an instance based on a name, or return null if the @@ -89,7 +90,7 @@ /* * Interface for a decompression algorithm. */ -class BOTAN_PUBLIC_API(2, 0) Decompression_Algorithm { +class BOTAN_PUBLIC_API(2, 0) Decompression_Algorithm /* NOLINT(*-special-member-functions) */ { public: /** * Create an instance based on a name, or return null if the @@ -186,7 +187,7 @@ /** * Adapts a zlib style API */ -class Compression_Stream { +class Compression_Stream /* NOLINT(*-special-member-functions) */ { public: virtual ~Compression_Stream() = default; @@ -228,7 +229,7 @@ }; /** -* FIXME add doc +* Used to implement decompression using Compression_Stream */ class Stream_Decompression : public Decompression_Algorithm { public: diff -Nru botan3-3.7.1+dfsg/src/lib/compression/lzma/lzma.cpp botan3-3.12.0+dfsg/src/lib/compression/lzma/lzma.cpp --- botan3-3.7.1+dfsg/src/lib/compression/lzma/lzma.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/compression/lzma/lzma.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -35,7 +35,7 @@ LZMA_Stream& operator=(LZMA_Stream&& other) = delete; bool run(uint32_t flags) override { - lzma_ret rc = ::lzma_code(streamp(), static_cast(flags)); + const lzma_ret rc = ::lzma_code(streamp(), static_cast(flags)); if(rc != LZMA_OK && rc != LZMA_STREAM_END) { throw Compression_Error("lzma_code", ErrorType::LzmaError, rc); @@ -51,7 +51,7 @@ uint32_t finish_flag() const override { return LZMA_FINISH; } private: - ::lzma_allocator m_allocator; + ::lzma_allocator m_allocator{}; }; class LZMA_Compression_Stream final : public LZMA_Stream { @@ -63,7 +63,7 @@ level = 9; // clamp to maximum allowed value } - lzma_ret rc = ::lzma_easy_encoder(streamp(), static_cast(level), LZMA_CHECK_CRC64); + const lzma_ret rc = ::lzma_easy_encoder(streamp(), static_cast(level), LZMA_CHECK_CRC64); if(rc != LZMA_OK) { throw Compression_Error("lzam_easy_encoder", ErrorType::LzmaError, rc); @@ -74,7 +74,7 @@ class LZMA_Decompression_Stream final : public LZMA_Stream { public: LZMA_Decompression_Stream() { - lzma_ret rc = ::lzma_stream_decoder(streamp(), UINT64_MAX, LZMA_TELL_UNSUPPORTED_CHECK); + const lzma_ret rc = ::lzma_stream_decoder(streamp(), UINT64_MAX, LZMA_TELL_UNSUPPORTED_CHECK); if(rc != LZMA_OK) { throw Compression_Error("lzma_stream_decoder", ErrorType::LzmaError, rc); diff -Nru botan3-3.7.1+dfsg/src/lib/compression/zlib/zlib.cpp botan3-3.12.0+dfsg/src/lib/compression/zlib/zlib.cpp --- botan3-3.7.1+dfsg/src/lib/compression/zlib/zlib.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/compression/zlib/zlib.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -31,7 +31,7 @@ uint32_t finish_flag() const override { return Z_FINISH; } - int compute_window_bits(int wbits, int wbits_offset) const { + static int compute_window_bits(int wbits, int wbits_offset) { if(wbits_offset == -1) { return -wbits; } else { @@ -51,7 +51,7 @@ level = 6; } - int rc = ::deflateInit2(streamp(), static_cast(level), Z_DEFLATED, wbits, 8, Z_DEFAULT_STRATEGY); + const int rc = ::deflateInit2(streamp(), static_cast(level), Z_DEFLATED, wbits, 8, Z_DEFAULT_STRATEGY); if(rc != Z_OK) { throw Compression_Error("deflateInit2", ErrorType::ZlibError, rc); @@ -66,7 +66,7 @@ Zlib_Compression_Stream& operator=(Zlib_Compression_Stream&& other) = delete; bool run(uint32_t flags) override { - int rc = ::deflate(streamp(), flags); + const int rc = ::deflate(streamp(), flags); if(rc != Z_OK && rc != Z_STREAM_END && rc != Z_BUF_ERROR) { throw Compression_Error("zlib deflate", ErrorType::ZlibError, rc); @@ -78,8 +78,8 @@ class Zlib_Decompression_Stream : public Zlib_Stream { public: - Zlib_Decompression_Stream(int wbits, int wbits_offset = 0) { - int rc = ::inflateInit2(streamp(), compute_window_bits(wbits, wbits_offset)); + explicit Zlib_Decompression_Stream(int wbits, int wbits_offset = 0) { + const int rc = ::inflateInit2(streamp(), compute_window_bits(wbits, wbits_offset)); if(rc != Z_OK) { throw Compression_Error("inflateInit2", ErrorType::ZlibError, rc); @@ -94,7 +94,7 @@ Zlib_Decompression_Stream& operator=(Zlib_Decompression_Stream&& other) = delete; bool run(uint32_t flags) override { - int rc = ::inflate(streamp(), flags); + const int rc = ::inflate(streamp(), flags); if(rc != Z_OK && rc != Z_STREAM_END && rc != Z_BUF_ERROR) { throw Compression_Error("zlib inflate", ErrorType::ZlibError, rc); @@ -122,14 +122,14 @@ m_header.os = os_code; m_header.time = static_cast(hdr_time); - int rc = deflateSetHeader(streamp(), &m_header); + const int rc = deflateSetHeader(streamp(), &m_header); if(rc != Z_OK) { throw Compression_Error("deflateSetHeader", ErrorType::ZlibError, rc); } } private: - ::gz_header m_header; + ::gz_header m_header{}; }; class Gzip_Decompression_Stream final : public Zlib_Decompression_Stream { diff -Nru botan3-3.7.1+dfsg/src/lib/entropy/entropy_src.h botan3-3.12.0+dfsg/src/lib/entropy/entropy_src.h --- botan3-3.7.1+dfsg/src/lib/entropy/entropy_src.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/entropy/entropy_src.h 2026-05-07 01:38:28.000000000 +0000 @@ -8,11 +8,11 @@ #ifndef BOTAN_ENTROPY_H_ #define BOTAN_ENTROPY_H_ -#include -#include +#include #include #include #include +#include #include namespace Botan { @@ -48,6 +48,7 @@ Entropy_Source(const Entropy_Source& other) = delete; Entropy_Source(Entropy_Source&& other) = delete; Entropy_Source& operator=(const Entropy_Source& other) = delete; + Entropy_Source& operator=(Entropy_Source&& other) = delete; virtual ~Entropy_Source() = default; }; @@ -70,10 +71,22 @@ * source blocks forever, this invocation will potentially also block. * * @returns the number of bits collected from the entropy sources + * + * TODO(Botan4) remove this variant, and the include above */ + BOTAN_DEPRECATED("Use version without a timeout argument") size_t poll(RandomNumberGenerator& rng, size_t bits, std::chrono::milliseconds timeout); /** + * Poll all sources to collect @p bits of entropy. Entropy collection is + * aborted as soon as the requested number of bits are obtained or the + * timeout runs out. + * + * @returns the number of bits collected from the entropy sources + */ + size_t poll(RandomNumberGenerator& rng, size_t bits); + + /** * Poll just a single named source. Ordinally only used for testing */ size_t poll_just(RandomNumberGenerator& rng, std::string_view src); @@ -84,6 +97,8 @@ Entropy_Sources(const Entropy_Sources& other) = delete; Entropy_Sources(Entropy_Sources&& other) = delete; Entropy_Sources& operator=(const Entropy_Sources& other) = delete; + Entropy_Sources& operator=(Entropy_Sources&& other) = delete; + ~Entropy_Sources() = default; private: std::vector> m_srcs; diff -Nru botan3-3.7.1+dfsg/src/lib/entropy/entropy_srcs.cpp botan3-3.12.0+dfsg/src/lib/entropy/entropy_srcs.cpp --- botan3-3.7.1+dfsg/src/lib/entropy/entropy_srcs.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/entropy/entropy_srcs.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,7 +7,9 @@ #include +#include #include +#include #if defined(BOTAN_HAS_SYSTEM_RNG) #include @@ -42,7 +44,7 @@ class System_RNG_EntropySource final : public Entropy_Source { public: size_t poll(RandomNumberGenerator& rng) override { - const size_t poll_bits = BOTAN_RNG_RESEED_POLL_BITS; + const size_t poll_bits = RandomNumberGenerator::DefaultPollBits; rng.reseed_from_rng(system_rng(), poll_bits); return poll_bits; } @@ -71,7 +73,7 @@ * * The reseeding conditions of the POWER and ARM processor RNGs are not known * but probably work in a somewhat similar manner. The exact amount requested - * may be tweaked if and when such conditions become publically known. + * may be tweaked if and when such conditions become publicly known. */ const size_t poll_bits = 65536; rng.reseed_from_rng(m_hwrng, poll_bits); @@ -92,9 +94,8 @@ class Jitter_RNG_EntropySource final : public Entropy_Source { public: size_t poll(RandomNumberGenerator& rng) override { - const size_t poll_bits = BOTAN_RNG_RESEED_POLL_BITS; - rng.reseed_from_rng(m_rng, poll_bits); - return poll_bits; + rng.reseed_from_rng(m_rng); + return RandomNumberGenerator::DefaultPollBits; } std::string name() const override { return m_rng.name(); } @@ -186,6 +187,20 @@ return bits_collected; } +size_t Entropy_Sources::poll(RandomNumberGenerator& rng, size_t poll_bits) { + size_t bits_collected = 0; + + for(auto& src : m_srcs) { + bits_collected += src->poll(rng); + + if(bits_collected >= poll_bits) { + break; + } + } + + return bits_collected; +} + size_t Entropy_Sources::poll_just(RandomNumberGenerator& rng, std::string_view the_src) { for(auto& src : m_srcs) { if(src->name() == the_src) { @@ -203,7 +218,7 @@ } Entropy_Sources& Entropy_Sources::global_sources() { - static Entropy_Sources global_entropy_sources(BOTAN_ENTROPY_DEFAULT_SOURCES); + static Entropy_Sources global_entropy_sources({"rdseed", "hwrng", "getentropy", "system_rng", "system_stats"}); return global_entropy_sources; } diff -Nru botan3-3.7.1+dfsg/src/lib/entropy/getentropy/getentropy.cpp botan3-3.12.0+dfsg/src/lib/entropy/getentropy/getentropy.cpp --- botan3-3.7.1+dfsg/src/lib/entropy/getentropy/getentropy.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/entropy/getentropy/getentropy.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,6 +6,8 @@ */ #include + +#include #include // macOS and Android include it in sys/random.h instead diff -Nru botan3-3.7.1+dfsg/src/lib/entropy/getentropy/info.txt botan3-3.12.0+dfsg/src/lib/entropy/getentropy/info.txt --- botan3-3.7.1+dfsg/src/lib/entropy/getentropy/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/entropy/getentropy/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + ENTROPY_SRC_GETENTROPY -> 20170327 - + name -> "getentropy" diff -Nru botan3-3.7.1+dfsg/src/lib/entropy/rdseed/info.txt botan3-3.12.0+dfsg/src/lib/entropy/rdseed/info.txt --- botan3-3.7.1+dfsg/src/lib/entropy/rdseed/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/entropy/rdseed/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + ENTROPY_SRC_RDSEED -> 20151218 - + name -> "RDSEED" @@ -15,3 +15,7 @@ rdseed.h + + +cpuid + diff -Nru botan3-3.7.1+dfsg/src/lib/entropy/rdseed/rdseed.cpp botan3-3.12.0+dfsg/src/lib/entropy/rdseed/rdseed.cpp --- botan3-3.7.1+dfsg/src/lib/entropy/rdseed/rdseed.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/entropy/rdseed/rdseed.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,15 +9,19 @@ #include #include +#include #include +#include -#include +#if !defined(BOTAN_USE_GCC_INLINE_ASM) + #include +#endif namespace Botan { namespace { -BOTAN_FUNC_ISA("rdseed") bool read_rdseed(secure_vector& seed) { +BOTAN_FUNC_ISA("rdseed,sse2") bool read_rdseed(secure_vector& seed) { /* * RDSEED is not guaranteed to generate an output within any specific number * of attempts. However in testing on a Skylake system, with all hyperthreads @@ -33,11 +37,11 @@ const size_t RDSEED_RETRIES = 1024; for(size_t i = 0; i != RDSEED_RETRIES; ++i) { - uint32_t r = 0; - int cf = 0; + uint32_t r = 0; // NOLINT(*-const-correctness) clang-tidy doesn't understand inline asm + int cf = 0; // NOLINT(*-const-correctness) clang-tidy doesn't understand inline asm #if defined(BOTAN_USE_GCC_INLINE_ASM) - asm("rdseed %0; adcl $0,%1" : "=r"(r), "=r"(cf) : "0"(r), "1"(cf) : "cc"); + asm("rdseed %0; adcl $0,%1" : "=r"(r), "=r"(cf) : "0"(r), "1"(cf) : "cc"); // NOLINT(*-no-assembler) #else cf = _rdseed32_step(&r); #endif @@ -48,7 +52,11 @@ } // Intel suggests pausing if RDSEED fails. - _mm_pause(); +#if defined(BOTAN_USE_GCC_INLINE_ASM) + asm volatile("pause"); // NOLINT(*-no-assembler) +#else + _mm_pause(); // NOLINT(portability-simd-intrinsics) +#endif } return false; // failed to produce an output after many attempts @@ -60,7 +68,7 @@ const size_t RDSEED_BYTES = 1024; static_assert(RDSEED_BYTES % 4 == 0, "Bad RDSEED configuration"); - if(CPUID::has_rdseed()) { + if(CPUID::has(CPUID::Feature::RDSEED)) { secure_vector seed; seed.reserve(RDSEED_BYTES / 4); diff -Nru botan3-3.7.1+dfsg/src/lib/entropy/win32_stats/es_win32.cpp botan3-3.12.0+dfsg/src/lib/entropy/win32_stats/es_win32.cpp --- botan3-3.7.1+dfsg/src/lib/entropy/win32_stats/es_win32.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/entropy/win32_stats/es_win32.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,6 +6,8 @@ #include +#include + #define NOMINMAX 1 #define _WINSOCKAPI_ // stop windows.h including winsock.h #include diff -Nru botan3-3.7.1+dfsg/src/lib/entropy/win32_stats/info.txt botan3-3.12.0+dfsg/src/lib/entropy/win32_stats/info.txt --- botan3-3.7.1+dfsg/src/lib/entropy/win32_stats/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/entropy/win32_stats/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + ENTROPY_SRC_WIN32 -> 20200209 - + name -> "Win32 Statistics" diff -Nru botan3-3.7.1+dfsg/src/lib/ffi/ffi.cpp botan3-3.12.0+dfsg/src/lib/ffi/ffi.cpp --- botan3-3.7.1+dfsg/src/lib/ffi/ffi.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/ffi/ffi.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -13,7 +13,6 @@ #include #include #include -#include #if defined(BOTAN_HAS_OS_UTILS) #include @@ -26,48 +25,6 @@ // NOLINTNEXTLINE(*-avoid-non-const-global-variables) thread_local std::string g_last_exception_what; -} // namespace - -int ffi_error_exception_thrown(const char* func_name, const char* exn, int rc) { - g_last_exception_what.assign(exn); - -#if defined(BOTAN_HAS_OS_UTILS) - std::string val; - if(Botan::OS::read_env_variable(val, "BOTAN_FFI_PRINT_EXCEPTIONS") == true && !val.empty()) { - static_cast(std::fprintf(stderr, "in %s exception '%s' returning %d\n", func_name, exn, rc)); - } -#endif - - return rc; -} - -int botan_view_str_bounce_fn(botan_view_ctx vctx, const char* str, size_t len) { - return botan_view_bin_bounce_fn(vctx, reinterpret_cast(str), len); -} - -int botan_view_bin_bounce_fn(botan_view_ctx vctx, const uint8_t* buf, size_t len) { - if(vctx == nullptr || buf == nullptr) { - return BOTAN_FFI_ERROR_NULL_POINTER; - } - - botan_view_bounce_struct* ctx = static_cast(vctx); - - const size_t avail = *ctx->out_len; - *ctx->out_len = len; - - if(avail < len || ctx->out_ptr == nullptr) { - if(ctx->out_ptr) { - Botan::clear_mem(ctx->out_ptr, avail); - } - return BOTAN_FFI_ERROR_INSUFFICIENT_BUFFER_SPACE; - } else { - Botan::copy_mem(ctx->out_ptr, buf, len); - return BOTAN_FFI_SUCCESS; - } -} - -namespace { - int ffi_map_error_type(Botan::ErrorType err) { switch(err) { case Botan::ErrorType::Unknown: @@ -125,21 +82,50 @@ } // namespace -int ffi_guard_thunk(const char* func_name, const std::function& thunk) { +void ffi_clear_last_exception() { g_last_exception_what.clear(); +} + +int ffi_error_exception_thrown(const char* func_name, const char* exn, int rc) { + g_last_exception_what.assign(exn); - try { - return thunk(); - } catch(std::bad_alloc&) { - return ffi_error_exception_thrown(func_name, "bad_alloc", BOTAN_FFI_ERROR_OUT_OF_MEMORY); - } catch(Botan_FFI::FFI_Error& e) { - return ffi_error_exception_thrown(func_name, e.what(), e.error_code()); - } catch(Botan::Exception& e) { - return ffi_error_exception_thrown(func_name, e.what(), ffi_map_error_type(e.error_type())); - } catch(std::exception& e) { - return ffi_error_exception_thrown(func_name, e.what()); - } catch(...) { - return ffi_error_exception_thrown(func_name, "unknown exception"); +#if defined(BOTAN_HAS_OS_UTILS) + std::string val; + if(Botan::OS::read_env_variable(val, "BOTAN_FFI_PRINT_EXCEPTIONS") && !val.empty()) { + // NOLINTNEXTLINE(*-vararg) + static_cast(std::fprintf(stderr, "in %s exception '%s' returning %d\n", func_name, exn, rc)); + } +#endif + + return rc; +} + +int ffi_error_exception_thrown(const char* func_name, const char* exn, Botan::ErrorType err) { + return ffi_error_exception_thrown(func_name, exn, ffi_map_error_type(err)); +} + +int botan_view_str_bounce_fn(botan_view_ctx vctx, const char* str, size_t len) { + return botan_view_bin_bounce_fn(vctx, reinterpret_cast(str), len); +} + +int botan_view_bin_bounce_fn(botan_view_ctx vctx, const uint8_t* buf, size_t len) { + if(vctx == nullptr || buf == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + const botan_view_bounce_struct* ctx = static_cast(vctx); + + const size_t avail = *ctx->out_len; + *ctx->out_len = len; + + if(avail < len || ctx->out_ptr == nullptr) { + if(ctx->out_ptr != nullptr) { + Botan::clear_mem(ctx->out_ptr, avail); + } + return BOTAN_FFI_ERROR_INSUFFICIENT_BUFFER_SPACE; + } else { + Botan::copy_mem(ctx->out_ptr, buf, len); + return BOTAN_FFI_SUCCESS; } } @@ -167,6 +153,9 @@ case BOTAN_FFI_ERROR_BAD_MAC: return "Invalid authentication code"; + case BOTAN_FFI_ERROR_NO_VALUE: + return "No value available"; + case BOTAN_FFI_ERROR_INSUFFICIENT_BUFFER_SPACE: return "Insufficient buffer space"; @@ -203,6 +192,9 @@ case BOTAN_FFI_ERROR_INVALID_OBJECT_STATE: return "Invalid object state"; + case BOTAN_FFI_ERROR_OUT_OF_RANGE: + return "Index out of range"; + case BOTAN_FFI_ERROR_NOT_IMPLEMENTED: return "Not implemented"; @@ -216,8 +208,6 @@ return "HTTP error"; case BOTAN_FFI_ERROR_UNKNOWN_ERROR: - return "Unknown error"; - default: return "Unknown error"; } @@ -231,6 +221,26 @@ } int botan_ffi_supports_api(uint32_t api_version) { + // This is the API introduced in 3.12 + if(api_version == 20260506) { + return BOTAN_FFI_SUCCESS; + } + + // This is the API introduced in 3.11 + if(api_version == 20260303) { + return BOTAN_FFI_SUCCESS; + } + + // This is the API introduced in 3.10 + if(api_version == 20250829) { + return BOTAN_FFI_SUCCESS; + } + + // This is the API introduced in 3.8 + if(api_version == 20250506) { + return BOTAN_FFI_SUCCESS; + } + // This is the API introduced in 3.4 if(api_version == 20240408) { return BOTAN_FFI_SUCCESS; @@ -306,6 +316,9 @@ } int botan_constant_time_compare(const uint8_t* x, const uint8_t* y, size_t len) { + if(len > 0 && any_null_pointers(x, y)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } auto same = Botan::CT::is_equal(x, y, len); // Return 0 if same or -1 otherwise return static_cast(same.select(1, 0)) - 1; @@ -316,11 +329,17 @@ } int botan_scrub_mem(void* mem, size_t bytes) { + if(bytes > 0 && mem == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } Botan::secure_scrub_memory(mem, bytes); return BOTAN_FFI_SUCCESS; } int botan_hex_encode(const uint8_t* in, size_t len, char* out, uint32_t flags) { + if(len > 0 && (in == nullptr || out == nullptr)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } return ffi_guard_thunk(__func__, [=]() -> int { const bool uppercase = (flags & BOTAN_FFI_HEX_LOWER_CASE) == 0; Botan::hex_encode(out, in, len, uppercase); @@ -329,6 +348,9 @@ } int botan_hex_decode(const char* hex_str, size_t in_len, uint8_t* out, size_t* out_len) { + if(any_null_pointers(hex_str, out_len)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } return ffi_guard_thunk(__func__, [=]() -> int { const std::vector bin = Botan::hex_decode(hex_str, in_len); return Botan_FFI::write_vec_output(out, out_len, bin); @@ -336,6 +358,9 @@ } int botan_base64_encode(const uint8_t* in, size_t len, char* out, size_t* out_len) { + if(len > 0 && in == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } return ffi_guard_thunk(__func__, [=]() -> int { const std::string base64 = Botan::base64_encode(in, len); return Botan_FFI::write_str_output(out, out_len, base64); @@ -343,6 +368,10 @@ } int botan_base64_decode(const char* base64_str, size_t in_len, uint8_t* out, size_t* out_len) { + if(any_null_pointers(out, out_len, base64_str)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + return ffi_guard_thunk(__func__, [=]() -> int { if(*out_len < Botan::base64_decode_max_output(in_len)) { *out_len = Botan::base64_decode_max_output(in_len); diff -Nru botan3-3.7.1+dfsg/src/lib/ffi/ffi.h botan3-3.12.0+dfsg/src/lib/ffi/ffi.h --- botan3-3.7.1+dfsg/src/lib/ffi/ffi.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/ffi/ffi.h 2026-05-07 01:38:28.000000000 +0000 @@ -2,6 +2,7 @@ * FFI (C89 API) * (C) 2015,2017 Jack Lloyd * (C) 2021 René Fischer +* (C) 2024,2025,2026 Amos Treiber, René Meusel, Rohde & Schwarz Cybersecurity * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -15,7 +16,7 @@ /* This header exports some of botan's functionality via a C89 interface. This API -is uesd by the Python, OCaml, Rust, Ruby, and Haskell bindings via those languages +is used by the Python, OCaml, Rust, Ruby, and Haskell bindings via those languages respective ctypes/FFI libraries. The API is intended to be as easy as possible to call from other @@ -32,33 +33,34 @@ - Use simple types: size_t for lengths, const char* NULL terminated strings, uint8_t for binary. -- No ownership of memory transfers across the API boundary. The API will - consume data from const pointers, and will produce output by writing to - buffers provided by (and allocated by) the caller. - -- If exporting a value (a string or a blob) the function takes a pointer to the - output array and a read/write pointer to the length. If the length is insufficient, an - error is returned. So passing nullptr/0 allows querying the final value. - - Typically there is also a function which allows querying the expected output - length of a function, for example `botan_hash_output_length` allows knowing in - advance the expected size for `botan_hash_final`. Some of these are exact, - while others such as `botan_pk_op_decrypt_output_length` only provide an upper - bound. - - The big exception to this currently is the various functions which serialize - public and private keys, where there are currently no function that can - estimate the serialized size. Here view functions are used; see the handbook - for further details. - - TODO: - - Doxygen comments for all functions/params - - TLS +- No ownership of memory transfers across the API boundary. The API will consume + data from const pointers with specified lengths. Outputs are either placed into + buffers provided by (and allocated by) the caller, or are returned via a + callback (what the FFI layer calls "view" functions). + + When writing to an application-provided buffer, the function takes a pointer + to the output array and a read/write pointer to the length. The length field + is always set to the actual amount of data that would have been written. If + the input buffer's size was insufficient an error is returned. + + In many situations the length of the output can be known in advance without + difficulty, in which case there will be a function which allows querying the + expected output length. For example `botan_hash_output_length` allows knowing + in advance the expected size for `botan_hash_final`. Some of these are exact, + while others such as `botan_pk_op_decrypt_output_length` can only provide an + upper bound for various technical reasons. + + In some cases knowing the exact size is difficult or impossible. In these + situations view functions are used; see the handbook for further details. + + TODO: Doxygen comments for all parameters */ #include #include +/* NOLINTBEGIN(*-macro-usage,*-misplaced-const) */ + /** * The compile time API version. This matches the value of * botan_ffi_api_version. This can be used for compile-time checking if a @@ -68,7 +70,7 @@ * that declaration is not visible here since this header is intentionally * free-standing, depending only on a few C standard library headers. */ -#define BOTAN_FFI_API_VERSION 20240408 +#define BOTAN_FFI_API_VERSION 20260506 /** * BOTAN_FFI_EXPORT indicates public FFI functions. @@ -90,7 +92,7 @@ #endif #endif -#if !defined(BOTAN_NO_DEPRECATED_WARNINGS) +#if !defined(BOTAN_NO_DEPRECATED_WARNINGS) && !defined(BOTAN_AMALGAMATION_H_) && !defined(BOTAN_IS_BEING_BUILT) #if defined(__has_attribute) #if __has_attribute(deprecated) #define BOTAN_FFI_DEPRECATED(msg) __attribute__((deprecated(msg))) @@ -110,12 +112,14 @@ * If you add a new value here be sure to also add it in * botan_error_description */ -enum BOTAN_FFI_ERROR { +enum BOTAN_FFI_ERROR /* NOLINT(*-enum-size,*-use-enum-class) */ { BOTAN_FFI_SUCCESS = 0, + BOTAN_FFI_INVALID_VERIFIER = 1, BOTAN_FFI_ERROR_INVALID_INPUT = -1, BOTAN_FFI_ERROR_BAD_MAC = -2, + BOTAN_FFI_ERROR_NO_VALUE = -3, BOTAN_FFI_ERROR_INSUFFICIENT_BUFFER_SPACE = -10, BOTAN_FFI_ERROR_STRING_CONVERSION_ERROR = -11, @@ -131,6 +135,7 @@ BOTAN_FFI_ERROR_KEY_NOT_SET = -33, BOTAN_FFI_ERROR_INVALID_KEY_LENGTH = -34, BOTAN_FFI_ERROR_INVALID_OBJECT_STATE = -35, + BOTAN_FFI_ERROR_OUT_OF_RANGE = -36, BOTAN_FFI_ERROR_NOT_IMPLEMENTED = -40, BOTAN_FFI_ERROR_INVALID_OBJECT = -50, @@ -143,6 +148,9 @@ BOTAN_FFI_ERROR_UNKNOWN_ERROR = -100, }; +/** +* The application provided context for a view function +*/ typedef void* botan_view_ctx; /** @@ -212,8 +220,9 @@ BOTAN_FFI_EXPORT(2, 0) uint32_t botan_version_patch(void); /** -* Return the date this version was released as -* an integer, or 0 if an unreleased version +* Return the date this version was released as an integer. +* +* Returns 0 if the library was not built from an official release */ BOTAN_FFI_EXPORT(2, 0) uint32_t botan_version_datestamp(void); @@ -234,6 +243,9 @@ */ BOTAN_FFI_EXPORT(2, 2) int botan_scrub_mem(void* mem, size_t bytes); +/** +* Flag that can be provided to botan_hex_encode to request lower case hex +*/ #define BOTAN_FFI_HEX_LOWER_CASE 1 /** @@ -258,6 +270,13 @@ /** * Perform base64 encoding +* +* @param x the input data +* @param len the length of x +* @param out the output buffer +* @param out_len the size of the output buffer on input, set to the number of bytes written +* @return 0 on success, a negative value on failure + */ BOTAN_FFI_EXPORT(2, 3) int botan_base64_encode(const uint8_t* x, size_t len, char* out, size_t* out_len); @@ -304,6 +323,7 @@ /** * Get random bytes from a random number generator +* * @param rng rng object * @param out output buffer of size out_len * @param out_len number of requested bytes @@ -313,6 +333,7 @@ /** * Get random bytes from system random number generator +* * @param out output buffer of size out_len * @param out_len number of requested bytes * @return 0 on success, negative on failure @@ -350,6 +371,35 @@ BOTAN_FFI_EXPORT(2, 8) int botan_rng_add_entropy(botan_rng_t rng, const uint8_t* entropy, size_t entropy_len); /** +* Create and seed a DRBG +* +* @param rng_out the new DRBG object +* @param drbg_name the name of the DRBG (e.g. "HMAC_DRBG(SHA-256)") +* @param seed the seed material (entropy || nonce || personalization_string) +* @param seed_len length of seed in bytes +* @return 0 on success, negative on failure +*/ +BOTAN_FFI_EXPORT(3, 12) +int botan_rng_init_drbg(botan_rng_t* rng_out, const char* drbg_name, const uint8_t* seed, size_t seed_len); + +/** +* Generate random bytes from an RNG with additional input. +* +* For a DRBG, the additional input is mixed in before generating. +* Many other RNG types (eg RDRAND or system RNG) will ignore the input. +* +* @param rng the RNG object +* @param out output buffer +* @param out_len number of bytes to generate +* @param addl_input additional input to mix in (may be NULL if addl_len is 0) +* @param addl_len length of additional input +* @return 0 on success, negative on failure +*/ +BOTAN_FFI_EXPORT(3, 12) +int botan_rng_generate_with_input( + botan_rng_t rng, uint8_t* out, size_t out_len, const uint8_t* addl_input, size_t addl_len); + +/** * Frees all resources of the random number generator object * @param rng rng object * @return 0 if success, error if invalid object handle @@ -357,7 +407,85 @@ BOTAN_FFI_EXPORT(2, 0) int botan_rng_destroy(botan_rng_t rng); /* -* Hash type +* Opaque type of an eXtendable Output Function (XOF) +*/ +typedef struct botan_xof_struct* botan_xof_t; + +/** +* Initialize an eXtendable Output Function +* @param xof XOF object +* @param xof_name name of the XOF, e.g., "SHAKE-128" +* @param flags should be 0 in current API revision, all other uses are reserved +* and return BOTAN_FFI_ERROR_BAD_FLAG +*/ +BOTAN_FFI_EXPORT(3, 11) int botan_xof_init(botan_xof_t* xof, const char* xof_name, uint32_t flags); + +/** +* Copy the state of an eXtendable Output Function +* @param dest destination XOF object +* @param source source XOF object +* @return 0 on success, a negative value on failure +*/ +BOTAN_FFI_EXPORT(3, 11) int botan_xof_copy_state(botan_xof_t* dest, botan_xof_t source); + +/** +* Writes the block size of the eXtendable Output Function to *block_size +* @param xof XOF object +* @param block_size variable to hold the XOF's block size +* @return 0 on success, a negative value on failure +*/ +BOTAN_FFI_EXPORT(3, 11) int botan_xof_block_size(botan_xof_t xof, size_t* block_size); + +/** +* Get the name of this eXtendable Output Function +* @param xof the object to read +* @param name output buffer +* @param name_len on input, the length of buffer, on success the number of bytes written +*/ +BOTAN_FFI_EXPORT(3, 11) int botan_xof_name(botan_xof_t xof, char* name, size_t* name_len); + +/** +* Get the input/output state of this eXtendable Output Function +* Typically, XOFs don't accept input as soon as the first output bytes were requested. +* @param xof the object to read +* @returns 1 iff the XOF is still accepting input bytes +*/ +BOTAN_FFI_EXPORT(3, 11) int botan_xof_accepts_input(botan_xof_t xof); + +/** +* Reinitializes the state of the eXtendable Output Function. +* @param xof XOF object +* @return 0 on success, a negative value on failure +*/ +BOTAN_FFI_EXPORT(3, 11) int botan_xof_clear(botan_xof_t xof); + +/** +* Send more input to the eXtendable Output Function +* @param xof XOF object +* @param in input buffer +* @param in_len number of bytes to read from the input buffer +* @return 0 on success, a negative value on failure +*/ +BOTAN_FFI_EXPORT(3, 11) int botan_xof_update(botan_xof_t xof, const uint8_t* in, size_t in_len); + +/** +* Generate output bytes from the eXtendable Output Function +* @param xof XOF object +* @param out output buffer +* @param out_len number of bytes to write into the output buffer +* @return 0 on success, a negative value on failure +*/ +BOTAN_FFI_EXPORT(3, 11) int botan_xof_output(botan_xof_t xof, uint8_t* out, size_t out_len); + +/** +* Frees all resources of the eXtendable Output Function object +* @param xof xof object +* @return 0 if success, error if invalid object handle +*/ +BOTAN_FFI_EXPORT(3, 11) int botan_xof_destroy(botan_xof_t xof); + +/* +* Opaque type of a hash function */ typedef struct botan_hash_struct* botan_hash_t; @@ -437,7 +565,7 @@ BOTAN_FFI_EXPORT(2, 8) int botan_hash_name(botan_hash_t hash, char* name, size_t* name_len); /* -* Message Authentication type +* Opaque type of a message authentication code */ typedef struct botan_mac_struct* botan_mac_t; @@ -533,7 +661,7 @@ BOTAN_FFI_EXPORT(2, 0) int botan_mac_destroy(botan_mac_t mac); /* -* Cipher modes +* Opaque type of a cipher mode */ typedef struct botan_cipher_struct* botan_cipher_t; @@ -599,13 +727,13 @@ * Get information about the key lengths. Prefer botan_cipher_get_keyspec */ BOTAN_FFI_EXPORT(2, 0) -int botan_cipher_query_keylen(botan_cipher_t, size_t* out_minimum_keylength, size_t* out_maximum_keylength); +int botan_cipher_query_keylen(botan_cipher_t cipher, size_t* out_minimum_keylength, size_t* out_maximum_keylength); /** * Get information about the supported key lengths. */ BOTAN_FFI_EXPORT(2, 8) -int botan_cipher_get_keyspec(botan_cipher_t, size_t* min_keylen, size_t* max_keylen, size_t* mod_keylen); +int botan_cipher_get_keyspec(botan_cipher_t cipher, size_t* min_keylen, size_t* max_keylen, size_t* mod_keylen); /** * Set the key for this cipher object @@ -751,16 +879,17 @@ * @param salt_len length of salt in bytes * @return 0 on success, a negative value on failure */ -int BOTAN_FFI_EXPORT(2, 8) botan_pwdhash(const char* algo, - size_t param1, - size_t param2, - size_t param3, - uint8_t out[], - size_t out_len, - const char* passphrase, - size_t passphrase_len, - const uint8_t salt[], - size_t salt_len); +BOTAN_FFI_EXPORT(2, 8) +int botan_pwdhash(const char* algo, + size_t param1, + size_t param2, + size_t param3, + uint8_t out[], + size_t out_len, + const char* passphrase, + size_t passphrase_len, + const uint8_t salt[], + size_t salt_len); /* * Derive a key from a passphrase @@ -778,17 +907,18 @@ * @param salt_len length of salt in bytes * @return 0 on success, a negative value on failure */ -int BOTAN_FFI_EXPORT(2, 8) botan_pwdhash_timed(const char* algo, - uint32_t msec, - size_t* param1, - size_t* param2, - size_t* param3, - uint8_t out[], - size_t out_len, - const char* passphrase, - size_t passphrase_len, - const uint8_t salt[], - size_t salt_len); +BOTAN_FFI_EXPORT(2, 8) +int botan_pwdhash_timed(const char* algo, + uint32_t msec, + size_t* param1, + size_t* param2, + size_t* param3, + uint8_t out[], + size_t out_len, + const char* passphrase, + size_t passphrase_len, + const uint8_t salt[], + size_t salt_len); /** * Derive a key using scrypt @@ -913,14 +1043,26 @@ BOTAN_FFI_EXPORT(2, 1) int botan_mp_destroy(botan_mp_t mp); /** -* Convert the MPI to a hex string. Writes botan_mp_num_bytes(mp)*2 + 1 bytes +* Convert the MPI to a hex string. Writes up to botan_mp_num_bytes(mp)*2 + 5 bytes +* +* Prefer botan_mp_view_hex */ BOTAN_FFI_EXPORT(2, 1) int botan_mp_to_hex(botan_mp_t mp, char* out); /** -* Convert the MPI to a string. Currently base == 10 and base == 16 are supported. +* View the hex string encoding of the MPI. +*/ +BOTAN_FFI_EXPORT(3, 10) int botan_mp_view_hex(botan_mp_t mp, botan_view_ctx ctx, botan_view_str_fn view); + +/** +* Convert the MPI to a string. Currently radix == 10 and radix == 16 are supported. +*/ +BOTAN_FFI_EXPORT(2, 1) int botan_mp_to_str(botan_mp_t mp, uint8_t radix, char* out, size_t* out_len); + +/** +* View the MPI as a radix-N integer. Currently only radix 10 and radix 16 are supported */ -BOTAN_FFI_EXPORT(2, 1) int botan_mp_to_str(botan_mp_t mp, uint8_t base, char* out, size_t* out_len); +BOTAN_FFI_EXPORT(3, 10) int botan_mp_view_str(botan_mp_t mp, uint8_t radix, botan_view_ctx ctx, botan_view_str_fn view); /** * Set the MPI to zero @@ -960,10 +1102,19 @@ /* * Convert the MPI to a big-endian binary string. Writes botan_mp_num_bytes to vec +* +* Note that the sign of the integer is ignored here; only the absolute value is copied */ BOTAN_FFI_EXPORT(2, 1) int botan_mp_to_bin(botan_mp_t mp, uint8_t vec[]); /* +* View the big-endian binary string encoding of this integer +* +* Note that the sign of the integer is ignored here; only the absolute value is viewed +*/ +BOTAN_FFI_EXPORT(3, 10) int botan_mp_view_bin(botan_mp_t mp, botan_view_ctx ctx, botan_view_bin_fn view); + +/* * Set an MP to the big-endian binary value */ BOTAN_FFI_EXPORT(2, 1) int botan_mp_from_bin(botan_mp_t mp, const uint8_t vec[], size_t vec_len); @@ -1076,8 +1227,10 @@ * @param flags should be 0 in current API revision, all other uses are reserved * and return BOTAN_FFI_ERROR_BAD_FLAG * @return 0 on success, a negative value on failure - +* * Output is formatted bcrypt $2a$... +* +* TOD(Botan4) this should use char for the type of `out` */ BOTAN_FFI_EXPORT(2, 0) int botan_bcrypt_generate( @@ -1094,6 +1247,318 @@ BOTAN_FFI_EXPORT(2, 0) int botan_bcrypt_is_valid(const char* pass, const char* hash); /* +* OIDs +*/ + +typedef struct botan_asn1_oid_struct* botan_asn1_oid_t; + +/** +* @returns negative number on error, or zero on success +*/ +BOTAN_FFI_EXPORT(3, 8) int botan_oid_destroy(botan_asn1_oid_t oid); + +/** +* Create an OID from a string, either dot notation (e.g. '1.2.3.4') or a registered name (e.g. 'RSA') +* @param oid handle to the resulting OID +* @param oid_str the name of the OID to create +* @returns negative number on error, or zero on success +*/ +BOTAN_FFI_EXPORT(3, 8) int botan_oid_from_string(botan_asn1_oid_t* oid, const char* oid_str); + +/** +* Registers an OID so that it may later be retrieved by name +* @returns negative number on error, or zero on success +*/ +BOTAN_FFI_EXPORT(3, 8) int botan_oid_register(botan_asn1_oid_t oid, const char* name); + +/** +* View an OID in dot notation +*/ +BOTAN_FFI_EXPORT(3, 8) int botan_oid_view_string(botan_asn1_oid_t oid, botan_view_ctx ctx, botan_view_str_fn view); + +/** +* View an OIDs registered name if it exists, else its dot notation +*/ +BOTAN_FFI_EXPORT(3, 8) int botan_oid_view_name(botan_asn1_oid_t oid, botan_view_ctx ctx, botan_view_str_fn view); + +/** +* @returns 0 if a != b +* @returns 1 if a == b +* @returns negative number on error +*/ +BOTAN_FFI_EXPORT(3, 8) int botan_oid_equal(botan_asn1_oid_t a, botan_asn1_oid_t b); + +/** +* Sets @param result to comparison result: +* -1 if a < b, 0 if a == b, 1 if a > b +* @returns negative number on error or zero on success +*/ +BOTAN_FFI_EXPORT(3, 8) int botan_oid_cmp(int* result, botan_asn1_oid_t a, botan_asn1_oid_t b); + +/* +* EC Groups +*/ + +typedef struct botan_ec_group_struct* botan_ec_group_t; + +/** +* @returns negative number on error, or zero on success +*/ +BOTAN_FFI_EXPORT(3, 8) int botan_ec_group_destroy(botan_ec_group_t ec_group); + +/** +* Checks if in this build configuration it is possible to register an application specific elliptic curve and sets +* @param out to 1 if so, 0 otherwise +* @returns 0 on success, a negative value on failure +*/ +BOTAN_FFI_EXPORT(3, 8) int botan_ec_group_supports_application_specific_group(int* out); + +/** +* Checks if in this build configuration botan_ec_group_from_name(group_ptr, name) will succeed and sets +* @param out to 1 if so, 0 otherwise. +* @returns negative number on error, or zero on success +*/ +BOTAN_FFI_EXPORT(3, 8) int botan_ec_group_supports_named_group(const char* name, int* out); + +/** +* Create a new EC Group from parameters +* @warning use only elliptic curve parameters that you trust +* +* @param ec_group the new object will be placed here +* @param p the elliptic curve prime (at most 521 bits) +* @param a the elliptic curve a param +* @param b the elliptic curve b param +* @param base_x the x coordinate of the group generator +* @param base_y the y coordinate of the group generator +* @param order the order of the group +* @returns negative number on error, or zero on success +*/ +BOTAN_FFI_EXPORT(3, 8) +int botan_ec_group_from_params(botan_ec_group_t* ec_group, + botan_asn1_oid_t oid, + botan_mp_t p, + botan_mp_t a, + botan_mp_t b, + botan_mp_t base_x, + botan_mp_t base_y, + botan_mp_t order); + +/** +* Decode a BER encoded ECC domain parameter set +* @param ec_group the new object will be placed here +* @param ber encoding +* @param ber_len size of the encoding in bytes +* @returns negative number on error, or zero on success +*/ +BOTAN_FFI_EXPORT(3, 8) int botan_ec_group_from_ber(botan_ec_group_t* ec_group, const uint8_t* ber, size_t ber_len); + +/** +* Initialize an EC Group from the PEM/ASN.1 encoding +* @param ec_group the new object will be placed here +* @param pem encoding +* @returns negative number on error, or zero on success +*/ +BOTAN_FFI_EXPORT(3, 8) int botan_ec_group_from_pem(botan_ec_group_t* ec_group, const char* pem); + +/** +* Initialize an EC Group from a group named by an object identifier +* @param ec_group the new object will be placed here +* @param oid a known OID +* @returns negative number on error, or zero on success +*/ +BOTAN_FFI_EXPORT(3, 8) int botan_ec_group_from_oid(botan_ec_group_t* ec_group, botan_asn1_oid_t oid); + +/** +* Initialize an EC Group from a common group name (eg "secp256r1") +* @param ec_group the new object will be placed here +* @param name a known group name +* @returns negative number on error, or zero on success +*/ +BOTAN_FFI_EXPORT(3, 8) int botan_ec_group_from_name(botan_ec_group_t* ec_group, const char* name); + +/** +* Unregister a previously registered group. +* @param oid the oid associated with the group to unregister +* @returns 1 if the group was found and unregistered, else 0 +* +* Using this is discouraged for normal use. This is only useful or necessary if +* you are registering a very large number of distinct groups, and need to worry about memory constraints. +*/ +BOTAN_FFI_EXPORT(3, 11) int botan_ec_group_unregister(botan_asn1_oid_t oid); + +/** +* View an EC Group in DER encoding +*/ +BOTAN_FFI_EXPORT(3, 8) +int botan_ec_group_view_der(botan_ec_group_t ec_group, botan_view_ctx ctx, botan_view_bin_fn view); + +/** +* View an EC Group in PEM encoding +*/ +BOTAN_FFI_EXPORT(3, 8) +int botan_ec_group_view_pem(botan_ec_group_t ec_group, botan_view_ctx ctx, botan_view_str_fn view); + +/** +* Get the curve OID of an EC Group +*/ +BOTAN_FFI_EXPORT(3, 8) int botan_ec_group_get_curve_oid(botan_asn1_oid_t* oid, botan_ec_group_t ec_group); + +/** +* Get the prime modulus of the field +*/ +BOTAN_FFI_EXPORT(3, 8) int botan_ec_group_get_p(botan_mp_t* p, botan_ec_group_t ec_group); + +/** +* Get the a parameter of the elliptic curve equation +*/ +BOTAN_FFI_EXPORT(3, 8) int botan_ec_group_get_a(botan_mp_t* a, botan_ec_group_t ec_group); + +/** +* Get the b parameter of the elliptic curve equation +*/ +BOTAN_FFI_EXPORT(3, 8) int botan_ec_group_get_b(botan_mp_t* b, botan_ec_group_t ec_group); + +/** +* Get the x coordinate of the base point +*/ +BOTAN_FFI_EXPORT(3, 8) int botan_ec_group_get_g_x(botan_mp_t* g_x, botan_ec_group_t ec_group); + +/** +* Get the y coordinate of the base point +*/ +BOTAN_FFI_EXPORT(3, 8) int botan_ec_group_get_g_y(botan_mp_t* g_y, botan_ec_group_t ec_group); + +/** +* Get the order of the base point +*/ +BOTAN_FFI_EXPORT(3, 8) int botan_ec_group_get_order(botan_mp_t* order, botan_ec_group_t ec_group); + +/** +* @returns 0 if curve1 != curve2 +* @returns 1 if curve1 == curve2 +* @returns negative number on error +*/ +BOTAN_FFI_EXPORT(3, 8) int botan_ec_group_equal(botan_ec_group_t curve1, botan_ec_group_t curve2); + +/* +* EC Points and Scalars +*/ +typedef struct botan_ec_scalar_struct* botan_ec_scalar_t; +typedef struct botan_ec_point_struct* botan_ec_point_t; + +BOTAN_FFI_EXPORT(3, 12) int botan_ec_scalar_destroy(botan_ec_scalar_t ec_scalar); + +/** +* Create a new random scalar value +*/ +BOTAN_FFI_EXPORT(3, 12) +int botan_ec_scalar_random(botan_ec_scalar_t* ec_scalar, botan_ec_group_t ec_group, botan_rng_t rng); + +/** +* Convert from an MPI to a scalar +* @returns a negative number if the provided MPI is negative or too large, 0 on success +*/ +BOTAN_FFI_EXPORT(3, 12) +int botan_ec_scalar_from_mp(botan_ec_scalar_t* ec_scalar, botan_ec_group_t ec_group, botan_mp_t mp); + +/** +* Convert from a scalar to an MPI +* @returns a negative number on failure, 0 on success +*/ +BOTAN_FFI_EXPORT(3, 12) +int botan_ec_scalar_to_mp(botan_ec_scalar_t ec_scalar, botan_mp_t* mp); + +BOTAN_FFI_EXPORT(3, 12) int botan_ec_point_destroy(botan_ec_point_t ec_point); + +/** +* Create a point set to the identity element of the group +*/ +BOTAN_FFI_EXPORT(3, 12) int botan_ec_point_identity(botan_ec_point_t* ec_point, botan_ec_group_t ec_group); + +/** +* Create a point set to the standard group generator +*/ +BOTAN_FFI_EXPORT(3, 12) int botan_ec_point_generator(botan_ec_point_t* ec_point, botan_ec_group_t ec_group); + +/** +* Create a point from a pair (x,y) of integers +* The integers must be within the field and must satisfy the curve equation +*/ +BOTAN_FFI_EXPORT(3, 12) +int botan_ec_point_from_xy(botan_ec_point_t* ec_point, botan_ec_group_t ec_group, botan_mp_t x, botan_mp_t y); + +/** +* Create a point from a SEC1 compressed or uncompressed format. +* @returns negative number on error +*/ +BOTAN_FFI_EXPORT(3, 12) +int botan_ec_point_from_bytes(botan_ec_point_t* ec_point, + botan_ec_group_t ec_group, + const uint8_t* bytes, + size_t bytes_len); + +/** +* View the fixed length encoding of the affine x coordinate +* @returns negative number on error +*/ +BOTAN_FFI_EXPORT(3, 12) +int botan_ec_point_view_x_bytes(botan_ec_point_t ec_point, botan_view_ctx ctx, botan_view_bin_fn view); + +/** +* View the fixed length encoding of the affine y coordinate +* @returns negative number on error +*/ +BOTAN_FFI_EXPORT(3, 12) +int botan_ec_point_view_y_bytes(botan_ec_point_t ec_point, botan_view_ctx ctx, botan_view_bin_fn view); + +/** +* View the fixed length encoding of the affine x and y coordinates +* @returns negative number on error +*/ +BOTAN_FFI_EXPORT(3, 12) +int botan_ec_point_view_xy_bytes(botan_ec_point_t ec_point, botan_view_ctx ctx, botan_view_bin_fn view); + +/** +* View the fixed length SEC1 uncompressed encoding +* @returns negative number on error +*/ +BOTAN_FFI_EXPORT(3, 12) +int botan_ec_point_view_uncompressed(botan_ec_point_t ec_point, botan_view_ctx ctx, botan_view_bin_fn view); + +/** +* View the fixed length SEC1 compressed encoding +* @returns negative number on error +*/ +BOTAN_FFI_EXPORT(3, 12) +int botan_ec_point_view_compressed(botan_ec_point_t ec_point, botan_view_ctx ctx, botan_view_bin_fn view); + +/** +* @returns 1 if @param ec_point is the identity element, else 0 +* @returns negative number on error +*/ +BOTAN_FFI_EXPORT(3, 12) int botan_ec_point_is_identity(botan_ec_point_t ec_point); + +/** +* @returns 1 if @param x == @param y else 0 otherwise +* @returns negative number on error +*/ +BOTAN_FFI_EXPORT(3, 12) int botan_ec_point_equal(botan_ec_point_t x, botan_ec_point_t y); + +/** +* @param ec_point point to negate +* @param result contains the result +*/ +BOTAN_FFI_EXPORT(3, 12) int botan_ec_point_negate(botan_ec_point_t* result, botan_ec_point_t ec_point); + +BOTAN_FFI_EXPORT(3, 12) int botan_ec_point_add(botan_ec_point_t* result, botan_ec_point_t x, botan_ec_point_t y); + +BOTAN_FFI_EXPORT(3, 12) +int botan_ec_point_mul(botan_ec_point_t* result, + botan_ec_point_t ec_point, + botan_ec_scalar_t ec_scalar, + botan_rng_t rng); + +/* * Public/private key creation, import, ... */ typedef struct botan_privkey_struct* botan_privkey_t; @@ -1109,6 +1574,16 @@ BOTAN_FFI_EXPORT(2, 0) int botan_privkey_create(botan_privkey_t* key, const char* algo_name, const char* algo_params, botan_rng_t rng); +/** +* Create a new ec private key +* @param key the new object will be placed here +* @param algo_name something like "ECDSA" or "ECDH" +* @param ec_group a (possibly application specific) elliptic curve +* @param rng a random number generator +*/ +BOTAN_FFI_EXPORT(3, 8) +int botan_ec_privkey_create(botan_privkey_t* key, const char* algo_name, botan_ec_group_t ec_group, botan_rng_t rng); + #define BOTAN_CHECK_KEY_EXPENSIVE_TESTS 1 BOTAN_FFI_EXPORT(2, 0) int botan_privkey_check_key(botan_privkey_t key, botan_rng_t rng, uint32_t flags); @@ -1190,6 +1665,7 @@ * Returns 0 on success and sets * If some other error occurs a negative integer is returned. */ +BOTAN_FFI_DEPRECATED("Use botan_privkey_view_{der,pem,raw}") BOTAN_FFI_EXPORT(2, 0) int botan_privkey_export(botan_privkey_t key, uint8_t out[], size_t* out_len, uint32_t flags); /** @@ -1228,6 +1704,7 @@ * * Note: starting in 3.0, the output iterations count is not provided */ +BOTAN_FFI_DEPRECATED("Use botan_privkey_view_encrypted_{der,pem}_timed") BOTAN_FFI_EXPORT(2, 0) int botan_privkey_export_encrypted_pbkdf_msec(botan_privkey_t key, uint8_t out[], @@ -1243,6 +1720,7 @@ /** * Export a private key using the specified number of iterations. */ +BOTAN_FFI_DEPRECATED("Use botan_privkey_view_encrypted_{der,pem}") BOTAN_FFI_EXPORT(2, 0) int botan_privkey_export_encrypted_pbkdf_iter(botan_privkey_t key, uint8_t out[], @@ -1322,6 +1800,7 @@ BOTAN_FFI_EXPORT(2, 0) int botan_privkey_export_pubkey(botan_pubkey_t* out, botan_privkey_t in); +BOTAN_FFI_DEPRECATED("Use botan_pubkey_view_{der,pem,raw}") BOTAN_FFI_EXPORT(2, 0) int botan_pubkey_export(botan_pubkey_t key, uint8_t out[], size_t* out_len, uint32_t flags); /** @@ -1364,6 +1843,29 @@ BOTAN_FFI_EXPORT(2, 0) int botan_privkey_get_field(botan_mp_t output, botan_privkey_t key, const char* field_name); /* +* Get the OID from public or private keys +*/ +BOTAN_FFI_EXPORT(3, 8) +int botan_pubkey_oid(botan_asn1_oid_t* oid, botan_pubkey_t key); + +BOTAN_FFI_EXPORT(3, 8) +int botan_privkey_oid(botan_asn1_oid_t* oid, botan_privkey_t key); + +/** +* Checks whether a key is stateful and sets +* @param out to 1 if it is, or 0 if the key is not stateful +* @return 0 on success, a negative value on failure +*/ +BOTAN_FFI_EXPORT(3, 8) int botan_privkey_stateful_operation(botan_privkey_t key, int* out); + +/** +* Gets information on many operations a (stateful) key has remaining and sets +* @param out to that value +* @return 0 on success, a negative value on failure or if the key is not stateful +*/ +BOTAN_FFI_EXPORT(3, 8) int botan_privkey_remaining_operations(botan_privkey_t key, uint64_t* out); + +/* * Algorithm specific key operations: RSA */ BOTAN_FFI_EXPORT(2, 0) int botan_privkey_load_rsa(botan_privkey_t* key, botan_mp_t p, botan_mp_t q, botan_mp_t e); @@ -1386,6 +1888,8 @@ BOTAN_FFI_EXPORT(2, 0) int botan_pubkey_load_rsa(botan_pubkey_t* key, botan_mp_t n, botan_mp_t e); +BOTAN_FFI_EXPORT(3, 11) int botan_pubkey_load_rsa_pkcs1(botan_pubkey_t* key, const uint8_t bits[], size_t len); + BOTAN_FFI_DEPRECATED("Use botan_pubkey_get_field") BOTAN_FFI_EXPORT(2, 0) int botan_pubkey_rsa_get_e(botan_mp_t e, botan_pubkey_t rsa_key); BOTAN_FFI_DEPRECATED("Use botan_pubkey_get_field") @@ -1475,6 +1979,15 @@ BOTAN_FFI_EXPORT(2, 0) int botan_privkey_load_elgamal(botan_privkey_t* key, botan_mp_t p, botan_mp_t g, botan_mp_t x); /* +* Algorithm specific key operations: EC keys +*/ + +BOTAN_FFI_EXPORT(3, 12) int botan_ec_privkey_get_private_key(botan_privkey_t key, botan_ec_scalar_t* value); + +BOTAN_FFI_EXPORT(3, 12) int botan_ec_privkey_get_group(botan_privkey_t key, botan_ec_group_t* ec_group); + +BOTAN_FFI_EXPORT(3, 12) int botan_ec_pubkey_get_group(botan_pubkey_t key, botan_ec_group_t* ec_group); +/* * Algorithm specific key operations: Ed25519 */ @@ -1482,8 +1995,10 @@ BOTAN_FFI_EXPORT(2, 2) int botan_pubkey_load_ed25519(botan_pubkey_t* key, const uint8_t pubkey[32]); +BOTAN_FFI_DEPRECATED("Use botan_privkey_view_raw") BOTAN_FFI_EXPORT(2, 2) int botan_privkey_ed25519_get_privkey(botan_privkey_t key, uint8_t output[64]); +BOTAN_FFI_DEPRECATED("Use botan_pubkey_view_raw") BOTAN_FFI_EXPORT(2, 2) int botan_pubkey_ed25519_get_pubkey(botan_pubkey_t key, uint8_t pubkey[32]); /* @@ -1494,8 +2009,10 @@ BOTAN_FFI_EXPORT(3, 4) int botan_pubkey_load_ed448(botan_pubkey_t* key, const uint8_t pubkey[57]); +BOTAN_FFI_DEPRECATED("Use botan_privkey_view_raw") BOTAN_FFI_EXPORT(3, 4) int botan_privkey_ed448_get_privkey(botan_privkey_t key, uint8_t output[57]); +BOTAN_FFI_DEPRECATED("Use botan_pubkey_view_raw") BOTAN_FFI_EXPORT(3, 4) int botan_pubkey_ed448_get_pubkey(botan_pubkey_t key, uint8_t pubkey[57]); /* @@ -1506,8 +2023,10 @@ BOTAN_FFI_EXPORT(2, 8) int botan_pubkey_load_x25519(botan_pubkey_t* key, const uint8_t pubkey[32]); +BOTAN_FFI_DEPRECATED("Use botan_privkey_view_raw") BOTAN_FFI_EXPORT(2, 8) int botan_privkey_x25519_get_privkey(botan_privkey_t key, uint8_t output[32]); +BOTAN_FFI_DEPRECATED("Use botan_pubkey_view_raw") BOTAN_FFI_EXPORT(2, 8) int botan_pubkey_x25519_get_pubkey(botan_pubkey_t key, uint8_t pubkey[32]); /* @@ -1518,8 +2037,10 @@ BOTAN_FFI_EXPORT(3, 4) int botan_pubkey_load_x448(botan_pubkey_t* key, const uint8_t pubkey[56]); +BOTAN_FFI_DEPRECATED("Use botan_privkey_view_raw") BOTAN_FFI_EXPORT(3, 4) int botan_privkey_x448_get_privkey(botan_privkey_t key, uint8_t output[56]); +BOTAN_FFI_DEPRECATED("Use botan_pubkey_view_raw") BOTAN_FFI_EXPORT(3, 4) int botan_pubkey_x448_get_pubkey(botan_pubkey_t key, uint8_t pubkey[56]); /* @@ -1533,11 +2054,17 @@ int botan_pubkey_load_ml_dsa(botan_pubkey_t* key, const uint8_t pubkey[], size_t key_len, const char* mldsa_mode); /* -* Algorithm specific key operations: Kyber +* Algorithm specific key operations: Kyber R3 +* +* Note that Kyber R3 support is somewhat deprecated and may be removed in a +* future major release. Using the final ML-KEM is highly recommended in any new +* system. */ +BOTAN_FFI_DEPRECATED("Kyber R3 support is deprecated") BOTAN_FFI_EXPORT(3, 1) int botan_privkey_load_kyber(botan_privkey_t* key, const uint8_t privkey[], size_t key_len); +BOTAN_FFI_DEPRECATED("Kyber R3 support is deprecated") BOTAN_FFI_EXPORT(3, 1) int botan_pubkey_load_kyber(botan_pubkey_t* key, const uint8_t pubkey[], size_t key_len); BOTAN_FFI_DEPRECATED("Use generic botan_privkey_view_raw") @@ -1606,15 +2133,24 @@ BOTAN_FFI_EXPORT(2, 2) int botan_pubkey_load_ecdsa(botan_pubkey_t* key, botan_mp_t public_x, botan_mp_t public_y, const char* curve_name); +BOTAN_FFI_EXPORT(3, 10) +int botan_pubkey_load_ecdsa_sec1(botan_pubkey_t* key, const uint8_t sec1[], size_t sec1_len, const char* curve_name); + BOTAN_FFI_EXPORT(2, 2) int botan_pubkey_load_ecdh(botan_pubkey_t* key, botan_mp_t public_x, botan_mp_t public_y, const char* curve_name); +BOTAN_FFI_EXPORT(3, 10) +int botan_pubkey_load_ecdh_sec1(botan_pubkey_t* key, const uint8_t sec1[], size_t sec1_len, const char* curve_name); + BOTAN_FFI_EXPORT(2, 2) int botan_privkey_load_ecdh(botan_privkey_t* key, botan_mp_t scalar, const char* curve_name); BOTAN_FFI_EXPORT(2, 2) int botan_pubkey_load_sm2(botan_pubkey_t* key, botan_mp_t public_x, botan_mp_t public_y, const char* curve_name); +BOTAN_FFI_EXPORT(3, 10) +int botan_pubkey_load_sm2_sec1(botan_pubkey_t* key, const uint8_t sec1[], size_t sec1_len, const char* curve_name); + BOTAN_FFI_EXPORT(2, 2) int botan_privkey_load_sm2(botan_privkey_t* key, botan_mp_t scalar, const char* curve_name); @@ -1850,6 +2386,39 @@ typedef struct botan_x509_cert_struct* botan_x509_cert_t; +/** + * Generic values that may be retrieved from X.509 certificates or CRLs via + * the generic getter functions. + * + * When extending this list the existing entries must stay backward-compatible + * to remain ABI compatible across versions. Therefore, new values must be added + * to the end of this list. + * + * See: + * * botan_x509_cert_view_binary_values() + * * botan_x509_crl_view_binary_values() + * * botan_x509_cert_view_string_values() + */ +typedef enum /* NOLINT(*-enum-size,*-use-enum-class) */ { + BOTAN_X509_SERIAL_NUMBER = 0, /** singleton binary big-endian encoding */ + BOTAN_X509_SUBJECT_DN_BITS = 1, /** singleton binary DER encoding of the subject distinguished name */ + BOTAN_X509_ISSUER_DN_BITS = 2, /** singleton binary DER encoding of the issuer distinguished name */ + BOTAN_X509_SUBJECT_KEY_IDENTIFIER = 3, /** singleton binary encoding */ + BOTAN_X509_AUTHORITY_KEY_IDENTIFIER = 4, /** singleton binary encoding */ + + BOTAN_X509_PUBLIC_KEY_PKCS8_BITS = 200, /** singleton binary DER encoding of the PKCS#8 public key */ + BOTAN_X509_TBS_DATA_BITS = 201, /** singleton binary DER encoding */ + BOTAN_X509_SIGNATURE_SCHEME_BITS = 202, /** singleton binary DER encoding of the algorithm identifier */ + BOTAN_X509_SIGNATURE_BITS = 203, /** singleton binary signature bits */ + + BOTAN_X509_DER_ENCODING = 300, /** singleton binary DER encoding of the whole object */ + BOTAN_X509_PEM_ENCODING = 301, /** singleton string value PEM encoding of the whole object */ + + BOTAN_X509_CRL_DISTRIBUTION_URLS = 400, /** multi-value string of the CRL distribution points */ + BOTAN_X509_OCSP_RESPONDER_URLS = 401, /** multi-value string of the OCSP responder URLs */ + BOTAN_X509_CA_ISSUERS_URLS = 402, /** multi-value string of the CA issuer URLs */ +} botan_x509_value_type; + BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_load(botan_x509_cert_t* cert_obj, const uint8_t cert[], size_t cert_len); BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_load_file(botan_x509_cert_t* cert_obj, const char* filename); @@ -1860,6 +2429,38 @@ BOTAN_FFI_EXPORT(2, 8) int botan_x509_cert_dup(botan_x509_cert_t* new_cert, botan_x509_cert_t cert); +/** + * Retrieve a specific binary value from an X.509 certificate. + * + * For multi-values @p index allows enumerating the available entries, until + * BOTAN_FFI_ERROR_OUT_OF_RANGE is returned. For singleton values, an @p index + * of value "0" is expected. + * + * @returns BOTAN_FFI_ERROR_NO_VALUE if the provided @p cert does not provide + * the requested @p value_type at all or not in binary format. + */ +BOTAN_FFI_EXPORT(3, 11) +int botan_x509_cert_view_binary_values( + botan_x509_cert_t cert, botan_x509_value_type value_type, size_t index, botan_view_ctx ctx, botan_view_bin_fn view); +BOTAN_FFI_EXPORT(3, 11) +int botan_x509_cert_view_binary_values_count(botan_x509_cert_t cert, botan_x509_value_type value_type, size_t* count); + +/** + * Retrieve a specific string value from an X.509 certificate. + * + * For multi-values @p index allows enumerating the available entries, until + * BOTAN_FFI_ERROR_OUT_OF_RANGE is returned. For singleton values, an @p index + * of value "0" is expected. + * + * @returns BOTAN_FFI_ERROR_NO_VALUE if the provided @p cert does not provide + * the requested @p value_type at all or not in string format. + */ +BOTAN_FFI_EXPORT(3, 11) +int botan_x509_cert_view_string_values( + botan_x509_cert_t cert, botan_x509_value_type value_type, size_t index, botan_view_ctx ctx, botan_view_str_fn view); +BOTAN_FFI_EXPORT(3, 11) +int botan_x509_cert_view_string_values_count(botan_x509_cert_t cert, botan_x509_value_type value_type, size_t* count); + /* Prefer botan_x509_cert_not_before and botan_x509_cert_not_after */ BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_get_time_starts(botan_x509_cert_t cert, char out[], size_t* out_len); BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_get_time_expires(botan_x509_cert_t cert, char out[], size_t* out_len); @@ -1867,10 +2468,12 @@ BOTAN_FFI_EXPORT(2, 8) int botan_x509_cert_not_before(botan_x509_cert_t cert, uint64_t* time_since_epoch); BOTAN_FFI_EXPORT(2, 8) int botan_x509_cert_not_after(botan_x509_cert_t cert, uint64_t* time_since_epoch); +/* TODO(Botan4) this should use char for the out param */ BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_get_fingerprint(botan_x509_cert_t cert, const char* hash, uint8_t out[], size_t* out_len); BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_get_serial_number(botan_x509_cert_t cert, uint8_t out[], size_t* out_len); +BOTAN_FFI_EXPORT(3, 11) int botan_x509_cert_serial_number(botan_x509_cert_t cert, botan_mp_t* serial_number); BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_get_authority_key_id(botan_x509_cert_t cert, uint8_t out[], size_t* out_len); BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_get_subject_key_id(botan_x509_cert_t cert, uint8_t out[], size_t* out_len); @@ -1881,21 +2484,49 @@ BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_get_public_key(botan_x509_cert_t cert, botan_pubkey_t* key); +/** + * Returns 1 iff the cert is a CA certificate + */ +BOTAN_FFI_EXPORT(3, 11) int botan_x509_cert_is_ca(botan_x509_cert_t cert); + +/** + * Retrieves the path length constraint from the certificate. + * If no such constraint is present, BOTAN_FFI_ERROR_NO_VALUE is returned. + */ +BOTAN_FFI_EXPORT(3, 11) int botan_x509_cert_get_path_length_constraint(botan_x509_cert_t cert, size_t* path_limit); + +/** + * Enumerates the names of the given @p key in the issuer DN. If @p index is + * out of bounds, BOTAN_FFI_ERROR_BAD_PARAMETER is returned. + * + * TODO(Botan4) use BOTAN_FFI_ERROR_OUT_OF_RANGE instead of BAD_PARAMETER + * TODO(Botan4) this should use char for the out param + */ BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_get_issuer_dn( botan_x509_cert_t cert, const char* key, size_t index, uint8_t out[], size_t* out_len); +BOTAN_FFI_EXPORT(3, 11) int botan_x509_cert_get_issuer_dn_count(botan_x509_cert_t cert, const char* key, size_t* count); +/** + * Enumerates the names of the given @p key in the subject DN. If @p index is + * out of bounds, BOTAN_FFI_ERROR_BAD_PARAMETER is returned. + * + * TODO(Botan4) use BOTAN_FFI_ERROR_OUT_OF_RANGE instead of BAD_PARAMETER + * TODO(Botan4) this should use char for the out param + */ BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_get_subject_dn( botan_x509_cert_t cert, const char* key, size_t index, uint8_t out[], size_t* out_len); +BOTAN_FFI_EXPORT(3, 11) +int botan_x509_cert_get_subject_dn_count(botan_x509_cert_t cert, const char* key, size_t* count); BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_to_string(botan_x509_cert_t cert, char out[], size_t* out_len); BOTAN_FFI_EXPORT(3, 0) int botan_x509_cert_view_as_string(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view); -/* Must match values of Key_Constraints in key_constraints.h */ -enum botan_x509_cert_key_constraints { +/* Must match values of Key_Constraints in pkix_enums.h */ +enum botan_x509_cert_key_constraints /* NOLINT(*-enum-size,*-use-enum-class) */ { NO_CONSTRAINTS = 0, DIGITAL_SIGNATURE = 32768, NON_REPUDIATION = 16384, @@ -1911,6 +2542,127 @@ BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_allowed_usage(botan_x509_cert_t cert, unsigned int key_usage); /** +* Check if the certificate allows the specified extended usage OID. See RFC 5280 +* Section 4.2.1.12 for OIDs to query for this. If no extended key usage +* extension is found in the certificate, this always returns "not success". +* +* Typical OIDs to check for: +* * "PKIX.ServerAuth" +* * "PKIX.ClientAuth" +* * "PKIX.CodeSigning" +* * "PKIX.OCSPSigning" +* +* The @p oid parameter can be either a canonical OID string or identifiers as +* indicated in the examples above. +*/ +BOTAN_FFI_EXPORT(3, 11) int botan_x509_cert_allowed_extended_usage_str(botan_x509_cert_t cert, const char* oid); + +/** +* Check if the certificate allows the specified extended usage OID. See RFC 5280 +* Section 4.2.1.12 for OIDs to query for this. If no extended key usage +* extension is found in the certificate, this always returns "not success". +* +* This is similar to botan_x509_cert_allowed_extended_usage_str but takes an OID +* object instead of a string describing the OID. +*/ +BOTAN_FFI_EXPORT(3, 11) int botan_x509_cert_allowed_extended_usage_oid(botan_x509_cert_t cert, botan_asn1_oid_t oid); + +typedef struct botan_x509_general_name_struct* botan_x509_general_name_t; + +/** +* GeneralName type identifiers as defined in RFC 5280 A.2 (GeneralName ::= CHOICE) +* Type identifiers that are omitted here are (currently) not supported. Also, +* there is currently no way to access OTHER_NAME values via the FFI. +*/ +enum botan_x509_general_name_types /* NOLINT(*-enum-size,*-use-enum-class) */ { + BOTAN_X509_OTHER_NAME = 0, + BOTAN_X509_EMAIL_ADDRESS = 1, + BOTAN_X509_DNS_NAME = 2, + BOTAN_X509_DIRECTORY_NAME = 4, + BOTAN_X509_URI = 6, + BOTAN_X509_IP_ADDRESS = 7, +}; + +/** +* Provides the contained type of the @p name and returns BOTAN_FFI_SUCCESS if +* that type is supported and may be retrieved via the view functions below. +* Otherwise BOTAN_FFI_ERROR_INVALID_OBJECT_STATE is returned. +*/ +BOTAN_FFI_EXPORT(3, 11) int botan_x509_general_name_get_type(botan_x509_general_name_t name, unsigned int* type); + +/** +* Views the name as a string or returns BOTAN_FFI_ERROR_INVALID_OBJECT_STATE +* if the contained GeneralName value cannot be represented as a string. +* +* The types BOTAN_X509_EMAIL_ADDRESS, BOTAN_X509_DNS_NAME, BOTAN_X509_URI, +* BOTAN_X509_IP_ADDRESS may be viewed as "string". +*/ +BOTAN_FFI_EXPORT(3, 11) +int botan_x509_general_name_view_string_value(botan_x509_general_name_t name, + botan_view_ctx ctx, + botan_view_str_fn view); + +/** +* Views the name as a bit string or returns BOTAN_FFI_ERROR_INVALID_OBJECT_STATE +* if the contained GeneralName value cannot be represented as a binary string. +* +* The types BOTAN_X509_DIRECTORY_NAME, BOTAN_X509_IP_ADDRESS may be viewed as +* "binary". +*/ +BOTAN_FFI_EXPORT(3, 11) +int botan_x509_general_name_view_binary_value(botan_x509_general_name_t name, + botan_view_ctx ctx, + botan_view_bin_fn view); + +BOTAN_FFI_EXPORT(3, 11) int botan_x509_general_name_destroy(botan_x509_general_name_t alt_names); + +/** +* Extracts "permitted" name constraints from a given @p cert one-by-one. +* Returns BOTAN_FFI_ERROR_OUT_OF_RANGE if the given @p index is larger than the +* available number of "permitted" name constraints. +*/ +BOTAN_FFI_EXPORT(3, 11) +int botan_x509_cert_permitted_name_constraints(botan_x509_cert_t cert, + size_t index, + botan_x509_general_name_t* constraint); +BOTAN_FFI_EXPORT(3, 11) int botan_x509_cert_permitted_name_constraints_count(botan_x509_cert_t cert, size_t* count); + +/** +* Extracts "excluded" name constraints from a given @p cert one-by-one. +* Returns BOTAN_FFI_ERROR_OUT_OF_RANGE if the given @p index is larger than the +* available number of "excluded" name constraints. +*/ +BOTAN_FFI_EXPORT(3, 11) +int botan_x509_cert_excluded_name_constraints(botan_x509_cert_t cert, + size_t index, + botan_x509_general_name_t* constraint); +BOTAN_FFI_EXPORT(3, 11) int botan_x509_cert_excluded_name_constraints_count(botan_x509_cert_t cert, size_t* count); + +/** +* Provides access to all "subject alternative names", where each entry is +* returned as a botan_x509_general_name_t. If the given @p index is not +* within range of the available entries, BOTAN_FFI_ERROR_OUT_OF_RANGE is +* returned. If @p cert does not contain a SubjectAlternativeNames extension, +* BOTAN_FFI_ERROR_NO_VALUE is returned. +*/ +BOTAN_FFI_EXPORT(3, 11) +int botan_x509_cert_subject_alternative_names(botan_x509_cert_t cert, + size_t index, + botan_x509_general_name_t* alt_name); +BOTAN_FFI_EXPORT(3, 11) int botan_x509_cert_subject_alternative_names_count(botan_x509_cert_t cert, size_t* count); + +/** +* Provides access to all "issuer alternative names", where each entry is +* returned as a botan_x509_general_name_t. If the given @p index is not +* within range of the available entries, BOTAN_FFI_ERROR_OUT_OF_RANGE is +* returned. If @p cert does not contain an IssuerAlternativeNames extension, +* BOTAN_FFI_ERROR_NO_VALUE is returned. +*/ +BOTAN_FFI_EXPORT(3, 11) +int botan_x509_cert_issuer_alternative_names(botan_x509_cert_t cert, size_t index, botan_x509_general_name_t* alt_name); +BOTAN_FFI_EXPORT(3, 11) int botan_x509_cert_issuer_alternative_names_count(botan_x509_cert_t cert, size_t* count); + +/** * Check if the certificate matches the specified hostname via alternative name or CN match. * RFC 5280 wildcards also supported. */ @@ -1947,20 +2699,176 @@ **************************/ typedef struct botan_x509_crl_struct* botan_x509_crl_t; +typedef struct botan_x509_crl_entry_struct* botan_x509_crl_entry_t; BOTAN_FFI_EXPORT(2, 13) int botan_x509_crl_load_file(botan_x509_crl_t* crl_obj, const char* crl_path); BOTAN_FFI_EXPORT(2, 13) int botan_x509_crl_load(botan_x509_crl_t* crl_obj, const uint8_t crl_bits[], size_t crl_bits_len); +BOTAN_FFI_EXPORT(3, 11) int botan_x509_crl_this_update(botan_x509_crl_t crl, uint64_t* time_since_epoch); +BOTAN_FFI_EXPORT(3, 11) int botan_x509_crl_next_update(botan_x509_crl_t crl, uint64_t* time_since_epoch); + +/** +* Create a new CRL +* @param crl_obj The newly created CRL +* @param rng a random number generator object +* @param ca_cert The CA Certificate the CRL belongs to +* @param ca_key The private key of that CA +* @param issue_time The time when the CRL becomes valid +* @param next_update The number of seconds after issue_time until the CRL expires +* @param hash_fn The hash function to use, may be null +* @param padding The padding to use, may be null +*/ +BOTAN_FFI_EXPORT(3, 11) +int botan_x509_crl_create(botan_x509_crl_t* crl_obj, + botan_rng_t rng, + botan_x509_cert_t ca_cert, + botan_privkey_t ca_key, + uint64_t issue_time, + uint32_t next_update, + const char* hash_fn, + const char* padding); + +/* Must match values of CRL_Code in pkix_enums.h */ +enum botan_x509_crl_reason_code /* NOLINT(*-enum-size,*-use-enum-class) */ { + BOTAN_CRL_ENTRY_UNSPECIFIED = 0, + BOTAN_CRL_ENTRY_KEY_COMPROMISE = 1, + BOTAN_CRL_ENTRY_CA_COMPROMISE = 2, + BOTAN_CRL_ENTRY_AFFILIATION_CHANGED = 3, + BOTAN_CRL_ENTRY_SUPERSEDED = 4, + BOTAN_CRL_ENTRY_CESSATION_OF_OPERATION = 5, + BOTAN_CRL_ENTRY_CERTIFICATE_HOLD = 6, + BOTAN_CRL_ENTRY_REMOVE_FROM_CRL = 8, + BOTAN_CRL_ENTRY_PRIVILEGE_WITHDRAWN = 9, + BOTAN_CRL_ENTRY_AA_COMPROMISE = 10 +}; + +/** +* Create a new CRL entry that marks @p cert as revoked +* @param entry The newly created CRL entry +* @param cert The certificate to mark as revoked +* @param reason_code The reason code for revocation +*/ +BOTAN_FFI_EXPORT(3, 11) +int botan_x509_crl_entry_create(botan_x509_crl_entry_t* entry, botan_x509_cert_t cert, int reason_code); + +/** +* Update a CRL with new revoked entries. This does not modify the old crl, and instead creates a new one. +* @param crl_obj The newly created CRL +* @param last_crl The CRL to update +* @param rng a random number generator object +* @param ca_cert The CA Certificate the CRL belongs to +* @param ca_key The private key of that CA +* @param issue_time The time when the CRL becomes valid +* @param next_update The number of seconds after issue_time until the CRL expires +* @param new_entries The entries to add to the CRL +* @param new_entries_len The number of entries +* @param hash_fn The hash function to use, may be null +* @param padding The padding to use, may be null +*/ +BOTAN_FFI_EXPORT(3, 11) +int botan_x509_crl_update(botan_x509_crl_t* crl_obj, + botan_x509_crl_t last_crl, + botan_rng_t rng, + botan_x509_cert_t ca_cert, + botan_privkey_t ca_key, + uint64_t issue_time, + uint32_t next_update, + const botan_x509_crl_entry_t* new_entries, + size_t new_entries_len, + const char* hash_fn, + const char* padding); + +BOTAN_FFI_EXPORT(3, 11) int botan_x509_crl_verify_signature(botan_x509_crl_t crl, botan_pubkey_t key); + BOTAN_FFI_EXPORT(2, 13) int botan_x509_crl_destroy(botan_x509_crl_t crl); /** + * Retrieve a specific binary value from an X.509 certificate revocation list. + * + * For multi-values @p index allows enumerating the available entries, until + * BOTAN_FFI_ERROR_OUT_OF_RANGE is returned. For singleton values, an @p index + * of value "0" is expected. + * + * @returns BOTAN_FFI_ERROR_NO_VALUE if the provided @p crl_obj does not provide + * the requested @p value_type at all or not in binary format. + */ +BOTAN_FFI_EXPORT(3, 11) +int botan_x509_crl_view_binary_values(botan_x509_crl_t crl_obj, + botan_x509_value_type value_type, + size_t index, + botan_view_ctx ctx, + botan_view_bin_fn view); +BOTAN_FFI_EXPORT(3, 11) +int botan_x509_crl_view_binary_values_count(botan_x509_crl_t crl_obj, botan_x509_value_type value_type, size_t* count); + +/** + * Retrieve a specific string value from an X.509 certificate revocation list. + * + * For multi-values @p index allows enumerating the available entries, until + * BOTAN_FFI_ERROR_OUT_OF_RANGE is returned. For singleton values, an @p index + * of value "0" is expected. + * + * @returns BOTAN_FFI_ERROR_NO_VALUE if the provided @p crl_obj does not provide + * the requested @p value_type at all or not in string format. + */ +BOTAN_FFI_EXPORT(3, 11) +int botan_x509_crl_view_string_values(botan_x509_crl_t crl_obj, + botan_x509_value_type value_type, + size_t index, + botan_view_ctx ctx, + botan_view_str_fn view); +BOTAN_FFI_EXPORT(3, 11) +int botan_x509_crl_view_string_values_count(botan_x509_crl_t crl_obj, botan_x509_value_type value_type, size_t* count); + +/** * Given a CRL and a certificate, * check if the certificate is revoked on that particular CRL */ BOTAN_FFI_EXPORT(2, 13) int botan_x509_is_revoked(botan_x509_crl_t crl, botan_x509_cert_t cert); /** +* Allows iterating all entries of the CRL. +* +* @param crl the CRL whose entries should be listed +* @param index the index of the CRL entry to return +* @param entry an object handle containing the CRL entry data +* +* @returns BOTAN_FFI_ERROR_OUT_OF_RANGE if the given @p index is out of range of +* the CRL entry list. +*/ +BOTAN_FFI_EXPORT(3, 11) +int botan_x509_crl_entries(botan_x509_crl_t crl, size_t index, botan_x509_crl_entry_t* entry); +BOTAN_FFI_EXPORT(3, 11) int botan_x509_crl_entries_count(botan_x509_crl_t crl, size_t* count); + +/** +* Return the revocation reason code for the given CRL @p entry. +* See `botan_x509_crl_reason_code` and RFC 5280 - 5.3.1 for possible reason codes. +*/ +BOTAN_FFI_EXPORT(3, 11) int botan_x509_crl_entry_reason(botan_x509_crl_entry_t entry, int* reason_code); + +/** +* Return the revocation date for the given CRL @p entry as time since epoch +* in seconds. +*/ +BOTAN_FFI_EXPORT(3, 11) +int botan_x509_crl_entry_revocation_date(botan_x509_crl_entry_t entry, uint64_t* time_since_epoch); + +/** +* Return the serial number associated with the given CRL @p entry. +*/ +BOTAN_FFI_EXPORT(3, 11) +int botan_x509_crl_entry_serial_number(botan_x509_crl_entry_t entry, botan_mp_t* serial_number); + +/** +* View the serial number associated with the given CRL @p entry. +*/ +BOTAN_FFI_EXPORT(3, 11) +int botan_x509_crl_entry_view_serial_number(botan_x509_crl_entry_t entry, botan_view_ctx ctx, botan_view_bin_fn view); + +BOTAN_FFI_EXPORT(3, 11) int botan_x509_crl_entry_destroy(botan_x509_crl_entry_t entry); + +/** * Different flavor of `botan_x509_cert_verify`, supports revocation lists. * CRLs are passed as an array, same as intermediates and trusted CAs */ @@ -2295,7 +3203,7 @@ * @returns 1 if the crypto backend can be enabled */ BOTAN_FFI_EXPORT(3, 6) -int botan_tpm2_supports_crypto_backend(); +int botan_tpm2_supports_crypto_backend(void); /** * Initialize a TPM2 context @@ -2339,7 +3247,7 @@ int botan_tpm2_ctx_enable_crypto_backend(botan_tpm2_ctx_t ctx, botan_rng_t rng); /** -* Frees all resouces of a TPM2 context +* Frees all resources of a TPM2 context * @param ctx TPM2 context * @return 0 on success */ @@ -2362,7 +3270,7 @@ botan_rng_t rng); /** -* Frees all resouces of a TPM2 Crypto Callback State +* Frees all resources of a TPM2 Crypto Callback State * Note that this does not attempt to de-register the crypto backend, * it just frees the resource pointed to by @p cbs. Use the ESAPI function * ``Esys_SetCryptoCallbacks(ctx, nullptr)`` to deregister manually. @@ -2401,6 +3309,8 @@ BOTAN_FFI_EXPORT(3, 6) int botan_tpm2_session_destroy(botan_tpm2_session_t session); +/* NOLINTEND(*-macro-usage,*-misplaced-const) */ + #ifdef __cplusplus } #endif diff -Nru botan3-3.7.1+dfsg/src/lib/ffi/ffi_block.cpp botan3-3.12.0+dfsg/src/lib/ffi/ffi_block.cpp --- botan3-3.7.1+dfsg/src/lib/ffi/ffi_block.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/ffi/ffi_block.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -28,8 +28,7 @@ return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; } - *bc = new botan_block_cipher_struct(std::move(cipher)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(bc, std::move(cipher)); }); } diff -Nru botan3-3.7.1+dfsg/src/lib/ffi/ffi_cert.cpp botan3-3.12.0+dfsg/src/lib/ffi/ffi_cert.cpp --- botan3-3.7.1+dfsg/src/lib/ffi/ffi_cert.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/ffi/ffi_cert.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,29 +6,176 @@ #include +#include +#include #include +#include #include #include #if defined(BOTAN_HAS_X509_CERTIFICATES) #include #include + #include #include #include + #include + #include + #include + #include #endif -extern "C" { +#if defined(BOTAN_HAS_X509_CERTIFICATES) -using namespace Botan_FFI; +namespace Botan_FFI { + +namespace { + +/** + * As specified in RFC 5280 Section 4.2.1.6. alternative names essentially are a + * collection of GeneralNames. This allows mapping a single entry of @p altnames + * to a GeneralName by its @p index. If the index is out of range, std::nullopt + * is returned. + * + * NOTE: if the set of alternative name types handled here is extended, + * count_general_names_in() must be updated accordingly! + */ +std::optional extract_general_name_at(const Botan::AlternativeName& altnames, size_t index) { + if(index < altnames.email().size()) { + auto itr = altnames.email().begin(); + std::advance(itr, index); + return Botan::GeneralName::email(*itr); + } + index -= altnames.email().size(); -#if defined(BOTAN_HAS_X509_CERTIFICATES) + if(index < altnames.dns().size()) { + auto itr = altnames.dns().begin(); + std::advance(itr, index); + return Botan::GeneralName::dns(*itr); + } + index -= altnames.dns().size(); + + if(index < altnames.directory_names().size()) { + auto itr = altnames.directory_names().begin(); + std::advance(itr, index); + return Botan::GeneralName::directory_name(*itr); + } + index -= altnames.directory_names().size(); + + if(index < altnames.uris().size()) { + auto itr = altnames.uris().begin(); + std::advance(itr, index); + return Botan::GeneralName::uri(*itr); + } + index -= altnames.uris().size(); + + if(index < altnames.ipv4_address().size()) { + auto itr = altnames.ipv4_address().begin(); + std::advance(itr, index); + return Botan::GeneralName::ipv4_address(*itr); + } + index -= altnames.ipv4_address().size(); + + if(index < altnames.ipv6_address().size()) { + auto itr = altnames.ipv6_address().begin(); + std::advance(itr, index); + return Botan::GeneralName::ipv6_address(*itr); + } + + return std::nullopt; +} + +/** + * Counts the total number of GeneralNames contained in the given + * AlternativeName @p alt_names. + * + * NOTE: if the set of alternative name types handled here is extended, + * extract_general_name_at() must be updated accordingly! + */ +size_t count_general_names_in(const Botan::AlternativeName& alt_names) { + return alt_names.email().size() + alt_names.dns().size() + alt_names.directory_names().size() + + alt_names.uris().size() + alt_names.ipv4_address().size() + alt_names.ipv6_address().size(); +} + +std::optional to_botan_x509_general_name_types(Botan::GeneralName::NameType gn_type) { + using Type = Botan::GeneralName::NameType; + switch(gn_type) { + case Type::Unknown: + return std::nullopt; + case Type::RFC822: + return BOTAN_X509_EMAIL_ADDRESS; + case Type::DNS: + return BOTAN_X509_DNS_NAME; + case Type::URI: + return BOTAN_X509_URI; + case Type::DN: + return BOTAN_X509_DIRECTORY_NAME; + case Type::IPv4: + case Type::IPv6: + return BOTAN_X509_IP_ADDRESS; + case Type::Other: + return BOTAN_X509_OTHER_NAME; + } + + BOTAN_ASSERT_UNREACHABLE(); +} + +/** + * Given some enumerator-style function @p fn, count how many values it can + * produce before returning BOTAN_FFI_ERROR_OUT_OF_RANGE. If the first call to + * @p fn returns BOTAN_FFI_ERROR_NO_VALUE, zero is written to @p count. + * + * If this function returns BOTAN_FFI_SUCCESS, @p count contains the number of + * values that can be enumerated. Otherwise, the value of @p count is undefined. + */ +template EnumeratorT> +int enumerator_count_values(size_t* count, EnumeratorT fn) { + if(Botan::any_null_pointers(count)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + *count = 0; + for(;; ++(*count)) { + const auto rc = fn(*count); + switch(rc) { + case BOTAN_FFI_ERROR_NO_VALUE: + case BOTAN_FFI_ERROR_OUT_OF_RANGE: + // hit the end of the enumeration + return BOTAN_FFI_SUCCESS; + case BOTAN_FFI_SUCCESS: + // got a value, continue counting + break; + default: + // unexpected error from enumerator function + return rc; + } + } +} + +std::chrono::system_clock::time_point timepoint_from_timestamp(uint64_t time_since_epoch) { + return std::chrono::system_clock::time_point(std::chrono::seconds(time_since_epoch)); +} + +std::string default_from_ptr(const char* value) { + std::string ret; + if(value != nullptr) { + ret = value; + } + return ret; +} -BOTAN_FFI_DECLARE_STRUCT(botan_x509_cert_struct, Botan::X509_Certificate, 0x8F628937); +} // namespace + +} // namespace Botan_FFI #endif +extern "C" { + +using namespace Botan_FFI; + int botan_x509_cert_load_file(botan_x509_cert_t* cert_obj, const char* cert_path) { - if(!cert_obj || !cert_path) { + if(cert_obj == nullptr || cert_path == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } @@ -36,8 +183,7 @@ return ffi_guard_thunk(__func__, [=]() -> int { auto c = std::make_unique(cert_path); - *cert_obj = new botan_x509_cert_struct(std::move(c)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(cert_obj, std::move(c)); }); #else @@ -46,7 +192,7 @@ } int botan_x509_cert_dup(botan_x509_cert_t* cert_obj, botan_x509_cert_t cert) { - if(!cert_obj) { + if(cert_obj == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } @@ -54,8 +200,7 @@ return ffi_guard_thunk(__func__, [=]() -> int { auto c = std::make_unique(safe_get(cert)); - *cert_obj = new botan_x509_cert_struct(std::move(c)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(cert_obj, std::move(c)); }); #else @@ -65,7 +210,7 @@ } int botan_x509_cert_load(botan_x509_cert_t* cert_obj, const uint8_t cert_bits[], size_t cert_bits_len) { - if(!cert_obj || !cert_bits) { + if(cert_obj == nullptr || cert_bits == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } @@ -73,14 +218,247 @@ return ffi_guard_thunk(__func__, [=]() -> int { Botan::DataSource_Memory bits(cert_bits, cert_bits_len); auto c = std::make_unique(bits); - *cert_obj = new botan_x509_cert_struct(std::move(c)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(cert_obj, std::move(c)); }); #else BOTAN_UNUSED(cert_bits_len); return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } +} + +namespace { + +#if defined(BOTAN_HAS_X509_CERTIFICATES) + +int botan_x509_object_view_value(const Botan::X509_Object& object, + botan_x509_value_type value_type, + size_t index, + botan_view_ctx ctx, + botan_view_str_fn view_fn) { + if(index != 0) { + // As of now there are no multi-value generic string entries. + return BOTAN_FFI_ERROR_OUT_OF_RANGE; + } + + auto view = [=](const std::string& value) { return invoke_view_callback(view_fn, ctx, value); }; + + switch(value_type) { + case BOTAN_X509_PEM_ENCODING: + return view(object.PEM_encode()); + default: + BOTAN_ASSERT_UNREACHABLE(); /* called with unexpected (non-generic) value_type */ + } +} + +int botan_x509_object_view_value(const Botan::X509_Object& object, + botan_x509_value_type value_type, + size_t index, + botan_view_ctx ctx, + botan_view_bin_fn view_fn) { + if(index != 0) { + // As of now there are no multi-value generic binary entries. + return BOTAN_FFI_ERROR_OUT_OF_RANGE; + } + + auto view = [=](std::span value) { return invoke_view_callback(view_fn, ctx, value); }; + + switch(value_type) { + case BOTAN_X509_TBS_DATA_BITS: + return view(object.tbs_data()); + case BOTAN_X509_SIGNATURE_SCHEME_BITS: + return view(object.signature_algorithm().BER_encode()); + case BOTAN_X509_SIGNATURE_BITS: + return view(object.signature()); + case BOTAN_X509_DER_ENCODING: + return view(object.BER_encode()); + default: + BOTAN_ASSERT_UNREACHABLE(); /* called with unexpected (non-generic) value_type */ + } +} + +#endif + +} // namespace + +extern "C" { + +int botan_x509_cert_view_binary_values(botan_x509_cert_t cert, + botan_x509_value_type value_type, + size_t index, + botan_view_ctx ctx, + botan_view_bin_fn view_fn) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + if(index != 0) { + // As of now there are no multi-value binary entries. + return BOTAN_FFI_ERROR_OUT_OF_RANGE; + } + + auto view = [=](std::span value) -> int { + if(value.empty()) { + return BOTAN_FFI_ERROR_NO_VALUE; + } else { + return invoke_view_callback(view_fn, ctx, value); + } + }; + + return BOTAN_FFI_VISIT(cert, [=](const Botan::X509_Certificate& c) -> int { + switch(value_type) { + case BOTAN_X509_SERIAL_NUMBER: + return view(c.serial_number()); + case BOTAN_X509_SUBJECT_DN_BITS: + return view(c.raw_subject_dn()); + case BOTAN_X509_ISSUER_DN_BITS: + return view(c.raw_issuer_dn()); + case BOTAN_X509_SUBJECT_KEY_IDENTIFIER: + return view(c.subject_key_id()); + case BOTAN_X509_AUTHORITY_KEY_IDENTIFIER: + return view(c.authority_key_id()); + case BOTAN_X509_PUBLIC_KEY_PKCS8_BITS: + return view(c.subject_public_key_info()); + + case BOTAN_X509_TBS_DATA_BITS: + case BOTAN_X509_SIGNATURE_SCHEME_BITS: + case BOTAN_X509_SIGNATURE_BITS: + case BOTAN_X509_DER_ENCODING: + return botan_x509_object_view_value(c, value_type, index, ctx, view_fn); + + case BOTAN_X509_PEM_ENCODING: + case BOTAN_X509_CRL_DISTRIBUTION_URLS: + case BOTAN_X509_OCSP_RESPONDER_URLS: + case BOTAN_X509_CA_ISSUERS_URLS: + return BOTAN_FFI_ERROR_NO_VALUE; + } + + return BOTAN_FFI_ERROR_BAD_PARAMETER; + }); +#else + BOTAN_UNUSED(cert, value_type, index, ctx, view_fn); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_cert_view_binary_values_count(botan_x509_cert_t cert, botan_x509_value_type value_type, size_t* count) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return enumerator_count_values(count, [=](size_t index) { + return botan_x509_cert_view_binary_values( + cert, value_type, index, nullptr, [](auto, auto, auto) -> int { return BOTAN_FFI_SUCCESS; }); + }); +#else + BOTAN_UNUSED(cert, value_type, count); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_cert_view_string_values(botan_x509_cert_t cert, + botan_x509_value_type value_type, + size_t index, + botan_view_ctx ctx, + botan_view_str_fn view_fn) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + auto enumerate = [view_fn, ctx](auto values, size_t idx) -> int { + if(idx >= values.size()) { + return BOTAN_FFI_ERROR_OUT_OF_RANGE; + } else { + return invoke_view_callback(view_fn, ctx, values[idx]); + } + }; + + auto enumerate_crl_distribution_points = [view_fn, ctx](const Botan::X509_Certificate& c, size_t idx) -> int { + const auto* crl_dp_ext = + c.v3_extensions().get_extension_object_as(); + if(crl_dp_ext == nullptr) { + return BOTAN_FFI_ERROR_OUT_OF_RANGE; // essentially an empty list + } + + const auto& dps = crl_dp_ext->distribution_points(); + for(size_t i = idx; const auto& dp : dps) { + const auto& uris = dp.point().uris(); + if(i >= uris.size()) { + i -= uris.size(); + continue; + } + + auto itr = uris.begin(); + std::advance(itr, i); + return invoke_view_callback(view_fn, ctx, *itr); + } + + return BOTAN_FFI_ERROR_OUT_OF_RANGE; + }; + + return BOTAN_FFI_VISIT(cert, [=](const Botan::X509_Certificate& c) -> int { + switch(value_type) { + case BOTAN_X509_CRL_DISTRIBUTION_URLS: + return enumerate_crl_distribution_points(c, index); + case BOTAN_X509_OCSP_RESPONDER_URLS: + return enumerate(c.ocsp_responders(), index); + case BOTAN_X509_CA_ISSUERS_URLS: + return enumerate(c.ca_issuers(), index); + case BOTAN_X509_PEM_ENCODING: + return botan_x509_object_view_value(c, value_type, index, ctx, view_fn); + + case BOTAN_X509_SERIAL_NUMBER: + case BOTAN_X509_SUBJECT_DN_BITS: + case BOTAN_X509_ISSUER_DN_BITS: + case BOTAN_X509_SUBJECT_KEY_IDENTIFIER: + case BOTAN_X509_AUTHORITY_KEY_IDENTIFIER: + case BOTAN_X509_PUBLIC_KEY_PKCS8_BITS: + case BOTAN_X509_TBS_DATA_BITS: + case BOTAN_X509_SIGNATURE_SCHEME_BITS: + case BOTAN_X509_SIGNATURE_BITS: + case BOTAN_X509_DER_ENCODING: + return BOTAN_FFI_ERROR_NO_VALUE; + } + + return BOTAN_FFI_ERROR_BAD_PARAMETER; + }); +#else + BOTAN_UNUSED(cert, value_type, index, ctx, view_fn); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_cert_view_string_values_count(botan_x509_cert_t cert, botan_x509_value_type value_type, size_t* count) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return enumerator_count_values(count, [=](size_t index) { + return botan_x509_cert_view_string_values( + cert, value_type, index, nullptr, [](auto, auto, auto) -> int { return BOTAN_FFI_SUCCESS; }); + }); +#else + BOTAN_UNUSED(cert, value_type, count); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_cert_is_ca(botan_x509_cert_t cert) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(cert, [=](const auto& c) { return c.is_CA_cert() ? 1 : 0; }); +#else + BOTAN_UNUSED(cert); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_cert_get_path_length_constraint(botan_x509_cert_t cert, size_t* path_limit) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(cert, [=](const auto& c) -> int { + if(Botan::any_null_pointers(path_limit)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + if(const auto path_len = c.path_length_constraint()) { + *path_limit = path_len.value(); + return BOTAN_FFI_SUCCESS; + } else { + return BOTAN_FFI_ERROR_NO_VALUE; + } + }); +#else + BOTAN_UNUSED(cert, path_limit); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} int botan_x509_cert_get_public_key(botan_x509_cert_t cert, botan_pubkey_t* key) { if(key == nullptr) { @@ -92,8 +470,7 @@ #if defined(BOTAN_HAS_X509_CERTIFICATES) return ffi_guard_thunk(__func__, [=]() -> int { auto public_key = safe_get(cert).subject_public_key(); - *key = new botan_pubkey_struct(std::move(public_key)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(key, std::move(public_key)); }); #else BOTAN_UNUSED(cert); @@ -103,13 +480,17 @@ int botan_x509_cert_get_issuer_dn( botan_x509_cert_t cert, const char* key, size_t index, uint8_t out[], size_t* out_len) { + if(key == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } #if defined(BOTAN_HAS_X509_CERTIFICATES) return BOTAN_FFI_VISIT(cert, [=](const auto& c) -> int { auto issuer_info = c.issuer_info(key); if(index < issuer_info.size()) { - return write_str_output(out, out_len, c.issuer_info(key).at(index)); + // TODO(Botan4) change the type of out and remove this cast + return write_str_output(reinterpret_cast(out), out_len, c.issuer_info(key).at(index)); } else { - return BOTAN_FFI_ERROR_BAD_PARAMETER; + return BOTAN_FFI_ERROR_BAD_PARAMETER; // TODO(Botan4): use BOTAN_FFI_ERROR_OUT_OF_RANGE } }); #else @@ -118,15 +499,35 @@ #endif } +int botan_x509_cert_get_issuer_dn_count(botan_x509_cert_t cert, const char* key, size_t* count) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(cert, [=](const auto& c) -> int { + if(Botan::any_null_pointers(count)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + *count = c.issuer_info(key).size(); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(cert, key, count); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + int botan_x509_cert_get_subject_dn( botan_x509_cert_t cert, const char* key, size_t index, uint8_t out[], size_t* out_len) { + if(key == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } #if defined(BOTAN_HAS_X509_CERTIFICATES) return BOTAN_FFI_VISIT(cert, [=](const auto& c) -> int { auto subject_info = c.subject_info(key); if(index < subject_info.size()) { - return write_str_output(out, out_len, c.subject_info(key).at(index)); + // TODO(Botan4) change the type of out and remove this cast + return write_str_output(reinterpret_cast(out), out_len, c.subject_info(key).at(index)); } else { - return BOTAN_FFI_ERROR_BAD_PARAMETER; + return BOTAN_FFI_ERROR_BAD_PARAMETER; // TODO(Botan4): use BOTAN_FFI_ERROR_OUT_OF_RANGE } }); #else @@ -135,6 +536,22 @@ #endif } +int botan_x509_cert_get_subject_dn_count(botan_x509_cert_t cert, const char* key, size_t* count) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(cert, [=](const auto& c) -> int { + if(Botan::any_null_pointers(count)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + *count = c.subject_info(key).size(); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(cert, key, count); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + int botan_x509_cert_to_string(botan_x509_cert_t cert, char out[], size_t* out_len) { return copy_view_str(reinterpret_cast(out), out_len, botan_x509_cert_view_as_string, cert); } @@ -163,6 +580,30 @@ #endif } +int botan_x509_cert_allowed_extended_usage_str(botan_x509_cert_t cert, const char* oid) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(cert, [=](const auto& c) -> int { + if(Botan::any_null_pointers(oid)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + return c.has_ex_constraint(oid) ? 1 : 0; + }); +#else + BOTAN_UNUSED(cert, oid); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_cert_allowed_extended_usage_oid(botan_x509_cert_t cert, botan_asn1_oid_t oid) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(cert, [=](const auto& c) -> int { return c.has_ex_constraint(safe_get(oid)) ? 1 : 0; }); +#else + BOTAN_UNUSED(cert, oid); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + int botan_x509_cert_destroy(botan_x509_cert_t cert) { #if defined(BOTAN_HAS_X509_CERTIFICATES) return BOTAN_FFI_CHECKED_DELETE(cert); @@ -193,6 +634,9 @@ } int botan_x509_cert_not_before(botan_x509_cert_t cert, uint64_t* time_since_epoch) { + if(time_since_epoch == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } #if defined(BOTAN_HAS_X509_CERTIFICATES) return BOTAN_FFI_VISIT(cert, [=](const auto& c) { *time_since_epoch = c.not_before().time_since_epoch(); }); #else @@ -202,6 +646,9 @@ } int botan_x509_cert_not_after(botan_x509_cert_t cert, uint64_t* time_since_epoch) { + if(time_since_epoch == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } #if defined(BOTAN_HAS_X509_CERTIFICATES) return BOTAN_FFI_VISIT(cert, [=](const auto& c) { *time_since_epoch = c.not_after().time_since_epoch(); }); #else @@ -219,9 +666,32 @@ #endif } +int botan_x509_cert_serial_number(botan_x509_cert_t cert, botan_mp_t* serial_number) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(cert, [=](const Botan::X509_Certificate& c) { + if(Botan::any_null_pointers(serial_number)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + auto serial_bn = Botan::BigInt::from_bytes(c.serial_number()); + return ffi_new_object(serial_number, std::make_unique(std::move(serial_bn))); + }); +#else + BOTAN_UNUSED(cert, serial_number); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + int botan_x509_cert_get_fingerprint(botan_x509_cert_t cert, const char* hash, uint8_t out[], size_t* out_len) { + if(hash == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } #if defined(BOTAN_HAS_X509_CERTIFICATES) - return BOTAN_FFI_VISIT(cert, [=](const auto& c) { return write_str_output(out, out_len, c.fingerprint(hash)); }); + // TODO(Botan4) change the type of out and remove this cast + + return BOTAN_FFI_VISIT(cert, [=](const auto& c) { + return write_str_output(reinterpret_cast(out), out_len, c.fingerprint(hash)); + }); #else BOTAN_UNUSED(cert, hash, out, out_len); return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; @@ -260,6 +730,233 @@ #endif } +int botan_x509_general_name_get_type(botan_x509_general_name_t name, unsigned int* type) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(name, [=](const Botan::GeneralName& n) { + if(Botan::any_null_pointers(type)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + const auto mapped_type = to_botan_x509_general_name_types(n.type_code()); + if(!mapped_type.has_value()) { + return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; + } + + *type = mapped_type.value(); + if(*type == BOTAN_X509_OTHER_NAME /* ... viewing of other-names not supported */) { + return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; + } + + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(name, type); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_general_name_view_string_value(botan_x509_general_name_t name, + botan_view_ctx ctx, + botan_view_str_fn view) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(name, [=](const Botan::GeneralName& n) -> int { + const auto type = to_botan_x509_general_name_types(n.type_code()); + if(!type) { + return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; + } + + if(type != BOTAN_X509_EMAIL_ADDRESS && type != BOTAN_X509_DNS_NAME && type != BOTAN_X509_URI && + type != BOTAN_X509_IP_ADDRESS) { + return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; + } + + return invoke_view_callback(view, ctx, n.name()); + }); +#else + BOTAN_UNUSED(name, ctx, view); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_general_name_view_binary_value(botan_x509_general_name_t name, + botan_view_ctx ctx, + botan_view_bin_fn view) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(name, [=](const Botan::GeneralName& n) -> int { + const auto type = to_botan_x509_general_name_types(n.type_code()); + if(!type) { + return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; + } + + if(type != BOTAN_X509_DIRECTORY_NAME && type != BOTAN_X509_IP_ADDRESS) { + return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; + } + + return invoke_view_callback(view, ctx, n.binary_name()); + }); +#else + BOTAN_UNUSED(name, ctx, view); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_general_name_destroy(botan_x509_general_name_t name) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_CHECKED_DELETE(name); +#else + BOTAN_UNUSED(name); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_cert_permitted_name_constraints(botan_x509_cert_t cert, + size_t index, + botan_x509_general_name_t* constraint) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(cert, [=](const Botan::X509_Certificate& c) { + if(Botan::any_null_pointers(constraint)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + const auto& constraints = c.name_constraints().permitted(); + if(index >= constraints.size()) { + return BOTAN_FFI_ERROR_OUT_OF_RANGE; + } + + return ffi_new_object(constraint, std::make_unique(constraints[index].base())); + }); +#else + BOTAN_UNUSED(cert, index, constraint); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_cert_permitted_name_constraints_count(botan_x509_cert_t cert, size_t* count) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + if(Botan::any_null_pointers(count)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + return BOTAN_FFI_VISIT(cert, [=](const auto& c) { *count = c.name_constraints().permitted().size(); }); +#else + BOTAN_UNUSED(cert, count); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_cert_excluded_name_constraints(botan_x509_cert_t cert, + size_t index, + botan_x509_general_name_t* constraint) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(cert, [=](const Botan::X509_Certificate& c) { + if(Botan::any_null_pointers(constraint)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + const auto& constraints = c.name_constraints().excluded(); + if(index >= constraints.size()) { + return BOTAN_FFI_ERROR_OUT_OF_RANGE; + } + + return ffi_new_object(constraint, std::make_unique(constraints[index].base())); + }); +#else + BOTAN_UNUSED(cert, index, constraint); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_cert_excluded_name_constraints_count(botan_x509_cert_t cert, size_t* count) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + if(Botan::any_null_pointers(count)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + return BOTAN_FFI_VISIT(cert, [=](const auto& c) { *count = c.name_constraints().excluded().size(); }); +#else + BOTAN_UNUSED(cert, count); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_cert_subject_alternative_names(botan_x509_cert_t cert, + size_t index, + botan_x509_general_name_t* alt_name) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(cert, [=](const Botan::X509_Certificate& c) { + if(Botan::any_null_pointers(alt_name)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + if(!c.v3_extensions().extension_set(Botan::OID::from_string("X509v3.SubjectAlternativeName"))) { + return BOTAN_FFI_ERROR_NO_VALUE; + } + + if(auto name = extract_general_name_at(c.subject_alt_name(), index)) { + return ffi_new_object(alt_name, std::make_unique(std::move(name).value())); + } + + return BOTAN_FFI_ERROR_OUT_OF_RANGE; + }); +#else + BOTAN_UNUSED(cert, index, alt_name); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_cert_subject_alternative_names_count(botan_x509_cert_t cert, size_t* count) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + if(Botan::any_null_pointers(count)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + return BOTAN_FFI_VISIT( + cert, [=](const Botan::X509_Certificate& c) { *count = count_general_names_in(c.subject_alt_name()); }); +#else + BOTAN_UNUSED(cert, count); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_cert_issuer_alternative_names(botan_x509_cert_t cert, + size_t index, + botan_x509_general_name_t* alt_name) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(cert, [=](const Botan::X509_Certificate& c) { + if(Botan::any_null_pointers(alt_name)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + if(!c.v3_extensions().extension_set(Botan::OID::from_string("X509v3.IssuerAlternativeName"))) { + return BOTAN_FFI_ERROR_NO_VALUE; + } + + if(auto name = extract_general_name_at(c.issuer_alt_name(), index)) { + return ffi_new_object(alt_name, std::make_unique(std::move(name).value())); + } + + return BOTAN_FFI_ERROR_OUT_OF_RANGE; + }); +#else + BOTAN_UNUSED(cert, index, alt_name); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_cert_issuer_alternative_names_count(botan_x509_cert_t cert, size_t* count) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + if(Botan::any_null_pointers(count)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + return BOTAN_FFI_VISIT( + cert, [=](const Botan::X509_Certificate& c) { *count = count_general_names_in(c.issuer_alt_name()); }); +#else + BOTAN_UNUSED(cert, count); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + int botan_x509_cert_hostname_match(botan_x509_cert_t cert, const char* hostname) { if(hostname == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; @@ -305,7 +1002,7 @@ std::unique_ptr trusted_extra; std::vector trusted_roots; - if(trusted_path && *trusted_path) { + if(trusted_path != nullptr && *trusted_path != 0) { trusted_from_path = std::make_unique(trusted_path); trusted_roots.push_back(trusted_from_path.get()); } @@ -318,12 +1015,12 @@ trusted_roots.push_back(trusted_extra.get()); } - Botan::Path_Validation_Restrictions restrictions(false, required_strength); + const Botan::Path_Validation_Restrictions restrictions(false, required_strength); auto validation_result = Botan::x509_path_validate(end_certs, restrictions, trusted_roots, hostname, usage, validation_time); - if(result_code) { + if(result_code != nullptr) { *result_code = static_cast(validation_result.result()); } @@ -346,21 +1043,15 @@ } #if defined(BOTAN_HAS_X509_CERTIFICATES) - Botan::Certificate_Status_Code sc = static_cast(code); + const Botan::Certificate_Status_Code sc = static_cast(code); return Botan::to_string(sc); #else return nullptr; #endif } -#if defined(BOTAN_HAS_X509_CERTIFICATES) - -BOTAN_FFI_DECLARE_STRUCT(botan_x509_crl_struct, Botan::X509_CRL, 0x2C628910); - -#endif - int botan_x509_crl_load_file(botan_x509_crl_t* crl_obj, const char* crl_path) { - if(!crl_obj || !crl_path) { + if(crl_obj == nullptr || crl_path == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } @@ -368,8 +1059,7 @@ return ffi_guard_thunk(__func__, [=]() -> int { auto c = std::make_unique(crl_path); - *crl_obj = new botan_x509_crl_struct(std::move(c)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(crl_obj, std::move(c)); }); #else @@ -378,7 +1068,7 @@ } int botan_x509_crl_load(botan_x509_crl_t* crl_obj, const uint8_t crl_bits[], size_t crl_bits_len) { - if(!crl_obj || !crl_bits) { + if(crl_obj == nullptr || crl_bits == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } @@ -386,8 +1076,7 @@ return ffi_guard_thunk(__func__, [=]() -> int { Botan::DataSource_Memory bits(crl_bits, crl_bits_len); auto c = std::make_unique(bits); - *crl_obj = new botan_x509_crl_struct(std::move(c)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(crl_obj, std::move(c)); }); #else BOTAN_UNUSED(crl_bits_len); @@ -395,6 +1084,132 @@ #endif } +int botan_x509_crl_this_update(botan_x509_crl_t crl, uint64_t* time_since_epoch) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(crl, [=](const auto& c) { + if(Botan::any_null_pointers(time_since_epoch)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + *time_since_epoch = c.this_update().time_since_epoch(); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(crl, time_since_epoch); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_crl_next_update(botan_x509_crl_t crl, uint64_t* time_since_epoch) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(crl, [=](const auto& c) { + const auto& time = c.next_update(); + if(!time.time_is_set()) { + return BOTAN_FFI_ERROR_NO_VALUE; + } + + if(Botan::any_null_pointers(time_since_epoch)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + *time_since_epoch = c.next_update().time_since_epoch(); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(crl, time_since_epoch); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_crl_create(botan_x509_crl_t* crl_obj, + botan_rng_t rng, + botan_x509_cert_t ca_cert, + botan_privkey_t ca_key, + uint64_t issue_time, + uint32_t next_update, + const char* hash_fn, + const char* padding) { + if(Botan::any_null_pointers(crl_obj)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + auto& rng_ = safe_get(rng); + auto ca = Botan::X509_CA( + safe_get(ca_cert), safe_get(ca_key), default_from_ptr(hash_fn), default_from_ptr(padding), rng_); + auto crl = std::make_unique( + ca.new_crl(rng_, timepoint_from_timestamp(issue_time), std::chrono::seconds(next_update))); + return ffi_new_object(crl_obj, std::move(crl)); + }); +#else + BOTAN_UNUSED(rng, ca_cert, ca_key, hash_fn, padding, issue_time, next_update); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_crl_entry_create(botan_x509_crl_entry_t* entry, botan_x509_cert_t cert, int reason_code) { + if(Botan::any_null_pointers(entry)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + return ffi_new_object( + entry, std::make_unique(safe_get(cert), static_cast(reason_code))); + }); +#else + BOTAN_UNUSED(cert, reason_code); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_crl_update(botan_x509_crl_t* crl_obj, + botan_x509_crl_t last_crl, + botan_rng_t rng, + botan_x509_cert_t ca_cert, + botan_privkey_t ca_key, + uint64_t issue_time, + uint32_t next_update, + const botan_x509_crl_entry_t* new_entries, + size_t new_entries_len, + const char* hash_fn, + const char* padding) { + if(Botan::any_null_pointers(crl_obj)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + if(new_entries_len > 0 && Botan::any_null_pointers(new_entries)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + auto& rng_ = safe_get(rng); + auto ca = Botan::X509_CA( + safe_get(ca_cert), safe_get(ca_key), default_from_ptr(hash_fn), default_from_ptr(padding), rng_); + + std::vector entries; + entries.reserve(new_entries_len); + for(size_t i = 0; i < new_entries_len; i++) { + entries.push_back(safe_get(new_entries[i])); + } + + auto crl = std::make_unique(ca.update_crl( + safe_get(last_crl), entries, rng_, timepoint_from_timestamp(issue_time), std::chrono::seconds(next_update))); + return ffi_new_object(crl_obj, std::move(crl)); + }); +#else + BOTAN_UNUSED( + last_crl, rng, ca_cert, ca_key, hash_fn, padding, issue_time, next_update, new_entries, new_entries_len); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_crl_verify_signature(botan_x509_crl_t crl, botan_pubkey_t key) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(crl, [=](const auto& c) -> int { return c.check_signature(safe_get(key)) ? 1 : 0; }); +#else + BOTAN_UNUSED(crl, key); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + int botan_x509_crl_destroy(botan_x509_crl_t crl) { #if defined(BOTAN_HAS_X509_CERTIFICATES) return BOTAN_FFI_CHECKED_DELETE(crl); @@ -404,6 +1219,117 @@ #endif } +int botan_x509_crl_view_binary_values(botan_x509_crl_t crl_obj, + botan_x509_value_type value_type, + size_t index, + botan_view_ctx ctx, + botan_view_bin_fn view_fn) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + if(index != 0) { + // As of now there are no multi-value binary entries. + return BOTAN_FFI_ERROR_OUT_OF_RANGE; + } + + auto view = [=](std::span value) -> int { + if(value.empty()) { + return BOTAN_FFI_ERROR_NO_VALUE; + } else { + return invoke_view_callback(view_fn, ctx, value); + } + }; + + return BOTAN_FFI_VISIT(crl_obj, [=](const Botan::X509_CRL& crl) -> int { + switch(value_type) { + case BOTAN_X509_SERIAL_NUMBER: + return view(Botan::store_be(crl.crl_number())); + case BOTAN_X509_ISSUER_DN_BITS: + return view(Botan::ASN1::put_in_sequence(crl.issuer_dn().get_bits())); + case BOTAN_X509_AUTHORITY_KEY_IDENTIFIER: + return view(crl.authority_key_id()); + + case BOTAN_X509_TBS_DATA_BITS: + case BOTAN_X509_SIGNATURE_SCHEME_BITS: + case BOTAN_X509_SIGNATURE_BITS: + case BOTAN_X509_DER_ENCODING: + return botan_x509_object_view_value(crl, value_type, index, ctx, view_fn); + + case BOTAN_X509_SUBJECT_DN_BITS: + case BOTAN_X509_SUBJECT_KEY_IDENTIFIER: + case BOTAN_X509_PUBLIC_KEY_PKCS8_BITS: + case BOTAN_X509_PEM_ENCODING: + case BOTAN_X509_CRL_DISTRIBUTION_URLS: + case BOTAN_X509_OCSP_RESPONDER_URLS: + case BOTAN_X509_CA_ISSUERS_URLS: + return BOTAN_FFI_ERROR_NO_VALUE; + } + + return BOTAN_FFI_ERROR_BAD_PARAMETER; + }); +#else + BOTAN_UNUSED(crl_obj, value_type, index, ctx, view_fn); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_crl_view_binary_values_count(botan_x509_crl_t crl_obj, botan_x509_value_type value_type, size_t* count) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return enumerator_count_values(count, [=](size_t index) { + return botan_x509_crl_view_binary_values( + crl_obj, value_type, index, nullptr, [](auto, auto, auto) -> int { return BOTAN_FFI_SUCCESS; }); + }); +#else + BOTAN_UNUSED(crl_obj, value_type, count); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_crl_view_string_values(botan_x509_crl_t crl_obj, + botan_x509_value_type value_type, + size_t index, + botan_view_ctx ctx, + botan_view_str_fn view) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(crl_obj, [=](const Botan::X509_CRL& crl) -> int { + switch(value_type) { + case BOTAN_X509_PEM_ENCODING: + return botan_x509_object_view_value(crl, value_type, index, ctx, view); + + case BOTAN_X509_SERIAL_NUMBER: + case BOTAN_X509_SUBJECT_DN_BITS: + case BOTAN_X509_ISSUER_DN_BITS: + case BOTAN_X509_SUBJECT_KEY_IDENTIFIER: + case BOTAN_X509_AUTHORITY_KEY_IDENTIFIER: + case BOTAN_X509_PUBLIC_KEY_PKCS8_BITS: + case BOTAN_X509_TBS_DATA_BITS: + case BOTAN_X509_SIGNATURE_SCHEME_BITS: + case BOTAN_X509_SIGNATURE_BITS: + case BOTAN_X509_DER_ENCODING: + case BOTAN_X509_CRL_DISTRIBUTION_URLS: + case BOTAN_X509_OCSP_RESPONDER_URLS: + case BOTAN_X509_CA_ISSUERS_URLS: + return BOTAN_FFI_ERROR_NO_VALUE; + } + + return BOTAN_FFI_ERROR_BAD_PARAMETER; + }); +#else + BOTAN_UNUSED(crl_obj, value_type, index, ctx, view); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_crl_view_string_values_count(botan_x509_crl_t crl_obj, botan_x509_value_type value_type, size_t* count) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return enumerator_count_values(count, [=](size_t index) { + return botan_x509_crl_view_string_values( + crl_obj, value_type, index, nullptr, [](auto, auto, auto) -> int { return BOTAN_FFI_SUCCESS; }); + }); +#else + BOTAN_UNUSED(crl_obj, value_type, count); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + int botan_x509_is_revoked(botan_x509_crl_t crl, botan_x509_cert_t cert) { #if defined(BOTAN_HAS_X509_CERTIFICATES) return BOTAN_FFI_VISIT(crl, [=](const auto& c) { return c.is_revoked(safe_get(cert)) ? 0 : -1; }); @@ -414,6 +1340,106 @@ #endif } +int botan_x509_crl_entries(botan_x509_crl_t crl, size_t index, botan_x509_crl_entry_t* entry) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(crl, [=](const Botan::X509_CRL& c) -> int { + const auto& entries = c.get_revoked(); + if(index >= entries.size()) { + return BOTAN_FFI_ERROR_OUT_OF_RANGE; + } + + if(Botan::any_null_pointers(entry)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + return ffi_new_object(entry, std::make_unique(entries[index])); + }); +#else + BOTAN_UNUSED(crl, index, entry); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_crl_entries_count(botan_x509_crl_t crl, size_t* count) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + if(Botan::any_null_pointers(count)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + return BOTAN_FFI_VISIT(crl, [=](const Botan::X509_CRL& c) { *count = c.get_revoked().size(); }); +#else + BOTAN_UNUSED(crl, count); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_crl_entry_destroy(botan_x509_crl_entry_t entry) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_CHECKED_DELETE(entry); +#else + BOTAN_UNUSED(entry); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_crl_entry_reason(botan_x509_crl_entry_t entry, int* reason_code) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(entry, [=](const Botan::CRL_Entry& e) { + if(Botan::any_null_pointers(reason_code)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + *reason_code = static_cast(e.reason_code()); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(entry, reason_code); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_crl_entry_serial_number(botan_x509_crl_entry_t entry, botan_mp_t* serial_number) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(entry, [=](const Botan::CRL_Entry& e) { + if(Botan::any_null_pointers(serial_number)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + auto serial_bn = Botan::BigInt::from_bytes(e.serial_number()); + return ffi_new_object(serial_number, std::make_unique(std::move(serial_bn))); + }); +#else + BOTAN_UNUSED(entry, serial_number); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_crl_entry_view_serial_number(botan_x509_crl_entry_t entry, botan_view_ctx ctx, botan_view_bin_fn view) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT( + entry, [=](const Botan::CRL_Entry& e) { return invoke_view_callback(view, ctx, e.serial_number()); }); +#else + BOTAN_UNUSED(entry, ctx, view); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_crl_entry_revocation_date(botan_x509_crl_entry_t entry, uint64_t* time_since_epoch) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(entry, [=](const Botan::CRL_Entry& e) { + if(Botan::any_null_pointers(time_since_epoch)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + *time_since_epoch = e.expire_time().time_since_epoch(); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(entry, time_since_epoch); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + int botan_x509_cert_verify_with_crl(int* result_code, botan_x509_cert_t cert, const botan_x509_cert_t* intermediates, @@ -449,7 +1475,7 @@ std::unique_ptr trusted_crls; std::vector trusted_roots; - if(trusted_path && *trusted_path) { + if(trusted_path != nullptr && *trusted_path != 0) { trusted_from_path = std::make_unique(trusted_path); trusted_roots.push_back(trusted_from_path.get()); } @@ -470,12 +1496,12 @@ trusted_roots.push_back(trusted_crls.get()); } - Botan::Path_Validation_Restrictions restrictions(false, required_strength); + const Botan::Path_Validation_Restrictions restrictions(false, required_strength); auto validation_result = Botan::x509_path_validate(end_certs, restrictions, trusted_roots, hostname, usage, validation_time); - if(result_code) { + if(result_code != nullptr) { *result_code = static_cast(validation_result.result()); } diff -Nru botan3-3.7.1+dfsg/src/lib/ffi/ffi_cert.h botan3-3.12.0+dfsg/src/lib/ffi/ffi_cert.h --- botan3-3.7.1+dfsg/src/lib/ffi/ffi_cert.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/ffi/ffi_cert.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,31 @@ +/* +* (C) 2026 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_FFI_CERT_H_ +#define BOTAN_FFI_CERT_H_ + +#include + +#if defined(BOTAN_HAS_X509_CERTIFICATES) + #include + #include + #include + #include + #include +#endif + +extern "C" { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + +BOTAN_FFI_DECLARE_STRUCT(botan_x509_cert_struct, Botan::X509_Certificate, 0x8F628937); +BOTAN_FFI_DECLARE_STRUCT(botan_x509_crl_struct, Botan::X509_CRL, 0x2C628910); +BOTAN_FFI_DECLARE_STRUCT(botan_x509_crl_entry_struct, Botan::CRL_Entry, 0x4EAA5346); +BOTAN_FFI_DECLARE_STRUCT(botan_x509_general_name_struct, Botan::GeneralName, 0x563654FD); + +#endif +} + +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/ffi/ffi_cipher.cpp botan3-3.12.0+dfsg/src/lib/ffi/ffi_cipher.cpp --- botan3-3.7.1+dfsg/src/lib/ffi/ffi_cipher.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/ffi/ffi_cipher.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,9 +7,12 @@ #include #include +#include #include +#include +#include #include -#include +#include #include @@ -95,6 +98,9 @@ int botan_cipher_init(botan_cipher_t* cipher, const char* cipher_name, uint32_t flags) { return ffi_guard_thunk(__func__, [=]() -> int { + if(any_null_pointers(cipher, cipher_name)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } const bool encrypt_p = ((flags & BOTAN_CIPHER_INIT_FLAG_MASK_DIRECTION) == BOTAN_CIPHER_INIT_FLAG_ENCRYPT); const Botan::Cipher_Dir dir = encrypt_p ? Botan::Cipher_Dir::Encryption : Botan::Cipher_Dir::Decryption; @@ -106,8 +112,7 @@ const size_t update_size = ffi_choose_update_size(*mode); const size_t ideal_update_size = std::max(mode->ideal_granularity(), update_size); - *cipher = new botan_cipher_struct(std::move(mode), update_size, ideal_update_size); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(cipher, std::move(mode), update_size, ideal_update_size); }); } @@ -116,11 +121,17 @@ } int botan_cipher_clear(botan_cipher_t cipher) { - return BOTAN_FFI_VISIT(cipher, [](auto& c) { c.clear(); }); + return BOTAN_FFI_VISIT(cipher, [=](auto& c) { + cipher->buf().clear(); + c.clear(); + }); } int botan_cipher_reset(botan_cipher_t cipher) { - return BOTAN_FFI_VISIT(cipher, [](auto& c) { c.reset(); }); + return BOTAN_FFI_VISIT(cipher, [=](auto& c) { + cipher->buf().clear(); + c.reset(); + }); } int botan_cipher_output_length(botan_cipher_t cipher, size_t in_len, size_t* out_len) { @@ -133,8 +144,12 @@ int botan_cipher_query_keylen(botan_cipher_t cipher, size_t* out_minimum_keylength, size_t* out_maximum_keylength) { return BOTAN_FFI_VISIT(cipher, [=](const auto& c) { - *out_minimum_keylength = c.key_spec().minimum_keylength(); - *out_maximum_keylength = c.key_spec().maximum_keylength(); + if(out_minimum_keylength) { + *out_minimum_keylength = c.key_spec().minimum_keylength(); + } + if(out_maximum_keylength) { + *out_maximum_keylength = c.key_spec().maximum_keylength(); + } }); } @@ -156,6 +171,9 @@ } int botan_cipher_set_key(botan_cipher_t cipher, const uint8_t* key, size_t key_len) { + if(key_len > 0 && key == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } return BOTAN_FFI_VISIT(cipher, [=](auto& c) { c.set_key(key, key_len); }); } @@ -175,6 +193,10 @@ const uint8_t input[], size_t input_size, size_t* input_consumed) { + if(any_null_pointers(output_written, input_consumed)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + return ffi_guard_thunk(__func__, [=]() -> int { using namespace Botan; Cipher_Mode& cipher = safe_get(cipher_obj); @@ -185,7 +207,7 @@ // called with the final flag set but not enough buffer space was provided // to accommodate the final output. const bool was_finished_before = !mbuf.empty(); - const bool final_input = (flags & BOTAN_CIPHER_UPDATE_FLAG_FINAL); + const bool final_input = (flags & BOTAN_CIPHER_UPDATE_FLAG_FINAL) != 0; // Bring the output variables into a defined state. *output_written = 0; @@ -300,18 +322,30 @@ } int botan_cipher_get_default_nonce_length(botan_cipher_t cipher, size_t* nl) { + if(nl == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } return BOTAN_FFI_VISIT(cipher, [=](const auto& c) { *nl = c.default_nonce_length(); }); } int botan_cipher_get_update_granularity(botan_cipher_t cipher, size_t* ug) { + if(ug == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } return BOTAN_FFI_VISIT(cipher, [=](const auto& /*c*/) { *ug = cipher->update_size(); }); } int botan_cipher_get_ideal_update_granularity(botan_cipher_t cipher, size_t* ug) { + if(ug == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } return BOTAN_FFI_VISIT(cipher, [=](const auto& c) { *ug = c.ideal_granularity(); }); } int botan_cipher_get_tag_length(botan_cipher_t cipher, size_t* tl) { + if(tl == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } return BOTAN_FFI_VISIT(cipher, [=](const auto& c) { *tl = c.tag_size(); }); } diff -Nru botan3-3.7.1+dfsg/src/lib/ffi/ffi_ec.cpp botan3-3.12.0+dfsg/src/lib/ffi/ffi_ec.cpp --- botan3-3.7.1+dfsg/src/lib/ffi/ffi_ec.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/ffi/ffi_ec.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,355 @@ +/* +* (C) 2025 Jack Lloyd +* (C) 2025,2026 Dominik Schricker +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include +#include +#include + +extern "C" { + +using namespace Botan_FFI; + +int botan_ec_group_destroy(botan_ec_group_t ec_group) { + return BOTAN_FFI_CHECKED_DELETE(ec_group); +} + +int botan_ec_group_supports_application_specific_group(int* out) { + if(out == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + if(Botan::EC_Group::supports_application_specific_group()) { + *out = 1; + } else { + *out = 0; + } + return BOTAN_FFI_SUCCESS; +} + +int botan_ec_group_supports_named_group(const char* name, int* out) { + return ffi_guard_thunk(__func__, [=]() -> int { + if(name == nullptr || out == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + if(Botan::EC_Group::supports_named_group(name)) { + *out = 1; + } else { + *out = 0; + } + return BOTAN_FFI_SUCCESS; + }); +} + +int botan_ec_group_from_params(botan_ec_group_t* ec_group, + botan_asn1_oid_t oid, + botan_mp_t p, + botan_mp_t a, + botan_mp_t b, + botan_mp_t base_x, + botan_mp_t base_y, + botan_mp_t order) { + return ffi_guard_thunk(__func__, [=]() -> int { + if(ec_group == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + Botan::EC_Group group( + safe_get(oid), safe_get(p), safe_get(a), safe_get(b), safe_get(base_x), safe_get(base_y), safe_get(order)); + + auto group_ptr = std::make_unique(std::move(group)); + return ffi_new_object(ec_group, std::move(group_ptr)); + }); +} + +int botan_ec_group_from_ber(botan_ec_group_t* ec_group, const uint8_t* ber, size_t ber_len) { + return ffi_guard_thunk(__func__, [=]() -> int { + if(ec_group == nullptr || ber == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + Botan::EC_Group group(ber, ber_len); + + auto group_ptr = std::make_unique(std::move(group)); + return ffi_new_object(ec_group, std::move(group_ptr)); + }); +} + +int botan_ec_group_from_pem(botan_ec_group_t* ec_group, const char* pem) { + return ffi_guard_thunk(__func__, [=]() -> int { + if(ec_group == nullptr || pem == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + Botan::EC_Group group = Botan::EC_Group::from_PEM(pem); + + auto group_ptr = std::make_unique(std::move(group)); + return ffi_new_object(ec_group, std::move(group_ptr)); + }); +} + +int botan_ec_group_from_oid(botan_ec_group_t* ec_group, botan_asn1_oid_t oid) { + return ffi_guard_thunk(__func__, [=]() -> int { + if(ec_group == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + Botan::EC_Group group = Botan::EC_Group::from_OID(safe_get(oid)); + + auto group_ptr = std::make_unique(std::move(group)); + return ffi_new_object(ec_group, std::move(group_ptr)); + }); +} + +int botan_ec_group_from_name(botan_ec_group_t* ec_group, const char* name) { + return ffi_guard_thunk(__func__, [=]() -> int { + if(ec_group == nullptr || name == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + Botan::EC_Group group = Botan::EC_Group::from_name(name); + + auto group_ptr = std::make_unique(std::move(group)); + return ffi_new_object(ec_group, std::move(group_ptr)); + }); +} + +int botan_ec_group_unregister(botan_asn1_oid_t oid) { + return BOTAN_FFI_VISIT(oid, [=](const auto& o) -> int { return Botan::EC_Group::unregister(o) ? 1 : 0; }); +} + +int botan_ec_group_view_der(botan_ec_group_t ec_group, botan_view_ctx ctx, botan_view_bin_fn view) { + return BOTAN_FFI_VISIT(ec_group, + [=](const auto& g) -> int { return invoke_view_callback(view, ctx, g.DER_encode()); }); +} + +int botan_ec_group_view_pem(botan_ec_group_t ec_group, botan_view_ctx ctx, botan_view_str_fn view) { + return BOTAN_FFI_VISIT(ec_group, [=](const auto& g) -> int { + return invoke_view_callback(view, ctx, g.PEM_encode(Botan::EC_Group_Encoding::NamedCurve)); + }); +} + +int botan_ec_group_get_curve_oid(botan_asn1_oid_t* oid, botan_ec_group_t ec_group) { + return BOTAN_FFI_VISIT(ec_group, [=](const auto& g) -> int { + if(oid == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + auto oid_ptr = std::make_unique(g.get_curve_oid()); + return ffi_new_object(oid, std::move(oid_ptr)); + }); +} + +namespace { +int botan_ec_group_get_component(botan_mp_t* out, + botan_ec_group_t ec_group, + const std::function& getter) { + return BOTAN_FFI_VISIT(ec_group, [=](const auto& g) -> int { + if(out == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + auto val = std::make_unique(getter(g)); + return ffi_new_object(out, std::move(val)); + }); +} +} // namespace + +int botan_ec_group_get_p(botan_mp_t* p, botan_ec_group_t ec_group) { + return botan_ec_group_get_component(p, ec_group, [](const auto& g) -> const Botan::BigInt& { return g.get_p(); }); +} + +int botan_ec_group_get_a(botan_mp_t* a, botan_ec_group_t ec_group) { + return botan_ec_group_get_component(a, ec_group, [](const auto& g) -> const Botan::BigInt& { return g.get_a(); }); +} + +int botan_ec_group_get_b(botan_mp_t* b, botan_ec_group_t ec_group) { + return botan_ec_group_get_component(b, ec_group, [](const auto& g) -> const Botan::BigInt& { return g.get_b(); }); +} + +int botan_ec_group_get_g_x(botan_mp_t* g_x, botan_ec_group_t ec_group) { + return botan_ec_group_get_component( + g_x, ec_group, [](const auto& g) -> const Botan::BigInt& { return g.get_g_x(); }); +} + +int botan_ec_group_get_g_y(botan_mp_t* g_y, botan_ec_group_t ec_group) { + return botan_ec_group_get_component( + g_y, ec_group, [](const auto& g) -> const Botan::BigInt& { return g.get_g_y(); }); +} + +int botan_ec_group_get_order(botan_mp_t* order, botan_ec_group_t ec_group) { + return botan_ec_group_get_component( + order, ec_group, [](const auto& g) -> const Botan::BigInt& { return g.get_order(); }); +} + +int botan_ec_group_equal(botan_ec_group_t curve1_w, botan_ec_group_t curve2_w) { + return BOTAN_FFI_VISIT(curve1_w, [=](const auto& curve1) -> int { return curve1 == safe_get(curve2_w); }); +} + +// ec scalars + +int botan_ec_scalar_destroy(botan_ec_scalar_t ec_scalar) { + return BOTAN_FFI_CHECKED_DELETE(ec_scalar); +} + +int botan_ec_scalar_random(botan_ec_scalar_t* ec_scalar, botan_ec_group_t ec_group, botan_rng_t rng) { + if(Botan::any_null_pointers(ec_scalar)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + return BOTAN_FFI_VISIT(ec_group, [=](const auto& g) -> int { + return ffi_new_object(ec_scalar, std::make_unique(Botan::EC_Scalar::random(g, safe_get(rng)))); + }); +} + +int botan_ec_scalar_from_mp(botan_ec_scalar_t* ec_scalar, botan_ec_group_t ec_group, botan_mp_t mp) { + if(Botan::any_null_pointers(ec_scalar)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + return BOTAN_FFI_VISIT(ec_group, [=](const auto& g) -> int { + return ffi_new_object(ec_scalar, + std::make_unique(Botan::EC_Scalar::from_bigint(g, safe_get(mp)))); + }); +} + +int botan_ec_scalar_to_mp(botan_ec_scalar_t ec_scalar, botan_mp_t* mp) { + if(Botan::any_null_pointers(mp)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + return BOTAN_FFI_VISIT(ec_scalar, [=](const auto& sc) -> int { + return ffi_new_object(mp, std::make_unique(sc.to_bigint())); + }); +} + +// ec points + +int botan_ec_point_destroy(botan_ec_point_t ec_point) { + return BOTAN_FFI_CHECKED_DELETE(ec_point); +} + +int botan_ec_point_identity(botan_ec_point_t* ec_point, botan_ec_group_t ec_group) { + if(Botan::any_null_pointers(ec_point)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + return BOTAN_FFI_VISIT(ec_group, [=](const auto& g) -> int { + return ffi_new_object(ec_point, std::make_unique(Botan::EC_AffinePoint::identity(g))); + }); +} + +int botan_ec_point_generator(botan_ec_point_t* ec_point, botan_ec_group_t ec_group) { + if(Botan::any_null_pointers(ec_point)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + return BOTAN_FFI_VISIT(ec_group, [=](const auto& g) -> int { + return ffi_new_object(ec_point, std::make_unique(Botan::EC_AffinePoint::generator(g))); + }); +} + +int botan_ec_point_from_xy(botan_ec_point_t* ec_point, botan_ec_group_t ec_group, botan_mp_t x, botan_mp_t y) { + if(Botan::any_null_pointers(ec_point)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + return ffi_guard_thunk(__func__, [=]() -> int { + std::optional pt = + Botan::EC_AffinePoint::from_bigint_xy(safe_get(ec_group), safe_get(x), safe_get(y)); + if(!pt.has_value()) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + + return ffi_new_object(ec_point, std::make_unique(pt.value())); + }); +} + +int botan_ec_point_from_bytes(botan_ec_point_t* ec_point, + botan_ec_group_t ec_group, + const uint8_t* bytes, + size_t bytes_len) { + if(Botan::any_null_pointers(ec_point, bytes)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + return BOTAN_FFI_VISIT(ec_group, [=](const auto& g) -> int { + Botan::EC_AffinePoint pt(g, std::span{bytes, bytes_len}); + return ffi_new_object(ec_point, std::make_unique(std::move(pt))); + }); +} + +int botan_ec_point_view_x_bytes(botan_ec_point_t ec_point, botan_view_ctx ctx, botan_view_bin_fn view) { + return BOTAN_FFI_VISIT(ec_point, [=](const auto& p) -> int { + auto bytes = p.x_bytes(); + return invoke_view_callback(view, ctx, bytes); + }); +} + +int botan_ec_point_view_y_bytes(botan_ec_point_t ec_point, botan_view_ctx ctx, botan_view_bin_fn view) { + return BOTAN_FFI_VISIT(ec_point, [=](const auto& p) -> int { + auto bytes = p.y_bytes(); + return invoke_view_callback(view, ctx, bytes); + }); +} + +int botan_ec_point_view_xy_bytes(botan_ec_point_t ec_point, botan_view_ctx ctx, botan_view_bin_fn view) { + return BOTAN_FFI_VISIT(ec_point, [=](const auto& p) -> int { + auto bytes = p.xy_bytes(); + return invoke_view_callback(view, ctx, bytes); + }); +} + +int botan_ec_point_view_uncompressed(botan_ec_point_t ec_point, botan_view_ctx ctx, botan_view_bin_fn view) { + return BOTAN_FFI_VISIT(ec_point, [=](const auto& p) -> int { + auto bytes = p.serialize_uncompressed(); + return invoke_view_callback(view, ctx, bytes); + }); +} + +int botan_ec_point_view_compressed(botan_ec_point_t ec_point, botan_view_ctx ctx, botan_view_bin_fn view) { + return BOTAN_FFI_VISIT(ec_point, [=](const auto& p) -> int { + auto bytes = p.serialize_compressed(); + return invoke_view_callback(view, ctx, bytes); + }); +} + +int botan_ec_point_is_identity(botan_ec_point_t ec_point) { + return BOTAN_FFI_VISIT(ec_point, [=](const auto& p) -> int { return p.is_identity() ? 1 : 0; }); +} + +int botan_ec_point_equal(botan_ec_point_t x_w, botan_ec_point_t y_w) { + return BOTAN_FFI_VISIT(x_w, [=](const auto& x) -> int { return x == safe_get(y_w) ? 1 : 0; }); +} + +int botan_ec_point_mul(botan_ec_point_t* result, + botan_ec_point_t ec_point, + botan_ec_scalar_t ec_scalar, + botan_rng_t rng) { + if(Botan::any_null_pointers(result)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + return BOTAN_FFI_VISIT(ec_point, [=](auto& pt) -> int { + Botan::EC_AffinePoint res = pt.mul(safe_get(ec_scalar), safe_get(rng)); + return ffi_new_object(result, std::make_unique(std::move(res))); + }); +} + +int botan_ec_point_negate(botan_ec_point_t* result, botan_ec_point_t ec_point) { + if(Botan::any_null_pointers(result)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + return BOTAN_FFI_VISIT(ec_point, [=](auto& pt) -> int { + Botan::EC_AffinePoint res = pt.negate(); + return ffi_new_object(result, std::make_unique(std::move(res))); + }); +} + +int botan_ec_point_add(botan_ec_point_t* result, botan_ec_point_t x_w, botan_ec_point_t y_w) { + if(Botan::any_null_pointers(result)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + return BOTAN_FFI_VISIT(x_w, [=](auto& x) -> int { + Botan::EC_AffinePoint res = x.add(safe_get(y_w)); + return ffi_new_object(result, std::make_unique(std::move(res))); + }); +} +} diff -Nru botan3-3.7.1+dfsg/src/lib/ffi/ffi_ec.h botan3-3.12.0+dfsg/src/lib/ffi/ffi_ec.h --- botan3-3.7.1+dfsg/src/lib/ffi/ffi_ec.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/ffi/ffi_ec.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,21 @@ +/* +* (C) 2025 Jack Lloyd +* (C) 2025,2026 Dominik Schricker +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_FFI_EC_H_ +#define BOTAN_FFI_EC_H_ + +#include +#include + +extern "C" { + +BOTAN_FFI_DECLARE_STRUCT(botan_ec_group_struct, Botan::EC_Group, 0xC5A5DB46); +BOTAN_FFI_DECLARE_STRUCT(botan_ec_scalar_struct, Botan::EC_Scalar, 0x504CC641); +BOTAN_FFI_DECLARE_STRUCT(botan_ec_point_struct, Botan::EC_AffinePoint, 0xE3DAD046); +} + +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/ffi/ffi_fpe.cpp botan3-3.12.0+dfsg/src/lib/ffi/ffi_fpe.cpp --- botan3-3.7.1+dfsg/src/lib/ffi/ffi_fpe.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/ffi/ffi_fpe.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,6 +6,7 @@ #include +#include #include #include #include @@ -38,14 +39,13 @@ return BOTAN_FFI_ERROR_BAD_FLAG; } - const bool compat_mode = (flags & BOTAN_FPE_FLAG_FE1_COMPAT_MODE); + const bool compat_mode = (flags & BOTAN_FPE_FLAG_FE1_COMPAT_MODE) != 0; - std::unique_ptr fpe_obj(new Botan::FPE_FE1(safe_get(n), rounds, compat_mode)); + auto fpe_obj = std::make_unique(safe_get(n), rounds, compat_mode); fpe_obj->set_key(key, key_len); - *fpe = new botan_fpe_struct(std::move(fpe_obj)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(fpe, std::move(fpe_obj)); }); #else BOTAN_UNUSED(fpe, n, key, key_len, rounds, flags); @@ -65,7 +65,7 @@ int botan_fpe_encrypt(botan_fpe_t fpe, botan_mp_t x, const uint8_t tweak[], size_t tweak_len) { #if defined(BOTAN_HAS_FPE_FE1) return ffi_guard_thunk(__func__, [=]() { - Botan::BigInt r = safe_get(fpe).encrypt(safe_get(x), tweak, tweak_len); + const Botan::BigInt r = safe_get(fpe).encrypt(safe_get(x), tweak, tweak_len); safe_get(x) = r; return BOTAN_FFI_SUCCESS; }); @@ -78,7 +78,7 @@ int botan_fpe_decrypt(botan_fpe_t fpe, botan_mp_t x, const uint8_t tweak[], size_t tweak_len) { #if defined(BOTAN_HAS_FPE_FE1) return ffi_guard_thunk(__func__, [=]() { - Botan::BigInt r = safe_get(fpe).decrypt(safe_get(x), tweak, tweak_len); + const Botan::BigInt r = safe_get(fpe).decrypt(safe_get(x), tweak, tweak_len); safe_get(x) = r; return BOTAN_FFI_SUCCESS; }); diff -Nru botan3-3.7.1+dfsg/src/lib/ffi/ffi_hash.cpp botan3-3.12.0+dfsg/src/lib/ffi/ffi_hash.cpp --- botan3-3.7.1+dfsg/src/lib/ffi/ffi_hash.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/ffi/ffi_hash.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -29,7 +29,7 @@ return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; } - *hash = new botan_hash_struct(std::move(h)); + ffi_new_object(hash, std::move(h)); return BOTAN_FFI_SUCCESS; }); } @@ -75,8 +75,12 @@ return BOTAN_FFI_VISIT(hash, [=](auto& h) { h.final(out); }); } +// NOLINTNEXTLINE(misc-misplaced-const) int botan_hash_copy_state(botan_hash_t* dest, const botan_hash_t source) { - return BOTAN_FFI_VISIT(source, [=](const auto& src) { *dest = new botan_hash_struct(src.copy_state()); }); + if(dest == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + return BOTAN_FFI_VISIT(source, [=](const auto& src) { return ffi_new_object(dest, src.copy_state()); }); } int botan_hash_name(botan_hash_t hash, char* name, size_t* name_len) { diff -Nru botan3-3.7.1+dfsg/src/lib/ffi/ffi_hotp.cpp botan3-3.12.0+dfsg/src/lib/ffi/ffi_hotp.cpp --- botan3-3.7.1+dfsg/src/lib/ffi/ffi_hotp.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/ffi/ffi_hotp.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,6 +6,7 @@ #include +#include #include #if defined(BOTAN_HAS_HOTP) @@ -32,9 +33,7 @@ #if defined(BOTAN_HAS_HOTP) return ffi_guard_thunk(__func__, [=]() -> int { auto otp = std::make_unique(key, key_len, hash_algo, digits); - *hotp = new botan_hotp_struct(std::move(otp)); - - return BOTAN_FFI_SUCCESS; + return ffi_new_object(hotp, std::move(otp)); }); #else BOTAN_UNUSED(hotp, key, key_len, hash_algo, digits); diff -Nru botan3-3.7.1+dfsg/src/lib/ffi/ffi_kdf.cpp botan3-3.12.0+dfsg/src/lib/ffi/ffi_kdf.cpp --- botan3-3.7.1+dfsg/src/lib/ffi/ffi_kdf.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/ffi/ffi_kdf.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,6 +6,7 @@ #include +#include #include #include #include @@ -63,6 +64,12 @@ if(algo == nullptr || password == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } + if(out_len > 0 && out == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + if(salt_len > 0 && salt == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } if(password_len == 0) { password_len = std::strlen(password); @@ -97,6 +104,12 @@ if(algo == nullptr || password == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } + if(out_len > 0 && out == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + if(salt_len > 0 && salt == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } if(password_len == 0) { password_len = std::strlen(password); @@ -109,15 +122,15 @@ return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; } - auto pwdhash = pwdhash_fam->tune(out_len, std::chrono::milliseconds(msec)); + auto pwdhash = pwdhash_fam->tune_params(out_len, msec); - if(param1) { + if(param1 != nullptr) { *param1 = pwdhash->iterations(); } - if(param2) { + if(param2 != nullptr) { *param2 = pwdhash->parallelism(); } - if(param3) { + if(param3 != nullptr) { *param3 = pwdhash->memory_param(); } @@ -136,6 +149,13 @@ size_t salt_len, const uint8_t label[], size_t label_len) { + if(kdf_algo == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + if((out_len > 0 && out == nullptr) || (secret_len > 0 && secret == nullptr) || (salt_len > 0 && salt == nullptr) || + (label_len > 0 && label == nullptr)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } return ffi_guard_thunk(__func__, [=]() -> int { auto kdf = Botan::KDF::create_or_throw(kdf_algo); kdf->kdf(out, out_len, secret, secret_len, salt, salt_len, label, label_len); @@ -177,7 +197,8 @@ Botan::RandomNumberGenerator& rng = safe_get(rng_obj); const std::string bcrypt = Botan::generate_bcrypt(pass, rng, static_cast(wf)); - return write_str_output(out, out_len, bcrypt); + // TODO(Botan4) change the type of out and remove this cast + return write_str_output(reinterpret_cast(out), out_len, bcrypt); }); #else BOTAN_UNUSED(out, out_len, pass, rng_obj, wf, flags); @@ -186,6 +207,9 @@ } int botan_bcrypt_is_valid(const char* pass, const char* hash) { + if(any_null_pointers(pass, hash)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } #if defined(BOTAN_HAS_BCRYPT) return ffi_guard_thunk(__func__, [=]() -> int { return Botan::check_bcrypt(pass, hash) ? BOTAN_FFI_SUCCESS : BOTAN_FFI_INVALID_VERIFIER; diff -Nru botan3-3.7.1+dfsg/src/lib/ffi/ffi_keywrap.cpp botan3-3.12.0+dfsg/src/lib/ffi/ffi_keywrap.cpp --- botan3-3.7.1+dfsg/src/lib/ffi/ffi_keywrap.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/ffi/ffi_keywrap.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,6 +7,7 @@ #include +#include #include #if defined(BOTAN_HAS_NIST_KEYWRAP) @@ -26,6 +27,9 @@ size_t kek_len, uint8_t wrapped_key[], size_t* wrapped_key_len) { + if(any_null_pointers(cipher_algo, key, kek)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } #if defined(BOTAN_HAS_NIST_KEYWRAP) return ffi_guard_thunk(__func__, [=]() -> int { if(padded != 0 && padded != 1) { @@ -58,6 +62,9 @@ size_t kek_len, uint8_t key[], size_t* key_len) { + if(any_null_pointers(cipher_algo, wrapped_key, kek)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } #if defined(BOTAN_HAS_NIST_KEYWRAP) return ffi_guard_thunk(__func__, [=]() -> int { if(padded != 0 && padded != 1) { @@ -89,7 +96,7 @@ size_t kek_len, uint8_t wrapped_key[], size_t* wrapped_key_len) { - std::string cipher_name = "AES-" + std::to_string(8 * kek_len); + const std::string cipher_name = "AES-" + std::to_string(8 * kek_len); return botan_nist_kw_enc(cipher_name.c_str(), 0, key, key_len, kek, kek_len, wrapped_key, wrapped_key_len); } @@ -100,7 +107,7 @@ size_t kek_len, uint8_t key[], size_t* key_len) { - std::string cipher_name = "AES-" + std::to_string(8 * kek_len); + const std::string cipher_name = "AES-" + std::to_string(8 * kek_len); return botan_nist_kw_dec(cipher_name.c_str(), 0, wrapped_key, wrapped_key_len, kek, kek_len, key, key_len); } diff -Nru botan3-3.7.1+dfsg/src/lib/ffi/ffi_mac.cpp botan3-3.12.0+dfsg/src/lib/ffi/ffi_mac.cpp --- botan3-3.7.1+dfsg/src/lib/ffi/ffi_mac.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/ffi/ffi_mac.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -17,18 +17,19 @@ int botan_mac_init(botan_mac_t* mac, const char* mac_name, uint32_t flags) { return ffi_guard_thunk(__func__, [=]() -> int { - if(!mac || !mac_name || flags != 0) { + if(any_null_pointers(mac, mac_name)) { return BOTAN_FFI_ERROR_NULL_POINTER; } - std::unique_ptr m = Botan::MessageAuthenticationCode::create(mac_name); + if(flags != 0) { + return BOTAN_FFI_ERROR_BAD_FLAG; + } - if(m == nullptr) { + if(auto m = Botan::MessageAuthenticationCode::create(mac_name)) { + return ffi_new_object(mac, std::move(m)); + } else { return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; } - - *mac = new botan_mac_struct(std::move(m)); - return BOTAN_FFI_SUCCESS; }); } @@ -37,6 +38,9 @@ } int botan_mac_set_key(botan_mac_t mac, const uint8_t* key, size_t key_len) { + if(key_len > 0 && key == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } return BOTAN_FFI_VISIT(mac, [=](auto& m) { m.set_key(key, key_len); }); } @@ -45,6 +49,9 @@ } int botan_mac_output_length(botan_mac_t mac, size_t* out) { + if(out == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } return BOTAN_FFI_VISIT(mac, [=](const auto& m) { *out = m.output_length(); }); } @@ -53,10 +60,19 @@ } int botan_mac_update(botan_mac_t mac, const uint8_t* buf, size_t len) { + if(len == 0) { + return BOTAN_FFI_SUCCESS; + } + if(buf == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } return BOTAN_FFI_VISIT(mac, [=](auto& m) { m.update(buf, len); }); } int botan_mac_final(botan_mac_t mac, uint8_t out[]) { + if(out == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } return BOTAN_FFI_VISIT(mac, [=](auto& m) { m.final(out); }); } diff -Nru botan3-3.7.1+dfsg/src/lib/ffi/ffi_mp.cpp botan3-3.12.0+dfsg/src/lib/ffi/ffi_mp.cpp --- botan3-3.7.1+dfsg/src/lib/ffi/ffi_mp.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/ffi/ffi_mp.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,12 +7,14 @@ #include +#include #include -#include +#include #include #include #include #include +#include #include extern "C" { @@ -26,8 +28,7 @@ } auto mp = std::make_unique(); - *mp_out = new botan_mp_struct(std::move(mp)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(mp_out, std::move(mp)); }); } @@ -45,33 +46,27 @@ int botan_mp_set_from_radix_str(botan_mp_t mp, const char* str, size_t radix) { return BOTAN_FFI_VISIT(mp, [=](auto& bn) { - Botan::BigInt::Base base; - if(radix == 10) { - base = Botan::BigInt::Decimal; - } else if(radix == 16) { - base = Botan::BigInt::Hexadecimal; - } else { + if(radix != 10 && radix != 16) { return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; } - const uint8_t* bytes = Botan::cast_char_ptr_to_uint8(str); - const size_t len = strlen(str); - - bn = Botan::BigInt(bytes, len, base); + bn = Botan::BigInt::from_radix_digits(std::string_view(str), radix); return BOTAN_FFI_SUCCESS; }); } +// NOLINTBEGIN(misc-misplaced-const) + int botan_mp_set_from_mp(botan_mp_t dest, const botan_mp_t source) { return BOTAN_FFI_VISIT(dest, [=](auto& bn) { bn = safe_get(source); }); } int botan_mp_is_negative(const botan_mp_t mp) { - return BOTAN_FFI_VISIT(mp, [](const auto& bn) { return bn.is_negative() ? 1 : 0; }); + return BOTAN_FFI_VISIT(mp, [](const auto& bn) { return bn.signum() < 0 ? 1 : 0; }); } int botan_mp_is_positive(const botan_mp_t mp) { - return BOTAN_FFI_VISIT(mp, [](const auto& bn) { return bn.is_positive() ? 1 : 0; }); + return BOTAN_FFI_VISIT(mp, [](const auto& bn) { return bn.signum() >= 0 ? 1 : 0; }); } int botan_mp_flip_sign(botan_mp_t mp) { @@ -79,21 +74,38 @@ } int botan_mp_from_bin(botan_mp_t mp, const uint8_t bin[], size_t bin_len) { + if(bin_len > 0 && bin == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } return BOTAN_FFI_VISIT(mp, [=](auto& bn) { bn._assign_from_bytes({bin, bin_len}); }); } int botan_mp_to_hex(const botan_mp_t mp, char* out) { + if(out == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } return BOTAN_FFI_VISIT(mp, [=](const auto& bn) { const std::string hex = bn.to_hex_string(); + + // Check that we are about to write no more than the documented upper bound + const size_t upper_bound = 2 * bn.bytes() + 5; + BOTAN_ASSERT_NOMSG(hex.size() + 1 <= upper_bound); std::memcpy(out, hex.c_str(), 1 + hex.size()); }); } -int botan_mp_to_str(const botan_mp_t mp, uint8_t digit_base, char* out, size_t* out_len) { +int botan_mp_view_hex(const botan_mp_t mp, botan_view_ctx ctx, botan_view_str_fn view) { + return BOTAN_FFI_VISIT(mp, [=](const auto& bn) -> int { + const std::string hex = bn.to_hex_string(); + return invoke_view_callback(view, ctx, hex); + }); +} + +int botan_mp_to_str(const botan_mp_t mp, uint8_t radix, char* out, size_t* out_len) { return BOTAN_FFI_VISIT(mp, [=](const auto& bn) -> int { - if(digit_base == 0 || digit_base == 10) { + if(radix == 0 || radix == 10) { return write_str_output(out, out_len, bn.to_dec_string()); - } else if(digit_base == 16) { + } else if(radix == 16) { return write_str_output(out, out_len, bn.to_hex_string()); } else { return BOTAN_FFI_ERROR_BAD_PARAMETER; @@ -101,10 +113,32 @@ }); } +int botan_mp_view_str(const botan_mp_t mp, uint8_t radix, botan_view_ctx ctx, botan_view_str_fn view) { + return BOTAN_FFI_VISIT(mp, [=](const auto& bn) -> int { + if(radix == 10) { + return invoke_view_callback(view, ctx, bn.to_dec_string()); + } else if(radix == 16) { + return invoke_view_callback(view, ctx, bn.to_hex_string()); + } else { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + }); +} + int botan_mp_to_bin(const botan_mp_t mp, uint8_t vec[]) { + if(vec == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } return BOTAN_FFI_VISIT(mp, [=](const auto& bn) { bn.serialize_to(std::span{vec, bn.bytes()}); }); } +int botan_mp_view_bin(const botan_mp_t mp, botan_view_ctx ctx, botan_view_bin_fn view) { + return BOTAN_FFI_VISIT(mp, [=](const auto& bn) { + const auto bytes = bn.serialize(); + return invoke_view_callback(view, ctx, bytes); + }); +} + int botan_mp_to_uint32(const botan_mp_t mp, uint32_t* val) { if(val == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; @@ -191,6 +225,9 @@ } int botan_mp_cmp(int* result, const botan_mp_t x_w, const botan_mp_t y_w) { + if(result == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } return BOTAN_FFI_VISIT(x_w, [=](auto& x) { *result = x.cmp(safe_get(y_w)); }); } @@ -220,7 +257,7 @@ int botan_mp_mod_mul(botan_mp_t out, const botan_mp_t x, const botan_mp_t y, const botan_mp_t modulus) { return BOTAN_FFI_VISIT(out, [=](auto& o) { - auto reducer = Botan::Modular_Reducer::for_secret_modulus(safe_get(modulus)); + auto reducer = Botan::Barrett_Reduction::for_secret_modulus(safe_get(modulus)); o = reducer.multiply(safe_get(x), safe_get(y)); }); } @@ -255,10 +292,18 @@ } int botan_mp_num_bits(const botan_mp_t mp, size_t* bits) { + if(bits == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } return BOTAN_FFI_VISIT(mp, [=](const auto& n) { *bits = n.bits(); }); } int botan_mp_num_bytes(const botan_mp_t mp, size_t* bytes) { + if(bytes == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } return BOTAN_FFI_VISIT(mp, [=](const auto& n) { *bytes = n.bytes(); }); } + +// NOLINTEND(misc-misplaced-const) } diff -Nru botan3-3.7.1+dfsg/src/lib/ffi/ffi_oid.cpp botan3-3.12.0+dfsg/src/lib/ffi/ffi_oid.cpp --- botan3-3.7.1+dfsg/src/lib/ffi/ffi_oid.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/ffi/ffi_oid.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,80 @@ +/* +* (C) 2025 Jack Lloyd +* (C) 2025 Dominik Schricker +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include + +extern "C" { + +using namespace Botan_FFI; + +int botan_oid_destroy(botan_asn1_oid_t oid) { + return BOTAN_FFI_CHECKED_DELETE(oid); +} + +int botan_oid_from_string(botan_asn1_oid_t* oid_obj, const char* oid_str) { + return ffi_guard_thunk(__func__, [=]() -> int { + if(oid_obj == nullptr || oid_str == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + Botan::OID oid; + // This returns a Lookup_Error if an unknown name is passed, + // which would get turned into NOT_IMPLEMENTED + try { + oid = Botan::OID::from_string(oid_str); + } catch(Botan::Lookup_Error&) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + auto oid_ptr = std::make_unique(std::move(oid)); + return ffi_new_object(oid_obj, std::move(oid_ptr)); + }); +} + +int botan_oid_register(botan_asn1_oid_t oid, const char* name) { + return BOTAN_FFI_VISIT(oid, [=](const auto& o) -> int { + if(name == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + Botan::OID::register_oid(o, name); + return BOTAN_FFI_SUCCESS; + }); +} + +int botan_oid_view_string(botan_asn1_oid_t oid, botan_view_ctx ctx, botan_view_str_fn view) { + return BOTAN_FFI_VISIT(oid, [=](const auto& o) -> int { return invoke_view_callback(view, ctx, o.to_string()); }); +} + +int botan_oid_view_name(botan_asn1_oid_t oid, botan_view_ctx ctx, botan_view_str_fn view) { + return BOTAN_FFI_VISIT( + oid, [=](const auto& o) -> int { return invoke_view_callback(view, ctx, o.to_formatted_string()); }); +} + +int botan_oid_equal(botan_asn1_oid_t a_w, botan_asn1_oid_t b_w) { + return BOTAN_FFI_VISIT(a_w, [=](const auto& a) -> int { return a == safe_get(b_w); }); +} + +int botan_oid_cmp(int* result, botan_asn1_oid_t a_w, botan_asn1_oid_t b_w) { + return BOTAN_FFI_VISIT(a_w, [=](auto& a) { + if(result == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + const Botan::OID b = safe_get(b_w); + // we don't have .cmp for OID + if(a == b) { + *result = 0; + } else if(a < b) { + *result = -1; + } else { + *result = 1; + } + return BOTAN_FFI_SUCCESS; + }); +} +} diff -Nru botan3-3.7.1+dfsg/src/lib/ffi/ffi_oid.h botan3-3.12.0+dfsg/src/lib/ffi/ffi_oid.h --- botan3-3.7.1+dfsg/src/lib/ffi/ffi_oid.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/ffi/ffi_oid.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,19 @@ +/* +* (C) 2025 Jack Lloyd +* (C) 2025 Dominik Schricker +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_FFI_OID_H_ +#define BOTAN_FFI_OID_H_ + +#include +#include + +extern "C" { + +BOTAN_FFI_DECLARE_STRUCT(botan_asn1_oid_struct, Botan::OID, 0x9217DA20); +} + +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/ffi/ffi_pk_op.cpp botan3-3.12.0+dfsg/src/lib/ffi/ffi_pk_op.cpp --- botan3-3.7.1+dfsg/src/lib/ffi/ffi_pk_op.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/ffi/ffi_pk_op.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -38,8 +38,7 @@ *op = nullptr; auto pk = std::make_unique(safe_get(key_obj), Botan::system_rng(), padding); - *op = new botan_pk_op_encrypt_struct(std::move(pk)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(op, std::move(pk)); }); } @@ -84,8 +83,7 @@ *op = nullptr; auto pk = std::make_unique(safe_get(key_obj), Botan::system_rng(), padding); - *op = new botan_pk_op_decrypt_struct(std::move(pk)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(op, std::move(pk)); }); } @@ -121,12 +119,11 @@ return ffi_guard_thunk(__func__, [=]() -> int { *op = nullptr; - auto format = (flags & BOTAN_PUBKEY_DER_FORMAT_SIGNATURE) ? Botan::Signature_Format::DerSequence - : Botan::Signature_Format::Standard; + const bool use_der = (flags & BOTAN_PUBKEY_DER_FORMAT_SIGNATURE) != 0; + auto format = use_der ? Botan::Signature_Format::DerSequence : Botan::Signature_Format::Standard; auto pk = std::make_unique(safe_get(key_obj), Botan::system_rng(), hash, format); - *op = new botan_pk_op_sign_struct(std::move(pk)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(op, std::move(pk)); }); } @@ -161,11 +158,10 @@ return ffi_guard_thunk(__func__, [=]() -> int { *op = nullptr; - auto format = (flags & BOTAN_PUBKEY_DER_FORMAT_SIGNATURE) ? Botan::Signature_Format::DerSequence - : Botan::Signature_Format::Standard; + const bool use_der = (flags & BOTAN_PUBKEY_DER_FORMAT_SIGNATURE) != 0; + auto format = use_der ? Botan::Signature_Format::DerSequence : Botan::Signature_Format::Standard; auto pk = std::make_unique(safe_get(key_obj), hash, format); - *op = new botan_pk_op_verify_struct(std::move(pk)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(op, std::move(pk)); }); } @@ -201,8 +197,7 @@ return ffi_guard_thunk(__func__, [=]() -> int { *op = nullptr; auto pk = std::make_unique(safe_get(key_obj), Botan::system_rng(), kdf); - *op = new botan_pk_op_ka_struct(std::move(pk)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(op, std::move(pk)); }); } @@ -254,8 +249,7 @@ return ffi_guard_thunk(__func__, [=]() -> int { auto pk = std::make_unique(safe_get(key_obj), padding); - *op = new botan_pk_op_kem_encrypt_struct(std::move(pk)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(op, std::move(pk)); }); } @@ -300,7 +294,7 @@ return BOTAN_FFI_VISIT(op, [=](auto& kem) { const auto result = kem.encrypt(safe_get(rng), desired_shared_key_len, {salt, salt_len}); - int rc = write_vec_output(encapsulated_key_out, encapsulated_key_len, result.encapsulated_shared_key()); + const int rc = write_vec_output(encapsulated_key_out, encapsulated_key_len, result.encapsulated_shared_key()); if(rc != 0) { return rc; @@ -317,8 +311,7 @@ return ffi_guard_thunk(__func__, [=]() -> int { auto pk = std::make_unique(safe_get(key_obj), Botan::system_rng(), padding); - *op = new botan_pk_op_kem_decrypt_struct(std::move(pk)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(op, std::move(pk)); }); } diff -Nru botan3-3.7.1+dfsg/src/lib/ffi/ffi_pkey.cpp botan3-3.12.0+dfsg/src/lib/ffi/ffi_pkey.cpp --- botan3-3.7.1+dfsg/src/lib/ffi/ffi_pkey.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/ffi/ffi_pkey.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,12 +6,15 @@ #include +#include #include #include #include #include #include #include +#include +#include #include #include #include @@ -28,6 +31,11 @@ const char* algo_name, const char* algo_params, botan_rng_t rng_obj) { + // TODO(Botan4) remove this implicit algorithm choice and reject nullptr algo_name + if(algo_name == nullptr) { + return botan_privkey_create(key_obj, "RSA", algo_params, rng_obj); + } + return ffi_guard_thunk(__func__, [=]() -> int { if(key_obj == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; @@ -38,13 +46,38 @@ return BOTAN_FFI_ERROR_NULL_POINTER; } + const std::string params(algo_params != nullptr ? algo_params : ""); + Botan::RandomNumberGenerator& rng = safe_get(rng_obj); - std::unique_ptr key( - Botan::create_private_key(algo_name ? algo_name : "RSA", rng, algo_params ? algo_params : "")); - if(key) { - *key_obj = new botan_privkey_struct(std::move(key)); - return BOTAN_FFI_SUCCESS; + if(auto key = Botan::create_private_key(algo_name, rng, params)) { + return ffi_new_object(key_obj, std::move(key)); + } else { + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; + } + }); +} + +int botan_ec_privkey_create(botan_privkey_t* key_obj, + const char* algo_name, + botan_ec_group_t ec_group_obj, + botan_rng_t rng_obj) { + // TODO(Botan4) remove this implicit algorithm choice and reject nullptr algo_name + if(algo_name == nullptr) { + return botan_ec_privkey_create(key_obj, "ECDSA", ec_group_obj, rng_obj); + } + + return ffi_guard_thunk(__func__, [=]() -> int { + if(key_obj == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + *key_obj = nullptr; + + const Botan::EC_Group ec_group = safe_get(ec_group_obj); + Botan::RandomNumberGenerator& rng = safe_get(rng_obj); + + if(auto key = Botan::create_ec_private_key(algo_name, ec_group, rng)) { + return ffi_new_object(key_obj, std::move(key)); } else { return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; } @@ -55,8 +88,16 @@ botan_privkey_t* key, botan_rng_t rng_obj, const uint8_t bits[], size_t len, const char* password) { BOTAN_UNUSED(rng_obj); + if(key == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + *key = nullptr; + if(bits == nullptr && len > 0) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + return ffi_guard_thunk(__func__, [=]() -> int { Botan::DataSource_Memory src(bits, len); @@ -69,7 +110,7 @@ } if(pkcs8) { - *key = new botan_privkey_struct(std::move(pkcs8)); + ffi_new_object(key, std::move(pkcs8)); return BOTAN_FFI_SUCCESS; } return BOTAN_FFI_ERROR_UNKNOWN_ERROR; @@ -81,8 +122,16 @@ } int botan_pubkey_load(botan_pubkey_t* key, const uint8_t bits[], size_t bits_len) { + if(key == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + *key = nullptr; + if(bits == nullptr && bits_len > 0) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + return ffi_guard_thunk(__func__, [=]() -> int { Botan::DataSource_Memory src(bits, bits_len); std::unique_ptr pubkey(Botan::X509::load_key(src)); @@ -91,7 +140,7 @@ return BOTAN_FFI_ERROR_UNKNOWN_ERROR; } - *key = new botan_pubkey_struct(std::move(pubkey)); + ffi_new_object(key, std::move(pubkey)); return BOTAN_FFI_SUCCESS; }); } @@ -101,10 +150,12 @@ } int botan_privkey_export_pubkey(botan_pubkey_t* pubout, botan_privkey_t key_obj) { + if(pubout == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } return ffi_guard_thunk(__func__, [=]() -> int { auto public_key = safe_get(key_obj).public_key(); - *pubout = new botan_pubkey_struct(std::move(public_key)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(pubout, std::move(public_key)); }); } @@ -117,7 +168,7 @@ } int botan_pubkey_check_key(botan_pubkey_t key, botan_rng_t rng, uint32_t flags) { - const bool strong = (flags & BOTAN_CHECK_KEY_EXPENSIVE_TESTS); + const bool strong = (flags & BOTAN_CHECK_KEY_EXPENSIVE_TESTS) != 0; return BOTAN_FFI_VISIT(key, [=](const auto& k) { return (k.check_key(safe_get(rng), strong) == true) ? 0 : BOTAN_FFI_ERROR_INVALID_INPUT; @@ -125,7 +176,7 @@ } int botan_privkey_check_key(botan_privkey_t key, botan_rng_t rng, uint32_t flags) { - const bool strong = (flags & BOTAN_CHECK_KEY_EXPENSIVE_TESTS); + const bool strong = (flags & BOTAN_CHECK_KEY_EXPENSIVE_TESTS) != 0; return BOTAN_FFI_VISIT(key, [=](const auto& k) { return (k.check_key(safe_get(rng), strong) == true) ? 0 : BOTAN_FFI_ERROR_INVALID_INPUT; }); @@ -145,7 +196,7 @@ int botan_pubkey_view_der(botan_pubkey_t key, botan_view_ctx ctx, botan_view_bin_fn view) { return BOTAN_FFI_VISIT( - key, [=](const auto& k) -> int { return invoke_view_callback(view, ctx, Botan::X509::BER_encode(k)); }); + key, [=](const auto& k) -> int { return invoke_view_callback(view, ctx, k.subject_public_key()); }); } int botan_pubkey_view_pem(botan_pubkey_t key, botan_view_ctx ctx, botan_view_str_fn view) { @@ -171,8 +222,8 @@ } int botan_privkey_view_der(botan_privkey_t key, botan_view_ctx ctx, botan_view_bin_fn view) { - return BOTAN_FFI_VISIT( - key, [=](const auto& k) -> int { return invoke_view_callback(view, ctx, Botan::PKCS8::BER_encode(k)); }); + return BOTAN_FFI_VISIT(key, + [=](const auto& k) -> int { return invoke_view_callback(view, ctx, k.private_key_info()); }); } int botan_privkey_view_pem(botan_privkey_t key, botan_view_ctx ctx, botan_view_str_fn view) { @@ -205,7 +256,7 @@ const char* cipher, const char* pbkdf_hash, uint32_t flags) { - if(pbkdf_iters_out) { + if(pbkdf_iters_out != nullptr) { *pbkdf_iters_out = 0; } @@ -342,11 +393,73 @@ }); } +int botan_pubkey_oid(botan_asn1_oid_t* oid, botan_pubkey_t key) { + return BOTAN_FFI_VISIT(key, [=](const auto& k) { + if(oid == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + auto oid_ptr = std::make_unique(k.object_identifier()); + ffi_new_object(oid, std::move(oid_ptr)); + + return BOTAN_FFI_SUCCESS; + }); +} + +int botan_privkey_oid(botan_asn1_oid_t* oid, botan_privkey_t key) { + return BOTAN_FFI_VISIT(key, [=](const auto& k) { + if(oid == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + auto oid_ptr = std::make_unique(k.object_identifier()); + ffi_new_object(oid, std::move(oid_ptr)); + + return BOTAN_FFI_SUCCESS; + }); +} + +int botan_privkey_stateful_operation(botan_privkey_t key, int* out) { + return BOTAN_FFI_VISIT(key, [=](const auto& k) { + if(out == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + if(k.stateful_operation()) { + *out = 1; + } else { + *out = 0; + } + return BOTAN_FFI_SUCCESS; + }); +} + +int botan_privkey_remaining_operations(botan_privkey_t key, uint64_t* out) { + return BOTAN_FFI_VISIT(key, [=](const auto& k) { + if(out == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + if(auto remaining = k.remaining_operations()) { + *out = remaining.value(); + return BOTAN_FFI_SUCCESS; + } else { + return BOTAN_FFI_ERROR_NO_VALUE; + } + }); +} + int botan_pubkey_estimated_strength(botan_pubkey_t key, size_t* estimate) { + if(estimate == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } return BOTAN_FFI_VISIT(key, [=](const auto& k) { *estimate = k.estimated_strength(); }); } int botan_pubkey_fingerprint(botan_pubkey_t key, const char* hash_fn, uint8_t out[], size_t* out_len) { + if(hash_fn == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } return BOTAN_FFI_VISIT(key, [=](const auto& k) { auto h = Botan::HashFunction::create_or_throw(hash_fn); return write_vec_output(out, out_len, h->process(k.public_key_bits())); @@ -354,6 +467,9 @@ } int botan_pkcs_hash_id(const char* hash_name, uint8_t pkcs_id[], size_t* pkcs_id_len) { + if(hash_name == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } #if defined(BOTAN_HAS_HASH_ID) return ffi_guard_thunk(__func__, [=]() -> int { const std::vector hash_id = Botan::pkcs_hash_id(hash_name); diff -Nru botan3-3.7.1+dfsg/src/lib/ffi/ffi_pkey_algs.cpp botan3-3.12.0+dfsg/src/lib/ffi/ffi_pkey_algs.cpp --- botan3-3.7.1+dfsg/src/lib/ffi/ffi_pkey_algs.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/ffi/ffi_pkey_algs.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,8 +8,12 @@ #include +#include +#include #include +#include #include +#include #include #include #include @@ -63,10 +67,6 @@ #include #endif -#if defined(BOTAN_HAS_MCELIECE) - #include -#endif - #if defined(BOTAN_HAS_DIFFIE_HELLMAN) #include #endif @@ -106,6 +106,9 @@ if(curve_name == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } + if(!Botan::EC_Group::supports_named_group(curve_name)) { + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; + } Botan::Null_RNG null_rng; const auto grp = Botan::EC_Group::from_name(curve_name); @@ -122,6 +125,10 @@ return BOTAN_FFI_ERROR_NULL_POINTER; } + if(!Botan::EC_Group::supports_named_group(curve_name)) { + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; + } + const auto group = Botan::EC_Group::from_name(curve_name); if(auto pt = Botan::EC_AffinePoint::from_bigint_xy(group, public_x, public_y)) { @@ -132,6 +139,24 @@ } } +template +int pubkey_load_ec_sec1(std::unique_ptr& key, + std::span sec1, + std::string_view curve_name) { + if(!Botan::EC_Group::supports_named_group(curve_name)) { + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; + } + + const auto group = Botan::EC_Group::from_name(curve_name); + + if(auto pt = Botan::EC_AffinePoint::deserialize(group, sec1)) { + key.reset(new ECPublicKey_t(group, pt.value())); + return BOTAN_FFI_SUCCESS; + } else { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } +} + #endif Botan::BigInt pubkey_get_field(const Botan::Public_Key& key, std::string_view field) { @@ -205,19 +230,21 @@ return BOTAN_FFI_ERROR_BAD_PARAMETER; } - std::string n_str = std::to_string(n_bits); + const std::string n_str = std::to_string(n_bits); return botan_privkey_create(key_obj, "RSA", n_str.c_str(), rng_obj); } int botan_privkey_load_rsa(botan_privkey_t* key, botan_mp_t rsa_p, botan_mp_t rsa_q, botan_mp_t rsa_e) { #if defined(BOTAN_HAS_RSA) + if(key == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } *key = nullptr; return ffi_guard_thunk(__func__, [=]() -> int { auto rsa = std::make_unique(safe_get(rsa_p), safe_get(rsa_q), safe_get(rsa_e)); - *key = new botan_privkey_struct(std::move(rsa)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(key, std::move(rsa)); }); #else BOTAN_UNUSED(key, rsa_p, rsa_q, rsa_e); @@ -227,14 +254,15 @@ int botan_privkey_load_rsa_pkcs1(botan_privkey_t* key, const uint8_t bits[], size_t len) { #if defined(BOTAN_HAS_RSA) + if(Botan::any_null_pointers(key, bits)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } *key = nullptr; - Botan::secure_vector src(bits, bits + len); return ffi_guard_thunk(__func__, [=]() -> int { - Botan::AlgorithmIdentifier alg_id("RSA", Botan::AlgorithmIdentifier::USE_NULL_PARAM); - auto rsa = std::make_unique(alg_id, src); - *key = new botan_privkey_struct(std::move(rsa)); - return BOTAN_FFI_SUCCESS; + const Botan::AlgorithmIdentifier alg_id("RSA", Botan::AlgorithmIdentifier::USE_NULL_PARAM); + auto rsa = std::make_unique(alg_id, std::span{bits, len}); + return ffi_new_object(key, std::move(rsa)); }); #else BOTAN_UNUSED(key, bits, len); @@ -244,11 +272,13 @@ int botan_pubkey_load_rsa(botan_pubkey_t* key, botan_mp_t n, botan_mp_t e) { #if defined(BOTAN_HAS_RSA) + if(key == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } *key = nullptr; return ffi_guard_thunk(__func__, [=]() -> int { auto rsa = std::make_unique(safe_get(n), safe_get(e)); - *key = new botan_pubkey_struct(std::move(rsa)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(key, std::move(rsa)); }); #else BOTAN_UNUSED(key, n, e); @@ -256,6 +286,24 @@ #endif } +int botan_pubkey_load_rsa_pkcs1(botan_pubkey_t* key, const uint8_t bits[], size_t len) { +#if defined(BOTAN_HAS_RSA) + if(Botan::any_null_pointers(key, bits)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + *key = nullptr; + + return ffi_guard_thunk(__func__, [=]() -> int { + const Botan::AlgorithmIdentifier alg_id("RSA", Botan::AlgorithmIdentifier::USE_NULL_PARAM); + auto rsa = std::make_unique(alg_id, std::span{bits, len}); + return ffi_new_object(key, std::move(rsa)); + }); +#else + BOTAN_UNUSED(key, bits, len); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + int botan_privkey_rsa_get_p(botan_mp_t p, botan_privkey_t key) { return botan_privkey_get_field(p, key, "p"); } @@ -291,7 +339,10 @@ if(flags == BOTAN_PRIVKEY_EXPORT_FLAG_DER) { return write_vec_output(out, out_len, rsa->private_key_bits()); } else if(flags == BOTAN_PRIVKEY_EXPORT_FLAG_PEM) { - return write_str_output(out, out_len, Botan::PEM_Code::encode(rsa->private_key_bits(), "RSA PRIVATE KEY")); + // TODO define new generic functions for this + return write_str_output(reinterpret_cast(out), + out_len, + Botan::PEM_Code::encode(rsa->private_key_bits(), "RSA PRIVATE KEY")); } else { return BOTAN_FFI_ERROR_BAD_FLAG; } @@ -309,20 +360,19 @@ int botan_privkey_create_dsa(botan_privkey_t* key, botan_rng_t rng_obj, size_t pbits, size_t qbits) { #if defined(BOTAN_HAS_DSA) - if((rng_obj == nullptr) || (key == nullptr)) { + if(Botan::any_null_pointers(rng_obj, key)) { return BOTAN_FFI_ERROR_NULL_POINTER; } - if((pbits % 64) || (qbits % 8) || (pbits < 1024) || (pbits > 3072) || (qbits < 160) || (qbits > 256)) { + if((pbits % 64 != 0) || (qbits % 8 != 0) || (pbits < 1024) || (pbits > 3072) || (qbits < 160) || (qbits > 256)) { return BOTAN_FFI_ERROR_BAD_PARAMETER; } return ffi_guard_thunk(__func__, [=]() -> int { Botan::RandomNumberGenerator& rng = safe_get(rng_obj); - Botan::DL_Group group(rng, Botan::DL_Group::Prime_Subgroup, pbits, qbits); + const Botan::DL_Group group(rng, Botan::DL_Group::Prime_Subgroup, pbits, qbits); auto dsa = std::make_unique(rng, group); - *key = new botan_privkey_struct(std::move(dsa)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(key, std::move(dsa)); }); #else BOTAN_UNUSED(key, rng_obj, pbits, qbits); @@ -332,13 +382,15 @@ int botan_privkey_load_dsa(botan_privkey_t* key, botan_mp_t p, botan_mp_t q, botan_mp_t g, botan_mp_t x) { #if defined(BOTAN_HAS_DSA) + if(key == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } *key = nullptr; return ffi_guard_thunk(__func__, [=]() -> int { - Botan::DL_Group group(safe_get(p), safe_get(q), safe_get(g)); + const Botan::DL_Group group(safe_get(p), safe_get(q), safe_get(g)); auto dsa = std::make_unique(group, safe_get(x)); - *key = new botan_privkey_struct(std::move(dsa)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(key, std::move(dsa)); }); #else BOTAN_UNUSED(key, p, q, g, x); @@ -348,13 +400,15 @@ int botan_pubkey_load_dsa(botan_pubkey_t* key, botan_mp_t p, botan_mp_t q, botan_mp_t g, botan_mp_t y) { #if defined(BOTAN_HAS_DSA) + if(key == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } *key = nullptr; return ffi_guard_thunk(__func__, [=]() -> int { - Botan::DL_Group group(safe_get(p), safe_get(q), safe_get(g)); + const Botan::DL_Group group(safe_get(p), safe_get(q), safe_get(g)); auto dsa = std::make_unique(group, safe_get(y)); - *key = new botan_pubkey_struct(std::move(dsa)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(key, std::move(dsa)); }); #else BOTAN_UNUSED(key, p, q, g, y); @@ -406,17 +460,24 @@ #endif } +// NOLINTBEGIN(misc-misplaced-const) + int botan_pubkey_load_ecdsa(botan_pubkey_t* key, const botan_mp_t public_x, const botan_mp_t public_y, const char* curve_name) { #if defined(BOTAN_HAS_ECDSA) + if(Botan::any_null_pointers(key, curve_name)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + *key = nullptr; + return ffi_guard_thunk(__func__, [=]() -> int { std::unique_ptr p_key; - int rc = pubkey_load_ec(p_key, safe_get(public_x), safe_get(public_y), curve_name); + const int rc = pubkey_load_ec(p_key, safe_get(public_x), safe_get(public_y), curve_name); if(rc == BOTAN_FFI_SUCCESS) { - *key = new botan_pubkey_struct(std::move(p_key)); + ffi_new_object(key, std::move(p_key)); } return rc; @@ -427,13 +488,41 @@ #endif } +int botan_pubkey_load_ecdsa_sec1(botan_pubkey_t* key, const uint8_t sec1[], size_t sec1_len, const char* curve_name) { +#if defined(BOTAN_HAS_ECDSA) + if(Botan::any_null_pointers(key, sec1, curve_name)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + *key = nullptr; + + return ffi_guard_thunk(__func__, [=]() -> int { + std::unique_ptr p_key; + + const int rc = pubkey_load_ec_sec1(p_key, {sec1, sec1_len}, curve_name); + if(rc == BOTAN_FFI_SUCCESS) { + ffi_new_object(key, std::move(p_key)); + } + + return rc; + }); +#else + BOTAN_UNUSED(key, sec1, sec1_len, curve_name); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + int botan_privkey_load_ecdsa(botan_privkey_t* key, const botan_mp_t scalar, const char* curve_name) { #if defined(BOTAN_HAS_ECDSA) + if(Botan::any_null_pointers(key, curve_name)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + *key = nullptr; + return ffi_guard_thunk(__func__, [=]() -> int { std::unique_ptr p_key; - int rc = privkey_load_ec(p_key, safe_get(scalar), curve_name); + const int rc = privkey_load_ec(p_key, safe_get(scalar), curve_name); if(rc == BOTAN_FFI_SUCCESS) { - *key = new botan_privkey_struct(std::move(p_key)); + ffi_new_object(key, std::move(p_key)); } return rc; }); @@ -446,24 +535,23 @@ /* ElGamal specific operations */ int botan_privkey_create_elgamal(botan_privkey_t* key, botan_rng_t rng_obj, size_t pbits, size_t qbits) { #if defined(BOTAN_HAS_ELGAMAL) - - if((rng_obj == nullptr) || (key == nullptr)) { + if(Botan::any_null_pointers(key, rng_obj)) { return BOTAN_FFI_ERROR_NULL_POINTER; } + *key = nullptr; - if((pbits < 1024) || (qbits < 160)) { + if(pbits < 1024 || qbits < 160) { return BOTAN_FFI_ERROR_BAD_PARAMETER; } - Botan::DL_Group::PrimeType prime_type = + const Botan::DL_Group::PrimeType prime_type = ((pbits - 1) == qbits) ? Botan::DL_Group::Strong : Botan::DL_Group::Prime_Subgroup; return ffi_guard_thunk(__func__, [=]() -> int { Botan::RandomNumberGenerator& rng = safe_get(rng_obj); - Botan::DL_Group group(rng, prime_type, pbits, qbits); + const Botan::DL_Group group(rng, prime_type, pbits, qbits); auto elg = std::make_unique(rng, group); - *key = new botan_privkey_struct(std::move(elg)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(key, std::move(elg)); }); #else BOTAN_UNUSED(key, rng_obj, pbits, qbits); @@ -473,12 +561,14 @@ int botan_pubkey_load_elgamal(botan_pubkey_t* key, botan_mp_t p, botan_mp_t g, botan_mp_t y) { #if defined(BOTAN_HAS_ELGAMAL) + if(key == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } *key = nullptr; return ffi_guard_thunk(__func__, [=]() -> int { - Botan::DL_Group group(safe_get(p), safe_get(g)); + const Botan::DL_Group group(safe_get(p), safe_get(g)); auto elg = std::make_unique(group, safe_get(y)); - *key = new botan_pubkey_struct(std::move(elg)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(key, std::move(elg)); }); #else BOTAN_UNUSED(key, p, g, y); @@ -488,12 +578,14 @@ int botan_privkey_load_elgamal(botan_privkey_t* key, botan_mp_t p, botan_mp_t g, botan_mp_t x) { #if defined(BOTAN_HAS_ELGAMAL) + if(key == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } *key = nullptr; return ffi_guard_thunk(__func__, [=]() -> int { - Botan::DL_Group group(safe_get(p), safe_get(g)); + const Botan::DL_Group group(safe_get(p), safe_get(g)); auto elg = std::make_unique(group, safe_get(x)); - *key = new botan_privkey_struct(std::move(elg)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(key, std::move(elg)); }); #else BOTAN_UNUSED(key, p, g, x); @@ -509,12 +601,14 @@ int botan_privkey_load_dh(botan_privkey_t* key, botan_mp_t p, botan_mp_t g, botan_mp_t x) { #if defined(BOTAN_HAS_DIFFIE_HELLMAN) + if(key == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } *key = nullptr; return ffi_guard_thunk(__func__, [=]() -> int { - Botan::DL_Group group(safe_get(p), safe_get(g)); + const Botan::DL_Group group(safe_get(p), safe_get(g)); auto dh = std::make_unique(group, safe_get(x)); - *key = new botan_privkey_struct(std::move(dh)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(key, std::move(dh)); }); #else BOTAN_UNUSED(key, p, g, x); @@ -524,12 +618,14 @@ int botan_pubkey_load_dh(botan_pubkey_t* key, botan_mp_t p, botan_mp_t g, botan_mp_t y) { #if defined(BOTAN_HAS_DIFFIE_HELLMAN) + if(key == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } *key = nullptr; return ffi_guard_thunk(__func__, [=]() -> int { - Botan::DL_Group group(safe_get(p), safe_get(g)); + const Botan::DL_Group group(safe_get(p), safe_get(g)); auto dh = std::make_unique(group, safe_get(y)); - *key = new botan_pubkey_struct(std::move(dh)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(key, std::move(dh)); }); #else BOTAN_UNUSED(key, p, g, y); @@ -540,17 +636,18 @@ /* ECDH + x25519/x448 specific operations */ int botan_privkey_create_ecdh(botan_privkey_t* key_obj, botan_rng_t rng_obj, const char* param_str) { - if(param_str == nullptr) { + if(Botan::any_null_pointers(key_obj, param_str)) { return BOTAN_FFI_ERROR_NULL_POINTER; } + *key_obj = nullptr; const std::string params(param_str); - if(params == "x25519" || params == "curve25519") { + if(params == "X25519" || params == "x25519" || params == "curve25519") { return botan_privkey_create(key_obj, "X25519", "", rng_obj); } - if(params == "x448") { + if(params == "X448" || params == "x448") { return botan_privkey_create(key_obj, "X448", "", rng_obj); } @@ -562,12 +659,16 @@ const botan_mp_t public_y, const char* curve_name) { #if defined(BOTAN_HAS_ECDH) + if(Botan::any_null_pointers(key, curve_name)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + *key = nullptr; return ffi_guard_thunk(__func__, [=]() -> int { std::unique_ptr p_key; - int rc = pubkey_load_ec(p_key, safe_get(public_x), safe_get(public_y), curve_name); + const int rc = pubkey_load_ec(p_key, safe_get(public_x), safe_get(public_y), curve_name); if(rc == BOTAN_FFI_SUCCESS) { - *key = new botan_pubkey_struct(std::move(p_key)); + ffi_new_object(key, std::move(p_key)); } return rc; }); @@ -577,13 +678,40 @@ #endif } +int botan_pubkey_load_ecdh_sec1(botan_pubkey_t* key, const uint8_t sec1[], size_t sec1_len, const char* curve_name) { +#if defined(BOTAN_HAS_ECDH) + if(Botan::any_null_pointers(key, sec1, curve_name)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + *key = nullptr; + + return ffi_guard_thunk(__func__, [=]() -> int { + std::unique_ptr p_key; + + const int rc = pubkey_load_ec_sec1(p_key, {sec1, sec1_len}, curve_name); + if(rc == BOTAN_FFI_SUCCESS) { + ffi_new_object(key, std::move(p_key)); + } + + return rc; + }); +#else + BOTAN_UNUSED(key, sec1, sec1_len, curve_name); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + int botan_privkey_load_ecdh(botan_privkey_t* key, const botan_mp_t scalar, const char* curve_name) { #if defined(BOTAN_HAS_ECDH) + if(Botan::any_null_pointers(key, curve_name)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + *key = nullptr; return ffi_guard_thunk(__func__, [=]() -> int { std::unique_ptr p_key; - int rc = privkey_load_ec(p_key, safe_get(scalar), curve_name); + const int rc = privkey_load_ec(p_key, safe_get(scalar), curve_name); if(rc == BOTAN_FFI_SUCCESS) { - *key = new botan_privkey_struct(std::move(p_key)); + ffi_new_object(key, std::move(p_key)); } return rc; }); @@ -597,10 +725,7 @@ int botan_pubkey_sm2_compute_za( uint8_t out[], size_t* out_len, const char* ident, const char* hash_algo, const botan_pubkey_t key) { - if(out == nullptr || out_len == nullptr) { - return BOTAN_FFI_ERROR_NULL_POINTER; - } - if(ident == nullptr || hash_algo == nullptr || key == nullptr) { + if(Botan::any_null_pointers(out, out_len, ident, hash_algo, key)) { return BOTAN_FFI_ERROR_NULL_POINTER; } @@ -620,8 +745,9 @@ const std::string ident_str(ident); std::unique_ptr hash = Botan::HashFunction::create_or_throw(hash_algo); - const std::vector za = - Botan::sm2_compute_za(*hash, ident_str, ec_key->domain(), ec_key->_public_ec_point()); + const auto& pt = ec_key->_public_ec_point(); + + const auto za = Botan::sm2_compute_za(*hash, ident_str, ec_key->domain(), pt); return write_vec_output(out, out_len, za); }); @@ -635,13 +761,18 @@ const botan_mp_t public_y, const char* curve_name) { #if defined(BOTAN_HAS_SM2) + if(Botan::any_null_pointers(key, curve_name)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + *key = nullptr; + return ffi_guard_thunk(__func__, [=]() -> int { std::unique_ptr p_key; - if(!pubkey_load_ec(p_key, safe_get(public_x), safe_get(public_y), curve_name)) { - *key = new botan_pubkey_struct(std::move(p_key)); - return BOTAN_FFI_SUCCESS; + if(pubkey_load_ec(p_key, safe_get(public_x), safe_get(public_y), curve_name) == 0) { + return ffi_new_object(key, std::move(p_key)); + } else { + return BOTAN_FFI_ERROR_UNKNOWN_ERROR; } - return BOTAN_FFI_ERROR_UNKNOWN_ERROR; }); #else BOTAN_UNUSED(key, public_x, public_y, curve_name); @@ -649,14 +780,42 @@ #endif } +int botan_pubkey_load_sm2_sec1(botan_pubkey_t* key, const uint8_t sec1[], size_t sec1_len, const char* curve_name) { +#if defined(BOTAN_HAS_SM2) + if(Botan::any_null_pointers(key, sec1, curve_name)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + *key = nullptr; + + return ffi_guard_thunk(__func__, [=]() -> int { + std::unique_ptr p_key; + + const int rc = pubkey_load_ec_sec1(p_key, {sec1, sec1_len}, curve_name); + if(rc == BOTAN_FFI_SUCCESS) { + ffi_new_object(key, std::move(p_key)); + } + + return rc; + }); +#else + BOTAN_UNUSED(key, sec1, sec1_len, curve_name); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + int botan_privkey_load_sm2(botan_privkey_t* key, const botan_mp_t scalar, const char* curve_name) { #if defined(BOTAN_HAS_SM2) + if(Botan::any_null_pointers(key, curve_name)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + *key = nullptr; + return ffi_guard_thunk(__func__, [=]() -> int { std::unique_ptr p_key; - int rc = privkey_load_ec(p_key, safe_get(scalar), curve_name); + const int rc = privkey_load_ec(p_key, safe_get(scalar), curve_name); if(rc == BOTAN_FFI_SUCCESS) { - *key = new botan_privkey_struct(std::move(p_key)); + ffi_new_object(key, std::move(p_key)); } return rc; }); @@ -677,16 +836,75 @@ return botan_privkey_load_sm2(key, scalar, curve_name); } +/* EC key specific operations */ + +int botan_ec_privkey_get_private_key(botan_privkey_t key, botan_ec_scalar_t* value) { + if(Botan::any_null_pointers(value)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_ECC_KEY) + return ffi_guard_thunk(__func__, [=]() -> int { + const Botan::EC_PrivateKey* ec_key = dynamic_cast(&safe_get(key)); + if(ec_key == nullptr) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + return ffi_new_object(value, std::make_unique(ec_key->_private_key())); + }); +#else + BOTAN_UNUSED(key, value); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_ec_privkey_get_group(botan_privkey_t key, botan_ec_group_t* ec_group) { + if(Botan::any_null_pointers(ec_group)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + +#if defined(BOTAN_HAS_ECC_KEY) + return ffi_guard_thunk(__func__, [=]() -> int { + const Botan::EC_PrivateKey* ec_key = dynamic_cast(&safe_get(key)); + if(ec_key == nullptr) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + return ffi_new_object(ec_group, std::make_unique(ec_key->domain())); + }); +#else + BOTAN_UNUSED(key, ec_group); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_ec_pubkey_get_group(botan_pubkey_t key, botan_ec_group_t* ec_group) { + if(Botan::any_null_pointers(ec_group)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_ECC_KEY) + return ffi_guard_thunk(__func__, [=]() -> int { + const Botan::EC_PublicKey* ec_key = dynamic_cast(&safe_get(key)); + if(ec_key == nullptr) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + return ffi_new_object(ec_group, std::make_unique(ec_key->domain())); + }); +#else + BOTAN_UNUSED(key, ec_group); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + /* Ed25519 specific operations */ int botan_privkey_load_ed25519(botan_privkey_t* key, const uint8_t privkey[32]) { #if defined(BOTAN_HAS_ED25519) + if(key == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } *key = nullptr; return ffi_guard_thunk(__func__, [=]() -> int { - const Botan::secure_vector privkey_vec(privkey, privkey + 32); - auto ed25519 = std::make_unique(privkey_vec); - *key = new botan_privkey_struct(std::move(ed25519)); - return BOTAN_FFI_SUCCESS; + auto ed25519 = + std::make_unique(Botan::Ed25519_PrivateKey::from_seed(std::span{privkey, 32})); + return ffi_new_object(key, std::move(ed25519)); }); #else BOTAN_UNUSED(key, privkey); @@ -696,12 +914,14 @@ int botan_pubkey_load_ed25519(botan_pubkey_t* key, const uint8_t pubkey[32]) { #if defined(BOTAN_HAS_ED25519) + if(key == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } *key = nullptr; return ffi_guard_thunk(__func__, [=]() -> int { const std::vector pubkey_vec(pubkey, pubkey + 32); auto ed25519 = std::make_unique(pubkey_vec); - *key = new botan_pubkey_struct(std::move(ed25519)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(key, std::move(ed25519)); }); #else BOTAN_UNUSED(key, pubkey); @@ -710,6 +930,9 @@ } int botan_privkey_ed25519_get_privkey(botan_privkey_t key, uint8_t output[64]) { + if(output == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } #if defined(BOTAN_HAS_ED25519) return BOTAN_FFI_VISIT(key, [=](const auto& k) { if(auto ed = dynamic_cast(&k)) { @@ -730,6 +953,9 @@ } int botan_pubkey_ed25519_get_pubkey(botan_pubkey_t key, uint8_t output[32]) { + if(output == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } #if defined(BOTAN_HAS_ED25519) return BOTAN_FFI_VISIT(key, [=](const auto& k) { if(auto ed = dynamic_cast(&k)) { @@ -753,11 +979,13 @@ int botan_privkey_load_ed448(botan_privkey_t* key, const uint8_t privkey[57]) { #if defined(BOTAN_HAS_ED448) + if(key == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } *key = nullptr; return ffi_guard_thunk(__func__, [=]() -> int { auto ed448 = std::make_unique(std::span(privkey, 57)); - *key = new botan_privkey_struct(std::move(ed448)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(key, std::move(ed448)); }); #else BOTAN_UNUSED(key, privkey); @@ -767,11 +995,13 @@ int botan_pubkey_load_ed448(botan_pubkey_t* key, const uint8_t pubkey[57]) { #if defined(BOTAN_HAS_ED448) + if(key == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } *key = nullptr; return ffi_guard_thunk(__func__, [=]() -> int { auto ed448 = std::make_unique(std::span(pubkey, 57)); - *key = new botan_pubkey_struct(std::move(ed448)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(key, std::move(ed448)); }); #else BOTAN_UNUSED(key, pubkey); @@ -780,6 +1010,9 @@ } int botan_privkey_ed448_get_privkey(botan_privkey_t key, uint8_t output[57]) { + if(output == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } #if defined(BOTAN_HAS_ED448) return BOTAN_FFI_VISIT(key, [=](const auto& k) { if(auto ed = dynamic_cast(&k)) { @@ -797,6 +1030,9 @@ } int botan_pubkey_ed448_get_pubkey(botan_pubkey_t key, uint8_t output[57]) { + if(output == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } #if defined(BOTAN_HAS_ED448) return BOTAN_FFI_VISIT(key, [=](const auto& k) { if(auto ed = dynamic_cast(&k)) { @@ -817,12 +1053,13 @@ int botan_privkey_load_x25519(botan_privkey_t* key, const uint8_t privkey[32]) { #if defined(BOTAN_HAS_X25519) + if(key == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } *key = nullptr; return ffi_guard_thunk(__func__, [=]() -> int { - const Botan::secure_vector privkey_vec(privkey, privkey + 32); - auto x25519 = std::make_unique(privkey_vec); - *key = new botan_privkey_struct(std::move(x25519)); - return BOTAN_FFI_SUCCESS; + auto x25519 = std::make_unique(std::span{privkey, 32}); + return ffi_new_object(key, std::move(x25519)); }); #else BOTAN_UNUSED(key, privkey); @@ -832,12 +1069,13 @@ int botan_pubkey_load_x25519(botan_pubkey_t* key, const uint8_t pubkey[32]) { #if defined(BOTAN_HAS_X25519) + if(key == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } *key = nullptr; return ffi_guard_thunk(__func__, [=]() -> int { - const std::vector pubkey_vec(pubkey, pubkey + 32); - auto x25519 = std::make_unique(pubkey_vec); - *key = new botan_pubkey_struct(std::move(x25519)); - return BOTAN_FFI_SUCCESS; + auto x25519 = std::make_unique(std::span{pubkey, 32}); + return ffi_new_object(key, std::move(x25519)); }); #else BOTAN_UNUSED(key, pubkey); @@ -846,6 +1084,9 @@ } int botan_privkey_x25519_get_privkey(botan_privkey_t key, uint8_t output[32]) { + if(output == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } #if defined(BOTAN_HAS_X25519) return BOTAN_FFI_VISIT(key, [=](const auto& k) { if(auto x25519 = dynamic_cast(&k)) { @@ -866,14 +1107,13 @@ } int botan_pubkey_x25519_get_pubkey(botan_pubkey_t key, uint8_t output[32]) { + if(output == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } #if defined(BOTAN_HAS_X25519) return BOTAN_FFI_VISIT(key, [=](const auto& k) { if(auto x25519 = dynamic_cast(&k)) { - const std::vector& x25519_key = x25519->public_value(); - if(x25519_key.size() != 32) { - return BOTAN_FFI_ERROR_INSUFFICIENT_BUFFER_SPACE; - } - Botan::copy_mem(output, x25519_key.data(), x25519_key.size()); + Botan::copy_mem(std::span{output, 32}, x25519->raw_public_key_bits()); return BOTAN_FFI_SUCCESS; } else { return BOTAN_FFI_ERROR_BAD_PARAMETER; @@ -889,11 +1129,13 @@ int botan_privkey_load_x448(botan_privkey_t* key, const uint8_t privkey[56]) { #if defined(BOTAN_HAS_X448) + if(key == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } *key = nullptr; return ffi_guard_thunk(__func__, [=]() -> int { - auto x448 = std::make_unique(std::span(privkey, 56)); - *key = new botan_privkey_struct(std::move(x448)); - return BOTAN_FFI_SUCCESS; + auto x448 = std::make_unique(std::span{privkey, 56}); + return ffi_new_object(key, std::move(x448)); }); #else BOTAN_UNUSED(key, privkey); @@ -903,11 +1145,13 @@ int botan_pubkey_load_x448(botan_pubkey_t* key, const uint8_t pubkey[56]) { #if defined(BOTAN_HAS_X448) + if(key == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } *key = nullptr; return ffi_guard_thunk(__func__, [=]() -> int { - auto x448 = std::make_unique(std::span(pubkey, 56)); - *key = new botan_pubkey_struct(std::move(x448)); - return BOTAN_FFI_SUCCESS; + auto x448 = std::make_unique(std::span{pubkey, 56}); + return ffi_new_object(key, std::move(x448)); }); #else BOTAN_UNUSED(key, pubkey); @@ -916,11 +1160,14 @@ } int botan_privkey_x448_get_privkey(botan_privkey_t key, uint8_t output[56]) { + if(output == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } #if defined(BOTAN_HAS_X448) return BOTAN_FFI_VISIT(key, [=](const auto& k) { if(auto x448 = dynamic_cast(&k)) { const auto x448_key = x448->raw_private_key_bits(); - Botan::copy_mem(std::span(output, 56), x448_key); + Botan::copy_mem(std::span{output, 56}, x448_key); return BOTAN_FFI_SUCCESS; } else { return BOTAN_FFI_ERROR_BAD_PARAMETER; @@ -933,11 +1180,13 @@ } int botan_pubkey_x448_get_pubkey(botan_pubkey_t key, uint8_t output[56]) { + if(output == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } #if defined(BOTAN_HAS_X448) return BOTAN_FFI_VISIT(key, [=](const auto& k) { if(auto x448 = dynamic_cast(&k)) { - const std::vector& x448_key = x448->public_value(); - Botan::copy_mem(std::span(output, 56), x448_key); + Botan::copy_mem(std::span{output, 56}, x448->raw_public_key_bits()); return BOTAN_FFI_SUCCESS; } else { return BOTAN_FFI_ERROR_BAD_PARAMETER; @@ -955,32 +1204,30 @@ int botan_privkey_load_kyber(botan_privkey_t* key, const uint8_t privkey[], size_t key_len) { #if defined(BOTAN_HAS_KYBER) + if(Botan::any_null_pointers(key, privkey)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } *key = nullptr; - switch(key_len) { - case 1632: - return ffi_guard_thunk(__func__, [=]() -> int { - const Botan::secure_vector privkey_vec(privkey, privkey + 1632); - auto kyber512 = std::make_unique(privkey_vec, Botan::KyberMode::Kyber512_R3); - *key = new botan_privkey_struct(std::move(kyber512)); - return BOTAN_FFI_SUCCESS; - }); - case 2400: - return ffi_guard_thunk(__func__, [=]() -> int { - const Botan::secure_vector privkey_vec(privkey, privkey + 2400); - auto kyber768 = std::make_unique(privkey_vec, Botan::KyberMode::Kyber768_R3); - *key = new botan_privkey_struct(std::move(kyber768)); - return BOTAN_FFI_SUCCESS; - }); - case 3168: - return ffi_guard_thunk(__func__, [=]() -> int { - const Botan::secure_vector privkey_vec(privkey, privkey + 3168); - auto kyber1024 = std::make_unique(privkey_vec, Botan::KyberMode::Kyber1024_R3); - *key = new botan_privkey_struct(std::move(kyber1024)); - return BOTAN_FFI_SUCCESS; - }); - default: - BOTAN_UNUSED(key, privkey, key_len); - return BOTAN_FFI_ERROR_BAD_PARAMETER; + + const auto mode = [](size_t len) -> std::optional { + if(len == 1632) { + return Botan::KyberMode::Kyber512_R3; + } else if(len == 2400) { + return Botan::KyberMode::Kyber768_R3; + } else if(len == 3168) { + return Botan::KyberMode::Kyber1024_R3; + } else { + return {}; + } + }(key_len); + + if(mode.has_value()) { + return ffi_guard_thunk(__func__, [=]() -> int { + auto kyber = std::make_unique(std::span{privkey, key_len}, *mode); + return ffi_new_object(key, std::move(kyber)); + }); + } else { + return BOTAN_FFI_ERROR_BAD_PARAMETER; } #else BOTAN_UNUSED(key, key_len, privkey); @@ -990,32 +1237,30 @@ int botan_pubkey_load_kyber(botan_pubkey_t* key, const uint8_t pubkey[], size_t key_len) { #if defined(BOTAN_HAS_KYBER) + if(Botan::any_null_pointers(key, pubkey)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } *key = nullptr; - switch(key_len) { - case 800: - return ffi_guard_thunk(__func__, [=]() -> int { - const std::vector pubkey_vec(pubkey, pubkey + 800); - auto kyber512 = std::make_unique(pubkey_vec, Botan::KyberMode::Kyber512_R3); - *key = new botan_pubkey_struct(std::move(kyber512)); - return BOTAN_FFI_SUCCESS; - }); - case 1184: - return ffi_guard_thunk(__func__, [=]() -> int { - const std::vector pubkey_vec(pubkey, pubkey + 1184); - auto kyber768 = std::make_unique(pubkey_vec, Botan::KyberMode::Kyber768_R3); - *key = new botan_pubkey_struct(std::move(kyber768)); - return BOTAN_FFI_SUCCESS; - }); - case 1568: - return ffi_guard_thunk(__func__, [=]() -> int { - const std::vector pubkey_vec(pubkey, pubkey + 1568); - auto kyber1024 = std::make_unique(pubkey_vec, Botan::KyberMode::Kyber1024_R3); - *key = new botan_pubkey_struct(std::move(kyber1024)); - return BOTAN_FFI_SUCCESS; - }); - default: - BOTAN_UNUSED(key, pubkey, key_len); - return BOTAN_FFI_ERROR_BAD_PARAMETER; + + const auto mode = [](size_t len) -> std::optional { + if(len == 800) { + return Botan::KyberMode::Kyber512_R3; + } else if(len == 1184) { + return Botan::KyberMode::Kyber768_R3; + } else if(len == 1568) { + return Botan::KyberMode::Kyber1024_R3; + } else { + return {}; + } + }(key_len); + + if(mode.has_value()) { + return ffi_guard_thunk(__func__, [=]() -> int { + auto kyber = std::make_unique(std::span{pubkey, key_len}, *mode); + return ffi_new_object(key, std::move(kyber)); + }); + } else { + return BOTAN_FFI_ERROR_BAD_PARAMETER; } #else BOTAN_UNUSED(key, pubkey, key_len); @@ -1059,7 +1304,7 @@ int botan_privkey_load_ml_kem(botan_privkey_t* key, const uint8_t privkey[], size_t key_len, const char* mlkem_mode) { #if defined(BOTAN_HAS_ML_KEM) - if(key == nullptr || privkey == nullptr || mlkem_mode == nullptr) { + if(Botan::any_null_pointers(key, privkey, mlkem_mode)) { return BOTAN_FFI_ERROR_NULL_POINTER; } @@ -1072,8 +1317,7 @@ } auto mlkem_key = std::make_unique(std::span{privkey, key_len}, mode); - *key = new botan_privkey_struct(std::move(mlkem_key)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(key, std::move(mlkem_key)); }); #else BOTAN_UNUSED(key, key_len, privkey, mlkem_mode); @@ -1083,7 +1327,7 @@ int botan_pubkey_load_ml_kem(botan_pubkey_t* key, const uint8_t pubkey[], size_t key_len, const char* mlkem_mode) { #if defined(BOTAN_HAS_ML_KEM) - if(key == nullptr || pubkey == nullptr || mlkem_mode == nullptr) { + if(Botan::any_null_pointers(key, pubkey, mlkem_mode)) { return BOTAN_FFI_ERROR_NULL_POINTER; } @@ -1096,8 +1340,7 @@ } auto mlkem_key = std::make_unique(std::span{pubkey, key_len}, mode.mode()); - *key = new botan_pubkey_struct(std::move(mlkem_key)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(key, std::move(mlkem_key)); }); #else BOTAN_UNUSED(key, key_len, pubkey, mlkem_mode); @@ -1111,7 +1354,7 @@ int botan_privkey_load_ml_dsa(botan_privkey_t* key, const uint8_t privkey[], size_t key_len, const char* mldsa_mode) { #if defined(BOTAN_HAS_ML_DSA) - if(key == nullptr || privkey == nullptr || mldsa_mode == nullptr) { + if(Botan::any_null_pointers(key, privkey, mldsa_mode)) { return BOTAN_FFI_ERROR_NULL_POINTER; } @@ -1124,8 +1367,7 @@ } auto mldsa_key = std::make_unique(std::span{privkey, key_len}, mode); - *key = new botan_privkey_struct(std::move(mldsa_key)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(key, std::move(mldsa_key)); }); #else BOTAN_UNUSED(key, key_len, privkey, mldsa_mode); @@ -1135,7 +1377,7 @@ int botan_pubkey_load_ml_dsa(botan_pubkey_t* key, const uint8_t pubkey[], size_t key_len, const char* mldsa_mode) { #if defined(BOTAN_HAS_ML_DSA) - if(key == nullptr || pubkey == nullptr || mldsa_mode == nullptr) { + if(Botan::any_null_pointers(key, pubkey, mldsa_mode)) { return BOTAN_FFI_ERROR_NULL_POINTER; } @@ -1148,8 +1390,7 @@ } auto mldsa_key = std::make_unique(std::span{pubkey, key_len}, mode); - *key = new botan_pubkey_struct(std::move(mldsa_key)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(key, std::move(mldsa_key)); }); #else BOTAN_UNUSED(key, key_len, pubkey, mldsa_mode); @@ -1163,7 +1404,7 @@ int botan_privkey_load_slh_dsa(botan_privkey_t* key, const uint8_t privkey[], size_t key_len, const char* slhdsa_mode) { #if defined(BOTAN_HAS_SLH_DSA_WITH_SHA2) || defined(BOTAN_HAS_SLH_DSA_WITH_SHAKE) - if(key == nullptr || privkey == nullptr || slhdsa_mode == nullptr) { + if(Botan::any_null_pointers(key, privkey, slhdsa_mode)) { return BOTAN_FFI_ERROR_NULL_POINTER; } @@ -1176,8 +1417,7 @@ } auto slhdsa_key = std::make_unique(std::span{privkey, key_len}, mode); - *key = new botan_privkey_struct(std::move(slhdsa_key)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(key, std::move(slhdsa_key)); }); #else BOTAN_UNUSED(key, key_len, privkey, slhdsa_mode); @@ -1187,7 +1427,7 @@ int botan_pubkey_load_slh_dsa(botan_pubkey_t* key, const uint8_t pubkey[], size_t key_len, const char* slhdsa_mode) { #if defined(BOTAN_HAS_SLH_DSA_WITH_SHA2) || defined(BOTAN_HAS_SLH_DSA_WITH_SHAKE) - if(key == nullptr || pubkey == nullptr || slhdsa_mode == nullptr) { + if(Botan::any_null_pointers(key, pubkey, slhdsa_mode)) { return BOTAN_FFI_ERROR_NULL_POINTER; } @@ -1200,8 +1440,7 @@ } auto mldsa_key = std::make_unique(std::span{pubkey, key_len}, mode); - *key = new botan_pubkey_struct(std::move(mldsa_key)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(key, std::move(mldsa_key)); }); #else BOTAN_UNUSED(key, key_len, pubkey, slhdsa_mode); @@ -1215,7 +1454,7 @@ int botan_privkey_load_frodokem(botan_privkey_t* key, const uint8_t privkey[], size_t key_len, const char* frodo_mode) { #if defined(BOTAN_HAS_FRODOKEM) - if(key == nullptr || privkey == nullptr || frodo_mode == nullptr) { + if(Botan::any_null_pointers(key, privkey, frodo_mode)) { return BOTAN_FFI_ERROR_NULL_POINTER; } @@ -1224,8 +1463,7 @@ return ffi_guard_thunk(__func__, [=]() -> int { const auto mode = Botan::FrodoKEMMode(frodo_mode); auto frodo_key = std::make_unique(std::span{privkey, key_len}, mode); - *key = new botan_privkey_struct(std::move(frodo_key)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(key, std::move(frodo_key)); }); #else BOTAN_UNUSED(key, privkey, key_len, frodo_mode); @@ -1235,7 +1473,7 @@ int botan_pubkey_load_frodokem(botan_pubkey_t* key, const uint8_t pubkey[], size_t key_len, const char* frodo_mode) { #if defined(BOTAN_HAS_FRODOKEM) - if(key == nullptr || pubkey == nullptr || frodo_mode == nullptr) { + if(Botan::any_null_pointers(key, pubkey, frodo_mode)) { return BOTAN_FFI_ERROR_NULL_POINTER; } @@ -1244,8 +1482,7 @@ return ffi_guard_thunk(__func__, [=]() -> int { const auto mode = Botan::FrodoKEMMode(frodo_mode); auto frodo_key = std::make_unique(std::span{pubkey, key_len}, mode); - *key = new botan_pubkey_struct(std::move(frodo_key)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(key, std::move(frodo_key)); }); #else BOTAN_UNUSED(key, pubkey, key_len, frodo_mode); @@ -1262,7 +1499,7 @@ size_t key_len, const char* cmce_mode) { #if defined(BOTAN_HAS_CLASSICMCELIECE) - if(key == nullptr || privkey == nullptr || cmce_mode == nullptr) { + if(Botan::any_null_pointers(key, privkey, cmce_mode)) { return BOTAN_FFI_ERROR_NULL_POINTER; } @@ -1271,8 +1508,7 @@ return ffi_guard_thunk(__func__, [=]() -> int { const auto mode = Botan::Classic_McEliece_Parameter_Set::from_string(cmce_mode); auto cmce_key = std::make_unique(std::span{privkey, key_len}, mode); - *key = new botan_privkey_struct(std::move(cmce_key)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(key, std::move(cmce_key)); }); #else BOTAN_UNUSED(key, privkey, key_len, cmce_mode); @@ -1285,7 +1521,7 @@ size_t key_len, const char* cmce_mode) { #if defined(BOTAN_HAS_CLASSICMCELIECE) - if(key == nullptr || pubkey == nullptr || cmce_mode == nullptr) { + if(Botan::any_null_pointers(key, pubkey, cmce_mode)) { return BOTAN_FFI_ERROR_NULL_POINTER; } @@ -1294,8 +1530,7 @@ return ffi_guard_thunk(__func__, [=]() -> int { const auto mode = Botan::Classic_McEliece_Parameter_Set::from_string(cmce_mode); auto cmce_key = std::make_unique(std::span{pubkey, key_len}, mode); - *key = new botan_pubkey_struct(std::move(cmce_key)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(key, std::move(cmce_key)); }); #else BOTAN_UNUSED(key, pubkey, key_len, cmce_mode); @@ -1319,6 +1554,8 @@ #endif } +// NOLINTEND(misc-misplaced-const) + int botan_privkey_create_mceliece(botan_privkey_t* key_obj, botan_rng_t rng_obj, size_t n, size_t t) { const std::string mce_params = std::to_string(n) + "," + std::to_string(t); return botan_privkey_create(key_obj, "McEliece", mce_params.c_str(), rng_obj); diff -Nru botan3-3.7.1+dfsg/src/lib/ffi/ffi_rng.cpp botan3-3.12.0+dfsg/src/lib/ffi/ffi_rng.cpp --- botan3-3.7.1+dfsg/src/lib/ffi/ffi_rng.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/ffi/ffi_rng.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,5 +1,5 @@ /* -* (C) 2015,2017 Jack Lloyd +* (C) 2015,2017,2026 Jack Lloyd * (C) 2021 René Fischer * * Botan is released under the Simplified BSD License (see license.txt) @@ -15,6 +15,10 @@ #include #include +#if defined(BOTAN_HAS_HMAC_DRBG) + #include +#endif + #if defined(BOTAN_HAS_PROCESSOR_RNG) #include #endif @@ -22,6 +26,7 @@ #if defined(BOTAN_HAS_JITTER_RNG) #include #endif + #if defined(BOTAN_HAS_ESDM_RNG) #include #endif @@ -36,7 +41,7 @@ return BOTAN_FFI_ERROR_NULL_POINTER; } - const std::string rng_type_s(rng_type ? rng_type : "system"); + const std::string rng_type_s(rng_type != nullptr ? rng_type : "system"); std::unique_ptr rng; @@ -69,8 +74,7 @@ return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; } - *rng_out = new botan_rng_struct(std::move(rng)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(rng_out, std::move(rng)); }); } @@ -93,19 +97,18 @@ return BOTAN_FFI_ERROR_NULL_POINTER; } - class Custom_RNG : public Botan::RandomNumberGenerator { + class Custom_RNG final : public Botan::RandomNumberGenerator { public: Custom_RNG(std::string_view name, void* context, int (*get_cb)(void* context, uint8_t* out, size_t out_len), int (*add_entropy_cb)(void* context, const uint8_t input[], size_t length), void (*destroy_cb)(void* context)) : - m_name(name) { - m_context = context; - m_get_cb = get_cb; - m_add_entropy_cb = add_entropy_cb; - m_destroy_cb = destroy_cb; - } + m_name(name), + m_context(context), + m_get_cb(get_cb), + m_add_entropy_cb(add_entropy_cb), + m_destroy_cb(destroy_cb) {} ~Custom_RNG() override { if(m_destroy_cb) { @@ -121,15 +124,15 @@ protected: void fill_bytes_with_input(std::span output, std::span input) override { if(accepts_input() && !input.empty()) { - int rc = m_add_entropy_cb(m_context, input.data(), input.size()); - if(rc) { + const int rc = m_add_entropy_cb(m_context, input.data(), input.size()); + if(rc != 0) { throw Botan::Invalid_State("Failed to add entropy via C callback, rc=" + std::to_string(rc)); } } if(!output.empty()) { - int rc = m_get_cb(m_context, output.data(), output.size()); - if(rc) { + const int rc = m_get_cb(m_context, output.data(), output.size()); + if(rc != 0) { throw Botan::Invalid_State("Failed to get random from C callback, rc=" + std::to_string(rc)); } } @@ -154,8 +157,7 @@ auto rng = std::make_unique(rng_name, context, get_cb, add_entropy_cb, destroy_cb); - *rng_out = new botan_rng_struct(std::move(rng)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(rng_out, std::move(rng)); }); } @@ -164,10 +166,16 @@ } int botan_rng_get(botan_rng_t rng, uint8_t* out, size_t out_len) { + if(out_len > 0 && out == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } return BOTAN_FFI_VISIT(rng, [=](auto& r) { r.randomize(out, out_len); }); } int botan_system_rng_get(uint8_t* out, size_t out_len) { + if(out_len > 0 && out == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } return ffi_guard_thunk(__func__, [=]() -> int { Botan::system_rng().randomize(out, out_len); return BOTAN_FFI_SUCCESS; @@ -179,10 +187,54 @@ } int botan_rng_add_entropy(botan_rng_t rng, const uint8_t* input, size_t len) { + if(len > 0 && input == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } return BOTAN_FFI_VISIT(rng, [=](auto& r) { r.add_entropy(input, len); }); } int botan_rng_reseed_from_rng(botan_rng_t rng, botan_rng_t source_rng, size_t bits) { return BOTAN_FFI_VISIT(rng, [=](auto& r) { r.reseed_from_rng(safe_get(source_rng), bits); }); } + +int botan_rng_init_drbg(botan_rng_t* rng_out, const char* drbg_name, const uint8_t* seed, size_t seed_len) { + return ffi_guard_thunk(__func__, [=]() -> int { + if(rng_out == nullptr || drbg_name == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + if(seed_len > 0 && seed == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + std::unique_ptr drbg; + const std::string name(drbg_name); + +#if defined(BOTAN_HAS_HMAC_DRBG) + if(name.starts_with("HMAC_DRBG(") && name.ends_with(")") && name.size() > 12) { + const std::string hash = name.substr(10, name.size() - 11); + drbg = std::make_unique(hash); + } +#endif + + if(!drbg) { + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; + } + + drbg->initialize_with(std::span(seed, seed_len)); + // Upcast to RandomNumberGenerator for the FFI object + std::unique_ptr rng(std::move(drbg)); + return ffi_new_object(rng_out, std::move(rng)); + }); +} + +int botan_rng_generate_with_input( + botan_rng_t rng, uint8_t* out, size_t out_len, const uint8_t* addl_input, size_t addl_len) { + if(out_len > 0 && out == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + if(addl_len > 0 && addl_input == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + return BOTAN_FFI_VISIT(rng, [=](auto& r) { r.randomize_with_input({out, out_len}, {addl_input, addl_len}); }); +} } diff -Nru botan3-3.7.1+dfsg/src/lib/ffi/ffi_srp6.cpp botan3-3.12.0+dfsg/src/lib/ffi/ffi_srp6.cpp --- botan3-3.7.1+dfsg/src/lib/ffi/ffi_srp6.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/ffi/ffi_srp6.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,6 +7,7 @@ #include +#include #include #include @@ -30,10 +31,11 @@ int botan_srp6_server_session_init(botan_srp6_server_session_t* srp6) { #if defined(BOTAN_HAS_SRP6) - return ffi_guard_thunk(__func__, [=]() -> int { - *srp6 = new botan_srp6_server_session_struct(std::make_unique()); - return BOTAN_FFI_SUCCESS; - }); + if(srp6 == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + return ffi_guard_thunk( + __func__, [=]() -> int { return ffi_new_object(srp6, std::make_unique()); }); #else BOTAN_UNUSED(srp6); return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; @@ -46,12 +48,12 @@ int botan_srp6_group_size(const char* group_id, size_t* group_p_bytes) { #if defined(BOTAN_HAS_SRP6) - if(group_id == nullptr || group_p_bytes == nullptr) { + if(any_null_pointers(group_id, group_p_bytes)) { return BOTAN_FFI_ERROR_NULL_POINTER; } return ffi_guard_thunk(__func__, [=]() -> int { - auto group = Botan::DL_Group::from_name(group_id); + const auto group = Botan::DL_Group::from_name(group_id); *group_p_bytes = group.p_bytes(); return BOTAN_FFI_SUCCESS; }); @@ -71,14 +73,20 @@ size_t* b_pub_len) { #if defined(BOTAN_HAS_SRP6) return BOTAN_FFI_VISIT(srp6, [=](auto& s) -> int { - if(!verifier || !group_id || !hash_id || !rng_obj) { + if(any_null_pointers(verifier, group_id, hash_id, rng_obj)) { return BOTAN_FFI_ERROR_NULL_POINTER; } try { + const auto group = Botan::DL_Group::from_name(group_id); + const auto rc = check_and_prepare_output_space(b_pub, b_pub_len, group.p_bytes()); + if(rc != BOTAN_FFI_SUCCESS) { + return rc; + } + Botan::RandomNumberGenerator& rng = safe_get(rng_obj); auto v_bn = Botan::BigInt::from_bytes(std::span{verifier, verifier_len}); - auto b_pub_bn = s.step1(v_bn, group_id, hash_id, rng); - return write_vec_output(b_pub, b_pub_len, b_pub_bn.serialize()); + auto b_pub_bn = s.step1(v_bn, group, hash_id, group.exponent_bits(), rng); + return write_vec_output(b_pub, b_pub_len, b_pub_bn.serialize(group.p_bytes())); } catch(Botan::Decoding_Error&) { return BOTAN_FFI_ERROR_BAD_PARAMETER; } catch(Botan::Lookup_Error&) { @@ -99,7 +107,7 @@ return BOTAN_FFI_ERROR_NULL_POINTER; } try { - Botan::BigInt a_bn = Botan::BigInt::from_bytes({a, a_len}); + const Botan::BigInt a_bn = Botan::BigInt::from_bytes({a, a_len}); auto key_sk = s.step2(a_bn); return write_vec_output(key, key_len, key_sk.bits_of()); } catch(Botan::Decoding_Error&) { @@ -122,13 +130,15 @@ size_t* verifier_len) { #if defined(BOTAN_HAS_SRP6) return ffi_guard_thunk(__func__, [=]() -> int { - if(!username || !password || !salt || !group_id || !hash_id) { + if(any_null_pointers(username, password, salt, group_id, hash_id)) { return BOTAN_FFI_ERROR_NULL_POINTER; } try { - std::vector salt_vec(salt, salt + salt_len); - auto verifier_bn = Botan::srp6_generate_verifier(username, password, salt_vec, group_id, hash_id); - return write_vec_output(verifier, verifier_len, verifier_bn.serialize()); + const std::vector salt_vec(salt, salt + salt_len); + const auto group = Botan::DL_Group::from_name(group_id); + const size_t p_bytes = group.p_bytes(); + auto verifier_bn = Botan::srp6_generate_verifier(username, password, salt_vec, group, hash_id); + return write_vec_output(verifier, verifier_len, verifier_bn.serialize(p_bytes)); } catch(Botan::Lookup_Error&) { return BOTAN_FFI_ERROR_BAD_PARAMETER; } @@ -155,15 +165,17 @@ size_t* K_len) { #if defined(BOTAN_HAS_SRP6) return ffi_guard_thunk(__func__, [=]() -> int { - if(!identity || !password || !salt || !group_id || !hash_id || !b || !rng_obj) { + if(any_null_pointers(identity, password, salt, group_id, hash_id, b, rng_obj)) { return BOTAN_FFI_ERROR_NULL_POINTER; } try { - std::vector saltv(salt, salt + salt_len); + const std::vector saltv(salt, salt + salt_len); Botan::RandomNumberGenerator& rng = safe_get(rng_obj); auto b_bn = Botan::BigInt::from_bytes({b, b_len}); - auto [A_bn, K_sk] = Botan::srp6_client_agree(identity, password, group_id, hash_id, saltv, b_bn, rng); - auto ret_a = write_vec_output(A, A_len, A_bn.serialize()); + const auto group = Botan::DL_Group::from_name(group_id); + const size_t a_bits = group.exponent_bits(); + auto [A_bn, K_sk] = Botan::srp6_client_agree(identity, password, group, hash_id, saltv, b_bn, a_bits, rng); + auto ret_a = write_vec_output(A, A_len, A_bn.serialize(group.p_bytes())); auto ret_k = write_vec_output(K, K_len, K_sk.bits_of()); if(ret_a != BOTAN_FFI_SUCCESS) { return ret_a; diff -Nru botan3-3.7.1+dfsg/src/lib/ffi/ffi_totp.cpp botan3-3.12.0+dfsg/src/lib/ffi/ffi_totp.cpp --- botan3-3.7.1+dfsg/src/lib/ffi/ffi_totp.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/ffi/ffi_totp.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,6 +6,7 @@ #include +#include #include #if defined(BOTAN_HAS_TOTP) @@ -33,9 +34,7 @@ #if defined(BOTAN_HAS_TOTP) return ffi_guard_thunk(__func__, [=]() -> int { auto otp = std::make_unique(key, key_len, hash_algo, digits, time_step); - *totp = new botan_totp_struct(std::move(otp)); - - return BOTAN_FFI_SUCCESS; + return ffi_new_object(totp, std::move(otp)); }); #else BOTAN_UNUSED(totp, key, key_len, hash_algo, digits, time_step); diff -Nru botan3-3.7.1+dfsg/src/lib/ffi/ffi_tpm2.cpp botan3-3.12.0+dfsg/src/lib/ffi/ffi_tpm2.cpp --- botan3-3.7.1+dfsg/src/lib/ffi/ffi_tpm2.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/ffi/ffi_tpm2.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,6 +7,7 @@ #include +#include #include #include #include @@ -88,8 +89,7 @@ }(); ctx->ctx = Botan::TPM2::Context::create(std::move(tcti)); - *ctx_out = new botan_tpm2_ctx_struct(std::move(ctx)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(ctx_out, std::move(ctx)); }); #else BOTAN_UNUSED(ctx_out, tcti_nameconf); @@ -122,8 +122,7 @@ }(); ctx->ctx = Botan::TPM2::Context::create(std::move(tcti_name_str), std::move(tcti_conf_str)); - *ctx_out = new botan_tpm2_ctx_struct(std::move(ctx)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(ctx_out, std::move(ctx)); }); #else BOTAN_UNUSED(ctx_out, tcti_name, tcti_conf); @@ -140,8 +139,7 @@ auto ctx = std::make_unique(); ctx->ctx = Botan::TPM2::Context::create(esys_ctx); - *ctx_out = new botan_tpm2_ctx_struct(std::move(ctx)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(ctx_out, std::move(ctx)); }); #else BOTAN_UNUSED(ctx_out, esys_ctx); @@ -157,8 +155,7 @@ // The lifetime of the RNG used for the crypto backend should be managed // by the TPM2::Context. Here, we just need to trust the user that they // keep the passed-in RNG instance intact for the lifetime of the context. - std::shared_ptr rng_ptr(&rng_ref, [](auto*) {}); - ctx_wrapper.ctx->use_botan_crypto_backend(rng_ptr); + ctx_wrapper.ctx->use_botan_crypto_backend(std::shared_ptr(&rng_ref, [](auto*) {})); return BOTAN_FFI_SUCCESS; }); #else @@ -168,7 +165,7 @@ } /** - * Frees all resouces of a TPM2 context + * Frees all resources of a TPM2 context * @param ctx TPM2 context * @return 0 on success */ @@ -194,9 +191,8 @@ // Here, we just need to trust the user that they keep the passed-in RNG // instance intact for the lifetime of the context. - std::shared_ptr rng_ptr(&rng_ref, [](auto*) {}); - *cbs_out = new botan_tpm2_crypto_backend_state_struct(Botan::TPM2::use_botan_crypto_backend(esys_ctx, rng_ptr)); - return BOTAN_FFI_SUCCESS; + const std::shared_ptr rng_ptr(&rng_ref, [](auto*) {}); + return ffi_new_object(cbs_out, Botan::TPM2::use_botan_crypto_backend(esys_ctx, rng_ptr)); }); #else BOTAN_UNUSED(cbs_out, esys_ctx, rng); @@ -224,9 +220,8 @@ return BOTAN_FFI_ERROR_NULL_POINTER; } - *rng_out = new botan_rng_struct( - std::make_unique(ctx_wrapper.ctx, sessions(s1, s2, s3))); - return BOTAN_FFI_SUCCESS; + return ffi_new_object( + rng_out, std::make_unique(ctx_wrapper.ctx, sessions(s1, s2, s3))); }); #else BOTAN_UNUSED(rng_out, ctx, s1, s2, s3); @@ -243,8 +238,7 @@ auto session = std::make_unique(); session->session = Botan::TPM2::Session::unauthenticated_session(ctx_wrapper.ctx); - *session_out = new botan_tpm2_session_struct(std::move(session)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(session_out, std::move(session)); }); #else BOTAN_UNUSED(session_out, ctx); diff -Nru botan3-3.7.1+dfsg/src/lib/ffi/ffi_util.h botan3-3.12.0+dfsg/src/lib/ffi/ffi_util.h --- botan3-3.7.1+dfsg/src/lib/ffi/ffi_util.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/ffi/ffi_util.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,14 +9,17 @@ #include #include -#include +#include +#include #include -#include +#include +#include #include -#include namespace Botan_FFI { +using Botan::any_null_pointers; + class BOTAN_UNSTABLE_API FFI_Error final : public Botan::Exception { public: FFI_Error(std::string_view what, int err_code) : Exception("FFI error", what), m_err_code(err_code) {} @@ -32,13 +35,18 @@ template struct botan_struct { public: - botan_struct(std::unique_ptr obj) : m_magic(MAGIC), m_obj(std::move(obj)) {} + explicit botan_struct(std::unique_ptr obj) : m_magic(MAGIC), m_obj(std::move(obj)) {} virtual ~botan_struct() { m_magic = 0; - m_obj.reset(); + m_obj.reset(); // NOLINT(*-ambiguous-smartptr-reset-call) } + botan_struct(const botan_struct& other) = delete; + botan_struct(botan_struct&& other) = delete; + botan_struct& operator=(const botan_struct& other) = delete; + botan_struct& operator=(botan_struct&& other) = delete; + bool magic_ok() const { return (m_magic == MAGIC); } T* unsafe_get() const { return m_obj.get(); } @@ -48,6 +56,8 @@ std::unique_ptr m_obj; }; +// NOLINTBEGIN(*-macro-usage) + #define BOTAN_FFI_DECLARE_STRUCT(NAME, TYPE, MAGIC) \ struct NAME final : public Botan_FFI::botan_struct { \ explicit NAME(std::unique_ptr x) : botan_struct(std::move(x)) {} \ @@ -56,15 +66,21 @@ #define BOTAN_FFI_DECLARE_DUMMY_STRUCT(NAME, MAGIC) \ struct NAME final : public Botan_FFI::botan_struct {} +// NOLINTEND(*-macro-usage) + // Declared in ffi.cpp -int ffi_error_exception_thrown(const char* func_name, const char* exn, int rc = BOTAN_FFI_ERROR_EXCEPTION_THROWN); +void ffi_clear_last_exception(); + +int ffi_error_exception_thrown(const char* func_name, const char* exn, int rc); + +int ffi_error_exception_thrown(const char* func_name, const char* exn, Botan::ErrorType err); template T& safe_get(botan_struct* p) { if(!p) { throw FFI_Error("Null pointer argument", BOTAN_FFI_ERROR_NULL_POINTER); } - if(p->magic_ok() == false) { + if(!p->magic_ok()) { throw FFI_Error("Bad magic in ffi object", BOTAN_FFI_ERROR_INVALID_OBJECT); } @@ -75,7 +91,24 @@ throw FFI_Error("Invalid object pointer", BOTAN_FFI_ERROR_INVALID_OBJECT); } -int ffi_guard_thunk(const char* func_name, const std::function& thunk); +template +int ffi_guard_thunk(const char* func_name, T thunk) { + ffi_clear_last_exception(); + + try { + return thunk(); + } catch(std::bad_alloc&) { + return ffi_error_exception_thrown(func_name, "bad_alloc", BOTAN_FFI_ERROR_OUT_OF_MEMORY); + } catch(Botan_FFI::FFI_Error& e) { + return ffi_error_exception_thrown(func_name, e.what(), e.error_code()); + } catch(Botan::Exception& e) { + return ffi_error_exception_thrown(func_name, e.what(), e.error_type()); + } catch(std::exception& e) { + return ffi_error_exception_thrown(func_name, e.what(), BOTAN_FFI_ERROR_EXCEPTION_THROWN); + } catch(...) { + return ffi_error_exception_thrown(func_name, "unknown exception", BOTAN_FFI_ERROR_EXCEPTION_THROWN); + } +} template int botan_ffi_visit(botan_struct* o, F func, const char* func_name) { @@ -87,7 +120,7 @@ return BOTAN_FFI_ERROR_NULL_POINTER; } - if(o->magic_ok() == false) { + if(!o->magic_ok()) { return BOTAN_FFI_ERROR_INVALID_OBJECT; } @@ -121,6 +154,7 @@ // } // // [...] // } +// NOLINTNEXTLINE(*-macro-usage) #define BOTAN_FFI_VISIT(obj, lambda) botan_ffi_visit(obj, lambda, __func__) template @@ -131,23 +165,40 @@ return BOTAN_FFI_SUCCESS; } - if(obj->magic_ok() == false) { + if(!obj->magic_ok()) { return BOTAN_FFI_ERROR_INVALID_OBJECT; } - delete obj; + delete obj; // NOLINT(*-owning-memory) return BOTAN_FFI_SUCCESS; }); } +template +BOTAN_FFI_ERROR ffi_new_object(T* obj, Args&&... args) { + if(obj == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + // NOLINTNEXTLINE(*-owning-memory) + *obj = new std::remove_pointer_t(std::forward(args)...); + return BOTAN_FFI_SUCCESS; +} + +// NOLINTNEXTLINE(*-macro-usage) #define BOTAN_FFI_CHECKED_DELETE(o) ffi_delete_object(o, __func__) -template -inline int invoke_view_callback(botan_view_bin_fn view, botan_view_ctx ctx, const std::vector& buf) { +inline int invoke_view_callback(botan_view_bin_fn view, botan_view_ctx ctx, std::span buf) { + if(view == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } return view(ctx, buf.data(), buf.size()); } -inline int invoke_view_callback(botan_view_str_fn view, botan_view_ctx ctx, std::string_view str) { +// Should not be std::string_view as we rely on being able to NULL terminate +inline int invoke_view_callback(botan_view_str_fn view, botan_view_ctx ctx, const std::string& str) { + if(view == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } return view(ctx, str.data(), str.size() + 1); } @@ -161,9 +212,7 @@ template int copy_view_bin(uint8_t out[], size_t* out_len, Fn fn, Args... args) { - botan_view_bounce_struct ctx; - ctx.out_ptr = out; - ctx.out_len = out_len; + botan_view_bounce_struct ctx{out, out_len}; return fn(args..., &ctx, botan_view_bin_bounce_fn); } @@ -172,46 +221,52 @@ if(fn == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } - botan_view_bounce_struct ctx; - ctx.out_ptr = out; - ctx.out_len = out_len; + botan_view_bounce_struct ctx{out, out_len}; return fn(args..., &ctx, botan_view_str_bounce_fn); } -inline int write_output(uint8_t out[], size_t* out_len, const uint8_t buf[], size_t buf_len) { +template + requires(sizeof(T) == 1) +inline int check_and_prepare_output_space(T out[], size_t* out_len, size_t required_len) { if(out_len == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } const size_t avail = *out_len; - *out_len = buf_len; + *out_len = required_len; - if((avail >= buf_len) && (out != nullptr)) { - Botan::copy_mem(out, buf, buf_len); - return BOTAN_FFI_SUCCESS; - } else { + if(avail < required_len || out == nullptr) { if(out != nullptr) { - Botan::clear_mem(out, avail); + std::memset(out, 0, sizeof(T) * avail); } return BOTAN_FFI_ERROR_INSUFFICIENT_BUFFER_SPACE; + } else { + return BOTAN_FFI_SUCCESS; } } -template -int write_vec_output(uint8_t out[], size_t* out_len, const std::vector& buf) { - return write_output(out, out_len, buf.data(), buf.size()); -} +template +inline int write_output(T out[], size_t* out_len, const T buf[], size_t buf_len) { + static_assert(sizeof(T) == 1, "T should be either uint8_t or char"); + + const auto rc = check_and_prepare_output_space(out, out_len, buf_len); + if(rc != BOTAN_FFI_SUCCESS) { + return rc; + } + + if(out != nullptr) { + std::memcpy(out, buf, sizeof(T) * buf_len); + } -inline int write_str_output(uint8_t out[], size_t* out_len, std::string_view str) { - return write_output(out, out_len, Botan::cast_char_ptr_to_uint8(str.data()), str.size() + 1); + return BOTAN_FFI_SUCCESS; } -inline int write_str_output(char out[], size_t* out_len, std::string_view str) { - return write_str_output(Botan::cast_char_ptr_to_uint8(out), out_len, str); +inline int write_vec_output(uint8_t out[], size_t* out_len, std::span buf) { + return write_output(out, out_len, buf.data(), buf.size()); } -inline int write_str_output(char out[], size_t* out_len, const std::vector& str_vec) { - return write_output(Botan::cast_char_ptr_to_uint8(out), out_len, str_vec.data(), str_vec.size()); +inline int write_str_output(char out[], size_t* out_len, const std::string& str) { + return write_output(out, out_len, str.data(), str.size() + 1); } } // namespace Botan_FFI diff -Nru botan3-3.7.1+dfsg/src/lib/ffi/ffi_xof.cpp botan3-3.12.0+dfsg/src/lib/ffi/ffi_xof.cpp --- botan3-3.7.1+dfsg/src/lib/ffi/ffi_xof.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/ffi/ffi_xof.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,96 @@ +/* +* (C) 2025 Jack Lloyd +* 2025 René Meusel, Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include + +extern "C" { + +using namespace Botan_FFI; + +BOTAN_FFI_DECLARE_STRUCT(botan_xof_struct, Botan::XOF, 0x0f1303a0); + +int botan_xof_init(botan_xof_t* this_xof, const char* xof_name, uint32_t flags) { + return ffi_guard_thunk(__func__, [=]() -> int { + if(Botan::any_null_pointers(this_xof, xof_name) || *xof_name == 0) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + if(flags != 0) { + return BOTAN_FFI_ERROR_BAD_FLAG; + } + + auto xof = Botan::XOF::create(xof_name); + if(xof == nullptr) { + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; + } + + ffi_new_object(this_xof, std::move(xof)); + return BOTAN_FFI_SUCCESS; + }); +} + +// NOLINTNEXTLINE(misc-misplaced-const) +int botan_xof_copy_state(botan_xof_t* dest, const botan_xof_t this_xof) { + if(dest == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + return BOTAN_FFI_VISIT(this_xof, [=](const auto& src) { return ffi_new_object(dest, src.copy_state()); }); +} + +int botan_xof_block_size(botan_xof_t this_xof, size_t* out) { + if(Botan::any_null_pointers(out)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + return BOTAN_FFI_VISIT(this_xof, [=](const auto& xof) { *out = xof.block_size(); }); +} + +int botan_xof_name(botan_xof_t this_xof, char* name, size_t* name_len) { + if(Botan::any_null_pointers(name_len)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + return BOTAN_FFI_VISIT(this_xof, [=](const auto& xof) { return write_str_output(name, name_len, xof.name()); }); +} + +int botan_xof_accepts_input(botan_xof_t this_xof) { + return BOTAN_FFI_VISIT(this_xof, [=](const auto& xof) { return xof.accepts_input() ? 1 : 0; }); +} + +int botan_xof_clear(botan_xof_t this_xof) { + return BOTAN_FFI_VISIT(this_xof, [](auto& xof) { xof.clear(); }); +} + +int botan_xof_update(botan_xof_t this_xof, const uint8_t* in, size_t in_len) { + if(in_len == 0) { + return 0; + } + + if(Botan::any_null_pointers(in)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + return BOTAN_FFI_VISIT(this_xof, [=](auto& xof) { xof.update({in, in_len}); }); +} + +int botan_xof_output(botan_xof_t this_xof, uint8_t* out, size_t out_len) { + if(out_len == 0) { + return 0; + } + + if(Botan::any_null_pointers(out)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + return BOTAN_FFI_VISIT(this_xof, [=](auto& xof) { xof.output({out, out_len}); }); +} + +int botan_xof_destroy(botan_xof_t xof) { + return BOTAN_FFI_CHECKED_DELETE(xof); +} +} diff -Nru botan3-3.7.1+dfsg/src/lib/ffi/ffi_zfec.cpp botan3-3.12.0+dfsg/src/lib/ffi/ffi_zfec.cpp --- botan3-3.7.1+dfsg/src/lib/ffi/ffi_zfec.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/ffi/ffi_zfec.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,6 +6,7 @@ #include +#include #include #if defined(BOTAN_HAS_ZFEC) @@ -15,6 +16,9 @@ extern "C" { int botan_zfec_encode(size_t K, size_t N, const uint8_t* input, size_t size, uint8_t** outputs) { + if(Botan::any_null_pointers(input, outputs)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } #if defined(BOTAN_HAS_ZFEC) return Botan_FFI::ffi_guard_thunk(__func__, [=]() -> int { Botan::ZFEC(K, N).encode(input, size, [=](size_t index, const uint8_t block[], size_t blockSize) -> void { @@ -30,6 +34,9 @@ int botan_zfec_decode( size_t K, size_t N, const size_t* indexes, uint8_t* const* const inputs, size_t shareSize, uint8_t** outputs) { + if(Botan::any_null_pointers(indexes, inputs, outputs)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } #if defined(BOTAN_HAS_ZFEC) return Botan_FFI::ffi_guard_thunk(__func__, [=]() -> int { std::map shares; diff -Nru botan3-3.7.1+dfsg/src/lib/ffi/info.txt botan3-3.12.0+dfsg/src/lib/ffi/info.txt --- botan3-3.7.1+dfsg/src/lib/ffi/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/ffi/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,5 +1,10 @@ -FFI -> 20240408 +# When bumping remember to also update +# - BOTAN_FFI_API_VERSION in ffi.h +# - botan_ffi_supports_api in ffi.cpp +# - The table of versions in ffi.rst +# - BOTAN_FFI_VERSION in botan3.py +FFI -> 20260506 @@ -8,7 +13,10 @@ +ffi_cert.h +ffi_ec.h ffi_mp.h +ffi_oid.h ffi_pkey.h ffi_rng.h ffi_util.h @@ -26,6 +34,7 @@ kdf pbkdf pubkey +xof pem bigint sha2_32 @@ -33,4 +42,7 @@ #tls system_rng auto_rng + +# TODO this should be made optional +ec_group diff -Nru botan3-3.7.1+dfsg/src/lib/filters/algo_filt.cpp botan3-3.12.0+dfsg/src/lib/filters/algo_filt.cpp --- botan3-3.7.1+dfsg/src/lib/filters/algo_filt.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/filters/algo_filt.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -27,8 +27,8 @@ } void StreamCipher_Filter::write(const uint8_t input[], size_t length) { - while(length) { - size_t copied = std::min(length, m_buffer.size()); + while(length > 0) { + const size_t copied = std::min(length, m_buffer.size()); m_cipher->cipher(input, m_buffer.data(), copied); send(m_buffer, copied); input += copied; @@ -45,7 +45,7 @@ void Hash_Filter::end_msg() { secure_vector output = m_hash->final(); - if(m_out_len) { + if(m_out_len != 0) { send(output, std::min(m_out_len, output.size())); } else { send(output); @@ -64,7 +64,7 @@ void MAC_Filter::end_msg() { secure_vector output = m_mac->final(); - if(m_out_len) { + if(m_out_len != 0) { send(output, std::min(m_out_len, output.size())); } else { send(output); diff -Nru botan3-3.7.1+dfsg/src/lib/filters/b64_filt.cpp botan3-3.12.0+dfsg/src/lib/filters/b64_filt.cpp --- botan3-3.7.1+dfsg/src/lib/filters/b64_filt.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/filters/b64_filt.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,6 +9,7 @@ #include #include +#include #include namespace Botan { @@ -20,19 +21,17 @@ m_line_length(line_breaks ? line_length : 0), m_trailing_newline(trailing_newline && line_breaks), m_in(48), - m_out(64), - m_position(0), - m_out_position(0) {} + m_out(64) {} /* * Encode and send a block */ void Base64_Encoder::encode_and_send(const uint8_t input[], size_t length, bool final_inputs) { - while(length) { + while(length > 0) { const size_t proc = std::min(length, m_in.size()); size_t consumed = 0; - size_t produced = base64_encode(cast_uint8_ptr_to_char(m_out.data()), input, proc, consumed, final_inputs); + const size_t produced = base64_encode(cast_uint8_ptr_to_char(m_out.data()), input, proc, consumed, final_inputs); do_output(m_out.data(), produced); @@ -49,9 +48,10 @@ if(m_line_length == 0) { send(input, length); } else { - size_t remaining = length, offset = 0; - while(remaining) { - size_t sent = std::min(m_line_length - m_out_position, remaining); + size_t remaining = length; + size_t offset = 0; + while(remaining > 0) { + const size_t sent = std::min(m_line_length - m_out_position, remaining); send(input + offset, sent); m_out_position += sent; remaining -= sent; @@ -92,7 +92,7 @@ void Base64_Encoder::end_msg() { encode_and_send(m_in.data(), m_position, true); - if(m_trailing_newline || (m_out_position && m_line_length)) { + if(m_trailing_newline || (m_out_position > 0 && m_line_length > 0)) { send('\n'); } @@ -102,14 +102,14 @@ /* * Base64_Decoder Constructor */ -Base64_Decoder::Base64_Decoder(Decoder_Checking c) : m_checking(c), m_in(64), m_out(48), m_position(0) {} +Base64_Decoder::Base64_Decoder(Decoder_Checking c) : m_checking(c), m_in(64), m_out(48) {} /* * Convert some data from Base64 */ void Base64_Decoder::write(const uint8_t input[], size_t length) { - while(length) { - size_t to_copy = std::min(length, m_in.size() - m_position); + while(length > 0) { + const size_t to_copy = std::min(length, m_in.size() - m_position); if(to_copy == 0) { m_in.resize(m_in.size() * 2); m_out.resize(m_out.size() * 2); @@ -118,7 +118,7 @@ m_position += to_copy; size_t consumed = 0; - size_t written = base64_decode( + const size_t written = base64_decode( m_out.data(), cast_uint8_ptr_to_char(m_in.data()), m_position, consumed, false, m_checking != FULL_CHECK); send(m_out, written); @@ -140,7 +140,7 @@ */ void Base64_Decoder::end_msg() { size_t consumed = 0; - size_t written = base64_decode( + const size_t written = base64_decode( m_out.data(), cast_uint8_ptr_to_char(m_in.data()), m_position, consumed, true, m_checking != FULL_CHECK); send(m_out, written); diff -Nru botan3-3.7.1+dfsg/src/lib/filters/basefilt.cpp botan3-3.12.0+dfsg/src/lib/filters/basefilt.cpp --- botan3-3.7.1+dfsg/src/lib/filters/basefilt.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/filters/basefilt.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -12,19 +12,19 @@ * Chain Constructor */ Chain::Chain(Filter* f1, Filter* f2, Filter* f3, Filter* f4) { - if(f1) { + if(f1 != nullptr) { attach(f1); incr_owns(); } - if(f2) { + if(f2 != nullptr) { attach(f2); incr_owns(); } - if(f3) { + if(f3 != nullptr) { attach(f3); incr_owns(); } - if(f4) { + if(f4 != nullptr) { attach(f4); incr_owns(); } @@ -35,7 +35,7 @@ */ Chain::Chain(Filter* filters[], size_t count) { for(size_t j = 0; j != count; ++j) { - if(filters[j]) { + if(filters[j] != nullptr) { attach(filters[j]); incr_owns(); } diff -Nru botan3-3.7.1+dfsg/src/lib/filters/buf_filt.cpp botan3-3.12.0+dfsg/src/lib/filters/buf_filt.cpp --- botan3-3.7.1+dfsg/src/lib/filters/buf_filt.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/filters/buf_filt.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -32,12 +32,12 @@ * Buffer input into blocks, trying to minimize copying */ void Buffered_Filter::write(const uint8_t input[], size_t input_size) { - if(!input_size) { + if(input_size == 0) { return; } if(m_buffer_pos + input_size >= m_main_block_mod + m_final_minimum) { - size_t to_copy = std::min(m_buffer.size() - m_buffer_pos, input_size); + const size_t to_copy = std::min(m_buffer.size() - m_buffer_pos, input_size); copy_mem(&m_buffer[m_buffer_pos], input, to_copy); m_buffer_pos += to_copy; @@ -58,10 +58,10 @@ } if(input_size >= m_final_minimum) { - size_t full_blocks = (input_size - m_final_minimum) / m_main_block_mod; - size_t to_copy = full_blocks * m_main_block_mod; + const size_t full_blocks = (input_size - m_final_minimum) / m_main_block_mod; + const size_t to_copy = full_blocks * m_main_block_mod; - if(to_copy) { + if(to_copy > 0) { buffered_block(input, to_copy); input += to_copy; @@ -81,10 +81,10 @@ throw Invalid_State("Buffered filter end_msg without enough input"); } - size_t spare_blocks = (m_buffer_pos - m_final_minimum) / m_main_block_mod; + const size_t spare_blocks = (m_buffer_pos - m_final_minimum) / m_main_block_mod; - if(spare_blocks) { - size_t spare_bytes = m_main_block_mod * spare_blocks; + if(spare_blocks > 0) { + const size_t spare_bytes = m_main_block_mod * spare_blocks; buffered_block(m_buffer.data(), spare_bytes); buffered_final(&m_buffer[spare_bytes], m_buffer_pos - spare_bytes); } else { diff -Nru botan3-3.7.1+dfsg/src/lib/filters/cipher_filter.cpp botan3-3.12.0+dfsg/src/lib/filters/cipher_filter.cpp --- botan3-3.7.1+dfsg/src/lib/filters/cipher_filter.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/filters/cipher_filter.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -69,7 +69,7 @@ } void Cipher_Mode_Filter::buffered_block(const uint8_t input[], size_t input_length) { - while(input_length) { + while(input_length > 0) { const size_t take = std::min(m_mode->ideal_granularity(), input_length); m_buffer.assign(input, input + take); diff -Nru botan3-3.7.1+dfsg/src/lib/filters/comp_filter.cpp botan3-3.12.0+dfsg/src/lib/filters/comp_filter.cpp --- botan3-3.7.1+dfsg/src/lib/filters/comp_filter.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/filters/comp_filter.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,6 +8,7 @@ #include +#include #include #include @@ -37,7 +38,7 @@ } void Compression_Filter::write(const uint8_t input[], size_t input_length) { - while(input_length) { + while(input_length > 0) { const size_t take = std::min(m_buffersize, input_length); BOTAN_ASSERT(take > 0, "Consumed something"); @@ -81,7 +82,7 @@ } void Decompression_Filter::write(const uint8_t input[], size_t input_length) { - while(input_length) { + while(input_length > 0) { const size_t take = std::min(m_buffersize, input_length); BOTAN_ASSERT(take > 0, "Consumed something"); diff -Nru botan3-3.7.1+dfsg/src/lib/filters/data_snk.cpp botan3-3.12.0+dfsg/src/lib/filters/data_snk.cpp --- botan3-3.7.1+dfsg/src/lib/filters/data_snk.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/filters/data_snk.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -23,8 +23,8 @@ /* * Write to a stream */ -void DataSink_Stream::write(const uint8_t out[], size_t length) { - m_sink.write(cast_uint8_ptr_to_char(out), length); +void DataSink_Stream::write(const uint8_t buf[], size_t length) { + m_sink.write(cast_uint8_ptr_to_char(buf), length); if(!m_sink.good()) { throw Stream_IO_Error("DataSink_Stream: Failure writing to " + m_identifier); } diff -Nru botan3-3.7.1+dfsg/src/lib/filters/data_snk.h botan3-3.12.0+dfsg/src/lib/filters/data_snk.h --- botan3-3.7.1+dfsg/src/lib/filters/data_snk.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/filters/data_snk.h 2026-05-07 01:38:28.000000000 +0000 @@ -21,12 +21,6 @@ class BOTAN_PUBLIC_API(2, 0) DataSink : public Filter { public: bool attachable() override { return false; } - - DataSink() = default; - ~DataSink() override = default; - - DataSink& operator=(const DataSink&) = delete; - DataSink(const DataSink&) = delete; }; /** @@ -39,7 +33,7 @@ * @param stream the stream to write to * @param name identifier */ - DataSink_Stream(std::ostream& stream, std::string_view name = ""); + BOTAN_FUTURE_EXPLICIT DataSink_Stream(std::ostream& stream, std::string_view name = ""); #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) @@ -49,12 +43,17 @@ * @param use_binary indicates whether to treat the file * as a binary file or not */ - DataSink_Stream(std::string_view pathname, bool use_binary = false); + BOTAN_FUTURE_EXPLICIT DataSink_Stream(std::string_view pathname, bool use_binary = false); #endif + DataSink_Stream(const DataSink_Stream& other) = delete; + DataSink_Stream(DataSink_Stream&& other) = delete; + DataSink_Stream& operator=(const DataSink_Stream& other) = delete; + DataSink_Stream& operator=(DataSink_Stream&& other) = delete; + std::string name() const override { return m_identifier; } - void write(const uint8_t[], size_t) override; + void write(const uint8_t buf[], size_t len) override; void end_msg() override; diff -Nru botan3-3.7.1+dfsg/src/lib/filters/fd_unix/fd_unix.cpp botan3-3.12.0+dfsg/src/lib/filters/fd_unix/fd_unix.cpp --- botan3-3.7.1+dfsg/src/lib/filters/fd_unix/fd_unix.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/filters/fd_unix/fd_unix.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -16,12 +16,12 @@ * Write data from a pipe into a Unix fd */ int operator<<(int fd, Pipe& pipe) { - secure_vector buffer(BOTAN_DEFAULT_BUFFER_SIZE); - while(pipe.remaining()) { + secure_vector buffer(DefaultBufferSize); + while(pipe.remaining() > 0) { size_t got = pipe.read(buffer.data(), buffer.size()); size_t position = 0; - while(got) { - ssize_t ret = ::write(fd, &buffer[position], got); + while(got > 0) { + const ssize_t ret = ::write(fd, &buffer[position], got); if(ret < 0) { throw Stream_IO_Error("Pipe output operator (unixfd) has failed"); } @@ -37,9 +37,9 @@ * Read data from a Unix fd into a pipe */ int operator>>(int fd, Pipe& pipe) { - secure_vector buffer(BOTAN_DEFAULT_BUFFER_SIZE); + secure_vector buffer(DefaultBufferSize); while(true) { - ssize_t ret = ::read(fd, buffer.data(), buffer.size()); + const ssize_t ret = ::read(fd, buffer.data(), buffer.size()); if(ret < 0) { throw Stream_IO_Error("Pipe input operator (unixfd) has failed"); } else if(ret == 0) { diff -Nru botan3-3.7.1+dfsg/src/lib/filters/filter.cpp botan3-3.12.0+dfsg/src/lib/filters/filter.cpp --- botan3-3.7.1+dfsg/src/lib/filters/filter.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/filters/filter.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,6 +7,7 @@ #include +#include #include namespace Botan { @@ -16,22 +17,24 @@ */ Filter::Filter() { m_next.resize(1); - m_port_num = 0; - m_filter_owns = 0; - m_owned = false; +} + +void Filter::send(std::span in, size_t length) { + BOTAN_ASSERT_NOMSG(length <= in.size()); + send(in.data(), length); } /* * Send data to all ports */ void Filter::send(const uint8_t input[], size_t length) { - if(!length) { + if(length == 0) { return; } bool nothing_attached = true; for(size_t j = 0; j != total_ports(); ++j) { - if(m_next[j]) { + if(m_next[j] != nullptr) { if(!m_write_queue.empty()) { m_next[j]->write(m_write_queue.data(), m_write_queue.size()); } @@ -53,7 +56,7 @@ void Filter::new_msg() { start_msg(); for(size_t j = 0; j != total_ports(); ++j) { - if(m_next[j]) { + if(m_next[j] != nullptr) { m_next[j]->new_msg(); } } @@ -65,7 +68,7 @@ void Filter::finish_msg() { end_msg(); for(size_t j = 0; j != total_ports(); ++j) { - if(m_next[j]) { + if(m_next[j] != nullptr) { m_next[j]->finish_msg(); } } @@ -75,9 +78,9 @@ * Attach a filter to the current port */ void Filter::attach(Filter* new_filter) { - if(new_filter) { + if(new_filter != nullptr) { Filter* last = this; - while(last->get_next()) { + while(last->get_next() != nullptr) { last = last->get_next(); } last->m_next[last->current_port()] = new_filter; @@ -113,11 +116,11 @@ m_port_num = 0; m_filter_owns = 0; - while(size && filters && (filters[size - 1] == nullptr)) { + while(size > 0 && filters != nullptr && (filters[size - 1] == nullptr)) { --size; } - if(filters && size) { + if(filters != nullptr && size > 0) { m_next.assign(filters, filters + size); } } diff -Nru botan3-3.7.1+dfsg/src/lib/filters/filter.h botan3-3.12.0+dfsg/src/lib/filters/filter.h --- botan3-3.7.1+dfsg/src/lib/filters/filter.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/filters/filter.h 2026-05-07 01:38:28.000000000 +0000 @@ -10,6 +10,7 @@ #define BOTAN_FILTER_H_ #include +#include #include #include @@ -55,7 +56,9 @@ virtual ~Filter() = default; Filter(const Filter&) = delete; + Filter(Filter&&) = delete; Filter& operator=(const Filter&) = delete; + Filter& operator=(Filter&&) = delete; protected: /** @@ -72,20 +75,18 @@ /** * @param in some input for the filter */ - template - void send(const std::vector& in) { - send(in.data(), in.size()); - } + void send(std::span in) { send(in.data(), in.size()); } /** * @param in some input for the filter * @param length the number of bytes of in to send + * + * This previously took a std::vector, for which the length field (allowing + * using just a prefix of the vector) somewhat made sense. It makes less + * sense now that we are using a span here; you can just use `first` to get + * a prefix. */ - template - void send(const std::vector& in, size_t length) { - BOTAN_ASSERT_NOMSG(length <= in.size()); - send(in.data(), length); - } + void send(std::span in, size_t length); Filter(); @@ -133,10 +134,11 @@ secure_vector m_write_queue; std::vector m_next; // not owned - size_t m_port_num, m_filter_owns; + size_t m_port_num = 0; + size_t m_filter_owns = 0; // true if filter belongs to a pipe --> prohibit filter sharing! - bool m_owned; + bool m_owned = false; }; /** @@ -149,10 +151,13 @@ */ void incr_owns() { ++m_filter_owns; } + // NOLINTNEXTLINE(bugprone-derived-method-shadowing-base-method) void set_port(size_t n) { Filter::set_port(n); } + // NOLINTNEXTLINE(bugprone-derived-method-shadowing-base-method) void set_next(Filter* f[], size_t n) { Filter::set_next(f, n); } + // NOLINTNEXTLINE(bugprone-derived-method-shadowing-base-method) void attach(Filter* f) { Filter::attach(f); } }; @@ -162,7 +167,7 @@ * whitespaces, FULL_CHECK - perform checks, also complain * about white spaces. */ -enum Decoder_Checking { NONE, IGNORE_WS, FULL_CHECK }; +enum Decoder_Checking : uint8_t /* NOLINT(*-use-enum-class) */ { NONE, IGNORE_WS, FULL_CHECK }; } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/filters/filters.h botan3-3.12.0+dfsg/src/lib/filters/filters.h --- botan3-3.7.1+dfsg/src/lib/filters/filters.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/filters/filters.h 2026-05-07 01:38:28.000000000 +0000 @@ -37,7 +37,7 @@ * Filter mixin that breaks input into blocks, useful for * cipher modes */ -class BOTAN_PUBLIC_API(2, 0) Buffered_Filter { +class BOTAN_PUBLIC_API(2, 0) Buffered_Filter /* NOLINT(*-special-member-functions) */ { public: /** * Write bytes into the buffered filter, which will them emit them @@ -127,7 +127,7 @@ * @param iv the initialization vector to use */ virtual void set_iv(const InitializationVector& iv) { - if(iv.length() != 0) { + if(!iv.empty()) { throw Invalid_IV_Length(name(), iv.length()); } } @@ -200,6 +200,7 @@ */ inline Keyed_Filter* get_cipher(std::string_view algo_spec, Cipher_Dir direction) { auto c = Cipher_Mode::create_or_throw(algo_spec, direction); + // NOLINTNEXTLINE(*-owning-memory) return new Cipher_Mode_Filter(c.release()); } @@ -233,7 +234,7 @@ const InitializationVector& iv, Cipher_Dir direction) { Keyed_Filter* cipher = get_cipher(algo_spec, key, direction); - if(iv.length()) { + if(!iv.empty()) { cipher->set_iv(iv); } return cipher; @@ -324,7 +325,7 @@ * hash. Otherwise, specify a smaller value here so that the * output of the hash algorithm will be cut off. */ - Hash_Filter(HashFunction* hash, size_t len = 0) : m_hash(hash), m_out_len(len) {} + BOTAN_FUTURE_EXPLICIT Hash_Filter(HashFunction* hash, size_t len = 0) : m_hash(hash), m_out_len(len) {} /** * Construct a hash filter. @@ -334,7 +335,7 @@ * hash. Otherwise, specify a smaller value here so that the * output of the hash algorithm will be cut off. */ - Hash_Filter(std::string_view request, size_t len = 0); + BOTAN_FUTURE_EXPLICIT Hash_Filter(std::string_view request, size_t len = 0); private: std::unique_ptr m_hash; @@ -371,7 +372,8 @@ * MAC. Otherwise, specify a smaller value here so that the * output of the MAC will be cut off. */ - MAC_Filter(MessageAuthenticationCode* mac, size_t out_len = 0) : m_mac(mac), m_out_len(out_len) {} + BOTAN_FUTURE_EXPLICIT MAC_Filter(MessageAuthenticationCode* mac, size_t out_len = 0) : + m_mac(mac), m_out_len(out_len) {} /** * Construct a MAC filter. @@ -395,7 +397,7 @@ * MAC. Otherwise, specify a smaller value here so that the * output of the MAC will be cut off. */ - MAC_Filter(std::string_view mac, size_t len = 0); + BOTAN_FUTURE_EXPLICIT MAC_Filter(std::string_view mac, size_t len = 0); /** * Construct a MAC filter. @@ -436,6 +438,11 @@ ~Compression_Filter() override; + Compression_Filter(const Compression_Filter& other) = delete; + Compression_Filter(Compression_Filter&& other) = delete; + Compression_Filter& operator=(const Compression_Filter& other) = delete; + Compression_Filter& operator=(Compression_Filter&& other) = delete; + private: std::unique_ptr m_comp; size_t m_buffersize, m_level; @@ -453,10 +460,15 @@ std::string name() const override; - Decompression_Filter(std::string_view type, size_t buffer_size = 4096); + BOTAN_FUTURE_EXPLICIT Decompression_Filter(std::string_view type, size_t buffer_size = 4096); ~Decompression_Filter() override; + Decompression_Filter(const Decompression_Filter& other) = delete; + Decompression_Filter(Decompression_Filter&& other) = delete; + Decompression_Filter& operator=(const Decompression_Filter& other) = delete; + Decompression_Filter& operator=(Decompression_Filter&& other) = delete; + private: std::unique_ptr m_comp; std::size_t m_buffersize; @@ -490,7 +502,9 @@ * @param line_length the length of the lines of the output * @param trailing_newline whether to use a trailing newline */ - Base64_Encoder(bool line_breaks = false, size_t line_length = 72, bool trailing_newline = false); + BOTAN_FUTURE_EXPLICIT Base64_Encoder(bool line_breaks = false, + size_t line_length = 72, + bool trailing_newline = false); private: void encode_and_send(const uint8_t input[], size_t length, bool final_inputs = false); @@ -499,7 +513,8 @@ const size_t m_line_length; const bool m_trailing_newline; std::vector m_in, m_out; - size_t m_position, m_out_position; + size_t m_position = 0; + size_t m_out_position = 0; }; /** @@ -530,8 +545,9 @@ private: const Decoder_Checking m_checking; - std::vector m_in, m_out; - size_t m_position; + std::vector m_in; + std::vector m_out; + size_t m_position = 0; }; /** @@ -543,7 +559,7 @@ /** * Whether to use uppercase or lowercase letters for the encoded string. */ - enum Case { Uppercase, Lowercase }; + enum Case : uint8_t /* NOLINT(*-use-enum-class) */ { Uppercase, Lowercase }; std::string name() const override { return "Hex_Encoder"; } @@ -562,15 +578,17 @@ * @param line_length if newlines are used, how long are lines * @param the_case the case to use in the encoded strings */ - Hex_Encoder(bool newlines = false, size_t line_length = 72, Case the_case = Uppercase); + BOTAN_FUTURE_EXPLICIT Hex_Encoder(bool newlines = false, size_t line_length = 72, Case the_case = Uppercase); private: - void encode_and_send(const uint8_t[], size_t); + void encode_and_send(const uint8_t input[], size_t length); const Case m_casing; const size_t m_line_length; - std::vector m_in, m_out; - size_t m_position, m_counter; + std::vector m_in; + std::vector m_out; + size_t m_position = 0; + size_t m_counter = 0; }; /** @@ -580,7 +598,7 @@ public: std::string name() const override { return "Hex_Decoder"; } - void write(const uint8_t[], size_t) override; + void write(const uint8_t input[], size_t length) override; void end_msg() override; /** @@ -593,7 +611,7 @@ private: const Decoder_Checking m_checking; std::vector m_in, m_out; - size_t m_position; + size_t m_position = 0; }; /** @@ -601,7 +619,7 @@ */ class BOTAN_PUBLIC_API(2, 0) BitBucket final : public Filter { public: - void write(const uint8_t[], size_t) override { /* discard */ + void write(const uint8_t /*input*/[], size_t /*length*/) override { /* discard */ } std::string name() const override { return "BitBucket"; } @@ -623,7 +641,10 @@ * Construct a chain of up to four filters. The filters are set * up in the same order as the arguments. */ - Chain(Filter* = nullptr, Filter* = nullptr, Filter* = nullptr, Filter* = nullptr); + BOTAN_FUTURE_EXPLICIT Chain(Filter* f1 = nullptr, + Filter* f2 = nullptr, + Filter* f3 = nullptr, + Filter* f4 = nullptr); /** * Construct a chain from range of filters @@ -642,6 +663,7 @@ public: void write(const uint8_t input[], size_t length) override { send(input, length); } + // NOLINTNEXTLINE(bugprone-derived-method-shadowing-base-method) void set_port(size_t n) { Fanout_Filter::set_port(n); } std::string name() const override { return "Fork"; } @@ -649,7 +671,7 @@ /** * Construct a Fork filter with up to four forks. */ - Fork(Filter*, Filter*, Filter* = nullptr, Filter* = nullptr); + Fork(Filter* f1, Filter* f2, Filter* f3 = nullptr, Filter* f4 = nullptr); /** * Construct a Fork from range of filters @@ -665,6 +687,12 @@ * This class is a threaded version of the Fork filter. While this uses * threads, the class itself is NOT thread-safe. This is meant as a drop- * in replacement for Fork where performance gains are possible. +* +* This is deprecated as supporting it requires quite a bit of extra complexity +* and realistically if performance is a concern, avoiding this Pipe/Filters +* interface entirely is highly recommended. +* +* TODO(Botan4) remove this and all associated helpers (like Barrier and Semaphore) */ class BOTAN_PUBLIC_API(2, 0) Threaded_Fork final : public Fork { public: @@ -673,17 +701,23 @@ /** * Construct a Threaded_Fork filter with up to four forks. */ - Threaded_Fork(Filter*, Filter*, Filter* = nullptr, Filter* = nullptr); + BOTAN_DEPRECATED("Deprecated, use plain Fork") + Threaded_Fork(Filter* f1, Filter* f2, Filter* f3 = nullptr, Filter* f4 = nullptr); /** * Construct a Threaded_Fork from range of filters * @param filter_arr the list of filters * @param length how many filters */ - Threaded_Fork(Filter* filter_arr[], size_t length); + BOTAN_DEPRECATED("Deprecated, use plain Fork") Threaded_Fork(Filter* filter_arr[], size_t length); ~Threaded_Fork() override; + Threaded_Fork(const Threaded_Fork& other) = delete; + Threaded_Fork(Threaded_Fork&& other) = delete; + Threaded_Fork& operator=(const Threaded_Fork& other) = delete; + Threaded_Fork& operator=(Threaded_Fork&& other) = delete; + private: void set_next(Filter* f[], size_t n); void send(const uint8_t in[], size_t length) override; diff -Nru botan3-3.7.1+dfsg/src/lib/filters/hex_filt.cpp botan3-3.12.0+dfsg/src/lib/filters/hex_filt.cpp --- botan3-3.7.1+dfsg/src/lib/filters/hex_filt.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/filters/hex_filt.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,6 +9,7 @@ #include #include +#include #include namespace Botan { @@ -24,7 +25,6 @@ Hex_Encoder::Hex_Encoder(bool breaks, size_t length, Case c) : m_casing(c), m_line_length(breaks ? length : 0) { m_in.resize(HEX_CODEC_BUFFER_SIZE); m_out.resize(2 * m_in.size()); - m_counter = m_position = 0; } /* @@ -33,7 +33,6 @@ Hex_Encoder::Hex_Encoder(Case c) : m_casing(c), m_line_length(0) { m_in.resize(HEX_CODEC_BUFFER_SIZE); m_out.resize(2 * m_in.size()); - m_counter = m_position = 0; } /* @@ -45,9 +44,10 @@ if(m_line_length == 0) { send(m_out, 2 * length); } else { - size_t remaining = 2 * length, offset = 0; - while(remaining) { - size_t sent = std::min(m_line_length - m_counter, remaining); + size_t remaining = 2 * length; + size_t offset = 0; + while(remaining > 0) { + const size_t sent = std::min(m_line_length - m_counter, remaining); send(&m_out[offset], sent); m_counter += sent; remaining -= sent; @@ -87,7 +87,7 @@ */ void Hex_Encoder::end_msg() { encode_and_send(m_in.data(), m_position); - if(m_counter && m_line_length) { + if(m_counter > 0 && m_line_length > 0) { send('\n'); } m_counter = m_position = 0; @@ -99,20 +99,19 @@ Hex_Decoder::Hex_Decoder(Decoder_Checking c) : m_checking(c) { m_in.resize(HEX_CODEC_BUFFER_SIZE); m_out.resize(m_in.size() / 2); - m_position = 0; } /* * Convert some data from hex format */ void Hex_Decoder::write(const uint8_t input[], size_t length) { - while(length) { - size_t to_copy = std::min(length, m_in.size() - m_position); + while(length > 0) { + const size_t to_copy = std::min(length, m_in.size() - m_position); copy_mem(&m_in[m_position], input, to_copy); m_position += to_copy; size_t consumed = 0; - size_t written = + const size_t written = hex_decode(m_out.data(), cast_uint8_ptr_to_char(m_in.data()), m_position, consumed, m_checking != FULL_CHECK); send(m_out, written); @@ -134,7 +133,7 @@ */ void Hex_Decoder::end_msg() { size_t consumed = 0; - size_t written = + const size_t written = hex_decode(m_out.data(), cast_uint8_ptr_to_char(m_in.data()), m_position, consumed, m_checking != FULL_CHECK); send(m_out, written); diff -Nru botan3-3.7.1+dfsg/src/lib/filters/out_buf.cpp botan3-3.12.0+dfsg/src/lib/filters/out_buf.cpp --- botan3-3.7.1+dfsg/src/lib/filters/out_buf.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/filters/out_buf.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -18,7 +18,7 @@ */ size_t Output_Buffers::read(uint8_t output[], size_t length, Pipe::message_id msg) { SecureQueue* q = get(msg); - if(q) { + if(q != nullptr) { return q->read(output, length); } return 0; @@ -28,8 +28,8 @@ * Peek at data in a message */ size_t Output_Buffers::peek(uint8_t output[], size_t length, size_t stream_offset, Pipe::message_id msg) const { - SecureQueue* q = get(msg); - if(q) { + const SecureQueue* q = get(msg); + if(q != nullptr) { return q->peek(output, length, stream_offset); } return 0; @@ -39,8 +39,8 @@ * Check available bytes in a message */ size_t Output_Buffers::remaining(Pipe::message_id msg) const { - SecureQueue* q = get(msg); - if(q) { + const SecureQueue* q = get(msg); + if(q != nullptr) { return q->size(); } return 0; @@ -50,8 +50,8 @@ * Return the total bytes of a message that have already been read. */ size_t Output_Buffers::get_bytes_read(Pipe::message_id msg) const { - SecureQueue* q = get(msg); - if(q) { + const SecureQueue* q = get(msg); + if(q != nullptr) { return q->get_bytes_read(); } return 0; @@ -104,11 +104,4 @@ return (m_offset + m_buffers.size()); } -/* -* Output_Buffers Constructor -*/ -Output_Buffers::Output_Buffers() { - m_offset = 0; -} - } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/filters/out_buf.h botan3-3.12.0+dfsg/src/lib/filters/out_buf.h --- botan3-3.7.1+dfsg/src/lib/filters/out_buf.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/filters/out_buf.h 2026-05-07 01:38:28.000000000 +0000 @@ -22,23 +22,23 @@ */ class Output_Buffers final { public: - size_t read(uint8_t[], size_t, Pipe::message_id); - size_t peek(uint8_t[], size_t, size_t, Pipe::message_id) const; - size_t get_bytes_read(Pipe::message_id) const; - size_t remaining(Pipe::message_id) const; + size_t read(uint8_t output[], size_t length, Pipe::message_id msg); + size_t peek(uint8_t output[], size_t length, size_t stream_offset, Pipe::message_id msg) const; + size_t get_bytes_read(Pipe::message_id msg) const; + size_t remaining(Pipe::message_id msg) const; - void add(SecureQueue*); + void add(SecureQueue* queue); void retire(); Pipe::message_id message_count() const; - Output_Buffers(); + Output_Buffers() = default; private: - class SecureQueue* get(Pipe::message_id) const; + SecureQueue* get(Pipe::message_id msg) const; std::deque> m_buffers; - Pipe::message_id m_offset; + Pipe::message_id m_offset = 0; }; } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/filters/pipe.cpp botan3-3.12.0+dfsg/src/lib/filters/pipe.cpp --- botan3-3.7.1+dfsg/src/lib/filters/pipe.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/filters/pipe.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,8 +7,8 @@ #include -#include #include +#include #include #include #include @@ -29,6 +29,8 @@ } // namespace +Pipe::Pipe(Pipe&&) noexcept = default; + Pipe::Invalid_Message_Number::Invalid_Message_Number(std::string_view where, message_id msg) : Invalid_Argument(fmt("Pipe::{}: Invalid message number {}", where, msg)) {} @@ -40,13 +42,10 @@ /* * Pipe Constructor */ -Pipe::Pipe(std::initializer_list args) { +Pipe::Pipe(std::initializer_list args) : m_pipe(nullptr), m_default_read(0), m_inside_msg(false) { m_outputs = std::make_unique(); - m_pipe = nullptr; - m_default_read = 0; - m_inside_msg = false; - for(auto arg : args) { + for(auto* arg : args) { do_append(arg); } } @@ -71,13 +70,18 @@ * Destroy the Pipe */ void Pipe::destruct(Filter* to_kill) { - if(!to_kill || dynamic_cast(to_kill)) { + if(to_kill == nullptr) { + return; + } + + if(dynamic_cast(to_kill) != nullptr) { return; } + for(size_t j = 0; j != to_kill->total_ports(); ++j) { destruct(to_kill->m_next[j]); } - delete to_kill; + delete to_kill; // NOLINT(*owning-memory) } /* @@ -106,22 +110,26 @@ end_msg(); } +void Pipe::process_msg(std::span input) { + this->process_msg(input.data(), input.size()); +} + /* * Process a full message at once */ void Pipe::process_msg(const secure_vector& input) { - process_msg(input.data(), input.size()); + this->process_msg(std::span{input}); } void Pipe::process_msg(const std::vector& input) { - process_msg(input.data(), input.size()); + this->process_msg(std::span{input}); } /* * Process a full message at once */ void Pipe::process_msg(std::string_view input) { - process_msg(cast_char_ptr_to_uint8(input.data()), input.length()); + process_msg(as_span_of_bytes(input)); } /* @@ -141,7 +149,7 @@ throw Invalid_State("Pipe::start_msg: Message was already started"); } if(m_pipe == nullptr) { - m_pipe = new Null_Filter; + m_pipe = new Null_Filter; // NOLINT(*-owning-memory) } find_endpoints(m_pipe); m_pipe->new_msg(); @@ -157,7 +165,7 @@ } m_pipe->finish_msg(); clear_endpoints(m_pipe); - if(dynamic_cast(m_pipe)) { + if(dynamic_cast(m_pipe) != nullptr) { delete m_pipe; m_pipe = nullptr; } @@ -171,10 +179,10 @@ */ void Pipe::find_endpoints(Filter* f) { for(size_t j = 0; j != f->total_ports(); ++j) { - if(f->m_next[j] && !dynamic_cast(f->m_next[j])) { + if(f->m_next[j] != nullptr && dynamic_cast(f->m_next[j]) == nullptr) { find_endpoints(f->m_next[j]); } else { - SecureQueue* q = new SecureQueue; + SecureQueue* q = new SecureQueue; // NOLINT(*-owning-memory) f->m_next[j] = q; m_outputs->add(q); } @@ -185,11 +193,11 @@ * Remove the SecureQueues attached to the Filter */ void Pipe::clear_endpoints(Filter* f) { - if(!f) { + if(f == nullptr) { return; } for(size_t j = 0; j != f->total_ports(); ++j) { - if(f->m_next[j] && dynamic_cast(f->m_next[j])) { + if(f->m_next[j] != nullptr && dynamic_cast(f->m_next[j]) != nullptr) { f->m_next[j] = nullptr; } clear_endpoints(f->m_next[j]); @@ -224,10 +232,10 @@ * Append a Filter to the Pipe */ void Pipe::do_append(Filter* filter) { - if(!filter) { + if(filter == nullptr) { return; } - if(dynamic_cast(filter)) { + if(dynamic_cast(filter) != nullptr) { throw Invalid_Argument("Pipe::append: SecureQueue cannot be used"); } if(filter->m_owned) { @@ -240,7 +248,7 @@ filter->m_owned = true; - if(!m_pipe) { + if(m_pipe == nullptr) { m_pipe = filter; } else { m_pipe->attach(filter); @@ -254,10 +262,10 @@ if(m_inside_msg) { throw Invalid_State("Cannot prepend to a Pipe while it is processing"); } - if(!filter) { + if(filter == nullptr) { return; } - if(dynamic_cast(filter)) { + if(dynamic_cast(filter) != nullptr) { throw Invalid_Argument("Pipe::prepend: SecureQueue cannot be used"); } if(filter->m_owned) { @@ -266,7 +274,7 @@ filter->m_owned = true; - if(m_pipe) { + if(m_pipe != nullptr) { filter->attach(m_pipe); } m_pipe = filter; @@ -280,7 +288,7 @@ throw Invalid_State("Cannot pop off a Pipe while it is processing"); } - if(!m_pipe) { + if(m_pipe == nullptr) { return; } @@ -290,9 +298,10 @@ size_t to_remove = m_pipe->owns() + 1; - while(to_remove--) { - std::unique_ptr to_destroy(m_pipe); + while(to_remove > 0) { + const std::unique_ptr to_destroy(m_pipe); m_pipe = m_pipe->m_next[0]; + to_remove -= 1; } } diff -Nru botan3-3.7.1+dfsg/src/lib/filters/pipe.h botan3-3.12.0+dfsg/src/lib/filters/pipe.h --- botan3-3.7.1+dfsg/src/lib/filters/pipe.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/filters/pipe.h 2026-05-07 01:38:28.000000000 +0000 @@ -13,6 +13,7 @@ #include #include #include +#include namespace Botan { @@ -25,6 +26,10 @@ * through the pipe until it reaches the end, where the output is * collected for retrieval. If you're familiar with the Unix shell * environment, this design will sound quite familiar. +* +* @warning This Pipe interface, and all associated types (Filter, etc) +* are considered decrepit, no longer used within the library itself, +* and likely will see no future development. Avoid in new code. */ class BOTAN_PUBLIC_API(2, 0) Pipe final : public DataSource { public: @@ -65,6 +70,12 @@ /** * Write input to the pipe, i.e. to its first filter. + * @param in the byte array to write + */ + void write(std::span in); + + /** + * Write input to the pipe, i.e. to its first filter. * @param in the secure_vector containing the data to write */ void write(const secure_vector& in) { write(in.data(), in.size()); } @@ -102,6 +113,12 @@ /** * Perform start_msg(), write() and end_msg() sequentially. + * @param input the byte array containing the data to write + */ + void process_msg(std::span input); + + /** + * Perform start_msg(), write() and end_msg() sequentially. * @param in the secure_vector containing the data to write */ void process_msg(const secure_vector& in); @@ -313,27 +330,32 @@ * Construct a Pipe of up to four filters. The filters are set up * in the same order as the arguments. */ - Pipe(Filter* = nullptr, Filter* = nullptr, Filter* = nullptr, Filter* = nullptr); + BOTAN_FUTURE_EXPLICIT Pipe(Filter* f1 = nullptr, + Filter* f2 = nullptr, + Filter* f3 = nullptr, + Filter* f4 = nullptr); /** * Construct a Pipe from a list of filters * @param filters the set of filters to use */ - explicit Pipe(std::initializer_list filters); + Pipe(std::initializer_list filters); Pipe(const Pipe&) = delete; + Pipe(Pipe&&) noexcept; Pipe& operator=(const Pipe&) = delete; + Pipe& operator=(Pipe&&) = delete; ~Pipe() override; private: - void destruct(Filter*); + void destruct(Filter* filt); void do_append(Filter* filt); void do_prepend(Filter* filt); - void find_endpoints(Filter*); - void clear_endpoints(Filter*); + void find_endpoints(Filter* filt); + void clear_endpoints(Filter* filt); - message_id get_message_no(std::string_view, message_id) const; + message_id get_message_no(std::string_view func_name, message_id msg) const; Filter* m_pipe; std::unique_ptr m_outputs; diff -Nru botan3-3.7.1+dfsg/src/lib/filters/pipe_io.cpp botan3-3.12.0+dfsg/src/lib/filters/pipe_io.cpp --- botan3-3.7.1+dfsg/src/lib/filters/pipe_io.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/filters/pipe_io.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -17,10 +17,10 @@ * Write data from a pipe into an ostream */ std::ostream& operator<<(std::ostream& stream, Pipe& pipe) { - secure_vector buffer(BOTAN_DEFAULT_BUFFER_SIZE); - while(stream.good() && pipe.remaining()) { + secure_vector buffer(DefaultBufferSize); + while(stream.good() && pipe.remaining() > 0) { const size_t got = pipe.read(buffer.data(), buffer.size()); - stream.write(cast_uint8_ptr_to_char(buffer.data()), got); + stream.write(cast_uint8_ptr_to_char(buffer.data()), static_cast(got)); } if(!stream.good()) { throw Stream_IO_Error("Pipe output operator (iostream) has failed"); @@ -32,9 +32,9 @@ * Read data from an istream into a pipe */ std::istream& operator>>(std::istream& stream, Pipe& pipe) { - secure_vector buffer(BOTAN_DEFAULT_BUFFER_SIZE); + secure_vector buffer(DefaultBufferSize); while(stream.good()) { - stream.read(cast_uint8_ptr_to_char(buffer.data()), buffer.size()); + stream.read(cast_uint8_ptr_to_char(buffer.data()), static_cast(buffer.size())); const size_t got = static_cast(stream.gcount()); pipe.write(buffer.data(), got); } diff -Nru botan3-3.7.1+dfsg/src/lib/filters/pipe_rw.cpp botan3-3.12.0+dfsg/src/lib/filters/pipe_rw.cpp --- botan3-3.7.1+dfsg/src/lib/filters/pipe_rw.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/filters/pipe_rw.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -10,6 +10,7 @@ #include #include +#include #include namespace Botan { @@ -31,6 +32,10 @@ return msg; } +void Pipe::write(std::span input) { + this->write(input.data(), input.size()); +} + /* * Write into a Pipe */ @@ -45,7 +50,7 @@ * Write a string into a Pipe */ void Pipe::write(std::string_view str) { - write(cast_char_ptr_to_uint8(str.data()), str.size()); + write(as_span_of_bytes(str)); } /* @@ -59,9 +64,9 @@ * Write the contents of a DataSource into a Pipe */ void Pipe::write(DataSource& source) { - secure_vector buffer(BOTAN_DEFAULT_BUFFER_SIZE); + secure_vector buffer(DefaultBufferSize); while(!source.end_of_data()) { - size_t got = source.read(buffer.data(), buffer.size()); + const size_t got = source.read(buffer.data(), buffer.size()); write(buffer.data(), got); } } @@ -93,7 +98,7 @@ secure_vector Pipe::read_all(message_id msg) { msg = ((msg != DEFAULT_MESSAGE) ? msg : default_msg()); secure_vector buffer(remaining(msg)); - size_t got = read(buffer.data(), buffer.size(), msg); + const size_t got = read(buffer.data(), buffer.size(), msg); buffer.resize(got); return buffer; } @@ -103,12 +108,12 @@ */ std::string Pipe::read_all_as_string(message_id msg) { msg = ((msg != DEFAULT_MESSAGE) ? msg : default_msg()); - secure_vector buffer(BOTAN_DEFAULT_BUFFER_SIZE); + secure_vector buffer(DefaultBufferSize); std::string str; str.reserve(remaining(msg)); while(true) { - size_t got = read(buffer.data(), buffer.size(), msg); + const size_t got = read(buffer.data(), buffer.size(), msg); if(got == 0) { break; } diff -Nru botan3-3.7.1+dfsg/src/lib/filters/secqueue.cpp botan3-3.12.0+dfsg/src/lib/filters/secqueue.cpp --- botan3-3.7.1+dfsg/src/lib/filters/secqueue.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/filters/secqueue.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -18,10 +18,7 @@ */ class SecureQueueNode final { public: - SecureQueueNode() : m_buffer(BOTAN_DEFAULT_BUFFER_SIZE) { - m_next = nullptr; - m_start = m_end = 0; - } + SecureQueueNode() : m_next(nullptr), m_buffer(DefaultBufferSize), m_start(0), m_end(0) {} ~SecureQueueNode() { m_next = nullptr; @@ -34,14 +31,14 @@ SecureQueueNode& operator=(SecureQueueNode&& other) = delete; size_t write(const uint8_t input[], size_t length) { - size_t copied = std::min(length, m_buffer.size() - m_end); + const size_t copied = std::min(length, m_buffer.size() - m_end); copy_mem(m_buffer.data() + m_end, input, copied); m_end += copied; return copied; } size_t read(uint8_t output[], size_t length) { - size_t copied = std::min(length, m_end - m_start); + const size_t copied = std::min(length, m_end - m_start); copy_mem(output, m_buffer.data() + m_start, copied); m_start += copied; return copied; @@ -52,7 +49,7 @@ if(offset >= left) { return 0; } - size_t copied = std::min(length, left - offset); + const size_t copied = std::min(length, left - offset); copy_mem(output, m_buffer.data() + m_start + offset, copied); return copied; } @@ -69,22 +66,20 @@ /* * Create a SecureQueue */ -SecureQueue::SecureQueue() { - m_bytes_read = 0; +SecureQueue::SecureQueue() : m_bytes_read(0) { set_next(nullptr, 0); - m_head = m_tail = new SecureQueueNode; + m_head = m_tail = new SecureQueueNode; // NOLINT(*-owning-memory) } /* * Copy a SecureQueue */ -SecureQueue::SecureQueue(const SecureQueue& input) : Fanout_Filter(), DataSource() { - m_bytes_read = 0; +SecureQueue::SecureQueue(const SecureQueue& input) : Fanout_Filter(), m_bytes_read(0) { set_next(nullptr, 0); - m_head = m_tail = new SecureQueueNode; + m_head = m_tail = new SecureQueueNode; // NOLINT(*-owning-memory) SecureQueueNode* temp = input.m_head; - while(temp) { + while(temp != nullptr) { write(&temp->m_buffer[temp->m_start], temp->m_end - temp->m_start); temp = temp->m_next; } @@ -94,10 +89,10 @@ * Destroy this SecureQueue */ void SecureQueue::destroy() { - SecureQueueNode* temp = m_head; - while(temp) { - SecureQueueNode* holder = temp->m_next; - delete temp; + const SecureQueueNode* temp = m_head; + while(temp != nullptr) { + const SecureQueueNode* holder = temp->m_next; + delete temp; // NOLINT(*-owning-memory) temp = holder; } m_head = m_tail = nullptr; @@ -113,9 +108,9 @@ destroy(); m_bytes_read = input.get_bytes_read(); - m_head = m_tail = new SecureQueueNode; + m_head = m_tail = new SecureQueueNode; // NOLINT(*-owning-memory) SecureQueueNode* temp = input.m_head; - while(temp) { + while(temp != nullptr) { write(&temp->m_buffer[temp->m_start], temp->m_end - temp->m_start); temp = temp->m_next; } @@ -126,15 +121,15 @@ * Add some bytes to the queue */ void SecureQueue::write(const uint8_t input[], size_t length) { - if(!m_head) { - m_head = m_tail = new SecureQueueNode; + if(m_head == nullptr) { + m_head = m_tail = new SecureQueueNode; // NOLINT(*-owning-memory) } - while(length) { + while(length > 0) { const size_t n = m_tail->write(input, length); input += n; length -= n; - if(length) { - m_tail->m_next = new SecureQueueNode; + if(length > 0) { + m_tail->m_next = new SecureQueueNode; // NOLINT(*-owning-memory) m_tail = m_tail->m_next; } } @@ -145,14 +140,14 @@ */ size_t SecureQueue::read(uint8_t output[], size_t length) { size_t got = 0; - while(length && m_head) { + while(length > 0 && m_head != nullptr) { const size_t n = m_head->read(output, length); output += n; got += n; length -= n; if(m_head->size() == 0) { SecureQueueNode* holder = m_head->m_next; - delete m_head; + delete m_head; // NOLINT(*-owning-memory) m_head = holder; } } @@ -166,7 +161,7 @@ size_t SecureQueue::peek(uint8_t output[], size_t length, size_t offset) const { SecureQueueNode* current = m_head; - while(offset && current) { + while(offset > 0 && current != nullptr) { if(offset >= current->size()) { offset -= current->size(); current = current->m_next; @@ -176,7 +171,7 @@ } size_t got = 0; - while(length && current) { + while(length > 0 && current != nullptr) { const size_t n = current->peek(output, length, offset); offset = 0; output += n; @@ -198,10 +193,10 @@ * Return how many bytes the queue holds */ size_t SecureQueue::size() const { - SecureQueueNode* current = m_head; + const SecureQueueNode* current = m_head; size_t count = 0; - while(current) { + while(current != nullptr) { count += current->size(); current = current->m_next; } @@ -212,7 +207,7 @@ * Test if the queue has any data in it */ bool SecureQueue::end_of_data() const { - return (size() == 0); + return empty(); } bool SecureQueue::empty() const { diff -Nru botan3-3.7.1+dfsg/src/lib/filters/secqueue.h botan3-3.12.0+dfsg/src/lib/filters/secqueue.h --- botan3-3.7.1+dfsg/src/lib/filters/secqueue.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/filters/secqueue.h 2026-05-07 01:38:28.000000000 +0000 @@ -22,10 +22,10 @@ public: std::string name() const override { return "Queue"; } - void write(const uint8_t[], size_t) override; + void write(const uint8_t input[], size_t length) override; - size_t read(uint8_t[], size_t) override; - size_t peek(uint8_t[], size_t, size_t = 0) const override; + size_t read(uint8_t output[], size_t length) override; + size_t peek(uint8_t output[], size_t length, size_t offset = 0) const override; size_t get_bytes_read() const override; bool end_of_data() const override; diff -Nru botan3-3.7.1+dfsg/src/lib/filters/threaded_fork.cpp botan3-3.12.0+dfsg/src/lib/filters/threaded_fork.cpp --- botan3-3.7.1+dfsg/src/lib/filters/threaded_fork.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/filters/threaded_fork.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -12,7 +12,6 @@ #include #include - #include namespace Botan { @@ -82,7 +81,7 @@ } else { m_threads.reserve(n); for(size_t i = m_threads.size(); i != n; ++i) { - m_threads.push_back(std::make_shared(std::bind(&Threaded_Fork::thread_entry, this, m_next[i]))); + m_threads.push_back(std::make_shared([this, next = m_next[i]] { thread_entry(next); })); } } } @@ -95,7 +94,7 @@ bool nothing_attached = true; for(size_t j = 0; j != total_ports(); ++j) { - if(m_next[j]) { + if(m_next[j] != nullptr) { nothing_attached = false; } } @@ -128,7 +127,7 @@ while(true) { m_thread_data->m_input_ready_semaphore.acquire(); - if(!m_thread_data->m_input) { + if(m_thread_data->m_input == nullptr) { break; } diff -Nru botan3-3.7.1+dfsg/src/lib/hash/ascon_hash256/ascon_hash256.cpp botan3-3.12.0+dfsg/src/lib/hash/ascon_hash256/ascon_hash256.cpp --- botan3-3.7.1+dfsg/src/lib/hash/ascon_hash256/ascon_hash256.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/ascon_hash256/ascon_hash256.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,56 @@ +/* +* Ascon-Hash256 (NIST SP.800-232) +* (C) 2025 Jack Lloyd +* 2025 René Meusel +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +namespace Botan { + +namespace { + +// NIST SP.800-232 Appendix A (Table 12) +constexpr Ascon_p initial_state_of_ascon_hash_permutation({ + .init_and_final_rounds = 12, + .processing_rounds = 12, + .bit_rate = 64, + .initial_state = + { + 0x9b1e5494e934d681, + 0x4bc3a01e333751d2, + 0xae65396c6b34b81a, + 0x3c7fd4a4d56a4db3, + 0x1a5c464906c5976d, + }, +}); + +} // namespace + +Ascon_Hash256::Ascon_Hash256() : m_ascon_p(initial_state_of_ascon_hash_permutation) {} + +void Ascon_Hash256::clear() { + m_ascon_p = initial_state_of_ascon_hash_permutation; +} + +std::unique_ptr Ascon_Hash256::new_object() const { + return std::make_unique(); +} + +std::unique_ptr Ascon_Hash256::copy_state() const { + return std::make_unique(*this); +} + +void Ascon_Hash256::add_data(std::span input) { + m_ascon_p.absorb(input); +} + +void Ascon_Hash256::final_result(std::span out) { + m_ascon_p.finish(); + m_ascon_p.squeeze(out); + clear(); +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/hash/ascon_hash256/ascon_hash256.h botan3-3.12.0+dfsg/src/lib/hash/ascon_hash256/ascon_hash256.h --- botan3-3.7.1+dfsg/src/lib/hash/ascon_hash256/ascon_hash256.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/ascon_hash256/ascon_hash256.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,45 @@ +/* +* Ascon-Hash256 (NIST SP.800-232) +* (C) 2025 Jack Lloyd +* 2025 René Meusel +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_ASCON_HASH256_H_ +#define BOTAN_ASCON_HASH256_H_ + +#include +#include + +namespace Botan { + +/** +* Ascon-Hash256 (NIST SP.800-232 Section 5.1) +*/ +class Ascon_Hash256 final : public HashFunction { + public: + Ascon_Hash256(); + + size_t output_length() const override { return 32; } + + std::string name() const override { return "Ascon-Hash256"; } + + std::string provider() const override { return m_ascon_p.provider(); } + + void clear() override; + + std::unique_ptr new_object() const override; + std::unique_ptr copy_state() const override; + + private: + void add_data(std::span input) override; + void final_result(std::span out) override; + + private: + Ascon_p m_ascon_p; +}; + +} // namespace Botan + +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/hash/ascon_hash256/info.txt botan3-3.12.0+dfsg/src/lib/hash/ascon_hash256/info.txt --- botan3-3.7.1+dfsg/src/lib/hash/ascon_hash256/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/ascon_hash256/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,11 @@ + +ASCON_HASH256 -> 20250816 + + + +name -> "Ascon-Hash256" + + + +ascon_perm + diff -Nru botan3-3.7.1+dfsg/src/lib/hash/blake2/blake2b.cpp botan3-3.12.0+dfsg/src/lib/hash/blake2/blake2b.cpp --- botan3-3.7.1+dfsg/src/lib/hash/blake2/blake2b.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/blake2/blake2b.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -10,12 +10,10 @@ #include #include +#include #include #include #include -#include - -#include #include namespace Botan { @@ -192,7 +190,7 @@ m_padded_key_buffer.resize(m_buffer.size()); if(m_padded_key_buffer.size() > m_key_size) { - size_t padding = m_padded_key_buffer.size() - m_key_size; + const size_t padding = m_padded_key_buffer.size() - m_key_size; clear_mem(m_padded_key_buffer.data() + m_key_size, padding); } diff -Nru botan3-3.7.1+dfsg/src/lib/hash/blake2/blake2b.h botan3-3.12.0+dfsg/src/lib/hash/blake2/blake2b.h --- botan3-3.7.1+dfsg/src/lib/hash/blake2/blake2b.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/blake2/blake2b.h 2026-05-07 01:38:28.000000000 +0000 @@ -11,13 +11,10 @@ #include #include #include -#include #include namespace Botan { -class BLAKE2bMAC; - constexpr size_t BLAKE2B_BLOCKBYTES = 128; /** diff -Nru botan3-3.7.1+dfsg/src/lib/hash/blake2/info.txt botan3-3.12.0+dfsg/src/lib/hash/blake2/info.txt --- botan3-3.7.1+dfsg/src/lib/hash/blake2/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/blake2/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -2,6 +2,8 @@ BLAKE2B -> 20130131 +# TODO(Botan4) rename this module blake2b + name -> "BLAKE2b" diff -Nru botan3-3.7.1+dfsg/src/lib/hash/blake2s/blake2s.cpp botan3-3.12.0+dfsg/src/lib/hash/blake2s/blake2s.cpp --- botan3-3.7.1+dfsg/src/lib/hash/blake2s/blake2s.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/blake2s/blake2s.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,8 @@ /* * BLAKE2s - * (C) 2023 Richard Huveneers + * (C) 2023, 2025 Richard Huveneers + * (C) 2025 Kagan Can Sit + * (C) 2025 René Meusel, Rohde & Schwarz Cybersecurity * * Based on the RFC7693 reference implementation * @@ -10,6 +12,7 @@ #include #include +#include #include #include #include @@ -20,12 +23,14 @@ // Initialization Vector. -const uint32_t blake2s_iv[8] = { +constexpr std::array blake2s_iv{ 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19}; // Mixing function G. -inline void B2S_G(uint8_t a, uint8_t b, uint8_t c, uint8_t d, uint32_t x, uint32_t y, uint32_t* v) { +template + requires(a < 16 && b < 16 && c < 16 && d < 16) +constexpr void B2S_G(uint32_t x, uint32_t y, std::span v) { v[a] = v[a] + v[b] + x; v[d] = rotr<16>(v[d] ^ v[a]); v[c] = v[c] + v[d]; @@ -42,65 +47,52 @@ return fmt("BLAKE2s({})", m_outlen << 3); } -// Secret key (also <= 32 bytes) is optional (keylen = 0). -// (keylen=0: no key) +// BLAKE2s is specified as a message authentication code. For that, the +// key would need to be zero-padded and incorporated into the initial hash +// state. See RFC 7693 Section 3.3 and Appendix D.2 `blake2s_init()`. +void BLAKE2s::state_init(size_t outlen) { + m_h = blake2s_iv; // state, "param block" + m_h[0] ^= 0x01010000 ^ outlen; -void BLAKE2s::state_init(size_t outlen, const uint8_t* key, size_t keylen) { - for(size_t i = 0; i < 8; i++) { // state, "param block" - m_h[i] = blake2s_iv[i]; - } - m_h[0] ^= 0x01010000 ^ (keylen << 8) ^ outlen; - - m_t[0] = 0; // input count low word - m_t[1] = 0; // input count high word - m_c = 0; // pointer within buffer + m_bytes_processed = 0; m_outlen = outlen; - - for(size_t i = keylen; i < 64; i++) { // zero input block - m_b[i] = 0; - } - if(keylen > 0) { - add_data(std::span(key, keylen)); - m_c = 64; // at the end - } + m_buffer.clear(); } // Compression function. "last" flag indicates last block. - -void BLAKE2s::compress(bool last) { - const uint8_t sigma[10][16] = {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, - {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}, - {11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4}, - {7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8}, - {9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13}, - {2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9}, - {12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11}, - {13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10}, - {6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5}, - {10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0}}; - uint32_t v[16], m[16]; - - for(size_t i = 0; i < 8; i++) { // init work variables - v[i] = m_h[i]; - v[i + 8] = blake2s_iv[i]; - } - - v[12] ^= m_t[0]; // low 32 bits of offset - v[13] ^= m_t[1]; // high 32 bits - if(last) { // last block flag set ? +void BLAKE2s::compress(bool last, std::span buf) { + BOTAN_ASSERT_NOMSG(buf.size() == block_size); + constexpr std::array, 10> sigma{{{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}, + {11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4}, + {7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8}, + {9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13}, + {2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9}, + {12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11}, + {13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10}, + {6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5}, + {10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0}}}; + + // init work variables + std::array v = concat(m_h, blake2s_iv); + + v[12] ^= static_cast(m_bytes_processed); + v[13] ^= static_cast(m_bytes_processed >> 32); + if(last) { // last block flag set ? v[14] = ~v[14]; } - load_le(m, m_b, 16); // get little-endian words - for(size_t i = 0; i < 10; i++) { // ten rounds - B2S_G(0, 4, 8, 12, m[sigma[i][0]], m[sigma[i][1]], v); - B2S_G(1, 5, 9, 13, m[sigma[i][2]], m[sigma[i][3]], v); - B2S_G(2, 6, 10, 14, m[sigma[i][4]], m[sigma[i][5]], v); - B2S_G(3, 7, 11, 15, m[sigma[i][6]], m[sigma[i][7]], v); - B2S_G(0, 5, 10, 15, m[sigma[i][8]], m[sigma[i][9]], v); - B2S_G(1, 6, 11, 12, m[sigma[i][10]], m[sigma[i][11]], v); - B2S_G(2, 7, 8, 13, m[sigma[i][12]], m[sigma[i][13]], v); - B2S_G(3, 4, 9, 14, m[sigma[i][14]], m[sigma[i][15]], v); + const auto m = load_le>(buf); // get little-endian words + + for(const auto& perm : sigma) { // ten rounds + B2S_G<0, 4, 8, 12>(m[perm[0]], m[perm[1]], v); + B2S_G<1, 5, 9, 13>(m[perm[2]], m[perm[3]], v); + B2S_G<2, 6, 10, 14>(m[perm[4]], m[perm[5]], v); + B2S_G<3, 7, 11, 15>(m[perm[6]], m[perm[7]], v); + B2S_G<0, 5, 10, 15>(m[perm[8]], m[perm[9]], v); + B2S_G<1, 6, 11, 12>(m[perm[10]], m[perm[11]], v); + B2S_G<2, 7, 8, 13>(m[perm[12]], m[perm[13]], v); + B2S_G<3, 4, 9, 14>(m[perm[14]], m[perm[15]], v); } for(size_t i = 0; i < 8; ++i) { @@ -112,33 +104,32 @@ * Clear memory of sensitive data */ void BLAKE2s::clear() { - state_init(m_outlen, nullptr, 0); + state_init(m_outlen); } -void BLAKE2s::add_data(std::span in) { - for(size_t i = 0; i < in.size(); i++) { - if(m_c == 64) { // buffer full ? - m_t[0] += m_c; // add counters - if(m_t[0] < m_c) { // carry overflow ? - m_t[1]++; // high word +void BLAKE2s::add_data(std::span input) { + BufferSlicer in(input); + + while(!in.empty()) { + if(const auto one_block = m_buffer.handle_unaligned_data(in)) { + m_bytes_processed += block_size; + compress(false, *one_block); + } + + if(m_buffer.in_alignment()) { + while(const auto aligned_block = m_buffer.next_aligned_block_to_process(in)) { + m_bytes_processed += block_size; + compress(false, *aligned_block); } - compress(false); // compress (not last) - m_c = 0; // counter to zero } - m_b[m_c++] = in[i]; } } void BLAKE2s::final_result(std::span out) { - m_t[0] += m_c; // mark last block offset - if(m_t[0] < m_c) { // carry overflow - m_t[1]++; // high word - } + m_bytes_processed += m_buffer.elements_in_buffer(); - while(m_c < 64) { // fill up with zeros - m_b[m_c++] = 0; - } - compress(true); // final block flag = 1 + m_buffer.fill_up_with_zeros(); + compress(true, m_buffer.consume()); // little endian convert and store copy_out_le(out.first(output_length()), m_h); @@ -147,12 +138,7 @@ } std::unique_ptr BLAKE2s::copy_state() const { - std::unique_ptr h = std::make_unique(m_outlen << 3); - memcpy(h->m_b, m_b, sizeof(m_b)); - memcpy(h->m_h, m_h, sizeof(m_h)); - memcpy(h->m_t, m_t, sizeof(m_t)); - h->m_c = m_c; - return h; + return std::make_unique(*this); } /* @@ -161,14 +147,12 @@ BLAKE2s::BLAKE2s(size_t output_bits) { if(output_bits == 0 || output_bits > 256 || output_bits % 8 != 0) { throw Invalid_Argument("Bad output bits size for BLAKE2s"); - }; - state_init(output_bits >> 3, nullptr, 0); + } + state_init(output_bits >> 3); } BLAKE2s::~BLAKE2s() { - secure_scrub_memory(m_b, sizeof(m_b)); - secure_scrub_memory(m_h, sizeof(m_h)); - secure_scrub_memory(m_t, sizeof(m_t)); + secure_scrub_memory(m_h); } } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/hash/blake2s/blake2s.h botan3-3.12.0+dfsg/src/lib/hash/blake2s/blake2s.h --- botan3-3.7.1+dfsg/src/lib/hash/blake2s/blake2s.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/blake2s/blake2s.h 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,8 @@ /* * BLAKE2s - * (C) 2023 Richard Huveneers + * (C) 2023, 2025 Richard Huveneers + * (C) 2025 Kagan Can Sit + * (C) 2025 René Meusel, Rohde & Schwarz Cybersecurity * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -9,6 +11,7 @@ #define BOTAN_BLAKE2S_H_ #include +#include namespace Botan { @@ -16,15 +19,23 @@ * BLAKE2s */ class BLAKE2s final : public HashFunction { + private: + static constexpr size_t block_size = 64; + public: explicit BLAKE2s(size_t output_bits = 256); ~BLAKE2s() override; + BLAKE2s(const BLAKE2s&) = default; + BLAKE2s& operator=(const BLAKE2s&) = delete; + BLAKE2s(BLAKE2s&&) = delete; + BLAKE2s& operator=(BLAKE2s&&) = delete; + std::string name() const override; size_t output_length() const override { return m_outlen; } - size_t hash_block_size() const override { return 64; } + size_t hash_block_size() const override { return block_size; } std::unique_ptr copy_state() const override; @@ -33,16 +44,17 @@ void clear() override; private: - void add_data(std::span) override; - void final_result(std::span) override; - void state_init(size_t outlen, const uint8_t* key, size_t keylen); - void compress(bool last); - - uint8_t m_b[64]; // input buffer - uint32_t m_h[8]; // chained state - uint32_t m_t[2]; // total number of bytes - uint8_t m_c; // pointer for b[] - size_t m_outlen; // digest size + void add_data(std::span input) override; + void final_result(std::span output) override; + void state_init(size_t outlen); + void compress(bool last, std::span buf); + + private: + uint64_t m_bytes_processed = 0; + AlignmentBuffer m_buffer; + + std::array m_h{}; // chained state + size_t m_outlen = 0; // digest size }; } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/hash/checksum/adler32/adler32.h botan3-3.12.0+dfsg/src/lib/hash/checksum/adler32/adler32.h --- botan3-3.7.1+dfsg/src/lib/hash/checksum/adler32/adler32.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/checksum/adler32/adler32.h 2026-05-07 01:38:28.000000000 +0000 @@ -30,14 +30,11 @@ m_S2 = 0; } - Adler32() { clear(); } - - ~Adler32() override { clear(); } - private: - void add_data(std::span) override; - void final_result(std::span) override; - uint16_t m_S1, m_S2; + void add_data(std::span input) override; + void final_result(std::span output) override; + uint16_t m_S1 = 1; + uint16_t m_S2 = 0; }; } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/hash/checksum/crc24/crc24.cpp botan3-3.12.0+dfsg/src/lib/hash/checksum/crc24/crc24.cpp --- botan3-3.7.1+dfsg/src/lib/hash/checksum/crc24/crc24.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/checksum/crc24/crc24.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -178,7 +178,7 @@ static const uint8_t WA = sizeof(size_t) - 1; // Ensure input is word aligned before processing in parallel - for(; !input.empty() && (reinterpret_cast(input.data()) & WA); input = input.last(input.size() - 1)) { + for(; !input.empty() && (reinterpret_cast(input.data()) & WA) > 0; input = input.last(input.size() - 1)) { tmp = process8(tmp, input.front()); } diff -Nru botan3-3.7.1+dfsg/src/lib/hash/checksum/crc24/crc24.h botan3-3.12.0+dfsg/src/lib/hash/checksum/crc24/crc24.h --- botan3-3.7.1+dfsg/src/lib/hash/checksum/crc24/crc24.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/checksum/crc24/crc24.h 2026-05-07 01:38:28.000000000 +0000 @@ -28,16 +28,12 @@ std::unique_ptr copy_state() const override; - void clear() override { m_crc = 0XCE04B7L; } - - CRC24() { clear(); } - - ~CRC24() override { clear(); } + void clear() override { m_crc = 0xCE04B7; } private: - void add_data(std::span) override; - void final_result(std::span) override; - uint32_t m_crc; + void add_data(std::span input) override; + void final_result(std::span output) override; + uint32_t m_crc = 0xCE04B7; }; } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/hash/checksum/crc32/crc32.cpp botan3-3.12.0+dfsg/src/lib/hash/checksum/crc32/crc32.cpp --- botan3-3.7.1+dfsg/src/lib/hash/checksum/crc32/crc32.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/checksum/crc32/crc32.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -50,31 +50,31 @@ * Update a CRC32 Checksum */ void CRC32::add_data(std::span input) { - uint32_t tmp = m_crc; + uint32_t crc = m_crc; for(; input.size() >= 16; input = input.last(input.size() - 16)) { - tmp = CRC32_T0[(tmp ^ input[0]) & 0xFF] ^ (tmp >> 8); - tmp = CRC32_T0[(tmp ^ input[1]) & 0xFF] ^ (tmp >> 8); - tmp = CRC32_T0[(tmp ^ input[2]) & 0xFF] ^ (tmp >> 8); - tmp = CRC32_T0[(tmp ^ input[3]) & 0xFF] ^ (tmp >> 8); - tmp = CRC32_T0[(tmp ^ input[4]) & 0xFF] ^ (tmp >> 8); - tmp = CRC32_T0[(tmp ^ input[5]) & 0xFF] ^ (tmp >> 8); - tmp = CRC32_T0[(tmp ^ input[6]) & 0xFF] ^ (tmp >> 8); - tmp = CRC32_T0[(tmp ^ input[7]) & 0xFF] ^ (tmp >> 8); - tmp = CRC32_T0[(tmp ^ input[8]) & 0xFF] ^ (tmp >> 8); - tmp = CRC32_T0[(tmp ^ input[9]) & 0xFF] ^ (tmp >> 8); - tmp = CRC32_T0[(tmp ^ input[10]) & 0xFF] ^ (tmp >> 8); - tmp = CRC32_T0[(tmp ^ input[11]) & 0xFF] ^ (tmp >> 8); - tmp = CRC32_T0[(tmp ^ input[12]) & 0xFF] ^ (tmp >> 8); - tmp = CRC32_T0[(tmp ^ input[13]) & 0xFF] ^ (tmp >> 8); - tmp = CRC32_T0[(tmp ^ input[14]) & 0xFF] ^ (tmp >> 8); - tmp = CRC32_T0[(tmp ^ input[15]) & 0xFF] ^ (tmp >> 8); + crc = CRC32_T0[(crc ^ input[0]) & 0xFF] ^ (crc >> 8); + crc = CRC32_T0[(crc ^ input[1]) & 0xFF] ^ (crc >> 8); + crc = CRC32_T0[(crc ^ input[2]) & 0xFF] ^ (crc >> 8); + crc = CRC32_T0[(crc ^ input[3]) & 0xFF] ^ (crc >> 8); + crc = CRC32_T0[(crc ^ input[4]) & 0xFF] ^ (crc >> 8); + crc = CRC32_T0[(crc ^ input[5]) & 0xFF] ^ (crc >> 8); + crc = CRC32_T0[(crc ^ input[6]) & 0xFF] ^ (crc >> 8); + crc = CRC32_T0[(crc ^ input[7]) & 0xFF] ^ (crc >> 8); + crc = CRC32_T0[(crc ^ input[8]) & 0xFF] ^ (crc >> 8); + crc = CRC32_T0[(crc ^ input[9]) & 0xFF] ^ (crc >> 8); + crc = CRC32_T0[(crc ^ input[10]) & 0xFF] ^ (crc >> 8); + crc = CRC32_T0[(crc ^ input[11]) & 0xFF] ^ (crc >> 8); + crc = CRC32_T0[(crc ^ input[12]) & 0xFF] ^ (crc >> 8); + crc = CRC32_T0[(crc ^ input[13]) & 0xFF] ^ (crc >> 8); + crc = CRC32_T0[(crc ^ input[14]) & 0xFF] ^ (crc >> 8); + crc = CRC32_T0[(crc ^ input[15]) & 0xFF] ^ (crc >> 8); } - for(size_t i = 0; i != input.size(); ++i) { - tmp = CRC32_T0[(tmp ^ input[i]) & 0xFF] ^ (tmp >> 8); + for(const uint8_t b : input) { + crc = CRC32_T0[(crc ^ b) & 0xFF] ^ (crc >> 8); } - m_crc = tmp; + m_crc = crc; } /* diff -Nru botan3-3.7.1+dfsg/src/lib/hash/checksum/crc32/crc32.h botan3-3.12.0+dfsg/src/lib/hash/checksum/crc32/crc32.h --- botan3-3.7.1+dfsg/src/lib/hash/checksum/crc32/crc32.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/checksum/crc32/crc32.h 2026-05-07 01:38:28.000000000 +0000 @@ -27,14 +27,10 @@ void clear() override { m_crc = 0xFFFFFFFF; } - CRC32() { clear(); } - - ~CRC32() override { clear(); } - private: - void add_data(std::span) override; - void final_result(std::span) override; - uint32_t m_crc; + void add_data(std::span input) override; + void final_result(std::span output) override; + uint32_t m_crc = 0xFFFFFFFF; }; } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/hash/comb4p/comb4p.cpp botan3-3.12.0+dfsg/src/lib/hash/comb4p/comb4p.cpp --- botan3-3.7.1+dfsg/src/lib/hash/comb4p/comb4p.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/comb4p/comb4p.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,8 +8,9 @@ #include #include +#include +#include #include -#include namespace Botan { diff -Nru botan3-3.7.1+dfsg/src/lib/hash/gost_3411/gost_3411.cpp botan3-3.12.0+dfsg/src/lib/hash/gost_3411/gost_3411.cpp --- botan3-3.7.1+dfsg/src/lib/hash/gost_3411/gost_3411.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/gost_3411/gost_3411.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,17 +7,15 @@ #include +#include #include -#include namespace Botan { /** * GOST 34.11 Constructor */ -GOST_34_11::GOST_34_11() : m_cipher(GOST_28147_89_Params("R3411_CryptoPro")), m_sum(32), m_hash(32) { - m_count = 0; -} +GOST_34_11::GOST_34_11() : m_cipher(GOST_28147_89_Params("R3411_CryptoPro")), m_sum(32), m_hash(32), m_count(0) {} void GOST_34_11::clear() { m_cipher.clear(); @@ -59,14 +57,15 @@ void GOST_34_11::compress_n(const uint8_t input[], size_t blocks) { for(size_t i = 0; i != blocks; ++i) { for(uint16_t j = 0, carry = 0; j != 32; ++j) { - uint16_t s = m_sum[j] + input[32 * i + j] + carry; + const uint16_t s = m_sum[j] + input[32 * i + j] + carry; carry = get_byte<0>(s); m_sum[j] = get_byte<1>(s); } uint8_t S[32] = {0}; - uint64_t U[4], V[4]; + uint64_t U[4]; + uint64_t V[4]; load_be(U, m_hash.data(), 4); load_be(V, input + 32 * i, 4); @@ -89,7 +88,7 @@ } // A(x) - uint64_t A_U = U[0]; + const uint64_t A_U = U[0]; U[0] = U[1]; U[1] = U[2]; U[2] = U[3]; @@ -104,8 +103,8 @@ } // A(A(x)) - uint64_t AA_V_1 = V[0] ^ V[1]; - uint64_t AA_V_2 = V[1] ^ V[2]; + const uint64_t AA_V_1 = V[0] ^ V[1]; + const uint64_t AA_V_2 = V[1] ^ V[2]; V[0] = V[2]; V[1] = V[3]; V[2] = AA_V_1; diff -Nru botan3-3.7.1+dfsg/src/lib/hash/gost_3411/gost_3411.h botan3-3.12.0+dfsg/src/lib/hash/gost_3411/gost_3411.h --- botan3-3.7.1+dfsg/src/lib/hash/gost_3411/gost_3411.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/gost_3411/gost_3411.h 2026-05-07 01:38:28.000000000 +0000 @@ -36,8 +36,8 @@ private: void compress_n(const uint8_t input[], size_t blocks); - void add_data(std::span) override; - void final_result(std::span) override; + void add_data(std::span input) override; + void final_result(std::span output) override; GOST_28147_89 m_cipher; AlignmentBuffer m_buffer; diff -Nru botan3-3.7.1+dfsg/src/lib/hash/hash.cpp botan3-3.12.0+dfsg/src/lib/hash/hash.cpp --- botan3-3.7.1+dfsg/src/lib/hash/hash.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/hash.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -14,6 +14,10 @@ #include #endif +#if defined(BOTAN_HAS_ASCON_HASH256) + #include +#endif + #if defined(BOTAN_HAS_CRC24) #include #endif @@ -185,6 +189,12 @@ } #endif +#if defined(BOTAN_HAS_ASCON_HASH256) + if(algo_spec == "Ascon-Hash256") { + return std::make_unique(); + } +#endif + #if defined(BOTAN_HAS_CRC24) if(algo_spec == "CRC24") { return std::make_unique(); diff -Nru botan3-3.7.1+dfsg/src/lib/hash/hash.h botan3-3.12.0+dfsg/src/lib/hash/hash.h --- botan3-3.7.1+dfsg/src/lib/hash/hash.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/hash.h 2026-05-07 01:38:28.000000000 +0000 @@ -48,8 +48,6 @@ */ virtual std::string provider() const { return "base"; } - ~HashFunction() override = default; - /** * Reset the state. */ diff -Nru botan3-3.7.1+dfsg/src/lib/hash/keccak/keccak.cpp botan3-3.12.0+dfsg/src/lib/hash/keccak/keccak.cpp --- botan3-3.7.1+dfsg/src/lib/hash/keccak/keccak.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/keccak/keccak.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,7 +9,6 @@ #include #include -#include namespace Botan { @@ -17,7 +16,9 @@ return std::make_unique(*this); } -Keccak_1600::Keccak_1600(size_t output_bits) : m_keccak(2 * output_bits, 0, 0), m_output_length(output_bits / 8) { +Keccak_1600::Keccak_1600(size_t output_bits) : + m_keccak({.capacity_bits = 2 * output_bits, .padding = KeccakPadding::keccak1600()}), + m_output_length(output_bits / 8) { // We only support the parameters for the SHA-3 proposal if(output_bits != 224 && output_bits != 256 && output_bits != 384 && output_bits != 512) { diff -Nru botan3-3.7.1+dfsg/src/lib/hash/md4/md4.cpp botan3-3.12.0+dfsg/src/lib/hash/md4/md4.cpp --- botan3-3.7.1+dfsg/src/lib/hash/md4/md4.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/md4/md4.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -75,11 +76,14 @@ * MD4 Compression Function */ void MD4::compress_n(digest_type& digest, std::span input, size_t blocks) { - uint32_t A = digest[0], B = digest[1], C = digest[2], D = digest[3]; + uint32_t A = digest[0]; + uint32_t B = digest[1]; + uint32_t C = digest[2]; + uint32_t D = digest[3]; BufferSlicer in(input); - std::array M; + std::array M{}; for(size_t i = 0; i != blocks; ++i) { load_le(M, in.take()); diff -Nru botan3-3.7.1+dfsg/src/lib/hash/md5/md5.cpp botan3-3.12.0+dfsg/src/lib/hash/md5/md5.cpp --- botan3-3.7.1+dfsg/src/lib/hash/md5/md5.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/md5/md5.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,9 +8,9 @@ #include #include +#include #include #include -#include #include @@ -60,8 +60,11 @@ * MD5 Compression Function */ void MD5::compress_n(MD5::digest_type& digest, std::span input, size_t blocks) { - uint32_t A = digest[0], B = digest[1], C = digest[2], D = digest[3]; - std::array M; + uint32_t A = digest[0]; + uint32_t B = digest[1]; + uint32_t C = digest[2]; + uint32_t D = digest[3]; + std::array M{}; BufferSlicer in(input); diff -Nru botan3-3.7.1+dfsg/src/lib/hash/mdx_hash/info.txt botan3-3.12.0+dfsg/src/lib/hash/mdx_hash/info.txt --- botan3-3.7.1+dfsg/src/lib/hash/mdx_hash/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/mdx_hash/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + MDX_HASH_FUNCTION -> 20131128 - + name -> "Merkle-Damgård Helper" diff -Nru botan3-3.7.1+dfsg/src/lib/hash/mdx_hash/mdx_hash.h botan3-3.12.0+dfsg/src/lib/hash/mdx_hash/mdx_hash.h --- botan3-3.7.1+dfsg/src/lib/hash/mdx_hash/mdx_hash.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/mdx_hash/mdx_hash.h 2026-05-07 01:38:28.000000000 +0000 @@ -12,12 +12,12 @@ #include #include +#include #include -#include namespace Botan { -enum class MD_Endian { +enum class MD_Endian : uint8_t { Little, Big, }; @@ -122,7 +122,7 @@ private: typename MD::digest_type m_digest; - uint64_t m_count; + uint64_t m_count = 0; AlignmentBuffer m_buffer; }; diff -Nru botan3-3.7.1+dfsg/src/lib/hash/par_hash/par_hash.cpp botan3-3.12.0+dfsg/src/lib/hash/par_hash/par_hash.cpp --- botan3-3.7.1+dfsg/src/lib/hash/par_hash/par_hash.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/par_hash/par_hash.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,7 +7,7 @@ #include -#include +#include #include diff -Nru botan3-3.7.1+dfsg/src/lib/hash/par_hash/par_hash.h botan3-3.12.0+dfsg/src/lib/hash/par_hash/par_hash.h --- botan3-3.7.1+dfsg/src/lib/hash/par_hash/par_hash.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/par_hash/par_hash.h 2026-05-07 01:38:28.000000000 +0000 @@ -32,11 +32,14 @@ explicit Parallel(std::vector>& hashes); Parallel(const Parallel&) = delete; + Parallel(Parallel&&) = default; Parallel& operator=(const Parallel&) = delete; + Parallel& operator=(Parallel&&) = default; + ~Parallel() override = default; private: - void add_data(std::span) override; - void final_result(std::span) override; + void add_data(std::span input) override; + void final_result(std::span output) override; std::vector> m_hashes; }; diff -Nru botan3-3.7.1+dfsg/src/lib/hash/rmd160/rmd160.cpp botan3-3.12.0+dfsg/src/lib/hash/rmd160/rmd160.cpp --- botan3-3.7.1+dfsg/src/lib/hash/rmd160/rmd160.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/rmd160/rmd160.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,9 +8,9 @@ #include #include +#include #include #include -#include #include @@ -74,17 +74,33 @@ * RIPEMD-160 Compression Function */ void RIPEMD_160::compress_n(digest_type& digest, std::span input, size_t blocks) { - const uint32_t MAGIC2 = 0x5A827999, MAGIC3 = 0x6ED9EBA1, MAGIC4 = 0x8F1BBCDC, MAGIC5 = 0xA953FD4E, - MAGIC6 = 0x50A28BE6, MAGIC7 = 0x5C4DD124, MAGIC8 = 0x6D703EF3, MAGIC9 = 0x7A6D76E9; - std::array M; + constexpr uint32_t MAGIC2 = 0x5A827999; + constexpr uint32_t MAGIC3 = 0x6ED9EBA1; + constexpr uint32_t MAGIC4 = 0x8F1BBCDC; + constexpr uint32_t MAGIC5 = 0xA953FD4E; + constexpr uint32_t MAGIC6 = 0x50A28BE6; + constexpr uint32_t MAGIC7 = 0x5C4DD124; + constexpr uint32_t MAGIC8 = 0x6D703EF3; + constexpr uint32_t MAGIC9 = 0x7A6D76E9; + + std::array M{}; BufferSlicer in(input); for(size_t i = 0; i != blocks; ++i) { load_le(M, in.take()); - uint32_t A1 = digest[0], A2 = A1, B1 = digest[1], B2 = B1, C1 = digest[2], C2 = C1, D1 = digest[3], D2 = D1, - E1 = digest[4], E2 = E1; + uint32_t A1 = digest[0]; + uint32_t B1 = digest[1]; + uint32_t C1 = digest[2]; + uint32_t D1 = digest[3]; + uint32_t E1 = digest[4]; + + uint32_t A2 = A1; + uint32_t B2 = B1; + uint32_t C2 = C1; + uint32_t D2 = D1; + uint32_t E2 = E1; // clang-format off diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha1/sha1.cpp botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1.cpp --- botan3-3.7.1+dfsg/src/lib/hash/sha1/sha1.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,55 +7,16 @@ #include -#include -#include #include #include -#include - +#include #include -namespace Botan { - -namespace SHA1_F { - -namespace { - -/* -* SHA-1 F1 Function -*/ -inline void F1(uint32_t A, uint32_t& B, uint32_t C, uint32_t D, uint32_t& E, uint32_t msg) { - E += choose(B, C, D) + msg + 0x5A827999 + rotl<5>(A); - B = rotl<30>(B); -} - -/* -* SHA-1 F2 Function -*/ -inline void F2(uint32_t A, uint32_t& B, uint32_t C, uint32_t D, uint32_t& E, uint32_t msg) { - E += (B ^ C ^ D) + msg + 0x6ED9EBA1 + rotl<5>(A); - B = rotl<30>(B); -} - -/* -* SHA-1 F3 Function -*/ -inline void F3(uint32_t A, uint32_t& B, uint32_t C, uint32_t D, uint32_t& E, uint32_t msg) { - E += majority(B, C, D) + msg + 0x8F1BBCDC + rotl<5>(A); - B = rotl<30>(B); -} - -/* -* SHA-1 F4 Function -*/ -inline void F4(uint32_t A, uint32_t& B, uint32_t C, uint32_t D, uint32_t& E, uint32_t msg) { - E += (B ^ C ^ D) + msg + 0xCA62C1D6 + rotl<5>(A); - B = rotl<30>(B); -} - -} // namespace +#if defined(BOTAN_HAS_CPUID) + #include +#endif -} // namespace SHA1_F +namespace Botan { /* * SHA-1 Compression Function @@ -64,26 +25,35 @@ using namespace SHA1_F; #if defined(BOTAN_HAS_SHA1_X86_SHA_NI) - if(CPUID::has_intel_sha()) { + if(CPUID::has(CPUID::Feature::SHA)) { return sha1_compress_x86(digest, input, blocks); } #endif #if defined(BOTAN_HAS_SHA1_ARMV8) - if(CPUID::has_arm_sha1()) { + if(CPUID::has(CPUID::Feature::SHA1)) { return sha1_armv8_compress_n(digest, input, blocks); } #endif -#if defined(BOTAN_HAS_SHA1_SSE2) - if(CPUID::has_sse2()) { - return sse2_compress_n(digest, input, blocks); +#if defined(BOTAN_HAS_SHA1_AVX2) + if(CPUID::has(CPUID::Feature::AVX2, CPUID::Feature::BMI)) { + return avx2_compress_n(digest, input, blocks); } +#endif +#if defined(BOTAN_HAS_SHA1_SIMD_4X32) + if(CPUID::has(CPUID::Feature::SIMD_4X32)) { + return simd_compress_n(digest, input, blocks); + } #endif - uint32_t A = digest[0], B = digest[1], C = digest[2], D = digest[3], E = digest[4]; - std::array W; + uint32_t A = digest[0]; + uint32_t B = digest[1]; + uint32_t C = digest[2]; + uint32_t D = digest[3]; + uint32_t E = digest[4]; + std::array W{}; auto W_in = std::span{W}.first(); BufferSlicer in(input); @@ -106,89 +76,89 @@ // clang-format on - F1(A, B, C, D, E, W[0]); - F1(E, A, B, C, D, W[1]); - F1(D, E, A, B, C, W[2]); - F1(C, D, E, A, B, W[3]); - F1(B, C, D, E, A, W[4]); - F1(A, B, C, D, E, W[5]); - F1(E, A, B, C, D, W[6]); - F1(D, E, A, B, C, W[7]); - F1(C, D, E, A, B, W[8]); - F1(B, C, D, E, A, W[9]); - F1(A, B, C, D, E, W[10]); - F1(E, A, B, C, D, W[11]); - F1(D, E, A, B, C, W[12]); - F1(C, D, E, A, B, W[13]); - F1(B, C, D, E, A, W[14]); - F1(A, B, C, D, E, W[15]); - F1(E, A, B, C, D, W[16]); - F1(D, E, A, B, C, W[17]); - F1(C, D, E, A, B, W[18]); - F1(B, C, D, E, A, W[19]); - - F2(A, B, C, D, E, W[20]); - F2(E, A, B, C, D, W[21]); - F2(D, E, A, B, C, W[22]); - F2(C, D, E, A, B, W[23]); - F2(B, C, D, E, A, W[24]); - F2(A, B, C, D, E, W[25]); - F2(E, A, B, C, D, W[26]); - F2(D, E, A, B, C, W[27]); - F2(C, D, E, A, B, W[28]); - F2(B, C, D, E, A, W[29]); - F2(A, B, C, D, E, W[30]); - F2(E, A, B, C, D, W[31]); - F2(D, E, A, B, C, W[32]); - F2(C, D, E, A, B, W[33]); - F2(B, C, D, E, A, W[34]); - F2(A, B, C, D, E, W[35]); - F2(E, A, B, C, D, W[36]); - F2(D, E, A, B, C, W[37]); - F2(C, D, E, A, B, W[38]); - F2(B, C, D, E, A, W[39]); - - F3(A, B, C, D, E, W[40]); - F3(E, A, B, C, D, W[41]); - F3(D, E, A, B, C, W[42]); - F3(C, D, E, A, B, W[43]); - F3(B, C, D, E, A, W[44]); - F3(A, B, C, D, E, W[45]); - F3(E, A, B, C, D, W[46]); - F3(D, E, A, B, C, W[47]); - F3(C, D, E, A, B, W[48]); - F3(B, C, D, E, A, W[49]); - F3(A, B, C, D, E, W[50]); - F3(E, A, B, C, D, W[51]); - F3(D, E, A, B, C, W[52]); - F3(C, D, E, A, B, W[53]); - F3(B, C, D, E, A, W[54]); - F3(A, B, C, D, E, W[55]); - F3(E, A, B, C, D, W[56]); - F3(D, E, A, B, C, W[57]); - F3(C, D, E, A, B, W[58]); - F3(B, C, D, E, A, W[59]); - - F4(A, B, C, D, E, W[60]); - F4(E, A, B, C, D, W[61]); - F4(D, E, A, B, C, W[62]); - F4(C, D, E, A, B, W[63]); - F4(B, C, D, E, A, W[64]); - F4(A, B, C, D, E, W[65]); - F4(E, A, B, C, D, W[66]); - F4(D, E, A, B, C, W[67]); - F4(C, D, E, A, B, W[68]); - F4(B, C, D, E, A, W[69]); - F4(A, B, C, D, E, W[70]); - F4(E, A, B, C, D, W[71]); - F4(D, E, A, B, C, W[72]); - F4(C, D, E, A, B, W[73]); - F4(B, C, D, E, A, W[74]); - F4(A, B, C, D, E, W[75]); - F4(E, A, B, C, D, W[76]); - F4(D, E, A, B, C, W[77]); - F4(C, D, E, A, B, W[78]); - F4(B, C, D, E, A, W[79]); + F1(A, B, C, D, E, W[0] + K1); + F1(E, A, B, C, D, W[1] + K1); + F1(D, E, A, B, C, W[2] + K1); + F1(C, D, E, A, B, W[3] + K1); + F1(B, C, D, E, A, W[4] + K1); + F1(A, B, C, D, E, W[5] + K1); + F1(E, A, B, C, D, W[6] + K1); + F1(D, E, A, B, C, W[7] + K1); + F1(C, D, E, A, B, W[8] + K1); + F1(B, C, D, E, A, W[9] + K1); + F1(A, B, C, D, E, W[10] + K1); + F1(E, A, B, C, D, W[11] + K1); + F1(D, E, A, B, C, W[12] + K1); + F1(C, D, E, A, B, W[13] + K1); + F1(B, C, D, E, A, W[14] + K1); + F1(A, B, C, D, E, W[15] + K1); + F1(E, A, B, C, D, W[16] + K1); + F1(D, E, A, B, C, W[17] + K1); + F1(C, D, E, A, B, W[18] + K1); + F1(B, C, D, E, A, W[19] + K1); + + F2(A, B, C, D, E, W[20] + K2); + F2(E, A, B, C, D, W[21] + K2); + F2(D, E, A, B, C, W[22] + K2); + F2(C, D, E, A, B, W[23] + K2); + F2(B, C, D, E, A, W[24] + K2); + F2(A, B, C, D, E, W[25] + K2); + F2(E, A, B, C, D, W[26] + K2); + F2(D, E, A, B, C, W[27] + K2); + F2(C, D, E, A, B, W[28] + K2); + F2(B, C, D, E, A, W[29] + K2); + F2(A, B, C, D, E, W[30] + K2); + F2(E, A, B, C, D, W[31] + K2); + F2(D, E, A, B, C, W[32] + K2); + F2(C, D, E, A, B, W[33] + K2); + F2(B, C, D, E, A, W[34] + K2); + F2(A, B, C, D, E, W[35] + K2); + F2(E, A, B, C, D, W[36] + K2); + F2(D, E, A, B, C, W[37] + K2); + F2(C, D, E, A, B, W[38] + K2); + F2(B, C, D, E, A, W[39] + K2); + + F3(A, B, C, D, E, W[40] + K3); + F3(E, A, B, C, D, W[41] + K3); + F3(D, E, A, B, C, W[42] + K3); + F3(C, D, E, A, B, W[43] + K3); + F3(B, C, D, E, A, W[44] + K3); + F3(A, B, C, D, E, W[45] + K3); + F3(E, A, B, C, D, W[46] + K3); + F3(D, E, A, B, C, W[47] + K3); + F3(C, D, E, A, B, W[48] + K3); + F3(B, C, D, E, A, W[49] + K3); + F3(A, B, C, D, E, W[50] + K3); + F3(E, A, B, C, D, W[51] + K3); + F3(D, E, A, B, C, W[52] + K3); + F3(C, D, E, A, B, W[53] + K3); + F3(B, C, D, E, A, W[54] + K3); + F3(A, B, C, D, E, W[55] + K3); + F3(E, A, B, C, D, W[56] + K3); + F3(D, E, A, B, C, W[57] + K3); + F3(C, D, E, A, B, W[58] + K3); + F3(B, C, D, E, A, W[59] + K3); + + F4(A, B, C, D, E, W[60] + K4); + F4(E, A, B, C, D, W[61] + K4); + F4(D, E, A, B, C, W[62] + K4); + F4(C, D, E, A, B, W[63] + K4); + F4(B, C, D, E, A, W[64] + K4); + F4(A, B, C, D, E, W[65] + K4); + F4(E, A, B, C, D, W[66] + K4); + F4(D, E, A, B, C, W[67] + K4); + F4(C, D, E, A, B, W[68] + K4); + F4(B, C, D, E, A, W[69] + K4); + F4(A, B, C, D, E, W[70] + K4); + F4(E, A, B, C, D, W[71] + K4); + F4(D, E, A, B, C, W[72] + K4); + F4(C, D, E, A, B, W[73] + K4); + F4(B, C, D, E, A, W[74] + K4); + F4(A, B, C, D, E, W[75] + K4); + F4(E, A, B, C, D, W[76] + K4); + F4(D, E, A, B, C, W[77] + K4); + F4(C, D, E, A, B, W[78] + K4); + F4(B, C, D, E, A, W[79] + K4); A = (digest[0] += A); B = (digest[1] += B); @@ -207,20 +177,26 @@ std::string SHA_1::provider() const { #if defined(BOTAN_HAS_SHA1_X86_SHA_NI) - if(CPUID::has_intel_sha()) { - return "intel_sha"; + if(auto feat = CPUID::check(CPUID::Feature::SHA)) { + return *feat; } #endif #if defined(BOTAN_HAS_SHA1_ARMV8) - if(CPUID::has_arm_sha1()) { - return "armv8_sha"; + if(auto feat = CPUID::check(CPUID::Feature::SHA1)) { + return *feat; + } +#endif + +#if defined(BOTAN_HAS_SHA1_AVX2) + if(auto feat = CPUID::check(CPUID::Feature::AVX2, CPUID::Feature::BMI)) { + return *feat; } #endif -#if defined(BOTAN_HAS_SHA1_SSE2) - if(CPUID::has_sse2()) { - return "sse2"; +#if defined(BOTAN_HAS_SHA1_SIMD_4X32) + if(auto feat = CPUID::check(CPUID::Feature::SIMD_4X32)) { + return *feat; } #endif diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha1/sha1.h botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1.h --- botan3-3.7.1+dfsg/src/lib/hash/sha1/sha1.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1.h 2026-05-07 01:38:28.000000000 +0000 @@ -47,8 +47,12 @@ static void sha1_armv8_compress_n(digest_type& digest, std::span blocks, size_t block_count); #endif -#if defined(BOTAN_HAS_SHA1_SSE2) - static void sse2_compress_n(digest_type& digest, std::span blocks, size_t block_count); +#if defined(BOTAN_HAS_SHA1_SIMD_4X32) + static void simd_compress_n(digest_type& digest, std::span blocks, size_t block_count); +#endif + +#if defined(BOTAN_HAS_SHA1_AVX2) + static void avx2_compress_n(digest_type& digest, std::span blocks, size_t block_count); #endif #if defined(BOTAN_HAS_SHA1_X86_SHA_NI) diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha1/sha1_armv8/info.txt botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1_armv8/info.txt --- botan3-3.7.1+dfsg/src/lib/hash/sha1/sha1_armv8/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1_armv8/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + SHA1_ARMV8 -> 20170117 - + name -> "SHA-1 ARMv8" @@ -10,3 +10,7 @@ armv8crypto + + +cpuid + diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha1/sha1_armv8/sha1_armv8.cpp botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1_armv8/sha1_armv8.cpp --- botan3-3.7.1+dfsg/src/lib/hash/sha1/sha1_armv8/sha1_armv8.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1_armv8/sha1_armv8.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,6 +8,8 @@ */ #include + +#include #include namespace Botan { @@ -16,47 +18,40 @@ * SHA-1 using CPU instructions in ARMv8 */ //static -BOTAN_FUNC_ISA("+crypto+sha2") -void SHA_1::sha1_armv8_compress_n(digest_type& digest, std::span input8, size_t blocks) { - uint32x4_t ABCD; - uint32_t E0; - +void BOTAN_FN_ISA_SHA2 SHA_1::sha1_armv8_compress_n(digest_type& digest, + std::span input8, + size_t blocks) { // Load magic constants const uint32x4_t C0 = vdupq_n_u32(0x5A827999); const uint32x4_t C1 = vdupq_n_u32(0x6ED9EBA1); const uint32x4_t C2 = vdupq_n_u32(0x8F1BBCDC); const uint32x4_t C3 = vdupq_n_u32(0xCA62C1D6); - ABCD = vld1q_u32(&digest[0]); - E0 = digest[4]; + uint32x4_t ABCD = vld1q_u32(&digest[0]); // NOLINT(*-container-data-pointer) + uint32_t E0 = digest[4]; - // Intermediate void* cast due to https://llvm.org/bugs/show_bug.cgi?id=20670 - const uint32_t* input32 = reinterpret_cast(reinterpret_cast(input8.data())); + const uint32_t* input32 = reinterpret_cast(input8.data()); - while(blocks) { + while(blocks > 0) { // Save current hash const uint32x4_t ABCD_SAVED = ABCD; const uint32_t E0_SAVED = E0; - uint32x4_t MSG0, MSG1, MSG2, MSG3; - uint32x4_t TMP0, TMP1; - uint32_t E1; - - MSG0 = vld1q_u32(input32 + 0); - MSG1 = vld1q_u32(input32 + 4); - MSG2 = vld1q_u32(input32 + 8); - MSG3 = vld1q_u32(input32 + 12); + uint32x4_t MSG0 = vld1q_u32(input32 + 0); + uint32x4_t MSG1 = vld1q_u32(input32 + 4); + uint32x4_t MSG2 = vld1q_u32(input32 + 8); + uint32x4_t MSG3 = vld1q_u32(input32 + 12); MSG0 = vreinterpretq_u32_u8(vrev32q_u8(vreinterpretq_u8_u32(MSG0))); MSG1 = vreinterpretq_u32_u8(vrev32q_u8(vreinterpretq_u8_u32(MSG1))); MSG2 = vreinterpretq_u32_u8(vrev32q_u8(vreinterpretq_u8_u32(MSG2))); MSG3 = vreinterpretq_u32_u8(vrev32q_u8(vreinterpretq_u8_u32(MSG3))); - TMP0 = vaddq_u32(MSG0, C0); - TMP1 = vaddq_u32(MSG1, C0); + uint32x4_t TMP0 = vaddq_u32(MSG0, C0); + uint32x4_t TMP1 = vaddq_u32(MSG1, C0); // Rounds 0-3 - E1 = vsha1h_u32(vgetq_lane_u32(ABCD, 0)); + uint32_t E1 = vsha1h_u32(vgetq_lane_u32(ABCD, 0)); ABCD = vsha1cq_u32(ABCD, E0, TMP0); TMP0 = vaddq_u32(MSG2, C0); MSG0 = vsha1su0q_u32(MSG0, MSG1, MSG2); @@ -177,7 +172,6 @@ E0 = vsha1h_u32(vgetq_lane_u32(ABCD, 0)); ABCD = vsha1pq_u32(ABCD, E1, TMP1); TMP1 = vaddq_u32(MSG3, C3); - MSG0 = vsha1su1q_u32(MSG0, MSG3); // Rounds 72-75 E1 = vsha1h_u32(vgetq_lane_u32(ABCD, 0)); @@ -196,7 +190,7 @@ } // Save digest - vst1q_u32(&digest[0], ABCD); + vst1q_u32(&digest[0], ABCD); // NOLINT(*-container-data-pointer) digest[4] = E0; } diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha1/sha1_avx2/info.txt botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1_avx2/info.txt --- botan3-3.7.1+dfsg/src/lib/hash/sha1/sha1_avx2/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1_avx2/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,24 @@ + +SHA1_AVX2 -> 20250505 + + + +name -> "SHA-1 AVX2/BMI2" +brief -> "SHA-1 using AVX2/BMI2 instructions" + + + +avx2 +bmi2 + + + +x86_32 +x86_64 +x32 + + + +cpuid +simd_avx2 + diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha1/sha1_avx2/sha1_avx2.cpp botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1_avx2/sha1_avx2.cpp --- botan3-3.7.1+dfsg/src/lib/hash/sha1/sha1_avx2/sha1_avx2.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1_avx2/sha1_avx2.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,554 @@ +/* +* (C) 2025 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include + +namespace Botan { + +namespace { + +/* +* This is exactly the same approach as used in sha1_simd.cpp, just done +* twice in the two AVX2 "lanes" - remember that alignr and slli/srli +* here are working not across the entire register but instead as if +* there were two smaller vectors. +*/ +BOTAN_FN_ISA_AVX2_BMI2 BOTAN_FORCE_INLINE SIMD_8x32 sha1_avx2_next_w(SIMD_8x32& XW0, + SIMD_8x32 XW1, + SIMD_8x32 XW2, + SIMD_8x32 XW3) { + SIMD_8x32 T0 = XW0; // W[t-16..t-13] + T0 ^= SIMD_8x32(_mm256_alignr_epi8(XW1.raw(), XW0.raw(), 8)); + T0 ^= XW2; // W[t-8..t-5] + T0 ^= SIMD_8x32(_mm256_srli_si256(XW3.raw(), 4)); // W[t-3..t-1] || 0 + + /* unrotated W[t]..W[t+2] in T0 ... still need W[t+3] */ + + // Extract w[t+0] into T2 + auto T2 = SIMD_8x32(_mm256_slli_si256(T0.raw(), 3 * 4)); + + // Main rotation + T0 = T0.rotl<1>(); + + // Rotation of W[t+3] has rot by 2 to account for us working on non-rotated words + T2 = T2.rotl<2>(); + + // Merge rol(W[t+0], 1) into W[t+3] + T0 ^= T2; + + XW0 = T0; + return T0; +} + +/* +* Helper for word permutation with zeroing because AVX2 is awful +* +* Clang and GCC both compile this to a couple of stored constants plus +* a vpermd/vpand pair. +*/ +template +BOTAN_FN_ISA_AVX2_BMI2 BOTAN_FORCE_INLINE SIMD_8x32 permute_words(SIMD_8x32 v) { + const __m256i tbl = _mm256_setr_epi32(I0, I1, I2, I3, I4, I5, I6, I7); + const __m256i mask = _mm256_setr_epi32(I0 >= 0 ? 0xFFFFFFFF : 0, + I1 >= 0 ? 0xFFFFFFFF : 0, + I2 >= 0 ? 0xFFFFFFFF : 0, + I3 >= 0 ? 0xFFFFFFFF : 0, + I4 >= 0 ? 0xFFFFFFFF : 0, + I5 >= 0 ? 0xFFFFFFFF : 0, + I6 >= 0 ? 0xFFFFFFFF : 0, + I7 >= 0 ? 0xFFFFFFFF : 0); + + return SIMD_8x32(_mm256_and_si256(mask, _mm256_permutevar8x32_epi32(v.raw(), tbl))); +} + +/* +This is the same approach as the (single buffer) SHA-1 expansion in sha1_simd.cpp +except unrolled further; instead of computing 4 words of W at once, we compute 8. + +However this is complicated both by the SHA-1 recurrence and AVX2 +limitations; it is faster than what's done in sha1_simd.cpp but only just barely. + +The basic idea here is that when computing this (8x per message block): + +W[j + 0] = rotl<1>(W[j - 3] ^ W[j - 8] ^ W[j - 14] ^ W[j - 16]); +W[j + 1] = rotl<1>(W[j - 2] ^ W[j - 7] ^ W[j - 13] ^ W[j - 15]); +W[j + 2] = rotl<1>(W[j - 1] ^ W[j - 6] ^ W[j - 12] ^ W[j - 14]); +W[j + 3] = rotl<1>(W[j ] ^ W[j - 5] ^ W[j - 11] ^ W[j - 13]); +W[j + 4] = rotl<1>(W[j + 1] ^ W[j - 4] ^ W[j - 10] ^ W[j - 12]); +W[j + 5] = rotl<1>(W[j + 2] ^ W[j - 3] ^ W[j - 9] ^ W[j - 11]); +W[j + 6] = rotl<1>(W[j + 3] ^ W[j - 2] ^ W[j - 8] ^ W[j - 10]); +W[j + 7] = rotl<1>(W[j + 4] ^ W[j - 1] ^ W[j - 7] ^ W[j - 9]); + +We instead compute a partial expansion: + +W[j + 0] = rotl<1>(W[j - 3] ^ W[j - 8] ^ W[j - 14] ^ W[j - 16]); +W[j + 1] = rotl<1>(W[j - 2] ^ W[j - 7] ^ W[j - 13] ^ W[j - 15]); +W[j + 2] = rotl<1>(W[j - 1] ^ W[j - 6] ^ W[j - 12] ^ W[j - 14]); +W[j + 3] = rotl<1>( W[j - 5] ^ W[j - 11] ^ W[j - 13]); +W[j + 4] = rotl<1>( W[j - 4] ^ W[j - 10] ^ W[j - 12]); +W[j + 5] = rotl<1>( W[j - 3] ^ W[j - 9] ^ W[j - 11]); +W[j + 6] = rotl<1>( W[j - 2] ^ W[j - 8] ^ W[j - 10]); +W[j + 7] = rotl<1>( W[j - 1] ^ W[j - 7] ^ W[j - 9]); + +Then update it with values that were not available until the first expansion is +completed: + +W[j + 3] ^= rotl<1>(W[j ]); +W[j + 4] ^= rotl<1>(W[j + 1]); +W[j + 5] ^= rotl<1>(W[j + 2]); + +And then update again with values not available until the second expansion step +is completed: + +W[j + 6] ^= rotl<1>(W[j + 3]); +W[j + 7] ^= rotl<1>(W[j + 4]); +*/ + +BOTAN_FN_ISA_AVX2_BMI2 BOTAN_FORCE_INLINE SIMD_8x32 sha1_avx2_next_w2(SIMD_8x32& W0, SIMD_8x32 W2) { + // W[j-16..j-9] ^ W[j-8...j-1] + auto WN = W0 ^ W2; + + // XOR in W[j-3..j-1] || 0 || 0 || 0 || W[j-8...j-7] + WN ^= permute_words<5, 6, 7, -1, -1, -1, 0, 1>(W2); + + // XOR in W[j-14...j-9] || 0 || 0 + WN ^= permute_words<2, 3, 4, 5, 6, 7, -1, -1>(W0); + + // Extract W[j...j+2], rotate, and XOR into W[j+3...j+5] + auto T0 = permute_words<-1, -1, -1, 0, 1, 2, -1, -1>(WN).rotl<2>(); + WN = WN.rotl<1>(); // main block rotation + + WN ^= T0; + + // Extract W[j+3...j+4], rotate, and XOR into W[j+6...j+7] + WN ^= permute_words<-1, -1, -1, -1, -1, -1, 3, 4>(WN).rotl<1>(); + + W0 = WN; + return WN; +} + +} // namespace + +/* +* SHA-1 Compression Function using SIMD for message expansion +*/ +//static +void BOTAN_FN_ISA_AVX2_BMI2 SHA_1::avx2_compress_n(digest_type& digest, std::span input, size_t blocks) { + using namespace SHA1_F; + + const SIMD_8x32 K11 = SIMD_8x32::splat(K1); + const SIMD_8x32 K22 = SIMD_8x32::splat(K2); + const SIMD_8x32 K33 = SIMD_8x32::splat(K3); + const SIMD_8x32 K44 = SIMD_8x32::splat(K4); + + const SIMD_8x32 K12(K1, K1, K1, K1, K2, K2, K2, K2); + const SIMD_8x32 K34(K3, K3, K3, K3, K4, K4, K4, K4); + + uint32_t A = digest[0]; + uint32_t B = digest[1]; + uint32_t C = digest[2]; + uint32_t D = digest[3]; + uint32_t E = digest[4]; + + BufferSlicer in(input); + + while(blocks >= 2) { + const auto block = in.take(2 * block_bytes); + blocks -= 2; + + uint32_t W2[80] = {0}; + + uint32_t PT[4]; + + // NOLINTNEXTLINE(*-container-data-pointer) + SIMD_8x32 XW0 = SIMD_8x32::load_be128(&block[0], &block[64]); + SIMD_8x32 XW1 = SIMD_8x32::load_be128(&block[16], &block[80]); + SIMD_8x32 XW2 = SIMD_8x32::load_be128(&block[32], &block[96]); + SIMD_8x32 XW3 = SIMD_8x32::load_be128(&block[48], &block[112]); + + SIMD_8x32 P0 = XW0 + SIMD_8x32::splat(K1); + SIMD_8x32 P1 = XW1 + SIMD_8x32::splat(K1); + SIMD_8x32 P2 = XW2 + SIMD_8x32::splat(K1); + SIMD_8x32 P3 = XW3 + SIMD_8x32::splat(K1); + + // NOLINTBEGIN(readability-suspicious-call-argument) XW rotation + + P0.store_le128(PT, &W2[0]); + P0 = sha1_avx2_next_w(XW0, XW1, XW2, XW3) + SIMD_8x32::splat(K1); + F1(A, B, C, D, E, PT[0]); + F1(E, A, B, C, D, PT[1]); + F1(D, E, A, B, C, PT[2]); + F1(C, D, E, A, B, PT[3]); + + P1.store_le128(PT, &W2[4]); + P1 = sha1_avx2_next_w(XW1, XW2, XW3, XW0) + SIMD_8x32::splat(K2); + F1(B, C, D, E, A, PT[0]); + F1(A, B, C, D, E, PT[1]); + F1(E, A, B, C, D, PT[2]); + F1(D, E, A, B, C, PT[3]); + + P2.store_le128(PT, &W2[8]); + P2 = sha1_avx2_next_w(XW2, XW3, XW0, XW1) + SIMD_8x32::splat(K2); + F1(C, D, E, A, B, PT[0]); + F1(B, C, D, E, A, PT[1]); + F1(A, B, C, D, E, PT[2]); + F1(E, A, B, C, D, PT[3]); + + P3.store_le128(PT, &W2[12]); + P3 = sha1_avx2_next_w(XW3, XW0, XW1, XW2) + SIMD_8x32::splat(K2); + F1(D, E, A, B, C, PT[0]); + F1(C, D, E, A, B, PT[1]); + F1(B, C, D, E, A, PT[2]); + F1(A, B, C, D, E, PT[3]); + + P0.store_le128(PT, &W2[16]); + P0 = sha1_avx2_next_w(XW0, XW1, XW2, XW3) + SIMD_8x32::splat(K2); + F1(E, A, B, C, D, PT[0]); + F1(D, E, A, B, C, PT[1]); + F1(C, D, E, A, B, PT[2]); + F1(B, C, D, E, A, PT[3]); + + P1.store_le128(PT, &W2[20]); + P1 = sha1_avx2_next_w(XW1, XW2, XW3, XW0) + SIMD_8x32::splat(K2); + F2(A, B, C, D, E, PT[0]); + F2(E, A, B, C, D, PT[1]); + F2(D, E, A, B, C, PT[2]); + F2(C, D, E, A, B, PT[3]); + + P2.store_le128(PT, &W2[24]); + P2 = sha1_avx2_next_w(XW2, XW3, XW0, XW1) + SIMD_8x32::splat(K3); + F2(B, C, D, E, A, PT[0]); + F2(A, B, C, D, E, PT[1]); + F2(E, A, B, C, D, PT[2]); + F2(D, E, A, B, C, PT[3]); + + P3.store_le128(PT, &W2[28]); + P3 = sha1_avx2_next_w(XW3, XW0, XW1, XW2) + SIMD_8x32::splat(K3); + F2(C, D, E, A, B, PT[0]); + F2(B, C, D, E, A, PT[1]); + F2(A, B, C, D, E, PT[2]); + F2(E, A, B, C, D, PT[3]); + + P0.store_le128(PT, &W2[32]); + P0 = sha1_avx2_next_w(XW0, XW1, XW2, XW3) + SIMD_8x32::splat(K3); + F2(D, E, A, B, C, PT[0]); + F2(C, D, E, A, B, PT[1]); + F2(B, C, D, E, A, PT[2]); + F2(A, B, C, D, E, PT[3]); + + P1.store_le128(PT, &W2[36]); + P1 = sha1_avx2_next_w(XW1, XW2, XW3, XW0) + SIMD_8x32::splat(K3); + F2(E, A, B, C, D, PT[0]); + F2(D, E, A, B, C, PT[1]); + F2(C, D, E, A, B, PT[2]); + F2(B, C, D, E, A, PT[3]); + + P2.store_le128(PT, &W2[40]); + P2 = sha1_avx2_next_w(XW2, XW3, XW0, XW1) + SIMD_8x32::splat(K3); + F3(A, B, C, D, E, PT[0]); + F3(E, A, B, C, D, PT[1]); + F3(D, E, A, B, C, PT[2]); + F3(C, D, E, A, B, PT[3]); + + P3.store_le128(PT, &W2[44]); + P3 = sha1_avx2_next_w(XW3, XW0, XW1, XW2) + SIMD_8x32::splat(K4); + F3(B, C, D, E, A, PT[0]); + F3(A, B, C, D, E, PT[1]); + F3(E, A, B, C, D, PT[2]); + F3(D, E, A, B, C, PT[3]); + + P0.store_le128(PT, &W2[48]); + P0 = sha1_avx2_next_w(XW0, XW1, XW2, XW3) + SIMD_8x32::splat(K4); + F3(C, D, E, A, B, PT[0]); + F3(B, C, D, E, A, PT[1]); + F3(A, B, C, D, E, PT[2]); + F3(E, A, B, C, D, PT[3]); + + P1.store_le128(PT, &W2[52]); + P1 = sha1_avx2_next_w(XW1, XW2, XW3, XW0) + SIMD_8x32::splat(K4); + F3(D, E, A, B, C, PT[0]); + F3(C, D, E, A, B, PT[1]); + F3(B, C, D, E, A, PT[2]); + F3(A, B, C, D, E, PT[3]); + + P2.store_le128(PT, &W2[56]); + P2 = sha1_avx2_next_w(XW2, XW3, XW0, XW1) + SIMD_8x32::splat(K4); + F3(E, A, B, C, D, PT[0]); + F3(D, E, A, B, C, PT[1]); + F3(C, D, E, A, B, PT[2]); + F3(B, C, D, E, A, PT[3]); + + P3.store_le128(PT, &W2[60]); + P3 = sha1_avx2_next_w(XW3, XW0, XW1, XW2) + SIMD_8x32::splat(K4); + F4(A, B, C, D, E, PT[0]); + F4(E, A, B, C, D, PT[1]); + F4(D, E, A, B, C, PT[2]); + F4(C, D, E, A, B, PT[3]); + + P0.store_le128(PT, &W2[64]); + F4(B, C, D, E, A, PT[0]); + F4(A, B, C, D, E, PT[1]); + F4(E, A, B, C, D, PT[2]); + F4(D, E, A, B, C, PT[3]); + + P1.store_le128(PT, &W2[68]); + F4(C, D, E, A, B, PT[0]); + F4(B, C, D, E, A, PT[1]); + F4(A, B, C, D, E, PT[2]); + F4(E, A, B, C, D, PT[3]); + + P2.store_le128(PT, &W2[72]); + F4(D, E, A, B, C, PT[0]); + F4(C, D, E, A, B, PT[1]); + F4(B, C, D, E, A, PT[2]); + F4(A, B, C, D, E, PT[3]); + + P3.store_le128(PT, &W2[76]); + F4(E, A, B, C, D, PT[0]); + F4(D, E, A, B, C, PT[1]); + F4(C, D, E, A, B, PT[2]); + F4(B, C, D, E, A, PT[3]); + + // NOLINTEND(readability-suspicious-call-argument) + + A = (digest[0] += A); + B = (digest[1] += B); + C = (digest[2] += C); + D = (digest[3] += D); + E = (digest[4] += E); + + // Second block with pre-expanded message + F1(A, B, C, D, E, W2[0]); + F1(E, A, B, C, D, W2[1]); + F1(D, E, A, B, C, W2[2]); + F1(C, D, E, A, B, W2[3]); + F1(B, C, D, E, A, W2[4]); + F1(A, B, C, D, E, W2[5]); + F1(E, A, B, C, D, W2[6]); + F1(D, E, A, B, C, W2[7]); + F1(C, D, E, A, B, W2[8]); + F1(B, C, D, E, A, W2[9]); + F1(A, B, C, D, E, W2[10]); + F1(E, A, B, C, D, W2[11]); + F1(D, E, A, B, C, W2[12]); + F1(C, D, E, A, B, W2[13]); + F1(B, C, D, E, A, W2[14]); + F1(A, B, C, D, E, W2[15]); + F1(E, A, B, C, D, W2[16]); + F1(D, E, A, B, C, W2[17]); + F1(C, D, E, A, B, W2[18]); + F1(B, C, D, E, A, W2[19]); + F2(A, B, C, D, E, W2[20]); + F2(E, A, B, C, D, W2[21]); + F2(D, E, A, B, C, W2[22]); + F2(C, D, E, A, B, W2[23]); + F2(B, C, D, E, A, W2[24]); + F2(A, B, C, D, E, W2[25]); + F2(E, A, B, C, D, W2[26]); + F2(D, E, A, B, C, W2[27]); + F2(C, D, E, A, B, W2[28]); + F2(B, C, D, E, A, W2[29]); + F2(A, B, C, D, E, W2[30]); + F2(E, A, B, C, D, W2[31]); + F2(D, E, A, B, C, W2[32]); + F2(C, D, E, A, B, W2[33]); + F2(B, C, D, E, A, W2[34]); + F2(A, B, C, D, E, W2[35]); + F2(E, A, B, C, D, W2[36]); + F2(D, E, A, B, C, W2[37]); + F2(C, D, E, A, B, W2[38]); + F2(B, C, D, E, A, W2[39]); + F3(A, B, C, D, E, W2[40]); + F3(E, A, B, C, D, W2[41]); + F3(D, E, A, B, C, W2[42]); + F3(C, D, E, A, B, W2[43]); + F3(B, C, D, E, A, W2[44]); + F3(A, B, C, D, E, W2[45]); + F3(E, A, B, C, D, W2[46]); + F3(D, E, A, B, C, W2[47]); + F3(C, D, E, A, B, W2[48]); + F3(B, C, D, E, A, W2[49]); + F3(A, B, C, D, E, W2[50]); + F3(E, A, B, C, D, W2[51]); + F3(D, E, A, B, C, W2[52]); + F3(C, D, E, A, B, W2[53]); + F3(B, C, D, E, A, W2[54]); + F3(A, B, C, D, E, W2[55]); + F3(E, A, B, C, D, W2[56]); + F3(D, E, A, B, C, W2[57]); + F3(C, D, E, A, B, W2[58]); + F3(B, C, D, E, A, W2[59]); + F4(A, B, C, D, E, W2[60]); + F4(E, A, B, C, D, W2[61]); + F4(D, E, A, B, C, W2[62]); + F4(C, D, E, A, B, W2[63]); + F4(B, C, D, E, A, W2[64]); + F4(A, B, C, D, E, W2[65]); + F4(E, A, B, C, D, W2[66]); + F4(D, E, A, B, C, W2[67]); + F4(C, D, E, A, B, W2[68]); + F4(B, C, D, E, A, W2[69]); + F4(A, B, C, D, E, W2[70]); + F4(E, A, B, C, D, W2[71]); + F4(D, E, A, B, C, W2[72]); + F4(C, D, E, A, B, W2[73]); + F4(B, C, D, E, A, W2[74]); + F4(A, B, C, D, E, W2[75]); + F4(E, A, B, C, D, W2[76]); + F4(D, E, A, B, C, W2[77]); + F4(C, D, E, A, B, W2[78]); + F4(B, C, D, E, A, W2[79]); + + A = (digest[0] += A); + B = (digest[1] += B); + C = (digest[2] += C); + D = (digest[3] += D); + E = (digest[4] += E); + } + + for(size_t i = 0; i != blocks; ++i) { + uint32_t PT[8]; + + const auto block = in.take(block_bytes); + + SIMD_8x32 W0 = SIMD_8x32::load_be(&block[0]); // NOLINT(*-container-data-pointer) + SIMD_8x32 W2 = SIMD_8x32::load_be(&block[32]); + + SIMD_8x32 P0 = W0 + K11; + SIMD_8x32 P2 = W2 + K11; + + P0.store_le(PT); + P0 = sha1_avx2_next_w2(W0, W2) + K12; + + F1(A, B, C, D, E, PT[0]); + F1(E, A, B, C, D, PT[1]); + F1(D, E, A, B, C, PT[2]); + F1(C, D, E, A, B, PT[3]); + F1(B, C, D, E, A, PT[4]); + F1(A, B, C, D, E, PT[5]); + F1(E, A, B, C, D, PT[6]); + F1(D, E, A, B, C, PT[7]); + + P2.store_le(PT); + P2 = sha1_avx2_next_w2(W2, W0) + K22; + + F1(C, D, E, A, B, PT[0]); + F1(B, C, D, E, A, PT[1]); + F1(A, B, C, D, E, PT[2]); + F1(E, A, B, C, D, PT[3]); + F1(D, E, A, B, C, PT[4]); + F1(C, D, E, A, B, PT[5]); + F1(B, C, D, E, A, PT[6]); + F1(A, B, C, D, E, PT[7]); + + P0.store_le(PT); + P0 = sha1_avx2_next_w2(W0, W2) + K22; + + F1(E, A, B, C, D, PT[0]); + F1(D, E, A, B, C, PT[1]); + F1(C, D, E, A, B, PT[2]); + F1(B, C, D, E, A, PT[3]); + F2(A, B, C, D, E, PT[4]); + F2(E, A, B, C, D, PT[5]); + F2(D, E, A, B, C, PT[6]); + F2(C, D, E, A, B, PT[7]); + + P2.store_le(PT); + P2 = sha1_avx2_next_w2(W2, W0) + K33; + + F2(B, C, D, E, A, PT[0]); + F2(A, B, C, D, E, PT[1]); + F2(E, A, B, C, D, PT[2]); + F2(D, E, A, B, C, PT[3]); + F2(C, D, E, A, B, PT[4]); + F2(B, C, D, E, A, PT[5]); + F2(A, B, C, D, E, PT[6]); + F2(E, A, B, C, D, PT[7]); + + P0.store_le(PT); + P0 = sha1_avx2_next_w2(W0, W2) + K33; + + F2(D, E, A, B, C, PT[0]); + F2(C, D, E, A, B, PT[1]); + F2(B, C, D, E, A, PT[2]); + F2(A, B, C, D, E, PT[3]); + F2(E, A, B, C, D, PT[4]); + F2(D, E, A, B, C, PT[5]); + F2(C, D, E, A, B, PT[6]); + F2(B, C, D, E, A, PT[7]); + + P2.store_le(PT); + P2 = sha1_avx2_next_w2(W2, W0) + K34; + + F3(A, B, C, D, E, PT[0]); + F3(E, A, B, C, D, PT[1]); + F3(D, E, A, B, C, PT[2]); + F3(C, D, E, A, B, PT[3]); + F3(B, C, D, E, A, PT[4]); + F3(A, B, C, D, E, PT[5]); + F3(E, A, B, C, D, PT[6]); + F3(D, E, A, B, C, PT[7]); + + P0.store_le(PT); + P0 = sha1_avx2_next_w2(W0, W2) + K44; + + F3(C, D, E, A, B, PT[0]); + F3(B, C, D, E, A, PT[1]); + F3(A, B, C, D, E, PT[2]); + F3(E, A, B, C, D, PT[3]); + F3(D, E, A, B, C, PT[4]); + F3(C, D, E, A, B, PT[5]); + F3(B, C, D, E, A, PT[6]); + F3(A, B, C, D, E, PT[7]); + + P2.store_le(PT); + P2 = sha1_avx2_next_w2(W2, W0) + K44; + + F3(E, A, B, C, D, PT[0]); + F3(D, E, A, B, C, PT[1]); + F3(C, D, E, A, B, PT[2]); + F3(B, C, D, E, A, PT[3]); + F4(A, B, C, D, E, PT[4]); + F4(E, A, B, C, D, PT[5]); + F4(D, E, A, B, C, PT[6]); + F4(C, D, E, A, B, PT[7]); + + P0.store_le(PT); + + F4(B, C, D, E, A, PT[0]); + F4(A, B, C, D, E, PT[1]); + F4(E, A, B, C, D, PT[2]); + F4(D, E, A, B, C, PT[3]); + F4(C, D, E, A, B, PT[4]); + F4(B, C, D, E, A, PT[5]); + F4(A, B, C, D, E, PT[6]); + F4(E, A, B, C, D, PT[7]); + + P2.store_le(PT); + + F4(D, E, A, B, C, PT[0]); + F4(C, D, E, A, B, PT[1]); + F4(B, C, D, E, A, PT[2]); + F4(A, B, C, D, E, PT[3]); + F4(E, A, B, C, D, PT[4]); + F4(D, E, A, B, C, PT[5]); + F4(C, D, E, A, B, PT[6]); + F4(B, C, D, E, A, PT[7]); + + A = (digest[0] += A); + B = (digest[1] += B); + C = (digest[2] += C); + D = (digest[3] += D); + E = (digest[4] += E); + } +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha1/sha1_f.h botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1_f.h --- botan3-3.7.1+dfsg/src/lib/hash/sha1/sha1_f.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1_f.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,44 @@ +/* +* (C) 2025 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_SHA1_FN_H_ +#define BOTAN_SHA1_FN_H_ + +#include +#include +#include + +namespace Botan::SHA1_F { + +constexpr uint32_t K1 = 0x5A827999; +constexpr uint32_t K2 = 0x6ED9EBA1; +constexpr uint32_t K3 = 0x8F1BBCDC; +constexpr uint32_t K4 = 0xCA62C1D6; + +inline void F1(uint32_t A, uint32_t& B, uint32_t C, uint32_t D, uint32_t& E, uint32_t M) { + E += choose(B, C, D) + M + rotl<5>(A); + B = rotl<30>(B); +} + +inline void F2(uint32_t A, uint32_t& B, uint32_t C, uint32_t D, uint32_t& E, uint32_t M) { + E += (B ^ C ^ D) + M + rotl<5>(A); + B = rotl<30>(B); +} + +inline void F3(uint32_t A, uint32_t& B, uint32_t C, uint32_t D, uint32_t& E, uint32_t M) { + E += majority(B, C, D) + M + rotl<5>(A); + B = rotl<30>(B); +} + +// NOTE: identical to F4 besides the constant addition +inline void F4(uint32_t A, uint32_t& B, uint32_t C, uint32_t D, uint32_t& E, uint32_t M) { + E += (B ^ C ^ D) + M + rotl<5>(A); + B = rotl<30>(B); +} + +} // namespace Botan::SHA1_F + +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha1/sha1_simd/info.txt botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1_simd/info.txt --- botan3-3.7.1+dfsg/src/lib/hash/sha1/sha1_simd/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1_simd/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,37 @@ + +SHA1_SIMD_4X32 -> 20250331 + + + +name -> "SHA-1 SIMD" +brief -> "SHA-1 using SIMD instructions" + + + +x86_32:ssse3 +x86_64:ssse3 +x32:ssse3 +arm32:neon +arm64:neon +loongarch64:lsx +wasm:simd128 + +# AltiVec/VMX also does work, but at least on the machines tested (POWER8 and +# POWER10) this was slower than scalar, while for ARM and x86 speedups of +# 25-30% are typical. + + + +x86_32 +x86_64 +x32 +arm32 +arm64 +loongarch64 +wasm + + + +cpuid +simd_4x32 + diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha1/sha1_simd/sha1_simd.cpp botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1_simd/sha1_simd.cpp --- botan3-3.7.1+dfsg/src/lib/hash/sha1/sha1_simd/sha1_simd.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1_simd/sha1_simd.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,254 @@ +/* +* SHA-1 using SIMD instructions +* Based on public domain code by Dean Gaudet +* (http://arctic.org/~dean/crypto/sha1.html) +* (C) 2009-2011,2023,2025 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include + +namespace Botan { + +namespace { + +/* +For each multiple of 4, t, we want to calculate this: + +W[t+0] = rol(W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16], 1); +W[t+1] = rol(W[t-2] ^ W[t-7] ^ W[t-13] ^ W[t-15], 1); +W[t+2] = rol(W[t-1] ^ W[t-6] ^ W[t-12] ^ W[t-14], 1); +W[t+3] = rol(W[t] ^ W[t-5] ^ W[t-11] ^ W[t-13], 1); + +we'll actually calculate this: + +W[t+0] = rol(W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16], 1); +W[t+1] = rol(W[t-2] ^ W[t-7] ^ W[t-13] ^ W[t-15], 1); +W[t+2] = rol(W[t-1] ^ W[t-6] ^ W[t-12] ^ W[t-14], 1); +W[t+3] = rol( 0 ^ W[t-5] ^ W[t-11] ^ W[t-13], 1); +W[t+3] ^= rol(W[t+0], 1); + +the parameters are: + +W0 = &W[t-16]; +W1 = &W[t-12]; +W2 = &W[t- 8]; +W3 = &W[t- 4]; + +and on output: +W0 = W[t]..W[t+3] +*/ +BOTAN_FORCE_INLINE SIMD_4x32 BOTAN_FN_ISA_SIMD_4X32 sha1_simd_next_w(SIMD_4x32& XW0, + SIMD_4x32 XW1, + SIMD_4x32 XW2, + SIMD_4x32 XW3) { + SIMD_4x32 T0 = XW0; // W[t-16..t-13] + T0 ^= SIMD_4x32::alignr8(XW1, XW0); // W[t-14..t-11] + T0 ^= XW2; // W[t-8..t-5] + T0 ^= XW3.shift_elems_right<1>(); // W[t-3..t-1] || 0 + + /* unrotated W[t]..W[t+2] in T0 ... still need W[t+3] */ + + // Extract w[t+0] into T2 + auto T2 = T0.shift_elems_left<3>(); + + // Main rotation + T0 = T0.rotl<1>(); + + // Rotation of W[t+3] has rot by 2 to account for us working on non-rotated words + T2 = T2.rotl<2>(); + + // Merge rol(W[t+0], 1) into W[t+3] + T0 ^= T2; + + XW0 = T0; + return T0; +} + +} // namespace + +/* +* SHA-1 Compression Function using SIMD for message expansion +*/ +//static +void BOTAN_FN_ISA_SIMD_4X32 SHA_1::simd_compress_n(digest_type& digest, std::span input, size_t blocks) { + using namespace SHA1_F; + + const SIMD_4x32 K00_19 = SIMD_4x32::splat(K1); + const SIMD_4x32 K20_39 = SIMD_4x32::splat(K2); + const SIMD_4x32 K40_59 = SIMD_4x32::splat(K3); + const SIMD_4x32 K60_79 = SIMD_4x32::splat(K4); + + uint32_t A = digest[0]; + uint32_t B = digest[1]; + uint32_t C = digest[2]; + uint32_t D = digest[3]; + uint32_t E = digest[4]; + + BufferSlicer in(input); + + for(size_t i = 0; i != blocks; ++i) { + uint32_t PT[4]; + + const auto block = in.take(block_bytes); + + SIMD_4x32 W0 = SIMD_4x32::load_be(&block[0]); // NOLINT(*-container-data-pointer) + SIMD_4x32 W1 = SIMD_4x32::load_be(&block[16]); + SIMD_4x32 W2 = SIMD_4x32::load_be(&block[32]); + SIMD_4x32 W3 = SIMD_4x32::load_be(&block[48]); + + SIMD_4x32 P0 = W0 + K00_19; + SIMD_4x32 P1 = W1 + K00_19; + SIMD_4x32 P2 = W2 + K00_19; + SIMD_4x32 P3 = W3 + K00_19; + + P0.store_le(PT); + F1(A, B, C, D, E, PT[0]); + F1(E, A, B, C, D, PT[1]); + F1(D, E, A, B, C, PT[2]); + F1(C, D, E, A, B, PT[3]); + P0 = sha1_simd_next_w(W0, W1, W2, W3) + K00_19; + + P1.store_le(PT); + F1(B, C, D, E, A, PT[0]); + F1(A, B, C, D, E, PT[1]); + F1(E, A, B, C, D, PT[2]); + F1(D, E, A, B, C, PT[3]); + P1 = sha1_simd_next_w(W1, W2, W3, W0) + K20_39; + + P2.store_le(PT); + F1(C, D, E, A, B, PT[0]); + F1(B, C, D, E, A, PT[1]); + F1(A, B, C, D, E, PT[2]); + F1(E, A, B, C, D, PT[3]); + P2 = sha1_simd_next_w(W2, W3, W0, W1) + K20_39; + + P3.store_le(PT); + F1(D, E, A, B, C, PT[0]); + F1(C, D, E, A, B, PT[1]); + F1(B, C, D, E, A, PT[2]); + F1(A, B, C, D, E, PT[3]); + P3 = sha1_simd_next_w(W3, W0, W1, W2) + K20_39; + + P0.store_le(PT); + F1(E, A, B, C, D, PT[0]); + F1(D, E, A, B, C, PT[1]); + F1(C, D, E, A, B, PT[2]); + F1(B, C, D, E, A, PT[3]); + P0 = sha1_simd_next_w(W0, W1, W2, W3) + K20_39; + + P1.store_le(PT); + F2(A, B, C, D, E, PT[0]); + F2(E, A, B, C, D, PT[1]); + F2(D, E, A, B, C, PT[2]); + F2(C, D, E, A, B, PT[3]); + P1 = sha1_simd_next_w(W1, W2, W3, W0) + K20_39; + + P2.store_le(PT); + F2(B, C, D, E, A, PT[0]); + F2(A, B, C, D, E, PT[1]); + F2(E, A, B, C, D, PT[2]); + F2(D, E, A, B, C, PT[3]); + P2 = sha1_simd_next_w(W2, W3, W0, W1) + K40_59; + + P3.store_le(PT); + F2(C, D, E, A, B, PT[0]); + F2(B, C, D, E, A, PT[1]); + F2(A, B, C, D, E, PT[2]); + F2(E, A, B, C, D, PT[3]); + P3 = sha1_simd_next_w(W3, W0, W1, W2) + K40_59; + + P0.store_le(PT); + F2(D, E, A, B, C, PT[0]); + F2(C, D, E, A, B, PT[1]); + F2(B, C, D, E, A, PT[2]); + F2(A, B, C, D, E, PT[3]); + P0 = sha1_simd_next_w(W0, W1, W2, W3) + K40_59; + + P1.store_le(PT); + F2(E, A, B, C, D, PT[0]); + F2(D, E, A, B, C, PT[1]); + F2(C, D, E, A, B, PT[2]); + F2(B, C, D, E, A, PT[3]); + P1 = sha1_simd_next_w(W1, W2, W3, W0) + K40_59; + + P2.store_le(PT); + F3(A, B, C, D, E, PT[0]); + F3(E, A, B, C, D, PT[1]); + F3(D, E, A, B, C, PT[2]); + F3(C, D, E, A, B, PT[3]); + P2 = sha1_simd_next_w(W2, W3, W0, W1) + K40_59; + + P3.store_le(PT); + F3(B, C, D, E, A, PT[0]); + F3(A, B, C, D, E, PT[1]); + F3(E, A, B, C, D, PT[2]); + F3(D, E, A, B, C, PT[3]); + P3 = sha1_simd_next_w(W3, W0, W1, W2) + K60_79; + + P0.store_le(PT); + F3(C, D, E, A, B, PT[0]); + F3(B, C, D, E, A, PT[1]); + F3(A, B, C, D, E, PT[2]); + F3(E, A, B, C, D, PT[3]); + P0 = sha1_simd_next_w(W0, W1, W2, W3) + K60_79; + + P1.store_le(PT); + F3(D, E, A, B, C, PT[0]); + F3(C, D, E, A, B, PT[1]); + F3(B, C, D, E, A, PT[2]); + F3(A, B, C, D, E, PT[3]); + P1 = sha1_simd_next_w(W1, W2, W3, W0) + K60_79; + + P2.store_le(PT); + F3(E, A, B, C, D, PT[0]); + F3(D, E, A, B, C, PT[1]); + F3(C, D, E, A, B, PT[2]); + F3(B, C, D, E, A, PT[3]); + P2 = sha1_simd_next_w(W2, W3, W0, W1) + K60_79; + + P3.store_le(PT); + F4(A, B, C, D, E, PT[0]); + F4(E, A, B, C, D, PT[1]); + F4(D, E, A, B, C, PT[2]); + F4(C, D, E, A, B, PT[3]); + P3 = sha1_simd_next_w(W3, W0, W1, W2) + K60_79; + + P0.store_le(PT); + F4(B, C, D, E, A, PT[0]); + F4(A, B, C, D, E, PT[1]); + F4(E, A, B, C, D, PT[2]); + F4(D, E, A, B, C, PT[3]); + + P1.store_le(PT); + F4(C, D, E, A, B, PT[0]); + F4(B, C, D, E, A, PT[1]); + F4(A, B, C, D, E, PT[2]); + F4(E, A, B, C, D, PT[3]); + + P2.store_le(PT); + F4(D, E, A, B, C, PT[0]); + F4(C, D, E, A, B, PT[1]); + F4(B, C, D, E, A, PT[2]); + F4(A, B, C, D, E, PT[3]); + + P3.store_le(PT); + F4(E, A, B, C, D, PT[0]); + F4(D, E, A, B, C, PT[1]); + F4(C, D, E, A, B, PT[2]); + F4(B, C, D, E, A, PT[3]); + + A = (digest[0] += A); + B = (digest[1] += B); + C = (digest[2] += C); + D = (digest[3] += D); + E = (digest[4] += E); + } +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha1/sha1_sse2/info.txt botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1_sse2/info.txt --- botan3-3.7.1+dfsg/src/lib/hash/sha1/sha1_sse2/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1_sse2/info.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,16 +0,0 @@ - -SHA1_SSE2 -> 20160803 - - - -name -> "SHA-1 SSE2" -brief -> "SHA-1 using SSE2 instructions" - - - -sse2 - - - -simd - diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha1/sha1_sse2/sha1_sse2.cpp botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1_sse2/sha1_sse2.cpp --- botan3-3.7.1+dfsg/src/lib/hash/sha1/sha1_sse2/sha1_sse2.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1_sse2/sha1_sse2.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,286 +0,0 @@ -/* -* SHA-1 using SSE2 -* Based on public domain code by Dean Gaudet -* (http://arctic.org/~dean/crypto/sha1.html) -* (C) 2009-2011,2023 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include - -#include -#include -#include -#include -#include - -namespace Botan { - -namespace SHA1_SSE2_F { - -namespace { - -/* -For each multiple of 4, t, we want to calculate this: - -W[t+0] = rol(W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16], 1); -W[t+1] = rol(W[t-2] ^ W[t-7] ^ W[t-13] ^ W[t-15], 1); -W[t+2] = rol(W[t-1] ^ W[t-6] ^ W[t-12] ^ W[t-14], 1); -W[t+3] = rol(W[t] ^ W[t-5] ^ W[t-11] ^ W[t-13], 1); - -we'll actually calculate this: - -W[t+0] = rol(W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16], 1); -W[t+1] = rol(W[t-2] ^ W[t-7] ^ W[t-13] ^ W[t-15], 1); -W[t+2] = rol(W[t-1] ^ W[t-6] ^ W[t-12] ^ W[t-14], 1); -W[t+3] = rol( 0 ^ W[t-5] ^ W[t-11] ^ W[t-13], 1); -W[t+3] ^= rol(W[t+0], 1); - -the parameters are: - -W0 = &W[t-16]; -W1 = &W[t-12]; -W2 = &W[t- 8]; -W3 = &W[t- 4]; - -and on output: -prepared = W0 + K -W0 = W[t]..W[t+3] -*/ -BOTAN_FORCE_INLINE SIMD_4x32 prep(SIMD_4x32& XW0, SIMD_4x32 XW1, SIMD_4x32 XW2, SIMD_4x32 XW3, SIMD_4x32 K) { - SIMD_4x32 T0 = XW0; - /* load W[t-4] 16-byte aligned, and shift */ - SIMD_4x32 T2 = XW3.shift_elems_right<1>(); - /* get high 64-bits of XW0 into low 64-bits */ - SIMD_4x32 T1 = SIMD_4x32(_mm_shuffle_epi32(XW0.raw(), _MM_SHUFFLE(1, 0, 3, 2))); - /* load high 64-bits of T1 */ - T1 = SIMD_4x32(_mm_unpacklo_epi64(T1.raw(), XW1.raw())); - - T0 ^= T1; - T2 ^= XW2; - T0 ^= T2; - /* unrotated W[t]..W[t+2] in T0 ... still need W[t+3] */ - - T2 = T0.shift_elems_left<3>(); - T0 = T0.rotl<1>(); - T2 = T2.rotl<2>(); - - T0 ^= T2; /* T0 now has W[t+3] */ - - XW0 = T0; - return T0 + K; -} - -/* -* SHA-1 F1 Function -*/ -inline void F1(uint32_t A, uint32_t& B, uint32_t C, uint32_t D, uint32_t& E, uint32_t msg) { - E += choose(B, C, D) + msg + rotl<5>(A); - B = rotl<30>(B); -} - -/* -* SHA-1 F2 Function -*/ -inline void F2(uint32_t A, uint32_t& B, uint32_t C, uint32_t D, uint32_t& E, uint32_t msg) { - E += (B ^ C ^ D) + msg + rotl<5>(A); - B = rotl<30>(B); -} - -/* -* SHA-1 F3 Function -*/ -inline void F3(uint32_t A, uint32_t& B, uint32_t C, uint32_t D, uint32_t& E, uint32_t msg) { - E += majority(B, C, D) + msg + rotl<5>(A); - B = rotl<30>(B); -} - -/* -* SHA-1 F4 Function -*/ -inline void F4(uint32_t A, uint32_t& B, uint32_t C, uint32_t D, uint32_t& E, uint32_t msg) { - E += (B ^ C ^ D) + msg + rotl<5>(A); - B = rotl<30>(B); -} - -} // namespace - -} // namespace SHA1_SSE2_F - -/* -* SHA-1 Compression Function using SSE for message expansion -*/ -//static -BOTAN_FUNC_ISA("sse2") void SHA_1::sse2_compress_n(digest_type& digest, std::span input, size_t blocks) { - using namespace SHA1_SSE2_F; - - const SIMD_4x32 K00_19 = SIMD_4x32::splat(0x5A827999); - const SIMD_4x32 K20_39 = SIMD_4x32::splat(0x6ED9EBA1); - const SIMD_4x32 K40_59 = SIMD_4x32::splat(0x8F1BBCDC); - const SIMD_4x32 K60_79 = SIMD_4x32::splat(0xCA62C1D6); - - uint32_t A = digest[0], B = digest[1], C = digest[2], D = digest[3], E = digest[4]; - - BufferSlicer in(input); - - for(size_t i = 0; i != blocks; ++i) { - uint32_t PT[4]; - - const auto block = in.take(block_bytes); - - SIMD_4x32 W0 = SIMD_4x32::load_be(&block[0]); - SIMD_4x32 W1 = SIMD_4x32::load_be(&block[16]); - SIMD_4x32 W2 = SIMD_4x32::load_be(&block[32]); - SIMD_4x32 W3 = SIMD_4x32::load_be(&block[48]); - - SIMD_4x32 P0 = W0 + K00_19; - SIMD_4x32 P1 = W1 + K00_19; - SIMD_4x32 P2 = W2 + K00_19; - SIMD_4x32 P3 = W3 + K00_19; - - SIMD_4x32(P0).store_le(PT); - F1(A, B, C, D, E, PT[0]); - F1(E, A, B, C, D, PT[1]); - F1(D, E, A, B, C, PT[2]); - F1(C, D, E, A, B, PT[3]); - P0 = prep(W0, W1, W2, W3, K00_19); - - SIMD_4x32(P1).store_le(PT); - F1(B, C, D, E, A, PT[0]); - F1(A, B, C, D, E, PT[1]); - F1(E, A, B, C, D, PT[2]); - F1(D, E, A, B, C, PT[3]); - P1 = prep(W1, W2, W3, W0, K20_39); - - SIMD_4x32(P2).store_le(PT); - F1(C, D, E, A, B, PT[0]); - F1(B, C, D, E, A, PT[1]); - F1(A, B, C, D, E, PT[2]); - F1(E, A, B, C, D, PT[3]); - P2 = prep(W2, W3, W0, W1, K20_39); - - SIMD_4x32(P3).store_le(PT); - F1(D, E, A, B, C, PT[0]); - F1(C, D, E, A, B, PT[1]); - F1(B, C, D, E, A, PT[2]); - F1(A, B, C, D, E, PT[3]); - P3 = prep(W3, W0, W1, W2, K20_39); - - SIMD_4x32(P0).store_le(PT); - F1(E, A, B, C, D, PT[0]); - F1(D, E, A, B, C, PT[1]); - F1(C, D, E, A, B, PT[2]); - F1(B, C, D, E, A, PT[3]); - P0 = prep(W0, W1, W2, W3, K20_39); - - SIMD_4x32(P1).store_le(PT); - F2(A, B, C, D, E, PT[0]); - F2(E, A, B, C, D, PT[1]); - F2(D, E, A, B, C, PT[2]); - F2(C, D, E, A, B, PT[3]); - P1 = prep(W1, W2, W3, W0, K20_39); - - SIMD_4x32(P2).store_le(PT); - F2(B, C, D, E, A, PT[0]); - F2(A, B, C, D, E, PT[1]); - F2(E, A, B, C, D, PT[2]); - F2(D, E, A, B, C, PT[3]); - P2 = prep(W2, W3, W0, W1, K40_59); - - SIMD_4x32(P3).store_le(PT); - F2(C, D, E, A, B, PT[0]); - F2(B, C, D, E, A, PT[1]); - F2(A, B, C, D, E, PT[2]); - F2(E, A, B, C, D, PT[3]); - P3 = prep(W3, W0, W1, W2, K40_59); - - SIMD_4x32(P0).store_le(PT); - F2(D, E, A, B, C, PT[0]); - F2(C, D, E, A, B, PT[1]); - F2(B, C, D, E, A, PT[2]); - F2(A, B, C, D, E, PT[3]); - P0 = prep(W0, W1, W2, W3, K40_59); - - SIMD_4x32(P1).store_le(PT); - F2(E, A, B, C, D, PT[0]); - F2(D, E, A, B, C, PT[1]); - F2(C, D, E, A, B, PT[2]); - F2(B, C, D, E, A, PT[3]); - P1 = prep(W1, W2, W3, W0, K40_59); - - SIMD_4x32(P2).store_le(PT); - F3(A, B, C, D, E, PT[0]); - F3(E, A, B, C, D, PT[1]); - F3(D, E, A, B, C, PT[2]); - F3(C, D, E, A, B, PT[3]); - P2 = prep(W2, W3, W0, W1, K40_59); - - SIMD_4x32(P3).store_le(PT); - F3(B, C, D, E, A, PT[0]); - F3(A, B, C, D, E, PT[1]); - F3(E, A, B, C, D, PT[2]); - F3(D, E, A, B, C, PT[3]); - P3 = prep(W3, W0, W1, W2, K60_79); - - SIMD_4x32(P0).store_le(PT); - F3(C, D, E, A, B, PT[0]); - F3(B, C, D, E, A, PT[1]); - F3(A, B, C, D, E, PT[2]); - F3(E, A, B, C, D, PT[3]); - P0 = prep(W0, W1, W2, W3, K60_79); - - SIMD_4x32(P1).store_le(PT); - F3(D, E, A, B, C, PT[0]); - F3(C, D, E, A, B, PT[1]); - F3(B, C, D, E, A, PT[2]); - F3(A, B, C, D, E, PT[3]); - P1 = prep(W1, W2, W3, W0, K60_79); - - SIMD_4x32(P2).store_le(PT); - F3(E, A, B, C, D, PT[0]); - F3(D, E, A, B, C, PT[1]); - F3(C, D, E, A, B, PT[2]); - F3(B, C, D, E, A, PT[3]); - P2 = prep(W2, W3, W0, W1, K60_79); - - SIMD_4x32(P3).store_le(PT); - F4(A, B, C, D, E, PT[0]); - F4(E, A, B, C, D, PT[1]); - F4(D, E, A, B, C, PT[2]); - F4(C, D, E, A, B, PT[3]); - P3 = prep(W3, W0, W1, W2, K60_79); - - SIMD_4x32(P0).store_le(PT); - F4(B, C, D, E, A, PT[0]); - F4(A, B, C, D, E, PT[1]); - F4(E, A, B, C, D, PT[2]); - F4(D, E, A, B, C, PT[3]); - - SIMD_4x32(P1).store_le(PT); - F4(C, D, E, A, B, PT[0]); - F4(B, C, D, E, A, PT[1]); - F4(A, B, C, D, E, PT[2]); - F4(E, A, B, C, D, PT[3]); - - SIMD_4x32(P2).store_le(PT); - F4(D, E, A, B, C, PT[0]); - F4(C, D, E, A, B, PT[1]); - F4(B, C, D, E, A, PT[2]); - F4(A, B, C, D, E, PT[3]); - - SIMD_4x32(P3).store_le(PT); - F4(E, A, B, C, D, PT[0]); - F4(D, E, A, B, C, PT[1]); - F4(C, D, E, A, B, PT[2]); - F4(B, C, D, E, A, PT[3]); - - A = (digest[0] += A); - B = (digest[1] += B); - C = (digest[2] += C); - D = (digest[3] += D); - E = (digest[4] += E); - } -} - -} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha1/sha1_x86/info.txt botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1_x86/info.txt --- botan3-3.7.1+dfsg/src/lib/hash/sha1/sha1_x86/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1_x86/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + SHA1_X86_SHA_NI -> 20170518 - + name -> "SHA-1 SIMD" @@ -13,3 +13,8 @@ ssse3 sse41 + + +cpuid +simd_4x32 + diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha1/sha1_x86/sha1_x86.cpp botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1_x86/sha1_x86.cpp --- botan3-3.7.1+dfsg/src/lib/hash/sha1/sha1_x86/sha1_x86.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha1/sha1_x86/sha1_x86.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,210 +1,142 @@ /* -* SHA-1 using Intel SHA intrinsic -* * Based on public domain code by Sean Gulley -* (https://github.com/mitls/hacl-star/tree/master/experimental/hash) +* * Adapted to Botan by Jeffrey Walton. * * Further changes * -* (C) 2017 Jack Lloyd +* (C) 2017,2025 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ #include + +#include +#include #include namespace Botan { -BOTAN_FUNC_ISA("sha,ssse3,sse4.1") -void SHA_1::sha1_compress_x86(digest_type& digest, std::span input, size_t blocks) { - const __m128i MASK = _mm_set_epi64x(0x0001020304050607, 0x08090a0b0c0d0e0f); - const __m128i* input_mm = reinterpret_cast(input.data()); - - uint32_t* state = digest.data(); - - // Load initial values - __m128i ABCD = _mm_loadu_si128(reinterpret_cast<__m128i*>(state)); - __m128i E0 = _mm_set_epi32(state[4], 0, 0, 0); - ABCD = _mm_shuffle_epi32(ABCD, 0x1B); +namespace { + +// NOLINTBEGIN(portability-simd-intrinsics) + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_SHANI SIMD_4x32 sha1_x86_nexte(const SIMD_4x32& x, const SIMD_4x32& y) { + return SIMD_4x32(_mm_sha1nexte_epu32(x.raw(), y.raw())); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_SHANI SIMD_4x32 sha1_x86_msg1(const SIMD_4x32& W0, const SIMD_4x32& W1) { + return SIMD_4x32(_mm_sha1msg1_epu32(W0.raw(), W1.raw())); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_SHANI void sha1_x86_next_msg(const SIMD_4x32& W0, + SIMD_4x32& W1, + SIMD_4x32& W2, + SIMD_4x32& W3) { + W3 = SIMD_4x32(_mm_sha1msg1_epu32(W3.raw(), W0.raw())); + W1 = SIMD_4x32(_mm_sha1msg2_epu32(W1.raw(), W0.raw())); + W2 ^= W0; +} + +template +BOTAN_FORCE_INLINE BOTAN_FN_ISA_SHANI void sha1_x86_first8(SIMD_4x32& ABCD, + SIMD_4x32& E, + const SIMD_4x32& W0, + const SIMD_4x32& W1) { + auto TE = ABCD; + ABCD = SIMD_4x32(_mm_sha1rnds4_epu32(ABCD.raw(), (E + W0).raw(), R1)); + + E = ABCD; + ABCD = SIMD_4x32(_mm_sha1rnds4_epu32(ABCD.raw(), sha1_x86_nexte(TE, W1).raw(), R2)); +} + +template +BOTAN_FORCE_INLINE BOTAN_FN_ISA_SHANI void sha1_x86_rnds8(SIMD_4x32& ABCD, + SIMD_4x32& E, + const SIMD_4x32& W0, + const SIMD_4x32& W1) { + auto TE = ABCD; + ABCD = SIMD_4x32(_mm_sha1rnds4_epu32(ABCD.raw(), sha1_x86_nexte(E, W0).raw(), R1)); + + E = ABCD; + ABCD = SIMD_4x32(_mm_sha1rnds4_epu32(ABCD.raw(), sha1_x86_nexte(TE, W1).raw(), R2)); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_SHANI SIMD_4x32 rev_words(const SIMD_4x32& v) { + return SIMD_4x32(_mm_shuffle_epi32(v.raw(), 0b00011011)); +} + +// NOLINTEND(portability-simd-intrinsics) + +} // namespace - while(blocks) { +void BOTAN_FN_ISA_SHANI SHA_1::sha1_compress_x86(digest_type& digest, + std::span input_span, + size_t blocks) { + const uint8_t* input = input_span.data(); + + SIMD_4x32 ABCD = rev_words(SIMD_4x32::load_le(&digest[0])); // NOLINT(*-container-data-pointer) + SIMD_4x32 E0 = SIMD_4x32(0, 0, 0, digest[4]); + + while(blocks > 0) { // Save current hash - const __m128i ABCD_SAVE = ABCD; - const __m128i E0_SAVE = E0; + const auto ABCD_SAVE = ABCD; + const auto E0_SAVE = E0; + + auto W0 = rev_words(SIMD_4x32::load_be(input)); + auto W1 = rev_words(SIMD_4x32::load_be(input + 16)); + auto W2 = rev_words(SIMD_4x32::load_be(input + 32)); + auto W3 = rev_words(SIMD_4x32::load_be(input + 48)); + + sha1_x86_first8<0>(ABCD, E0, W0, W1); + sha1_x86_rnds8<0>(ABCD, E0, W2, W3); + + W0 = sha1_x86_msg1(W0, W1); + W1 = sha1_x86_msg1(W1, W2); + W0 ^= W2; + + sha1_x86_next_msg(W3, W0, W1, W2); + sha1_x86_next_msg(W0, W1, W2, W3); + sha1_x86_rnds8<0, 1>(ABCD, E0, W0, W1); + + sha1_x86_next_msg(W1, W2, W3, W0); + sha1_x86_next_msg(W2, W3, W0, W1); + sha1_x86_rnds8<1>(ABCD, E0, W2, W3); + + sha1_x86_next_msg(W3, W0, W1, W2); + sha1_x86_next_msg(W0, W1, W2, W3); + sha1_x86_rnds8<1>(ABCD, E0, W0, W1); + + sha1_x86_next_msg(W1, W2, W3, W0); + sha1_x86_next_msg(W2, W3, W0, W1); + sha1_x86_rnds8<2>(ABCD, E0, W2, W3); + + sha1_x86_next_msg(W3, W0, W1, W2); + sha1_x86_next_msg(W0, W1, W2, W3); + sha1_x86_rnds8<2>(ABCD, E0, W0, W1); + + sha1_x86_next_msg(W1, W2, W3, W0); + sha1_x86_next_msg(W2, W3, W0, W1); + sha1_x86_rnds8<2, 3>(ABCD, E0, W2, W3); + + sha1_x86_next_msg(W3, W0, W1, W2); + sha1_x86_next_msg(W0, W1, W2, W3); + sha1_x86_rnds8<3>(ABCD, E0, W0, W1); - __m128i MSG0, MSG1, MSG2, MSG3; - __m128i E1; + sha1_x86_next_msg(W1, W2, W3, W0); + sha1_x86_next_msg(W2, W3, W0, W1); + sha1_x86_rnds8<3>(ABCD, E0, W2, W3); - // Rounds 0-3 - MSG0 = _mm_loadu_si128(input_mm + 0); - MSG0 = _mm_shuffle_epi8(MSG0, MASK); - E0 = _mm_add_epi32(E0, MSG0); - E1 = ABCD; - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 0); - - // Rounds 4-7 - MSG1 = _mm_loadu_si128(input_mm + 1); - MSG1 = _mm_shuffle_epi8(MSG1, MASK); - E1 = _mm_sha1nexte_epu32(E1, MSG1); - E0 = ABCD; - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 0); - MSG0 = _mm_sha1msg1_epu32(MSG0, MSG1); - - // Rounds 8-11 - MSG2 = _mm_loadu_si128(input_mm + 2); - MSG2 = _mm_shuffle_epi8(MSG2, MASK); - E0 = _mm_sha1nexte_epu32(E0, MSG2); - E1 = ABCD; - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 0); - MSG1 = _mm_sha1msg1_epu32(MSG1, MSG2); - MSG0 = _mm_xor_si128(MSG0, MSG2); - - // Rounds 12-15 - MSG3 = _mm_loadu_si128(input_mm + 3); - MSG3 = _mm_shuffle_epi8(MSG3, MASK); - E1 = _mm_sha1nexte_epu32(E1, MSG3); - E0 = ABCD; - MSG0 = _mm_sha1msg2_epu32(MSG0, MSG3); - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 0); - MSG2 = _mm_sha1msg1_epu32(MSG2, MSG3); - MSG1 = _mm_xor_si128(MSG1, MSG3); - - // Rounds 16-19 - E0 = _mm_sha1nexte_epu32(E0, MSG0); - E1 = ABCD; - MSG1 = _mm_sha1msg2_epu32(MSG1, MSG0); - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 0); - MSG3 = _mm_sha1msg1_epu32(MSG3, MSG0); - MSG2 = _mm_xor_si128(MSG2, MSG0); - - // Rounds 20-23 - E1 = _mm_sha1nexte_epu32(E1, MSG1); - E0 = ABCD; - MSG2 = _mm_sha1msg2_epu32(MSG2, MSG1); - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 1); - MSG0 = _mm_sha1msg1_epu32(MSG0, MSG1); - MSG3 = _mm_xor_si128(MSG3, MSG1); - - // Rounds 24-27 - E0 = _mm_sha1nexte_epu32(E0, MSG2); - E1 = ABCD; - MSG3 = _mm_sha1msg2_epu32(MSG3, MSG2); - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 1); - MSG1 = _mm_sha1msg1_epu32(MSG1, MSG2); - MSG0 = _mm_xor_si128(MSG0, MSG2); - - // Rounds 28-31 - E1 = _mm_sha1nexte_epu32(E1, MSG3); - E0 = ABCD; - MSG0 = _mm_sha1msg2_epu32(MSG0, MSG3); - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 1); - MSG2 = _mm_sha1msg1_epu32(MSG2, MSG3); - MSG1 = _mm_xor_si128(MSG1, MSG3); - - // Rounds 32-35 - E0 = _mm_sha1nexte_epu32(E0, MSG0); - E1 = ABCD; - MSG1 = _mm_sha1msg2_epu32(MSG1, MSG0); - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 1); - MSG3 = _mm_sha1msg1_epu32(MSG3, MSG0); - MSG2 = _mm_xor_si128(MSG2, MSG0); - - // Rounds 36-39 - E1 = _mm_sha1nexte_epu32(E1, MSG1); - E0 = ABCD; - MSG2 = _mm_sha1msg2_epu32(MSG2, MSG1); - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 1); - MSG0 = _mm_sha1msg1_epu32(MSG0, MSG1); - MSG3 = _mm_xor_si128(MSG3, MSG1); - - // Rounds 40-43 - E0 = _mm_sha1nexte_epu32(E0, MSG2); - E1 = ABCD; - MSG3 = _mm_sha1msg2_epu32(MSG3, MSG2); - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 2); - MSG1 = _mm_sha1msg1_epu32(MSG1, MSG2); - MSG0 = _mm_xor_si128(MSG0, MSG2); - - // Rounds 44-47 - E1 = _mm_sha1nexte_epu32(E1, MSG3); - E0 = ABCD; - MSG0 = _mm_sha1msg2_epu32(MSG0, MSG3); - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 2); - MSG2 = _mm_sha1msg1_epu32(MSG2, MSG3); - MSG1 = _mm_xor_si128(MSG1, MSG3); - - // Rounds 48-51 - E0 = _mm_sha1nexte_epu32(E0, MSG0); - E1 = ABCD; - MSG1 = _mm_sha1msg2_epu32(MSG1, MSG0); - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 2); - MSG3 = _mm_sha1msg1_epu32(MSG3, MSG0); - MSG2 = _mm_xor_si128(MSG2, MSG0); - - // Rounds 52-55 - E1 = _mm_sha1nexte_epu32(E1, MSG1); - E0 = ABCD; - MSG2 = _mm_sha1msg2_epu32(MSG2, MSG1); - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 2); - MSG0 = _mm_sha1msg1_epu32(MSG0, MSG1); - MSG3 = _mm_xor_si128(MSG3, MSG1); - - // Rounds 56-59 - E0 = _mm_sha1nexte_epu32(E0, MSG2); - E1 = ABCD; - MSG3 = _mm_sha1msg2_epu32(MSG3, MSG2); - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 2); - MSG1 = _mm_sha1msg1_epu32(MSG1, MSG2); - MSG0 = _mm_xor_si128(MSG0, MSG2); - - // Rounds 60-63 - E1 = _mm_sha1nexte_epu32(E1, MSG3); - E0 = ABCD; - MSG0 = _mm_sha1msg2_epu32(MSG0, MSG3); - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 3); - MSG2 = _mm_sha1msg1_epu32(MSG2, MSG3); - MSG1 = _mm_xor_si128(MSG1, MSG3); - - // Rounds 64-67 - E0 = _mm_sha1nexte_epu32(E0, MSG0); - E1 = ABCD; - MSG1 = _mm_sha1msg2_epu32(MSG1, MSG0); - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 3); - MSG3 = _mm_sha1msg1_epu32(MSG3, MSG0); - MSG2 = _mm_xor_si128(MSG2, MSG0); - - // Rounds 68-71 - E1 = _mm_sha1nexte_epu32(E1, MSG1); - E0 = ABCD; - MSG2 = _mm_sha1msg2_epu32(MSG2, MSG1); - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 3); - MSG3 = _mm_xor_si128(MSG3, MSG1); - - // Rounds 72-75 - E0 = _mm_sha1nexte_epu32(E0, MSG2); - E1 = ABCD; - MSG3 = _mm_sha1msg2_epu32(MSG3, MSG2); - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 3); - - // Rounds 76-79 - E1 = _mm_sha1nexte_epu32(E1, MSG3); - E0 = ABCD; - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 3); - - // Add values back to state - E0 = _mm_sha1nexte_epu32(E0, E0_SAVE); - ABCD = _mm_add_epi32(ABCD, ABCD_SAVE); + ABCD += ABCD_SAVE; + E0 = sha1_x86_nexte(E0, E0_SAVE); - input_mm += 4; + input += 64; blocks--; } - // Save state - ABCD = _mm_shuffle_epi32(ABCD, 0x1B); - _mm_storeu_si128(reinterpret_cast<__m128i*>(state), ABCD); - state[4] = _mm_extract_epi32(E0, 3); + rev_words(ABCD).store_le(&digest[0]); // NOLINT(*-container-data-pointer) + digest[4] = _mm_extract_epi32(E0.raw(), 3); } } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha2_32/info.txt botan3-3.12.0+dfsg/src/lib/hash/sha2_32/info.txt --- botan3-3.7.1+dfsg/src/lib/hash/sha2_32/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha2_32/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,7 +1,9 @@ -SHA2_32 -> 20131128 SHA_224 -> 20250130 SHA_256 -> 20250130 + +# TODO(Botan4) remove this macro +SHA2_32 -> 20131128 diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha2_32/sha2_32.cpp botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32.cpp --- botan3-3.7.1+dfsg/src/lib/hash/sha2_32/sha2_32.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,33 +8,41 @@ #include -#include -#include +#include #include -#include #include -#include +#include + +#if defined(BOTAN_HAS_CPUID) + #include +#endif namespace Botan { namespace { std::string sha256_provider() { +#if defined(BOTAN_HAS_SHA2_32_ARMV8) + if(auto feat = CPUID::check(CPUID::Feature::SHA2)) { + return *feat; + } +#endif + #if defined(BOTAN_HAS_SHA2_32_X86) - if(CPUID::has_intel_sha()) { - return "shani"; + if(auto feat = CPUID::check(CPUID::Feature::SHA)) { + return *feat; } #endif -#if defined(BOTAN_HAS_SHA2_32_X86_BMI2) - if(CPUID::has_bmi2()) { - return "bmi2"; +#if defined(BOTAN_HAS_SHA2_32_X86_AVX2) + if(auto feat = CPUID::check(CPUID::Feature::AVX2, CPUID::Feature::BMI)) { + return *feat; } #endif -#if defined(BOTAN_HAS_SHA2_32_ARMV8) - if(CPUID::has_arm_sha2()) { - return "armv8"; +#if defined(BOTAN_HAS_SHA2_32_SIMD) + if(auto feat = CPUID::check(CPUID::Feature::SIMD_4X32)) { + return *feat; } #endif @@ -46,29 +54,43 @@ /* * SHA-224 / SHA-256 compression function */ -void SHA_256::compress_digest(digest_type& digest, std::span input, size_t blocks) { +void BOTAN_SCRUB_STACK_AFTER_RETURN SHA_256::compress_digest(digest_type& digest, + std::span input, + size_t blocks) { #if defined(BOTAN_HAS_SHA2_32_X86) - if(CPUID::has_intel_sha()) { + if(CPUID::has(CPUID::Feature::SHA)) { return SHA_256::compress_digest_x86(digest, input, blocks); } #endif -#if defined(BOTAN_HAS_SHA2_32_X86_BMI2) - if(CPUID::has_bmi2()) { - return SHA_256::compress_digest_x86_bmi2(digest, input, blocks); +#if defined(BOTAN_HAS_SHA2_32_ARMV8) + if(CPUID::has(CPUID::Feature::SHA2)) { + return SHA_256::compress_digest_armv8(digest, input, blocks); } #endif -#if defined(BOTAN_HAS_SHA2_32_ARMV8) - if(CPUID::has_arm_sha2()) { - return SHA_256::compress_digest_armv8(digest, input, blocks); +#if defined(BOTAN_HAS_SHA2_32_X86_AVX2) + if(CPUID::has(CPUID::Feature::AVX2, CPUID::Feature::BMI)) { + return SHA_256::compress_digest_x86_avx2(digest, input, blocks); + } +#endif + +#if defined(BOTAN_HAS_SHA2_32_SIMD) + if(CPUID::has(CPUID::Feature::SIMD_4X32)) { + return SHA_256::compress_digest_x86_simd(digest, input, blocks); } #endif - uint32_t A = digest[0], B = digest[1], C = digest[2], D = digest[3], E = digest[4], F = digest[5], G = digest[6], - H = digest[7]; + uint32_t A = digest[0]; + uint32_t B = digest[1]; + uint32_t C = digest[2]; + uint32_t D = digest[3]; + uint32_t E = digest[4]; + uint32_t F = digest[5]; + uint32_t G = digest[6]; + uint32_t H = digest[7]; - std::array W; + std::array W{}; BufferSlicer in(input); diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha2_32/sha2_32.h botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32.h --- botan3-3.7.1+dfsg/src/lib/hash/sha2_32/sha2_32.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32.h 2026-05-07 01:38:28.000000000 +0000 @@ -91,8 +91,12 @@ static void compress_digest_armv8(digest_type& digest, std::span input, size_t blocks); #endif -#if defined(BOTAN_HAS_SHA2_32_X86_BMI2) - static void compress_digest_x86_bmi2(digest_type& digest, std::span input, size_t blocks); +#if defined(BOTAN_HAS_SHA2_32_SIMD) + static void compress_digest_x86_simd(digest_type& digest, std::span input, size_t blocks); +#endif + +#if defined(BOTAN_HAS_SHA2_32_X86_AVX2) + static void compress_digest_x86_avx2(digest_type& digest, std::span input, size_t blocks); #endif #if defined(BOTAN_HAS_SHA2_32_X86) diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha2_32/sha2_32_armv8/info.txt botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32_armv8/info.txt --- botan3-3.7.1+dfsg/src/lib/hash/sha2_32/sha2_32_armv8/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32_armv8/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + SHA2_32_ARMV8 -> 20170117 - + name -> "SHA-256 ARMv8" @@ -10,3 +10,8 @@ armv8crypto + + +cpuid +simd_4x32 + diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha2_32/sha2_32_armv8/sha2_32_armv8.cpp botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32_armv8/sha2_32_armv8.cpp --- botan3-3.7.1+dfsg/src/lib/hash/sha2_32/sha2_32_armv8/sha2_32_armv8.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32_armv8/sha2_32_armv8.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -4,22 +4,48 @@ * Contributed by Jeffrey Walton. Based on public domain code by * Johannes Schneiders, Skip Hovsmith and Barry O'Rourke. * -* Further changes (C) 2020 Jack Lloyd +* Further changes (C) 2020,2025 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ #include + +#include +#include +#include #include namespace Botan { +namespace { + +inline BOTAN_FN_ISA_SHA2 SIMD_4x32 aarch64_sha256_expand_w(const SIMD_4x32 w0, + const SIMD_4x32 w1, + const SIMD_4x32 w2, + const SIMD_4x32 w3) { + return SIMD_4x32(vsha256su1q_u32(vsha256su0q_u32(w0.raw(), w1.raw()), w2.raw(), w3.raw())); +} + +inline BOTAN_FN_ISA_SHA2 void aarch64_sha256_update(SIMD_4x32& s0, + SIMD_4x32& s1, + const SIMD_4x32 w, + const uint32_t K[4]) { + auto w_k = w + SIMD_4x32::load_le(K); + auto t = vsha256hq_u32(s0.raw(), s1.raw(), w_k.raw()); + s1 = SIMD_4x32(vsha256h2q_u32(s1.raw(), s0.raw(), w_k.raw())); + s0 = SIMD_4x32(t); +} + +} // namespace + /* * SHA-256 using CPU instructions in ARMv8 */ //static -BOTAN_FUNC_ISA("+crypto+sha2") -void SHA_256::compress_digest_armv8(digest_type& digest, std::span input8, size_t blocks) { +void BOTAN_FN_ISA_SHA2 BOTAN_SCRUB_STACK_AFTER_RETURN SHA_256::compress_digest_armv8(digest_type& digest, + std::span input8, + size_t blocks) { alignas(64) static const uint32_t K[] = { 0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, @@ -32,148 +58,48 @@ }; // Load initial values - uint32x4_t STATE0 = vld1q_u32(&digest[0]); - uint32x4_t STATE1 = vld1q_u32(&digest[4]); + SIMD_4x32 s0 = SIMD_4x32::load_le(&digest[0]); // NOLINT(*-container-data-pointer) + SIMD_4x32 s1 = SIMD_4x32::load_le(&digest[4]); - // Intermediate void* cast due to https://llvm.org/bugs/show_bug.cgi?id=20670 - const uint32_t* input32 = reinterpret_cast(reinterpret_cast(input8.data())); + const uint32_t* input32 = reinterpret_cast(input8.data()); while(blocks > 0) { - // Save current state - const uint32x4_t ABCD_SAVE = STATE0; - const uint32x4_t EFGH_SAVE = STATE1; - - uint32x4_t MSG0 = vld1q_u32(input32 + 0); - uint32x4_t MSG1 = vld1q_u32(input32 + 4); - uint32x4_t MSG2 = vld1q_u32(input32 + 8); - uint32x4_t MSG3 = vld1q_u32(input32 + 12); - - MSG0 = vreinterpretq_u32_u8(vrev32q_u8(vreinterpretq_u8_u32(MSG0))); - MSG1 = vreinterpretq_u32_u8(vrev32q_u8(vreinterpretq_u8_u32(MSG1))); - MSG2 = vreinterpretq_u32_u8(vrev32q_u8(vreinterpretq_u8_u32(MSG2))); - MSG3 = vreinterpretq_u32_u8(vrev32q_u8(vreinterpretq_u8_u32(MSG3))); - - uint32x4_t MSG_K, TSTATE; - - // Rounds 0-3 - MSG_K = vaddq_u32(MSG0, vld1q_u32(&K[4 * 0])); - TSTATE = vsha256hq_u32(STATE0, STATE1, MSG_K); - STATE1 = vsha256h2q_u32(STATE1, STATE0, MSG_K); - STATE0 = TSTATE; - MSG0 = vsha256su1q_u32(vsha256su0q_u32(MSG0, MSG1), MSG2, MSG3); - - // Rounds 4-7 - MSG_K = vaddq_u32(MSG1, vld1q_u32(&K[4 * 1])); - TSTATE = vsha256hq_u32(STATE0, STATE1, MSG_K); - STATE1 = vsha256h2q_u32(STATE1, STATE0, MSG_K); - STATE0 = TSTATE; - MSG1 = vsha256su1q_u32(vsha256su0q_u32(MSG1, MSG2), MSG3, MSG0); - - // Rounds 8-11 - MSG_K = vaddq_u32(MSG2, vld1q_u32(&K[4 * 2])); - TSTATE = vsha256hq_u32(STATE0, STATE1, MSG_K); - STATE1 = vsha256h2q_u32(STATE1, STATE0, MSG_K); - STATE0 = TSTATE; - MSG2 = vsha256su1q_u32(vsha256su0q_u32(MSG2, MSG3), MSG0, MSG1); - - // Rounds 12-15 - MSG_K = vaddq_u32(MSG3, vld1q_u32(&K[4 * 3])); - TSTATE = vsha256hq_u32(STATE0, STATE1, MSG_K); - STATE1 = vsha256h2q_u32(STATE1, STATE0, MSG_K); - STATE0 = TSTATE; - MSG3 = vsha256su1q_u32(vsha256su0q_u32(MSG3, MSG0), MSG1, MSG2); - - // Rounds 16-19 - MSG_K = vaddq_u32(MSG0, vld1q_u32(&K[4 * 4])); - TSTATE = vsha256hq_u32(STATE0, STATE1, MSG_K); - STATE1 = vsha256h2q_u32(STATE1, STATE0, MSG_K); - STATE0 = TSTATE; - MSG0 = vsha256su1q_u32(vsha256su0q_u32(MSG0, MSG1), MSG2, MSG3); - - // Rounds 20-23 - MSG_K = vaddq_u32(MSG1, vld1q_u32(&K[4 * 5])); - TSTATE = vsha256hq_u32(STATE0, STATE1, MSG_K); - STATE1 = vsha256h2q_u32(STATE1, STATE0, MSG_K); - STATE0 = TSTATE; - MSG1 = vsha256su1q_u32(vsha256su0q_u32(MSG1, MSG2), MSG3, MSG0); - - // Rounds 24-27 - MSG_K = vaddq_u32(MSG2, vld1q_u32(&K[4 * 6])); - TSTATE = vsha256hq_u32(STATE0, STATE1, MSG_K); - STATE1 = vsha256h2q_u32(STATE1, STATE0, MSG_K); - STATE0 = TSTATE; - MSG2 = vsha256su1q_u32(vsha256su0q_u32(MSG2, MSG3), MSG0, MSG1); - - // Rounds 28-31 - MSG_K = vaddq_u32(MSG3, vld1q_u32(&K[4 * 7])); - TSTATE = vsha256hq_u32(STATE0, STATE1, MSG_K); - STATE1 = vsha256h2q_u32(STATE1, STATE0, MSG_K); - STATE0 = TSTATE; - MSG3 = vsha256su1q_u32(vsha256su0q_u32(MSG3, MSG0), MSG1, MSG2); - - // Rounds 32-35 - MSG_K = vaddq_u32(MSG0, vld1q_u32(&K[4 * 8])); - TSTATE = vsha256hq_u32(STATE0, STATE1, MSG_K); - STATE1 = vsha256h2q_u32(STATE1, STATE0, MSG_K); - STATE0 = TSTATE; - MSG0 = vsha256su1q_u32(vsha256su0q_u32(MSG0, MSG1), MSG2, MSG3); - - // Rounds 36-39 - MSG_K = vaddq_u32(MSG1, vld1q_u32(&K[4 * 9])); - TSTATE = vsha256hq_u32(STATE0, STATE1, MSG_K); - STATE1 = vsha256h2q_u32(STATE1, STATE0, MSG_K); - STATE0 = TSTATE; - MSG1 = vsha256su1q_u32(vsha256su0q_u32(MSG1, MSG2), MSG3, MSG0); - - // Rounds 40-43 - MSG_K = vaddq_u32(MSG2, vld1q_u32(&K[4 * 10])); - TSTATE = vsha256hq_u32(STATE0, STATE1, MSG_K); - STATE1 = vsha256h2q_u32(STATE1, STATE0, MSG_K); - STATE0 = TSTATE; - MSG2 = vsha256su1q_u32(vsha256su0q_u32(MSG2, MSG3), MSG0, MSG1); - - // Rounds 44-47 - MSG_K = vaddq_u32(MSG3, vld1q_u32(&K[4 * 11])); - TSTATE = vsha256hq_u32(STATE0, STATE1, MSG_K); - STATE1 = vsha256h2q_u32(STATE1, STATE0, MSG_K); - STATE0 = TSTATE; - MSG3 = vsha256su1q_u32(vsha256su0q_u32(MSG3, MSG0), MSG1, MSG2); - - // Rounds 48-51 - MSG_K = vaddq_u32(MSG0, vld1q_u32(&K[4 * 12])); - TSTATE = vsha256hq_u32(STATE0, STATE1, MSG_K); - STATE1 = vsha256h2q_u32(STATE1, STATE0, MSG_K); - STATE0 = TSTATE; - - // Rounds 52-55 - MSG_K = vaddq_u32(MSG1, vld1q_u32(&K[4 * 13])); - TSTATE = vsha256hq_u32(STATE0, STATE1, MSG_K); - STATE1 = vsha256h2q_u32(STATE1, STATE0, MSG_K); - STATE0 = TSTATE; - - // Rounds 56-59 - MSG_K = vaddq_u32(MSG2, vld1q_u32(&K[4 * 14])); - TSTATE = vsha256hq_u32(STATE0, STATE1, MSG_K); - STATE1 = vsha256h2q_u32(STATE1, STATE0, MSG_K); - STATE0 = TSTATE; - - // Rounds 60-63 - MSG_K = vaddq_u32(MSG3, vld1q_u32(&K[4 * 15])); - TSTATE = vsha256hq_u32(STATE0, STATE1, MSG_K); - STATE1 = vsha256h2q_u32(STATE1, STATE0, MSG_K); - STATE0 = TSTATE; - - // Add back to state - STATE0 = vaddq_u32(STATE0, ABCD_SAVE); - STATE1 = vaddq_u32(STATE1, EFGH_SAVE); + const auto s0_save = s0; + const auto s1_save = s1; + + auto w0 = SIMD_4x32::load_be(input32); + auto w1 = SIMD_4x32::load_be(input32 + 4); + auto w2 = SIMD_4x32::load_be(input32 + 8); + auto w3 = SIMD_4x32::load_be(input32 + 12); + + for(size_t r = 0; r != 48; r += 16) { + aarch64_sha256_update(s0, s1, w0, &K[r]); + w0 = aarch64_sha256_expand_w(w0, w1, w2, w3); + + aarch64_sha256_update(s0, s1, w1, &K[r + 4 * 1]); + w1 = aarch64_sha256_expand_w(w1, w2, w3, w0); + + aarch64_sha256_update(s0, s1, w2, &K[r + 4 * 2]); + w2 = aarch64_sha256_expand_w(w2, w3, w0, w1); + + aarch64_sha256_update(s0, s1, w3, &K[r + 4 * 3]); + w3 = aarch64_sha256_expand_w(w3, w0, w1, w2); + } + + aarch64_sha256_update(s0, s1, w0, &K[4 * 12]); + aarch64_sha256_update(s0, s1, w1, &K[4 * 13]); + aarch64_sha256_update(s0, s1, w2, &K[4 * 14]); + aarch64_sha256_update(s0, s1, w3, &K[4 * 15]); + + s0 += s0_save; + s1 += s1_save; input32 += 64 / 4; blocks--; } - // Save state - vst1q_u32(&digest[0], STATE0); - vst1q_u32(&digest[4], STATE1); + s0.store_le(&digest[0]); // NOLINT(*-container-data-pointer) + s1.store_le(&digest[4]); } } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha2_32/sha2_32_avx2/info.txt botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32_avx2/info.txt --- botan3-3.7.1+dfsg/src/lib/hash/sha2_32/sha2_32_avx2/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32_avx2/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,19 @@ + +SHA2_32_X86_AVX2 -> 20250402 + + + +name -> "SHA-256 using AVX2/BMI2" +brief -> "SHA-256 using AVX2/BMI2 instructions" + + + +avx2 +bmi2 + + + +cpuid +simd_4x32 +simd_avx2 + diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha2_32/sha2_32_avx2/sha2_32_avx2.cpp botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32_avx2/sha2_32_avx2.cpp --- botan3-3.7.1+dfsg/src/lib/hash/sha2_32/sha2_32_avx2/sha2_32_avx2.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32_avx2/sha2_32_avx2.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,362 @@ +/* +* (C) 2025 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include +#include + +#include + +namespace Botan { + +namespace { + +BOTAN_FN_ISA_AVX2_BMI2 inline SIMD_4x32 alignr4(const SIMD_4x32& a, const SIMD_4x32& b) { + return SIMD_4x32(_mm_alignr_epi8(a.raw(), b.raw(), 4)); +} + +template +BOTAN_FN_ISA_AVX2_BMI2 inline SIMD_4x32 shr64(const SIMD_4x32& a) { + return SIMD_4x32(_mm_srli_epi64(a.raw(), S)); +} + +template +BOTAN_FN_ISA_AVX2_BMI2 inline SIMD_4x32 shuffle_32(const SIMD_4x32& a) { + return SIMD_4x32(_mm_shuffle_epi32(a.raw(), S)); +} + +BOTAN_FN_ISA_AVX2_BMI2 inline SIMD_8x32 alignr4(const SIMD_8x32& a, const SIMD_8x32& b) { + return SIMD_8x32(_mm256_alignr_epi8(a.raw(), b.raw(), 4)); +} + +template +BOTAN_FN_ISA_AVX2_BMI2 inline SIMD_8x32 shr64(const SIMD_8x32& a) { + return SIMD_8x32(_mm256_srli_epi64(a.raw(), S)); +} + +template +BOTAN_FN_ISA_AVX2_BMI2 inline SIMD_8x32 shuffle_32(const SIMD_8x32& a) { + return SIMD_8x32(_mm256_shuffle_epi32(a.raw(), S)); +} + +template +BOTAN_FN_ISA_AVX2_BMI2 BOTAN_FORCE_INLINE SIMD_T next_w(SIMD_T x[4]) { + constexpr size_t sigma0_0 = 7; + constexpr size_t sigma0_1 = 18; + constexpr size_t sigma0_2 = 3; + constexpr size_t sigma1_0 = 17; + constexpr size_t sigma1_1 = 19; + constexpr size_t sigma1_2 = 10; + + const SIMD_T lo_mask = SIMD_T(0x03020100, 0x0b0a0908, 0x80808080, 0x80808080); + const SIMD_T hi_mask = SIMD_T(0x80808080, 0x80808080, 0x03020100, 0x0b0a0908); + + auto t0 = alignr4(x[1], x[0]); + x[0] += alignr4(x[3], x[2]); + + auto t1 = t0.template shl<32 - sigma0_1>(); + auto t2 = t0.template shr(); + auto t3 = t0.template shr(); + t0 = t3 ^ t2; + + t3 = shuffle_32<0b11111010>(x[3]); + t2 = t2.template shr(); + t0 ^= t1 ^ t2; + t1 = t1.template shl(); + t2 = t3.template shr(); + t3 = shr64(t3); + x[0] += t0 ^ t1; + + t2 ^= t3; + t3 = shr64(t3); + x[0] += SIMD_T::byte_shuffle(t2 ^ t3, lo_mask); + + t3 = shuffle_32<0b01010000>(x[0]); + t2 = t3.template shr(); + t3 = shr64(t3); + t2 ^= t3; + t3 = shr64(t3); + x[0] += SIMD_T::byte_shuffle(t2 ^ t3, hi_mask); + + const auto tmp = x[0]; + x[0] = x[1]; + x[1] = x[2]; + x[2] = x[3]; + x[3] = tmp; + + return x[3]; +} + +} // namespace + +BOTAN_FN_ISA_AVX2_BMI2 BOTAN_SCRUB_STACK_AFTER_RETURN void SHA_256::compress_digest_x86_avx2( + digest_type& digest, std::span input, size_t blocks) { + // clang-format off + + alignas(64) const uint32_t K[64] = { + 0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, + 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, + 0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC, 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, + 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, 0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967, + 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, + 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, + 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, + 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2}; + + // clang-format on + + alignas(64) uint32_t W[16]; + alignas(64) uint32_t W2[64]; + + uint32_t A = digest[0]; + uint32_t B = digest[1]; + uint32_t C = digest[2]; + uint32_t D = digest[3]; + uint32_t E = digest[4]; + uint32_t F = digest[5]; + uint32_t G = digest[6]; + uint32_t H = digest[7]; + + const uint8_t* data = input.data(); + + while(blocks >= 2) { + SIMD_8x32 WS[4]; + + for(size_t i = 0; i < 4; i++) { + WS[i] = SIMD_8x32::load_be128(&data[16 * i], &data[64 + 16 * i]); + auto WK = WS[i] + SIMD_8x32::load_le128(&K[4 * i]); + WK.store_le128(&W[4 * i], &W2[4 * i]); + } + + data += 2 * 64; + blocks -= 2; + + for(size_t r = 0; r != 48; r += 16) { + auto w = next_w(WS) + SIMD_8x32::load_le128(&K[r + 16]); + + SHA2_32_F(A, B, C, D, E, F, G, H, W[0]); + SHA2_32_F(H, A, B, C, D, E, F, G, W[1]); + SHA2_32_F(G, H, A, B, C, D, E, F, W[2]); + SHA2_32_F(F, G, H, A, B, C, D, E, W[3]); + + w.store_le128(&W[0], &W2[r + 16]); + + w = next_w(WS) + SIMD_8x32::load_le128(&K[r + 20]); + + SHA2_32_F(E, F, G, H, A, B, C, D, W[4]); + SHA2_32_F(D, E, F, G, H, A, B, C, W[5]); + SHA2_32_F(C, D, E, F, G, H, A, B, W[6]); + SHA2_32_F(B, C, D, E, F, G, H, A, W[7]); + + w.store_le128(&W[4], &W2[r + 20]); + + w = next_w(WS) + SIMD_8x32::load_le128(&K[r + 24]); + + SHA2_32_F(A, B, C, D, E, F, G, H, W[8]); + SHA2_32_F(H, A, B, C, D, E, F, G, W[9]); + SHA2_32_F(G, H, A, B, C, D, E, F, W[10]); + SHA2_32_F(F, G, H, A, B, C, D, E, W[11]); + + w.store_le128(&W[8], &W2[r + 24]); + + w = next_w(WS) + SIMD_8x32::load_le128(&K[r + 28]); + + SHA2_32_F(E, F, G, H, A, B, C, D, W[12]); + SHA2_32_F(D, E, F, G, H, A, B, C, W[13]); + SHA2_32_F(C, D, E, F, G, H, A, B, W[14]); + SHA2_32_F(B, C, D, E, F, G, H, A, W[15]); + + w.store_le128(&W[12], &W2[r + 28]); + } + + SHA2_32_F(A, B, C, D, E, F, G, H, W[0]); + SHA2_32_F(H, A, B, C, D, E, F, G, W[1]); + SHA2_32_F(G, H, A, B, C, D, E, F, W[2]); + SHA2_32_F(F, G, H, A, B, C, D, E, W[3]); + SHA2_32_F(E, F, G, H, A, B, C, D, W[4]); + SHA2_32_F(D, E, F, G, H, A, B, C, W[5]); + SHA2_32_F(C, D, E, F, G, H, A, B, W[6]); + SHA2_32_F(B, C, D, E, F, G, H, A, W[7]); + SHA2_32_F(A, B, C, D, E, F, G, H, W[8]); + SHA2_32_F(H, A, B, C, D, E, F, G, W[9]); + SHA2_32_F(G, H, A, B, C, D, E, F, W[10]); + SHA2_32_F(F, G, H, A, B, C, D, E, W[11]); + SHA2_32_F(E, F, G, H, A, B, C, D, W[12]); + SHA2_32_F(D, E, F, G, H, A, B, C, W[13]); + SHA2_32_F(C, D, E, F, G, H, A, B, W[14]); + SHA2_32_F(B, C, D, E, F, G, H, A, W[15]); + + A = (digest[0] += A); + B = (digest[1] += B); + C = (digest[2] += C); + D = (digest[3] += D); + E = (digest[4] += E); + F = (digest[5] += F); + G = (digest[6] += G); + H = (digest[7] += H); + + // Now the second block, with already expanded message + SHA2_32_F(A, B, C, D, E, F, G, H, W2[0]); + SHA2_32_F(H, A, B, C, D, E, F, G, W2[1]); + SHA2_32_F(G, H, A, B, C, D, E, F, W2[2]); + SHA2_32_F(F, G, H, A, B, C, D, E, W2[3]); + SHA2_32_F(E, F, G, H, A, B, C, D, W2[4]); + SHA2_32_F(D, E, F, G, H, A, B, C, W2[5]); + SHA2_32_F(C, D, E, F, G, H, A, B, W2[6]); + SHA2_32_F(B, C, D, E, F, G, H, A, W2[7]); + SHA2_32_F(A, B, C, D, E, F, G, H, W2[8]); + SHA2_32_F(H, A, B, C, D, E, F, G, W2[9]); + SHA2_32_F(G, H, A, B, C, D, E, F, W2[10]); + SHA2_32_F(F, G, H, A, B, C, D, E, W2[11]); + SHA2_32_F(E, F, G, H, A, B, C, D, W2[12]); + SHA2_32_F(D, E, F, G, H, A, B, C, W2[13]); + SHA2_32_F(C, D, E, F, G, H, A, B, W2[14]); + SHA2_32_F(B, C, D, E, F, G, H, A, W2[15]); + + SHA2_32_F(A, B, C, D, E, F, G, H, W2[16]); + SHA2_32_F(H, A, B, C, D, E, F, G, W2[17]); + SHA2_32_F(G, H, A, B, C, D, E, F, W2[18]); + SHA2_32_F(F, G, H, A, B, C, D, E, W2[19]); + SHA2_32_F(E, F, G, H, A, B, C, D, W2[20]); + SHA2_32_F(D, E, F, G, H, A, B, C, W2[21]); + SHA2_32_F(C, D, E, F, G, H, A, B, W2[22]); + SHA2_32_F(B, C, D, E, F, G, H, A, W2[23]); + SHA2_32_F(A, B, C, D, E, F, G, H, W2[24]); + SHA2_32_F(H, A, B, C, D, E, F, G, W2[25]); + SHA2_32_F(G, H, A, B, C, D, E, F, W2[26]); + SHA2_32_F(F, G, H, A, B, C, D, E, W2[27]); + SHA2_32_F(E, F, G, H, A, B, C, D, W2[28]); + SHA2_32_F(D, E, F, G, H, A, B, C, W2[29]); + SHA2_32_F(C, D, E, F, G, H, A, B, W2[30]); + SHA2_32_F(B, C, D, E, F, G, H, A, W2[31]); + + SHA2_32_F(A, B, C, D, E, F, G, H, W2[32]); + SHA2_32_F(H, A, B, C, D, E, F, G, W2[33]); + SHA2_32_F(G, H, A, B, C, D, E, F, W2[34]); + SHA2_32_F(F, G, H, A, B, C, D, E, W2[35]); + SHA2_32_F(E, F, G, H, A, B, C, D, W2[36]); + SHA2_32_F(D, E, F, G, H, A, B, C, W2[37]); + SHA2_32_F(C, D, E, F, G, H, A, B, W2[38]); + SHA2_32_F(B, C, D, E, F, G, H, A, W2[39]); + SHA2_32_F(A, B, C, D, E, F, G, H, W2[40]); + SHA2_32_F(H, A, B, C, D, E, F, G, W2[41]); + SHA2_32_F(G, H, A, B, C, D, E, F, W2[42]); + SHA2_32_F(F, G, H, A, B, C, D, E, W2[43]); + SHA2_32_F(E, F, G, H, A, B, C, D, W2[44]); + SHA2_32_F(D, E, F, G, H, A, B, C, W2[45]); + SHA2_32_F(C, D, E, F, G, H, A, B, W2[46]); + SHA2_32_F(B, C, D, E, F, G, H, A, W2[47]); + + SHA2_32_F(A, B, C, D, E, F, G, H, W2[48]); + SHA2_32_F(H, A, B, C, D, E, F, G, W2[49]); + SHA2_32_F(G, H, A, B, C, D, E, F, W2[50]); + SHA2_32_F(F, G, H, A, B, C, D, E, W2[51]); + SHA2_32_F(E, F, G, H, A, B, C, D, W2[52]); + SHA2_32_F(D, E, F, G, H, A, B, C, W2[53]); + SHA2_32_F(C, D, E, F, G, H, A, B, W2[54]); + SHA2_32_F(B, C, D, E, F, G, H, A, W2[55]); + SHA2_32_F(A, B, C, D, E, F, G, H, W2[56]); + SHA2_32_F(H, A, B, C, D, E, F, G, W2[57]); + SHA2_32_F(G, H, A, B, C, D, E, F, W2[58]); + SHA2_32_F(F, G, H, A, B, C, D, E, W2[59]); + SHA2_32_F(E, F, G, H, A, B, C, D, W2[60]); + SHA2_32_F(D, E, F, G, H, A, B, C, W2[61]); + SHA2_32_F(C, D, E, F, G, H, A, B, W2[62]); + SHA2_32_F(B, C, D, E, F, G, H, A, W2[63]); + + A = (digest[0] += A); + B = (digest[1] += B); + C = (digest[2] += C); + D = (digest[3] += D); + E = (digest[4] += E); + F = (digest[5] += F); + G = (digest[6] += G); + H = (digest[7] += H); + } + + while(blocks > 0) { + SIMD_4x32 WS[4]; + + for(size_t i = 0; i < 4; i++) { + WS[i] = SIMD_4x32::load_be(&data[16 * i]); + auto WK = WS[i] + SIMD_4x32::load_le(&K[4 * i]); + WK.store_le(&W[4 * i]); + } + + data += 64; + blocks -= 1; + + for(size_t r = 0; r != 48; r += 16) { + auto w = next_w(WS) + SIMD_4x32::load_le(&K[r + 16]); + + SHA2_32_F(A, B, C, D, E, F, G, H, W[0]); + SHA2_32_F(H, A, B, C, D, E, F, G, W[1]); + SHA2_32_F(G, H, A, B, C, D, E, F, W[2]); + SHA2_32_F(F, G, H, A, B, C, D, E, W[3]); + + w.store_le(&W[0]); + + w = next_w(WS) + SIMD_4x32::load_le(&K[r + 20]); + + SHA2_32_F(E, F, G, H, A, B, C, D, W[4]); + SHA2_32_F(D, E, F, G, H, A, B, C, W[5]); + SHA2_32_F(C, D, E, F, G, H, A, B, W[6]); + SHA2_32_F(B, C, D, E, F, G, H, A, W[7]); + + w.store_le(&W[4]); + + w = next_w(WS) + SIMD_4x32::load_le(&K[r + 24]); + + SHA2_32_F(A, B, C, D, E, F, G, H, W[8]); + SHA2_32_F(H, A, B, C, D, E, F, G, W[9]); + SHA2_32_F(G, H, A, B, C, D, E, F, W[10]); + SHA2_32_F(F, G, H, A, B, C, D, E, W[11]); + + w.store_le(&W[8]); + + w = next_w(WS) + SIMD_4x32::load_le(&K[r + 28]); + + SHA2_32_F(E, F, G, H, A, B, C, D, W[12]); + SHA2_32_F(D, E, F, G, H, A, B, C, W[13]); + SHA2_32_F(C, D, E, F, G, H, A, B, W[14]); + SHA2_32_F(B, C, D, E, F, G, H, A, W[15]); + + w.store_le(&W[12]); + } + + SHA2_32_F(A, B, C, D, E, F, G, H, W[0]); + SHA2_32_F(H, A, B, C, D, E, F, G, W[1]); + SHA2_32_F(G, H, A, B, C, D, E, F, W[2]); + SHA2_32_F(F, G, H, A, B, C, D, E, W[3]); + SHA2_32_F(E, F, G, H, A, B, C, D, W[4]); + SHA2_32_F(D, E, F, G, H, A, B, C, W[5]); + SHA2_32_F(C, D, E, F, G, H, A, B, W[6]); + SHA2_32_F(B, C, D, E, F, G, H, A, W[7]); + SHA2_32_F(A, B, C, D, E, F, G, H, W[8]); + SHA2_32_F(H, A, B, C, D, E, F, G, W[9]); + SHA2_32_F(G, H, A, B, C, D, E, F, W[10]); + SHA2_32_F(F, G, H, A, B, C, D, E, W[11]); + SHA2_32_F(E, F, G, H, A, B, C, D, W[12]); + SHA2_32_F(D, E, F, G, H, A, B, C, W[13]); + SHA2_32_F(C, D, E, F, G, H, A, B, W[14]); + SHA2_32_F(B, C, D, E, F, G, H, A, W[15]); + + A = (digest[0] += A); + B = (digest[1] += B); + C = (digest[2] += C); + D = (digest[3] += D); + E = (digest[4] += E); + F = (digest[5] += F); + G = (digest[6] += G); + H = (digest[7] += H); + } +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha2_32/sha2_32_bmi2/info.txt botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32_bmi2/info.txt --- botan3-3.7.1+dfsg/src/lib/hash/sha2_32/sha2_32_bmi2/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32_bmi2/info.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,12 +0,0 @@ - -SHA2_32_X86_BMI2 -> 20180526 - - - -name -> "SHA-256 BMI2" -brief -> "SHA-256 using BMI2 instructions" - - - -bmi2 - diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha2_32/sha2_32_bmi2/sha2_32_bmi2.cpp botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32_bmi2/sha2_32_bmi2.cpp --- botan3-3.7.1+dfsg/src/lib/hash/sha2_32/sha2_32_bmi2/sha2_32_bmi2.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32_bmi2/sha2_32_bmi2.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,118 +0,0 @@ -/* -* (C) 2018 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include - -#include -#include -#include -#include -#include - -namespace Botan { - -/* -Your eyes do not decieve you; this is currently just a copy of the -baseline SHA-256 implementation. Because we compile it with BMI2 -flags, GCC and Clang use the BMI2 instructions without further help. - -Likely instruction scheduling could be improved by using inline asm. -*/ -void SHA_256::compress_digest_x86_bmi2(digest_type& digest, std::span input, size_t blocks) { - uint32_t A = digest[0], B = digest[1], C = digest[2], D = digest[3], E = digest[4], F = digest[5], G = digest[6], - H = digest[7]; - - std::array W; - - BufferSlicer in(input); - - for(size_t i = 0; i != blocks; ++i) { - load_be(W, in.take()); - - // clang-format off - - SHA2_32_F(A, B, C, D, E, F, G, H, W[ 0], W[14], W[ 9], W[ 1], 0x428A2F98); - SHA2_32_F(H, A, B, C, D, E, F, G, W[ 1], W[15], W[10], W[ 2], 0x71374491); - SHA2_32_F(G, H, A, B, C, D, E, F, W[ 2], W[ 0], W[11], W[ 3], 0xB5C0FBCF); - SHA2_32_F(F, G, H, A, B, C, D, E, W[ 3], W[ 1], W[12], W[ 4], 0xE9B5DBA5); - SHA2_32_F(E, F, G, H, A, B, C, D, W[ 4], W[ 2], W[13], W[ 5], 0x3956C25B); - SHA2_32_F(D, E, F, G, H, A, B, C, W[ 5], W[ 3], W[14], W[ 6], 0x59F111F1); - SHA2_32_F(C, D, E, F, G, H, A, B, W[ 6], W[ 4], W[15], W[ 7], 0x923F82A4); - SHA2_32_F(B, C, D, E, F, G, H, A, W[ 7], W[ 5], W[ 0], W[ 8], 0xAB1C5ED5); - SHA2_32_F(A, B, C, D, E, F, G, H, W[ 8], W[ 6], W[ 1], W[ 9], 0xD807AA98); - SHA2_32_F(H, A, B, C, D, E, F, G, W[ 9], W[ 7], W[ 2], W[10], 0x12835B01); - SHA2_32_F(G, H, A, B, C, D, E, F, W[10], W[ 8], W[ 3], W[11], 0x243185BE); - SHA2_32_F(F, G, H, A, B, C, D, E, W[11], W[ 9], W[ 4], W[12], 0x550C7DC3); - SHA2_32_F(E, F, G, H, A, B, C, D, W[12], W[10], W[ 5], W[13], 0x72BE5D74); - SHA2_32_F(D, E, F, G, H, A, B, C, W[13], W[11], W[ 6], W[14], 0x80DEB1FE); - SHA2_32_F(C, D, E, F, G, H, A, B, W[14], W[12], W[ 7], W[15], 0x9BDC06A7); - SHA2_32_F(B, C, D, E, F, G, H, A, W[15], W[13], W[ 8], W[ 0], 0xC19BF174); - - SHA2_32_F(A, B, C, D, E, F, G, H, W[ 0], W[14], W[ 9], W[ 1], 0xE49B69C1); - SHA2_32_F(H, A, B, C, D, E, F, G, W[ 1], W[15], W[10], W[ 2], 0xEFBE4786); - SHA2_32_F(G, H, A, B, C, D, E, F, W[ 2], W[ 0], W[11], W[ 3], 0x0FC19DC6); - SHA2_32_F(F, G, H, A, B, C, D, E, W[ 3], W[ 1], W[12], W[ 4], 0x240CA1CC); - SHA2_32_F(E, F, G, H, A, B, C, D, W[ 4], W[ 2], W[13], W[ 5], 0x2DE92C6F); - SHA2_32_F(D, E, F, G, H, A, B, C, W[ 5], W[ 3], W[14], W[ 6], 0x4A7484AA); - SHA2_32_F(C, D, E, F, G, H, A, B, W[ 6], W[ 4], W[15], W[ 7], 0x5CB0A9DC); - SHA2_32_F(B, C, D, E, F, G, H, A, W[ 7], W[ 5], W[ 0], W[ 8], 0x76F988DA); - SHA2_32_F(A, B, C, D, E, F, G, H, W[ 8], W[ 6], W[ 1], W[ 9], 0x983E5152); - SHA2_32_F(H, A, B, C, D, E, F, G, W[ 9], W[ 7], W[ 2], W[10], 0xA831C66D); - SHA2_32_F(G, H, A, B, C, D, E, F, W[10], W[ 8], W[ 3], W[11], 0xB00327C8); - SHA2_32_F(F, G, H, A, B, C, D, E, W[11], W[ 9], W[ 4], W[12], 0xBF597FC7); - SHA2_32_F(E, F, G, H, A, B, C, D, W[12], W[10], W[ 5], W[13], 0xC6E00BF3); - SHA2_32_F(D, E, F, G, H, A, B, C, W[13], W[11], W[ 6], W[14], 0xD5A79147); - SHA2_32_F(C, D, E, F, G, H, A, B, W[14], W[12], W[ 7], W[15], 0x06CA6351); - SHA2_32_F(B, C, D, E, F, G, H, A, W[15], W[13], W[ 8], W[ 0], 0x14292967); - - SHA2_32_F(A, B, C, D, E, F, G, H, W[ 0], W[14], W[ 9], W[ 1], 0x27B70A85); - SHA2_32_F(H, A, B, C, D, E, F, G, W[ 1], W[15], W[10], W[ 2], 0x2E1B2138); - SHA2_32_F(G, H, A, B, C, D, E, F, W[ 2], W[ 0], W[11], W[ 3], 0x4D2C6DFC); - SHA2_32_F(F, G, H, A, B, C, D, E, W[ 3], W[ 1], W[12], W[ 4], 0x53380D13); - SHA2_32_F(E, F, G, H, A, B, C, D, W[ 4], W[ 2], W[13], W[ 5], 0x650A7354); - SHA2_32_F(D, E, F, G, H, A, B, C, W[ 5], W[ 3], W[14], W[ 6], 0x766A0ABB); - SHA2_32_F(C, D, E, F, G, H, A, B, W[ 6], W[ 4], W[15], W[ 7], 0x81C2C92E); - SHA2_32_F(B, C, D, E, F, G, H, A, W[ 7], W[ 5], W[ 0], W[ 8], 0x92722C85); - SHA2_32_F(A, B, C, D, E, F, G, H, W[ 8], W[ 6], W[ 1], W[ 9], 0xA2BFE8A1); - SHA2_32_F(H, A, B, C, D, E, F, G, W[ 9], W[ 7], W[ 2], W[10], 0xA81A664B); - SHA2_32_F(G, H, A, B, C, D, E, F, W[10], W[ 8], W[ 3], W[11], 0xC24B8B70); - SHA2_32_F(F, G, H, A, B, C, D, E, W[11], W[ 9], W[ 4], W[12], 0xC76C51A3); - SHA2_32_F(E, F, G, H, A, B, C, D, W[12], W[10], W[ 5], W[13], 0xD192E819); - SHA2_32_F(D, E, F, G, H, A, B, C, W[13], W[11], W[ 6], W[14], 0xD6990624); - SHA2_32_F(C, D, E, F, G, H, A, B, W[14], W[12], W[ 7], W[15], 0xF40E3585); - SHA2_32_F(B, C, D, E, F, G, H, A, W[15], W[13], W[ 8], W[ 0], 0x106AA070); - - SHA2_32_F(A, B, C, D, E, F, G, H, W[ 0], W[14], W[ 9], W[ 1], 0x19A4C116); - SHA2_32_F(H, A, B, C, D, E, F, G, W[ 1], W[15], W[10], W[ 2], 0x1E376C08); - SHA2_32_F(G, H, A, B, C, D, E, F, W[ 2], W[ 0], W[11], W[ 3], 0x2748774C); - SHA2_32_F(F, G, H, A, B, C, D, E, W[ 3], W[ 1], W[12], W[ 4], 0x34B0BCB5); - SHA2_32_F(E, F, G, H, A, B, C, D, W[ 4], W[ 2], W[13], W[ 5], 0x391C0CB3); - SHA2_32_F(D, E, F, G, H, A, B, C, W[ 5], W[ 3], W[14], W[ 6], 0x4ED8AA4A); - SHA2_32_F(C, D, E, F, G, H, A, B, W[ 6], W[ 4], W[15], W[ 7], 0x5B9CCA4F); - SHA2_32_F(B, C, D, E, F, G, H, A, W[ 7], W[ 5], W[ 0], W[ 8], 0x682E6FF3); - SHA2_32_F(A, B, C, D, E, F, G, H, W[ 8], W[ 6], W[ 1], W[ 9], 0x748F82EE); - SHA2_32_F(H, A, B, C, D, E, F, G, W[ 9], W[ 7], W[ 2], W[10], 0x78A5636F); - SHA2_32_F(G, H, A, B, C, D, E, F, W[10], W[ 8], W[ 3], W[11], 0x84C87814); - SHA2_32_F(F, G, H, A, B, C, D, E, W[11], W[ 9], W[ 4], W[12], 0x8CC70208); - SHA2_32_F(E, F, G, H, A, B, C, D, W[12], W[10], W[ 5], W[13], 0x90BEFFFA); - SHA2_32_F(D, E, F, G, H, A, B, C, W[13], W[11], W[ 6], W[14], 0xA4506CEB); - SHA2_32_F(C, D, E, F, G, H, A, B, W[14], W[12], W[ 7], W[15], 0xBEF9A3F7); - SHA2_32_F(B, C, D, E, F, G, H, A, W[15], W[13], W[ 8], W[ 0], 0xC67178F2); - - // clang-format on - - A = (digest[0] += A); - B = (digest[1] += B); - C = (digest[2] += C); - D = (digest[3] += D); - E = (digest[4] += E); - F = (digest[5] += F); - G = (digest[6] += G); - H = (digest[7] += H); - } -} - -} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha2_32/sha2_32_f.h botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32_f.h --- botan3-3.7.1+dfsg/src/lib/hash/sha2_32/sha2_32_f.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32_f.h 2026-05-07 01:38:28.000000000 +0000 @@ -29,14 +29,20 @@ uint32_t M3, uint32_t M4, uint32_t magic) { - uint32_t A_rho = rho<2, 13, 22>(A); - uint32_t E_rho = rho<6, 11, 25>(E); - uint32_t M2_sigma = sigma<17, 19, 10>(M2); - uint32_t M4_sigma = sigma<7, 18, 3>(M4); - H += magic + E_rho + choose(E, F, G) + M1; + H += magic + rho<6, 11, 25>(E) + choose(E, F, G) + M1; D += H; - H += A_rho + majority(A, B, C); - M1 += M2_sigma + M3 + M4_sigma; + H += rho<2, 13, 22>(A) + majority(A, B, C); + M1 += sigma<17, 19, 10>(M2) + M3 + sigma<7, 18, 3>(M4); +} + +/* +* SHA-256 F1 Function (No Message Expansion) +*/ +BOTAN_FORCE_INLINE void SHA2_32_F( + uint32_t A, uint32_t B, uint32_t C, uint32_t& D, uint32_t E, uint32_t F, uint32_t G, uint32_t& H, uint32_t M) { + H += rho<6, 11, 25>(E) + choose(E, F, G) + M; + D += H; + H += rho<2, 13, 22>(A) + majority(A, B, C); } } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha2_32/sha2_32_simd/info.txt botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32_simd/info.txt --- botan3-3.7.1+dfsg/src/lib/hash/sha2_32/sha2_32_simd/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32_simd/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,51 @@ + +SHA2_32_SIMD -> 20250402 + + + +name -> "SHA-256 using SIMD" +brief -> "SHA-256 using SIMD instructions" + + + +x86_64:ssse3 +x86_32:ssse3 +x32:ssse3 + +arm32:neon +arm64:neon + +wasm:simd128 + + + +x86_64 +x86_32 +x32 + +arm32 +arm64 + +# Works for VMX and LSX but not faster on systems tested so far +#ppc64 +#loongson64 + +wasm + + + +x86_32:ssse3 +x86_64:ssse3 +x32:ssse3 +arm32:neon +arm64:neon +ppc32:altivec +ppc64:altivec +loongarch64:lsx +wasm:simd128 + + + +cpuid +simd_4x32 + diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha2_32/sha2_32_simd/sha2_32_simd.cpp botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32_simd/sha2_32_simd.cpp --- botan3-3.7.1+dfsg/src/lib/hash/sha2_32/sha2_32_simd/sha2_32_simd.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32_simd/sha2_32_simd.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,155 @@ +/* +* (C) 2025 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include + +namespace Botan { + +namespace { + +BOTAN_FN_ISA_SIMD_4X32 BOTAN_FORCE_INLINE SIMD_4x32 sha256_simd_next_w(SIMD_4x32 x[4]) { + const SIMD_4x32 lo_mask = SIMD_4x32(0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0x00000000); + const SIMD_4x32 hi_mask = SIMD_4x32(0x00000000, 0x00000000, 0xFFFFFFFF, 0xFFFFFFFF); + + const SIMD_4x32 lo_word_shuf = SIMD_4x32(0x03020100, 0x07060504, 0x03020100, 0x07060504); + const SIMD_4x32 hi_word_shuf = SIMD_4x32(0x0B0A0908, 0x0F0E0D0C, 0x0B0A0908, 0x0F0E0D0C); + + auto t0 = SIMD_4x32::alignr4(x[1], x[0]); + x[0] += SIMD_4x32::alignr4(x[3], x[2]); + + x[0] += t0.rotr<7>() ^ t0.rotr<18>() ^ t0.shr<3>(); + + t0 = SIMD_4x32::byte_shuffle(x[3], hi_word_shuf); + auto s1 = t0.rotr<17>() ^ t0.rotr<19>() ^ t0.shr<10>(); + x[0] += s1 & lo_mask; + + t0 = SIMD_4x32::byte_shuffle(x[0], lo_word_shuf); + s1 = t0.rotr<17>() ^ t0.rotr<19>() ^ t0.shr<10>(); + x[0] += s1 & hi_mask; + + const auto tmp = x[0]; + x[0] = x[1]; + x[1] = x[2]; + x[2] = x[3]; + x[3] = tmp; + + return x[3]; +} + +} // namespace + +void BOTAN_FN_ISA_SIMD_4X32 BOTAN_SCRUB_STACK_AFTER_RETURN +SHA_256::compress_digest_x86_simd(digest_type& digest, std::span input, size_t blocks) { + // clang-format off + + alignas(64) const uint32_t K[64] = { + 0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, + 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, + 0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC, 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, + 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, 0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967, + 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, + 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, + 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, + 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2}; + + // clang-format on + + alignas(64) uint32_t W[16]; + + uint32_t A = digest[0]; + uint32_t B = digest[1]; + uint32_t C = digest[2]; + uint32_t D = digest[3]; + uint32_t E = digest[4]; + uint32_t F = digest[5]; + uint32_t G = digest[6]; + uint32_t H = digest[7]; + + const uint8_t* data = input.data(); + + while(blocks > 0) { + SIMD_4x32 WS[4]; + + for(size_t i = 0; i < 4; i++) { + WS[i] = SIMD_4x32::load_be(&data[16 * i]); + auto WK = WS[i] + SIMD_4x32::load_le(&K[4 * i]); + WK.store_le(&W[4 * i]); + } + + data += 64; + blocks -= 1; + + for(size_t r = 0; r != 48; r += 16) { + auto w = sha256_simd_next_w(WS) + SIMD_4x32::load_le(&K[r + 16]); + + SHA2_32_F(A, B, C, D, E, F, G, H, W[0]); + SHA2_32_F(H, A, B, C, D, E, F, G, W[1]); + SHA2_32_F(G, H, A, B, C, D, E, F, W[2]); + SHA2_32_F(F, G, H, A, B, C, D, E, W[3]); + + w.store_le(&W[0]); + + w = sha256_simd_next_w(WS) + SIMD_4x32::load_le(&K[r + 20]); + + SHA2_32_F(E, F, G, H, A, B, C, D, W[4]); + SHA2_32_F(D, E, F, G, H, A, B, C, W[5]); + SHA2_32_F(C, D, E, F, G, H, A, B, W[6]); + SHA2_32_F(B, C, D, E, F, G, H, A, W[7]); + + w.store_le(&W[4]); + + w = sha256_simd_next_w(WS) + SIMD_4x32::load_le(&K[r + 24]); + + SHA2_32_F(A, B, C, D, E, F, G, H, W[8]); + SHA2_32_F(H, A, B, C, D, E, F, G, W[9]); + SHA2_32_F(G, H, A, B, C, D, E, F, W[10]); + SHA2_32_F(F, G, H, A, B, C, D, E, W[11]); + + w.store_le(&W[8]); + + w = sha256_simd_next_w(WS) + SIMD_4x32::load_le(&K[r + 28]); + + SHA2_32_F(E, F, G, H, A, B, C, D, W[12]); + SHA2_32_F(D, E, F, G, H, A, B, C, W[13]); + SHA2_32_F(C, D, E, F, G, H, A, B, W[14]); + SHA2_32_F(B, C, D, E, F, G, H, A, W[15]); + + w.store_le(&W[12]); + } + + SHA2_32_F(A, B, C, D, E, F, G, H, W[0]); + SHA2_32_F(H, A, B, C, D, E, F, G, W[1]); + SHA2_32_F(G, H, A, B, C, D, E, F, W[2]); + SHA2_32_F(F, G, H, A, B, C, D, E, W[3]); + SHA2_32_F(E, F, G, H, A, B, C, D, W[4]); + SHA2_32_F(D, E, F, G, H, A, B, C, W[5]); + SHA2_32_F(C, D, E, F, G, H, A, B, W[6]); + SHA2_32_F(B, C, D, E, F, G, H, A, W[7]); + SHA2_32_F(A, B, C, D, E, F, G, H, W[8]); + SHA2_32_F(H, A, B, C, D, E, F, G, W[9]); + SHA2_32_F(G, H, A, B, C, D, E, F, W[10]); + SHA2_32_F(F, G, H, A, B, C, D, E, W[11]); + SHA2_32_F(E, F, G, H, A, B, C, D, W[12]); + SHA2_32_F(D, E, F, G, H, A, B, C, W[13]); + SHA2_32_F(C, D, E, F, G, H, A, B, W[14]); + SHA2_32_F(B, C, D, E, F, G, H, A, W[15]); + + A = (digest[0] += A); + B = (digest[1] += B); + C = (digest[2] += C); + D = (digest[3] += D); + E = (digest[4] += E); + F = (digest[5] += F); + G = (digest[6] += G); + H = (digest[7] += H); + } +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha2_32/sha2_32_x86/info.txt botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32_x86/info.txt --- botan3-3.7.1+dfsg/src/lib/hash/sha2_32/sha2_32_x86/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32_x86/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + SHA2_32_X86 -> 20170518 - + name -> "SHA-256 SIMD" @@ -13,3 +13,8 @@ ssse3 sse41 + + +cpuid +simd_4x32 + diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha2_32/sha2_32_x86/sha2_32_x86.cpp botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32_x86/sha2_32_x86.cpp --- botan3-3.7.1+dfsg/src/lib/hash/sha2_32/sha2_32_x86/sha2_32_x86.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha2_32/sha2_32_x86/sha2_32_x86.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,19 +1,61 @@ /* -* Support for SHA-256 x86 instrinsic * Based on public domain code by Sean Gulley -* (https://github.com/mitls/hacl-star/tree/master/experimental/hash) +* +* Further changes +* +* (C) 2017,2020,2025,2026 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ #include + +#include +#include +#include #include namespace Botan { -// called from sha2_32.cpp -BOTAN_FUNC_ISA("sha,sse4.1,ssse3") -void SHA_256::compress_digest_x86(digest_type& digest, std::span input, size_t blocks) { +namespace { + +// NOLINTBEGIN(portability-simd-intrinsics) + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_SHANI void sha256_rnds4(SIMD_4x32& S0, + SIMD_4x32& S1, + const SIMD_4x32& msg, + const SIMD_4x32& k) { + const auto mk = msg + k; + S1 = SIMD_4x32(_mm_sha256rnds2_epu32(S1.raw(), S0.raw(), mk.raw())); + S0 = SIMD_4x32(_mm_sha256rnds2_epu32(S0.raw(), S1.raw(), mk.shift_elems_right<2>().raw())); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_SHANI void sha256_msg_exp(SIMD_4x32& W0, SIMD_4x32& W1, SIMD_4x32& W2, SIMD_4x32& W3) { + W2 += SIMD_4x32::alignr4(W1, W0); + W0 = SIMD_4x32(_mm_sha256msg1_epu32(W0.raw(), W1.raw())); + W2 = SIMD_4x32(_mm_sha256msg2_epu32(W2.raw(), W1.raw())); + + W3 += SIMD_4x32::alignr4(W2, W1); + W1 = SIMD_4x32(_mm_sha256msg1_epu32(W1.raw(), W2.raw())); + W3 = SIMD_4x32(_mm_sha256msg2_epu32(W3.raw(), W2.raw())); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_SHANI void sha256_permute_state(SIMD_4x32& S0, SIMD_4x32& S1) { + S0 = SIMD_4x32(_mm_shuffle_epi32(S0.raw(), 0b10110001)); // CDAB + S1 = SIMD_4x32(_mm_shuffle_epi32(S1.raw(), 0b00011011)); // EFGH + + const auto T = SIMD_4x32::alignr8(S0, S1); // ABEF + S1 = SIMD_4x32(_mm_blend_epi16(S1.raw(), S0.raw(), 0xF0)); // CDGH + S0 = T; +} + +// NOLINTEND(portability-simd-intrinsics) + +} // namespace + +void BOTAN_FN_ISA_SHANI BOTAN_SCRUB_STACK_AFTER_RETURN SHA_256::compress_digest_x86(digest_type& digest, + std::span input_span, + size_t blocks) { alignas(64) static const uint32_t K[] = { 0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, @@ -25,180 +67,72 @@ 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2, }; - const __m128i* K_mm = reinterpret_cast(K); + const uint8_t* input = input_span.data(); - uint32_t* state = &digest[0]; + SIMD_4x32 S0 = SIMD_4x32::load_le(&digest[0]); // NOLINT(*container-data-pointer) + SIMD_4x32 S1 = SIMD_4x32::load_le(&digest[4]); - const __m128i* input_mm = reinterpret_cast(input.data()); - const __m128i MASK = _mm_set_epi64x(0x0c0d0e0f08090a0b, 0x0405060700010203); + sha256_permute_state(S0, S1); - // Load initial values - __m128i STATE0 = _mm_loadu_si128(reinterpret_cast<__m128i*>(&state[0])); - __m128i STATE1 = _mm_loadu_si128(reinterpret_cast<__m128i*>(&state[4])); + while(blocks > 0) { + const auto S0_SAVE = S0; + const auto S1_SAVE = S1; - STATE0 = _mm_shuffle_epi32(STATE0, 0xB1); // CDAB - STATE1 = _mm_shuffle_epi32(STATE1, 0x1B); // EFGH + auto W0 = SIMD_4x32::load_be(input); + auto W1 = SIMD_4x32::load_be(input + 16); + auto W2 = SIMD_4x32::load_be(input + 32); + auto W3 = SIMD_4x32::load_be(input + 48); - __m128i TMP = _mm_alignr_epi8(STATE0, STATE1, 8); // ABEF - STATE1 = _mm_blend_epi16(STATE1, STATE0, 0xF0); // CDGH - STATE0 = TMP; + sha256_rnds4(S0, S1, W0, SIMD_4x32::load_le(&K[0])); + sha256_rnds4(S0, S1, W1, SIMD_4x32::load_le(&K[4])); + sha256_rnds4(S0, S1, W2, SIMD_4x32::load_le(&K[8])); + sha256_rnds4(S0, S1, W3, SIMD_4x32::load_le(&K[12])); - while(blocks > 0) { - // Save current state - const __m128i ABEF_SAVE = STATE0; - const __m128i CDGH_SAVE = STATE1; - - __m128i MSG; - - __m128i TMSG0 = _mm_shuffle_epi8(_mm_loadu_si128(input_mm), MASK); - __m128i TMSG1 = _mm_shuffle_epi8(_mm_loadu_si128(input_mm + 1), MASK); - __m128i TMSG2 = _mm_shuffle_epi8(_mm_loadu_si128(input_mm + 2), MASK); - __m128i TMSG3 = _mm_shuffle_epi8(_mm_loadu_si128(input_mm + 3), MASK); - - // Rounds 0-3 - MSG = _mm_add_epi32(TMSG0, _mm_load_si128(K_mm)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32(MSG, 0x0E)); - - // Rounds 4-7 - MSG = _mm_add_epi32(TMSG1, _mm_load_si128(K_mm + 1)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32(MSG, 0x0E)); - - TMSG0 = _mm_sha256msg1_epu32(TMSG0, TMSG1); - - // Rounds 8-11 - MSG = _mm_add_epi32(TMSG2, _mm_load_si128(K_mm + 2)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32(MSG, 0x0E)); - - TMSG1 = _mm_sha256msg1_epu32(TMSG1, TMSG2); - - // Rounds 12-15 - MSG = _mm_add_epi32(TMSG3, _mm_load_si128(K_mm + 3)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32(MSG, 0x0E)); - - TMSG0 = _mm_add_epi32(TMSG0, _mm_alignr_epi8(TMSG3, TMSG2, 4)); - TMSG0 = _mm_sha256msg2_epu32(TMSG0, TMSG3); - TMSG2 = _mm_sha256msg1_epu32(TMSG2, TMSG3); - - // Rounds 16-19 - MSG = _mm_add_epi32(TMSG0, _mm_load_si128(K_mm + 4)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32(MSG, 0x0E)); - - TMSG1 = _mm_add_epi32(TMSG1, _mm_alignr_epi8(TMSG0, TMSG3, 4)); - TMSG1 = _mm_sha256msg2_epu32(TMSG1, TMSG0); - TMSG3 = _mm_sha256msg1_epu32(TMSG3, TMSG0); - - // Rounds 20-23 - MSG = _mm_add_epi32(TMSG1, _mm_load_si128(K_mm + 5)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32(MSG, 0x0E)); - - TMSG2 = _mm_add_epi32(TMSG2, _mm_alignr_epi8(TMSG1, TMSG0, 4)); - TMSG2 = _mm_sha256msg2_epu32(TMSG2, TMSG1); - TMSG0 = _mm_sha256msg1_epu32(TMSG0, TMSG1); - - // Rounds 24-27 - MSG = _mm_add_epi32(TMSG2, _mm_load_si128(K_mm + 6)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32(MSG, 0x0E)); - - TMSG3 = _mm_add_epi32(TMSG3, _mm_alignr_epi8(TMSG2, TMSG1, 4)); - TMSG3 = _mm_sha256msg2_epu32(TMSG3, TMSG2); - TMSG1 = _mm_sha256msg1_epu32(TMSG1, TMSG2); - - // Rounds 28-31 - MSG = _mm_add_epi32(TMSG3, _mm_load_si128(K_mm + 7)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32(MSG, 0x0E)); - - TMSG0 = _mm_add_epi32(TMSG0, _mm_alignr_epi8(TMSG3, TMSG2, 4)); - TMSG0 = _mm_sha256msg2_epu32(TMSG0, TMSG3); - TMSG2 = _mm_sha256msg1_epu32(TMSG2, TMSG3); - - // Rounds 32-35 - MSG = _mm_add_epi32(TMSG0, _mm_load_si128(K_mm + 8)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32(MSG, 0x0E)); - - TMSG1 = _mm_add_epi32(TMSG1, _mm_alignr_epi8(TMSG0, TMSG3, 4)); - TMSG1 = _mm_sha256msg2_epu32(TMSG1, TMSG0); - TMSG3 = _mm_sha256msg1_epu32(TMSG3, TMSG0); - - // Rounds 36-39 - MSG = _mm_add_epi32(TMSG1, _mm_load_si128(K_mm + 9)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32(MSG, 0x0E)); - - TMSG2 = _mm_add_epi32(TMSG2, _mm_alignr_epi8(TMSG1, TMSG0, 4)); - TMSG2 = _mm_sha256msg2_epu32(TMSG2, TMSG1); - TMSG0 = _mm_sha256msg1_epu32(TMSG0, TMSG1); - - // Rounds 40-43 - MSG = _mm_add_epi32(TMSG2, _mm_load_si128(K_mm + 10)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32(MSG, 0x0E)); - - TMSG3 = _mm_add_epi32(TMSG3, _mm_alignr_epi8(TMSG2, TMSG1, 4)); - TMSG3 = _mm_sha256msg2_epu32(TMSG3, TMSG2); - TMSG1 = _mm_sha256msg1_epu32(TMSG1, TMSG2); - - // Rounds 44-47 - MSG = _mm_add_epi32(TMSG3, _mm_load_si128(K_mm + 11)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32(MSG, 0x0E)); - - TMSG0 = _mm_add_epi32(TMSG0, _mm_alignr_epi8(TMSG3, TMSG2, 4)); - TMSG0 = _mm_sha256msg2_epu32(TMSG0, TMSG3); - TMSG2 = _mm_sha256msg1_epu32(TMSG2, TMSG3); - - // Rounds 48-51 - MSG = _mm_add_epi32(TMSG0, _mm_load_si128(K_mm + 12)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32(MSG, 0x0E)); - - TMSG1 = _mm_add_epi32(TMSG1, _mm_alignr_epi8(TMSG0, TMSG3, 4)); - TMSG1 = _mm_sha256msg2_epu32(TMSG1, TMSG0); - TMSG3 = _mm_sha256msg1_epu32(TMSG3, TMSG0); - - // Rounds 52-55 - MSG = _mm_add_epi32(TMSG1, _mm_load_si128(K_mm + 13)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32(MSG, 0x0E)); - - TMSG2 = _mm_add_epi32(TMSG2, _mm_alignr_epi8(TMSG1, TMSG0, 4)); - TMSG2 = _mm_sha256msg2_epu32(TMSG2, TMSG1); - - // Rounds 56-59 - MSG = _mm_add_epi32(TMSG2, _mm_load_si128(K_mm + 14)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32(MSG, 0x0E)); - - TMSG3 = _mm_add_epi32(TMSG3, _mm_alignr_epi8(TMSG2, TMSG1, 4)); - TMSG3 = _mm_sha256msg2_epu32(TMSG3, TMSG2); - - // Rounds 60-63 - MSG = _mm_add_epi32(TMSG3, _mm_load_si128(K_mm + 15)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32(MSG, 0x0E)); + W0 = SIMD_4x32(_mm_sha256msg1_epu32(W0.raw(), W1.raw())); + W1 = SIMD_4x32(_mm_sha256msg1_epu32(W1.raw(), W2.raw())); + + sha256_msg_exp(W2, W3, W0, W1); + + sha256_rnds4(S0, S1, W0, SIMD_4x32::load_le(&K[4 * 4])); + sha256_rnds4(S0, S1, W1, SIMD_4x32::load_le(&K[4 * 5])); + + sha256_msg_exp(W0, W1, W2, W3); + + sha256_rnds4(S0, S1, W2, SIMD_4x32::load_le(&K[4 * 6])); + sha256_rnds4(S0, S1, W3, SIMD_4x32::load_le(&K[4 * 7])); + + sha256_msg_exp(W2, W3, W0, W1); + + sha256_rnds4(S0, S1, W0, SIMD_4x32::load_le(&K[4 * 8])); + sha256_rnds4(S0, S1, W1, SIMD_4x32::load_le(&K[4 * 9])); + + sha256_msg_exp(W0, W1, W2, W3); + + sha256_rnds4(S0, S1, W2, SIMD_4x32::load_le(&K[4 * 10])); + sha256_rnds4(S0, S1, W3, SIMD_4x32::load_le(&K[4 * 11])); + + sha256_msg_exp(W2, W3, W0, W1); + + sha256_rnds4(S0, S1, W0, SIMD_4x32::load_le(&K[4 * 12])); + sha256_rnds4(S0, S1, W1, SIMD_4x32::load_le(&K[4 * 13])); + + sha256_msg_exp(W0, W1, W2, W3); + + sha256_rnds4(S0, S1, W2, SIMD_4x32::load_le(&K[4 * 14])); + sha256_rnds4(S0, S1, W3, SIMD_4x32::load_le(&K[4 * 15])); // Add values back to state - STATE0 = _mm_add_epi32(STATE0, ABEF_SAVE); - STATE1 = _mm_add_epi32(STATE1, CDGH_SAVE); + S0 += S0_SAVE; + S1 += S1_SAVE; - input_mm += 4; + input += 64; blocks--; } - STATE0 = _mm_shuffle_epi32(STATE0, 0x1B); // FEBA - STATE1 = _mm_shuffle_epi32(STATE1, 0xB1); // DCHG + sha256_permute_state(S1, S0); - // Save state - _mm_storeu_si128(reinterpret_cast<__m128i*>(&state[0]), _mm_blend_epi16(STATE0, STATE1, 0xF0)); // DCBA - _mm_storeu_si128(reinterpret_cast<__m128i*>(&state[4]), _mm_alignr_epi8(STATE1, STATE0, 8)); // ABEF + S0.store_le(&digest[0]); // NOLINT(*container-data-pointer) + S1.store_le(&digest[4]); } } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha2_64/info.txt botan3-3.12.0+dfsg/src/lib/hash/sha2_64/info.txt --- botan3-3.7.1+dfsg/src/lib/hash/sha2_64/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha2_64/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,8 +1,10 @@ -SHA2_64 -> 20131128 SHA_384 -> 20250130 SHA_512 -> 20250130 SHA_512_256 -> 20250130 + +# TODO(Botan4) remove this macro +SHA2_64 -> 20131128 diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha2_64/sha2_64.cpp botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64.cpp --- botan3-3.7.1+dfsg/src/lib/hash/sha2_64/sha2_64.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,27 +7,40 @@ #include -#include -#include +#include #include -#include #include -#include + +#if defined(BOTAN_HAS_CPUID) + #include +#endif namespace Botan { namespace { std::string sha512_provider() { -#if defined(BOTAN_HAS_SHA2_64_BMI2) - if(CPUID::has_bmi2()) { - return "bmi2"; +#if defined(BOTAN_HAS_SHA2_64_X86) + if(auto feat = CPUID::check(CPUID::Feature::SHA512)) { + return *feat; } #endif #if defined(BOTAN_HAS_SHA2_64_ARMV8) - if(CPUID::has_arm_sha2_512()) { - return "armv8"; + if(auto feat = CPUID::check(CPUID::Feature::SHA2_512)) { + return *feat; + } +#endif + +#if defined(BOTAN_HAS_SHA2_64_X86_AVX512) + if(auto feat = CPUID::check(CPUID::Feature::AVX512, CPUID::Feature::BMI)) { + return *feat; + } +#endif + +#if defined(BOTAN_HAS_SHA2_64_X86_AVX2) + if(auto feat = CPUID::check(CPUID::Feature::AVX2, CPUID::Feature::BMI)) { + return *feat; } #endif @@ -41,22 +54,40 @@ */ //static void SHA_512::compress_digest(digest_type& digest, std::span input, size_t blocks) { -#if defined(BOTAN_HAS_SHA2_64_BMI2) - if(CPUID::has_bmi2()) { - return compress_digest_bmi2(digest, input, blocks); +#if defined(BOTAN_HAS_SHA2_64_X86) + if(CPUID::has(CPUID::Feature::SHA512)) { + return compress_digest_x86(digest, input, blocks); } #endif #if defined(BOTAN_HAS_SHA2_64_ARMV8) - if(CPUID::has_arm_sha2_512()) { + if(CPUID::has(CPUID::Feature::SHA2_512)) { return compress_digest_armv8(digest, input, blocks); } #endif - uint64_t A = digest[0], B = digest[1], C = digest[2], D = digest[3], E = digest[4], F = digest[5], G = digest[6], - H = digest[7]; +#if defined(BOTAN_HAS_SHA2_64_X86_AVX512) + if(CPUID::has(CPUID::Feature::AVX512, CPUID::Feature::BMI)) { + return compress_digest_x86_avx512(digest, input, blocks); + } +#endif + +#if defined(BOTAN_HAS_SHA2_64_X86_AVX2) + if(CPUID::has(CPUID::Feature::AVX2, CPUID::Feature::BMI)) { + return compress_digest_x86_avx2(digest, input, blocks); + } +#endif + + uint64_t A = digest[0]; + uint64_t B = digest[1]; + uint64_t C = digest[2]; + uint64_t D = digest[3]; + uint64_t E = digest[4]; + uint64_t F = digest[5]; + uint64_t G = digest[6]; + uint64_t H = digest[7]; - std::array W; + std::array W{}; BufferSlicer in(input); diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha2_64/sha2_64.h botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64.h --- botan3-3.7.1+dfsg/src/lib/hash/sha2_64/sha2_64.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64.h 2026-05-07 01:38:28.000000000 +0000 @@ -86,8 +86,16 @@ public: static void compress_digest(digest_type& digest, std::span input, size_t blocks); -#if defined(BOTAN_HAS_SHA2_64_BMI2) - static void compress_digest_bmi2(digest_type& digest, std::span input, size_t blocks); +#if defined(BOTAN_HAS_SHA2_64_X86_AVX2) + static void compress_digest_x86_avx2(digest_type& digest, std::span input, size_t blocks); +#endif + +#if defined(BOTAN_HAS_SHA2_64_X86_AVX512) + static void compress_digest_x86_avx512(digest_type& digest, std::span input, size_t blocks); +#endif + +#if defined(BOTAN_HAS_SHA2_64_X86) + static void compress_digest_x86(digest_type& digest, std::span input, size_t blocks); #endif #if defined(BOTAN_HAS_SHA2_64_ARMV8) diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha2_64/sha2_64_armv8/info.txt botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64_armv8/info.txt --- botan3-3.7.1+dfsg/src/lib/hash/sha2_64/sha2_64_armv8/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64_armv8/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + SHA2_64_ARMV8 -> 20231220 - + name -> "SHA-512 ARMv8" @@ -8,5 +8,10 @@ +armv8crypto armv8sha512 + + +cpuid + diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha2_64/sha2_64_armv8/sha2_64_armv8.cpp botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64_armv8/sha2_64_armv8.cpp --- botan3-3.7.1+dfsg/src/lib/hash/sha2_64/sha2_64_armv8/sha2_64_armv8.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64_armv8/sha2_64_armv8.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,6 +7,8 @@ */ #include + +#include #include namespace Botan { @@ -14,8 +16,9 @@ /* * SHA-512 using CPU instructions in ARMv8 */ -BOTAN_FUNC_ISA("arch=armv8.2-a+sha3") -void SHA_512::compress_digest_armv8(digest_type& digest, std::span input8, size_t blocks) { +void BOTAN_FN_ISA_SHA512 SHA_512::compress_digest_armv8(digest_type& digest, + std::span input8, + size_t blocks) { alignas(128) static const uint64_t K[] = { 0x428A2F98D728AE22, 0x7137449123EF65CD, 0xB5C0FBCFEC4D3B2F, 0xE9B5DBA58189DBBC, 0x3956C25BF348B538, 0x59F111F1B605D019, 0x923F82A4AF194F9B, 0xAB1C5ED5DA6D8118, 0xD807AA98A3030242, 0x12835B0145706FBE, @@ -35,7 +38,7 @@ 0x431D67C49C100D4C, 0x4CC5D4BECB3E42B6, 0x597F299CFC657E2A, 0x5FCB6FAB3AD6FAEC, 0x6C44198C4A475817}; // Load initial values - uint64x2_t STATE0 = vld1q_u64(&digest[0]); // ab + uint64x2_t STATE0 = vld1q_u64(&digest[0]); // ab NOLINT(*-container-data-pointer) uint64x2_t STATE1 = vld1q_u64(&digest[2]); // cd uint64x2_t STATE2 = vld1q_u64(&digest[4]); // ef uint64x2_t STATE3 = vld1q_u64(&digest[6]); // gh @@ -67,7 +70,9 @@ MSG6 = vreinterpretq_u64_u8(vrev64q_u8(vreinterpretq_u8_u64(MSG6))); MSG7 = vreinterpretq_u64_u8(vrev64q_u8(vreinterpretq_u8_u64(MSG7))); - uint64x2_t MSG_K, TSTATE0, TSTATE1; + uint64x2_t MSG_K; + uint64x2_t TSTATE0; + uint64x2_t TSTATE1; // Rounds 0-1 MSG_K = vaddq_u64(MSG0, vld1q_u64(&K[2 * 0])); @@ -392,7 +397,7 @@ } // Save state - vst1q_u64(&digest[0], STATE0); + vst1q_u64(&digest[0], STATE0); // NOLINT(*-container-data-pointer) vst1q_u64(&digest[2], STATE1); vst1q_u64(&digest[4], STATE2); vst1q_u64(&digest[6], STATE3); diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha2_64/sha2_64_avx2/info.txt botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64_avx2/info.txt --- botan3-3.7.1+dfsg/src/lib/hash/sha2_64/sha2_64_avx2/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64_avx2/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,25 @@ + +SHA2_64_X86_AVX2 -> 20190117 + + + +name -> "SHA-512 AVX2/BMI2" +brief -> "SHA-512 using AVX2/BMI2 instructions" + + + +bmi2 +avx2 + + +# Needs 64-bit registers to be useful + +x86_64 +x32 + + + +cpuid +simd_4x64 +simd_2x64 + diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha2_64/sha2_64_avx2/sha2_64_avx2.cpp botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64_avx2/sha2_64_avx2.cpp --- botan3-3.7.1+dfsg/src/lib/hash/sha2_64/sha2_64_avx2/sha2_64_avx2.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64_avx2/sha2_64_avx2.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,346 @@ +/* +* (C) 2025 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include + +namespace Botan { + +namespace { + +template +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX2_BMI2 SIMD_T sha512_next_w(SIMD_T x[8]) { + auto t0 = SIMD_T::alignr8(x[1], x[0]); + auto t1 = SIMD_T::alignr8(x[5], x[4]); + + auto s0 = t0.template rotr<1>() ^ t0.template rotr<8>() ^ t0.template shr<7>(); + auto s1 = x[7].template rotr<19>() ^ x[7].template rotr<61>() ^ x[7].template shr<6>(); + + auto nx = x[0] + s0 + s1 + t1; + + x[0] = x[1]; + x[1] = x[2]; + x[2] = x[3]; + x[3] = x[4]; + x[4] = x[5]; + x[5] = x[6]; + x[6] = x[7]; + x[7] = nx; + + return x[7]; +} + +} // namespace + +BOTAN_FN_ISA_AVX2_BMI2 void SHA_512::compress_digest_x86_avx2(digest_type& digest, + std::span input, + size_t blocks) { + // clang-format off + alignas(64) const uint64_t K[80] = { + 0x428A2F98D728AE22, 0x7137449123EF65CD, 0xB5C0FBCFEC4D3B2F, 0xE9B5DBA58189DBBC, + 0x3956C25BF348B538, 0x59F111F1B605D019, 0x923F82A4AF194F9B, 0xAB1C5ED5DA6D8118, + 0xD807AA98A3030242, 0x12835B0145706FBE, 0x243185BE4EE4B28C, 0x550C7DC3D5FFB4E2, + 0x72BE5D74F27B896F, 0x80DEB1FE3B1696B1, 0x9BDC06A725C71235, 0xC19BF174CF692694, + 0xE49B69C19EF14AD2, 0xEFBE4786384F25E3, 0x0FC19DC68B8CD5B5, 0x240CA1CC77AC9C65, + 0x2DE92C6F592B0275, 0x4A7484AA6EA6E483, 0x5CB0A9DCBD41FBD4, 0x76F988DA831153B5, + 0x983E5152EE66DFAB, 0xA831C66D2DB43210, 0xB00327C898FB213F, 0xBF597FC7BEEF0EE4, + 0xC6E00BF33DA88FC2, 0xD5A79147930AA725, 0x06CA6351E003826F, 0x142929670A0E6E70, + 0x27B70A8546D22FFC, 0x2E1B21385C26C926, 0x4D2C6DFC5AC42AED, 0x53380D139D95B3DF, + 0x650A73548BAF63DE, 0x766A0ABB3C77B2A8, 0x81C2C92E47EDAEE6, 0x92722C851482353B, + 0xA2BFE8A14CF10364, 0xA81A664BBC423001, 0xC24B8B70D0F89791, 0xC76C51A30654BE30, + 0xD192E819D6EF5218, 0xD69906245565A910, 0xF40E35855771202A, 0x106AA07032BBD1B8, + 0x19A4C116B8D2D0C8, 0x1E376C085141AB53, 0x2748774CDF8EEB99, 0x34B0BCB5E19B48A8, + 0x391C0CB3C5C95A63, 0x4ED8AA4AE3418ACB, 0x5B9CCA4F7763E373, 0x682E6FF3D6B2B8A3, + 0x748F82EE5DEFB2FC, 0x78A5636F43172F60, 0x84C87814A1F0AB72, 0x8CC702081A6439EC, + 0x90BEFFFA23631E28, 0xA4506CEBDE82BDE9, 0xBEF9A3F7B2C67915, 0xC67178F2E372532B, + 0xCA273ECEEA26619C, 0xD186B8C721C0C207, 0xEADA7DD6CDE0EB1E, 0xF57D4F7FEE6ED178, + 0x06F067AA72176FBA, 0x0A637DC5A2C898A6, 0x113F9804BEF90DAE, 0x1B710B35131C471B, + 0x28DB77F523047D84, 0x32CAAB7B40C72493, 0x3C9EBE0A15C9BEBC, 0x431D67C49C100D4C, + 0x4CC5D4BECB3E42B6, 0x597F299CFC657E2A, 0x5FCB6FAB3AD6FAEC, 0x6C44198C4A475817, + }; + // clang-format on + + alignas(64) uint64_t W[16] = {0}; + alignas(64) uint64_t W2[80]; + + uint64_t A = digest[0]; + uint64_t B = digest[1]; + uint64_t C = digest[2]; + uint64_t D = digest[3]; + uint64_t E = digest[4]; + uint64_t F = digest[5]; + uint64_t G = digest[6]; + uint64_t H = digest[7]; + + const uint8_t* data = input.data(); + + while(blocks >= 2) { + SIMD_4x64 WS[8]; + + for(size_t i = 0; i < 8; i++) { + WS[i] = SIMD_4x64::load_be2(&data[16 * i], &data[128 + 16 * i]); + auto WK = WS[i] + SIMD_4x64::broadcast_2x64(&K[2 * i]); + WK.store_le2(&W[2 * i], &W2[2 * i]); + } + + data += 2 * 128; + blocks -= 2; + + // First 64 rounds of SHA-512 + for(size_t r = 0; r != 64; r += 16) { + auto w = sha512_next_w(WS) + SIMD_4x64::broadcast_2x64(&K[r + 16]); + SHA2_64_F(A, B, C, D, E, F, G, H, W[0]); + SHA2_64_F(H, A, B, C, D, E, F, G, W[1]); + w.store_le2(&W[0], &W2[r + 16]); + + w = sha512_next_w(WS) + SIMD_4x64::broadcast_2x64(&K[r + 18]); + SHA2_64_F(G, H, A, B, C, D, E, F, W[2]); + SHA2_64_F(F, G, H, A, B, C, D, E, W[3]); + w.store_le2(&W[2], &W2[r + 18]); + + w = sha512_next_w(WS) + SIMD_4x64::broadcast_2x64(&K[r + 20]); + SHA2_64_F(E, F, G, H, A, B, C, D, W[4]); + SHA2_64_F(D, E, F, G, H, A, B, C, W[5]); + w.store_le2(&W[4], &W2[r + 20]); + + w = sha512_next_w(WS) + SIMD_4x64::broadcast_2x64(&K[r + 22]); + SHA2_64_F(C, D, E, F, G, H, A, B, W[6]); + SHA2_64_F(B, C, D, E, F, G, H, A, W[7]); + w.store_le2(&W[6], &W2[r + 22]); + + w = sha512_next_w(WS) + SIMD_4x64::broadcast_2x64(&K[r + 24]); + SHA2_64_F(A, B, C, D, E, F, G, H, W[8]); + SHA2_64_F(H, A, B, C, D, E, F, G, W[9]); + w.store_le2(&W[8], &W2[r + 24]); + + w = sha512_next_w(WS) + SIMD_4x64::broadcast_2x64(&K[r + 26]); + SHA2_64_F(G, H, A, B, C, D, E, F, W[10]); + SHA2_64_F(F, G, H, A, B, C, D, E, W[11]); + w.store_le2(&W[10], &W2[r + 26]); + + w = sha512_next_w(WS) + SIMD_4x64::broadcast_2x64(&K[r + 28]); + SHA2_64_F(E, F, G, H, A, B, C, D, W[12]); + SHA2_64_F(D, E, F, G, H, A, B, C, W[13]); + w.store_le2(&W[12], &W2[r + 28]); + + w = sha512_next_w(WS) + SIMD_4x64::broadcast_2x64(&K[r + 30]); + SHA2_64_F(C, D, E, F, G, H, A, B, W[14]); + SHA2_64_F(B, C, D, E, F, G, H, A, W[15]); + w.store_le2(&W[14], &W2[r + 30]); + } + + // Final 16 rounds of SHA-512 + SHA2_64_F(A, B, C, D, E, F, G, H, W[0]); + SHA2_64_F(H, A, B, C, D, E, F, G, W[1]); + SHA2_64_F(G, H, A, B, C, D, E, F, W[2]); + SHA2_64_F(F, G, H, A, B, C, D, E, W[3]); + SHA2_64_F(E, F, G, H, A, B, C, D, W[4]); + SHA2_64_F(D, E, F, G, H, A, B, C, W[5]); + SHA2_64_F(C, D, E, F, G, H, A, B, W[6]); + SHA2_64_F(B, C, D, E, F, G, H, A, W[7]); + SHA2_64_F(A, B, C, D, E, F, G, H, W[8]); + SHA2_64_F(H, A, B, C, D, E, F, G, W[9]); + SHA2_64_F(G, H, A, B, C, D, E, F, W[10]); + SHA2_64_F(F, G, H, A, B, C, D, E, W[11]); + SHA2_64_F(E, F, G, H, A, B, C, D, W[12]); + SHA2_64_F(D, E, F, G, H, A, B, C, W[13]); + SHA2_64_F(C, D, E, F, G, H, A, B, W[14]); + SHA2_64_F(B, C, D, E, F, G, H, A, W[15]); + + A = (digest[0] += A); + B = (digest[1] += B); + C = (digest[2] += C); + D = (digest[3] += D); + E = (digest[4] += E); + F = (digest[5] += F); + G = (digest[6] += G); + H = (digest[7] += H); + + // Second block of SHA-512 compression, with pre-expanded message + SHA2_64_F(A, B, C, D, E, F, G, H, W2[0]); + SHA2_64_F(H, A, B, C, D, E, F, G, W2[1]); + SHA2_64_F(G, H, A, B, C, D, E, F, W2[2]); + SHA2_64_F(F, G, H, A, B, C, D, E, W2[3]); + SHA2_64_F(E, F, G, H, A, B, C, D, W2[4]); + SHA2_64_F(D, E, F, G, H, A, B, C, W2[5]); + SHA2_64_F(C, D, E, F, G, H, A, B, W2[6]); + SHA2_64_F(B, C, D, E, F, G, H, A, W2[7]); + SHA2_64_F(A, B, C, D, E, F, G, H, W2[8]); + SHA2_64_F(H, A, B, C, D, E, F, G, W2[9]); + SHA2_64_F(G, H, A, B, C, D, E, F, W2[10]); + SHA2_64_F(F, G, H, A, B, C, D, E, W2[11]); + SHA2_64_F(E, F, G, H, A, B, C, D, W2[12]); + SHA2_64_F(D, E, F, G, H, A, B, C, W2[13]); + SHA2_64_F(C, D, E, F, G, H, A, B, W2[14]); + SHA2_64_F(B, C, D, E, F, G, H, A, W2[15]); + + SHA2_64_F(A, B, C, D, E, F, G, H, W2[16]); + SHA2_64_F(H, A, B, C, D, E, F, G, W2[17]); + SHA2_64_F(G, H, A, B, C, D, E, F, W2[18]); + SHA2_64_F(F, G, H, A, B, C, D, E, W2[19]); + SHA2_64_F(E, F, G, H, A, B, C, D, W2[20]); + SHA2_64_F(D, E, F, G, H, A, B, C, W2[21]); + SHA2_64_F(C, D, E, F, G, H, A, B, W2[22]); + SHA2_64_F(B, C, D, E, F, G, H, A, W2[23]); + SHA2_64_F(A, B, C, D, E, F, G, H, W2[24]); + SHA2_64_F(H, A, B, C, D, E, F, G, W2[25]); + SHA2_64_F(G, H, A, B, C, D, E, F, W2[26]); + SHA2_64_F(F, G, H, A, B, C, D, E, W2[27]); + SHA2_64_F(E, F, G, H, A, B, C, D, W2[28]); + SHA2_64_F(D, E, F, G, H, A, B, C, W2[29]); + SHA2_64_F(C, D, E, F, G, H, A, B, W2[30]); + SHA2_64_F(B, C, D, E, F, G, H, A, W2[31]); + + SHA2_64_F(A, B, C, D, E, F, G, H, W2[32]); + SHA2_64_F(H, A, B, C, D, E, F, G, W2[33]); + SHA2_64_F(G, H, A, B, C, D, E, F, W2[34]); + SHA2_64_F(F, G, H, A, B, C, D, E, W2[35]); + SHA2_64_F(E, F, G, H, A, B, C, D, W2[36]); + SHA2_64_F(D, E, F, G, H, A, B, C, W2[37]); + SHA2_64_F(C, D, E, F, G, H, A, B, W2[38]); + SHA2_64_F(B, C, D, E, F, G, H, A, W2[39]); + SHA2_64_F(A, B, C, D, E, F, G, H, W2[40]); + SHA2_64_F(H, A, B, C, D, E, F, G, W2[41]); + SHA2_64_F(G, H, A, B, C, D, E, F, W2[42]); + SHA2_64_F(F, G, H, A, B, C, D, E, W2[43]); + SHA2_64_F(E, F, G, H, A, B, C, D, W2[44]); + SHA2_64_F(D, E, F, G, H, A, B, C, W2[45]); + SHA2_64_F(C, D, E, F, G, H, A, B, W2[46]); + SHA2_64_F(B, C, D, E, F, G, H, A, W2[47]); + + SHA2_64_F(A, B, C, D, E, F, G, H, W2[48]); + SHA2_64_F(H, A, B, C, D, E, F, G, W2[49]); + SHA2_64_F(G, H, A, B, C, D, E, F, W2[50]); + SHA2_64_F(F, G, H, A, B, C, D, E, W2[51]); + SHA2_64_F(E, F, G, H, A, B, C, D, W2[52]); + SHA2_64_F(D, E, F, G, H, A, B, C, W2[53]); + SHA2_64_F(C, D, E, F, G, H, A, B, W2[54]); + SHA2_64_F(B, C, D, E, F, G, H, A, W2[55]); + SHA2_64_F(A, B, C, D, E, F, G, H, W2[56]); + SHA2_64_F(H, A, B, C, D, E, F, G, W2[57]); + SHA2_64_F(G, H, A, B, C, D, E, F, W2[58]); + SHA2_64_F(F, G, H, A, B, C, D, E, W2[59]); + SHA2_64_F(E, F, G, H, A, B, C, D, W2[60]); + SHA2_64_F(D, E, F, G, H, A, B, C, W2[61]); + SHA2_64_F(C, D, E, F, G, H, A, B, W2[62]); + SHA2_64_F(B, C, D, E, F, G, H, A, W2[63]); + + SHA2_64_F(A, B, C, D, E, F, G, H, W2[64]); + SHA2_64_F(H, A, B, C, D, E, F, G, W2[65]); + SHA2_64_F(G, H, A, B, C, D, E, F, W2[66]); + SHA2_64_F(F, G, H, A, B, C, D, E, W2[67]); + SHA2_64_F(E, F, G, H, A, B, C, D, W2[68]); + SHA2_64_F(D, E, F, G, H, A, B, C, W2[69]); + SHA2_64_F(C, D, E, F, G, H, A, B, W2[70]); + SHA2_64_F(B, C, D, E, F, G, H, A, W2[71]); + SHA2_64_F(A, B, C, D, E, F, G, H, W2[72]); + SHA2_64_F(H, A, B, C, D, E, F, G, W2[73]); + SHA2_64_F(G, H, A, B, C, D, E, F, W2[74]); + SHA2_64_F(F, G, H, A, B, C, D, E, W2[75]); + SHA2_64_F(E, F, G, H, A, B, C, D, W2[76]); + SHA2_64_F(D, E, F, G, H, A, B, C, W2[77]); + SHA2_64_F(C, D, E, F, G, H, A, B, W2[78]); + SHA2_64_F(B, C, D, E, F, G, H, A, W2[79]); + + A = (digest[0] += A); + B = (digest[1] += B); + C = (digest[2] += C); + D = (digest[3] += D); + E = (digest[4] += E); + F = (digest[5] += F); + G = (digest[6] += G); + H = (digest[7] += H); + } + + while(blocks > 0) { + SIMD_2x64 WS[8]; + + for(size_t i = 0; i < 8; i++) { + WS[i] = SIMD_2x64::load_be(&data[16 * i]); + auto WK = WS[i] + SIMD_2x64::load_le(&K[2 * i]); + WK.store_le(&W[2 * i]); + } + + data += 128; + blocks -= 1; + + // First 64 rounds of SHA-512 + for(size_t r = 0; r != 64; r += 16) { + auto w = sha512_next_w(WS) + SIMD_2x64::load_le(&K[r + 16]); + SHA2_64_F(A, B, C, D, E, F, G, H, W[0]); + SHA2_64_F(H, A, B, C, D, E, F, G, W[1]); + w.store_le(&W[0]); + + w = sha512_next_w(WS) + SIMD_2x64::load_le(&K[r + 18]); + SHA2_64_F(G, H, A, B, C, D, E, F, W[2]); + SHA2_64_F(F, G, H, A, B, C, D, E, W[3]); + w.store_le(&W[2]); + + w = sha512_next_w(WS) + SIMD_2x64::load_le(&K[r + 20]); + SHA2_64_F(E, F, G, H, A, B, C, D, W[4]); + SHA2_64_F(D, E, F, G, H, A, B, C, W[5]); + w.store_le(&W[4]); + + w = sha512_next_w(WS) + SIMD_2x64::load_le(&K[r + 22]); + SHA2_64_F(C, D, E, F, G, H, A, B, W[6]); + SHA2_64_F(B, C, D, E, F, G, H, A, W[7]); + w.store_le(&W[6]); + + w = sha512_next_w(WS) + SIMD_2x64::load_le(&K[r + 24]); + SHA2_64_F(A, B, C, D, E, F, G, H, W[8]); + SHA2_64_F(H, A, B, C, D, E, F, G, W[9]); + w.store_le(&W[8]); + + w = sha512_next_w(WS) + SIMD_2x64::load_le(&K[r + 26]); + SHA2_64_F(G, H, A, B, C, D, E, F, W[10]); + SHA2_64_F(F, G, H, A, B, C, D, E, W[11]); + w.store_le(&W[10]); + + w = sha512_next_w(WS) + SIMD_2x64::load_le(&K[r + 28]); + SHA2_64_F(E, F, G, H, A, B, C, D, W[12]); + SHA2_64_F(D, E, F, G, H, A, B, C, W[13]); + w.store_le(&W[12]); + + w = sha512_next_w(WS) + SIMD_2x64::load_le(&K[r + 30]); + SHA2_64_F(C, D, E, F, G, H, A, B, W[14]); + SHA2_64_F(B, C, D, E, F, G, H, A, W[15]); + w.store_le(&W[14]); + } + + // Final 16 rounds of SHA-512 + SHA2_64_F(A, B, C, D, E, F, G, H, W[0]); + SHA2_64_F(H, A, B, C, D, E, F, G, W[1]); + SHA2_64_F(G, H, A, B, C, D, E, F, W[2]); + SHA2_64_F(F, G, H, A, B, C, D, E, W[3]); + SHA2_64_F(E, F, G, H, A, B, C, D, W[4]); + SHA2_64_F(D, E, F, G, H, A, B, C, W[5]); + SHA2_64_F(C, D, E, F, G, H, A, B, W[6]); + SHA2_64_F(B, C, D, E, F, G, H, A, W[7]); + SHA2_64_F(A, B, C, D, E, F, G, H, W[8]); + SHA2_64_F(H, A, B, C, D, E, F, G, W[9]); + SHA2_64_F(G, H, A, B, C, D, E, F, W[10]); + SHA2_64_F(F, G, H, A, B, C, D, E, W[11]); + SHA2_64_F(E, F, G, H, A, B, C, D, W[12]); + SHA2_64_F(D, E, F, G, H, A, B, C, W[13]); + SHA2_64_F(C, D, E, F, G, H, A, B, W[14]); + SHA2_64_F(B, C, D, E, F, G, H, A, W[15]); + + A = (digest[0] += A); + B = (digest[1] += B); + C = (digest[2] += C); + D = (digest[3] += D); + E = (digest[4] += E); + F = (digest[5] += F); + G = (digest[6] += G); + H = (digest[7] += H); + } +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha2_64/sha2_64_avx512/info.txt botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64_avx512/info.txt --- botan3-3.7.1+dfsg/src/lib/hash/sha2_64/sha2_64_avx512/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64_avx512/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,25 @@ + +SHA2_64_X86_AVX512 -> 20250427 + + + +name -> "SHA-512 AVX512/BMI2" +brief -> "SHA-512 using AVX512/BMI2 instructions" + + + +bmi2 +avx512 + + +# Needs 64-bit registers to be useful + +x86_64 +x32 + + + +cpuid +simd_8x64 +simd_2x64 + diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha2_64/sha2_64_avx512/sha2_64_avx512.cpp botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64_avx512/sha2_64_avx512.cpp --- botan3-3.7.1+dfsg/src/lib/hash/sha2_64/sha2_64_avx512/sha2_64_avx512.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64_avx512/sha2_64_avx512.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,235 @@ +/* +* (C) 2025,2026 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include + +namespace Botan { + +namespace SHA512_AVX512 { + +namespace { + +template +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512_BMI2 SIMD_2x64 avx512_sigma(SIMD_2x64 v) { + const auto vr1 = _mm_ror_epi64(v.raw(), R1); + const auto vr2 = _mm_ror_epi64(v.raw(), R2); + const auto vs1 = _mm_srli_epi64(v.raw(), S1); + return SIMD_2x64(_mm_ternarylogic_epi64(vr1, vr2, vs1, 0x96)); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512_BMI2 SIMD_2x64 sha512_next_w_avx512(SIMD_2x64 x[8]) { + auto t0 = SIMD_2x64::alignr8(x[1], x[0]); + auto t1 = SIMD_2x64::alignr8(x[5], x[4]); + + auto s0 = avx512_sigma<1, 8, 7>(t0); + auto s1 = avx512_sigma<19, 61, 6>(x[7]); + + auto nx = x[0] + s0 + s1 + t1; + + x[0] = x[1]; + x[1] = x[2]; + x[2] = x[3]; + x[3] = x[4]; + x[4] = x[5]; + x[5] = x[6]; + x[6] = x[7]; + x[7] = nx; + + return nx; +} + +template +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512_BMI2 SIMD_2x64 rho(SIMD_2x64 v) { + const auto vr1 = _mm_ror_epi64(v.raw(), R1); + const auto vr2 = _mm_ror_epi64(v.raw(), R2); + const auto vr3 = _mm_ror_epi64(v.raw(), R3); + return SIMD_2x64(_mm_ternarylogic_epi64(vr1, vr2, vr3, 0x96)); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512_BMI2 void SHA2_64_F(SIMD_2x64 A, + SIMD_2x64 B, + SIMD_2x64 C, + SIMD_2x64& D, + SIMD_2x64 E, + SIMD_2x64 F, + SIMD_2x64 G, + SIMD_2x64& H, + uint64_t M) { + constexpr uint8_t ch = 0xca; + constexpr uint8_t maj = 0xe8; + + H += rho<14, 18, 41>(E) + SIMD_2x64(_mm_ternarylogic_epi64(E.raw(), F.raw(), G.raw(), ch)) + SIMD_2x64::splat(M); + D += H; + H += rho<28, 34, 39>(A) + SIMD_2x64(_mm_ternarylogic_epi64(A.raw(), B.raw(), C.raw(), maj)); +} + +} // namespace + +} // namespace SHA512_AVX512 + +BOTAN_FN_ISA_AVX512_BMI2 void SHA_512::compress_digest_x86_avx512(digest_type& digest, + std::span input, + size_t blocks) { + using namespace SHA512_AVX512; + + // clang-format off + alignas(64) const uint64_t K[80] = { + 0x428A2F98D728AE22, 0x7137449123EF65CD, 0xB5C0FBCFEC4D3B2F, 0xE9B5DBA58189DBBC, + 0x3956C25BF348B538, 0x59F111F1B605D019, 0x923F82A4AF194F9B, 0xAB1C5ED5DA6D8118, + 0xD807AA98A3030242, 0x12835B0145706FBE, 0x243185BE4EE4B28C, 0x550C7DC3D5FFB4E2, + 0x72BE5D74F27B896F, 0x80DEB1FE3B1696B1, 0x9BDC06A725C71235, 0xC19BF174CF692694, + 0xE49B69C19EF14AD2, 0xEFBE4786384F25E3, 0x0FC19DC68B8CD5B5, 0x240CA1CC77AC9C65, + 0x2DE92C6F592B0275, 0x4A7484AA6EA6E483, 0x5CB0A9DCBD41FBD4, 0x76F988DA831153B5, + 0x983E5152EE66DFAB, 0xA831C66D2DB43210, 0xB00327C898FB213F, 0xBF597FC7BEEF0EE4, + 0xC6E00BF33DA88FC2, 0xD5A79147930AA725, 0x06CA6351E003826F, 0x142929670A0E6E70, + 0x27B70A8546D22FFC, 0x2E1B21385C26C926, 0x4D2C6DFC5AC42AED, 0x53380D139D95B3DF, + 0x650A73548BAF63DE, 0x766A0ABB3C77B2A8, 0x81C2C92E47EDAEE6, 0x92722C851482353B, + 0xA2BFE8A14CF10364, 0xA81A664BBC423001, 0xC24B8B70D0F89791, 0xC76C51A30654BE30, + 0xD192E819D6EF5218, 0xD69906245565A910, 0xF40E35855771202A, 0x106AA07032BBD1B8, + 0x19A4C116B8D2D0C8, 0x1E376C085141AB53, 0x2748774CDF8EEB99, 0x34B0BCB5E19B48A8, + 0x391C0CB3C5C95A63, 0x4ED8AA4AE3418ACB, 0x5B9CCA4F7763E373, 0x682E6FF3D6B2B8A3, + 0x748F82EE5DEFB2FC, 0x78A5636F43172F60, 0x84C87814A1F0AB72, 0x8CC702081A6439EC, + 0x90BEFFFA23631E28, 0xA4506CEBDE82BDE9, 0xBEF9A3F7B2C67915, 0xC67178F2E372532B, + 0xCA273ECEEA26619C, 0xD186B8C721C0C207, 0xEADA7DD6CDE0EB1E, 0xF57D4F7FEE6ED178, + 0x06F067AA72176FBA, 0x0A637DC5A2C898A6, 0x113F9804BEF90DAE, 0x1B710B35131C471B, + 0x28DB77F523047D84, 0x32CAAB7B40C72493, 0x3C9EBE0A15C9BEBC, 0x431D67C49C100D4C, + 0x4CC5D4BECB3E42B6, 0x597F299CFC657E2A, 0x5FCB6FAB3AD6FAEC, 0x6C44198C4A475817, + }; + + // clang-format on + + alignas(64) uint64_t W[16] = {0}; + + auto digest0 = SIMD_2x64::splat(digest[0]); + auto digest1 = SIMD_2x64::splat(digest[1]); + auto digest2 = SIMD_2x64::splat(digest[2]); + auto digest3 = SIMD_2x64::splat(digest[3]); + auto digest4 = SIMD_2x64::splat(digest[4]); + auto digest5 = SIMD_2x64::splat(digest[5]); + auto digest6 = SIMD_2x64::splat(digest[6]); + auto digest7 = SIMD_2x64::splat(digest[7]); + + auto A = digest0; + auto B = digest1; + auto C = digest2; + auto D = digest3; + auto E = digest4; + auto F = digest5; + auto G = digest6; + auto H = digest7; + + const uint8_t* data = input.data(); + + while(blocks > 0) { + SIMD_2x64 WS[8]; + + for(size_t i = 0; i < 8; i++) { + WS[i] = SIMD_2x64::load_be(&data[16 * i]); + auto WK = WS[i] + SIMD_2x64::load_le(&K[2 * i]); + WK.store_le(&W[2 * i]); + } + + data += 128; + blocks -= 1; + + // First 64 rounds of SHA-512 + for(size_t r = 0; r != 64; r += 16) { + auto w = sha512_next_w_avx512(WS) + SIMD_2x64::load_le(&K[r + 16]); + SHA2_64_F(A, B, C, D, E, F, G, H, W[0]); + SHA2_64_F(H, A, B, C, D, E, F, G, W[1]); + w.store_le(&W[0]); + + w = sha512_next_w_avx512(WS) + SIMD_2x64::load_le(&K[r + 18]); + SHA2_64_F(G, H, A, B, C, D, E, F, W[2]); + SHA2_64_F(F, G, H, A, B, C, D, E, W[3]); + w.store_le(&W[2]); + + w = sha512_next_w_avx512(WS) + SIMD_2x64::load_le(&K[r + 20]); + SHA2_64_F(E, F, G, H, A, B, C, D, W[4]); + SHA2_64_F(D, E, F, G, H, A, B, C, W[5]); + w.store_le(&W[4]); + + w = sha512_next_w_avx512(WS) + SIMD_2x64::load_le(&K[r + 22]); + SHA2_64_F(C, D, E, F, G, H, A, B, W[6]); + SHA2_64_F(B, C, D, E, F, G, H, A, W[7]); + w.store_le(&W[6]); + + w = sha512_next_w_avx512(WS) + SIMD_2x64::load_le(&K[r + 24]); + SHA2_64_F(A, B, C, D, E, F, G, H, W[8]); + SHA2_64_F(H, A, B, C, D, E, F, G, W[9]); + w.store_le(&W[8]); + + w = sha512_next_w_avx512(WS) + SIMD_2x64::load_le(&K[r + 26]); + SHA2_64_F(G, H, A, B, C, D, E, F, W[10]); + SHA2_64_F(F, G, H, A, B, C, D, E, W[11]); + w.store_le(&W[10]); + + w = sha512_next_w_avx512(WS) + SIMD_2x64::load_le(&K[r + 28]); + SHA2_64_F(E, F, G, H, A, B, C, D, W[12]); + SHA2_64_F(D, E, F, G, H, A, B, C, W[13]); + w.store_le(&W[12]); + + w = sha512_next_w_avx512(WS) + SIMD_2x64::load_le(&K[r + 30]); + SHA2_64_F(C, D, E, F, G, H, A, B, W[14]); + SHA2_64_F(B, C, D, E, F, G, H, A, W[15]); + w.store_le(&W[14]); + } + + // Final 16 rounds of SHA-512 + SHA2_64_F(A, B, C, D, E, F, G, H, W[0]); + SHA2_64_F(H, A, B, C, D, E, F, G, W[1]); + SHA2_64_F(G, H, A, B, C, D, E, F, W[2]); + SHA2_64_F(F, G, H, A, B, C, D, E, W[3]); + SHA2_64_F(E, F, G, H, A, B, C, D, W[4]); + SHA2_64_F(D, E, F, G, H, A, B, C, W[5]); + SHA2_64_F(C, D, E, F, G, H, A, B, W[6]); + SHA2_64_F(B, C, D, E, F, G, H, A, W[7]); + SHA2_64_F(A, B, C, D, E, F, G, H, W[8]); + SHA2_64_F(H, A, B, C, D, E, F, G, W[9]); + SHA2_64_F(G, H, A, B, C, D, E, F, W[10]); + SHA2_64_F(F, G, H, A, B, C, D, E, W[11]); + SHA2_64_F(E, F, G, H, A, B, C, D, W[12]); + SHA2_64_F(D, E, F, G, H, A, B, C, W[13]); + SHA2_64_F(C, D, E, F, G, H, A, B, W[14]); + SHA2_64_F(B, C, D, E, F, G, H, A, W[15]); + + digest0 += A; + digest1 += B; + digest2 += C; + digest3 += D; + digest4 += E; + digest5 += F; + digest6 += G; + digest7 += H; + + A = digest0; + B = digest1; + C = digest2; + D = digest3; + E = digest4; + F = digest5; + G = digest6; + H = digest7; + } + + // Could be optimized a bit by interleaving the registers, reducing store pressure + // but probably not worth bothering with + _mm_mask_storeu_epi64(&digest[0], 0b01, digest0.raw()); // NOLINT(*-container-data-pointer) + _mm_mask_storeu_epi64(&digest[1], 0b01, digest1.raw()); + _mm_mask_storeu_epi64(&digest[2], 0b01, digest2.raw()); + _mm_mask_storeu_epi64(&digest[3], 0b01, digest3.raw()); + _mm_mask_storeu_epi64(&digest[4], 0b01, digest4.raw()); + _mm_mask_storeu_epi64(&digest[5], 0b01, digest5.raw()); + _mm_mask_storeu_epi64(&digest[6], 0b01, digest6.raw()); + _mm_mask_storeu_epi64(&digest[7], 0b01, digest7.raw()); +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha2_64/sha2_64_bmi2/info.txt botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64_bmi2/info.txt --- botan3-3.7.1+dfsg/src/lib/hash/sha2_64/sha2_64_bmi2/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64_bmi2/info.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,17 +0,0 @@ - -SHA2_64_BMI2 -> 20190117 - - - -name -> "SHA-512 BMI2" -brief -> "SHA-512 using BMI2 instructions" - - - -bmi2 - - -# Needs 64-bit registers to be useful - -x86_64 - diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha2_64/sha2_64_bmi2/sha2_64_bmi2.cpp botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64_bmi2/sha2_64_bmi2.cpp --- botan3-3.7.1+dfsg/src/lib/hash/sha2_64/sha2_64_bmi2/sha2_64_bmi2.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64_bmi2/sha2_64_bmi2.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,124 +0,0 @@ -/* -* (C) 2019 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include - -#include -#include -#include -#include -#include - -namespace Botan { - -void SHA_512::compress_digest_bmi2(digest_type& digest, std::span input, size_t blocks) { - uint64_t A = digest[0], B = digest[1], C = digest[2], D = digest[3], E = digest[4], F = digest[5], G = digest[6], - H = digest[7]; - - std::array W; - - BufferSlicer in(input); - - for(size_t i = 0; i != blocks; ++i) { - load_be(W, in.take()); - - // clang-format off - - SHA2_64_F(A, B, C, D, E, F, G, H, W[ 0], W[14], W[ 9], W[ 1], 0x428A2F98D728AE22); - SHA2_64_F(H, A, B, C, D, E, F, G, W[ 1], W[15], W[10], W[ 2], 0x7137449123EF65CD); - SHA2_64_F(G, H, A, B, C, D, E, F, W[ 2], W[ 0], W[11], W[ 3], 0xB5C0FBCFEC4D3B2F); - SHA2_64_F(F, G, H, A, B, C, D, E, W[ 3], W[ 1], W[12], W[ 4], 0xE9B5DBA58189DBBC); - SHA2_64_F(E, F, G, H, A, B, C, D, W[ 4], W[ 2], W[13], W[ 5], 0x3956C25BF348B538); - SHA2_64_F(D, E, F, G, H, A, B, C, W[ 5], W[ 3], W[14], W[ 6], 0x59F111F1B605D019); - SHA2_64_F(C, D, E, F, G, H, A, B, W[ 6], W[ 4], W[15], W[ 7], 0x923F82A4AF194F9B); - SHA2_64_F(B, C, D, E, F, G, H, A, W[ 7], W[ 5], W[ 0], W[ 8], 0xAB1C5ED5DA6D8118); - SHA2_64_F(A, B, C, D, E, F, G, H, W[ 8], W[ 6], W[ 1], W[ 9], 0xD807AA98A3030242); - SHA2_64_F(H, A, B, C, D, E, F, G, W[ 9], W[ 7], W[ 2], W[10], 0x12835B0145706FBE); - SHA2_64_F(G, H, A, B, C, D, E, F, W[10], W[ 8], W[ 3], W[11], 0x243185BE4EE4B28C); - SHA2_64_F(F, G, H, A, B, C, D, E, W[11], W[ 9], W[ 4], W[12], 0x550C7DC3D5FFB4E2); - SHA2_64_F(E, F, G, H, A, B, C, D, W[12], W[10], W[ 5], W[13], 0x72BE5D74F27B896F); - SHA2_64_F(D, E, F, G, H, A, B, C, W[13], W[11], W[ 6], W[14], 0x80DEB1FE3B1696B1); - SHA2_64_F(C, D, E, F, G, H, A, B, W[14], W[12], W[ 7], W[15], 0x9BDC06A725C71235); - SHA2_64_F(B, C, D, E, F, G, H, A, W[15], W[13], W[ 8], W[ 0], 0xC19BF174CF692694); - SHA2_64_F(A, B, C, D, E, F, G, H, W[ 0], W[14], W[ 9], W[ 1], 0xE49B69C19EF14AD2); - SHA2_64_F(H, A, B, C, D, E, F, G, W[ 1], W[15], W[10], W[ 2], 0xEFBE4786384F25E3); - SHA2_64_F(G, H, A, B, C, D, E, F, W[ 2], W[ 0], W[11], W[ 3], 0x0FC19DC68B8CD5B5); - SHA2_64_F(F, G, H, A, B, C, D, E, W[ 3], W[ 1], W[12], W[ 4], 0x240CA1CC77AC9C65); - SHA2_64_F(E, F, G, H, A, B, C, D, W[ 4], W[ 2], W[13], W[ 5], 0x2DE92C6F592B0275); - SHA2_64_F(D, E, F, G, H, A, B, C, W[ 5], W[ 3], W[14], W[ 6], 0x4A7484AA6EA6E483); - SHA2_64_F(C, D, E, F, G, H, A, B, W[ 6], W[ 4], W[15], W[ 7], 0x5CB0A9DCBD41FBD4); - SHA2_64_F(B, C, D, E, F, G, H, A, W[ 7], W[ 5], W[ 0], W[ 8], 0x76F988DA831153B5); - SHA2_64_F(A, B, C, D, E, F, G, H, W[ 8], W[ 6], W[ 1], W[ 9], 0x983E5152EE66DFAB); - SHA2_64_F(H, A, B, C, D, E, F, G, W[ 9], W[ 7], W[ 2], W[10], 0xA831C66D2DB43210); - SHA2_64_F(G, H, A, B, C, D, E, F, W[10], W[ 8], W[ 3], W[11], 0xB00327C898FB213F); - SHA2_64_F(F, G, H, A, B, C, D, E, W[11], W[ 9], W[ 4], W[12], 0xBF597FC7BEEF0EE4); - SHA2_64_F(E, F, G, H, A, B, C, D, W[12], W[10], W[ 5], W[13], 0xC6E00BF33DA88FC2); - SHA2_64_F(D, E, F, G, H, A, B, C, W[13], W[11], W[ 6], W[14], 0xD5A79147930AA725); - SHA2_64_F(C, D, E, F, G, H, A, B, W[14], W[12], W[ 7], W[15], 0x06CA6351E003826F); - SHA2_64_F(B, C, D, E, F, G, H, A, W[15], W[13], W[ 8], W[ 0], 0x142929670A0E6E70); - SHA2_64_F(A, B, C, D, E, F, G, H, W[ 0], W[14], W[ 9], W[ 1], 0x27B70A8546D22FFC); - SHA2_64_F(H, A, B, C, D, E, F, G, W[ 1], W[15], W[10], W[ 2], 0x2E1B21385C26C926); - SHA2_64_F(G, H, A, B, C, D, E, F, W[ 2], W[ 0], W[11], W[ 3], 0x4D2C6DFC5AC42AED); - SHA2_64_F(F, G, H, A, B, C, D, E, W[ 3], W[ 1], W[12], W[ 4], 0x53380D139D95B3DF); - SHA2_64_F(E, F, G, H, A, B, C, D, W[ 4], W[ 2], W[13], W[ 5], 0x650A73548BAF63DE); - SHA2_64_F(D, E, F, G, H, A, B, C, W[ 5], W[ 3], W[14], W[ 6], 0x766A0ABB3C77B2A8); - SHA2_64_F(C, D, E, F, G, H, A, B, W[ 6], W[ 4], W[15], W[ 7], 0x81C2C92E47EDAEE6); - SHA2_64_F(B, C, D, E, F, G, H, A, W[ 7], W[ 5], W[ 0], W[ 8], 0x92722C851482353B); - SHA2_64_F(A, B, C, D, E, F, G, H, W[ 8], W[ 6], W[ 1], W[ 9], 0xA2BFE8A14CF10364); - SHA2_64_F(H, A, B, C, D, E, F, G, W[ 9], W[ 7], W[ 2], W[10], 0xA81A664BBC423001); - SHA2_64_F(G, H, A, B, C, D, E, F, W[10], W[ 8], W[ 3], W[11], 0xC24B8B70D0F89791); - SHA2_64_F(F, G, H, A, B, C, D, E, W[11], W[ 9], W[ 4], W[12], 0xC76C51A30654BE30); - SHA2_64_F(E, F, G, H, A, B, C, D, W[12], W[10], W[ 5], W[13], 0xD192E819D6EF5218); - SHA2_64_F(D, E, F, G, H, A, B, C, W[13], W[11], W[ 6], W[14], 0xD69906245565A910); - SHA2_64_F(C, D, E, F, G, H, A, B, W[14], W[12], W[ 7], W[15], 0xF40E35855771202A); - SHA2_64_F(B, C, D, E, F, G, H, A, W[15], W[13], W[ 8], W[ 0], 0x106AA07032BBD1B8); - SHA2_64_F(A, B, C, D, E, F, G, H, W[ 0], W[14], W[ 9], W[ 1], 0x19A4C116B8D2D0C8); - SHA2_64_F(H, A, B, C, D, E, F, G, W[ 1], W[15], W[10], W[ 2], 0x1E376C085141AB53); - SHA2_64_F(G, H, A, B, C, D, E, F, W[ 2], W[ 0], W[11], W[ 3], 0x2748774CDF8EEB99); - SHA2_64_F(F, G, H, A, B, C, D, E, W[ 3], W[ 1], W[12], W[ 4], 0x34B0BCB5E19B48A8); - SHA2_64_F(E, F, G, H, A, B, C, D, W[ 4], W[ 2], W[13], W[ 5], 0x391C0CB3C5C95A63); - SHA2_64_F(D, E, F, G, H, A, B, C, W[ 5], W[ 3], W[14], W[ 6], 0x4ED8AA4AE3418ACB); - SHA2_64_F(C, D, E, F, G, H, A, B, W[ 6], W[ 4], W[15], W[ 7], 0x5B9CCA4F7763E373); - SHA2_64_F(B, C, D, E, F, G, H, A, W[ 7], W[ 5], W[ 0], W[ 8], 0x682E6FF3D6B2B8A3); - SHA2_64_F(A, B, C, D, E, F, G, H, W[ 8], W[ 6], W[ 1], W[ 9], 0x748F82EE5DEFB2FC); - SHA2_64_F(H, A, B, C, D, E, F, G, W[ 9], W[ 7], W[ 2], W[10], 0x78A5636F43172F60); - SHA2_64_F(G, H, A, B, C, D, E, F, W[10], W[ 8], W[ 3], W[11], 0x84C87814A1F0AB72); - SHA2_64_F(F, G, H, A, B, C, D, E, W[11], W[ 9], W[ 4], W[12], 0x8CC702081A6439EC); - SHA2_64_F(E, F, G, H, A, B, C, D, W[12], W[10], W[ 5], W[13], 0x90BEFFFA23631E28); - SHA2_64_F(D, E, F, G, H, A, B, C, W[13], W[11], W[ 6], W[14], 0xA4506CEBDE82BDE9); - SHA2_64_F(C, D, E, F, G, H, A, B, W[14], W[12], W[ 7], W[15], 0xBEF9A3F7B2C67915); - SHA2_64_F(B, C, D, E, F, G, H, A, W[15], W[13], W[ 8], W[ 0], 0xC67178F2E372532B); - SHA2_64_F(A, B, C, D, E, F, G, H, W[ 0], W[14], W[ 9], W[ 1], 0xCA273ECEEA26619C); - SHA2_64_F(H, A, B, C, D, E, F, G, W[ 1], W[15], W[10], W[ 2], 0xD186B8C721C0C207); - SHA2_64_F(G, H, A, B, C, D, E, F, W[ 2], W[ 0], W[11], W[ 3], 0xEADA7DD6CDE0EB1E); - SHA2_64_F(F, G, H, A, B, C, D, E, W[ 3], W[ 1], W[12], W[ 4], 0xF57D4F7FEE6ED178); - SHA2_64_F(E, F, G, H, A, B, C, D, W[ 4], W[ 2], W[13], W[ 5], 0x06F067AA72176FBA); - SHA2_64_F(D, E, F, G, H, A, B, C, W[ 5], W[ 3], W[14], W[ 6], 0x0A637DC5A2C898A6); - SHA2_64_F(C, D, E, F, G, H, A, B, W[ 6], W[ 4], W[15], W[ 7], 0x113F9804BEF90DAE); - SHA2_64_F(B, C, D, E, F, G, H, A, W[ 7], W[ 5], W[ 0], W[ 8], 0x1B710B35131C471B); - SHA2_64_F(A, B, C, D, E, F, G, H, W[ 8], W[ 6], W[ 1], W[ 9], 0x28DB77F523047D84); - SHA2_64_F(H, A, B, C, D, E, F, G, W[ 9], W[ 7], W[ 2], W[10], 0x32CAAB7B40C72493); - SHA2_64_F(G, H, A, B, C, D, E, F, W[10], W[ 8], W[ 3], W[11], 0x3C9EBE0A15C9BEBC); - SHA2_64_F(F, G, H, A, B, C, D, E, W[11], W[ 9], W[ 4], W[12], 0x431D67C49C100D4C); - SHA2_64_F(E, F, G, H, A, B, C, D, W[12], W[10], W[ 5], W[13], 0x4CC5D4BECB3E42B6); - SHA2_64_F(D, E, F, G, H, A, B, C, W[13], W[11], W[ 6], W[14], 0x597F299CFC657E2A); - SHA2_64_F(C, D, E, F, G, H, A, B, W[14], W[12], W[ 7], W[15], 0x5FCB6FAB3AD6FAEC); - SHA2_64_F(B, C, D, E, F, G, H, A, W[15], W[13], W[ 8], W[ 0], 0x6C44198C4A475817); - - // clang-format on - - A = (digest[0] += A); - B = (digest[1] += B); - C = (digest[2] += C); - D = (digest[3] += D); - E = (digest[4] += E); - F = (digest[5] += F); - G = (digest[6] += G); - H = (digest[7] += H); - } -} - -} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha2_64/sha2_64_f.h botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64_f.h --- botan3-3.7.1+dfsg/src/lib/hash/sha2_64/sha2_64_f.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64_f.h 2026-05-07 01:38:28.000000000 +0000 @@ -29,14 +29,20 @@ uint64_t M3, uint64_t M4, uint64_t magic) { - const uint64_t E_rho = rho<14, 18, 41>(E); - const uint64_t A_rho = rho<28, 34, 39>(A); - const uint64_t M2_sigma = sigma<19, 61, 6>(M2); - const uint64_t M4_sigma = sigma<1, 8, 7>(M4); - H += magic + E_rho + choose(E, F, G) + M1; + H += magic + rho<14, 18, 41>(E) + choose(E, F, G) + M1; D += H; - H += A_rho + majority(A, B, C); - M1 += M2_sigma + M3 + M4_sigma; + H += rho<28, 34, 39>(A) + majority(A, B, C); + M1 += sigma<19, 61, 6>(M2) + M3 + sigma<1, 8, 7>(M4); +} + +/* +* SHA-512 F1 Function (No Message Expansion) +*/ +BOTAN_FORCE_INLINE void SHA2_64_F( + uint64_t A, uint64_t B, uint64_t C, uint64_t& D, uint64_t E, uint64_t F, uint64_t G, uint64_t& H, uint64_t M) { + H += rho<14, 18, 41>(E) + choose(E, F, G) + M; + D += H; + H += rho<28, 34, 39>(A) + majority(A, B, C); } } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha2_64/sha2_64_x86/info.txt botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64_x86/info.txt --- botan3-3.7.1+dfsg/src/lib/hash/sha2_64/sha2_64_x86/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64_x86/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,26 @@ + +SHA2_64_X86 -> 20250310 + + + +name -> "SHA-512 SHA-NI" +brief -> "SHA-512 using x86 instructions" + + + +sha512 + + + +x86_64 + + + +cpuid + + + +gcc:14 +clang:17 +msvc + diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha2_64/sha2_64_x86/sha2_64_x86.cpp botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64_x86/sha2_64_x86.cpp --- botan3-3.7.1+dfsg/src/lib/hash/sha2_64/sha2_64_x86/sha2_64_x86.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha2_64/sha2_64_x86/sha2_64_x86.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,125 @@ +/* +* (C) 2025 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include + +namespace Botan { + +namespace { + +// NOLINTBEGIN(portability-simd-intrinsics) + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_SHA512 void sha512_msg_expand(__m256i& m0, __m256i& m1, __m256i& m2, __m256i& m3) { + m3 = _mm256_sha512msg1_epi64(m3, _mm256_extracti128_si256(m0, 0)); + m2 = _mm256_add_epi64(m2, _mm256_permute4x64_epi64(_mm256_blend_epi32(m0, m1, 3), 0b00111001)); + m2 = _mm256_sha512msg2_epi64(m2, m1); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_SHA512 void sha512_4rounds(__m256i& state0, + __m256i& state1, + const __m256i msg, + const __m256i K) { + const auto tmp = _mm256_add_epi64(msg, K); + state0 = _mm256_sha512rnds2_epi64(state0, state1, _mm256_extracti128_si256(tmp, 0)); + state1 = _mm256_sha512rnds2_epi64(state1, state0, _mm256_extracti128_si256(tmp, 1)); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX2 void permute_state(__m256i& state0, __m256i& state1) { + state0 = _mm256_shuffle_epi32(state0, 0b01001110); + state1 = _mm256_shuffle_epi32(state1, 0b01001110); + auto statet = state0; + state0 = _mm256_permute2x128_si256(state0, state1, 0x13); + state1 = _mm256_permute2x128_si256(statet, state1, 0x02); +} + +// NOLINTEND(portability-simd-intrinsics) + +} // namespace + +BOTAN_FN_ISA_SHA512 +void SHA_512::compress_digest_x86(digest_type& digest, std::span input, size_t blocks) { + alignas(128) static const uint64_t K[] = { + 0x428A2F98D728AE22, 0x7137449123EF65CD, 0xB5C0FBCFEC4D3B2F, 0xE9B5DBA58189DBBC, 0x3956C25BF348B538, + 0x59F111F1B605D019, 0x923F82A4AF194F9B, 0xAB1C5ED5DA6D8118, 0xD807AA98A3030242, 0x12835B0145706FBE, + 0x243185BE4EE4B28C, 0x550C7DC3D5FFB4E2, 0x72BE5D74F27B896F, 0x80DEB1FE3B1696B1, 0x9BDC06A725C71235, + 0xC19BF174CF692694, 0xE49B69C19EF14AD2, 0xEFBE4786384F25E3, 0x0FC19DC68B8CD5B5, 0x240CA1CC77AC9C65, + 0x2DE92C6F592B0275, 0x4A7484AA6EA6E483, 0x5CB0A9DCBD41FBD4, 0x76F988DA831153B5, 0x983E5152EE66DFAB, + 0xA831C66D2DB43210, 0xB00327C898FB213F, 0xBF597FC7BEEF0EE4, 0xC6E00BF33DA88FC2, 0xD5A79147930AA725, + 0x06CA6351E003826F, 0x142929670A0E6E70, 0x27B70A8546D22FFC, 0x2E1B21385C26C926, 0x4D2C6DFC5AC42AED, + 0x53380D139D95B3DF, 0x650A73548BAF63DE, 0x766A0ABB3C77B2A8, 0x81C2C92E47EDAEE6, 0x92722C851482353B, + 0xA2BFE8A14CF10364, 0xA81A664BBC423001, 0xC24B8B70D0F89791, 0xC76C51A30654BE30, 0xD192E819D6EF5218, + 0xD69906245565A910, 0xF40E35855771202A, 0x106AA07032BBD1B8, 0x19A4C116B8D2D0C8, 0x1E376C085141AB53, + 0x2748774CDF8EEB99, 0x34B0BCB5E19B48A8, 0x391C0CB3C5C95A63, 0x4ED8AA4AE3418ACB, 0x5B9CCA4F7763E373, + 0x682E6FF3D6B2B8A3, 0x748F82EE5DEFB2FC, 0x78A5636F43172F60, 0x84C87814A1F0AB72, 0x8CC702081A6439EC, + 0x90BEFFFA23631E28, 0xA4506CEBDE82BDE9, 0xBEF9A3F7B2C67915, 0xC67178F2E372532B, 0xCA273ECEEA26619C, + 0xD186B8C721C0C207, 0xEADA7DD6CDE0EB1E, 0xF57D4F7FEE6ED178, 0x06F067AA72176FBA, 0x0A637DC5A2C898A6, + 0x113F9804BEF90DAE, 0x1B710B35131C471B, 0x28DB77F523047D84, 0x32CAAB7B40C72493, 0x3C9EBE0A15C9BEBC, + 0x431D67C49C100D4C, 0x4CC5D4BECB3E42B6, 0x597F299CFC657E2A, 0x5FCB6FAB3AD6FAEC, 0x6C44198C4A475817, + }; + + // NOLINTBEGIN(portability-simd-intrinsics) TODO Use SIMD_4x64 here + + const __m256i* K_mm = reinterpret_cast(K); + + const __m256i bswap_mask = + _mm256_set_epi64x(0x08090a0b0c0d0e0f, 0x0001020304050607, 0x08090a0b0c0d0e0f, 0x0001020304050607); + + __m256i* digest_mm = reinterpret_cast<__m256i*>(digest.data()); + const __m256i* input_mm = reinterpret_cast(input.data()); + + auto state0 = _mm256_loadu_si256(digest_mm); + auto state1 = _mm256_loadu_si256(digest_mm + 1); + + permute_state(state0, state1); + + for(size_t i = 0; i != blocks; ++i) { + const auto state0_save = state0; + const auto state1_save = state1; + + auto m0 = _mm256_shuffle_epi8(_mm256_loadu_si256(input_mm + 0), bswap_mask); + auto m1 = _mm256_shuffle_epi8(_mm256_loadu_si256(input_mm + 1), bswap_mask); + auto m2 = _mm256_shuffle_epi8(_mm256_loadu_si256(input_mm + 2), bswap_mask); + auto m3 = _mm256_shuffle_epi8(_mm256_loadu_si256(input_mm + 3), bswap_mask); + + sha512_4rounds(state0, state1, m0, _mm256_load_si256(&K_mm[0])); + sha512_4rounds(state0, state1, m1, _mm256_load_si256(&K_mm[1])); + m0 = _mm256_sha512msg1_epi64(m0, _mm256_extracti128_si256(m1, 0)); + + for(size_t r = 2; r != 18; r += 4) { + sha512_4rounds(state0, state1, m2, _mm256_load_si256(&K_mm[r + 0])); + sha512_msg_expand(m2, m3, m0, m1); + + sha512_4rounds(state0, state1, m3, _mm256_load_si256(&K_mm[r + 1])); + sha512_msg_expand(m3, m0, m1, m2); + + sha512_4rounds(state0, state1, m0, _mm256_load_si256(&K_mm[r + 2])); + sha512_msg_expand(m0, m1, m2, m3); + + sha512_4rounds(state0, state1, m1, _mm256_load_si256(&K_mm[r + 3])); + sha512_msg_expand(m1, m2, m3, m0); + } + + sha512_4rounds(state0, state1, m2, _mm256_load_si256(&K_mm[18])); + sha512_4rounds(state0, state1, m3, _mm256_load_si256(&K_mm[19])); + + state0 = _mm256_add_epi64(state0, state0_save); + state1 = _mm256_add_epi64(state1, state1_save); + + input_mm += 4; + } + + permute_state(state0, state1); + + _mm256_storeu_si256(digest_mm, state0); + _mm256_storeu_si256(digest_mm + 1, state1); + + // NOLINTEND(portability-simd-intrinsics) +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sha3/sha3.cpp botan3-3.12.0+dfsg/src/lib/hash/sha3/sha3.cpp --- botan3-3.7.1+dfsg/src/lib/hash/sha3/sha3.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sha3/sha3.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,14 +8,13 @@ #include #include -#include #include #include -#include namespace Botan { -SHA_3::SHA_3(size_t output_bits) : m_keccak(2 * output_bits, 2, 2), m_output_length(output_bits / 8) { +SHA_3::SHA_3(size_t output_bits) : + m_keccak({.capacity_bits = output_bits * 2, .padding = KeccakPadding::sha3()}), m_output_length(output_bits / 8) { // We only support the parameters for SHA-3 in this constructor if(output_bits != 224 && output_bits != 256 && output_bits != 384 && output_bits != 512) { diff -Nru botan3-3.7.1+dfsg/src/lib/hash/shake/shake.cpp botan3-3.12.0+dfsg/src/lib/hash/shake/shake.cpp --- botan3-3.7.1+dfsg/src/lib/hash/shake/shake.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/shake/shake.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -12,8 +12,9 @@ namespace Botan { -SHAKE_128::SHAKE_128(size_t output_bits) : m_keccak(256, 0xF, 4), m_output_bits(output_bits) { - if(output_bits % 8 != 0) { +SHAKE_128::SHAKE_128(size_t output_bits) : + m_keccak({.capacity_bits = 256, .padding = KeccakPadding::shake()}), m_output_bits(output_bits) { + if(output_bits == 0 || output_bits % 8 != 0) { throw Invalid_Argument(fmt("SHAKE_128: Invalid output length {}", output_bits)); } } @@ -40,8 +41,9 @@ clear(); } -SHAKE_256::SHAKE_256(size_t output_bits) : m_keccak(512, 0xF, 4), m_output_bits(output_bits) { - if(output_bits % 8 != 0) { +SHAKE_256::SHAKE_256(size_t output_bits) : + m_keccak({.capacity_bits = 512, .padding = KeccakPadding::shake()}), m_output_bits(output_bits) { + if(output_bits == 0 || output_bits % 8 != 0) { throw Invalid_Argument(fmt("SHAKE_256: Invalid output length {}", output_bits)); } } diff -Nru botan3-3.7.1+dfsg/src/lib/hash/skein/skein_512.cpp botan3-3.12.0+dfsg/src/lib/hash/skein/skein_512.cpp --- botan3-3.7.1+dfsg/src/lib/hash/skein/skein_512.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/skein/skein_512.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include namespace Botan { @@ -70,7 +70,7 @@ store_le(uint32_t(m_output_bits), config_str + 8); reset_tweak(SKEIN_CONFIG, true); - ubi_512(config_str, sizeof(config_str)); + ubi_512(std::span{config_str}); if(!m_personalization.empty()) { /* @@ -82,24 +82,24 @@ throw Invalid_Argument("Skein personalization must be less than 64 bytes"); } - const uint8_t* bits = cast_char_ptr_to_uint8(m_personalization.data()); reset_tweak(SKEIN_PERSONALIZATION, true); - ubi_512(bits, m_personalization.length()); + ubi_512(as_span_of_bytes(m_personalization)); } reset_tweak(SKEIN_MSG, false); } -void Skein_512::ubi_512(const uint8_t msg[], size_t msg_len) { +void Skein_512::ubi_512(std::span msg) { secure_vector M(8); + /* NOLINTNEXTLINE(*-avoid-do-while) */ do { - const size_t to_proc = std::min(msg_len, 64); + const size_t to_proc = std::min(msg.size(), 64); m_T[0] += to_proc; - load_le(M.data(), msg, to_proc / 8); + load_le(M.data(), msg.data(), to_proc / 8); - if(to_proc % 8) { + if(to_proc % 8 > 0) { for(size_t j = 0; j != to_proc % 8; ++j) { M[to_proc / 8] |= static_cast(msg[8 * (to_proc / 8) + j]) << (8 * j); } @@ -110,9 +110,8 @@ // clear first flag if set m_T[1] &= ~(static_cast(1) << 62); - msg_len -= to_proc; - msg += to_proc; - } while(msg_len); + msg = msg.subspan(to_proc); + } while(!msg.empty()); } void Skein_512::add_data(std::span input) { @@ -139,10 +138,10 @@ m_buffer.fill_up_with_zeros(); ubi_512(m_buffer.consume().data(), pos); - const uint8_t counter[8] = {0}; + std::array counter{}; // all zero reset_tweak(SKEIN_OUTPUT, true); - ubi_512(counter, sizeof(counter)); + ubi_512(counter); copy_out_le(out.first(m_output_bits / 8), m_threefish->m_K); diff -Nru botan3-3.7.1+dfsg/src/lib/hash/skein/skein_512.h botan3-3.12.0+dfsg/src/lib/hash/skein/skein_512.h --- botan3-3.7.1+dfsg/src/lib/hash/skein/skein_512.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/skein/skein_512.h 2026-05-07 01:38:28.000000000 +0000 @@ -13,6 +13,7 @@ #include #include +#include #include namespace Botan { @@ -27,7 +28,7 @@ * @param personalization is a string that will parameterize the * hash output */ - Skein_512(size_t output_bits = 512, std::string_view personalization = ""); + explicit Skein_512(size_t output_bits = 512, std::string_view personalization = ""); size_t hash_block_size() const override { return 64; } @@ -39,7 +40,7 @@ void clear() override; private: - enum type_code { + enum type_code : uint8_t /* NOLINT(*-use-enum-class) */ { SKEIN_KEY = 0, SKEIN_CONFIG = 4, SKEIN_PERSONALIZATION = 8, @@ -53,7 +54,9 @@ void add_data(std::span input) override; void final_result(std::span out) override; - void ubi_512(const uint8_t msg[], size_t msg_len); + void ubi_512(std::span msg); + + void ubi_512(const uint8_t msg[], size_t length) { ubi_512({msg, length}); } void initial_block(); void reset_tweak(type_code type, bool is_final); diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sm3/sm3.cpp botan3-3.12.0+dfsg/src/lib/hash/sm3/sm3.cpp --- botan3-3.7.1+dfsg/src/lib/hash/sm3/sm3.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sm3/sm3.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,80 +8,47 @@ #include -#include +#include #include -#include -#include +#include -namespace Botan { - -namespace { - -inline uint32_t P0(uint32_t X) { - return X ^ rotl<9>(X) ^ rotl<17>(X); -} - -inline void R1(uint32_t A, - uint32_t& B, - uint32_t C, - uint32_t& D, - uint32_t E, - uint32_t& F, - uint32_t G, - uint32_t& H, - uint32_t TJ, - uint32_t Wi, - uint32_t Wj) { - const uint32_t A12 = rotl<12>(A); - const uint32_t SS1 = rotl<7>(A12 + E + TJ); - const uint32_t TT1 = (A ^ B ^ C) + D + (SS1 ^ A12) + Wj; - const uint32_t TT2 = (E ^ F ^ G) + H + SS1 + Wi; - - B = rotl<9>(B); - D = TT1; - F = rotl<19>(F); - H = P0(TT2); -} - -inline void R2(uint32_t A, - uint32_t& B, - uint32_t C, - uint32_t& D, - uint32_t E, - uint32_t& F, - uint32_t G, - uint32_t& H, - uint32_t TJ, - uint32_t Wi, - uint32_t Wj) { - const uint32_t A12 = rotl<12>(A); - const uint32_t SS1 = rotl<7>(A12 + E + TJ); - const uint32_t TT1 = majority(A, B, C) + D + (SS1 ^ A12) + Wj; - const uint32_t TT2 = choose(E, F, G) + H + SS1 + Wi; - - B = rotl<9>(B); - D = TT1; - F = rotl<19>(F); - H = P0(TT2); -} - -inline uint32_t P1(uint32_t X) { - return X ^ rotl<15>(X) ^ rotl<23>(X); -} +#if defined(BOTAN_HAS_CPUID) + #include +#endif -inline uint32_t SM3_E(uint32_t W0, uint32_t W7, uint32_t W13, uint32_t W3, uint32_t W10) { - return P1(W0 ^ W7 ^ rotl<15>(W13)) ^ rotl<7>(W3) ^ W10; -} - -} // namespace +namespace Botan { /* * SM3 Compression Function */ void SM3::compress_n(digest_type& digest, std::span input, size_t blocks) { - uint32_t A = digest[0], B = digest[1], C = digest[2], D = digest[3], E = digest[4], F = digest[5], G = digest[6], - H = digest[7]; - std::array W; +#if defined(BOTAN_HAS_SM3_ARMV8) + if(CPUID::has(CPUID::Feature::SM3)) { + return compress_digest_armv8(digest, input, blocks); + } +#endif + +#if defined(BOTAN_HAS_SM3_X86) + if(CPUID::has(CPUID::Feature::SM3, CPUID::Feature::AVX2)) { + return compress_digest_x86(digest, input, blocks); + } +#endif + +#if defined(BOTAN_HAS_SM3_X86_AVX2_BMI2) + if(CPUID::has(CPUID::Feature::AVX2, CPUID::Feature::BMI)) { + return compress_digest_x86_avx2(digest, input, blocks); + } +#endif + + uint32_t A = digest[0]; + uint32_t B = digest[1]; + uint32_t C = digest[2]; + uint32_t D = digest[3]; + uint32_t E = digest[4]; + uint32_t F = digest[5]; + uint32_t G = digest[6]; + uint32_t H = digest[7]; + std::array W{}; BufferSlicer in(input); @@ -90,122 +57,122 @@ // clang-format off - R1(A, B, C, D, E, F, G, H, 0x79CC4519, W[ 0], W[ 0] ^ W[ 4]); + R1(A, B, C, D, E, F, G, H, 0x79CC4519, W[ 0], W[ 4]); W[ 0] = SM3_E(W[ 0], W[ 7], W[13], W[ 3], W[10]); - R1(D, A, B, C, H, E, F, G, 0xF3988A32, W[ 1], W[ 1] ^ W[ 5]); + R1(D, A, B, C, H, E, F, G, 0xF3988A32, W[ 1], W[ 5]); W[ 1] = SM3_E(W[ 1], W[ 8], W[14], W[ 4], W[11]); - R1(C, D, A, B, G, H, E, F, 0xE7311465, W[ 2], W[ 2] ^ W[ 6]); + R1(C, D, A, B, G, H, E, F, 0xE7311465, W[ 2], W[ 6]); W[ 2] = SM3_E(W[ 2], W[ 9], W[15], W[ 5], W[12]); - R1(B, C, D, A, F, G, H, E, 0xCE6228CB, W[ 3], W[ 3] ^ W[ 7]); + R1(B, C, D, A, F, G, H, E, 0xCE6228CB, W[ 3], W[ 7]); W[ 3] = SM3_E(W[ 3], W[10], W[ 0], W[ 6], W[13]); - R1(A, B, C, D, E, F, G, H, 0x9CC45197, W[ 4], W[ 4] ^ W[ 8]); + R1(A, B, C, D, E, F, G, H, 0x9CC45197, W[ 4], W[ 8]); W[ 4] = SM3_E(W[ 4], W[11], W[ 1], W[ 7], W[14]); - R1(D, A, B, C, H, E, F, G, 0x3988A32F, W[ 5], W[ 5] ^ W[ 9]); + R1(D, A, B, C, H, E, F, G, 0x3988A32F, W[ 5], W[ 9]); W[ 5] = SM3_E(W[ 5], W[12], W[ 2], W[ 8], W[15]); - R1(C, D, A, B, G, H, E, F, 0x7311465E, W[ 6], W[ 6] ^ W[10]); + R1(C, D, A, B, G, H, E, F, 0x7311465E, W[ 6], W[10]); W[ 6] = SM3_E(W[ 6], W[13], W[ 3], W[ 9], W[ 0]); - R1(B, C, D, A, F, G, H, E, 0xE6228CBC, W[ 7], W[ 7] ^ W[11]); + R1(B, C, D, A, F, G, H, E, 0xE6228CBC, W[ 7], W[11]); W[ 7] = SM3_E(W[ 7], W[14], W[ 4], W[10], W[ 1]); - R1(A, B, C, D, E, F, G, H, 0xCC451979, W[ 8], W[ 8] ^ W[12]); + R1(A, B, C, D, E, F, G, H, 0xCC451979, W[ 8], W[12]); W[ 8] = SM3_E(W[ 8], W[15], W[ 5], W[11], W[ 2]); - R1(D, A, B, C, H, E, F, G, 0x988A32F3, W[ 9], W[ 9] ^ W[13]); + R1(D, A, B, C, H, E, F, G, 0x988A32F3, W[ 9], W[13]); W[ 9] = SM3_E(W[ 9], W[ 0], W[ 6], W[12], W[ 3]); - R1(C, D, A, B, G, H, E, F, 0x311465E7, W[10], W[10] ^ W[14]); + R1(C, D, A, B, G, H, E, F, 0x311465E7, W[10], W[14]); W[10] = SM3_E(W[10], W[ 1], W[ 7], W[13], W[ 4]); - R1(B, C, D, A, F, G, H, E, 0x6228CBCE, W[11], W[11] ^ W[15]); + R1(B, C, D, A, F, G, H, E, 0x6228CBCE, W[11], W[15]); W[11] = SM3_E(W[11], W[ 2], W[ 8], W[14], W[ 5]); - R1(A, B, C, D, E, F, G, H, 0xC451979C, W[12], W[12] ^ W[ 0]); + R1(A, B, C, D, E, F, G, H, 0xC451979C, W[12], W[ 0]); W[12] = SM3_E(W[12], W[ 3], W[ 9], W[15], W[ 6]); - R1(D, A, B, C, H, E, F, G, 0x88A32F39, W[13], W[13] ^ W[ 1]); + R1(D, A, B, C, H, E, F, G, 0x88A32F39, W[13], W[ 1]); W[13] = SM3_E(W[13], W[ 4], W[10], W[ 0], W[ 7]); - R1(C, D, A, B, G, H, E, F, 0x11465E73, W[14], W[14] ^ W[ 2]); + R1(C, D, A, B, G, H, E, F, 0x11465E73, W[14], W[ 2]); W[14] = SM3_E(W[14], W[ 5], W[11], W[ 1], W[ 8]); - R1(B, C, D, A, F, G, H, E, 0x228CBCE6, W[15], W[15] ^ W[ 3]); + R1(B, C, D, A, F, G, H, E, 0x228CBCE6, W[15], W[ 3]); W[15] = SM3_E(W[15], W[ 6], W[12], W[ 2], W[ 9]); - R2(A, B, C, D, E, F, G, H, 0x9D8A7A87, W[ 0], W[ 0] ^ W[ 4]); + R2(A, B, C, D, E, F, G, H, 0x9D8A7A87, W[ 0], W[ 4]); W[ 0] = SM3_E(W[ 0], W[ 7], W[13], W[ 3], W[10]); - R2(D, A, B, C, H, E, F, G, 0x3B14F50F, W[ 1], W[ 1] ^ W[ 5]); + R2(D, A, B, C, H, E, F, G, 0x3B14F50F, W[ 1], W[ 5]); W[ 1] = SM3_E(W[ 1], W[ 8], W[14], W[ 4], W[11]); - R2(C, D, A, B, G, H, E, F, 0x7629EA1E, W[ 2], W[ 2] ^ W[ 6]); + R2(C, D, A, B, G, H, E, F, 0x7629EA1E, W[ 2], W[ 6]); W[ 2] = SM3_E(W[ 2], W[ 9], W[15], W[ 5], W[12]); - R2(B, C, D, A, F, G, H, E, 0xEC53D43C, W[ 3], W[ 3] ^ W[ 7]); + R2(B, C, D, A, F, G, H, E, 0xEC53D43C, W[ 3], W[ 7]); W[ 3] = SM3_E(W[ 3], W[10], W[ 0], W[ 6], W[13]); - R2(A, B, C, D, E, F, G, H, 0xD8A7A879, W[ 4], W[ 4] ^ W[ 8]); + R2(A, B, C, D, E, F, G, H, 0xD8A7A879, W[ 4], W[ 8]); W[ 4] = SM3_E(W[ 4], W[11], W[ 1], W[ 7], W[14]); - R2(D, A, B, C, H, E, F, G, 0xB14F50F3, W[ 5], W[ 5] ^ W[ 9]); + R2(D, A, B, C, H, E, F, G, 0xB14F50F3, W[ 5], W[ 9]); W[ 5] = SM3_E(W[ 5], W[12], W[ 2], W[ 8], W[15]); - R2(C, D, A, B, G, H, E, F, 0x629EA1E7, W[ 6], W[ 6] ^ W[10]); + R2(C, D, A, B, G, H, E, F, 0x629EA1E7, W[ 6], W[10]); W[ 6] = SM3_E(W[ 6], W[13], W[ 3], W[ 9], W[ 0]); - R2(B, C, D, A, F, G, H, E, 0xC53D43CE, W[ 7], W[ 7] ^ W[11]); + R2(B, C, D, A, F, G, H, E, 0xC53D43CE, W[ 7], W[11]); W[ 7] = SM3_E(W[ 7], W[14], W[ 4], W[10], W[ 1]); - R2(A, B, C, D, E, F, G, H, 0x8A7A879D, W[ 8], W[ 8] ^ W[12]); + R2(A, B, C, D, E, F, G, H, 0x8A7A879D, W[ 8], W[12]); W[ 8] = SM3_E(W[ 8], W[15], W[ 5], W[11], W[ 2]); - R2(D, A, B, C, H, E, F, G, 0x14F50F3B, W[ 9], W[ 9] ^ W[13]); + R2(D, A, B, C, H, E, F, G, 0x14F50F3B, W[ 9], W[13]); W[ 9] = SM3_E(W[ 9], W[ 0], W[ 6], W[12], W[ 3]); - R2(C, D, A, B, G, H, E, F, 0x29EA1E76, W[10], W[10] ^ W[14]); + R2(C, D, A, B, G, H, E, F, 0x29EA1E76, W[10], W[14]); W[10] = SM3_E(W[10], W[ 1], W[ 7], W[13], W[ 4]); - R2(B, C, D, A, F, G, H, E, 0x53D43CEC, W[11], W[11] ^ W[15]); + R2(B, C, D, A, F, G, H, E, 0x53D43CEC, W[11], W[15]); W[11] = SM3_E(W[11], W[ 2], W[ 8], W[14], W[ 5]); - R2(A, B, C, D, E, F, G, H, 0xA7A879D8, W[12], W[12] ^ W[ 0]); + R2(A, B, C, D, E, F, G, H, 0xA7A879D8, W[12], W[ 0]); W[12] = SM3_E(W[12], W[ 3], W[ 9], W[15], W[ 6]); - R2(D, A, B, C, H, E, F, G, 0x4F50F3B1, W[13], W[13] ^ W[ 1]); + R2(D, A, B, C, H, E, F, G, 0x4F50F3B1, W[13], W[ 1]); W[13] = SM3_E(W[13], W[ 4], W[10], W[ 0], W[ 7]); - R2(C, D, A, B, G, H, E, F, 0x9EA1E762, W[14], W[14] ^ W[ 2]); + R2(C, D, A, B, G, H, E, F, 0x9EA1E762, W[14], W[ 2]); W[14] = SM3_E(W[14], W[ 5], W[11], W[ 1], W[ 8]); - R2(B, C, D, A, F, G, H, E, 0x3D43CEC5, W[15], W[15] ^ W[ 3]); + R2(B, C, D, A, F, G, H, E, 0x3D43CEC5, W[15], W[ 3]); W[15] = SM3_E(W[15], W[ 6], W[12], W[ 2], W[ 9]); - R2(A, B, C, D, E, F, G, H, 0x7A879D8A, W[ 0], W[ 0] ^ W[ 4]); + R2(A, B, C, D, E, F, G, H, 0x7A879D8A, W[ 0], W[ 4]); W[ 0] = SM3_E(W[ 0], W[ 7], W[13], W[ 3], W[10]); - R2(D, A, B, C, H, E, F, G, 0xF50F3B14, W[ 1], W[ 1] ^ W[ 5]); + R2(D, A, B, C, H, E, F, G, 0xF50F3B14, W[ 1], W[ 5]); W[ 1] = SM3_E(W[ 1], W[ 8], W[14], W[ 4], W[11]); - R2(C, D, A, B, G, H, E, F, 0xEA1E7629, W[ 2], W[ 2] ^ W[ 6]); + R2(C, D, A, B, G, H, E, F, 0xEA1E7629, W[ 2], W[ 6]); W[ 2] = SM3_E(W[ 2], W[ 9], W[15], W[ 5], W[12]); - R2(B, C, D, A, F, G, H, E, 0xD43CEC53, W[ 3], W[ 3] ^ W[ 7]); + R2(B, C, D, A, F, G, H, E, 0xD43CEC53, W[ 3], W[ 7]); W[ 3] = SM3_E(W[ 3], W[10], W[ 0], W[ 6], W[13]); - R2(A, B, C, D, E, F, G, H, 0xA879D8A7, W[ 4], W[ 4] ^ W[ 8]); + R2(A, B, C, D, E, F, G, H, 0xA879D8A7, W[ 4], W[ 8]); W[ 4] = SM3_E(W[ 4], W[11], W[ 1], W[ 7], W[14]); - R2(D, A, B, C, H, E, F, G, 0x50F3B14F, W[ 5], W[ 5] ^ W[ 9]); + R2(D, A, B, C, H, E, F, G, 0x50F3B14F, W[ 5], W[ 9]); W[ 5] = SM3_E(W[ 5], W[12], W[ 2], W[ 8], W[15]); - R2(C, D, A, B, G, H, E, F, 0xA1E7629E, W[ 6], W[ 6] ^ W[10]); + R2(C, D, A, B, G, H, E, F, 0xA1E7629E, W[ 6], W[10]); W[ 6] = SM3_E(W[ 6], W[13], W[ 3], W[ 9], W[ 0]); - R2(B, C, D, A, F, G, H, E, 0x43CEC53D, W[ 7], W[ 7] ^ W[11]); + R2(B, C, D, A, F, G, H, E, 0x43CEC53D, W[ 7], W[11]); W[ 7] = SM3_E(W[ 7], W[14], W[ 4], W[10], W[ 1]); - R2(A, B, C, D, E, F, G, H, 0x879D8A7A, W[ 8], W[ 8] ^ W[12]); + R2(A, B, C, D, E, F, G, H, 0x879D8A7A, W[ 8], W[12]); W[ 8] = SM3_E(W[ 8], W[15], W[ 5], W[11], W[ 2]); - R2(D, A, B, C, H, E, F, G, 0x0F3B14F5, W[ 9], W[ 9] ^ W[13]); + R2(D, A, B, C, H, E, F, G, 0x0F3B14F5, W[ 9], W[13]); W[ 9] = SM3_E(W[ 9], W[ 0], W[ 6], W[12], W[ 3]); - R2(C, D, A, B, G, H, E, F, 0x1E7629EA, W[10], W[10] ^ W[14]); + R2(C, D, A, B, G, H, E, F, 0x1E7629EA, W[10], W[14]); W[10] = SM3_E(W[10], W[ 1], W[ 7], W[13], W[ 4]); - R2(B, C, D, A, F, G, H, E, 0x3CEC53D4, W[11], W[11] ^ W[15]); + R2(B, C, D, A, F, G, H, E, 0x3CEC53D4, W[11], W[15]); W[11] = SM3_E(W[11], W[ 2], W[ 8], W[14], W[ 5]); - R2(A, B, C, D, E, F, G, H, 0x79D8A7A8, W[12], W[12] ^ W[ 0]); + R2(A, B, C, D, E, F, G, H, 0x79D8A7A8, W[12], W[ 0]); W[12] = SM3_E(W[12], W[ 3], W[ 9], W[15], W[ 6]); - R2(D, A, B, C, H, E, F, G, 0xF3B14F50, W[13], W[13] ^ W[ 1]); + R2(D, A, B, C, H, E, F, G, 0xF3B14F50, W[13], W[ 1]); W[13] = SM3_E(W[13], W[ 4], W[10], W[ 0], W[ 7]); - R2(C, D, A, B, G, H, E, F, 0xE7629EA1, W[14], W[14] ^ W[ 2]); + R2(C, D, A, B, G, H, E, F, 0xE7629EA1, W[14], W[ 2]); W[14] = SM3_E(W[14], W[ 5], W[11], W[ 1], W[ 8]); - R2(B, C, D, A, F, G, H, E, 0xCEC53D43, W[15], W[15] ^ W[ 3]); + R2(B, C, D, A, F, G, H, E, 0xCEC53D43, W[15], W[ 3]); W[15] = SM3_E(W[15], W[ 6], W[12], W[ 2], W[ 9]); - R2(A, B, C, D, E, F, G, H, 0x9D8A7A87, W[ 0], W[ 0] ^ W[ 4]); + R2(A, B, C, D, E, F, G, H, 0x9D8A7A87, W[ 0], W[ 4]); W[ 0] = SM3_E(W[ 0], W[ 7], W[13], W[ 3], W[10]); - R2(D, A, B, C, H, E, F, G, 0x3B14F50F, W[ 1], W[ 1] ^ W[ 5]); + R2(D, A, B, C, H, E, F, G, 0x3B14F50F, W[ 1], W[ 5]); W[ 1] = SM3_E(W[ 1], W[ 8], W[14], W[ 4], W[11]); - R2(C, D, A, B, G, H, E, F, 0x7629EA1E, W[ 2], W[ 2] ^ W[ 6]); + R2(C, D, A, B, G, H, E, F, 0x7629EA1E, W[ 2], W[ 6]); W[ 2] = SM3_E(W[ 2], W[ 9], W[15], W[ 5], W[12]); - R2(B, C, D, A, F, G, H, E, 0xEC53D43C, W[ 3], W[ 3] ^ W[ 7]); + R2(B, C, D, A, F, G, H, E, 0xEC53D43C, W[ 3], W[ 7]); W[ 3] = SM3_E(W[ 3], W[10], W[ 0], W[ 6], W[13]); - R2(A, B, C, D, E, F, G, H, 0xD8A7A879, W[ 4], W[ 4] ^ W[ 8]); - R2(D, A, B, C, H, E, F, G, 0xB14F50F3, W[ 5], W[ 5] ^ W[ 9]); - R2(C, D, A, B, G, H, E, F, 0x629EA1E7, W[ 6], W[ 6] ^ W[10]); - R2(B, C, D, A, F, G, H, E, 0xC53D43CE, W[ 7], W[ 7] ^ W[11]); - R2(A, B, C, D, E, F, G, H, 0x8A7A879D, W[ 8], W[ 8] ^ W[12]); - R2(D, A, B, C, H, E, F, G, 0x14F50F3B, W[ 9], W[ 9] ^ W[13]); - R2(C, D, A, B, G, H, E, F, 0x29EA1E76, W[10], W[10] ^ W[14]); - R2(B, C, D, A, F, G, H, E, 0x53D43CEC, W[11], W[11] ^ W[15]); - R2(A, B, C, D, E, F, G, H, 0xA7A879D8, W[12], W[12] ^ W[ 0]); - R2(D, A, B, C, H, E, F, G, 0x4F50F3B1, W[13], W[13] ^ W[ 1]); - R2(C, D, A, B, G, H, E, F, 0x9EA1E762, W[14], W[14] ^ W[ 2]); - R2(B, C, D, A, F, G, H, E, 0x3D43CEC5, W[15], W[15] ^ W[ 3]); + R2(A, B, C, D, E, F, G, H, 0xD8A7A879, W[ 4], W[ 8]); + R2(D, A, B, C, H, E, F, G, 0xB14F50F3, W[ 5], W[ 9]); + R2(C, D, A, B, G, H, E, F, 0x629EA1E7, W[ 6], W[10]); + R2(B, C, D, A, F, G, H, E, 0xC53D43CE, W[ 7], W[11]); + R2(A, B, C, D, E, F, G, H, 0x8A7A879D, W[ 8], W[12]); + R2(D, A, B, C, H, E, F, G, 0x14F50F3B, W[ 9], W[13]); + R2(C, D, A, B, G, H, E, F, 0x29EA1E76, W[10], W[14]); + R2(B, C, D, A, F, G, H, E, 0x53D43CEC, W[11], W[15]); + R2(A, B, C, D, E, F, G, H, 0xA7A879D8, W[12], W[ 0]); + R2(D, A, B, C, H, E, F, G, 0x4F50F3B1, W[13], W[ 1]); + R2(C, D, A, B, G, H, E, F, 0x9EA1E762, W[14], W[ 2]); + R2(B, C, D, A, F, G, H, E, 0x3D43CEC5, W[15], W[ 3]); // clang-format on @@ -241,4 +208,26 @@ m_md.final(output); } +std::string SM3::provider() const { +#if defined(BOTAN_HAS_SM3_ARMV8) + if(auto feat = CPUID::check(CPUID::Feature::SM3)) { + return *feat; + } +#endif + +#if defined(BOTAN_HAS_SM3_X86) + if(auto feat = CPUID::check(CPUID::Feature::SM3, CPUID::Feature::AVX2)) { + return *feat; + } +#endif + +#if defined(BOTAN_HAS_SM3_X86_AVX2_BMI2) + if(auto feat = CPUID::check(CPUID::Feature::AVX2, CPUID::Feature::BMI)) { + return *feat; + } +#endif + + return "base"; +} + } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sm3/sm3.h botan3-3.12.0+dfsg/src/lib/hash/sm3/sm3.h --- botan3-3.7.1+dfsg/src/lib/hash/sm3/sm3.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sm3/sm3.h 2026-05-07 01:38:28.000000000 +0000 @@ -31,6 +31,8 @@ public: std::string name() const override { return "SM3"; } + std::string provider() const override; + size_t output_length() const override { return output_bytes; } size_t hash_block_size() const override { return block_bytes; } @@ -46,6 +48,18 @@ void final_result(std::span output) override; +#if defined(BOTAN_HAS_SM3_X86_AVX2_BMI2) + static void compress_digest_x86_avx2(digest_type& digest, std::span input, size_t blocks); +#endif + +#if defined(BOTAN_HAS_SM3_ARMV8) + static void compress_digest_armv8(digest_type& digest, std::span input, size_t blocks); +#endif + +#if defined(BOTAN_HAS_SM3_X86) + static void compress_digest_x86(digest_type& digest, std::span input, size_t blocks); +#endif + private: MerkleDamgard_Hash m_md; }; diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sm3/sm3_armv8/info.txt botan3-3.12.0+dfsg/src/lib/hash/sm3/sm3_armv8/info.txt --- botan3-3.7.1+dfsg/src/lib/hash/sm3/sm3_armv8/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sm3/sm3_armv8/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,16 @@ + +SM3_ARMV8 -> 20260314 + + + +name -> "SM3 ARMv8" +brief -> "SM3 using ARMv8 crypto instructions" + + + +armv8sm3 + + + +cpuid + diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sm3/sm3_armv8/sm3_armv8.cpp botan3-3.12.0+dfsg/src/lib/hash/sm3/sm3_armv8/sm3_armv8.cpp --- botan3-3.7.1+dfsg/src/lib/hash/sm3/sm3_armv8/sm3_armv8.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sm3/sm3_armv8/sm3_armv8.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,170 @@ +/* +* (C) 2026 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include + +namespace Botan { + +namespace { + +// clang-format off +alignas(64) const uint32_t SM3_TJ[64] = { + 0x79CC4519, 0xF3988A32, 0xE7311465, 0xCE6228CB, + 0x9CC45197, 0x3988A32F, 0x7311465E, 0xE6228CBC, + 0xCC451979, 0x988A32F3, 0x311465E7, 0x6228CBCE, + 0xC451979C, 0x88A32F39, 0x11465E73, 0x228CBCE6, + 0x9D8A7A87, 0x3B14F50F, 0x7629EA1E, 0xEC53D43C, + 0xD8A7A879, 0xB14F50F3, 0x629EA1E7, 0xC53D43CE, + 0x8A7A879D, 0x14F50F3B, 0x29EA1E76, 0x53D43CEC, + 0xA7A879D8, 0x4F50F3B1, 0x9EA1E762, 0x3D43CEC5, + 0x7A879D8A, 0xF50F3B14, 0xEA1E7629, 0xD43CEC53, + 0xA879D8A7, 0x50F3B14F, 0xA1E7629E, 0x43CEC53D, + 0x879D8A7A, 0x0F3B14F5, 0x1E7629EA, 0x3CEC53D4, + 0x79D8A7A8, 0xF3B14F50, 0xE7629EA1, 0xCEC53D43, + 0x9D8A7A87, 0x3B14F50F, 0x7629EA1E, 0xEC53D43C, + 0xD8A7A879, 0xB14F50F3, 0x629EA1E7, 0xC53D43CE, + 0x8A7A879D, 0x14F50F3B, 0x29EA1E76, 0x53D43CEC, + 0xA7A879D8, 0x4F50F3B1, 0x9EA1E762, 0x3D43CEC5, +}; + +// clang-format on + +// The SM3 instructions expect the state words in reverse order (why??) +BOTAN_FORCE_INLINE BOTAN_FN_ISA_SM3 uint32x4_t sm3_reverse_words(uint32x4_t v) { + v = vrev64q_u32(v); + return vextq_u32(v, v, 2); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_SM3 uint32x4_t sm3_tj(size_t round) { + // vsm3ss1q expects the constant to be in the top word + return vsetq_lane_u32(SM3_TJ[round], vdupq_n_u32(0), 3); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_SM3 void sm3_x4_r1( + uint32x4_t& S0, uint32x4_t& S1, uint32x4_t w, uint32x4_t w_prime, size_t round) { + auto t = vsm3ss1q_u32(S0, S1, sm3_tj(round)); + S0 = vsm3tt1aq_u32(S0, t, w_prime, 0); + S1 = vsm3tt2aq_u32(S1, t, w, 0); + + t = vsm3ss1q_u32(S0, S1, sm3_tj(round + 1)); + S0 = vsm3tt1aq_u32(S0, t, w_prime, 1); + S1 = vsm3tt2aq_u32(S1, t, w, 1); + + t = vsm3ss1q_u32(S0, S1, sm3_tj(round + 2)); + S0 = vsm3tt1aq_u32(S0, t, w_prime, 2); + S1 = vsm3tt2aq_u32(S1, t, w, 2); + + t = vsm3ss1q_u32(S0, S1, sm3_tj(round + 3)); + S0 = vsm3tt1aq_u32(S0, t, w_prime, 3); + S1 = vsm3tt2aq_u32(S1, t, w, 3); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_SM3 void sm3_x4_r2( + uint32x4_t& S0, uint32x4_t& S1, uint32x4_t w, uint32x4_t w_prime, size_t round) { + auto t = vsm3ss1q_u32(S0, S1, sm3_tj(round)); + S0 = vsm3tt1bq_u32(S0, t, w_prime, 0); + S1 = vsm3tt2bq_u32(S1, t, w, 0); + + t = vsm3ss1q_u32(S0, S1, sm3_tj(round + 1)); + S0 = vsm3tt1bq_u32(S0, t, w_prime, 1); + S1 = vsm3tt2bq_u32(S1, t, w, 1); + + t = vsm3ss1q_u32(S0, S1, sm3_tj(round + 2)); + S0 = vsm3tt1bq_u32(S0, t, w_prime, 2); + S1 = vsm3tt2bq_u32(S1, t, w, 2); + + t = vsm3ss1q_u32(S0, S1, sm3_tj(round + 3)); + S0 = vsm3tt1bq_u32(S0, t, w_prime, 3); + S1 = vsm3tt2bq_u32(S1, t, w, 3); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_SM3 void sm3_msg_expand(uint32x4_t& w0, + const uint32x4_t& w1, + const uint32x4_t& w2, + const uint32x4_t& w3) { + const uint32x4_t w7_10 = vextq_u32(w1, w2, 3); + const uint32x4_t w3_6 = vextq_u32(w0, w1, 3); + const uint32x4_t w10_13 = vextq_u32(w2, w3, 2); + + uint32x4_t t = vsm3partw1q_u32(w0, w7_10, w3); + w0 = vsm3partw2q_u32(t, w10_13, w3_6); +} + +} // namespace + +void BOTAN_FN_ISA_SM3 SM3::compress_digest_armv8(digest_type& digest, std::span input, size_t blocks) { + uint32x4_t S0 = sm3_reverse_words(vld1q_u32(&digest[0])); // NOLINT(*-container-data-pointer) + uint32x4_t S1 = sm3_reverse_words(vld1q_u32(&digest[4])); + + const uint8_t* data = input.data(); + + while(blocks > 0) { + const uint32x4_t S0_save = S0; + const uint32x4_t S1_save = S1; + + uint32x4_t W0 = vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(data))); + uint32x4_t W1 = vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(data + 16))); + uint32x4_t W2 = vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(data + 32))); + uint32x4_t W3 = vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(data + 48))); + + data += block_bytes; + blocks -= 1; + + sm3_x4_r1(S0, S1, W0, veorq_u32(W0, W1), 0); + sm3_msg_expand(W0, W1, W2, W3); + + sm3_x4_r1(S0, S1, W1, veorq_u32(W1, W2), 4); + sm3_msg_expand(W1, W2, W3, W0); + + sm3_x4_r1(S0, S1, W2, veorq_u32(W2, W3), 8); + sm3_msg_expand(W2, W3, W0, W1); + + sm3_x4_r1(S0, S1, W3, veorq_u32(W3, W0), 12); + sm3_msg_expand(W3, W0, W1, W2); + + sm3_x4_r2(S0, S1, W0, veorq_u32(W0, W1), 16); + sm3_msg_expand(W0, W1, W2, W3); + + sm3_x4_r2(S0, S1, W1, veorq_u32(W1, W2), 20); + sm3_msg_expand(W1, W2, W3, W0); + + sm3_x4_r2(S0, S1, W2, veorq_u32(W2, W3), 24); + sm3_msg_expand(W2, W3, W0, W1); + + sm3_x4_r2(S0, S1, W3, veorq_u32(W3, W0), 28); + sm3_msg_expand(W3, W0, W1, W2); + + sm3_x4_r2(S0, S1, W0, veorq_u32(W0, W1), 32); + sm3_msg_expand(W0, W1, W2, W3); + + sm3_x4_r2(S0, S1, W1, veorq_u32(W1, W2), 36); + sm3_msg_expand(W1, W2, W3, W0); + + sm3_x4_r2(S0, S1, W2, veorq_u32(W2, W3), 40); + sm3_msg_expand(W2, W3, W0, W1); + + sm3_x4_r2(S0, S1, W3, veorq_u32(W3, W0), 44); + sm3_msg_expand(W3, W0, W1, W2); + + sm3_x4_r2(S0, S1, W0, veorq_u32(W0, W1), 48); + sm3_msg_expand(W0, W1, W2, W3); + + sm3_x4_r2(S0, S1, W1, veorq_u32(W1, W2), 52); + sm3_x4_r2(S0, S1, W2, veorq_u32(W2, W3), 56); + sm3_x4_r2(S0, S1, W3, veorq_u32(W3, W0), 60); + + S0 = veorq_u32(S0, S0_save); + S1 = veorq_u32(S1, S1_save); + } + + vst1q_u32(&digest[0], sm3_reverse_words(S0)); // NOLINT(*-container-data-pointer) + vst1q_u32(&digest[4], sm3_reverse_words(S1)); +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sm3/sm3_avx2_bmi2/info.txt botan3-3.12.0+dfsg/src/lib/hash/sm3/sm3_avx2_bmi2/info.txt --- botan3-3.7.1+dfsg/src/lib/hash/sm3/sm3_avx2_bmi2/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sm3/sm3_avx2_bmi2/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,19 @@ + +SM3_X86_AVX2_BMI2 -> 20251212 + + + +name -> "SM3 using AVX2/BMI2" +brief -> "SM3 using AVX2/BMI2 instructions" + + + +avx2 +bmi2 + + + +cpuid +simd_4x32 +simd_avx2 + diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sm3/sm3_avx2_bmi2/sm3_avx2_bmi2.cpp botan3-3.12.0+dfsg/src/lib/hash/sm3/sm3_avx2_bmi2/sm3_avx2_bmi2.cpp --- botan3-3.7.1+dfsg/src/lib/hash/sm3/sm3_avx2_bmi2/sm3_avx2_bmi2.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sm3/sm3_avx2_bmi2/sm3_avx2_bmi2.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,422 @@ +/* +* (C) 2025 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include + +namespace Botan { + +namespace { + +BOTAN_FN_ISA_AVX2_BMI2 inline SIMD_8x32 alignr12(const SIMD_8x32& a, const SIMD_8x32& b) { + return SIMD_8x32(_mm256_alignr_epi8(a.raw(), b.raw(), 12)); +} + +BOTAN_FN_ISA_AVX2_BMI2 inline SIMD_4x32 alignr12(const SIMD_4x32& a, const SIMD_4x32& b) { + return SIMD_4x32(_mm_alignr_epi8(a.raw(), b.raw(), 12)); +} + +template +BOTAN_FN_ISA_AVX2_BMI2 inline void next_SM3_W(SIMD_T& W0, const SIMD_T& W1, const SIMD_T& W2, const SIMD_T& W3) { + auto X3 = alignr12(W1, W0); // W[3..6] + auto X7 = alignr12(W2, W1); // W[7..10] + auto X10 = SIMD_T::alignr8(W3, W2); // W[10..13] + auto X13 = W3.template shift_elems_right<1>(); // W[13..15] || 0 + + auto P1_I = W0 ^ X7 ^ X13.template rotl<15>(); + auto P1_O = P1_I ^ P1_I.template rotl<15>() ^ P1_I.template rotl<23>(); + auto T = P1_O ^ X3.template rotl<7>() ^ X10; + + /* + * There is one hole in the recurrence, we now must compute P1(rotl<15>(W[0])) + * and xor it into W[3] + */ + + // Extract W[0] into T2 in position 3 + auto T2 = T.template shift_elems_left<3>(); + + // Compute P1(rotl<15>(W[0])) [combining the rotation values] + auto P1_T2 = T2.template rotl<15>() ^ T2.template rotl<30>() ^ T2.template rotl<6>(); + + // XOR in + T ^= P1_T2; + + W0 = T; +} + +} // namespace + +BOTAN_FN_ISA_AVX2_BMI2 void SM3::compress_digest_x86_avx2(digest_type& digest, + std::span input, + size_t blocks) { + uint32_t A = digest[0]; + uint32_t B = digest[1]; + uint32_t C = digest[2]; + uint32_t D = digest[3]; + uint32_t E = digest[4]; + uint32_t F = digest[5]; + uint32_t G = digest[6]; + uint32_t H = digest[7]; + std::array W{}; + std::array E2{}; + + const uint8_t* data = input.data(); + + // NOLINTBEGIN(*-container-data-pointer) + + while(blocks >= 2) { + auto W0 = SIMD_8x32::load_be128(&data[0], &data[64]); + auto W1 = SIMD_8x32::load_be128(&data[16], &data[80]); + auto W2 = SIMD_8x32::load_be128(&data[32], &data[96]); + auto W3 = SIMD_8x32::load_be128(&data[48], &data[112]); + + W0.store_le128(&W[0], &E2[0]); + W1.store_le128(&W[4], &E2[4]); + W2.store_le128(&W[8], &E2[8]); + W3.store_le128(&W[12], &E2[12]); + + data += 2 * block_bytes; + blocks -= 2; + + // clang-format off + + R1(A, B, C, D, E, F, G, H, 0x79CC4519, W[ 0], W[ 4]); + R1(D, A, B, C, H, E, F, G, 0xF3988A32, W[ 1], W[ 5]); + R1(C, D, A, B, G, H, E, F, 0xE7311465, W[ 2], W[ 6]); + R1(B, C, D, A, F, G, H, E, 0xCE6228CB, W[ 3], W[ 7]); + next_SM3_W(W0, W1, W2, W3); + W0.store_le128(&W[0], &E2[16]); + + R1(A, B, C, D, E, F, G, H, 0x9CC45197, W[ 4], W[ 8]); + R1(D, A, B, C, H, E, F, G, 0x3988A32F, W[ 5], W[ 9]); + R1(C, D, A, B, G, H, E, F, 0x7311465E, W[ 6], W[10]); + R1(B, C, D, A, F, G, H, E, 0xE6228CBC, W[ 7], W[11]); + next_SM3_W(W1, W2, W3, W0); + W1.store_le128(&W[4], &E2[20]); + + R1(A, B, C, D, E, F, G, H, 0xCC451979, W[ 8], W[12]); + R1(D, A, B, C, H, E, F, G, 0x988A32F3, W[ 9], W[13]); + R1(C, D, A, B, G, H, E, F, 0x311465E7, W[10], W[14]); + R1(B, C, D, A, F, G, H, E, 0x6228CBCE, W[11], W[15]); + next_SM3_W(W2, W3, W0, W1); + W2.store_le128(&W[8], &E2[24]); + + R1(A, B, C, D, E, F, G, H, 0xC451979C, W[12], W[ 0]); + R1(D, A, B, C, H, E, F, G, 0x88A32F39, W[13], W[ 1]); + R1(C, D, A, B, G, H, E, F, 0x11465E73, W[14], W[ 2]); + R1(B, C, D, A, F, G, H, E, 0x228CBCE6, W[15], W[ 3]); + next_SM3_W(W3, W0, W1, W2); + W3.store_le128(&W[12], &E2[28]); + + R2(A, B, C, D, E, F, G, H, 0x9D8A7A87, W[ 0], W[ 4]); + R2(D, A, B, C, H, E, F, G, 0x3B14F50F, W[ 1], W[ 5]); + R2(C, D, A, B, G, H, E, F, 0x7629EA1E, W[ 2], W[ 6]); + R2(B, C, D, A, F, G, H, E, 0xEC53D43C, W[ 3], W[ 7]); + next_SM3_W(W0, W1, W2, W3); + W0.store_le128(&W[0], &E2[32]); + + R2(A, B, C, D, E, F, G, H, 0xD8A7A879, W[ 4], W[ 8]); + R2(D, A, B, C, H, E, F, G, 0xB14F50F3, W[ 5], W[ 9]); + R2(C, D, A, B, G, H, E, F, 0x629EA1E7, W[ 6], W[10]); + R2(B, C, D, A, F, G, H, E, 0xC53D43CE, W[ 7], W[11]); + next_SM3_W(W1, W2, W3, W0); + W1.store_le128(&W[4], &E2[36]); + + R2(A, B, C, D, E, F, G, H, 0x8A7A879D, W[ 8], W[12]); + R2(D, A, B, C, H, E, F, G, 0x14F50F3B, W[ 9], W[13]); + R2(C, D, A, B, G, H, E, F, 0x29EA1E76, W[10], W[14]); + R2(B, C, D, A, F, G, H, E, 0x53D43CEC, W[11], W[15]); + next_SM3_W(W2, W3, W0, W1); + W2.store_le128(&W[8], &E2[40]); + + R2(A, B, C, D, E, F, G, H, 0xA7A879D8, W[12], W[ 0]); + R2(D, A, B, C, H, E, F, G, 0x4F50F3B1, W[13], W[ 1]); + R2(C, D, A, B, G, H, E, F, 0x9EA1E762, W[14], W[ 2]); + R2(B, C, D, A, F, G, H, E, 0x3D43CEC5, W[15], W[ 3]); + next_SM3_W(W3, W0, W1, W2); + W3.store_le128(&W[12], &E2[44]); + + R2(A, B, C, D, E, F, G, H, 0x7A879D8A, W[ 0], W[ 4]); + R2(D, A, B, C, H, E, F, G, 0xF50F3B14, W[ 1], W[ 5]); + R2(C, D, A, B, G, H, E, F, 0xEA1E7629, W[ 2], W[ 6]); + R2(B, C, D, A, F, G, H, E, 0xD43CEC53, W[ 3], W[ 7]); + next_SM3_W(W0, W1, W2, W3); + W0.store_le128(&W[0], &E2[48]); + + R2(A, B, C, D, E, F, G, H, 0xA879D8A7, W[ 4], W[ 8]); + R2(D, A, B, C, H, E, F, G, 0x50F3B14F, W[ 5], W[ 9]); + R2(C, D, A, B, G, H, E, F, 0xA1E7629E, W[ 6], W[10]); + R2(B, C, D, A, F, G, H, E, 0x43CEC53D, W[ 7], W[11]); + next_SM3_W(W1, W2, W3, W0); + W1.store_le128(&W[4], &E2[52]); + + R2(A, B, C, D, E, F, G, H, 0x879D8A7A, W[ 8], W[12]); + R2(D, A, B, C, H, E, F, G, 0x0F3B14F5, W[ 9], W[13]); + R2(C, D, A, B, G, H, E, F, 0x1E7629EA, W[10], W[14]); + R2(B, C, D, A, F, G, H, E, 0x3CEC53D4, W[11], W[15]); + next_SM3_W(W2, W3, W0, W1); + W2.store_le128(&W[8], &E2[56]); + + R2(A, B, C, D, E, F, G, H, 0x79D8A7A8, W[12], W[ 0]); + R2(D, A, B, C, H, E, F, G, 0xF3B14F50, W[13], W[ 1]); + R2(C, D, A, B, G, H, E, F, 0xE7629EA1, W[14], W[ 2]); + R2(B, C, D, A, F, G, H, E, 0xCEC53D43, W[15], W[ 3]); + next_SM3_W(W3, W0, W1, W2); + W3.store_le128(&W[12], &E2[60]); + + R2(A, B, C, D, E, F, G, H, 0x9D8A7A87, W[ 0], W[ 4]); + R2(D, A, B, C, H, E, F, G, 0x3B14F50F, W[ 1], W[ 5]); + R2(C, D, A, B, G, H, E, F, 0x7629EA1E, W[ 2], W[ 6]); + R2(B, C, D, A, F, G, H, E, 0xEC53D43C, W[ 3], W[ 7]); + next_SM3_W(W0, W1, W2, W3); + W0.store_le128(&W[0], &E2[64]); + + R2(A, B, C, D, E, F, G, H, 0xD8A7A879, W[ 4], W[ 8]); + R2(D, A, B, C, H, E, F, G, 0xB14F50F3, W[ 5], W[ 9]); + R2(C, D, A, B, G, H, E, F, 0x629EA1E7, W[ 6], W[10]); + R2(B, C, D, A, F, G, H, E, 0xC53D43CE, W[ 7], W[11]); + + R2(A, B, C, D, E, F, G, H, 0x8A7A879D, W[ 8], W[12]); + R2(D, A, B, C, H, E, F, G, 0x14F50F3B, W[ 9], W[13]); + R2(C, D, A, B, G, H, E, F, 0x29EA1E76, W[10], W[14]); + R2(B, C, D, A, F, G, H, E, 0x53D43CEC, W[11], W[15]); + + R2(A, B, C, D, E, F, G, H, 0xA7A879D8, W[12], W[ 0]); + R2(D, A, B, C, H, E, F, G, 0x4F50F3B1, W[13], W[ 1]); + R2(C, D, A, B, G, H, E, F, 0x9EA1E762, W[14], W[ 2]); + R2(B, C, D, A, F, G, H, E, 0x3D43CEC5, W[15], W[ 3]); + + // clang-format on + + A = (digest[0] ^= A); + B = (digest[1] ^= B); + C = (digest[2] ^= C); + D = (digest[3] ^= D); + E = (digest[4] ^= E); + F = (digest[5] ^= F); + G = (digest[6] ^= G); + H = (digest[7] ^= H); + + // clang-format off + R1(A, B, C, D, E, F, G, H, 0x79CC4519, E2[0], E2[4]); + R1(D, A, B, C, H, E, F, G, 0xF3988A32, E2[1], E2[5]); + R1(C, D, A, B, G, H, E, F, 0xE7311465, E2[2], E2[6]); + R1(B, C, D, A, F, G, H, E, 0xCE6228CB, E2[3], E2[7]); + R1(A, B, C, D, E, F, G, H, 0x9CC45197, E2[4], E2[8]); + R1(D, A, B, C, H, E, F, G, 0x3988A32F, E2[5], E2[9]); + R1(C, D, A, B, G, H, E, F, 0x7311465E, E2[6], E2[10]); + R1(B, C, D, A, F, G, H, E, 0xE6228CBC, E2[7], E2[11]); + R1(A, B, C, D, E, F, G, H, 0xCC451979, E2[8], E2[12]); + R1(D, A, B, C, H, E, F, G, 0x988A32F3, E2[9], E2[13]); + R1(C, D, A, B, G, H, E, F, 0x311465E7, E2[10], E2[14]); + R1(B, C, D, A, F, G, H, E, 0x6228CBCE, E2[11], E2[15]); + R1(A, B, C, D, E, F, G, H, 0xC451979C, E2[12], E2[16]); + R1(D, A, B, C, H, E, F, G, 0x88A32F39, E2[13], E2[17]); + R1(C, D, A, B, G, H, E, F, 0x11465E73, E2[14], E2[18]); + R1(B, C, D, A, F, G, H, E, 0x228CBCE6, E2[15], E2[19]); + R2(A, B, C, D, E, F, G, H, 0x9D8A7A87, E2[16], E2[20]); + R2(D, A, B, C, H, E, F, G, 0x3B14F50F, E2[17], E2[21]); + R2(C, D, A, B, G, H, E, F, 0x7629EA1E, E2[18], E2[22]); + R2(B, C, D, A, F, G, H, E, 0xEC53D43C, E2[19], E2[23]); + R2(A, B, C, D, E, F, G, H, 0xD8A7A879, E2[20], E2[24]); + R2(D, A, B, C, H, E, F, G, 0xB14F50F3, E2[21], E2[25]); + R2(C, D, A, B, G, H, E, F, 0x629EA1E7, E2[22], E2[26]); + R2(B, C, D, A, F, G, H, E, 0xC53D43CE, E2[23], E2[27]); + R2(A, B, C, D, E, F, G, H, 0x8A7A879D, E2[24], E2[28]); + R2(D, A, B, C, H, E, F, G, 0x14F50F3B, E2[25], E2[29]); + R2(C, D, A, B, G, H, E, F, 0x29EA1E76, E2[26], E2[30]); + R2(B, C, D, A, F, G, H, E, 0x53D43CEC, E2[27], E2[31]); + R2(A, B, C, D, E, F, G, H, 0xA7A879D8, E2[28], E2[32]); + R2(D, A, B, C, H, E, F, G, 0x4F50F3B1, E2[29], E2[33]); + R2(C, D, A, B, G, H, E, F, 0x9EA1E762, E2[30], E2[34]); + R2(B, C, D, A, F, G, H, E, 0x3D43CEC5, E2[31], E2[35]); + R2(A, B, C, D, E, F, G, H, 0x7A879D8A, E2[32], E2[36]); + R2(D, A, B, C, H, E, F, G, 0xF50F3B14, E2[33], E2[37]); + R2(C, D, A, B, G, H, E, F, 0xEA1E7629, E2[34], E2[38]); + R2(B, C, D, A, F, G, H, E, 0xD43CEC53, E2[35], E2[39]); + R2(A, B, C, D, E, F, G, H, 0xA879D8A7, E2[36], E2[40]); + R2(D, A, B, C, H, E, F, G, 0x50F3B14F, E2[37], E2[41]); + R2(C, D, A, B, G, H, E, F, 0xA1E7629E, E2[38], E2[42]); + R2(B, C, D, A, F, G, H, E, 0x43CEC53D, E2[39], E2[43]); + R2(A, B, C, D, E, F, G, H, 0x879D8A7A, E2[40], E2[44]); + R2(D, A, B, C, H, E, F, G, 0x0F3B14F5, E2[41], E2[45]); + R2(C, D, A, B, G, H, E, F, 0x1E7629EA, E2[42], E2[46]); + R2(B, C, D, A, F, G, H, E, 0x3CEC53D4, E2[43], E2[47]); + R2(A, B, C, D, E, F, G, H, 0x79D8A7A8, E2[44], E2[48]); + R2(D, A, B, C, H, E, F, G, 0xF3B14F50, E2[45], E2[49]); + R2(C, D, A, B, G, H, E, F, 0xE7629EA1, E2[46], E2[50]); + R2(B, C, D, A, F, G, H, E, 0xCEC53D43, E2[47], E2[51]); + R2(A, B, C, D, E, F, G, H, 0x9D8A7A87, E2[48], E2[52]); + R2(D, A, B, C, H, E, F, G, 0x3B14F50F, E2[49], E2[53]); + R2(C, D, A, B, G, H, E, F, 0x7629EA1E, E2[50], E2[54]); + R2(B, C, D, A, F, G, H, E, 0xEC53D43C, E2[51], E2[55]); + R2(A, B, C, D, E, F, G, H, 0xD8A7A879, E2[52], E2[56]); + R2(D, A, B, C, H, E, F, G, 0xB14F50F3, E2[53], E2[57]); + R2(C, D, A, B, G, H, E, F, 0x629EA1E7, E2[54], E2[58]); + R2(B, C, D, A, F, G, H, E, 0xC53D43CE, E2[55], E2[59]); + R2(A, B, C, D, E, F, G, H, 0x8A7A879D, E2[56], E2[60]); + R2(D, A, B, C, H, E, F, G, 0x14F50F3B, E2[57], E2[61]); + R2(C, D, A, B, G, H, E, F, 0x29EA1E76, E2[58], E2[62]); + R2(B, C, D, A, F, G, H, E, 0x53D43CEC, E2[59], E2[63]); + R2(A, B, C, D, E, F, G, H, 0xA7A879D8, E2[60], E2[64]); + R2(D, A, B, C, H, E, F, G, 0x4F50F3B1, E2[61], E2[65]); + R2(C, D, A, B, G, H, E, F, 0x9EA1E762, E2[62], E2[66]); + R2(B, C, D, A, F, G, H, E, 0x3D43CEC5, E2[63], E2[67]); + + // clang-format on + + A = (digest[0] ^= A); + B = (digest[1] ^= B); + C = (digest[2] ^= C); + D = (digest[3] ^= D); + E = (digest[4] ^= E); + F = (digest[5] ^= F); + G = (digest[6] ^= G); + H = (digest[7] ^= H); + } + + while(blocks > 0) { + SIMD_4x32 W0 = SIMD_4x32::load_be(&data[0]); + SIMD_4x32 W1 = SIMD_4x32::load_be(&data[16]); + SIMD_4x32 W2 = SIMD_4x32::load_be(&data[32]); + SIMD_4x32 W3 = SIMD_4x32::load_be(&data[48]); + + W0.store_le(&W[0]); + W1.store_le(&W[4]); + W2.store_le(&W[8]); + W3.store_le(&W[12]); + + data += block_bytes; + blocks -= 1; + + // clang-format off + + R1(A, B, C, D, E, F, G, H, 0x79CC4519, W[ 0], W[ 4]); + R1(D, A, B, C, H, E, F, G, 0xF3988A32, W[ 1], W[ 5]); + R1(C, D, A, B, G, H, E, F, 0xE7311465, W[ 2], W[ 6]); + R1(B, C, D, A, F, G, H, E, 0xCE6228CB, W[ 3], W[ 7]); + next_SM3_W(W0, W1, W2, W3); + W0.store_le(&W[0]); + + R1(A, B, C, D, E, F, G, H, 0x9CC45197, W[ 4], W[ 8]); + R1(D, A, B, C, H, E, F, G, 0x3988A32F, W[ 5], W[ 9]); + R1(C, D, A, B, G, H, E, F, 0x7311465E, W[ 6], W[10]); + R1(B, C, D, A, F, G, H, E, 0xE6228CBC, W[ 7], W[11]); + next_SM3_W(W1, W2, W3, W0); + W1.store_le(&W[4]); + + R1(A, B, C, D, E, F, G, H, 0xCC451979, W[ 8], W[12]); + R1(D, A, B, C, H, E, F, G, 0x988A32F3, W[ 9], W[13]); + R1(C, D, A, B, G, H, E, F, 0x311465E7, W[10], W[14]); + R1(B, C, D, A, F, G, H, E, 0x6228CBCE, W[11], W[15]); + next_SM3_W(W2, W3, W0, W1); + W2.store_le(&W[8]); + + R1(A, B, C, D, E, F, G, H, 0xC451979C, W[12], W[ 0]); + R1(D, A, B, C, H, E, F, G, 0x88A32F39, W[13], W[ 1]); + R1(C, D, A, B, G, H, E, F, 0x11465E73, W[14], W[ 2]); + R1(B, C, D, A, F, G, H, E, 0x228CBCE6, W[15], W[ 3]); + next_SM3_W(W3, W0, W1, W2); + W3.store_le(&W[12]); + + R2(A, B, C, D, E, F, G, H, 0x9D8A7A87, W[ 0], W[ 4]); + R2(D, A, B, C, H, E, F, G, 0x3B14F50F, W[ 1], W[ 5]); + R2(C, D, A, B, G, H, E, F, 0x7629EA1E, W[ 2], W[ 6]); + R2(B, C, D, A, F, G, H, E, 0xEC53D43C, W[ 3], W[ 7]); + next_SM3_W(W0, W1, W2, W3); + W0.store_le(&W[0]); + + R2(A, B, C, D, E, F, G, H, 0xD8A7A879, W[ 4], W[ 8]); + R2(D, A, B, C, H, E, F, G, 0xB14F50F3, W[ 5], W[ 9]); + R2(C, D, A, B, G, H, E, F, 0x629EA1E7, W[ 6], W[10]); + R2(B, C, D, A, F, G, H, E, 0xC53D43CE, W[ 7], W[11]); + next_SM3_W(W1, W2, W3, W0); + W1.store_le(&W[4]); + + R2(A, B, C, D, E, F, G, H, 0x8A7A879D, W[ 8], W[12]); + R2(D, A, B, C, H, E, F, G, 0x14F50F3B, W[ 9], W[13]); + R2(C, D, A, B, G, H, E, F, 0x29EA1E76, W[10], W[14]); + R2(B, C, D, A, F, G, H, E, 0x53D43CEC, W[11], W[15]); + next_SM3_W(W2, W3, W0, W1); + W2.store_le(&W[8]); + + R2(A, B, C, D, E, F, G, H, 0xA7A879D8, W[12], W[ 0]); + R2(D, A, B, C, H, E, F, G, 0x4F50F3B1, W[13], W[ 1]); + R2(C, D, A, B, G, H, E, F, 0x9EA1E762, W[14], W[ 2]); + R2(B, C, D, A, F, G, H, E, 0x3D43CEC5, W[15], W[ 3]); + next_SM3_W(W3, W0, W1, W2); + W3.store_le(&W[12]); + + R2(A, B, C, D, E, F, G, H, 0x7A879D8A, W[ 0], W[ 4]); + R2(D, A, B, C, H, E, F, G, 0xF50F3B14, W[ 1], W[ 5]); + R2(C, D, A, B, G, H, E, F, 0xEA1E7629, W[ 2], W[ 6]); + R2(B, C, D, A, F, G, H, E, 0xD43CEC53, W[ 3], W[ 7]); + next_SM3_W(W0, W1, W2, W3); + W0.store_le(&W[0]); + + R2(A, B, C, D, E, F, G, H, 0xA879D8A7, W[ 4], W[ 8]); + R2(D, A, B, C, H, E, F, G, 0x50F3B14F, W[ 5], W[ 9]); + R2(C, D, A, B, G, H, E, F, 0xA1E7629E, W[ 6], W[10]); + R2(B, C, D, A, F, G, H, E, 0x43CEC53D, W[ 7], W[11]); + next_SM3_W(W1, W2, W3, W0); + W1.store_le(&W[4]); + + R2(A, B, C, D, E, F, G, H, 0x879D8A7A, W[ 8], W[12]); + R2(D, A, B, C, H, E, F, G, 0x0F3B14F5, W[ 9], W[13]); + R2(C, D, A, B, G, H, E, F, 0x1E7629EA, W[10], W[14]); + R2(B, C, D, A, F, G, H, E, 0x3CEC53D4, W[11], W[15]); + next_SM3_W(W2, W3, W0, W1); + W2.store_le(&W[8]); + + R2(A, B, C, D, E, F, G, H, 0x79D8A7A8, W[12], W[ 0]); + R2(D, A, B, C, H, E, F, G, 0xF3B14F50, W[13], W[ 1]); + R2(C, D, A, B, G, H, E, F, 0xE7629EA1, W[14], W[ 2]); + R2(B, C, D, A, F, G, H, E, 0xCEC53D43, W[15], W[ 3]); + next_SM3_W(W3, W0, W1, W2); + W3.store_le(&W[12]); + + R2(A, B, C, D, E, F, G, H, 0x9D8A7A87, W[ 0], W[ 4]); + R2(D, A, B, C, H, E, F, G, 0x3B14F50F, W[ 1], W[ 5]); + R2(C, D, A, B, G, H, E, F, 0x7629EA1E, W[ 2], W[ 6]); + R2(B, C, D, A, F, G, H, E, 0xEC53D43C, W[ 3], W[ 7]); + next_SM3_W(W0, W1, W2, W3); + W0.store_le(&W[0]); + + R2(A, B, C, D, E, F, G, H, 0xD8A7A879, W[ 4], W[ 8]); + R2(D, A, B, C, H, E, F, G, 0xB14F50F3, W[ 5], W[ 9]); + R2(C, D, A, B, G, H, E, F, 0x629EA1E7, W[ 6], W[10]); + R2(B, C, D, A, F, G, H, E, 0xC53D43CE, W[ 7], W[11]); + + R2(A, B, C, D, E, F, G, H, 0x8A7A879D, W[ 8], W[12]); + R2(D, A, B, C, H, E, F, G, 0x14F50F3B, W[ 9], W[13]); + R2(C, D, A, B, G, H, E, F, 0x29EA1E76, W[10], W[14]); + R2(B, C, D, A, F, G, H, E, 0x53D43CEC, W[11], W[15]); + + R2(A, B, C, D, E, F, G, H, 0xA7A879D8, W[12], W[ 0]); + R2(D, A, B, C, H, E, F, G, 0x4F50F3B1, W[13], W[ 1]); + R2(C, D, A, B, G, H, E, F, 0x9EA1E762, W[14], W[ 2]); + R2(B, C, D, A, F, G, H, E, 0x3D43CEC5, W[15], W[ 3]); + + // clang-format on + + A = (digest[0] ^= A); + B = (digest[1] ^= B); + C = (digest[2] ^= C); + D = (digest[3] ^= D); + E = (digest[4] ^= E); + F = (digest[5] ^= F); + G = (digest[6] ^= G); + H = (digest[7] ^= H); + } + + // NOLINTEND(*-container-data-pointer) +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sm3/sm3_fn.h botan3-3.12.0+dfsg/src/lib/hash/sm3/sm3_fn.h --- botan3-3.7.1+dfsg/src/lib/hash/sm3/sm3_fn.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sm3/sm3_fn.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,75 @@ +/* +* (C) 2017 Ribose Inc. +* (C) 2021 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_SM3_FN_H_ +#define BOTAN_SM3_FN_H_ + +#include +#include +#include + +namespace Botan { + +inline uint32_t P0(uint32_t X) { + return X ^ rotl<9>(X) ^ rotl<17>(X); +} + +inline void R1(uint32_t A, + uint32_t& B, + uint32_t C, + uint32_t& D, + uint32_t E, + uint32_t& F, + uint32_t G, + uint32_t& H, + uint32_t TJ, + uint32_t Wi, + uint32_t Wj) { + const uint32_t A12 = rotl<12>(A); + const uint32_t SS1 = rotl<7>(A12 + E + TJ); + const uint32_t TT1 = (A ^ B ^ C) + D + (SS1 ^ A12) + (Wi ^ Wj); + const uint32_t TT2 = (E ^ F ^ G) + H + SS1 + Wi; + + B = rotl<9>(B); + D = TT1; + F = rotl<19>(F); + H = P0(TT2); +} + +inline void R2(uint32_t A, + uint32_t& B, + uint32_t C, + uint32_t& D, + uint32_t E, + uint32_t& F, + uint32_t G, + uint32_t& H, + uint32_t TJ, + uint32_t Wi, + uint32_t Wj) { + const uint32_t A12 = rotl<12>(A); + const uint32_t SS1 = rotl<7>(A12 + E + TJ); + const uint32_t TT1 = majority(A, B, C) + D + (SS1 ^ A12) + (Wi ^ Wj); + const uint32_t TT2 = choose(E, F, G) + H + SS1 + Wi; + + B = rotl<9>(B); + D = TT1; + F = rotl<19>(F); + H = P0(TT2); +} + +inline uint32_t P1(uint32_t X) { + return X ^ rotl<15>(X) ^ rotl<23>(X); +} + +inline uint32_t SM3_E(uint32_t W0, uint32_t W7, uint32_t W13, uint32_t W3, uint32_t W10) { + return P1(W0 ^ W7 ^ rotl<15>(W13)) ^ rotl<7>(W3) ^ W10; +} + +} // namespace Botan + +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sm3/sm3_x86/info.txt botan3-3.12.0+dfsg/src/lib/hash/sm3/sm3_x86/info.txt --- botan3-3.7.1+dfsg/src/lib/hash/sm3/sm3_x86/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sm3/sm3_x86/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,25 @@ + +SM3_X86 -> 20251225 + + + +name -> "SM3 x86" +brief -> "SM3 using Intel SM3 extension" + + + +cpuid +simd_4x32 + + + +sm3 +ssse3 +avx2 + + + +gcc:14 +clang:17 +msvc + diff -Nru botan3-3.7.1+dfsg/src/lib/hash/sm3/sm3_x86/sm3_x86.cpp botan3-3.12.0+dfsg/src/lib/hash/sm3/sm3_x86/sm3_x86.cpp --- botan3-3.7.1+dfsg/src/lib/hash/sm3/sm3_x86/sm3_x86.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/sm3/sm3_x86/sm3_x86.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,134 @@ +/* +* (C) 2025 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include + +namespace Botan { + +namespace { + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX2_SM3 void sm3_permute_state_in(SIMD_4x32& S0, SIMD_4x32& S1) { + S0 = SIMD_4x32(_mm_shuffle_epi32(S0.raw(), 0b10110001)); // CDAB + S1 = SIMD_4x32(_mm_shuffle_epi32(S1.raw(), 0b00011011)); // EFGH + + const auto T = SIMD_4x32::alignr8(S0, S1); // ABEF + S1 = SIMD_4x32(_mm_blend_epi16(S1.rotr<19>().raw(), S0.rotr<9>().raw(), 0xF0)); // CDGH + S0 = T; +} + +BOTAN_FN_ISA_AVX2_SM3 inline void SM3_NI_next(SIMD_4x32& W0, + const SIMD_4x32& W1, + const SIMD_4x32& W2, + const SIMD_4x32& W3) { + auto X3 = SIMD_4x32(_mm_alignr_epi8(W1.raw(), W0.raw(), 12)); // W[3..6] + auto X7 = SIMD_4x32(_mm_alignr_epi8(W2.raw(), W1.raw(), 12)); // W[7..10] + auto X10 = SIMD_4x32::alignr8(W3, W2); // W[10..13] + auto X13 = W3.template shift_elems_right<1>(); // W[13..15] || 0 + + auto P1_O = SIMD_4x32(_mm_sm3msg1_epi32(X7.raw(), X13.raw(), W0.raw())); + W0 = SIMD_4x32(_mm_sm3msg2_epi32(P1_O.raw(), X3.raw(), X10.raw())); +} + +template +BOTAN_FN_ISA_AVX2_SM3 inline void SM3_NI_Rx4(SIMD_4x32& S0, SIMD_4x32& S1, SIMD_4x32 W0, SIMD_4x32 W1) { + const auto W0145 = SIMD_4x32(_mm_unpacklo_epi64(W0.raw(), W1.raw())); + const auto W2367 = SIMD_4x32(_mm_unpackhi_epi64(W0.raw(), W1.raw())); + + S0 = SIMD_4x32(_mm_sm3rnds2_epi32(S0.raw(), S1.raw(), W0145.raw(), R)); + S1 = SIMD_4x32(_mm_sm3rnds2_epi32(S1.raw(), S0.raw(), W2367.raw(), R + 2)); +} + +} // namespace + +BOTAN_FN_ISA_AVX2_SM3 void SM3::compress_digest_x86(digest_type& digest, + std::span input, + size_t blocks) { + auto S0 = SIMD_4x32::load_le(&digest[0]); // NOLINT(*-container-data-pointer) + auto S1 = SIMD_4x32::load_le(&digest[4]); + sm3_permute_state_in(S0, S1); + + const uint8_t* data = input.data(); + + while(blocks > 0) { + SIMD_4x32 W0 = SIMD_4x32::load_be(&data[0]); // NOLINT(*-container-data-pointer) + SIMD_4x32 W1 = SIMD_4x32::load_be(&data[16]); + SIMD_4x32 W2 = SIMD_4x32::load_be(&data[32]); + SIMD_4x32 W3 = SIMD_4x32::load_be(&data[48]); + + const auto S0_save = S0; + const auto S1_save = S1; + + data += block_bytes; + blocks -= 1; + + SM3_NI_Rx4<0>(S1, S0, W0, W1); + SM3_NI_next(W0, W1, W2, W3); + + SM3_NI_Rx4<4>(S1, S0, W1, W2); + SM3_NI_next(W1, W2, W3, W0); + + SM3_NI_Rx4<8>(S1, S0, W2, W3); + SM3_NI_next(W2, W3, W0, W1); + + SM3_NI_Rx4<12>(S1, S0, W3, W0); + SM3_NI_next(W3, W0, W1, W2); + + SM3_NI_Rx4<16>(S1, S0, W0, W1); + SM3_NI_next(W0, W1, W2, W3); + + SM3_NI_Rx4<20>(S1, S0, W1, W2); + SM3_NI_next(W1, W2, W3, W0); + + SM3_NI_Rx4<24>(S1, S0, W2, W3); + SM3_NI_next(W2, W3, W0, W1); + + SM3_NI_Rx4<28>(S1, S0, W3, W0); + SM3_NI_next(W3, W0, W1, W2); + + SM3_NI_Rx4<32>(S1, S0, W0, W1); + SM3_NI_next(W0, W1, W2, W3); + + SM3_NI_Rx4<36>(S1, S0, W1, W2); + SM3_NI_next(W1, W2, W3, W0); + + SM3_NI_Rx4<40>(S1, S0, W2, W3); + SM3_NI_next(W2, W3, W0, W1); + + SM3_NI_Rx4<44>(S1, S0, W3, W0); + SM3_NI_next(W3, W0, W1, W2); + + SM3_NI_Rx4<48>(S1, S0, W0, W1); + SM3_NI_next(W0, W1, W2, W3); + + SM3_NI_Rx4<52>(S1, S0, W1, W2); + SM3_NI_Rx4<56>(S1, S0, W2, W3); + SM3_NI_Rx4<60>(S1, S0, W3, W0); + + S0 ^= S0_save; + S1 ^= S1_save; + } + + // TODO do this with SIMD instead + uint32_t T[8] = {0}; + S0.store_le(&T[0]); + S1.store_le(&T[4]); + + digest[0] = T[3]; + digest[1] = T[2]; + digest[2] = rotr<23>(T[7]); + digest[3] = rotr<23>(T[6]); + digest[4] = T[1]; + digest[5] = T[0]; + digest[6] = rotr<13>(T[5]); + digest[7] = rotr<13>(T[4]); +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/hash/streebog/streebog.cpp botan3-3.12.0+dfsg/src/lib/hash/streebog/streebog.cpp --- botan3-3.7.1+dfsg/src/lib/hash/streebog/streebog.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/streebog/streebog.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,7 +1,7 @@ /* -* Streebog +* Streebog (GOST R 34.11-2012) * (C) 2017 Ribose Inc. -* (C) 2018 Jack Lloyd +* (C) 2018,2026 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -9,15 +9,117 @@ #include #include +#include #include +#include #include #include -#include +#include +#include namespace Botan { -extern const uint64_t STREEBOG_Ax[8][256]; -extern const uint64_t STREEBOG_C[12][8]; +namespace { + +// Build the combined T-tables at compile time +consteval std::array, 8> streebog_Ax_table() noexcept { + // Streebog sbox (same as Kuznyechik's), RFC 6986 Section 6.2 + alignas(256) const constexpr uint8_t S[256] = { + 252, 238, 221, 17, 207, 110, 49, 22, 251, 196, 250, 218, 35, 197, 4, 77, 233, 119, 240, 219, 147, 46, + 153, 186, 23, 54, 241, 187, 20, 205, 95, 193, 249, 24, 101, 90, 226, 92, 239, 33, 129, 28, 60, 66, + 139, 1, 142, 79, 5, 132, 2, 174, 227, 106, 143, 160, 6, 11, 237, 152, 127, 212, 211, 31, 235, 52, + 44, 81, 234, 200, 72, 171, 242, 42, 104, 162, 253, 58, 206, 204, 181, 112, 14, 86, 8, 12, 118, 18, + 191, 114, 19, 71, 156, 183, 93, 135, 21, 161, 150, 41, 16, 123, 154, 199, 243, 145, 120, 111, 157, 158, + 178, 177, 50, 117, 25, 61, 255, 53, 138, 126, 109, 84, 198, 128, 195, 189, 13, 87, 223, 245, 36, 169, + 62, 168, 67, 201, 215, 121, 214, 246, 124, 34, 185, 3, 224, 15, 236, 222, 122, 148, 176, 188, 220, 232, + 40, 80, 78, 51, 10, 74, 167, 151, 96, 115, 30, 0, 98, 68, 26, 184, 56, 130, 100, 159, 38, 65, + 173, 69, 70, 146, 39, 94, 85, 47, 140, 163, 165, 125, 105, 213, 149, 59, 7, 88, 179, 64, 134, 172, + 29, 247, 48, 55, 107, 228, 136, 217, 231, 137, 225, 27, 131, 73, 76, 63, 248, 254, 141, 83, 170, 144, + 202, 216, 133, 97, 32, 113, 103, 164, 45, 43, 9, 91, 203, 155, 37, 208, 190, 229, 108, 82, 89, 166, + 116, 210, 230, 244, 180, 192, 209, 102, 175, 194, 57, 75, 99, 182, + }; + + // Columns of the 8x8 linear transformation matrix over GF(2^8) + const constexpr uint64_t L[8] = { + 0x641c314b2b8ee083, + 0xa48b474f9ef5dc18, + 0xf97d86d98a327728, + 0x5b068c651810a89e, + 0x0321658cba93c138, + 0xaccc9ca9328a8950, + 0x46b60f011a83988e, + 0x83478b07b2468764, + }; + + std::array, 8> Ax = {}; + + for(size_t j = 0; j != 8; ++j) { + for(size_t x = 0; x != 256; ++x) { + Ax[j][x] = poly_mul<0x1D>(L[j], S[x]); + } + } + + return Ax; +} + +const constinit auto STREEBOG_Ax = streebog_Ax_table(); + +// Iteration constants C[1]..C[12] from GOST R 34.11-2012 (RFC 6986 Section 6.5) +// Word order matches the RFC (big-endian presentation); indexed with 7-j below +// clang-format off +const constexpr uint64_t STREEBOG_C[12][8] = { + {0xb1085bda1ecadae9, 0xebcb2f81c0657c1f, 0x2f6a76432e45d016, 0x714eb88d7585c4fc, + 0x4b7ce09192676901, 0xa2422a08a460d315, 0x05767436cc744d23, 0xdd806559f2a64507}, + {0x6fa3b58aa99d2f1a, 0x4fe39d460f70b5d7, 0xf3feea720a232b98, 0x61d55e0f16b50131, + 0x9ab5176b12d69958, 0x5cb561c2db0aa7ca, 0x55dda21bd7cbcd56, 0xe679047021b19bb7}, + {0xf574dcac2bce2fc7, 0x0a39fc286a3d8435, 0x06f15e5f529c1f8b, 0xf2ea7514b1297b7b, + 0xd3e20fe490359eb1, 0xc1c93a376062db09, 0xc2b6f443867adb31, 0x991e96f50aba0ab2}, + {0xef1fdfb3e81566d2, 0xf948e1a05d71e4dd, 0x488e857e335c3c7d, 0x9d721cad685e353f, + 0xa9d72c82ed03d675, 0xd8b71333935203be, 0x3453eaa193e837f1, 0x220cbebc84e3d12e}, + {0x4bea6bacad474799, 0x9a3f410c6ca92363, 0x7f151c1f1686104a, 0x359e35d7800fffbd, + 0xbfcd1747253af5a3, 0xdfff00b723271a16, 0x7a56a27ea9ea63f5, 0x601758fd7c6cfe57}, + {0xae4faeae1d3ad3d9, 0x6fa4c33b7a3039c0, 0x2d66c4f95142a46c, 0x187f9ab49af08ec6, + 0xcffaa6b71c9ab7b4, 0x0af21f66c2bec6b6, 0xbf71c57236904f35, 0xfa68407a46647d6e}, + {0xf4c70e16eeaac5ec, 0x51ac86febf240954, 0x399ec6c7e6bf87c9, 0xd3473e33197a93c9, + 0x0992abc52d822c37, 0x06476983284a0504, 0x3517454ca23c4af3, 0x8886564d3a14d493}, + {0x9b1f5b424d93c9a7, 0x03e7aa020c6e4141, 0x4eb7f8719c36de1e, 0x89b4443b4ddbc49a, + 0xf4892bcb929b0690, 0x69d18d2bd1a5c42f, 0x36acc2355951a8d9, 0xa47f0dd4bf02e71e}, + {0x378f5a541631229b, 0x944c9ad8ec165fde, 0x3a7d3a1b25894224, 0x3cd955b7e00d0984, + 0x800a440bdbb2ceb1, 0x7b2b8a9aa6079c54, 0x0e38dc92cb1f2a60, 0x7261445183235adb}, + {0xabbedea680056f52, 0x382ae548b2e4f3f3, 0x8941e71cff8a78db, 0x1fffe18a1b336103, + 0x9fe76702af69334b, 0x7a1e6c303b7652f4, 0x3698fad1153bb6c3, 0x74b4c7fb98459ced}, + {0x7bcd9ed0efc889fb, 0x3002c6cd635afe94, 0xd8fa6bbbebab0761, 0x2001802114846679, + 0x8a1d71efea48b9ca, 0xefbacd1d7d476e98, 0xdea2594ac06fd85d, 0x6bcaa4cd81f32d1b}, + {0x378ee767f11631ba, 0xd21380b00449b17a, 0xcda43c32bcdf1d77, 0xf82012d430219f9b, + 0x5d80ef9d1891cc86, 0xe71da4aa88e12852, 0xfaf417d5d9b21b99, 0x48bc924af11bd720}, +}; + +// clang-format on + +inline uint64_t force_le(uint64_t x) { + if constexpr(std::endian::native == std::endian::little) { + return x; + } else if constexpr(std::endian::native == std::endian::big) { + return reverse_bytes(x); + } else { + store_le(x, reinterpret_cast(&x)); + return x; + } +} + +inline void lps(uint64_t block[8]) { + const uint64_t block2[8] = {block[0], block[1], block[2], block[3], block[4], block[5], block[6], block[7]}; + const std::span r{reinterpret_cast(block2), 64}; + + for(int i = 0; i < 8; ++i) { + block[i] = force_le(STREEBOG_Ax[0][r[i + 0 * 8]]) ^ force_le(STREEBOG_Ax[1][r[i + 1 * 8]]) ^ + force_le(STREEBOG_Ax[2][r[i + 2 * 8]]) ^ force_le(STREEBOG_Ax[3][r[i + 3 * 8]]) ^ + force_le(STREEBOG_Ax[4][r[i + 4 * 8]]) ^ force_le(STREEBOG_Ax[5][r[i + 5 * 8]]) ^ + force_le(STREEBOG_Ax[6][r[i + 6 * 8]]) ^ force_le(STREEBOG_Ax[7][r[i + 7 * 8]]); + } +} + +} //namespace std::unique_ptr Streebog::copy_state() const { return std::make_unique(*this); @@ -86,43 +188,16 @@ compress(m_buffer.consume().data(), true); compress_64(m_S.data(), true); - // FIXME - std::memcpy(output.data(), &m_h[8 - output_length() / 8], output_length()); - clear(); -} - -namespace { - -inline uint64_t force_le(uint64_t x) { -#if defined(BOTAN_TARGET_CPU_IS_LITTLE_ENDIAN) - return x; -#elif defined(BOTAN_TARGET_CPU_IS_BIG_ENDIAN) - return reverse_bytes(x); -#else - store_le(x, reinterpret_cast(&x)); - return x; -#endif -} - -inline void lps(uint64_t block[8]) { - uint8_t r[64]; - // FIXME - std::memcpy(r, block, 64); - for(int i = 0; i < 8; ++i) { - block[i] = force_le(STREEBOG_Ax[0][r[i + 0 * 8]]) ^ force_le(STREEBOG_Ax[1][r[i + 1 * 8]]) ^ - force_le(STREEBOG_Ax[2][r[i + 2 * 8]]) ^ force_le(STREEBOG_Ax[3][r[i + 3 * 8]]) ^ - force_le(STREEBOG_Ax[4][r[i + 4 * 8]]) ^ force_le(STREEBOG_Ax[5][r[i + 5 * 8]]) ^ - force_le(STREEBOG_Ax[6][r[i + 6 * 8]]) ^ force_le(STREEBOG_Ax[7][r[i + 7 * 8]]); - } + const size_t offset = 8 - output_length() / 8; + const size_t count = output_length() / sizeof(uint64_t); + typecast_copy(output, std::span(&m_h[offset], count)); + clear(); } -} //namespace - void Streebog::compress(const uint8_t input[], bool last_block) { uint64_t M[8]; - std::memcpy(M, input, 64); - + typecast_copy(M, std::span(input, 64)); compress_64(M, last_block); } @@ -142,9 +217,9 @@ hN[i] ^= M[i]; } - for(size_t i = 0; i < 12; ++i) { + for(size_t i = 0; i < 12; ++i) { // NOLINT(modernize-loop-convert) for(size_t j = 0; j != 8; ++j) { - A[j] ^= force_le(STREEBOG_C[i][j]); + A[j] ^= force_le(STREEBOG_C[i][7 - j]); } lps(A); @@ -167,7 +242,7 @@ m_S[i] = force_le(t); if(t != m) { - carry = (t < m); + carry = (t < m) ? 1 : 0; } } } diff -Nru botan3-3.7.1+dfsg/src/lib/hash/streebog/streebog_precalc.cpp botan3-3.12.0+dfsg/src/lib/hash/streebog/streebog_precalc.cpp --- botan3-3.7.1+dfsg/src/lib/hash/streebog/streebog_precalc.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/streebog/streebog_precalc.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,549 +0,0 @@ -/* - * Derived from: - * https://github.com/degtyarevalexey/streebog - * - * Copyright (c) 2013, Alexey Degtyarev . - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include - -namespace Botan { - -extern const uint64_t STREEBOG_Ax[8][256] = { - {0xd01f715b5c7ef8e6, 0x16fa240980778325, 0xa8a42e857ee049c8, 0x6ac1068fa186465b, 0x6e417bd7a2e9320b, - 0x665c8167a437daab, 0x7666681aa89617f6, 0x4b959163700bdcf5, 0xf14be6b78df36248, 0xc585bd689a625cff, - 0x9557d7fca67d82cb, 0x89f0b969af6dd366, 0xb0833d48749f6c35, 0xa1998c23b1ecbc7c, 0x8d70c431ac02a736, - 0xd6dfbc2fd0a8b69e, 0x37aeb3e551fa198b, 0x0b7d128a40b5cf9c, 0x5a8f2008b5780cbc, 0xedec882284e333e5, - 0xd25fc177d3c7c2ce, 0x5e0f5d50b61778ec, 0x1d873683c0c24cb9, 0xad040bcbb45d208c, 0x2f89a0285b853c76, - 0x5732fff6791b8d58, 0x3e9311439ef6ec3f, 0xc9183a809fd3c00f, 0x83adf3f5260a01ee, 0xa6791941f4e8ef10, - 0x103ae97d0ca1cd5d, 0x2ce948121dee1b4a, 0x39738421dbf2bf53, 0x093da2a6cf0cf5b4, 0xcd9847d89cbcb45f, - 0xf9561c078b2d8ae8, 0x9c6a755a6971777f, 0xbc1ebaa0712ef0c5, 0x72e61542abf963a6, 0x78bb5fde229eb12e, - 0x14ba94250fceb90d, 0x844d6697630e5282, 0x98ea08026a1e032f, 0xf06bbea144217f5c, 0xdb6263d11ccb377a, - 0x641c314b2b8ee083, 0x320e96ab9b4770cf, 0x1ee7deb986a96b85, 0xe96cf57a878c47b5, 0xfdd6615f8842feb8, - 0xc83862965601dd1b, 0x2ea9f83e92572162, 0xf876441142ff97fc, 0xeb2c455608357d9d, 0x5612a7e0b0c9904c, - 0x6c01cbfb2d500823, 0x4548a6a7fa037a2d, 0xabc4c6bf388b6ef4, 0xbade77d4fdf8bebd, 0x799b07c8eb4cac3a, - 0x0c9d87e805b19cf0, 0xcb588aac106afa27, 0xea0c1d40c1e76089, 0x2869354a1e816f1a, 0xff96d17307fbc490, - 0x9f0a9d602f1a5043, 0x96373fc6e016a5f7, 0x5292dab8b3a6e41c, 0x9b8ae0382c752413, 0x4f15ec3b7364a8a5, - 0x3fb349555724f12b, 0xc7c50d4415db66d7, 0x92b7429ee379d1a7, 0xd37f99611a15dfda, 0x231427c05e34a086, - 0xa439a96d7b51d538, 0xb403401077f01865, 0xdda2aea5901d7902, 0x0a5d4a9c8967d288, 0xc265280adf660f93, - 0x8bb0094520d4e94e, 0x2a29856691385532, 0x42a833c5bf072941, 0x73c64d54622b7eb2, 0x07e095624504536c, - 0x8a905153e906f45a, 0x6f6123c16b3b2f1f, 0xc6e55552dc097bc3, 0x4468feb133d16739, 0xe211e7f0c7398829, - 0xa2f96419f7879b40, 0x19074bdbc3ad38e9, 0xf4ebc3f9474e0b0c, 0x43886bd376d53455, 0xd8028beb5aa01046, - 0x51f23282f5cdc320, 0xe7b1c2be0d84e16d, 0x081dfab006dee8a0, 0x3b33340d544b857b, 0x7f5bcabc679ae242, - 0x0edd37c48a08a6d8, 0x81ed43d9a9b33bc6, 0xb1a3655ebd4d7121, 0x69a1eeb5e7ed6167, 0xf6ab73d5c8f73124, - 0x1a67a3e185c61fd5, 0x2dc91004d43c065e, 0x0240b02c8fb93a28, 0x90f7f2b26cc0eb8f, 0x3cd3a16f114fd617, - 0xaae49ea9f15973e0, 0x06c0cd748cd64e78, 0xda423bc7d5192a6e, 0xc345701c16b41287, 0x6d2193ede4821537, - 0xfcf639494190e3ac, 0x7c3b228621f1c57e, 0xfb16ac2b0494b0c0, 0xbf7e529a3745d7f9, 0x6881b6a32e3f7c73, - 0xca78d2bad9b8e733, 0xbbfe2fc2342aa3a9, 0x0dbddffecc6381e4, 0x70a6a56e2440598e, 0xe4d12a844befc651, - 0x8c509c2765d0ba22, 0xee8c6018c28814d9, 0x17da7c1f49a59e31, 0x609c4c1328e194d3, 0xb3e3d57232f44b09, - 0x91d7aaa4a512f69b, 0x0ffd6fd243dabbcc, 0x50d26a943c1fde34, 0x6be15e9968545b4f, 0x94778fea6faf9fdf, - 0x2b09dd7058ea4826, 0x677cd9716de5c7bf, 0x49d5214fffb2e6dd, 0x0360e83a466b273c, 0x1fc786af4f7b7691, - 0xa0b9d435783ea168, 0xd49f0c035f118cb6, 0x01205816c9d21d14, 0xac2453dd7d8f3d98, 0x545217cc3f70aa64, - 0x26b4028e9489c9c2, 0xdec2469fd6765e3e, 0x04807d58036f7450, 0xe5f17292823ddb45, 0xf30b569b024a5860, - 0x62dcfc3fa758aefb, 0xe84cad6c4e5e5aa1, 0xccb81fce556ea94b, 0x53b282ae7a74f908, 0x1b47fbf74c1402c1, - 0x368eebf39828049f, 0x7afbeff2ad278b06, 0xbe5e0a8cfe97caed, 0xcfd8f7f413058e77, 0xf78b2bc301252c30, - 0x4d555c17fcdd928d, 0x5f2f05467fc565f8, 0x24f4b2a21b30f3ea, 0x860dd6bbecb768aa, 0x4c750401350f8f99, - 0x0000000000000000, 0xecccd0344d312ef1, 0xb5231806be220571, 0xc105c030990d28af, 0x653c695de25cfd97, - 0x159acc33c61ca419, 0xb89ec7f872418495, 0xa9847693b73254dc, 0x58cf90243ac13694, 0x59efc832f3132b80, - 0x5c4fed7c39ae42c4, 0x828dabe3efd81cfa, 0xd13f294d95ace5f2, 0x7d1b7a90e823d86a, 0xb643f03cf849224d, - 0x3df3f979d89dcb03, 0x7426d836272f2dde, 0xdfe21e891fa4432a, 0x3a136c1b9d99986f, 0xfa36f43dcd46add4, - 0xc025982650df35bb, 0x856d3e81aadc4f96, 0xc4a5e57e53b041eb, 0x4708168b75ba4005, 0xaf44bbe73be41aa4, - 0x971767d029c4b8e3, 0xb9be9feebb939981, 0x215497ecd18d9aae, 0x316e7e91dd2c57f3, 0xcef8afe2dad79363, - 0x3853dc371220a247, 0x35ee03c9de4323a3, 0xe6919aa8c456fc79, 0xe05157dc4880b201, 0x7bdbb7e464f59612, - 0x127a59518318f775, 0x332ecebd52956ddb, 0x8f30741d23bb9d1e, 0xd922d3fd93720d52, 0x7746300c61440ae2, - 0x25d4eab4d2e2eefe, 0x75068020eefd30ca, 0x135a01474acaea61, 0x304e268714fe4ae7, 0xa519f17bb283c82c, - 0xdc82f6b359cf6416, 0x5baf781e7caa11a8, 0xb2c38d64fb26561d, 0x34ce5bdf17913eb7, 0x5d6fb56af07c5fd0, - 0x182713cd0a7f25fd, 0x9e2ac576e6c84d57, 0x9aaab82ee5a73907, 0xa3d93c0f3e558654, 0x7e7b92aaae48ff56, - 0x872d8ead256575be, 0x41c8dbfff96c0e7d, 0x99ca5014a3cc1e3b, 0x40e883e930be1369, 0x1ca76e95091051ad, - 0x4e35b42dbab6b5b1, 0x05a0254ecabd6944, 0xe1710fca8152af15, 0xf22b0e8dcb984574, 0xb763a82a319b3f59, - 0x63fca4296e8ab3ef, 0x9d4a2d4ca0a36a6b, 0xe331bfe60eeb953d, 0xd5bf541596c391a2, 0xf5cb9bef8e9c1618, - 0x46284e9dbc685d11, 0x2074cffa185f87ba, 0xbd3ee2b6b8fcedd1, 0xae64e3f1f23607b0, 0xfeb68965ce29d984, - 0x55724fdaf6a2b770, 0x29496d5cd753720e, 0xa75941573d3af204, 0x8e102c0bea69800a, 0x111ab16bc573d049, - 0xd7ffe439197aab8a, 0xefac380e0b5a09cd, 0x48f579593660fbc9, 0x22347fd697e6bd92, 0x61bc1405e13389c7, - 0x4ab5c975b9d9c1e1, 0x80cd1bcf606126d2, 0x7186fd78ed92449a, 0x93971a882aabccb3, 0x88d0e17f66bfce72, - 0x27945a985d5bd4d6}, - {0xde553f8c05a811c8, 0x1906b59631b4f565, 0x436e70d6b1964ff7, 0x36d343cb8b1e9d85, 0x843dfacc858aab5a, - 0xfdfc95c299bfc7f9, 0x0f634bdea1d51fa2, 0x6d458b3b76efb3cd, 0x85c3f77cf8593f80, 0x3c91315fbe737cb2, - 0x2148b03366ace398, 0x18f8b8264c6761bf, 0xc830c1c495c9fb0f, 0x981a76102086a0aa, 0xaa16012142f35760, - 0x35cc54060c763cf6, 0x42907d66cc45db2d, 0x8203d44b965af4bc, 0x3d6f3cefc3a0e868, 0xbc73ff69d292bda7, - 0x8722ed0102e20a29, 0x8f8185e8cd34deb7, 0x9b0561dda7ee01d9, 0x5335a0193227fad6, 0xc9cecc74e81a6fd5, - 0x54f5832e5c2431ea, 0x99e47ba05d553470, 0xf7bee756acd226ce, 0x384e05a5571816fd, 0xd1367452a47d0e6a, - 0xf29fde1c386ad85b, 0x320c77316275f7ca, 0xd0c879e2d9ae9ab0, 0xdb7406c69110ef5d, 0x45505e51a2461011, - 0xfc029872e46c5323, 0xfa3cb6f5f7bc0cc5, 0x031f17cd8768a173, 0xbd8df2d9af41297d, 0x9d3b4f5ab43e5e3f, - 0x4071671b36feee84, 0x716207e7d3e3b83d, 0x48d20ff2f9283a1a, 0x27769eb4757cbc7e, 0x5c56ebc793f2e574, - 0xa48b474f9ef5dc18, 0x52cbada94ff46e0c, 0x60c7da982d8199c6, 0x0e9d466edc068b78, 0x4eec2175eaf865fc, - 0x550b8e9e21f7a530, 0x6b7ba5bc653fec2b, 0x5eb7f1ba6949d0dd, 0x57ea94e3db4c9099, 0xf640eae6d101b214, - 0xdd4a284182c0b0bb, 0xff1d8fbf6304f250, 0xb8accb933bf9d7e8, 0xe8867c478eb68c4d, 0x3f8e2692391bddc1, - 0xcb2fd60912a15a7c, 0xaec935dbab983d2f, 0xf55ffd2b56691367, 0x80e2ce366ce1c115, 0x179bf3f8edb27e1d, - 0x01fe0db07dd394da, 0xda8a0b76ecc37b87, 0x44ae53e1df9584cb, 0xb310b4b77347a205, 0xdfab323c787b8512, - 0x3b511268d070b78e, 0x65e6e3d2b9396753, 0x6864b271e2574d58, 0x259784c98fc789d7, 0x02e11a7dfabb35a9, - 0x8841a6dfa337158b, 0x7ade78c39b5dcdd0, 0xb7cf804d9a2cc84a, 0x20b6bd831b7f7742, 0x75bd331d3a88d272, - 0x418f6aab4b2d7a5e, 0xd9951cbb6babdaf4, 0xb6318dfde7ff5c90, 0x1f389b112264aa83, 0x492c024284fbaec0, - 0xe33a0363c608f9a0, 0x2688930408af28a4, 0xc7538a1a341ce4ad, 0x5da8e677ee2171ae, 0x8c9e92254a5c7fc4, - 0x63d8cd55aae938b5, 0x29ebd8daa97a3706, 0x959827b37be88aa1, 0x1484e4356adadf6e, 0xa7945082199d7d6b, - 0xbf6ce8a455fa1cd4, 0x9cc542eac9edcae5, 0x79c16f0e1c356ca3, 0x89bfab6fdee48151, 0xd4174d1830c5f0ff, - 0x9258048415eb419d, 0x6139d72850520d1c, 0x6a85a80c18ec78f1, 0xcd11f88e0171059a, 0xcceff53e7ca29140, - 0xd229639f2315af19, 0x90b91ef9ef507434, 0x5977d28d074a1be1, 0x311360fce51d56b9, 0xc093a92d5a1f2f91, - 0x1a19a25bb6dc5416, 0xeb996b8a09de2d3e, 0xfee3820f1ed7668a, 0xd7085ad5b7ad518c, 0x7fff41890fe53345, - 0xec5948bd67dde602, 0x2fd5f65dbaaa68e0, 0xa5754affe32648c2, 0xf8ddac880d07396c, 0x6fa491468c548664, - 0x0c7c5c1326bdbed1, 0x4a33158f03930fb3, 0x699abfc19f84d982, 0xe4fa2054a80b329c, 0x6707f9af438252fa, - 0x08a368e9cfd6d49e, 0x47b1442c58fd25b8, 0xbbb3dc5ebc91769b, 0x1665fe489061eac7, 0x33f27a811fa66310, - 0x93a609346838d547, 0x30ed6d4c98cec263, 0x1dd9816cd8df9f2a, 0x94662a03063b1e7b, 0x83fdd9fbeb896066, - 0x7b207573e68e590a, 0x5f49fc0a149a4407, 0x343259b671a5a82c, 0xfbc2bb458a6f981f, 0xc272b350a0a41a38, - 0x3aaf1fd8ada32354, 0x6cbb868b0b3c2717, 0xa2b569c88d2583fe, 0xf180c9d1bf027928, 0xaf37386bd64ba9f5, - 0x12bacab2790a8088, 0x4c0d3b0810435055, 0xb2eeb9070e9436df, 0xc5b29067cea7d104, 0xdcb425f1ff132461, - 0x4f122cc5972bf126, 0xac282fa651230886, 0xe7e537992f6393ef, 0xe61b3a2952b00735, 0x709c0a57ae302ce7, - 0xe02514ae416058d3, 0xc44c9dd7b37445de, 0x5a68c5408022ba92, 0x1c278cdca50c0bf0, 0x6e5a9cf6f18712be, - 0x86dce0b17f319ef3, 0x2d34ec2040115d49, 0x4bcd183f7e409b69, 0x2815d56ad4a9a3dc, 0x24698979f2141d0d, - 0x0000000000000000, 0x1ec696a15fb73e59, 0xd86b110b16784e2e, 0x8e7f8858b0e74a6d, 0x063e2e8713d05fe6, - 0xe2c40ed3bbdb6d7a, 0xb1f1aeca89fc97ac, 0xe1db191e3cb3cc09, 0x6418ee62c4eaf389, 0xc6ad87aa49cf7077, - 0xd6f65765ca7ec556, 0x9afb6c6dda3d9503, 0x7ce05644888d9236, 0x8d609f95378feb1e, 0x23a9aa4e9c17d631, - 0x6226c0e5d73aac6f, 0x56149953a69f0443, 0xeeb852c09d66d3ab, 0x2b0ac2a753c102af, 0x07c023376e03cb3c, - 0x2ccae1903dc2c993, 0xd3d76e2f5ec63bc3, 0x9e2458973356ff4c, 0xa66a5d32644ee9b1, 0x0a427294356de137, - 0x783f62be61e6f879, 0x1344c70204d91452, 0x5b96c8f0fdf12e48, 0xa90916ecc59bf613, 0xbe92e5142829880e, - 0x727d102a548b194e, 0x1be7afebcb0fc0cc, 0x3e702b2244c8491b, 0xd5e940a84d166425, 0x66f9f41f3e51c620, - 0xabe80c913f20c3ba, 0xf07ec461c2d1edf2, 0xf361d3ac45b94c81, 0x0521394a94b8fe95, 0xadd622162cf09c5c, - 0xe97871f7f3651897, 0xf4a1f09b2bba87bd, 0x095d6559b2054044, 0x0bbc7f2448be75ed, 0x2af4cf172e129675, - 0x157ae98517094bb4, 0x9fda55274e856b96, 0x914713499283e0ee, 0xb952c623462a4332, 0x74433ead475b46a8, - 0x8b5eb112245fb4f8, 0xa34b6478f0f61724, 0x11a5dd7ffe6221fb, 0xc16da49d27ccbb4b, 0x76a224d0bde07301, - 0x8aa0bca2598c2022, 0x4df336b86d90c48f, 0xea67663a740db9e4, 0xef465f70e0b54771, 0x39b008152acb8227, - 0x7d1e5bf4f55e06ec, 0x105bd0cf83b1b521, 0x775c2960c033e7db, 0x7e014c397236a79f, 0x811cc386113255cf, - 0xeda7450d1a0e72d8, 0x5889df3d7a998f3b, 0x2e2bfbedc779fc3a, 0xce0eef438619a4e9, 0x372d4e7bf6cd095f, - 0x04df34fae96b6a4f, 0xf923a13870d4adb6, 0xa1aa7e050a4d228d, 0xa8f71b5cb84862c9, 0xb52e9a306097fde3, - 0x0d8251a35b6e2a0b, 0x2257a7fee1c442eb, 0x73831d9a29588d94, 0x51d4ba64c89ccf7f, 0x502ab7d4b54f5ba5, - 0x97793dce8153bf08, 0xe5042de4d5d8a646, 0x9687307efc802bd2, 0xa05473b5779eb657, 0xb4d097801d446939, - 0xcff0e2f3fbca3033, 0xc38cbee0dd778ee2, 0x464f499c252eb162, 0xcad1dbb96f72cea6, 0xba4dd1eec142e241, - 0xb00fa37af42f0376}, - {0xcce4cd3aa968b245, 0x089d5484e80b7faf, 0x638246c1b3548304, 0xd2fe0ec8c2355492, 0xa7fbdf7ff2374eee, - 0x4df1600c92337a16, 0x84e503ea523b12fb, 0x0790bbfd53ab0c4a, 0x198a780f38f6ea9d, 0x2ab30c8f55ec48cb, - 0xe0f7fed6b2c49db5, 0xb6ecf3f422cadbdc, 0x409c9a541358df11, 0xd3ce8a56dfde3fe3, 0xc3e9224312c8c1a0, - 0x0d6dfa58816ba507, 0xddf3e1b179952777, 0x04c02a42748bb1d9, 0x94c2abff9f2decb8, 0x4f91752da8f8acf4, - 0x78682befb169bf7b, 0xe1c77a48af2ff6c4, 0x0c5d7ec69c80ce76, 0x4cc1e4928fd81167, 0xfeed3d24d9997b62, - 0x518bb6dfc3a54a23, 0x6dbf2d26151f9b90, 0xb5bc624b05ea664f, 0xe86aaa525acfe21a, 0x4801ced0fb53a0be, - 0xc91463e6c00868ed, 0x1027a815cd16fe43, 0xf67069a0319204cd, 0xb04ccc976c8abce7, 0xc0b9b3fc35e87c33, - 0xf380c77c58f2de65, 0x50bb3241de4e2152, 0xdf93f490435ef195, 0xf1e0d25d62390887, 0xaf668bfb1a3c3141, - 0xbc11b251f00a7291, 0x73a5eed47e427d47, 0x25bee3f6ee4c3b2e, 0x43cc0beb34786282, 0xc824e778dde3039c, - 0xf97d86d98a327728, 0xf2b043e24519b514, 0xe297ebf7880f4b57, 0x3a94a49a98fab688, 0x868516cb68f0c419, - 0xeffa11af0964ee50, 0xa4ab4ec0d517f37d, 0xa9c6b498547c567a, 0x8e18424f80fbbbb6, 0x0bcdc53bcf2bc23c, - 0x137739aaea3643d0, 0x2c1333ec1bac2ff0, 0x8d48d3f0a7db0625, 0x1e1ac3f26b5de6d7, 0xf520f81f16b2b95e, - 0x9f0f6ec450062e84, 0x0130849e1deb6b71, 0xd45e31ab8c7533a9, 0x652279a2fd14e43f, 0x3209f01e70f1c927, - 0xbe71a770cac1a473, 0x0e3d6be7a64b1894, 0x7ec8148cff29d840, 0xcb7476c7fac3be0f, 0x72956a4a63a91636, - 0x37f95ec21991138f, 0x9e3fea5a4ded45f5, 0x7b38ba50964902e8, 0x222e580bbde73764, 0x61e253e0899f55e6, - 0xfc8d2805e352ad80, 0x35994be3235ac56d, 0x09add01af5e014de, 0x5e8659a6780539c6, 0xb17c48097161d796, - 0x026015213acbd6e2, 0xd1ae9f77e515e901, 0xb7dc776a3f21b0ad, 0xaba6a1b96eb78098, 0x9bcf4486248d9f5d, - 0x582666c536455efd, 0xfdbdac9bfeb9c6f1, 0xc47999be4163cdea, 0x765540081722a7ef, 0x3e548ed8ec710751, - 0x3d041f67cb51bac2, 0x7958af71ac82d40a, 0x36c9da5c047a78fe, 0xed9a048e33af38b2, 0x26ee7249c96c86bd, - 0x900281bdeba65d61, 0x11172c8bd0fd9532, 0xea0abf73600434f8, 0x42fc8f75299309f3, 0x34a9cf7d3eb1ae1c, - 0x2b838811480723ba, 0x5ce64c8742ceef24, 0x1adae9b01fd6570e, 0x3c349bf9d6bad1b3, 0x82453c891c7b75c0, - 0x97923a40b80d512b, 0x4a61dbf1c198765c, 0xb48ce6d518010d3e, 0xcfb45c858e480fd6, 0xd933cbf30d1e96ae, - 0xd70ea014ab558e3a, 0xc189376228031742, 0x9262949cd16d8b83, 0xeb3a3bed7def5f89, 0x49314a4ee6b8cbcf, - 0xdcc3652f647e4c06, 0xda635a4c2a3e2b3d, 0x470c21a940f3d35b, 0x315961a157d174b4, 0x6672e81dda3459ac, - 0x5b76f77a1165e36e, 0x445cb01667d36ec8, 0xc5491d205c88a69b, 0x456c34887a3805b9, 0xffddb9bac4721013, - 0x99af51a71e4649bf, 0xa15be01cbc7729d5, 0x52db2760e485f7b0, 0x8c78576eba306d54, 0xae560f6507d75a30, - 0x95f22f6182c687c9, 0x71c5fbf54489aba5, 0xca44f259e728d57e, 0x88b87d2ccebbdc8d, 0xbab18d32be4a15aa, - 0x8be8ec93e99b611e, 0x17b713e89ebdf209, 0xb31c5d284baa0174, 0xeeca9531148f8521, 0xb8d198138481c348, - 0x8988f9b2d350b7fc, 0xb9e11c8d996aa839, 0x5a4673e40c8e881f, 0x1687977683569978, 0xbf4123eed72acf02, - 0x4ea1f1b3b513c785, 0xe767452be16f91ff, 0x7505d1b730021a7c, 0xa59bca5ec8fc980c, 0xad069eda20f7e7a3, - 0x38f4b1bba231606a, 0x60d2d77e94743e97, 0x9affc0183966f42c, 0x248e6768f3a7505f, 0xcdd449a4b483d934, - 0x87b59255751baf68, 0x1bea6d2e023d3c7f, 0x6b1f12455b5ffcab, 0x743555292de9710d, 0xd8034f6d10f5fddf, - 0xc6198c9f7ba81b08, 0xbb8109aca3a17edb, 0xfa2d1766ad12cabb, 0xc729080166437079, 0x9c5fff7b77269317, - 0x0000000000000000, 0x15d706c9a47624eb, 0x6fdf38072fd44d72, 0x5fb6dd3865ee52b7, 0xa33bf53d86bcff37, - 0xe657c1b5fc84fa8e, 0xaa962527735cebe9, 0x39c43525bfda0b1b, 0x204e4d2a872ce186, 0x7a083ece8ba26999, - 0x554b9c9db72efbfa, 0xb22cd9b656416a05, 0x96a2bedea5e63a5a, 0x802529a826b0a322, 0x8115ad363b5bc853, - 0x8375b81701901eb1, 0x3069e53f4a3a1fc5, 0xbd2136cfede119e0, 0x18bafc91251d81ec, 0x1d4a524d4c7d5b44, - 0x05f0aedc6960daa8, 0x29e39d3072ccf558, 0x70f57f6b5962c0d4, 0x989fd53903ad22ce, 0xf84d024797d91c59, - 0x547b1803aac5908b, 0xf0d056c37fd263f6, 0xd56eb535919e58d8, 0x1c7ad6d351963035, 0x2e7326cd2167f912, - 0xac361a443d1c8cd2, 0x697f076461942a49, 0x4b515f6fdc731d2d, 0x8ad8680df4700a6f, 0x41ac1eca0eb3b460, - 0x7d988533d80965d3, 0xa8f6300649973d0b, 0x7765c4960ac9cc9e, 0x7ca801adc5e20ea2, 0xdea3700e5eb59ae4, - 0xa06b6482a19c42a4, 0x6a2f96db46b497da, 0x27def6d7d487edcc, 0x463ca5375d18b82a, 0xa6cb5be1efdc259f, - 0x53eba3fef96e9cc1, 0xce84d81b93a364a7, 0xf4107c810b59d22f, 0x333974806d1aa256, 0x0f0def79bba073e5, - 0x231edc95a00c5c15, 0xe437d494c64f2c6c, 0x91320523f64d3610, 0x67426c83c7df32dd, 0x6eefbc99323f2603, - 0x9d6f7be56acdf866, 0x5916e25b2bae358c, 0x7ff89012e2c2b331, 0x035091bf2720bd93, 0x561b0d22900e4669, - 0x28d319ae6f279e29, 0x2f43a2533c8c9263, 0xd09e1be9f8fe8270, 0xf740ed3e2c796fbc, 0xdb53ded237d5404c, - 0x62b2c25faebfe875, 0x0afd41a5d2c0a94d, 0x6412fd3ce0ff8f4e, 0xe3a76f6995e42026, 0x6c8fa9b808f4f0e1, - 0xc2d9a6dd0f23aad1, 0x8f28c6d19d10d0c7, 0x85d587744fd0798a, 0xa20b71a39b579446, 0x684f83fa7c7f4138, - 0xe507500adba4471d, 0x3f640a46f19a6c20, 0x1247bd34f7dd28a1, 0x2d23b77206474481, 0x93521002cc86e0f2, - 0x572b89bc8de52d18, 0xfb1d93f8b0f9a1ca, 0xe95a2ecc4724896b, 0x3ba420048511ddf9, 0xd63e248ab6bee54b, - 0x5dd6c8195f258455, 0x06a03f634e40673b, 0x1f2a476c76b68da6, 0x217ec9b49ac78af7, 0xecaa80102e4453c3, - 0x14e78257b99d4f9a}, - {0x20329b2cc87bba05, 0x4f5eb6f86546a531, 0xd4f44775f751b6b1, 0x8266a47b850dfa8b, 0xbb986aa15a6ca985, - 0xc979eb08f9ae0f99, 0x2da6f447a2375ea1, 0x1e74275dcd7d8576, 0xbc20180a800bc5f8, 0xb4a2f701b2dc65be, - 0xe726946f981b6d66, 0x48e6c453bf21c94c, 0x42cad9930f0a4195, 0xefa47b64aacccd20, 0x71180a8960409a42, - 0x8bb3329bf6a44e0c, 0xd34c35de2d36dacc, 0xa92f5b7cbc23dc96, 0xb31a85aa68bb09c3, 0x13e04836a73161d2, - 0xb24dfc4129c51d02, 0x8ae44b70b7da5acd, 0xe671ed84d96579a7, 0xa4bb3417d66f3832, 0x4572ab38d56d2de8, - 0xb1b47761ea47215c, 0xe81c09cf70aba15d, 0xffbdb872ce7f90ac, 0xa8782297fd5dc857, 0x0d946f6b6a4ce4a4, - 0xe4df1f4f5b995138, 0x9ebc71edca8c5762, 0x0a2c1dc0b02b88d9, 0x3b503c115d9d7b91, 0xc64376a8111ec3a2, - 0xcec199a323c963e4, 0xdc76a87ec58616f7, 0x09d596e073a9b487, 0x14583a9d7d560daf, 0xf4c6dc593f2a0cb4, - 0xdd21d19584f80236, 0x4a4836983ddde1d3, 0xe58866a41ae745f9, 0xf591a5b27e541875, 0x891dc05074586693, - 0x5b068c651810a89e, 0xa30346bc0c08544f, 0x3dbf3751c684032d, 0x2a1e86ec785032dc, 0xf73f5779fca830ea, - 0xb60c05ca30204d21, 0x0cc316802b32f065, 0x8770241bdd96be69, 0xb861e18199ee95db, 0xf805cad91418fcd1, - 0x29e70dccbbd20e82, 0xc7140f435060d763, 0x0f3a9da0e8b0cc3b, 0xa2543f574d76408e, 0xbd7761e1c175d139, - 0x4b1f4f737ca3f512, 0x6dc2df1f2fc137ab, 0xf1d05c3967b14856, 0xa742bf3715ed046c, 0x654030141d1697ed, - 0x07b872abda676c7d, 0x3ce84eba87fa17ec, 0xc1fb0403cb79afdf, 0x3e46bc7105063f73, 0x278ae987121cd678, - 0xa1adb4778ef47cd0, 0x26dd906c5362c2b9, 0x05168060589b44e2, 0xfbfc41f9d79ac08f, 0x0e6de44ba9ced8fa, - 0x9feb08068bf243a3, 0x7b341749d06b129b, 0x229c69e74a87929a, 0xe09ee6c4427c011b, 0x5692e30e725c4c3a, - 0xda99a33e5e9f6e4b, 0x353dd85af453a36b, 0x25241b4c90e0fee7, 0x5de987258309d022, 0xe230140fc0802984, - 0x93281e86a0c0b3c6, 0xf229d719a4337408, 0x6f6c2dd4ad3d1f34, 0x8ea5b2fbae3f0aee, 0x8331dd90c473ee4a, - 0x346aa1b1b52db7aa, 0xdf8f235e06042aa9, 0xcc6f6b68a1354b7b, 0x6c95a6f46ebf236a, 0x52d31a856bb91c19, - 0x1a35ded6d498d555, 0xf37eaef2e54d60c9, 0x72e181a9a3c2a61c, 0x98537aad51952fde, 0x16f6c856ffaa2530, - 0xd960281e9d1d5215, 0x3a0745fa1ce36f50, 0x0b7b642bf1559c18, 0x59a87eae9aec8001, 0x5e100c05408bec7c, - 0x0441f98b19e55023, 0xd70dcc5534d38aef, 0x927f676de1bea707, 0x9769e70db925e3e5, 0x7a636ea29115065a, - 0x468b201816ef11b6, 0xab81a9b73edff409, 0xc0ac7de88a07bb1e, 0x1f235eb68c0391b7, 0x6056b074458dd30f, - 0xbe8eeac102f7ed67, 0xcd381283e04b5fba, 0x5cbefecec277c4e3, 0xd21b4c356c48ce0d, 0x1019c31664b35d8c, - 0x247362a7d19eea26, 0xebe582efb3299d03, 0x02aef2cb82fc289f, 0x86275df09ce8aaa8, 0x28b07427faac1a43, - 0x38a9b7319e1f47cf, 0xc82e92e3b8d01b58, 0x06ef0b409b1978bc, 0x62f842bfc771fb90, 0x9904034610eb3b1f, - 0xded85ab5477a3e68, 0x90d195a663428f98, 0x5384636e2ac708d8, 0xcbd719c37b522706, 0xae9729d76644b0eb, - 0x7c8c65e20a0c7ee6, 0x80c856b007f1d214, 0x8c0b40302cc32271, 0xdbcedad51fe17a8a, 0x740e8ae938dbdea0, - 0xa615c6dc549310ad, 0x19cc55f6171ae90b, 0x49b1bdb8fe5fdd8d, 0xed0a89af2830e5bf, 0x6a7aadb4f5a65bd6, - 0x7e22972988f05679, 0xf952b3325566e810, 0x39fecedadf61530e, 0x6101c99f04f3c7ce, 0x2e5f7f6761b562ff, - 0xf08725d226cf5c97, 0x63af3b54860fef51, 0x8ff2cb10ef411e2f, 0x884ab9bb35267252, 0x4df04433e7ba8dae, - 0x9afd8866d3690741, 0x66b9bb34de94abb3, 0x9baaf18d92171380, 0x543c11c5f0a064a5, 0x17a1b1bdbed431f1, - 0xb5f58eeaf3a2717f, 0xc355f6c849858740, 0xec5df044694ef17e, 0xd83751f5dc6346d4, 0xfc4433520dfdacf2, - 0x0000000000000000, 0x5a51f58e596ebc5f, 0x3285aaf12e34cf16, 0x8d5c39db6dbd36b0, 0x12b731dde64f7513, - 0x94906c2d7aa7dfbb, 0x302b583aacc8e789, 0x9d45facd090e6b3c, 0x2165e2c78905aec4, 0x68d45f7f775a7349, - 0x189b2c1d5664fdca, 0xe1c99f2f030215da, 0x6983269436246788, 0x8489af3b1e148237, 0xe94b702431d5b59c, - 0x33d2d31a6f4adbd7, 0xbfd9932a4389f9a6, 0xb0e30e8aab39359d, 0xd1e2c715afcaf253, 0x150f43763c28196e, - 0xc4ed846393e2eb3d, 0x03f98b20c3823c5e, 0xfd134ab94c83b833, 0x556b682eb1de7064, 0x36c4537a37d19f35, - 0x7559f30279a5ca61, 0x799ae58252973a04, 0x9c12832648707ffd, 0x78cd9c6913e92ec5, 0x1d8dac7d0effb928, - 0x439da0784e745554, 0x413352b3cc887dcb, 0xbacf134a1b12bd44, 0x114ebafd25cd494d, 0x2f08068c20cb763e, - 0x76a07822ba27f63f, 0xeab2fb04f25789c2, 0xe3676de481fe3d45, 0x1b62a73d95e6c194, 0x641749ff5c68832c, - 0xa5ec4dfc97112cf3, 0xf6682e92bdd6242b, 0x3f11c59a44782bb2, 0x317c21d1edb6f348, 0xd65ab5be75ad9e2e, - 0x6b2dd45fb4d84f17, 0xfaab381296e4d44e, 0xd0b5befeeeb4e692, 0x0882ef0b32d7a046, 0x512a91a5a83b2047, - 0x963e9ee6f85bf724, 0x4e09cf132438b1f0, 0x77f701c9fb59e2fe, 0x7ddb1c094b726a27, 0x5f4775ee01f5f8bd, - 0x9186ec4d223c9b59, 0xfeeac1998f01846d, 0xac39db1ce4b89874, 0xb75b7c21715e59e0, 0xafc0503c273aa42a, - 0x6e3b543fec430bf5, 0x704f7362213e8e83, 0x58ff0745db9294c0, 0x67eec2df9feabf72, 0xa0facd9ccf8a6811, - 0xb936986ad890811a, 0x95c715c63bd9cb7a, 0xca8060283a2c33c7, 0x507de84ee9453486, 0x85ded6d05f6a96f6, - 0x1cdad5964f81ade9, 0xd5a33e9eb62fa270, 0x40642b588df6690a, 0x7f75eec2c98e42b8, 0x2cf18dace3494a60, - 0x23cb100c0bf9865b, 0xeef3028febb2d9e1, 0x4425d2d394133929, 0xaad6d05c7fa1e0c8, 0xad6ea2f7a5c68cb5, - 0xc2028f2308fb9381, 0x819f2f5b468fc6d5, 0xc5bafd88d29cfffc, 0x47dc59f357910577, 0x2b49ff07392e261d, - 0x57c59ae5332258fb, 0x73b6f842e2bcb2dd, 0xcf96e04862b77725, 0x4ca73dd8a6c4996f, 0x015779eb417e14c1, - 0x37932a9176af8bf4}, - {0x190a2c9b249df23e, 0x2f62f8b62263e1e9, 0x7a7f754740993655, 0x330b7ba4d5564d9f, 0x4c17a16a46672582, - 0xb22f08eb7d05f5b8, 0x535f47f40bc148cc, 0x3aec5d27d4883037, 0x10ed0a1825438f96, 0x516101f72c233d17, - 0x13cc6f949fd04eae, 0x739853c441474bfd, 0x653793d90d3f5b1b, 0x5240647b96b0fc2f, 0x0c84890ad27623e0, - 0xd7189b32703aaea3, 0x2685de3523bd9c41, 0x99317c5b11bffefa, 0x0d9baa854f079703, 0x70b93648fbd48ac5, - 0xa80441fce30bc6be, 0x7287704bdc36ff1e, 0xb65384ed33dc1f13, 0xd36417343ee34408, 0x39cd38ab6e1bf10f, - 0x5ab861770a1f3564, 0x0ebacf09f594563b, 0xd04572b884708530, 0x3cae9722bdb3af47, 0x4a556b6f2f5cbaf2, - 0xe1704f1f76c4bd74, 0x5ec4ed7144c6dfcf, 0x16afc01d4c7810e6, 0x283f113cd629ca7a, 0xaf59a8761741ed2d, - 0xeed5a3991e215fac, 0x3bf37ea849f984d4, 0xe413e096a56ce33c, 0x2c439d3a98f020d1, 0x637559dc6404c46b, - 0x9e6c95d1e5f5d569, 0x24bb9836045fe99a, 0x44efa466dac8ecc9, 0xc6eab2a5c80895d6, 0x803b50c035220cc4, - 0x0321658cba93c138, 0x8f9ebc465dc7ee1c, 0xd15a5137190131d3, 0x0fa5ec8668e5e2d8, 0x91c979578d1037b1, - 0x0642ca05693b9f70, 0xefca80168350eb4f, 0x38d21b24f36a45ec, 0xbeab81e1af73d658, 0x8cbfd9cae7542f24, - 0xfd19cc0d81f11102, 0x0ac6430fbb4dbc90, 0x1d76a09d6a441895, 0x2a01573ff1cbbfa1, 0xb572e161894fde2b, - 0x8124734fa853b827, 0x614b1fdf43e6b1b0, 0x68ac395c4238cc18, 0x21d837bfd7f7b7d2, 0x20c714304a860331, - 0x5cfaab726324aa14, 0x74c5ba4eb50d606e, 0xf3a3030474654739, 0x23e671bcf015c209, 0x45f087e947b9582a, - 0xd8bd77b418df4c7b, 0xe06f6c90ebb50997, 0x0bd96080263c0873, 0x7e03f9410e40dcfe, 0xb8e94be4c6484928, - 0xfb5b0608e8ca8e72, 0x1a2b49179e0e3306, 0x4e29e76961855059, 0x4f36c4e6fcf4e4ba, 0x49740ee395cf7bca, - 0xc2963ea386d17f7d, 0x90d65ad810618352, 0x12d34c1b02a1fa4d, 0xfa44258775bb3a91, 0x18150f14b9ec46dd, - 0x1491861e6b9a653d, 0x9a1019d7ab2c3fc2, 0x3668d42d06fe13d7, 0xdcc1fbb25606a6d0, 0x969490dd795a1c22, - 0x3549b1a1bc6dd2ef, 0xc94f5e23a0ed770e, 0xb9f6686b5b39fdcb, 0xc4d4f4a6efeae00d, 0xe732851a1fff2204, - 0x94aad6de5eb869f9, 0x3f8ff2ae07206e7f, 0xfe38a9813b62d03a, 0xa7a1ad7a8bee2466, 0x7b6056c8dde882b6, - 0x302a1e286fc58ca7, 0x8da0fa457a259bc7, 0xb3302b64e074415b, 0x5402ae7eff8b635f, 0x08f8050c9cafc94b, - 0xae468bf98a3059ce, 0x88c355cca98dc58f, 0xb10e6d67c7963480, 0xbad70de7e1aa3cf3, 0xbfb4a26e320262bb, - 0xcb711820870f02d5, 0xce12b7a954a75c9d, 0x563ce87dd8691684, 0x9f73b65e7884618a, 0x2b1e74b06cba0b42, - 0x47cec1ea605b2df1, 0x1c698312f735ac76, 0x5fdbcefed9b76b2c, 0x831a354c8fb1cdfc, 0x820516c312c0791f, - 0xb74ca762aeadabf0, 0xfc06ef821c80a5e1, 0x5723cbf24518a267, 0x9d4df05d5f661451, 0x588627742dfd40bf, - 0xda8331b73f3d39a0, 0x17b0e392d109a405, 0xf965400bcf28fba9, 0x7c3dbf4229a2a925, 0x023e460327e275db, - 0x6cd0b55a0ce126b3, 0xe62da695828e96e7, 0x42ad6e63b3f373b9, 0xe50cc319381d57df, 0xc5cbd729729b54ee, - 0x46d1e265fd2a9912, 0x6428b056904eeff8, 0x8be23040131e04b7, 0x6709d5da2add2ec0, 0x075de98af44a2b93, - 0x8447dcc67bfbe66f, 0x6616f655b7ac9a23, 0xd607b8bded4b1a40, 0x0563af89d3a85e48, 0x3db1b4ad20c21ba4, - 0x11f22997b8323b75, 0x292032b34b587e99, 0x7f1cdace9331681d, 0x8e819fc9c0b65aff, 0xa1e3677fe2d5bb16, - 0xcd33d225ee349da5, 0xd9a2543b85aef898, 0x795e10cbfa0af76d, 0x25a4bbb9992e5d79, 0x78413344677b438e, - 0xf0826688cef68601, 0xd27b34bba392f0eb, 0x551d8df162fad7bc, 0x1e57c511d0d7d9ad, 0xdeffbdb171e4d30b, - 0xf4feea8e802f6caa, 0xa480c8f6317de55e, 0xa0fc44f07fa40ff5, 0x95b5f551c3c9dd1a, 0x22f952336d6476ea, - 0x0000000000000000, 0xa6be8ef5169f9085, 0xcc2cf1aa73452946, 0x2e7ddb39bf12550a, 0xd526dd3157d8db78, - 0x486b2d6c08becf29, 0x9b0f3a58365d8b21, 0xac78cdfaadd22c15, 0xbc95c7e28891a383, 0x6a927f5f65dab9c3, - 0xc3891d2c1ba0cb9e, 0xeaa92f9f50f8b507, 0xcf0d9426c9d6e87e, 0xca6e3baf1a7eb636, 0xab25247059980786, - 0x69b31ad3df4978fb, 0xe2512a93cc577c4c, 0xff278a0ea61364d9, 0x71a615c766a53e26, 0x89dc764334fc716c, - 0xf87a638452594f4a, 0xf2bc208be914f3da, 0x8766b94ac1682757, 0xbbc82e687cdb8810, 0x626a7a53f9757088, - 0xa2c202f358467a2e, 0x4d0882e5db169161, 0x09e7268301de7da8, 0xe897699c771ac0dc, 0xc8507dac3d9cc3ed, - 0xc0a878a0a1330aa6, 0x978bb352e42ba8c1, 0xe9884a13ea6b743f, 0x279afdbabecc28a2, 0x047c8c064ed9eaab, - 0x507e2278b15289f4, 0x599904fbb08cf45c, 0xbd8ae46d15e01760, 0x31353da7f2b43844, 0x8558ff49e68a528c, - 0x76fbfc4d92ef15b5, 0x3456922e211c660c, 0x86799ac55c1993b4, 0x3e90d1219a51da9c, 0x2d5cbeb505819432, - 0x982e5fd48cce4a19, 0xdb9c1238a24c8d43, 0xd439febecaa96f9b, 0x418c0bef0960b281, 0x158ea591f6ebd1de, - 0x1f48e69e4da66d4e, 0x8afd13cf8e6fb054, 0xf5e1c9011d5ed849, 0xe34e091c5126c8af, 0xad67ee7530a398f6, - 0x43b24dec2e82c75a, 0x75da99c1287cd48d, 0x92e81cdb3783f689, 0xa3dd217cc537cecd, 0x60543c50de970553, - 0x93f73f54aaf2426a, 0xa91b62737e7a725d, 0xf19d4507538732e2, 0x77e4dfc20f9ea156, 0x7d229ccdb4d31dc6, - 0x1b346a98037f87e5, 0xedf4c615a4b29e94, 0x4093286094110662, 0xb0114ee85ae78063, 0x6ff1d0d6b672e78b, - 0x6dcf96d591909250, 0xdfe09e3eec9567e8, 0x3214582b4827f97c, 0xb46dc2ee143e6ac8, 0xf6c0ac8da7cd1971, - 0xebb60c10cd8901e4, 0xf7df8f023abcad92, 0x9c52d3d2c217a0b2, 0x6b8d5cd0f8ab0d20, 0x3777f7a29b8fa734, - 0x011f238f9d71b4e3, 0xc1b75b2f3c42be45, 0x5de588fdfe551ef7, 0x6eeef3592b035368, 0xaa3a07ffc4e9b365, - 0xecebe59a39c32a77, 0x5ba742f8976e8187, 0x4b4a48e0b22d0e11, 0xddded83dcb771233, 0xa59feb79ac0c51bd, - 0xc7f5912a55792135}, - {0x6d6ae04668a9b08a, 0x3ab3f04b0be8c743, 0xe51e166b54b3c908, 0xbe90a9eb35c2f139, 0xb2c7066637f2bec1, - 0xaa6945613392202c, 0x9a28c36f3b5201eb, 0xddce5a93ab536994, 0x0e34133ef6382827, 0x52a02ba1ec55048b, - 0xa2f88f97c4b2a177, 0x8640e513ca2251a5, 0xcdf1d36258137622, 0xfe6cb708dedf8ddb, 0x8a174a9ec8121e5d, - 0x679896036b81560e, 0x59ed033395795fee, 0x1dd778ab8b74edaf, 0xee533ef92d9f926d, 0x2a8c79baf8a8d8f5, - 0x6bcf398e69b119f6, 0xe20491742fafdd95, 0x276488e0809c2aec, 0xea955b82d88f5cce, 0x7102c63a99d9e0c4, - 0xf9763017a5c39946, 0x429fa2501f151b3d, 0x4659c72bea05d59e, 0x984b7fdccf5a6634, 0xf742232953fbb161, - 0x3041860e08c021c7, 0x747bfd9616cd9386, 0x4bb1367192312787, 0x1b72a1638a6c44d3, 0x4a0e68a6e8359a66, - 0x169a5039f258b6ca, 0xb98a2ef44edee5a4, 0xd9083fe85e43a737, 0x967f6ce239624e13, 0x8874f62d3c1a7982, - 0x3c1629830af06e3f, 0x9165ebfd427e5a8e, 0xb5dd81794ceeaa5c, 0x0de8f15a7834f219, 0x70bd98ede3dd5d25, - 0xaccc9ca9328a8950, 0x56664eda1945ca28, 0x221db34c0f8859ae, 0x26dbd637fa98970d, 0x1acdffb4f068f932, - 0x4585254f64090fa0, 0x72de245e17d53afa, 0x1546b25d7c546cf4, 0x207e0ffffb803e71, 0xfaaad2732bcf4378, - 0xb462dfae36ea17bd, 0xcf926fd1ac1b11fd, 0xe0672dc7dba7ba4a, 0xd3fa49ad5d6b41b3, 0x8ba81449b216a3bc, - 0x14f9ec8a0650d115, 0x40fc1ee3eb1d7ce2, 0x23a2ed9b758ce44f, 0x782c521b14fddc7e, 0x1c68267cf170504e, - 0xbcf31558c1ca96e6, 0xa781b43b4ba6d235, 0xf6fd7dfe29ff0c80, 0xb0a4bad5c3fad91e, 0xd199f51ea963266c, - 0x414340349119c103, 0x5405f269ed4dadf7, 0xabd61bb649969dcd, 0x6813dbeae7bdc3c8, 0x65fb2ab09f8931d1, - 0xf1e7fae152e3181d, 0xc1a67cef5a2339da, 0x7a4feea8e0f5bba1, 0x1e0b9acf05783791, 0x5b8ebf8061713831, - 0x80e53cdbcb3af8d9, 0x7e898bd315e57502, 0xc6bcfbf0213f2d47, 0x95a38e86b76e942d, 0x092e94218d243cba, - 0x8339debf453622e7, 0xb11be402b9fe64ff, 0x57d9100d634177c9, 0xcc4e8db52217cbc3, 0x3b0cae9c71ec7aa2, - 0xfb158ca451cbfe99, 0x2b33276d82ac6514, 0x01bf5ed77a04bde1, 0xc5601994af33f779, 0x75c4a3416cc92e67, - 0xf3844652a6eb7fc2, 0x3487e375fdd0ef64, 0x18ae430704609eed, 0x4d14efb993298efb, 0x815a620cb13e4538, - 0x125c354207487869, 0x9eeea614ce42cf48, 0xce2d3106d61fac1c, 0xbbe99247bad6827b, 0x071a871f7b1c149d, - 0x2e4a1cc10db81656, 0x77a71ff298c149b8, 0x06a5d9c80118a97c, 0xad73c27e488e34b1, 0x443a7b981e0db241, - 0xe3bbcfa355ab6074, 0x0af276450328e684, 0x73617a896dd1871b, 0x58525de4ef7de20f, 0xb7be3dcab8e6cd83, - 0x19111dd07e64230c, 0x842359a03e2a367a, 0x103f89f1f3401fb6, 0xdc710444d157d475, 0xb835702334da5845, - 0x4320fc876511a6dc, 0xd026abc9d3679b8d, 0x17250eee885c0b2b, 0x90dab52a387ae76f, 0x31fed8d972c49c26, - 0x89cba8fa461ec463, 0x2ff5421677bcabb7, 0x396f122f85e41d7d, 0xa09b332430bac6a8, 0xc888e8ced7070560, - 0xaeaf201ac682ee8f, 0x1180d7268944a257, 0xf058a43628e7a5fc, 0xbd4c4b8fbbce2b07, 0xa1246df34abe7b49, - 0x7d5569b79be9af3c, 0xa9b5a705bd9efa12, 0xdb6b835baa4bc0e8, 0x05793bac8f147342, 0x21c1512881848390, - 0xfdb0556c50d357e5, 0x613d4fcb6a99ff72, 0x03dce2648e0cda3e, 0xe949b9e6568386f0, 0xfc0f0bbb2ad7ea04, - 0x6a70675913b5a417, 0x7f36d5046fe1c8e3, 0x0c57af8d02304ff8, 0x32223abdfcc84618, 0x0891caf6f720815b, - 0xa63eeaec31a26fd4, 0x2507345374944d33, 0x49d28ac266394058, 0xf5219f9aa7f3d6be, 0x2d96fea583b4cc68, - 0x5a31e1571b7585d0, 0x8ed12fe53d02d0fe, 0xdfade6205f5b0e4b, 0x4cabb16ee92d331a, 0x04c6657bf510cea3, - 0xd73c2cd6a87b8f10, 0xe1d87310a1a307ab, 0x6cd5be9112ad0d6b, 0x97c032354366f3f2, 0xd4e0ceb22677552e, - 0x0000000000000000, 0x29509bde76a402cb, 0xc27a9e8bd42fe3e4, 0x5ef7842cee654b73, 0xaf107ecdbc86536e, - 0x3fcacbe784fcb401, 0xd55f90655c73e8cf, 0xe6c2f40fdabf1336, 0xe8f6e7312c873b11, 0xeb2a0555a28be12f, - 0xe4a148bc2eb774e9, 0x9b979db84156bc0a, 0x6eb60222e6a56ab4, 0x87ffbbc4b026ec44, 0xc703a5275b3b90a6, - 0x47e699fc9001687f, 0x9c8d1aa73a4aa897, 0x7cea3760e1ed12dd, 0x4ec80ddd1d2554c5, 0x13e36b957d4cc588, - 0x5d2b66486069914d, 0x92b90999cc7280b0, 0x517cc9c56259deb5, 0xc937b619ad03b881, 0xec30824ad997f5b2, - 0xa45d565fc5aa080b, 0xd6837201d27f32f1, 0x635ef3789e9198ad, 0x531f75769651b96a, 0x4f77530a6721e924, - 0x486dd4151c3dfdb9, 0x5f48dafb9461f692, 0x375b011173dc355a, 0x3da9775470f4d3de, 0x8d0dcd81b30e0ac0, - 0x36e45fc609d888bb, 0x55baacbe97491016, 0x8cb29356c90ab721, 0x76184125e2c5f459, 0x99f4210bb55edbd5, - 0x6f095cf59ca1d755, 0x9f51f8c3b44672a9, 0x3538bda287d45285, 0x50c39712185d6354, 0xf23b1885dcefc223, - 0x79930ccc6ef9619f, 0xed8fdc9da3934853, 0xcb540aaa590bdf5e, 0x5c94389f1a6d2cac, 0xe77daad8a0bbaed7, - 0x28efc5090ca0bf2a, 0xbf2ff73c4fc64cd8, 0xb37858b14df60320, 0xf8c96ec0dfc724a7, 0x828680683f329f06, - 0x941cd051cd6a29cc, 0xc3c5c05cae2b5e05, 0xb601631dc2e27062, 0xc01922382027843b, 0x24b86a840e90f0d2, - 0xd245177a276ffc52, 0x0f8b4de98c3c95c6, 0x3e759530fef809e0, 0x0b4d2892792c5b65, 0xc4df4743d5374a98, - 0xa5e20888bfaeb5ea, 0xba56cc90c0d23f9a, 0x38d04cf8ffe0a09c, 0x62e1adafe495254c, 0x0263bcb3f40867df, - 0xcaeb547d230f62bf, 0x6082111c109d4293, 0xdad4dd8cd04f7d09, 0xefec602e579b2f8c, 0x1fb4c4187f7c8a70, - 0xffd3e9dfa4db303a, 0x7bf0b07f9af10640, 0xf49ec14dddf76b5f, 0x8f6e713247066d1f, 0x339d646a86ccfbf9, - 0x64447467e58d8c30, 0x2c29a072f9b07189, 0xd8b7613f24471ad6, 0x6627c8d41185ebef, 0xa347d140beb61c96, - 0xde12b8f7255fb3aa, 0x9d324470404e1576, 0x9306574eb6763d51, 0xa80af9d2c79a47f3, 0x859c0777442e8b9b, - 0x69ac853d9db97e29}, - {0xc3407dfc2de6377e, 0x5b9e93eea4256f77, 0xadb58fdd50c845e0, 0x5219ff11a75bed86, 0x356b61cfd90b1de9, - 0xfb8f406e25abe037, 0x7a5a0231c0f60796, 0x9d3cd216e1f5020b, 0x0c6550fb6b48d8f3, 0xf57508c427ff1c62, - 0x4ad35ffa71cb407d, 0x6290a2da1666aa6d, 0xe284ec2349355f9f, 0xb3c307c53d7c84ec, 0x05e23c0468365a02, - 0x190bac4d6c9ebfa8, 0x94bbbee9e28b80fa, 0xa34fc777529cb9b5, 0xcc7b39f095bcd978, 0x2426addb0ce532e3, - 0x7e79329312ce4fc7, 0xab09a72eebec2917, 0xf8d15499f6b9d6c2, 0x1a55b8babf8c895d, 0xdb8add17fb769a85, - 0xb57f2f368658e81b, 0x8acd36f18f3f41f6, 0x5ce3b7bba50f11d3, 0x114dcc14d5ee2f0a, 0xb91a7fcded1030e8, - 0x81d5425fe55de7a1, 0xb6213bc1554adeee, 0x80144ef95f53f5f2, 0x1e7688186db4c10c, 0x3b912965db5fe1bc, - 0xc281715a97e8252d, 0x54a5d7e21c7f8171, 0x4b12535ccbc5522e, 0x1d289cefbea6f7f9, 0x6ef5f2217d2e729e, - 0xe6a7dc819b0d17ce, 0x1b94b41c05829b0e, 0x33d7493c622f711e, 0xdcf7f942fa5ce421, 0x600fba8b7f7a8ecb, - 0x46b60f011a83988e, 0x235b898e0dcf4c47, 0x957ab24f588592a9, 0x4354330572b5c28c, 0xa5f3ef84e9b8d542, - 0x8c711e02341b2d01, 0x0b1874ae6a62a657, 0x1213d8e306fc19ff, 0xfe6d7c6a4d9dba35, 0x65ed868f174cd4c9, - 0x88522ea0e6236550, 0x899322065c2d7703, 0xc01e690bfef4018b, 0x915982ed8abddaf8, 0xbe675b98ec3a4e4c, - 0xa996bf7f82f00db1, 0xe1daf8d49a27696a, 0x2effd5d3dc8986e7, 0xd153a51f2b1a2e81, 0x18caa0ebd690adfb, - 0x390e3134b243c51a, 0x2778b92cdff70416, 0x029f1851691c24a6, 0x5e7cafeacc133575, 0xfa4e4cc89fa5f264, - 0x5a5f9f481e2b7d24, 0x484c47ab18d764db, 0x400a27f2a1a7f479, 0xaeeb9b2a83da7315, 0x721c626879869734, - 0x042330a2d2384851, 0x85f672fd3765aff0, 0xba446b3a3e02061d, 0x73dd6ecec3888567, 0xffac70ccf793a866, - 0xdfa9edb5294ed2d4, 0x6c6aea7014325638, 0x834a5a0e8c41c307, 0xcdba35562fb2cb2b, 0x0ad97808d06cb404, - 0x0f3b440cb85aee06, 0xe5f9c876481f213b, 0x98deee1289c35809, 0x59018bbfcd394bd1, 0xe01bf47220297b39, - 0xde68e1139340c087, 0x9fa3ca4788e926ad, 0xbb85679c840c144e, 0x53d8f3b71d55ffd5, 0x0da45c5dd146caa0, - 0x6f34fe87c72060cd, 0x57fbc315cf6db784, 0xcee421a1fca0fdde, 0x3d2d0196607b8d4b, 0x642c8a29ad42c69a, - 0x14aff010bdd87508, 0xac74837beac657b3, 0x3216459ad821634d, 0x3fb219c70967a9ed, 0x06bc28f3bb246cf7, - 0xf2082c9126d562c6, 0x66b39278c45ee23c, 0xbd394f6f3f2878b9, 0xfd33689d9e8f8cc0, 0x37f4799eb017394f, - 0x108cc0b26fe03d59, 0xda4bd1b1417888d6, 0xb09d1332ee6eb219, 0x2f3ed975668794b4, 0x58c0871977375982, - 0x7561463d78ace990, 0x09876cff037e82f1, 0x7fb83e35a8c05d94, 0x26b9b58a65f91645, 0xef20b07e9873953f, - 0x3148516d0b3355b8, 0x41cb2b541ba9e62a, 0x790416c613e43163, 0xa011d380818e8f40, 0x3a5025c36151f3ef, - 0xd57095bdf92266d0, 0x498d4b0da2d97688, 0x8b0c3a57353153a5, 0x21c491df64d368e1, 0x8f2f0af5e7091bf4, - 0x2da1c1240f9bb012, 0xc43d59a92ccc49da, 0xbfa6573e56345c1f, 0x828b56a8364fd154, 0x9a41f643e0df7caf, - 0xbcf843c985266aea, 0x2b1de9d7b4bfdce5, 0x20059d79dedd7ab2, 0x6dabe6d6ae3c446b, 0x45e81bf6c991ae7b, - 0x6351ae7cac68b83e, 0xa432e32253b6c711, 0xd092a9b991143cd2, 0xcac711032e98b58f, 0xd8d4c9e02864ac70, - 0xc5fc550f96c25b89, 0xd7ef8dec903e4276, 0x67729ede7e50f06f, 0xeac28c7af045cf3d, 0xb15c1f945460a04a, - 0x9cfddeb05bfb1058, 0x93c69abce3a1fe5e, 0xeb0380dc4a4bdd6e, 0xd20db1e8f8081874, 0x229a8528b7c15e14, - 0x44291750739fbc28, 0xd3ccbd4e42060a27, 0xf62b1c33f4ed2a97, 0x86a8660ae4779905, 0xd62e814a2a305025, - 0x477703a7a08d8add, 0x7b9b0e977af815c5, 0x78c51a60a9ea2330, 0xa6adfb733aaae3b7, 0x97e5aa1e3199b60f, - 0x0000000000000000, 0xf4b404629df10e31, 0x5564db44a6719322, 0x9207961a59afec0d, 0x9624a6b88b97a45c, - 0x363575380a192b1c, 0x2c60cd82b595a241, 0x7d272664c1dc7932, 0x7142769faa94a1c1, 0xa1d0df263b809d13, - 0x1630e841d4c451ae, 0xc1df65ad44fa13d8, 0x13d2d445bcf20bac, 0xd915c546926abe23, 0x38cf3d92084dd749, - 0xe766d0272103059d, 0xc7634d5effde7f2f, 0x077d2455012a7ea4, 0xedbfa82ff16fb199, 0xaf2a978c39d46146, - 0x42953fa3c8bbd0df, 0xcb061da59496a7dc, 0x25e7a17db6eb20b0, 0x34aa6d6963050fba, 0xa76cf7d580a4f1e4, - 0xf7ea10954ee338c4, 0xfcf2643b24819e93, 0xcf252d0746aeef8d, 0x4ef06f58a3f3082c, 0x563acfb37563a5d7, - 0x5086e740ce47c920, 0x2982f186dda3f843, 0x87696aac5e798b56, 0x5d22bb1d1f010380, 0x035e14f7d31236f5, - 0x3cec0d30da759f18, 0xf3c920379cdb7095, 0xb8db736b571e22bb, 0xdd36f5e44052f672, 0xaac8ab8851e23b44, - 0xa857b3d938fe1fe2, 0x17f1e4e76eca43fd, 0xec7ea4894b61a3ca, 0x9e62c6e132e734fe, 0xd4b1991b432c7483, - 0x6ad6c283af163acf, 0x1ce9904904a8e5aa, 0x5fbda34c761d2726, 0xf910583f4cb7c491, 0xc6a241f845d06d7c, - 0x4f3163fe19fd1a7f, 0xe99c988d2357f9c8, 0x8eee06535d0709a7, 0x0efa48aa0254fc55, 0xb4be23903c56fa48, - 0x763f52caabbedf65, 0xeee1bcd8227d876c, 0xe345e085f33b4dcc, 0x3e731561b369bbbe, 0x2843fd2067adea10, - 0x2adce5710eb1ceb6, 0xb7e03767ef44ccbd, 0x8db012a48e153f52, 0x61ceb62dc5749c98, 0xe85d942b9959eb9b, - 0x4c6f7709caef2c8a, 0x84377e5b8d6bbda3, 0x30895dcbb13d47eb, 0x74a04a9bc2a2fbc3, 0x6b17ce251518289c, - 0xe438c4d0f2113368, 0x1fb784bed7bad35f, 0x9b80fae55ad16efc, 0x77fe5e6c11b0cd36, 0xc858095247849129, - 0x08466059b97090a2, 0x01c10ca6ba0e1253, 0x6988d6747c040c3a, 0x6849dad2c60a1e69, 0x5147ebe67449db73, - 0xc99905f4fd8a837a, 0x991fe2b433cd4a5a, 0xf09734c04fc94660, 0xa28ecbd1e892abe6, 0xf1563866f5c75433, - 0x4dae7baf70e13ed9, 0x7ce62ac27bd26b61, 0x70837a39109ab392, 0x90988e4b30b3c8ab, 0xb2020b63877296bf, - 0x156efcb607d6675b}, - {0xe63f55ce97c331d0, 0x25b506b0015bba16, 0xc8706e29e6ad9ba8, 0x5b43d3775d521f6a, 0x0bfa3d577035106e, - 0xab95fc172afb0e66, 0xf64b63979e7a3276, 0xf58b4562649dad4b, 0x48f7c3dbae0c83f1, 0xff31916642f5c8c5, - 0xcbb048dc1c4a0495, 0x66b8f83cdf622989, 0x35c130e908e2b9b0, 0x7c761a61f0b34fa1, 0x3601161cf205268d, - 0x9e54ccfe2219b7d6, 0x8b7d90a538940837, 0x9cd403588ea35d0b, 0xbc3c6fea9ccc5b5a, 0xe5ff733b6d24aeed, - 0xceed22de0f7eb8d2, 0xec8581cab1ab545e, 0xb96105e88ff8e71d, 0x8ca03501871a5ead, 0x76ccce65d6db2a2f, - 0x5883f582a7b58057, 0x3f7be4ed2e8adc3e, 0x0fe7be06355cd9c9, 0xee054e6c1d11be83, 0x1074365909b903a6, - 0x5dde9f80b4813c10, 0x4a770c7d02b6692c, 0x5379c8d5d7809039, 0xb4067448161ed409, 0x5f5e5026183bd6cd, - 0xe898029bf4c29df9, 0x7fb63c940a54d09c, 0xc5171f897f4ba8bc, 0xa6f28db7b31d3d72, 0x2e4f3be7716eaa78, - 0x0d6771a099e63314, 0x82076254e41bf284, 0x2f0fd2b42733df98, 0x5c9e76d3e2dc49f0, 0x7aeb569619606cdb, - 0x83478b07b2468764, 0xcfadcb8d5923cd32, 0x85dac7f05b95a41e, 0xb5469d1b4043a1e9, 0xb821ecbbd9a592fd, - 0x1b8e0b0e798c13c8, 0x62a57b6d9a0be02e, 0xfcf1b793b81257f8, 0x9d94ea0bd8fe28eb, 0x4cea408aeb654a56, - 0x23284a47e888996c, 0x2d8f1d128b893545, 0xf4cbac3132c0d8ab, 0xbd7c86b9ca912eba, 0x3a268eef3dbe6079, - 0xf0d62f6077a9110c, 0x2735c916ade150cb, 0x89fd5f03942ee2ea, 0x1acee25d2fd16628, 0x90f39bab41181bff, - 0x430dfe8cde39939f, 0xf70b8ac4c8274796, 0x1c53aeaac6024552, 0x13b410acf35e9c9b, 0xa532ab4249faa24f, - 0x2b1251e5625a163f, 0xd7e3e676da4841c7, 0xa7b264e4e5404892, 0xda8497d643ae72d3, 0x861ae105a1723b23, - 0x38a6414991048aa4, 0x6578dec92585b6b4, 0x0280cfa6acbaeadd, 0x88bdb650c273970a, 0x9333bd5ebbff84c2, - 0x4e6a8f2c47dfa08b, 0x321c954db76cef2a, 0x418d312a72837942, 0xb29b38bfffcdf773, 0x6c022c38f90a4c07, - 0x5a033a240b0f6a8a, 0x1f93885f3ce5da6f, 0xc38a537e96988bc6, 0x39e6a81ac759ff44, 0x29929e43cee0fce2, - 0x40cdd87924de0ca2, 0xe9d8ebc8a29fe819, 0x0c2798f3cfbb46f4, 0x55e484223e53b343, 0x4650948ecd0d2fd8, - 0x20e86cb2126f0651, 0x6d42c56baf5739e7, 0xa06fc1405ace1e08, 0x7babbfc54f3d193b, 0x424d17df8864e67f, - 0xd8045870ef14980e, 0xc6d7397c85ac3781, 0x21a885e1443273b1, 0x67f8116f893f5c69, 0x24f5efe35706cff6, - 0xd56329d076f2ab1a, 0x5e1eb9754e66a32d, 0x28d2771098bd8902, 0x8f6013f47dfdc190, 0x17a993fdb637553c, - 0xe0a219397e1012aa, 0x786b9930b5da8606, 0x6e82e39e55b0a6da, 0x875a0856f72f4ec3, 0x3741ff4fa458536d, - 0xac4859b3957558fc, 0x7ef6d5c75c09a57c, 0xc04a758b6c7f14fb, 0xf9acdd91ab26ebbf, 0x7391a467c5ef9668, - 0x335c7c1ee1319aca, 0xa91533b18641e4bb, 0xe4bf9a683b79db0d, 0x8e20faa72ba0b470, 0x51f907737b3a7ae4, - 0x2268a314bed5ec8c, 0xd944b123b949edee, 0x31dcb3b84d8b7017, 0xd3fe65279f218860, 0x097af2f1dc8ffab3, - 0x9b09a6fc312d0b91, 0xcc6ded78a3c4520f, 0x3481d9ba5ebfcc50, 0x4f2a667f1182d56b, 0xdfd9fdd4509ace94, - 0x26752045fbbc252b, 0xbffc491f662bc467, 0xdd593272fc202449, 0x3cbbc218d46d4303, 0x91b372f817456e1f, - 0x681faf69bc6385a0, 0xb686bbeebaa43ed4, 0x1469b5084cd0ca01, 0x98c98009cbca94ac, 0x6438379a73d8c354, - 0xc2caba2dc0c5fe26, 0x3e3b0dbe78d7a9de, 0x50b9ee202d670f04, 0x4590b27b37eab0e5, 0x6025b4cb36b10af3, - 0xfb2c1237079c0162, 0xa12f28130c936be8, 0x4b37e52e54eb1ccc, 0x083a1ba28ad28f53, 0xc10a9cd83a22611b, - 0x9f1425ad7444c236, 0x069d4cf7e9d3237a, 0xedc56899e7f621be, 0x778c273680865fcf, 0x309c5aeb1bd605f7, - 0x8de0dc52d1472b4d, 0xf8ec34c2fd7b9e5f, 0xea18cd3d58787724, 0xaad515447ca67b86, 0x9989695a9d97e14c, - 0x0000000000000000, 0xf196c63321f464ec, 0x71116bc169557cb5, 0xaf887f466f92c7c1, 0x972e3e0ffe964d65, - 0x190ec4a8d536f915, 0x95aef1a9522ca7b8, 0xdc19db21aa7d51a9, 0x94ee18fa0471d258, 0x8087adf248a11859, - 0xc457f6da2916dd5c, 0xfa6cfb6451c17482, 0xf256e0c6db13fbd1, 0x6a9f60cf10d96f7d, 0x4daaa9d9bd383fb6, - 0x03c026f5fae79f3d, 0xde99148706c7bb74, 0x2a52b8b6340763df, 0x6fc20acd03edd33a, 0xd423c08320afdefa, - 0xbbe1ca4e23420dc0, 0x966ed75ca8cb3885, 0xeb58246e0e2502c4, 0x055d6a021334bc47, 0xa47242111fa7d7af, - 0xe3623fcc84f78d97, 0x81c744a11efc6db9, 0xaec8961539cfb221, 0xf31609958d4e8e31, 0x63e5923ecc5695ce, - 0x47107ddd9b505a38, 0xa3afe7b5a0298135, 0x792b7063e387f3e6, 0x0140e953565d75e0, 0x12f4f9ffa503e97b, - 0x750ce8902c3cb512, 0xdbc47e8515f30733, 0x1ed3610c6ab8af8f, 0x5239218681dde5d9, 0xe222d69fd2aaf877, - 0xfe71783514a8bd25, 0xcaf0a18f4a177175, 0x61655d9860ec7f13, 0xe77fbc9dc19e4430, 0x2ccff441ddd440a5, - 0x16e97aaee06a20dc, 0xa855dae2d01c915b, 0x1d1347f9905f30b2, 0xb7c652bdecf94b34, 0xd03e43d265c6175d, - 0xfdb15ec0ee4f2218, 0x57644b8492e9599e, 0x07dda5a4bf8e569a, 0x54a46d71680ec6a3, 0x5624a2d7c4b42c7e, - 0xbebca04c3076b187, 0x7d36f332a6ee3a41, 0x3b6667bc6be31599, 0x695f463aea3ef040, 0xad08b0e0c3282d1c, - 0xb15b1e4a052a684e, 0x44d05b2861b7c505, 0x15295c5b1a8dbfe1, 0x744c01c37a61c0f2, 0x59c31cd1f1e8f5b7, - 0xef45a73f4b4ccb63, 0x6bdf899c46841a9d, 0x3dfb2b4b823036e3, 0xa2ef0ee6f674f4d5, 0x184e2dfb836b8cf5, - 0x1134df0a5fe47646, 0xbaa1231d751f7820, 0xd17eaa81339b62bd, 0xb01bf71953771dae, 0x849a2ea30dc8d1fe, - 0x705182923f080955, 0x0ea757556301ac29, 0x041d83514569c9a7, 0x0abad4042668658e, 0x49b72a88f851f611, - 0x8a3d79f66ec97dd7, 0xcd2d042bf59927ef, 0xc930877ab0f0ee48, 0x9273540deda2f122, 0xc797d02fd3f14261, - 0xe1e2f06a284d674a, 0xd2be8c74c97cfd80, 0x9a494faf67707e71, 0xb3dbd1eca9908293, 0x72d14d3493b2e388, - 0xd6a30f258c153427}}; - -extern const uint64_t STREEBOG_C[12][8] = {{0xdd806559f2a64507, - 0x05767436cc744d23, - 0xa2422a08a460d315, - 0x4b7ce09192676901, - 0x714eb88d7585c4fc, - 0x2f6a76432e45d016, - 0xebcb2f81c0657c1f, - 0xb1085bda1ecadae9}, - {0xe679047021b19bb7, - 0x55dda21bd7cbcd56, - 0x5cb561c2db0aa7ca, - 0x9ab5176b12d69958, - 0x61d55e0f16b50131, - 0xf3feea720a232b98, - 0x4fe39d460f70b5d7, - 0x6fa3b58aa99d2f1a}, - {0x991e96f50aba0ab2, - 0xc2b6f443867adb31, - 0xc1c93a376062db09, - 0xd3e20fe490359eb1, - 0xf2ea7514b1297b7b, - 0x06f15e5f529c1f8b, - 0x0a39fc286a3d8435, - 0xf574dcac2bce2fc7}, - {0x220cbebc84e3d12e, - 0x3453eaa193e837f1, - 0xd8b71333935203be, - 0xa9d72c82ed03d675, - 0x9d721cad685e353f, - 0x488e857e335c3c7d, - 0xf948e1a05d71e4dd, - 0xef1fdfb3e81566d2}, - {0x601758fd7c6cfe57, - 0x7a56a27ea9ea63f5, - 0xdfff00b723271a16, - 0xbfcd1747253af5a3, - 0x359e35d7800fffbd, - 0x7f151c1f1686104a, - 0x9a3f410c6ca92363, - 0x4bea6bacad474799}, - {0xfa68407a46647d6e, - 0xbf71c57236904f35, - 0x0af21f66c2bec6b6, - 0xcffaa6b71c9ab7b4, - 0x187f9ab49af08ec6, - 0x2d66c4f95142a46c, - 0x6fa4c33b7a3039c0, - 0xae4faeae1d3ad3d9}, - {0x8886564d3a14d493, - 0x3517454ca23c4af3, - 0x06476983284a0504, - 0x0992abc52d822c37, - 0xd3473e33197a93c9, - 0x399ec6c7e6bf87c9, - 0x51ac86febf240954, - 0xf4c70e16eeaac5ec}, - {0xa47f0dd4bf02e71e, - 0x36acc2355951a8d9, - 0x69d18d2bd1a5c42f, - 0xf4892bcb929b0690, - 0x89b4443b4ddbc49a, - 0x4eb7f8719c36de1e, - 0x03e7aa020c6e4141, - 0x9b1f5b424d93c9a7}, - {0x7261445183235adb, - 0x0e38dc92cb1f2a60, - 0x7b2b8a9aa6079c54, - 0x800a440bdbb2ceb1, - 0x3cd955b7e00d0984, - 0x3a7d3a1b25894224, - 0x944c9ad8ec165fde, - 0x378f5a541631229b}, - {0x74b4c7fb98459ced, - 0x3698fad1153bb6c3, - 0x7a1e6c303b7652f4, - 0x9fe76702af69334b, - 0x1fffe18a1b336103, - 0x8941e71cff8a78db, - 0x382ae548b2e4f3f3, - 0xabbedea680056f52}, - {0x6bcaa4cd81f32d1b, - 0xdea2594ac06fd85d, - 0xefbacd1d7d476e98, - 0x8a1d71efea48b9ca, - 0x2001802114846679, - 0xd8fa6bbbebab0761, - 0x3002c6cd635afe94, - 0x7bcd9ed0efc889fb}, - {0x48bc924af11bd720, - 0xfaf417d5d9b21b99, - 0xe71da4aa88e12852, - 0x5d80ef9d1891cc86, - 0xf82012d430219f9b, - 0xcda43c32bcdf1d77, - 0xd21380b00449b17a, - 0x378ee767f11631ba}}; - -} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/hash/trunc_hash/trunc_hash.cpp botan3-3.12.0+dfsg/src/lib/hash/trunc_hash/trunc_hash.cpp --- botan3-3.7.1+dfsg/src/lib/hash/trunc_hash/trunc_hash.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/trunc_hash/trunc_hash.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,7 +8,9 @@ #include +#include #include +#include #include namespace Botan { @@ -24,7 +26,7 @@ // truncate output to a full number of bytes const auto bytes = output_length(); - std::copy_n(m_buffer.begin(), bytes, out.data()); + copy_mem(out.data(), m_buffer.data(), bytes); zeroise(m_buffer); // mask the unwanted bits in the final byte @@ -55,16 +57,18 @@ } Truncated_Hash::Truncated_Hash(std::unique_ptr hash, size_t bits) : - m_hash(std::move(hash)), m_output_bits(bits), m_buffer(m_hash->output_length()) { + m_hash(std::move(hash)), m_output_bits(bits) { BOTAN_ASSERT_NONNULL(m_hash); if(m_output_bits == 0) { throw Invalid_Argument("Truncating a hash to 0 does not make sense"); } - if(m_hash->output_length() * 8 < m_output_bits) { + const size_t hash_output_length = m_hash->output_length(); + if(hash_output_length * 8 < m_output_bits) { throw Invalid_Argument("Underlying hash function does not produce enough bytes for truncation"); } + m_buffer.resize(hash_output_length); } } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/hash/trunc_hash/trunc_hash.h botan3-3.12.0+dfsg/src/lib/hash/trunc_hash/trunc_hash.h --- botan3-3.7.1+dfsg/src/lib/hash/trunc_hash/trunc_hash.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/trunc_hash/trunc_hash.h 2026-05-07 01:38:28.000000000 +0000 @@ -36,8 +36,8 @@ Truncated_Hash(std::unique_ptr hash, size_t length); private: - void add_data(std::span) override; - void final_result(std::span) override; + void add_data(std::span input) override; + void final_result(std::span output) override; std::unique_ptr m_hash; size_t m_output_bits; diff -Nru botan3-3.7.1+dfsg/src/lib/hash/whirlpool/whirlpool.cpp botan3-3.12.0+dfsg/src/lib/hash/whirlpool/whirlpool.cpp --- botan3-3.7.1+dfsg/src/lib/hash/whirlpool/whirlpool.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/whirlpool/whirlpool.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,103 +1,119 @@ /* * Whirlpool -* (C) 1999-2007,2020 Jack Lloyd +* (C) 1999-2007,2020,2026 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ #include +#include +#include #include #include -#include +#include + +#if defined(BOTAN_HAS_CPUID) + #include +#endif namespace Botan { namespace { -alignas(256) const uint64_t WHIRL_S[256] = { - 0x18186018C07830D8, 0x23238C2305AF4626, 0xC6C63FC67EF991B8, 0xE8E887E8136FCDFB, 0x878726874CA113CB, - 0xB8B8DAB8A9626D11, 0x0101040108050209, 0x4F4F214F426E9E0D, 0x3636D836ADEE6C9B, 0xA6A6A2A6590451FF, - 0xD2D26FD2DEBDB90C, 0xF5F5F3F5FB06F70E, 0x7979F979EF80F296, 0x6F6FA16F5FCEDE30, 0x91917E91FCEF3F6D, - 0x52525552AA07A4F8, 0x60609D6027FDC047, 0xBCBCCABC89766535, 0x9B9B569BACCD2B37, 0x8E8E028E048C018A, - 0xA3A3B6A371155BD2, 0x0C0C300C603C186C, 0x7B7BF17BFF8AF684, 0x3535D435B5E16A80, 0x1D1D741DE8693AF5, - 0xE0E0A7E05347DDB3, 0xD7D77BD7F6ACB321, 0xC2C22FC25EED999C, 0x2E2EB82E6D965C43, 0x4B4B314B627A9629, - 0xFEFEDFFEA321E15D, 0x575741578216AED5, 0x15155415A8412ABD, 0x7777C1779FB6EEE8, 0x3737DC37A5EB6E92, - 0xE5E5B3E57B56D79E, 0x9F9F469F8CD92313, 0xF0F0E7F0D317FD23, 0x4A4A354A6A7F9420, 0xDADA4FDA9E95A944, - 0x58587D58FA25B0A2, 0xC9C903C906CA8FCF, 0x2929A429558D527C, 0x0A0A280A5022145A, 0xB1B1FEB1E14F7F50, - 0xA0A0BAA0691A5DC9, 0x6B6BB16B7FDAD614, 0x85852E855CAB17D9, 0xBDBDCEBD8173673C, 0x5D5D695DD234BA8F, - 0x1010401080502090, 0xF4F4F7F4F303F507, 0xCBCB0BCB16C08BDD, 0x3E3EF83EEDC67CD3, 0x0505140528110A2D, - 0x676781671FE6CE78, 0xE4E4B7E47353D597, 0x27279C2725BB4E02, 0x4141194132588273, 0x8B8B168B2C9D0BA7, - 0xA7A7A6A7510153F6, 0x7D7DE97DCF94FAB2, 0x95956E95DCFB3749, 0xD8D847D88E9FAD56, 0xFBFBCBFB8B30EB70, - 0xEEEE9FEE2371C1CD, 0x7C7CED7CC791F8BB, 0x6666856617E3CC71, 0xDDDD53DDA68EA77B, 0x17175C17B84B2EAF, - 0x4747014702468E45, 0x9E9E429E84DC211A, 0xCACA0FCA1EC589D4, 0x2D2DB42D75995A58, 0xBFBFC6BF9179632E, - 0x07071C07381B0E3F, 0xADAD8EAD012347AC, 0x5A5A755AEA2FB4B0, 0x838336836CB51BEF, 0x3333CC3385FF66B6, - 0x636391633FF2C65C, 0x02020802100A0412, 0xAAAA92AA39384993, 0x7171D971AFA8E2DE, 0xC8C807C80ECF8DC6, - 0x19196419C87D32D1, 0x494939497270923B, 0xD9D943D9869AAF5F, 0xF2F2EFF2C31DF931, 0xE3E3ABE34B48DBA8, - 0x5B5B715BE22AB6B9, 0x88881A8834920DBC, 0x9A9A529AA4C8293E, 0x262698262DBE4C0B, 0x3232C8328DFA64BF, - 0xB0B0FAB0E94A7D59, 0xE9E983E91B6ACFF2, 0x0F0F3C0F78331E77, 0xD5D573D5E6A6B733, 0x80803A8074BA1DF4, - 0xBEBEC2BE997C6127, 0xCDCD13CD26DE87EB, 0x3434D034BDE46889, 0x48483D487A759032, 0xFFFFDBFFAB24E354, - 0x7A7AF57AF78FF48D, 0x90907A90F4EA3D64, 0x5F5F615FC23EBE9D, 0x202080201DA0403D, 0x6868BD6867D5D00F, - 0x1A1A681AD07234CA, 0xAEAE82AE192C41B7, 0xB4B4EAB4C95E757D, 0x54544D549A19A8CE, 0x93937693ECE53B7F, - 0x222288220DAA442F, 0x64648D6407E9C863, 0xF1F1E3F1DB12FF2A, 0x7373D173BFA2E6CC, 0x12124812905A2482, - 0x40401D403A5D807A, 0x0808200840281048, 0xC3C32BC356E89B95, 0xECEC97EC337BC5DF, 0xDBDB4BDB9690AB4D, - 0xA1A1BEA1611F5FC0, 0x8D8D0E8D1C830791, 0x3D3DF43DF5C97AC8, 0x97976697CCF1335B, 0x0000000000000000, - 0xCFCF1BCF36D483F9, 0x2B2BAC2B4587566E, 0x7676C57697B3ECE1, 0x8282328264B019E6, 0xD6D67FD6FEA9B128, - 0x1B1B6C1BD87736C3, 0xB5B5EEB5C15B7774, 0xAFAF86AF112943BE, 0x6A6AB56A77DFD41D, 0x50505D50BA0DA0EA, - 0x45450945124C8A57, 0xF3F3EBF3CB18FB38, 0x3030C0309DF060AD, 0xEFEF9BEF2B74C3C4, 0x3F3FFC3FE5C37EDA, - 0x55554955921CAAC7, 0xA2A2B2A2791059DB, 0xEAEA8FEA0365C9E9, 0x656589650FECCA6A, 0xBABAD2BAB9686903, - 0x2F2FBC2F65935E4A, 0xC0C027C04EE79D8E, 0xDEDE5FDEBE81A160, 0x1C1C701CE06C38FC, 0xFDFDD3FDBB2EE746, - 0x4D4D294D52649A1F, 0x92927292E4E03976, 0x7575C9758FBCEAFA, 0x06061806301E0C36, 0x8A8A128A249809AE, - 0xB2B2F2B2F940794B, 0xE6E6BFE66359D185, 0x0E0E380E70361C7E, 0x1F1F7C1FF8633EE7, 0x6262956237F7C455, - 0xD4D477D4EEA3B53A, 0xA8A89AA829324D81, 0x96966296C4F43152, 0xF9F9C3F99B3AEF62, 0xC5C533C566F697A3, - 0x2525942535B14A10, 0x59597959F220B2AB, 0x84842A8454AE15D0, 0x7272D572B7A7E4C5, 0x3939E439D5DD72EC, - 0x4C4C2D4C5A619816, 0x5E5E655ECA3BBC94, 0x7878FD78E785F09F, 0x3838E038DDD870E5, 0x8C8C0A8C14860598, - 0xD1D163D1C6B2BF17, 0xA5A5AEA5410B57E4, 0xE2E2AFE2434DD9A1, 0x616199612FF8C24E, 0xB3B3F6B3F1457B42, - 0x2121842115A54234, 0x9C9C4A9C94D62508, 0x1E1E781EF0663CEE, 0x4343114322528661, 0xC7C73BC776FC93B1, - 0xFCFCD7FCB32BE54F, 0x0404100420140824, 0x51515951B208A2E3, 0x99995E99BCC72F25, 0x6D6DA96D4FC4DA22, - 0x0D0D340D68391A65, 0xFAFACFFA8335E979, 0xDFDF5BDFB684A369, 0x7E7EE57ED79BFCA9, 0x242490243DB44819, - 0x3B3BEC3BC5D776FE, 0xABAB96AB313D4B9A, 0xCECE1FCE3ED181F0, 0x1111441188552299, 0x8F8F068F0C890383, - 0x4E4E254E4A6B9C04, 0xB7B7E6B7D1517366, 0xEBEB8BEB0B60CBE0, 0x3C3CF03CFDCC78C1, 0x81813E817CBF1FFD, - 0x94946A94D4FE3540, 0xF7F7FBF7EB0CF31C, 0xB9B9DEB9A1676F18, 0x13134C13985F268B, 0x2C2CB02C7D9C5851, - 0xD3D36BD3D6B8BB05, 0xE7E7BBE76B5CD38C, 0x6E6EA56E57CBDC39, 0xC4C437C46EF395AA, 0x03030C03180F061B, - 0x565645568A13ACDC, 0x44440D441A49885E, 0x7F7FE17FDF9EFEA0, 0xA9A99EA921374F88, 0x2A2AA82A4D825467, - 0xBBBBD6BBB16D6B0A, 0xC1C123C146E29F87, 0x53535153A202A6F1, 0xDCDC57DCAE8BA572, 0x0B0B2C0B58271653, - 0x9D9D4E9D9CD32701, 0x6C6CAD6C47C1D82B, 0x3131C43195F562A4, 0x7474CD7487B9E8F3, 0xF6F6FFF6E309F115, - 0x464605460A438C4C, 0xACAC8AAC092645A5, 0x89891E893C970FB5, 0x14145014A04428B4, 0xE1E1A3E15B42DFBA, - 0x16165816B04E2CA6, 0x3A3AE83ACDD274F7, 0x6969B9696FD0D206, 0x09092409482D1241, 0x7070DD70A7ADE0D7, - 0xB6B6E2B6D954716F, 0xD0D067D0CEB7BD1E, 0xEDED93ED3B7EC7D6, 0xCCCC17CC2EDB85E2, 0x424215422A578468, - 0x98985A98B4C22D2C, 0xA4A4AAA4490E55ED, 0x2828A0285D885075, 0x5C5C6D5CDA31B886, 0xF8F8C7F8933FED6B, - 0x8686228644A411C2}; +// Derive the 256-byte S-box from the Whirlpool E and R mini-boxes +consteval std::array whirlpool_sbox() noexcept { + constexpr uint8_t Ebox[16] = {1, 11, 9, 12, 13, 6, 15, 3, 14, 8, 7, 4, 10, 2, 5, 0}; + constexpr uint8_t Rbox[16] = {7, 12, 11, 13, 14, 4, 9, 15, 6, 3, 8, 10, 2, 5, 1, 0}; + + // Derive the inverse of the E table + uint8_t Eibox[16] = {}; + for(size_t i = 0; i != 16; ++i) { + Eibox[Ebox[i]] = static_cast(i); + } + + std::array S = {}; + for(size_t i = 0; i != 256; ++i) { + const uint8_t L = Ebox[i >> 4]; + const uint8_t R = Eibox[i & 0x0F]; + const uint8_t T = Rbox[L ^ R]; + S[i] = static_cast((Ebox[L ^ T] << 4) | Eibox[R ^ T]); + } + return S; +} + +// Combined S-box + MDS diffusion table +consteval std::array whirlpool_T_table(const std::array& S) noexcept { + // MDS circulant matrix first row: [1, 1, 4, 1, 8, 5, 2, 9] over GF(2^8) + constexpr uint64_t MDS = 0x0101040108050209; + + std::array T = {}; + for(size_t i = 0; i != 256; ++i) { + T[i] = poly_mul<0x1D>(MDS, S[i]); + } + return T; +} + +// Round constants are from the first 64 elements of the sbox +consteval std::array whirlpool_rc(const std::array& S) noexcept { + std::array RC = {}; + for(size_t r = 0; r != 10; ++r) { + RC[r] = load_be(S.data(), r); + } + return RC; +} + +constexpr auto WHIRL_S = whirlpool_sbox(); +alignas(256) constexpr auto WHIRL_T = whirlpool_T_table(WHIRL_S); +constexpr auto WHIRL_RC = whirlpool_rc(WHIRL_S); uint64_t whirl(uint64_t x0, uint64_t x1, uint64_t x2, uint64_t x3, uint64_t x4, uint64_t x5, uint64_t x6, uint64_t x7) { - const uint64_t s0 = WHIRL_S[get_byte<0>(x0)]; - const uint64_t s1 = WHIRL_S[get_byte<1>(x1)]; - const uint64_t s2 = WHIRL_S[get_byte<2>(x2)]; - const uint64_t s3 = WHIRL_S[get_byte<3>(x3)]; - const uint64_t s4 = WHIRL_S[get_byte<4>(x4)]; - const uint64_t s5 = WHIRL_S[get_byte<5>(x5)]; - const uint64_t s6 = WHIRL_S[get_byte<6>(x6)]; - const uint64_t s7 = WHIRL_S[get_byte<7>(x7)]; + const uint64_t s0 = WHIRL_T[get_byte<0>(x0)]; + const uint64_t s1 = WHIRL_T[get_byte<1>(x1)]; + const uint64_t s2 = WHIRL_T[get_byte<2>(x2)]; + const uint64_t s3 = WHIRL_T[get_byte<3>(x3)]; + const uint64_t s4 = WHIRL_T[get_byte<4>(x4)]; + const uint64_t s5 = WHIRL_T[get_byte<5>(x5)]; + const uint64_t s6 = WHIRL_T[get_byte<6>(x6)]; + const uint64_t s7 = WHIRL_T[get_byte<7>(x7)]; return s0 ^ rotr<8>(s1) ^ rotr<16>(s2) ^ rotr<24>(s3) ^ rotr<32>(s4) ^ rotr<40>(s5) ^ rotr<48>(s6) ^ rotr<56>(s7); } } // namespace +std::string Whirlpool::provider() const { +#if defined(BOTAN_HAS_WHIRLPOOL_AVX512) + if(auto feat = CPUID::check(CPUID::Feature::AVX512)) { + return *feat; + } +#endif + +#if defined(BOTAN_HAS_WHIRLPOOL_AVX2) + if(auto feat = CPUID::check(CPUID::Feature::AVX2)) { + return *feat; + } +#endif + + return "base"; +} + /* * Whirlpool Compression Function */ void Whirlpool::compress_n(digest_type& digest, std::span input, size_t blocks) { - static const uint64_t RC[10] = {0x1823C6E887B8014F, - 0x36A6D2F5796F9152, - 0x60BC9B8EA30C7B35, - 0x1DE0D7C22E4BFE57, - 0x157737E59FF04ADA, - 0x58C9290AB1A06B85, - 0xBD5D10F4CB3E0567, - 0xE427418BA77D95D8, - 0xFBEE7C66DD17479E, - 0xCA2DBF07AD5A8333}; +#if defined(BOTAN_HAS_WHIRLPOOL_AVX512) + if(CPUID::has(CPUID::Feature::AVX512)) { + return compress_n_avx512(digest, input, blocks); + } +#endif + +#if defined(BOTAN_HAS_WHIRLPOOL_AVX2) + if(CPUID::has(CPUID::Feature::AVX2)) { + return compress_n_avx2(digest, input, blocks); + } +#endif + BufferSlicer in(input); for(size_t i = 0; i != blocks; ++i) { @@ -125,7 +141,7 @@ const uint64_t PK6 = K[8 * (r - 1) + 6]; const uint64_t PK7 = K[8 * (r - 1) + 7]; - K[8 * r + 0] = whirl(PK0, PK7, PK6, PK5, PK4, PK3, PK2, PK1) ^ RC[r - 1]; + K[8 * r + 0] = whirl(PK0, PK7, PK6, PK5, PK4, PK3, PK2, PK1) ^ WHIRL_RC[r - 1]; K[8 * r + 1] = whirl(PK1, PK0, PK7, PK6, PK5, PK4, PK3, PK2); K[8 * r + 2] = whirl(PK2, PK1, PK0, PK7, PK6, PK5, PK4, PK3); K[8 * r + 3] = whirl(PK3, PK2, PK1, PK0, PK7, PK6, PK5, PK4); @@ -149,14 +165,14 @@ uint64_t B7 = M[7] ^ K[7]; for(size_t r = 1; r != 11; ++r) { - uint64_t T0 = whirl(B0, B7, B6, B5, B4, B3, B2, B1) ^ K[8 * r + 0]; - uint64_t T1 = whirl(B1, B0, B7, B6, B5, B4, B3, B2) ^ K[8 * r + 1]; - uint64_t T2 = whirl(B2, B1, B0, B7, B6, B5, B4, B3) ^ K[8 * r + 2]; - uint64_t T3 = whirl(B3, B2, B1, B0, B7, B6, B5, B4) ^ K[8 * r + 3]; - uint64_t T4 = whirl(B4, B3, B2, B1, B0, B7, B6, B5) ^ K[8 * r + 4]; - uint64_t T5 = whirl(B5, B4, B3, B2, B1, B0, B7, B6) ^ K[8 * r + 5]; - uint64_t T6 = whirl(B6, B5, B4, B3, B2, B1, B0, B7) ^ K[8 * r + 6]; - uint64_t T7 = whirl(B7, B6, B5, B4, B3, B2, B1, B0) ^ K[8 * r + 7]; + const uint64_t T0 = whirl(B0, B7, B6, B5, B4, B3, B2, B1) ^ K[8 * r + 0]; + const uint64_t T1 = whirl(B1, B0, B7, B6, B5, B4, B3, B2) ^ K[8 * r + 1]; + const uint64_t T2 = whirl(B2, B1, B0, B7, B6, B5, B4, B3) ^ K[8 * r + 2]; + const uint64_t T3 = whirl(B3, B2, B1, B0, B7, B6, B5, B4) ^ K[8 * r + 3]; + const uint64_t T4 = whirl(B4, B3, B2, B1, B0, B7, B6, B5) ^ K[8 * r + 4]; + const uint64_t T5 = whirl(B5, B4, B3, B2, B1, B0, B7, B6) ^ K[8 * r + 5]; + const uint64_t T6 = whirl(B6, B5, B4, B3, B2, B1, B0, B7) ^ K[8 * r + 6]; + const uint64_t T7 = whirl(B7, B6, B5, B4, B3, B2, B1, B0) ^ K[8 * r + 7]; B0 = T0; B1 = T1; diff -Nru botan3-3.7.1+dfsg/src/lib/hash/whirlpool/whirlpool.h botan3-3.12.0+dfsg/src/lib/hash/whirlpool/whirlpool.h --- botan3-3.7.1+dfsg/src/lib/hash/whirlpool/whirlpool.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/whirlpool/whirlpool.h 2026-05-07 01:38:28.000000000 +0000 @@ -28,6 +28,14 @@ static void compress_n(digest_type& digest, std::span input, size_t blocks); static void init(digest_type& digest); +#if defined(BOTAN_HAS_WHIRLPOOL_AVX512) + static void compress_n_avx512(digest_type& digest, std::span input, size_t blocks); +#endif + +#if defined(BOTAN_HAS_WHIRLPOOL_AVX2) + static void compress_n_avx2(digest_type& digest, std::span input, size_t blocks); +#endif + public: std::string name() const override { return "Whirlpool"; } @@ -39,6 +47,8 @@ std::unique_ptr copy_state() const override; + std::string provider() const override; + void clear() override { m_md.clear(); } private: diff -Nru botan3-3.7.1+dfsg/src/lib/hash/whirlpool/whirlpool_avx2/info.txt botan3-3.12.0+dfsg/src/lib/hash/whirlpool/whirlpool_avx2/info.txt --- botan3-3.7.1+dfsg/src/lib/hash/whirlpool/whirlpool_avx2/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/whirlpool/whirlpool_avx2/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,16 @@ + +WHIRLPOOL_AVX2 -> 20260321 + + + +name -> "Whirlpool AVX2" + + + +avx2 + + + +cpuid +simd_avx2 + diff -Nru botan3-3.7.1+dfsg/src/lib/hash/whirlpool/whirlpool_avx2/whirlpool_avx2.cpp botan3-3.12.0+dfsg/src/lib/hash/whirlpool/whirlpool_avx2/whirlpool_avx2.cpp --- botan3-3.7.1+dfsg/src/lib/hash/whirlpool/whirlpool_avx2/whirlpool_avx2.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/whirlpool/whirlpool_avx2/whirlpool_avx2.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,254 @@ +/* +* (C) 2026 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include + +namespace Botan { + +namespace WhirlpoolAVX2 { + +namespace { + +// NOLINTBEGIN(portability-simd-intrinsics) + +class WhirlpoolState final { + public: + BOTAN_FN_ISA_AVX2 + WhirlpoolState() : m_lo(_mm256_setzero_si256()), m_hi(_mm256_setzero_si256()) {} + + BOTAN_FN_ISA_AVX2 + WhirlpoolState(__m256i lo, __m256i hi) : m_lo(lo), m_hi(hi) {} + + WhirlpoolState(const WhirlpoolState& other) = default; + WhirlpoolState(WhirlpoolState&& other) = default; + WhirlpoolState& operator=(const WhirlpoolState& other) = default; + WhirlpoolState& operator=(WhirlpoolState&& other) = default; + ~WhirlpoolState() = default; + + BOTAN_FN_ISA_AVX2 + static WhirlpoolState load_bytes(const uint8_t src[64]) { + return WhirlpoolState(_mm256_loadu_si256(reinterpret_cast(src)), + _mm256_loadu_si256(reinterpret_cast(src + 32))); + } + + BOTAN_FN_ISA_AVX2 + static WhirlpoolState load_be(const uint64_t src[8]) { + return WhirlpoolState(_mm256_loadu_si256(reinterpret_cast(src)), + _mm256_loadu_si256(reinterpret_cast(src + 4))) + .bswap(); + } + + BOTAN_FN_ISA_AVX2 + void store_be(uint64_t dst[8]) const { + auto s = bswap(); + _mm256_storeu_si256(reinterpret_cast<__m256i*>(dst), s.m_lo); + _mm256_storeu_si256(reinterpret_cast<__m256i*>(dst + 4), s.m_hi); + } + + BOTAN_FN_ISA_AVX2 + inline friend WhirlpoolState operator^(WhirlpoolState a, WhirlpoolState b) { + return WhirlpoolState(_mm256_xor_si256(a.m_lo, b.m_lo), _mm256_xor_si256(a.m_hi, b.m_hi)); + } + + BOTAN_FN_ISA_AVX2 + inline friend WhirlpoolState operator^(WhirlpoolState a, uint64_t rc) { + return WhirlpoolState(_mm256_xor_si256(a.m_lo, _mm256_set_epi64x(0, 0, 0, rc)), a.m_hi); + } + + BOTAN_FN_ISA_AVX2 + inline WhirlpoolState& operator^=(WhirlpoolState other) { + m_lo = _mm256_xor_si256(m_lo, other.m_lo); + m_hi = _mm256_xor_si256(m_hi, other.m_hi); + return *this; + } + + BOTAN_FN_ISA_AVX2 + inline WhirlpoolState sub_bytes() const { return WhirlpoolState(sub_bytes(m_lo), sub_bytes(m_hi)); } + + BOTAN_FN_ISA_AVX2 + inline WhirlpoolState shift_columns() const { + /* + * This is a lot more complicated than the AVX-512 version since first we have + * the state split between two registers and also AVX2 permutes are much weaker + * than AVX512's due to mostly only working on 128 bit lanes + */ + + constexpr char non = -1; + + const auto sc0 = _mm_setr_epi8(0x0, non, non, non, non, non, non, 0xF, 0x8, 0x1, non, non, non, non, non, non); + const auto sc1 = _mm_setr_epi8(non, 0x9, 0x2, non, non, non, non, non, non, non, 0xA, 0x3, non, non, non, non); + const auto sc2 = _mm_setr_epi8(non, non, non, 0xB, 0x4, non, non, non, non, non, non, non, 0xC, 0x5, non, non); + const auto sc3 = _mm_setr_epi8(non, non, non, non, non, 0xD, 0x6, non, non, non, non, non, non, non, 0xE, 0x7); + + const auto idx_same_lane = _mm256_broadcastsi128_si256(sc0); + const auto idx_other_half = _mm256_broadcastsi128_si256(sc2); + const auto idx_other_lane = _mm256_set_m128i(sc1, sc3); + const auto idx_other_both = _mm256_set_m128i(sc3, sc1); + + // Swap the two lanes within the registers so we can get at the values we need via in-lane shuffles + const auto r_lo = _mm256_permute2x128_si256(m_lo, m_lo, 0x01); + const auto r_hi = _mm256_permute2x128_si256(m_hi, m_hi, 0x01); + + /* + * Compute the shift column output by shuffling all 4 input lanes (lo[0], lo[1], hi[0], hi[1]) + * to select out the values we want from each source lane, placing them in the + * index we want, and OR each into the result. + */ + __m256i new_lo = _mm256_shuffle_epi8(m_lo, idx_same_lane); + new_lo = _mm256_or_si256(new_lo, _mm256_shuffle_epi8(r_lo, idx_other_lane)); + new_lo = _mm256_or_si256(new_lo, _mm256_shuffle_epi8(m_hi, idx_other_half)); + new_lo = _mm256_or_si256(new_lo, _mm256_shuffle_epi8(r_hi, idx_other_both)); + + // Same as above just with hi/lo swapped + __m256i new_hi = _mm256_shuffle_epi8(m_hi, idx_same_lane); + new_hi = _mm256_or_si256(new_hi, _mm256_shuffle_epi8(r_hi, idx_other_lane)); + new_hi = _mm256_or_si256(new_hi, _mm256_shuffle_epi8(m_lo, idx_other_half)); + new_hi = _mm256_or_si256(new_hi, _mm256_shuffle_epi8(r_lo, idx_other_both)); + + return WhirlpoolState(new_lo, new_hi); + } + + BOTAN_FN_ISA_AVX2 + BOTAN_FORCE_INLINE WhirlpoolState mix_rows() const { return WhirlpoolState(mix_rows(m_lo), mix_rows(m_hi)); } + + BOTAN_FN_ISA_AVX2 + BOTAN_FORCE_INLINE WhirlpoolState round() const { return sub_bytes().shift_columns().mix_rows(); } + + private: + BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX2 static __m256i sub_bytes(__m256i v) { + const auto Ebox = + _mm256_broadcastsi128_si256(_mm_setr_epi8(1, 11, 9, 12, 13, 6, 15, 3, 14, 8, 7, 4, 10, 2, 5, 0)); + const auto Eibox = + _mm256_broadcastsi128_si256(_mm_setr_epi8(15, 0, 13, 7, 11, 14, 5, 10, 9, 2, 12, 1, 3, 4, 8, 6)); + const auto Rbox = + _mm256_broadcastsi128_si256(_mm_setr_epi8(7, 12, 11, 13, 14, 4, 9, 15, 6, 3, 8, 10, 2, 5, 1, 0)); + + const auto lo_mask = _mm256_set1_epi8(0x0F); + + const auto lo_nib = _mm256_and_si256(v, lo_mask); + const auto hi_nib = _mm256_and_si256(_mm256_srli_epi16(v, 4), lo_mask); + + const auto L = _mm256_shuffle_epi8(Ebox, hi_nib); + const auto R = _mm256_shuffle_epi8(Eibox, lo_nib); + const auto T = _mm256_shuffle_epi8(Rbox, _mm256_xor_si256(L, R)); + + const auto out_hi = _mm256_shuffle_epi8(Ebox, _mm256_xor_si256(L, T)); + const auto out_lo = _mm256_shuffle_epi8(Eibox, _mm256_xor_si256(R, T)); + + return _mm256_or_si256(_mm256_slli_epi16(out_hi, 4), out_lo); + } + + BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX2 static __m256i mix_rows(__m256i v) { + // Shuffles for 64-bit rotations + const auto rot1 = + _mm256_broadcastsi128_si256(_mm_setr_epi8(7, 0, 1, 2, 3, 4, 5, 6, 15, 8, 9, 10, 11, 12, 13, 14)); + const auto rot2 = + _mm256_broadcastsi128_si256(_mm_setr_epi8(6, 7, 0, 1, 2, 3, 4, 5, 14, 15, 8, 9, 10, 11, 12, 13)); + const auto rot3 = + _mm256_broadcastsi128_si256(_mm_setr_epi8(5, 6, 7, 0, 1, 2, 3, 4, 13, 14, 15, 8, 9, 10, 11, 12)); + const auto rot4 = + _mm256_broadcastsi128_si256(_mm_setr_epi8(4, 5, 6, 7, 0, 1, 2, 3, 12, 13, 14, 15, 8, 9, 10, 11)); + const auto rot5 = + _mm256_broadcastsi128_si256(_mm_setr_epi8(3, 4, 5, 6, 7, 0, 1, 2, 11, 12, 13, 14, 15, 8, 9, 10)); + const auto rot6 = + _mm256_broadcastsi128_si256(_mm_setr_epi8(2, 3, 4, 5, 6, 7, 0, 1, 10, 11, 12, 13, 14, 15, 8, 9)); + const auto rot7 = + _mm256_broadcastsi128_si256(_mm_setr_epi8(1, 2, 3, 4, 5, 6, 7, 0, 9, 10, 11, 12, 13, 14, 15, 8)); + + const auto x2 = xtime(v); + const auto x4 = xtime(x2); + const auto x8 = xtime(x4); + const auto x5 = _mm256_xor_si256(x4, v); + const auto x9 = _mm256_xor_si256(x8, v); + + const auto t01 = _mm256_xor_si256(v, _mm256_shuffle_epi8(v, rot1)); + const auto t23 = _mm256_xor_si256(_mm256_shuffle_epi8(x4, rot2), _mm256_shuffle_epi8(v, rot3)); + const auto t45 = _mm256_xor_si256(_mm256_shuffle_epi8(x8, rot4), _mm256_shuffle_epi8(x5, rot5)); + const auto t67 = _mm256_xor_si256(_mm256_shuffle_epi8(x2, rot6), _mm256_shuffle_epi8(x9, rot7)); + + return _mm256_xor_si256(_mm256_xor_si256(t01, t23), _mm256_xor_si256(t45, t67)); + } + + BOTAN_FN_ISA_AVX2 + WhirlpoolState bswap() const { + // 64-bit byteswap + const auto tbl = + _mm256_broadcastsi128_si256(_mm_setr_epi8(7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8)); + + return WhirlpoolState(_mm256_shuffle_epi8(m_lo, tbl), _mm256_shuffle_epi8(m_hi, tbl)); + } + + BOTAN_FN_ISA_AVX2 + static __m256i xtime(__m256i a) { + const auto poly = _mm256_set1_epi8(0x1D); + const auto shifted = _mm256_add_epi8(a, a); // shifted = a << 1 + // blendv uses the top bit of the mask argument (a) to select between the inputs + return _mm256_blendv_epi8(shifted, _mm256_xor_si256(shifted, poly), a); + } + + __m256i m_lo; + __m256i m_hi; +}; + +// NOLINTEND(portability-simd-intrinsics) + +} // namespace + +} // namespace WhirlpoolAVX2 + +BOTAN_FN_ISA_AVX2 +void Whirlpool::compress_n_avx2(digest_type& digest, std::span input, size_t blocks) { + using WhirlpoolAVX2::WhirlpoolState; + + auto H = WhirlpoolState::load_be(digest.data()); + + for(size_t i = 0; i != blocks; ++i) { + const auto M = WhirlpoolState::load_bytes(input.data() + i * 64); + + auto K = H; + H ^= M; + auto B = H; // B = M ^ K + + K = K.round() ^ 0x4F01B887E8C62318; + B = B.round() ^ K; + + K = K.round() ^ 0x52916F79F5D2A636; + B = B.round() ^ K; + + K = K.round() ^ 0x357B0CA38E9BBC60; + B = B.round() ^ K; + + K = K.round() ^ 0x57FE4B2EC2D7E01D; + B = B.round() ^ K; + + K = K.round() ^ 0xDA4AF09FE5377715; + B = B.round() ^ K; + + K = K.round() ^ 0x856BA0B10A29C958; + B = B.round() ^ K; + + K = K.round() ^ 0x67053ECBF4105DBD; + B = B.round() ^ K; + + K = K.round() ^ 0xD8957DA78B4127E4; + B = B.round() ^ K; + + K = K.round() ^ 0x9E4717DD667CEEFB; + B = B.round() ^ K; + + K = K.round() ^ 0x33835AAD07BF2DCA; + B = B.round() ^ K; + + H ^= B; + } + + H.store_be(digest.data()); +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/hash/whirlpool/whirlpool_avx512/info.txt botan3-3.12.0+dfsg/src/lib/hash/whirlpool/whirlpool_avx512/info.txt --- botan3-3.7.1+dfsg/src/lib/hash/whirlpool/whirlpool_avx512/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/whirlpool/whirlpool_avx512/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,20 @@ + +WHIRLPOOL_AVX512 -> 20260316 + + + +name -> "Whirlpool using AVX512" +brief -> "Whirlpool using AVX512 instructions" + + + +avx512 + + + +x86_64 + + + +cpuid + diff -Nru botan3-3.7.1+dfsg/src/lib/hash/whirlpool/whirlpool_avx512/whirlpool_avx512.cpp botan3-3.12.0+dfsg/src/lib/hash/whirlpool/whirlpool_avx512/whirlpool_avx512.cpp --- botan3-3.7.1+dfsg/src/lib/hash/whirlpool/whirlpool_avx512/whirlpool_avx512.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/hash/whirlpool/whirlpool_avx512/whirlpool_avx512.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,239 @@ +/* +* (C) 2026 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include + +namespace Botan { + +namespace WhirlpoolAVX512 { + +namespace { + +// NOLINTBEGIN(portability-simd-intrinsics) + +class WhirlpoolState final { + public: + BOTAN_FN_ISA_AVX512 + WhirlpoolState() : m_v(_mm512_setzero_si512()) {} + + BOTAN_FN_ISA_AVX512 + explicit WhirlpoolState(__m512i v) : m_v(v) {} + + WhirlpoolState(const WhirlpoolState& other) = default; + WhirlpoolState(WhirlpoolState&& other) = default; + WhirlpoolState& operator=(const WhirlpoolState& other) = default; + WhirlpoolState& operator=(WhirlpoolState&& other) = default; + ~WhirlpoolState() = default; + + // Load 64 bytes of message data + BOTAN_FN_ISA_AVX512 + static WhirlpoolState load_bytes(const uint8_t src[64]) { return WhirlpoolState(_mm512_loadu_si512(src)); } + + BOTAN_FN_ISA_AVX512 + static WhirlpoolState load_be(const uint64_t src[8]) { return WhirlpoolState(_mm512_loadu_si512(src)).bswap(); } + + BOTAN_FN_ISA_AVX512 + void store_be(uint64_t dst[8]) const { _mm512_storeu_si512(dst, bswap().m_v); } + + BOTAN_FN_ISA_AVX512 + inline friend WhirlpoolState operator^(WhirlpoolState a, WhirlpoolState b) { + return WhirlpoolState(_mm512_xor_si512(a.m_v, b.m_v)); + } + + BOTAN_FN_ISA_AVX512 + inline WhirlpoolState& operator^=(WhirlpoolState other) { + m_v = _mm512_xor_si512(m_v, other.m_v); + return *this; + } + + /* + * The Whirlpool 8-bit Sbox is built out of 4-bit sboxes, which can be + * individually computed using pshufb-style shuffles. + */ + BOTAN_FN_ISA_AVX512 + inline WhirlpoolState sub_bytes() const { + const __m512i Ebox = + _mm512_broadcast_i32x4(_mm_setr_epi8(1, 11, 9, 12, 13, 6, 15, 3, 14, 8, 7, 4, 10, 2, 5, 0)); + const __m512i Eibox = + _mm512_broadcast_i32x4(_mm_setr_epi8(15, 0, 13, 7, 11, 14, 5, 10, 9, 2, 12, 1, 3, 4, 8, 6)); + const __m512i Rbox = + _mm512_broadcast_i32x4(_mm_setr_epi8(7, 12, 11, 13, 14, 4, 9, 15, 6, 3, 8, 10, 2, 5, 1, 0)); + + const __m512i lo_mask = _mm512_set1_epi8(0x0F); + + const __m512i lo_nib = _mm512_and_si512(m_v, lo_mask); + const __m512i hi_nib = _mm512_and_si512(_mm512_srli_epi16(m_v, 4), lo_mask); + + // L = Ebox[hi], R = Eibox[lo], T = Rbox[L ^ R] + const __m512i L = _mm512_shuffle_epi8(Ebox, hi_nib); + const __m512i R = _mm512_shuffle_epi8(Eibox, lo_nib); + const __m512i T = _mm512_shuffle_epi8(Rbox, _mm512_xor_si512(L, R)); + + // result = (Ebox[L ^ T] << 4) | Eibox[R ^ T] + const __m512i out_hi = _mm512_shuffle_epi8(Ebox, _mm512_xor_si512(L, T)); + const __m512i out_lo = _mm512_shuffle_epi8(Eibox, _mm512_xor_si512(R, T)); + + return WhirlpoolState(_mm512_or_si512(_mm512_slli_epi16(out_hi, 4), _mm512_and_si512(out_lo, lo_mask))); + } + + /* + * ShiftColumns: column j is cyclically shifted down by j positions. + * + * For output row r, column c: source = row (r - c + 8) % 8, column c. + * Implemented as a single vpermb with a fixed 64-byte permutation. + */ + BOTAN_FN_ISA_AVX512 + inline WhirlpoolState shift_columns() const { + // Register byte for (row r, col c) = r*8 + c + // Source byte = ((r - c + 8) % 8) * 8 + c + alignas(64) static constexpr uint8_t perm[64] = { + // clang-format off + 0*8+0, 7*8+1, 6*8+2, 5*8+3, 4*8+4, 3*8+5, 2*8+6, 1*8+7, + 1*8+0, 0*8+1, 7*8+2, 6*8+3, 5*8+4, 4*8+5, 3*8+6, 2*8+7, + 2*8+0, 1*8+1, 0*8+2, 7*8+3, 6*8+4, 5*8+5, 4*8+6, 3*8+7, + 3*8+0, 2*8+1, 1*8+2, 0*8+3, 7*8+4, 6*8+5, 5*8+6, 4*8+7, + 4*8+0, 3*8+1, 2*8+2, 1*8+3, 0*8+4, 7*8+5, 6*8+6, 5*8+7, + 5*8+0, 4*8+1, 3*8+2, 2*8+3, 1*8+4, 0*8+5, 7*8+6, 6*8+7, + 6*8+0, 5*8+1, 4*8+2, 3*8+3, 2*8+4, 1*8+5, 0*8+6, 7*8+7, + 7*8+0, 6*8+1, 5*8+2, 4*8+3, 3*8+4, 2*8+5, 1*8+6, 0*8+7, + // clang-format on + }; + return WhirlpoolState(_mm512_permutexvar_epi8(_mm512_load_si512(perm), m_v)); + } + + /* + * MixRows: MDS circulant [1, 1, 4, 1, 8, 5, 2, 9] over GF(2^8) mod 0x11D + * + * Since the MDS coefficients are so small we can easily compute them using + * a few xtimes plus additions (aka XOR) + */ + BOTAN_FN_ISA_AVX512 + inline WhirlpoolState mix_rows() const { + /* + Constants for quadword rotations by X bytes. + + Could use _mm512_rol_epi64 for this, but it's oddly slower even though + all documentation suggests that both instructions have the same latency + and throughput. + */ + const __m512i rot1 = + _mm512_broadcast_i32x4(_mm_setr_epi8(7, 0, 1, 2, 3, 4, 5, 6, 15, 8, 9, 10, 11, 12, 13, 14)); + const __m512i rot2 = + _mm512_broadcast_i32x4(_mm_setr_epi8(6, 7, 0, 1, 2, 3, 4, 5, 14, 15, 8, 9, 10, 11, 12, 13)); + const __m512i rot3 = + _mm512_broadcast_i32x4(_mm_setr_epi8(5, 6, 7, 0, 1, 2, 3, 4, 13, 14, 15, 8, 9, 10, 11, 12)); + const __m512i rot4 = + _mm512_broadcast_i32x4(_mm_setr_epi8(4, 5, 6, 7, 0, 1, 2, 3, 12, 13, 14, 15, 8, 9, 10, 11)); + const __m512i rot5 = + _mm512_broadcast_i32x4(_mm_setr_epi8(3, 4, 5, 6, 7, 0, 1, 2, 11, 12, 13, 14, 15, 8, 9, 10)); + const __m512i rot6 = + _mm512_broadcast_i32x4(_mm_setr_epi8(2, 3, 4, 5, 6, 7, 0, 1, 10, 11, 12, 13, 14, 15, 8, 9)); + const __m512i rot7 = + _mm512_broadcast_i32x4(_mm_setr_epi8(1, 2, 3, 4, 5, 6, 7, 0, 9, 10, 11, 12, 13, 14, 15, 8)); + + const __m512i x2 = xtime(m_v); + const __m512i x4 = xtime(x2); + const __m512i x8 = xtime(x4); + const __m512i x5 = _mm512_xor_si512(x4, m_v); + const __m512i x9 = _mm512_xor_si512(x8, m_v); + + const __m512i t01 = _mm512_xor_si512(m_v, _mm512_shuffle_epi8(m_v, rot1)); + const __m512i t23 = _mm512_xor_si512(_mm512_shuffle_epi8(x4, rot2), _mm512_shuffle_epi8(m_v, rot3)); + const __m512i t45 = _mm512_xor_si512(_mm512_shuffle_epi8(x8, rot4), _mm512_shuffle_epi8(x5, rot5)); + const __m512i t67 = _mm512_xor_si512(_mm512_shuffle_epi8(x2, rot6), _mm512_shuffle_epi8(x9, rot7)); + + return WhirlpoolState(_mm512_xor_si512(_mm512_xor_si512(t01, t23), _mm512_xor_si512(t45, t67))); + } + + /* + * Whirlpool round: SubBytes -> ShiftColumns -> MixRows + */ + BOTAN_FN_ISA_AVX512 + inline WhirlpoolState round() const { return sub_bytes().shift_columns().mix_rows(); } + + // Round constant + BOTAN_FN_ISA_AVX512 + static inline WhirlpoolState rc(uint64_t v) { return WhirlpoolState(_mm512_set_epi64(0, 0, 0, 0, 0, 0, 0, v)); } + + private: + BOTAN_FN_ISA_AVX512 + WhirlpoolState bswap() const { + const __m512i tbl = _mm512_broadcast_i32x4(_mm_set_epi8(8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7)); + + return WhirlpoolState(_mm512_shuffle_epi8(m_v, tbl)); + } + + // Packed 16-wide doubling in GF(2^8) mod 0x11D + BOTAN_FN_ISA_AVX512 + static __m512i xtime(__m512i a) { + const __m512i poly = _mm512_set1_epi8(0x1D); + const __mmask64 top_bits = _mm512_movepi8_mask(a); + const __m512i shifted = _mm512_add_epi8(a, a); // no 8-bit shift in AVX512 + return _mm512_mask_blend_epi8(top_bits, shifted, _mm512_xor_si512(shifted, poly)); + } + + __m512i m_v; +}; + +// NOLINTEND(portability-simd-intrinsics) + +} // namespace + +} // namespace WhirlpoolAVX512 + +BOTAN_FN_ISA_AVX512 +void Whirlpool::compress_n_avx512(digest_type& digest, std::span input, size_t blocks) { + using WhirlpoolAVX512::WhirlpoolState; + + auto H = WhirlpoolState::load_be(digest.data()); + + for(size_t i = 0; i != blocks; ++i) { + const auto M = WhirlpoolState::load_bytes(input.data() + i * 64); + + auto K = H; + H ^= M; + auto B = H; // B = M ^ K + + K = K.round() ^ WhirlpoolState::rc(0x4F01B887E8C62318); + B = B.round() ^ K; + + K = K.round() ^ WhirlpoolState::rc(0x52916F79F5D2A636); + B = B.round() ^ K; + + K = K.round() ^ WhirlpoolState::rc(0x357B0CA38E9BBC60); + B = B.round() ^ K; + + K = K.round() ^ WhirlpoolState::rc(0x57FE4B2EC2D7E01D); + B = B.round() ^ K; + + K = K.round() ^ WhirlpoolState::rc(0xDA4AF09FE5377715); + B = B.round() ^ K; + + K = K.round() ^ WhirlpoolState::rc(0x856BA0B10A29C958); + B = B.round() ^ K; + + K = K.round() ^ WhirlpoolState::rc(0x67053ECBF4105DBD); + B = B.round() ^ K; + + K = K.round() ^ WhirlpoolState::rc(0xD8957DA78B4127E4); + B = B.round() ^ K; + + K = K.round() ^ WhirlpoolState::rc(0x9E4717DD667CEEFB); + B = B.round() ^ K; + + K = K.round() ^ WhirlpoolState::rc(0x33835AAD07BF2DCA); + B = B.round() ^ K; + + H ^= B; + } + + H.store_be(digest.data()); +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/kdf/hkdf/hkdf.cpp botan3-3.12.0+dfsg/src/lib/kdf/hkdf/hkdf.cpp --- botan3-3.7.1+dfsg/src/lib/kdf/hkdf/hkdf.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/kdf/hkdf/hkdf.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,10 +9,11 @@ #include -#include +#include +#include #include #include -#include +#include namespace Botan { @@ -28,8 +29,8 @@ std::span secret, std::span salt, std::span label) const { - HKDF_Extract extract(m_prf->new_object()); - HKDF_Expand expand(m_prf->new_object()); + const HKDF_Extract extract(m_prf->new_object()); + const HKDF_Expand expand(m_prf->new_object()); secure_vector prk(m_prf->output_length()); extract.derive_key(prk, secret, salt, {}); @@ -49,7 +50,7 @@ std::span salt, std::span label) const { const size_t prf_output_len = m_prf->output_length(); - BOTAN_ARG_CHECK(key.size() <= prf_output_len, "HKDF-Extract maximum output length exceeeded"); + BOTAN_ARG_CHECK(key.size() <= prf_output_len, "HKDF-Extract maximum output length exceeded"); BOTAN_ARG_CHECK(label.empty(), "HKDF-Extract does not support a label input"); if(key.empty()) { @@ -85,7 +86,7 @@ std::span salt, std::span label) const { const auto prf_output_length = m_prf->output_length(); - BOTAN_ARG_CHECK(key.size() <= prf_output_length * 255, "HKDF-Expand maximum output length exceeeded"); + BOTAN_ARG_CHECK(key.size() <= prf_output_length * 255, "HKDF-Expand maximum output length exceeded"); if(key.empty()) { return; @@ -124,11 +125,11 @@ BOTAN_ARG_CHECK(label.size() <= 0xFF, "HKDF-Expand-Label label too long"); BOTAN_ARG_CHECK(hash_val.size() <= 0xFF, "HKDF-Expand-Label hash too long"); - HKDF_Expand hkdf(MessageAuthenticationCode::create_or_throw(fmt("HMAC({})", hash_fn))); + const HKDF_Expand hkdf(MessageAuthenticationCode::create_or_throw(fmt("HMAC({})", hash_fn))); const auto prefix = concat>(store_be(static_cast(length)), store_be(static_cast(label.size())), - std::span{cast_char_ptr_to_uint8(label.data()), label.size()}, + as_span_of_bytes(label), store_be(static_cast(hash_val.size()))); /* diff -Nru botan3-3.7.1+dfsg/src/lib/kdf/info.txt botan3-3.12.0+dfsg/src/lib/kdf/info.txt --- botan3-3.7.1+dfsg/src/lib/kdf/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/kdf/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,4 +1,7 @@ +KDF -> 20250528 + +# TODO(Botan4) remove this macro KDF_BASE -> 20131128 diff -Nru botan3-3.7.1+dfsg/src/lib/kdf/kdf.cpp botan3-3.12.0+dfsg/src/lib/kdf/kdf.cpp --- botan3-3.7.1+dfsg/src/lib/kdf/kdf.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/kdf/kdf.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,10 +7,12 @@ #include +#include #include #include #include #include +#include #include #if defined(BOTAN_HAS_HKDF) @@ -210,4 +212,9 @@ return probe_providers_of(algo_spec); } +//static +std::span KDF::_as_span(std::string_view s) { + return as_span_of_bytes(s); +} + } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/kdf/kdf.h botan3-3.12.0+dfsg/src/lib/kdf/kdf.h --- botan3-3.7.1+dfsg/src/lib/kdf/kdf.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/kdf/kdf.h 2026-05-07 01:38:28.000000000 +0000 @@ -10,9 +10,9 @@ #define BOTAN_KDF_BASE_H_ #include -#include -#include #include +#include +#include #include #include #include @@ -22,7 +22,7 @@ /** * Key Derivation Function */ -class BOTAN_PUBLIC_API(2, 0) KDF { +class BOTAN_PUBLIC_API(2, 0) KDF /* NOLINT(*-special-member-functions*) */ { public: virtual ~KDF() = default; @@ -111,10 +111,7 @@ std::span secret, std::string_view salt = "", std::string_view label = "") const { - return derive_key(key_len, - secret, - {cast_char_ptr_to_uint8(salt.data()), salt.length()}, - {cast_char_ptr_to_uint8(label.data()), label.length()}); + return derive_key(key_len, secret, _as_span(salt), _as_span(label)); } /** @@ -165,7 +162,7 @@ const uint8_t salt[], size_t salt_len, std::string_view label = "") const { - return derive_key(key_len, secret, {salt, salt_len}, {cast_char_ptr_to_uint8(label.data()), label.size()}); + return derive_key(key_len, secret, {salt, salt_len}, _as_span(label)); } /** @@ -184,10 +181,7 @@ size_t secret_len, std::string_view salt = "", std::string_view label = "") const { - return derive_key(key_len, - {secret, secret_len}, - {cast_char_ptr_to_uint8(salt.data()), salt.length()}, - {cast_char_ptr_to_uint8(label.data()), label.length()}); + return derive_key(key_len, {secret, secret_len}, _as_span(salt), _as_span(label)); } /** @@ -202,7 +196,7 @@ std::array derive_key(std::span secret, std::span salt = {}, std::span label = {}) { - std::array key; + std::array key{}; perform_kdf(key, secret, salt, label); return key; } @@ -219,7 +213,7 @@ std::array derive_key(std::span secret, std::span salt = {}, std::string_view label = "") { - return derive_key(secret, salt, {cast_char_ptr_to_uint8(label.data()), label.size()}); + return derive_key(secret, salt, _as_span(label)); } /** @@ -234,9 +228,7 @@ std::array derive_key(std::span secret, std::string_view salt = "", std::string_view label = "") { - return derive_key(secret, - {cast_char_ptr_to_uint8(salt.data()), salt.size()}, - {cast_char_ptr_to_uint8(label.data()), label.size()}); + return derive_key(secret, _as_span(salt), _as_span(label)); } /** @@ -265,6 +257,9 @@ std::span secret, std::span salt, std::span label) const = 0; + + private: + static std::span _as_span(std::string_view s); }; /** @@ -277,16 +272,11 @@ BOTAN_DEPRECATED("Use KDF::create") inline KDF* get_kdf(std::string_view algo_spec) { - auto kdf = KDF::create(algo_spec); - if(kdf) { - return kdf.release(); - } - if(algo_spec == "Raw") { return nullptr; } - throw Algorithm_Not_Found(algo_spec); + return KDF::create_or_throw(algo_spec).release(); } } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/kdf/kdf1/kdf1.cpp botan3-3.12.0+dfsg/src/lib/kdf/kdf1/kdf1.cpp --- botan3-3.7.1+dfsg/src/lib/kdf/kdf1/kdf1.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/kdf/kdf1/kdf1.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,7 +8,7 @@ #include -#include +#include #include namespace Botan { @@ -30,7 +30,7 @@ } const size_t hash_output_len = m_hash->output_length(); - BOTAN_ARG_CHECK(key.size() <= hash_output_len, "KDF1 maximum output length exceeeded"); + BOTAN_ARG_CHECK(key.size() <= hash_output_len, "KDF1 maximum output length exceeded"); m_hash->update(secret); m_hash->update(label); diff -Nru botan3-3.7.1+dfsg/src/lib/kdf/kdf1_iso18033/kdf1_iso18033.cpp botan3-3.12.0+dfsg/src/lib/kdf/kdf1_iso18033/kdf1_iso18033.cpp --- botan3-3.7.1+dfsg/src/lib/kdf/kdf1_iso18033/kdf1_iso18033.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/kdf/kdf1_iso18033/kdf1_iso18033.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,10 +8,9 @@ #include -#include #include +#include #include -#include namespace Botan { @@ -29,7 +28,7 @@ // This KDF uses a 32-bit counter for the hash blocks, initialized at 0. // It will wrap around after 2^32 iterations which limits the theoretically // possible output to 2^32 blocks. - BOTAN_ARG_CHECK(blocks_required <= 0xFFFFFFFF, "KDF1-18033 maximum output length exceeeded"); + BOTAN_ARG_CHECK(blocks_required <= 0xFFFFFFFF, "KDF1-18033 maximum output length exceeded"); BufferStuffer k(key); for(uint32_t counter = 0; !k.full(); ++counter) { diff -Nru botan3-3.7.1+dfsg/src/lib/kdf/kdf2/kdf2.cpp botan3-3.12.0+dfsg/src/lib/kdf/kdf2/kdf2.cpp --- botan3-3.7.1+dfsg/src/lib/kdf/kdf2/kdf2.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/kdf/kdf2/kdf2.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,10 +8,9 @@ #include -#include #include +#include #include -#include namespace Botan { @@ -37,7 +36,7 @@ // This KDF uses a 32-bit counter for the hash blocks, initialized at 1. // It will wrap around after 2^32 - 1 iterations limiting the theoretically // possible output to 2^32 - 1 blocks. - BOTAN_ARG_CHECK(blocks_required <= 0xFFFFFFFE, "KDF2 maximum output length exceeeded"); + BOTAN_ARG_CHECK(blocks_required <= 0xFFFFFFFE, "KDF2 maximum output length exceeded"); BufferStuffer k(key); for(uint32_t counter = 1; !k.full(); ++counter) { diff -Nru botan3-3.7.1+dfsg/src/lib/kdf/prf_tls/prf_tls.cpp botan3-3.12.0+dfsg/src/lib/kdf/prf_tls/prf_tls.cpp --- botan3-3.7.1+dfsg/src/lib/kdf/prf_tls/prf_tls.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/kdf/prf_tls/prf_tls.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,8 +9,10 @@ #include #include +#include +#include +#include #include -#include namespace Botan { diff -Nru botan3-3.7.1+dfsg/src/lib/kdf/prf_x942/prf_x942.cpp botan3-3.12.0+dfsg/src/lib/kdf/prf_x942/prf_x942.cpp --- botan3-3.7.1+dfsg/src/lib/kdf/prf_x942/prf_x942.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/kdf/prf_x942/prf_x942.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -11,8 +11,9 @@ #include #include #include +#include +#include #include -#include namespace Botan { @@ -45,7 +46,10 @@ // This KDF uses a 32-bit counter for the hash blocks, initialized at 1. // It will wrap around after 2^32 - 1 iterations limiting the theoretically // possible output to 2^32 - 1 blocks. - BOTAN_ARG_CHECK(blocks_required <= 0xFFFFFFFE, "X942_PRF maximum output length exceeeded"); + BOTAN_ARG_CHECK(blocks_required <= 0xFFFFFFFE, "X942_PRF maximum output length exceeded"); + + // The key length in bits is encoded as a uint32_t in the DER output + BOTAN_ARG_CHECK(key.size() <= 0x1FFFFFFF, "X942_PRF output length too large for DER encoding"); auto hash = HashFunction::create("SHA-1"); const auto in = concat>(label, salt); @@ -78,7 +82,7 @@ if(k.remaining_capacity() >= sha1_output_bytes) { hash->final(k.next(sha1_output_bytes)); } else { - std::array h; + std::array h{}; hash->final(h); k.append(std::span{h}.first(k.remaining_capacity())); } diff -Nru botan3-3.7.1+dfsg/src/lib/kdf/sp800_108/sp800_108.cpp botan3-3.12.0+dfsg/src/lib/kdf/sp800_108/sp800_108.cpp --- botan3-3.7.1+dfsg/src/lib/kdf/sp800_108/sp800_108.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/kdf/sp800_108/sp800_108.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,11 +9,11 @@ #include -#include #include +#include +#include #include #include -#include #include @@ -21,7 +21,7 @@ namespace { -class CounterParams { +class CounterParams final { public: constexpr static void validate_bit_lengths(size_t counter_bits, size_t output_length_bits) { BOTAN_ARG_CHECK(counter_bits % 8 == 0 && counter_bits <= 32, @@ -30,10 +30,10 @@ "SP.800-108 output length encoding may be one of {8, 16, 24, 32} only"); } - constexpr static CounterParams create_or_throw(size_t output_bytes, - size_t output_length_bits, - size_t counter_bits, - size_t prf_output_bytes) { + static CounterParams create_or_throw(size_t output_bytes, + size_t output_length_bits, + size_t counter_bits, + size_t prf_output_bytes) { // The maximum legal output bit length is limited by the requested encoding // bit length of the "L" field. BOTAN_ARG_CHECK(static_cast(output_bytes) * 8 <= std::numeric_limits::max(), @@ -52,12 +52,8 @@ const auto max_blocks = (uint64_t(1) << counter_bits) - 1; BOTAN_ARG_CHECK(blocks_required < max_blocks, "SP.800-108 output size too large"); - CounterParams out; - out.m_output_length_bits = output_bits; - out.m_output_length_encoding_bytes = output_length_bits / 8; - out.m_counter_bytes = counter_bits / 8; - out.m_blocks_required = static_cast(blocks_required); - return out; + return CounterParams( + output_bits, output_length_bits / 8, counter_bits / 8, static_cast(blocks_required)); } template , std::span> Fn> @@ -71,7 +67,14 @@ } private: - constexpr CounterParams() = default; + CounterParams(uint32_t output_length_bits, + size_t output_length_encoding_bytes, + size_t counter_bytes, + uint32_t blocks_required) : + m_output_length_bits(output_length_bits), + m_output_length_encoding_bytes(output_length_encoding_bytes), + m_counter_bytes(counter_bytes), + m_blocks_required(blocks_required) {} private: uint32_t m_output_length_bits; diff -Nru botan3-3.7.1+dfsg/src/lib/kdf/sp800_56a/sp800_56c_one_step.cpp botan3-3.12.0+dfsg/src/lib/kdf/sp800_56a/sp800_56c_one_step.cpp --- botan3-3.7.1+dfsg/src/lib/kdf/sp800_56a/sp800_56c_one_step.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/kdf/sp800_56a/sp800_56c_one_step.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -12,7 +12,9 @@ #include #include +#include #include +#include #include #include @@ -33,12 +35,19 @@ std::span fixed_info, HashOrMacType& hash_or_mac, const std::function& init_h_callback) { - size_t l = output_buffer.size() * 8; // 1. If L > 0, then set reps = ceil(L / H_outputBits); otherwise, // output an error indicator and exit this process without // performing the remaining actions (i.e., omit steps 2 through 8). - BOTAN_ARG_CHECK(l > 0, "Zero KDM output length"); - size_t reps = ceil_division(l, hash_or_mac.output_length() * 8); + // + // We follow the usual convention within the library that a KDF request for + // zero bytes is valid and, exactly as requested, outputs nothing. + if(output_buffer.empty()) { + return; + } + + const size_t output_len = output_buffer.size(); + const size_t h_output_len = hash_or_mac.output_length(); + const size_t reps = ceil_division(output_len, h_output_len); // 2. If reps > (2^32 − 1), then output an error indicator and exit this // process without performing the remaining actions @@ -55,11 +64,8 @@ // without performing any of the remaining actions (i.e., omit // steps 5 through 8). => SHA3 and KMAC are unlimited - // 5. Initialize Result(0) as an empty bit string - // (i.e., the null string). - secure_vector result; - - // 6. For i = 1 to reps, do the following: + // 5-7. Derive keying material directly into the output buffer. + BufferStuffer k(output_buffer); for(size_t i = 1; i <= reps; i++) { // 6.1. Increment counter by 1. counter++; @@ -71,14 +77,18 @@ hash_or_mac.update_be(counter); hash_or_mac.update(z); hash_or_mac.update(fixed_info); - auto k_i = hash_or_mac.final(); // 6.3. Set Result(i) = Result(i−1) || K(i). - result.insert(result.end(), k_i.begin(), k_i.end()); + if(k.remaining_capacity() >= h_output_len) { + hash_or_mac.final(k.next(h_output_len)); + } else { + // Needs truncation so can't write directly to the output buffer + const auto k_i = hash_or_mac.final(); + k.append(std::span{k_i}.first(k.remaining_capacity())); + } } - // 7. Set DerivedKeyingMaterial equal to the leftmost L bits of Result(reps). - copy_mem(output_buffer, std::span(result).subspan(0, output_buffer.size())); + BOTAN_ASSERT_NOMSG(k.full()); } } // namespace @@ -87,7 +97,7 @@ std::span secret, std::span salt, std::span label) const { - BOTAN_ARG_CHECK(salt.empty(), "SP800_56A_Hash does not support a non-empty salt"); + BOTAN_ARG_CHECK(salt.empty(), "SP800-56C KDF with hash does not support using a salt parameter"); kdm_internal(key, secret, label, *m_hash, [](HashFunction&) { /* NOP */ }); } diff -Nru botan3-3.7.1+dfsg/src/lib/kdf/xmd/xmd.cpp botan3-3.12.0+dfsg/src/lib/kdf/xmd/xmd.cpp --- botan3-3.7.1+dfsg/src/lib/kdf/xmd/xmd.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/kdf/xmd/xmd.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -59,7 +60,7 @@ for(;;) { const size_t produced = std::min(output.size(), hash_output_size); - copy_mem(&output[0], b_i.data(), produced); + copy_mem(output.data(), b_i.data(), produced); output = output.subspan(produced); if(output.empty()) { diff -Nru botan3-3.7.1+dfsg/src/lib/kdf/xmd/xmd.h botan3-3.12.0+dfsg/src/lib/kdf/xmd/xmd.h --- botan3-3.7.1+dfsg/src/lib/kdf/xmd/xmd.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/kdf/xmd/xmd.h 2026-05-07 01:38:28.000000000 +0000 @@ -24,17 +24,6 @@ std::span input, std::span domain_sep); -inline void expand_message_xmd(std::string_view hash_fn, - std::span output, - std::string_view input_str, - std::string_view domain_sep_str) { - std::span input(reinterpret_cast(input_str.data()), input_str.size()); - - std::span domain_sep(reinterpret_cast(domain_sep_str.data()), domain_sep_str.size()); - - expand_message_xmd(hash_fn, output, input, domain_sep); -} - } // namespace Botan #endif diff -Nru botan3-3.7.1+dfsg/src/lib/mac/blake2mac/blake2bmac.h botan3-3.12.0+dfsg/src/lib/mac/blake2mac/blake2bmac.h --- botan3-3.7.1+dfsg/src/lib/mac/blake2mac/blake2bmac.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/mac/blake2mac/blake2bmac.h 2026-05-07 01:38:28.000000000 +0000 @@ -21,9 +21,6 @@ public: explicit BLAKE2bMAC(size_t output_bits = 512); - BLAKE2bMAC(const BLAKE2bMAC&) = delete; - BLAKE2bMAC& operator=(const BLAKE2bMAC&) = delete; - std::string name() const override { return m_blake.name(); } size_t output_length() const override { return m_blake.output_length(); } diff -Nru botan3-3.7.1+dfsg/src/lib/mac/cmac/cmac.cpp botan3-3.12.0+dfsg/src/lib/mac/cmac/cmac.cpp --- botan3-3.7.1+dfsg/src/lib/mac/cmac/cmac.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/mac/cmac/cmac.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,9 +8,10 @@ #include #include +#include +#include #include #include -#include namespace Botan { @@ -109,7 +110,7 @@ * CMAC Constructor */ CMAC::CMAC(std::unique_ptr cipher) : m_cipher(std::move(cipher)), m_block_size(m_cipher->block_size()) { - if(poly_double_supported_size(m_block_size) == false) { + if(!poly_double_supported_size(m_block_size)) { throw Invalid_Argument(fmt("CMAC cannot use the {} bit cipher {}", m_block_size * 8, m_cipher->name())); } diff -Nru botan3-3.7.1+dfsg/src/lib/mac/cmac/cmac.h botan3-3.12.0+dfsg/src/lib/mac/cmac/cmac.h --- botan3-3.7.1+dfsg/src/lib/mac/cmac/cmac.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/mac/cmac/cmac.h 2026-05-07 01:38:28.000000000 +0000 @@ -34,13 +34,10 @@ */ explicit CMAC(std::unique_ptr cipher); - CMAC(const CMAC&) = delete; - CMAC& operator=(const CMAC&) = delete; - private: - void add_data(std::span) override; - void final_result(std::span) override; - void key_schedule(std::span) override; + void add_data(std::span input) override; + void final_result(std::span output) override; + void key_schedule(std::span key) override; std::unique_ptr m_cipher; secure_vector m_buffer, m_state, m_B, m_P; diff -Nru botan3-3.7.1+dfsg/src/lib/mac/gmac/gmac.cpp botan3-3.12.0+dfsg/src/lib/mac/gmac/gmac.cpp --- botan3-3.7.1+dfsg/src/lib/mac/gmac/gmac.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/mac/gmac/gmac.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -35,6 +36,10 @@ return fmt("GMAC({})", m_cipher->name()); } +std::string GMAC::provider() const { + return m_ghash->provider(); +} + size_t GMAC::output_length() const { return GCM_BS; } @@ -56,7 +61,7 @@ } void GMAC::start_msg(std::span nonce) { - secure_vector y0(GCM_BS); + std::array y0 = {0}; if(nonce.size() == 12) { copy_mem(y0.data(), nonce.data(), nonce.size()); @@ -65,9 +70,8 @@ m_ghash->nonce_hash(y0, nonce); } - secure_vector m_enc_y0(GCM_BS); - m_cipher->encrypt(y0.data(), m_enc_y0.data()); - m_ghash->start(m_enc_y0); + m_cipher->encrypt(y0.data()); + m_ghash->start(y0); m_initialized = true; } @@ -75,12 +79,12 @@ // This ensures the GMAC computation has been initialized with a fresh // nonce. The aim of this check is to prevent developers from re-using // nonces (and potential nonce-reuse attacks). - if(m_initialized == false) { + if(!m_initialized) { throw Invalid_State("GMAC was not used with a fresh nonce"); } m_ghash->final(mac.first(output_length())); - m_ghash->set_key(m_H); + m_ghash->reset_associated_data(); } std::unique_ptr GMAC::new_object() const { diff -Nru botan3-3.7.1+dfsg/src/lib/mac/gmac/gmac.h botan3-3.12.0+dfsg/src/lib/mac/gmac/gmac.h --- botan3-3.7.1+dfsg/src/lib/mac/gmac/gmac.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/mac/gmac/gmac.h 2026-05-07 01:38:28.000000000 +0000 @@ -26,6 +26,7 @@ public: void clear() override; std::string name() const override; + std::string provider() const override; size_t output_length() const override; std::unique_ptr new_object() const override; @@ -40,14 +41,16 @@ */ explicit GMAC(std::unique_ptr cipher); - GMAC(const GMAC&) = delete; - GMAC& operator=(const GMAC&) = delete; + GMAC(const GMAC& other) = delete; + GMAC(GMAC&& other) = default; + GMAC& operator=(const GMAC& other) = delete; + GMAC& operator=(GMAC&& other) = default; ~GMAC() override; private: - void add_data(std::span) override; - void final_result(std::span) override; + void add_data(std::span input) override; + void final_result(std::span output) override; void start_msg(std::span nonce) override; void key_schedule(std::span key) override; diff -Nru botan3-3.7.1+dfsg/src/lib/mac/hmac/hmac.cpp botan3-3.12.0+dfsg/src/lib/mac/hmac/hmac.cpp --- botan3-3.7.1+dfsg/src/lib/mac/hmac/hmac.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/mac/hmac/hmac.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,6 +8,7 @@ #include +#include #include #include @@ -35,7 +36,7 @@ Key_Length_Specification HMAC::key_spec() const { // Support very long lengths for things like PBKDF2 and the TLS PRF - return Key_Length_Specification(0, 4096); + return Key_Length_Specification(0, 8192); } size_t HMAC::output_length() const { diff -Nru botan3-3.7.1+dfsg/src/lib/mac/hmac/hmac.h botan3-3.12.0+dfsg/src/lib/mac/hmac/hmac.h --- botan3-3.7.1+dfsg/src/lib/mac/hmac/hmac.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/mac/hmac/hmac.h 2026-05-07 01:38:28.000000000 +0000 @@ -33,13 +33,10 @@ */ explicit HMAC(std::unique_ptr hash); - HMAC(const HMAC&) = delete; - HMAC& operator=(const HMAC&) = delete; - private: - void add_data(std::span) override; - void final_result(std::span) override; - void key_schedule(std::span) override; + void add_data(std::span input) override; + void final_result(std::span output) override; + void key_schedule(std::span key) override; std::unique_ptr m_hash; secure_vector m_ikey, m_okey; diff -Nru botan3-3.7.1+dfsg/src/lib/mac/kmac/kmac.cpp botan3-3.12.0+dfsg/src/lib/mac/kmac/kmac.cpp --- botan3-3.7.1+dfsg/src/lib/mac/kmac/kmac.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/mac/kmac/kmac.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -70,7 +70,10 @@ void KMAC::final_result(std::span output) { assert_key_material_set(); - std::array encoded_output_length_buffer; + if(!m_message_started) { + start(); + } + std::array encoded_output_length_buffer{}; m_cshake->update(keccak_int_right_encode(encoded_output_length_buffer, m_output_bit_length)); m_cshake->output(output.first(output_length())); m_cshake->clear(); diff -Nru botan3-3.7.1+dfsg/src/lib/mac/kmac/kmac.h botan3-3.12.0+dfsg/src/lib/mac/kmac/kmac.h --- botan3-3.7.1+dfsg/src/lib/mac/kmac/kmac.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/mac/kmac/kmac.h 2026-05-07 01:38:28.000000000 +0000 @@ -16,12 +16,12 @@ class cSHAKE_XOF; -class KMAC : public MessageAuthenticationCode { +class KMAC /* NOLINT(*-special-member-functions*) */ : public MessageAuthenticationCode { protected: KMAC(std::unique_ptr cshake, size_t output_bit_length); public: - virtual ~KMAC(); + ~KMAC() override; KMAC(const KMAC&) = delete; KMAC& operator=(const KMAC&) = delete; @@ -35,9 +35,9 @@ private: void start_msg(std::span nonce) final; - void add_data(std::span) final; - void final_result(std::span) final; - void key_schedule(std::span) final; + void add_data(std::span input) final; + void final_result(std::span output) final; + void key_schedule(std::span key) final; private: size_t m_output_bit_length; @@ -52,7 +52,7 @@ */ class KMAC128 final : public KMAC { public: - KMAC128(size_t output_bit_length); + explicit KMAC128(size_t output_bit_length); std::string name() const override; std::unique_ptr new_object() const override; }; @@ -62,7 +62,7 @@ */ class KMAC256 final : public KMAC { public: - KMAC256(size_t output_bit_length); + explicit KMAC256(size_t output_bit_length); std::string name() const override; std::unique_ptr new_object() const override; }; diff -Nru botan3-3.7.1+dfsg/src/lib/mac/mac.cpp botan3-3.12.0+dfsg/src/lib/mac/mac.cpp --- botan3-3.7.1+dfsg/src/lib/mac/mac.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/mac/mac.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,7 +8,6 @@ #include #include -#include #include #include diff -Nru botan3-3.7.1+dfsg/src/lib/mac/mac.h botan3-3.12.0+dfsg/src/lib/mac/mac.h --- botan3-3.7.1+dfsg/src/lib/mac/mac.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/mac/mac.h 2026-05-07 01:38:28.000000000 +0000 @@ -1,5 +1,5 @@ /* -* Base class for message authentiction codes +* Base class for message authentication codes * (C) 1999-2007 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) @@ -47,8 +47,6 @@ */ static std::vector providers(std::string_view algo_spec); - ~MessageAuthenticationCode() override = default; - /** * Prepare for processing a message under the specified nonce * diff -Nru botan3-3.7.1+dfsg/src/lib/mac/poly1305/poly1305.cpp botan3-3.12.0+dfsg/src/lib/mac/poly1305/poly1305.cpp --- botan3-3.7.1+dfsg/src/lib/mac/poly1305/poly1305.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/mac/poly1305/poly1305.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -3,107 +3,274 @@ * in https://github.com/floodyberry/poly1305-donna * * (C) 2014 Andrew Moon -* (C) 2014 Jack Lloyd +* (C) 2014,2025,2026 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ #include +#include #include #include #include -#include -#include + +#if defined(BOTAN_HAS_POLY1305_AVX2) || defined(BOTAN_HAS_POLY1305_AVX512) + #include +#endif namespace Botan { namespace { +// State layout: pad || accum || r || r^2 || r^3 || ... || r^n +// This ordering allows extending with more powers of r at the end +constexpr size_t PAD_BASE = 0; // pad[0..1] +constexpr size_t H_BASE = 2; // h[0..2] (accumulator) +constexpr size_t R_BASE = 5; // r^1[0..2], r^2[3..5], r^3[6..8], etc. + +// Multiply two values in radix 2^44 representation mod (2^130 - 5) +// h = a * b mod p +BOTAN_FORCE_INLINE void poly1305_mul_44(uint64_t& h0, + uint64_t& h1, + uint64_t& h2, + uint64_t a0, + uint64_t a1, + uint64_t a2, + uint64_t b0, + uint64_t b1, + uint64_t b2) { + constexpr uint64_t M44 = 0xFFFFFFFFFFF; + constexpr uint64_t M42 = 0x3FFFFFFFFFF; + +#if !defined(BOTAN_TARGET_HAS_NATIVE_UINT128) + typedef donna128 uint128_t; +#endif + + const uint64_t s1 = b1 * 20; + const uint64_t s2 = b2 * 20; + + const uint128_t d0 = uint128_t(a0) * b0 + uint128_t(a1) * s2 + uint128_t(a2) * s1; + const uint64_t c0 = carry_shift(d0, 44); + + const uint128_t d1 = uint128_t(a0) * b1 + uint128_t(a1) * b0 + uint128_t(a2) * s2 + c0; + const uint64_t c1 = carry_shift(d1, 44); + + const uint128_t d2 = uint128_t(a0) * b2 + uint128_t(a1) * b1 + uint128_t(a2) * b0 + c1; + const uint64_t c2 = carry_shift(d2, 42); + + h0 = (d0 & M44) + c2 * 5; + h1 = (d1 & M44) + (h0 >> 44); + h0 &= M44; + h2 = d2 & M42; +} + +// Extend powers of r from current max to target +void poly1305_extend_powers(secure_vector& X, size_t target_powers) { + const size_t current_powers = (X.size() - 5) / 3; + + if(current_powers >= target_powers) { + return; + } + + // Load r^1 for multiplication + const uint64_t r0 = X[R_BASE + 0]; + const uint64_t r1 = X[R_BASE + 1]; + const uint64_t r2 = X[R_BASE + 2]; + + X.resize(5 + target_powers * 3); + + // Compute r^(current+1) through r^target + for(size_t i = current_powers + 1; i <= target_powers; ++i) { + const size_t offset = R_BASE + (i - 1) * 3; + poly1305_mul_44( + X[offset + 0], X[offset + 1], X[offset + 2], X[offset - 3], X[offset - 2], X[offset - 1], r0, r1, r2); + } +} + +// Initialize Poly1305 state and precompute powers of r void poly1305_init(secure_vector& X, const uint8_t key[32]) { - /* r &= 0xffffffc0ffffffc0ffffffc0fffffff */ + X.clear(); + X.reserve(2 + 3 + 2 * 3); + X.resize(2 + 3 + 3); + + /* Save pad for later (first 2 slots) */ + X[PAD_BASE + 0] = load_le(key, 2); + X[PAD_BASE + 1] = load_le(key, 3); + + /* h = 0 (accumulator, next 3 slots) */ + X[H_BASE + 0] = 0; + X[H_BASE + 1] = 0; + X[H_BASE + 2] = 0; + + /* r &= 0xffffffc0ffffffc0ffffffc0fffffff (clamping) */ const uint64_t t0 = load_le(key, 0); const uint64_t t1 = load_le(key, 1); - X[0] = (t0) & 0xffc0fffffff; - X[1] = ((t0 >> 44) | (t1 << 20)) & 0xfffffc0ffff; - X[2] = ((t1 >> 24)) & 0x00ffffffc0f; - - /* h = 0 */ - X[3] = 0; - X[4] = 0; - X[5] = 0; - - /* save pad for later */ - X[6] = load_le(key, 2); - X[7] = load_le(key, 3); + const uint64_t r0 = (t0) & 0xffc0fffffff; + const uint64_t r1 = ((t0 >> 44) | (t1 << 20)) & 0xfffffc0ffff; + const uint64_t r2 = ((t1 >> 24)) & 0x00ffffffc0f; + + // Store r^1 + X[R_BASE + 0] = r0; + X[R_BASE + 1] = r1; + X[R_BASE + 2] = r2; + + poly1305_extend_powers(X, 2); } -void poly1305_blocks(secure_vector& X, const uint8_t* m, size_t blocks, bool is_final = false) { +// Process a single block: h = (h + m) * r mod p +BOTAN_FORCE_INLINE void poly1305_block_single(uint64_t& h0, + uint64_t& h1, + uint64_t& h2, + uint64_t r0, + uint64_t r1, + uint64_t r2, + uint64_t s1, + uint64_t s2, + const uint8_t* m, + uint64_t hibit) { + constexpr uint64_t M44 = 0xFFFFFFFFFFF; + constexpr uint64_t M42 = 0x3FFFFFFFFFF; + #if !defined(BOTAN_TARGET_HAS_NATIVE_UINT128) typedef donna128 uint128_t; #endif - const uint64_t hibit = is_final ? 0 : (static_cast(1) << 40); /* 1 << 128 */ + const uint64_t t0 = load_le(m, 0); + const uint64_t t1 = load_le(m, 1); - const uint64_t r0 = X[0]; - const uint64_t r1 = X[1]; - const uint64_t r2 = X[2]; - - const uint64_t M44 = 0xFFFFFFFFFFF; - const uint64_t M42 = 0x3FFFFFFFFFF; - - uint64_t h0 = X[3 + 0]; - uint64_t h1 = X[3 + 1]; - uint64_t h2 = X[3 + 2]; + h0 += (t0 & M44); + h1 += ((t0 >> 44) | (t1 << 20)) & M44; + h2 += ((t1 >> 24) & M42) | hibit; - const uint64_t s1 = r1 * 20; - const uint64_t s2 = r2 * 20; + const uint128_t d0 = uint128_t(h0) * r0 + uint128_t(h1) * s2 + uint128_t(h2) * s1; + const uint64_t c0 = carry_shift(d0, 44); + + const uint128_t d1 = uint128_t(h0) * r1 + uint128_t(h1) * r0 + uint128_t(h2) * s2 + c0; + const uint64_t c1 = carry_shift(d1, 44); - for(size_t i = 0; i != blocks; ++i) { - const uint64_t t0 = load_le(m, 0); - const uint64_t t1 = load_le(m, 1); + const uint128_t d2 = uint128_t(h0) * r2 + uint128_t(h1) * r1 + uint128_t(h2) * r0 + c1; + const uint64_t c2 = carry_shift(d2, 42); - h0 += ((t0)&M44); - h1 += (((t0 >> 44) | (t1 << 20)) & M44); - h2 += (((t1 >> 24)) & M42) | hibit; + h0 = (d0 & M44) + c2 * 5; + h1 = (d1 & M44) + (h0 >> 44); + h0 &= M44; + h2 = d2 & M42; +} + +// Process two blocks in parallel: h = ((h + m0) * r + m1) * r = (h + m0) * r^2 + m1 * r +// The multiplications by r^2 and r are independent, enabling ILP +BOTAN_FORCE_INLINE void poly1305_block_pair(uint64_t& h0, + uint64_t& h1, + uint64_t& h2, + uint64_t r0, + uint64_t r1, + uint64_t r2, + uint64_t s1, + uint64_t s2, + uint64_t rr0, + uint64_t rr1, + uint64_t rr2, + uint64_t ss1, + uint64_t ss2, + const uint8_t* m, + uint64_t hibit) { + constexpr uint64_t M44 = 0xFFFFFFFFFFF; + constexpr uint64_t M42 = 0x3FFFFFFFFFF; + +#if !defined(BOTAN_TARGET_HAS_NATIVE_UINT128) + typedef donna128 uint128_t; +#endif - const uint128_t d0 = uint128_t(h0) * r0 + uint128_t(h1) * s2 + uint128_t(h2) * s1; - const uint64_t c0 = carry_shift(d0, 44); + // Load first block (will be multiplied by r^2) + const uint64_t m0_t0 = load_le(m, 0); + const uint64_t m0_t1 = load_le(m, 1); + + // Load second block (will be multiplied by r) + const uint64_t m1_t0 = load_le(m + 16, 0); + const uint64_t m1_t1 = load_le(m + 16, 1); + + // Add first block to h + h0 += (m0_t0 & M44); + h1 += ((m0_t0 >> 44) | (m0_t1 << 20)) & M44; + h2 += ((m0_t1 >> 24) & M42) | hibit; + + // Convert second block to limbs + const uint64_t b0 = (m1_t0 & M44); + const uint64_t b1 = ((m1_t0 >> 44) | (m1_t1 << 20)) & M44; + const uint64_t b2 = ((m1_t1 >> 24) & M42) | hibit; + + // Compute (h + m0) * r^2 + m1 * r + const uint128_t d0 = uint128_t(h0) * rr0 + uint128_t(h1) * ss2 + uint128_t(h2) * ss1 + uint128_t(b0) * r0 + + uint128_t(b1) * s2 + uint128_t(b2) * s1; + const uint64_t c0 = carry_shift(d0, 44); + + const uint128_t d1 = uint128_t(h0) * rr1 + uint128_t(h1) * rr0 + uint128_t(h2) * ss2 + uint128_t(b0) * r1 + + uint128_t(b1) * r0 + uint128_t(b2) * s2 + c0; + const uint64_t c1 = carry_shift(d1, 44); + + const uint128_t d2 = uint128_t(h0) * rr2 + uint128_t(h1) * rr1 + uint128_t(h2) * rr0 + uint128_t(b0) * r2 + + uint128_t(b1) * r1 + uint128_t(b2) * r0 + c1; + const uint64_t c2 = carry_shift(d2, 42); - const uint128_t d1 = uint128_t(h0) * r1 + uint128_t(h1) * r0 + uint128_t(h2) * s2 + c0; - const uint64_t c1 = carry_shift(d1, 44); + h0 = (d0 & M44) + c2 * 5; + h1 = (d1 & M44) + (h0 >> 44); + h0 &= M44; + h2 = d2 & M42; +} - const uint128_t d2 = uint128_t(h0) * r2 + uint128_t(h1) * r1 + uint128_t(h2) * r0 + c1; - const uint64_t c2 = carry_shift(d2, 42); +void poly1305_blocks(secure_vector& X, const uint8_t* m, size_t blocks, bool is_final = false) { + const uint64_t hibit = is_final ? 0 : (static_cast(1) << 40); - h0 = d0 & M44; - h1 = d1 & M44; - h2 = d2 & M42; + // Load r (at R_BASE + 0) + const uint64_t r0 = X[R_BASE + 0]; + const uint64_t r1 = X[R_BASE + 1]; + const uint64_t r2 = X[R_BASE + 2]; + const uint64_t s1 = r1 * 20; + const uint64_t s2 = r2 * 20; - h0 += c2 * 5; - h1 += carry_shift(h0, 44); - h0 = h0 & M44; + // Load r^2 (at R_BASE + 3) + const uint64_t rr0 = X[R_BASE + 3]; + const uint64_t rr1 = X[R_BASE + 4]; + const uint64_t rr2 = X[R_BASE + 5]; + + // Precompute + const uint64_t ss1 = rr1 * 20; + const uint64_t ss2 = rr2 * 20; + + // Load accumulator + uint64_t h0 = X[H_BASE + 0]; + uint64_t h1 = X[H_BASE + 1]; + uint64_t h2 = X[H_BASE + 2]; + + while(blocks >= 2) { + poly1305_block_pair(h0, h1, h2, r0, r1, r2, s1, s2, rr0, rr1, rr2, ss1, ss2, m, hibit); + m += 32; + blocks -= 2; + } - m += 16; + // Final block? + if(blocks > 0) { + poly1305_block_single(h0, h1, h2, r0, r1, r2, s1, s2, m, hibit); } - X[3 + 0] = h0; - X[3 + 1] = h1; - X[3 + 2] = h2; + // Store accumulator + X[H_BASE + 0] = h0; + X[H_BASE + 1] = h1; + X[H_BASE + 2] = h2; } void poly1305_finish(secure_vector& X, uint8_t mac[16]) { - const uint64_t M44 = 0xFFFFFFFFFFF; - const uint64_t M42 = 0x3FFFFFFFFFF; + constexpr uint64_t M44 = 0xFFFFFFFFFFF; + constexpr uint64_t M42 = 0x3FFFFFFFFFF; /* fully carry h */ - uint64_t h0 = X[3 + 0]; - uint64_t h1 = X[3 + 1]; - uint64_t h2 = X[3 + 2]; + uint64_t h0 = X[H_BASE + 0]; + uint64_t h1 = X[H_BASE + 1]; + uint64_t h2 = X[H_BASE + 2]; - uint64_t c; - c = (h1 >> 44); + uint64_t c = (h1 >> 44); h1 &= M44; h2 += c; c = (h2 >> 42); @@ -129,17 +296,17 @@ uint64_t g1 = h1 + c; c = (g1 >> 44); g1 &= M44; - uint64_t g2 = h2 + c - (static_cast(1) << 42); + const uint64_t g2 = h2 + c - (static_cast(1) << 42); /* select h if h < p, or h + -p if h >= p */ - const auto c_mask = CT::Mask::expand(c); - h0 = c_mask.select(g0, h0); - h1 = c_mask.select(g1, h1); - h2 = c_mask.select(g2, h2); + const auto h_mask = CT::Mask::expand_top_bit(g2); + h0 = h_mask.select(h0, g0); + h1 = h_mask.select(h1, g1); + h2 = h_mask.select(h2, g2); /* h = (h + pad) */ - const uint64_t t0 = X[6]; - const uint64_t t1 = X[7]; + const uint64_t t0 = X[PAD_BASE + 0]; + const uint64_t t1 = X[PAD_BASE + 1]; h0 += ((t0)&M44); c = (h0 >> 44); @@ -168,16 +335,32 @@ } bool Poly1305::has_keying_material() const { - return m_poly.size() == 8; + // Minimum size: pad(2) + accum(3) + r(3) + r^2(3) = 11 + return m_poly.size() >= 11; } void Poly1305::key_schedule(std::span key) { m_buffer.clear(); - m_poly.resize(8); poly1305_init(m_poly, key.data()); } +std::string Poly1305::provider() const { +#if defined(BOTAN_HAS_POLY1305_AVX512) + if(auto feat = CPUID::check(CPUID::Feature::AVX512)) { + return *feat; + } +#endif + +#if defined(BOTAN_HAS_POLY1305_AVX2) + if(auto feat = CPUID::check(CPUID::Feature::AVX2)) { + return *feat; + } +#endif + + return "base"; +} + void Poly1305::add_data(std::span input) { assert_key_material_set(); @@ -191,7 +374,32 @@ if(m_buffer.in_alignment()) { const auto [aligned_data, full_blocks] = m_buffer.aligned_data_to_process(in); if(full_blocks > 0) { - poly1305_blocks(m_poly, aligned_data.data(), full_blocks); + const uint8_t* data_ptr = aligned_data.data(); + size_t blocks_remaining = full_blocks; + +#if defined(BOTAN_HAS_POLY1305_AVX512) + if(blocks_remaining >= 8 * 3 && CPUID::has(CPUID::Feature::AVX512)) { + // Lazily compute r^3 through r^8 on first AVX512 use + poly1305_extend_powers(m_poly, 8); + const size_t processed = poly1305_avx512_blocks(m_poly, data_ptr, blocks_remaining); + data_ptr += processed * 16; + blocks_remaining -= processed; + } +#endif + +#if defined(BOTAN_HAS_POLY1305_AVX2) + if(blocks_remaining >= 4 * 6 && CPUID::has(CPUID::Feature::AVX2)) { + // Lazily compute r^3 and r^4 on first AVX2 use + poly1305_extend_powers(m_poly, 4); + const size_t processed = poly1305_avx2_blocks(m_poly, data_ptr, blocks_remaining); + data_ptr += processed * 16; + blocks_remaining -= processed; + } +#endif + + if(blocks_remaining > 0) { + poly1305_blocks(m_poly, data_ptr, blocks_remaining); + } } } } diff -Nru botan3-3.7.1+dfsg/src/lib/mac/poly1305/poly1305.h botan3-3.12.0+dfsg/src/lib/mac/poly1305/poly1305.h --- botan3-3.7.1+dfsg/src/lib/mac/poly1305/poly1305.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/mac/poly1305/poly1305.h 2026-05-07 01:38:28.000000000 +0000 @@ -22,6 +22,8 @@ public: std::string name() const override { return "Poly1305"; } + std::string provider() const override; + std::unique_ptr new_object() const override { return std::make_unique(); } void clear() override; @@ -35,10 +37,19 @@ bool has_keying_material() const override; private: - void add_data(std::span) override; - void final_result(std::span) override; - void key_schedule(std::span) override; + void add_data(std::span input) override; + void final_result(std::span output) override; + void key_schedule(std::span key) override; + +#if defined(BOTAN_HAS_POLY1305_AVX2) + static size_t poly1305_avx2_blocks(secure_vector& X, const uint8_t m[], size_t blocks); +#endif + +#if defined(BOTAN_HAS_POLY1305_AVX512) + static size_t poly1305_avx512_blocks(secure_vector& X, const uint8_t m[], size_t blocks); +#endif + // State layout: pad [2] || accum [3] || r [3] || r^2 [3] || ... || r^n [3] secure_vector m_poly; AlignmentBuffer m_buffer; }; diff -Nru botan3-3.7.1+dfsg/src/lib/mac/poly1305/poly1305_avx2/info.txt botan3-3.12.0+dfsg/src/lib/mac/poly1305/poly1305_avx2/info.txt --- botan3-3.7.1+dfsg/src/lib/mac/poly1305/poly1305_avx2/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/mac/poly1305/poly1305_avx2/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,17 @@ + +POLY1305_AVX2 -> 20260108 + + + +name -> "Poly1305 AVX2" +brief -> "Poly1305 using AVX2 instructions" + + + +avx2 + + + +simd_avx2 +cpuid + diff -Nru botan3-3.7.1+dfsg/src/lib/mac/poly1305/poly1305_avx2/poly1305_avx2.cpp botan3-3.12.0+dfsg/src/lib/mac/poly1305/poly1305_avx2/poly1305_avx2.cpp --- botan3-3.7.1+dfsg/src/lib/mac/poly1305/poly1305_avx2/poly1305_avx2.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/mac/poly1305/poly1305_avx2/poly1305_avx2.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,255 @@ +/* +* (C) 2026 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include + +namespace Botan { + +// NOLINTBEGIN(portability-simd-intrinsics) + +namespace { + +constexpr uint32_t MASK26 = 0x3FFFFFF; + +/* +* 4x26 values packed in a 256-bit register +* +* The 26 bit is somewhat a lie; we actually use the full 64 bit width +* but assume that after a 32x32->64 multiply there is still enough +* space to store sums into 64 bits. We could pack slightly more bits, +* but 26x5 = 130 is enough. +*/ +class SIMD_4x26 final { + public: + BOTAN_FN_ISA_AVX2 SIMD_4x26() : m_v(_mm256_setzero_si256()) {} + + // Construct from raw __m256i (for vectorized loading) + static BOTAN_FN_ISA_AVX2 SIMD_4x26 from_raw(__m256i v) { return SIMD_4x26(v); } + + // Pack 4 values into lanes (high to low: v3, v2, v1, v0) + static BOTAN_FN_ISA_AVX2 SIMD_4x26 set(uint32_t v3, uint32_t v2, uint32_t v1, uint32_t v0) { + return SIMD_4x26(_mm256_set_epi32(0, v3, 0, v2, 0, v1, 0, v0)); + } + + // Multiply by 5: 5*x = (x << 2) + x + BOTAN_FN_ISA_AVX2 SIMD_4x26 mul_5() const { return SIMD_4x26(_mm256_add_epi32(_mm256_slli_epi32(m_v, 2), m_v)); } + + friend SIMD_4x26 BOTAN_FN_ISA_AVX2 operator+(const SIMD_4x26& x, const SIMD_4x26& y) { + return SIMD_4x26(_mm256_add_epi64(x.raw(), y.raw())); + } + + friend SIMD_4x26 BOTAN_FN_ISA_AVX2 operator*(const SIMD_4x26& x, const SIMD_4x26& y) { + return SIMD_4x26(_mm256_mul_epi32(x.raw(), y.raw())); + } + + // Horizontal sum of 4x64-bit values + BOTAN_FN_ISA_AVX2 uint64_t horizontal_add64() const { + uint64_t tmp[4]; + _mm256_storeu_si256(reinterpret_cast<__m256i*>(tmp), m_v); + return tmp[0] + tmp[1] + tmp[2] + tmp[3]; + } + + __m256i BOTAN_FN_ISA_AVX2 raw() const { return m_v; } + + private: + explicit BOTAN_FN_ISA_AVX2 SIMD_4x26(__m256i v) : m_v(v) {} + + __m256i m_v; +}; + +/* +* Vectorized load of 4 message blocks into radix 2^26 representation +* +* Loads 64 bytes (4 blocks), deinterleaves t0/t1 halves, and converts +* to radix 2^26 using vector shift/mask operations. +* +* Lane ordering: block 0 in lane 3, block 3 in lane 0 (reversed for multiply) +*/ +BOTAN_FN_ISA_AVX2 void load_4_blocks_26(SIMD_4x26& msg_0, + SIMD_4x26& msg_1, + SIMD_4x26& msg_2, + SIMD_4x26& msg_3, + SIMD_4x26& msg_4, + const uint8_t* m, + std::array h) { + // Load 64 bytes (4 blocks of 16 bytes each) + const __m256i d0 = _mm256_loadu_si256(reinterpret_cast(m)); + const __m256i d1 = _mm256_loadu_si256(reinterpret_cast(m + 32)); + + // Deinterleave: extract low 64-bit (t0) and high 64-bit (t1) from each block + // unpacklo/hi work within 128-bit lanes: pairs adjacent blocks + const __m256i t0_mixed = _mm256_unpacklo_epi64(d0, d1); // [blk3_lo, blk1_lo, blk2_lo, blk0_lo] + const __m256i t1_mixed = _mm256_unpackhi_epi64(d0, d1); // [blk3_hi, blk1_hi, blk2_hi, blk0_hi] + + const __m256i t0 = _mm256_permute4x64_epi64(t0_mixed, 0b00100111); + const __m256i t1 = _mm256_permute4x64_epi64(t1_mixed, 0b00100111); + + // Constants for radix conversion + const __m256i mask26 = _mm256_set1_epi64x(MASK26); + const __m256i hibit_vec = _mm256_set1_epi64x(1 << 24); + + // Convert to radix 2^26: + // limb0 = t0[25:0] + // limb1 = t0[51:26] + // limb2 = t0[63:52] | t1[13:0] << 12 (bits 52-77) + // limb3 = t1[39:14] (bits 78-103) + // limb4 = t1[63:40] | hibit (bits 104-127 + 2^128 marker) + __m256i limb0 = _mm256_and_si256(t0, mask26); + __m256i limb1 = _mm256_and_si256(_mm256_srli_epi64(t0, 26), mask26); + __m256i limb2 = _mm256_and_si256(_mm256_or_si256(_mm256_srli_epi64(t0, 52), _mm256_slli_epi64(t1, 12)), mask26); + __m256i limb3 = _mm256_and_si256(_mm256_srli_epi64(t1, 14), mask26); + __m256i limb4 = _mm256_or_si256(_mm256_srli_epi64(t1, 40), hibit_vec); + + // Add h to lane 3 (block 0): h + m[0] before multiply by r^4 + limb0 = _mm256_add_epi64(limb0, _mm256_set_epi64x(h[0], 0, 0, 0)); + limb1 = _mm256_add_epi64(limb1, _mm256_set_epi64x(h[1], 0, 0, 0)); + limb2 = _mm256_add_epi64(limb2, _mm256_set_epi64x(h[2], 0, 0, 0)); + limb3 = _mm256_add_epi64(limb3, _mm256_set_epi64x(h[3], 0, 0, 0)); + limb4 = _mm256_add_epi64(limb4, _mm256_set_epi64x(h[4], 0, 0, 0)); + + msg_0 = SIMD_4x26::from_raw(limb0); + msg_1 = SIMD_4x26::from_raw(limb1); + msg_2 = SIMD_4x26::from_raw(limb2); + msg_3 = SIMD_4x26::from_raw(limb3); + msg_4 = SIMD_4x26::from_raw(limb4); +} + +// NOLINTEND(portability-simd-intrinsics) + +// Convert radix-2^26 limbs back to radix-2^44 +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX2 void convert_26_to_44(uint64_t& r0, + uint64_t& r1, + uint64_t& r2, + const std::array in) { + constexpr uint64_t M44 = 0xFFFFFFFFFFF; + constexpr uint64_t M42 = 0x3FFFFFFFFFF; + + // Expand to 64 bits + const uint64_t i0 = in[0]; + const uint64_t i1 = in[1]; + const uint64_t i2 = in[2]; + const uint64_t i3 = in[3]; + const uint64_t i4 = in[4]; + + r0 = (i0 | (i1 << 26)) & M44; + r1 = ((i1 >> 18) | (i2 << 8) | (i3 << 34)) & M44; + r2 = ((i3 >> 10) | (i4 << 16)) & M42; +} + +// Convert radix-2^44 limbs to radix-2^26 +BOTAN_FORCE_INLINE std::array convert_44_to_26(uint64_t r0, uint64_t r1, uint64_t r2) { + std::array out{}; + out[0] = static_cast(r0) & MASK26; // bits 0-25 + out[1] = static_cast((r0 >> 26) | (r1 << 18)) & MASK26; // bits 26-51 + out[2] = static_cast(r1 >> 8) & MASK26; // bits 52-77 + out[3] = static_cast((r1 >> 34) | (r2 << 10)) & MASK26; // bits 78-103 + out[4] = static_cast(r2 >> 16) & MASK26; // bits 104-129 + return out; +} + +inline void BOTAN_FN_ISA_AVX2 +load_r(SIMD_4x26& r0, SIMD_4x26& r1, SIMD_4x26& r2, SIMD_4x26& r3, SIMD_4x26& r4, const secure_vector& X) { + // TODO do this in vector registers instead + const auto t = convert_44_to_26(X[5], X[6], X[7]); + const auto t2 = convert_44_to_26(X[8], X[9], X[10]); + const auto t3 = convert_44_to_26(X[11], X[12], X[13]); + const auto t4 = convert_44_to_26(X[14], X[15], X[16]); + + r0 = SIMD_4x26::set(t4[0], t3[0], t2[0], t[0]); + r1 = SIMD_4x26::set(t4[1], t3[1], t2[1], t[1]); + r2 = SIMD_4x26::set(t4[2], t3[2], t2[2], t[2]); + r3 = SIMD_4x26::set(t4[3], t3[3], t2[3], t[3]); + r4 = SIMD_4x26::set(t4[4], t3[4], t2[4], t[4]); +} + +} // namespace + +/* +* Process 4 blocks at a time using AVX2 +* h = (h + m[0]) * r^4 + m[1] * r^3 + m[2] * r^2 + m[3] * r +*/ +size_t BOTAN_FN_ISA_AVX2 Poly1305::poly1305_avx2_blocks(secure_vector& X, const uint8_t m[], size_t blocks) { + if(blocks < 4) { + return 0; + } + + const size_t incoming_blocks = blocks; + + auto h = convert_44_to_26(X[2], X[3], X[4]); + + SIMD_4x26 r0; + SIMD_4x26 r1; + SIMD_4x26 r2; + SIMD_4x26 r3; + SIMD_4x26 r4; + load_r(r0, r1, r2, r3, r4, X); + + const auto r1_5 = r1.mul_5(); + const auto r2_5 = r2.mul_5(); + const auto r3_5 = r3.mul_5(); + const auto r4_5 = r4.mul_5(); + + while(blocks >= 4) { + // Load 4 message blocks, convert to radix 2^26, and add h to block 0 + SIMD_4x26 m0; + SIMD_4x26 m1; + SIMD_4x26 m2; + SIMD_4x26 m3; + SIMD_4x26 m4; + load_4_blocks_26(m0, m1, m2, m3, m4, m, h); + + const auto d0 = m0 * r0 + m1 * r4_5 + m2 * r3_5 + m3 * r2_5 + m4 * r1_5; + const auto d1 = m0 * r1 + m1 * r0 + m2 * r4_5 + m3 * r3_5 + m4 * r2_5; + const auto d2 = m0 * r2 + m1 * r1 + m2 * r0 + m3 * r4_5 + m4 * r3_5; + const auto d3 = m0 * r3 + m1 * r2 + m2 * r1 + m3 * r0 + m4 * r4_5; + const auto d4 = m0 * r4 + m1 * r3 + m2 * r2 + m3 * r1 + m4 * r0; + + const uint64_t h0_64 = d0.horizontal_add64(); + uint64_t h1_64 = d1.horizontal_add64(); + uint64_t h2_64 = d2.horizontal_add64(); + uint64_t h3_64 = d3.horizontal_add64(); + uint64_t h4_64 = d4.horizontal_add64(); + + h1_64 += h0_64 >> 26; + h[0] = static_cast(h0_64) & MASK26; + h2_64 += h1_64 >> 26; + h[1] = static_cast(h1_64) & MASK26; + h3_64 += h2_64 >> 26; + h[2] = static_cast(h2_64) & MASK26; + h4_64 += h3_64 >> 26; + h[3] = static_cast(h3_64) & MASK26; + + const uint64_t c = h4_64 >> 26; + h[4] = static_cast(h4_64) & MASK26; + + uint64_t carry = c * 5; + carry += h[0]; + h[0] = static_cast(carry) & MASK26; + carry >>= 26; + carry += h[1]; + h[1] = static_cast(carry) & MASK26; + carry >>= 26; + carry += h[2]; + h[2] = static_cast(carry) & MASK26; + carry >>= 26; + carry += h[3]; + h[3] = static_cast(carry) & MASK26; + carry >>= 26; + h[4] += static_cast(carry); + + m += 64; + blocks -= 4; + } + + convert_26_to_44(X[2], X[3], X[4], h); + + return (incoming_blocks - blocks); +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/mac/poly1305/poly1305_avx512/info.txt botan3-3.12.0+dfsg/src/lib/mac/poly1305/poly1305_avx512/info.txt --- botan3-3.7.1+dfsg/src/lib/mac/poly1305/poly1305_avx512/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/mac/poly1305/poly1305_avx512/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,17 @@ + +POLY1305_AVX512 -> 20260108 + + + +name -> "Poly1305 AVX512" +brief -> "Poly1305 using AVX-512 IFMA instructions" + + + +avx512 + + + +simd_avx512 +cpuid + diff -Nru botan3-3.7.1+dfsg/src/lib/mac/poly1305/poly1305_avx512/poly1305_avx512.cpp botan3-3.12.0+dfsg/src/lib/mac/poly1305/poly1305_avx512/poly1305_avx512.cpp --- botan3-3.7.1+dfsg/src/lib/mac/poly1305/poly1305_avx512/poly1305_avx512.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/mac/poly1305/poly1305_avx512/poly1305_avx512.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,222 @@ +/* +* (C) 2026 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include + +namespace Botan { + +namespace { + +// NOLINTBEGIN(portability-simd-intrinsics) + +class SIMD_8x44 final { + public: + BOTAN_FN_ISA_AVX512 SIMD_8x44() : m_v(_mm512_setzero_si512()) {} + + static BOTAN_FN_ISA_AVX512 SIMD_8x44 splat(uint64_t x) { return SIMD_8x44(_mm512_set1_epi64(x)); } + + static BOTAN_FN_ISA_AVX512 SIMD_8x44 load(const void* p) { + return SIMD_8x44(_mm512_loadu_si512(reinterpret_cast(p))); + } + + BOTAN_FN_ISA_AVX512 SIMD_8x44(int e7, int e6, int e5, int e4, int e3, int e2, int e1, int e0) : + SIMD_8x44(_mm512_set_epi64(e7, e6, e5, e4, e3, e2, e1, e0)) {} + + // Permute across two vectors using index vector + static BOTAN_FN_ISA_AVX512 SIMD_8x44 permute2(const SIMD_8x44& idx, const SIMD_8x44& a, const SIMD_8x44& b) { + return SIMD_8x44(_mm512_permutex2var_epi64(a.m_v, idx.m_v, b.m_v)); + } + + static BOTAN_FN_ISA_AVX512 SIMD_8x44 permute3( + const SIMD_8x44& idx0, const SIMD_8x44& idx1, const SIMD_8x44& a, const SIMD_8x44& b, const SIMD_8x44& c) { + return SIMD_8x44::permute2(idx1, SIMD_8x44::permute2(idx0, a, b), c); + } + + // VBMI2 double shift right: concatenate (b:a) and shift right by count + template + static BOTAN_FN_ISA_AVX512 SIMD_8x44 shrdi(const SIMD_8x44& a, const SIMD_8x44& b) { + return SIMD_8x44(_mm512_shrdi_epi64(a.m_v, b.m_v, COUNT)); + } + + BOTAN_FN_ISA_AVX512 SIMD_8x44 add_lane_zero(uint64_t b) { + return SIMD_8x44(_mm512_mask_add_epi64(m_v, 0x01, m_v, _mm512_set1_epi64(b))); + } + + // IFMA: accumulator += (a * b) low 52 bits + BOTAN_FN_ISA_AVX512 SIMD_8x44& ifma_lo(const SIMD_8x44& a, const SIMD_8x44& b) { + m_v = _mm512_madd52lo_epu64(m_v, a.m_v, b.m_v); + return *this; + } + + // IFMA: accumulator += (a * b) high 52 bits + BOTAN_FN_ISA_AVX512 SIMD_8x44& ifma_hi(const SIMD_8x44& a, const SIMD_8x44& b) { + m_v = _mm512_madd52hi_epu64(m_v, a.m_v, b.m_v); + return *this; + } + + // Multiply by 20: 20*x = (x << 4) + (x << 2) + BOTAN_FN_ISA_AVX512 SIMD_8x44 mul_20() const { + return SIMD_8x44(_mm512_add_epi64(_mm512_slli_epi64(m_v, 4), _mm512_slli_epi64(m_v, 2))); + } + + template + BOTAN_FN_ISA_AVX512 SIMD_8x44 shr() const { + return SIMD_8x44(_mm512_srli_epi64(m_v, S)); + } + + BOTAN_FN_ISA_AVX512 uint64_t horizontal_add() const { return _mm512_reduce_add_epi64(m_v); } + + BOTAN_FN_ISA_AVX512 SIMD_8x44 operator&(const SIMD_8x44& other) const { + return SIMD_8x44(_mm512_and_si512(m_v, other.m_v)); + } + + BOTAN_FN_ISA_AVX512 SIMD_8x44 operator|(const SIMD_8x44& other) const { + return SIMD_8x44(_mm512_or_si512(m_v, other.m_v)); + } + + static BOTAN_FN_ISA_AVX512 void interleave_3x8(SIMD_8x44& r0, SIMD_8x44& r1, SIMD_8x44& r2) { + const auto idx1_z0 = SIMD_8x44(0, 3, 6, 9, 12, 15, -1, -1); + const auto idx2_z0 = SIMD_8x44(7, 6, 5, 4, 3, 2, 10, 13); + const auto idx1_z1 = SIMD_8x44(1, 4, 7, 10, 13, -1, -1, -1); + const auto idx2_z1 = SIMD_8x44(7, 6, 5, 4, 3, 8, 11, 14); + const auto idx1_z2 = SIMD_8x44(2, 5, 8, 11, 14, -1, -1, -1); + const auto idx2_z2 = SIMD_8x44(7, 6, 5, 4, 3, 9, 12, 15); + + // NOLINTBEGIN(*-suspicious-call-argument) + auto z0 = SIMD_8x44::permute3(idx1_z0, idx2_z0, r0, r1, r2); + auto z1 = SIMD_8x44::permute3(idx1_z1, idx2_z1, r0, r1, r2); + auto z2 = SIMD_8x44::permute3(idx1_z2, idx2_z2, r0, r1, r2); + // NOLINTEND(*-suspicious-call-argument) + + r0 = z0; + r1 = z1; + r2 = z2; + } + + private: + __m512i BOTAN_FN_ISA_AVX512 raw() const { return m_v; } + + explicit BOTAN_FN_ISA_AVX512 SIMD_8x44(__m512i v) : m_v(v) {} + + __m512i m_v; +}; + +// NOLINTEND(portability-simd-intrinsics) + +} // namespace + +/* +* Process 8 blocks at a time using AVX-512 IFMA +* h = (h + m[0]) * r^8 + m[1] * r^7 + ... + m[7] * r +*/ +size_t BOTAN_FN_ISA_AVX512 Poly1305::poly1305_avx512_blocks(secure_vector& X, + const uint8_t* m, + size_t blocks) { + constexpr uint64_t M44 = 0xFFFFFFFFFFF; + constexpr uint64_t M42 = 0x3FFFFFFFFFF; + constexpr uint64_t hibit64 = static_cast(1) << 40; + + if(blocks < 8) { + return 0; + } + + const size_t original_blocks = blocks; + + // Load h from state + uint64_t h0 = X[2]; + uint64_t h1 = X[3]; + uint64_t h2 = X[4]; + + SIMD_8x44 r0 = SIMD_8x44::load(&X[5]); + SIMD_8x44 r1 = SIMD_8x44::load(&X[5 + 8]); + SIMD_8x44 r2 = SIMD_8x44::load(&X[5 + 2 * 8]); + SIMD_8x44::interleave_3x8(r0, r1, r2); + + const auto s1 = r1.mul_20(); + const auto s2 = r2.mul_20(); + + // Constants for vectorized message loading + // Deinterleave indices: separate low (t0) and high (t1) 64-bit halves of each 128-bit block + // Memory layout: [t0_0, t1_0, t0_1, t1_1, ...] -> want [t0_0..t0_7] and [t1_0..t1_7] + const auto idx_lo = SIMD_8x44(14, 12, 10, 8, 6, 4, 2, 0); + const auto idx_hi = SIMD_8x44(15, 13, 11, 9, 7, 5, 3, 1); + const auto mask44 = SIMD_8x44::splat(M44); + const auto mask42 = SIMD_8x44::splat(M42); + const auto hibit = SIMD_8x44::splat(hibit64); + + while(blocks >= 8) { + // Load 8 message blocks (128 bytes) with two 512-bit loads + const auto data0 = SIMD_8x44::load(m); + const auto data1 = SIMD_8x44::load(m + 64); + + // Deinterleave: separate low and high 64-bit halves of each 128-bit block + const auto t0 = SIMD_8x44::permute2(idx_lo, data0, data1); + const auto t1 = SIMD_8x44::permute2(idx_hi, data0, data1); + + // Convert to radix 2^44 representation using VBMI2 + // limb0 = t0[43:0] + // limb1 = t1[23:0]:t0[63:44] (bits 44-87 of block) + // limb2 = t1[63:24] | hibit (bits 88-129 of block + high bit) + auto m0 = t0 & mask44; + auto m1 = SIMD_8x44::shrdi<44>(t0, t1) & mask44; + auto m2 = (t1.shr<24>() & mask42) | hibit; + + // Add h to first block + m0 = m0.add_lane_zero(h0); + m1 = m1.add_lane_zero(h1); + m2 = m2.add_lane_zero(h2); + + // d0 = m0*r0 + m1*s2 + m2*s1 + const SIMD_8x44 d0_lo = SIMD_8x44().ifma_lo(m0, r0).ifma_lo(m1, s2).ifma_lo(m2, s1); + const SIMD_8x44 d0_hi = SIMD_8x44().ifma_hi(m0, r0).ifma_hi(m1, s2).ifma_hi(m2, s1); + + // d1 = m0*r1 + m1*r0 + m2*s2 + const SIMD_8x44 d1_lo = SIMD_8x44().ifma_lo(m0, r1).ifma_lo(m1, r0).ifma_lo(m2, s2); + const SIMD_8x44 d1_hi = SIMD_8x44().ifma_hi(m0, r1).ifma_hi(m1, r0).ifma_hi(m2, s2); + + // d2 = m0*r2 + m1*r1 + m2*r0 + const SIMD_8x44 d2_lo = SIMD_8x44().ifma_lo(m0, r2).ifma_lo(m1, r1).ifma_lo(m2, r0); + const SIMD_8x44 d2_hi = SIMD_8x44().ifma_hi(m0, r2).ifma_hi(m1, r1).ifma_hi(m2, r0); + + // Horizontal adds can't overflow - at most 8*3*(2**52-1) ~= 2**57 + const uint64_t sum0_lo = d0_lo.horizontal_add(); + const uint64_t sum0_hi = d0_hi.horizontal_add(); + uint64_t sum1_lo = d1_lo.horizontal_add(); + const uint64_t sum1_hi = d1_hi.horizontal_add(); + uint64_t sum2_lo = d2_lo.horizontal_add(); + const uint64_t sum2_hi = d2_hi.horizontal_add(); + + h0 = sum0_lo & M44; + sum1_lo += (sum0_lo >> 44) + (sum0_hi << 8); + h1 = sum1_lo & M44; + sum2_lo += (sum1_lo >> 44) + (sum1_hi << 8); + h2 = sum2_lo & M42; + + // Wrap-around reduction: carry * 5 goes back to h0 + uint64_t carry = ((sum2_lo >> 42) + (sum2_hi << 10)) * 5; + carry += h0; + h0 = carry & M44; + carry >>= 44; + carry += h1; + h1 = carry & M44; + carry >>= 44; + h2 += carry; + + m += 8 * 16; + blocks -= 8; + } + + X[2] = h0; + X[3] = h1; + X[4] = h2; + + return (original_blocks - blocks); +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/mac/siphash/info.txt botan3-3.12.0+dfsg/src/lib/mac/siphash/info.txt --- botan3-3.7.1+dfsg/src/lib/mac/siphash/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/mac/siphash/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -4,4 +4,5 @@ name -> "SipHash" +lifecycle -> "Deprecated" diff -Nru botan3-3.7.1+dfsg/src/lib/mac/siphash/siphash.cpp botan3-3.12.0+dfsg/src/lib/mac/siphash/siphash.cpp --- botan3-3.7.1+dfsg/src/lib/mac/siphash/siphash.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/mac/siphash/siphash.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,17 +7,20 @@ #include +#include #include #include #include -#include namespace Botan { namespace { void SipRounds(uint64_t M, secure_vector& V, size_t r) { - uint64_t V0 = V[0], V1 = V[1], V2 = V[2], V3 = V[3]; + uint64_t V0 = V[0]; + uint64_t V1 = V[1]; + uint64_t V2 = V[2]; + uint64_t V3 = V[3]; V3 ^= M; for(size_t i = 0; i != r; ++i) { @@ -55,7 +58,7 @@ BufferSlicer in(input); - if(m_mbuf_pos) { + if(m_mbuf_pos > 0) { while(!in.empty() && m_mbuf_pos != 8) { m_mbuf = (m_mbuf >> 8) | (static_cast(in.take_byte()) << 56); ++m_mbuf_pos; diff -Nru botan3-3.7.1+dfsg/src/lib/mac/siphash/siphash.h botan3-3.12.0+dfsg/src/lib/mac/siphash/siphash.h --- botan3-3.7.1+dfsg/src/lib/mac/siphash/siphash.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/mac/siphash/siphash.h 2026-05-07 01:38:28.000000000 +0000 @@ -14,7 +14,7 @@ class SipHash final : public MessageAuthenticationCode { public: - SipHash(size_t c = 2, size_t d = 4) : m_C(c), m_D(d) {} + explicit SipHash(size_t c = 2, size_t d = 4) : m_C(c), m_D(d) {} void clear() override; std::string name() const override; @@ -28,9 +28,9 @@ Key_Length_Specification key_spec() const override { return Key_Length_Specification(16); } private: - void add_data(std::span) override; - void final_result(std::span) override; - void key_schedule(std::span) override; + void add_data(std::span input) override; + void final_result(std::span output) override; + void key_schedule(std::span key) override; const size_t m_C, m_D; secure_vector m_K; diff -Nru botan3-3.7.1+dfsg/src/lib/mac/x919_mac/x919_mac.cpp botan3-3.12.0+dfsg/src/lib/mac/x919_mac/x919_mac.cpp --- botan3-3.7.1+dfsg/src/lib/mac/x919_mac/x919_mac.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/mac/x919_mac/x919_mac.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,7 +7,8 @@ #include -#include +#include +#include namespace Botan { @@ -42,7 +43,7 @@ * Finalize an ANSI X9.19 MAC Calculation */ void ANSI_X919_MAC::final_result(std::span mac) { - if(m_position) { + if(m_position > 0) { m_des1->encrypt(m_state); } m_des2->decrypt(m_state.data(), mac.data()); @@ -91,6 +92,6 @@ /* * ANSI X9.19 MAC Constructor */ -ANSI_X919_MAC::ANSI_X919_MAC() : m_des1(BlockCipher::create("DES")), m_des2(m_des1->new_object()), m_position(0) {} +ANSI_X919_MAC::ANSI_X919_MAC() : m_des1(BlockCipher::create_or_throw("DES")), m_des2(m_des1->new_object()) {} } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/mac/x919_mac/x919_mac.h botan3-3.12.0+dfsg/src/lib/mac/x919_mac/x919_mac.h --- botan3-3.7.1+dfsg/src/lib/mac/x919_mac/x919_mac.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/mac/x919_mac/x919_mac.h 2026-05-07 01:38:28.000000000 +0000 @@ -31,17 +31,14 @@ ANSI_X919_MAC(); - ANSI_X919_MAC(const ANSI_X919_MAC&) = delete; - ANSI_X919_MAC& operator=(const ANSI_X919_MAC&) = delete; - private: - void add_data(std::span) override; - void final_result(std::span) override; - void key_schedule(std::span) override; + void add_data(std::span input) override; + void final_result(std::span output) override; + void key_schedule(std::span key) override; std::unique_ptr m_des1, m_des2; secure_vector m_state; - size_t m_position; + size_t m_position = 0; }; } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/math/bigint/big_code.cpp botan3-3.12.0+dfsg/src/lib/math/bigint/big_code.cpp --- botan3-3.7.1+dfsg/src/lib/math/bigint/big_code.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/bigint/big_code.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,24 +7,43 @@ #include +#include +#include #include +#include +#include #include -#include +#include namespace Botan { +namespace { + +consteval word decimal_conversion_radix() { + if constexpr(sizeof(word) == 8) { + return 10000000000000000000U; + } else { + return 1000000000U; + } +} + +consteval size_t decimal_conversion_radix_digits() { + if constexpr(sizeof(word) == 8) { + return 19; + } else { + return 9; + } +} + +} // namespace + std::string BigInt::to_dec_string() const { // Use the largest power of 10 that fits in a word -#if(BOTAN_MP_WORD_BITS == 64) - const word conversion_radix = 10000000000000000000U; - const word radix_digits = 19; -#else - const word conversion_radix = 1000000000U; - const word radix_digits = 9; -#endif + constexpr word conversion_radix = decimal_conversion_radix(); + constexpr size_t radix_digits = decimal_conversion_radix_digits(); // (over-)estimate of the number of digits needed; log2(10) ~ 3.3219 - const size_t digit_estimate = static_cast(1 + (this->bits() / 3.32)); + const size_t digit_estimate = static_cast(1 + (static_cast(this->bits()) / 3.32)); // (over-)estimate of db such that conversion_radix^db > *this const size_t digit_blocks = (digit_estimate + radix_digits - 1) / radix_digits; @@ -49,10 +68,10 @@ for(size_t i = 0; i != digit_blocks; ++i) { word remainder = digit_groups[i]; for(size_t j = 0; j != radix_digits; ++j) { - // Compiler should convert div/mod by 10 into mul by magic constant - const word digit = remainder % 10; - remainder /= 10; + const word new_remainder = divide_10(remainder); + const word digit = remainder - new_remainder * 10; digits[radix_digits * i + j] = static_cast(digit); + remainder = new_remainder; } } @@ -67,11 +86,13 @@ std::string s; s.reserve(1 + digits.size()); - if(is_negative()) { + if(signum() < 0) { s += "-"; } // Reverse and convert to textual digits + // TODO(Botan4) use std::ranges::reverse_view here once available (need newer Clang) + // NOLINTNEXTLINE(modernize-loop-convert) for(auto i = digits.rbegin(); i != digits.rend(); ++i) { s.push_back(*i + '0'); // assumes ASCII } @@ -92,7 +113,7 @@ } std::string hrep; - if(is_negative()) { + if(signum() < 0) { hrep += "-"; } hrep += "0x"; @@ -100,11 +121,70 @@ return hrep; } +//static +BigInt BigInt::from_radix_digits(std::string_view digits, size_t radix) { + if(radix == 16) { + secure_vector binary; + + if(digits.size() % 2 == 1) { + // Handle lack of leading 0 + const char buf0_with_leading_0[2] = {'0', digits[0]}; + + binary = hex_decode_locked(buf0_with_leading_0, 2); + + if(digits.size() > 1) { + binary += hex_decode_locked(&digits[1], digits.size() - 1, false); + } + } else { + binary = hex_decode_locked(digits, false); + } + + return BigInt::from_bytes(binary); + } else if(radix == 10) { + // Use the largest power of 10 that fits in a word, accumulating + // groups of digits into word-sized chunks to minimize the number + // of multiprecision multiplications. + constexpr word conversion_radix = decimal_conversion_radix(); + constexpr size_t radix_digits = decimal_conversion_radix_digits(); + + BigInt r; + + // Handle the initial partial block (if digit count is not a multiple of radix_digits) + const size_t partial_block = digits.size() % radix_digits; + + if(partial_block > 0) { + word acc = 0; + for(size_t i = 0; i < partial_block; ++i) { + const char c = digits[i]; + BOTAN_ARG_CHECK(c >= '0' && c <= '9', "Invalid decimal character"); + acc = acc * 10 + static_cast(c - '0'); + } + r += acc; + } + + // Process full blocks of radix_digits + for(size_t i = partial_block; i != digits.size(); i += radix_digits) { + word acc = 0; + for(size_t j = 0; j < radix_digits; ++j) { + const char c = digits[i + j]; + BOTAN_ARG_CHECK(c >= '0' && c <= '9', "Invalid decimal character"); + acc = acc * 10 + static_cast(c - '0'); + } + r *= conversion_radix; + r += acc; + } + + return r; + } else { + throw Invalid_Argument("BigInt::from_radix_digits unknown radix"); + } +} + /* * Encode two BigInt, with leading 0s if needed, and concatenate */ secure_vector BigInt::encode_fixed_length_int_pair(const BigInt& n1, const BigInt& n2, size_t bytes) { - if(n1.is_negative() || n2.is_negative()) { + if(n1.signum() < 0 || n2.signum() < 0) { throw Encoding_Error("encode_fixed_length_int_pair: values must be positive"); } if(n1.bytes() > bytes || n2.bytes() > bytes) { @@ -131,41 +211,11 @@ if(base == Binary) { return BigInt::from_bytes(std::span{buf, length}); } else if(base == Hexadecimal) { - BigInt r; - secure_vector binary; - - if(length % 2) { - // Handle lack of leading 0 - const char buf0_with_leading_0[2] = {'0', static_cast(buf[0])}; - - binary = hex_decode_locked(buf0_with_leading_0, 2); - - if(length > 1) { - binary += hex_decode_locked(cast_uint8_ptr_to_char(&buf[1]), length - 1, false); - } - } else { - binary = hex_decode_locked(cast_uint8_ptr_to_char(buf), length, false); - } - - r.assign_from_bytes(binary); - return r; + const std::string_view sv{cast_uint8_ptr_to_char(buf), length}; + return BigInt::from_radix_digits(sv, 16); } else if(base == Decimal) { - BigInt r; - // This could be made faster using the same trick as to_dec_string - for(size_t i = 0; i != length; ++i) { - const char c = buf[i]; - - if(c < '0' || c > '9') { - throw Invalid_Argument("BigInt::decode: invalid decimal char"); - } - - const uint8_t x = c - '0'; - BOTAN_ASSERT_NOMSG(x < 10); - - r *= 10; - r += x; - } - return r; + const std::string_view sv{cast_uint8_ptr_to_char(buf), length}; + return BigInt::from_radix_digits(sv, 10); } else { throw Invalid_Argument("Unknown BigInt decoding method"); } diff -Nru botan3-3.7.1+dfsg/src/lib/math/bigint/big_io.cpp botan3-3.12.0+dfsg/src/lib/math/bigint/big_io.cpp --- botan3-3.7.1+dfsg/src/lib/math/bigint/big_io.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/bigint/big_io.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,6 +6,8 @@ */ #include + +#include #include #include @@ -16,13 +18,11 @@ */ std::ostream& operator<<(std::ostream& stream, const BigInt& n) { const auto stream_flags = stream.flags(); - // NOLINTNEXTLINE(*-non-zero-enum-to-bool-conversion) - if(stream_flags & std::ios::oct) { + if((stream_flags & std::ios::oct) != 0) { throw Invalid_Argument("Octal output of BigInt not supported"); } - // NOLINTNEXTLINE(*-non-zero-enum-to-bool-conversion) - const size_t base = (stream_flags & std::ios::hex) ? 16 : 10; + const size_t base = (stream_flags & std::ios::hex) != 0 ? 16 : 10; if(base == 10) { stream << n.to_dec_string(); diff -Nru botan3-3.7.1+dfsg/src/lib/math/bigint/big_ops2.cpp botan3-3.12.0+dfsg/src/lib/math/bigint/big_ops2.cpp --- botan3-3.7.1+dfsg/src/lib/math/bigint/big_ops2.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/bigint/big_ops2.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,9 +7,9 @@ #include +#include #include #include -#include namespace Botan { @@ -19,7 +19,8 @@ grow_to(std::max(x_sw, y_words) + 1); if(sign() == y_sign) { - bigint_add2(mutable_data(), size() - 1, y, y_words); + const word carry = bigint_add2(mutable_data(), size() - 1, y, y_words); + mutable_data()[size() - 1] += carry; } else { const int32_t relative_size = bigint_cmp(_data(), x_sw, y, y_words); @@ -27,11 +28,10 @@ // *this >= y bigint_sub2(mutable_data(), x_sw, y, y_words); } else { - // *this < y + // *this < y: compute *this = y - *this bigint_sub2_rev(mutable_data(), y, y_words); } - //this->sign_fixup(relative_size, y_sign); if(relative_size < 0) { set_sign(y_sign); } else if(relative_size == 0) { @@ -43,7 +43,7 @@ } BigInt& BigInt::mod_add(const BigInt& s, const BigInt& mod, secure_vector& ws) { - if(this->is_negative() || s.is_negative() || mod.is_negative()) { + if(this->signum() < 0 || s.signum() < 0 || mod.signum() < 0) { throw Invalid_Argument("BigInt::mod_add expects all arguments are positive"); } @@ -71,6 +71,8 @@ ws.resize(3 * mod_sw); } + // NOLINTBEGIN(readability-container-data-pointer) + word borrow = bigint_sub3(&ws[0], mod._data(), mod_sw, s._data(), mod_sw); BOTAN_DEBUG_ASSERT(borrow == 0); BOTAN_UNUSED(borrow); @@ -79,16 +81,18 @@ borrow = bigint_sub3(&ws[mod_sw], this->_data(), mod_sw, &ws[0], mod_sw); // Compute t + s - bigint_add3_nc(&ws[mod_sw * 2], this->_data(), mod_sw, s._data(), mod_sw); + bigint_add3(&ws[mod_sw * 2], this->_data(), mod_sw, s._data(), mod_sw); CT::conditional_copy_mem(borrow, &ws[0], &ws[mod_sw * 2], &ws[mod_sw], mod_sw); set_words(&ws[0], mod_sw); + // NOLINTEND(readability-container-data-pointer) + return (*this); } BigInt& BigInt::mod_sub(const BigInt& s, const BigInt& mod, secure_vector& ws) { - if(this->is_negative() || s.is_negative() || mod.is_negative()) { + if(this->signum() < 0 || s.signum() < 0 || mod.signum() < 0) { throw Invalid_Argument("BigInt::mod_sub expects all arguments are positive"); } @@ -105,13 +109,18 @@ ws.resize(mod_sw); } - bigint_mod_sub(mutable_data(), s._data(), mod._data(), mod_sw, ws.data()); + const word borrow = bigint_sub3(ws.data(), mutable_data(), mod_sw, s._data(), mod_sw); + + // Conditionally add back the modulus + bigint_cnd_add(borrow, ws.data(), mod._data(), mod_sw); + + unchecked_copy_memory(mutable_data(), ws.data(), mod_sw); return (*this); } BigInt& BigInt::mod_mul(uint8_t y, const BigInt& mod, secure_vector& ws) { - BOTAN_ARG_CHECK(this->is_negative() == false, "*this must be positive"); + BOTAN_ARG_CHECK(this->signum() >= 0, "*this must be positive"); BOTAN_ARG_CHECK(y < 16, "y too large"); BOTAN_DEBUG_ASSERT(*this < mod); @@ -122,20 +131,10 @@ } BigInt& BigInt::rev_sub(const word y[], size_t y_sw, secure_vector& ws) { - if(this->sign() != BigInt::Positive) { - throw Invalid_State("BigInt::sub_rev requires this is positive"); - } - - const size_t x_sw = this->sig_words(); - - ws.resize(std::max(x_sw, y_sw)); - clear_mem(ws.data(), ws.size()); - - const int32_t relative_size = bigint_sub_abs(ws.data(), _data(), x_sw, y, y_sw); - - this->cond_flip_sign(relative_size > 0); - this->swap_reg(ws); - + BOTAN_UNUSED(ws); + BigInt y_bn; + y_bn.m_data.set_words(y, y_sw); + *this = y_bn - *this; return (*this); } @@ -155,15 +154,14 @@ if(x_sw == 0 || y_sw == 0) { clear(); set_sign(Positive); - } else if(x_sw == 1 && y_sw) { + } else if(x_sw == 1 && y_sw > 0) { grow_to(y_sw + 1); bigint_linmul3(mutable_data(), y._data(), y_sw, word_at(0)); - } else if(y_sw == 1 && x_sw) { - word carry = bigint_linmul2(mutable_data(), x_sw, y.word_at(0)); - set_word_at(x_sw, carry); } else { const size_t new_size = x_sw + y_sw + 1; - ws.resize(new_size); + if(ws.size() < new_size) { + ws.resize(new_size); + } secure_vector z_reg(new_size); bigint_mul(z_reg.data(), z_reg.size(), _data(), size(), x_sw, y._data(), y.size(), y_sw, ws.data(), ws.size()); @@ -204,7 +202,7 @@ * Division Operator */ BigInt& BigInt::operator/=(const BigInt& y) { - if(y.sig_words() == 1 && is_power_of_2(y.word_at(0))) { + if(y.sig_words() == 1 && signum() >= 0 && y.signum() >= 0 && is_power_of_2(y.word_at(0))) { (*this) >>= (y.bits() - 1); } else { (*this) = (*this) / y; @@ -232,13 +230,14 @@ if(is_power_of_2(mod)) { remainder = (word_at(0) & (mod - 1)); } else { + const divide_precomp redc_mod(mod); const size_t sw = sig_words(); for(size_t i = sw; i > 0; --i) { - remainder = bigint_modop_vartime(remainder, word_at(i - 1), mod); + remainder = redc_mod.vartime_mod_2to1(remainder, word_at(i - 1)); } } - if(remainder && sign() == BigInt::Negative) { + if(remainder != 0 && sign() == BigInt::Negative) { remainder = mod - remainder; } @@ -253,7 +252,7 @@ */ BigInt& BigInt::operator<<=(size_t shift) { const size_t sw = sig_words(); - const size_t new_size = sw + (shift + BOTAN_MP_WORD_BITS - 1) / BOTAN_MP_WORD_BITS; + const size_t new_size = sw + (shift + WordInfo::bits - 1) / WordInfo::bits; m_data.grow_to(new_size); @@ -268,8 +267,8 @@ BigInt& BigInt::operator>>=(size_t shift) { bigint_shr1(m_data.mutable_data(), m_data.size(), shift); - if(is_negative() && is_zero()) { - set_sign(Positive); + if(sig_words() == 0 && m_signedness == Negative) { + m_signedness = Positive; } return (*this); diff -Nru botan3-3.7.1+dfsg/src/lib/math/bigint/big_ops3.cpp botan3-3.12.0+dfsg/src/lib/math/bigint/big_ops3.cpp --- botan3-3.7.1+dfsg/src/lib/math/bigint/big_ops3.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/bigint/big_ops3.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,6 +8,7 @@ #include +#include #include #include #include @@ -16,23 +17,33 @@ namespace Botan { //static -BigInt BigInt::add2(const BigInt& x, const word y[], size_t y_words, BigInt::Sign y_sign) { +BigInt BigInt::add2(const BigInt& x, const word y[], size_t y_size, BigInt::Sign y_sign) { const size_t x_sw = x.sig_words(); - BigInt z = BigInt::with_capacity(std::max(x_sw, y_words) + 1); + BigInt z = BigInt::with_capacity(std::max(x_sw, y_size) + 1); if(x.sign() == y_sign) { - bigint_add3(z.mutable_data(), x._data(), x_sw, y, y_words); + const word carry = bigint_add3(z.mutable_data(), x._data(), x_sw, y, y_size); + z.mutable_data()[std::max(x_sw, y_size)] += carry; z.set_sign(x.sign()); } else { - const int32_t relative_size = bigint_sub_abs(z.mutable_data(), x._data(), x_sw, y, y_words); + const int32_t relative_size = bigint_cmp(x.data(), x_sw, y, y_size); - //z.sign_fixup(relative_size, y_sign); if(relative_size < 0) { + // x < y so z = abs(y - x) + // NOLINTNEXTLINE(*-suspicious-call-argument) intentionally swapping x and y here + bigint_sub3(z.mutable_data(), y, y_size, x.data(), x_sw); z.set_sign(y_sign); } else if(relative_size == 0) { - z.set_sign(BigInt::Positive); + // Positive zero (nothing to do in this case) } else { + /* + * We know at this point that x >= y so if y_size is larger than + * x_sw, we are guaranteed they are just leading zeros which can + * be ignored + */ + y_size = std::min(x_sw, y_size); + bigint_sub3(z.mutable_data(), x.data(), x_sw, y, y_size); z.set_sign(x.sign()); } } @@ -49,11 +60,11 @@ BigInt z = BigInt::with_capacity(x.size() + y.size()); - if(x_sw == 1 && y_sw) { + if(x_sw == 1 && y_sw > 0) { bigint_linmul3(z.mutable_data(), y._data(), y_sw, x.word_at(0)); - } else if(y_sw == 1 && x_sw) { + } else if(y_sw == 1 && x_sw > 0) { bigint_linmul3(z.mutable_data(), x._data(), x_sw, y.word_at(0)); - } else if(x_sw && y_sw) { + } else if(x_sw > 0 && y_sw > 0) { secure_vector workspace(z.size()); bigint_mul(z.mutable_data(), @@ -81,7 +92,7 @@ BigInt z = BigInt::with_capacity(x_sw + 1); - if(x_sw && y) { + if(x_sw > 0 && y > 0) { bigint_linmul3(z.mutable_data(), x._data(), x_sw, y); z.set_sign(x.sign()); } @@ -93,11 +104,12 @@ * Division Operator */ BigInt operator/(const BigInt& x, const BigInt& y) { - if(y.sig_words() == 1) { + if(y.sig_words() == 1 && y.signum() >= 0) { return x / y.word_at(0); } - BigInt q, r; + BigInt q; + BigInt r; vartime_divide(x, y, q, r); return q; } @@ -111,7 +123,7 @@ } BigInt q; - word r; + word r = 0; ct_divide_word(x, y, q, r); return q; } @@ -123,10 +135,10 @@ if(mod.is_zero()) { throw Invalid_Argument("BigInt::operator% divide by zero"); } - if(mod.is_negative()) { + if(mod.signum() < 0) { throw Invalid_Argument("BigInt::operator% modulus must be > 0"); } - if(n.is_positive() && mod.is_positive() && n < mod) { + if(n.signum() >= 0 && mod.signum() >= 0 && n < mod) { return n; } @@ -134,7 +146,8 @@ return BigInt::from_word(n % mod.word_at(0)); } - BigInt q, r; + BigInt q; + BigInt r; vartime_divide(n, mod, q, r); return r; } @@ -153,16 +166,17 @@ word remainder = 0; - if(is_power_of_2(mod)) { + if(n.signum() >= 0 && is_power_of_2(mod)) { remainder = (n.word_at(0) & (mod - 1)); } else { + const divide_precomp redc_mod(mod); const size_t sw = n.sig_words(); for(size_t i = sw; i > 0; --i) { - remainder = bigint_modop_vartime(remainder, n.word_at(i - 1), mod); + remainder = redc_mod.vartime_mod_2to1(remainder, n.word_at(i - 1)); } } - if(remainder && n.sign() == BigInt::Negative) { + if(remainder != 0 && n.sign() == BigInt::Negative) { return mod - remainder; } return remainder; @@ -172,9 +186,13 @@ * Left Shift Operator */ BigInt operator<<(const BigInt& x, size_t shift) { + if(x.is_zero()) { + return BigInt::zero(); + } + const size_t x_sw = x.sig_words(); - const size_t new_size = x_sw + (shift + BOTAN_MP_WORD_BITS - 1) / BOTAN_MP_WORD_BITS; + const size_t new_size = x_sw + (shift + WordInfo::bits - 1) / WordInfo::bits; BigInt y = BigInt::with_capacity(new_size); bigint_shl2(y.mutable_data(), x._data(), x_sw, shift); y.set_sign(x.sign()); @@ -185,7 +203,7 @@ * Right Shift Operator */ BigInt operator>>(const BigInt& x, size_t shift) { - const size_t shift_words = shift / BOTAN_MP_WORD_BITS; + const size_t shift_words = shift / WordInfo::bits; const size_t x_sw = x.sig_words(); if(shift_words >= x_sw) { @@ -195,7 +213,7 @@ BigInt y = BigInt::with_capacity(x_sw - shift_words); bigint_shr2(y.mutable_data(), x._data(), x_sw, shift); - if(x.is_negative() && y.is_zero()) { + if(x.signum() < 0 && y.is_zero()) { y.set_sign(BigInt::Positive); } else { y.set_sign(x.sign()); diff -Nru botan3-3.7.1+dfsg/src/lib/math/bigint/big_rand.cpp botan3-3.12.0+dfsg/src/lib/math/bigint/big_rand.cpp --- botan3-3.7.1+dfsg/src/lib/math/bigint/big_rand.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/bigint/big_rand.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,6 +7,7 @@ #include +#include #include #include @@ -24,13 +25,13 @@ secure_vector array = rng.random_vec(round_up(bitsize, 8) / 8); // Always cut unwanted bits - if(bitsize % 8) { + if(bitsize % 8 > 0) { array[0] &= 0xFF >> (8 - (bitsize % 8)); } // Set the highest bit if wanted if(set_high_bit) { - array[0] |= 0x80 >> ((bitsize % 8) ? (8 - bitsize % 8) : 0); + array[0] |= 0x80 >> ((bitsize % 8) > 0 ? (8 - bitsize % 8) : 0); } assign_from_bytes(array); @@ -41,7 +42,7 @@ * Generate a random integer within given range */ BigInt BigInt::random_integer(RandomNumberGenerator& rng, const BigInt& min, const BigInt& max) { - if(min.is_negative() || max.is_negative() || max <= min) { + if(min.signum() < 0 || max.signum() < 0 || max <= min) { throw Invalid_Argument("BigInt::random_integer invalid range"); } @@ -49,7 +50,7 @@ If min is > 1 then we generate a random number `r` in [0,max-min) and return min + r. - This same logic could also be reasonbly chosen for min == 1, but + This same logic could also be reasonably chosen for min == 1, but that breaks certain tests which expect stability of this function when generating within [1,n) */ @@ -63,13 +64,13 @@ const size_t bits = max.bits(); - BigInt r; - - do { + for(;;) { + BigInt r; r.randomize(rng, bits, false); - } while(r < min || r >= max); - - return r; + if(r >= min && r < max) { + return r; + } + } } } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/math/bigint/bigint.cpp botan3-3.12.0+dfsg/src/lib/math/bigint/bigint.cpp --- botan3-3.7.1+dfsg/src/lib/math/bigint/bigint.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/bigint/bigint.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,35 +7,28 @@ #include +#include #include #include #include +#include #include #include namespace Botan { BigInt::BigInt(uint64_t n) { -#if BOTAN_MP_WORD_BITS == 64 - m_data.set_word_at(0, n); -#else - m_data.set_word_at(1, static_cast(n >> 32)); - m_data.set_word_at(0, static_cast(n)); -#endif + if constexpr(sizeof(word) == 8) { + m_data.set_word_at(0, static_cast(n)); + } else { + m_data.set_word_at(1, static_cast(n >> 32)); + m_data.set_word_at(0, static_cast(n)); + } } //static BigInt BigInt::from_u64(uint64_t n) { - BigInt bn; - -#if BOTAN_MP_WORD_BITS == 64 - bn.set_word_at(0, n); -#else - bn.set_word_at(1, static_cast(n >> 32)); - bn.set_word_at(0, static_cast(n)); -#endif - - return bn; + return BigInt(n); } //static @@ -61,35 +54,30 @@ return bn; } -/* -* Construct a BigInt from a string -*/ -BigInt::BigInt(std::string_view str) { - Base base = Decimal; - size_t markers = 0; +BigInt BigInt::from_string(std::string_view str) { + size_t prefix_bytes = 0; bool negative = false; + size_t radix = 10; if(!str.empty() && str[0] == '-') { - markers += 1; + prefix_bytes += 1; negative = true; } - if(str.length() > markers + 2 && str[markers] == '0' && str[markers + 1] == 'x') { - markers += 2; - base = Hexadecimal; + if(str.length() > prefix_bytes + 2 && str[prefix_bytes] == '0' && str[prefix_bytes + 1] == 'x') { + prefix_bytes += 2; + radix = 16; } - *this = decode(cast_char_ptr_to_uint8(str.data()) + markers, str.length() - markers, base); + BigInt r = BigInt::from_radix_digits(str.substr(prefix_bytes), radix); if(negative) { - set_sign(Negative); + r.set_sign(Negative); } else { - set_sign(Positive); + r.set_sign(Positive); } -} -BigInt BigInt::from_string(std::string_view str) { - return BigInt(str); + return r; } BigInt BigInt::from_bytes(std::span input) { @@ -132,7 +120,7 @@ } int32_t BigInt::cmp_word(word other) const { - if(is_negative()) { + if(signum() < 0) { return -1; // other is positive ... } @@ -149,15 +137,15 @@ */ int32_t BigInt::cmp(const BigInt& other, bool check_signs) const { if(check_signs) { - if(other.is_positive() && this->is_negative()) { + if(other.signum() >= 0 && this->signum() < 0) { return -1; } - if(other.is_negative() && this->is_positive()) { + if(other.signum() < 0 && this->signum() >= 0) { return 1; } - if(other.is_negative() && this->is_negative()) { + if(other.signum() < 0 && this->signum() < 0) { return (-bigint_cmp(this->_data(), this->size(), other._data(), other.size())); } } @@ -170,23 +158,23 @@ return false; } - return bigint_ct_is_eq(this->_data(), this->sig_words(), other._data(), other.sig_words()).as_bool(); + return bigint_ct_is_eq(this->_data(), this->size(), other._data(), other.size()).as_bool(); } bool BigInt::is_less_than(const BigInt& other) const { - if(this->is_negative() && other.is_positive()) { + if(this->signum() < 0 && other.signum() >= 0) { return true; } - if(this->is_positive() && other.is_negative()) { + if(this->signum() >= 0 && other.signum() < 0) { return false; } - if(other.is_negative() && this->is_negative()) { - return bigint_ct_is_lt(other._data(), other.sig_words(), this->_data(), this->sig_words()).as_bool(); + if(other.signum() < 0 && this->signum() < 0) { + return bigint_ct_is_lt(other._data(), other.size(), this->_data(), this->size()).as_bool(); } - return bigint_ct_is_lt(this->_data(), this->sig_words(), other._data(), other.sig_words()).as_bool(); + return bigint_ct_is_lt(this->_data(), this->size(), other._data(), other.size()).as_bool(); } void BigInt::encode_words(word out[], size_t size) const { @@ -200,6 +188,30 @@ copy_mem(out, _data(), words); } +void BigInt::Data::set_to_zero() { + m_reg.resize(m_reg.capacity()); + clear_mem(m_reg.data(), m_reg.size()); + m_sig_words = 0; +} + +void BigInt::Data::mask_bits(size_t n) { + if(n == 0) { + return set_to_zero(); + } + + const size_t top_word = n / WordInfo::bits; + + if(top_word < size()) { + const word mask = (static_cast(1) << (n % WordInfo::bits)) - 1; + const size_t len = size() - (top_word + 1); + if(len > 0) { + clear_mem(&m_reg[top_word + 1], len); + } + m_reg[top_word] &= mask; + invalidate_sig_words(); + } +} + size_t BigInt::Data::calc_sig_words() const { const size_t sz = m_reg.size(); size_t sig = sz; @@ -231,8 +243,8 @@ const uint32_t mask = 0xFFFFFFFF >> (32 - length); - const size_t word_offset = offset / BOTAN_MP_WORD_BITS; - const size_t wshift = (offset % BOTAN_MP_WORD_BITS); + const size_t word_offset = offset / WordInfo::bits; + const size_t wshift = (offset % WordInfo::bits); /* * The substring is contained within one or at most two words. The @@ -241,11 +253,11 @@ */ const word w0 = word_at(word_offset); - if(wshift == 0 || (offset + length) / BOTAN_MP_WORD_BITS == word_offset) { + if(wshift == 0 || (offset + length) / WordInfo::bits == word_offset) { return static_cast(w0 >> wshift) & mask; } else { const word w1 = word_at(word_offset + 1); - return static_cast((w0 >> wshift) | (w1 << (BOTAN_MP_WORD_BITS - wshift))) & mask; + return static_cast((w0 >> wshift) | (w1 << (WordInfo::bits - wshift))) & mask; } } @@ -253,7 +265,7 @@ * Convert this number to a uint32_t, if possible */ uint32_t BigInt::to_u32bit() const { - if(is_negative()) { + if(signum() < 0) { throw Encoding_Error("BigInt::to_u32bit: Number is negative"); } if(bits() > 32) { @@ -271,10 +283,10 @@ * Clear bit number n */ void BigInt::clear_bit(size_t n) { - const size_t which = n / BOTAN_MP_WORD_BITS; + const size_t which = n / WordInfo::bits; if(which < size()) { - const word mask = ~(static_cast(1) << (n % BOTAN_MP_WORD_BITS)); + const word mask = ~(static_cast(1) << (n % WordInfo::bits)); m_data.set_word_at(which, word_at(which) & mask); } } @@ -289,7 +301,7 @@ const word top_word = word_at(words - 1); const size_t bits_used = high_bit(CT::value_barrier(top_word)); CT::unpoison(bits_used); - return BOTAN_MP_WORD_BITS - bits_used; + return WordInfo::bits - bits_used; } size_t BigInt::bits() const { @@ -299,8 +311,8 @@ return 0; } - const size_t full_words = (words - 1) * BOTAN_MP_WORD_BITS; - const size_t top_bits = BOTAN_MP_WORD_BITS - top_bits_free(); + const size_t full_words = (words - 1) * WordInfo::bits; + const size_t top_bits = WordInfo::bits - top_bits_free(); return full_words + top_bits; } @@ -315,7 +327,7 @@ } size_t BigInt::reduce_below(const BigInt& p, secure_vector& ws) { - if(p.is_negative() || this->is_negative()) { + if(p.signum() < 0 || this->signum() < 0) { throw Invalid_Argument("BigInt::reduce_below both values must be positive"); } @@ -334,8 +346,8 @@ size_t reductions = 0; for(;;) { - word borrow = bigint_sub3(ws.data(), _data(), p_words + 1, p._data(), p_words); - if(borrow) { + const word borrow = bigint_sub3(ws.data(), _data(), p_words + 1, p._data(), p_words); + if(borrow > 0) { break; } @@ -347,7 +359,7 @@ } void BigInt::ct_reduce_below(const BigInt& mod, secure_vector& ws, size_t bound) { - if(mod.is_negative() || this->is_negative()) { + if(mod.signum() < 0 || this->signum() < 0) { throw Invalid_Argument("BigInt::ct_reduce_below both values must be positive"); } @@ -362,7 +374,7 @@ clear_mem(ws.data(), sz); for(size_t i = 0; i != bound; ++i) { - word borrow = bigint_sub3(ws.data(), _data(), sz, mod._data(), mod_words); + const word borrow = bigint_sub3(ws.data(), _data(), sz, mod._data(), mod_words); CT::Mask::is_zero(borrow).select_n(mutable_data(), ws.data(), _data(), sz); } @@ -435,29 +447,30 @@ } void BigInt::ct_cond_add(bool predicate, const BigInt& value) { - if(this->is_negative() || value.is_negative()) { + if(this->signum() < 0 || value.signum() < 0) { throw Invalid_Argument("BigInt::ct_cond_add requires both values to be positive"); } - this->grow_to(1 + value.sig_words()); + const size_t v_words = value.sig_words(); + + this->grow_to(1 + v_words); - bigint_cnd_add(static_cast(predicate), this->mutable_data(), this->size(), value._data(), value.sig_words()); + const auto mask = CT::Mask::expand(static_cast(predicate)).value(); + + word carry = 0; + + word* x = this->mutable_data(); + const word* y = value._data(); + + for(size_t i = 0; i != v_words; ++i) { + x[i] = word_add(x[i], y[i] & mask, &carry); + } + + for(size_t i = v_words; i != size(); ++i) { + x[i] = word_add(x[i], static_cast(0), &carry); + } } void BigInt::ct_shift_left(size_t shift) { - auto shl_bit = [](const BigInt& a, BigInt& result) { - BOTAN_DEBUG_ASSERT(a.size() + 1 == result.size()); - bigint_shl2(result.mutable_data(), a._data(), a.size(), 1); - // shl2 may have shifted a bit into the next word, which must be dropped - clear_mem(result.mutable_data() + result.size() - 1, 1); - }; - - auto shl_word = [](const BigInt& a, BigInt& result) { - // the most significant word is not copied, aka. shifted out - bigint_shl2(result.mutable_data(), a._data(), a.size() - 1 /* ignore msw */, BOTAN_MP_WORD_BITS); - // we left-shifted by a full word, the least significant word must be zero'ed - clear_mem(result.mutable_data(), 1); - }; - BOTAN_ASSERT_NOMSG(size() > 0); constexpr size_t bits_in_word = sizeof(word) * 8; @@ -465,15 +478,33 @@ const size_t bit_shift = shift & ((1 << ceil_log2(bits_in_word)) - 1); // shift % bits_in_word const size_t iterations = std::max(size(), bits_in_word) - 1; // uint64_t i; i << 64 is undefined behaviour + const size_t n = size(); + + // Workspace 1 word larger to catch overflow from bigint_shl2 + secure_vector ws(n + 1); + // In every iteration, shift one bit and one word to the left and use the // shift results only when they are within the shift range. - BigInt tmp; - tmp.resize(size() + 1 /* to hold the shifted-out word */); for(size_t i = 0; i < iterations; ++i) { - shl_bit(*this, tmp); - ct_cond_assign(i < bit_shift, tmp); - shl_word(*this, tmp); - ct_cond_assign(i < word_shift, tmp); + // Shift left by 1 bit, dropping overflow + bigint_shl2(ws.data(), _data(), n, 1); + ws[n] = 0; + + // Conditionally assign the bit-shift result + const auto bmask = CT::Mask::expand_bool(i < bit_shift); + for(size_t j = 0; j != n; ++j) { + m_data.set_word_at(j, bmask.select(ws[j], word_at(j))); + } + + // Shift left by 1 word, dropping the most significant word + bigint_shl2(ws.data(), _data(), n - 1 /* ignore msw */, WordInfo::bits); + ws[0] = 0; + + // Conditionally assign the word-shift result + const auto wmask = CT::Mask::expand_bool(i < word_shift); + for(size_t j = 0; j != n; ++j) { + m_data.set_word_at(j, wmask.select(ws[j], word_at(j))); + } } } @@ -488,7 +519,7 @@ void BigInt::cond_flip_sign(bool predicate) { // This code is assuming Negative == 0, Positive == 1 - const auto mask = CT::Mask::expand(predicate); + const auto mask = CT::Mask::expand_bool(predicate); const uint8_t current_sign = static_cast(sign()); @@ -501,13 +532,13 @@ const size_t t_words = size(); const size_t o_words = other.size(); - if(o_words < t_words) { + if(t_words < o_words) { grow_to(o_words); } const size_t r_words = std::max(t_words, o_words); - const auto mask = CT::Mask::expand(predicate); + const auto mask = CT::Mask::expand_bool(predicate); for(size_t i = 0; i != r_words; ++i) { const word o_word = other.word_at(i); @@ -519,7 +550,6 @@ cond_flip_sign((mask.as_choice() && !same_sign).as_bool()); } -#if defined(BOTAN_CT_POISON_ENABLED) void BigInt::_const_time_poison() const { CT::poison(m_data.const_data(), m_data.size()); } @@ -527,6 +557,5 @@ void BigInt::_const_time_unpoison() const { CT::unpoison(m_data.const_data(), m_data.size()); } -#endif } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/math/bigint/bigint.h botan3-3.12.0+dfsg/src/lib/math/bigint/bigint.h --- botan3-3.7.1+dfsg/src/lib/math/bigint/bigint.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/bigint/bigint.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,12 +9,12 @@ #ifndef BOTAN_BIGINT_H_ #define BOTAN_BIGINT_H_ -#include -#include #include #include #include #include +#include +#include namespace Botan { @@ -28,7 +28,7 @@ /** * Base enumerator for encoding and decoding */ - enum Base { + enum Base : uint16_t /* NOLINT(*-use-enum-class) */ { Decimal BOTAN_DEPRECATED("All functions using this enum are deprecated") = 10, Hexadecimal BOTAN_DEPRECATED("All functions using this enum are deprecated") = 16, Binary BOTAN_DEPRECATED("All functions using this enum are deprecated") = 256 @@ -37,7 +37,7 @@ /** * Sign symbol definitions for positive and negative numbers */ - enum Sign { Negative = 0, Positive = 1 }; + enum Sign : uint8_t /* NOLINT(*-use-enum-class) */ { Negative = 0, Positive = 1 }; /** * Create empty (zero) BigInt @@ -79,7 +79,7 @@ * * Prefer BigInt::from_u64 */ - BigInt(uint64_t n); + BigInt(uint64_t n); // NOLINT(*-explicit-conversions) TODO(Botan4) make this explicit /** * Copy Constructor @@ -95,7 +95,7 @@ * @param str the string to parse for an integer value */ //BOTAN_DEPRECATED("Use BigInt::from_string") - explicit BigInt(std::string_view str); + explicit BigInt(std::string_view str) { *this = BigInt::from_string(str); } /** * Create BigInt from a string. @@ -111,6 +111,21 @@ static BigInt from_string(std::string_view str); /** + * Create BigInt from a sequence of digits + * + * The string is interpreted as a sequence of digits in base @p radix. + * + * Each character must be interpretable as such a digit; there is no support + * for whitespace or prefixes (eg '0x' or '-'). + * + * Currently radix must be 10 or 16. + * + * @param digits the sequence of digits + * @param radix the base + */ + static BigInt from_radix_digits(std::string_view digits, size_t radix); + + /** * Create a BigInt from an integer in a byte array * @param buf the byte array holding the value * @param length size of buf @@ -165,14 +180,14 @@ /** * Move constructor */ - BigInt(BigInt&& other) { this->swap(other); } + BigInt(BigInt&& other) noexcept { this->swap(other); } ~BigInt() { _const_time_unpoison(); } /** * Move assignment */ - BigInt& operator=(BigInt&& other) { + BigInt& operator=(BigInt&& other) noexcept { if(this != &other) { this->swap(other); } @@ -189,12 +204,12 @@ * Swap this value with another * @param other BigInt to swap values with */ - void swap(BigInt& other) { + void swap(BigInt& other) noexcept { m_data.swap(other.m_data); std::swap(m_signedness, other.m_signedness); } - friend void swap(BigInt& x, BigInt& y) { x.swap(y); } + friend void swap(BigInt& x, BigInt& y) noexcept { x.swap(y); } BOTAN_DEPRECATED("Deprecated no replacement") void swap_reg(secure_vector& reg) { m_data.swap(reg); @@ -305,7 +320,7 @@ * ! operator * @return true iff this is zero, otherwise false */ - bool operator!() const { return (!is_nonzero()); } + bool operator!() const { return is_zero(); } //BOTAN_DEPRECATED("Just use operator+/operator-") static BigInt add2(const BigInt& x, const word y[], size_t y_words, Sign y_sign); @@ -437,25 +452,36 @@ * Test if the integer has an even value * @result true if the integer is even, false otherwise */ - bool is_even() const { return (get_bit(0) == 0); } + bool is_even() const { return !get_bit(0); } /** * Test if the integer has an odd value * @result true if the integer is odd, false otherwise */ - bool is_odd() const { return (get_bit(0) == 1); } + bool is_odd() const { return get_bit(0); } + + /** + * Return the signum of this integer + * @result -1 if negative, 0 if zero, 1 if positive + */ + int signum() const { + if(sig_words() == 0) { + return 0; + } + return (sign() == Negative) ? -1 : 1; + } /** * Test if the integer is not zero * @result true if the integer is non-zero, false otherwise */ - bool is_nonzero() const { return (!is_zero()); } + BOTAN_DEPRECATED("Use signum() != 0") bool is_nonzero() const { return signum() != 0; } /** * Test if the integer is zero * @result true if the integer is zero, false otherwise */ - bool is_zero() const { return (sig_words() == 0); } + bool is_zero() const { return sig_words() == 0; } /** * Set bit at specified position @@ -472,8 +498,8 @@ * @param set_it if the bit should be set */ void conditionally_set_bit(size_t n, bool set_it) { - const size_t which = n / BOTAN_MP_WORD_BITS; - const word mask = static_cast(set_it) << (n % BOTAN_MP_WORD_BITS); + const size_t which = n / (sizeof(word) * 8); + const word mask = static_cast(set_it) << (n % (sizeof(word) * 8)); m_data.set_word_at(which, word_at(which) | mask); } @@ -487,14 +513,14 @@ * Clear all but the lowest n bits * @param n amount of bits to keep */ - void mask_bits(size_t n) { m_data.mask_bits(n); } + BOTAN_DEPRECATED("Deprecated no replacement") void mask_bits(size_t n) { m_data.mask_bits(n); } /** * Return bit value at specified position * @param n the bit offset to test * @result true, if the bit at position n is set, false otherwise */ - bool get_bit(size_t n) const { return ((word_at(n / BOTAN_MP_WORD_BITS) >> (n % BOTAN_MP_WORD_BITS)) & 1); } + bool get_bit(size_t n) const { return ((word_at(n / (sizeof(word) * 8)) >> (n % (sizeof(word) * 8))) & 1) == 1; } /** * Return (a maximum of) 32 bits of the complete value @@ -510,7 +536,7 @@ * [0 ... 2**32-1], or otherwise throw an exception. * @result the value as a uint32_t if conversion is possible */ - uint32_t to_u32bit() const; + BOTAN_DEPRECATED("Deprecated no replacement") uint32_t to_u32bit() const; /** * Convert this value to a decimal string. @@ -557,13 +583,19 @@ * Tests if the sign of the integer is negative * @result true, iff the integer has a negative sign */ - bool is_negative() const { return (sign() == Negative); } + BOTAN_DEPRECATED("Use signum() < 0") bool is_negative() const { return signum() < 0; } /** * Tests if the sign of the integer is positive + * + * Note that this is testing the sign, thus it returns true also for zero + * Prefer signum which is unambiguous + * * @result true, iff the integer has a positive sign */ - bool is_positive() const { return (sign() == Positive); } + BOTAN_DEPRECATED("Use signum() >= 0 or signum() > 0 as appropriate") bool is_positive() const { + return signum() >= 0; + } /** * Return the sign of the integer @@ -584,7 +616,7 @@ /** * Flip the sign of this BigInt */ - void flip_sign() { set_sign(reverse_sign()); } + BOTAN_DEPRECATED("Deprecated no replacement") void flip_sign() { set_sign(reverse_sign()); } /** * Set sign of the integer @@ -629,7 +661,7 @@ /** * Get the number of high bits unset in the top (allocated) word - * of this integer. Returns BOTAN_MP_WORD_BITS only iff *this is + * of this integer. Returns (sizeof(word) * 8) only iff *this is * zero. Ignores sign. */ BOTAN_DEPRECATED("Deprecated no replacement") size_t top_bits_free() const; @@ -777,24 +809,24 @@ * If predicate is true assign other to *this * Uses a masked operation to avoid side channels */ - void ct_cond_assign(bool predicate, const BigInt& other); + BOTAN_DEPRECATED("Deprecated no replacement") void ct_cond_assign(bool predicate, const BigInt& other); /** * If predicate is true swap *this and other * Uses a masked operation to avoid side channels */ - void ct_cond_swap(bool predicate, BigInt& other); + BOTAN_DEPRECATED("Deprecated no replacement") void ct_cond_swap(bool predicate, BigInt& other); /** * If predicate is true add value to *this */ - void ct_cond_add(bool predicate, const BigInt& value); + BOTAN_DEPRECATED("Deprecated no replacement") void ct_cond_add(bool predicate, const BigInt& value); /** * Shift @p shift bits to the left, runtime is independent of * the value of @p shift. */ - void ct_shift_left(size_t shift); + BOTAN_DEPRECATED("Deprecated no replacement") void ct_shift_left(size_t shift); /** * If predicate is true flip the sign of *this @@ -805,15 +837,6 @@ BOTAN_DEPRECATED("replaced by internal API") void const_time_unpoison() const { _const_time_unpoison(); } -#if defined(BOTAN_CT_POISON_ENABLED) - void _const_time_poison() const; - void _const_time_unpoison() const; -#else - constexpr void _const_time_poison() const {} - - constexpr void _const_time_unpoison() const {} -#endif - /** * @param rng a random number generator * @param min the minimum value (must be non-negative) @@ -926,6 +949,16 @@ static secure_vector encode_fixed_length_int_pair(const BigInt& n1, const BigInt& n2, size_t bytes); /** + * Return a span over the register + * + * @warning this is an implementation detail which is not for + * public use and not covered by SemVer. + * + * @result span over the internal register + */ + std::span _as_span() const { return m_data.const_span(); } + + /** * Return a const pointer to the register * * @warning this is an implementation detail which is not for @@ -946,6 +979,34 @@ */ void _assign_from_bytes(std::span bytes) { assign_from_bytes(bytes); } + /** + * Create a BigInt from a word vector + * + * @warning this is an implementation detail which is not for + * public use and not covered by SemVer. + */ + static BigInt _from_words(secure_vector& words) { + BigInt bn; + bn.m_data.swap(words); + return bn; + } + + /** + * Mark this BigInt as holding secret data + * + * @warning this is an implementation detail which is not for + * public use and not covered by SemVer. + */ + void _const_time_poison() const; + + /** + * Mark this BigInt as no longer holding secret data + * + * @warning this is an implementation detail which is not for + * public use and not covered by SemVer. + */ + void _const_time_unpoison() const; + private: /** * Read integer value from a byte vector (big endian) @@ -953,7 +1014,7 @@ */ void assign_from_bytes(std::span bytes); - class Data { + class Data final { public: word* mutable_data() { invalidate_sig_words(); @@ -962,6 +1023,8 @@ const word* const_data() const { return m_reg.data(); } + std::span const_span() const { return std::span{m_reg}; } + secure_vector& mutable_vector() { invalidate_sig_words(); return m_reg; @@ -992,36 +1055,9 @@ m_reg.assign(w, w + len); } - void set_to_zero() { - m_reg.resize(m_reg.capacity()); - clear_mem(m_reg.data(), m_reg.size()); - m_sig_words = 0; - } - - void set_size(size_t s) { - invalidate_sig_words(); - clear_mem(m_reg.data(), m_reg.size()); - m_reg.resize(s + (8 - (s % 8))); - } - - void mask_bits(size_t n) { - if(n == 0) { - return set_to_zero(); - } - - const size_t top_word = n / BOTAN_MP_WORD_BITS; + void set_to_zero(); - // if(top_word < sig_words()) ? - if(top_word < size()) { - const word mask = (static_cast(1) << (n % BOTAN_MP_WORD_BITS)) - 1; - const size_t len = size() - (top_word + 1); - if(len > 0) { - clear_mem(&m_reg[top_word + 1], len); - } - m_reg[top_word] &= mask; - invalidate_sig_words(); - } - } + void mask_bits(size_t n); void grow_to(size_t n) const { if(n > size()) { @@ -1042,23 +1078,21 @@ void resize(size_t s) { m_reg.resize(s); } - void swap(Data& other) { + void swap(Data& other) noexcept { m_reg.swap(other.m_reg); std::swap(m_sig_words, other.m_sig_words); } - void swap(secure_vector& reg) { + void swap(secure_vector& reg) noexcept { m_reg.swap(reg); invalidate_sig_words(); } - void invalidate_sig_words() const { m_sig_words = sig_words_npos; } + void invalidate_sig_words() const noexcept { m_sig_words = sig_words_npos; } size_t sig_words() const { if(m_sig_words == sig_words_npos) { m_sig_words = calc_sig_words(); - } else { - BOTAN_DEBUG_ASSERT(m_sig_words == calc_sig_words()); } return m_sig_words; } @@ -1168,9 +1202,10 @@ * I/O Operators */ BOTAN_DEPRECATED("Use BigInt::to_{hex,dec}_string") -BOTAN_PUBLIC_API(2, 0) std::ostream& operator<<(std::ostream&, const BigInt&); +BOTAN_PUBLIC_API(2, 0) std::ostream& operator<<(std::ostream& stream, const BigInt& n); -BOTAN_DEPRECATED("Use BigInt::from_string") BOTAN_PUBLIC_API(2, 0) std::istream& operator>>(std::istream&, BigInt&); +BOTAN_DEPRECATED("Use BigInt::from_string") +BOTAN_PUBLIC_API(2, 0) std::istream& operator>>(std::istream& stream, BigInt& n); } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/math/bigint/divide.cpp botan3-3.12.0+dfsg/src/lib/math/bigint/divide.cpp --- botan3-3.7.1+dfsg/src/lib/math/bigint/divide.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/bigint/divide.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,7 +7,7 @@ #include -#include +#include #include #include @@ -21,13 +21,13 @@ void sign_fixup(const BigInt& x, const BigInt& y, BigInt& q, BigInt& r) { q.cond_flip_sign(x.sign() != y.sign()); - if(x.is_negative() && r.is_nonzero()) { + if(x.signum() < 0 && r.signum() != 0) { q -= 1; r = y.abs() - r; } } -inline bool division_check(word q, word y2, word y1, word x3, word x2, word x1) { +inline bool division_check_vartime(word q, word y2, word y1, word x3, word x2, word x1) { /* Compute (y3,y2,y1) = (y2,y1) * q and return true if (y3,y2,y1) > (x3,x2,x1) @@ -37,10 +37,13 @@ y1 = word_madd2(q, y1, &y3); y2 = word_madd2(q, y2, &y3); - const word x[3] = {x1, x2, x3}; - const word y[3] = {y1, y2, y3}; - - return bigint_ct_is_lt(x, 3, y, 3).as_bool(); + if(x3 != y3) { + return (y3 > x3); + } + if(x2 != y2) { + return (y2 > x2); + } + return (y1 > x1); } } // namespace @@ -78,8 +81,8 @@ } BigInt ct_divide_pow2k(size_t k, const BigInt& y) { - BOTAN_ARG_CHECK(!y.is_zero(), "Cannot divide by zero"); - BOTAN_ARG_CHECK(!y.is_negative(), "Negative divisor not supported"); + BOTAN_ARG_CHECK(y.signum() != 0, "Cannot divide by zero"); + BOTAN_ARG_CHECK(y.signum() >= 0, "Negative divisor not supported"); BOTAN_ARG_CHECK(k > 1, "Invalid k"); const size_t x_bits = k + 1; @@ -90,7 +93,7 @@ } BOTAN_ASSERT_NOMSG(y_bits >= 1); - const size_t x_words = (x_bits + BOTAN_MP_WORD_BITS - 1) / BOTAN_MP_WORD_BITS; + const size_t x_words = (x_bits + WordInfo::bits - 1) / WordInfo::bits; const size_t y_words = y.sig_words(); BigInt q = BigInt::with_capacity(x_words); @@ -135,14 +138,14 @@ const auto r_carry = CT::Mask::expand_top_bit(r); r <<= 1; - r += x_b; + r += static_cast(x_b); const auto r_gte_y = CT::Mask::is_gte(r, y) | r_carry; q.conditionally_set_bit(b, r_gte_y.as_bool()); r = r_gte_y.select(r - y, r); } - if(x.is_negative()) { + if(x.signum() < 0) { q.flip_sign(); if(r != 0) { --q; @@ -154,8 +157,16 @@ q_out = q; } +BigInt ct_divide_word(const BigInt& x, word y) { + BigInt q; + word r = 0; + ct_divide_word(x, y, q, r); + BOTAN_UNUSED(r); + return q; +} + word ct_mod_word(const BigInt& x, word y) { - BOTAN_ARG_CHECK(x.is_positive(), "The argument x must be positive"); + BOTAN_ARG_CHECK(x.signum() >= 0, "The argument x must be non-negative"); BOTAN_ARG_CHECK(y != 0, "Cannot divide by zero"); const size_t x_bits = x.bits(); @@ -169,7 +180,7 @@ const auto r_carry = CT::Mask::expand_top_bit(r); r <<= 1; - r += x_b; + r += static_cast(x_b); const auto r_gte_y = CT::Mask::is_gte(r, y) | r_carry; r = r_gte_y.select(r - y, r); @@ -179,7 +190,7 @@ } BigInt ct_modulo(const BigInt& x, const BigInt& y) { - if(y.is_negative() || y.is_zero()) { + if(y.signum() <= 0) { throw Invalid_Argument("ct_modulo requires y > 0"); } @@ -202,8 +213,8 @@ r.ct_cond_swap(r_gte_y, t); } - if(x.is_negative()) { - if(r.is_nonzero()) { + if(x.signum() < 0) { + if(r.signum() != 0) { r = y - r; } } @@ -211,12 +222,109 @@ return r; } +BigInt vartime_divide_pow2k(size_t k, const BigInt& y_arg) { + constexpr size_t WB = WordInfo::bits; + + BOTAN_ARG_CHECK(y_arg.signum() != 0, "Cannot divide by zero"); + BOTAN_ARG_CHECK(y_arg.signum() >= 0, "Negative divisor not supported"); + BOTAN_ARG_CHECK(k > 1, "Invalid k"); + + BigInt y = y_arg; + + const size_t y_words = y.sig_words(); + + BOTAN_ASSERT_NOMSG(y_words > 0); + + // Calculate shifts needed to normalize y with high bit set + const size_t shifts = y.top_bits_free(); + + if(shifts > 0) { + y <<= shifts; + } + + BigInt r; + r.set_bit(k + shifts); // (2^k) << shifts + + // we know y has not changed size, since we only shifted up to set high bit + const size_t t = y_words - 1; + const size_t n = std::max(y_words, r.sig_words()) - 1; + + BOTAN_ASSERT_NOMSG(n >= t); + + BigInt q = BigInt::zero(); + q.grow_to(n - t + 1); + + word* q_words = q.mutable_data(); + + BigInt shifted_y = y << (WB * (n - t)); + + // Set q_{n-t} to number of times r > shifted_y + secure_vector ws; + q_words[n - t] = r.reduce_below(shifted_y, ws); + + const word y_t0 = y.word_at(t); + const word y_t1 = y.word_at(t - 1); + BOTAN_DEBUG_ASSERT((y_t0 >> (WB - 1)) == 1); + + const divide_precomp div_y_t0(y_t0); + + for(size_t i = n; i != t; --i) { + const word x_i0 = r.word_at(i); + const word x_i1 = r.word_at(i - 1); + const word x_i2 = r.word_at(i - 2); + + word qit = (x_i0 == y_t0) ? WordInfo::max : div_y_t0.vartime_div_2to1(x_i0, x_i1); + + // Per HAC 14.23, this operation is required at most twice + for(size_t j = 0; j != 2; ++j) { + if(division_check_vartime(qit, y_t0, y_t1, x_i0, x_i1, x_i2)) { + BOTAN_ASSERT_NOMSG(qit > 0); + qit--; + } else { + break; + } + } + + shifted_y >>= WB; + // Now shifted_y == y << (WB * (i-t-1)) + + /* + * Special case qit == 0 and qit == 1 which occurs relatively often here due to a + * combination of the fixed 2^k and in many cases the typical structure of + * public moduli (as this function is called by Barrett_Reduction::for_public_modulus). + * + * Over the test suite, about 5% of loop iterations have qit == 1 and 10% have qit == 0 + */ + + if(qit != 0) { + if(qit == 1) { + r -= shifted_y; + } else { + r -= qit * shifted_y; + } + + if(r.signum() < 0) { + BOTAN_ASSERT_NOMSG(qit > 0); + qit--; + r += shifted_y; + BOTAN_ASSERT_NOMSG(r.signum() >= 0); + } + } + + q_words[i - t - 1] = qit; + } + + return q; +} + /* * Solve x = q * y + r * -* See Handbook of Applied Cryptography section 14.2.5 +* See Handbook of Applied Cryptography algorithm 14.20 */ void vartime_divide(const BigInt& x, const BigInt& y_arg, BigInt& q_out, BigInt& r_out) { + constexpr size_t WB = WordInfo::bits; + if(y_arg.is_zero()) { throw Invalid_Argument("vartime_divide: cannot divide by zero"); } @@ -237,8 +345,10 @@ // Calculate shifts needed to normalize y with high bit set const size_t shifts = y.top_bits_free(); - y <<= shifts; - r <<= shifts; + if(shifts > 0) { + y <<= shifts; + r <<= shifts; + } // we know y has not changed size, since we only shifted up to set high bit const size_t t = y_words - 1; @@ -250,41 +360,53 @@ word* q_words = q.mutable_data(); - BigInt shifted_y = y << (BOTAN_MP_WORD_BITS * (n - t)); + BigInt shifted_y = y << (WB * (n - t)); // Set q_{n-t} to number of times r > shifted_y q_words[n - t] = r.reduce_below(shifted_y, ws); const word y_t0 = y.word_at(t); const word y_t1 = y.word_at(t - 1); - BOTAN_DEBUG_ASSERT((y_t0 >> (BOTAN_MP_WORD_BITS - 1)) == 1); + BOTAN_DEBUG_ASSERT((y_t0 >> (WB - 1)) == 1); - for(size_t j = n; j != t; --j) { - const word x_j0 = r.word_at(j); - const word x_j1 = r.word_at(j - 1); - const word x_j2 = r.word_at(j - 2); + const divide_precomp div_y_t0(y_t0); - word qjt = bigint_divop_vartime(x_j0, x_j1, y_t0); + for(size_t i = n; i != t; --i) { + const word x_i0 = r.word_at(i); + const word x_i1 = r.word_at(i - 1); + const word x_i2 = r.word_at(i - 2); - qjt = CT::Mask::is_equal(x_j0, y_t0).select(WordInfo::max, qjt); + word qit = (x_i0 == y_t0) ? WordInfo::max : div_y_t0.vartime_div_2to1(x_i0, x_i1); // Per HAC 14.23, this operation is required at most twice - qjt -= division_check(qjt, y_t0, y_t1, x_j0, x_j1, x_j2); - qjt -= division_check(qjt, y_t0, y_t1, x_j0, x_j1, x_j2); - BOTAN_DEBUG_ASSERT(division_check(qjt, y_t0, y_t1, x_j0, x_j1, x_j2) == false); + for(size_t j = 0; j != 2; ++j) { + if(division_check_vartime(qit, y_t0, y_t1, x_i0, x_i1, x_i2)) { + BOTAN_ASSERT_NOMSG(qit > 0); + qit--; + } else { + break; + } + } - shifted_y >>= BOTAN_MP_WORD_BITS; - // Now shifted_y == y << (BOTAN_MP_WORD_BITS * (j-t-1)) + shifted_y >>= WB; + // Now shifted_y == y << (WB * (i-t-1)) - // TODO this sequence could be better - r -= qjt * shifted_y; - qjt -= r.is_negative(); - r += static_cast(r.is_negative()) * shifted_y; + if(qit != 0) { + r -= qit * shifted_y; + if(r.signum() < 0) { + BOTAN_ASSERT_NOMSG(qit > 0); + qit--; + r += shifted_y; + BOTAN_ASSERT_NOMSG(r.signum() >= 0); + } + } - q_words[j - t - 1] = qjt; + q_words[i - t - 1] = qit; } - r >>= shifts; + if(shifts > 0) { + r >>= shifts; + } sign_fixup(x, y_arg, q, r); diff -Nru botan3-3.7.1+dfsg/src/lib/math/bigint/divide.h botan3-3.12.0+dfsg/src/lib/math/bigint/divide.h --- botan3-3.7.1+dfsg/src/lib/math/bigint/divide.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/bigint/divide.h 2026-05-07 01:38:28.000000000 +0000 @@ -5,8 +5,8 @@ * Botan is released under the Simplified BSD License (see license.txt) */ -#ifndef BOTAN_DIVISON_ALGORITHM_H_ -#define BOTAN_DIVISON_ALGORITHM_H_ +#ifndef BOTAN_BIGINT_DIVIDE_H_ +#define BOTAN_BIGINT_DIVIDE_H_ #include @@ -51,6 +51,20 @@ BigInt ct_divide_pow2k(size_t k, const BigInt& y); /** +* BigInt division, variable time, 2^k variant +* +* This is identical to ct_divide_pow2k in functionality, +* but leaks both k and y to side channels, so it should only +* be used with public inputs. +* +* @param k an integer +* @param y a positive integer +* @return q equal to 2**k / y +*/ +BOTAN_TEST_API +BigInt vartime_divide_pow2k(size_t k, const BigInt& y); + +/** * BigInt division, const time variant * * This runs with control flow independent of the values of x/y. @@ -61,7 +75,8 @@ * @return x/y with remainder discarded */ inline BigInt ct_divide(const BigInt& x, const BigInt& y) { - BigInt q, r; + BigInt q; + BigInt r; ct_divide(x, y, q, r); return q; } @@ -90,13 +105,7 @@ * @param y a non-zero word * @return quotient floor(x / y) */ -inline BigInt ct_divide_word(const BigInt& x, word y) { - BigInt q; - word r; - ct_divide_word(x, y, q, r); - BOTAN_UNUSED(r); - return q; -} +BigInt ct_divide_word(const BigInt& x, word y); /** * BigInt word modulo, const time variant diff -Nru botan3-3.7.1+dfsg/src/lib/math/mp/info.txt botan3-3.12.0+dfsg/src/lib/math/mp/info.txt --- botan3-3.7.1+dfsg/src/lib/math/mp/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/mp/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + BIGINT_MP -> 20151225 - + name -> "Big Integer (Low-Level)" diff -Nru botan3-3.7.1+dfsg/src/lib/math/mp/mp_asmi.h botan3-3.12.0+dfsg/src/lib/math/mp/mp_asmi.h --- botan3-3.7.1+dfsg/src/lib/math/mp/mp_asmi.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/mp/mp_asmi.h 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ /* * Lowest Level MPI Algorithms -* (C) 1999-2010 Jack Lloyd +* (C) 1999-2010,2025 Jack Lloyd * 2006 Luca Piccarreta * * Botan is released under the Simplified BSD License (see license.txt) @@ -11,6 +11,8 @@ #include #include +#include +#include #if !defined(BOTAN_TARGET_HAS_NATIVE_UINT128) #include @@ -18,10 +20,31 @@ namespace Botan { +// NOLINTBEGIN(*-macro-usage,*-no-assembler) + #if defined(BOTAN_USE_GCC_INLINE_ASM) && defined(BOTAN_TARGET_ARCH_IS_X86_64) #define BOTAN_MP_USE_X86_64_ASM #endif +#if defined(BOTAN_USE_GCC_INLINE_ASM) && defined(BOTAN_TARGET_ARCH_IS_ARM64) + #define BOTAN_MP_USE_AARCH64_ASM +#endif + +/* +* Expressing an add with carry is sadly quite difficult in standard C/C++. +* +* Compilers will recognize various idioms and generate a reasonable carry +* chain. Unfortunately which idioms the compiler will understand vary, so we +* have to decide what to do based on the compiler. This is fragile; what will +* work varies not just based on compiler but also version, target architecture, +* and optimization flags. +*/ +#if defined(__clang__) +static constexpr bool use_dword_for_word_add = false; +#else +static constexpr bool use_dword_for_word_add = true; +#endif + /* * Concept for allowed multiprecision word types */ @@ -78,6 +101,23 @@ return a; } +#elif defined(BOTAN_MP_USE_AARCH64_ASM) + if(std::same_as && !std::is_constant_evaluated()) { + W lo = 0; + W hi = 0; + asm(R"( + mul %[lo], %[a], %[b] + umulh %[hi], %[a], %[b] + adds %[lo], %[lo], %[c] + adc %[hi], %[hi], xzr + )" + : [lo] "=&r"(lo), [hi] "=&r"(hi) + : [a] "r"(a), [b] "r"(b), [c] "r"(*c) + : "cc"); + + *c = hi; + return lo; + } #endif typedef typename WordInfo::dword dword; @@ -108,6 +148,25 @@ return a; } +#elif defined(BOTAN_MP_USE_AARCH64_ASM) + if(std::same_as && !std::is_constant_evaluated()) { + W lo = 0; + W hi = 0; + asm(R"( + mul %[lo], %[a], %[b] + umulh %[hi], %[a], %[b] + adds %[lo], %[lo], %[c] + adc %[hi], %[hi], xzr + adds %[lo], %[lo], %[d] + adc %[hi], %[hi], xzr + )" + : [lo] "=&r"(lo), [hi] "=&r"(hi) + : [a] "r"(a), [b] "r"(b), [c] "r"(c), [d] "r"(*d) + : "cc"); + + *d = hi; + return lo; + } #endif typedef typename WordInfo::dword dword; @@ -120,12 +179,6 @@ #define ASM(x) x "\n\t" - #define DO_4_TIMES(MACRO, ARG) \ - MACRO(ARG, 0) \ - MACRO(ARG, 1) \ - MACRO(ARG, 2) \ - MACRO(ARG, 3) - #define DO_8_TIMES(MACRO, ARG) \ MACRO(ARG, 0) \ MACRO(ARG, 1) \ @@ -176,8 +229,8 @@ */ template inline constexpr auto word_add(W x, W y, W* carry) -> W { - if(!std::is_constant_evaluated()) { #if BOTAN_COMPILER_HAS_BUILTIN(__builtin_addc) + if(!std::is_constant_evaluated()) { if constexpr(std::same_as) { return __builtin_addc(x, y, *carry & 1, carry); } else if constexpr(std::same_as) { @@ -185,23 +238,27 @@ } else if constexpr(std::same_as) { return __builtin_addcll(x, y, *carry & 1, carry); } -#elif defined(BOTAN_MP_USE_X86_64_ASM) - if(std::same_as) { - asm(ADD_OR_SUBTRACT(ASM("adcq %[y],%[x]")) - : [x] "=r"(x), [carry] "=r"(*carry) - : "0"(x), [y] "rm"(y), "1"(*carry) - : "cc"); - return x; - } -#endif } +#endif - const W cb = *carry & 1; - W z = x + y; - W c1 = (z < x); - z += cb; - *carry = c1 | (z < cb); - return z; + if constexpr(WordInfo::dword_is_native && use_dword_for_word_add) { + /* + TODO(Botan4) this is largely a performance hack for GCCs that don't + support __builtin_addc, if we increase the minimum supported version of + GCC to GCC 14 then we can remove this and not worry about it + */ + const W cb = *carry & 1; + const auto s = typename WordInfo::dword(x) + y + cb; + *carry = static_cast(s >> WordInfo::bits); + return static_cast(s); + } else { + const W cb = *carry & 1; + W z = x + y; + W c1 = (z < x); + z += cb; + *carry = c1 | (z < cb); + return z; + } } /* @@ -256,32 +313,13 @@ return carry; } -template -inline constexpr auto word4_add3(W z[4], const W x[4], const W y[4], W carry) -> W { -#if defined(BOTAN_MP_USE_X86_64_ASM) - if(std::same_as && !std::is_constant_evaluated()) { - asm volatile(ADD_OR_SUBTRACT(DO_4_TIMES(ADDSUB3_OP, "adcq")) - : [carry] "=r"(carry) - : [x] "r"(x), [y] "r"(y), [z] "r"(z), "0"(carry) - : "cc", "memory"); - return carry; - } -#endif - - z[0] = word_add(x[0], y[0], &carry); - z[1] = word_add(x[1], y[1], &carry); - z[2] = word_add(x[2], y[2], &carry); - z[3] = word_add(x[3], y[3], &carry); - return carry; -} - /* * Word Subtraction */ template inline constexpr auto word_sub(W x, W y, W* carry) -> W { - if(!std::is_constant_evaluated()) { #if BOTAN_COMPILER_HAS_BUILTIN(__builtin_subc) + if(!std::is_constant_evaluated()) { if constexpr(std::same_as) { return __builtin_subc(x, y, *carry & 1, carry); } else if constexpr(std::same_as) { @@ -289,16 +327,8 @@ } else if constexpr(std::same_as) { return __builtin_subcll(x, y, *carry & 1, carry); } -#elif defined(BOTAN_MP_USE_X86_64_ASM) - if(std::same_as) { - asm(ADD_OR_SUBTRACT(ASM("sbbq %[y],%[x]")) - : [x] "=r"(x), [carry] "=r"(*carry) - : "0"(x), [y] "rm"(y), "1"(*carry) - : "cc"); - return x; - } -#endif } +#endif const W cb = *carry & 1; W t0 = x - y; @@ -335,32 +365,6 @@ } /* -* Eight Word Block Subtraction, Two Argument -*/ -template -inline constexpr auto word8_sub2_rev(W x[8], const W y[8], W carry) -> W { -#if defined(BOTAN_MP_USE_X86_64_ASM) - if(std::same_as && !std::is_constant_evaluated()) { - asm(ADD_OR_SUBTRACT(DO_8_TIMES(ADDSUB3_OP, "sbbq")) - : [carry] "=r"(carry) - : [x] "r"(y), [y] "r"(x), [z] "r"(x), "0"(carry) - : "cc", "memory"); - return carry; - } -#endif - - x[0] = word_sub(y[0], x[0], &carry); - x[1] = word_sub(y[1], x[1], &carry); - x[2] = word_sub(y[2], x[2], &carry); - x[3] = word_sub(y[3], x[3], &carry); - x[4] = word_sub(y[4], x[4], &carry); - x[5] = word_sub(y[5], x[5], &carry); - x[6] = word_sub(y[6], x[6], &carry); - x[7] = word_sub(y[7], x[7], &carry); - return carry; -} - -/* * Eight Word Block Subtraction, Three Argument */ template @@ -386,51 +390,6 @@ return carry; } -template -inline constexpr auto word4_sub3(W z[4], const W x[4], const W y[4], W carry) -> W { -#if defined(BOTAN_MP_USE_X86_64_ASM) - if(std::same_as && !std::is_constant_evaluated()) { - asm volatile(ADD_OR_SUBTRACT(DO_4_TIMES(ADDSUB3_OP, "sbbq")) - : [carry] "=r"(carry) - : [x] "r"(x), [y] "r"(y), [z] "r"(z), "0"(carry) - : "cc", "memory"); - return carry; - } -#endif - - z[0] = word_sub(x[0], y[0], &carry); - z[1] = word_sub(x[1], y[1], &carry); - z[2] = word_sub(x[2], y[2], &carry); - z[3] = word_sub(x[3], y[3], &carry); - return carry; -} - -/* -* Eight Word Block Linear Multiplication -*/ -template -inline constexpr auto word8_linmul2(W x[8], W y, W carry) -> W { -#if defined(BOTAN_MP_USE_X86_64_ASM) - if(std::same_as && !std::is_constant_evaluated()) { - asm(DO_8_TIMES(LINMUL_OP, "x") - : [carry] "=r"(carry) - : [x] "r"(x), [y] "rm"(y), "0"(carry) - : "cc", "%rax", "%rdx"); - return carry; - } -#endif - - x[0] = word_madd2(x[0], y, &carry); - x[1] = word_madd2(x[1], y, &carry); - x[2] = word_madd2(x[2], y, &carry); - x[3] = word_madd2(x[3], y, &carry); - x[4] = word_madd2(x[4], y, &carry); - x[5] = word_madd2(x[5], y, &carry); - x[6] = word_madd2(x[6], y, &carry); - x[7] = word_madd2(x[7], y, &carry); - return carry; -} - /* * Eight Word Block Linear Multiplication */ @@ -483,108 +442,6 @@ return carry; } -/* -* Multiply-Add Accumulator -* (w2,w1,w0) += x * y -*/ -template -inline constexpr void word3_muladd(W* w2, W* w1, W* w0, W x, W y) { -#if defined(BOTAN_MP_USE_X86_64_ASM) - if(std::same_as && !std::is_constant_evaluated()) { - W z0 = 0, z1 = 0; - - asm("mulq %[y]" : "=a"(z0), "=d"(z1) : "a"(x), [y] "rm"(y) : "cc"); - - asm(R"( - addq %[z0],%[w0] - adcq %[z1],%[w1] - adcq $0,%[w2] - )" - : [w0] "=r"(*w0), [w1] "=r"(*w1), [w2] "=r"(*w2) - : [z0] "r"(z0), [z1] "r"(z1), "0"(*w0), "1"(*w1), "2"(*w2) - : "cc"); - return; - } -#endif - - W carry = *w0; - *w0 = word_madd2(x, y, &carry); - *w1 += carry; - *w2 += (*w1 < carry); -} - -/* -* 3-word addition -* (w2,w1,w0) += x -*/ -template -inline constexpr void word3_add(W* w2, W* w1, W* w0, W x) { -#if defined(BOTAN_MP_USE_X86_64_ASM) - if(std::same_as && !std::is_constant_evaluated()) { - asm(R"( - addq %[x],%[w0] - adcq $0,%[w1] - adcq $0,%[w2] - )" - : [w0] "=r"(*w0), [w1] "=r"(*w1), [w2] "=r"(*w2) - : [x] "r"(x), "0"(*w0), "1"(*w1), "2"(*w2) - : "cc"); - return; - } -#endif - - *w0 += x; - W c1 = (*w0 < x); - *w1 += c1; - W c2 = (*w1 < c1); - *w2 += c2; -} - -/* -* Multiply-Add Accumulator -* (w2,w1,w0) += 2 * x * y -*/ -template -inline constexpr void word3_muladd_2(W* w2, W* w1, W* w0, W x, W y) { -#if defined(BOTAN_MP_USE_X86_64_ASM) - if(std::same_as && !std::is_constant_evaluated()) { - W z0 = 0, z1 = 0; - - asm("mulq %[y]" : "=a"(z0), "=d"(z1) : "a"(x), [y] "rm"(y) : "cc"); - - asm(R"( - addq %[z0],%[w0] - adcq %[z1],%[w1] - adcq $0,%[w2] - - addq %[z0],%[w0] - adcq %[z1],%[w1] - adcq $0,%[w2] - )" - : [w0] "=r"(*w0), [w1] "=r"(*w1), [w2] "=r"(*w2) - : [z0] "r"(z0), [z1] "r"(z1), "0"(*w0), "1"(*w1), "2"(*w2) - : "cc"); - return; - } -#endif - - W carry = 0; - x = word_madd2(x, y, &carry); - y = carry; - - const size_t top_bit_shift = WordInfo::bits - 1; - - W top = (y >> top_bit_shift); - y <<= 1; - y |= (x >> top_bit_shift); - x <<= 1; - - carry = 0; - *w0 = word_add(*w0, x, &carry); - *w1 = word_add(*w1, y, &carry); - *w2 = word_add(*w2, top, &carry); -} - /** * Helper for 3-word accumulators * @@ -598,7 +455,7 @@ #if defined(__BITINT_MAXWIDTH__) && (__BITINT_MAXWIDTH__ >= 3 * 64) public: - constexpr word3() { m_w = 0; } + constexpr word3() : m_w(0) {} inline constexpr void mul(W x, W y) { m_w += static_cast(x) * y; } @@ -633,17 +490,122 @@ #else public: - constexpr word3() { - m_w2 = 0; - m_w1 = 0; - m_w0 = 0; - } + constexpr word3() : m_w0(0), m_w1(0), m_w2(0) {} - inline constexpr void mul(W x, W y) { word3_muladd(&m_w2, &m_w1, &m_w0, x, y); } - - inline constexpr void mul_x2(W x, W y) { word3_muladd_2(&m_w2, &m_w1, &m_w0, x, y); } - - inline constexpr void add(W x) { word3_add(&m_w2, &m_w1, &m_w0, x); } + inline constexpr void mul(W x, W y) { + #if defined(BOTAN_MP_USE_X86_64_ASM) + if(std::same_as && !std::is_constant_evaluated()) { + W z0 = 0; + W z1 = 0; + + asm("mulq %[y]" : "=a"(z0), "=d"(z1) : "a"(x), [y] "rm"(y) : "cc"); + + asm(R"( + addq %[z0],%[w0] + adcq %[z1],%[w1] + adcq $0,%[w2] + )" + : [w0] "=r"(m_w0), [w1] "=r"(m_w1), [w2] "=r"(m_w2) + : [z0] "r"(z0), [z1] "r"(z1), "0"(m_w0), "1"(m_w1), "2"(m_w2) + : "cc"); + return; + } + #elif defined(BOTAN_MP_USE_AARCH64_ASM) + if(std::same_as && !std::is_constant_evaluated()) { + W t0 = 0; + W t1 = 0; + asm(R"( + mul %[t0], %[x], %[y] + umulh %[t1], %[x], %[y] + adds %[w0], %[w0], %[t0] + adcs %[w1], %[w1], %[t1] + adc %[w2], %[w2], xzr + )" + : [w0] "+r"(m_w0), [w1] "+r"(m_w1), [w2] "+r"(m_w2), [t0] "=&r"(t0), [t1] "=&r"(t1) + : [x] "r"(x), [y] "r"(y) + : "cc"); + return; + } + #endif + + typedef typename WordInfo::dword dword; + const auto z = dword(x) * y; + const auto z0 = static_cast(z); + const auto z1 = static_cast(z >> WordInfo::bits); + + W carry = 0; + m_w0 = word_add(m_w0, z0, &carry); + m_w1 = word_add(m_w1, z1, &carry); + m_w2 += carry; + } + + inline constexpr void mul_x2(W x, W y) { + #if defined(BOTAN_MP_USE_X86_64_ASM) + if(std::same_as && !std::is_constant_evaluated()) { + W z0 = 0; + W z1 = 0; + + asm("mulq %[y]" : "=a"(z0), "=d"(z1) : "a"(x), [y] "rm"(y) : "cc"); + + asm(R"( + addq %[z0],%[w0] + adcq %[z1],%[w1] + adcq $0,%[w2] + + addq %[z0],%[w0] + adcq %[z1],%[w1] + adcq $0,%[w2] + )" + : [w0] "=r"(m_w0), [w1] "=r"(m_w1), [w2] "=r"(m_w2) + : [z0] "r"(z0), [z1] "r"(z1), "0"(m_w0), "1"(m_w1), "2"(m_w2) + : "cc"); + return; + } + #elif defined(BOTAN_MP_USE_AARCH64_ASM) + if(std::same_as && !std::is_constant_evaluated()) { + W t0 = 0; + W t1 = 0; + asm(R"( + mul %[t0], %[x], %[y] + umulh %[t1], %[x], %[y] + adds %[w0], %[w0], %[t0] + adcs %[w1], %[w1], %[t1] + adc %[w2], %[w2], xzr + adds %[w0], %[w0], %[t0] + adcs %[w1], %[w1], %[t1] + adc %[w2], %[w2], xzr + )" + : [w0] "+r"(m_w0), [w1] "+r"(m_w1), [w2] "+r"(m_w2), [t0] "=&r"(t0), [t1] "=&r"(t1) + : [x] "r"(x), [y] "r"(y) + : "cc"); + return; + } + #endif + + typedef typename WordInfo::dword dword; + const auto z = dword(x) * y; + const auto z0 = static_cast(z); + const auto z1 = static_cast(z >> WordInfo::bits); + + W carry = 0; + m_w0 = word_add(m_w0, z0, &carry); + m_w1 = word_add(m_w1, z1, &carry); + m_w2 += carry; + + carry = 0; + m_w0 = word_add(m_w0, z0, &carry); + m_w1 = word_add(m_w1, z1, &carry); + m_w2 += carry; + } + + inline constexpr void add(W x) { + constexpr W z = 0; + + W carry = 0; + m_w0 = word_add(m_w0, x, &carry); + m_w1 = word_add(m_w1, z, &carry); + m_w2 += carry; + } inline constexpr W extract() { W r = m_w0; @@ -672,13 +634,14 @@ } private: - W m_w0, m_w1, m_w2; + W m_w0; + W m_w1; + W m_w2; #endif }; #if defined(ASM) #undef ASM - #undef DO_4_TIMES #undef DO_8_TIMES #undef ADD_OR_SUBTRACT #undef ADDSUB2_OP @@ -687,6 +650,8 @@ #undef MULADD_OP #endif +// NOLINTEND(*-macro-usage,*-no-assembler) + } // namespace Botan #endif diff -Nru botan3-3.7.1+dfsg/src/lib/math/mp/mp_comba.cpp botan3-3.12.0+dfsg/src/lib/math/mp/mp_comba.cpp --- botan3-3.7.1+dfsg/src/lib/math/mp/mp_comba.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/mp/mp_comba.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,7 +1,8 @@ /* * Comba Multiplication and Squaring * -* This file was automatically generated by ./src/scripts/dev_tools/gen_mp_comba.py on 2024-06-27 +* This file was automatically generated by ./src/scripts/dev_tools/gen_mp_comba.py on 2026-04-24 +* All manual changes will be lost. Edit the script instead. * * Botan is released under the Simplified BSD License (see license.txt) */ diff -Nru botan3-3.7.1+dfsg/src/lib/math/mp/mp_core.h botan3-3.12.0+dfsg/src/lib/math/mp/mp_core.h --- botan3-3.7.1+dfsg/src/lib/math/mp/mp_core.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/mp/mp_core.h 2026-05-07 01:38:28.000000000 +0000 @@ -11,12 +11,10 @@ #define BOTAN_MP_CORE_OPS_H_ #include -#include -#include #include #include +#include #include -#include #include #include @@ -39,129 +37,38 @@ } } -template -inline constexpr W bigint_cnd_add(W cnd, W x[], size_t x_size, const W y[], size_t y_size) { - BOTAN_ASSERT(x_size >= y_size, "Expected sizes"); - - const auto mask = CT::Mask::expand(cnd).value(); - - W carry = 0; - - for(size_t i = 0; i != y_size; ++i) { - x[i] = word_add(x[i], y[i] & mask, &carry); - } - - for(size_t i = y_size; i != x_size; ++i) { - x[i] = word_add(x[i], static_cast(0), &carry); - } - - return (mask & carry); -} - /* * If cond > 0 adds x[0:size] and y[0:size] and returns carry * Runs in constant time */ template inline constexpr W bigint_cnd_add(W cnd, W x[], const W y[], size_t size) { - return bigint_cnd_add(cnd, x, size, y, size); -} - -/* -* If cond > 0 subtracts x[0:size] and y[0:size] and returns borrow -* Runs in constant time -*/ -template -inline constexpr auto bigint_cnd_sub(W cnd, W x[], size_t x_size, const W y[], size_t y_size) -> W { - BOTAN_ASSERT(x_size >= y_size, "Expected sizes"); - const auto mask = CT::Mask::expand(cnd).value(); W carry = 0; - for(size_t i = 0; i != y_size; ++i) { - x[i] = word_sub(x[i], y[i] & mask, &carry); - } - - for(size_t i = y_size; i != x_size; ++i) { - x[i] = word_sub(x[i], static_cast(0), &carry); + for(size_t i = 0; i != size; ++i) { + x[i] = word_add(x[i], y[i] & mask, &carry); } return (mask & carry); } /* -* If cond > 0 adds x[0:size] and y[0:size] and returns carry +* If cond > 0 subtracts y[0:size] from x[0:size] and returns borrow * Runs in constant time */ template inline constexpr auto bigint_cnd_sub(W cnd, W x[], const W y[], size_t size) -> W { - return bigint_cnd_sub(cnd, x, size, y, size); -} - -/* -* Equivalent to -* bigint_cnd_add( mask, x, y, size); -* bigint_cnd_sub(~mask, x, y, size); -* -* Mask must be either 0 or all 1 bits -*/ -template -inline constexpr void bigint_cnd_add_or_sub(CT::Mask mask, W x[], const W y[], size_t size) { - const size_t blocks = size - (size % 8); - - W carry = 0; - W borrow = 0; - - W t0[8] = {0}; - W t1[8] = {0}; - - for(size_t i = 0; i != blocks; i += 8) { - carry = word8_add3(t0, x + i, y + i, carry); - borrow = word8_sub3(t1, x + i, y + i, borrow); - mask.select_n(x + i, t0, t1, 8); - } - - for(size_t i = blocks; i != size; ++i) { - const W a = word_add(x[i], y[i], &carry); - const W s = word_sub(x[i], y[i], &borrow); - - x[i] = mask.select(a, s); - } -} - -/* -* Equivalent to -* bigint_cnd_add( mask, x, size, y, size); -* bigint_cnd_sub(~mask, x, size, z, size); -* -* Mask must be either 0 or all 1 bits -* -* Returns the carry or borrow resp -*/ -template -inline constexpr auto bigint_cnd_addsub(CT::Mask mask, W x[], const W y[], const W z[], size_t size) -> W { - const size_t blocks = size - (size % 8); + const auto mask = CT::Mask::expand(cnd).value(); W carry = 0; - W borrow = 0; - W t0[8] = {0}; - W t1[8] = {0}; - - for(size_t i = 0; i != blocks; i += 8) { - carry = word8_add3(t0, x + i, y + i, carry); - borrow = word8_sub3(t1, x + i, z + i, borrow); - mask.select_n(x + i, t0, t1, 8); - } - - for(size_t i = blocks; i != size; ++i) { - t0[0] = word_add(x[i], y[i], &carry); - t1[0] = word_sub(x[i], z[i], &borrow); - x[i] = mask.select(t0[0], t1[0]); + for(size_t i = 0; i != size; ++i) { + x[i] = word_sub(x[i], y[i] & mask, &carry); } - return mask.select(carry, borrow); + return (mask & carry); } /* @@ -184,7 +91,7 @@ * Two operand addition with carry out */ template -inline constexpr auto bigint_add2_nc(W x[], size_t x_size, const W y[], size_t y_size) -> W { +inline constexpr auto bigint_add2(W x[], size_t x_size, const W y[], size_t y_size) -> W { W carry = 0; BOTAN_ASSERT(x_size >= y_size, "Expected sizes"); @@ -210,9 +117,9 @@ * Three operand addition with carry out */ template -inline constexpr auto bigint_add3_nc(W z[], const W x[], size_t x_size, const W y[], size_t y_size) -> W { +inline constexpr auto bigint_add3(W z[], const W x[], size_t x_size, const W y[], size_t y_size) -> W { if(x_size < y_size) { - return bigint_add3_nc(z, y, y_size, x, x_size); + return bigint_add3(z, y, y_size, x, x_size); } W carry = 0; @@ -234,37 +141,6 @@ return carry; } -template -inline constexpr auto bigint_add(std::span z, std::span x, std::span y) -> W { - if constexpr(N == 4) { - return word4_add3(z.data(), x.data(), y.data(), 0); - } else if constexpr(N == 8) { - return word8_add3(z.data(), x.data(), y.data(), 0); - } else { - return bigint_add3_nc(z.data(), x.data(), N, y.data(), N); - } -} - -/** -* Two operand addition -* @param x the first operand (and output) -* @param x_size size of x -* @param y the second operand -* @param y_size size of y (must be <= x_size) -*/ -template -inline constexpr void bigint_add2(W x[], size_t x_size, const W y[], size_t y_size) { - x[x_size] += bigint_add2_nc(x, x_size, y, y_size); -} - -/** -* Three operand addition -*/ -template -inline constexpr void bigint_add3(W z[], const W x[], size_t x_size, const W y[], size_t y_size) { - z[x_size > y_size ? x_size : y_size] += bigint_add3_nc(z, x, x_size, y, y_size); -} - /** * Two operand subtraction */ @@ -298,13 +174,7 @@ inline constexpr void bigint_sub2_rev(W x[], const W y[], size_t y_size) { W borrow = 0; - const size_t blocks = y_size - (y_size % 8); - - for(size_t i = 0; i != blocks; i += 8) { - borrow = word8_sub2_rev(x + i, y + i, borrow); - } - - for(size_t i = blocks; i != y_size; ++i) { + for(size_t i = 0; i != y_size; ++i) { x[i] = word_sub(y[i], x[i], &borrow); } @@ -384,19 +254,8 @@ inline constexpr void bigint_monty_maybe_sub(W z[N], W x0, const W x[N], const W y[N]) { W borrow = 0; - if constexpr(N == 4) { - borrow = word4_sub3(z, x, y, borrow); - } else if constexpr(N == 8) { - borrow = word8_sub3(z, x, y, borrow); - } else { - const constexpr size_t blocks = N - (N % 8); - for(size_t i = 0; i != blocks; i += 8) { - borrow = word8_sub3(z + i, x + i, y + i, borrow); - } - - for(size_t i = blocks; i != N; ++i) { - z[i] = word_sub(x[i], y[i], &borrow); - } + for(size_t i = 0; i != N; ++i) { + z[i] = word_sub(x[i], y[i], &borrow); } borrow = (x0 - borrow) > x0; @@ -409,7 +268,7 @@ * Otherwise compute z = y - x * No borrow is possible since the result is always >= 0 * -* Returns ~0 if x >= y or 0 if x < y +* Returns a Mask: |1| if x >= y or |0| if x < y * @param z output array of at least N words * @param x input array of N words * @param y input array of N words @@ -449,8 +308,8 @@ const size_t word_shift = shift / WordInfo::bits; const size_t bit_shift = shift % WordInfo::bits; - copy_mem(x + word_shift, x, x_words); - clear_mem(x, word_shift); + unchecked_copy_memory(x + word_shift, x, x_words); + zeroize_buffer(x, word_shift); const auto carry_mask = CT::Mask::expand(bit_shift); const W carry_shift = carry_mask.if_set_return(WordInfo::bits - bit_shift); @@ -471,9 +330,9 @@ const size_t top = x_size >= word_shift ? (x_size - word_shift) : 0; if(top > 0) { - copy_mem(x, x + word_shift, top); + unchecked_copy_memory(x, x + word_shift, top); } - clear_mem(x + top, std::min(word_shift, x_size)); + zeroize_buffer(x + top, std::min(word_shift, x_size)); const auto carry_mask = CT::Mask::expand(bit_shift); const W carry_shift = carry_mask.if_set_return(WordInfo::bits - bit_shift); @@ -492,7 +351,7 @@ const size_t word_shift = shift / WordInfo::bits; const size_t bit_shift = shift % WordInfo::bits; - copy_mem(y + word_shift, x, x_size); + unchecked_copy_memory(y + word_shift, x, x_size); const auto carry_mask = CT::Mask::expand(bit_shift); const W carry_shift = carry_mask.if_set_return(WordInfo::bits - bit_shift); @@ -512,7 +371,7 @@ const size_t new_size = x_size < word_shift ? 0 : (x_size - word_shift); if(new_size > 0) { - copy_mem(y, x + word_shift, new_size); + unchecked_copy_memory(y, x + word_shift, new_size); } const auto carry_mask = CT::Mask::expand(bit_shift); @@ -531,15 +390,9 @@ */ template [[nodiscard]] inline constexpr auto bigint_linmul2(W x[], size_t x_size, W y) -> W { - const size_t blocks = x_size - (x_size % 8); - W carry = 0; - for(size_t i = 0; i != blocks; i += 8) { - carry = word8_linmul2(x + i, y, carry); - } - - for(size_t i = blocks; i != x_size; ++i) { + for(size_t i = 0; i != x_size; ++i) { x[i] = word_madd2(x[i], y, &carry); } @@ -613,8 +466,8 @@ /** * Compare x and y -* Return ~0 if x[0:x_size] < y[0:y_size] or 0 otherwise -* If lt_or_equal is true, returns ~0 also for x == y +* Returns a Mask: |1| if x[0:x_size] < y[0:y_size] or |0| otherwise +* If lt_or_equal is true, returns |1| also for x == y */ template inline constexpr auto bigint_ct_is_lt(const W x[], size_t x_size, const W y[], size_t y_size, bool lt_or_equal = false) @@ -673,109 +526,158 @@ return CT::Mask::is_zero(diff); } -/** -* Set z to abs(x-y), ie if x >= y, then compute z = x - y -* Otherwise compute z = y - x -* No borrow is possible since the result is always >= 0 -* -* Return the relative size of x vs y (-1, 0, 1) -* -* @param z output array of max(x_size,y_size) words -* @param x input param -* @param x_size length of x -* @param y input param -* @param y_size length of y -*/ -template -inline constexpr int32_t bigint_sub_abs(W z[], const W x[], size_t x_size, const W y[], size_t y_size) { - const int32_t relative_size = bigint_cmp(x, x_size, y, y_size); - - // Swap if relative_size == -1 - const bool need_swap = relative_size < 0; - CT::conditional_swap_ptr(need_swap, x, y); - CT::conditional_swap(need_swap, x_size, y_size); - - /* - * We know at this point that x >= y so if y_size is larger than - * x_size, we are guaranteed they are just leading zeros which can - * be ignored - */ - y_size = std::min(x_size, y_size); - - bigint_sub3(z, x, x_size, y, y_size); +template +consteval std::pair div_magic() + requires(div == 10) +{ + if constexpr(div == 10 && std::same_as) { + constexpr W magic = 0xCCCCCCCD; + constexpr size_t shift = 35; + return std::make_pair(magic, shift); + } else if constexpr(div == 10 && std::same_as) { + constexpr W magic = 0xCCCCCCCCCCCCCCCD; + constexpr size_t shift = 67; + return std::make_pair(magic, shift); + } +} - return relative_size; +template +inline constexpr W divide_10(W x) { + auto [magic, shift] = div_magic(); + const auto p = typename WordInfo::dword(magic) * x; + return static_cast(p >> shift); } /** -* Set t to t-s modulo mod +* Setup for variable-time word level division/modulo operations * -* @param t first integer -* @param s second integer -* @param mod the modulus -* @param mod_sw size of t, s, and mod -* @param ws workspace of size mod_sw +* Currently this just uses the compiler's support for a 2/1 word division, +* but likely could be improved by precomputed values based on the divisor, +* for example using the approaches outlined in Hacker's Delight chapter 10. */ template -inline constexpr void bigint_mod_sub(W t[], const W s[], const W mod[], size_t mod_sw, W ws[]) { - // ws = t - s - const W borrow = bigint_sub3(ws, t, mod_sw, s, mod_sw); +class divide_precomp final { + public: + explicit constexpr divide_precomp(W divisor) : m_divisor(divisor) { + BOTAN_ARG_CHECK(m_divisor != 0, "Division by zero"); + } - // Conditionally add back the modulus - bigint_cnd_add(borrow, ws, mod, mod_sw); + // Return floor((n1 || n0) / d) + // + // This assumes n1 < d so that the quotient fits in a word + inline constexpr W vartime_div_2to1(W n1, W n0) const { + BOTAN_ASSERT_NOMSG(n1 < m_divisor); - copy_mem(t, ws, mod_sw); -} + if(m_divisor == WordInfo::max) { + return vartime_div_2to1_max_d(n1, n0); + } -/** -* Compute ((n1< -inline constexpr auto bigint_divop_vartime(W n1, W n0, W d) -> W { - if(d == 0) { - throw Invalid_Argument("bigint_divop_vartime divide by zero"); - } + if(m_divisor == WordInfo::top_bit) { + // Simply a shift by N-1 bits + return (n1 << 1) | (n0 >> (WordInfo::bits - 1)); + } - if constexpr(WordInfo::dword_is_native) { - typename WordInfo::dword n = n1; - n <<= WordInfo::bits; - n |= n0; - return static_cast(n / d); - } else { - W high = n1 % d; - W quotient = 0; + if(!std::is_constant_evaluated()) { +#if defined(BOTAN_MP_USE_X86_64_ASM) + if constexpr(std::same_as) { + W quotient = 0; + W remainder = 0; + // NOLINTNEXTLINE(*-no-assembler) + asm("divq %[v]" : "=a"(quotient), "=d"(remainder) : [v] "r"(m_divisor), "a"(n0), "d"(n1)); + return quotient; + } +#endif + +#if !defined(BOTAN_BUILD_COMPILER_IS_CLANGCL) + + /* clang-cl has a bug where on encountering a 128/64 division it emits + * a call to __udivti3() but then fails to link the relevant builtin into + * the binary, causing a link failure. Work around this by simply omitting + * such code for clang-cl + * + * See https://github.com/llvm/llvm-project/issues/25679 + */ + if constexpr(WordInfo::dword_is_native) { + typename WordInfo::dword n = n1; + n <<= WordInfo::bits; + n |= n0; + return static_cast(n / m_divisor); + } +#endif + } + + W high = n1; + W quotient = 0; - for(size_t i = 0; i != WordInfo::bits; ++i) { - const W high_top_bit = high >> (WordInfo::bits - 1); + for(size_t i = 0; i != WordInfo::bits; ++i) { + const W high_top_bit = high >> (WordInfo::bits - 1); - high <<= 1; - high |= (n0 >> (WordInfo::bits - 1 - i)) & 1; - quotient <<= 1; - - if(high_top_bit || high >= d) { - high -= d; - quotient |= 1; + high <<= 1; + high |= (n0 >> (WordInfo::bits - 1 - i)) & 1; + quotient <<= 1; + + if(high_top_bit || high >= m_divisor) { + high -= m_divisor; + quotient |= 1; + } } + + return quotient; } - return quotient; - } -} + // Return floor((n1 || n0) % d) + // + // This assumes n1 < d so that the quotient fits in a word + inline constexpr W vartime_mod_2to1(W n1, W n0) const { + BOTAN_ASSERT_NOMSG(n1 < m_divisor); + W q = this->vartime_div_2to1(n1, n0); + W carry = 0; + q = word_madd2(q, m_divisor, &carry); + return (n0 - q); + } -/** -* Compute ((n1< -inline constexpr auto bigint_modop_vartime(W n1, W n0, W d) -> W { - if(d == 0) { - throw Invalid_Argument("bigint_modop_vartime divide by zero"); - } + private: + /* + * When the divisor is the maximum integer value, then a two word + * division becomes simple. + */ + static inline constexpr W vartime_div_2to1_max_d(W n1, W n0) { + /* + Use k to refer to WordInfo::bits - W z = bigint_divop_vartime(n1, n0, d); - W carry = 0; - z = word_madd2(z, d, &carry); - return (n0 - z); -} + We are dividing n = (n1 * 2^k) + n0 by 2^k - 1 + + Recall that 2^k = 1 (mod 2^k - 1) + + Rewrite n = n1*2^k + n0 as n1*(2^k - 1) + n1 + n0 + + The result of dividing n by (2^k - 1) will be equal to + (n1*(2^k-1) + n1 + n0) / (2^k-1) = + n1 + ((n1 + n0) / (2^k-1) + + Use c to refer to ((n1 + n0) / (2^k-1)) + + If (n1 + n0) < (2^k - 1) then c is 0 + If (n1 + n0) >= (2^k - 1) then c is 1 + + Since n1 < 2^k - 1 [*] and n0 <= 2^k - 1 it is impossible for (n1 + n0) / (2^k -1) + to be greater than 1. + + [*] We require n1 be strictly less than the divisor to ensure that the + output fits in a single word; this is checked at the start of vartime_div_2to1. + */ + + const W s = n0 + n1; + // did n0 + n1 overflow? or does (n0 + n1) == 2^k - 1? if either, c == 1 + if(s < n0 || s == WordInfo::max) { + n1 += 1; + } + + return n1; + } + + W m_divisor; +}; /* * Compute an integer x such that (a*x) == -1 (mod 2^n) @@ -786,25 +688,18 @@ */ template inline constexpr auto monty_inverse(W a) -> W { - if(a % 2 == 0) { - throw Invalid_Argument("monty_inverse only valid for odd integers"); - } + BOTAN_ARG_CHECK(a % 2 == 1, "Cannot compute Montgomery inverse of an even integer"); - /* - * From "A New Algorithm for Inversion mod p^k" by Çetin Kaya Koç - * https://eprint.iacr.org/2017/411.pdf sections 5 and 7. - */ + // Newton's Method, following https://lemire.me/blog/2017/09/18/computing-the-inverse-of-odd-integers/ - W b = 1; - W r = 0; + constexpr size_t iter = WordInfo::bits == 64 ? 4 : 3; - for(size_t i = 0; i != WordInfo::bits; ++i) { - const W bi = b % 2; - r >>= 1; - r += bi << (WordInfo::bits - 1); + // Initial guess provides 5 bits of accuracy + W r = (3 * a) ^ 2; - b -= a * bi; - b >>= 1; + // Each iteration doubles the accuracy + for(size_t i = 0; i != iter; ++i) { + r = r * (2 - r * a); } // Now invert in addition space @@ -815,28 +710,30 @@ template inline constexpr W shift_left(std::array& x) { + static_assert(N >= 1, "Invalid input size"); static_assert(S < WordInfo::bits, "Shift too large"); - W carry = 0; - for(size_t i = 0; i != N; ++i) { - const W w = x[i]; - x[i] = (w << S) | carry; - carry = w >> (WordInfo::bits - S); + const W carry = x[N - 1] >> (WordInfo::bits - S); + + for(size_t i = N - 1; i != 0; --i) { + x[i] = (x[i] << S) | (x[i - 1] >> (WordInfo::bits - S)); } + x[0] <<= S; return carry; } template inline constexpr W shift_right(std::array& x) { + static_assert(N >= 1, "Invalid input size"); static_assert(S < WordInfo::bits, "Shift too large"); - W carry = 0; - for(size_t i = 0; i != N; ++i) { - const W w = x[N - 1 - i]; - x[N - 1 - i] = (w >> S) | carry; - carry = w << (WordInfo::bits - S); + const W carry = x[0] << (WordInfo::bits - S); + + for(size_t i = 0; i != N - 1; ++i) { + x[i] = (x[i] >> S) | (x[i + 1] << (WordInfo::bits - S)); } + x[N - 1] >>= S; return carry; } @@ -975,53 +872,67 @@ /* * Montgomery reduction * -* Each of these functions makes the following assumptions: +* Sets r to the Montgomery reduction of z using parameters p / p_dash * -* z_size == 2*p_size -* ws_size >= p_size +* The workspace should be of size equal to the prime */ -BOTAN_FUZZER_API void bigint_monty_redc_4(word z[8], const word p[4], word p_dash, word ws[]); -BOTAN_FUZZER_API void bigint_monty_redc_6(word z[12], const word p[6], word p_dash, word ws[]); -BOTAN_FUZZER_API void bigint_monty_redc_8(word z[16], const word p[8], word p_dash, word ws[]); -BOTAN_FUZZER_API void bigint_monty_redc_16(word z[32], const word p[16], word p_dash, word ws[]); -BOTAN_FUZZER_API void bigint_monty_redc_24(word z[48], const word p[24], word p_dash, word ws[]); -BOTAN_FUZZER_API void bigint_monty_redc_32(word z[64], const word p[32], word p_dash, word ws[]); +BOTAN_FUZZER_API void bigint_monty_redc_4(word r[4], const word z[8], const word p[4], word p_dash, word ws[4]); +BOTAN_FUZZER_API void bigint_monty_redc_6(word r[6], const word z[12], const word p[6], word p_dash, word ws[6]); +BOTAN_FUZZER_API void bigint_monty_redc_8(word r[8], const word z[16], const word p[8], word p_dash, word ws[8]); +BOTAN_FUZZER_API void bigint_monty_redc_12(word r[12], const word z[24], const word p[12], word p_dash, word ws[12]); +BOTAN_FUZZER_API void bigint_monty_redc_16(word r[16], const word z[32], const word p[16], word p_dash, word ws[16]); +BOTAN_FUZZER_API void bigint_monty_redc_24(word r[24], const word z[48], const word p[24], word p_dash, word ws[24]); +BOTAN_FUZZER_API void bigint_monty_redc_32(word r[32], const word z[64], const word p[32], word p_dash, word ws[32]); BOTAN_FUZZER_API -void bigint_monty_redc_generic(word z[], size_t z_size, const word p[], size_t p_size, word p_dash, word ws[]); +void bigint_monty_redc_generic( + word r[], const word z[], size_t z_size, const word p[], size_t p_size, word p_dash, word ws[]); /** * Montgomery Reduction -* @param z integer to reduce, of size exactly 2*p_size. Output is in -* the first p_size words, higher words are set to zero. +* @param r result of exactly p_size words +* @param z integer to reduce, of size exactly 2*p_size. * @param p modulus * @param p_size size of p * @param p_dash Montgomery value * @param ws array of at least p_size words * @param ws_size size of ws in words +* +* It is allowed to set &r[0] == &z[0] however in this case note that only the +* first p_size words of r will be written to and the high p_size words of r/z +* will still hold the original inputs, these must be cleared after use. +* See bigint_monty_redc_inplace */ -inline void bigint_monty_redc(word z[], const word p[], size_t p_size, word p_dash, word ws[], size_t ws_size) { +inline void bigint_monty_redc( + word r[], const word z[], const word p[], size_t p_size, word p_dash, word ws[], size_t ws_size) { const size_t z_size = 2 * p_size; BOTAN_ARG_CHECK(ws_size >= p_size, "Montgomery reduction workspace too small"); if(p_size == 4) { - bigint_monty_redc_4(z, p, p_dash, ws); + bigint_monty_redc_4(r, z, p, p_dash, ws); } else if(p_size == 6) { - bigint_monty_redc_6(z, p, p_dash, ws); + bigint_monty_redc_6(r, z, p, p_dash, ws); } else if(p_size == 8) { - bigint_monty_redc_8(z, p, p_dash, ws); + bigint_monty_redc_8(r, z, p, p_dash, ws); + } else if(p_size == 12) { + bigint_monty_redc_12(r, z, p, p_dash, ws); } else if(p_size == 16) { - bigint_monty_redc_16(z, p, p_dash, ws); + bigint_monty_redc_16(r, z, p, p_dash, ws); } else if(p_size == 24) { - bigint_monty_redc_24(z, p, p_dash, ws); + bigint_monty_redc_24(r, z, p, p_dash, ws); } else if(p_size == 32) { - bigint_monty_redc_32(z, p, p_dash, ws); + bigint_monty_redc_32(r, z, p, p_dash, ws); } else { - bigint_monty_redc_generic(z, z_size, p, p_size, p_dash, ws); + bigint_monty_redc_generic(r, z, z_size, p, p_size, p_dash, ws); } } +inline void bigint_monty_redc_inplace(word z[], const word p[], size_t p_size, word p_dash, word ws[], size_t ws_size) { + bigint_monty_redc(z, z, p, p_size, p_dash, ws, ws_size); + zeroize_buffer(z + p_size, p_size); +} + /** * Basecase O(N^2) multiplication */ @@ -1051,20 +962,6 @@ void bigint_sqr(word z[], size_t z_size, const word x[], size_t x_size, size_t x_sw, word workspace[], size_t ws_size); /** -* Return 2**B - C -*/ -template -consteval std::array crandall_p() { - static_assert(C % 2 == 1); - std::array P; - for(size_t i = 0; i != N; ++i) { - P[i] = WordInfo::max; - } - P[0] = WordInfo::max - (C - 1); - return P; -} - -/** * Reduce z modulo p = 2**B - C where C is small * * z is assumed to be at most (p-1)**2 @@ -1091,24 +988,90 @@ word carry_c[2] = {0}; carry_c[0] = word_madd2(carry, C, &carry_c[1]); - carry = bigint_add2_nc(hi.data(), N, carry_c, 2); + carry = bigint_add2(hi.data(), N, carry_c, 2); - constexpr auto P = crandall_p(); + constexpr W P0 = WordInfo::max - (C - 1); std::array r = {}; - bigint_monty_maybe_sub(r.data(), carry, hi.data(), P.data()); + + W borrow = 0; + + /* + * For undetermined reasons, on GCC (only) removing this asm block causes + * massive (up to 20%) performance regressions in secp256k1. + * + * The generated code without the asm seems quite reasonable, and timing + * repeated calls to redc_crandall with the cycle counter show that GCC + * computes it in about the same number of cycles with or without the asm. + * + * So the cause of the regression is unclear. But it is reproducible across + * machines and GCC versions. + */ +#if defined(BOTAN_MP_USE_X86_64_ASM) && defined(__GNUC__) && !defined(__clang__) + if constexpr(N == 4 && std::same_as) { + if(!std::is_constant_evaluated()) { + asm volatile(R"( + movq 0(%[x]), %[borrow] + subq %[p0], %[borrow] + movq %[borrow], 0(%[r]) + movq 8(%[x]), %[borrow] + sbbq $-1, %[borrow] + movq %[borrow], 8(%[r]) + movq 16(%[x]), %[borrow] + sbbq $-1, %[borrow] + movq %[borrow], 16(%[r]) + movq 24(%[x]), %[borrow] + sbbq $-1, %[borrow] + movq %[borrow], 24(%[r]) + sbbq %[borrow],%[borrow] + negq %[borrow] + )" + : [borrow] "=r"(borrow) + : [x] "r"(hi.data()), [p0] "r"(P0), [r] "r"(r.data()), "0"(borrow) + : "cc", "memory"); + + borrow = (carry - borrow) > carry; + CT::conditional_assign_mem(borrow, r.data(), hi.data(), N); + return r; + } + } +#endif + + r[0] = word_sub(hi[0], P0, &borrow); + for(size_t i = 1; i != N; ++i) { + r[i] = word_sub(hi[i], WordInfo::max, &borrow); + } + + borrow = (carry - borrow) > carry; + + CT::conditional_assign_mem(borrow, r.data(), hi.data(), N); return r; } -/** -* Set r to r - C. Then if r < 0, add P to r -*/ -template -constexpr inline void bigint_correct_redc(std::array& r, const std::array& P, const std::array& C) { - // TODO look into combining the two operations for important values of N - W borrow = bigint_sub2(r.data(), N, C.data(), N); - bigint_cnd_add(borrow, r.data(), N, P.data(), N); +// Extract a WindowBits sized window out of s, depending on offset. +template +constexpr size_t read_window_bits(std::span words, size_t offset) { + static_assert(WindowBits >= 1 && WindowBits <= 7); + + constexpr uint8_t WindowMask = static_cast(1 << WindowBits) - 1; + + constexpr size_t W_bits = sizeof(W) * 8; + const auto bit_shift = offset % W_bits; + const auto word_offset = words.size() - 1 - (offset / W_bits); + + const bool single_byte_window = bit_shift <= (W_bits - WindowBits) || word_offset == 0; + + const auto w0 = words[word_offset]; + + if(single_byte_window) { + return (w0 >> bit_shift) & WindowMask; + } else { + // Otherwise we must join two words and extract the result + const auto w1 = words[word_offset - 1]; + const auto combined = ((w0 >> bit_shift) | (w1 << (W_bits - bit_shift))); + return combined & WindowMask; + } } } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/math/mp/mp_karat.cpp botan3-3.12.0+dfsg/src/lib/math/mp/mp_karat.cpp --- botan3-3.7.1+dfsg/src/lib/math/mp/mp_karat.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/mp/mp_karat.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,8 +9,8 @@ #include #include -#include #include +#include namespace Botan { @@ -24,7 +24,7 @@ const size_t x_size_8 = x_size - (x_size % 8); - clear_mem(z, z_size); + zeroize_buffer(z, z_size); for(size_t i = 0; i != y_size; ++i) { const word y_i = y[i]; @@ -50,7 +50,7 @@ const size_t x_size_8 = x_size - (x_size % 8); - clear_mem(z, z_size); + zeroize_buffer(z, z_size); for(size_t i = 0; i != x_size; ++i) { const word x_i = x[i]; @@ -78,7 +78,7 @@ * Karatsuba Multiplication Operation */ void karatsuba_mul(word z[], const word x[], const word y[], size_t N, word workspace[]) { - if(N < KARATSUBA_MULTIPLY_THRESHOLD || N % 2) { + if(N < KARATSUBA_MULTIPLY_THRESHOLD || N % 2 != 0) { switch(N) { case 6: return bigint_comba_mul6(z, x, y); @@ -107,12 +107,12 @@ word* ws0 = workspace; word* ws1 = workspace + N; - clear_mem(workspace, 2 * N); + zeroize_buffer(workspace, 2 * N); /* * If either of cmp0 or cmp1 is zero then z0 or z1 resp is zero here, * resulting in a no-op - z0*z1 will be equal to zero so we don't need to do - * anything, clear_mem above already set the correct result. + * anything, zeroize_buffer above already set the correct result. * * However we ignore the result of the comparisons and always perform the * subtractions and recursively multiply to avoid the timing channel. @@ -131,22 +131,23 @@ // Compute X_hi * Y_hi karatsuba_mul(z1, x1, y1, N2, ws1); - const word ws_carry = bigint_add3_nc(ws1, z0, N, z1, N); - word z_carry = bigint_add2_nc(z + N2, N, ws1, N); + const word ws_carry = bigint_add3(ws1, z0, N, z1, N); + word z_carry = bigint_add2(z + N2, N, ws1, N); - z_carry += bigint_add2_nc(z + N + N2, N2, &ws_carry, 1); - bigint_add2_nc(z + N + N2, N2, &z_carry, 1); + z_carry += bigint_add2(z + N + N2, N2, &ws_carry, 1); + bigint_add2(z + N + N2, N2, &z_carry, 1); - clear_mem(workspace + N, N2); + zeroize_buffer(workspace + N, N2); - bigint_cnd_add_or_sub(neg_mask, z + N2, workspace, 2 * N - N2); + bigint_cnd_add(neg_mask.value(), z + N2, workspace, 2 * N - N2); + bigint_cnd_sub((~neg_mask).value(), z + N2, workspace, 2 * N - N2); } /* * Karatsuba Squaring Operation */ void karatsuba_sqr(word z[], const word x[], size_t N, word workspace[]) { - if(N < KARATSUBA_SQUARE_THRESHOLD || N % 2) { + if(N < KARATSUBA_SQUARE_THRESHOLD || N % 2 != 0) { switch(N) { case 6: return bigint_comba_sqr6(z, x); @@ -173,7 +174,7 @@ word* ws0 = workspace; word* ws1 = workspace + N; - clear_mem(workspace, 2 * N); + zeroize_buffer(workspace, 2 * N); // See comment in karatsuba_mul bigint_sub_abs(z0, x0, x1, N2, workspace); @@ -182,11 +183,11 @@ karatsuba_sqr(z0, x0, N2, ws1); karatsuba_sqr(z1, x1, N2, ws1); - const word ws_carry = bigint_add3_nc(ws1, z0, N, z1, N); - word z_carry = bigint_add2_nc(z + N2, N, ws1, N); + const word ws_carry = bigint_add3(ws1, z0, N, z1, N); + word z_carry = bigint_add2(z + N2, N, ws1, N); - z_carry += bigint_add2_nc(z + N + N2, N2, &ws_carry, 1); - bigint_add2_nc(z + N + N2, N2, &z_carry, 1); + z_carry += bigint_add2(z + N + N2, N2, &ws_carry, 1); + bigint_add2(z + N + N2, N2, &z_carry, 1); /* * This is only actually required if cmp (result of bigint_sub_abs) is != 0, @@ -204,7 +205,7 @@ return 0; } - if(((x_size == x_sw) && (x_size % 2)) || ((y_size == y_sw) && (y_size % 2))) { + if(((x_size == x_sw) && (x_size % 2 != 0)) || ((y_size == y_sw) && (y_size % 2 != 0))) { return 0; } @@ -212,14 +213,14 @@ const size_t end = (x_size < y_size) ? x_size : y_size; if(start == end) { - if(start % 2) { + if(start % 2 != 0) { return 0; } return start; } for(size_t j = start; j <= end; ++j) { - if(j % 2) { + if(j % 2 != 0) { continue; } @@ -243,14 +244,14 @@ */ size_t karatsuba_size(size_t z_size, size_t x_size, size_t x_sw) { if(x_sw == x_size) { - if(x_sw % 2) { + if(x_sw % 2 != 0) { return 0; } return x_sw; } for(size_t j = x_sw; j <= x_size; ++j) { - if(j % 2) { + if(j % 2 != 0) { continue; } @@ -289,7 +290,7 @@ size_t y_sw, word workspace[], size_t ws_size) { - clear_mem(z, z_size); + zeroize_buffer(z, z_size); if(x_sw == 1) { bigint_linmul3(z, y, y_sw, x[0]); @@ -307,12 +308,12 @@ bigint_comba_mul16(z, x, y); } else if(sized_for_comba_mul<24>(x_sw, x_size, y_sw, y_size, z_size)) { bigint_comba_mul24(z, x, y); - } else if(x_sw < KARATSUBA_MULTIPLY_THRESHOLD || y_sw < KARATSUBA_MULTIPLY_THRESHOLD || !workspace) { + } else if(x_sw < KARATSUBA_MULTIPLY_THRESHOLD || y_sw < KARATSUBA_MULTIPLY_THRESHOLD || workspace == nullptr) { basecase_mul(z, z_size, x, x_sw, y, y_sw); } else { const size_t N = karatsuba_size(z_size, x_size, x_sw, y_size, y_sw); - if(N && z_size >= 2 * N && ws_size >= 2 * N) { + if(N > 0 && z_size >= 2 * N && ws_size >= 2 * N) { karatsuba_mul(z, x, y, N, workspace); } else { basecase_mul(z, z_size, x, x_sw, y, y_sw); @@ -324,7 +325,7 @@ * Squaring Algorithm Dispatcher */ void bigint_sqr(word z[], size_t z_size, const word x[], size_t x_size, size_t x_sw, word workspace[], size_t ws_size) { - clear_mem(z, z_size); + zeroize_buffer(z, z_size); BOTAN_ASSERT(z_size / 2 >= x_sw, "Output size is sufficient"); @@ -342,12 +343,12 @@ bigint_comba_sqr16(z, x); } else if(sized_for_comba_sqr<24>(x_sw, x_size, z_size)) { bigint_comba_sqr24(z, x); - } else if(x_size < KARATSUBA_SQUARE_THRESHOLD || !workspace) { + } else if(x_size < KARATSUBA_SQUARE_THRESHOLD || workspace == nullptr) { basecase_sqr(z, z_size, x, x_sw); } else { const size_t N = karatsuba_size(z_size, x_size, x_sw); - if(N && z_size >= 2 * N && ws_size >= 2 * N) { + if(N > 0 && z_size >= 2 * N && ws_size >= 2 * N) { karatsuba_sqr(z, x, N, workspace); } else { basecase_sqr(z, z_size, x, x_sw); diff -Nru botan3-3.7.1+dfsg/src/lib/math/mp/mp_monty.cpp botan3-3.12.0+dfsg/src/lib/math/mp/mp_monty.cpp --- botan3-3.7.1+dfsg/src/lib/math/mp/mp_monty.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/mp/mp_monty.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ /* * Montgomery Reduction -* (C) 1999-2011 Jack Lloyd +* (C) 1999-2011,2025 Jack Lloyd * 2006 Luca Piccarreta * 2016 Matthias Gierlings * @@ -10,12 +10,71 @@ #include #include -#include -#include -#include namespace Botan { +namespace { + +BOTAN_FORCE_INLINE void mul_rev_range(word3& accum, const word ws[], const word p[], size_t bound) { + /* + Unrolled version of: + + for(size_t i = 0; i < bound; ++i) { + accum.mul(ws[i], p[bound - i]); + } + */ + + size_t lower = 0; + while(lower < bound) { + const size_t upper = bound - lower; + + if(upper >= 16) { + accum.mul(ws[lower], p[upper]); + accum.mul(ws[lower + 1], p[upper - 1]); + accum.mul(ws[lower + 2], p[upper - 2]); + accum.mul(ws[lower + 3], p[upper - 3]); + accum.mul(ws[lower + 4], p[upper - 4]); + accum.mul(ws[lower + 5], p[upper - 5]); + accum.mul(ws[lower + 6], p[upper - 6]); + accum.mul(ws[lower + 7], p[upper - 7]); + accum.mul(ws[lower + 8], p[upper - 8]); + accum.mul(ws[lower + 9], p[upper - 9]); + accum.mul(ws[lower + 10], p[upper - 10]); + accum.mul(ws[lower + 11], p[upper - 11]); + accum.mul(ws[lower + 12], p[upper - 12]); + accum.mul(ws[lower + 13], p[upper - 13]); + accum.mul(ws[lower + 14], p[upper - 14]); + accum.mul(ws[lower + 15], p[upper - 15]); + lower += 16; + } else if(upper >= 8) { + accum.mul(ws[lower], p[upper]); + accum.mul(ws[lower + 1], p[upper - 1]); + accum.mul(ws[lower + 2], p[upper - 2]); + accum.mul(ws[lower + 3], p[upper - 3]); + accum.mul(ws[lower + 4], p[upper - 4]); + accum.mul(ws[lower + 5], p[upper - 5]); + accum.mul(ws[lower + 6], p[upper - 6]); + accum.mul(ws[lower + 7], p[upper - 7]); + lower += 8; + } else if(upper >= 4) { + accum.mul(ws[lower], p[upper]); + accum.mul(ws[lower + 1], p[upper - 1]); + accum.mul(ws[lower + 2], p[upper - 2]); + accum.mul(ws[lower + 3], p[upper - 3]); + lower += 4; + } else if(upper >= 2) { + accum.mul(ws[lower], p[upper]); + accum.mul(ws[lower + 1], p[upper - 1]); + lower += 2; + } else { + accum.mul(ws[lower], p[upper]); + lower += 1; + } + } +} + +} // namespace + /* * Montgomery reduction - product scanning form * @@ -28,7 +87,8 @@ * https://eprint.iacr.org/2013/882.pdf * https://www.microsoft.com/en-us/research/wp-content/uploads/1996/01/j37acmon.pdf */ -void bigint_monty_redc_generic(word z[], size_t z_size, const word p[], size_t p_size, word p_dash, word ws[]) { +void bigint_monty_redc_generic( + word r[], const word z[], size_t z_size, const word p[], size_t p_size, word p_dash, word ws[]) { BOTAN_ARG_CHECK(z_size >= 2 * p_size && p_size > 0, "Invalid sizes for bigint_monty_redc_generic"); word3 accum; @@ -38,19 +98,13 @@ ws[0] = accum.monty_step(p[0], p_dash); for(size_t i = 1; i != p_size; ++i) { - for(size_t j = 0; j < i; ++j) { - accum.mul(ws[j], p[i - j]); - } - + mul_rev_range(accum, ws, p, i); accum.add(z[i]); ws[i] = accum.monty_step(p[0], p_dash); } for(size_t i = 0; i != p_size - 1; ++i) { - for(size_t j = i + 1; j != p_size; ++j) { - accum.mul(ws[j], p[p_size + i - j]); - } - + mul_rev_range(accum, &ws[i + 1], &p[i], p_size - (i + 1)); accum.add(z[p_size + i]); ws[i] = accum.extract(); } @@ -63,7 +117,7 @@ /* * The result might need to be reduced mod p. To avoid a timing - * channel, always perform the subtraction. If in the compution + * channel, always perform the subtraction. If in the computation * of x - p a borrow is required then x was already < p. * * x starts at ws[0] and is p_size bytes long plus a possible high @@ -79,10 +133,7 @@ * the Montgomery result is < P */ - bigint_monty_maybe_sub(p_size, z, w1, ws, p); - - // Clear the high words that contain the original input - clear_mem(z + p_size, z_size - p_size); + bigint_monty_maybe_sub(p_size, r, w1, ws, p); } } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/math/mp/mp_monty_n.cpp botan3-3.12.0+dfsg/src/lib/math/mp/mp_monty_n.cpp --- botan3-3.7.1+dfsg/src/lib/math/mp/mp_monty_n.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/mp/mp_monty_n.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,5 +1,5 @@ /* -* This file was automatically generated by ./src/scripts/dev_tools/gen_mp_monty.py on 2024-04-09 +* This file was automatically generated by ./src/scripts/dev_tools/gen_mp_monty.py on 2026-04-24 * All manual changes will be lost. Edit the script instead. * * Botan is released under the Simplified BSD License (see license.txt) @@ -7,11 +7,9 @@ #include -#include - namespace Botan { -void bigint_monty_redc_4(word z[8], const word p[4], word p_dash, word ws[]) { +void bigint_monty_redc_4(word r[4], const word z[8], const word p[4], word p_dash, word ws[4]) { word3 accum; accum.add(z[0]); ws[0] = accum.monty_step(p[0], p_dash); @@ -41,12 +39,11 @@ ws[2] = accum.extract(); accum.add(z[7]); ws[3] = accum.extract(); - word w1 = accum.extract(); - bigint_monty_maybe_sub<4>(z, w1, ws, p); - clear_mem(z + 4, 4); + const word w1 = accum.extract(); + bigint_monty_maybe_sub<4>(r, w1, ws, p); } -void bigint_monty_redc_6(word z[12], const word p[6], word p_dash, word ws[]) { +void bigint_monty_redc_6(word r[6], const word z[12], const word p[6], word p_dash, word ws[6]) { word3 accum; accum.add(z[0]); ws[0] = accum.monty_step(p[0], p_dash); @@ -102,12 +99,11 @@ ws[4] = accum.extract(); accum.add(z[11]); ws[5] = accum.extract(); - word w1 = accum.extract(); - bigint_monty_maybe_sub<6>(z, w1, ws, p); - clear_mem(z + 6, 6); + const word w1 = accum.extract(); + bigint_monty_maybe_sub<6>(r, w1, ws, p); } -void bigint_monty_redc_8(word z[16], const word p[8], word p_dash, word ws[]) { +void bigint_monty_redc_8(word r[8], const word z[16], const word p[8], word p_dash, word ws[8]) { word3 accum; accum.add(z[0]); ws[0] = accum.monty_step(p[0], p_dash); @@ -197,12 +193,197 @@ ws[6] = accum.extract(); accum.add(z[15]); ws[7] = accum.extract(); - word w1 = accum.extract(); - bigint_monty_maybe_sub<8>(z, w1, ws, p); - clear_mem(z + 8, 8); + const word w1 = accum.extract(); + bigint_monty_maybe_sub<8>(r, w1, ws, p); +} + +void bigint_monty_redc_12(word r[12], const word z[24], const word p[12], word p_dash, word ws[12]) { + word3 accum; + accum.add(z[0]); + ws[0] = accum.monty_step(p[0], p_dash); + accum.mul(ws[0], p[1]); + accum.add(z[1]); + ws[1] = accum.monty_step(p[0], p_dash); + accum.mul(ws[0], p[2]); + accum.mul(ws[1], p[1]); + accum.add(z[2]); + ws[2] = accum.monty_step(p[0], p_dash); + accum.mul(ws[0], p[3]); + accum.mul(ws[1], p[2]); + accum.mul(ws[2], p[1]); + accum.add(z[3]); + ws[3] = accum.monty_step(p[0], p_dash); + accum.mul(ws[0], p[4]); + accum.mul(ws[1], p[3]); + accum.mul(ws[2], p[2]); + accum.mul(ws[3], p[1]); + accum.add(z[4]); + ws[4] = accum.monty_step(p[0], p_dash); + accum.mul(ws[0], p[5]); + accum.mul(ws[1], p[4]); + accum.mul(ws[2], p[3]); + accum.mul(ws[3], p[2]); + accum.mul(ws[4], p[1]); + accum.add(z[5]); + ws[5] = accum.monty_step(p[0], p_dash); + accum.mul(ws[0], p[6]); + accum.mul(ws[1], p[5]); + accum.mul(ws[2], p[4]); + accum.mul(ws[3], p[3]); + accum.mul(ws[4], p[2]); + accum.mul(ws[5], p[1]); + accum.add(z[6]); + ws[6] = accum.monty_step(p[0], p_dash); + accum.mul(ws[0], p[7]); + accum.mul(ws[1], p[6]); + accum.mul(ws[2], p[5]); + accum.mul(ws[3], p[4]); + accum.mul(ws[4], p[3]); + accum.mul(ws[5], p[2]); + accum.mul(ws[6], p[1]); + accum.add(z[7]); + ws[7] = accum.monty_step(p[0], p_dash); + accum.mul(ws[0], p[8]); + accum.mul(ws[1], p[7]); + accum.mul(ws[2], p[6]); + accum.mul(ws[3], p[5]); + accum.mul(ws[4], p[4]); + accum.mul(ws[5], p[3]); + accum.mul(ws[6], p[2]); + accum.mul(ws[7], p[1]); + accum.add(z[8]); + ws[8] = accum.monty_step(p[0], p_dash); + accum.mul(ws[0], p[9]); + accum.mul(ws[1], p[8]); + accum.mul(ws[2], p[7]); + accum.mul(ws[3], p[6]); + accum.mul(ws[4], p[5]); + accum.mul(ws[5], p[4]); + accum.mul(ws[6], p[3]); + accum.mul(ws[7], p[2]); + accum.mul(ws[8], p[1]); + accum.add(z[9]); + ws[9] = accum.monty_step(p[0], p_dash); + accum.mul(ws[0], p[10]); + accum.mul(ws[1], p[9]); + accum.mul(ws[2], p[8]); + accum.mul(ws[3], p[7]); + accum.mul(ws[4], p[6]); + accum.mul(ws[5], p[5]); + accum.mul(ws[6], p[4]); + accum.mul(ws[7], p[3]); + accum.mul(ws[8], p[2]); + accum.mul(ws[9], p[1]); + accum.add(z[10]); + ws[10] = accum.monty_step(p[0], p_dash); + accum.mul(ws[0], p[11]); + accum.mul(ws[1], p[10]); + accum.mul(ws[2], p[9]); + accum.mul(ws[3], p[8]); + accum.mul(ws[4], p[7]); + accum.mul(ws[5], p[6]); + accum.mul(ws[6], p[5]); + accum.mul(ws[7], p[4]); + accum.mul(ws[8], p[3]); + accum.mul(ws[9], p[2]); + accum.mul(ws[10], p[1]); + accum.add(z[11]); + ws[11] = accum.monty_step(p[0], p_dash); + accum.mul(ws[1], p[11]); + accum.mul(ws[2], p[10]); + accum.mul(ws[3], p[9]); + accum.mul(ws[4], p[8]); + accum.mul(ws[5], p[7]); + accum.mul(ws[6], p[6]); + accum.mul(ws[7], p[5]); + accum.mul(ws[8], p[4]); + accum.mul(ws[9], p[3]); + accum.mul(ws[10], p[2]); + accum.mul(ws[11], p[1]); + accum.add(z[12]); + ws[0] = accum.extract(); + accum.mul(ws[2], p[11]); + accum.mul(ws[3], p[10]); + accum.mul(ws[4], p[9]); + accum.mul(ws[5], p[8]); + accum.mul(ws[6], p[7]); + accum.mul(ws[7], p[6]); + accum.mul(ws[8], p[5]); + accum.mul(ws[9], p[4]); + accum.mul(ws[10], p[3]); + accum.mul(ws[11], p[2]); + accum.add(z[13]); + ws[1] = accum.extract(); + accum.mul(ws[3], p[11]); + accum.mul(ws[4], p[10]); + accum.mul(ws[5], p[9]); + accum.mul(ws[6], p[8]); + accum.mul(ws[7], p[7]); + accum.mul(ws[8], p[6]); + accum.mul(ws[9], p[5]); + accum.mul(ws[10], p[4]); + accum.mul(ws[11], p[3]); + accum.add(z[14]); + ws[2] = accum.extract(); + accum.mul(ws[4], p[11]); + accum.mul(ws[5], p[10]); + accum.mul(ws[6], p[9]); + accum.mul(ws[7], p[8]); + accum.mul(ws[8], p[7]); + accum.mul(ws[9], p[6]); + accum.mul(ws[10], p[5]); + accum.mul(ws[11], p[4]); + accum.add(z[15]); + ws[3] = accum.extract(); + accum.mul(ws[5], p[11]); + accum.mul(ws[6], p[10]); + accum.mul(ws[7], p[9]); + accum.mul(ws[8], p[8]); + accum.mul(ws[9], p[7]); + accum.mul(ws[10], p[6]); + accum.mul(ws[11], p[5]); + accum.add(z[16]); + ws[4] = accum.extract(); + accum.mul(ws[6], p[11]); + accum.mul(ws[7], p[10]); + accum.mul(ws[8], p[9]); + accum.mul(ws[9], p[8]); + accum.mul(ws[10], p[7]); + accum.mul(ws[11], p[6]); + accum.add(z[17]); + ws[5] = accum.extract(); + accum.mul(ws[7], p[11]); + accum.mul(ws[8], p[10]); + accum.mul(ws[9], p[9]); + accum.mul(ws[10], p[8]); + accum.mul(ws[11], p[7]); + accum.add(z[18]); + ws[6] = accum.extract(); + accum.mul(ws[8], p[11]); + accum.mul(ws[9], p[10]); + accum.mul(ws[10], p[9]); + accum.mul(ws[11], p[8]); + accum.add(z[19]); + ws[7] = accum.extract(); + accum.mul(ws[9], p[11]); + accum.mul(ws[10], p[10]); + accum.mul(ws[11], p[9]); + accum.add(z[20]); + ws[8] = accum.extract(); + accum.mul(ws[10], p[11]); + accum.mul(ws[11], p[10]); + accum.add(z[21]); + ws[9] = accum.extract(); + accum.mul(ws[11], p[11]); + accum.add(z[22]); + ws[10] = accum.extract(); + accum.add(z[23]); + ws[11] = accum.extract(); + const word w1 = accum.extract(); + bigint_monty_maybe_sub<12>(r, w1, ws, p); } -void bigint_monty_redc_16(word z[32], const word p[16], word p_dash, word ws[]) { +void bigint_monty_redc_16(word r[16], const word z[32], const word p[16], word p_dash, word ws[16]) { word3 accum; accum.add(z[0]); ws[0] = accum.monty_step(p[0], p_dash); @@ -508,12 +689,11 @@ ws[14] = accum.extract(); accum.add(z[31]); ws[15] = accum.extract(); - word w1 = accum.extract(); - bigint_monty_maybe_sub<16>(z, w1, ws, p); - clear_mem(z + 16, 16); + const word w1 = accum.extract(); + bigint_monty_maybe_sub<16>(r, w1, ws, p); } -void bigint_monty_redc_24(word z[48], const word p[24], word p_dash, word ws[]) { +void bigint_monty_redc_24(word r[24], const word z[48], const word p[24], word p_dash, word ws[24]) { word3 accum; accum.add(z[0]); ws[0] = accum.monty_step(p[0], p_dash); @@ -1163,12 +1343,11 @@ ws[22] = accum.extract(); accum.add(z[47]); ws[23] = accum.extract(); - word w1 = accum.extract(); - bigint_monty_maybe_sub<24>(z, w1, ws, p); - clear_mem(z + 24, 24); + const word w1 = accum.extract(); + bigint_monty_maybe_sub<24>(r, w1, ws, p); } -void bigint_monty_redc_32(word z[64], const word p[32], word p_dash, word ws[]) { +void bigint_monty_redc_32(word r[32], const word z[64], const word p[32], word p_dash, word ws[32]) { word3 accum; accum.add(z[0]); ws[0] = accum.monty_step(p[0], p_dash); @@ -2290,9 +2469,8 @@ ws[30] = accum.extract(); accum.add(z[63]); ws[31] = accum.extract(); - word w1 = accum.extract(); - bigint_monty_maybe_sub<32>(z, w1, ws, p); - clear_mem(z + 32, 32); + const word w1 = accum.extract(); + bigint_monty_maybe_sub<32>(r, w1, ws, p); } } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/math/numbertheory/barrett.cpp botan3-3.12.0+dfsg/src/lib/math/numbertheory/barrett.cpp --- botan3-3.7.1+dfsg/src/lib/math/numbertheory/barrett.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/numbertheory/barrett.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,203 @@ +/* +* (C) 2025 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include + +namespace Botan { + +Barrett_Reduction::Barrett_Reduction(const BigInt& m, BigInt mu, size_t mw) : + m_modulus(m), m_mu(std::move(mu)), m_mod_words(mw), m_modulus_bits(m.bits()) { + // Give some extra space for Karatsuba + m_modulus.grow_to(m_mod_words + 8); + m_mu.grow_to(m_mod_words + 8); +} + +Barrett_Reduction Barrett_Reduction::for_secret_modulus(const BigInt& mod) { + BOTAN_ARG_CHECK(mod.signum() > 0, "Modulus must be positive"); + + const size_t mod_words = mod.sig_words(); + + // Compute mu = floor(2^{2k} / m) + const size_t mu_bits = 2 * WordInfo::bits * mod_words; + return Barrett_Reduction(mod, ct_divide_pow2k(mu_bits, mod), mod_words); +} + +Barrett_Reduction Barrett_Reduction::for_public_modulus(const BigInt& mod) { + BOTAN_ARG_CHECK(mod.signum() > 0, "Modulus must be positive"); + + const size_t mod_words = mod.sig_words(); + + // Compute mu = floor(2^{2k} / m) + const size_t mu_bits = 2 * WordInfo::bits * mod_words; + return Barrett_Reduction(mod, vartime_divide_pow2k(mu_bits, mod), mod_words); +} + +namespace { + +/* +* Barrett Reduction +* +* This function assumes that the significant size of x_words (ie the number of +* words with a value other than zero) is at most 2 * mod_words. In any case, any +* larger value cannot be reduced using Barrett reduction; callers should have +* already checked for this. +*/ +BigInt barrett_reduce( + size_t mod_words, const BigInt& modulus, const BigInt& mu, std::span x_words, secure_vector& ws) { + BOTAN_ASSERT_NOMSG(modulus.sig_words() == mod_words); + + // Caller must expand input to be at least this size + BOTAN_ASSERT_NOMSG(x_words.size() >= 2 * mod_words); + + // Normally mod_words + 1 but can be + 2 if the modulus is a power of 2 + const size_t mu_words = mu.sig_words(); + BOTAN_ASSERT_NOMSG(mu_words <= mod_words + 2); + + if(ws.size() < 2 * (mod_words + 2)) { + ws.resize(2 * (mod_words + 2)); + } + + CT::poison(x_words); + + /* + * Following the notation of Handbook of Applied Cryptography + * Algorithm 14.42 "Barrett modular reduction", page 604 + * + * + * Using `mu` for μ in the code + */ + + // Compute q1 = floor(x / 2^(k - 1)) which is equivalent to ignoring the low (k-1) words + + // 2 * mod_words + 1 is sufficient, extra is to enable Karatsuba + secure_vector r(2 * mu_words + 2); + + copy_mem(r.data(), x_words.data() + (mod_words - 1), mod_words + 1); + + // Now compute q2 = q1 * μ + + // We allocate more size than required since this allows Karatsuba more often; + // just `mu_words + (mod_words + 1)` is sufficient + const size_t q2_size = 2 * mu_words + 2; + + secure_vector q2(q2_size); + + bigint_mul( + q2.data(), q2.size(), r.data(), r.size(), mod_words + 1, mu._data(), mu.size(), mu_words, ws.data(), ws.size()); + + // Compute r2 = (floor(q2 / b^(k+1)) * m) mod 2^(k+1) + // The division/floor is again effected by just ignoring the low k + 1 words + bigint_mul(r.data(), + r.size(), + &q2[mod_words + 1], // ignoring the low mod_words + 1 words of the first product + q2.size() - (mod_words + 1), + mod_words + 1, + modulus._data(), + modulus.size(), + mod_words, + ws.data(), + ws.size()); + + // Clear the high words of the product, equivalent to computing mod 2^(k+1) + // TODO add masked mul to avoid computing high bits at all + clear_mem(std::span{r}.subspan(mod_words + 1)); + + // Compute r = r1 - r2 + + // The return value of bigint_sub_abs isn't quite right for what we need here so first compare + const int32_t relative_size = bigint_cmp(r.data(), mod_words + 1, x_words.data(), mod_words + 1); + + bigint_sub_abs(r.data(), r.data(), x_words.data(), mod_words + 1, ws.data()); + + /* + If r is negative then we have to set r to r + 2^(k+1) + + However for r negative computing this sum is equivalent to computing 2^(k+1) - abs(r) + */ + clear_mem(ws.data(), mod_words + 2); + ws[mod_words + 1] = 1; + bigint_sub2(ws.data(), mod_words + 2, r.data(), mod_words + 2); + + // If relative_size > 0 then assign r to 2^(k+1) - r + CT::Mask::is_equal(static_cast(relative_size), 1).select_n(r.data(), ws.data(), r.data(), mod_words + 2); + + /* + * Per HAC Note 14.44 (ii) "step 4 is repeated at most twice since 0 ≤ r < 3m" + */ + const size_t bound = 2; + + BOTAN_ASSERT_NOMSG(r.size() >= mod_words + 1); + for(size_t i = 0; i != bound; ++i) { + const word borrow = bigint_sub3(ws.data(), r.data(), mod_words + 1, modulus._data(), mod_words); + CT::Mask::is_zero(borrow).select_n(r.data(), ws.data(), r.data(), mod_words + 1); + } + + CT::unpoison(q2); + CT::unpoison(r); + CT::unpoison(ws); + CT::unpoison(x_words); + + return BigInt::_from_words(r); +} + +CT::Choice acceptable_barrett_input(const BigInt& x, const BigInt& modulus) { + auto x_is_positive = CT::Choice::from_int(static_cast(x.signum() >= 0)); + auto x_lt_mod = bigint_ct_is_lt(x._data(), x.size(), modulus._data(), modulus.sig_words()).as_choice(); + return x_is_positive && x_lt_mod; +} + +} // namespace + +BigInt Barrett_Reduction::multiply(const BigInt& x, const BigInt& y) const { + BOTAN_ARG_CHECK(acceptable_barrett_input(x, m_modulus).as_bool(), "Invalid x param for Barrett multiply"); + BOTAN_ARG_CHECK(acceptable_barrett_input(y, m_modulus).as_bool(), "Invalid y param for Barrett multiply"); + + secure_vector ws(2 * (m_mod_words + 2)); + secure_vector xy(2 * m_mod_words); + + bigint_mul(xy.data(), + xy.size(), + x._data(), + x.size(), + std::min(x.size(), m_mod_words), + y._data(), + y.size(), + std::min(y.size(), m_mod_words), + ws.data(), + ws.size()); + + return barrett_reduce(m_mod_words, m_modulus, m_mu, xy, ws); +} + +BigInt Barrett_Reduction::square(const BigInt& x) const { + BOTAN_ARG_CHECK(acceptable_barrett_input(x, m_modulus).as_bool(), "Invalid x param for Barrett square"); + + secure_vector ws(2 * (m_mod_words + 2)); + secure_vector x2(2 * m_mod_words); + + bigint_sqr(x2.data(), x2.size(), x._data(), x.size(), std::min(x.size(), m_mod_words), ws.data(), ws.size()); + + return barrett_reduce(m_mod_words, m_modulus, m_mu, x2, ws); +} + +BigInt Barrett_Reduction::reduce(const BigInt& x) const { + BOTAN_ARG_CHECK(x.signum() >= 0, "Argument must be non-negative"); + + const size_t x_sw = x.sig_words(); + BOTAN_ARG_CHECK(x_sw <= 2 * m_mod_words, "Argument is too large for Barrett reduction"); + + x.grow_to(2 * m_mod_words); + + secure_vector ws; + return barrett_reduce(m_mod_words, m_modulus, m_mu, x._as_span(), ws); +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/math/numbertheory/barrett.h botan3-3.12.0+dfsg/src/lib/math/numbertheory/barrett.h --- botan3-3.7.1+dfsg/src/lib/math/numbertheory/barrett.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/numbertheory/barrett.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,84 @@ +/* +* (C) 2025 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_BARRETT_REDUCTION_H_ +#define BOTAN_BARRETT_REDUCTION_H_ + +#include + +namespace Botan { + +/** +* Barrett Reduction +*/ +class BOTAN_TEST_API Barrett_Reduction final { + public: + /** + * Setup for reduction where the modulus itself is public + * + * Requires that m > 0 + */ + static Barrett_Reduction for_public_modulus(const BigInt& m); + + /** + * Setup for reduction where the modulus itself is secret. + * + * This is slower than for_public_modulus since it must avoid using + * variable time division. + * + * Requires that m > 0 + */ + static Barrett_Reduction for_secret_modulus(const BigInt& m); + + /** + * Perform modular reduction of x + * + * The parameter must be greater than or equal to zero, and less than 2^(2*b), where + * b is the bitlength of the modulus. + */ + BigInt reduce(const BigInt& x) const; + + /** + * Multiply mod p + * @param x the first operand in [0..p) + * @param y the second operand in [0..p) + * @return (x * y) % p + */ + BigInt multiply(const BigInt& x, const BigInt& y) const; + + /** + * Square mod p + * @param x a value to square must be in [0..p) + * @return (x * x) % p + */ + BigInt square(const BigInt& x) const; + + /** + * Cube mod p + * @param x the value to cube + * @return (x * x * x) % p + * + * TODO(Botan4) remove this, last few remaining callers go away in Botan4 + */ + BigInt cube(const BigInt& x) const { return this->multiply(x, this->square(x)); } + + /** + * Return length of the modulus in bits + */ + size_t modulus_bits() const { return m_modulus_bits; } + + private: + Barrett_Reduction(const BigInt& m, BigInt mu, size_t mw); + + BigInt m_modulus; + BigInt m_mu; + size_t m_mod_words; + size_t m_modulus_bits; +}; + +} // namespace Botan + +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/math/numbertheory/dsa_gen.cpp botan3-3.12.0+dfsg/src/lib/math/numbertheory/dsa_gen.cpp --- botan3-3.7.1+dfsg/src/lib/math/numbertheory/dsa_gen.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/numbertheory/dsa_gen.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,9 +8,9 @@ #include #include +#include #include #include -#include #include #include @@ -80,7 +80,8 @@ Seed& operator++() { for(size_t j = m_seed.size(); j > 0; --j) { - if(++m_seed[j - 1]) { + m_seed[j - 1] += 1; + if(m_seed[j - 1] != 0) { break; } } @@ -101,12 +102,13 @@ return false; } - const size_t n = (pbits - 1) / (HASH_SIZE * 8), b = (pbits - 1) % (HASH_SIZE * 8); + const size_t n = (pbits - 1) / (HASH_SIZE * 8); + const size_t b = (pbits - 1) % (HASH_SIZE * 8); BigInt X; std::vector V(HASH_SIZE * (n + 1)); - auto mod_2q = Modular_Reducer::for_public_modulus(2 * q); + const BigInt q2 = 2 * q; for(size_t j = 0; j != 4 * pbits; ++j) { for(size_t k = 0; k <= n; ++k) { @@ -119,7 +121,8 @@ X._assign_from_bytes(std::span{V}.subspan(HASH_SIZE - 1 - b / 8)); X.set_bit(pbits - 1); - p = X - (mod_2q.reduce(X) - 1); + // Variable time division is OK here since DSA primes are public anyway + p = X - ((X % q2) - 1); if(p.bits() == pbits && is_prime(p, rng, 128, true)) { return true; diff -Nru botan3-3.7.1+dfsg/src/lib/math/numbertheory/info.txt botan3-3.12.0+dfsg/src/lib/math/numbertheory/info.txt --- botan3-3.7.1+dfsg/src/lib/math/numbertheory/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/numbertheory/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -13,6 +13,7 @@ +barrett.h mod_inv.h monty.h monty_exp.h diff -Nru botan3-3.7.1+dfsg/src/lib/math/numbertheory/make_prm.cpp botan3-3.12.0+dfsg/src/lib/math/numbertheory/make_prm.cpp --- botan3-3.7.1+dfsg/src/lib/math/numbertheory/make_prm.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/numbertheory/make_prm.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,13 +7,15 @@ #include +#include #include -#include #include +#include #include #include +#include #include -#include +#include namespace Botan { @@ -24,7 +26,7 @@ Prime_Sieve(const BigInt& init_value, size_t sieve_size, word step, bool check_2p1) : m_sieve(std::min(sieve_size, PRIME_TABLE_SIZE)), m_step(step), m_check_2p1(check_2p1) { for(size_t i = 0; i != m_sieve.size(); ++i) { - m_sieve[i] = init_value % PRIMES[i]; + m_sieve[i] = ct_mod_word(init_value, PRIMES[i]); } } @@ -35,7 +37,7 @@ bool next() { auto passes = CT::Mask::set(); for(size_t i = 0; i != m_sieve.size(); ++i) { - m_sieve[i] = (m_sieve[i] + m_step) % PRIMES[i]; + m_sieve[i] = sieve_step_incr(m_sieve[i], m_step, PRIMES[i]); // If m_sieve[i] == 0 then val % p == 0 -> not prime passes &= CT::Mask::expand(m_sieve[i]); @@ -58,6 +60,19 @@ } private: + // Return (v + step) % mod + // + // This assumes v is already < mod, which is an invariant of the sieve + static constexpr word sieve_step_incr(word v, word step, word mod) { + BOTAN_DEBUG_ASSERT(v < mod); + // The sieve step and primes are public so this modulo is ok + const word stepmod = (step >= mod) ? (step % mod) : step; + + // This sum is at most 2*(mod-1) + const word next = (v + stepmod); + return next - CT::Mask::is_gte(next, mod).if_set_return(mod); + } + std::vector m_sieve; const word m_step; const bool m_check_2p1; @@ -99,9 +114,10 @@ if(bits <= 1) { throw Invalid_Argument("random_prime: Can't make a prime of " + std::to_string(bits) + " bits"); } - if(coprime.is_negative() || (!coprime.is_zero() && coprime.is_even()) || coprime.bits() >= bits) { + if(coprime.signum() < 0 || (coprime.signum() != 0 && coprime.is_even()) || coprime.bits() >= bits) { throw Invalid_Argument("random_prime: invalid coprime"); } + // TODO(Botan4) reduce this to ~1000 if(modulo == 0 || modulo >= 100000) { throw Invalid_Argument("random_prime: Invalid modulo value"); } @@ -120,11 +136,11 @@ } if(bits == 2) { - return BigInt::from_word(((rng.next_byte() % 2) ? 2 : 3)); + return BigInt::from_word(((rng.next_byte() % 2) == 0 ? 2 : 3)); } else if(bits == 3) { - return BigInt::from_word(((rng.next_byte() % 2) ? 5 : 7)); + return BigInt::from_word(((rng.next_byte() % 2) == 0 ? 5 : 7)); } else if(bits == 4) { - return BigInt::from_word(((rng.next_byte() % 2) ? 11 : 13)); + return BigInt::from_word(((rng.next_byte() % 2) == 0 ? 11 : 13)); } else { for(;;) { // This is slightly biased, but for small primes it does not seem to matter @@ -171,14 +187,15 @@ BOTAN_DEBUG_ASSERT(no_small_multiples(p, sieve)); - auto mod_p = Modular_Reducer::for_secret_modulus(p); + auto mod_p = Barrett_Reduction::for_secret_modulus(p); + const Montgomery_Params monty_p(p, mod_p); if(coprime > 1) { /* - First do a single M-R iteration to quickly elimate most non-primes, + First do a single M-R iteration to quickly eliminate most non-primes, before doing the coprimality check which is expensive */ - if(is_miller_rabin_probable_prime(p, mod_p, rng, 1) == false) { + if(!is_miller_rabin_probable_prime(p, mod_p, monty_p, rng, 1)) { continue; } @@ -195,7 +212,7 @@ break; } - if(is_miller_rabin_probable_prime(p, mod_p, rng, mr_trials) == false) { + if(!is_miller_rabin_probable_prime(p, mod_p, monty_p, rng, mr_trials)) { continue; } @@ -233,6 +250,8 @@ while(true) { BigInt p(keygen_rng, bits); + auto scope = CT::scoped_poison(p); + /* Force high two bits so multiplication always results in expected n bit integer @@ -259,14 +278,15 @@ BOTAN_DEBUG_ASSERT(no_small_multiples(p, sieve)); - auto mod_p = Modular_Reducer::for_secret_modulus(p); + auto mod_p = Barrett_Reduction::for_secret_modulus(p); + const Montgomery_Params monty_p(p, mod_p); /* * Do a single primality test first before checking coprimality, since * currently a single Miller-Rabin test is faster than computing gcd, * and this eliminates almost all wasted gcd computations. */ - if(is_miller_rabin_probable_prime(p, mod_p, prime_test_rng, 1) == false) { + if(!is_miller_rabin_probable_prime(p, mod_p, monty_p, prime_test_rng, 1)) { continue; } @@ -281,7 +301,7 @@ break; } - if(is_miller_rabin_probable_prime(p, mod_p, prime_test_rng, mr_trials) == true) { + if(is_miller_rabin_probable_prime(p, mod_p, monty_p, prime_test_rng, mr_trials)) { return p; } } @@ -298,7 +318,8 @@ const size_t error_bound = 128; - BigInt q, p; + BigInt q; + BigInt p; for(;;) { /* Generate q == 2 (mod 3), since otherwise [in the case of q == 1 (mod 3)], diff -Nru botan3-3.7.1+dfsg/src/lib/math/numbertheory/mod_inv.cpp botan3-3.12.0+dfsg/src/lib/math/numbertheory/mod_inv.cpp --- botan3-3.7.1+dfsg/src/lib/math/numbertheory/mod_inv.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/numbertheory/mod_inv.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,6 +6,8 @@ #include +#include +#include #include #include #include @@ -18,8 +20,8 @@ BigInt inverse_mod_odd_modulus(const BigInt& n, const BigInt& mod) { // Caller should assure these preconditions: - BOTAN_ASSERT_NOMSG(n.is_positive()); - BOTAN_ASSERT_NOMSG(mod.is_positive()); + BOTAN_ASSERT_NOMSG(n.signum() >= 0); + BOTAN_ASSERT_NOMSG(mod.signum() > 0); BOTAN_ASSERT_NOMSG(n < mod); BOTAN_ASSERT_NOMSG(mod >= 3 && mod.is_odd()); @@ -47,14 +49,12 @@ secure_vector tmp_mem(5 * mod_words); - word* v_w = &tmp_mem[0]; + word* v_w = &tmp_mem[0]; // NOLINT(readability-container-data-pointer) word* u_w = &tmp_mem[1 * mod_words]; word* b_w = &tmp_mem[2 * mod_words]; word* a_w = &tmp_mem[3 * mod_words]; word* mp1o2 = &tmp_mem[4 * mod_words]; - CT::poison(tmp_mem.data(), tmp_mem.size()); - copy_mem(a_w, n._data(), std::min(n.size(), mod_words)); copy_mem(b_w, mod._data(), std::min(mod.size(), mod_words)); u_w[0] = 1; @@ -64,9 +64,11 @@ // (mod / 2) + 1 copy_mem(mp1o2, mod._data(), std::min(mod.size(), mod_words)); bigint_shr1(mp1o2, mod_words, 1); - word carry = bigint_add2_nc(mp1o2, mod_words, u_w, 1); + const word carry = bigint_add2(mp1o2, mod_words, u_w, 1); BOTAN_ASSERT_NOMSG(carry == 0); + CT::poison(tmp_mem.data(), tmp_mem.size()); + // Only n.bits() + mod.bits() iterations are required, but avoid leaking the size of n const size_t execs = 2 * mod.bits(); @@ -74,7 +76,7 @@ const word odd_a = a_w[0] & 1; //if(odd_a) a -= b - word underflow = bigint_cnd_sub(odd_a, a_w, b_w, mod_words); + const word underflow = bigint_cnd_sub(odd_a, a_w, b_w, mod_words); //if(underflow) { b -= a; a = abs(a); swap(u, v); } bigint_cnd_add(underflow, b_w, a_w, mod_words); @@ -85,7 +87,7 @@ bigint_shr1(a_w, mod_words, 1); //if(odd_a) u -= v; - word borrow = bigint_cnd_sub(odd_a, u_w, v_w, mod_words); + const word borrow = bigint_cnd_sub(odd_a, u_w, v_w, mod_words); // if(borrow) u += p bigint_cnd_add(borrow, u_w, mod._data(), mod_words); @@ -148,7 +150,7 @@ const size_t a_words = a.sig_words(); - X.grow_to(round_up(k, BOTAN_MP_WORD_BITS) / BOTAN_MP_WORD_BITS); + X.grow_to(round_up(k, WordInfo::bits) / WordInfo::bits); b.grow_to(a_words); /* @@ -156,7 +158,7 @@ granularity because of the length of a, so no point in doing more than this. */ - const size_t iter = round_up(k, BOTAN_MP_WORD_BITS); + const size_t iter = round_up(k, WordInfo::bits); for(size_t i = 0; i != iter; ++i) { const bool b0 = b.get_bit(0); @@ -261,14 +263,14 @@ const BigInt c = inverse_mod_pow2(o, mod_lz); // This should never happen; o is odd so gcd is 1 and inverse mod 2^k exists - BOTAN_ASSERT_NOMSG(!c.is_zero()); + BOTAN_ASSERT_NOMSG(c.signum() != 0); // Compute h = c*(inv_2k-inv_o) mod 2^k BigInt h = c * (inv_2k - inv_o); - const bool h_neg = h.is_negative(); + const bool h_neg = h.signum() < 0; h.set_sign(BigInt::Positive); h.mask_bits(mod_lz); - const bool h_nonzero = h.is_nonzero(); + const bool h_nonzero = h.signum() != 0; h.ct_cond_assign(h_nonzero && h_neg, m2k - h); // Return result inv_o + h * o @@ -278,9 +280,10 @@ } BigInt inverse_mod_secret_prime(const BigInt& x, const BigInt& p) { - BOTAN_ARG_CHECK(x.is_positive() && p.is_positive(), "Parameters must be positive"); - BOTAN_ARG_CHECK(x < p, "x must be less than p"); - BOTAN_ARG_CHECK(p.is_odd() and p > 1, "Primes are odd integers greater than 1"); + BOTAN_ARG_CHECK(p.signum() > 0, "Modulus must be positive"); + BOTAN_ARG_CHECK(x.signum() > 0, "Input must be positive"); + BOTAN_ARG_CHECK(x < p, "Input must be less than modulus"); + BOTAN_ARG_CHECK(p.is_odd() && p > 1, "Primes are odd integers greater than 1"); // TODO possibly use FLT, or the algorithm presented for this case in // Handbook of Elliptic and Hyperelliptic Curve Cryptography @@ -289,14 +292,22 @@ } BigInt inverse_mod_public_prime(const BigInt& x, const BigInt& p) { - return inverse_mod_secret_prime(x, p); + BOTAN_ARG_CHECK(p.signum() > 0, "Modulus must be positive"); + BOTAN_ARG_CHECK(x.signum() > 0, "Input must be positive"); + BOTAN_ARG_CHECK(x < p, "Input must be less than modulus"); + BOTAN_ARG_CHECK(p.is_odd() && p > 1, "Primes are odd integers greater than 1"); + + // TODO possibly use FLT, or the algorithm presented for this case in + // Handbook of Elliptic and Hyperelliptic Curve Cryptography + + return inverse_mod_odd_modulus(x, p); } BigInt inverse_mod_rsa_public_modulus(const BigInt& x, const BigInt& n) { - BOTAN_ARG_CHECK(n.is_positive() && n.is_odd(), "RSA public modulus must be odd and positive"); - BOTAN_ARG_CHECK(x.is_positive() && x < n, "Input must be positive and less than RSA modulus"); + BOTAN_ARG_CHECK(n.signum() > 0 && n.is_odd(), "RSA public modulus must be odd and positive"); + BOTAN_ARG_CHECK(x.signum() > 0 && x < n, "Input must be positive and less than RSA modulus"); BigInt z = inverse_mod_odd_modulus(x, n); - BOTAN_ASSERT(!z.is_zero(), "Accidentally factored the public modulus"); // whoops + BOTAN_ASSERT(z.signum() != 0, "Accidentally factored the public modulus"); // whoops return z; } @@ -307,8 +318,8 @@ constexpr size_t s = 32; constexpr uint64_t c = (static_cast(1) << s) / mod; - uint64_t q = (x * c) >> s; - uint64_t r = x - q * mod; + const uint64_t q = (x * c) >> s; + const uint64_t r = x - q * mod; auto r_gt_mod = CT::Mask::is_gte(r, mod); return r - r_gt_mod.if_set_return(mod); @@ -367,9 +378,8 @@ } BigInt inverse_mod(const BigInt& n, const BigInt& mod) { - BOTAN_ARG_CHECK(!mod.is_zero(), "modulus cannot be zero"); - BOTAN_ARG_CHECK(!mod.is_negative(), "modulus cannot be negative"); - BOTAN_ARG_CHECK(!n.is_negative(), "value cannot be negative"); + BOTAN_ARG_CHECK(mod.signum() > 0, "Modulus must be positive"); + BOTAN_ARG_CHECK(n.signum() >= 0, "Value cannot be negative"); if(n.is_zero() || (n.is_even() && mod.is_even())) { return BigInt::zero(); diff -Nru botan3-3.7.1+dfsg/src/lib/math/numbertheory/mod_inv.h botan3-3.12.0+dfsg/src/lib/math/numbertheory/mod_inv.h --- botan3-3.7.1+dfsg/src/lib/math/numbertheory/mod_inv.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/numbertheory/mod_inv.h 2026-05-07 01:38:28.000000000 +0000 @@ -90,7 +90,7 @@ * * This always returns a result since any integer in [1,n) has an inverse modulo * a RSA public modulus n, unless you have happened to guess one of the factors -* at random. In the unlikely event of this occuring, Internal_Error will be thrown. +* at random. In the unlikely event of this occurring, Internal_Error will be thrown. */ BigInt inverse_mod_rsa_public_modulus(const BigInt& x, const BigInt& n); diff -Nru botan3-3.7.1+dfsg/src/lib/math/numbertheory/monty.cpp botan3-3.12.0+dfsg/src/lib/math/numbertheory/monty.cpp --- botan3-3.7.1+dfsg/src/lib/math/numbertheory/monty.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/numbertheory/monty.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,20 +1,28 @@ /* -* (C) 2018,2024 Jack Lloyd +* (C) 2018,2024,2025 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ #include -#include -#include +#include +#include +#include #include - -#include +#include namespace Botan { -Montgomery_Params::Montgomery_Params(const BigInt& p, const Modular_Reducer& mod_p) { +namespace { + +// If the modulus is at most this many words, then use the stack instead +// of a heap variable for some temporary values +constexpr size_t MontgomeryUseStackLimit = 32; + +} // namespace + +Montgomery_Params::Data::Data(const BigInt& p, const Barrett_Reduction& mod_p) { if(p.is_even() || p < 3) { throw Invalid_Argument("Montgomery_Params invalid modulus"); } @@ -23,192 +31,147 @@ m_p_words = m_p.sig_words(); m_p_dash = monty_inverse(m_p.word_at(0)); - const BigInt r = BigInt::power_of_2(m_p_words * BOTAN_MP_WORD_BITS); + const BigInt r = BigInt::power_of_2(m_p_words * WordInfo::bits); m_r1 = mod_p.reduce(r); m_r2 = mod_p.square(m_r1); m_r3 = mod_p.multiply(m_r1, m_r2); + + // Barrett should be at least zero prefixing up to modulus size + BOTAN_ASSERT_NOMSG(m_r1.size() >= m_p_words); + BOTAN_ASSERT_NOMSG(m_r2.size() >= m_p_words); + BOTAN_ASSERT_NOMSG(m_r3.size() >= m_p_words); } -Montgomery_Params::Montgomery_Params(const BigInt& p) { - if(p.is_even() || p < 3) { - throw Invalid_Argument("Montgomery_Params invalid modulus"); - } +Montgomery_Params::Montgomery_Params(const BigInt& p, const Barrett_Reduction& mod_p) : + m_data(std::make_shared(p, mod_p)) {} - m_p = p; - m_p_words = m_p.sig_words(); - m_p_dash = monty_inverse(m_p.word_at(0)); +Montgomery_Params::Montgomery_Params(const BigInt& p) : + Montgomery_Params(p, Barrett_Reduction::for_secret_modulus(p)) {} - const BigInt r = BigInt::power_of_2(m_p_words * BOTAN_MP_WORD_BITS); - - auto mod_p = Modular_Reducer::for_secret_modulus(p); +bool Montgomery_Params::operator==(const Montgomery_Params& other) const { + if(this->m_data == other.m_data) { + return true; + } - m_r1 = mod_p.reduce(r); - m_r2 = mod_p.square(m_r1); - m_r3 = mod_p.multiply(m_r1, m_r2); + return (this->m_data->p() == other.m_data->p()); } BigInt Montgomery_Params::redc(const BigInt& x, secure_vector& ws) const { - const size_t output_size = m_p_words + 1; + const size_t p_size = this->p_words(); - if(ws.size() < output_size) { - ws.resize(output_size); + if(ws.size() < p_size) { + ws.resize(p_size); } BigInt z = x; - z.grow_to(2 * m_p_words); + z.grow_to(2 * p_size); - bigint_monty_redc(z.mutable_data(), m_p._data(), m_p_words, m_p_dash, ws.data(), ws.size()); + bigint_monty_redc_inplace(z.mutable_data(), this->p()._data(), p_size, this->p_dash(), ws.data(), ws.size()); return z; } -void Montgomery_Params::redc_in_place(BigInt& x, secure_vector& ws) const { - const size_t output_size = 2 * m_p_words; - - if(ws.size() < output_size) { - ws.resize(output_size); - } - - x.grow_to(output_size); - - bigint_monty_redc(x.mutable_data(), m_p._data(), m_p_words, m_p_dash, ws.data(), ws.size()); -} - BigInt Montgomery_Params::mul(const BigInt& x, const BigInt& y, secure_vector& ws) const { - BigInt z = BigInt::with_capacity(2 * m_p_words); + const size_t p_size = this->p_words(); + BigInt z = BigInt::with_capacity(2 * p_size); this->mul(z, x, y, ws); return z; } void Montgomery_Params::mul(BigInt& z, const BigInt& x, const BigInt& y, secure_vector& ws) const { - const size_t output_size = 2 * m_p_words; + const size_t p_size = this->p_words(); - if(ws.size() < output_size) { - ws.resize(output_size); + if(ws.size() < 2 * p_size) { + ws.resize(2 * p_size); } - BOTAN_DEBUG_ASSERT(x.sig_words() <= m_p_words); - BOTAN_DEBUG_ASSERT(y.sig_words() <= m_p_words); + BOTAN_DEBUG_ASSERT(x.sig_words() <= p_size); + BOTAN_DEBUG_ASSERT(y.sig_words() <= p_size); - if(z.size() < output_size) { - z.grow_to(output_size); + if(z.size() < 2 * p_size) { + z.grow_to(2 * p_size); } bigint_mul(z.mutable_data(), z.size(), x._data(), x.size(), - std::min(m_p_words, x.size()), + std::min(p_size, x.size()), y._data(), y.size(), - std::min(m_p_words, y.size()), + std::min(p_size, y.size()), ws.data(), ws.size()); - bigint_monty_redc(z.mutable_data(), m_p._data(), m_p_words, m_p_dash, ws.data(), ws.size()); -} - -BigInt Montgomery_Params::mul(const BigInt& x, std::span y, secure_vector& ws) const { - BigInt z = BigInt::with_capacity(2 * m_p_words); - this->mul(z, x, y, ws); - return z; + bigint_monty_redc_inplace(z.mutable_data(), this->p()._data(), p_size, this->p_dash(), ws.data(), ws.size()); } void Montgomery_Params::mul(BigInt& z, const BigInt& x, std::span y, secure_vector& ws) const { - const size_t output_size = 2 * m_p_words; - if(ws.size() < output_size) { - ws.resize(output_size); + const size_t p_size = this->p_words(); + + if(ws.size() < 2 * p_size) { + ws.resize(2 * p_size); } - if(z.size() < output_size) { - z.grow_to(output_size); + if(z.size() < 2 * p_size) { + z.grow_to(2 * p_size); } - BOTAN_DEBUG_ASSERT(x.sig_words() <= m_p_words); + BOTAN_DEBUG_ASSERT(x.sig_words() <= p_size); bigint_mul(z.mutable_data(), z.size(), x._data(), x.size(), - std::min(m_p_words, x.size()), + std::min(p_size, x.size()), y.data(), y.size(), - std::min(m_p_words, y.size()), + std::min(p_size, y.size()), ws.data(), ws.size()); - bigint_monty_redc(z.mutable_data(), m_p._data(), m_p_words, m_p_dash, ws.data(), ws.size()); -} - -void Montgomery_Params::mul_by(BigInt& x, std::span y, secure_vector& ws) const { - const size_t output_size = 2 * m_p_words; - - if(ws.size() < 2 * output_size) { - ws.resize(2 * output_size); - } - - word* z_data = &ws[0]; - word* ws_data = &ws[output_size]; - - BOTAN_DEBUG_ASSERT(x.sig_words() <= m_p_words); - - bigint_mul(z_data, - output_size, - x._data(), - x.size(), - std::min(m_p_words, x.size()), - y.data(), - y.size(), - std::min(m_p_words, y.size()), - ws_data, - output_size); - - bigint_monty_redc(z_data, m_p._data(), m_p_words, m_p_dash, ws_data, output_size); - - if(x.size() < output_size) { - x.grow_to(output_size); - } - copy_mem(x.mutable_data(), z_data, output_size); + bigint_monty_redc_inplace(z.mutable_data(), this->p()._data(), p_size, this->p_dash(), ws.data(), ws.size()); } void Montgomery_Params::mul_by(BigInt& x, const BigInt& y, secure_vector& ws) const { - const size_t output_size = 2 * m_p_words; + const size_t p_size = this->p_words(); - if(ws.size() < 2 * output_size) { - ws.resize(2 * output_size); + if(ws.size() < 4 * p_size) { + ws.resize(4 * p_size); } - word* z_data = &ws[0]; - word* ws_data = &ws[output_size]; + word* z_data = ws.data(); + word* ws_data = &ws[2 * p_size]; - BOTAN_DEBUG_ASSERT(x.sig_words() <= m_p_words); + BOTAN_DEBUG_ASSERT(x.sig_words() <= p_size); bigint_mul(z_data, - output_size, + 2 * p_size, x._data(), x.size(), - std::min(m_p_words, x.size()), + std::min(p_size, x.size()), y._data(), y.size(), - std::min(m_p_words, y.size()), + std::min(p_size, y.size()), ws_data, - output_size); + 2 * p_size); - bigint_monty_redc(z_data, m_p._data(), m_p_words, m_p_dash, ws_data, output_size); + bigint_monty_redc_inplace(z_data, this->p()._data(), p_size, this->p_dash(), ws_data, 2 * p_size); - if(x.size() < output_size) { - x.grow_to(output_size); + if(x.size() < 2 * p_size) { + x.grow_to(2 * p_size); } - copy_mem(x.mutable_data(), z_data, output_size); + copy_mem(x.mutable_data(), z_data, 2 * p_size); } BigInt Montgomery_Params::sqr(const BigInt& x, secure_vector& ws) const { - BOTAN_DEBUG_ASSERT(x.sig_words() <= m_p_words); + BOTAN_DEBUG_ASSERT(x.sig_words() <= this->p_words()); return this->sqr(std::span{x._data(), x.size()}, ws); } BigInt Montgomery_Params::sqr(std::span x, secure_vector& ws) const { - BigInt z = BigInt::with_capacity(2 * m_p_words); + const size_t p_size = this->p_words(); + BigInt z = BigInt::with_capacity(2 * p_size); this->sqr(z, x, ws); return z; } @@ -218,240 +181,200 @@ } void Montgomery_Params::sqr(BigInt& z, std::span x, secure_vector& ws) const { - const size_t output_size = 2 * m_p_words; + const size_t p_size = this->p_words(); - if(ws.size() < output_size) { - ws.resize(output_size); + if(ws.size() < 2 * p_size) { + ws.resize(2 * p_size); } - if(z.size() < output_size) { - z.grow_to(output_size); + if(z.size() < 2 * p_size) { + z.grow_to(2 * p_size); } - bigint_sqr(z.mutable_data(), z.size(), x.data(), x.size(), std::min(m_p_words, x.size()), ws.data(), ws.size()); + bigint_sqr(z.mutable_data(), z.size(), x.data(), x.size(), std::min(p_size, x.size()), ws.data(), ws.size()); - bigint_monty_redc(z.mutable_data(), m_p._data(), m_p_words, m_p_dash, ws.data(), ws.size()); + bigint_monty_redc_inplace(z.mutable_data(), this->p()._data(), p_size, this->p_dash(), ws.data(), ws.size()); } -void Montgomery_Params::square_this(BigInt& x, secure_vector& ws) const { - const size_t output_size = 2 * m_p_words; - - if(ws.size() < 2 * output_size) { - ws.resize(2 * output_size); - } - - word* z_data = &ws[0]; - word* ws_data = &ws[output_size]; - - BOTAN_DEBUG_ASSERT(x.sig_words() <= m_p_words); - - bigint_sqr(z_data, output_size, x._data(), x.size(), std::min(m_p_words, x.size()), ws_data, output_size); - - bigint_monty_redc(z_data, m_p._data(), m_p_words, m_p_dash, ws_data, output_size); - - if(x.size() < output_size) { - x.grow_to(output_size); - } - copy_mem(x.mutable_data(), z_data, output_size); +Montgomery_Int::Montgomery_Int(const Montgomery_Params& params, secure_vector words) : + m_params(params), m_v(std::move(words)) { + BOTAN_ASSERT_NOMSG(m_v.size() == m_params.p_words()); } -Montgomery_Int Montgomery_Int::one(const std::shared_ptr& params) { - return Montgomery_Int(params, params->R1(), false); +Montgomery_Int Montgomery_Int::one(const Montgomery_Params& params) { + return Montgomery_Int(params, params.R1(), false); } -Montgomery_Int Montgomery_Int::from_wide_int(const std::shared_ptr& params, const BigInt& x) { - //BOTAN_ARG_CHECK(x < params->p() * params->p(), "Input too large"); - +Montgomery_Int Montgomery_Int::from_wide_int(const Montgomery_Params& params, const BigInt& x) { secure_vector ws; - auto redc_x = params->mul(params->redc(x, ws), params->R3(), ws); + auto redc_x = params.mul(params.redc(x, ws), params.R3(), ws); return Montgomery_Int(params, redc_x, false); } -Montgomery_Int::Montgomery_Int(const std::shared_ptr& params, - const BigInt& v, - bool redc_needed) : - m_params(params) { - if(redc_needed == false) { - m_v = v; - } else { - BOTAN_ASSERT_NOMSG(m_v < m_params->p()); - secure_vector ws; - m_v = m_params->mul(v, m_params->R2(), ws); - } -} +Montgomery_Int::Montgomery_Int(const Montgomery_Params& params, const BigInt& v, bool redc_needed) : + m_params(params), m_v(m_params.p_words()) { + BOTAN_ASSERT_NOMSG(v < m_params.p()); -Montgomery_Int::Montgomery_Int(const std::shared_ptr& params, - const uint8_t bits[], - size_t len, - bool redc_needed) : - m_params(params), m_v(bits, len) { - if(redc_needed) { - BOTAN_ASSERT_NOMSG(m_v < m_params->p()); - secure_vector ws; - m_v = m_params->mul(m_v, m_params->R2(), ws); + const size_t p_size = m_params.p_words(); + + auto v_span = v._as_span(); + + if(v_span.size() > p_size) { + // Safe to truncate the span since we already checked v < p + v_span = v_span.first(p_size); } -} -Montgomery_Int::Montgomery_Int(std::shared_ptr params, - const word words[], - size_t len, - bool redc_needed) : - m_params(std::move(params)) { - m_v.set_words(words, len); + BOTAN_ASSERT_NOMSG(m_v.size() >= v_span.size()); + + copy_mem(std::span{m_v}.first(v_span.size()), v_span); if(redc_needed) { - BOTAN_ASSERT_NOMSG(m_v < m_params->p()); secure_vector ws; - m_v = m_params->mul(m_v, m_params->R2(), ws); + this->mul_by(m_params.R2()._as_span().first(p_size), ws); } } -void Montgomery_Int::fix_size() { - const size_t p_words = m_params->p_words(); - BOTAN_DEBUG_ASSERT(m_v.sig_words() <= p_words); - m_v.grow_to(p_words); -} - -bool Montgomery_Int::operator==(const Montgomery_Int& other) const { - return m_v == other.m_v && m_params->p() == other.m_params->p(); +Montgomery_Int::Montgomery_Int(const Montgomery_Params& params, std::span words) : + m_params(params), m_v(words.begin(), words.end()) { + BOTAN_ARG_CHECK(m_v.size() == m_params.p_words(), "Invalid input span"); } std::vector Montgomery_Int::serialize() const { return value().serialize(); } -size_t Montgomery_Int::size() const { - return m_params->p().bytes(); -} +BigInt Montgomery_Int::value() const { + secure_vector ws(m_params.p_words()); -bool Montgomery_Int::is_one() const { - return m_v == m_params->R1(); -} + secure_vector z = m_v; + z.resize(2 * m_params.p_words()); // zero extend -bool Montgomery_Int::is_zero() const { - return m_v.is_zero(); -} + bigint_monty_redc_inplace( + z.data(), m_params.p()._data(), m_params.p_words(), m_params.p_dash(), ws.data(), ws.size()); -BigInt Montgomery_Int::value() const { - secure_vector ws; - return m_params->redc(m_v, ws); + return BigInt::_from_words(z); } Montgomery_Int Montgomery_Int::operator+(const Montgomery_Int& other) const { BOTAN_STATE_CHECK(other.m_params == m_params); - secure_vector ws; - BigInt z = m_v; - z.mod_add(other.m_v, m_params->p(), ws); - return Montgomery_Int(m_params, z, false); -} -Montgomery_Int Montgomery_Int::operator-(const Montgomery_Int& other) const { - BOTAN_STATE_CHECK(other.m_params == m_params); - secure_vector ws; - BigInt z = m_v; - z.mod_sub(other.m_v, m_params->p(), ws); - return Montgomery_Int(m_params, z, false); -} + const size_t p_size = m_params.p_words(); + BOTAN_ASSERT_NOMSG(m_v.size() == p_size && other.m_v.size() == p_size); -Montgomery_Int& Montgomery_Int::operator+=(const Montgomery_Int& other) { - BOTAN_STATE_CHECK(other.m_params == m_params); - secure_vector ws; - return this->add(other, ws); -} + secure_vector z(2 * p_size); -Montgomery_Int& Montgomery_Int::add(const Montgomery_Int& other, secure_vector& ws) { - BOTAN_STATE_CHECK(other.m_params == m_params); - m_v.mod_add(other.m_v, m_params->p(), ws); - return (*this); -} + word* r = std::span{z}.first(p_size).data(); + word* t = std::span{z}.last(p_size).data(); -Montgomery_Int& Montgomery_Int::operator-=(const Montgomery_Int& other) { - BOTAN_STATE_CHECK(other.m_params == m_params); - secure_vector ws; - return this->sub(other, ws); -} + // t = this + other + const word carry = bigint_add3(t, m_v.data(), p_size, other.m_v.data(), p_size); -Montgomery_Int& Montgomery_Int::sub(const Montgomery_Int& other, secure_vector& ws) { - BOTAN_STATE_CHECK(other.m_params == m_params); - m_v.mod_sub(other.m_v, m_params->p(), ws); - return (*this); + // Conditionally subtract r = t - p + bigint_monty_maybe_sub(p_size, r, carry, t, m_params.p()._data()); + + z.resize(p_size); // truncate leaving only r + return Montgomery_Int(m_params, std::move(z)); } -Montgomery_Int Montgomery_Int::operator*(const Montgomery_Int& other) const { +Montgomery_Int Montgomery_Int::operator-(const Montgomery_Int& other) const { BOTAN_STATE_CHECK(other.m_params == m_params); - secure_vector ws; - return Montgomery_Int(m_params, m_params->mul(m_v, other.m_v, ws), false); + + const size_t p_size = m_params.p_words(); + BOTAN_ASSERT_NOMSG(m_v.size() == p_size && other.m_v.size() == p_size); + + secure_vector t(p_size); + const word borrow = bigint_sub3(t.data(), m_v.data(), p_size, other.m_v.data(), p_size); + + bigint_cnd_add(borrow, t.data(), m_params.p()._data(), p_size); + + return Montgomery_Int(m_params, std::move(t)); } Montgomery_Int Montgomery_Int::mul(const Montgomery_Int& other, secure_vector& ws) const { BOTAN_STATE_CHECK(other.m_params == m_params); - return Montgomery_Int(m_params, m_params->mul(m_v, other.m_v, ws), false); + + const size_t p_size = m_params.p_words(); + BOTAN_ASSERT_NOMSG(m_v.size() == p_size && other.m_v.size() == p_size); + + if(ws.size() < 2 * p_size) { + ws.resize(2 * p_size); + } + + secure_vector z(2 * p_size); + + bigint_mul(z.data(), z.size(), m_v.data(), p_size, p_size, other.m_v.data(), p_size, p_size, ws.data(), ws.size()); + + bigint_monty_redc_inplace(z.data(), m_params.p()._data(), p_size, m_params.p_dash(), ws.data(), ws.size()); + z.resize(p_size); // truncate off high zero words + + return Montgomery_Int(m_params, std::move(z)); } Montgomery_Int& Montgomery_Int::mul_by(const Montgomery_Int& other, secure_vector& ws) { BOTAN_STATE_CHECK(other.m_params == m_params); - m_params->mul_by(m_v, other.m_v, ws); - return (*this); + return this->mul_by(std::span{other.m_v}, ws); } -Montgomery_Int& Montgomery_Int::mul_by(const secure_vector& other, secure_vector& ws) { - m_params->mul_by(m_v, other, ws); - return (*this); -} +Montgomery_Int& Montgomery_Int::mul_by(std::span other, secure_vector& ws) { + const size_t p_size = m_params.p_words(); + BOTAN_ASSERT_NOMSG(m_v.size() == p_size && other.size() == p_size); -Montgomery_Int& Montgomery_Int::operator*=(const Montgomery_Int& other) { - BOTAN_STATE_CHECK(other.m_params == m_params); - secure_vector ws; - return mul_by(other, ws); -} + if(ws.size() < 2 * p_size) { + ws.resize(2 * p_size); + } -Montgomery_Int& Montgomery_Int::operator*=(const secure_vector& other) { - secure_vector ws; - return mul_by(other, ws); -} + auto do_mul_by = [&](std::span z) { + bigint_mul(z.data(), z.size(), m_v.data(), p_size, p_size, other.data(), p_size, p_size, ws.data(), ws.size()); -Montgomery_Int& Montgomery_Int::square_this_n_times(secure_vector& ws, size_t n) { - for(size_t i = 0; i != n; ++i) { - m_params->square_this(m_v, ws); + bigint_monty_redc_inplace(z.data(), m_params.p()._data(), p_size, m_params.p_dash(), ws.data(), ws.size()); + + copy_mem(m_v, z.first(p_size)); + }; + + if(p_size <= MontgomeryUseStackLimit) { + std::array z{}; + do_mul_by(z); + } else { + secure_vector z(2 * p_size); + do_mul_by(z); } - return (*this); -} -Montgomery_Int& Montgomery_Int::square_this(secure_vector& ws) { - m_params->square_this(m_v, ws); return (*this); } -Montgomery_Int Montgomery_Int::square(secure_vector& ws) const { - return Montgomery_Int(m_params, m_params->sqr(m_v, ws), false); -} +Montgomery_Int& Montgomery_Int::square_this_n_times(secure_vector& ws, size_t n) { + const size_t p_size = m_params.p_words(); + BOTAN_ASSERT_NOMSG(m_v.size() == p_size); -Montgomery_Int Montgomery_Int::cube(secure_vector& ws) const { - return Montgomery_Int(m_params, m_params->sqr(m_v, ws), false); -} + if(ws.size() < 2 * p_size) { + ws.resize(2 * p_size); + } -Montgomery_Int Montgomery_Int::additive_inverse() const { - return Montgomery_Int(m_params, m_params->p()) - (*this); -} + auto do_sqr_n = [&](std::span z) { + for(size_t i = 0; i != n; ++i) { + bigint_sqr(z.data(), 2 * p_size, m_v.data(), p_size, p_size, ws.data(), ws.size()); -Montgomery_Int& Montgomery_Int::mul_by_2(secure_vector& ws) { - m_v.mod_mul(2, m_params->p(), ws); - return (*this); -} + bigint_monty_redc_inplace(z.data(), m_params.p()._data(), p_size, m_params.p_dash(), ws.data(), ws.size()); -Montgomery_Int& Montgomery_Int::mul_by_3(secure_vector& ws) { - m_v.mod_mul(3, m_params->p(), ws); - return (*this); -} + copy_mem(m_v, std::span{z}.first(p_size)); + } + }; + + if(p_size <= MontgomeryUseStackLimit) { + std::array z{}; + do_sqr_n(z); + } else { + secure_vector z(2 * p_size); + do_sqr_n(z); + } -Montgomery_Int& Montgomery_Int::mul_by_4(secure_vector& ws) { - m_v.mod_mul(4, m_params->p(), ws); return (*this); } -Montgomery_Int& Montgomery_Int::mul_by_8(secure_vector& ws) { - m_v.mod_mul(8, m_params->p(), ws); - return (*this); +Montgomery_Int Montgomery_Int::square(secure_vector& ws) const { + auto z = (*this); + z.square_this_n_times(ws, 1); + return z; } } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/math/numbertheory/monty.h botan3-3.12.0+dfsg/src/lib/math/numbertheory/monty.h --- botan3-3.7.1+dfsg/src/lib/math/numbertheory/monty.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/numbertheory/monty.h 2026-05-07 01:38:28.000000000 +0000 @@ -10,188 +10,163 @@ #include #include +#include +#include namespace Botan { -class Modular_Reducer; - -class Montgomery_Params; +class Barrett_Reduction; /** -* The Montgomery representation of an integer +* Parameters for Montgomery Reduction */ -class BOTAN_TEST_API Montgomery_Int final { +class BOTAN_TEST_API Montgomery_Params final { public: /** - * Create a zero-initialized Montgomery_Int - */ - Montgomery_Int(std::shared_ptr params) : m_params(std::move(params)) {} - - /** - * Create a Montgomery_Int - */ - Montgomery_Int(const std::shared_ptr& params, const BigInt& v, bool redc_needed = true); - - /** - * Create a Montgomery_Int - */ - Montgomery_Int(const std::shared_ptr& params, - const uint8_t bits[], - size_t len, - bool redc_needed = true); - - /** - * Create a Montgomery_Int - */ - Montgomery_Int(std::shared_ptr params, - const word words[], - size_t len, - bool redc_needed = true); - - static Montgomery_Int one(const std::shared_ptr& params); - - /** - * Wide reduction - input can be at most 2*bytes long + * Initialize a set of Montgomery reduction parameters. These values + * can be shared by all values in a specific Montgomery domain. */ - static Montgomery_Int from_wide_int(const std::shared_ptr& params, const BigInt& x); - - bool operator==(const Montgomery_Int& other) const; - - bool operator!=(const Montgomery_Int& other) const { return (m_v != other.m_v); } - - std::vector serialize() const; - - size_t size() const; - bool is_one() const; - bool is_zero() const; - - void fix_size(); + Montgomery_Params(const BigInt& p, const Barrett_Reduction& mod_p); /** - * Return the value to normal mod-p space + * Initialize a set of Montgomery reduction parameters. These values + * can be shared by all values in a specific Montgomery domain. */ - BigInt value() const; + explicit Montgomery_Params(const BigInt& p); - /** - * Return the Montgomery representation - */ - const BigInt& repr() const { return m_v; } + bool operator==(const Montgomery_Params& other) const; - Montgomery_Int operator+(const Montgomery_Int& other) const; + bool operator!=(const Montgomery_Params& other) const { return !((*this) == other); } - Montgomery_Int operator-(const Montgomery_Int& other) const; + const BigInt& p() const { return m_data->p(); } - Montgomery_Int& operator+=(const Montgomery_Int& other); + const BigInt& R1() const { return m_data->r1(); } - Montgomery_Int& operator-=(const Montgomery_Int& other); + const BigInt& R2() const { return m_data->r2(); } - Montgomery_Int operator*(const Montgomery_Int& other) const; + const BigInt& R3() const { return m_data->r3(); } - Montgomery_Int& operator*=(const Montgomery_Int& other); + word p_dash() const { return m_data->p_dash(); } - Montgomery_Int& operator*=(const secure_vector& other); + size_t p_words() const { return m_data->p_size(); } - Montgomery_Int& add(const Montgomery_Int& other, secure_vector& ws); + BigInt redc(const BigInt& x, secure_vector& ws) const; - Montgomery_Int& sub(const Montgomery_Int& other, secure_vector& ws); + void mul(BigInt& z, const BigInt& x, const BigInt& y, secure_vector& ws) const; - Montgomery_Int mul(const Montgomery_Int& other, secure_vector& ws) const; + void mul(BigInt& z, const BigInt& x, std::span y, secure_vector& ws) const; - Montgomery_Int& mul_by(const Montgomery_Int& other, secure_vector& ws); + BigInt mul(const BigInt& x, const BigInt& y, secure_vector& ws) const; - Montgomery_Int& mul_by(const secure_vector& other, secure_vector& ws); + void mul_by(BigInt& x, const BigInt& y, secure_vector& ws) const; - Montgomery_Int square(secure_vector& ws) const; + BigInt sqr(const BigInt& x, secure_vector& ws) const; - Montgomery_Int cube(secure_vector& ws) const; + void sqr(BigInt& z, const BigInt& x, secure_vector& ws) const; - Montgomery_Int& square_this(secure_vector& ws); + void sqr(BigInt& z, std::span x, secure_vector& ws) const; - Montgomery_Int& square_this_n_times(secure_vector& ws, size_t n); + private: + BigInt sqr(std::span x, secure_vector& ws) const; - Montgomery_Int additive_inverse() const; + class Data final { + public: + Data(const BigInt& p, const Barrett_Reduction& mod_p); - Montgomery_Int& mul_by_2(secure_vector& ws); + const BigInt& p() const { return m_p; } - Montgomery_Int& mul_by_3(secure_vector& ws); + const BigInt& r1() const { return m_r1; } - Montgomery_Int& mul_by_4(secure_vector& ws); + const BigInt& r2() const { return m_r2; } - Montgomery_Int& mul_by_8(secure_vector& ws); + const BigInt& r3() const { return m_r3; } - void _const_time_poison() const { CT::poison(m_v); } + word p_dash() const { return m_p_dash; } - void _const_time_unpoison() const { CT::unpoison(m_v); } + size_t p_size() const { return m_p_words; } - const std::shared_ptr& _params() const { return m_params; } + private: + BigInt m_p; + BigInt m_r1; + BigInt m_r2; + BigInt m_r3; + word m_p_dash; + size_t m_p_words; + }; - private: - std::shared_ptr m_params; - BigInt m_v; + std::shared_ptr m_data; }; /** -* Parameters for Montgomery Reduction +* The Montgomery representation of an integer */ -class BOTAN_TEST_API Montgomery_Params final { +class BOTAN_TEST_API Montgomery_Int final { public: /** - * Initialize a set of Montgomery reduction parameters. These values - * can be shared by all values in a specific Montgomery domain. + * Create a zero-initialized Montgomery_Int */ - Montgomery_Params(const BigInt& p, const Modular_Reducer& mod_p); + explicit Montgomery_Int(const Montgomery_Params& params) : m_params(params) {} /** - * Initialize a set of Montgomery reduction parameters. These values - * can be shared by all values in a specific Montgomery domain. + * Create a Montgomery_Int from a BigInt */ - Montgomery_Params(const BigInt& p); - - const BigInt& p() const { return m_p; } - - const BigInt& R1() const { return m_r1; } - - const BigInt& R2() const { return m_r2; } + Montgomery_Int(const Montgomery_Params& params, const BigInt& v, bool redc_needed = true); - const BigInt& R3() const { return m_r3; } + /** + * Create a Montgomery_Int + * + * The span must be exactly p_words long and encoding a value less than p already + * in Montgomery form + */ + Montgomery_Int(const Montgomery_Params& params, std::span words); - word p_dash() const { return m_p_dash; } + /** + * Return the value 1 in Montgomery form + */ + static Montgomery_Int one(const Montgomery_Params& params); - size_t p_words() const { return m_p_words; } + /** + * Wide reduction - input can be at most 2*bytes long + */ + static Montgomery_Int from_wide_int(const Montgomery_Params& params, const BigInt& x); - BigInt redc(const BigInt& x, secure_vector& ws) const; + std::vector serialize() const; - void redc_in_place(BigInt& x, secure_vector& ws) const; + /** + * Return the value to normal mod-p space + */ + BigInt value() const; - void mul(BigInt& z, const BigInt& x, const BigInt& y, secure_vector& ws) const; + /** + * Return the Montgomery representation + */ + const secure_vector& repr() const { return m_v; } - void mul(BigInt& z, const BigInt& x, std::span y, secure_vector& ws) const; + Montgomery_Int operator+(const Montgomery_Int& other) const; - BigInt mul(const BigInt& x, const BigInt& y, secure_vector& ws) const; + Montgomery_Int operator-(const Montgomery_Int& other) const; - BigInt mul(const BigInt& x, std::span y, secure_vector& ws) const; + Montgomery_Int mul(const Montgomery_Int& other, secure_vector& ws) const; - void mul_by(BigInt& x, std::span y, secure_vector& ws) const; + Montgomery_Int& mul_by(const Montgomery_Int& other, secure_vector& ws); - void mul_by(BigInt& x, const BigInt& y, secure_vector& ws) const; + Montgomery_Int& mul_by(std::span other, secure_vector& ws); - BigInt sqr(const BigInt& x, secure_vector& ws) const; + Montgomery_Int square(secure_vector& ws) const; - BigInt sqr(std::span x, secure_vector& ws) const; + Montgomery_Int& square_this_n_times(secure_vector& ws, size_t n); - void sqr(BigInt& z, const BigInt& x, secure_vector& ws) const; + void _const_time_poison() const { CT::poison(m_v); } - void sqr(BigInt& z, std::span x, secure_vector& ws) const; + void _const_time_unpoison() const { CT::unpoison(m_v); } - void square_this(BigInt& x, secure_vector& ws) const; + const Montgomery_Params& _params() const { return m_params; } private: - BigInt m_p; - BigInt m_r1; - BigInt m_r2; - BigInt m_r3; - word m_p_dash; - size_t m_p_words; + Montgomery_Int(const Montgomery_Params& params, secure_vector words); + + Montgomery_Params m_params; + secure_vector m_v; }; } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/math/numbertheory/monty_exp.cpp botan3-3.12.0+dfsg/src/lib/math/numbertheory/monty_exp.cpp --- botan3-3.7.1+dfsg/src/lib/math/numbertheory/monty_exp.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/numbertheory/monty_exp.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,29 +8,31 @@ #include +#include +#include #include #include #include namespace Botan { -class Montgomery_Exponentation_State final { +class Montgomery_Exponentiation_State final { public: - Montgomery_Exponentation_State(const Montgomery_Int& g, size_t window_bits, bool const_time); + Montgomery_Exponentiation_State(const Montgomery_Int& g, size_t window_bits, bool const_time); Montgomery_Int exponentiation(const BigInt& k, size_t max_k_bits) const; Montgomery_Int exponentiation_vartime(const BigInt& k) const; private: - std::shared_ptr m_params; + Montgomery_Params m_params; std::vector m_g; size_t m_window_bits; }; -Montgomery_Exponentation_State::Montgomery_Exponentation_State(const Montgomery_Int& g, - size_t window_bits, - bool const_time) : +Montgomery_Exponentiation_State::Montgomery_Exponentiation_State(const Montgomery_Int& g, + size_t window_bits, + bool const_time) : m_params(g._params()), m_window_bits(window_bits == 0 ? 4 : window_bits) { if(m_window_bits < 1 || m_window_bits > 12) { // really even 8 is too large ... throw Invalid_Argument("Invalid window bits for Montgomery exponentiation"); @@ -44,13 +46,14 @@ m_g.push_back(g); - for(size_t i = 2; i != window_size; ++i) { - m_g.push_back(m_g[1] * m_g[i - 1]); - } + secure_vector ws(2 * m_params.p_words()); - // Resize each element to exactly p words - for(auto& x : m_g) { - x.fix_size(); + for(size_t i = 2; i != window_size; ++i) { + if(i % 2 == 0) { + m_g.push_back(m_g[i / 2].square(ws)); + } else { + m_g.push_back(m_g[1].mul(m_g[i - 1], ws)); + } } if(const_time) { @@ -68,8 +71,8 @@ clear_mem(output.data(), output.size()); for(size_t i = 0; i != g.size(); i += 2) { - const secure_vector& vec_0 = g[i].repr().get_word_vector(); - const secure_vector& vec_1 = g[i + 1].repr().get_word_vector(); + const secure_vector& vec_0 = g[i].repr(); + const secure_vector& vec_1 = g[i + 1].repr(); BOTAN_ASSERT_NOMSG(vec_0.size() >= words && vec_1.size() >= words); @@ -85,7 +88,7 @@ } // namespace -Montgomery_Int Montgomery_Exponentation_State::exponentiation(const BigInt& scalar, size_t max_k_bits) const { +Montgomery_Int Montgomery_Exponentiation_State::exponentiation(const BigInt& scalar, size_t max_k_bits) const { BOTAN_DEBUG_ASSERT(scalar.bits() <= max_k_bits); // TODO add a const-time implementation of above assert and use it in release builds @@ -95,11 +98,11 @@ return Montgomery_Int::one(m_params); } - secure_vector e_bits(m_params->p_words()); - secure_vector ws; + secure_vector e_bits(m_params.p_words()); + secure_vector ws(2 * m_params.p_words()); const_time_lookup(e_bits, m_g, scalar.get_substring(m_window_bits * (exp_nibbles - 1), m_window_bits)); - Montgomery_Int x(m_params, e_bits.data(), e_bits.size(), false); + Montgomery_Int x(m_params, std::span{e_bits}); for(size_t i = exp_nibbles - 1; i > 0; --i) { x.square_this_n_times(ws, m_window_bits); @@ -111,10 +114,10 @@ return x; } -Montgomery_Int Montgomery_Exponentation_State::exponentiation_vartime(const BigInt& scalar) const { +Montgomery_Int Montgomery_Exponentiation_State::exponentiation_vartime(const BigInt& scalar) const { const size_t exp_nibbles = (scalar.bits() + m_window_bits - 1) / m_window_bits; - secure_vector ws; + secure_vector ws(2 * m_params.p_words()); if(exp_nibbles == 0) { return Montgomery_Int::one(m_params); @@ -135,44 +138,42 @@ return x; } -std::shared_ptr monty_precompute(const Montgomery_Int& g, - size_t window_bits, - bool const_time) { - return std::make_shared(g, window_bits, const_time); +std::shared_ptr monty_precompute(const Montgomery_Int& g, + size_t window_bits, + bool const_time) { + return std::make_shared(g, window_bits, const_time); } -std::shared_ptr monty_precompute( - const std::shared_ptr& params, const BigInt& g, size_t window_bits, bool const_time) { - BOTAN_ARG_CHECK(g < params->p(), "Montgomery base too big"); - Montgomery_Int monty_g(params, g); +std::shared_ptr monty_precompute(const Montgomery_Params& params, + const BigInt& g, + size_t window_bits, + bool const_time) { + BOTAN_ARG_CHECK(g < params.p(), "Montgomery base too big"); + const Montgomery_Int monty_g(params, g); return monty_precompute(monty_g, window_bits, const_time); } -Montgomery_Int monty_execute(const Montgomery_Exponentation_State& precomputed_state, +Montgomery_Int monty_execute(const Montgomery_Exponentiation_State& precomputed_state, const BigInt& k, size_t max_k_bits) { return precomputed_state.exponentiation(k, max_k_bits); } -Montgomery_Int monty_execute_vartime(const Montgomery_Exponentation_State& precomputed_state, const BigInt& k) { +Montgomery_Int monty_execute_vartime(const Montgomery_Exponentiation_State& precomputed_state, const BigInt& k) { return precomputed_state.exponentiation_vartime(k); } -Montgomery_Int monty_multi_exp(const std::shared_ptr& params_p, - const BigInt& x_bn, - const BigInt& z1, - const BigInt& y_bn, - const BigInt& z2) { - if(z1.is_negative() || z2.is_negative()) { +Montgomery_Int monty_multi_exp( + const Montgomery_Params& params_p, const BigInt& x_bn, const BigInt& z1, const BigInt& y_bn, const BigInt& z2) { + if(z1.signum() < 0 || z2.signum() < 0) { throw Invalid_Argument("multi_exponentiate exponents must be positive"); } const size_t z_bits = round_up(std::max(z1.bits(), z2.bits()), 2); - secure_vector ws; + secure_vector ws(2 * params_p.p_words()); - const Montgomery_Int one(params_p, params_p->R1(), false); - //const Montgomery_Int one(params_p, 1); + const Montgomery_Int one = Montgomery_Int::one(params_p); const Montgomery_Int x1(params_p, x_bn); const Montgomery_Int x2 = x1.square(ws); @@ -194,6 +195,7 @@ const Montgomery_Int y3x2 = y3.mul(x2, ws); const Montgomery_Int y3x3 = y3.mul(x3, ws); + // NOLINTNEXTLINE(*-const-correctness) bug in clang-tidy const Montgomery_Int* M[16] = {&one, &x1, // 0001 &x2, // 0010 @@ -215,8 +217,7 @@ for(size_t i = 0; i != z_bits; i += 2) { if(i > 0) { - H.square_this(ws); - H.square_this(ws); + H.square_this_n_times(ws, 2); } const uint32_t z1_b = z1.get_substring(z_bits - i - 2, 2); @@ -224,7 +225,9 @@ const uint32_t z12 = (4 * z2_b) + z1_b; - H.mul_by(*M[z12], ws); + if(z12 > 0) { + H.mul_by(*M[z12], ws); + } } return H; diff -Nru botan3-3.7.1+dfsg/src/lib/math/numbertheory/monty_exp.h botan3-3.12.0+dfsg/src/lib/math/numbertheory/monty_exp.h --- botan3-3.7.1+dfsg/src/lib/math/numbertheory/monty_exp.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/numbertheory/monty_exp.h 2026-05-07 01:38:28.000000000 +0000 @@ -13,29 +13,27 @@ namespace Botan { class BigInt; -class Modular_Reducer; -class Montgomery_Exponentation_State; +class Montgomery_Exponentiation_State; /* * Precompute for calculating values g^x mod p */ -std::shared_ptr monty_precompute( - const std::shared_ptr& params_p, - const BigInt& g, - size_t window_bits, - bool const_time = true); +std::shared_ptr monty_precompute(const Montgomery_Params& params_p, + const BigInt& g, + size_t window_bits, + bool const_time = true); /* * Precompute for calculating values g^x mod p */ -std::shared_ptr monty_precompute(const Montgomery_Int& g, - size_t window_bits, - bool const_time = true); +std::shared_ptr monty_precompute(const Montgomery_Int& g, + size_t window_bits, + bool const_time = true); /* * Return g^k mod p */ -Montgomery_Int monty_execute(const Montgomery_Exponentation_State& precomputed_state, +Montgomery_Int monty_execute(const Montgomery_Exponentiation_State& precomputed_state, const BigInt& k, size_t max_k_bits); @@ -43,9 +41,9 @@ * Return g^k mod p taking variable time depending on k * @warning only use this if k is public */ -Montgomery_Int monty_execute_vartime(const Montgomery_Exponentation_State& precomputed_state, const BigInt& k); +Montgomery_Int monty_execute_vartime(const Montgomery_Exponentiation_State& precomputed_state, const BigInt& k); -inline Montgomery_Int monty_exp(const std::shared_ptr& params_p, +inline Montgomery_Int monty_exp(const Montgomery_Params& params_p, const BigInt& g, const BigInt& k, size_t max_k_bits) { @@ -53,9 +51,7 @@ return monty_execute(*precomputed, k, max_k_bits); } -inline Montgomery_Int monty_exp_vartime(const std::shared_ptr& params_p, - const BigInt& g, - const BigInt& k) { +inline Montgomery_Int monty_exp_vartime(const Montgomery_Params& params_p, const BigInt& g, const BigInt& k) { auto precomputed = monty_precompute(params_p, g, 4, false); return monty_execute_vartime(*precomputed, k); } @@ -63,11 +59,8 @@ /** * Return (x^z1 * y^z2) % p */ -Montgomery_Int monty_multi_exp(const std::shared_ptr& params_p, - const BigInt& x, - const BigInt& z1, - const BigInt& y, - const BigInt& z2); +Montgomery_Int monty_multi_exp( + const Montgomery_Params& params_p, const BigInt& x, const BigInt& z1, const BigInt& y, const BigInt& z2); } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/math/numbertheory/numthry.cpp botan3-3.12.0+dfsg/src/lib/math/numbertheory/numthry.cpp --- botan3-3.7.1+dfsg/src/lib/math/numbertheory/numthry.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/numbertheory/numthry.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,8 +8,9 @@ #include -#include +#include #include +#include #include #include #include @@ -39,8 +40,8 @@ return BigInt::from_s32(-1); } - auto mod_p = Modular_Reducer::for_public_modulus(p); - auto monty_p = std::make_shared(p, mod_p); + auto mod_p = Barrett_Reduction::for_public_modulus(p); + const Montgomery_Params monty_p(p, mod_p); // If p == 3 (mod 4) there is a simple solution if(p % 4 == 3) { @@ -112,43 +113,69 @@ /* * Calculate the Jacobi symbol +* +* See Algorithm 2.149 in Handbook of Applied Cryptography */ -int32_t jacobi(const BigInt& a, const BigInt& n) { - if(n.is_even() || n < 2) { - throw Invalid_Argument("jacobi: second argument must be odd and > 1"); +int32_t jacobi(BigInt a, BigInt n) { + BOTAN_ARG_CHECK(n.is_odd() && n >= 3, "Argument n must be an odd integer >= 3"); + + if(a < 0 || a >= n) { + a %= n; } - BigInt x = a % n; - BigInt y = n; - int32_t J = 1; - - while(y > 1) { - x %= y; - if(x > y / 2) { - x = y - x; - if(y % 4 == 3) { - J = -J; - } + if(a == 0) { + return 0; + } + if(a == 1) { + return 1; + } + + int32_t s = 1; + + for(;;) { + const size_t e = low_zero_bits(a); + a >>= e; + const word n_mod_8 = n.word_at(0) % 8; + const word n_mod_4 = n_mod_8 % 4; + + if(e % 2 == 1 && (n_mod_8 == 3 || n_mod_8 == 5)) { + s = -s; } - if(x.is_zero()) { - return 0; + + if(n_mod_4 == 3 && a % 4 == 3) { + s = -s; } - size_t shifts = low_zero_bits(x); - x >>= shifts; - if(shifts % 2) { - word y_mod_8 = y % 8; - if(y_mod_8 == 3 || y_mod_8 == 5) { - J = -J; - } + /* + * The HAC presentation of the algorithm uses recursion, which is not + * desirable or necessary. + * + * Instead we loop accumulating the product of the various jacobi() + * subcomputations into s, until we reach algorithm termination, which + * occurs in one of two ways. + * + * If a == 1 then the recursion has completed; we can return the value of s. + * + * Otherwise, after swapping and reducing, check for a == 0 [this value is + * called `n1` in HAC's presentation]. This would imply that jacobi(n1,a1) + * would have the value 0, due to Line 1 in HAC 2.149, in which case the + * entire product is zero, and we can immediately return that result. + */ + + if(a == 1) { + return s; } - if(x % 4 == 3 && y % 4 == 3) { - J = -J; + std::swap(a, n); + + BOTAN_ASSERT_NOMSG(n.is_odd()); + + a %= n; + + if(a == 0) { + return 0; } - std::swap(x, y); } - return J; } /* @@ -213,25 +240,24 @@ // shifting so many times, we'll have reached the result for sure. const size_t loop_cnt = u.bits() + v.bits(); - using WordMask = CT::Mask; - // This temporary is big enough to hold all intermediate results of the // algorithm. No reallocation will happen during the loop. // Note however, that `ct_cond_assign()` will invalidate the 'sig_words' // cache, which _does not_ shrink the capacity of the underlying buffer. auto tmp = BigInt::with_capacity(sz); + secure_vector ws(sz * 2); size_t factors_of_two = 0; for(size_t i = 0; i != loop_cnt; ++i) { - auto both_odd = WordMask::expand(u.is_odd()) & WordMask::expand(v.is_odd()); + auto both_odd = CT::Mask::expand_bool(u.is_odd()) & CT::Mask::expand_bool(v.is_odd()); // Subtract the smaller from the larger if both are odd - auto u_gt_v = WordMask::expand(bigint_cmp(u._data(), u.size(), v._data(), v.size()) > 0); - bigint_sub_abs(tmp.mutable_data(), u._data(), sz, v._data(), sz); + auto u_gt_v = CT::Mask::expand_bool(bigint_cmp(u._data(), u.size(), v._data(), v.size()) > 0); + bigint_sub_abs(tmp.mutable_data(), u._data(), v._data(), sz, ws.data()); u.ct_cond_assign((u_gt_v & both_odd).as_bool(), tmp); v.ct_cond_assign((~u_gt_v & both_odd).as_bool(), tmp); - const auto u_is_even = WordMask::expand(u.is_even()); - const auto v_is_even = WordMask::expand(v.is_even()); + const auto u_is_even = CT::Mask::expand_bool(u.is_even()); + const auto v_is_even = CT::Mask::expand_bool(v.is_even()); BOTAN_DEBUG_ASSERT((u_is_even | v_is_even).as_bool()); // When both are even, we're going to eliminate a factor of 2. @@ -282,7 +308,7 @@ * Modular Exponentiation */ BigInt power_mod(const BigInt& base, const BigInt& exp, const BigInt& mod) { - if(mod.is_negative() || mod == 1) { + if(mod.signum() < 0 || mod == 1) { return BigInt::zero(); } @@ -293,13 +319,13 @@ return BigInt::zero(); } - auto reduce_mod = Modular_Reducer::for_secret_modulus(mod); + auto reduce_mod = Barrett_Reduction::for_secret_modulus(mod); const size_t exp_bits = exp.bits(); if(mod.is_odd()) { - auto monty_params = std::make_shared(mod, reduce_mod); - return monty_exp(monty_params, reduce_mod.reduce(base), exp, exp_bits).value(); + const Montgomery_Params monty_params(mod, reduce_mod); + return monty_exp(monty_params, ct_modulo(base, mod), exp, exp_bits).value(); } /* @@ -307,7 +333,7 @@ cryptographically important, so this implementation is slow ... */ BigInt accum = BigInt::one(); - BigInt g = reduce_mod.reduce(base); + BigInt g = ct_modulo(base, mod); BigInt t; for(size_t i = 0; i != exp_bits; ++i) { @@ -369,12 +395,13 @@ return std::binary_search(PRIMES, PRIMES + PRIME_TABLE_SIZE, num); } - auto mod_n = Modular_Reducer::for_secret_modulus(n); + auto mod_n = Barrett_Reduction::for_secret_modulus(n); + const Montgomery_Params monty_n(n, mod_n); if(rng.is_seeded()) { const size_t t = miller_rabin_test_iterations(n_bits, prob, is_random); - if(is_miller_rabin_probable_prime(n, mod_n, rng, t) == false) { + if(!is_miller_rabin_probable_prime(n, mod_n, monty_n, rng, t)) { return false; } diff -Nru botan3-3.7.1+dfsg/src/lib/math/numbertheory/numthry.h botan3-3.12.0+dfsg/src/lib/math/numbertheory/numthry.h --- botan3-3.7.1+dfsg/src/lib/math/numbertheory/numthry.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/numbertheory/numthry.h 2026-05-07 01:38:28.000000000 +0000 @@ -10,8 +10,6 @@ #include -BOTAN_FUTURE_INTERNAL_HEADER(numthry.h) - namespace Botan { class RandomNumberGenerator; @@ -39,13 +37,13 @@ * @param y a positive integer * @return z, smallest integer such that z % x == 0 and z % y == 0 */ -BigInt BOTAN_PUBLIC_API(2, 0) lcm(const BigInt& x, const BigInt& y); +BOTAN_DEPRECATED("Deprecated no replacement") BigInt BOTAN_PUBLIC_API(2, 0) lcm(const BigInt& x, const BigInt& y); /** * @param x an integer * @return (x*x) */ -BigInt BOTAN_PUBLIC_API(2, 0) square(const BigInt& x); +BOTAN_DEPRECATED("Just use x*x") BigInt BOTAN_PUBLIC_API(2, 0) square(const BigInt& x); /** * Modular inversion. This algorithm is const time with respect to x, @@ -70,10 +68,10 @@ * @param n is an odd integer > 1 * @return (n / m) */ -int32_t BOTAN_PUBLIC_API(2, 0) jacobi(const BigInt& a, const BigInt& n); +BOTAN_DEPRECATED("Deprecated no replacement") int32_t BOTAN_PUBLIC_API(2, 0) jacobi(BigInt a, BigInt n); /** -* Modular exponentation +* Modular exponentiation * @param b an integer base * @param x a positive exponent * @param m a positive modulus @@ -92,6 +90,7 @@ * @param p the prime modulus * @return y such that (y*y)%p == x, or -1 if no such integer */ +BOTAN_DEPRECATED("Deprecated no replacement") BigInt BOTAN_PUBLIC_API(3, 0) sqrt_modulo_prime(const BigInt& x, const BigInt& p); /** @@ -100,7 +99,7 @@ * largest value of n such that 2^n divides x evenly. Returns * zero if x is equal to zero. */ -size_t BOTAN_PUBLIC_API(2, 0) low_zero_bits(const BigInt& x); +BOTAN_DEPRECATED("Deprecated no replacement") size_t BOTAN_PUBLIC_API(2, 0) low_zero_bits(const BigInt& x); /** * Check for primality @@ -125,7 +124,7 @@ * @return 0 if the integer is not a perfect square, otherwise * returns the positive y st y*y == x */ -BigInt BOTAN_PUBLIC_API(2, 8) is_perfect_square(const BigInt& x); +BOTAN_DEPRECATED("Deprecated no replacement") BigInt BOTAN_PUBLIC_API(2, 8) is_perfect_square(const BigInt& x); /** * Randomly generate a prime suitable for discrete logarithm parameters @@ -154,6 +153,7 @@ * @param prob use test so false positive is bounded by 1/2**prob * @return random prime with the specified criteria */ +BOTAN_DEPRECATED("Deprecated no replacement") BigInt BOTAN_PUBLIC_API(2, 7) generate_rsa_prime(RandomNumberGenerator& keygen_rng, RandomNumberGenerator& prime_test_rng, size_t bits, @@ -166,12 +166,13 @@ * @param bits is how long the resulting prime should be * @return prime randomly chosen from safe primes of length bits */ +BOTAN_DEPRECATED("Deprecated no replacement") BigInt BOTAN_PUBLIC_API(2, 0) random_safe_prime(RandomNumberGenerator& rng, size_t bits); /** * The size of the PRIMES[] array */ -const size_t PRIME_TABLE_SIZE = 6541; +BOTAN_DEPRECATED("Deprecated no replacement") const size_t PRIME_TABLE_SIZE = 6541; /** * A const array of all odd primes less than 65535 diff -Nru botan3-3.7.1+dfsg/src/lib/math/numbertheory/primality.cpp botan3-3.12.0+dfsg/src/lib/math/numbertheory/primality.cpp --- botan3-3.7.1+dfsg/src/lib/math/numbertheory/primality.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/numbertheory/primality.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,15 +8,16 @@ #include #include -#include #include +#include #include #include -#include namespace Botan { -bool is_lucas_probable_prime(const BigInt& C, const Modular_Reducer& mod_C) { +bool is_lucas_probable_prime(const BigInt& C, const Barrett_Reduction& mod_C) { + BOTAN_ARG_CHECK(C.signum() >= 0, "Argument must be non-negative"); + if(C == 2 || C == 3 || C == 5 || C == 7 || C == 11 || C == 13) { return true; } @@ -28,7 +29,7 @@ BigInt D = BigInt::from_word(5); for(;;) { - int32_t j = jacobi(D, C); + const int32_t j = jacobi(D, C); if(j == 0) { return false; } @@ -38,7 +39,7 @@ } // Check 5, -7, 9, -11, 13, -15, 17, ... - if(D.is_negative()) { + if(D.signum() < 0) { D.flip_sign(); D += 2; } else { @@ -46,18 +47,25 @@ D.flip_sign(); } - if(D == 17 && is_perfect_square(C).is_nonzero()) { + if(D == 17 && is_perfect_square(C).signum() != 0) { return false; } } + if(D.signum() < 0) { + D += C; + } + const BigInt K = C + 1; const size_t K_bits = K.bits() - 1; BigInt U = BigInt::one(); BigInt V = BigInt::one(); - BigInt Ut, Vt, U2, V2; + BigInt Ut; + BigInt Vt; + BigInt U2; + BigInt V2; for(size_t i = 0; i != K_bits; ++i) { const bool k_bit = K.get_bit(K_bits - 1 - i); @@ -76,7 +84,7 @@ U2.ct_cond_add(U2.is_odd(), C); U2 >>= 1; - V2 = mod_C.reduce(Vt + Ut * D); + V2 = mod_C.reduce(Vt + mod_C.multiply(Ut, D)); V2.ct_cond_add(V2.is_odd(), C); V2 >>= 1; @@ -87,21 +95,21 @@ return (U == 0); } -bool is_bailie_psw_probable_prime(const BigInt& n, const Modular_Reducer& mod_n) { +bool is_bailie_psw_probable_prime(const BigInt& n, const Barrett_Reduction& mod_n) { if(n == 2) { return true; } else if(n <= 1 || n.is_even()) { return false; } - auto monty_n = std::make_shared(n, mod_n); + const Montgomery_Params monty_n(n, mod_n); const auto base = BigInt::from_word(2); return passes_miller_rabin_test(n, mod_n, monty_n, base) && is_lucas_probable_prime(n, mod_n); } bool passes_miller_rabin_test(const BigInt& n, - const Modular_Reducer& mod_n, - const std::shared_ptr& monty_n, + const Barrett_Reduction& mod_n, + const Montgomery_Params& monty_n, const BigInt& a) { if(n < 3 || n.is_even()) { return false; @@ -110,7 +118,13 @@ BOTAN_ASSERT_NOMSG(n > 1); const BigInt n_minus_1 = n - 1; - const size_t s = low_zero_bits(n_minus_1); + /* + * This unpoison is not ideal but realistically there is no way to + * hide the number of loop iterations (below). The main user of + * secret primes is RSA and we always generate RSA primes such that + * p == 3 (mod 4), which means s is always 1. + */ + const size_t s = CT::driveby_unpoison(low_zero_bits(n_minus_1)); const BigInt nm1_s = n_minus_1 >> s; const size_t n_bits = n.bits(); @@ -144,15 +158,14 @@ } bool is_miller_rabin_probable_prime(const BigInt& n, - const Modular_Reducer& mod_n, + const Barrett_Reduction& mod_n, + const Montgomery_Params& monty_n, RandomNumberGenerator& rng, size_t test_iterations) { if(n < 3 || n.is_even()) { return false; } - auto monty_n = std::make_shared(n, mod_n); - for(size_t i = 0; i != test_iterations; ++i) { const BigInt a = BigInt::random_integer(rng, BigInt::from_word(2), n); @@ -172,7 +185,7 @@ * If the candidate prime was maliciously constructed, we can't rely * on arguments based on p being random. */ - if(random == false) { + if(!random) { return base; } diff -Nru botan3-3.7.1+dfsg/src/lib/math/numbertheory/primality.h botan3-3.12.0+dfsg/src/lib/math/numbertheory/primality.h --- botan3-3.7.1+dfsg/src/lib/math/numbertheory/primality.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/numbertheory/primality.h 2026-05-07 01:38:28.000000000 +0000 @@ -8,13 +8,12 @@ #define BOTAN_PRIMALITY_TEST_H_ #include -#include #include namespace Botan { class BigInt; -class Modular_Reducer; +class Barrett_Reduction; class Montgomery_Params; class RandomNumberGenerator; @@ -26,10 +25,10 @@ * this test alone. * * @param n the positive integer to test -* @param mod_n a pre-created Modular_Reducer for n +* @param mod_n a pre-created Barrett_Reduction for n * @return true if n seems probably prime, false if n is composite */ -bool BOTAN_TEST_API is_lucas_probable_prime(const BigInt& n, const Modular_Reducer& mod_n); +bool BOTAN_TEST_API is_lucas_probable_prime(const BigInt& n, const Barrett_Reduction& mod_n); /** * Perform Bailie-PSW primality test @@ -39,10 +38,10 @@ * many composite counterexamples exist. * * @param n the positive integer to test -* @param mod_n a pre-created Modular_Reducer for n +* @param mod_n a pre-created Barrett_Reduction for n * @return true if n seems probably prime, false if n is composite */ -bool BOTAN_TEST_API is_bailie_psw_probable_prime(const BigInt& n, const Modular_Reducer& mod_n); +bool BOTAN_TEST_API is_bailie_psw_probable_prime(const BigInt& n, const Barrett_Reduction& mod_n); /** * Return required number of Miller-Rabin tests in order to @@ -59,28 +58,30 @@ * Perform a single Miller-Rabin test with specified base * * @param n the positive integer to test -* @param mod_n a pre-created Modular_Reducer for n +* @param mod_n a pre-created Barrett_Reduction for n * @param monty_n Montgomery parameters for n * @param a the base to check * @return result of primality test */ bool passes_miller_rabin_test(const BigInt& n, - const Modular_Reducer& mod_n, - const std::shared_ptr& monty_n, + const Barrett_Reduction& mod_n, + const Montgomery_Params& monty_n, const BigInt& a); /** * Perform t iterations of a Miller-Rabin primality test with random bases * * @param n the positive integer to test -* @param mod_n a pre-created Modular_Reducer for n +* @param mod_n a pre-created Barrett_Reduction for n +* @param monty_n pre-created Montgomery parameters for n * @param rng a random number generator * @param t number of tests to perform * * @return result of primality test */ bool BOTAN_TEST_API is_miller_rabin_probable_prime(const BigInt& n, - const Modular_Reducer& mod_n, + const Barrett_Reduction& mod_n, + const Montgomery_Params& monty_n, RandomNumberGenerator& rng, size_t t); diff -Nru botan3-3.7.1+dfsg/src/lib/math/numbertheory/reducer.cpp botan3-3.12.0+dfsg/src/lib/math/numbertheory/reducer.cpp --- botan3-3.7.1+dfsg/src/lib/math/numbertheory/reducer.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/numbertheory/reducer.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,145 +1,27 @@ /* * Modular Reducer -* (C) 1999-2011,2018 Jack Lloyd +* (C) 1999-2011,2018,2025 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ #include -#include +#include #include -#include namespace Botan { -/* -* Modular_Reducer Constructor -*/ -Modular_Reducer::Modular_Reducer(const BigInt& mod) { +Modular_Reducer::Modular_Reducer(const BigInt& mod) : m_mod_words(mod.sig_words()) { if(mod < 0) { throw Invalid_Argument("Modular_Reducer: modulus must be positive"); } - // Left uninitialized if mod == 0 - m_mod_words = 0; - - if(mod > 0) { - *this = Modular_Reducer::for_secret_modulus(mod); - } -} - -Modular_Reducer Modular_Reducer::for_secret_modulus(const BigInt& mod) { - BOTAN_ARG_CHECK(!mod.is_zero(), "Modulus cannot be zero"); - BOTAN_ARG_CHECK(!mod.is_negative(), "Modulus cannot be negative"); - - size_t mod_words = mod.sig_words(); - - // Compute mu = floor(2^{2k} / m) - const size_t mu_bits = 2 * BOTAN_MP_WORD_BITS * mod_words; - return Modular_Reducer(mod, ct_divide_pow2k(mu_bits, mod), mod_words); -} - -Modular_Reducer Modular_Reducer::for_public_modulus(const BigInt& mod) { - BOTAN_ARG_CHECK(!mod.is_zero(), "Modulus cannot be zero"); - BOTAN_ARG_CHECK(!mod.is_negative(), "Modulus cannot be negative"); - - size_t mod_words = mod.sig_words(); - - // Compute mu = floor(2^{2k} / m) - const size_t mu_bits = 2 * BOTAN_MP_WORD_BITS * mod_words; - return Modular_Reducer(mod, BigInt::power_of_2(mu_bits) / mod, mod_words); + m_modulus = mod; } BigInt Modular_Reducer::reduce(const BigInt& x) const { - BigInt r; - secure_vector ws; - reduce(r, x, ws); - return r; -} - -BigInt Modular_Reducer::square(const BigInt& x) const { - secure_vector ws; - BigInt x2 = x; - x2.square(ws); - BigInt r; - reduce(r, x2, ws); - return r; -} - -namespace { - -/* -* Like if(cnd) x.rev_sub(...) but in const time -*/ -void cnd_rev_sub(bool cnd, BigInt& x, const word y[], size_t y_sw, secure_vector& ws) { - if(x.sign() != BigInt::Positive) { - throw Invalid_State("BigInt::sub_rev requires this is positive"); - } - - const size_t x_sw = x.sig_words(); - - const size_t max_words = std::max(x_sw, y_sw); - ws.resize(std::max(x_sw, y_sw)); - clear_mem(ws.data(), ws.size()); - x.grow_to(max_words); - - const int32_t relative_size = bigint_sub_abs(ws.data(), x._data(), x_sw, y, y_sw); - - x.cond_flip_sign((relative_size > 0) && cnd); - bigint_cnd_swap(static_cast(cnd), x.mutable_data(), ws.data(), max_words); -} - -} // namespace - -void Modular_Reducer::reduce(BigInt& t1, const BigInt& x, secure_vector& ws) const { - if(&t1 == &x) { - throw Invalid_State("Modular_Reducer arguments cannot alias"); - } - if(m_mod_words == 0) { - throw Invalid_State("Modular_Reducer: Never initalized"); - } - - const size_t x_sw = x.sig_words(); - - if(x_sw > 2 * m_mod_words) { - // too big, fall back to slow boat division - t1 = ct_modulo(x, m_modulus); - return; - } - - t1 = x; - t1.set_sign(BigInt::Positive); - t1 >>= (BOTAN_MP_WORD_BITS * (m_mod_words - 1)); - - t1.mul(m_mu, ws); - t1 >>= (BOTAN_MP_WORD_BITS * (m_mod_words + 1)); - - // TODO add masked mul to avoid computing high bits - t1.mul(m_modulus, ws); - t1.mask_bits(BOTAN_MP_WORD_BITS * (m_mod_words + 1)); - - t1.rev_sub(x._data(), std::min(x_sw, m_mod_words + 1), ws); - - /* - * If t1 < 0 then we must add b^(k+1) where b = 2^w. To avoid a - * side channel perform the addition unconditionally, with ws set - * to either b^(k+1) or else 0. - */ - const word t1_neg = t1.is_negative(); - - if(ws.size() < m_mod_words + 2) { - ws.resize(m_mod_words + 2); - } - clear_mem(ws.data(), ws.size()); - ws[m_mod_words + 1] = t1_neg; - - t1.add(ws.data(), m_mod_words + 2, BigInt::Positive); - - // Per HAC this step requires at most 2 subtractions - t1.ct_reduce_below(m_modulus, ws, 2); - - cnd_rev_sub(t1.is_nonzero() && x.is_negative(), t1, m_modulus._data(), m_modulus.size(), ws); + return ct_modulo(x, m_modulus); } } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/math/numbertheory/reducer.h botan3-3.12.0+dfsg/src/lib/math/numbertheory/reducer.h --- botan3-3.7.1+dfsg/src/lib/math/numbertheory/reducer.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/numbertheory/reducer.h 2026-05-07 01:38:28.000000000 +0000 @@ -10,12 +10,14 @@ #include -BOTAN_FUTURE_INTERNAL_HEADER(reducer.h) +BOTAN_DEPRECATED_HEADER("reducer.h") namespace Botan { /** -* Modular Reducer (using Barrett's technique) +* Modular Reducer +* +* This class is deprecated without replacement */ class BOTAN_PUBLIC_API(2, 0) Modular_Reducer final { public: @@ -42,7 +44,7 @@ * @param x the value to square * @return (x * x) % p */ - BigInt square(const BigInt& x) const; + BigInt square(const BigInt& x) const { return reduce(x * x); } /** * Cube mod p @@ -58,28 +60,28 @@ * * @warning X and out must not reference each other * - * ws is a temporary workspace. + * ws is an (ignored) a temporary workspace. */ - void reduce(BigInt& out, const BigInt& x, secure_vector& ws) const; + void reduce(BigInt& out, const BigInt& x, secure_vector& /*ws*/) const { out = reduce(x); } bool initialized() const { return (m_mod_words != 0); } - BOTAN_DEPRECATED("Use for_public_modulus or for_secret_modulus") Modular_Reducer() { m_mod_words = 0; } + BOTAN_DEPRECATED("Use for_public_modulus or for_secret_modulus") Modular_Reducer() : m_mod_words(0) {} /** * Accepts m == 0 and leaves the Modular_Reducer in an uninitialized state */ - BOTAN_DEPRECATED("Use for_public_modulus or for_secret_modulus") explicit Modular_Reducer(const BigInt& mod); + explicit Modular_Reducer(const BigInt& mod); /** * Requires that m > 0 */ - static Modular_Reducer for_public_modulus(const BigInt& m); + static Modular_Reducer for_public_modulus(const BigInt& m) { return Modular_Reducer(m); } /** * Requires that m > 0 */ - static Modular_Reducer for_secret_modulus(const BigInt& m); + static Modular_Reducer for_secret_modulus(const BigInt& m) { return Modular_Reducer(m); } private: Modular_Reducer(const BigInt& m, BigInt mu, size_t mw) : m_modulus(m), m_mu(std::move(mu)), m_mod_words(mw) {} diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/info.txt botan3-3.12.0+dfsg/src/lib/math/pcurves/info.txt --- botan3-3.7.1+dfsg/src/lib/math/pcurves/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + PCURVES -> 20240404 - + name -> "Prime Order Curves" @@ -10,6 +10,7 @@ pcurves.h -pcurves_id.h +pcurves_algos.h pcurves_instance.h +pcurves_mul.h diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves.cpp botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves.cpp --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,207 +1,109 @@ /* -* (C) 2024 Jack Lloyd +* This file was automatically generated by ./src/scripts/dev_tools/gen_ec_groups.py on 2026-04-24 +* All manual changes will be lost. Edit the script instead. * * Botan is released under the Simplified BSD License (see license.txt) */ #include +#include +#include #include -#if defined(BOTAN_HAS_ASN1) - #include -#endif - namespace Botan::PCurve { -#if !defined(BOTAN_HAS_PCURVES_SECP192R1) -//static -std::shared_ptr PCurveInstance::secp192r1() { - return nullptr; +void PrimeOrderCurve::Scalar::_zeroize() { + secure_zeroize_buffer(m_value.data(), m_value.size() * sizeof(word)); } -#endif -#if !defined(BOTAN_HAS_PCURVES_SECP224R1) //static -std::shared_ptr PCurveInstance::secp224r1() { - return nullptr; -} +std::shared_ptr PrimeOrderCurve::from_params( + const BigInt& p, const BigInt& a, const BigInt& b, const BigInt& base_x, const BigInt& base_y, const BigInt& order) { +#if defined(BOTAN_HAS_PCURVES_GENERIC) + return PCurveInstance::from_params(p, a, b, base_x, base_y, order); #endif -#if !defined(BOTAN_HAS_PCURVES_SECP256R1) -//static -std::shared_ptr PCurveInstance::secp256r1() { - return nullptr; + BOTAN_UNUSED(p, a, b, base_x, base_y, order); + return {}; } -#endif -#if !defined(BOTAN_HAS_PCURVES_SECP384R1) //static -std::shared_ptr PCurveInstance::secp384r1() { - return nullptr; -} +std::shared_ptr PrimeOrderCurve::for_named_curve(std::string_view name) { +#if defined(BOTAN_HAS_PCURVES_SECP256R1) + if(name == "secp256r1") { + return PCurveInstance::secp256r1(); + } #endif -#if !defined(BOTAN_HAS_PCURVES_SECP521R1) -//static -std::shared_ptr PCurveInstance::secp521r1() { - return nullptr; -} +#if defined(BOTAN_HAS_PCURVES_SECP384R1) + if(name == "secp384r1") { + return PCurveInstance::secp384r1(); + } #endif -#if !defined(BOTAN_HAS_PCURVES_SECP256K1) -//static -std::shared_ptr PCurveInstance::secp256k1() { - return nullptr; -} +#if defined(BOTAN_HAS_PCURVES_SECP521R1) + if(name == "secp521r1") { + return PCurveInstance::secp521r1(); + } #endif -#if !defined(BOTAN_HAS_PCURVES_BRAINPOOL256R1) -//static -std::shared_ptr PCurveInstance::brainpool256r1() { - return nullptr; -} +#if defined(BOTAN_HAS_PCURVES_BRAINPOOL256R1) + if(name == "brainpool256r1") { + return PCurveInstance::brainpool256r1(); + } #endif -#if !defined(BOTAN_HAS_PCURVES_BRAINPOOL384R1) -//static -std::shared_ptr PCurveInstance::brainpool384r1() { - return nullptr; -} +#if defined(BOTAN_HAS_PCURVES_BRAINPOOL384R1) + if(name == "brainpool384r1") { + return PCurveInstance::brainpool384r1(); + } #endif -#if !defined(BOTAN_HAS_PCURVES_BRAINPOOL512R1) -//static -std::shared_ptr PCurveInstance::brainpool512r1() { - return nullptr; -} +#if defined(BOTAN_HAS_PCURVES_BRAINPOOL512R1) + if(name == "brainpool512r1") { + return PCurveInstance::brainpool512r1(); + } #endif -#if !defined(BOTAN_HAS_PCURVES_FRP256V1) -//static -std::shared_ptr PCurveInstance::frp256v1() { - return nullptr; -} +#if defined(BOTAN_HAS_PCURVES_FRP256V1) + if(name == "frp256v1") { + return PCurveInstance::frp256v1(); + } #endif -#if !defined(BOTAN_HAS_PCURVES_SM2P256V1) -//static -std::shared_ptr PCurveInstance::sm2p256v1() { - return nullptr; -} +#if defined(BOTAN_HAS_PCURVES_SECP192R1) + if(name == "secp192r1") { + return PCurveInstance::secp192r1(); + } #endif -#if !defined(BOTAN_HAS_PCURVES_NUMSP512D1) -//static -std::shared_ptr PCurveInstance::numsp512d1() { - return nullptr; -} +#if defined(BOTAN_HAS_PCURVES_SECP224R1) + if(name == "secp224r1") { + return PCurveInstance::secp224r1(); + } #endif -std::shared_ptr PrimeOrderCurve::from_id(PrimeOrderCurveId id) { - switch(id.code()) { - case PrimeOrderCurveId::secp192r1: - return PCurveInstance::secp192r1(); - case PrimeOrderCurveId::secp224r1: - return PCurveInstance::secp224r1(); - case PrimeOrderCurveId::secp256r1: - return PCurveInstance::secp256r1(); - case PrimeOrderCurveId::secp384r1: - return PCurveInstance::secp384r1(); - case PrimeOrderCurveId::secp521r1: - return PCurveInstance::secp521r1(); - case PrimeOrderCurveId::secp256k1: - return PCurveInstance::secp256k1(); - case PrimeOrderCurveId::brainpool256r1: - return PCurveInstance::brainpool256r1(); - case PrimeOrderCurveId::brainpool384r1: - return PCurveInstance::brainpool384r1(); - case PrimeOrderCurveId::brainpool512r1: - return PCurveInstance::brainpool512r1(); - case PrimeOrderCurveId::frp256v1: - return PCurveInstance::frp256v1(); - case PrimeOrderCurveId::sm2p256v1: - return PCurveInstance::sm2p256v1(); - case PrimeOrderCurveId::numsp512d1: - return PCurveInstance::numsp512d1(); +#if defined(BOTAN_HAS_PCURVES_SECP256K1) + if(name == "secp256k1") { + return PCurveInstance::secp256k1(); } - return {}; -} +#endif -std::string PrimeOrderCurveId::to_string() const { - switch(this->code()) { - case PrimeOrderCurveId::secp192r1: - return "secp192r1"; - case PrimeOrderCurveId::secp224r1: - return "secp224r1"; - case PrimeOrderCurveId::secp256r1: - return "secp256r1"; - case PrimeOrderCurveId::secp384r1: - return "secp384r1"; - case PrimeOrderCurveId::secp521r1: - return "secp521r1"; - case PrimeOrderCurveId::secp256k1: - return "secp256k1"; - case PrimeOrderCurveId::brainpool256r1: - return "brainpool256r1"; - case PrimeOrderCurveId::brainpool384r1: - return "brainpool384r1"; - case PrimeOrderCurveId::brainpool512r1: - return "brainpool512r1"; - case PrimeOrderCurveId::frp256v1: - return "frp256v1"; - case PrimeOrderCurveId::sm2p256v1: - return "sm2p256v1"; - case PrimeOrderCurveId::numsp512d1: - return "numsp512d1"; +#if defined(BOTAN_HAS_PCURVES_SM2P256V1) + if(name == "sm2p256v1") { + return PCurveInstance::sm2p256v1(); } +#endif - return "unknown"; -} - -//static -std::optional PrimeOrderCurveId::from_string(std::string_view name) { - if(name == "secp192r1") { - return PCurve::PrimeOrderCurveId::secp192r1; - } else if(name == "secp224r1") { - return PCurve::PrimeOrderCurveId::secp224r1; - } else if(name == "secp256r1") { - return PCurve::PrimeOrderCurveId::secp256r1; - } else if(name == "secp384r1") { - return PCurve::PrimeOrderCurveId::secp384r1; - } else if(name == "secp521r1") { - return PCurve::PrimeOrderCurveId::secp521r1; - } else if(name == "secp256k1") { - return PCurve::PrimeOrderCurveId::secp256k1; - } else if(name == "brainpool256r1") { - return PCurve::PrimeOrderCurveId::brainpool256r1; - } else if(name == "brainpool384r1") { - return PCurve::PrimeOrderCurveId::brainpool384r1; - } else if(name == "brainpool512r1") { - return PCurve::PrimeOrderCurveId::brainpool512r1; - } else if(name == "frp256v1") { - return PCurve::PrimeOrderCurveId::frp256v1; - } else if(name == "sm2p256v1") { - return PCurve::PrimeOrderCurveId::sm2p256v1; - } else if(name == "numsp512d1") { - return PCurve::PrimeOrderCurveId::numsp512d1; - } else { - return {}; +#if defined(BOTAN_HAS_PCURVES_NUMSP512D1) + if(name == "numsp512d1") { + return PCurveInstance::numsp512d1(); } -} - -#if defined(BOTAN_HAS_ASN1) +#endif -//static -std::optional PrimeOrderCurveId::from_oid(const OID& oid) { - const std::string name = oid.human_name_or_empty(); - if(name.empty()) { - return {}; - } else { - return PrimeOrderCurveId::from_string(name); - } + BOTAN_UNUSED(name); + return {}; } -#endif - } // namespace Botan::PCurve diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves.h botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves.h --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves.h 2026-05-07 01:38:28.000000000 +0000 @@ -7,19 +7,19 @@ #ifndef BOTAN_PCURVES_H_ #define BOTAN_PCURVES_H_ -#include - #include #include #include #include +#include +#include #include #include #include -#include namespace Botan { +class BigInt; class RandomNumberGenerator; } // namespace Botan @@ -29,27 +29,32 @@ /** * An elliptic curve without cofactor in Weierstrass form */ -class PrimeOrderCurve { +class PrimeOrderCurve /* NOLINT(*-special-member-functions) */ { public: /// Somewhat arbitrary maximum size for a field or scalar /// /// Sized to fit at least P-521 - static const size_t MaximumBitLength = 521; + static constexpr size_t MaximumBitLength = 521; - static const size_t MaximumByteLength = (MaximumBitLength + 7) / 8; + static constexpr size_t MaximumByteLength = (MaximumBitLength + 7) / 8; /// Number of words used to store MaximumByteLength - static const size_t StorageWords = (MaximumByteLength + sizeof(word) - 1) / sizeof(word); + static constexpr size_t StorageWords = (MaximumByteLength + sizeof(word) - 1) / sizeof(word); - static std::shared_ptr from_name(std::string_view name) { - if(auto id = PrimeOrderCurveId::from_string(name)) { - return PrimeOrderCurve::from_id(id.value()); - } else { - return {}; - } - } + /// @returns nullptr if the curve specified is not available + static std::shared_ptr for_named_curve(std::string_view name); - static std::shared_ptr from_id(PrimeOrderCurveId id); + /// @returns nullptr if the parameters seem unsuitable for pcurves + /// for example if the prime is too large + /// + /// This function *should* accept the same subset of curves as + /// the EC_Group constructor that accepts BigInts. + static std::shared_ptr from_params(const BigInt& p, + const BigInt& a, + const BigInt& b, + const BigInt& base_x, + const BigInt& base_y, + const BigInt& order); typedef std::array StorageUnit; typedef std::shared_ptr CurvePtr; @@ -66,69 +71,7 @@ Scalar& operator=(Scalar&& other) = default; ~Scalar() = default; - /** - * Return the size of the byte encoding of Scalars - */ - size_t bytes() const { return m_curve->scalar_bytes(); } - - /** - * Return the fixed length serialization of this scalar - */ - template > - T serialize() const { - T bytes(this->bytes()); - m_curve->serialize_scalar(bytes, *this); - return bytes; - } - - /** - * Perform integer multiplication modulo the group order - */ - friend Scalar operator*(const Scalar& a, const Scalar& b) { return a.m_curve->scalar_mul(a, b); } - - /** - * Perform integer addition modulo the group order - */ - friend Scalar operator+(const Scalar& a, const Scalar& b) { return a.m_curve->scalar_add(a, b); } - - /** - * Perform integer subtraction modulo the group order - */ - friend Scalar operator-(const Scalar& a, const Scalar& b) { return a.m_curve->scalar_sub(a, b); } - - /** - * Check for equality - */ - friend bool operator==(const Scalar& a, const Scalar& b) { return a.m_curve->scalar_equal(a, b); } - - /** - * Negate modulo the group order (ie return p - *this where p is the group order) - */ - Scalar negate() const { return m_curve->scalar_negate(*this); } - - /** - * Square modulo the group order - */ - Scalar square() const { return m_curve->scalar_square(*this); } - - /** - * Return the modular inverse of *this - * - * If *this is zero then returns zero. - */ - Scalar invert() const { return m_curve->scalar_invert(*this); } - - /** - * Return the modular inverse of *this (variable time) - * - * If *this is zero then returns zero. - */ - Scalar invert_vartime() const { return m_curve->scalar_invert_vartime(*this); } - - /** - * Returns true if this is equal to zero - */ - bool is_zero() const { return m_curve->scalar_is_zero(*this); } + void _zeroize(); const auto& _curve() const { return m_curve; } @@ -156,57 +99,7 @@ AffinePoint& operator=(AffinePoint&& other) = default; ~AffinePoint() = default; - static AffinePoint generator(CurvePtr curve) { return curve->generator(); } - - /** - * Return the size of the uncompressed encoding of points - */ - size_t bytes() const { return 1 + 2 * m_curve->field_element_bytes(); } - - /** - * Return the size of the compressed encoding of points - */ - size_t compressed_bytes() const { return 1 + m_curve->field_element_bytes(); } - - /** - * Return the serialization of the point in uncompressed form - */ - template > - T serialize() const { - T bytes(this->bytes()); - m_curve->serialize_point(bytes, *this); - return bytes; - } - - /** - * Return the serialization of the point in compressed form - */ - template > - T serialize_compressed() const { - T bytes(this->compressed_bytes()); - m_curve->serialize_point_compressed(bytes, *this); - return bytes; - } - - /** - * Return the serialization of the x coordinate - */ - template > - T x_bytes() const { - secure_vector bytes(m_curve->field_element_bytes()); - m_curve->serialize_point_x(bytes, *this); - return bytes; - } - - /** - * Point negation - */ - AffinePoint negate() const { return m_curve->point_negate(*this); } - - /** - * Return true if this is the curve identity element (aka the point at infinity) - */ - bool is_identity() const { return m_curve->affine_point_is_identity(*this); } + static AffinePoint generator(const CurvePtr& curve) { return curve->generator(); } const auto& _curve() const { return m_curve; } @@ -240,29 +133,6 @@ ProjectivePoint& operator=(ProjectivePoint&& other) = default; ~ProjectivePoint() = default; - /** - * Convert a point from affine to projective form - */ - static ProjectivePoint from_affine(const AffinePoint& pt) { return pt._curve()->point_to_projective(pt); } - - /** - * Convert a point from projective to affine form - * - * This operation is expensive; perform it only when required for - * serialization - */ - AffinePoint to_affine() const { return m_curve->point_to_affine(*this); } - - ProjectivePoint dbl() const { return m_curve->point_double(*this); } - - friend ProjectivePoint operator+(const ProjectivePoint& x, const ProjectivePoint& y) { - return x.m_curve->point_add(x, y); - } - - friend ProjectivePoint operator+(const ProjectivePoint& x, const AffinePoint& y) { - return x.m_curve->point_add_mixed(x, y); - } - const auto& _curve() const { return m_curve; } const auto& _x() const { return m_x; } @@ -285,7 +155,7 @@ StorageUnit m_z; }; - class PrecomputedMul2Table { + class PrecomputedMul2Table /* NOLINT(*-special-member-functions) */ { public: virtual ~PrecomputedMul2Table() = default; }; @@ -327,10 +197,6 @@ const Scalar& scalar, RandomNumberGenerator& rng) const = 0; - /// Setup a table for 2-ary multiplication - virtual std::unique_ptr mul2_setup(const AffinePoint& p, - const AffinePoint& pq) const = 0; - /// Setup a table for 2-ary multiplication where the first point is the generator virtual std::unique_ptr mul2_setup_g(const AffinePoint& q) const = 0; @@ -393,44 +259,46 @@ virtual AffinePoint point_to_affine(const ProjectivePoint& pt) const = 0; - virtual ProjectivePoint point_to_projective(const AffinePoint& pt) const = 0; - virtual bool affine_point_is_identity(const AffinePoint& pt) const = 0; - virtual ProjectivePoint point_double(const ProjectivePoint& pt) const = 0; - virtual AffinePoint point_negate(const AffinePoint& pt) const = 0; - virtual ProjectivePoint point_add(const ProjectivePoint& a, const ProjectivePoint& b) const = 0; - - virtual ProjectivePoint point_add_mixed(const ProjectivePoint& a, const AffinePoint& b) const = 0; + virtual ProjectivePoint point_add(const AffinePoint& a, const AffinePoint& b) const = 0; virtual void serialize_point(std::span bytes, const AffinePoint& pt) const = 0; - virtual void serialize_point_compressed(std::span bytes, const AffinePoint& pt) const = 0; - - virtual void serialize_point_x(std::span bytes, const AffinePoint& pt) const = 0; - virtual void serialize_scalar(std::span bytes, const Scalar& scalar) const = 0; /** - * Return the scalar zero - */ - virtual Scalar scalar_zero() const = 0; - - /** * Return the scalar one */ virtual Scalar scalar_one() const = 0; + /// Scalar addition virtual Scalar scalar_add(const Scalar& a, const Scalar& b) const = 0; + + /// Scalar subtraction virtual Scalar scalar_sub(const Scalar& a, const Scalar& b) const = 0; + + /// Scalar multiplication virtual Scalar scalar_mul(const Scalar& a, const Scalar& b) const = 0; + + /// Scalar squaring virtual Scalar scalar_square(const Scalar& s) const = 0; + + /// Scalar inversion virtual Scalar scalar_invert(const Scalar& s) const = 0; + + /// Scalar inversion (variable time) virtual Scalar scalar_invert_vartime(const Scalar& s) const = 0; + + /// Scalar negation virtual Scalar scalar_negate(const Scalar& s) const = 0; + + /// Test if scalar is zero virtual bool scalar_is_zero(const Scalar& s) const = 0; + + /// Test if two scalars are equal virtual bool scalar_equal(const Scalar& a, const Scalar& b) const = 0; /** @@ -442,19 +310,25 @@ * RFC 9380 hash to curve (NU variant) * * This is currently only supported for a few specific curves + * + * @param expand_message is a callback which must fill the provided output + * span with a sequence of uniform bytes, or if this is not possible due to + * length limitations or some other issue, throw an exception. It is + * invoked to produce the `uniform_bytes` value; see RFC 9380 section 5.2 */ - virtual AffinePoint hash_to_curve_nu(std::string_view hash, - std::span input, - std::span domain_sep) const = 0; + virtual AffinePoint hash_to_curve_nu(std::function)> expand_message) const = 0; /** * RFC 9380 hash to curve (RO variant) * * This is currently only supported for a few specific curves + * + * @param expand_message is a callback which must fill the provided output + * span with a sequence of uniform bytes, or if this is not possible due to + * length limitations or some other issue, throw an exception. It is + * invoked to produce the `uniform_bytes` value; see RFC 9380 section 5.2 */ - virtual ProjectivePoint hash_to_curve_ro(std::string_view hash, - std::span input, - std::span domain_sep) const = 0; + virtual ProjectivePoint hash_to_curve_ro(std::function)> expand_message) const = 0; }; } // namespace Botan::PCurve diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_algos.h botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_algos.h --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_algos.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_algos.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,503 @@ +/* +* (C) 2024,2025 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_PCURVES_ALGOS_H_ +#define BOTAN_PCURVES_ALGOS_H_ + +#include +#include +#include +#include +#include + +namespace Botan { + +/** +* Field inversion concept +* +* This concept checks if the curve class supports fe_invert2 +*/ +template +concept curve_supports_fe_invert2 = requires(const typename C::FieldElement& fe) { + { C::fe_invert2(fe) } -> std::same_as; +}; + +/** +* Field inversion +* +* Uses the specialized fe_invert2 if available, or otherwise the standard +* (FLT-based) field inversion. +*/ +template +inline constexpr auto invert_field_element(const typename C::FieldElement& fe) { + if constexpr(curve_supports_fe_invert2) { + return C::fe_invert2(fe) * fe; + } else { + return fe.invert(); + } +} + +/** +* Field square root +* +* This concept checks if the curve class supports fe_sqrt +*/ +template +concept curve_supports_fe_sqrt = requires(const typename C::FieldElement& fe) { + { C::fe_sqrt(fe) } -> std::same_as; +}; + +/** +* Field square root +* +* Uses the specialized fe_sqrt if available, or otherwise the standard +* square root +*/ +template +inline constexpr CT::Option sqrt_field_element(const typename C::FieldElement& fe) { + if constexpr(curve_supports_fe_sqrt) { + auto z = C::fe_sqrt(fe); + // Zero out the return value if it would otherwise be incorrect + const CT::Choice correct = (z.square() == fe); + z.conditional_assign(!correct, C::FieldElement::zero()); + return CT::Option(z, correct); + } else { + return fe.sqrt(); + } +} + +/** +* Convert a projective point into affine +*/ +template +inline constexpr auto to_affine(const typename C::ProjectivePoint& pt) { + // Not strictly required right? - default should work as long + // as (0,0) is identity and invert returns 0 on 0 + + if constexpr(curve_supports_fe_invert2) { + const auto z2_inv = C::fe_invert2(pt.z()); + const auto z3_inv = z2_inv.square() * pt.z(); + return typename C::AffinePoint(pt.x() * z2_inv, pt.y() * z3_inv); + } else { + const auto z_inv = invert_field_element(pt.z()); + const auto z2_inv = z_inv.square(); + const auto z3_inv = z_inv * z2_inv; + return typename C::AffinePoint(pt.x() * z2_inv, pt.y() * z3_inv); + } +} + +/** +* Convert a projective point into affine and return x coordinate only +*/ +template +auto to_affine_x(const typename C::ProjectivePoint& pt) { + if constexpr(curve_supports_fe_invert2) { + return pt.x() * C::fe_invert2(pt.z()); + } else { + const auto z_inv = invert_field_element(pt.z()); + const auto z2_inv = z_inv.square(); + return pt.x() * z2_inv; + } +} + +template +auto to_affine_batch(std::span projective) { + using AffinePoint = typename C::AffinePoint; + + const size_t N = projective.size(); + std::vector affine; + affine.reserve(N); + + CT::Choice any_identity = CT::Choice::no(); + + for(const auto& pt : projective) { + any_identity = any_identity || pt.is_identity(); + } + + // Conditional acceptable: N is public. State of points is not necessarily + // public, but we don't leak which point was the identity. In practice with + // the algorithms currently in use, the only time an identity can occur is + // during mul2 where the two points g/h have a small relation (ie h = g*k for + // some k < 16) + + if(N <= 2 || any_identity.as_bool()) { + // If there are identity elements, using the batch inversion gets + // tricky. It can be done, but this should be a rare situation so + // just punt to the serial conversion if it occurs + for(size_t i = 0; i != N; ++i) { + affine.push_back(to_affine(projective[i])); + } + } else { + std::vector c; + c.reserve(N); + + /* + Batch projective->affine using Montgomery's trick + + See Algorithm 2.26 in "Guide to Elliptic Curve Cryptography" + (Hankerson, Menezes, Vanstone) + */ + + c.push_back(projective[0].z()); + for(size_t i = 1; i != N; ++i) { + c.push_back(c[i - 1] * projective[i].z()); + } + + auto s_inv = [&]() { + if constexpr(VariableTime) { + return c[N - 1].invert_vartime(); + } else { + return invert_field_element(c[N - 1]); + } + }(); + + for(size_t i = N - 1; i > 0; --i) { + const auto& p = projective[i]; + + const auto z_inv = s_inv * c[i - 1]; + const auto z2_inv = z_inv.square(); + const auto z3_inv = z_inv * z2_inv; + + s_inv = s_inv * p.z(); + + affine.push_back(AffinePoint(p.x() * z2_inv, p.y() * z3_inv)); + } + + const auto z2_inv = s_inv.square(); + const auto z3_inv = s_inv * z2_inv; + affine.push_back(AffinePoint(projective[0].x() * z2_inv, projective[0].y() * z3_inv)); + std::reverse(affine.begin(), affine.end()); + return affine; + } + + return affine; +} + +/* +Projective point addition + +https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#addition-add-1998-cmo-2 + +Cost: 12M + 4S + 6add + 1*2 +*/ +template +inline constexpr ProjectivePoint point_add(const ProjectivePoint& a, const ProjectivePoint& b) { + const auto a_is_identity = a.is_identity(); + const auto b_is_identity = b.is_identity(); + + const auto Z1Z1 = a.z().square(); + const auto Z2Z2 = b.z().square(); + const auto U1 = a.x() * Z2Z2; + const auto U2 = b.x() * Z1Z1; + const auto S1 = a.y() * b.z() * Z2Z2; + const auto S2 = b.y() * a.z() * Z1Z1; + const auto H = U2 - U1; + const auto r = S2 - S1; + + /* Risky conditional + * + * This implementation uses projective coordinates, which do not have an efficient complete + * addition formula. We rely on the design of the multiplication algorithms to avoid doublings. + * + * This conditional only comes into play for the actual doubling case, not x + (-x) which + * is another exceptional case in some circumstances. Here if a == -b then H == 0 && r != 0, + * in which case at the end we'll set z to a.z * b.z * H = 0, resulting in the correct + * output (the identity element) + */ + if((r.is_zero() && H.is_zero() && !(a_is_identity && b_is_identity)).as_bool()) { + return a.dbl(); + } + + const auto HH = H.square(); + const auto HHH = H * HH; + const auto V = U1 * HH; + const auto t2 = r.square(); + const auto t3 = V + V; + const auto t4 = t2 - HHH; + auto X3 = t4 - t3; + const auto t5 = V - X3; + const auto t6 = S1 * HHH; + const auto t7 = r * t5; + auto Y3 = t7 - t6; + const auto t8 = b.z() * H; + auto Z3 = a.z() * t8; + + // if a is identity then return b + FieldElement::conditional_assign(X3, Y3, Z3, a_is_identity, b.x(), b.y(), b.z()); + + // if b is identity then return a + FieldElement::conditional_assign(X3, Y3, Z3, b_is_identity, a.x(), a.y(), a.z()); + + return ProjectivePoint(X3, Y3, Z3); +} + +template +inline constexpr ProjectivePoint point_add_mixed(const ProjectivePoint& a, + const AffinePoint& b, + const FieldElement& one) { + const auto a_is_identity = a.is_identity(); + const auto b_is_identity = b.is_identity(); + + /* + https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#addition-add-1998-cmo-2 + + Cost: 8M + 3S + 6add + 1*2 + */ + + const auto Z1Z1 = a.z().square(); + const auto U2 = b.x() * Z1Z1; + const auto S2 = b.y() * a.z() * Z1Z1; + const auto H = U2 - a.x(); + const auto r = S2 - a.y(); + + /* Risky conditional + * + * This implementation uses projective coordinates, which do not have an efficient complete + * addition formula. We rely on the design of the multiplication algorithms to avoid doublings. + * + * This conditional only comes into play for the actual doubling case, not x + (-x) which + * is another exceptional case in some circumstances. Here if a == -b then H == 0 && r != 0, + * in which case at the end we'll set z to a.z * H = 0, resulting in the correct output + * (the identity element) + */ + if((r.is_zero() && H.is_zero() && !(a_is_identity && b_is_identity)).as_bool()) { + return a.dbl(); + } + + const auto HH = H.square(); + const auto HHH = H * HH; + const auto V = a.x() * HH; + const auto t2 = r.square(); + const auto t3 = V + V; + const auto t4 = t2 - HHH; + auto X3 = t4 - t3; + const auto t5 = V - X3; + const auto t6 = a.y() * HHH; + const auto t7 = r * t5; + auto Y3 = t7 - t6; + auto Z3 = a.z() * H; + + // if a is identity then return b + FieldElement::conditional_assign(X3, Y3, Z3, a_is_identity, b.x(), b.y(), one); + + // if b is identity then return a + FieldElement::conditional_assign(X3, Y3, Z3, b_is_identity, a.x(), a.y(), a.z()); + + return ProjectivePoint(X3, Y3, Z3); +} + +template +inline constexpr ProjectivePoint point_add_or_sub_mixed(const ProjectivePoint& a, + const AffinePoint& b, + CT::Choice sub, + const FieldElement& one) { + const auto a_is_identity = a.is_identity(); + const auto b_is_identity = b.is_identity(); + + /* + https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#addition-add-1998-cmo-2 + + Cost: 8M + 3S + 6add + 1*2 + */ + + auto by = b.y(); + by.conditional_assign(sub, by.negate()); + + const auto Z1Z1 = a.z().square(); + const auto U2 = b.x() * Z1Z1; + const auto S2 = by * a.z() * Z1Z1; + const auto H = U2 - a.x(); + const auto r = S2 - a.y(); + + /* Risky conditional + * + * This implementation uses projective coordinates, which do not have an efficient complete + * addition formula. We rely on the design of the multiplication algorithms to avoid doublings. + * + * This conditional only comes into play for the actual doubling case, not x + (-x) which + * is another exceptional case in some circumstances. Here if a == -b then H == 0 && r != 0, + * in which case at the end we'll set z to a.z * H = 0, resulting in the correct output + * (the identity element) + */ + if((r.is_zero() && H.is_zero() && !(a_is_identity && b_is_identity)).as_bool()) { + return a.dbl(); + } + + const auto HH = H.square(); + const auto HHH = H * HH; + const auto V = a.x() * HH; + const auto t2 = r.square(); + const auto t3 = V + V; + const auto t4 = t2 - HHH; + auto X3 = t4 - t3; + const auto t5 = V - X3; + const auto t6 = a.y() * HHH; + const auto t7 = r * t5; + auto Y3 = t7 - t6; + auto Z3 = a.z() * H; + + // if a is identity then return b + FieldElement::conditional_assign(X3, Y3, Z3, a_is_identity, b.x(), by, one); + + // if b is identity then return a + FieldElement::conditional_assign(X3, Y3, Z3, b_is_identity, a.x(), a.y(), a.z()); + + return ProjectivePoint(X3, Y3, Z3); +} + +/* +Point doubling + +Using https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#doubling-dbl-1998-cmo-2 + +Cost (generic A): 4M + 6S + 4A + 2*2 + 1*3 + 1*4 + 1*8 +Cost (A == -3): 4M + 4S + 5A + 2*2 + 1*3 + 1*4 + 1*8 +Cost (A == 0): 3M + 4S + 3A + 2*2 + 1*3 + 1*4 + 1*8 +*/ + +template +inline constexpr ProjectivePoint dbl_a_minus_3(const ProjectivePoint& pt) { + /* + if a == -3 then + 3*x^2 + a*z^4 == 3*x^2 - 3*z^4 == 3*(x^2-z^4) == 3*(x-z^2)*(x+z^2) + */ + const auto z2 = pt.z().square(); + const auto m = (pt.x() - z2).mul3() * (pt.x() + z2); + + // Remaining cost: 3M + 3S + 3A + 2*2 + 1*4 + 1*8 + const auto y2 = pt.y().square(); + const auto s = pt.x().mul4() * y2; + const auto nx = m.square() - s.mul2(); + const auto ny = m * (s - nx) - y2.square().mul8(); + const auto nz = pt.y().mul2() * pt.z(); + + return ProjectivePoint(nx, ny, nz); +} + +template +inline constexpr ProjectivePoint dbl_a_zero(const ProjectivePoint& pt) { + // If a == 0 then 3*x^2 + a*z^4 == 3*x^2 + // Cost: 1S + 1*3 + const auto m = pt.x().square().mul3(); + + // Remaining cost: 3M + 3S + 3A + 2*2 + 1*4 + 1*8 + const auto y2 = pt.y().square(); + const auto s = pt.x().mul4() * y2; + const auto nx = m.square() - s.mul2(); + const auto ny = m * (s - nx) - y2.square().mul8(); + const auto nz = pt.y().mul2() * pt.z(); + + return ProjectivePoint(nx, ny, nz); +} + +template +inline constexpr ProjectivePoint dbl_generic(const ProjectivePoint& pt, const FieldElement& A) { + // Cost: 1M + 3S + 1A + 1*3 + const auto z2 = pt.z().square(); + const auto m = pt.x().square().mul3() + A * z2.square(); + + // Remaining cost: 3M + 3S + 3A + 2*2 + 1*4 + 1*8 + const auto y2 = pt.y().square(); + const auto s = pt.x().mul4() * y2; + const auto nx = m.square() - s.mul2(); + const auto ny = m * (s - nx) - y2.square().mul8(); + const auto nz = pt.y().mul2() * pt.z(); + + return ProjectivePoint(nx, ny, nz); +} + +/* +Repeated doubling using an adaptation of Algorithm 3.23 in +"Guide To Elliptic Curve Cryptography" (Hankerson, Menezes, Vanstone) + +Curiously the book gives the algorithm only for A == -3, but +the largest gains come from applying it to the generic A case, +where it saves 2 squarings per iteration. + +For A == 0 +Pay 1*2 + 1half to save n*(1*4 + 1*8) + +For A == -3: +Pay 2S + 1*2 + 1half to save n*(1A + 1*4 + 1*8) + 1M + +For generic A: +Pay 2S + 1*2 + 1half to save n*(2S + 1*4 + 1*8) + +The value of n is assumed to be public and should be a constant +*/ +template +inline constexpr ProjectivePoint dbl_n_a_minus_3(const ProjectivePoint& pt, size_t n) { + auto nx = pt.x(); + auto ny = pt.y().mul2(); + auto nz = pt.z(); + auto w = nz.square().square(); + + // Conditional ok: loop iteration count is public + while(n > 0) { + const auto ny2 = ny.square(); + const auto ny4 = ny2.square(); + const auto t1 = (nx.square() - w).mul3(); + const auto t2 = nx * ny2; + nx = t1.square() - t2.mul2(); + nz *= ny; + ny = t1 * (t2 - nx).mul2() - ny4; + n--; + // Conditional ok: loop iteration count is public + if(n > 0) { + w *= ny4; + } + } + return ProjectivePoint(nx, ny.div2(), nz); +} + +template +inline constexpr ProjectivePoint dbl_n_a_zero(const ProjectivePoint& pt, size_t n) { + auto nx = pt.x(); + auto ny = pt.y().mul2(); + auto nz = pt.z(); + + // Conditional ok: loop iteration count is public + while(n > 0) { + const auto ny2 = ny.square(); + const auto ny4 = ny2.square(); + const auto t1 = nx.square().mul3(); + const auto t2 = nx * ny2; + nx = t1.square() - t2.mul2(); + nz *= ny; + ny = t1 * (t2 - nx).mul2() - ny4; + n--; + } + return ProjectivePoint(nx, ny.div2(), nz); +} + +template +inline constexpr ProjectivePoint dbl_n_generic(const ProjectivePoint& pt, const FieldElement& A, size_t n) { + auto nx = pt.x(); + auto ny = pt.y().mul2(); + auto nz = pt.z(); + auto w = nz.square().square() * A; + + // Conditional ok: loop iteration count is public + while(n > 0) { + const auto ny2 = ny.square(); + const auto ny4 = ny2.square(); + const auto t1 = nx.square().mul3() + w; + const auto t2 = nx * ny2; + nx = t1.square() - t2.mul2(); + nz *= ny; + ny = t1 * (t2 - nx).mul2() - ny4; + n--; + // Conditional ok: loop iteration count is public + if(n > 0) { + w *= ny4; + } + } + return ProjectivePoint(nx, ny.div2(), nz); +} + +} // namespace Botan + +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_brainpool256r1/info.txt botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_brainpool256r1/info.txt --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_brainpool256r1/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_brainpool256r1/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + PCURVES_BRAINPOOL256R1 -> 20240608 - + name -> "PCurve brainpool256r1" diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_brainpool256r1/pcurves_brainpool256r1.cpp botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_brainpool256r1/pcurves_brainpool256r1.cpp --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_brainpool256r1/pcurves_brainpool256r1.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_brainpool256r1/pcurves_brainpool256r1.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -12,9 +12,10 @@ namespace { -// clang-format off namespace brainpool256r1 { +// clang-format off + class Params final : public EllipticCurveParameters< "A9FB57DBA1EEA9BC3E660A909D838D726E3BF623D52620282013481D1F6E5377", "7D5A0975FC2C3057EEF67530417AFFE7FB8055C126DC5C6CE94A4B44F330B5D9", @@ -24,11 +25,11 @@ "547EF835C3DAC4FD97F8461A14611DC9C27745132DED8E545C1D54C72F046997"> { }; -class Curve final : public EllipticCurve {}; +// clang-format on -} +class Curve final : public EllipticCurve {}; -// clang-format on +} // namespace brainpool256r1 } // namespace diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_brainpool384r1/info.txt botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_brainpool384r1/info.txt --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_brainpool384r1/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_brainpool384r1/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + PCURVES_BRAINPOOL384R1 -> 20240608 - + name -> "PCurve brainpool384r1" diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_brainpool384r1/pcurves_brainpool384r1.cpp botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_brainpool384r1/pcurves_brainpool384r1.cpp --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_brainpool384r1/pcurves_brainpool384r1.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_brainpool384r1/pcurves_brainpool384r1.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -12,8 +12,10 @@ namespace { -// clang-format off namespace brainpool384r1 { + +// clang-format off + class Params final : public EllipticCurveParameters< "8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B412B1DA197FB71123ACD3A729901D1A71874700133107EC53", "7BC382C63D8C150C3C72080ACE05AFA0C2BEA28E4FB22787139165EFBA91F90F8AA5814A503AD4EB04A8C7DD22CE2826", @@ -23,11 +25,11 @@ "8ABE1D7520F9C2A45CB1EB8E95CFD55262B70B29FEEC5864E19C054FF99129280E4646217791811142820341263C5315"> { }; -class Curve final : public EllipticCurve {}; +// clang-format on -} +class Curve final : public EllipticCurve {}; -// clang-format on +} // namespace brainpool384r1 } // namespace diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_brainpool512r1/info.txt botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_brainpool512r1/info.txt --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_brainpool512r1/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_brainpool512r1/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + PCURVES_BRAINPOOL512R1 -> 20240608 - + name -> "PCurve brainpool512r1" diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_brainpool512r1/pcurves_brainpool512r1.cpp botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_brainpool512r1/pcurves_brainpool512r1.cpp --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_brainpool512r1/pcurves_brainpool512r1.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_brainpool512r1/pcurves_brainpool512r1.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -12,9 +12,10 @@ namespace { -// clang-format off namespace brainpool512r1 { +// clang-format off + class Params final : public EllipticCurveParameters< "AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA703308717D4D9B009BC66842AECDA12AE6A380E62881FF2F2D82C68528AA6056583A48F3", "7830A3318B603B89E2327145AC234CC594CBDD8D3DF91610A83441CAEA9863BC2DED5D5AA8253AA10A2EF1C98B9AC8B57F1117A72BF2C7B9E7C1AC4D77FC94CA", @@ -24,11 +25,11 @@ "7DDE385D566332ECC0EABFA9CF7822FDF209F70024A57B1AA000C55B881F8111B2DCDE494A5F485E5BCA4BD88A2763AED1CA2B2FA8F0540678CD1E0F3AD80892"> { }; -class Curve final : public EllipticCurve {}; +// clang-format on -} +class Curve final : public EllipticCurve {}; -// clang-format on +} // namespace brainpool512r1 } // namespace diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_frp256v1/info.txt botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_frp256v1/info.txt --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_frp256v1/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_frp256v1/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + PCURVES_FRP256V1 -> 20240608 - + name -> "PCurve frp256v1" diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_frp256v1/pcurves_frp256v1.cpp botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_frp256v1/pcurves_frp256v1.cpp --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_frp256v1/pcurves_frp256v1.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_frp256v1/pcurves_frp256v1.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -12,9 +12,10 @@ namespace { -// clang-format off namespace frp256v1 { +// clang-format off + class Params final : public EllipticCurveParameters< "F1FD178C0B3AD58F10126DE8CE42435B3961ADBCABC8CA6DE8FCF353D86E9C03", "F1FD178C0B3AD58F10126DE8CE42435B3961ADBCABC8CA6DE8FCF353D86E9C00", @@ -24,11 +25,11 @@ "6142E0F7C8B204911F9271F0F3ECEF8C2701C307E8E4C9E183115A1554062CFB"> { }; -class Curve final : public EllipticCurve {}; +// clang-format on -} +class Curve final : public EllipticCurve {}; -// clang-format on +} // namespace frp256v1 } // namespace diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_generic/info.txt botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_generic/info.txt --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_generic/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_generic/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,13 @@ + +PCURVES_GENERIC -> 20250112 + + + +name -> "PCurve generic" + + + +bigint +numbertheory +mp + diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_generic/pcurves_generic.cpp botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_generic/pcurves_generic.cpp --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_generic/pcurves_generic.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_generic/pcurves_generic.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,1751 @@ +/* +* (C) 2025 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Botan::PCurve { + +namespace { + +template +constexpr std::optional> bytes_to_words(std::span bytes) { + if(bytes.size() > WordInfo::bytes * N) { + return std::nullopt; + } + + std::array r{}; + + const size_t full_words = bytes.size() / WordInfo::bytes; + const size_t extra_bytes = bytes.size() % WordInfo::bytes; + + for(size_t i = 0; i != full_words; ++i) { + r[i] = load_be(bytes.data(), full_words - 1 - i); + } + + if(extra_bytes > 0) { + const size_t shift = extra_bytes * 8; + bigint_shl1(r.data(), r.size(), r.size(), shift); + + for(size_t i = 0; i != extra_bytes; ++i) { + const word b0 = bytes[WordInfo::bytes * full_words + i]; + r[0] |= (b0 << (8 * (extra_bytes - 1 - i))); + } + } + + return r; +} + +template +T impl_pow_vartime(const T& elem, const T& one, size_t bits, std::span exp) { + constexpr size_t WindowBits = 4; + constexpr size_t WindowElements = (1 << WindowBits) - 1; + + const size_t Windows = (bits + WindowBits - 1) / WindowBits; + + std::vector tbl; + tbl.reserve(WindowElements); + + tbl.push_back(elem); + + for(size_t i = 1; i != WindowElements; ++i) { + if(i % 2 == 1) { + tbl.push_back(tbl[i / 2].square()); + } else { + tbl.push_back(tbl[i - 1] * tbl[0]); + } + } + + auto r = one; + + const size_t w0 = read_window_bits(exp, (Windows - 1) * WindowBits); + + if(w0 > 0) { + r = tbl[w0 - 1]; + } + + for(size_t i = 1; i != Windows; ++i) { + for(size_t j = 0; j != WindowBits; ++j) { + r = r.square(); + } + const size_t w = read_window_bits(exp, (Windows - i - 1) * WindowBits); + + if(w > 0) { + r *= tbl[w - 1]; + } + } + + return r; +} + +} // namespace + +class GenericCurveParams final { + public: + typedef PrimeOrderCurve::StorageUnit StorageUnit; + static constexpr size_t N = PrimeOrderCurve::StorageWords; + + GenericCurveParams(const BigInt& p, + const BigInt& a, + const BigInt& b, + const BigInt& base_x, + const BigInt& base_y, + const BigInt& order) : + m_words(p.sig_words()), + m_order_bits(order.bits()), + m_order_bytes(order.bytes()), + m_field_bits(p.bits()), + m_field_bytes(p.bytes()), + m_monty_order(order), + m_monty_field(p), + m_field(bn_to_fixed(p)), + m_field_minus_2(bn_to_fixed_rev(p - 2)), + m_field_monty_r1(bn_to_fixed(m_monty_field.R1())), + m_field_monty_r2(bn_to_fixed(m_monty_field.R2())), + m_field_p_plus_1_over_4(bn_to_fixed_rev((p + 1) / 4)), + m_field_inv_2(bn_to_fixed((p / 2) + 1)), + m_field_p_dash(m_monty_field.p_dash()), + + m_order(bn_to_fixed(order)), + m_order_minus_2(bn_to_fixed_rev(order - 2)), + m_order_monty_r1(bn_to_fixed(m_monty_order.R1())), + m_order_monty_r2(bn_to_fixed(m_monty_order.R2())), + m_order_monty_r3(bn_to_fixed(m_monty_order.R3())), + m_order_inv_2(bn_to_fixed((order / 2) + 1)), + m_order_p_dash(m_monty_order.p_dash()), + + m_a_is_minus_3(a + 3 == p), + m_a_is_zero(a.is_zero()), + m_order_is_lt_field(order < p) { + secure_vector ws; + m_monty_curve_a = bn_to_fixed(m_monty_field.mul(a, m_monty_field.R2(), ws)); + m_monty_curve_b = bn_to_fixed(m_monty_field.mul(b, m_monty_field.R2(), ws)); + + m_base_x = bn_to_fixed(m_monty_field.mul(base_x, m_monty_field.R2(), ws)); + m_base_y = bn_to_fixed(m_monty_field.mul(base_y, m_monty_field.R2(), ws)); + } + + size_t words() const { return m_words; } + + size_t order_bits() const { return m_order_bits; } + + size_t order_bytes() const { return m_order_bytes; } + + size_t field_bits() const { return m_field_bits; } + + size_t field_bytes() const { return m_field_bytes; } + + const Montgomery_Params& monty_order() const { return m_monty_order; } + + const Montgomery_Params& monty_field() const { return m_monty_field; } + + const StorageUnit& field() const { return m_field; } + + const StorageUnit& field_minus_2() const { return m_field_minus_2; } + + const StorageUnit& field_monty_r1() const { return m_field_monty_r1; } + + const StorageUnit& field_monty_r2() const { return m_field_monty_r2; } + + const StorageUnit& field_p_plus_1_over_4() const { return m_field_p_plus_1_over_4; } + + const StorageUnit& field_inv_2() const { return m_field_inv_2; } + + word field_p_dash() const { return m_field_p_dash; } + + const StorageUnit& order() const { return m_order; } + + const StorageUnit& order_minus_2() const { return m_order_minus_2; } + + const StorageUnit& order_monty_r1() const { return m_order_monty_r1; } + + const StorageUnit& order_monty_r2() const { return m_order_monty_r2; } + + const StorageUnit& order_monty_r3() const { return m_order_monty_r3; } + + const StorageUnit& order_inv_2() const { return m_order_inv_2; } + + word order_p_dash() const { return m_order_p_dash; } + + const StorageUnit& monty_curve_a() const { return m_monty_curve_a; } + + const StorageUnit& monty_curve_b() const { return m_monty_curve_b; } + + const StorageUnit& base_x() const { return m_base_x; } + + const StorageUnit& base_y() const { return m_base_y; } + + bool a_is_minus_3() const { return m_a_is_minus_3; } + + bool a_is_zero() const { return m_a_is_zero; } + + bool order_is_less_than_field() const { return m_order_is_lt_field; } + + void mul(std::array& z, const std::array& x, const std::array& y) const { + clear_mem(z); + + if(m_words == 4) { + bigint_comba_mul4(z.data(), x.data(), y.data()); + } else if(m_words == 6) { + bigint_comba_mul6(z.data(), x.data(), y.data()); + } else if(m_words == 8) { + bigint_comba_mul8(z.data(), x.data(), y.data()); + } else if(m_words == 9) { + bigint_comba_mul9(z.data(), x.data(), y.data()); + } else { + bigint_mul(z.data(), z.size(), x.data(), m_words, m_words, y.data(), m_words, m_words, nullptr, 0); + } + } + + void sqr(std::array& z, const std::array& x) const { + clear_mem(z); + + if(m_words == 4) { + bigint_comba_sqr4(z.data(), x.data()); + } else if(m_words == 6) { + bigint_comba_sqr6(z.data(), x.data()); + } else if(m_words == 8) { + bigint_comba_sqr8(z.data(), x.data()); + } else if(m_words == 9) { + bigint_comba_sqr9(z.data(), x.data()); + } else { + bigint_sqr(z.data(), z.size(), x.data(), m_words, m_words, nullptr, 0); + } + } + + private: + static std::array bn_to_fixed(const BigInt& n) { + const size_t n_words = n.sig_words(); + BOTAN_ASSERT_NOMSG(n_words <= PrimeOrderCurve::StorageWords); + + std::array r{}; + copy_mem(std::span{r}.first(n_words), n._as_span().first(n_words)); + return r; + } + + static std::array bn_to_fixed_rev(const BigInt& n) { + auto v = bn_to_fixed(n); + std::reverse(v.begin(), v.end()); + return v; + } + + private: + size_t m_words; + size_t m_order_bits; + size_t m_order_bytes; + size_t m_field_bits; + size_t m_field_bytes; + + Montgomery_Params m_monty_order; + Montgomery_Params m_monty_field; + + StorageUnit m_field; + StorageUnit m_field_minus_2; + StorageUnit m_field_monty_r1; + StorageUnit m_field_monty_r2; + StorageUnit m_field_p_plus_1_over_4; + StorageUnit m_field_inv_2; + word m_field_p_dash; + + StorageUnit m_order; + StorageUnit m_order_minus_2; + StorageUnit m_order_monty_r1; + StorageUnit m_order_monty_r2; + StorageUnit m_order_monty_r3; + StorageUnit m_order_inv_2; + word m_order_p_dash; + + StorageUnit m_monty_curve_a{}; + StorageUnit m_monty_curve_b{}; + + StorageUnit m_base_x{}; + StorageUnit m_base_y{}; + + bool m_a_is_minus_3; + bool m_a_is_zero; + bool m_order_is_lt_field; +}; + +class GenericScalar final { + public: + typedef word W; + typedef PrimeOrderCurve::StorageUnit StorageUnit; + static constexpr size_t N = PrimeOrderCurve::StorageWords; + + static std::optional from_wide_bytes(const GenericPrimeOrderCurve* curve, + std::span bytes) { + const size_t mlen = curve->_params().order_bytes(); + + if(bytes.size() > 2 * mlen) { + return {}; + } + + std::array padded_bytes{}; + copy_mem(std::span{padded_bytes}.last(bytes.size()), bytes); + + auto words = bytes_to_words<2 * N>(std::span{padded_bytes}); + if(words) { + auto in_rep = wide_to_rep(curve, words.value()); + return GenericScalar(curve, in_rep); + } else { + return {}; + } + } + + static std::optional deserialize(const GenericPrimeOrderCurve* curve, + std::span bytes) { + const size_t len = curve->_params().order_bytes(); + + if(bytes.size() != len) { + return {}; + } + + const auto words = bytes_to_words(bytes); + + if(words) { + if(!bigint_ct_is_lt(words->data(), N, curve->_params().order().data(), N).as_bool()) { + return {}; + } + + // Safe because we checked above that words is an integer < P + return GenericScalar(curve, to_rep(curve, *words)); + } else { + return {}; + } + } + + static GenericScalar zero(const GenericPrimeOrderCurve* curve) { + const StorageUnit zeros{}; + return GenericScalar(curve, zeros); + } + + static GenericScalar one(const GenericPrimeOrderCurve* curve) { + return GenericScalar(curve, curve->_params().order_monty_r1()); + } + + static GenericScalar random(const GenericPrimeOrderCurve* curve, RandomNumberGenerator& rng) { + constexpr size_t MAX_ATTEMPTS = 1000; + + const size_t bits = curve->_params().order_bits(); + + std::vector buf(curve->_params().order_bytes()); + + for(size_t i = 0; i != MAX_ATTEMPTS; ++i) { + rng.randomize(buf); + + // Zero off high bits that if set would certainly cause us + // to be out of range + if(bits % 8 != 0) { + const uint8_t mask = 0xFF >> (8 - (bits % 8)); + buf[0] &= mask; + } + + if(auto s = GenericScalar::deserialize(curve, buf)) { + if(s.value().is_nonzero().as_bool()) { + return s.value(); + } + } + } + + throw Internal_Error("Failed to generate random Scalar within bounded number of attempts"); + } + + friend GenericScalar operator+(const GenericScalar& a, const GenericScalar& b) { + const auto* curve = check_curve(a, b); + const size_t words = curve->_params().words(); + + StorageUnit t{}; + const W carry = bigint_add3(t.data(), a.data(), words, b.data(), words); + + StorageUnit r{}; + bigint_monty_maybe_sub(words, r.data(), carry, t.data(), curve->_params().order().data()); + return GenericScalar(curve, r); + } + + friend GenericScalar operator-(const GenericScalar& a, const GenericScalar& b) { return a + b.negate(); } + + friend GenericScalar operator*(const GenericScalar& a, const GenericScalar& b) { + const auto* curve = check_curve(a, b); + + std::array z; // NOLINT(*-member-init) + curve->_params().mul(z, a.value(), b.value()); + return GenericScalar(curve, redc(curve, z)); + } + + GenericScalar& operator*=(const GenericScalar& other) { + const auto* curve = check_curve(*this, other); + + std::array z; // NOLINT(*-member-init) + curve->_params().mul(z, value(), other.value()); + m_val = redc(curve, z); + return (*this); + } + + GenericScalar square() const { + const auto* curve = this->m_curve; + + std::array z; // NOLINT(*-member-init) + curve->_params().sqr(z, value()); + return GenericScalar(curve, redc(curve, z)); + } + + GenericScalar pow_vartime(const StorageUnit& exp) const { + auto one = GenericScalar::one(curve()); + auto bits = curve()->_params().order_bits(); + auto words = curve()->_params().words(); + return impl_pow_vartime(*this, one, bits, std::span{exp}.last(words)); + } + + GenericScalar negate() const { + auto x_is_zero = CT::all_zeros(this->data(), N); + + StorageUnit r; + bigint_sub3(r.data(), m_curve->_params().order().data(), N, this->data(), N); + x_is_zero.if_set_zero_out(r.data(), N); + return GenericScalar(m_curve, r); + } + + GenericScalar invert() const { return pow_vartime(m_curve->_params().order_minus_2()); } + + /** + * Helper for variable time BEEA + * + * Note this function assumes that its arguments are in the standard + * domain, not the Montgomery domain. invert_vartime converts its argument + * out of Montgomery, and then back to Montgomery when returning the result. + */ + static void _invert_vartime_div2_helper(GenericScalar& a, GenericScalar& x) { + const auto& inv_2 = a.curve()->_params().order_inv_2(); + + // Conditional ok: this function is variable time + while((a.m_val[0] & 1) != 1) { + shift_right<1>(a.m_val); + + const W borrow = shift_right<1>(x.m_val); + + // Conditional ok: this function is variable time + if(borrow > 0) { + bigint_add2(x.m_val.data(), N, inv_2.data(), N); + } + } + } + + /* + * See the comments on invert_vartime in pcurves_impl.h for background + */ + GenericScalar invert_vartime() const { + if(this->is_zero().as_bool()) { + return (*this); + } + + auto x = GenericScalar(m_curve, std::array{1}); + auto b = GenericScalar(m_curve, from_rep(m_curve, m_val)); + + // First loop iteration + GenericScalar::_invert_vartime_div2_helper(b, x); + + auto a = b.negate(); + // y += x but y is zero at the outset + auto y = x; + + // First half of second loop iteration + GenericScalar::_invert_vartime_div2_helper(a, y); + + for(;;) { + // Conditional ok: this function is variable time + if(a.m_val == b.m_val) { + // At this point it should be that a == b == 1 + auto r = y.negate(); + + // Convert back to Montgomery + return GenericScalar(curve(), to_rep(curve(), r.m_val)); + } + + auto nx = x + y; + + /* + * Otherwise either b > a or a > b + * + * If b > a we want to set b to b - a + * Otherwise we want to set a to a - b + * + * Compute r = b - a and check if it underflowed + * If it did not then we are in the b > a path + */ + std::array r{}; + const word carry = bigint_sub3(r.data(), b.data(), N, a.data(), N); + + // Conditional ok: this function is variable time + if(carry == 0) { + // b > a + b.m_val = r; + x = nx; + GenericScalar::_invert_vartime_div2_helper(b, x); + } else { + // We know this can't underflow because a > b + bigint_sub3(r.data(), a.data(), N, b.data(), N); + a.m_val = r; + y = nx; + GenericScalar::_invert_vartime_div2_helper(a, y); + } + } + } + + template + T serialize() const { + T bytes(m_curve->_params().order_bytes()); + this->serialize_to(bytes); + return bytes; + } + + void serialize_to(std::span bytes) const { + auto v = from_rep(m_curve, m_val); + std::reverse(v.begin(), v.end()); + + const size_t flen = m_curve->_params().order_bytes(); + BOTAN_ARG_CHECK(bytes.size() == flen, "Expected output span provided"); + + // Remove leading zero bytes + const auto padded_bytes = store_be(v); + const size_t extra = N * WordInfo::bytes - flen; + copy_mem(bytes, std::span{padded_bytes}.subspan(extra, flen)); + } + + CT::Choice is_zero() const { return CT::all_zeros(m_val.data(), m_curve->_params().words()).as_choice(); } + + CT::Choice is_nonzero() const { return !is_zero(); } + + CT::Choice operator==(const GenericScalar& other) const { + if(this->m_curve != other.m_curve) { + return CT::Choice::no(); + } + + return CT::is_equal(m_val.data(), other.m_val.data(), m_curve->_params().words()).as_choice(); + } + + /** + * Convert the integer to standard representation and return the sequence of words + */ + StorageUnit to_words() const { return from_rep(m_curve, m_val); } + + const StorageUnit& stash_value() const { return m_val; } + + const GenericPrimeOrderCurve* curve() const { return m_curve; } + + GenericScalar(const GenericPrimeOrderCurve* curve, StorageUnit val) : m_curve(curve), m_val(val) {} + + private: + const StorageUnit& value() const { return m_val; } + + const W* data() const { return m_val.data(); } + + static const GenericPrimeOrderCurve* check_curve(const GenericScalar& a, const GenericScalar& b) { + BOTAN_STATE_CHECK(a.m_curve == b.m_curve); + return a.m_curve; + } + + static StorageUnit redc(const GenericPrimeOrderCurve* curve, std::array z) { + const auto& mod = curve->_params().order(); + const size_t words = curve->_params().words(); + StorageUnit r{}; + StorageUnit ws{}; + bigint_monty_redc( + r.data(), z.data(), mod.data(), words, curve->_params().order_p_dash(), ws.data(), ws.size()); + return r; + } + + static StorageUnit from_rep(const GenericPrimeOrderCurve* curve, StorageUnit z) { + std::array ze{}; + copy_mem(std::span{ze}.template first(), z); + return redc(curve, ze); + } + + static StorageUnit to_rep(const GenericPrimeOrderCurve* curve, StorageUnit x) { + std::array z; // NOLINT(*-member-init) + curve->_params().mul(z, x, curve->_params().order_monty_r2()); + return redc(curve, z); + } + + static StorageUnit wide_to_rep(const GenericPrimeOrderCurve* curve, std::array x) { + auto redc_x = redc(curve, x); + std::array z; // NOLINT(*-member-init) + curve->_params().mul(z, redc_x, curve->_params().order_monty_r3()); + return redc(curve, z); + } + + const GenericPrimeOrderCurve* m_curve; + StorageUnit m_val; +}; + +namespace { + +class GenericField final { + public: + typedef word W; + typedef PrimeOrderCurve::StorageUnit StorageUnit; + static constexpr size_t N = PrimeOrderCurve::StorageWords; + + static std::optional deserialize(const GenericPrimeOrderCurve* curve, + std::span bytes) { + const size_t len = curve->_params().field_bytes(); + + if(bytes.size() != len) { + return {}; + } + + const auto words = bytes_to_words(bytes); + + if(words) { + if(!bigint_ct_is_lt(words->data(), N, curve->_params().field().data(), N).as_bool()) { + return {}; + } + + // Safe because we checked above that words is an integer < P + return GenericField::from_words(curve, *words); + } else { + return {}; + } + } + + static GenericField from_words(const GenericPrimeOrderCurve* curve, const std::array& words) { + return GenericField(curve, to_rep(curve, words)); + } + + static GenericField zero(const GenericPrimeOrderCurve* curve) { + const StorageUnit zeros{}; + return GenericField(curve, zeros); + } + + static GenericField one(const GenericPrimeOrderCurve* curve) { + return GenericField(curve, curve->_params().field_monty_r1()); + } + + static GenericField curve_a(const GenericPrimeOrderCurve* curve) { + return GenericField(curve, curve->_params().monty_curve_a()); + } + + static GenericField curve_b(const GenericPrimeOrderCurve* curve) { + return GenericField(curve, curve->_params().monty_curve_b()); + } + + static GenericField random(const GenericPrimeOrderCurve* curve, RandomNumberGenerator& rng) { + constexpr size_t MAX_ATTEMPTS = 1000; + + const size_t bits = curve->_params().field_bits(); + + std::vector buf(curve->_params().field_bytes()); + + for(size_t i = 0; i != MAX_ATTEMPTS; ++i) { + rng.randomize(buf); + + // Zero off high bits that if set would certainly cause us + // to be out of range + if(bits % 8 != 0) { + const uint8_t mask = 0xFF >> (8 - (bits % 8)); + buf[0] &= mask; + } + + if(auto s = GenericField::deserialize(curve, buf)) { + if(s.value().is_nonzero().as_bool()) { + return s.value(); + } + } + } + + throw Internal_Error("Failed to generate random Scalar within bounded number of attempts"); + } + + /** + * Return the value of this divided by 2 + */ + GenericField div2() const { + StorageUnit t = value(); + const W borrow = shift_right<1>(t); + + // If value was odd, add (P/2)+1 + bigint_cnd_add(borrow, t.data(), m_curve->_params().field_inv_2().data(), N); + + return GenericField(m_curve, t); + } + + /// Return (*this) multiplied by 2 + GenericField mul2() const { + StorageUnit t = value(); + const W carry = shift_left<1>(t); + + StorageUnit r; + bigint_monty_maybe_sub(r.data(), carry, t.data(), m_curve->_params().field().data()); + return GenericField(m_curve, r); + } + + /// Return (*this) multiplied by 3 + GenericField mul3() const { return mul2() + (*this); } + + /// Return (*this) multiplied by 4 + GenericField mul4() const { return mul2().mul2(); } + + /// Return (*this) multiplied by 8 + GenericField mul8() const { return mul2().mul2().mul2(); } + + friend GenericField operator+(const GenericField& a, const GenericField& b) { + const auto* curve = check_curve(a, b); + const size_t words = curve->_params().words(); + + StorageUnit t{}; + const W carry = bigint_add3(t.data(), a.data(), words, b.data(), words); + + StorageUnit r{}; + bigint_monty_maybe_sub(words, r.data(), carry, t.data(), curve->_params().field().data()); + return GenericField(curve, r); + } + + friend GenericField operator-(const GenericField& a, const GenericField& b) { return a + b.negate(); } + + friend GenericField operator*(const GenericField& a, const GenericField& b) { + const auto* curve = check_curve(a, b); + + std::array z; // NOLINT(*-member-init) + curve->_params().mul(z, a.value(), b.value()); + return GenericField(curve, redc(curve, z)); + } + + GenericField& operator*=(const GenericField& other) { + const auto* curve = check_curve(*this, other); + + std::array z; // NOLINT(*-member-init) + curve->_params().mul(z, value(), other.value()); + m_val = redc(curve, z); + return (*this); + } + + GenericField square() const { + std::array z; // NOLINT(*-member-init) + m_curve->_params().sqr(z, value()); + return GenericField(m_curve, redc(m_curve, z)); + } + + GenericField pow_vartime(const StorageUnit& exp) const { + auto one = GenericField::one(curve()); + auto bits = curve()->_params().field_bits(); + auto words = curve()->_params().words(); + return impl_pow_vartime(*this, one, bits, std::span{exp}.last(words)); + } + + GenericField negate() const { + auto x_is_zero = CT::all_zeros(this->data(), N); + + StorageUnit r; + bigint_sub3(r.data(), m_curve->_params().field().data(), N, this->data(), N); + x_is_zero.if_set_zero_out(r.data(), N); + return GenericField(m_curve, r); + } + + GenericField invert() const { return pow_vartime(m_curve->_params().field_minus_2()); } + + GenericField invert_vartime() const { + // TODO take advantage of variable time here using eg BEEA + // see IntMod::invert_vartime in pcurves_impl.h + return invert(); + } + + template + T serialize() const { + T bytes(m_curve->_params().field_bytes()); + serialize_to(bytes); + return bytes; + } + + void serialize_to(std::span bytes) const { + auto v = from_rep(m_curve, m_val); + std::reverse(v.begin(), v.end()); + + const size_t flen = m_curve->_params().field_bytes(); + BOTAN_ARG_CHECK(bytes.size() == flen, "Expected output span provided"); + + // Remove leading zero bytes + const auto padded_bytes = store_be(v); + const size_t extra = N * WordInfo::bytes - flen; + copy_mem(bytes, std::span{padded_bytes}.subspan(extra, flen)); + } + + CT::Choice is_zero() const { return CT::all_zeros(m_val.data(), m_curve->_params().words()).as_choice(); } + + CT::Choice is_nonzero() const { return !is_zero(); } + + CT::Choice operator==(const GenericField& other) const { + if(this->m_curve != other.m_curve) { + return CT::Choice::no(); + } + + return CT::is_equal(m_val.data(), other.m_val.data(), m_curve->_params().words()).as_choice(); + } + + const StorageUnit& stash_value() const { return m_val; } + + const GenericPrimeOrderCurve* curve() const { return m_curve; } + + CT::Choice is_even() const { + auto v = from_rep(m_curve, m_val); + return !CT::Choice::from_int(v[0] & 0x01); + } + + /** + * Convert the integer to standard representation and return the sequence of words + */ + StorageUnit to_words() const { return from_rep(m_curve, m_val); } + + void _const_time_poison() const { CT::poison(m_val); } + + void _const_time_unpoison() const { CT::unpoison(m_val); } + + static void conditional_swap(CT::Choice cond, GenericField& x, GenericField& y) { + const W mask = cond.into_bitmask(); + + for(size_t i = 0; i != N; ++i) { + auto nx = choose(mask, y.m_val[i], x.m_val[i]); + auto ny = choose(mask, x.m_val[i], y.m_val[i]); + x.m_val[i] = nx; + y.m_val[i] = ny; + } + } + + void conditional_assign(CT::Choice cond, const GenericField& nx) { + const W mask = cond.into_bitmask(); + + for(size_t i = 0; i != N; ++i) { + m_val[i] = choose(mask, nx.m_val[i], m_val[i]); + } + } + + /** + * Conditional assignment + * + * If `cond` is true, sets `x` to `nx` and `y` to `ny` + */ + static void conditional_assign( + GenericField& x, GenericField& y, CT::Choice cond, const GenericField& nx, const GenericField& ny) { + const W mask = cond.into_bitmask(); + + for(size_t i = 0; i != N; ++i) { + x.m_val[i] = choose(mask, nx.m_val[i], x.m_val[i]); + y.m_val[i] = choose(mask, ny.m_val[i], y.m_val[i]); + } + } + + /** + * Conditional assignment + * + * If `cond` is true, sets `x` to `nx`, `y` to `ny`, and `z` to `nz` + */ + static void conditional_assign(GenericField& x, + GenericField& y, + GenericField& z, + CT::Choice cond, + const GenericField& nx, + const GenericField& ny, + const GenericField& nz) { + const W mask = cond.into_bitmask(); + + for(size_t i = 0; i != N; ++i) { + x.m_val[i] = choose(mask, nx.m_val[i], x.m_val[i]); + y.m_val[i] = choose(mask, ny.m_val[i], y.m_val[i]); + z.m_val[i] = choose(mask, nz.m_val[i], z.m_val[i]); + } + } + + std::pair sqrt() const { + BOTAN_STATE_CHECK(m_curve->_params().field()[0] % 4 == 3); + + auto z = pow_vartime(m_curve->_params().field_p_plus_1_over_4()); + const CT::Choice correct = (z.square() == *this); + // Zero out the return value if it would otherwise be incorrect + z.conditional_assign(!correct, zero(m_curve)); + return {z, correct}; + } + + GenericField(const GenericPrimeOrderCurve* curve, StorageUnit val) : m_curve(curve), m_val(val) {} + + private: + const StorageUnit& value() const { return m_val; } + + const W* data() const { return m_val.data(); } + + static const GenericPrimeOrderCurve* check_curve(const GenericField& a, const GenericField& b) { + BOTAN_STATE_CHECK(a.m_curve == b.m_curve); + return a.m_curve; + } + + static StorageUnit redc(const GenericPrimeOrderCurve* curve, std::array z) { + const auto& mod = curve->_params().field(); + const size_t words = curve->_params().words(); + StorageUnit r{}; + StorageUnit ws{}; + bigint_monty_redc( + r.data(), z.data(), mod.data(), words, curve->_params().field_p_dash(), ws.data(), ws.size()); + return r; + } + + static StorageUnit from_rep(const GenericPrimeOrderCurve* curve, StorageUnit z) { + std::array ze{}; + copy_mem(std::span{ze}.template first(), z); + return redc(curve, ze); + } + + static StorageUnit to_rep(const GenericPrimeOrderCurve* curve, StorageUnit x) { + std::array z{}; + curve->_params().mul(z, x, curve->_params().field_monty_r2()); + return redc(curve, z); + } + + const GenericPrimeOrderCurve* m_curve; + StorageUnit m_val; +}; + +} // namespace + +/** +* Affine Curve Point +* +* This contains a pair of integers (x,y) which satisfy the curve equation +*/ +class GenericAffinePoint final { + public: + GenericAffinePoint(const GenericField& x, const GenericField& y) : m_x(x), m_y(y) {} + + explicit GenericAffinePoint(const GenericPrimeOrderCurve* curve) : + m_x(GenericField::zero(curve)), m_y(GenericField::zero(curve)) {} + + static GenericAffinePoint identity(const GenericPrimeOrderCurve* curve) { + return GenericAffinePoint(GenericField::zero(curve), GenericField::zero(curve)); + } + + static GenericAffinePoint identity(const GenericAffinePoint& pt) { return identity(pt.curve()); } + + CT::Choice is_identity() const { return x().is_zero() && y().is_zero(); } + + GenericAffinePoint negate() const { return GenericAffinePoint(x(), y().negate()); } + + /** + * Serialize the point in uncompressed format + */ + void serialize_to(std::span bytes) const { + const size_t fe_bytes = curve()->_params().field_bytes(); + BOTAN_ARG_CHECK(bytes.size() == 1 + 2 * fe_bytes, "Buffer size incorrect"); + BOTAN_STATE_CHECK(this->is_identity().as_bool() == false); + BufferStuffer pack(bytes); + pack.append(0x04); + x().serialize_to(pack.next(fe_bytes)); + y().serialize_to(pack.next(fe_bytes)); + BOTAN_DEBUG_ASSERT(pack.full()); + } + + /** + * If idx is zero then return the identity element. Otherwise return pts[idx - 1] + * + * Returns the identity element also if idx is out of range + */ + static auto ct_select(std::span pts, size_t idx) { + BOTAN_ARG_CHECK(!pts.empty(), "Cannot select from an empty set"); + auto result = GenericAffinePoint::identity(pts[0].curve()); + + // Intentionally wrapping; set to maximum size_t if idx == 0 + const size_t idx1 = static_cast(idx - 1); + for(size_t i = 0; i != pts.size(); ++i) { + const auto found = CT::Mask::is_equal(idx1, i).as_choice(); + result.conditional_assign(found, pts[i]); + } + + return result; + } + + /** + * Return (x^3 + A*x + B) mod p + */ + static GenericField x3_ax_b(const GenericField& x) { + return (x.square() + GenericField::curve_a(x.curve())) * x + GenericField::curve_b(x.curve()); + } + + /** + * Point deserialization + * + * This accepts compressed or uncompressed formats. + */ + static std::optional deserialize(const GenericPrimeOrderCurve* curve, + std::span bytes) { + const size_t fe_bytes = curve->_params().field_bytes(); + + if(bytes.size() == 1 + 2 * fe_bytes && bytes[0] == 0x04) { + auto x = GenericField::deserialize(curve, bytes.subspan(1, fe_bytes)); + auto y = GenericField::deserialize(curve, bytes.subspan(1 + fe_bytes, fe_bytes)); + + if(x && y) { + const auto lhs = (*y).square(); + const auto rhs = GenericAffinePoint::x3_ax_b(*x); + if((lhs == rhs).as_bool()) { + return GenericAffinePoint(*x, *y); + } + } + } else if(bytes.size() == 1 + fe_bytes && (bytes[0] == 0x02 || bytes[0] == 0x03)) { + const CT::Choice y_is_even = CT::Mask::is_equal(bytes[0], 0x02).as_choice(); + + if(auto x = GenericField::deserialize(curve, bytes.subspan(1, fe_bytes))) { + auto [y, is_square] = x3_ax_b(*x).sqrt(); + + if(is_square.as_bool()) { + const auto flip_y = y_is_even != y.is_even(); + y.conditional_assign(flip_y, y.negate()); + return GenericAffinePoint(*x, y); + } + } + } else if(bytes.size() == 1 && bytes[0] == 0x00) { + // See SEC1 section 2.3.4 + return GenericAffinePoint::identity(curve); + } + + return {}; + } + + /** + * Return the affine x coordinate + */ + const GenericField& x() const { return m_x; } + + /** + * Return the affine y coordinate + */ + const GenericField& y() const { return m_y; } + + /** + * Conditional assignment of an affine point + */ + void conditional_assign(CT::Choice cond, const GenericAffinePoint& pt) { + GenericField::conditional_assign(m_x, m_y, cond, pt.x(), pt.y()); + } + + const GenericPrimeOrderCurve* curve() const { return m_x.curve(); } + + void _const_time_poison() const { CT::poison_all(m_x, m_y); } + + void _const_time_unpoison() const { CT::unpoison_all(m_x, m_y); } + + private: + GenericField m_x; + GenericField m_y; +}; + +class GenericProjectivePoint final { + public: + typedef GenericProjectivePoint Self; + + using FieldElement = GenericField; + + /** + * Convert a point from affine to projective form + */ + static Self from_affine(const GenericAffinePoint& pt) { + auto x = pt.x(); + auto y = pt.y(); + auto z = GenericField::one(x.curve()); + + // If pt is identity (0,0) swap y/z to convert (0,0,1) into (0,1,0) + GenericField::conditional_swap(pt.is_identity(), y, z); + return GenericProjectivePoint(x, y, z); + } + + /** + * Return the identity element + */ + static Self identity(const GenericPrimeOrderCurve* curve) { + return Self(GenericField::zero(curve), GenericField::one(curve), GenericField::zero(curve)); + } + + /** + * Default constructor: the identity element + */ + explicit GenericProjectivePoint(const GenericPrimeOrderCurve* curve) : + m_x(GenericField::zero(curve)), m_y(GenericField::one(curve)), m_z(GenericField::zero(curve)) {} + + /** + * Affine constructor: take x/y coordinates + */ + GenericProjectivePoint(const GenericField& x, const GenericField& y) : + m_x(x), m_y(y), m_z(GenericField::one(m_x.curve())) {} + + /** + * Projective constructor: take x/y/z coordinates + */ + GenericProjectivePoint(const GenericField& x, const GenericField& y, const GenericField& z) : + m_x(x), m_y(y), m_z(z) {} + + friend Self operator+(const Self& a, const Self& b) { return Self::add(a, b); } + + friend Self operator+(const Self& a, const GenericAffinePoint& b) { return Self::add_mixed(a, b); } + + friend Self operator+(const GenericAffinePoint& a, const Self& b) { return Self::add_mixed(b, a); } + + Self& operator+=(const Self& other) { + (*this) = (*this) + other; + return (*this); + } + + Self& operator+=(const GenericAffinePoint& other) { + (*this) = (*this) + other; + return (*this); + } + + CT::Choice is_identity() const { return z().is_zero(); } + + void conditional_assign(CT::Choice cond, const Self& pt) { + GenericField::conditional_assign(m_x, m_y, m_z, cond, pt.x(), pt.y(), pt.z()); + } + + /** + * Mixed (projective + affine) point addition + */ + static Self add_mixed(const Self& a, const GenericAffinePoint& b) { + return point_add_mixed(a, b, GenericField::one(a.curve())); + } + + static Self add_or_sub(const Self& a, const GenericAffinePoint& b, CT::Choice sub) { + return point_add_or_sub_mixed(a, b, sub, GenericField::one(a.curve())); + } + + /** + * Projective point addition + */ + static Self add(const Self& a, const Self& b) { return point_add(a, b); } + + /** + * Iterated point doubling + */ + Self dbl_n(size_t n) const { + if(curve()->_params().a_is_minus_3()) { + return dbl_n_a_minus_3(*this, n); + } else if(curve()->_params().a_is_zero()) { + return dbl_n_a_zero(*this, n); + } else { + const auto A = GenericField::curve_a(curve()); + return dbl_n_generic(*this, A, n); + } + } + + /** + * Point doubling + */ + Self dbl() const { + if(curve()->_params().a_is_minus_3()) { + return dbl_a_minus_3(*this); + } else if(curve()->_params().a_is_zero()) { + return dbl_a_zero(*this); + } else { + const auto A = GenericField::curve_a(curve()); + return dbl_generic(*this, A); + } + } + + /** + * Point negation + */ + Self negate() const { return Self(x(), y().negate(), z()); } + + /** + * Randomize the point representation + * + * Projective coordinates are redundant; if (x,y,z) is a projective + * point then so is (x*r^2,y*r^3,z*r) for any non-zero r. + */ + void randomize_rep(RandomNumberGenerator& rng) { + // In certain contexts we may be called with a Null_RNG; in that case the + // caller is accepting that randomization will not occur + + if(rng.is_seeded()) { + auto r = GenericField::random(curve(), rng); + + auto r2 = r.square(); + auto r3 = r2 * r; + + m_x *= r2; + m_y *= r3; + m_z *= r; + } + } + + /** + * Return the projective x coordinate + */ + const GenericField& x() const { return m_x; } + + /** + * Return the projective y coordinate + */ + const GenericField& y() const { return m_y; } + + /** + * Return the projective z coordinate + */ + const GenericField& z() const { return m_z; } + + const GenericPrimeOrderCurve* curve() const { return m_x.curve(); } + + void _const_time_poison() const { CT::poison_all(m_x, m_y, m_z); } + + void _const_time_unpoison() const { CT::unpoison_all(m_x, m_y, m_z); } + + private: + GenericField m_x; + GenericField m_y; + GenericField m_z; +}; + +namespace { + +class GenericCurve final { + public: + typedef GenericField FieldElement; + typedef GenericScalar Scalar; + typedef GenericAffinePoint AffinePoint; + typedef GenericProjectivePoint ProjectivePoint; + + typedef word WordType; +}; + +class GenericBlindedScalarBits final { + public: + GenericBlindedScalarBits(const GenericScalar& scalar, RandomNumberGenerator& rng, size_t wb) { + BOTAN_ASSERT_NOMSG(wb == 1 || wb == 2 || wb == 3 || wb == 4 || wb == 5 || wb == 6 || wb == 7); + + const auto& params = scalar.curve()->_params(); + + const size_t order_bits = params.order_bits(); + m_window_bits = wb; + + const size_t blinder_bits = scalar_blinding_bits(order_bits); + + if(blinder_bits > 0 && rng.is_seeded()) { + const size_t mask_words = (blinder_bits + WordInfo::bits - 1) / WordInfo::bits; + const size_t mask_bytes = mask_words * WordInfo::bytes; + + const size_t words = params.words(); + + secure_vector maskb(mask_bytes); + rng.randomize(maskb); + + std::array mask{}; + load_le(mask.data(), maskb.data(), mask_words); + + // Mask to exactly blinder_bits and set MSB and LSB + const size_t excess = mask_words * WordInfo::bits - blinder_bits; + if(excess > 0) { + mask[mask_words - 1] &= (static_cast(1) << (WordInfo::bits - excess)) - 1; + } + const size_t msb_pos = (blinder_bits - 1) % WordInfo::bits; + mask[(blinder_bits - 1) / WordInfo::bits] |= static_cast(1) << msb_pos; + mask[0] |= 1; + + std::array mask_n{}; + + const auto sw = scalar.to_words(); + + // Compute masked scalar s + k*n + params.mul(mask_n, mask, params.order()); + bigint_add2(mask_n.data(), 2 * words, sw.data(), words); + + std::reverse(mask_n.begin(), mask_n.end()); + m_bytes = store_be>(mask_n); + m_bits = order_bits + blinder_bits; + } else { + // No RNG available, skip blinding + m_bytes = scalar.serialize>(); + m_bits = order_bits; + } + + m_windows = (m_bits + wb - 1) / wb; + } + + size_t windows() const { return m_windows; } + + size_t bits() const { return m_bits; } + + size_t get_window(size_t offset) const { + if(m_window_bits == 1) { + return read_window_bits<1>(std::span{m_bytes}, offset); + } else if(m_window_bits == 2) { + return read_window_bits<2>(std::span{m_bytes}, offset); + } else if(m_window_bits == 3) { + return read_window_bits<3>(std::span{m_bytes}, offset); + } else if(m_window_bits == 4) { + return read_window_bits<4>(std::span{m_bytes}, offset); + } else if(m_window_bits == 5) { + return read_window_bits<5>(std::span{m_bytes}, offset); + } else if(m_window_bits == 6) { + return read_window_bits<6>(std::span{m_bytes}, offset); + } else if(m_window_bits == 7) { + return read_window_bits<7>(std::span{m_bytes}, offset); + } else { + BOTAN_ASSERT_UNREACHABLE(); + } + } + + private: + std::vector m_bytes; + size_t m_bits; + size_t m_windows; + size_t m_window_bits; +}; + +class GenericWindowedMul final { + public: + static constexpr size_t WindowBits = VarPointWindowBits; + static constexpr size_t TableSize = (1 << WindowBits) - 1; + + explicit GenericWindowedMul(const GenericAffinePoint& pt) : + m_table(varpoint_setup(pt)) {} + + GenericProjectivePoint mul(const GenericScalar& s, RandomNumberGenerator& rng) { + const GenericBlindedScalarBits bits(s, rng, WindowBits); + + return varpoint_exec(m_table, bits, rng); + } + + private: + AffinePointTable m_table; +}; + +} // namespace + +class GenericBaseMulTable final { + public: + static constexpr size_t WindowBits = BasePointWindowBits; + + // +1 for Booth carry from the top window + explicit GenericBaseMulTable(const GenericAffinePoint& pt) : + m_table(basemul_booth_setup(pt, blinded_scalar_bits(*pt.curve()) + 1)) {} + + GenericProjectivePoint mul(const GenericScalar& s, RandomNumberGenerator& rng) { + // W+1 bit windows for Booth recoding overlap + const GenericBlindedScalarBits scalar(s, rng, WindowBits + 1); + return basemul_booth_exec(m_table, scalar, rng); + } + + private: + static size_t blinded_scalar_bits(const GenericPrimeOrderCurve& curve) { + const size_t order_bits = curve.order_bits(); + return order_bits + scalar_blinding_bits(order_bits); + } + + std::vector m_table; +}; + +namespace { + +class GenericWindowedMul2 final { + public: + static constexpr size_t WindowBits = Mul2PrecompWindowBits; + + GenericWindowedMul2(const GenericWindowedMul2& other) = delete; + GenericWindowedMul2(GenericWindowedMul2&& other) = delete; + GenericWindowedMul2& operator=(const GenericWindowedMul2& other) = delete; + GenericWindowedMul2& operator=(GenericWindowedMul2&& other) = delete; + + ~GenericWindowedMul2() = default; + + GenericWindowedMul2(const GenericAffinePoint& p, const GenericAffinePoint& q) : + m_table(mul2_setup(p, q)) {} + + GenericProjectivePoint mul2(const GenericScalar& x, const GenericScalar& y, RandomNumberGenerator& rng) const { + const GenericBlindedScalarBits x_bits(x, rng, WindowBits); + const GenericBlindedScalarBits y_bits(y, rng, WindowBits); + return mul2_exec(m_table, x_bits, y_bits, rng); + } + + private: + AffinePointTable m_table; +}; + +class GenericVartimeWindowedMul2 final : public PrimeOrderCurve::PrecomputedMul2Table { + public: + static constexpr size_t WindowBits = Mul2PrecompWindowBits; + + GenericVartimeWindowedMul2(const GenericVartimeWindowedMul2& other) = delete; + GenericVartimeWindowedMul2(GenericVartimeWindowedMul2&& other) = delete; + GenericVartimeWindowedMul2& operator=(const GenericVartimeWindowedMul2& other) = delete; + GenericVartimeWindowedMul2& operator=(GenericVartimeWindowedMul2&& other) = delete; + + ~GenericVartimeWindowedMul2() override = default; + + GenericVartimeWindowedMul2(const GenericAffinePoint& p, const GenericAffinePoint& q) : + m_table(to_affine_batch(mul2_setup(p, q))) {} + + GenericProjectivePoint mul2_vartime(const GenericScalar& x, const GenericScalar& y) const { + const auto x_bits = x.serialize>(); + const auto y_bits = y.serialize>(); + + const auto& curve = m_table[0].curve(); + auto accum = GenericProjectivePoint(curve); + + const size_t order_bits = curve->order_bits(); + + const size_t windows = (order_bits + WindowBits - 1) / WindowBits; + + for(size_t i = 0; i != windows; ++i) { + auto x_i = read_window_bits(std::span{x_bits}, (windows - i - 1) * WindowBits); + auto y_i = read_window_bits(std::span{y_bits}, (windows - i - 1) * WindowBits); + + if(i > 0) { + accum = accum.dbl_n(WindowBits); + } + + const size_t idx = (y_i << WindowBits) + x_i; + + if(idx > 0) { + accum += m_table[idx - 1]; + } + } + + return accum; + } + + private: + std::vector m_table; +}; + +} // namespace + +GenericPrimeOrderCurve::GenericPrimeOrderCurve( + const BigInt& p, const BigInt& a, const BigInt& b, const BigInt& base_x, const BigInt& base_y, const BigInt& order) : + m_params(std::make_unique(p, a, b, base_x, base_y, order)) {} + +void GenericPrimeOrderCurve::_precompute_base_mul() { + BOTAN_STATE_CHECK(m_basemul == nullptr); + m_basemul = std::make_unique(from_stash(generator())); +} + +size_t GenericPrimeOrderCurve::order_bits() const { + return _params().order_bits(); +} + +size_t GenericPrimeOrderCurve::scalar_bytes() const { + return _params().order_bytes(); +} + +size_t GenericPrimeOrderCurve::field_element_bytes() const { + return _params().field_bytes(); +} + +PrimeOrderCurve::ProjectivePoint GenericPrimeOrderCurve::mul_by_g(const Scalar& scalar, + RandomNumberGenerator& rng) const { + BOTAN_STATE_CHECK(m_basemul != nullptr); + return stash(m_basemul->mul(from_stash(scalar), rng)); +} + +PrimeOrderCurve::Scalar GenericPrimeOrderCurve::base_point_mul_x_mod_order(const Scalar& scalar, + RandomNumberGenerator& rng) const { + BOTAN_STATE_CHECK(m_basemul != nullptr); + auto pt_s = m_basemul->mul(from_stash(scalar), rng); + BOTAN_STATE_CHECK(!pt_s.is_identity().as_bool()); + const auto x_bytes = to_affine_x(pt_s).serialize>(); + if(auto s = GenericScalar::from_wide_bytes(this, x_bytes)) { + return stash(*s); + } else { + throw Internal_Error("Failed to convert x coordinate to integer modulo scalar"); + } +} + +PrimeOrderCurve::ProjectivePoint GenericPrimeOrderCurve::mul(const AffinePoint& pt, + const Scalar& scalar, + RandomNumberGenerator& rng) const { + GenericWindowedMul pt_table(from_stash(pt)); + return stash(pt_table.mul(from_stash(scalar), rng)); +} + +secure_vector GenericPrimeOrderCurve::mul_x_only(const AffinePoint& pt, + const Scalar& scalar, + RandomNumberGenerator& rng) const { + GenericWindowedMul pt_table(from_stash(pt)); + auto pt_s = pt_table.mul(from_stash(scalar), rng); + BOTAN_STATE_CHECK(!pt_s.is_identity().as_bool()); + return to_affine_x(pt_s).serialize>(); +} + +std::unique_ptr GenericPrimeOrderCurve::mul2_setup_g( + const AffinePoint& q) const { + return std::make_unique(from_stash(generator()), from_stash(q)); +} + +std::optional GenericPrimeOrderCurve::mul2_vartime(const PrecomputedMul2Table& tableb, + const Scalar& s1, + const Scalar& s2) const { + const auto& tbl = dynamic_cast(tableb); + auto pt = tbl.mul2_vartime(from_stash(s1), from_stash(s2)); + if(pt.is_identity().as_bool()) { + return {}; + } else { + return stash(pt); + } +} + +std::optional GenericPrimeOrderCurve::mul_px_qy( + const AffinePoint& p, const Scalar& x, const AffinePoint& q, const Scalar& y, RandomNumberGenerator& rng) const { + const GenericWindowedMul2 table(from_stash(p), from_stash(q)); + auto pt = table.mul2(from_stash(x), from_stash(y), rng); + if(pt.is_identity().as_bool()) { + return {}; + } else { + return stash(pt); + } +} + +bool GenericPrimeOrderCurve::mul2_vartime_x_mod_order_eq(const PrecomputedMul2Table& tableb, + const Scalar& v, + const Scalar& s1, + const Scalar& s2) const { + const auto& tbl = dynamic_cast(tableb); + auto pt = tbl.mul2_vartime(from_stash(s1), from_stash(s2)); + + if(!pt.is_identity().as_bool()) { + const auto z2 = pt.z().square(); + + const auto v_bytes = from_stash(v).serialize>(); + + if(auto fe_v = GenericField::deserialize(this, v_bytes)) { + if((*fe_v * z2 == pt.x()).as_bool()) { + return true; + } + + if(_params().order_is_less_than_field()) { + const auto n = GenericField::from_words(this, _params().order()); + const auto neg_n = n.negate().to_words(); + + const auto vw = fe_v->to_words(); + if(bigint_ct_is_lt(vw.data(), vw.size(), neg_n.data(), neg_n.size()).as_bool()) { + return (((*fe_v + n) * z2) == pt.x()).as_bool(); + } + } + } + } + + return false; +} + +PrimeOrderCurve::AffinePoint GenericPrimeOrderCurve::generator() const { + return PrimeOrderCurve::AffinePoint::_create(shared_from_this(), _params().base_x(), _params().base_y()); +} + +PrimeOrderCurve::AffinePoint GenericPrimeOrderCurve::point_to_affine(const ProjectivePoint& pt) const { + auto affine = to_affine(from_stash(pt)); + + const auto y2 = affine.y().square(); + const auto x3_ax_b = GenericCurve::AffinePoint::x3_ax_b(affine.x()); + const auto valid_point = affine.is_identity() || (y2 == x3_ax_b); + + BOTAN_ASSERT(valid_point.as_bool(), "Computed point is on the curve"); + + return stash(affine); +} + +PrimeOrderCurve::ProjectivePoint GenericPrimeOrderCurve::point_add(const AffinePoint& a, const AffinePoint& b) const { + return stash(GenericProjectivePoint::from_affine(from_stash(a)) + from_stash(b)); +} + +PrimeOrderCurve::AffinePoint GenericPrimeOrderCurve::point_negate(const AffinePoint& pt) const { + return stash(from_stash(pt).negate()); +} + +bool GenericPrimeOrderCurve::affine_point_is_identity(const AffinePoint& pt) const { + return from_stash(pt).is_identity().as_bool(); +} + +void GenericPrimeOrderCurve::serialize_point(std::span bytes, const AffinePoint& pt) const { + from_stash(pt).serialize_to(bytes); +} + +void GenericPrimeOrderCurve::serialize_scalar(std::span bytes, const Scalar& scalar) const { + BOTAN_ARG_CHECK(bytes.size() == _params().order_bytes(), "Invalid length to serialize_scalar"); + from_stash(scalar).serialize_to(bytes); +} + +std::optional GenericPrimeOrderCurve::deserialize_scalar( + std::span bytes) const { + if(auto s = GenericScalar::deserialize(this, bytes)) { + if(s->is_nonzero().as_bool()) { + return stash(s.value()); + } + } + + return {}; +} + +std::optional GenericPrimeOrderCurve::scalar_from_wide_bytes( + std::span bytes) const { + if(auto s = GenericScalar::from_wide_bytes(this, bytes)) { + return stash(s.value()); + } else { + return {}; + } +} + +std::optional GenericPrimeOrderCurve::deserialize_point( + std::span bytes) const { + if(auto pt = GenericAffinePoint::deserialize(this, bytes)) { + return stash(pt.value()); + } else { + return {}; + } +} + +PrimeOrderCurve::Scalar GenericPrimeOrderCurve::scalar_add(const Scalar& a, const Scalar& b) const { + return stash(from_stash(a) + from_stash(b)); +} + +PrimeOrderCurve::Scalar GenericPrimeOrderCurve::scalar_sub(const Scalar& a, const Scalar& b) const { + return stash(from_stash(a) - from_stash(b)); +} + +PrimeOrderCurve::Scalar GenericPrimeOrderCurve::scalar_mul(const Scalar& a, const Scalar& b) const { + return stash(from_stash(a) * from_stash(b)); +} + +PrimeOrderCurve::Scalar GenericPrimeOrderCurve::scalar_square(const Scalar& s) const { + return stash(from_stash(s).square()); +} + +PrimeOrderCurve::Scalar GenericPrimeOrderCurve::scalar_invert(const Scalar& s) const { + return stash(from_stash(s).invert()); +} + +PrimeOrderCurve::Scalar GenericPrimeOrderCurve::scalar_invert_vartime(const Scalar& s) const { + return stash(from_stash(s).invert_vartime()); +} + +PrimeOrderCurve::Scalar GenericPrimeOrderCurve::scalar_negate(const Scalar& s) const { + return stash(from_stash(s).negate()); +} + +bool GenericPrimeOrderCurve::scalar_is_zero(const Scalar& s) const { + return from_stash(s).is_zero().as_bool(); +} + +bool GenericPrimeOrderCurve::scalar_equal(const Scalar& a, const Scalar& b) const { + return (from_stash(a) == from_stash(b)).as_bool(); +} + +PrimeOrderCurve::Scalar GenericPrimeOrderCurve::scalar_one() const { + return stash(GenericScalar::one(this)); +} + +PrimeOrderCurve::Scalar GenericPrimeOrderCurve::random_scalar(RandomNumberGenerator& rng) const { + return stash(GenericScalar::random(this, rng)); +} + +PrimeOrderCurve::Scalar GenericPrimeOrderCurve::stash(const GenericScalar& s) const { + return Scalar::_create(shared_from_this(), s.stash_value()); +} + +GenericScalar GenericPrimeOrderCurve::from_stash(const PrimeOrderCurve::Scalar& s) const { + BOTAN_ARG_CHECK(s._curve().get() == this, "Curve mismatch"); + return GenericScalar(this, s._value()); +} + +PrimeOrderCurve::AffinePoint GenericPrimeOrderCurve::stash(const GenericAffinePoint& pt) const { + auto x_w = pt.x().stash_value(); + auto y_w = pt.y().stash_value(); + return AffinePoint::_create(shared_from_this(), x_w, y_w); +} + +GenericAffinePoint GenericPrimeOrderCurve::from_stash(const PrimeOrderCurve::AffinePoint& pt) const { + BOTAN_ARG_CHECK(pt._curve().get() == this, "Curve mismatch"); + auto x = GenericField(this, pt._x()); + auto y = GenericField(this, pt._y()); + return GenericAffinePoint(x, y); +} + +PrimeOrderCurve::ProjectivePoint GenericPrimeOrderCurve::stash(const GenericProjectivePoint& pt) const { + auto x_w = pt.x().stash_value(); + auto y_w = pt.y().stash_value(); + auto z_w = pt.z().stash_value(); + return ProjectivePoint::_create(shared_from_this(), x_w, y_w, z_w); +} + +GenericProjectivePoint GenericPrimeOrderCurve::from_stash(const PrimeOrderCurve::ProjectivePoint& pt) const { + BOTAN_ARG_CHECK(pt._curve().get() == this, "Curve mismatch"); + auto x = GenericField(this, pt._x()); + auto y = GenericField(this, pt._y()); + auto z = GenericField(this, pt._z()); + return GenericProjectivePoint(x, y, z); +} + +PrimeOrderCurve::AffinePoint GenericPrimeOrderCurve::hash_to_curve_nu( + std::function)> expand_message) const { + BOTAN_UNUSED(expand_message); + throw Not_Implemented("Hash to curve is not implemented for this curve"); +} + +PrimeOrderCurve::ProjectivePoint GenericPrimeOrderCurve::hash_to_curve_ro( + std::function)> expand_message) const { + BOTAN_UNUSED(expand_message); + throw Not_Implemented("Hash to curve is not implemented for this curve"); +} + +std::shared_ptr PCurveInstance::from_params( + const BigInt& p, const BigInt& a, const BigInt& b, const BigInt& base_x, const BigInt& base_y, const BigInt& order) { + // We don't check that p and order are prime here on the assumption this has + // been checked already by EC_Group + + BOTAN_ARG_CHECK(a >= 0 && a < p, "a is invalid"); + BOTAN_ARG_CHECK(b > 0 && b < p, "b is invalid"); + BOTAN_ARG_CHECK(base_x >= 0 && base_x < p, "base_x is invalid"); + BOTAN_ARG_CHECK(base_y >= 0 && base_y < p, "base_y is invalid"); + + const size_t p_bits = p.bits(); + + // Same size restrictions as EC_Group however here we do not require + // exactly the primes for the 521 or 239 bit exceptions; this code + // should work fine with any such prime and we are relying on the higher + // levels to prevent creating such a group in the first place + // + // TODO(Botan4) increase the 128 here to 192 when the corresponding EC_Group constructor is changed + // + if(p_bits != 521 && p_bits != 239 && (p_bits < 128 || p_bits > 512 || p_bits % 32 != 0)) { + return {}; + } + + // We don't want to deal with Shanks-Tonelli in the generic case + if(p % 4 != 3) { + return {}; + } + + // The bit length of the field and order being the same simplifies things + if(p_bits != order.bits()) { + return {}; + } + + auto gpoc = std::make_shared(p, a, b, base_x, base_y, order); + /* + The implementation of this needs to call shared_from_this which is not usable + until after the constructor has completed, so we have to do a two-stage + construction process. This is certainly not so clean but it is contained to + this single file so seems tolerable. + + Alternately we could lazily compute the base mul table but this brings in + locking issues which seem a worse alternative overall. + */ + gpoc->_precompute_base_mul(); + return gpoc; +} + +} // namespace Botan::PCurve diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_generic/pcurves_generic.h botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_generic/pcurves_generic.h --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_generic/pcurves_generic.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_generic/pcurves_generic.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,136 @@ +/* +* (C) 2025 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_PCURVES_GENERIC_H_ +#define BOTAN_PCURVES_GENERIC_H_ + +#include + +#include +#include + +namespace Botan::PCurve { + +class GenericCurveParams; +class GenericScalar; +class GenericAffinePoint; +class GenericProjectivePoint; +class GenericBaseMulTable; + +class GenericPrimeOrderCurve final : public PrimeOrderCurve, + public std::enable_shared_from_this { + public: + // This class should only be created via PCurveInstance::from_params + GenericPrimeOrderCurve(const BigInt& p, + const BigInt& a, + const BigInt& b, + const BigInt& base_x, + const BigInt& base_y, + const BigInt& order); + + size_t order_bits() const override; + + size_t scalar_bytes() const override; + + size_t field_element_bytes() const override; + + ProjectivePoint mul_by_g(const Scalar& scalar, RandomNumberGenerator& rng) const override; + + ProjectivePoint mul(const AffinePoint& pt, const Scalar& scalar, RandomNumberGenerator& rng) const override; + + secure_vector mul_x_only(const AffinePoint& pt, + const Scalar& scalar, + RandomNumberGenerator& rng) const override; + + std::unique_ptr mul2_setup_g(const AffinePoint& q) const override; + + std::optional mul2_vartime(const PrecomputedMul2Table& tableb, + const Scalar& x, + const Scalar& y) const override; + + std::optional mul_px_qy(const AffinePoint& p, + const Scalar& x, + const AffinePoint& q, + const Scalar& y, + RandomNumberGenerator& rng) const override; + + bool mul2_vartime_x_mod_order_eq(const PrecomputedMul2Table& tableb, + const Scalar& v, + const Scalar& s1, + const Scalar& s2) const override; + + Scalar base_point_mul_x_mod_order(const Scalar& scalar, RandomNumberGenerator& rng) const override; + + AffinePoint generator() const override; + + AffinePoint point_to_affine(const ProjectivePoint& pt) const override; + + ProjectivePoint point_add(const AffinePoint& a, const AffinePoint& b) const override; + + AffinePoint point_negate(const AffinePoint& pt) const override; + + bool affine_point_is_identity(const AffinePoint& pt) const override; + + void serialize_point(std::span bytes, const AffinePoint& pt) const override; + + void serialize_scalar(std::span bytes, const Scalar& scalar) const override; + + std::optional deserialize_scalar(std::span bytes) const override; + + std::optional scalar_from_wide_bytes(std::span bytes) const override; + + std::optional deserialize_point(std::span bytes) const override; + + AffinePoint hash_to_curve_nu(std::function)> expand_message) const override; + + ProjectivePoint hash_to_curve_ro(std::function)> expand_message) const override; + + Scalar scalar_add(const Scalar& a, const Scalar& b) const override; + + Scalar scalar_sub(const Scalar& a, const Scalar& b) const override; + + Scalar scalar_mul(const Scalar& a, const Scalar& b) const override; + + Scalar scalar_square(const Scalar& s) const override; + + Scalar scalar_invert(const Scalar& s) const override; + + Scalar scalar_invert_vartime(const Scalar& s) const override; + + Scalar scalar_negate(const Scalar& s) const override; + + bool scalar_is_zero(const Scalar& s) const override; + + bool scalar_equal(const Scalar& a, const Scalar& b) const override; + + Scalar scalar_one() const override; + + Scalar random_scalar(RandomNumberGenerator& rng) const override; + + const GenericCurveParams& _params() const { return *m_params; } + + void _precompute_base_mul(); + + private: + PrimeOrderCurve::Scalar stash(const GenericScalar& s) const; + + PrimeOrderCurve::AffinePoint stash(const GenericAffinePoint& pt) const; + + PrimeOrderCurve::ProjectivePoint stash(const GenericProjectivePoint& pt) const; + + GenericScalar from_stash(const PrimeOrderCurve::Scalar& s) const; + + GenericAffinePoint from_stash(const PrimeOrderCurve::AffinePoint& pt) const; + + GenericProjectivePoint from_stash(const PrimeOrderCurve::ProjectivePoint& pt) const; + + std::unique_ptr m_params; + std::unique_ptr m_basemul; +}; + +} // namespace Botan::PCurve + +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_id.h botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_id.h --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_id.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_id.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,76 +0,0 @@ -/* -* (C) 2024 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#ifndef BOTAN_PCURVES_ID_H_ -#define BOTAN_PCURVES_ID_H_ - -#include -#include -#include -#include -#include - -namespace Botan { - -#if defined(BOTAN_HAS_ASN1) -class OID; -#endif - -} // namespace Botan - -namespace Botan::PCurve { - -/// Identifier for a named prime order curve -class BOTAN_TEST_API PrimeOrderCurveId final { - public: - enum class Code : uint8_t { - /// secp192r1 aka P-192 - secp192r1, - /// secp224r1 aka P-224 - secp224r1, - /// secp256r1 aka P-256 - secp256r1, - /// secp384r1 aka P-384 - secp384r1, - /// secp521r1 aka P-521 - secp521r1, - /// secp256k1 - secp256k1, - /// brainpool256r1 - brainpool256r1, - brainpool384r1, - brainpool512r1, - frp256v1, - sm2p256v1, - numsp512d1, - }; - - using enum Code; - - Code code() const { return m_code; } - - /// Convert the ID to it's commonly used name (inverse of from_string) - std::string to_string() const; - - PrimeOrderCurveId(Code id) : m_code(id) {} - - /// Map a string to a curve identifier - static std::optional from_string(std::string_view name); - -#if defined(BOTAN_HAS_ASN1) - /// Map an OID to a curve identifier - /// - /// Uses the internal OID table - static std::optional from_oid(const OID& oid); -#endif - - private: - const Code m_code; -}; - -} // namespace Botan::PCurve - -#endif diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_impl/info.txt botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_impl/info.txt --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_impl/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_impl/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + PCURVES_IMPL -> 20240714 - + name -> "Prime Order Curves Implementation Helpers" diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_impl/pcurves_impl.h botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_impl/pcurves_impl.h --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_impl/pcurves_impl.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_impl/pcurves_impl.h 2026-05-07 01:38:28.000000000 +0000 @@ -1,5 +1,5 @@ /* -* (C) 2024,2025 Jack Lloyd +* (C) 2024,2025,2026 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -8,19 +8,39 @@ #define BOTAN_PCURVES_IMPL_H_ #include +#include #include #include +#include +#include +#include #include -#include +#include #include -#if defined(BOTAN_HAS_XMD) - #include -#endif - namespace Botan { /* + * @brief Helper class to pass literal strings to C++ templates + * + * This is a generic utility so it may make sense to move this into utils + * if someday such functionality is useful outside of pcurves. + */ +template +class StringLiteral final { + public: + // NOLINTNEXTLINE(*-explicit-conversions) + consteval StringLiteral(const char (&str)[N]) : value() { + for(size_t i = 0; i != N; ++i) { + value[i] = str[i]; + } + } + + // NOLINTNEXTLINE(*non-private-member-variable*) + char value[N]; +}; + +/* This file implements a system for compile-time instantiation of elliptic curve arithmetic. All computations including point multiplication are implemented to be constant time, @@ -42,8 +62,6 @@ the bells and whistles. */ -namespace { - /** * Montomgomery Representation of Integers * @@ -95,7 +113,7 @@ * Convert an integer into Montgomery representation */ constexpr static std::array to_rep(const std::array& x) { - std::array z; + std::array z; // NOLINT(*-member-init) comba_mul(z.data(), x.data(), R2.data()); return Self::redc(z); } @@ -108,7 +126,7 @@ */ constexpr static std::array wide_to_rep(const std::array& x) { auto redc_x = Self::redc(x); - std::array z; + std::array z; // NOLINT(*-member-init) comba_mul(z.data(), redc_x.data(), R3.data()); return Self::redc(z); } @@ -165,6 +183,7 @@ IntMod(Self&& other) = default; IntMod& operator=(const Self& other) = default; IntMod& operator=(Self&& other) = default; + ~IntMod() = default; /** * Return integer zero @@ -224,13 +243,34 @@ } /** + * Return either this or -this depending on which is even + */ + constexpr Self correct_sign(CT::Choice even) const { + const auto flip = (even != this->is_even()); + return Self::choose(flip, this->negate(), *this); + } + + /** + * Return x or y depending on if choice is set or not + */ + static constexpr Self choose(CT::Choice choice, const Self& x, const Self& y) { + auto r = y; + r.conditional_assign(choice, x); + return r; + } + + /** * Modular addition; return c = a + b */ - friend constexpr Self operator+(const Self& a, const Self& b) { - std::array t; - W carry = bigint_add(t, a.value(), b.value()); + friend constexpr BOTAN_FORCE_INLINE Self operator+(const Self& a, const Self& b) { + std::array t; // NOLINT(*-member-init) + + W carry = 0; + for(size_t i = 0; i != N; ++i) { + t[i] = word_add(a.m_val[i], b.m_val[i], &carry); + } - std::array r; + std::array r; // NOLINT(*-member-init) bigint_monty_maybe_sub(r.data(), carry, t.data(), P.data()); return Self(r); } @@ -238,10 +278,21 @@ /** * Modular subtraction; return c = a - b */ - friend constexpr Self operator-(const Self& a, const Self& b) { - std::array r; - word carry = bigint_sub3(r.data(), a.data(), N, b.data(), N); - bigint_cnd_add(carry, r.data(), N, P.data(), N); + friend constexpr BOTAN_FORCE_INLINE Self operator-(const Self& a, const Self& b) { + std::array r; // NOLINT(*-member-init) + W carry = 0; + for(size_t i = 0; i != N; ++i) { + r[i] = word_sub(a.m_val[i], b.m_val[i], &carry); + } + + const auto mask = CT::Mask::expand(carry).value(); + + carry = 0; + + for(size_t i = 0; i != N; ++i) { + r[i] = word_add(r[i], P[i] & mask, &carry); + } + return Self(r); } @@ -256,38 +307,44 @@ // We could multiply by INV_2 but there is a better way ... std::array t = value(); - W borrow = shift_right<1>(t); + const W borrow = shift_right<1>(t); // If value was odd, add (P/2)+1 - bigint_cnd_add(borrow, t.data(), N, INV_2.data(), N); + const auto mask = CT::Mask::expand(borrow).value(); + + W carry = 0; + + for(size_t i = 0; i != N; ++i) { + t[i] = word_add(t[i], INV_2[i] & mask, &carry); + } return Self(t); } /// Return (*this) multiplied by 2 - constexpr Self mul2() const { + constexpr BOTAN_FORCE_INLINE Self mul2() const { std::array t = value(); - W carry = shift_left<1>(t); + const W carry = shift_left<1>(t); - std::array r; + std::array r; // NOLINT(*-member-init) bigint_monty_maybe_sub(r.data(), carry, t.data(), P.data()); return Self(r); } /// Return (*this) multiplied by 3 - constexpr Self mul3() const { return mul2() + (*this); } + constexpr inline Self mul3() const { return mul2() + (*this); } /// Return (*this) multiplied by 4 - constexpr Self mul4() const { return mul2().mul2(); } + constexpr inline Self mul4() const { return mul2().mul2(); } /// Return (*this) multiplied by 8 - constexpr Self mul8() const { return mul2().mul2().mul2(); } + constexpr inline Self mul8() const { return mul2().mul2().mul2(); } /** * Modular multiplication; return c = a * b */ - friend constexpr Self operator*(const Self& a, const Self& b) { - std::array z; + friend constexpr BOTAN_FORCE_INLINE Self operator*(const Self& a, const Self& b) { + std::array z; // NOLINT(*-member-init) comba_mul(z.data(), a.data(), b.data()); return Self(Rep::redc(z)); } @@ -295,8 +352,8 @@ /** * Modular multiplication; set this to this * other */ - constexpr Self& operator*=(const Self& other) { - std::array z; + constexpr BOTAN_FORCE_INLINE Self& operator*=(const Self& other) { + std::array z; // NOLINT(*-member-init) comba_mul(z.data(), data(), other.data()); m_val = Rep::redc(z); return (*this); @@ -305,13 +362,13 @@ /** * Conditional assignment * - * If `cond` is true, sets `x` to `nx` + * If `cond` is true, sets *this to `nx` */ - static constexpr void conditional_assign(Self& x, CT::Choice cond, const Self& nx) { - const W mask = CT::Mask::from_choice(cond).value(); + constexpr void conditional_assign(CT::Choice cond, const Self& nx) { + const W mask = cond.into_bitmask(); for(size_t i = 0; i != N; ++i) { - x.m_val[i] = choose(mask, nx.m_val[i], x.m_val[i]); + m_val[i] = Botan::choose(mask, nx.m_val[i], m_val[i]); } } @@ -321,11 +378,11 @@ * If `cond` is true, sets `x` to `nx` and `y` to `ny` */ static constexpr void conditional_assign(Self& x, Self& y, CT::Choice cond, const Self& nx, const Self& ny) { - const W mask = CT::Mask::from_choice(cond).value(); + const W mask = cond.into_bitmask(); for(size_t i = 0; i != N; ++i) { - x.m_val[i] = choose(mask, nx.m_val[i], x.m_val[i]); - y.m_val[i] = choose(mask, ny.m_val[i], y.m_val[i]); + x.m_val[i] = Botan::choose(mask, nx.m_val[i], x.m_val[i]); + y.m_val[i] = Botan::choose(mask, ny.m_val[i], y.m_val[i]); } } @@ -336,12 +393,28 @@ */ static constexpr void conditional_assign( Self& x, Self& y, Self& z, CT::Choice cond, const Self& nx, const Self& ny, const Self& nz) { - const W mask = CT::Mask::from_choice(cond).value(); + const W mask = cond.into_bitmask(); for(size_t i = 0; i != N; ++i) { - x.m_val[i] = choose(mask, nx.m_val[i], x.m_val[i]); - y.m_val[i] = choose(mask, ny.m_val[i], y.m_val[i]); - z.m_val[i] = choose(mask, nz.m_val[i], z.m_val[i]); + x.m_val[i] = Botan::choose(mask, nx.m_val[i], x.m_val[i]); + y.m_val[i] = Botan::choose(mask, ny.m_val[i], y.m_val[i]); + z.m_val[i] = Botan::choose(mask, nz.m_val[i], z.m_val[i]); + } + } + + /** + * Conditional swap + * + * If `cond` is true, swaps the values of `x` and `y` + */ + static constexpr void conditional_swap(CT::Choice cond, Self& x, Self& y) { + const W mask = cond.into_bitmask(); + + for(size_t i = 0; i != N; ++i) { + auto nx = Botan::choose(mask, y.m_val[i], x.m_val[i]); + auto ny = Botan::choose(mask, x.m_val[i], y.m_val[i]); + x.m_val[i] = nx; + y.m_val[i] = ny; } } @@ -350,8 +423,8 @@ * * Returns the square of this after modular reduction */ - constexpr Self square() const { - std::array z; + constexpr BOTAN_FORCE_INLINE Self square() const { + std::array z; // NOLINT(*-member-init) comba_sqr(z.data(), this->data()); return Self(Rep::redc(z)); } @@ -364,7 +437,7 @@ * (Alternate view, returns this raised to the 2^nth power) */ constexpr void square_n(size_t n) { - std::array z; + std::array z; // NOLINT(*-member-init) for(size_t i = 0; i != n; ++i) { comba_sqr(z.data(), this->data()); m_val = Rep::redc(z); @@ -377,11 +450,14 @@ * Returns the additive inverse of (*this) */ constexpr Self negate() const { - auto x_is_zero = CT::all_zeros(this->data(), N); + const W x_is_zero = ~CT::all_zeros(this->data(), N).value(); + + std::array r; // NOLINT(*-member-init) + W carry = 0; + for(size_t i = 0; i != N; ++i) { + r[i] = word_sub(P[i] & x_is_zero, m_val[i], &carry); + } - std::array r; - bigint_sub3(r.data(), P.data(), N, this->data(), N); - x_is_zero.if_set_zero_out(r.data(), N); return Self(r); } @@ -415,6 +491,7 @@ tbl[0] = (*this); for(size_t i = 1; i != WindowElements; ++i) { + // Conditional ok: table indexes are public here if(i % 2 == 1) { tbl[i] = tbl[i / 2].square(); } else { @@ -426,6 +503,7 @@ const size_t w0 = read_window_bits(std::span{exp}, (Windows - 1) * WindowBits); + // Conditional ok: this function is variable time if(w0 > 0) { r = tbl[w0 - 1]; } @@ -435,6 +513,7 @@ const size_t w = read_window_bits(std::span{exp}, (Windows - i - 1) * WindowBits); + // Conditional ok: this function is variable time if(w > 0) { r *= tbl[w - 1]; } @@ -459,20 +538,139 @@ constexpr Self invert() const { return pow_vartime(Self::P_MINUS_2); } /** + * Helper for variable time BEEA + * + * Note this function assumes that its arguments are in the standard + * domain, not the Montgomery domain. invert_vartime converts its argument + * out of Montgomery, and then back to Montgomery when returning the result. + */ + static constexpr void _invert_vartime_div2_helper(Self& a, Self& x) { + constexpr auto INV_2 = p_div_2_plus_1(Rep::P); + + // Conditional ok: this function is variable time + while((a.m_val[0] & 1) != 1) { + shift_right<1>(a.m_val); + + const W borrow = shift_right<1>(x.m_val); + + // Conditional ok: this function is variable time + if(borrow) { + bigint_add2(x.m_val.data(), N, INV_2.data(), N); + } + } + } + + /** + * Returns the modular inverse, or 0 if no modular inverse exists. + * + * This function assumes that the modulus is prime + * + * This function does something a bit nasty and converts from the normal + * representation (for scalars, Montgomery) into the "standard" + * representation. This relies on the fact that we aren't doing any + * multiplications within this function, just additions, subtractions, + * division by 2, and comparisons. + * + * The reason is there is no good way to compare integers in the Montgomery + * domain; we could convert out for each comparison but this is slower than + * just doing a constant-time inversion. + * + * This is loosely based on the algorithm BoringSSL uses in + * BN_mod_inverse_odd, which is a variant of the Binary Extended Euclidean + * algorithm. It is optimized somewhat by taking advantage of a couple of + * observations. + * + * In the first two iterations, the control flow is known because `a` is + * less than the modulus and not zero, and we know that the modulus is + * odd. So we peel out those iterations. This also avoids having to + * initialize `a` with the modulus, because we instead set it directly to + * what the first loop iteration would have updated it to. This ensures + * that all values are always less than or equal to the modulus. + * + * Then we take advantage of the fact that in each iteration of the loop, + * at the end we update either b/x or a/y, but never both. In the next + * iteration of the loop, we attempt to modify b/x or a/y depending on the + * low zero bits of b or a. But if a or b were not updated in the previous + * iteration than they will still be odd, and nothing will happen. Instead + * update just the pair we need to update, right after writing to b/x or + * a/y resp. + */ + constexpr Self invert_vartime() const { + // Conditional ok: this function is variable time + if(this->is_zero().as_bool()) { + return Self::zero(); + } + + auto x = Self(std::array{1}); // 1 in standard domain + auto b = Self(this->to_words()); // *this in standard domain + + // First loop iteration + Self::_invert_vartime_div2_helper(b, x); + + auto a = b.negate(); + // y += x but y is zero at the outset + auto y = x; + + // First half of second loop iteration + Self::_invert_vartime_div2_helper(a, y); + + for(;;) { + // Conditional ok: this function is variable time + if(a.m_val == b.m_val) { + // At this point it should be that a == b == 1 + auto r = y.negate(); + + // Convert back to Montgomery if required + r.m_val = Rep::to_rep(r.m_val); + return r; + } + + auto nx = x + y; + + /* + * Otherwise either b > a or a > b + * + * If b > a we want to set b to b - a + * Otherwise we want to set a to a - b + * + * Compute r = b - a and check if it underflowed + * If it did not then we are in the b > a path + */ + std::array r; // NOLINT(*-member-init) + const word carry = bigint_sub3(r.data(), b.data(), N, a.data(), N); + + // Conditional ok: this function is variable time + if(carry == 0) { + // b > a + b.m_val = r; + x = nx; + Self::_invert_vartime_div2_helper(b, x); + } else { + // We know this can't underflow because a > b + bigint_sub3(r.data(), a.data(), N, b.data(), N); + a.m_val = r; + y = nx; + Self::_invert_vartime_div2_helper(a, y); + } + } + } + + /** * Return the modular square root if it exists * - * The CT::Choice indicates if the square root exists or not. + * The CT::Option will be unset if the square root does not exist */ - constexpr std::pair sqrt() const { + constexpr CT::Option sqrt() const { if constexpr(Self::P_MOD_4 == 3) { // The easy case for square root is when p == 3 (mod 4) constexpr auto P_PLUS_1_OVER_4 = p_plus_1_over_4(P); auto z = pow_vartime(P_PLUS_1_OVER_4); - const CT::Choice correct = (z.square() == *this); + // Zero out the return value if it would otherwise be incorrect - Self::conditional_assign(z, !correct, Self::zero()); - return {z, correct}; + const CT::Choice correct = (z.square() == *this); + z.conditional_assign(!correct, Self::zero()); + return CT::Option(z, correct); } else { // Shanks-Tonelli, following I.4 in RFC 9380 @@ -502,15 +700,16 @@ for(size_t i = C1_C2.first; i >= 2; i--) { b.square_n(i - 2); const CT::Choice e = b.is_one(); - Self::conditional_assign(z, !e, z * c); + z.conditional_assign(!e, z * c); c.square_n(1); - Self::conditional_assign(t, !e, t * c); + t.conditional_assign(!e, t * c); b = t; } + // Zero out the return value if it would otherwise be incorrect const CT::Choice correct = (z.square() == *this); - Self::conditional_assign(z, !correct, Self::zero()); - return {z, correct}; + z.conditional_assign(!correct, Self::zero()); + return CT::Option(z, correct); } } @@ -591,12 +790,14 @@ * also rejected. */ static std::optional deserialize(std::span bytes) { + // Conditional ok: input length is public if(bytes.size() != Self::BYTES) { return {}; } const auto words = bytes_to_words(bytes.first()); + // Conditional acceptable: std::optional is implicitly not constant time if(!bigint_ct_is_lt(words.data(), N, P.data(), N).as_bool()) { return {}; } @@ -626,6 +827,7 @@ * modular reduces it. */ static constexpr std::optional from_wide_bytes_varlen(std::span bytes) { + // Conditional ok: input length is public if(bytes.size() > 2 * Self::BYTES) { return {}; } @@ -649,7 +851,7 @@ static Self random(RandomNumberGenerator& rng) { constexpr size_t MAX_ATTEMPTS = 1000; - std::array buf; + std::array buf{}; for(size_t i = 0; i != MAX_ATTEMPTS; ++i) { rng.randomize(buf); @@ -661,6 +863,7 @@ buf[0] &= mask; } + // Conditionals ok: rejection sampling reveals only values we didn't use if(auto s = Self::deserialize(buf)) { if(s.value().is_nonzero().as_bool()) { return s.value(); @@ -677,7 +880,7 @@ * Notice this function is consteval, and so can only be called at compile time */ static consteval Self constant(int8_t x) { - std::array v; + std::array v{}; v[0] = (x >= 0) ? x : -x; auto s = Self::from_words(v); return (x >= 0) ? s : s.negate(); @@ -702,32 +905,33 @@ * * This contains a pair of integers (x,y) which satisfy the curve equation */ -template +template class AffineCurvePoint final { public: - // We can't pass a FieldElement directly because FieldElement is - // not "structural" due to having private members, so instead - // recreate it here from the words. - static constexpr FieldElement A = FieldElement::from_words(Params::AW); - static constexpr FieldElement B = FieldElement::from_words(Params::BW); - static constexpr size_t BYTES = 1 + 2 * FieldElement::BYTES; - static constexpr size_t COMPRESSED_BYTES = 1 + FieldElement::BYTES; - using Self = AffineCurvePoint; + using Self = AffineCurvePoint; + // Note this constructor does not check the validity of the x/y pair + // This must be verified prior to this constructor being called constexpr AffineCurvePoint(const FieldElement& x, const FieldElement& y) : m_x(x), m_y(y) {} constexpr AffineCurvePoint() : m_x(FieldElement::zero()), m_y(FieldElement::zero()) {} static constexpr Self identity() { return Self(FieldElement::zero(), FieldElement::zero()); } + // Helper for ct_select of pcurves_generic + static constexpr Self identity(const Self& /*unused*/) { + return Self(FieldElement::zero(), FieldElement::zero()); + } + constexpr CT::Choice is_identity() const { return x().is_zero() && y().is_zero(); } AffineCurvePoint(const Self& other) = default; AffineCurvePoint(Self&& other) = default; AffineCurvePoint& operator=(const Self& other) = default; AffineCurvePoint& operator=(Self&& other) = default; + ~AffineCurvePoint() = default; constexpr Self negate() const { return Self(x(), y().negate()); } @@ -744,33 +948,12 @@ } /** - * Serialize the point in compressed format - */ - constexpr void serialize_compressed_to(std::span bytes) const { - BOTAN_STATE_CHECK(this->is_identity().as_bool() == false); - const uint8_t hdr = CT::Mask::from_choice(y().is_even()).select(0x02, 0x03); - - BufferStuffer pack(bytes); - pack.append(hdr); - x().serialize_to(pack.next()); - BOTAN_DEBUG_ASSERT(pack.full()); - } - - /** - * Serialize the affine x coordinate only - */ - constexpr void serialize_x_to(std::span bytes) const { - BOTAN_STATE_CHECK(this->is_identity().as_bool() == false); - x().serialize_to(bytes); - } - - /** * If idx is zero then return the identity element. Otherwise return pts[idx - 1] * * Returns the identity element also if idx is out of range */ static constexpr auto ct_select(std::span pts, size_t idx) { - auto result = Self::identity(); + auto result = Self::identity(pts[0]); // Intentionally wrapping; set to maximum size_t if idx == 0 const size_t idx1 = static_cast(idx - 1); @@ -783,68 +966,6 @@ } /** - * Return (x^3 + A*x + B) mod p - */ - static constexpr FieldElement x3_ax_b(const FieldElement& x) { return (x.square() + Self::A) * x + Self::B; } - - /** - * Point deserialization - * - * This accepts compressed or uncompressed formats. - * - * It also currently accepts the deprecated hybrid format. - * TODO(Botan4): remove support for decoding hybrid points - */ - static std::optional deserialize(std::span bytes) { - if(bytes.size() == Self::BYTES) { - if(bytes[0] == 0x04) { - auto x = FieldElement::deserialize(bytes.subspan(1, FieldElement::BYTES)); - auto y = FieldElement::deserialize(bytes.subspan(1 + FieldElement::BYTES, FieldElement::BYTES)); - - if(x && y) { - const auto lhs = (*y).square(); - const auto rhs = Self::x3_ax_b(*x); - if((lhs == rhs).as_bool()) { - return Self(*x, *y); - } - } - } else if(bytes[0] == 0x06 || bytes[0] == 0x07) { - // Deprecated "hybrid" encoding - const CT::Choice y_is_even = CT::Mask::is_equal(bytes[0], 0x06).as_choice(); - auto x = FieldElement::deserialize(bytes.subspan(1, FieldElement::BYTES)); - auto y = FieldElement::deserialize(bytes.subspan(1 + FieldElement::BYTES, FieldElement::BYTES)); - - if(x && y && (y_is_even == y->is_even()).as_bool()) { - const auto lhs = (*y).square(); - const auto rhs = Self::x3_ax_b(*x); - if((lhs == rhs).as_bool()) { - return Self(*x, *y); - } - } - } - } else if(bytes.size() == Self::COMPRESSED_BYTES) { - if(bytes[0] == 0x02 || bytes[0] == 0x03) { - const CT::Choice y_is_even = CT::Mask::is_equal(bytes[0], 0x02).as_choice(); - - if(auto x = FieldElement::deserialize(bytes.subspan(1, FieldElement::BYTES))) { - auto [y, is_square] = x3_ax_b(*x).sqrt(); - - if(is_square.as_bool()) { - const auto flip_y = y_is_even != y.is_even(); - FieldElement::conditional_assign(y, flip_y, y.negate()); - return Self(*x, y); - } - } - } - } else if(bytes.size() == 1 && bytes[0] == 0x00) { - // See SEC1 section 2.3.4 - return Self::identity(); - } - - return {}; - } - - /** * Return the affine x coordinate */ constexpr const FieldElement& x() const { return m_x; } @@ -876,7 +997,7 @@ * This uses Jacobian coordinates */ template -class ProjectiveCurvePoint { +class ProjectiveCurvePoint final { public: // We can't pass a FieldElement directly because FieldElement is // not "structural" due to having private members, so instead @@ -887,17 +1008,28 @@ static constexpr bool A_is_minus_3 = (A == FieldElement::constant(-3)).as_bool(); using Self = ProjectiveCurvePoint; - using AffinePoint = AffineCurvePoint; + using AffinePoint = AffineCurvePoint; /** * Convert a point from affine to projective form */ static constexpr Self from_affine(const AffinePoint& pt) { - if(pt.is_identity().as_bool()) { - return Self::identity(); - } else { - return ProjectiveCurvePoint(pt.x(), pt.y()); - } + /* + * If the point is the identity element (x=0, y=0) then instead of + * creating (x, y, 1) = (0, 0, 1) we want our projective identity + * encoding of (0, 1, 0) + * + * Which we can achieve by a conditional swap of y and z if the + * affine point is the identity. + */ + + auto x = pt.x(); + auto y = pt.y(); + auto z = FieldElement::one(); + + FieldElement::conditional_swap(pt.is_identity(), y, z); + + return ProjectiveCurvePoint(x, y, z); } /** @@ -927,6 +1059,7 @@ ProjectiveCurvePoint(Self&& other) = default; ProjectiveCurvePoint& operator=(const Self& other) = default; ProjectiveCurvePoint& operator=(Self&& other) = default; + ~ProjectiveCurvePoint() = default; friend constexpr Self operator+(const Self& a, const Self& b) { return Self::add(a, b); } @@ -956,176 +1089,29 @@ * Mixed (projective + affine) point addition */ constexpr static Self add_mixed(const Self& a, const AffinePoint& b) { - const auto a_is_identity = a.is_identity(); - const auto b_is_identity = b.is_identity(); - if((a_is_identity && b_is_identity).as_bool()) { - return Self::identity(); - } - - /* - https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#addition-add-1998-cmo-2 - - Cost: 8M + 3S + 6add + 1*2 - */ - - const auto Z1Z1 = a.z().square(); - const auto U2 = b.x() * Z1Z1; - const auto S2 = b.y() * a.z() * Z1Z1; - const auto H = U2 - a.x(); - const auto r = S2 - a.y(); - - // If r == H == 0 then we are in the doubling case - // For a == -b we compute the correct result because - // H will be zero, leading to Z3 being zero also - if((r.is_zero() && H.is_zero()).as_bool()) { - return a.dbl(); - } - - const auto HH = H.square(); - const auto HHH = H * HH; - const auto V = a.x() * HH; - const auto t2 = r.square(); - const auto t3 = V + V; - const auto t4 = t2 - HHH; - auto X3 = t4 - t3; - const auto t5 = V - X3; - const auto t6 = a.y() * HHH; - const auto t7 = r * t5; - auto Y3 = t7 - t6; - auto Z3 = a.z() * H; - - // if a is identity then return b - FieldElement::conditional_assign(X3, Y3, Z3, a_is_identity, b.x(), b.y(), FieldElement::one()); - - // if b is identity then return a - FieldElement::conditional_assign(X3, Y3, Z3, b_is_identity, a.x(), a.y(), a.z()); + return point_add_mixed(a, b, FieldElement::one()); + } - return Self(X3, Y3, Z3); + // Either add or subtract based on the CT::Choice + constexpr static Self add_or_sub(const Self& a, const AffinePoint& b, CT::Choice sub) { + return point_add_or_sub_mixed(a, b, sub, FieldElement::one()); } /** * Projective point addition */ - constexpr static Self add(const Self& a, const Self& b) { - const auto a_is_identity = a.is_identity(); - const auto b_is_identity = b.is_identity(); - - if((a_is_identity && b_is_identity).as_bool()) { - return Self::identity(); - } - - /* - https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#addition-add-1998-cmo-2 - - Cost: 12M + 4S + 6add + 1*2 - */ - - const auto Z1Z1 = a.z().square(); - const auto Z2Z2 = b.z().square(); - const auto U1 = a.x() * Z2Z2; - const auto U2 = b.x() * Z1Z1; - const auto S1 = a.y() * b.z() * Z2Z2; - const auto S2 = b.y() * a.z() * Z1Z1; - const auto H = U2 - U1; - const auto r = S2 - S1; - - // If a == -b then H == 0 && r != 0, in which case - // at the end we'll set z = a.z * b.z * H = 0, resulting - // in the correct output (point at infinity) - if((r.is_zero() && H.is_zero()).as_bool()) { - return a.dbl(); - } - - const auto HH = H.square(); - const auto HHH = H * HH; - const auto V = U1 * HH; - const auto t2 = r.square(); - const auto t3 = V + V; - const auto t4 = t2 - HHH; - auto X3 = t4 - t3; - const auto t5 = V - X3; - const auto t6 = S1 * HHH; - const auto t7 = r * t5; - auto Y3 = t7 - t6; - const auto t8 = b.z() * H; - auto Z3 = a.z() * t8; - - // if a is identity then return b - FieldElement::conditional_assign(X3, Y3, Z3, a_is_identity, b.x(), b.y(), b.z()); - - // if b is identity then return a - FieldElement::conditional_assign(X3, Y3, Z3, b_is_identity, a.x(), a.y(), a.z()); - - return Self(X3, Y3, Z3); - } + constexpr static Self add(const Self& a, const Self& b) { return point_add(a, b); } /** * Iterated point doubling */ constexpr Self dbl_n(size_t n) const { - /* - Repeated doubling using an adaptation of Algorithm 3.23 in - "Guide To Elliptic Curve Cryptography" (Hankerson, Menezes, Vanstone) - - Curiously the book gives the algorithm only for A == -3, but - the largest gains come from applying it to the generic A case, - where it saves 2 squarings per iteration. - - For A == 0 - Pay 1*2 + 1half to save n*(1*4 + 1*8) - - For A == -3: - Pay 2S + 1*2 + 1half to save n*(1A + 1*4 + 1*8) + 1M - - For generic A: - Pay 2S + 1*2 + 1half to save n*(2S + 1*4 + 1*8) - */ - - if constexpr(Self::A_is_zero) { - auto nx = x(); - auto ny = y().mul2(); - auto nz = z(); - - while(n > 0) { - const auto ny2 = ny.square(); - const auto ny4 = ny2.square(); - const auto t1 = nx.square().mul3(); - const auto t2 = nx * ny2; - nx = t1.square() - t2.mul2(); - nz *= ny; - ny = t1 * (t2 - nx).mul2() - ny4; - n--; - } - return Self(nx, ny.div2(), nz); + if constexpr(Self::A_is_minus_3) { + return dbl_n_a_minus_3(*this, n); + } else if constexpr(Self::A_is_zero) { + return dbl_n_a_zero(*this, n); } else { - auto nx = x(); - auto ny = y().mul2(); - auto nz = z(); - auto w = nz.square().square(); - - if constexpr(!Self::A_is_minus_3) { - w *= A; - } - - while(n > 0) { - const auto ny2 = ny.square(); - const auto ny4 = ny2.square(); - FieldElement t1; - if constexpr(Self::A_is_minus_3) { - t1 = (nx.square() - w).mul3(); - } else { - t1 = nx.square().mul3() + w; - } - const auto t2 = nx * ny2; - nx = t1.square() - t2.mul2(); - nz *= ny; - ny = t1 * (t2 - nx).mul2() - ny4; - n--; - if(n > 0) { - w *= ny4; - } - } - return Self(nx, ny.div2(), nz); + return dbl_n_generic(*this, A, n); } } @@ -1133,43 +1119,13 @@ * Point doubling */ constexpr Self dbl() const { - /* - Using https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#doubling-dbl-1998-cmo-2 - - Cost (generic A): 4M + 6S + 4A + 2*2 + 1*3 + 1*4 + 1*8 - Cost (A == -3): 4M + 4S + 5A + 2*2 + 1*3 + 1*4 + 1*8 - Cost (A == 0): 3M + 4S + 3A + 2*2 + 1*3 + 1*4 + 1*8 - */ - - FieldElement m = FieldElement::zero(); - if constexpr(Self::A_is_minus_3) { - /* - if a == -3 then - 3*x^2 + a*z^4 == 3*x^2 - 3*z^4 == 3*(x^2-z^4) == 3*(x-z^2)*(x+z^2) - - Cost: 1M + 1S + 2A + 1*3 - */ - const auto z2 = z().square(); - m = (x() - z2).mul3() * (x() + z2); + return dbl_a_minus_3(*this); } else if constexpr(Self::A_is_zero) { - // If a == 0 then 3*x^2 + a*z^4 == 3*x^2 - // Cost: 1S + 1*3 - m = x().square().mul3(); + return dbl_a_zero(*this); } else { - // Cost: 1M + 3S + 1A + 1*3 - const auto z2 = z().square(); - m = x().square().mul3() + A * z2.square(); + return dbl_generic(*this, A); } - - // Remaining cost: 3M + 3S + 3A + 2*2 + 1*4 + 1*8 - const auto y2 = y().square(); - const auto s = x().mul4() * y2; - const auto nx = m.square() - s.mul2(); - const auto ny = m * (s - nx) - y2.square().mul8(); - const auto nz = y().mul2() * z(); - - return Self(nx, ny, nz); } /** @@ -1187,6 +1143,7 @@ // In certain contexts we may be called with a Null_RNG; in that case the // caller is accepting that randomization will not occur + // Conditional ok: caller's RNG state (seeded vs not) is presumed public if(rng.is_seeded()) { auto r = FieldElement::random(rng); @@ -1252,7 +1209,7 @@ }; /** -* This exists soley as a hack which somewhat reduces symbol lengths +* This exists solely as a hack which somewhat reduces symbol lengths */ template PI> struct IntParams { @@ -1272,6 +1229,8 @@ public: typedef typename Params::W W; + typedef W WordType; + static constexpr auto PW = Params::PW; static constexpr auto NW = Params::NW; static constexpr auto AW = Params::AW; @@ -1279,15 +1238,17 @@ // Simplifying assumption static_assert(PW.size() == NW.size()); - class ScalarParams final : public IntParams {}; + static constexpr size_t Words = PW.size(); + + class ScalarParams final : public IntParams {}; using Scalar = IntMod>; - class FieldParams final : public IntParams {}; + class FieldParams final : public IntParams {}; using FieldElement = IntMod>; - using AffinePoint = AffineCurvePoint; + using AffinePoint = AffineCurvePoint; using ProjectivePoint = ProjectiveCurvePoint; static constexpr size_t OrderBits = Scalar::BITS; @@ -1306,131 +1267,13 @@ static constexpr bool ValidForSswuHash = (Params::Z != 0 && A.is_nonzero().as_bool() && B.is_nonzero().as_bool() && FieldElement::P_MOD_4 == 3); - static constexpr bool OrderIsLessThanField = bigint_cmp(NW.data(), NW.size(), PW.data(), PW.size()) == -1; -}; - -/** -* Field inversion concept -* -* This concept checks if the FieldElement supports fe_invert2 -*/ -template -concept curve_supports_fe_invert2 = requires(const typename C::FieldElement& fe) { - { C::fe_invert2(fe) } -> std::same_as; -}; - -/** -* Field inversion -* -* Uses the specialized fe_invert2 if available, or otherwise the standard -* (FLT-based) field inversion. -*/ -template -inline constexpr auto invert_field_element(const typename C::FieldElement& fe) { - if constexpr(curve_supports_fe_invert2) { - return C::fe_invert2(fe) * fe; - } else { - return fe.invert(); - } -} - -/** -* Convert a projective point into affine -*/ -template -auto to_affine(const typename C::ProjectivePoint& pt) { - // Not strictly required right? - default should work as long - // as (0,0) is identity and invert returns 0 on 0 - if(pt.is_identity().as_bool()) { - return C::AffinePoint::identity(); - } - - if constexpr(curve_supports_fe_invert2) { - const auto z2_inv = C::fe_invert2(pt.z()); - const auto z3_inv = z2_inv.square() * pt.z(); - return typename C::AffinePoint(pt.x() * z2_inv, pt.y() * z3_inv); - } else { - const auto z_inv = invert_field_element(pt.z()); - const auto z2_inv = z_inv.square(); - const auto z3_inv = z_inv * z2_inv; - return typename C::AffinePoint(pt.x() * z2_inv, pt.y() * z3_inv); - } -} - -/** -* Convert a projective point into affine and return x coordinate only -*/ -template -auto to_affine_x(const typename C::ProjectivePoint& pt) { - if constexpr(curve_supports_fe_invert2) { - return pt.x() * C::fe_invert2(pt.z()); - } else { - const auto z_inv = invert_field_element(pt.z()); - const auto z2_inv = z_inv.square(); - return pt.x() * z2_inv; - } -} - -/** -* Batch projective->affine conversion -*/ -template -auto to_affine_batch(std::span projective) { - typedef typename C::AffinePoint AffinePoint; - typedef typename C::FieldElement FieldElement; - - const size_t N = projective.size(); - std::vector affine(N, AffinePoint::identity()); - - CT::Choice any_identity = CT::Choice::no(); - - for(const auto& pt : projective) { - any_identity = any_identity || pt.is_identity(); - } - - if(N <= 2 || any_identity.as_bool()) { - // If there are identity elements, using the batch inversion gets - // tricky. It can be done, but this should be a rare situation so - // just punt to the serial conversion if it occurs - for(size_t i = 0; i != N; ++i) { - affine[i] = to_affine(projective[i]); - } - } else { - std::vector c(N); - - /* - Batch projective->affine using Montgomery's trick + static constexpr bool OrderIsLessThanField = bigint_cmp(NW.data(), Words, PW.data(), Words) == -1; - See Algorithm 2.26 in "Guide to Elliptic Curve Cryptography" - (Hankerson, Menezes, Vanstone) + /** + * Return (x^3 + A*x + B) mod p */ - - c[0] = projective[0].z(); - for(size_t i = 1; i != N; ++i) { - c[i] = c[i - 1] * projective[i].z(); - } - - auto s_inv = invert_field_element(c[N - 1]); - - for(size_t i = N - 1; i > 0; --i) { - const auto& p = projective[i]; - - const auto z_inv = s_inv * c[i - 1]; - const auto z2_inv = z_inv.square(); - const auto z3_inv = z_inv * z2_inv; - - s_inv = s_inv * p.z(); - - affine[i] = AffinePoint(p.x() * z2_inv, p.y() * z3_inv); - } - - const auto z2_inv = s_inv.square(); - const auto z3_inv = s_inv * z2_inv; - affine[0] = AffinePoint(projective[0].x() * z2_inv, projective[0].y() * z3_inv); - } - - return affine; -} + static constexpr FieldElement x3_ax_b(const FieldElement& x) { return (x.square() + A) * x + B; } +}; /** * Blinded Scalar @@ -1443,75 +1286,47 @@ * an additional precaution to guard against compilers introducing conditional * jumps where not expected. * -* If you would like a "go faster" button, change the BlindingEnabled variable -* below to false. +* If the provided RNG is not seeded, blinding is skipped and the scalar +* is used directly. This allows blinding to be disabled at runtime. */ template class BlindedScalarBits final { private: typedef typename C::W W; - static constexpr bool BlindingEnabled = true; + static constexpr size_t BlindingBits = scalar_blinding_bits(C::OrderBits); - // Decide size of scalar blinding factor based on bitlength of the scalar - // - // This can return any value between 0 and the scalar bit length, as long - // as it is a multiple of the word size. - static constexpr size_t blinding_bits(size_t sb) { - constexpr size_t wb = WordInfo::bits; + static_assert(BlindingBits < C::Scalar::BITS); - static_assert(wb == 32 || wb == 64, "Unexpected W size"); + public: + // Maximum number of bits (used for table sizing) + static constexpr size_t Bits = C::Scalar::BITS + BlindingBits; - if(sb == 521) { - /* - Treat P-521 as if it was a 512 bit field; otherwise it is penalized - by the below computation, using either 160 or 192 bits of blinding - (depending on wb), vs 128 bits used for 512 bit groups. - */ - return blinding_bits(512); - } else { - // For blinding use 1/4 the order, rounded up to the next word - return ((sb / 4 + wb - 1) / wb) * wb; - } - } + size_t bits() const { return m_bits; } + + BlindedScalarBits(const typename C::Scalar& scalar, RandomNumberGenerator& rng) { + if(BlindingBits > 0 && rng.is_seeded()) { + constexpr size_t MaskWords = (BlindingBits + WordInfo::bits - 1) / WordInfo::bits; + constexpr size_t MaskBytes = MaskWords * WordInfo::bytes; - static constexpr size_t BlindingBits = blinding_bits(C::OrderBits); + constexpr size_t n_words = C::Words; - static_assert(BlindingBits % WordInfo::bits == 0); - static_assert(BlindingBits < C::Scalar::BITS); + uint8_t maskb[MaskBytes + (BlindingBits == 0 ? 1 : 0)] = {0}; + rng.randomize(maskb, MaskBytes); - public: - static constexpr size_t Bits = C::Scalar::BITS + (BlindingEnabled ? BlindingBits : 0); - static constexpr size_t Bytes = (Bits + 7) / 8; + W mask[n_words] = {0}; + load_le(mask, maskb, MaskWords); - BlindedScalarBits(const typename C::Scalar& scalar, RandomNumberGenerator& rng) { - if constexpr(BlindingEnabled) { - constexpr size_t mask_words = BlindingBits / WordInfo::bits; - constexpr size_t mask_bytes = mask_words * WordInfo::bytes; - - constexpr size_t n_words = C::NW.size(); - - uint8_t maskb[mask_bytes] = {0}; - if(rng.is_seeded()) { - rng.randomize(maskb, mask_bytes); - } else { - // If we don't have an RNG we don't have many good options. We - // could just omit the blinding entirely, but this changes the - // size of the blinded scalar, which we're expecting otherwise is - // knowable at compile time. So generate a mask by XORing the - // bytes of the scalar together. At worst, it's equivalent to - // omitting the blinding entirely. - - std::array sbytes; - scalar.serialize_to(sbytes); - for(size_t i = 0; i != sbytes.size(); ++i) { - maskb[i % mask_bytes] ^= sbytes[i]; - } + // Mask to exactly BlindingBits + constexpr size_t ExcessBits = MaskWords * WordInfo::bits - BlindingBits; + if constexpr(ExcessBits > 0) { + constexpr W ExcessMask = (static_cast(1) << (WordInfo::bits - ExcessBits)) - 1; + mask[MaskWords - 1] &= ExcessMask; } - W mask[n_words] = {0}; - load_le(mask, maskb, mask_words); - mask[mask_words - 1] |= WordInfo::top_bit; + // Set top and bottom bits of mask + constexpr size_t TopMaskBit = (BlindingBits - 1) % WordInfo::bits; + mask[(BlindingBits - 1) / WordInfo::bits] |= static_cast(1) << TopMaskBit; mask[0] |= 1; W mask_n[2 * n_words] = {0}; @@ -1520,14 +1335,16 @@ // Compute masked scalar s + k*n comba_mul(mask_n, mask, C::NW.data()); - bigint_add2_nc(mask_n, 2 * n_words, sw.data(), sw.size()); + bigint_add2(mask_n, 2 * n_words, sw.data(), sw.size()); std::reverse(mask_n, mask_n + 2 * n_words); m_bytes = store_be>(mask_n); + m_bits = C::Scalar::BITS + BlindingBits; } else { - static_assert(Bytes == C::Scalar::BYTES); - m_bytes.resize(Bytes); - scalar.serialize_to(std::span{m_bytes}.template first()); + // No RNG available, skip blinding + m_bytes.resize(C::Scalar::BYTES); + scalar.serialize_to(std::span{m_bytes}.template first()); + m_bits = C::Scalar::BITS; } CT::poison(m_bytes.data(), m_bytes.size()); @@ -1539,13 +1356,18 @@ } ~BlindedScalarBits() { - secure_scrub_memory(m_bytes.data(), m_bytes.size()); + secure_zeroize_buffer(m_bytes.data(), m_bytes.size()); CT::unpoison(m_bytes.data(), m_bytes.size()); } + BlindedScalarBits(const BlindedScalarBits& other) = delete; + BlindedScalarBits(BlindedScalarBits&& other) = delete; + BlindedScalarBits& operator=(const BlindedScalarBits& other) = delete; + BlindedScalarBits& operator=(BlindedScalarBits&& other) = delete; + private: - // TODO this could be a fixed size array std::vector m_bytes; + size_t m_bits; }; template @@ -1553,7 +1375,7 @@ public: static constexpr size_t Bits = C::Scalar::BITS; - UnblindedScalarBits(const typename C::Scalar& scalar) { scalar.serialize_to(std::span{m_bytes}); } + explicit UnblindedScalarBits(const typename C::Scalar& scalar) { scalar.serialize_to(std::span{m_bytes}); } size_t get_window(size_t offset) const { // Extract a WindowBits sized window out of s, depending on offset. @@ -1564,43 +1386,6 @@ std::array m_bytes; }; -/** -* Base point precomputation table -* -* This algorithm works by precomputing a set of points such that -* the online phase of the point multiplication can be effected by -* a sequence of point additions. -* -* The tables, even for W = 1, are large and costly to precompute, so -* this is only used for the base point. -* -* The online phase of the algorithm uess `ceil(SB/W)` additions, -* and no point doublings. The table is of size -* `ceil(SB + W - 1)/W * ((1 << W) - 1)` -* where SB is the bit length of the (blinded) scalar. -* -* Each window of the scalar is associated with a window in the table. -* The table windows are unique to that offset within the scalar. -* -* The simplest version to understand is when W = 1. There the table -* consists of [P, 2*P, 4*P, ..., 2^N*P] where N is the bit length of -* the group order. The online phase consists of conditionally adding -* table[i] depending on if bit i of the scalar is set or not. -* -* When W = 2, the scalar is examined 2 bits at a time, and the table -* for a window index `I` is [(2^I)*P, (2^(I+1))*P, (2^I+2^(I+1))*P]. -* -* This extends similarly for larger W -* -* At a certain point, the side channel silent table lookup becomes the -* dominating cost -* -* For all W, each window in the table has an implicit element of -* the identity element which is used if the scalar bits were all zero. -* This is omitted to save space; AffinePoint::ct_select is designed -* to assist in this by returning the identity element if its index -* argument is zero, or otherwise it returns table[idx - 1] -*/ template class PrecomputedBaseMulTable final { public: @@ -1611,73 +1396,17 @@ static constexpr size_t WindowBits = W; static_assert(WindowBits >= 1 && WindowBits <= 8); - using BlindedScalar = BlindedScalarBits; - - static constexpr size_t Windows = (BlindedScalar::Bits + WindowBits - 1) / WindowBits; + // W+1 bit extraction windows for Booth recoding overlap + using BlindedScalar = BlindedScalarBits; - static_assert(Windows > 1); - - // 2^W elements, less the identity element - static constexpr size_t WindowElements = (1 << WindowBits) - 1; - - static constexpr size_t TableSize = Windows * WindowElements; - - PrecomputedBaseMulTable(const AffinePoint& p) : m_table{} { - std::vector table; - table.reserve(TableSize); - - auto accum = ProjectivePoint::from_affine(p); - - for(size_t i = 0; i != TableSize; i += WindowElements) { - table.push_back(accum); - - for(size_t j = 1; j != WindowElements; ++j) { - if(j % 2 == 1) { - table.emplace_back(table[i + j / 2].dbl()); - } else { - table.emplace_back(table[i + j - 1] + table[i]); - } - } - - accum = table[i + (WindowElements / 2)].dbl(); - } - - m_table = to_affine_batch(table); - } + // +1 for Booth carry: if the top window's sign bit is set, the + // carry propagates into an extra window + explicit PrecomputedBaseMulTable(const AffinePoint& p) : + m_table(basemul_booth_setup(p, BlindedScalar::Bits + 1)) {} ProjectivePoint mul(const Scalar& s, RandomNumberGenerator& rng) const { - const BlindedScalar bits(s, rng); - - // TODO: C++23 - use std::mdspan to access m_table - auto table = std::span{m_table}; - - auto accum = [&]() { - const size_t w_0 = bits.get_window(0); - const auto tbl_0 = table.first(WindowElements); - auto pt = ProjectivePoint::from_affine(AffinePoint::ct_select(tbl_0, w_0)); - CT::poison(pt); - pt.randomize_rep(rng); - return pt; - }(); - - for(size_t i = 1; i != Windows; ++i) { - const size_t w_i = bits.get_window(WindowBits * i); - const auto tbl_i = table.subspan(WindowElements * i, WindowElements); - - /* - None of these additions can be doublings, because in each iteration, the - discrete logarithms of the points we're selecting out of the table are - larger than the largest possible dlog of accum. - */ - accum += AffinePoint::ct_select(tbl_i, w_i); - - if(i <= 3) { - accum.randomize_rep(rng); - } - } - - CT::unpoison(accum); - return accum; + const BlindedScalar scalar(s, rng); + return basemul_booth_exec(m_table, scalar, rng); } private: @@ -1708,99 +1437,103 @@ // 2^W elements, less the identity element static constexpr size_t TableSize = (1 << WindowBits) - 1; - WindowedMulTable(const AffinePoint& p) : m_table{} { - std::vector table; - table.reserve(TableSize); + explicit WindowedMulTable(const AffinePoint& p) : m_table(varpoint_setup(p)) {} - table.push_back(ProjectivePoint::from_affine(p)); - for(size_t i = 1; i != TableSize; ++i) { - if(i % 2 == 1) { - table.push_back(table[i / 2].dbl()); - } else { - table.push_back(table[i - 1] + p); - } + ProjectivePoint mul(const Scalar& s, RandomNumberGenerator& rng) const { + const BlindedScalar bits(s, rng); + return varpoint_exec(m_table, bits, rng); + } + + private: + std::vector m_table; +}; + +/** +* Precomputed point multiplication table with Booth +*/ +template +class WindowedBoothMulTable final { + public: + typedef typename C::Scalar Scalar; + typedef typename C::AffinePoint AffinePoint; + typedef typename C::ProjectivePoint ProjectivePoint; + + static constexpr size_t TableBits = W; + static_assert(TableBits >= 1 && TableBits <= 7); + + static constexpr size_t WindowBits = TableBits + 1; + + using BlindedScalar = BlindedScalarBits; + + static constexpr size_t compute_full_windows(size_t sb, size_t wb) { + if(sb % wb == 0) { + return (sb - 1) / wb; + } else { + return sb / wb; } + } - m_table = to_affine_batch(table); + static constexpr size_t compute_initial_shift(size_t sb, size_t wb) { + if(sb % wb == 0) { + return wb; + } else { + return sb - (sb / wb) * wb; + } } + // 2^W elements [1*P, 2*P, ..., 2^W*P] + static constexpr size_t TableSize = 1 << TableBits; + + explicit WindowedBoothMulTable(const AffinePoint& p) : m_table(varpoint_setup(p)) {} + ProjectivePoint mul(const Scalar& s, RandomNumberGenerator& rng) const { const BlindedScalar bits(s, rng); - auto accum = [&]() { - const size_t w_0 = bits.get_window((Windows - 1) * WindowBits); - // Guaranteed because we set the high bit of the randomizer - BOTAN_DEBUG_ASSERT(w_0 != 0); - auto pt = ProjectivePoint::from_affine(AffinePoint::ct_select(m_table, w_0)); - CT::poison(pt); - pt.randomize_rep(rng); - return pt; - }(); + const size_t scalar_bits = bits.bits(); + const size_t full_windows = compute_full_windows(scalar_bits + 1, WindowBits); + const size_t initial_shift = compute_initial_shift(scalar_bits + 1, WindowBits); - for(size_t i = 1; i != Windows; ++i) { - accum = accum.dbl_n(WindowBits); - const size_t w_i = bits.get_window((Windows - i - 1) * WindowBits); + BOTAN_DEBUG_ASSERT(full_windows * WindowBits + initial_shift == scalar_bits + 1); + BOTAN_DEBUG_ASSERT(initial_shift > 0); - /* - This point addition cannot be a doubling (except once) + auto accum = ProjectivePoint::identity(); + CT::poison(accum); - Consider the sequence of points that are operated on, and specifically - their discrete logarithms. We start out at the point at infinity - (dlog 0) and then add the initial window which is precisely P*w_0 - - We then perform WindowBits doublings, so accum's dlog at the point - of the addition in the first iteration of the loop (when i == 1) is - at least 2^W * w_0. - - Since we know w_0 > 0, then in every iteration of the loop, accums - dlog will always be greater than the dlog of the table element we - just looked up (something between 0 and 2^W-1), and thus the - addition into accum cannot be a doubling. - - However due to blinding this argument fails, since we perform - multiplications using a scalar that is larger than the group - order. In this case it's possible that the dlog of accum becomes - `order + x` (or, effectively, `x`) and `x` is smaller than 2^W. - In this case, a doubling may occur. Future iterations of the loop - cannot be doublings by the same argument above. Since the blinding - factor is always less than the group order (substantially so), - it is not possible for the dlog of accum to overflow a second time. - */ - accum += AffinePoint::ct_select(m_table, w_i); + for(size_t i = 0; i != full_windows; ++i) { + const size_t idx = scalar_bits - initial_shift - WindowBits * i; + + const size_t w_i = bits.get_window(idx); + const auto [tidx, tneg] = booth_recode(w_i); + + // Conditional ok: loop iteration count is public + if(i == 0) { + accum = ProjectivePoint::from_affine(m_table.ct_select(tidx)); + accum.conditional_assign(tneg, accum.negate()); + } else { + accum = ProjectivePoint::add_or_sub(accum, m_table.ct_select(tidx), tneg); + } + + accum = accum.dbl_n(WindowBits); + // Conditional ok: loop iteration count is public if(i <= 3) { accum.randomize_rep(rng); } } + // final window (note one bit shorter than previous reads) + const size_t w_l = bits.get_window(0) & ((1 << WindowBits) - 1); + const auto [tidx, tneg] = booth_recode(w_l << 1); + accum = ProjectivePoint::add_or_sub(accum, m_table.ct_select(tidx), tneg); + CT::unpoison(accum); return accum; } private: - std::vector m_table; + AffinePointTable m_table; }; -/** -* Effect 2-ary multiplication ie x*G + y*H -* -* This is done using a windowed variant of what is usually called -* Shamir's trick. -* -* The W = 1 case is simple; we precompute an extra point GH = G + H, -* and then examine 1 bit in each of x and y. If one or the other bits -* are set then add G or H resp. If both bits are set, add GH. -* -* The example below is a precomputed table for W=2. The flattened table -* begins at (x_i,y_i) = (1,0), i.e. the identity element is omitted. -* The indices in each cell refer to the cell's location in m_table. -* -* x-> 0 1 2 3 -* 0 |/ (ident) |0 x |1 2x |2 3x | -* 1 |3 y |4 x+y |5 2x+y |6 3x+y | -* y = 2 |7 2y |8 x+2y |9 2(x+y) |10 3x+2y | -* 3 |11 3y |12 x+3y |13 2x+3y |14 3x+3y | -*/ template class WindowedMul2Table final { public: @@ -1811,95 +1544,37 @@ typedef typename C::AffinePoint AffinePoint; typedef typename C::ProjectivePoint ProjectivePoint; - static constexpr size_t WindowBits = W; - - static constexpr size_t WindowSize = (1 << WindowBits); - - // 2^(2*W) elements, less the identity element - static constexpr size_t TableSize = (1 << (2 * WindowBits)) - 1; - - WindowedMul2Table(const AffinePoint& x, const AffinePoint& y) { - std::vector table; - table.reserve(TableSize); - - for(size_t i = 0; i != TableSize; ++i) { - const size_t t_i = (i + 1); - const size_t x_i = t_i % WindowSize; - const size_t y_i = (t_i >> WindowBits) % WindowSize; - - // Returns x_i * x + y_i * y - auto next_tbl_e = [&]() { - if(x_i % 2 == 0 && y_i % 2 == 0) { - // Where possible using doubling (eg indices 1, 7, 9 in - // the table above) - return table[(t_i / 2) - 1].dbl(); - } else if(x_i > 0 && y_i > 0) { - // A combination of x and y - if(x_i == 1) { - return x + table[(y_i << WindowBits) - 1]; - } else if(y_i == 1) { - return table[x_i - 1] + y; - } else { - return table[x_i - 1] + table[(y_i << WindowBits) - 1]; - } - } else if(x_i > 0 && y_i == 0) { - // A multiple of x without a y component - if(x_i == 1) { - // Just x - return ProjectivePoint::from_affine(x); - } else { - // x * x_{i-1} - return x + table[x_i - 1 - 1]; - } - } else if(x_i == 0 && y_i > 0) { - if(y_i == 1) { - // Just y - return ProjectivePoint::from_affine(y); - } else { - // y * y_{i-1} - return y + table[((y_i - 1) << WindowBits) - 1]; - } - } else { - BOTAN_ASSERT_UNREACHABLE(); - } - }; - - table.emplace_back(next_tbl_e()); - } - - m_table = to_affine_batch(table); - } + WindowedMul2Table(const AffinePoint& p, const AffinePoint& q) : m_table(mul2_setup(p, q)) {} /** * Constant time 2-ary multiplication */ ProjectivePoint mul2(const Scalar& s1, const Scalar& s2, RandomNumberGenerator& rng) const { - using BlindedScalar = BlindedScalarBits; + using BlindedScalar = BlindedScalarBits; + const BlindedScalar bits1(s1, rng); + const BlindedScalar bits2(s2, rng); - BlindedScalar bits1(s1, rng); - BlindedScalar bits2(s2, rng); - - constexpr size_t Windows = (BlindedScalar::Bits + WindowBits - 1) / WindowBits; + return mul2_exec(m_table, bits1, bits2, rng); + } - auto accum = ProjectivePoint::identity(); + private: + AffinePointTable m_table; +}; - for(size_t i = 0; i != Windows; ++i) { - if(i > 0) { - accum = accum.dbl_n(WindowBits); - } +template +class VartimeMul2Table final { + public: + // We look at W bits of each scalar per iteration + static_assert(W >= 1 && W <= 4); - const size_t w_1 = bits1.get_window((Windows - i - 1) * WindowBits); - const size_t w_2 = bits2.get_window((Windows - i - 1) * WindowBits); - const size_t window = w_1 + (w_2 << WindowBits); - accum += AffinePoint::ct_select(m_table, window); + static constexpr size_t WindowBits = W; - if(i <= 3) { - accum.randomize_rep(rng); - } - } + using Scalar = typename C::Scalar; + using AffinePoint = typename C::AffinePoint; + using ProjectivePoint = typename C::ProjectivePoint; - return accum; - } + VartimeMul2Table(const AffinePoint& p, const AffinePoint& q) : + m_table(to_affine_batch(mul2_setup(p, q))) {} /** * Variable time 2-ary multiplication @@ -1916,18 +1591,40 @@ const UnblindedScalarBits bits1(s1); const UnblindedScalarBits bits2(s2); - auto accum = ProjectivePoint::identity(); + const bool s1_is_zero = s1.is_zero().as_bool(); + const bool s2_is_zero = s2.is_zero().as_bool(); - for(size_t i = 0; i != Windows; ++i) { - if(i > 0) { - accum = accum.dbl_n(WindowBits); + // Conditional ok: this function is variable time + if(s1_is_zero && s2_is_zero) { + return ProjectivePoint::identity(); + } + + auto [w_0, first_nonempty_window] = [&]() { + for(size_t i = 0; i != Windows; ++i) { + const size_t w_1 = bits1.get_window((Windows - i - 1) * WindowBits); + const size_t w_2 = bits2.get_window((Windows - i - 1) * WindowBits); + const size_t window = w_1 + (w_2 << WindowBits); + // Conditional ok: this function is variable time + if(window > 0) { + return std::make_pair(window, i); + } } + // We checked for s1 == s2 == 0 above, so we must see a window eventually + BOTAN_ASSERT_UNREACHABLE(); + }(); + + BOTAN_ASSERT_NOMSG(w_0 > 0); + auto accum = ProjectivePoint::from_affine(m_table[w_0 - 1]); + + for(size_t i = first_nonempty_window + 1; i < Windows; ++i) { + accum = accum.dbl_n(WindowBits); const size_t w_1 = bits1.get_window((Windows - i - 1) * WindowBits); const size_t w_2 = bits2.get_window((Windows - i - 1) * WindowBits); const size_t window = w_1 + (w_2 << WindowBits); + // Conditional ok: this function is variable time if(window > 0) { accum += m_table[window - 1]; } @@ -1981,25 +1678,19 @@ const auto z2_u4 = z_u2.square(); const auto tv1 = invert_field_element(z2_u4 + z_u2); auto x1 = SSWU_C1() * (C::FieldElement::one() + tv1); - C::FieldElement::conditional_assign(x1, tv1.is_zero(), SSWU_C2()); - const auto gx1 = C::AffinePoint::x3_ax_b(x1); - + x1.conditional_assign(tv1.is_zero(), SSWU_C2()); const auto x2 = z_u2 * x1; - const auto gx2 = C::AffinePoint::x3_ax_b(x2); - // Will be zero if gx1 is not a square - const auto [gx1_sqrt, gx1_is_square] = gx1.sqrt(); - - auto x = x2; // By design one of gx1 and gx2 must be a quadratic residue - auto y = gx2.sqrt().first; + const CT::Option y1 = sqrt_field_element(C::x3_ax_b(x1)); + const CT::Option y2 = sqrt_field_element(C::x3_ax_b(x2)); - C::FieldElement::conditional_assign(x, y, gx1_is_square, x1, gx1_sqrt); + const auto use_y1 = y1.has_value(); - const auto flip_y = y.is_even() != u.is_even(); - C::FieldElement::conditional_assign(y, flip_y, y.negate()); + auto x = C::FieldElement::choose(use_y1, x1, x2); + auto y = C::FieldElement::choose(use_y1, y1.value_or(C::FieldElement::zero()), y2.value_or(C::FieldElement::zero())); - auto pt = typename C::AffinePoint(x, y); + auto pt = typename C::AffinePoint(x, y.correct_sign(u.is_even())); CT::unpoison(pt); return pt; @@ -2008,43 +1699,38 @@ /** * Hash to curve (SSWU); RFC 9380 * -* Hashes the input using XMD and the specified hash function, producing either one or -* two field elements `u`/(`u0`,`u1`) resp. These are then mapped to curve point(s) -* using SSWU, and if a pair of points were generated these are combined using point -* addition. +* This is the Simplified Shallue-van de Woestijne-Ulas (SSWU) map. +* +* The parameter expand_message models the function of RFC 9380 and is provided +* by higher levels. For the curves implemented here it will typically be XMD, +* but could also be an XOF (expand_message_xof) or a MHF like Argon2. +* +* For details see RFC 9380 sections 3, 5.2 and 6.6.2. */ -template +template > ExpandMsg> requires C::ValidForSswuHash -inline auto hash_to_curve_sswu(std::string_view hash, std::span pw, std::span dst) +inline auto hash_to_curve_sswu(const ExpandMsg& expand_message) -> std::conditional_t { -#if defined(BOTAN_HAS_XMD) constexpr size_t SecurityLevel = (C::OrderBits + 1) / 2; constexpr size_t L = (C::PrimeFieldBits + SecurityLevel + 7) / 8; constexpr size_t Cnt = RO ? 2 : 1; - std::array xmd; - - expand_message_xmd(hash, xmd, pw, dst); + std::array uniform_bytes = {}; + expand_message(uniform_bytes); if constexpr(RO) { - const auto u0 = C::FieldElement::from_wide_bytes(std::span(xmd.data(), L)); - const auto u1 = C::FieldElement::from_wide_bytes(std::span(xmd.data() + L, L)); + const auto u0 = C::FieldElement::from_wide_bytes(std::span(uniform_bytes.data(), L)); + const auto u1 = C::FieldElement::from_wide_bytes(std::span(uniform_bytes.data() + L, L)); auto accum = C::ProjectivePoint::from_affine(map_to_curve_sswu(u0)); accum += map_to_curve_sswu(u1); return accum; } else { - const auto u = C::FieldElement::from_wide_bytes(std::span(xmd.data(), L)); + const auto u = C::FieldElement::from_wide_bytes(std::span(uniform_bytes.data(), L)); return map_to_curve_sswu(u); } -#else - BOTAN_UNUSED(hash, pw, dst); - throw Not_Implemented("Hash to curve not available due to missing XMD"); -#endif } -} // namespace - } // namespace Botan #endif diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_impl/pcurves_solinas.h botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_impl/pcurves_solinas.h --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_impl/pcurves_solinas.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_impl/pcurves_solinas.h 2026-05-07 01:38:28.000000000 +0000 @@ -47,7 +47,7 @@ static constexpr size_t N32 = N * (WordInfo::bits / 32); - constexpr SolinasAccum(std::array& r) : m_r(r), m_S(0), m_idx(0) {} + constexpr explicit SolinasAccum(std::array& r) : m_r(r) {} constexpr void accum(int64_t v) { BOTAN_DEBUG_ASSERT(m_idx < N32); @@ -73,10 +73,30 @@ private: std::array& m_r; - int64_t m_S; - size_t m_idx; + int64_t m_S = 0; + size_t m_idx = 0; }; +/** +* Set r to r - C. Then if r < 0, add P to r +*/ +template +constexpr inline void solinas_correct_redc(std::array& r, const std::array& P, const std::array& C) { + W borrow = 0; + for(size_t i = 0; i != N; ++i) { + r[i] = word_sub(r[i], C[i], &borrow); + } + + // borrow is either 0 or 1, perfect for setting up a mask without extra work + const W mask = CT::value_barrier(0 - borrow); + + W carry = 0; + + for(size_t i = 0; i != N; ++i) { + r[i] = word_add(r[i], P[i] & mask, &carry); + } +} + } // namespace Botan #endif diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_impl/pcurves_util.h botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_impl/pcurves_util.h --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_impl/pcurves_util.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_impl/pcurves_util.h 2026-05-07 01:38:28.000000000 +0000 @@ -1,5 +1,5 @@ /* -* (C) 2024 Jack Lloyd +* (C) 2024,2025 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -12,8 +12,6 @@ namespace Botan { -namespace { - template inline consteval std::array reduce_mod(const std::array& x, const std::array& p) { std::array r = {0}; @@ -29,25 +27,27 @@ const bool x_b = (x[b_word] >> b_bit) & 1; shift_left<1>(r); + // Conditional ok: this function is consteval if(x_b) { r[0] += 1; } const W carry = bigint_sub3(t.data(), r.data(), N + 1, p.data(), N); + // Conditional ok: this function is consteval if(carry == 0) { std::swap(r, t); } } - std::array rs; + std::array rs = {}; copy_mem(rs, std::span{r}.template first()); return rs; } template inline consteval std::array montygomery_r(const std::array& p) { - std::array x = {0}; + std::array x = {}; x[N] = 1; return reduce_mod(x, p); } @@ -56,7 +56,7 @@ inline consteval std::array mul_mod(const std::array& x, const std::array& y, const std::array& p) { - std::array z; + std::array z = {}; comba_mul(z.data(), x.data(), y.data()); return reduce_mod(z, p); } @@ -65,7 +65,8 @@ inline constexpr auto monty_redc_pdash1(const std::array& z, const std::array& p) -> std::array { static_assert(N >= 1); - std::array ws; + std::array ws; // NOLINT(*-member-init); + std::array r; // NOLINT(*-member-init) word3 accum; @@ -99,7 +100,6 @@ // w1 is the final part, which is not stored in the workspace const W w1 = accum.extract(); - std::array r; bigint_monty_maybe_sub(r.data(), w1, ws.data(), p.data()); return r; @@ -110,7 +110,29 @@ -> std::array { static_assert(N >= 1); - std::array ws; + std::array ws; // NOLINT(*-member-init) + std::array r; // NOLINT(*-member-init) + + // Conditional ok: the parameter size is public + if(!std::is_constant_evaluated()) { + // This range ensures we cover fields of 256, 384 and 512 bits for both 32 and 64 bit words + if constexpr(N == 4) { + bigint_monty_redc_4(r.data(), z.data(), p.data(), p_dash, ws.data()); + return r; + } else if constexpr(N == 6) { + bigint_monty_redc_6(r.data(), z.data(), p.data(), p_dash, ws.data()); + return r; + } else if constexpr(N == 8) { + bigint_monty_redc_8(r.data(), z.data(), p.data(), p_dash, ws.data()); + return r; + } else if constexpr(N == 12) { + bigint_monty_redc_12(r.data(), z.data(), p.data(), p_dash, ws.data()); + return r; + } else if constexpr(N == 16) { + bigint_monty_redc_16(r.data(), z.data(), p.data(), p_dash, ws.data()); + return r; + } + } word3 accum; @@ -144,7 +166,6 @@ // w1 is the final part, which is not stored in the workspace const W w1 = accum.extract(); - std::array r; bigint_monty_maybe_sub(r.data(), w1, ws.data(), p.data()); return r; @@ -154,7 +175,7 @@ inline consteval std::array p_minus(const std::array& p) { // TODO combine into p_plus_x_over_y<-1, 1> static_assert(X > 0); - std::array r; + std::array r{}; W x = X; bigint_sub3(r.data(), p.data(), N, &x, 1); std::reverse(r.begin(), r.end()); @@ -164,8 +185,8 @@ template inline consteval std::array p_plus_1_over_4(const std::array& p) { const W one = 1; - std::array r; - bigint_add3_nc(r.data(), p.data(), N, &one, 1); + std::array r{}; + bigint_add3(r.data(), p.data(), N, &one, 1); shift_right<2>(r); std::reverse(r.begin(), r.end()); return r; @@ -174,7 +195,7 @@ template inline consteval std::array p_minus_1_over_2(const std::array& p) { const W one = 1; - std::array r; + std::array r{}; bigint_sub3(r.data(), p.data(), N, &one, 1); shift_right<1>(r); std::reverse(r.begin(), r.end()); @@ -186,7 +207,7 @@ const W one = 1; std::array r = p; shift_right<1>(r); - bigint_add2_nc(r.data(), N, &one, 1); + bigint_add2(r.data(), N, &one, 1); return r; } @@ -201,6 +222,7 @@ for(;;) { // If we found another one bit past the first, stop + // Conditional ok: this function is consteval if(c2[0] % 2 == 1) { break; } @@ -234,6 +256,7 @@ auto is_square = c.is_zero() || c.is_one(); + // Conditional ok: this function is consteval if(!is_square.as_bool()) { return z; } @@ -252,6 +275,7 @@ size_t b = WordInfo::bits * N; + // Conditional ok: this function is consteval while(get_bit(b - 1) == 0) { b -= 1; } @@ -287,33 +311,6 @@ return r; } -// Extract a WindowBits sized window out of s, depending on offset. -template -constexpr size_t read_window_bits(std::span words, size_t offset) { - static_assert(WindowBits >= 1 && WindowBits <= 7); - - constexpr uint8_t WindowMask = static_cast(1 << WindowBits) - 1; - - constexpr size_t W_bits = sizeof(W) * 8; - const auto bit_shift = offset % W_bits; - const auto word_offset = words.size() - 1 - (offset / W_bits); - - const bool single_byte_window = bit_shift <= (W_bits - WindowBits) || word_offset == 0; - - const auto w0 = words[word_offset]; - - if(single_byte_window) { - return (w0 >> bit_shift) & WindowMask; - } else { - // Otherwise we must join two words and extract the result - const auto w1 = words[word_offset - 1]; - const auto combined = ((w0 >> bit_shift) | (w1 << (W_bits - bit_shift))); - return combined & WindowMask; - } -} - -} // namespace - } // namespace Botan #endif diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_impl/pcurves_wrap.h botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_impl/pcurves_wrap.h --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_impl/pcurves_wrap.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_impl/pcurves_wrap.h 2026-05-07 01:38:28.000000000 +0000 @@ -7,6 +7,7 @@ #ifndef BOTAN_PCURVES_WRAP_H_ #define BOTAN_PCURVES_WRAP_H_ +#include #include #include @@ -25,11 +26,6 @@ template class PrimeOrderCurveImpl final : public PrimeOrderCurve { public: - static constexpr size_t BasePointWindowBits = 5; - static constexpr size_t VarPointWindowBits = 4; - static constexpr size_t Mul2PrecompWindowBits = 3; - static constexpr size_t Mul2WindowBits = 2; - static_assert(C::OrderBits <= PrimeOrderCurve::MaximumBitLength); static_assert(C::PrimeFieldBits <= PrimeOrderCurve::MaximumBitLength); @@ -44,15 +40,17 @@ } ProjectivePoint mul(const AffinePoint& pt, const Scalar& scalar, RandomNumberGenerator& rng) const override { - auto tbl = WindowedMulTable(from_stash(pt)); + auto tbl = WindowedBoothMulTable(from_stash(pt)); return stash(tbl.mul(from_stash(scalar), rng)); } secure_vector mul_x_only(const AffinePoint& pt, const Scalar& scalar, RandomNumberGenerator& rng) const override { - auto tbl = WindowedMulTable(from_stash(pt)); - auto pt_x = to_affine_x(tbl.mul(from_stash(scalar), rng)); + auto tbl = WindowedBoothMulTable(from_stash(pt)); + auto result = tbl.mul(from_stash(scalar), rng); + BOTAN_STATE_CHECK(!result.is_identity().as_bool()); + auto pt_x = to_affine_x(result); secure_vector x_bytes(C::FieldElement::BYTES); pt_x.serialize_to(std::span{x_bytes}); return x_bytes; @@ -66,14 +64,9 @@ m_table(x, y) {} private: - WindowedMul2Table m_table; + VartimeMul2Table m_table; }; - std::unique_ptr mul2_setup(const AffinePoint& p, - const AffinePoint& q) const override { - return std::make_unique(from_stash(p), from_stash(q)); - } - std::unique_ptr mul2_setup_g(const AffinePoint& q) const override { return std::make_unique(C::G, from_stash(q)); } @@ -99,7 +92,7 @@ const AffinePoint& q, const Scalar& y, RandomNumberGenerator& rng) const override { - WindowedMul2Table tbl(from_stash(p), from_stash(q)); + const WindowedMul2Table tbl(from_stash(p), from_stash(q)); auto pt = tbl.mul2(from_stash(x), from_stash(y), rng); if(pt.is_identity().as_bool()) { return {}; @@ -131,11 +124,11 @@ * With overwhelming probability, this conversion is correct. The * only time it is not is in the extremely unlikely case where the * signer actually reduced the x coordinate modulo the group order. - * That is handled seperately in a second step. + * That is handled separately in a second step. */ const auto z2 = pt.z().square(); - std::array v_bytes; + std::array v_bytes{}; from_stash(v).serialize_to(v_bytes); if(const auto fe_v = C::FieldElement::deserialize(v_bytes)) { @@ -187,7 +180,8 @@ Scalar base_point_mul_x_mod_order(const Scalar& scalar, RandomNumberGenerator& rng) const override { auto pt = m_mul_by_g.mul(from_stash(scalar), rng); - std::array x_bytes; + BOTAN_STATE_CHECK(!pt.is_identity().as_bool()); + std::array x_bytes{}; to_affine_x(pt).serialize_to(std::span{x_bytes}); // Reduction might be required (if unlikely) return stash(C::Scalar::from_wide_bytes(std::span{x_bytes})); @@ -196,21 +190,19 @@ AffinePoint generator() const override { return stash(C::G); } AffinePoint point_to_affine(const ProjectivePoint& pt) const override { - return stash(to_affine(from_stash(pt))); - } + auto affine = to_affine(from_stash(pt)); - ProjectivePoint point_to_projective(const AffinePoint& pt) const override { - return stash(C::ProjectivePoint::from_affine(from_stash(pt))); - } + const auto y2 = affine.y().square(); + const auto x3_ax_b = C::x3_ax_b(affine.x()); + const auto valid_point = affine.is_identity() || (y2 == x3_ax_b); - ProjectivePoint point_double(const ProjectivePoint& pt) const override { return stash(from_stash(pt).dbl()); } + BOTAN_ASSERT(valid_point.as_bool(), "Computed point is on the curve"); - ProjectivePoint point_add(const ProjectivePoint& a, const ProjectivePoint& b) const override { - return stash(from_stash(a) + from_stash(b)); + return stash(affine); } - ProjectivePoint point_add_mixed(const ProjectivePoint& a, const AffinePoint& b) const override { - return stash(from_stash(a) + from_stash(b)); + ProjectivePoint point_add(const AffinePoint& a, const AffinePoint& b) const override { + return stash(C::ProjectivePoint::from_affine(from_stash(a)) + from_stash(b)); } AffinePoint point_negate(const AffinePoint& pt) const override { return stash(from_stash(pt).negate()); } @@ -224,17 +216,6 @@ from_stash(pt).serialize_to(bytes.subspan<0, C::AffinePoint::BYTES>()); } - void serialize_point_compressed(std::span bytes, const AffinePoint& pt) const override { - BOTAN_ARG_CHECK(bytes.size() == C::AffinePoint::COMPRESSED_BYTES, - "Invalid length for serialize_point_compressed"); - from_stash(pt).serialize_compressed_to(bytes.subspan<0, C::AffinePoint::COMPRESSED_BYTES>()); - } - - void serialize_point_x(std::span bytes, const AffinePoint& pt) const override { - BOTAN_ARG_CHECK(bytes.size() == C::FieldElement::BYTES, "Invalid length for serialize_point_x"); - from_stash(pt).serialize_x_to(bytes.subspan<0, C::FieldElement::BYTES>()); - } - void serialize_scalar(std::span bytes, const Scalar& scalar) const override { BOTAN_ARG_CHECK(bytes.size() == C::Scalar::BYTES, "Invalid length to serialize_scalar"); return from_stash(scalar).serialize_to(bytes.subspan<0, C::Scalar::BYTES>()); @@ -259,28 +240,54 @@ } std::optional deserialize_point(std::span bytes) const override { - if(auto pt = C::AffinePoint::deserialize(bytes)) { - return stash(*pt); - } else { - return {}; + // The identity element (see SEC1 section 2.3.4) + // TODO(Botan4) remove this - we should reject the identity encoding + if(bytes.size() == 1 && bytes[0] == 0x00) { + return stash(C::AffinePoint::identity()); + } + + constexpr size_t FieldElementBytes = C::FieldElement::BYTES; + constexpr size_t CompressedBytes = C::FieldElement::BYTES + 1; + constexpr size_t UncompressedBytes = 2 * C::FieldElement::BYTES + 1; + + if(bytes.size() == UncompressedBytes && bytes[0] == 0x04) { + const auto encoded_point = bytes.subspan(1); + auto x = C::FieldElement::deserialize(encoded_point.first(FieldElementBytes)); + auto y = C::FieldElement::deserialize(encoded_point.last(FieldElementBytes)); + + if(x && y) { + // Check that y^2 = x^3 + ax + b + const auto lhs = (*y).square(); + const auto rhs = C::x3_ax_b(*x); + const auto valid = (lhs == rhs); + if(valid.as_bool()) { + return stash(typename C::AffinePoint(*x, *y)); + } + } + } else if(bytes.size() == CompressedBytes && (bytes[0] == 0x02 || bytes[0] == 0x03)) { + const CT::Choice y_is_even = CT::Mask::is_equal(bytes[0], 0x02).as_choice(); + + if(auto x = C::FieldElement::deserialize(bytes.subspan(1, FieldElementBytes))) { + if(auto y = sqrt_field_element(C::x3_ax_b(*x)).as_optional_vartime()) { + return stash(typename C::AffinePoint(*x, y->correct_sign(y_is_even))); + } + } } + + return {}; } - AffinePoint hash_to_curve_nu(std::string_view hash, - std::span input, - std::span domain_sep) const override { + AffinePoint hash_to_curve_nu(std::function)> expand_message) const override { if constexpr(C::ValidForSswuHash) { - return stash(hash_to_curve_sswu(hash, input, domain_sep)); + return stash(hash_to_curve_sswu(expand_message)); } else { throw Not_Implemented("Hash to curve is not implemented for this curve"); } } - ProjectivePoint hash_to_curve_ro(std::string_view hash, - std::span input, - std::span domain_sep) const override { + ProjectivePoint hash_to_curve_ro(std::function)> expand_message) const override { if constexpr(C::ValidForSswuHash) { - return stash(hash_to_curve_sswu(hash, input, domain_sep)); + return stash(hash_to_curve_sswu(expand_message)); } else { throw Not_Implemented("Hash to curve is not implemented for this curve"); } @@ -311,12 +318,7 @@ Scalar scalar_invert_vartime(const Scalar& ss) const override { auto s = from_stash(ss); - // TODO take advantage of variable time - if constexpr(curve_supports_scalar_invert) { - return stash(C::scalar_invert(s)); - } else { - return stash(s.invert()); - } + return stash(s.invert_vartime()); } Scalar scalar_negate(const Scalar& s) const override { return stash(from_stash(s).negate()); } @@ -327,8 +329,6 @@ return (from_stash(a) == from_stash(b)).as_bool(); } - Scalar scalar_zero() const override { return stash(C::Scalar::zero()); } - Scalar scalar_one() const override { return stash(C::Scalar::one()); } Scalar random_scalar(RandomNumberGenerator& rng) const override { return stash(C::Scalar::random(rng)); } diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_instance.h botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_instance.h --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_instance.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_instance.h 2026-05-07 01:38:28.000000000 +0000 @@ -1,5 +1,6 @@ /* -* (C) 2024 Jack Lloyd +* This file was automatically generated by ./src/scripts/dev_tools/gen_ec_groups.py on 2026-04-24 +* All manual changes will be lost. Edit the script instead. * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -7,43 +8,77 @@ #ifndef BOTAN_PCURVES_INSTANCE_H_ #define BOTAN_PCURVES_INSTANCE_H_ +#include #include +namespace Botan { + +class BigInt; + +} + namespace Botan::PCurve { class PrimeOrderCurve; class PCurveInstance final { public: - /* - * All functions here are always defined, however if the cooresponding - * curve is not available at build time a default implementation is - * provided in pcurves_instance.cpp that returns a nullptr - */ - - static std::shared_ptr secp192r1(); - - static std::shared_ptr secp224r1(); - +#if defined(BOTAN_HAS_PCURVES_SECP256R1) static std::shared_ptr secp256r1(); +#endif +#if defined(BOTAN_HAS_PCURVES_SECP384R1) static std::shared_ptr secp384r1(); +#endif +#if defined(BOTAN_HAS_PCURVES_SECP521R1) static std::shared_ptr secp521r1(); +#endif - static std::shared_ptr secp256k1(); - +#if defined(BOTAN_HAS_PCURVES_BRAINPOOL256R1) static std::shared_ptr brainpool256r1(); +#endif +#if defined(BOTAN_HAS_PCURVES_BRAINPOOL384R1) static std::shared_ptr brainpool384r1(); +#endif +#if defined(BOTAN_HAS_PCURVES_BRAINPOOL512R1) static std::shared_ptr brainpool512r1(); +#endif +#if defined(BOTAN_HAS_PCURVES_FRP256V1) static std::shared_ptr frp256v1(); +#endif + +#if defined(BOTAN_HAS_PCURVES_SECP192R1) + static std::shared_ptr secp192r1(); +#endif + +#if defined(BOTAN_HAS_PCURVES_SECP224R1) + static std::shared_ptr secp224r1(); +#endif + +#if defined(BOTAN_HAS_PCURVES_SECP256K1) + static std::shared_ptr secp256k1(); +#endif +#if defined(BOTAN_HAS_PCURVES_SM2P256V1) static std::shared_ptr sm2p256v1(); +#endif +#if defined(BOTAN_HAS_PCURVES_NUMSP512D1) static std::shared_ptr numsp512d1(); +#endif + +#if defined(BOTAN_HAS_PCURVES_GENERIC) + static std::shared_ptr from_params(const BigInt& p, + const BigInt& a, + const BigInt& b, + const BigInt& base_x, + const BigInt& base_y, + const BigInt& order); +#endif }; } // namespace Botan::PCurve diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_mul.h botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_mul.h --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_mul.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_mul.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,546 @@ +/* +* (C) 2024,2025,2026 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_PCURVES_MUL_H_ +#define BOTAN_PCURVES_MUL_H_ + +#include +#include +#include +#include + +namespace Botan { + +class RandomNumberGenerator; + +/* +* Multiplication algorithm window size parameters +*/ + +static constexpr size_t BasePointWindowBits = 6; +static constexpr size_t VarPointWindowBits = 4; +static constexpr size_t Mul2PrecompWindowBits = 3; +static constexpr size_t Mul2WindowBits = 2; + +/** +* Return number of blinding bits to use +* +* This can return any value between 0 and the scalar bit length. +* +* The field arithmetic and scalar multiplication algorithms are anyway written and tested +* to be constant time; blinding is just used as a safety net in the case that the compiler +* rewrites constant time code to include variable time behavior. If utmost performance is +* of concern and you are in a position to test that your specific compiler for your +* specific architecture is not inserting variable time behavior where not expected (for +* example by using the existing valgrind-based CT checking) it is safe to modify this +* function to just return 0, or some very small blinding factor of 1-4 bits. +*/ +constexpr size_t scalar_blinding_bits(size_t scalar_bits) { + // For blinding use 1/8 the order length for most curves; for P-521 we round down a bit + // so the masked scalar fits exactly in 9 or 18 words. + + if(scalar_bits == 521) { + return 55; + } else { + return scalar_bits / 8; + } +} + +/** +* A precomputed table of affine points with constant time lookup +* +* If R is zero then the entire table is scanned for each lookup. +* +* If R is not zero, then the table must be a multiple of R points long. +* Each lookup will be examine a range of length R, as in +* pts[0..R], pts[R..2*R], ... +*/ +template +class AffinePointTable final { + public: + using AffinePoint = typename C::AffinePoint; + using ProjectivePoint = typename C::ProjectivePoint; + using WordType = typename C::WordType; + + static constexpr bool WholeRangeSearch = (R == 0); + + explicit AffinePointTable(std::span pts) { + BOTAN_ASSERT_NOMSG(pts.size() > 1); + + if constexpr(R > 0) { + BOTAN_ASSERT_NOMSG(pts.size() % R == 0); + } + + // TODO scatter/gather with SIMD lookup + m_table = to_affine_batch(pts); + } + + /** + * If idx is zero then return the identity element. Otherwise return pts[idx - 1] + */ + inline AffinePoint ct_select(size_t idx) const + requires(WholeRangeSearch) + { + BOTAN_DEBUG_ASSERT(idx < m_table.size() + 1); + + auto result = AffinePoint::identity(m_table[0]); + + // Intentionally wrapping; set to maximum size_t if idx == 0 + const size_t idx1 = static_cast(idx - 1); + for(size_t i = 0; i != m_table.size(); ++i) { + const auto found = CT::Mask::is_equal(idx1, i).as_choice(); + result.conditional_assign(found, m_table[i]); + } + + return result; + } + + /** + * If idx is zero then return the identity element. Otherwise return pts[idx - 1] + * out of the table subrange pts[iter*R..(iter+1)*R] + */ + inline AffinePoint ct_select(size_t idx, size_t iter) const + requires(!WholeRangeSearch) + { + BOTAN_DEBUG_ASSERT(idx < R + 1); + BOTAN_DEBUG_ASSERT(R * (iter + 1) <= m_table.size()); + + auto result = AffinePoint::identity(m_table[R * iter]); + + // Intentionally wrapping; set to maximum size_t if idx == 0 + const size_t idx1 = static_cast(idx - 1); + for(size_t i = 0; i != R; ++i) { + const auto found = CT::Mask::is_equal(idx1, i).as_choice(); + result.conditional_assign(found, m_table[R * iter + i]); + } + + return result; + } + + private: + std::vector m_table; +}; + +/* +* Base point precomputation table +* +* This algorithm works by precomputing a set of points such that +* the online phase of the point multiplication can be effected by +* a sequence of point additions. +* +* The tables, even for W = 1, are large and costly to precompute, so +* this is only used for the base point. +* +* The online phase of the algorithm uess `ceil(SB/W)` additions, +* and no point doublings. The table is of size +* `ceil(SB + W - 1)/W * ((1 << W) - 1)` +* where SB is the bit length of the (blinded) scalar. +* +* Each window of the scalar is associated with a window in the table. +* The table windows are unique to that offset within the scalar. +* +* The simplest version to understand is when W = 1. There the table +* consists of [P, 2*P, 4*P, ..., 2^N*P] where N is the bit length of +* the group order. The online phase consists of conditionally adding +* table[i] depending on if bit i of the scalar is set or not. +* +* When W = 2, the scalar is examined 2 bits at a time, and the table +* for a window index `I` is [(2^I)*P, (2^(I+1))*P, (2^I+2^(I+1))*P]. +* +* This extends similarly for larger W +* +* At a certain point, the side channel silent table lookup becomes the +* dominating cost +* +* For all W, each window in the table has an implicit element of +* the identity element which is used if the scalar bits were all zero. +* This is omitted to save space; AffinePoint::ct_select is designed +* to assist in this by returning the identity element if its index +* argument is zero, or otherwise it returns table[idx - 1] +*/ +template +std::vector basemul_setup(const typename C::AffinePoint& p, size_t max_scalar_bits) { + static_assert(WindowBits >= 1 && WindowBits <= 8); + + // 2^W elements, less the identity element + constexpr size_t WindowElements = (1 << WindowBits) - 1; + + const size_t Windows = (max_scalar_bits + WindowBits - 1) / WindowBits; + + const size_t TableSize = Windows * WindowElements; + + std::vector table; + table.reserve(TableSize); + + auto accum = C::ProjectivePoint::from_affine(p); + + for(size_t i = 0; i != TableSize; i += WindowElements) { + table.push_back(accum); + + for(size_t j = 1; j != WindowElements; ++j) { + // Conditional ok: loop iteration count is public + if(j % 2 == 1) { + table.emplace_back(table[i + j / 2].dbl()); + } else { + table.emplace_back(table[i + j - 1] + table[i]); + } + } + + accum = table[i + (WindowElements / 2)].dbl(); + } + + // Variable time batch conversion is fine since generator is public + return to_affine_batch(table); +} + +template +typename C::ProjectivePoint basemul_exec(std::span table, + const BlindedScalar& scalar, + RandomNumberGenerator& rng) { + // 2^W elements, less the identity element + static constexpr size_t WindowElements = (1 << WindowBits) - 1; + + // TODO: C++23 - use std::mdspan to access table? + + auto accum = [&]() { + const size_t w_0 = scalar.get_window(0); + const auto tbl_0 = table.first(WindowElements); + auto pt = C::ProjectivePoint::from_affine(C::AffinePoint::ct_select(tbl_0, w_0)); + CT::poison(pt); + pt.randomize_rep(rng); + return pt; + }(); + + const size_t windows = (scalar.bits() + WindowBits - 1) / WindowBits; + + for(size_t i = 1; i != windows; ++i) { + const size_t w_i = scalar.get_window(WindowBits * i); + const auto tbl_i = table.subspan(WindowElements * i, WindowElements); + + /* + None of these additions can be doublings, because in each iteration, the + discrete logarithms of the points we're selecting out of the table are + larger than the largest possible dlog of accum. + */ + accum += C::AffinePoint::ct_select(tbl_i, w_i); + + // Conditional ok: loop iteration count is public + if(i <= 3) { + accum.randomize_rep(rng); + } + } + + CT::unpoison(accum); + return accum; +} + +/* +* Base point precomputation table with Booth recoding +* +* Same structure as basemul, but uses Booth recoding to halve the +* table size per window. Instead of storing 2^W - 1 entries per +* window, we store 2^(W-1) entries (multiples 1..2^(W-1) of the +* window base point). The sign is handled by conditional negation +* after the constant-time table lookup. +* +* The scalar is prepared with one extra blinding bit (WindowBits+1), +* and windows overlap by one bit to allow carry propagation from +* the Booth encoding. +*/ +template +std::vector basemul_booth_setup(const typename C::AffinePoint& p, size_t max_scalar_bits) { + static_assert(WindowBits >= 1 && WindowBits <= 8); + + // 2^(W-1) elements per window [1*base .. 2^(W-1)*base] + constexpr size_t WindowElements = 1 << (WindowBits - 1); + + const size_t Windows = (max_scalar_bits + WindowBits - 1) / WindowBits; + + const size_t TableSize = Windows * WindowElements; + + std::vector table; + table.reserve(TableSize); + + auto accum = C::ProjectivePoint::from_affine(p); + + for(size_t i = 0; i != TableSize; i += WindowElements) { + table.push_back(accum); + + for(size_t j = 1; j != WindowElements; ++j) { + // Conditional ok: loop iteration count is public + if(j % 2 == 1) { + table.emplace_back(table[i + j / 2].dbl()); + } else { + table.emplace_back(table[i + j - 1] + table[i]); + } + } + + // Advance to next window's base: 2^W * current_base + // The last entry is 2^(W-1) * base, so doubling gives 2^W * base + accum = table[i + WindowElements - 1].dbl(); + } + + // Variable time batch conversion is fine since generator is public + return to_affine_batch(table); +} + +/* +* Booth recoding for base point multiplication +*/ +template +constexpr std::pair booth_recode(T x) { + static_assert(WindowBits >= 1 && WindowBits <= 8); + + auto s_mask = CT::Mask::expand(x >> WindowBits); + const T neg_x = (1 << (WindowBits + 1)) - x - 1; + T d = s_mask.select(neg_x, x); + d = (d >> 1) + (d & 1); + + return std::make_pair(static_cast(d), s_mask.as_choice()); +} + +template +typename C::ProjectivePoint basemul_booth_exec(std::span table, + const BlindedScalar& scalar, + RandomNumberGenerator& rng) { + static constexpr size_t WindowElements = 1 << (WindowBits - 1); + + const size_t windows = (scalar.bits() + WindowBits) / WindowBits; + + auto accum = [&]() { + // First window: extract W bits, shift left 1 to insert implicit carry in of zero + const size_t w_bits = scalar.get_window(0) & ((1 << WindowBits) - 1); + const size_t raw = w_bits << 1; + const auto [tidx, tneg] = booth_recode(raw); + const auto tbl_0 = table.first(WindowElements); + + auto pt = C::ProjectivePoint::from_affine(C::AffinePoint::ct_select(tbl_0, tidx)); + pt.conditional_assign(tneg, pt.negate()); + CT::poison(pt); + pt.randomize_rep(rng); + return pt; + }(); + + for(size_t i = 1; i != windows; ++i) { + // Extract W+1 bits overlapping by 1 with the previous window + const size_t bit_pos = WindowBits * i - 1; + const size_t raw = scalar.get_window(bit_pos); + const auto [tidx, tneg] = booth_recode(raw); + + const auto tbl_i = table.subspan(WindowElements * i, WindowElements); + + accum = C::ProjectivePoint::add_or_sub(accum, C::AffinePoint::ct_select(tbl_i, tidx), tneg); + + // Conditional ok: loop iteration count is public + if(i <= 3) { + accum.randomize_rep(rng); + } + } + + CT::unpoison(accum); + return accum; +} + +/* +* Variable point table mul setup and online phase +*/ +template +AffinePointTable varpoint_setup(const typename C::AffinePoint& p) { + static_assert(TableSize > 2); + + std::vector table; + table.reserve(TableSize); + table.push_back(C::ProjectivePoint::from_affine(p)); + + for(size_t i = 1; i != TableSize; ++i) { + // Conditional ok: loop iteration count is public + if(i % 2 == 1) { + table.push_back(table[i / 2].dbl()); + } else { + table.push_back(table[i - 1] + p); + } + } + + return AffinePointTable(table); +} + +template +typename C::ProjectivePoint varpoint_exec(const AffinePointTable& table, + const BlindedScalar& scalar, + RandomNumberGenerator& rng) { + const size_t windows = (scalar.bits() + WindowBits - 1) / WindowBits; + + auto accum = [&]() { + const size_t w_0 = scalar.get_window((windows - 1) * WindowBits); + auto pt = C::ProjectivePoint::from_affine(table.ct_select(w_0)); + CT::poison(pt); + pt.randomize_rep(rng); + return pt; + }(); + + for(size_t i = 1; i != windows; ++i) { + accum = accum.dbl_n(WindowBits); + auto w_i = scalar.get_window((windows - i - 1) * WindowBits); + + /* + This point addition cannot be a doubling (except once) + + Consider the sequence of points that are operated on, and specifically + their discrete logarithms. We start out at the point at infinity + (dlog 0) and then add the initial window which is precisely P*w_0 + + We then perform WindowBits doublings, so accum's dlog at the point + of the addition in the first iteration of the loop (when i == 1) is + at least 2^W * w_0. + + Since we know w_0 > 0, then in every iteration of the loop, accums + dlog will always be greater than the dlog of the table element we + just looked up (something between 0 and 2^W-1), and thus the + addition into accum cannot be a doubling. + + However due to blinding this argument fails, since we perform + multiplications using a scalar that is larger than the group + order. In this case it's possible that the dlog of accum becomes + `order + x` (or, effectively, `x`) and `x` is smaller than 2^W. + In this case, a doubling may occur. Future iterations of the loop + cannot be doublings by the same argument above. Since the blinding + factor is always less than the group order (substantially so), + it is not possible for the dlog of accum to overflow a second time. + */ + + accum += table.ct_select(w_i); + + // Conditional ok: loop iteration count is public + if(i <= 3) { + accum.randomize_rep(rng); + } + } + + CT::unpoison(accum); + return accum; +} + +/* +* Effect 2-ary multiplication ie x*G + y*H +* +* This is done using a windowed variant of what is usually called +* Shamir's trick. +* +* The W = 1 case is simple; we precompute an extra point GH = G + H, +* and then examine 1 bit in each of x and y. If one or the other bits +* are set then add G or H resp. If both bits are set, add GH. +* +* The example below is a precomputed table for W=2. The flattened table +* begins at (x_i,y_i) = (1,0), i.e. the identity element is omitted. +* The indices in each cell refer to the cell's location in m_table. +* +* x-> 0 1 2 3 +* 0 |/ (ident) |0 x |1 2x |2 3x | +* 1 |3 y |4 x+y |5 2x+y |6 3x+y | +* y = 2 |7 2y |8 x+2y |9 2(x+y) |10 3x+2y | +* 3 |11 3y |12 x+3y |13 2x+3y |14 3x+3y | +*/ + +template +std::vector mul2_setup(const typename C::AffinePoint& p, + const typename C::AffinePoint& q) { + static_assert(WindowBits >= 1 && WindowBits <= 4); + + // 2^(2*W) elements, less the identity element + constexpr size_t TableSize = (1 << (2 * WindowBits)) - 1; + constexpr size_t WindowSize = (1 << WindowBits); + + std::vector table; + table.reserve(TableSize); + + for(size_t i = 0; i != TableSize; ++i) { + const size_t t_i = (i + 1); + const size_t p_i = t_i % WindowSize; + const size_t q_i = (t_i >> WindowBits) % WindowSize; + + // Conditionals ok: all based on t_i/p_i/q_i which in turn are derived from public i + + // Returns x_i * x + y_i * y + auto next_tbl_e = [&]() { + if(p_i % 2 == 0 && q_i % 2 == 0) { + // Where possible using doubling (eg indices 1, 7, 9 in + // the table above) + return table[(t_i / 2) - 1].dbl(); + } else if(p_i > 0 && q_i > 0) { + // A combination of p and q + if(p_i == 1) { + return p + table[(q_i << WindowBits) - 1]; + } else if(q_i == 1) { + return table[p_i - 1] + q; + } else { + return table[p_i - 1] + table[(q_i << WindowBits) - 1]; + } + } else if(p_i > 0 && q_i == 0) { + // A multiple of p without a q component + if(p_i == 1) { + // Just p + return C::ProjectivePoint::from_affine(p); + } else { + // p * p_{i-1} + return p + table[p_i - 1 - 1]; + } + } else if(p_i == 0 && q_i > 0) { + if(q_i == 1) { + // Just q + return C::ProjectivePoint::from_affine(q); + } else { + // q * q_{i-1} + return q + table[((q_i - 1) << WindowBits) - 1]; + } + } else { + BOTAN_ASSERT_UNREACHABLE(); + } + }; + + table.emplace_back(next_tbl_e()); + } + + return table; +} + +template +typename C::ProjectivePoint mul2_exec(const AffinePointTable& table, + const BlindedScalar& x, + const BlindedScalar& y, + RandomNumberGenerator& rng) { + const size_t Windows = (x.bits() + WindowBits - 1) / WindowBits; + + auto accum = [&]() { + const size_t w_1 = x.get_window((Windows - 1) * WindowBits); + const size_t w_2 = y.get_window((Windows - 1) * WindowBits); + const size_t window = w_1 + (w_2 << WindowBits); + auto pt = C::ProjectivePoint::from_affine(table.ct_select(window)); + CT::poison(pt); + pt.randomize_rep(rng); + return pt; + }(); + + for(size_t i = 1; i != Windows; ++i) { + accum = accum.dbl_n(WindowBits); + + const size_t w_1 = x.get_window((Windows - i - 1) * WindowBits); + const size_t w_2 = y.get_window((Windows - i - 1) * WindowBits); + const size_t window = w_1 + (w_2 << WindowBits); + accum += table.ct_select(window); + + // Conditional ok: loop iteration count is public + if(i <= 3) { + accum.randomize_rep(rng); + } + } + + CT::unpoison(accum); + return accum; +} + +} // namespace Botan + +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_numsp512d1/info.txt botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_numsp512d1/info.txt --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_numsp512d1/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_numsp512d1/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + PCURVES_NUMSP512D1 -> 20240723 - + name -> "PCurve numsp512d1" diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_numsp512d1/pcurves_numsp512d1.cpp botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_numsp512d1/pcurves_numsp512d1.cpp --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_numsp512d1/pcurves_numsp512d1.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_numsp512d1/pcurves_numsp512d1.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -37,6 +37,7 @@ }; // clang-format off + class Params final : public EllipticCurveParameters< "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDC7", "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDC4", @@ -50,7 +51,7 @@ class Curve final : public EllipticCurve { public: - static FieldElement fe_invert2(const FieldElement& x) { + static constexpr FieldElement fe_invert2(const FieldElement& x) { // Generated by https://github.com/mmcloughlin/addchain auto z = x.square(); z *= x; @@ -92,6 +93,49 @@ z.square_n(2); return z; } + + static constexpr FieldElement fe_sqrt(const FieldElement& x) { + // Generated by https://github.com/mmcloughlin/addchain + auto z = x.square(); + z *= x; + z = z.square(); + z *= x; + auto t0 = z; + t0.square_n(3); + t0 *= z; + t0.square_n(3); + auto t1 = t0 * z; + t0 = t1; + t0.square_n(9); + t0 *= t1; + t0.square_n(3); + t0 *= z; + auto t2 = t0; + t2.square_n(9); + t1 *= t2; + t2 = t1; + t2.square_n(30); + t1 *= t2; + t2 = t1; + t2.square_n(60); + t1 *= t2; + t2 = t1; + t2.square_n(120); + t1 *= t2; + t2 = t1; + t2.square_n(240); + t1 *= t2; + t1.square_n(21); + t0 *= t1; + t0 = t0.square(); + t0 *= x; + t0.square_n(4); + z *= t0; + z.square_n(3); + z *= x; + z = z.square(); + return z; + } }; } // namespace numsp512d1 diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_secp192r1/info.txt botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_secp192r1/info.txt --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_secp192r1/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_secp192r1/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + PCURVES_SECP192R1 -> 20240709 - + name -> "PCurve secp192r1" diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_secp192r1/pcurves_secp192r1.cpp botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_secp192r1/pcurves_secp192r1.cpp --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_secp192r1/pcurves_secp192r1.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_secp192r1/pcurves_secp192r1.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -23,6 +23,34 @@ typedef typename Params::W W; constexpr static std::array redc(const std::array& z) { + if constexpr(std::same_as && WordInfo::dword_is_native) { + using dword = typename WordInfo::dword; + + const dword S01 = dword(z[0]) + z[3] + z[5]; + const dword S23 = dword(z[1]) + z[3] + z[4] + z[5]; + const dword S45 = dword(z[2]) + z[4] + z[5]; + + std::array r = {}; + + dword S = S01; + r[0] = static_cast(S); + S >>= 64; + + S += S23; + r[1] = static_cast(S); + S >>= 64; + + S += S45; + r[2] = static_cast(S); + S >>= 64; + + BOTAN_DEBUG_ASSERT(S <= 3); + + solinas_correct_redc(r, P, p192_mul_mod_192(static_cast(S))); + + return r; + } + const int64_t X00 = get_uint32(z.data(), 0); const int64_t X01 = get_uint32(z.data(), 1); const int64_t X02 = get_uint32(z.data(), 2); @@ -57,7 +85,7 @@ BOTAN_DEBUG_ASSERT(S <= 3); - bigint_correct_redc(r, P, p192_mul_mod_192(S)); + solinas_correct_redc(r, P, p192_mul_mod_192(S)); return r; } @@ -94,6 +122,7 @@ }; // clang-format off + class Params final : public EllipticCurveParameters< "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFC", @@ -105,7 +134,44 @@ // clang-format on -class Curve final : public EllipticCurve {}; +class Curve final : public EllipticCurve { + public: + // Return the square of the inverse of x + static constexpr FieldElement fe_invert2(const FieldElement& x) { + // Generated using https://github.com/mmcloughlin/addchain + auto z = x.square(); + z *= x; + auto t0 = z.square(); + t0 *= x; + auto t2 = t0.square(); + auto t1 = t2.square(); + auto t3 = t1; + t3.square_n(3); + t1 *= t3; + t3 = t1; + t3.square_n(2); + t2 *= t3; + t2.square_n(7); + t1 *= t2; + t2 = t1; + t2.square_n(15); + t1 *= t2; + t2 = t1; + t2.square_n(30); + t1 *= t2; + z *= t1; + t1 = z; + t1.square_n(3); + t2 = t1; + t2.square_n(62); + t1 *= t2; + t0 *= t1; + t0.square_n(63); + z *= t0; + z.square_n(2); + return z; + } +}; } // namespace secp192r1 diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_secp224r1/info.txt botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_secp224r1/info.txt --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_secp224r1/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_secp224r1/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + PCURVES_SECP224R1 -> 20240716 - + name -> "PCurve secp224r1" diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_secp224r1/pcurves_secp224r1.cpp botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_secp224r1/pcurves_secp224r1.cpp --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_secp224r1/pcurves_secp224r1.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_secp224r1/pcurves_secp224r1.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -61,7 +61,7 @@ BOTAN_DEBUG_ASSERT(S <= 2); - bigint_correct_redc(r, P, p224_mul_mod_224(S)); + solinas_correct_redc(r, P, p224_mul_mod_224(S)); return r; } @@ -99,6 +99,7 @@ }; // clang-format off + class Params final : public EllipticCurveParameters< "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000001", "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFE", diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_secp256k1/info.txt botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_secp256k1/info.txt --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_secp256k1/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_secp256k1/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + PCURVES_SECP256K1 -> 20240608 - + name -> "PCurve secp256k1" diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_secp256k1/pcurves_secp256k1.cpp botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_secp256k1/pcurves_secp256k1.cpp --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_secp256k1/pcurves_secp256k1.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_secp256k1/pcurves_secp256k1.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -39,6 +39,7 @@ }; // clang-format off + class Params final : public EllipticCurveParameters< "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", "0", @@ -49,11 +50,9 @@ }; // clang-format on -#if BOTAN_MP_WORD_BITS == 64 -typedef EllipticCurve Secp256k1Base; -#else -typedef EllipticCurve Secp256k1Base; -#endif + +using Secp256k1Base = + std::conditional_t::bits >= 33, EllipticCurve, EllipticCurve>; class Curve final : public Secp256k1Base { public: @@ -97,6 +96,45 @@ z *= t0; z.square_n(2); return z; + } + + static constexpr FieldElement fe_sqrt(const FieldElement& x) { + auto z = x.square(); + z *= x; + auto t0 = z; + t0.square_n(2); + t0 *= z; + auto t1 = t0.square(); + auto t2 = t1 * x; + t1 = t2; + t1.square_n(2); + t1 *= z; + auto t3 = t1; + t3.square_n(4); + t0 *= t3; + t3 = t0; + t3.square_n(11); + t0 *= t3; + t3 = t0; + t3.square_n(5); + t2 *= t3; + t3 = t2; + t3.square_n(27); + t2 *= t3; + t3 = t2; + t3.square_n(54); + t2 *= t3; + t3 = t2; + t3.square_n(108); + t2 *= t3; + t2.square_n(7); + t1 *= t2; + t1.square_n(23); + t0 *= t1; + t0.square_n(6); + z *= t0; + z.square_n(2); + return z; } static constexpr Scalar scalar_invert(const Scalar& x) { diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_secp256r1/info.txt botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_secp256r1/info.txt --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_secp256r1/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_secp256r1/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + PCURVES_SECP256R1 -> 20240608 - + name -> "PCurve secp256r1" diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_secp256r1/pcurves_secp256r1.cpp botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_secp256r1/pcurves_secp256r1.cpp --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_secp256r1/pcurves_secp256r1.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_secp256r1/pcurves_secp256r1.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -69,7 +69,7 @@ BOTAN_DEBUG_ASSERT(S <= 8); - bigint_correct_redc(r, P, p256_mul_mod_256(S)); + solinas_correct_redc(r, P, p256_mul_mod_256(S)); return r; } @@ -112,6 +112,7 @@ namespace secp256r1 { // clang-format off + class Params final : public EllipticCurveParameters< "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC", @@ -160,6 +161,31 @@ return z; } + // Return the square root of x + static constexpr FieldElement fe_sqrt(const FieldElement& x) { + // Generated using addchain + auto z = x.square(); + z *= x; + auto t0 = z; + t0.square_n(2); + z *= t0; + t0 = z; + t0.square_n(4); + z *= t0; + t0 = z; + t0.square_n(8); + z *= t0; + t0 = z; + t0.square_n(16); + z *= t0; + z.square_n(32); + z *= x; + z.square_n(96); + z *= x; + z.square_n(94); + return z; + } + static constexpr Scalar scalar_invert(const Scalar& x) { auto t1 = x.square(); auto t5 = t1.square(); diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_secp384r1/info.txt botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_secp384r1/info.txt --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_secp384r1/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_secp384r1/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + PCURVES_SECP384R1 -> 20240608 - + name -> "PCurve secp384r1" diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_secp384r1/pcurves_secp384r1.cpp botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_secp384r1/pcurves_secp384r1.cpp --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_secp384r1/pcurves_secp384r1.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_secp384r1/pcurves_secp384r1.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -80,7 +80,7 @@ BOTAN_DEBUG_ASSERT(S <= 4); - bigint_correct_redc(r, P, p384_mul_mod_384(S)); + solinas_correct_redc(r, P, p384_mul_mod_384(S)); return r; } @@ -120,9 +120,10 @@ } }; -// clang-format off namespace secp384r1 { +// clang-format off + class Params final : public EllipticCurveParameters< "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF", "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC", @@ -179,6 +180,46 @@ return r; } + static constexpr FieldElement fe_sqrt(const FieldElement& x) { + // Generated using https://github.com/mmcloughlin/addchain + + auto z = x.square(); + z *= x; + z = z.square(); + auto t0 = x * z; + z = t0; + z.square_n(3); + auto t1 = t0 * z; + auto t2 = t1.square(); + z = t2 * x; + t2.square_n(5); + t1 *= t2; + t2 = t1; + t2.square_n(12); + t1 *= t2; + t1.square_n(7); + t1 *= z; + z = t1.square(); + z *= x; + t2 = z; + t2.square_n(31); + t1 *= t2; + t2 = t1; + t2.square_n(63); + t1 *= t2; + t2 = t1; + t2.square_n(126); + t1 *= t2; + t1.square_n(3); + t0 *= t1; + t0.square_n(33); + z *= t0; + z.square_n(64); + z *= x; + z.square_n(30); + return z; + } + static constexpr Scalar scalar_invert(const Scalar& x) { // Generated using https://github.com/mmcloughlin/addchain diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_secp521r1/info.txt botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_secp521r1/info.txt --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_secp521r1/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_secp521r1/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + PCURVES_SECP521R1 -> 20240608 - + name -> "PCurve secp521r1" diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_secp521r1/pcurves_secp521r1.cpp botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_secp521r1/pcurves_secp521r1.cpp --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_secp521r1/pcurves_secp521r1.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_secp521r1/pcurves_secp521r1.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -24,12 +24,13 @@ constexpr static std::array one() { return std::array{1}; } constexpr static std::array redc(const std::array& z) { - constexpr W TOP_MASK = static_cast(0x1FF); + // Regardless of word size (32 or 64) the top word is 9 bits long + constexpr W TOP_BITS = static_cast(0x1FF); /* * Extract the high part of z (z >> 521) */ - std::array t; + std::array t; // NOLINT(*-member-init) for(size_t i = 0; i != N; ++i) { t[i] = z[(N - 1) + i] >> 9; @@ -40,22 +41,36 @@ } // Now t += z & (2**521-1) - W carry = word8_add2(t.data(), z.data(), static_cast(0)); - - if constexpr(WordInfo::bits == 32) { - constexpr size_t HN = N / 2; - carry = word8_add2(t.data() + HN, z.data() + HN, carry); + W carry = 0; + for(size_t i = 0; i != N - 1; ++i) { + t[i] = word_add(t[i], z[i], &carry); } // Now add the (partial) top words; this can't carry out // since both inputs are at most 2**9-1 - t[N - 1] += (z[N - 1] & TOP_MASK) + carry; + t[N - 1] += (z[N - 1] & TOP_BITS) + carry; - // But might be greater than modulus: - std::array r; - bigint_monty_maybe_sub(r.data(), static_cast(0), t.data(), P.data()); + /* + Since the modulus P is exactly 2**521 - 1 the only way the computed + result can be larger than P is if the top word is larger than TOP_BITS - return r; + If this is the case then we need to conditionally subtract P + + Since TOP_BITS has the low 9 bits set, we can check if t[N - 1] > TOP_BITS + by checking if t[N - 1] >> 9 has any bits set. Doing it this way is + faster than a standard comparison since CT::Mask::is_gt requires + several bit operations. + */ + + const W need_sub = ~CT::Mask::is_zero(t[N - 1] >> 9).value(); + + W borrow = 0; + for(size_t i = 0; i != N - 1; ++i) { + t[i] = word_sub(t[i], need_sub & WordInfo::max, &borrow); + } + t[N - 1] = word_sub(t[N - 1], need_sub & TOP_BITS, &borrow); + + return t; } constexpr static std::array to_rep(const std::array& x) { return x; } @@ -66,6 +81,7 @@ }; // clang-format off + class Params final : public EllipticCurveParameters< "1FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", "1FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC", @@ -121,6 +137,12 @@ return r; } + static constexpr FieldElement fe_sqrt(const FieldElement& x) { + auto z = x; + z.square_n(519); + return z; + } + static constexpr Scalar scalar_invert(const Scalar& x) { // Generated using https://github.com/mmcloughlin/addchain diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_sm2p256v1/info.txt botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_sm2p256v1/info.txt --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_sm2p256v1/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_sm2p256v1/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + PCURVES_SM2P256V1 -> 20240608 - + name -> "PCurve sm2p256v1" diff -Nru botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_sm2p256v1/pcurves_sm2p256v1.cpp botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_sm2p256v1/pcurves_sm2p256v1.cpp --- botan3-3.7.1+dfsg/src/lib/math/pcurves/pcurves_sm2p256v1/pcurves_sm2p256v1.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/math/pcurves/pcurves_sm2p256v1/pcurves_sm2p256v1.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -63,7 +63,7 @@ sum.accum(S7); const auto S = sum.final_carry(0); - bigint_correct_redc(r, P, sm2_mul_mod_256(S)); + solinas_correct_redc(r, P, sm2_mul_mod_256(S)); return r; } @@ -119,7 +119,7 @@ class Curve final : public EllipticCurve { public: // Return the square of the inverse of x - static FieldElement fe_invert2(const FieldElement& x) { + static constexpr FieldElement fe_invert2(const FieldElement& x) { // Generated by https://github.com/mmcloughlin/addchain auto z = x.square(); auto t0 = x * z; @@ -154,6 +154,42 @@ z.square_n(2); return z; } + + static constexpr FieldElement fe_sqrt(const FieldElement& x) { + auto z = x.square(); + z *= x; + z = z.square(); + auto t0 = x * z; + z = t0.square(); + z *= x; + auto t2 = z.square(); + auto t3 = t2.square(); + auto t1 = t3.square(); + auto t4 = t1; + t4.square_n(3); + t3 *= t4; + t3.square_n(5); + t1 *= t3; + t3 = t1; + t3.square_n(2); + t2 *= t3; + t2.square_n(14); + t1 *= t2; + t0 *= t1; + t0.square_n(4); + t1 = t0; + t1.square_n(31); + t0 *= t1; + t1.square_n(32); + t1 *= t0; + t1.square_n(62); + t0 *= t1; + z *= t0; + z.square_n(32); + z *= x; + z.square_n(62); + return z; + } }; } // namespace sm2p256v1 diff -Nru botan3-3.7.1+dfsg/src/lib/misc/cryptobox/cryptobox.cpp botan3-3.12.0+dfsg/src/lib/misc/cryptobox/cryptobox.cpp --- botan3-3.7.1+dfsg/src/lib/misc/cryptobox/cryptobox.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/misc/cryptobox/cryptobox.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,13 +9,14 @@ #include #include +#include #include -#include #include #include #include #include #include +#include namespace Botan::CryptoBox { @@ -102,7 +103,7 @@ } for(size_t i = 0; i != VERSION_CODE_LEN; ++i) { - uint32_t version = load_be(ciphertext.data(), 0); + const uint32_t version = load_be(ciphertext.data(), 0); if(version != CRYPTOBOX_VERSION_CODE) { throw Decoding_Error("Bad CryptoBox version"); } @@ -149,18 +150,28 @@ BOTAN_DIAGNOSTIC_PUSH BOTAN_DIAGNOSTIC_IGNORE_DEPRECATED_DECLARATIONS +namespace { + +secure_vector decrypt_bin(std::span input, std::string_view passphrase) { + return CryptoBox::decrypt_bin(input.data(), input.size(), passphrase); +} + +std::string decrypt(std::span input, std::string_view passphrase) { + return CryptoBox::decrypt(input.data(), input.size(), passphrase); +} + +} // namespace + secure_vector decrypt_bin(std::string_view input, std::string_view passphrase) { - return decrypt_bin(cast_char_ptr_to_uint8(input.data()), input.size(), passphrase); + return decrypt_bin(as_span_of_bytes(input), passphrase); } std::string decrypt(const uint8_t input[], size_t input_len, std::string_view passphrase) { - const secure_vector bin = decrypt_bin(input, input_len, passphrase); - - return std::string(cast_uint8_ptr_to_char(&bin[0]), bin.size()); + return bytes_to_string(decrypt_bin(input, input_len, passphrase)); } std::string decrypt(std::string_view input, std::string_view passphrase) { - return decrypt(cast_char_ptr_to_uint8(input.data()), input.size(), passphrase); + return decrypt(as_span_of_bytes(input), passphrase); } BOTAN_DIAGNOSTIC_POP diff -Nru botan3-3.7.1+dfsg/src/lib/misc/cryptobox/info.txt botan3-3.12.0+dfsg/src/lib/misc/cryptobox/info.txt --- botan3-3.7.1+dfsg/src/lib/misc/cryptobox/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/misc/cryptobox/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -4,7 +4,8 @@ name -> "Crypto Box" -brief -> "High-Level API for password-based encryption" +brief -> "High-Level API for password-based encryption (deprecated)" +lifecycle -> "Deprecated" diff -Nru botan3-3.7.1+dfsg/src/lib/misc/fpe_fe1/fpe_fe1.cpp botan3-3.12.0+dfsg/src/lib/misc/fpe_fe1/fpe_fe1.cpp --- botan3-3.7.1+dfsg/src/lib/misc/fpe_fe1/fpe_fe1.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/misc/fpe_fe1/fpe_fe1.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,9 +7,9 @@ #include +#include #include #include -#include #include #include #include @@ -27,10 +27,21 @@ * typical uses of FPE (typically, n is a power of 10) */ void factor(BigInt n, BigInt& a, BigInt& b) { + BOTAN_ARG_CHECK(n >= 2, "Invalid FPE modulus"); + a = BigInt::one(); b = BigInt::one(); - size_t n_low_zero = low_zero_bits(n); + /* + * This algorithm was poorly designed. It should have fully factored n (to the + * extent possible) and then built a/b starting from the largest factor first. + * + * This can't be fixed now without breaking existing users but if some + * incompatible change (or new flag, etc) is added in the future, consider + * fixing the factoring for those users. + */ + + const size_t n_low_zero = low_zero_bits(n); a <<= (n_low_zero / 2); b <<= n_low_zero - (n_low_zero / 2); @@ -82,12 +93,10 @@ std::swap(m_a, m_b); } } - - // The modulus is usually a system parameter and anyway is easily deduced from - // the ciphertexts - mod_a = std::make_unique(Modular_Reducer::for_public_modulus(m_a)); } +FPE_FE1::FPE_FE1(FPE_FE1&& other) noexcept = default; + FPE_FE1::~FPE_FE1() = default; void FPE_FE1::clear() { @@ -145,11 +154,13 @@ secure_vector tmp; - BigInt L, R, Fi; + BigInt L; + BigInt R; + BigInt Fi; for(size_t i = 0; i != m_rounds; ++i) { ct_divide(X, m_b, L, R); Fi = F(R, i, tweak_mac, tmp); - X = m_a * R + mod_a->reduce(L + Fi); + X = m_a * R + ct_modulo(L + Fi, m_a); } return X; @@ -161,12 +172,14 @@ BigInt X = input; secure_vector tmp; - BigInt W, R, Fi; + BigInt W; + BigInt R; + BigInt Fi; for(size_t i = 0; i != m_rounds; ++i) { ct_divide(X, m_a, R, W); Fi = F(R, m_rounds - i - 1, tweak_mac, tmp); - X = m_b * mod_a->reduce(W - Fi) + R; + X = m_b * ct_modulo(W - Fi, m_a) + R; } return X; diff -Nru botan3-3.7.1+dfsg/src/lib/misc/fpe_fe1/fpe_fe1.h botan3-3.12.0+dfsg/src/lib/misc/fpe_fe1/fpe_fe1.h --- botan3-3.7.1+dfsg/src/lib/misc/fpe_fe1/fpe_fe1.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/misc/fpe_fe1/fpe_fe1.h 2026-05-07 01:38:28.000000000 +0000 @@ -10,10 +10,11 @@ #include #include +#include +#include namespace Botan { -class Modular_Reducer; class MessageAuthenticationCode; /** @@ -31,10 +32,10 @@ * values for a and b. Set compat_mode to true to select this version. * @param mac_algo the PRF to use as the encryption function */ - FPE_FE1(const BigInt& n, - size_t rounds = 5, - bool compat_mode = false, - std::string_view mac_algo = "HMAC(SHA-256)"); + BOTAN_FUTURE_EXPLICIT FPE_FE1(const BigInt& n, + size_t rounds = 5, + bool compat_mode = false, + std::string_view mac_algo = "HMAC(SHA-256)"); ~FPE_FE1() override; @@ -66,6 +67,11 @@ BigInt decrypt(const BigInt& x, uint64_t tweak) const; + FPE_FE1(const FPE_FE1& other) = delete; + FPE_FE1(FPE_FE1&& other) noexcept; + FPE_FE1& operator=(const FPE_FE1& other) = delete; + FPE_FE1& operator=(FPE_FE1&& other) = delete; + private: void key_schedule(std::span key) override; @@ -74,13 +80,14 @@ secure_vector compute_tweak_mac(const uint8_t tweak[], size_t tweak_len) const; std::unique_ptr m_mac; - std::unique_ptr mod_a; std::vector m_n_bytes; BigInt m_a; BigInt m_b; size_t m_rounds; }; +class OctetString; + namespace FPE { /** @@ -98,7 +105,7 @@ * may be insecure for some values of n. Prefer FPE_FE1 class */ BigInt BOTAN_PUBLIC_API(2, 0) - fe1_encrypt(const BigInt& n, const BigInt& X, const SymmetricKey& key, const std::vector& tweak); + fe1_encrypt(const BigInt& n, const BigInt& X, const OctetString& key, const std::vector& tweak); /** * Decrypt X from and onto the group Z_n using key and tweak @@ -111,7 +118,7 @@ * may be insecure for some values of n. Prefer FPE_FE1 class */ BigInt BOTAN_PUBLIC_API(2, 0) - fe1_decrypt(const BigInt& n, const BigInt& X, const SymmetricKey& key, const std::vector& tweak); + fe1_decrypt(const BigInt& n, const BigInt& X, const OctetString& key, const std::vector& tweak); } // namespace FPE diff -Nru botan3-3.7.1+dfsg/src/lib/misc/hotp/hotp.cpp botan3-3.12.0+dfsg/src/lib/misc/hotp/hotp.cpp --- botan3-3.7.1+dfsg/src/lib/misc/hotp/hotp.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/misc/hotp/hotp.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ /* * HOTP -* (C) 2017 Jack Lloyd +* (C) 2017,2026 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -12,16 +12,27 @@ namespace Botan { -HOTP::HOTP(const uint8_t key[], size_t key_len, std::string_view hash_algo, size_t digits) { - BOTAN_ARG_CHECK(digits == 6 || digits == 7 || digits == 8, "Invalid HOTP digits"); +namespace { - if(digits == 6) { - m_digit_mod = 1000000; - } else if(digits == 7) { - m_digit_mod = 10000000; - } else if(digits == 8) { - m_digit_mod = 100000000; +// Use compile-time constant divisors to ensure the compiler emits a +// multiply+shift sequence instead of a variable-time division instruction +uint32_t hotp_truncate(uint32_t code, size_t digits) { + switch(digits) { + case 6: + return code % 1000000; + case 7: + return code % 10000000; + case 8: + return code % 100000000; + default: + BOTAN_ASSERT_UNREACHABLE(); } +} + +} // namespace + +HOTP::HOTP(const uint8_t key[], size_t key_len, std::string_view hash_algo, size_t digits) : m_digits(digits) { + BOTAN_ARG_CHECK(m_digits == 6 || m_digits == 7 || m_digits == 8, "Invalid HOTP digits"); /* RFC 4228 only supports SHA-1 but TOTP allows SHA-256 and SHA-512 @@ -46,7 +57,7 @@ const size_t offset = mac[mac.size() - 1] & 0x0F; const uint32_t code = load_be(mac.data() + offset, 0) & 0x7FFFFFFF; - return code % m_digit_mod; + return hotp_truncate(code, m_digits); } std::pair HOTP::verify_hotp(uint32_t otp, uint64_t starting_counter, size_t resync_range) { diff -Nru botan3-3.7.1+dfsg/src/lib/misc/hotp/otp.h botan3-3.12.0+dfsg/src/lib/misc/hotp/otp.h --- botan3-3.7.1+dfsg/src/lib/misc/hotp/otp.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/misc/hotp/otp.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,6 +9,7 @@ #define BOTAN_ONE_TIME_PASSWORDS_H_ #include +#include #include namespace Botan { @@ -22,8 +23,9 @@ * @param key the secret key shared between client and server * @param hash_algo the hash algorithm to use, should be SHA-1 or SHA-256 * @param digits the number of digits in the OTP (must be 6, 7, or 8) + * TODO(Botan4) remove the default hash param here */ - HOTP(const SymmetricKey& key, std::string_view hash_algo = "SHA-1", size_t digits = 6) : + BOTAN_FUTURE_EXPLICIT HOTP(const SymmetricKey& key, std::string_view hash_algo = "SHA-1", size_t digits = 6) : HOTP(key.begin(), key.size(), hash_algo, digits) {} /** @@ -31,6 +33,7 @@ * @param key_len length of key param * @param hash_algo the hash algorithm to use, should be SHA-1 or SHA-256 * @param digits the number of digits in the OTP (must be 6, 7, or 8) + * TODO(Botan4) remove the default hash param here */ HOTP(const uint8_t key[], size_t key_len, std::string_view hash_algo = "SHA-1", size_t digits = 6); @@ -54,7 +57,7 @@ private: std::unique_ptr m_mac; - uint32_t m_digit_mod; + size_t m_digits; }; /** @@ -67,8 +70,12 @@ * @param hash_algo the hash algorithm to use, should be SHA-1, SHA-256 or SHA-512 * @param digits the number of digits in the OTP (must be 6, 7, or 8) * @param time_step granularity of OTP in seconds + * TODO(Botan4) remove the default hash param here */ - TOTP(const SymmetricKey& key, std::string_view hash_algo = "SHA-1", size_t digits = 6, size_t time_step = 30) : + BOTAN_FUTURE_EXPLICIT TOTP(const SymmetricKey& key, + std::string_view hash_algo = "SHA-1", + size_t digits = 6, + size_t time_step = 30) : TOTP(key.begin(), key.size(), hash_algo, digits, time_step) {} /** @@ -77,6 +84,7 @@ * @param hash_algo the hash algorithm to use, should be SHA-1, SHA-256 or SHA-512 * @param digits the number of digits in the OTP (must be 6, 7, or 8) * @param time_step granularity of OTP in seconds + * TODO(Botan4) remove the default hash param here */ TOTP(const uint8_t key[], size_t key_len, diff -Nru botan3-3.7.1+dfsg/src/lib/misc/hotp/totp.cpp botan3-3.12.0+dfsg/src/lib/misc/hotp/totp.cpp --- botan3-3.7.1+dfsg/src/lib/misc/hotp/totp.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/misc/hotp/totp.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,6 +7,7 @@ #include +#include #include namespace Botan { @@ -37,7 +38,7 @@ } bool TOTP::verify_totp(uint32_t otp, uint64_t unix_time, size_t clock_drift_accepted) { - uint64_t t = unix_time / m_time_step; + const uint64_t t = unix_time / m_time_step; for(size_t i = 0; i <= clock_drift_accepted; ++i) { if(m_hotp.generate_hotp(t - i) == otp) { diff -Nru botan3-3.7.1+dfsg/src/lib/misc/nist_keywrap/nist_keywrap.cpp botan3-3.12.0+dfsg/src/lib/misc/nist_keywrap/nist_keywrap.cpp --- botan3-3.7.1+dfsg/src/lib/misc/nist_keywrap/nist_keywrap.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/misc/nist_keywrap/nist_keywrap.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -189,26 +189,30 @@ R = raw_nist_key_unwrap(input, input_len, bc, ICV_out); } - if((ICV_out >> 32) != 0xA65959A6) { - throw Invalid_Authentication_Tag("NIST key unwrap failed"); - } + /* + The padded key wrap ICV is 0xA65959A6 || uint32(plaintext_length). - const size_t len = (ICV_out & 0xFFFFFFFF); + We know the expected ICV almost entirely: the top 32 bits are the + fixed constant and the bottom 32 bits encode the original plaintext + length, which is R.size() minus 0 to 7 bytes of padding. Compute + the ICV we'd expect for the zero-padding case and subtract ICV_out; + for a valid unwrap the difference is at most 7, and equals the padding. + */ + const uint64_t expected_ICV_max = 0xA65959A600000000 | static_cast(R.size()); + const uint64_t padding = expected_ICV_max - ICV_out; - if(R.size() < 8 || len > R.size() || len <= R.size() - 8) { + if(padding > 7) { throw Invalid_Authentication_Tag("NIST key unwrap failed"); } - const size_t padding = R.size() - len; - - for(size_t i = 0; i != padding; ++i) { - if(R[R.size() - i - 1] != 0) { - throw Invalid_Authentication_Tag("NIST key unwrap failed"); - } + // Verify padding bytes are zero + const uint64_t last_block = load_be(R.data() + R.size() - 8, 0); + const uint64_t padding_mask = (static_cast(1) << (padding * 8)) - 1; + if((last_block & padding_mask) != 0) { + throw Invalid_Authentication_Tag("NIST key unwrap failed"); } - R.resize(R.size() - padding); - + R.resize(R.size() - static_cast(padding)); return R; } diff -Nru botan3-3.7.1+dfsg/src/lib/misc/nist_keywrap/nist_keywrap.h botan3-3.12.0+dfsg/src/lib/misc/nist_keywrap/nist_keywrap.h --- botan3-3.7.1+dfsg/src/lib/misc/nist_keywrap/nist_keywrap.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/misc/nist_keywrap/nist_keywrap.h 2026-05-07 01:38:28.000000000 +0000 @@ -8,6 +8,7 @@ #define BOTAN_NIST_KEY_WRAP_H_ #include +#include namespace Botan { @@ -24,6 +25,16 @@ nist_key_wrap(const uint8_t input[], size_t input_len, const BlockCipher& bc); /** +* Key wrap. See RFC 3394 and NIST SP800-38F +* @param input the value to be encrypted +* @param bc a keyed 128-bit block cipher that will be used to encrypt input +* @return input encrypted under NIST key wrap algorithm +*/ +inline std::vector nist_key_wrap(std::span input, const BlockCipher& bc) { + return nist_key_wrap(input.data(), input.size(), bc); +} + +/** * @param input the value to be decrypted, output of nist_key_wrap * @param input_len length of input * @param bc a keyed 128-bit block cipher that will be used to decrypt input @@ -34,6 +45,16 @@ nist_key_unwrap(const uint8_t input[], size_t input_len, const BlockCipher& bc); /** +* @param input the value to be decrypted, output of nist_key_wrap +* @param bc a keyed 128-bit block cipher that will be used to decrypt input +* @return input decrypted under NIST key wrap algorithm +* Throws an exception if decryption fails. +*/ +inline secure_vector nist_key_unwrap(std::span input, const BlockCipher& bc) { + return nist_key_unwrap(input.data(), input.size(), bc); +} + +/** * KWP (key wrap with padding). See RFC 5649 and NIST SP800-38F * @param input the value to be encrypted * @param input_len length of input @@ -44,6 +65,16 @@ nist_key_wrap_padded(const uint8_t input[], size_t input_len, const BlockCipher& bc); /** +* KWP (key wrap with padding). See RFC 5649 and NIST SP800-38F +* @param input the value to be encrypted +* @param bc a keyed 128-bit block cipher that will be used to encrypt input +* @return input encrypted under NIST key wrap algorithm +*/ +inline std::vector nist_key_wrap_padded(std::span input, const BlockCipher& bc) { + return nist_key_wrap_padded(input.data(), input.size(), bc); +} + +/** * @param input the value to be decrypted, output of nist_key_wrap * @param input_len length of input * @param bc a keyed 128-bit block cipher that will be used to decrypt input @@ -53,6 +84,16 @@ secure_vector BOTAN_PUBLIC_API(2, 4) nist_key_unwrap_padded(const uint8_t input[], size_t input_len, const BlockCipher& bc); +/** +* @param input the value to be decrypted, output of nist_key_wrap +* @param bc a keyed 128-bit block cipher that will be used to decrypt input +* @return input decrypted under NIST key wrap algorithm +* Throws an exception if decryption fails. +*/ +inline secure_vector nist_key_unwrap_padded(std::span input, const BlockCipher& bc) { + return nist_key_unwrap_padded(input.data(), input.size(), bc); +} + } // namespace Botan #endif diff -Nru botan3-3.7.1+dfsg/src/lib/misc/rfc3394/rfc3394.cpp botan3-3.12.0+dfsg/src/lib/misc/rfc3394/rfc3394.cpp --- botan3-3.7.1+dfsg/src/lib/misc/rfc3394/rfc3394.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/misc/rfc3394/rfc3394.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,6 +7,7 @@ #include +#include #include #include diff -Nru botan3-3.7.1+dfsg/src/lib/misc/roughtime/roughtime.cpp botan3-3.12.0+dfsg/src/lib/misc/roughtime/roughtime.cpp --- botan3-3.7.1+dfsg/src/lib/misc/roughtime/roughtime.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/misc/roughtime/roughtime.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,11 +9,12 @@ #include #include +#include #include #include +#include #include -#include #include #include @@ -46,7 +47,7 @@ T copy(const uint8_t* t) requires(is_array::value) { - return typecast_copy(t); //arrays are endianess independent, so we do a memcpy + return typecast_copy(t); //arrays are endianness independent, so we do a memcpy } template @@ -113,10 +114,10 @@ bool verify_signature(const std::array& pk, const std::vector& payload, const std::array& signature) { - const char context[] = "RoughTime v1 response signature"; - Ed25519_PublicKey key(std::vector(pk.data(), pk.data() + pk.size())); + constexpr std::string_view context("RoughTime v1 response signature\0", 32); + const Ed25519_PublicKey key(std::vector(pk.data(), pk.data() + pk.size())); PK_Verifier verifier(key, "Pure"); - verifier.update(cast_char_ptr_to_uint8(context), sizeof(context)); //add context including \0 + verifier.update(context); verifier.update(payload); return verifier.check_signature(signature.data(), signature.size()); } @@ -130,7 +131,7 @@ return ret; } -void hashNode(std::array& hash, const std::array& node, bool reverse) { +void hashNode(std::span hash, std::span node, bool reverse) { auto h = HashFunction::create_or_throw("SHA-512"); h->update(1); if(reverse) { @@ -154,16 +155,14 @@ namespace Roughtime { -Nonce::Nonce(const std::vector& nonce) { +Nonce::Nonce(const std::vector& nonce) : m_nonce{} { if(nonce.size() != 64) { throw Invalid_Argument("Roughtime nonce must be 64 bytes long"); } m_nonce = typecast_copy>(nonce.data()); } -Nonce::Nonce(RandomNumberGenerator& rng) { - rng.randomize(m_nonce.data(), m_nonce.size()); -} +Nonce::Nonce(RandomNumberGenerator& rng) : m_nonce(rng.random_array<64>()) {} std::array encode_request(const Nonce& nonce) { std::array buf = {{2, 0, 0, 0, 64, 0, 0, 0, 'N', 'O', 'N', 'C', 'P', 'A', 'D', 0xff}}; @@ -193,19 +192,18 @@ const size_t size = path.size(); const size_t levels = size / 64; - if(size % 64) { + if(size % 64 != 0) { throw Roughtime_Error("Merkle tree path size must be multiple of 64 bytes"); } - if(indx >= (1U << levels)) { + if(levels >= 32 || indx >= (uint32_t(1) << levels)) { throw Roughtime_Error("Merkle tree path is too short"); } + BufferSlicer slicer(path); auto hash = hashLeaf(nonce.get_nonce()); auto index = indx; - size_t level = 0; - while(level < levels) { - hashNode(hash, typecast_copy>(path.data() + level * 64), index & 1); - ++level; + for(std::size_t level = 0; level < levels; ++level) { + hashNode(hash, slicer.take<64>(), index % 2 == 1); index >>= 1; } @@ -227,9 +225,9 @@ } bool Response::validate(const Ed25519_PublicKey& pk) const { - const char context[] = "RoughTime v1 delegation signature--"; + constexpr std::string_view context("RoughTime v1 delegation signature--\0", 36); PK_Verifier verifier(pk, "Pure"); - verifier.update(cast_char_ptr_to_uint8(context), sizeof(context)); //add context including \0 + verifier.update(context); verifier.update(m_cert_dele.data(), m_cert_dele.size()); return verifier.check_signature(m_cert_sig.data(), m_cert_sig.size()); } @@ -243,14 +241,15 @@ hash->update(blind_arr.data(), blind_arr.size()); hash->final(ret.data()); - return ret; + return Nonce(ret); } Chain::Chain(std::string_view str) { std::istringstream ss{std::string(str)}; // FIXME C++23 avoid copy const std::string ERROR_MESSAGE = "Line does not have 4 space separated fields"; for(std::string s; std::getline(ss, s);) { - size_t start = 0, end = 0; + size_t start = 0; + size_t end = 0; end = s.find(' ', start); if(end == std::string::npos) { throw Decoding_Error(ERROR_MESSAGE); @@ -291,9 +290,9 @@ std::vector Chain::responses() const { std::vector responses; - for(unsigned i = 0; i < m_links.size(); ++i) { + for(size_t i = 0; i < m_links.size(); ++i) { const auto& l = m_links[i]; - const auto nonce = i ? nonce_from_blind(m_links[i - 1].response(), l.nonce_or_blind()) : l.nonce_or_blind(); + const auto nonce = i > 0 ? nonce_from_blind(m_links[i - 1].response(), l.nonce_or_blind()) : l.nonce_or_blind(); const auto response = Response::from_bits(l.response(), nonce); if(!response.validate(l.public_key())) { throw Roughtime_Error("Invalid signature or public key"); @@ -365,7 +364,7 @@ //add one additional byte to be able to differentiate if datagram got truncated const auto n = socket->read(buffer.data(), buffer.size()); - if(!n || std::chrono::system_clock::now() - start_time > timeout) { + if(n == 0 || std::chrono::system_clock::now() - start_time > timeout) { throw System_Error("Timeout waiting for response"); } @@ -383,7 +382,8 @@ const std::string ERROR_MESSAGE = "Line does not have at least 5 space separated fields"; for(std::string s; std::getline(ss, s);) { - size_t start = 0, end = 0; + size_t start = 0; + size_t end = 0; end = s.find(' ', start); if(end == std::string::npos) { throw Decoding_Error(ERROR_MESSAGE); diff -Nru botan3-3.7.1+dfsg/src/lib/misc/roughtime/roughtime.h botan3-3.12.0+dfsg/src/lib/misc/roughtime/roughtime.h --- botan3-3.7.1+dfsg/src/lib/misc/roughtime/roughtime.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/misc/roughtime/roughtime.h 2026-05-07 01:38:28.000000000 +0000 @@ -32,10 +32,10 @@ class BOTAN_PUBLIC_API(2, 13) Nonce final { public: Nonce() = default; - Nonce(const std::vector& nonce); - Nonce(RandomNumberGenerator& rng); + explicit Nonce(const std::vector& nonce); + explicit Nonce(RandomNumberGenerator& rng); - Nonce(const std::array& nonce) { m_nonce = nonce; } + explicit Nonce(const std::array& nonce) : m_nonce(nonce) {} bool operator==(const Nonce& rhs) const { return m_nonce == rhs.m_nonce; } @@ -103,7 +103,7 @@ class BOTAN_PUBLIC_API(2, 13) Chain final { public: Chain() = default; //empty - Chain(std::string_view str); + explicit Chain(std::string_view str); const std::vector& links() const { return m_links; } diff -Nru botan3-3.7.1+dfsg/src/lib/misc/srp6/srp6.cpp botan3-3.12.0+dfsg/src/lib/misc/srp6/srp6.cpp --- botan3-3.7.1+dfsg/src/lib/misc/srp6/srp6.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/misc/srp6/srp6.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,5 +1,5 @@ /* -* SRP-6a (RFC 5054 compatatible) +* SRP-6a (RFC 5054 compatible) * (C) 2011,2012,2019,2020 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) @@ -7,7 +7,9 @@ #include +#include #include +#include #include #include @@ -105,12 +107,13 @@ const BigInt A = group.power_g_p(a, a_bits); const BigInt u = hash_seq(*hash_fn, p_bytes, A, B); + BOTAN_ASSERT_NOMSG(!u.is_zero()); const BigInt x = compute_x(*hash_fn, identifier, password, salt); const BigInt g_x_p = group.power_g_p(x, hash_fn->output_length() * 8); - const BigInt B_k_g_x_p = group.mod_p(B - group.multiply_mod_p(k, g_x_p)); + const BigInt B_k_g_x_p = group.mod_p(B + group.mod_p(p - group.multiply_mod_p(k, g_x_p))); const BigInt a_ux = a + u * x; @@ -194,6 +197,7 @@ } const BigInt u = hash_seq(*hash_fn, m_group->p_bytes(), A, m_B); + BOTAN_ASSERT_NOMSG(!u.is_zero()); const BigInt vup = m_group->power_b_p(m_v, u, m_group->p_bits()); const BigInt S = m_group->power_b_p(m_group->multiply_mod_p(A, vup), m_b, m_group->p_bits()); diff -Nru botan3-3.7.1+dfsg/src/lib/misc/srp6/srp6.h botan3-3.12.0+dfsg/src/lib/misc/srp6/srp6.h --- botan3-3.7.1+dfsg/src/lib/misc/srp6/srp6.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/misc/srp6/srp6.h 2026-05-07 01:38:28.000000000 +0000 @@ -1,5 +1,5 @@ /* -* SRP-6a (RFC 5054 compatatible) +* SRP-6a (RFC 5054 compatible) * (C) 2011,2012,2019 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) diff -Nru botan3-3.7.1+dfsg/src/lib/misc/tss/tss.cpp botan3-3.12.0+dfsg/src/lib/misc/tss/tss.cpp --- botan3-3.7.1+dfsg/src/lib/misc/tss/tss.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/misc/tss/tss.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,12 +1,13 @@ /* * RTSS (threshold secret sharing) -* (C) 2009,2018 Jack Lloyd +* (C) 2009,2018,2026 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ #include +#include #include #include #include @@ -20,48 +21,38 @@ const size_t RTSS_HEADER_SIZE = 20; /** -Table for GF(2^8) arithmetic (exponentials) +* Constant-time multiplication in GF(2^8) mod 0x11B */ -alignas(64) const uint8_t RTSS_EXP[256] = { - 0x01, 0x03, 0x05, 0x0F, 0x11, 0x33, 0x55, 0xFF, 0x1A, 0x2E, 0x72, 0x96, 0xA1, 0xF8, 0x13, 0x35, 0x5F, 0xE1, 0x38, - 0x48, 0xD8, 0x73, 0x95, 0xA4, 0xF7, 0x02, 0x06, 0x0A, 0x1E, 0x22, 0x66, 0xAA, 0xE5, 0x34, 0x5C, 0xE4, 0x37, 0x59, - 0xEB, 0x26, 0x6A, 0xBE, 0xD9, 0x70, 0x90, 0xAB, 0xE6, 0x31, 0x53, 0xF5, 0x04, 0x0C, 0x14, 0x3C, 0x44, 0xCC, 0x4F, - 0xD1, 0x68, 0xB8, 0xD3, 0x6E, 0xB2, 0xCD, 0x4C, 0xD4, 0x67, 0xA9, 0xE0, 0x3B, 0x4D, 0xD7, 0x62, 0xA6, 0xF1, 0x08, - 0x18, 0x28, 0x78, 0x88, 0x83, 0x9E, 0xB9, 0xD0, 0x6B, 0xBD, 0xDC, 0x7F, 0x81, 0x98, 0xB3, 0xCE, 0x49, 0xDB, 0x76, - 0x9A, 0xB5, 0xC4, 0x57, 0xF9, 0x10, 0x30, 0x50, 0xF0, 0x0B, 0x1D, 0x27, 0x69, 0xBB, 0xD6, 0x61, 0xA3, 0xFE, 0x19, - 0x2B, 0x7D, 0x87, 0x92, 0xAD, 0xEC, 0x2F, 0x71, 0x93, 0xAE, 0xE9, 0x20, 0x60, 0xA0, 0xFB, 0x16, 0x3A, 0x4E, 0xD2, - 0x6D, 0xB7, 0xC2, 0x5D, 0xE7, 0x32, 0x56, 0xFA, 0x15, 0x3F, 0x41, 0xC3, 0x5E, 0xE2, 0x3D, 0x47, 0xC9, 0x40, 0xC0, - 0x5B, 0xED, 0x2C, 0x74, 0x9C, 0xBF, 0xDA, 0x75, 0x9F, 0xBA, 0xD5, 0x64, 0xAC, 0xEF, 0x2A, 0x7E, 0x82, 0x9D, 0xBC, - 0xDF, 0x7A, 0x8E, 0x89, 0x80, 0x9B, 0xB6, 0xC1, 0x58, 0xE8, 0x23, 0x65, 0xAF, 0xEA, 0x25, 0x6F, 0xB1, 0xC8, 0x43, - 0xC5, 0x54, 0xFC, 0x1F, 0x21, 0x63, 0xA5, 0xF4, 0x07, 0x09, 0x1B, 0x2D, 0x77, 0x99, 0xB0, 0xCB, 0x46, 0xCA, 0x45, - 0xCF, 0x4A, 0xDE, 0x79, 0x8B, 0x86, 0x91, 0xA8, 0xE3, 0x3E, 0x42, 0xC6, 0x51, 0xF3, 0x0E, 0x12, 0x36, 0x5A, 0xEE, - 0x29, 0x7B, 0x8D, 0x8C, 0x8F, 0x8A, 0x85, 0x94, 0xA7, 0xF2, 0x0D, 0x17, 0x39, 0x4B, 0xDD, 0x7C, 0x84, 0x97, 0xA2, - 0xFD, 0x1C, 0x24, 0x6C, 0xB4, 0xC7, 0x52, 0xF6, 0x01}; +uint8_t tss_gf_mul(uint8_t x, uint8_t y) { + uint8_t r = 0; + for(size_t i = 0; i != 8; ++i) { + r ^= CT::Mask::expand(y & 1).if_set_return(x); + x = (x << 1) ^ CT::Mask::expand_top_bit(x).if_set_return(0x1B); + y >>= 1; + } + return r; +} /** -Table for GF(2^8) arithmetic (logarithms) +* Inversion in GF(2^8) via Fermat's little theorem. +* +* Returns 0 for input 0 - a case which should not occur in our usage. +* +* Really this does not need to be constant-time - we only use inversion when +* computing the Lagrange coefficients, which is derived entirely from public data. */ -alignas(64) const uint8_t RTSS_LOG[] = { - 0x90, 0x00, 0x19, 0x01, 0x32, 0x02, 0x1A, 0xC6, 0x4B, 0xC7, 0x1B, 0x68, 0x33, 0xEE, 0xDF, 0x03, 0x64, 0x04, 0xE0, - 0x0E, 0x34, 0x8D, 0x81, 0xEF, 0x4C, 0x71, 0x08, 0xC8, 0xF8, 0x69, 0x1C, 0xC1, 0x7D, 0xC2, 0x1D, 0xB5, 0xF9, 0xB9, - 0x27, 0x6A, 0x4D, 0xE4, 0xA6, 0x72, 0x9A, 0xC9, 0x09, 0x78, 0x65, 0x2F, 0x8A, 0x05, 0x21, 0x0F, 0xE1, 0x24, 0x12, - 0xF0, 0x82, 0x45, 0x35, 0x93, 0xDA, 0x8E, 0x96, 0x8F, 0xDB, 0xBD, 0x36, 0xD0, 0xCE, 0x94, 0x13, 0x5C, 0xD2, 0xF1, - 0x40, 0x46, 0x83, 0x38, 0x66, 0xDD, 0xFD, 0x30, 0xBF, 0x06, 0x8B, 0x62, 0xB3, 0x25, 0xE2, 0x98, 0x22, 0x88, 0x91, - 0x10, 0x7E, 0x6E, 0x48, 0xC3, 0xA3, 0xB6, 0x1E, 0x42, 0x3A, 0x6B, 0x28, 0x54, 0xFA, 0x85, 0x3D, 0xBA, 0x2B, 0x79, - 0x0A, 0x15, 0x9B, 0x9F, 0x5E, 0xCA, 0x4E, 0xD4, 0xAC, 0xE5, 0xF3, 0x73, 0xA7, 0x57, 0xAF, 0x58, 0xA8, 0x50, 0xF4, - 0xEA, 0xD6, 0x74, 0x4F, 0xAE, 0xE9, 0xD5, 0xE7, 0xE6, 0xAD, 0xE8, 0x2C, 0xD7, 0x75, 0x7A, 0xEB, 0x16, 0x0B, 0xF5, - 0x59, 0xCB, 0x5F, 0xB0, 0x9C, 0xA9, 0x51, 0xA0, 0x7F, 0x0C, 0xF6, 0x6F, 0x17, 0xC4, 0x49, 0xEC, 0xD8, 0x43, 0x1F, - 0x2D, 0xA4, 0x76, 0x7B, 0xB7, 0xCC, 0xBB, 0x3E, 0x5A, 0xFB, 0x60, 0xB1, 0x86, 0x3B, 0x52, 0xA1, 0x6C, 0xAA, 0x55, - 0x29, 0x9D, 0x97, 0xB2, 0x87, 0x90, 0x61, 0xBE, 0xDC, 0xFC, 0xBC, 0x95, 0xCF, 0xCD, 0x37, 0x3F, 0x5B, 0xD1, 0x53, - 0x39, 0x84, 0x3C, 0x41, 0xA2, 0x6D, 0x47, 0x14, 0x2A, 0x9E, 0x5D, 0x56, 0xF2, 0xD3, 0xAB, 0x44, 0x11, 0x92, 0xD9, - 0x23, 0x20, 0x2E, 0x89, 0xB4, 0x7C, 0xB8, 0x26, 0x77, 0x99, 0xE3, 0xA5, 0x67, 0x4A, 0xED, 0xDE, 0xC5, 0x31, 0xFE, - 0x18, 0x0D, 0x63, 0x8C, 0x80, 0xC0, 0xF7, 0x70, 0x07}; - -uint8_t gfp_mul(uint8_t x, uint8_t y) { - if(x == 0 || y == 0) { - return 0; - } - return RTSS_EXP[(RTSS_LOG[x] + RTSS_LOG[y]) % 255]; +uint8_t tss_gf_inv(uint8_t x) { + const uint8_t x2 = tss_gf_mul(x, x); + const uint8_t x3 = tss_gf_mul(x2, x); + const uint8_t x6 = tss_gf_mul(x3, x3); + const uint8_t x12 = tss_gf_mul(x6, x6); + const uint8_t x15 = tss_gf_mul(x12, x3); + const uint8_t x30 = tss_gf_mul(x15, x15); + const uint8_t x60 = tss_gf_mul(x30, x30); + const uint8_t x120 = tss_gf_mul(x60, x60); + const uint8_t x126 = tss_gf_mul(x120, x6); + const uint8_t x127 = tss_gf_mul(x126, x); + return tss_gf_mul(x127, x127); } uint8_t rtss_hash_id(std::string_view hash_name) { @@ -156,7 +147,7 @@ const uint16_t share_len = static_cast(secret.size() + 1); secure_vector share_header(RTSS_HEADER_SIZE); - copy_mem(&share_header[0], identifier.data(), identifier.size()); + copy_mem(share_header.data(), identifier.data(), identifier.size()); share_header[16] = hash_id; share_header[17] = M; share_header[18] = get_byte<0>(share_len); @@ -175,19 +166,19 @@ shares[i].m_contents.push_back(i + 1); } - for(size_t i = 0; i != secret.size(); ++i) { + for(const uint8_t secret_byte : secret) { std::vector coefficients(M - 1); rng.randomize(coefficients.data(), coefficients.size()); for(uint8_t j = 0; j != N; ++j) { const uint8_t X = j + 1; - uint8_t sum = secret[i]; + uint8_t sum = secret_byte; uint8_t X_i = X; - for(size_t k = 0; k != coefficients.size(); ++k) { - sum ^= gfp_mul(X_i, coefficients[k]); - X_i = gfp_mul(X_i, X); + for(const uint8_t cb : coefficients) { + sum ^= tss_gf_mul(X_i, cb); + X_i = tss_gf_mul(X_i, X); } shares[j].m_contents.push_back(sum); @@ -216,7 +207,7 @@ throw Decoding_Error("Different sized RTSS shares detected"); } - if(!CT::is_equal(&shares[0].m_contents[0], &shares[i].m_contents[0], RTSS_HEADER_SIZE).as_bool()) { + if(!CT::is_equal(shares[0].m_contents.data(), shares[i].m_contents.data(), RTSS_HEADER_SIZE).as_bool()) { throw Decoding_Error("Different RTSS headers detected"); } } @@ -236,7 +227,7 @@ if(shares[0].size() != RTSS_HEADER_SIZE + share_len) { /* - * This second (laxer) check accomodates a bug in TSS that was + * This second (laxer) check accommodates a bug in TSS that was * fixed in 2.9.0 - previous versions used the length of the * *secret* here, instead of the length of the *share*, which is * precisely 1 + hash_len longer. @@ -249,33 +240,43 @@ std::vector V(shares.size()); secure_vector recovered; + // Compute the Lagrange coefficients + std::vector lagrange_coeffs(shares.size()); + for(size_t k = 0; k != shares.size(); ++k) { + uint8_t coeff = 1; + for(size_t l = 0; l != shares.size(); ++l) { + if(k == l) { + continue; + } + const uint8_t share_k = shares[k].share_id(); + const uint8_t share_l = shares[l].share_id(); + if(share_k == share_l) { + throw Decoding_Error("Duplicate shares found in RTSS recovery"); + } + // We already verified this earlier in the function + BOTAN_ASSERT_NOMSG(share_k > 0 && share_l > 0); + const uint8_t div = tss_gf_mul(share_l, tss_gf_inv(share_k ^ share_l)); + coeff = tss_gf_mul(coeff, div); + } + lagrange_coeffs[k] = coeff; + } + for(size_t i = RTSS_HEADER_SIZE + 1; i != shares[0].size(); ++i) { for(size_t j = 0; j != V.size(); ++j) { V[j] = shares[j].m_contents[i]; } + /* + * Interpolation step + * + * This is effectively a multi-scalar multiplication (aka sum-of-products) + * where one of the inputs, namely the Lagrange coefficients, are public. + * If optimizing this function further was useful, this would be the place + * to start, for example by using Pippeneger's algorithm. + */ uint8_t r = 0; for(size_t k = 0; k != shares.size(); ++k) { - // L_i function: - uint8_t r2 = 1; - for(size_t l = 0; l != shares.size(); ++l) { - if(k == l) { - continue; - } - - uint8_t share_k = shares[k].share_id(); - uint8_t share_l = shares[l].share_id(); - - if(share_k == share_l) { - throw Decoding_Error("Duplicate shares found in RTSS recovery"); - } - - uint8_t div = RTSS_EXP[(255 + RTSS_LOG[share_l] - RTSS_LOG[share_k ^ share_l]) % 255]; - - r2 = gfp_mul(r2, div); - } - - r ^= gfp_mul(V[k], r2); + r ^= tss_gf_mul(V[k], lagrange_coeffs[k]); } recovered.push_back(r); } diff -Nru botan3-3.7.1+dfsg/src/lib/misc/zfec/zfec.cpp botan3-3.12.0+dfsg/src/lib/misc/zfec/zfec.cpp --- botan3-3.7.1+dfsg/src/lib/misc/zfec/zfec.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/misc/zfec/zfec.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -12,15 +12,18 @@ #include #include -#include #include #include +#if defined(BOTAN_HAS_CPUID) + #include +#endif + namespace Botan { namespace { -/* Tables for arithetic in GF(2^8) using 1+x^2+x^3+x^4+x^8 +/* Tables for arithmetic in GF(2^8) using 1+x^2+x^3+x^4+x^8 * * See Lin & Costello, Appendix A, and Lee & Messerschmitt, p. 453. * @@ -99,7 +102,7 @@ std::vector m_table; }; - static GF_Table table; + static const GF_Table table; return table.ptr(y); } @@ -108,7 +111,7 @@ * (Gauss-Jordan algorithm, adapted from Numerical Recipes in C) */ void invert_matrix(uint8_t matrix[], size_t K) { - class pivot_searcher { + class pivot_searcher final { public: explicit pivot_searcher(size_t K) : m_ipiv(K) {} @@ -174,7 +177,7 @@ pivot_row[icol] = 1; if(c == 0) { - throw Invalid_Argument("ZFEC: singlar matrix"); + throw Invalid_Argument("ZFEC: singular matrix"); } if(c != 1) { @@ -289,7 +292,7 @@ const uint8_t* GF_MUL_Y = GF_MUL_TABLE(y); // first align z to 16 bytes - while(size > 0 && reinterpret_cast(z) % 16) { + while(size > 0 && reinterpret_cast(z) % 16 > 0) { z[0] ^= GF_MUL_Y[x[0]]; ++z; ++x; @@ -297,7 +300,7 @@ } #if defined(BOTAN_HAS_ZFEC_VPERM) - if(size >= 16 && CPUID::has_vperm()) { + if(size >= 16 && CPUID::has(CPUID::Feature::SIMD_4X32)) { const size_t consumed = addmul_vperm(z, x, y, size); z += consumed; x += consumed; @@ -305,15 +308,6 @@ } #endif -#if defined(BOTAN_HAS_ZFEC_SSE2) - if(size >= 64 && CPUID::has_sse2()) { - const size_t consumed = addmul_sse2(z, x, y, size); - z += consumed; - x += consumed; - size -= consumed; - } -#endif - while(size >= 16) { z[0] ^= GF_MUL_Y[x[0]]; z[1] ^= GF_MUL_Y[x[1]]; @@ -364,7 +358,7 @@ * K*K Vandermonde matrix, multiply right the bottom n-K rows * by the inverse, and construct the identity matrix at the top. */ - create_inverted_vdm(&temp_matrix[0], m_K); + create_inverted_vdm(temp_matrix.data(), m_K); for(size_t i = m_K * m_K; i != temp_matrix.size(); ++i) { temp_matrix[i] = GF_EXP[((i / m_K) * (i % m_K)) % 255]; @@ -429,10 +423,10 @@ clear_mem(fec_buf.data(), fec_buf.size()); for(size_t j = 0; j != m_K; ++j) { - addmul(&fec_buf[0], shares[j], m_enc_matrix[i * m_K + j], share_size); + addmul(fec_buf.data(), shares[j], m_enc_matrix[i * m_K + j], share_size); } - output_cb(i, &fec_buf[0], fec_buf.size()); + output_cb(i, fec_buf.data(), fec_buf.size()); } } @@ -506,35 +500,29 @@ // If we had the original data shares then no need to perform // a matrix inversion, return immediately. if(!missing_primary_share) { - for(size_t i = 0; i != indexes.size(); ++i) { - BOTAN_ASSERT_NOMSG(indexes[i] < m_K); + for(const size_t index : indexes) { + BOTAN_ASSERT_NOMSG(index < m_K); } return; } - invert_matrix(&decoding_matrix[0], m_K); + invert_matrix(decoding_matrix.data(), m_K); for(size_t i = 0; i != indexes.size(); ++i) { if(indexes[i] >= m_K) { std::vector buf(share_size); for(size_t col = 0; col != m_K; ++col) { - addmul(&buf[0], sharesv[col], decoding_matrix[i * m_K + col], share_size); + addmul(buf.data(), sharesv[col], decoding_matrix[i * m_K + col], share_size); } - output_cb(i, &buf[0], share_size); + output_cb(i, buf.data(), share_size); } } } std::string ZFEC::provider() const { #if defined(BOTAN_HAS_ZFEC_VPERM) - if(CPUID::has_vperm()) { - return "vperm"; - } -#endif - -#if defined(BOTAN_HAS_ZFEC_SSE2) - if(CPUID::has_sse2()) { - return "sse2"; + if(auto feat = CPUID::check(CPUID::Feature::SIMD_4X32)) { + return *feat; } #endif diff -Nru botan3-3.7.1+dfsg/src/lib/misc/zfec/zfec.h botan3-3.12.0+dfsg/src/lib/misc/zfec/zfec.h --- botan3-3.7.1+dfsg/src/lib/misc/zfec/zfec.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/misc/zfec/zfec.h 2026-05-07 01:38:28.000000000 +0000 @@ -72,10 +72,6 @@ private: static void addmul(uint8_t z[], const uint8_t x[], uint8_t y, size_t size); -#if defined(BOTAN_HAS_ZFEC_SSE2) - static size_t addmul_sse2(uint8_t z[], const uint8_t x[], uint8_t y, size_t size); -#endif - #if defined(BOTAN_HAS_ZFEC_VPERM) static size_t addmul_vperm(uint8_t z[], const uint8_t x[], uint8_t y, size_t size); #endif diff -Nru botan3-3.7.1+dfsg/src/lib/misc/zfec/zfec_sse2/info.txt botan3-3.12.0+dfsg/src/lib/misc/zfec/zfec_sse2/info.txt --- botan3-3.7.1+dfsg/src/lib/misc/zfec/zfec_sse2/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/misc/zfec/zfec_sse2/info.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,16 +0,0 @@ - -ZFEC_SSE2 -> 20211211 - - - -name -> "ZFEC SSE2" -brief -> "ZFEC using SSE2 instructions" - - - -sse2 - - - -simd - diff -Nru botan3-3.7.1+dfsg/src/lib/misc/zfec/zfec_sse2/zfec_sse2.cpp botan3-3.12.0+dfsg/src/lib/misc/zfec/zfec_sse2/zfec_sse2.cpp --- botan3-3.7.1+dfsg/src/lib/misc/zfec/zfec_sse2/zfec_sse2.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/misc/zfec/zfec_sse2/zfec_sse2.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,93 +0,0 @@ -/* -* (C) 2009,2010,2021 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include - -#include -#include - -namespace Botan { - -namespace { - -inline SIMD_4x32 rshift_1_u8(SIMD_4x32 v) { - return SIMD_4x32(_mm_add_epi8(v.raw(), v.raw())); -} - -inline SIMD_4x32 high_bit_set_u8(SIMD_4x32 v) { - return SIMD_4x32(_mm_cmpgt_epi8(_mm_setzero_si128(), v.raw())); -} - -} // namespace - -BOTAN_FUNC_ISA("sse2") size_t ZFEC::addmul_sse2(uint8_t z[], const uint8_t x[], uint8_t y, size_t size) { - const SIMD_4x32 polynomial = SIMD_4x32::splat_u8(0x1D); - - const size_t orig_size = size; - - // unrolled out to cache line size - while(size >= 64) { - SIMD_4x32 x_1 = SIMD_4x32::load_le(x); - SIMD_4x32 x_2 = SIMD_4x32::load_le(x + 16); - SIMD_4x32 x_3 = SIMD_4x32::load_le(x + 32); - SIMD_4x32 x_4 = SIMD_4x32::load_le(x + 48); - - SIMD_4x32 z_1 = SIMD_4x32::load_le(z); - SIMD_4x32 z_2 = SIMD_4x32::load_le(z + 16); - SIMD_4x32 z_3 = SIMD_4x32::load_le(z + 32); - SIMD_4x32 z_4 = SIMD_4x32::load_le(z + 48); - - if(y & 0x01) { - z_1 ^= x_1; - z_2 ^= x_2; - z_3 ^= x_3; - z_4 ^= x_4; - } - - for(size_t j = 1; j != 8; ++j) { - /* - * Each byte of each mask is either 0 or the polynomial 0x1D, - * depending on if the high bit of x_i is set or not. - */ - - const SIMD_4x32 mask_1(high_bit_set_u8(x_1)); - const SIMD_4x32 mask_2(high_bit_set_u8(x_2)); - const SIMD_4x32 mask_3(high_bit_set_u8(x_3)); - const SIMD_4x32 mask_4(high_bit_set_u8(x_4)); - - // x <<= 1 - x_1 = rshift_1_u8(x_1); - x_2 = rshift_1_u8(x_2); - x_3 = rshift_1_u8(x_3); - x_4 = rshift_1_u8(x_4); - - x_1 ^= mask_1 & polynomial; - x_2 ^= mask_2 & polynomial; - x_3 ^= mask_3 & polynomial; - x_4 ^= mask_4 & polynomial; - - if((y >> j) & 1) { - z_1 ^= x_1; - z_2 ^= x_2; - z_3 ^= x_3; - z_4 ^= x_4; - } - } - - z_1.store_le(z); - z_2.store_le(z + 16); - z_3.store_le(z + 32); - z_4.store_le(z + 48); - - x += 64; - z += 64; - size -= 64; - } - - return orig_size - size; -} - -} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/misc/zfec/zfec_vperm/info.txt botan3-3.12.0+dfsg/src/lib/misc/zfec/zfec_vperm/info.txt --- botan3-3.7.1+dfsg/src/lib/misc/zfec/zfec_vperm/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/misc/zfec/zfec_vperm/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + ZFEC_VPERM -> 20211211 - + name -> "ZFEC Vector Permutation" @@ -8,12 +8,12 @@ -x86_32:sse2 -x86_64:sse2 x86_32:ssse3 x86_64:ssse3 arm32:neon arm64:neon +loongarch64:lsx +wasm:simd128 @@ -21,8 +21,11 @@ x86_64 arm32 arm64 +loongarch64 +wasm -simd +cpuid +simd_4x32 diff -Nru botan3-3.7.1+dfsg/src/lib/misc/zfec/zfec_vperm/zfec_vperm.cpp botan3-3.12.0+dfsg/src/lib/misc/zfec/zfec_vperm/zfec_vperm.cpp --- botan3-3.7.1+dfsg/src/lib/misc/zfec/zfec_vperm/zfec_vperm.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/misc/zfec/zfec_vperm/zfec_vperm.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,11 +7,8 @@ #include -#include - -#if defined(BOTAN_SIMD_USE_SSE2) - #include -#endif +#include +#include namespace Botan { @@ -458,29 +455,9 @@ 0x48, 0xb7, 0x70, 0x8f, 0x93, 0x6c, 0x00, 0x4b, 0x96, 0xdd, 0x31, 0x7a, 0xa7, 0xec, 0x62, 0x29, 0xf4, 0xbf, 0x53, 0x18, 0xc5, 0x8e}; -inline SIMD_4x32 BOTAN_FUNC_ISA(BOTAN_VPERM_ISA) table_lookup(SIMD_4x32 t, SIMD_4x32 v) { -#if defined(BOTAN_SIMD_USE_SSE2) - return SIMD_4x32(_mm_shuffle_epi8(t.raw(), v.raw())); -#elif defined(BOTAN_SIMD_USE_NEON) - const uint8x16_t tbl = vreinterpretq_u8_u32(t.raw()); - const uint8x16_t idx = vreinterpretq_u8_u32(v.raw()); - - #if defined(BOTAN_TARGET_ARCH_IS_ARM32) - const uint8x8x2_t tbl2 = {vget_low_u8(tbl), vget_high_u8(tbl)}; - - return SIMD_4x32( - vreinterpretq_u32_u8(vcombine_u8(vtbl2_u8(tbl2, vget_low_u8(idx)), vtbl2_u8(tbl2, vget_high_u8(idx))))); - - #else - return SIMD_4x32(vreinterpretq_u32_u8(vqtbl1q_u8(tbl, idx))); - #endif - -#endif -} - } // namespace -BOTAN_FUNC_ISA(BOTAN_VPERM_ISA) size_t ZFEC::addmul_vperm(uint8_t z[], const uint8_t x[], uint8_t y, size_t size) { +BOTAN_FN_ISA_SIMD_4X32 size_t ZFEC::addmul_vperm(uint8_t z[], const uint8_t x[], uint8_t y, size_t size) { const auto mask = SIMD_4x32::splat_u8(0x0F); // fetch the lookup tables for the given y @@ -499,8 +476,8 @@ const auto x_hi = x_1.shr<4>() & mask; // 16x parallel lookups - const auto r_lo = table_lookup(t_lo, x_lo); - const auto r_hi = table_lookup(t_hi, x_hi); + const auto r_lo = SIMD_4x32::byte_shuffle(t_lo, x_lo); + const auto r_hi = SIMD_4x32::byte_shuffle(t_hi, x_hi); // sum the outputs. z_1 ^= r_lo; diff -Nru botan3-3.7.1+dfsg/src/lib/modes/aead/aead.cpp botan3-3.12.0+dfsg/src/lib/modes/aead/aead.cpp --- botan3-3.7.1+dfsg/src/lib/modes/aead/aead.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/modes/aead/aead.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,6 +6,8 @@ #include +#include +#include #include #include #include @@ -38,6 +40,10 @@ #include #endif +#if defined(BOTAN_HAS_ASCON_AEAD128) + #include +#endif + namespace Botan { std::unique_ptr AEAD_Mode::create_or_throw(std::string_view algo, @@ -62,9 +68,19 @@ } #endif +#if defined(BOTAN_HAS_ASCON_AEAD128) + if(algo == "Ascon-AEAD128") { + if(dir == Cipher_Dir::Encryption) { + return std::make_unique(); + } else { + return std::make_unique(); + } + } +#endif + if(algo.find('/') != std::string::npos) { const std::vector algo_parts = split_on(algo, '/'); - std::string_view cipher_name = algo_parts[0]; + const std::string_view cipher_name = algo_parts[0]; const std::vector mode_info = parse_algorithm_name(algo_parts[1]); if(mode_info.empty()) { @@ -87,7 +103,7 @@ #if defined(BOTAN_HAS_BLOCK_CIPHER) - SCAN_Name req(algo); + const SCAN_Name req(algo); if(req.arg_count() == 0) { return std::unique_ptr(); @@ -101,8 +117,8 @@ #if defined(BOTAN_HAS_AEAD_CCM) if(req.algo_name() == "CCM") { - size_t tag_len = req.arg_as_integer(1, 16); - size_t L_len = req.arg_as_integer(2, 3); + const size_t tag_len = req.arg_as_integer(1, 16); + const size_t L_len = req.arg_as_integer(2, 3); if(dir == Cipher_Dir::Encryption) { return std::make_unique(std::move(bc), tag_len, L_len); } else { @@ -113,7 +129,7 @@ #if defined(BOTAN_HAS_AEAD_GCM) if(req.algo_name() == "GCM") { - size_t tag_len = req.arg_as_integer(1, 16); + const size_t tag_len = req.arg_as_integer(1, 16); if(dir == Cipher_Dir::Encryption) { return std::make_unique(std::move(bc), tag_len); } else { @@ -124,7 +140,7 @@ #if defined(BOTAN_HAS_AEAD_OCB) if(req.algo_name() == "OCB") { - size_t tag_len = req.arg_as_integer(1, 16); + const size_t tag_len = req.arg_as_integer(1, 16); if(dir == Cipher_Dir::Encryption) { return std::make_unique(std::move(bc), tag_len); } else { @@ -135,7 +151,7 @@ #if defined(BOTAN_HAS_AEAD_EAX) if(req.algo_name() == "EAX") { - size_t tag_len = req.arg_as_integer(1, bc->block_size()); + const size_t tag_len = req.arg_as_integer(1, bc->block_size()); if(dir == Cipher_Dir::Encryption) { return std::make_unique(std::move(bc), tag_len); } else { diff -Nru botan3-3.7.1+dfsg/src/lib/modes/aead/aead.h botan3-3.12.0+dfsg/src/lib/modes/aead/aead.h --- botan3-3.7.1+dfsg/src/lib/modes/aead/aead.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/modes/aead/aead.h 2026-05-07 01:38:28.000000000 +0000 @@ -128,8 +128,6 @@ * modes, and large enough that random collisions are unlikely) */ size_t default_nonce_length() const override { return 12; } - - ~AEAD_Mode() override = default; }; /** diff -Nru botan3-3.7.1+dfsg/src/lib/modes/aead/ascon_aead128/ascon_aead128.cpp botan3-3.12.0+dfsg/src/lib/modes/aead/ascon_aead128/ascon_aead128.cpp --- botan3-3.7.1+dfsg/src/lib/modes/aead/ascon_aead128/ascon_aead128.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/modes/aead/ascon_aead128/ascon_aead128.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,156 @@ +/* +* Ascon-AEAD128 AEAD +* (C) 2025 Jack Lloyd +* 2025 René Meusel +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include + +namespace Botan { + +namespace { + +constexpr void xor2x64(std::span lhs, std::span rhs) { + lhs[0] ^= rhs[0]; + lhs[1] ^= rhs[1]; +} + +template +constexpr auto as_array_of_uint64(std::span in) { + BOTAN_DEBUG_ASSERT(in.size() == N * sizeof(uint64_t)); + return load_le>(in.first()); +} + +// NIST SP.800-232 Appendix B (Table 13) +constexpr Ascon_p initial_state_of_ascon_aead_permutation({ + .init_and_final_rounds = 12, + .processing_rounds = 8, + .bit_rate = 128, + .initial_state = {}, +}); + +// NIST SP.800-232 Section 5.1 +constexpr uint64_t ascon_aead_128_iv = 0x00001000808c0001; + +// NIST SP.800-232 Appendix A.2 +constexpr uint64_t ascon_aead_128_domain_sep = 0x8000000000000000; + +} // namespace + +Ascon_AEAD128_Mode::Ascon_AEAD128_Mode() : m_ascon_p(initial_state_of_ascon_aead_permutation) {} + +void Ascon_AEAD128_Mode::clear() { + m_key.reset(); + m_ad.clear(); + reset(); +} + +void Ascon_AEAD128_Mode::reset() { + m_ascon_p = initial_state_of_ascon_aead_permutation; + m_started = false; + m_has_nonce = false; +} + +void Ascon_AEAD128_Mode::key_schedule(std::span key) { + clear(); + m_key = as_array_of_uint64<2>(key); +} + +void Ascon_AEAD128_Mode::set_associated_data_n(size_t idx, std::span ad) { + BOTAN_ARG_CHECK(idx == 0, "Ascon-AEAD128: cannot handle non-zero index in set_associated_data_n"); + m_ad.assign(ad.begin(), ad.end()); +} + +void Ascon_AEAD128_Mode::start_msg(const uint8_t nonce[], size_t nonce_len) { + BOTAN_ARG_CHECK(valid_nonce_length(nonce_len), "Invalid nonce length in Ascon-AEAD128"); + + BOTAN_STATE_CHECK(has_keying_material()); + BOTAN_STATE_CHECK(!m_started); + + m_ascon_p.state() = concat(std::array{ascon_aead_128_iv}, *m_key, as_array_of_uint64<2>({nonce, nonce_len})); + m_ascon_p.initial_permute(); + xor2x64(m_ascon_p.range_of_state<3, 2>(), *m_key); + + m_has_nonce = true; +} + +void Ascon_AEAD128_Mode::maybe_absorb_associated_data() { + BOTAN_DEBUG_ASSERT(has_keying_material()); + BOTAN_DEBUG_ASSERT(m_has_nonce); + + if(!m_started) { + if(!m_ad.empty()) { + m_ascon_p.absorb(m_ad); + m_ascon_p.intermediate_finish(); + } + m_ascon_p.state()[4] ^= ascon_aead_128_domain_sep; + + m_started = true; + } +} + +std::array Ascon_AEAD128_Mode::calculate_tag_and_finish() { + BOTAN_DEBUG_ASSERT(m_started); + + xor2x64(m_ascon_p.range_of_state<2, 2>(), *m_key); + m_ascon_p.finish(); + xor2x64(m_ascon_p.range_of_state<3, 2>(), *m_key); + + auto tag = store_le(m_ascon_p.range_of_state<3, 2>()); + + reset(); + return tag; +} + +size_t Ascon_AEAD128_Encryption::process_msg(uint8_t buf[], size_t size) { + BOTAN_STATE_CHECK(has_keying_material()); + BOTAN_STATE_CHECK(m_has_nonce); + + maybe_absorb_associated_data(); + m_ascon_p.percolate_in({buf, size}); + return size; +} + +void Ascon_AEAD128_Encryption::finish_msg(secure_vector& final_block, size_t offset) { + BOTAN_STATE_CHECK(has_keying_material()); + + const auto final_block_at_offset = std::span{final_block}.subspan(offset); + process_msg(final_block_at_offset.data(), final_block_at_offset.size()); + const auto tag = calculate_tag_and_finish(); + final_block.insert(final_block.end(), tag.begin(), tag.end()); +} + +size_t Ascon_AEAD128_Decryption::process_msg(uint8_t buf[], size_t size) { + BOTAN_STATE_CHECK(has_keying_material()); + BOTAN_STATE_CHECK(m_has_nonce); + + maybe_absorb_associated_data(); + m_ascon_p.percolate_out({buf, size}); + return size; +} + +void Ascon_AEAD128_Decryption::finish_msg(secure_vector& final_block, size_t offset) { + BOTAN_STATE_CHECK(has_keying_material()); + + const auto final_block_at_offset = std::span{final_block}.subspan(offset); + BOTAN_ARG_CHECK(final_block_at_offset.size() >= tag_size(), "input did not include the tag"); + const auto final_ciphertext_block = final_block_at_offset.first(final_block_at_offset.size() - tag_size()); + const auto expected_tag = final_block_at_offset.last(tag_size()); + + process_msg(final_ciphertext_block.data(), final_ciphertext_block.size()); + if(!constant_time_compare(calculate_tag_and_finish(), expected_tag)) { + clear_mem(std::span{final_block}.subspan(offset, final_ciphertext_block.size())); + throw Invalid_Authentication_Tag("Ascon-AEAD128 tag check failed"); + } + + final_block.resize(offset + final_ciphertext_block.size()); +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/modes/aead/ascon_aead128/ascon_aead128.h botan3-3.12.0+dfsg/src/lib/modes/aead/ascon_aead128/ascon_aead128.h --- botan3-3.7.1+dfsg/src/lib/modes/aead/ascon_aead128/ascon_aead128.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/modes/aead/ascon_aead128/ascon_aead128.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,99 @@ +/* +* Ascon-AEAD128 AEAD +* (C) 2025 Jack Lloyd +* 2025 René Meusel +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_ASCON_AEAD128_H_ +#define BOTAN_ASCON_AEAD128_H_ + +#include + +#include +#include + +#include + +namespace Botan { + +class Ascon_AEAD128_Mode : public AEAD_Mode { + public: + void set_associated_data_n(size_t idx, std::span ad) final; + + bool associated_data_requires_key() const final { return false; } + + std::string name() const final { return "Ascon-AEAD128"; } + + size_t update_granularity() const final { return 1; } + + size_t ideal_granularity() const final { return 32; } + + Key_Length_Specification key_spec() const final { return Key_Length_Specification(16); } + + bool valid_nonce_length(size_t n) const final { return n == 16; } + + size_t default_nonce_length() const final { return 16; } + + size_t tag_size() const final { return 16; } + + void clear() final; + + void reset() final; + + bool has_keying_material() const final { return m_key.has_value(); } + + protected: + Ascon_AEAD128_Mode(); + + void start_msg(const uint8_t nonce[], size_t nonce_len) final; + void key_schedule(std::span key) final; + + void maybe_absorb_associated_data(); + std::array calculate_tag_and_finish(); + + protected: + std::optional> m_key; // NOLINT(*-non-private-member-*) + Ascon_p m_ascon_p; // NOLINT(*-non-private-member-*) + bool m_has_nonce = false; // NOLINT(*-non-private-member-*) + + private: + std::vector m_ad; + bool m_started = false; +}; + +/** +* Ascon-AEAD128 Encryption +*/ +class Ascon_AEAD128_Encryption final : public Ascon_AEAD128_Mode { + public: + size_t output_length(size_t input_length) const override { return input_length + tag_size(); } + + size_t minimum_final_size() const override { return 0; } + + private: + size_t process_msg(uint8_t buf[], size_t size) final; + void finish_msg(secure_vector& final_block, size_t offset = 0) override; +}; + +/** +* Ascon-AEAD128 Decryption +*/ +class Ascon_AEAD128_Decryption final : public Ascon_AEAD128_Mode { + public: + size_t output_length(size_t input_length) const override { + BOTAN_ARG_CHECK(input_length >= tag_size(), "Sufficient input"); + return input_length - tag_size(); + } + + size_t minimum_final_size() const override { return tag_size(); } + + private: + size_t process_msg(uint8_t buf[], size_t size) final; + void finish_msg(secure_vector& final_block, size_t offset = 0) override; +}; + +} // namespace Botan + +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/modes/aead/ascon_aead128/info.txt botan3-3.12.0+dfsg/src/lib/modes/aead/ascon_aead128/info.txt --- botan3-3.7.1+dfsg/src/lib/modes/aead/ascon_aead128/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/modes/aead/ascon_aead128/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,11 @@ + +ASCON_AEAD128 -> 20250823 + + + +name -> "Ascon-AEAD128" + + + +ascon_perm + diff -Nru botan3-3.7.1+dfsg/src/lib/modes/aead/ccm/ccm.cpp botan3-3.12.0+dfsg/src/lib/modes/aead/ccm/ccm.cpp --- botan3-3.7.1+dfsg/src/lib/modes/aead/ccm/ccm.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/modes/aead/ccm/ccm.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,6 +8,8 @@ #include +#include +#include #include #include #include @@ -50,8 +52,8 @@ return fmt("{}/CCM({},{})", m_cipher->name(), tag_size(), L()); } -bool CCM_Mode::valid_nonce_length(size_t n) const { - return (n == (15 - L())); +bool CCM_Mode::valid_nonce_length(size_t length) const { + return (length == (15 - L())); } size_t CCM_Mode::default_nonce_length() const { @@ -95,7 +97,7 @@ m_ad_buf.push_back(get_byte<0>(static_cast(ad.size()))); m_ad_buf.push_back(get_byte<1>(static_cast(ad.size()))); m_ad_buf.insert(m_ad_buf.end(), ad.begin(), ad.end()); - while(m_ad_buf.size() % CCM_BS) { + while(m_ad_buf.size() % CCM_BS != 0) { m_ad_buf.push_back(0); // pad with zeros to full block size } } @@ -113,6 +115,15 @@ size_t CCM_Mode::process_msg(uint8_t buf[], size_t sz) { BOTAN_STATE_CHECK(!m_nonce.empty()); m_msg_buf.insert(m_msg_buf.end(), buf, buf + sz); + + // CCM message length is limited to 2^(8*L) - 1 bytes + if(L() < 8) { + const uint64_t max_msg_len = (static_cast(1) << (8 * L())) - 1; + if(m_msg_buf.size() > max_msg_len) { + throw Invalid_State("CCM message length exceeds the limit for L"); + } + } + return 0; // no output until finished } @@ -132,7 +143,9 @@ void CCM_Mode::inc(secure_vector& C) { for(size_t i = 0; i != C.size(); ++i) { - if(++C[C.size() - i - 1]) { + uint8_t& b = C[C.size() - i - 1]; + b += 1; + if(b > 0) { break; } } @@ -267,6 +280,7 @@ T ^= S0; if(!CT::is_equal(T.data(), buf_end, tag_size()).as_bool()) { + clear_mem(std::span{buffer}.subspan(offset, sz - tag_size())); throw Invalid_Authentication_Tag("CCM tag check failed"); } diff -Nru botan3-3.7.1+dfsg/src/lib/modes/aead/ccm/ccm.h botan3-3.12.0+dfsg/src/lib/modes/aead/ccm/ccm.h --- botan3-3.7.1+dfsg/src/lib/modes/aead/ccm/ccm.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/modes/aead/ccm/ccm.h 2026-05-07 01:38:28.000000000 +0000 @@ -10,6 +10,8 @@ #define BOTAN_AEAD_CCM_H_ #include + +#include #include namespace Botan { @@ -34,7 +36,7 @@ Key_Length_Specification key_spec() const final; - bool valid_nonce_length(size_t) const final; + bool valid_nonce_length(size_t length) const final; size_t default_nonce_length() const final; @@ -89,7 +91,7 @@ * @param L length of L parameter. The total message length * must be less than 2**L bytes, and the nonce is 15-L bytes. */ - CCM_Encryption(std::unique_ptr cipher, size_t tag_size = 16, size_t L = 3) : + explicit CCM_Encryption(std::unique_ptr cipher, size_t tag_size = 16, size_t L = 3) : CCM_Mode(std::move(cipher), tag_size, L) {} size_t output_length(size_t input_length) const override { return input_length + tag_size(); } @@ -112,7 +114,7 @@ * @param L length of L parameter. The total message length * must be less than 2**L bytes, and the nonce is 15-L bytes. */ - CCM_Decryption(std::unique_ptr cipher, size_t tag_size = 16, size_t L = 3) : + explicit CCM_Decryption(std::unique_ptr cipher, size_t tag_size = 16, size_t L = 3) : CCM_Mode(std::move(cipher), tag_size, L) {} size_t output_length(size_t input_length) const override { diff -Nru botan3-3.7.1+dfsg/src/lib/modes/aead/chacha20poly1305/chacha20poly1305.cpp botan3-3.12.0+dfsg/src/lib/modes/aead/chacha20poly1305/chacha20poly1305.cpp --- botan3-3.7.1+dfsg/src/lib/modes/aead/chacha20poly1305/chacha20poly1305.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/modes/aead/chacha20poly1305/chacha20poly1305.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,6 +8,8 @@ #include +#include +#include #include #include @@ -60,9 +62,9 @@ m_ad.assign(ad.begin(), ad.end()); } -void ChaCha20Poly1305_Mode::update_len(size_t len) { +void ChaCha20Poly1305_Mode::update_len(uint64_t len) { uint8_t len8[8] = {0}; - store_le(static_cast(len), len8); + store_le(len, len8); m_poly1305->update(len8, 8); } @@ -86,7 +88,7 @@ m_poly1305->update(m_ad); if(cfrg_version()) { - if(m_ad.size() % 16) { + if(m_ad.size() % 16 != 0) { const uint8_t zeros[16] = {0}; m_poly1305->update(zeros, 16 - m_ad.size() % 16); } @@ -99,15 +101,23 @@ m_chacha->cipher1(buf, sz); m_poly1305->update(buf, sz); // poly1305 of ciphertext m_ctext_len += sz; + + // RFC 8439 limits messages to 2^38-64 bytes + constexpr uint64_t MAX_CHACHA20POLY1305_INPUT = (static_cast(1) << 38) - 64; + if(cfrg_version() && m_ctext_len > MAX_CHACHA20POLY1305_INPUT) { + throw Invalid_State("ChaCha20Poly1305 message length limit exceeded"); + } + return sz; } void ChaCha20Poly1305_Encryption::finish_msg(secure_vector& buffer, size_t offset) { update(buffer, offset); if(cfrg_version()) { - if(m_ctext_len % 16) { + if(m_ctext_len % 16 != 0) { const uint8_t zeros[16] = {0}; - m_poly1305->update(zeros, 16 - m_ctext_len % 16); + const size_t padding = static_cast(16 - m_ctext_len % 16); + m_poly1305->update(zeros, padding); } update_len(m_ad.size()); } @@ -123,6 +133,12 @@ m_poly1305->update(buf, sz); // poly1305 of ciphertext m_chacha->cipher1(buf, sz); m_ctext_len += sz; + + constexpr uint64_t MAX_CHACHA20POLY1305_INPUT = (static_cast(1) << 38) - 64; + if(cfrg_version() && m_ctext_len > MAX_CHACHA20POLY1305_INPUT) { + throw Invalid_State("ChaCha20Poly1305 message length limit exceeded"); + } + return sz; } @@ -135,16 +151,17 @@ const size_t remaining = sz - tag_size(); - if(remaining) { + if(remaining > 0) { m_poly1305->update(buf, remaining); // poly1305 of ciphertext m_chacha->cipher1(buf, remaining); m_ctext_len += remaining; } if(cfrg_version()) { - if(m_ctext_len % 16) { + if(m_ctext_len % 16 != 0) { const uint8_t zeros[16] = {0}; - m_poly1305->update(zeros, 16 - m_ctext_len % 16); + const size_t padding = static_cast(16 - m_ctext_len % 16); + m_poly1305->update(zeros, padding); } update_len(m_ad.size()); } @@ -160,6 +177,7 @@ m_nonce_len = 0; if(!CT::is_equal(mac, included_tag, tag_size()).as_bool()) { + clear_mem(std::span{buffer}.subspan(offset, remaining)); throw Invalid_Authentication_Tag("ChaCha20Poly1305 tag check failed"); } buffer.resize(offset + remaining); diff -Nru botan3-3.7.1+dfsg/src/lib/modes/aead/chacha20poly1305/chacha20poly1305.h botan3-3.12.0+dfsg/src/lib/modes/aead/chacha20poly1305/chacha20poly1305.h --- botan3-3.7.1+dfsg/src/lib/modes/aead/chacha20poly1305/chacha20poly1305.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/modes/aead/chacha20poly1305/chacha20poly1305.h 2026-05-07 01:38:28.000000000 +0000 @@ -10,6 +10,8 @@ #define BOTAN_AEAD_CHACHA20_POLY1305_H_ #include + +#include #include #include @@ -47,18 +49,18 @@ bool has_keying_material() const final; protected: - std::unique_ptr m_chacha; - std::unique_ptr m_poly1305; + std::unique_ptr m_chacha; // NOLINT(*non-private-member-variable*) + std::unique_ptr m_poly1305; // NOLINT(*non-private-member-variable*) ChaCha20Poly1305_Mode(); - secure_vector m_ad; - size_t m_nonce_len = 0; - size_t m_ctext_len = 0; + secure_vector m_ad; // NOLINT(*non-private-member-variable*) + size_t m_nonce_len = 0; // NOLINT(*non-private-member-variable*) + uint64_t m_ctext_len = 0; // NOLINT(*non-private-member-variable*) bool cfrg_version() const { return m_nonce_len == 12 || m_nonce_len == 24; } - void update_len(size_t len); + void update_len(uint64_t len); private: void start_msg(const uint8_t nonce[], size_t nonce_len) override; diff -Nru botan3-3.7.1+dfsg/src/lib/modes/aead/eax/eax.cpp botan3-3.12.0+dfsg/src/lib/modes/aead/eax/eax.cpp --- botan3-3.7.1+dfsg/src/lib/modes/aead/eax/eax.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/modes/aead/eax/eax.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,6 +8,8 @@ #include +#include +#include #include #include #include @@ -99,7 +101,7 @@ */ void EAX_Mode::set_associated_data_n(size_t idx, std::span ad) { BOTAN_ARG_CHECK(idx == 0, "EAX: cannot handle non-zero index in set_associated_data_n"); - if(m_nonce_mac.empty() == false) { + if(!m_nonce_mac.empty()) { throw Invalid_State("Cannot set AD for EAX while processing a message"); } m_ad_mac = eax_prf(1, block_size(), *m_cmac, ad.data(), ad.size()); @@ -153,6 +155,7 @@ } void EAX_Decryption::finish_msg(secure_vector& buffer, size_t offset) { + BOTAN_STATE_CHECK(!m_nonce_mac.empty()); BOTAN_ARG_CHECK(buffer.size() >= offset, "Offset is out of range"); const size_t sz = buffer.size() - offset; uint8_t* buf = buffer.data() + offset; @@ -161,7 +164,7 @@ const size_t remaining = sz - tag_size(); - if(remaining) { + if(remaining > 0) { m_cmac->update(buf, remaining); m_ctr->cipher(buf, buf, remaining); } @@ -184,6 +187,7 @@ m_nonce_mac.clear(); if(!accept_mac) { + clear_mem(std::span{buffer}.subspan(offset, remaining)); throw Invalid_Authentication_Tag("EAX tag check failed"); } } diff -Nru botan3-3.7.1+dfsg/src/lib/modes/aead/eax/eax.h botan3-3.12.0+dfsg/src/lib/modes/aead/eax/eax.h --- botan3-3.7.1+dfsg/src/lib/modes/aead/eax/eax.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/modes/aead/eax/eax.h 2026-05-07 01:38:28.000000000 +0000 @@ -10,6 +10,8 @@ #define BOTAN_AEAD_EAX_H_ #include + +#include #include #include #include @@ -32,7 +34,7 @@ Key_Length_Specification key_spec() const final; // EAX supports arbitrary nonce lengths - bool valid_nonce_length(size_t) const final { return true; } + bool valid_nonce_length(size_t /*length*/) const final { return true; } size_t tag_size() const final { return m_tag_size; } @@ -51,15 +53,15 @@ size_t block_size() const { return m_cipher->block_size(); } - size_t m_tag_size; + size_t m_tag_size; // NOLINT(*non-private-member-variable*) - std::unique_ptr m_cipher; - std::unique_ptr m_ctr; - std::unique_ptr m_cmac; + std::unique_ptr m_cipher; // NOLINT(*non-private-member-variable*) + std::unique_ptr m_ctr; // NOLINT(*non-private-member-variable*) + std::unique_ptr m_cmac; // NOLINT(*non-private-member-variable*) - secure_vector m_ad_mac; + secure_vector m_ad_mac; // NOLINT(*non-private-member-variable*) - secure_vector m_nonce_mac; + secure_vector m_nonce_mac; // NOLINT(*non-private-member-variable*) private: void start_msg(const uint8_t nonce[], size_t nonce_len) final; @@ -76,7 +78,7 @@ * @param cipher a 128-bit block cipher * @param tag_size is how big the auth tag will be */ - EAX_Encryption(std::unique_ptr cipher, size_t tag_size = 0) : + explicit EAX_Encryption(std::unique_ptr cipher, size_t tag_size = 0) : EAX_Mode(std::move(cipher), tag_size) {} size_t output_length(size_t input_length) const override { return input_length + tag_size(); } @@ -97,7 +99,7 @@ * @param cipher a 128-bit block cipher * @param tag_size is how big the auth tag will be */ - EAX_Decryption(std::unique_ptr cipher, size_t tag_size = 0) : + explicit EAX_Decryption(std::unique_ptr cipher, size_t tag_size = 0) : EAX_Mode(std::move(cipher), tag_size) {} size_t output_length(size_t input_length) const override { diff -Nru botan3-3.7.1+dfsg/src/lib/modes/aead/gcm/gcm.cpp botan3-3.12.0+dfsg/src/lib/modes/aead/gcm/gcm.cpp --- botan3-3.7.1+dfsg/src/lib/modes/aead/gcm/gcm.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/modes/aead/gcm/gcm.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,11 +9,12 @@ #include #include +#include +#include #include #include #include #include - #include namespace Botan { @@ -46,7 +47,7 @@ } void GCM_Mode::reset() { - m_ghash->reset(); + m_ghash->reset_state(); } std::string GCM_Mode::name() const { @@ -62,7 +63,7 @@ } size_t GCM_Mode::ideal_granularity() const { - return GCM_BS * std::max(2, BOTAN_BLOCK_CIPHER_PAR_MULT); + return GCM_BS * std::max(2, BlockCipher::ParallelismMult); } bool GCM_Mode::valid_nonce_length(size_t len) const { @@ -81,10 +82,10 @@ void GCM_Mode::key_schedule(std::span key) { m_ctr->set_key(key); - const std::vector zeros(GCM_BS); - m_ctr->set_iv(zeros.data(), zeros.size()); + std::array zeros{}; + m_ctr->set_iv(zeros); - secure_vector H(GCM_BS); + uint8_t H[GCM_BS] = {0}; m_ctr->encipher(H); m_ghash->set_key(H); } @@ -99,26 +100,22 @@ throw Invalid_IV_Length(name(), nonce_len); } - if(m_y0.size() != GCM_BS) { - m_y0.resize(GCM_BS); - } - - clear_mem(m_y0.data(), m_y0.size()); + std::array y0 = {}; if(nonce_len == 12) { - copy_mem(m_y0.data(), nonce, nonce_len); - m_y0[15] = 1; + copy_mem(y0.data(), nonce, nonce_len); + y0[15] = 1; } else { - m_ghash->nonce_hash(m_y0, {nonce, nonce_len}); + m_ghash->nonce_hash(std::span(y0), {nonce, nonce_len}); } - m_ctr->set_iv(m_y0.data(), m_y0.size()); + m_ctr->set_iv(y0.data(), y0.size()); - clear_mem(m_y0.data(), m_y0.size()); - m_ctr->encipher(m_y0); + clear_mem(y0.data(), y0.size()); + m_ctr->encipher(y0); - m_ghash->start(m_y0); - clear_mem(m_y0.data(), m_y0.size()); + m_ghash->start(y0); + secure_scrub_memory(y0); } size_t GCM_Encryption::process_msg(uint8_t buf[], size_t sz) { @@ -158,7 +155,7 @@ const size_t remaining = sz - tag_size(); // handle any final input before the tag - if(remaining) { + if(remaining > 0) { m_ghash->update({buf, remaining}); m_ctr->cipher(buf, buf, remaining); } @@ -169,6 +166,7 @@ const uint8_t* included_tag = &buffer[remaining + offset]; if(!CT::is_equal(mac.data(), included_tag, tag_size()).as_bool()) { + clear_mem(std::span{buffer}.subspan(offset, remaining)); throw Invalid_Authentication_Tag("GCM tag check failed"); } diff -Nru botan3-3.7.1+dfsg/src/lib/modes/aead/gcm/gcm.h botan3-3.12.0+dfsg/src/lib/modes/aead/gcm/gcm.h --- botan3-3.7.1+dfsg/src/lib/modes/aead/gcm/gcm.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/modes/aead/gcm/gcm.h 2026-05-07 01:38:28.000000000 +0000 @@ -10,6 +10,8 @@ #define BOTAN_AEAD_GCM_H_ #include + +#include #include #include @@ -21,49 +23,47 @@ /** * GCM Mode */ -class GCM_Mode : public AEAD_Mode { +class GCM_Mode : public AEAD_Mode /* NOLINT(*-special-member-functions) */ { public: - void set_associated_data_n(size_t idx, std::span ad) override final; + void set_associated_data_n(size_t idx, std::span ad) final; - std::string name() const override final; + std::string name() const final; - size_t update_granularity() const override final; + size_t update_granularity() const final; - size_t ideal_granularity() const override final; + size_t ideal_granularity() const final; - Key_Length_Specification key_spec() const override final; + Key_Length_Specification key_spec() const final; - bool valid_nonce_length(size_t len) const override final; + bool valid_nonce_length(size_t len) const final; - size_t tag_size() const override final { return m_tag_size; } + size_t tag_size() const final { return m_tag_size; } - void clear() override final; + void clear() final; - void reset() override final; + void reset() final; - std::string provider() const override final; + std::string provider() const final; - bool has_keying_material() const override final; + bool has_keying_material() const final; - ~GCM_Mode(); + ~GCM_Mode() override; protected: GCM_Mode(std::unique_ptr cipher, size_t tag_size); static const size_t GCM_BS = 16; - const size_t m_tag_size; - const std::string m_cipher_name; + const size_t m_tag_size; // NOLINT(*non-private-member-variable*) + const std::string m_cipher_name; // NOLINT(*non-private-member-variable*) - std::unique_ptr m_ctr; - std::unique_ptr m_ghash; + std::unique_ptr m_ctr; // NOLINT(*non-private-member-variable*) + std::unique_ptr m_ghash; // NOLINT(*non-private-member-variable*) private: void start_msg(const uint8_t nonce[], size_t nonce_len) override; void key_schedule(std::span key) override; - - secure_vector m_y0; }; /** @@ -75,7 +75,7 @@ * @param cipher the 128 bit block cipher to use * @param tag_size is how big the auth tag will be */ - GCM_Encryption(std::unique_ptr cipher, size_t tag_size = 16) : + explicit GCM_Encryption(std::unique_ptr cipher, size_t tag_size = 16) : GCM_Mode(std::move(cipher), tag_size) {} size_t output_length(size_t input_length) const override { return input_length + tag_size(); } @@ -96,7 +96,7 @@ * @param cipher the 128 bit block cipher to use * @param tag_size is how big the auth tag will be */ - GCM_Decryption(std::unique_ptr cipher, size_t tag_size = 16) : + explicit GCM_Decryption(std::unique_ptr cipher, size_t tag_size = 16) : GCM_Mode(std::move(cipher), tag_size) {} size_t output_length(size_t input_length) const override { diff -Nru botan3-3.7.1+dfsg/src/lib/modes/aead/ocb/ocb.cpp botan3-3.12.0+dfsg/src/lib/modes/aead/ocb/ocb.cpp --- botan3-3.7.1+dfsg/src/lib/modes/aead/ocb/ocb.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/modes/aead/ocb/ocb.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,6 +9,8 @@ #include #include +#include +#include #include #include #include @@ -33,7 +35,7 @@ // memory overhead is negligible. // // See also https://github.com/randombit/botan/issues/3812 - m_L.reserve(31); + m_L.reserve(65); m_L.push_back(poly_double(dollar())); while(m_L.size() < 8) { @@ -45,7 +47,7 @@ void init(const secure_vector& offset) { m_offset = offset; } - bool initialized() const { return m_offset.empty() == false; } + bool initialized() const { return !m_offset.empty(); } const secure_vector& star() const { return m_L_star; } @@ -61,7 +63,7 @@ return m_L[i]; } - const uint8_t* compute_offsets(size_t block_index, size_t blocks) { + const uint8_t* compute_offsets(uint64_t block_index, size_t blocks) { BOTAN_ASSERT(blocks <= m_max_blocks, "OCB offsets"); uint8_t* offsets = m_offset_buf.data(); @@ -75,7 +77,7 @@ // ntz(4*i+2) == 1 // ntz(4*i+3) == 0 block_index += 4; - const size_t ntz4 = var_ctz32(static_cast(block_index)); + const size_t ntz4 = var_ctz64(block_index); xor_buf(offsets, m_offset.data(), L0.data(), m_BS); offsets += m_BS; @@ -96,7 +98,7 @@ } for(size_t i = 0; i != blocks; ++i) { // could be done in parallel - const size_t ntz = var_ctz32(static_cast(block_index + i + 1)); + const size_t ntz = var_ctz64(block_index + i + 1); xor_buf(m_offset.data(), get(ntz).data(), m_BS); copy_mem(offsets, m_offset.data(), m_BS); offsets += m_BS; @@ -136,14 +138,14 @@ for(size_t i = 0; i != ad_blocks; ++i) { // this loop could run in parallel - offset ^= L.get(var_ctz32(static_cast(i + 1))); + offset ^= L.get(var_ctz64(i + 1)); buf = offset; xor_buf(buf.data(), &ad[BS * i], BS); cipher.encrypt(buf); sum ^= buf; } - if(ad_remainder) { + if(ad_remainder > 0) { offset ^= L.star(); buf = offset; xor_buf(buf.data(), &ad[BS * ad_blocks], ad_remainder); @@ -191,6 +193,8 @@ zeroise(m_checksum); m_last_nonce.clear(); m_stretch.clear(); + zeroise(m_nonce_buf); + zeroise(m_offset); } bool OCB_Mode::valid_nonce_length(size_t length) const { @@ -246,7 +250,7 @@ const uint8_t BOTTOM_MASK = static_cast((static_cast(1) << MASKLEN) - 1); m_nonce_buf.resize(BS); - clear_mem(&m_nonce_buf[0], m_nonce_buf.size()); + clear_mem(m_nonce_buf.data(), m_nonce_buf.size()); copy_mem(&m_nonce_buf[BS - nonce_len], nonce, nonce_len); m_nonce_buf[0] = static_cast(((tag_size() * 8) % (BS * 8)) << (BS <= 16 ? 1 : 0)); @@ -337,7 +341,7 @@ const size_t BS = block_size(); - while(blocks) { + while(blocks > 0) { const size_t proc_blocks = std::min(blocks, par_blocks()); const size_t proc_bytes = proc_blocks * BS; @@ -345,7 +349,9 @@ xor_buf(m_checksum.data(), buffer, proc_bytes); - m_cipher->encrypt_n_xex(buffer, offsets, proc_blocks); + xor_buf(buffer, offsets, proc_bytes); + m_cipher->encrypt_n(buffer, buffer, proc_blocks); + xor_buf(buffer, offsets, proc_bytes); buffer += proc_bytes; blocks -= proc_blocks; @@ -371,14 +377,14 @@ secure_vector mac(BS); - if(sz) { + if(sz > 0) { const size_t final_full_blocks = sz / BS; const size_t remainder_bytes = sz - (final_full_blocks * BS); encrypt(buf, final_full_blocks); mac = m_L->offset(); - if(remainder_bytes) { + if(remainder_bytes > 0) { BOTAN_ASSERT(remainder_bytes < BS, "Only a partial block left"); uint8_t* remainder = &buf[sz - remainder_bytes]; @@ -419,13 +425,15 @@ const size_t BS = block_size(); - while(blocks) { + while(blocks > 0) { const size_t proc_blocks = std::min(blocks, par_blocks()); const size_t proc_bytes = proc_blocks * BS; const uint8_t* offsets = m_L->compute_offsets(m_block_index, proc_blocks); - m_cipher->decrypt_n_xex(buffer, offsets, proc_blocks); + xor_buf(buffer, offsets, proc_bytes); + m_cipher->decrypt_n(buffer, buffer, proc_blocks); + xor_buf(buffer, offsets, proc_bytes); xor_buf(m_checksum.data(), buffer, proc_bytes); @@ -457,14 +465,14 @@ secure_vector mac(BS); - if(remaining) { + if(remaining > 0) { const size_t final_full_blocks = remaining / BS; const size_t final_bytes = remaining - (final_full_blocks * BS); decrypt(buf, final_full_blocks); mac ^= m_L->offset(); - if(final_bytes) { + if(final_bytes > 0) { BOTAN_ASSERT(final_bytes < BS, "Only a partial block left"); uint8_t* remainder = &buf[remaining - final_bytes]; @@ -500,6 +508,7 @@ const uint8_t* included_tag = &buf[remaining]; if(!CT::is_equal(mac.data(), included_tag, tag_size()).as_bool()) { + clear_mem(std::span{buffer}.subspan(offset, remaining)); throw Invalid_Authentication_Tag("OCB tag check failed"); } diff -Nru botan3-3.7.1+dfsg/src/lib/modes/aead/ocb/ocb.h botan3-3.12.0+dfsg/src/lib/modes/aead/ocb/ocb.h --- botan3-3.7.1+dfsg/src/lib/modes/aead/ocb/ocb.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/modes/aead/ocb/ocb.h 2026-05-07 01:38:28.000000000 +0000 @@ -10,6 +10,8 @@ #define BOTAN_AEAD_OCB_H_ #include + +#include #include namespace Botan { @@ -28,29 +30,29 @@ * block ciphers with larger block sizes. * @see https://mailarchive.ietf.org/arch/msg/cfrg/qLTveWOdTJcLn4HP3ev-vrj05Vg/ */ -class BOTAN_TEST_API OCB_Mode : public AEAD_Mode { +class BOTAN_TEST_API OCB_Mode : public AEAD_Mode /* NOLINT(*-special-member-functions) */ { public: - void set_associated_data_n(size_t idx, std::span ad) override final; + void set_associated_data_n(size_t idx, std::span ad) final; - std::string name() const override final; + std::string name() const final; - size_t update_granularity() const override final; + size_t update_granularity() const final; - size_t ideal_granularity() const override final; + size_t ideal_granularity() const final; - Key_Length_Specification key_spec() const override final; + Key_Length_Specification key_spec() const final; - bool valid_nonce_length(size_t) const override final; + bool valid_nonce_length(size_t length) const final; - size_t tag_size() const override final { return m_tag_size; } + size_t tag_size() const final { return m_tag_size; } - void clear() override final; + void clear() final; - void reset() override final; + void reset() final; - bool has_keying_material() const override final; + bool has_keying_material() const final; - ~OCB_Mode(); + ~OCB_Mode() override; protected: /** @@ -66,18 +68,18 @@ size_t par_bytes() const { return m_checksum.size(); } // fixme make these private - std::unique_ptr m_cipher; - std::unique_ptr m_L; + std::unique_ptr m_cipher; // NOLINT(*non-private-member-variables*) + std::unique_ptr m_L; // NOLINT(*non-private-member-variables*) - size_t m_block_index = 0; + uint64_t m_block_index = 0; // NOLINT(*non-private-member-variables*) - secure_vector m_checksum; - secure_vector m_ad_hash; + secure_vector m_checksum; // NOLINT(*non-private-member-variables*) + secure_vector m_ad_hash; // NOLINT(*non-private-member-variables*) private: - void start_msg(const uint8_t nonce[], size_t nonce_len) override final; + void start_msg(const uint8_t nonce[], size_t nonce_len) final; - void key_schedule(std::span key) override final; + void key_schedule(std::span key) final; const secure_vector& update_nonce(const uint8_t nonce[], size_t nonce_len); @@ -96,7 +98,7 @@ * @param cipher the block cipher to use * @param tag_size is how big the auth tag will be */ - OCB_Encryption(std::unique_ptr cipher, size_t tag_size = 16) : + explicit OCB_Encryption(std::unique_ptr cipher, size_t tag_size = 16) : OCB_Mode(std::move(cipher), tag_size) {} size_t output_length(size_t input_length) const override { return input_length + tag_size(); } @@ -115,7 +117,7 @@ * @param cipher the block cipher to use * @param tag_size is how big the auth tag will be */ - OCB_Decryption(std::unique_ptr cipher, size_t tag_size = 16) : + explicit OCB_Decryption(std::unique_ptr cipher, size_t tag_size = 16) : OCB_Mode(std::move(cipher), tag_size) {} size_t output_length(size_t input_length) const override { diff -Nru botan3-3.7.1+dfsg/src/lib/modes/aead/siv/siv.cpp botan3-3.12.0+dfsg/src/lib/modes/aead/siv/siv.cpp --- botan3-3.7.1+dfsg/src/lib/modes/aead/siv/siv.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/modes/aead/siv/siv.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,6 +9,8 @@ #include #include +#include +#include #include #include #include @@ -45,7 +47,7 @@ return m_name; } -bool SIV_Mode::valid_nonce_length(size_t /*nonce_len*/) const { +bool SIV_Mode::valid_nonce_length(size_t /*length*/) const { return true; } @@ -99,7 +101,7 @@ throw Invalid_IV_Length(name(), nonce_len); } - if(nonce_len) { + if(nonce_len > 0) { m_nonce = m_mac->process(nonce, nonce_len); } else { m_nonce.clear(); @@ -119,9 +121,9 @@ secure_vector V = m_mac->process(zeros.data(), zeros.size()); - for(size_t i = 0; i != m_ad_macs.size(); ++i) { + for(const auto& ad_mac : m_ad_macs) { poly_double_n(V.data(), V.size()); - V ^= m_ad_macs[i]; + V ^= ad_mac; } if(!m_nonce.empty()) { @@ -188,7 +190,8 @@ const secure_vector T = S2V(buffer.data() + offset, buffer.size() - offset - V.size()); - if(!CT::is_equal(T.data(), V.data(), T.size()).as_bool()) { + if(!CT::is_equal(T, V).as_bool()) { + clear_mem(std::span{buffer}.subspan(offset, buffer.size() - offset - V.size())); throw Invalid_Authentication_Tag("SIV tag check failed"); } diff -Nru botan3-3.7.1+dfsg/src/lib/modes/aead/siv/siv.h botan3-3.12.0+dfsg/src/lib/modes/aead/siv/siv.h --- botan3-3.7.1+dfsg/src/lib/modes/aead/siv/siv.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/modes/aead/siv/siv.h 2026-05-07 01:38:28.000000000 +0000 @@ -10,6 +10,8 @@ #define BOTAN_AEAD_SIV_H_ #include + +#include #include #include @@ -20,38 +22,38 @@ /** * Base class for SIV encryption and decryption (@see RFC 5297) */ -class BOTAN_TEST_API SIV_Mode : public AEAD_Mode { +class BOTAN_TEST_API SIV_Mode : public AEAD_Mode /* NOLINT(*-special-member-functions) */ { public: /** * Sets the nth element of the vector of associated data * @param n index into the AD vector * @param ad associated data */ - void set_associated_data_n(size_t n, std::span ad) override final; + void set_associated_data_n(size_t n, std::span ad) final; - size_t maximum_associated_data_inputs() const override final; + size_t maximum_associated_data_inputs() const final; - std::string name() const override final; + std::string name() const final; - size_t update_granularity() const override final; + size_t update_granularity() const final; - size_t ideal_granularity() const override final; + size_t ideal_granularity() const final; - Key_Length_Specification key_spec() const override final; + Key_Length_Specification key_spec() const final; - bool valid_nonce_length(size_t) const override final; + bool valid_nonce_length(size_t length) const final; - bool requires_entire_message() const override final; + bool requires_entire_message() const final; - void clear() override final; + void clear() final; - void reset() override final; + void reset() final; - size_t tag_size() const override final { return 16; } + size_t tag_size() const final { return 16; } - bool has_keying_material() const override final; + bool has_keying_material() const final; - ~SIV_Mode(); + ~SIV_Mode() override; protected: explicit SIV_Mode(std::unique_ptr cipher); @@ -67,10 +69,10 @@ secure_vector S2V(const uint8_t text[], size_t text_len); private: - void start_msg(const uint8_t nonce[], size_t nonce_len) override final; - size_t process_msg(uint8_t buf[], size_t size) override final; + void start_msg(const uint8_t nonce[], size_t nonce_len) final; + size_t process_msg(uint8_t buf[], size_t size) final; - void key_schedule(std::span key) override final; + void key_schedule(std::span key) final; const std::string m_name; const size_t m_bs; diff -Nru botan3-3.7.1+dfsg/src/lib/modes/cbc/cbc.cpp botan3-3.12.0+dfsg/src/lib/modes/cbc/cbc.cpp --- botan3-3.7.1+dfsg/src/lib/modes/cbc/cbc.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/modes/cbc/cbc.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,9 +9,10 @@ #include +#include +#include #include #include -#include namespace Botan { @@ -78,7 +79,7 @@ * as the new IV, as unfortunately some protocols require this. If * this is the first message then we use an IV of all zeros. */ - if(nonce_len) { + if(nonce_len > 0) { m_state.assign(nonce, nonce + nonce_len); } else if(m_state.empty()) { m_state.resize(m_cipher->block_size()); @@ -91,11 +92,7 @@ } size_t CBC_Encryption::output_length(size_t input_length) const { - if(input_length == 0) { - return block_size(); - } else { - return round_up(input_length, block_size()); - } + return padding().output_length(input_length, block_size()); } size_t CBC_Encryption::process_msg(uint8_t buf[], size_t sz) { @@ -126,9 +123,10 @@ const size_t BS = block_size(); + const size_t output_bytes = offset + padding().output_length(buffer.size() - offset, BS); const size_t bytes_in_final_block = (buffer.size() - offset) % BS; - - padding().add_padding(buffer, bytes_in_final_block, BS); + buffer.resize(output_bytes); + padding().add_padding(std::span(buffer).subspan(offset), bytes_in_final_block, BS); BOTAN_ASSERT_EQUAL(buffer.size() % BS, offset % BS, "Padded to block boundary"); @@ -205,7 +203,7 @@ BOTAN_ARG_CHECK(sz % BS == 0, "Input is not full blocks"); size_t blocks = sz / BS; - while(blocks) { + while(blocks > 0) { const size_t to_proc = std::min(BS * blocks, m_tempbuf.size()); cipher().decrypt_n(buf, m_tempbuf.data(), to_proc / BS); @@ -230,13 +228,13 @@ const size_t BS = block_size(); - if(sz == 0 || sz % BS) { + if(sz == 0 || sz % BS != 0) { throw Decoding_Error(name() + ": Ciphertext not a multiple of block size"); } update(buffer, offset); - const size_t pad_bytes = BS - padding().unpad(&buffer[buffer.size() - BS], BS); + const size_t pad_bytes = BS - padding().unpad(std::span{buffer}.last(BS)); buffer.resize(buffer.size() - pad_bytes); // remove padding if(pad_bytes == 0 && padding().name() != "NoPadding") { throw Decoding_Error("Invalid CBC padding"); diff -Nru botan3-3.7.1+dfsg/src/lib/modes/cbc/cbc.h botan3-3.12.0+dfsg/src/lib/modes/cbc/cbc.h --- botan3-3.7.1+dfsg/src/lib/modes/cbc/cbc.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/modes/cbc/cbc.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,6 +9,7 @@ #ifndef BOTAN_MODE_CBC_H_ #define BOTAN_MODE_CBC_H_ +#include #include #include #include diff -Nru botan3-3.7.1+dfsg/src/lib/modes/cfb/cfb.cpp botan3-3.12.0+dfsg/src/lib/modes/cfb/cfb.cpp --- botan3-3.7.1+dfsg/src/lib/modes/cfb/cfb.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/modes/cfb/cfb.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,6 +8,8 @@ #include +#include +#include #include namespace Botan { @@ -15,8 +17,8 @@ CFB_Mode::CFB_Mode(std::unique_ptr cipher, size_t feedback_bits) : m_cipher(std::move(cipher)), m_block_size(m_cipher->block_size()), - m_feedback_bytes(feedback_bits ? feedback_bits / 8 : m_block_size) { - if(feedback_bits % 8 || feedback() > m_block_size) { + m_feedback_bytes(feedback_bits != 0 ? feedback_bits / 8 : m_block_size) { + if(feedback_bits % 8 != 0 || feedback() > m_block_size) { throw Invalid_Argument(fmt("{} does not support feedback bits of {}", name(), feedback_bits)); } } @@ -30,6 +32,7 @@ void CFB_Mode::reset() { m_state.clear(); zeroise(m_keystream); + m_keystream_pos = 0; } std::string CFB_Mode::name() const { @@ -158,7 +161,7 @@ inline void xor_copy(uint8_t buf[], uint8_t key_buf[], size_t len) { for(size_t i = 0; i != len; ++i) { - uint8_t k = key_buf[i]; + const uint8_t k = key_buf[i]; key_buf[i] = buf[i]; buf[i] ^= k; } diff -Nru botan3-3.7.1+dfsg/src/lib/modes/cfb/cfb.h botan3-3.12.0+dfsg/src/lib/modes/cfb/cfb.h --- botan3-3.7.1+dfsg/src/lib/modes/cfb/cfb.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/modes/cfb/cfb.h 2026-05-07 01:38:28.000000000 +0000 @@ -52,9 +52,9 @@ size_t block_size() const { return m_block_size; } - secure_vector m_state; - secure_vector m_keystream; - size_t m_keystream_pos = 0; + secure_vector m_state; // NOLINT(*non-private-member-variable*) + secure_vector m_keystream; // NOLINT(*non-private-member-variable*) + size_t m_keystream_pos = 0; // NOLINT(*non-private-member-variable*) private: void start_msg(const uint8_t nonce[], size_t nonce_len) override; diff -Nru botan3-3.7.1+dfsg/src/lib/modes/cipher_mode.cpp botan3-3.12.0+dfsg/src/lib/modes/cipher_mode.cpp --- botan3-3.7.1+dfsg/src/lib/modes/cipher_mode.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/modes/cipher_mode.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,10 +7,13 @@ #include +#include #include #include #include +#include #include +#include #if defined(BOTAN_HAS_BLOCK_CIPHER) #include @@ -79,7 +82,7 @@ if(algo.find('/') != std::string::npos) { const std::vector algo_parts = split_on(algo, '/'); - std::string_view cipher_name = algo_parts[0]; + const std::string_view cipher_name = algo_parts[0]; const std::vector mode_info = parse_algorithm_name(algo_parts[1]); if(mode_info.empty()) { @@ -102,7 +105,7 @@ #if defined(BOTAN_HAS_BLOCK_CIPHER) - SCAN_Name spec(algo); + const SCAN_Name spec(algo); if(spec.arg_count() == 0) { return std::unique_ptr(); diff -Nru botan3-3.7.1+dfsg/src/lib/modes/cipher_mode.h botan3-3.12.0+dfsg/src/lib/modes/cipher_mode.h --- botan3-3.7.1+dfsg/src/lib/modes/cipher_mode.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/modes/cipher_mode.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,9 +9,9 @@ #define BOTAN_CIPHER_MODE_H_ #include -#include #include #include +#include #include #include #include @@ -22,9 +22,9 @@ /** * The two possible directions a Cipher_Mode can operate in */ -enum class Cipher_Dir : int { - Encryption, - Decryption, +enum class Cipher_Dir : uint8_t { + Encryption = 0, + Decryption = 1, ENCRYPTION BOTAN_DEPRECATED("Use Cipher_Dir::Encryption") = Encryption, DECRYPTION BOTAN_DEPRECATED("Use Cipher_Dir::Decryption") = Decryption, @@ -146,7 +146,6 @@ */ template void update(T& buffer, size_t offset = 0) { - BOTAN_ASSERT(buffer.size() >= offset, "Offset ok"); const size_t written = process(std::span(buffer).subspan(offset)); buffer.resize(offset + written); } @@ -233,7 +232,7 @@ virtual bool requires_entire_message() const { return false; } /** - * @return required minimium size to finalize() - may be any + * @return required minimum size to finalize() - may be any * length larger than this. */ virtual size_t minimum_final_size() const = 0; diff -Nru botan3-3.7.1+dfsg/src/lib/modes/mode_pad/mode_pad.cpp botan3-3.12.0+dfsg/src/lib/modes/mode_pad/mode_pad.cpp --- botan3-3.7.1+dfsg/src/lib/modes/mode_pad/mode_pad.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/modes/mode_pad/mode_pad.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -2,13 +2,13 @@ * CBC Padding Methods * (C) 1999-2007,2013,2018,2020 Jack Lloyd * (C) 2016 René Korthaus, Rohde & Schwarz Cybersecurity +* (C) 2025 René Meusel, Rohde & Schwarz Cybersecurity * * Botan is released under the Simplified BSD License (see license.txt) */ #include -#include #include namespace Botan { @@ -40,10 +40,29 @@ return nullptr; } +void BlockCipherModePaddingMethod::add_padding(std::span buffer, size_t last_byte_pos, size_t BS) const { + BOTAN_ASSERT_NOMSG(valid_blocksize(BS)); + BOTAN_ASSERT_NOMSG(last_byte_pos < BS); + BOTAN_ASSERT_NOMSG(buffer.size() % BS == 0); + BOTAN_ASSERT_NOMSG(buffer.size() >= BS); + + auto poison = CT::scoped_poison(last_byte_pos, buffer); + apply_padding(buffer.last(BS), last_byte_pos); +} + +size_t BlockCipherModePaddingMethod::unpad(std::span last_block) const { + if(!valid_blocksize(last_block.size())) { + return last_block.size(); + } + + auto poison = CT::scoped_poison(last_block); + return CT::driveby_unpoison(remove_padding(last_block)); +} + /* * Pad with PKCS #7 Method */ -void PKCS7_Padding::add_padding(secure_vector& buffer, size_t last_byte_pos, size_t BS) const { +void PKCS7_Padding::apply_padding(std::span last_block, size_t padding_start_pos) const { /* Padding format is 01 @@ -51,52 +70,31 @@ 030303 ... */ - BOTAN_DEBUG_ASSERT(last_byte_pos < BS); - - const uint8_t padding_len = static_cast(BS - last_byte_pos); - - buffer.resize(buffer.size() + padding_len); - - CT::poison(&last_byte_pos, 1); - CT::poison(buffer.data(), buffer.size()); - - BOTAN_DEBUG_ASSERT(buffer.size() % BS == 0); - BOTAN_DEBUG_ASSERT(buffer.size() >= BS); - - const size_t start_of_last_block = buffer.size() - BS; - const size_t end_of_last_block = buffer.size(); - const size_t start_of_padding = buffer.size() - padding_len; - - for(size_t i = start_of_last_block; i != end_of_last_block; ++i) { - auto needs_padding = CT::Mask(CT::Mask::is_gte(i, start_of_padding)); - buffer[i] = needs_padding.select(padding_len, buffer[i]); + const uint8_t BS = static_cast(last_block.size()); + const uint8_t start_pos = static_cast(padding_start_pos); + const uint8_t padding_len = BS - start_pos; + for(uint8_t i = 0; i < BS; ++i) { + auto needs_padding = CT::Mask::is_gte(i, start_pos); + last_block[i] = needs_padding.select(padding_len, last_block[i]); } - - CT::unpoison(buffer.data(), buffer.size()); - CT::unpoison(last_byte_pos); } /* * Unpad with PKCS #7 Method */ -size_t PKCS7_Padding::unpad(const uint8_t input[], size_t input_length) const { - if(!valid_blocksize(input_length)) { - return input_length; - } - - CT::poison(input, input_length); - - const uint8_t last_byte = input[input_length - 1]; +size_t PKCS7_Padding::remove_padding(std::span input) const { + const size_t BS = input.size(); + const uint8_t last_byte = input.back(); /* The input should == the block size so if the last byte exceeds that then the padding is certainly invalid */ - auto bad_input = CT::Mask::is_gt(last_byte, input_length); + auto bad_input = CT::Mask::is_gt(last_byte, BS); - const size_t pad_pos = input_length - last_byte; + const size_t pad_pos = BS - last_byte; - for(size_t i = 0; i != input_length - 1; ++i) { + for(size_t i = 0; i != BS - 1; ++i) { // Does this byte equal the expected pad byte? const auto pad_eq = CT::Mask::is_equal(input[i], last_byte); @@ -105,15 +103,13 @@ bad_input |= in_range & (~pad_eq); } - CT::unpoison(input, input_length); - - return bad_input.select_and_unpoison(input_length, pad_pos); + return bad_input.select(BS, pad_pos); } /* * Pad with ANSI X9.23 Method */ -void ANSI_X923_Padding::add_padding(secure_vector& buffer, size_t last_byte_pos, size_t BS) const { +void ANSI_X923_Padding::apply_padding(std::span last_block, size_t padding_start_pos) const { /* Padding format is 01 @@ -121,64 +117,42 @@ 000003 ... */ - BOTAN_DEBUG_ASSERT(last_byte_pos < BS); - - const uint8_t padding_len = static_cast(BS - last_byte_pos); - - buffer.resize(buffer.size() + padding_len); - - CT::poison(&last_byte_pos, 1); - CT::poison(buffer.data(), buffer.size()); - - BOTAN_DEBUG_ASSERT(buffer.size() % BS == 0); - BOTAN_DEBUG_ASSERT(buffer.size() >= BS); - - const size_t start_of_last_block = buffer.size() - BS; - const size_t end_of_zero_padding = buffer.size() - 1; - const size_t start_of_padding = buffer.size() - padding_len; - - for(size_t i = start_of_last_block; i != end_of_zero_padding; ++i) { - auto needs_padding = CT::Mask(CT::Mask::is_gte(i, start_of_padding)); - buffer[i] = needs_padding.select(0, buffer[i]); + const uint8_t BS = static_cast(last_block.size()); + const uint8_t start_pos = static_cast(padding_start_pos); + const uint8_t padding_len = BS - start_pos; + for(uint8_t i = 0; i != BS - 1; ++i) { + auto needs_padding = CT::Mask::is_gte(i, start_pos); + last_block[i] = needs_padding.select(0, last_block[i]); } - buffer[buffer.size() - 1] = padding_len; - CT::unpoison(buffer.data(), buffer.size()); - CT::unpoison(last_byte_pos); + last_block.back() = padding_len; } /* * Unpad with ANSI X9.23 Method */ -size_t ANSI_X923_Padding::unpad(const uint8_t input[], size_t input_length) const { - if(!valid_blocksize(input_length)) { - return input_length; - } +size_t ANSI_X923_Padding::remove_padding(std::span input) const { + const size_t BS = input.size(); + const size_t last_byte = input.back(); - CT::poison(input, input_length); + auto bad_input = CT::Mask::is_gt(last_byte, BS); - const size_t last_byte = input[input_length - 1]; + const size_t pad_pos = BS - last_byte; - auto bad_input = CT::Mask::is_gt(last_byte, input_length); - - const size_t pad_pos = input_length - last_byte; - - for(size_t i = 0; i != input_length - 1; ++i) { + for(size_t i = 0; i != BS - 1; ++i) { // Ignore values that are not part of the padding const auto in_range = CT::Mask::is_gte(i, pad_pos); const auto pad_is_nonzero = CT::Mask::expand(input[i]); bad_input |= pad_is_nonzero & in_range; } - CT::unpoison(input, input_length); - - return bad_input.select_and_unpoison(input_length, pad_pos); + return bad_input.select(BS, pad_pos); } /* * Pad with One and Zeros Method */ -void OneAndZeros_Padding::add_padding(secure_vector& buffer, size_t last_byte_pos, size_t BS) const { +void OneAndZeros_Padding::apply_padding(std::span last_block, size_t padding_start_pos) const { /* Padding format is 80 @@ -186,69 +160,40 @@ 800000 ... */ - - BOTAN_DEBUG_ASSERT(last_byte_pos < BS); - - const uint8_t padding_len = static_cast(BS - last_byte_pos); - - buffer.resize(buffer.size() + padding_len); - - CT::poison(&last_byte_pos, 1); - CT::poison(buffer.data(), buffer.size()); - - BOTAN_DEBUG_ASSERT(buffer.size() % BS == 0); - BOTAN_DEBUG_ASSERT(buffer.size() >= BS); - - const size_t start_of_last_block = buffer.size() - BS; - const size_t end_of_last_block = buffer.size(); - const size_t start_of_padding = buffer.size() - padding_len; - - for(size_t i = start_of_last_block; i != end_of_last_block; ++i) { - auto needs_80 = CT::Mask(CT::Mask::is_equal(i, start_of_padding)); - auto needs_00 = CT::Mask(CT::Mask::is_gt(i, start_of_padding)); - buffer[i] = needs_00.select(0x00, needs_80.select(0x80, buffer[i])); + for(size_t i = 0; i != last_block.size(); ++i) { + auto needs_80 = CT::Mask(CT::Mask::is_equal(i, padding_start_pos)); + auto needs_00 = CT::Mask(CT::Mask::is_gt(i, padding_start_pos)); + last_block[i] = needs_00.select(0x00, needs_80.select(0x80, last_block[i])); } - - CT::unpoison(buffer.data(), buffer.size()); - CT::unpoison(last_byte_pos); } /* * Unpad with One and Zeros Method */ -size_t OneAndZeros_Padding::unpad(const uint8_t input[], size_t input_length) const { - if(!valid_blocksize(input_length)) { - return input_length; - } - - CT::poison(input, input_length); - +size_t OneAndZeros_Padding::remove_padding(std::span input) const { + const size_t BS = input.size(); auto bad_input = CT::Mask::cleared(); auto seen_0x80 = CT::Mask::cleared(); - size_t pad_pos = input_length - 1; - size_t i = input_length; + size_t pad_pos = BS - 1; - while(i) { + for(size_t i = BS; i != 0; --i) { const auto is_0x80 = CT::Mask::is_equal(input[i - 1], 0x80); const auto is_zero = CT::Mask::is_zero(input[i - 1]); seen_0x80 |= is_0x80; pad_pos -= seen_0x80.if_not_set_return(1); bad_input |= ~seen_0x80 & ~is_zero; - i--; } bad_input |= ~seen_0x80; - CT::unpoison(input, input_length); - - return CT::Mask::expand(bad_input).select_and_unpoison(input_length, pad_pos); + return CT::Mask::expand(bad_input).select(BS, pad_pos); } /* * Pad with ESP Padding Method */ -void ESP_Padding::add_padding(secure_vector& buffer, size_t last_byte_pos, size_t BS) const { +void ESP_Padding::apply_padding(std::span last_block, size_t padding_start_pos) const { /* Padding format is 01 @@ -256,61 +201,35 @@ 010203 ... */ - BOTAN_DEBUG_ASSERT(last_byte_pos < BS); - - const uint8_t padding_len = static_cast(BS - last_byte_pos); - - buffer.resize(buffer.size() + padding_len); - - CT::poison(&last_byte_pos, 1); - CT::poison(buffer.data(), buffer.size()); - - BOTAN_DEBUG_ASSERT(buffer.size() % BS == 0); - BOTAN_DEBUG_ASSERT(buffer.size() >= BS); - - const size_t start_of_last_block = buffer.size() - BS; - const size_t end_of_last_block = buffer.size(); - const size_t start_of_padding = buffer.size() - padding_len; + const uint8_t BS = static_cast(last_block.size()); + const uint8_t start_pos = static_cast(padding_start_pos); uint8_t pad_ctr = 0x01; - - for(size_t i = start_of_last_block; i != end_of_last_block; ++i) { - auto needs_padding = CT::Mask(CT::Mask::is_gte(i, start_of_padding)); - buffer[i] = needs_padding.select(pad_ctr, buffer[i]); + for(uint8_t i = 0; i != BS; ++i) { + auto needs_padding = CT::Mask::is_gte(i, start_pos); + last_block[i] = needs_padding.select(pad_ctr, last_block[i]); pad_ctr = needs_padding.select(pad_ctr + 1, pad_ctr); } - - CT::unpoison(buffer.data(), buffer.size()); - CT::unpoison(last_byte_pos); } /* * Unpad with ESP Padding Method */ -size_t ESP_Padding::unpad(const uint8_t input[], size_t input_length) const { - if(!valid_blocksize(input_length)) { - return input_length; - } - - CT::poison(input, input_length); - - const uint8_t input_length_8 = static_cast(input_length); - const uint8_t last_byte = input[input_length - 1]; +size_t ESP_Padding::remove_padding(std::span input) const { + const size_t BS = input.size(); + const uint8_t last_byte = input.back(); - auto bad_input = CT::Mask::is_zero(last_byte) | CT::Mask::is_gt(last_byte, input_length_8); + auto bad_input = CT::Mask::is_zero(last_byte) | CT::Mask::is_gt(last_byte, BS); - const uint8_t pad_pos = input_length_8 - last_byte; - size_t i = input_length_8 - 1; - while(i) { + const size_t pad_pos = BS - last_byte; + for(size_t i = BS - 1; i != 0; --i) { const auto in_range = CT::Mask::is_gt(i, pad_pos); - const auto incrementing = CT::Mask::is_equal(input[i - 1], input[i] - 1); + const auto incrementing = CT::Mask::is_equal(input[i - 1], input[i] - 1); - bad_input |= CT::Mask(in_range) & ~incrementing; - --i; + bad_input |= CT::Mask(in_range) & ~incrementing; } - CT::unpoison(input, input_length); - return bad_input.select_and_unpoison(input_length_8, pad_pos); + return bad_input.select(BS, pad_pos); } } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/modes/mode_pad/mode_pad.h botan3-3.12.0+dfsg/src/lib/modes/mode_pad/mode_pad.h --- botan3-3.7.1+dfsg/src/lib/modes/mode_pad/mode_pad.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/modes/mode_pad/mode_pad.h 2026-05-07 01:38:28.000000000 +0000 @@ -2,6 +2,7 @@ * CBC Padding Methods * (C) 1999-2008,2013 Jack Lloyd * (C) 2016 René Korthaus, Rohde & Schwarz Cybersecurity +* (C) 2025 René Meusel, Rohde & Schwarz Cybersecurity * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -9,7 +10,10 @@ #ifndef BOTAN_MODE_PADDING_H_ #define BOTAN_MODE_PADDING_H_ +#include #include +#include +#include #include namespace Botan { @@ -23,7 +27,7 @@ * a padding mode for CBC, which happens to consume the last * two block (and requires use of the block cipher). */ -class BOTAN_TEST_API BlockCipherModePaddingMethod { +class BOTAN_TEST_API BlockCipherModePaddingMethod /* NOLINT(*-special-member-functions) */ { public: /** * Get a block cipher padding mode by name (eg "NoPadding" or "PKCS7") @@ -33,19 +37,20 @@ /** * Add padding bytes to buffer. - * @param buffer data to pad + * @param buffer data to pad, span must be large enough to hold the padding + * behind the final (partial) block * @param final_block_bytes size of the final block in bytes * @param block_size size of each block in bytes */ - virtual void add_padding(secure_vector& buffer, size_t final_block_bytes, size_t block_size) const = 0; + virtual void add_padding(std::span buffer, size_t final_block_bytes, size_t block_size) const; /** * Remove padding bytes from block - * @param block the last block - * @param len the size of the block in bytes - * @return number of data bytes, or if the padding is invalid returns len + * @param last_block the last block containing the padding + * @return number of data bytes, or if the padding is invalid returns the + * byte length of @p last_block (i.e. the block size) */ - virtual size_t unpad(const uint8_t block[], size_t len) const = 0; + size_t unpad(std::span last_block) const; /** * @param block_size of the cipher @@ -54,6 +59,15 @@ virtual bool valid_blocksize(size_t block_size) const = 0; /** + * @param input_length number of bytes to be padded + * @param block_size size of each block in bytes + * @return the total number of output bytes (including the padding) + */ + virtual size_t output_length(size_t input_length, size_t block_size) const { + return ((input_length + block_size) / block_size) * block_size; + } + + /** * @return name of the mode */ virtual std::string name() const = 0; @@ -62,6 +76,28 @@ * virtual destructor */ virtual ~BlockCipherModePaddingMethod() = default; + + protected: + /** + * Applies the concrete padding to the @p last_block assuming the padding + * bytes should start at @p padding_start_pos within the last block. + * + * Concrete implementations of this function must ensure not to leak + * @p padding_start_pos via side channels. Both the bytes of @p last_block + * and @p padding_start_pos are passed in with CT::poison applied. + */ + virtual void apply_padding(std::span last_block, size_t padding_start_pos) const = 0; + + /** + * Removes the padding from @p last_block and returns the number of data + * bytes. If the padding is invalid, this returns the byte length of + * @p last_block. + * + * Concrete implementations of this function must ensure not to leak + * the size or validity of the padding via side channels. The bytes of + * @p last_block are passed in with CT::poison applied to them. + */ + virtual size_t remove_padding(std::span last_block) const = 0; }; /** @@ -69,9 +105,9 @@ */ class BOTAN_FUZZER_API PKCS7_Padding final : public BlockCipherModePaddingMethod { public: - void add_padding(secure_vector& buffer, size_t final_block_bytes, size_t block_size) const override; + void apply_padding(std::span last_block, size_t final_block_bytes) const override; - size_t unpad(const uint8_t[], size_t) const override; + size_t remove_padding(std::span last_block) const override; bool valid_blocksize(size_t bs) const override { return (bs > 2 && bs < 256); } @@ -83,9 +119,9 @@ */ class BOTAN_FUZZER_API ANSI_X923_Padding final : public BlockCipherModePaddingMethod { public: - void add_padding(secure_vector& buffer, size_t final_block_bytes, size_t block_size) const override; + void apply_padding(std::span last_block, size_t final_block_bytes) const override; - size_t unpad(const uint8_t[], size_t) const override; + size_t remove_padding(std::span last_block) const override; bool valid_blocksize(size_t bs) const override { return (bs > 2 && bs < 256); } @@ -97,9 +133,9 @@ */ class BOTAN_FUZZER_API OneAndZeros_Padding final : public BlockCipherModePaddingMethod { public: - void add_padding(secure_vector& buffer, size_t final_block_bytes, size_t block_size) const override; + void apply_padding(std::span last_block, size_t final_block_bytes) const override; - size_t unpad(const uint8_t[], size_t) const override; + size_t remove_padding(std::span last_block) const override; bool valid_blocksize(size_t bs) const override { return (bs > 2); } @@ -111,9 +147,9 @@ */ class BOTAN_FUZZER_API ESP_Padding final : public BlockCipherModePaddingMethod { public: - void add_padding(secure_vector& buffer, size_t final_block_bytes, size_t block_size) const override; + void apply_padding(std::span last_block, size_t final_block_bytes) const override; - size_t unpad(const uint8_t[], size_t) const override; + size_t remove_padding(std::span last_block) const override; bool valid_blocksize(size_t bs) const override { return (bs > 2 && bs < 256); } @@ -125,14 +161,26 @@ */ class Null_Padding final : public BlockCipherModePaddingMethod { public: - void add_padding(secure_vector&, size_t, size_t) const override { /* no padding */ + void add_padding(std::span /*buffer*/, + size_t /*final_block_bytes*/, + size_t /*block_size*/) const override { + // no padding } - size_t unpad(const uint8_t[], size_t size) const override { return size; } + size_t remove_padding(std::span last_block) const override { return last_block.size(); } + + bool valid_blocksize(size_t /*block_size*/) const override { return true; } - bool valid_blocksize(size_t) const override { return true; } + size_t output_length(size_t input_length, size_t /*block_size*/) const override { return input_length; } std::string name() const override { return "NoPadding"; } + + private: + void apply_padding(std::span /*last_block*/, size_t /*padding_start_pos*/) const override { + // This class overrides add_padding() as a NOOP, so this customization + // point can never be called by anyone. + BOTAN_ASSERT_UNREACHABLE(); + } }; } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/modes/stream_mode.h botan3-3.12.0+dfsg/src/lib/modes/stream_mode.h --- botan3-3.7.1+dfsg/src/lib/modes/stream_mode.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/modes/stream_mode.h 2026-05-07 01:38:28.000000000 +0000 @@ -10,6 +10,8 @@ #include +#include + #if defined(BOTAN_HAS_STREAM_CIPHER) #include #endif @@ -21,7 +23,7 @@ class Stream_Cipher_Mode final : public Cipher_Mode { public: /** - * @param cipher underyling stream cipher + * @param cipher underlying stream cipher */ explicit Stream_Cipher_Mode(std::unique_ptr cipher) : m_cipher(std::move(cipher)) {} diff -Nru botan3-3.7.1+dfsg/src/lib/modes/xts/xts.cpp botan3-3.12.0+dfsg/src/lib/modes/xts/xts.cpp --- botan3-3.7.1+dfsg/src/lib/modes/xts/xts.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/modes/xts/xts.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ /* * XTS Mode -* (C) 2009,2013 Jack Lloyd +* (C) 2009,2013,2026 Jack Lloyd * (C) 2016 Daniel Neus, Rohde & Schwarz Cybersecurity * * Botan is released under the Simplified BSD License (see license.txt) @@ -8,9 +8,15 @@ #include +#include +#include #include #include +#if defined(BOTAN_HAS_MODE_XTS_AVX512_CLMUL) + #include +#endif + namespace Botan { XTS_Mode::XTS_Mode(std::unique_ptr cipher) : @@ -18,7 +24,7 @@ m_cipher_block_size(m_cipher->block_size()), m_cipher_parallelism(m_cipher->parallel_bytes()), m_tweak_blocks(m_cipher_parallelism / m_cipher_block_size) { - if(poly_double_supported_size(m_cipher_block_size) == false) { + if(!poly_double_supported_size(m_cipher_block_size)) { throw Invalid_Argument(fmt("Cannot use {} with XTS", m_cipher->name())); } @@ -88,19 +94,46 @@ copy_mem(m_tweak.data(), nonce, nonce_len); m_tweak_cipher->encrypt(m_tweak.data()); - update_tweak(0); + // Just repeated doubling from first, remaining contents are junk... + xts_compute_tweak_block(m_tweak.data(), m_tweak_cipher->block_size(), tweak_blocks()); } -void XTS_Mode::update_tweak(size_t which) { - const size_t BS = m_tweak_cipher->block_size(); - - if(which > 0) { - poly_double_n_le(m_tweak.data(), &m_tweak[(which - 1) * BS], BS); +//static +void XTS_Mode::update_tweak_block(uint8_t tweak[], size_t BS, size_t blocks_in_tweak) { +#if defined(BOTAN_HAS_MODE_XTS_AVX512_CLMUL) + if(BS == 16 && blocks_in_tweak % 8 == 0 && CPUID::has(CPUID::Feature::AVX512_CLMUL)) { + return update_tweak_block_avx512_clmul(tweak, BS, blocks_in_tweak); } +#endif + + /* + * If we don't have a fast method available, just set the first tweak block to + * the doubling of the last tweak block, and recompute all the rest via + * successive doublings. + */ + poly_double_n_le(tweak, &tweak[(blocks_in_tweak - 1) * BS], BS); + xts_compute_tweak_block(tweak, BS, blocks_in_tweak); +} +void XTS_Mode::update_tweak(size_t consumed) { + const size_t BS = m_tweak_cipher->block_size(); const size_t blocks_in_tweak = tweak_blocks(); - xts_update_tweak_block(m_tweak.data(), BS, blocks_in_tweak); + BOTAN_ASSERT_NOMSG(consumed > 0 && consumed <= blocks_in_tweak); + + if(consumed == blocks_in_tweak) { + // Update all in parallel + update_tweak_block(m_tweak.data(), BS, blocks_in_tweak); + } else { + /* + The last remaining tweaks can just be shifted over + + This could be a lot better though! We can copy all of the remaining tweaks + and just recompute the last few + */ + copy_mem(m_tweak.data(), &m_tweak[(consumed * BS)], BS); + xts_compute_tweak_block(m_tweak.data(), BS, blocks_in_tweak); + } } size_t XTS_Encryption::output_length(size_t input_length) const { @@ -116,12 +149,15 @@ const size_t blocks_in_tweak = tweak_blocks(); - while(blocks) { + while(blocks > 0) { const size_t to_proc = std::min(blocks, blocks_in_tweak); + const size_t proc_bytes = to_proc * BS; - cipher().encrypt_n_xex(buf, tweak(), to_proc); + xor_buf(buf, tweak(), proc_bytes); + cipher().encrypt_n(buf, buf, to_proc); + xor_buf(buf, tweak(), proc_bytes); - buf += to_proc * BS; + buf += proc_bytes; blocks -= to_proc; update_tweak(to_proc); @@ -182,12 +218,15 @@ const size_t blocks_in_tweak = tweak_blocks(); - while(blocks) { + while(blocks > 0) { const size_t to_proc = std::min(blocks, blocks_in_tweak); + const size_t proc_bytes = to_proc * BS; - cipher().decrypt_n_xex(buf, tweak(), to_proc); + xor_buf(buf, tweak(), proc_bytes); + cipher().decrypt_n(buf, buf, to_proc); + xor_buf(buf, tweak(), proc_bytes); - buf += to_proc * BS; + buf += proc_bytes; blocks -= to_proc; update_tweak(to_proc); diff -Nru botan3-3.7.1+dfsg/src/lib/modes/xts/xts.h botan3-3.12.0+dfsg/src/lib/modes/xts/xts.h --- botan3-3.7.1+dfsg/src/lib/modes/xts/xts.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/modes/xts/xts.h 2026-05-07 01:38:28.000000000 +0000 @@ -44,13 +44,13 @@ const uint8_t* tweak() const { return m_tweak.data(); } - bool tweak_set() const { return m_tweak.empty() == false; } + bool tweak_set() const { return !m_tweak.empty(); } size_t tweak_blocks() const { return m_tweak_blocks; } const BlockCipher& cipher() const { return *m_cipher; } - void update_tweak(size_t last_used); + void update_tweak(size_t consumed); size_t cipher_block_size() const { return m_cipher_block_size; } @@ -58,6 +58,20 @@ void start_msg(const uint8_t nonce[], size_t nonce_len) override; void key_schedule(std::span key) override; + /* + * Tweak block update step for XTS + * + * Assumes tweak is BS * n bytes long. + * + * Assumes that each block of tweak is already set to the successive doublings + * of the block prior. + */ + static void update_tweak_block(uint8_t tweak[], size_t BS, size_t blocks_in_tweak); + +#if defined(BOTAN_HAS_MODE_XTS_AVX512_CLMUL) + static void update_tweak_block_avx512_clmul(uint8_t tweak[], size_t BS, size_t blocks_in_tweak); +#endif + std::unique_ptr m_cipher; std::unique_ptr m_tweak_cipher; secure_vector m_tweak; diff -Nru botan3-3.7.1+dfsg/src/lib/modes/xts/xts_avx512_clmul/info.txt botan3-3.12.0+dfsg/src/lib/modes/xts/xts_avx512_clmul/info.txt --- botan3-3.7.1+dfsg/src/lib/modes/xts/xts_avx512_clmul/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/modes/xts/xts_avx512_clmul/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,15 @@ + +MODE_XTS_AVX512_CLMUL -> 20260119 + + + +name -> "XTS tweak computation using AVX-512/clmul" + + + +avx512_clmul + + + +cpuid + diff -Nru botan3-3.7.1+dfsg/src/lib/modes/xts/xts_avx512_clmul/xts_avx512_clmul.cpp botan3-3.12.0+dfsg/src/lib/modes/xts/xts_avx512_clmul/xts_avx512_clmul.cpp --- botan3-3.7.1+dfsg/src/lib/modes/xts/xts_avx512_clmul/xts_avx512_clmul.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/modes/xts/xts_avx512_clmul/xts_avx512_clmul.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,65 @@ +/* +* (C) 2026 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include + +namespace Botan { + +void BOTAN_FN_ISA_AVX512_CLMUL XTS_Mode::update_tweak_block_avx512_clmul(uint8_t tweak[], size_t BS, size_t N) { + BOTAN_ASSERT_NOMSG(N > 0); + + if(BS == 16 && N % 8 == 0) { + constexpr uint64_t P128 = 0x87; + const __m512i poly = _mm512_set_epi64(0, P128, 0, P128, 0, P128, 0, P128); + + /* + * We need to perform N doublings on each block. + * + * We can compute the carryless multiplication with any size. Here, curiously, the + * constraint is that AVX2/AVX512 don't include an equivalent of psrldq (aka + * _mm_srli_si128), which allows shifting 128-bit lanes by any number of bits. + * Instead only byte-wide lane shifts are available, so we can only raise to powers + * where N is a multiple of 8. + */ + const size_t N_32 = N / 32; + const size_t N_8 = (N - N_32 * 32) / 8; + + // Since we must anyway require N % 8 == 0, unrolling once is free and allows better ILP + for(size_t i = 0; i != N; i += 8) { + __m512i W0 = _mm512_loadu_si512(&tweak[i * BS]); + __m512i W1 = _mm512_loadu_si512(&tweak[(i + 4) * BS]); + + for(size_t r = 0; r != N_32; ++r) { + // (W << 32) ^ compute_carry(W >> 96) + const auto C0 = _mm512_clmulepi64_epi128(_mm512_bsrli_epi128(W0, 12), poly, 0); + const auto C1 = _mm512_clmulepi64_epi128(_mm512_bsrli_epi128(W1, 12), poly, 0); + W0 = _mm512_xor_si512(_mm512_bslli_epi128(W0, 4), C0); + W1 = _mm512_xor_si512(_mm512_bslli_epi128(W1, 4), C1); + } + + for(size_t r = 0; r != N_8; ++r) { + // (W << 8) ^ compute_carry(W >> 120) + const auto C0 = _mm512_clmulepi64_epi128(_mm512_bsrli_epi128(W0, 15), poly, 0); + const auto C1 = _mm512_clmulepi64_epi128(_mm512_bsrli_epi128(W1, 15), poly, 0); + W0 = _mm512_xor_si512(_mm512_bslli_epi128(W0, 1), C0); + W1 = _mm512_xor_si512(_mm512_bslli_epi128(W1, 1), C1); + } + + _mm512_storeu_epi64(&tweak[i * BS], W0); + _mm512_storeu_epi64(&tweak[(i + 4) * BS], W1); + } + } else { + poly_double_n_le(tweak, &tweak[(N - 1) * BS], BS); + xts_compute_tweak_block(tweak, BS, N); + } +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/passhash/argon2fmt/argon2fmt.cpp botan3-3.12.0+dfsg/src/lib/passhash/argon2fmt/argon2fmt.cpp --- botan3-3.7.1+dfsg/src/lib/passhash/argon2fmt/argon2fmt.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/passhash/argon2fmt/argon2fmt.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,7 +7,7 @@ #include #include -#include +#include #include #include #include @@ -75,73 +75,79 @@ } bool argon2_check_pwhash(const char* password, size_t password_len, std::string_view input_hash) { - const std::vector parts = split_on(input_hash, '$'); + try { + const std::vector parts = split_on(input_hash, '$'); - if(parts.size() != 5) { - return false; - } + if(parts.size() != 5) { + return false; + } - uint8_t family = 0; + uint8_t family = 0; - if(parts[0] == "argon2d") { - family = 0; - } else if(parts[0] == "argon2i") { - family = 1; - } else if(parts[0] == "argon2id") { - family = 2; - } else { - return false; - } + if(parts[0] == "argon2d") { + family = 0; + } else if(parts[0] == "argon2i") { + family = 1; + } else if(parts[0] == "argon2id") { + family = 2; + } else { + return false; + } - if(parts[1] != "v=19") { - return false; - } + if(parts[1] != "v=19") { + return false; + } - const std::vector params = split_on(parts[2], ','); + const std::vector params = split_on(parts[2], ','); - if(params.size() != 3) { - return false; - } + if(params.size() != 3) { + return false; + } - size_t M = 0, t = 0, p = 0; + size_t M = 0; + size_t t = 0; + size_t p = 0; + + for(const auto& param_str : params) { + const std::vector param = split_on(param_str, '='); + + if(param.size() != 2) { + return false; + } + + const std::string_view key = param[0]; + const size_t val = to_u32bit(param[1]); + if(key == "m") { + M = val; + } else if(key == "t") { + t = val; + } else if(key == "p") { + p = val; + } else { + return false; + } + } - for(const auto& param_str : params) { - const std::vector param = split_on(param_str, '='); + std::vector salt(base64_decode_max_output(parts[3].size())); + salt.resize(base64_decode(salt.data(), parts[3], false)); - if(param.size() != 2) { - return false; - } + std::vector hash(base64_decode_max_output(parts[4].size())); + hash.resize(base64_decode(hash.data(), parts[4], false)); - std::string_view key = param[0]; - const size_t val = to_u32bit(param[1]); - if(key == "m") { - M = val; - } else if(key == "t") { - t = val; - } else if(key == "p") { - p = val; - } else { + if(hash.size() < 4) { return false; } - } - std::vector salt(base64_decode_max_output(parts[3].size())); - salt.resize(base64_decode(salt.data(), parts[3], false)); + std::vector generated(hash.size()); + auto pwdhash_fam = PasswordHashFamily::create_or_throw(argon2_family(family)); + auto pwdhash = pwdhash_fam->from_params(M, t, p); - std::vector hash(base64_decode_max_output(parts[4].size())); - hash.resize(base64_decode(hash.data(), parts[4], false)); + pwdhash->derive_key(generated.data(), generated.size(), password, password_len, salt.data(), salt.size()); - if(hash.size() < 4) { + return CT::is_equal(generated.data(), hash.data(), generated.size()).as_bool(); + } catch(...) { return false; } - - std::vector generated(hash.size()); - auto pwdhash_fam = PasswordHashFamily::create_or_throw(argon2_family(family)); - auto pwdhash = pwdhash_fam->from_params(M, t, p); - - pwdhash->derive_key(generated.data(), generated.size(), password, password_len, salt.data(), salt.size()); - - return CT::is_equal(generated.data(), hash.data(), generated.size()).as_bool(); } } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/passhash/bcrypt/bcrypt.cpp botan3-3.12.0+dfsg/src/lib/passhash/bcrypt/bcrypt.cpp --- botan3-3.7.1+dfsg/src/lib/passhash/bcrypt/bcrypt.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/passhash/bcrypt/bcrypt.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,10 +8,13 @@ #include #include +#include +#include #include #include #include #include +#include #include namespace Botan { @@ -73,15 +76,15 @@ return ret; } -std::string bcrypt_base64_encode(const uint8_t input[], size_t length) { - std::string b64 = base64_encode(input, length); +std::string bcrypt_base64_encode(std::span input) { + std::string b64 = base64_encode(input); - while(!b64.empty() && b64[b64.size() - 1] == '=') { - b64 = b64.substr(0, b64.size() - 1); + while(!b64.empty() && b64.back() == '=') { + b64.pop_back(); } - for(size_t i = 0; i != b64.size(); ++i) { - b64[i] = static_cast(base64_to_bcrypt_encoding(static_cast(b64[i]))); + for(char& c : b64) { + c = static_cast(base64_to_bcrypt_encoding(static_cast(c))); } return b64; @@ -89,15 +92,14 @@ std::vector bcrypt_base64_decode(std::string_view input) { std::string translated; - for(size_t i = 0; i != input.size(); ++i) { - char c = bcrypt_encoding_to_base64(static_cast(input[i])); - translated.push_back(c); + for(const char c : input) { + translated.push_back(bcrypt_encoding_to_base64(static_cast(c))); } return unlock(base64_decode(translated)); } -std::string make_bcrypt(std::string_view pass, const std::vector& salt, uint16_t work_factor, char version) { +std::string make_bcrypt(std::string_view pass, std::span salt, uint16_t work_factor, char version) { /* * On a 4 GHz Skylake, workfactor == 18 takes about 15 seconds to * hash a password. This seems like a reasonable upper bound for the @@ -112,12 +114,12 @@ Blowfish blowfish; - secure_vector pass_with_trailing_null(pass.size() + 1); - copy_mem(pass_with_trailing_null.data(), cast_char_ptr_to_uint8(pass.data()), pass.length()); + // Bcrypt is defined with the key including the trailing NULL so we must copy it to a local + // variable since std::string_view is not necessarily NULL terminated. + secure_vector pass_w_null(pass.size() + 1); + copy_mem(std::span{pass_w_null}.first(pass.size()), as_span_of_bytes(pass)); - // Include the trailing NULL byte, so we need c_str() not data() - blowfish.salted_set_key( - pass_with_trailing_null.data(), pass_with_trailing_null.size(), salt.data(), salt.size(), work_factor); + blowfish.salted_set_key(pass_w_null.data(), pass_w_null.size(), salt.data(), salt.size(), work_factor); std::vector ctext(BCRYPT_MAGIC, BCRYPT_MAGIC + 8 * 3); @@ -125,7 +127,7 @@ blowfish.encrypt_n(ctext.data(), ctext.data(), 3); } - std::string salt_b64 = bcrypt_base64_encode(salt.data(), salt.size()); + const std::string salt_b64 = bcrypt_base64_encode(salt); std::string work_factor_str = std::to_string(work_factor); if(work_factor_str.length() == 1) { @@ -136,7 +138,7 @@ version, work_factor_str, salt_b64.substr(0, 22), - bcrypt_base64_encode(ctext.data(), ctext.size() - 1)); + bcrypt_base64_encode(std::span{ctext}.first(ctext.size() - 1))); } } // namespace @@ -167,7 +169,17 @@ return false; } - const uint16_t workfactor = to_uint16(hash.substr(4, 2)); + // bcrypt workfactor spec is always two characters + const char wf0 = hash[4]; + const char wf1 = hash[5]; + if(wf0 < '0' || wf0 > '9' || wf1 < '0' || wf1 > '9') { + return false; + } + const uint16_t workfactor = static_cast((wf0 - '0') * 10 + (wf1 - '0')); + // bcrypt does support larger range of workfactors, this is what make_bcrypt allows + if(workfactor < 4 || workfactor > 18) { + return false; + } const std::vector salt = bcrypt_base64_decode(hash.substr(7, 22)); if(salt.size() != 16) { @@ -176,8 +188,7 @@ const std::string compare = make_bcrypt(pass, salt, workfactor, bcrypt_version); - return CT::is_equal(cast_char_ptr_to_uint8(hash.data()), cast_char_ptr_to_uint8(compare.data()), compare.size()) - .as_bool(); + return CT::is_equal(as_span_of_bytes(hash), as_span_of_bytes(compare)).as_bool(); } } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/passhash/passhash9/passhash9.cpp botan3-3.12.0+dfsg/src/lib/passhash/passhash9/passhash9.cpp --- botan3-3.7.1+dfsg/src/lib/passhash/passhash9/passhash9.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/passhash/passhash9/passhash9.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -17,7 +18,7 @@ namespace { -const std::string MAGIC_PREFIX = "$9$"; +const std::string_view MAGIC_PREFIX = "$9$"; const size_t WORKFACTOR_BYTES = 2; const size_t ALGID_BYTES = 1; @@ -55,7 +56,7 @@ throw Invalid_Argument("Passhash9: Algorithm id " + std::to_string(alg_id) + " is not defined"); } - PKCS5_PBKDF2 kdf(std::move(prf)); + const PKCS5_PBKDF2 kdf(std::move(prf)); secure_vector salt(SALT_BYTES); rng.randomize(salt.data(), salt.size()); @@ -69,7 +70,7 @@ blob += salt; blob += kdf.derive_key(PASSHASH9_PBKDF_OUTPUT_LEN, pass, salt.data(), salt.size(), kdf_iterations).bits_of(); - return MAGIC_PREFIX + base64_encode(blob); + return std::string(MAGIC_PREFIX) + base64_encode(blob); } bool check_passhash9(std::string_view pass, std::string_view hash) { @@ -93,7 +94,7 @@ return false; } - uint8_t alg_id = bin[0]; + const uint8_t alg_id = bin[0]; const size_t work_factor = load_be(&bin[ALGID_BYTES], 0); @@ -114,7 +115,7 @@ return false; // unknown algorithm, reject } - PKCS5_PBKDF2 kdf(std::move(pbkdf_prf)); + const PKCS5_PBKDF2 kdf(std::move(pbkdf_prf)); secure_vector cmp = kdf.derive_key(PASSHASH9_PBKDF_OUTPUT_LEN, pass, &bin[ALGID_BYTES + WORKFACTOR_BYTES], SALT_BYTES, kdf_iterations) diff -Nru botan3-3.7.1+dfsg/src/lib/pbkdf/argon2/argon2.cpp botan3-3.12.0+dfsg/src/lib/pbkdf/argon2/argon2.cpp --- botan3-3.7.1+dfsg/src/lib/pbkdf/argon2/argon2.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pbkdf/argon2/argon2.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,11 +6,12 @@ #include -#include #include #include +#include #include #include +#include #include #include @@ -18,7 +19,7 @@ #include #endif -#if defined(BOTAN_HAS_ARGON2_AVX2) || defined(BOTAN_HAS_ARGON2_SSSE3) +#if defined(BOTAN_HAS_CPUID) #include #endif @@ -53,7 +54,7 @@ blake2b.update_le(static_cast(y)); blake2b.update_le(static_cast(password_len)); - blake2b.update(cast_char_ptr_to_uint8(password), password_len); + blake2b.update(as_span_of_bytes(password, password_len)); blake2b.update_le(static_cast(salt_len)); blake2b.update(salt, salt_len); @@ -84,7 +85,7 @@ if(output_len <= 64) { auto blake2b = HashFunction::create_or_throw(fmt("BLAKE2b({})", output_len * 8)); blake2b->update_le(static_cast(output_len)); - for(size_t i = 0; i != 128; ++i) { + for(size_t i = 0; i != 128; ++i) { // NOLINT(modernize-loop-convert) blake2b->update_le(sum[i]); } blake2b->final(output); @@ -93,19 +94,19 @@ auto blake2b = HashFunction::create_or_throw("BLAKE2b(512)"); blake2b->update_le(static_cast(output_len)); - for(size_t i = 0; i != 128; ++i) { + for(size_t i = 0; i != 128; ++i) { // NOLINT(modernize-loop-convert) blake2b->update_le(sum[i]); } - blake2b->final(&T[0]); + blake2b->final(std::span{T}); while(output_len > 64) { - copy_mem(output, &T[0], 32); + copy_mem(output, T.data(), 32); output_len -= 32; output += 32; if(output_len > 64) { blake2b->update(T); - blake2b->final(&T[0]); + blake2b->final(std::span{T}); } } @@ -166,15 +167,21 @@ } // namespace void Argon2::blamka(uint64_t N[128], uint64_t T[128]) { +#if defined(BOTAN_HAS_ARGON2_AVX512) + if(CPUID::has(CPUID::Feature::AVX512)) { + return Argon2::blamka_avx512(N, T); + } +#endif + #if defined(BOTAN_HAS_ARGON2_AVX2) - if(CPUID::has_avx2()) { + if(CPUID::has(CPUID::Feature::AVX2)) { return Argon2::blamka_avx2(N, T); } #endif -#if defined(BOTAN_HAS_ARGON2_SSSE3) - if(CPUID::has_ssse3()) { - return Argon2::blamka_ssse3(N, T); +#if defined(BOTAN_HAS_ARGON2_SIMD64) + if(CPUID::has(CPUID::Feature::SIMD_2X64)) { + return Argon2::blamka_simd64(N, T); } #endif @@ -235,9 +242,27 @@ } } +// Reduce random modulo Argon2 thread count (normally a power of 2) +inline size_t mod_threads(uint32_t random, size_t threads) { + if(is_power_of_2(threads)) { + return random & static_cast(threads - 1); + } else { + return random % threads; + } +} + +// Reduce alpha modulo the lane length; always a multiple of 4 and commonly a power of 2 +inline size_t mod_lanes(uint64_t alpha, size_t lanes) { + if(is_power_of_2(lanes)) { + return static_cast(alpha & static_cast(lanes - 1)); + } else { + return alpha % lanes; + } +} + uint32_t index_alpha( uint64_t random, size_t lanes, size_t segments, size_t threads, size_t n, size_t slice, size_t lane, size_t index) { - size_t ref_lane = static_cast(random >> 32) % threads; + size_t ref_lane = mod_threads(static_cast(random >> 32), threads); if(n == 0 && slice == 0) { ref_lane = lane; @@ -266,7 +291,7 @@ p = (p * p) >> 32; p = (p * m) >> 32; - return static_cast(ref_lane * lanes + (s + m - (p + 1)) % lanes); + return static_cast(ref_lane * lanes + mod_lanes(s + m - (p + 1), lanes)); } void process_block(secure_vector& B, diff -Nru botan3-3.7.1+dfsg/src/lib/pbkdf/argon2/argon2.h botan3-3.12.0+dfsg/src/lib/pbkdf/argon2/argon2.h --- botan3-3.7.1+dfsg/src/lib/pbkdf/argon2/argon2.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pbkdf/argon2/argon2.h 2026-05-07 01:38:28.000000000 +0000 @@ -18,8 +18,6 @@ namespace Botan { -class RandomNumberGenerator; - /** * Argon2 key derivation function */ @@ -27,9 +25,6 @@ public: Argon2(uint8_t family, size_t M, size_t t, size_t p); - Argon2(const Argon2& other) = default; - Argon2& operator=(const Argon2&) = default; - /** * Derive a new key under the current Argon2 parameter set */ @@ -77,12 +72,16 @@ static void blamka(uint64_t N[128], uint64_t T[128]); private: +#if defined(BOTAN_HAS_ARGON2_AVX512) + static void blamka_avx512(uint64_t N[128], uint64_t T[128]); +#endif + #if defined(BOTAN_HAS_ARGON2_AVX2) static void blamka_avx2(uint64_t N[128], uint64_t T[128]); #endif -#if defined(BOTAN_HAS_ARGON2_SSSE3) - static void blamka_ssse3(uint64_t N[128], uint64_t T[128]); +#if defined(BOTAN_HAS_ARGON2_SIMD64) + static void blamka_simd64(uint64_t N[128], uint64_t T[128]); #endif void argon2(uint8_t output[], @@ -102,14 +101,14 @@ class BOTAN_PUBLIC_API(2, 11) Argon2_Family final : public PasswordHashFamily { public: - Argon2_Family(uint8_t family); + BOTAN_FUTURE_EXPLICIT Argon2_Family(uint8_t family); std::string name() const override; - std::unique_ptr tune(size_t output_length, - std::chrono::milliseconds msec, - size_t max_memory, - std::chrono::milliseconds tune_msec) const override; + std::unique_ptr tune_params(size_t output_len, + uint64_t desired_runtime_msec, + std::optional max_memory, + uint64_t tune_msec) const override; std::unique_ptr default_params() const override; diff -Nru botan3-3.7.1+dfsg/src/lib/pbkdf/argon2/argon2_avx2/argon2_avx2.cpp botan3-3.12.0+dfsg/src/lib/pbkdf/argon2/argon2_avx2/argon2_avx2.cpp --- botan3-3.7.1+dfsg/src/lib/pbkdf/argon2/argon2_avx2/argon2_avx2.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pbkdf/argon2/argon2_avx2/argon2_avx2.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,128 +7,14 @@ #include #include -#include +#include +#include namespace Botan { namespace { -class SIMD_4x64 final { - public: - SIMD_4x64& operator=(const SIMD_4x64& other) = default; - SIMD_4x64(const SIMD_4x64& other) = default; - - SIMD_4x64& operator=(SIMD_4x64&& other) = default; - SIMD_4x64(SIMD_4x64&& other) = default; - - ~SIMD_4x64() = default; - - // zero initialized - BOTAN_FUNC_ISA("avx2") SIMD_4x64() { m_simd = _mm256_setzero_si256(); } - - // Load two halves at different addresses - static BOTAN_FUNC_ISA("avx2") SIMD_4x64 load_le2(const void* inl, const void* inh) { - return SIMD_4x64( - _mm256_loadu2_m128i(reinterpret_cast(inl), reinterpret_cast(inh))); - } - - static BOTAN_FUNC_ISA("avx2") SIMD_4x64 load_le(const void* in) { - return SIMD_4x64(_mm256_loadu_si256(reinterpret_cast(in))); - } - - void store_le(uint64_t out[4]) const { this->store_le(reinterpret_cast(out)); } - - BOTAN_FUNC_ISA("avx2") void store_le(uint8_t out[]) const { - _mm256_storeu_si256(reinterpret_cast<__m256i*>(out), m_simd); - } - - BOTAN_FUNC_ISA("avx2") void store_le2(void* outh, void* outl) { - _mm256_storeu2_m128i(reinterpret_cast<__m128i*>(outh), reinterpret_cast<__m128i*>(outl), m_simd); - } - - SIMD_4x64 operator+(const SIMD_4x64& other) const { - SIMD_4x64 retval(*this); - retval += other; - return retval; - } - - SIMD_4x64 operator^(const SIMD_4x64& other) const { - SIMD_4x64 retval(*this); - retval ^= other; - return retval; - } - - BOTAN_FUNC_ISA("avx2") void operator+=(const SIMD_4x64& other) { - m_simd = _mm256_add_epi64(m_simd, other.m_simd); - } - - BOTAN_FUNC_ISA("avx2") void operator^=(const SIMD_4x64& other) { - m_simd = _mm256_xor_si256(m_simd, other.m_simd); - } - - template - BOTAN_FUNC_ISA("avx2") - SIMD_4x64 rotr() const - requires(ROT > 0 && ROT < 64) - { - if constexpr(ROT == 16) { - auto shuf_rot_16 = - _mm256_set_epi64x(0x09080f0e0d0c0b0a, 0x0100070605040302, 0x09080f0e0d0c0b0a, 0x0100070605040302); - - return SIMD_4x64(_mm256_shuffle_epi8(m_simd, shuf_rot_16)); - } else if constexpr(ROT == 24) { - auto shuf_rot_24 = - _mm256_set_epi64x(0x0a09080f0e0d0c0b, 0x0201000706050403, 0x0a09080f0e0d0c0b, 0x0201000706050403); - - return SIMD_4x64(_mm256_shuffle_epi8(m_simd, shuf_rot_24)); - } else if constexpr(ROT == 32) { - auto shuf_rot_32 = - _mm256_set_epi64x(0x0b0a09080f0e0d0c, 0x0302010007060504, 0x0b0a09080f0e0d0c, 0x0302010007060504); - - return SIMD_4x64(_mm256_shuffle_epi8(m_simd, shuf_rot_32)); - } else { - return SIMD_4x64(_mm256_or_si256(_mm256_srli_epi64(m_simd, static_cast(ROT)), - _mm256_slli_epi64(m_simd, static_cast(64 - ROT)))); - } - } - - template - SIMD_4x64 rotl() const { - return this->rotr<64 - ROT>(); - } - - // Argon2 specific operation - static BOTAN_FUNC_ISA("avx2") SIMD_4x64 mul2_32(SIMD_4x64 x, SIMD_4x64 y) { - const __m256i m = _mm256_mul_epu32(x.m_simd, y.m_simd); - return SIMD_4x64(_mm256_add_epi64(m, m)); - } - - template - static BOTAN_FUNC_ISA("avx2") SIMD_4x64 permute_4x64(SIMD_4x64 x) { - return SIMD_4x64(_mm256_permute4x64_epi64(x.m_simd, CTRL)); - } - - // Argon2 specific - static void twist(SIMD_4x64& B, SIMD_4x64& C, SIMD_4x64& D) { - B = SIMD_4x64::permute_4x64<0b00'11'10'01>(B); - C = SIMD_4x64::permute_4x64<0b01'00'11'10>(C); - D = SIMD_4x64::permute_4x64<0b10'01'00'11>(D); - } - - // Argon2 specific - static void untwist(SIMD_4x64& B, SIMD_4x64& C, SIMD_4x64& D) { - B = SIMD_4x64::permute_4x64<0b10'01'00'11>(B); - C = SIMD_4x64::permute_4x64<0b01'00'11'10>(C); - D = SIMD_4x64::permute_4x64<0b00'11'10'01>(D); - } - - explicit BOTAN_FUNC_ISA("avx2") SIMD_4x64(__m256i x) : m_simd(x) {} - - private: - __m256i m_simd; -}; - -BOTAN_FORCE_INLINE void blamka_G(SIMD_4x64& A, SIMD_4x64& B, SIMD_4x64& C, SIMD_4x64& D) { +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX2 void blamka_G(SIMD_4x64& A, SIMD_4x64& B, SIMD_4x64& C, SIMD_4x64& D) { A += B + SIMD_4x64::mul2_32(A, B); D ^= A; D = D.rotr<32>(); @@ -146,7 +32,7 @@ B = B.rotr<63>(); } -BOTAN_FORCE_INLINE void blamka_R(SIMD_4x64& A, SIMD_4x64& B, SIMD_4x64& C, SIMD_4x64& D) { +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX2 void blamka_R(SIMD_4x64& A, SIMD_4x64& B, SIMD_4x64& C, SIMD_4x64& D) { blamka_G(A, B, C, D); SIMD_4x64::twist(B, C, D); @@ -156,7 +42,7 @@ } // namespace -BOTAN_FUNC_ISA("avx2") void Argon2::blamka_avx2(uint64_t N[128], uint64_t T[128]) { +BOTAN_FN_ISA_AVX2 void Argon2::blamka_avx2(uint64_t N[128], uint64_t T[128]) { for(size_t i = 0; i != 8; ++i) { SIMD_4x64 A = SIMD_4x64::load_le(&N[16 * i + 4 * 0]); SIMD_4x64 B = SIMD_4x64::load_le(&N[16 * i + 4 * 1]); @@ -188,8 +74,8 @@ for(size_t i = 0; i != 128 / 8; ++i) { SIMD_4x64 n0 = SIMD_4x64::load_le(&N[8 * i]); SIMD_4x64 n1 = SIMD_4x64::load_le(&N[8 * i + 4]); - SIMD_4x64 t0 = SIMD_4x64::load_le(&T[8 * i]); - SIMD_4x64 t1 = SIMD_4x64::load_le(&T[8 * i + 4]); + const SIMD_4x64 t0 = SIMD_4x64::load_le(&T[8 * i]); + const SIMD_4x64 t1 = SIMD_4x64::load_le(&T[8 * i + 4]); n0 ^= t0; n1 ^= t1; diff -Nru botan3-3.7.1+dfsg/src/lib/pbkdf/argon2/argon2_avx2/info.txt botan3-3.12.0+dfsg/src/lib/pbkdf/argon2/argon2_avx2/info.txt --- botan3-3.7.1+dfsg/src/lib/pbkdf/argon2/argon2_avx2/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pbkdf/argon2/argon2_avx2/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + ARGON2_AVX2 -> 20221216 - + name -> "Argon2 AVX2" @@ -10,3 +10,8 @@ avx2 + + +cpuid +simd_4x64 + diff -Nru botan3-3.7.1+dfsg/src/lib/pbkdf/argon2/argon2_avx512/argon2_avx512.cpp botan3-3.12.0+dfsg/src/lib/pbkdf/argon2/argon2_avx512/argon2_avx512.cpp --- botan3-3.7.1+dfsg/src/lib/pbkdf/argon2/argon2_avx512/argon2_avx512.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pbkdf/argon2/argon2_avx512/argon2_avx512.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,88 @@ +/** +* (C) 2026 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include + +namespace Botan { + +namespace { + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512 void blamka_G(SIMD_8x64& A, SIMD_8x64& B, SIMD_8x64& C, SIMD_8x64& D) { + A += B + SIMD_8x64::mul2_32(A, B); + D ^= A; + D = D.rotr<32>(); + + C += D + SIMD_8x64::mul2_32(C, D); + B ^= C; + B = B.rotr<24>(); + + A += B + SIMD_8x64::mul2_32(A, B); + D ^= A; + D = D.rotr<16>(); + + C += D + SIMD_8x64::mul2_32(C, D); + B ^= C; + B = B.rotr<63>(); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_AVX512 void blamka_R(SIMD_8x64& A, SIMD_8x64& B, SIMD_8x64& C, SIMD_8x64& D) { + blamka_G(A, B, C, D); + + SIMD_8x64::twist(B, C, D); + blamka_G(A, B, C, D); + SIMD_8x64::untwist(B, C, D); +} + +} // namespace + +BOTAN_FN_ISA_AVX512 void Argon2::blamka_avx512(uint64_t N[128], uint64_t T[128]) { + for(size_t i = 0; i != 8; i += 2) { + SIMD_8x64 A = SIMD_8x64::load_le4( + &N[16 * i + 4 * 0], &N[16 * i + 4 * 0 + 2], &N[16 * (i + 1) + 4 * 0], &N[16 * (i + 1) + 4 * 0 + 2]); + SIMD_8x64 B = SIMD_8x64::load_le4( + &N[16 * i + 4 * 1], &N[16 * i + 4 * 1 + 2], &N[16 * (i + 1) + 4 * 1], &N[16 * (i + 1) + 4 * 1 + 2]); + SIMD_8x64 C = SIMD_8x64::load_le4( + &N[16 * i + 4 * 2], &N[16 * i + 4 * 2 + 2], &N[16 * (i + 1) + 4 * 2], &N[16 * (i + 1) + 4 * 2 + 2]); + SIMD_8x64 D = SIMD_8x64::load_le4( + &N[16 * i + 4 * 3], &N[16 * i + 4 * 3 + 2], &N[16 * (i + 1) + 4 * 3], &N[16 * (i + 1) + 4 * 3 + 2]); + + blamka_R(A, B, C, D); + + A.store_le4(&T[16 * i + 4 * 0], &T[16 * i + 4 * 0 + 2], &T[16 * (i + 1) + 4 * 0], &T[16 * (i + 1) + 4 * 0 + 2]); + B.store_le4(&T[16 * i + 4 * 1], &T[16 * i + 4 * 1 + 2], &T[16 * (i + 1) + 4 * 1], &T[16 * (i + 1) + 4 * 1 + 2]); + C.store_le4(&T[16 * i + 4 * 2], &T[16 * i + 4 * 2 + 2], &T[16 * (i + 1) + 4 * 2], &T[16 * (i + 1) + 4 * 2 + 2]); + D.store_le4(&T[16 * i + 4 * 3], &T[16 * i + 4 * 3 + 2], &T[16 * (i + 1) + 4 * 3], &T[16 * (i + 1) + 4 * 3 + 2]); + } + + for(size_t i = 0; i != 8; i += 2) { + SIMD_8x64 A = SIMD_8x64::load_le4( + &T[2 * i + 32 * 0], &T[2 * i + 32 * 0 + 16], &T[2 * (i + 1) + 32 * 0], &T[2 * (i + 1) + 32 * 0 + 16]); + SIMD_8x64 B = SIMD_8x64::load_le4( + &T[2 * i + 32 * 1], &T[2 * i + 32 * 1 + 16], &T[2 * (i + 1) + 32 * 1], &T[2 * (i + 1) + 32 * 1 + 16]); + SIMD_8x64 C = SIMD_8x64::load_le4( + &T[2 * i + 32 * 2], &T[2 * i + 32 * 2 + 16], &T[2 * (i + 1) + 32 * 2], &T[2 * (i + 1) + 32 * 2 + 16]); + SIMD_8x64 D = SIMD_8x64::load_le4( + &T[2 * i + 32 * 3], &T[2 * i + 32 * 3 + 16], &T[2 * (i + 1) + 32 * 3], &T[2 * (i + 1) + 32 * 3 + 16]); + + blamka_R(A, B, C, D); + + A.store_le4(&T[2 * i + 32 * 0], &T[2 * i + 32 * 0 + 16], &T[2 * (i + 1) + 32 * 0], &T[2 * (i + 1) + 32 * 0 + 16]); + B.store_le4(&T[2 * i + 32 * 1], &T[2 * i + 32 * 1 + 16], &T[2 * (i + 1) + 32 * 1], &T[2 * (i + 1) + 32 * 1 + 16]); + C.store_le4(&T[2 * i + 32 * 2], &T[2 * i + 32 * 2 + 16], &T[2 * (i + 1) + 32 * 2], &T[2 * (i + 1) + 32 * 2 + 16]); + D.store_le4(&T[2 * i + 32 * 3], &T[2 * i + 32 * 3 + 16], &T[2 * (i + 1) + 32 * 3], &T[2 * (i + 1) + 32 * 3 + 16]); + } + + for(size_t i = 0; i != 128 / 8; ++i) { + SIMD_8x64 n = SIMD_8x64::load_le(&N[8 * i]); + n ^= SIMD_8x64::load_le(&T[8 * i]); + n.store_le(&N[8 * i]); + } +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pbkdf/argon2/argon2_avx512/info.txt botan3-3.12.0+dfsg/src/lib/pbkdf/argon2/argon2_avx512/info.txt --- botan3-3.7.1+dfsg/src/lib/pbkdf/argon2/argon2_avx512/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pbkdf/argon2/argon2_avx512/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,17 @@ + +ARGON2_AVX512 -> 20260318 + + + +name -> "Argon2 AVX-512" +brief -> "Argon2 using AVX-512 instructions" + + + +avx512 + + + +cpuid +simd_8x64 + diff -Nru botan3-3.7.1+dfsg/src/lib/pbkdf/argon2/argon2_simd64/argon2_simd64.cpp botan3-3.12.0+dfsg/src/lib/pbkdf/argon2/argon2_simd64/argon2_simd64.cpp --- botan3-3.7.1+dfsg/src/lib/pbkdf/argon2/argon2_simd64/argon2_simd64.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pbkdf/argon2/argon2_simd64/argon2_simd64.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,114 @@ +/** +* (C) 2022 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include + +namespace Botan { + +namespace { + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_SIMD_2X64 void blamka_G(SIMD_2x64& A0, + SIMD_2x64& A1, + SIMD_2x64& B0, + SIMD_2x64& B1, + SIMD_2x64& C0, + SIMD_2x64& C1, + SIMD_2x64& D0, + SIMD_2x64& D1) { + A0 += B0 + SIMD_2x64::mul2_32(A0, B0); + A1 += B1 + SIMD_2x64::mul2_32(A1, B1); + D0 ^= A0; + D1 ^= A1; + D0 = D0.rotr<32>(); + D1 = D1.rotr<32>(); + + C0 += D0 + SIMD_2x64::mul2_32(C0, D0); + C1 += D1 + SIMD_2x64::mul2_32(C1, D1); + B0 ^= C0; + B1 ^= C1; + B0 = B0.rotr<24>(); + B1 = B1.rotr<24>(); + + A0 += B0 + SIMD_2x64::mul2_32(A0, B0); + A1 += B1 + SIMD_2x64::mul2_32(A1, B1); + D0 ^= A0; + D1 ^= A1; + D0 = D0.rotr<16>(); + D1 = D1.rotr<16>(); + + C0 += D0 + SIMD_2x64::mul2_32(C0, D0); + C1 += D1 + SIMD_2x64::mul2_32(C1, D1); + B0 ^= C0; + B1 ^= C1; + B0 = B0.rotr<63>(); + B1 = B1.rotr<63>(); +} + +BOTAN_FORCE_INLINE BOTAN_FN_ISA_SIMD_2X64 void blamka_R(SIMD_2x64& A0, + SIMD_2x64& A1, + SIMD_2x64& B0, + SIMD_2x64& B1, + SIMD_2x64& C0, + SIMD_2x64& C1, + SIMD_2x64& D0, + SIMD_2x64& D1) { + blamka_G(A0, A1, B0, B1, C0, C1, D0, D1); + + SIMD_2x64::twist(B0, B1, C0, C1, D0, D1); + blamka_G(A0, A1, B0, B1, C0, C1, D0, D1); + SIMD_2x64::untwist(B0, B1, C0, C1, D0, D1); +} + +} // namespace + +void BOTAN_FN_ISA_SIMD_2X64 Argon2::blamka_simd64(uint64_t N[128], uint64_t T[128]) { + for(size_t i = 0; i != 8; ++i) { + SIMD_2x64 Tv[8]; + for(size_t j = 0; j != 4; ++j) { + Tv[2 * j] = SIMD_2x64::load_le(&N[16 * i + 4 * j]); + Tv[2 * j + 1] = SIMD_2x64::load_le(&N[16 * i + 4 * j + 2]); + } + + blamka_R(Tv[0], Tv[1], Tv[2], Tv[3], Tv[4], Tv[5], Tv[6], Tv[7]); + + for(size_t j = 0; j != 4; ++j) { + Tv[2 * j].store_le(&T[16 * i + 4 * j]); + Tv[2 * j + 1].store_le(&T[16 * i + 4 * j + 2]); + } + } + + for(size_t i = 0; i != 8; ++i) { + SIMD_2x64 Tv[8]; + for(size_t j = 0; j != 4; ++j) { + Tv[2 * j] = SIMD_2x64::load_le(&T[2 * i + 32 * j]); + Tv[2 * j + 1] = SIMD_2x64::load_le(&T[2 * i + 32 * j + 16]); + } + + blamka_R(Tv[0], Tv[1], Tv[2], Tv[3], Tv[4], Tv[5], Tv[6], Tv[7]); + + for(size_t j = 0; j != 4; ++j) { + Tv[2 * j].store_le(&T[2 * i + 32 * j]); + Tv[2 * j + 1].store_le(&T[2 * i + 32 * j + 16]); + } + } + + for(size_t i = 0; i != 128 / 4; ++i) { + SIMD_2x64 n0 = SIMD_2x64::load_le(&N[4 * i]); + SIMD_2x64 n1 = SIMD_2x64::load_le(&N[4 * i + 2]); + const SIMD_2x64 t0 = SIMD_2x64::load_le(&T[4 * i]); + const SIMD_2x64 t1 = SIMD_2x64::load_le(&T[4 * i + 2]); + + n0 ^= t0; + n1 ^= t1; + n0.store_le(&N[4 * i]); + n1.store_le(&N[4 * i + 2]); + } +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pbkdf/argon2/argon2_simd64/info.txt botan3-3.12.0+dfsg/src/lib/pbkdf/argon2/argon2_simd64/info.txt --- botan3-3.7.1+dfsg/src/lib/pbkdf/argon2/argon2_simd64/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pbkdf/argon2/argon2_simd64/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,18 @@ + +ARGON2_SIMD64 -> 20251107 + + + +name -> "Argon2 SIMD_2x64" +brief -> "Argon2 using SIMD_2x64" + + +# MSVC miscompiles this code on x86-32 + +!msvc + + + +cpuid +simd_2x64 + diff -Nru botan3-3.7.1+dfsg/src/lib/pbkdf/argon2/argon2_ssse3/argon2_ssse3.cpp botan3-3.12.0+dfsg/src/lib/pbkdf/argon2/argon2_ssse3/argon2_ssse3.cpp --- botan3-3.7.1+dfsg/src/lib/pbkdf/argon2/argon2_ssse3/argon2_ssse3.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pbkdf/argon2/argon2_ssse3/argon2_ssse3.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,234 +0,0 @@ -/** -* (C) 2022 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include - -#include -#include - -namespace Botan { - -namespace { - -class SIMD_2x64 final { - public: - SIMD_2x64& operator=(const SIMD_2x64& other) = default; - SIMD_2x64(const SIMD_2x64& other) = default; - - SIMD_2x64& operator=(SIMD_2x64&& other) = default; - SIMD_2x64(SIMD_2x64&& other) = default; - - ~SIMD_2x64() = default; - - // zero initialized - SIMD_2x64() { m_simd = _mm_setzero_si128(); } - - static SIMD_2x64 load_le(const void* in) { - return SIMD_2x64(_mm_loadu_si128(reinterpret_cast(in))); - } - - void store_le(uint64_t out[2]) const { this->store_le(reinterpret_cast(out)); } - - void store_le(uint8_t out[]) const { _mm_storeu_si128(reinterpret_cast<__m128i*>(out), m_simd); } - - SIMD_2x64 operator+(const SIMD_2x64& other) const { - SIMD_2x64 retval(*this); - retval += other; - return retval; - } - - SIMD_2x64 operator^(const SIMD_2x64& other) const { - SIMD_2x64 retval(*this); - retval ^= other; - return retval; - } - - void operator+=(const SIMD_2x64& other) { m_simd = _mm_add_epi64(m_simd, other.m_simd); } - - void operator^=(const SIMD_2x64& other) { m_simd = _mm_xor_si128(m_simd, other.m_simd); } - - template - BOTAN_FUNC_ISA("ssse3") - SIMD_2x64 rotr() const - requires(ROT > 0 && ROT < 64) - { - if constexpr(ROT == 16) { - auto tab = _mm_setr_epi8(2, 3, 4, 5, 6, 7, 0, 1, 10, 11, 12, 13, 14, 15, 8, 9); - return SIMD_2x64(_mm_shuffle_epi8(m_simd, tab)); - } else if constexpr(ROT == 24) { - auto tab = _mm_setr_epi8(3, 4, 5, 6, 7, 0, 1, 2, 11, 12, 13, 14, 15, 8, 9, 10); - return SIMD_2x64(_mm_shuffle_epi8(m_simd, tab)); - } else if constexpr(ROT == 32) { - auto tab = _mm_setr_epi8(4, 5, 6, 7, 0, 1, 2, 3, 12, 13, 14, 15, 8, 9, 10, 11); - return SIMD_2x64(_mm_shuffle_epi8(m_simd, tab)); - } else { - return SIMD_2x64(_mm_or_si128(_mm_srli_epi64(m_simd, static_cast(ROT)), - _mm_slli_epi64(m_simd, static_cast(64 - ROT)))); - } - } - - template - SIMD_2x64 rotl() const { - return this->rotr<64 - ROT>(); - } - - // Argon2 specific operation - static SIMD_2x64 mul2_32(SIMD_2x64 x, SIMD_2x64 y) { - const __m128i m = _mm_mul_epu32(x.m_simd, y.m_simd); - return SIMD_2x64(_mm_add_epi64(m, m)); - } - - template - BOTAN_FUNC_ISA("ssse3") - static SIMD_2x64 alignr(SIMD_2x64 a, SIMD_2x64 b) - requires(T > 0 && T < 16) - { - return SIMD_2x64(_mm_alignr_epi8(a.m_simd, b.m_simd, T)); - } - - // Argon2 specific - static void twist(SIMD_2x64& B0, SIMD_2x64& B1, SIMD_2x64& C0, SIMD_2x64& C1, SIMD_2x64& D0, SIMD_2x64& D1) { - SIMD_2x64 T0, T1; - - T0 = SIMD_2x64::alignr<8>(B1, B0); - T1 = SIMD_2x64::alignr<8>(B0, B1); - B0 = T0; - B1 = T1; - - T0 = C0; - C0 = C1; - C1 = T0; - - T0 = SIMD_2x64::alignr<8>(D0, D1); - T1 = SIMD_2x64::alignr<8>(D1, D0); - D0 = T0; - D1 = T1; - } - - // Argon2 specific - static void untwist(SIMD_2x64& B0, SIMD_2x64& B1, SIMD_2x64& C0, SIMD_2x64& C1, SIMD_2x64& D0, SIMD_2x64& D1) { - SIMD_2x64 T0, T1; - - T0 = SIMD_2x64::alignr<8>(B0, B1); - T1 = SIMD_2x64::alignr<8>(B1, B0); - B0 = T0; - B1 = T1; - - T0 = C0; - C0 = C1; - C1 = T0; - - T0 = SIMD_2x64::alignr<8>(D1, D0); - T1 = SIMD_2x64::alignr<8>(D0, D1); - D0 = T0; - D1 = T1; - } - - explicit SIMD_2x64(__m128i x) : m_simd(x) {} - - private: - __m128i m_simd; -}; - -BOTAN_FORCE_INLINE void blamka_G(SIMD_2x64& A0, - SIMD_2x64& A1, - SIMD_2x64& B0, - SIMD_2x64& B1, - SIMD_2x64& C0, - SIMD_2x64& C1, - SIMD_2x64& D0, - SIMD_2x64& D1) { - A0 += B0 + SIMD_2x64::mul2_32(A0, B0); - A1 += B1 + SIMD_2x64::mul2_32(A1, B1); - D0 ^= A0; - D1 ^= A1; - D0 = D0.rotr<32>(); - D1 = D1.rotr<32>(); - - C0 += D0 + SIMD_2x64::mul2_32(C0, D0); - C1 += D1 + SIMD_2x64::mul2_32(C1, D1); - B0 ^= C0; - B1 ^= C1; - B0 = B0.rotr<24>(); - B1 = B1.rotr<24>(); - - A0 += B0 + SIMD_2x64::mul2_32(A0, B0); - A1 += B1 + SIMD_2x64::mul2_32(A1, B1); - D0 ^= A0; - D1 ^= A1; - D0 = D0.rotr<16>(); - D1 = D1.rotr<16>(); - - C0 += D0 + SIMD_2x64::mul2_32(C0, D0); - C1 += D1 + SIMD_2x64::mul2_32(C1, D1); - B0 ^= C0; - B1 ^= C1; - B0 = B0.rotr<63>(); - B1 = B1.rotr<63>(); -} - -BOTAN_FORCE_INLINE void blamka_R(SIMD_2x64& A0, - SIMD_2x64& A1, - SIMD_2x64& B0, - SIMD_2x64& B1, - SIMD_2x64& C0, - SIMD_2x64& C1, - SIMD_2x64& D0, - SIMD_2x64& D1) { - blamka_G(A0, A1, B0, B1, C0, C1, D0, D1); - - SIMD_2x64::twist(B0, B1, C0, C1, D0, D1); - blamka_G(A0, A1, B0, B1, C0, C1, D0, D1); - SIMD_2x64::untwist(B0, B1, C0, C1, D0, D1); -} - -} // namespace - -void Argon2::blamka_ssse3(uint64_t N[128], uint64_t T[128]) { - for(size_t i = 0; i != 8; ++i) { - SIMD_2x64 Tv[8]; - for(size_t j = 0; j != 4; ++j) { - Tv[2 * j] = SIMD_2x64::load_le(&N[16 * i + 4 * j]); - Tv[2 * j + 1] = SIMD_2x64::load_le(&N[16 * i + 4 * j + 2]); - } - - blamka_R(Tv[0], Tv[1], Tv[2], Tv[3], Tv[4], Tv[5], Tv[6], Tv[7]); - - for(size_t j = 0; j != 4; ++j) { - Tv[2 * j].store_le(&T[16 * i + 4 * j]); - Tv[2 * j + 1].store_le(&T[16 * i + 4 * j + 2]); - } - } - - for(size_t i = 0; i != 8; ++i) { - SIMD_2x64 Tv[8]; - for(size_t j = 0; j != 4; ++j) { - Tv[2 * j] = SIMD_2x64::load_le(&T[2 * i + 32 * j]); - Tv[2 * j + 1] = SIMD_2x64::load_le(&T[2 * i + 32 * j + 16]); - } - - blamka_R(Tv[0], Tv[1], Tv[2], Tv[3], Tv[4], Tv[5], Tv[6], Tv[7]); - - for(size_t j = 0; j != 4; ++j) { - Tv[2 * j].store_le(&T[2 * i + 32 * j]); - Tv[2 * j + 1].store_le(&T[2 * i + 32 * j + 16]); - } - } - - for(size_t i = 0; i != 128 / 4; ++i) { - SIMD_2x64 n0 = SIMD_2x64::load_le(&N[4 * i]); - SIMD_2x64 n1 = SIMD_2x64::load_le(&N[4 * i + 2]); - SIMD_2x64 t0 = SIMD_2x64::load_le(&T[4 * i]); - SIMD_2x64 t1 = SIMD_2x64::load_le(&T[4 * i + 2]); - - n0 ^= t0; - n1 ^= t1; - n0.store_le(&N[4 * i]); - n1.store_le(&N[4 * i + 2]); - } -} - -} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pbkdf/argon2/argon2_ssse3/info.txt botan3-3.12.0+dfsg/src/lib/pbkdf/argon2/argon2_ssse3/info.txt --- botan3-3.7.1+dfsg/src/lib/pbkdf/argon2/argon2_ssse3/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pbkdf/argon2/argon2_ssse3/info.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,17 +0,0 @@ - -ARGON2_SSSE3 -> 20220303 - - - -name -> "Argon2 SSSE3" -brief -> "Argon2 using SSSE3 instructions" - - - -ssse3 - - -# MSVC miscompiles this code on x86-32 - -!msvc - diff -Nru botan3-3.7.1+dfsg/src/lib/pbkdf/argon2/argon2pwhash.cpp botan3-3.12.0+dfsg/src/lib/pbkdf/argon2/argon2pwhash.cpp --- botan3-3.7.1+dfsg/src/lib/pbkdf/argon2/argon2pwhash.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pbkdf/argon2/argon2pwhash.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -74,15 +74,16 @@ return argon2_family_name(m_family); } -std::unique_ptr Argon2_Family::tune(size_t /*output_length*/, - std::chrono::milliseconds msec, - size_t max_memory, - std::chrono::milliseconds tune_time) const { - const size_t max_kib = (max_memory == 0) ? 256 * 1024 : max_memory * 1024; +std::unique_ptr Argon2_Family::tune_params(size_t /*output_length*/, + uint64_t desired_msec, + std::optional max_memory, + uint64_t tune_msec) const { + // If not set use 256 MB as default max + const size_t max_kib = max_memory.value_or(256) * 1024; // Tune with a large memory otherwise we measure cache vs RAM speeds and underestimate // costs for larger params. Default is 36 MiB, or use 128 for long times. - const size_t tune_M = (msec >= std::chrono::milliseconds(200) ? 128 : 36) * 1024; + const size_t tune_M = (desired_msec >= 200 ? 128 : 36) * 1024; const size_t p = 1; size_t t = 1; @@ -95,9 +96,9 @@ pwhash->derive_key(output, sizeof(output), "test", 4, nullptr, 0); }; - const uint64_t measured_time = measure_cost(tune_time, tune_fn) / (tune_M / M); + const uint64_t measured_time = measure_cost(tune_msec, tune_fn) / (tune_M / M); - const uint64_t target_nsec = msec.count() * static_cast(1000000); + const uint64_t target_nsec = desired_msec * static_cast(1000000); /* * Argon2 scaling rules: diff -Nru botan3-3.7.1+dfsg/src/lib/pbkdf/bcrypt_pbkdf/bcrypt_pbkdf.cpp botan3-3.12.0+dfsg/src/lib/pbkdf/bcrypt_pbkdf/bcrypt_pbkdf.cpp --- botan3-3.7.1+dfsg/src/lib/pbkdf/bcrypt_pbkdf/bcrypt_pbkdf.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pbkdf/bcrypt_pbkdf/bcrypt_pbkdf.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -10,6 +10,7 @@ #include #include #include +#include #include namespace Botan { @@ -26,10 +27,10 @@ return "Bcrypt-PBKDF"; } -std::unique_ptr Bcrypt_PBKDF_Family::tune(size_t output_length, - std::chrono::milliseconds msec, - size_t /*max_memory*/, - std::chrono::milliseconds tune_time) const { +std::unique_ptr Bcrypt_PBKDF_Family::tune_params(size_t output_length, + uint64_t desired_msec, + std::optional /*max_memory*/, + uint64_t tune_msec) const { const size_t blocks = (output_length + 32 - 1) / 32; if(blocks == 0) { @@ -45,9 +46,9 @@ pwhash->derive_key(output, sizeof(output), "test", 4, nullptr, 0); }; - const uint64_t measured_time = measure_cost(tune_time, tune_fn) / blocks; + const uint64_t measured_time = measure_cost(tune_msec, tune_fn) / blocks; - const uint64_t target_nsec = msec.count() * static_cast(1000000); + const uint64_t target_nsec = desired_msec * static_cast(1000000); const uint64_t desired_increase = target_nsec / measured_time; @@ -62,12 +63,12 @@ return this->from_iterations(32); // About 100 ms on fast machine } -std::unique_ptr Bcrypt_PBKDF_Family::from_iterations(size_t iter) const { - return std::make_unique(iter); +std::unique_ptr Bcrypt_PBKDF_Family::from_iterations(size_t iterations) const { + return std::make_unique(iterations); } -std::unique_ptr Bcrypt_PBKDF_Family::from_params(size_t iter, size_t /*t*/, size_t /*p*/) const { - return this->from_iterations(iter); +std::unique_ptr Bcrypt_PBKDF_Family::from_params(size_t iterations, size_t /*t*/, size_t /*p*/) const { + return this->from_iterations(iterations); } namespace { @@ -128,7 +129,7 @@ const size_t blocks = (output_len + BCRYPT_BLOCK_SIZE - 1) / BCRYPT_BLOCK_SIZE; auto sha512 = HashFunction::create_or_throw("SHA-512"); - const auto pass_hash = sha512->process(reinterpret_cast(password), password_len); + const auto pass_hash = sha512->process(as_span_of_bytes(password, password_len)); secure_vector salt_hash(sha512->output_length()); diff -Nru botan3-3.7.1+dfsg/src/lib/pbkdf/bcrypt_pbkdf/bcrypt_pbkdf.h botan3-3.12.0+dfsg/src/lib/pbkdf/bcrypt_pbkdf/bcrypt_pbkdf.h --- botan3-3.7.1+dfsg/src/lib/pbkdf/bcrypt_pbkdf/bcrypt_pbkdf.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pbkdf/bcrypt_pbkdf/bcrypt_pbkdf.h 2026-05-07 01:38:28.000000000 +0000 @@ -19,10 +19,7 @@ */ class BOTAN_PUBLIC_API(2, 11) Bcrypt_PBKDF final : public PasswordHash { public: - Bcrypt_PBKDF(size_t iterations); - - Bcrypt_PBKDF(const Bcrypt_PBKDF& other) = default; - Bcrypt_PBKDF& operator=(const Bcrypt_PBKDF&) = default; + BOTAN_FUTURE_EXPLICIT Bcrypt_PBKDF(size_t iterations); /** * Derive a new key under the current Bcrypt-PBKDF parameter set @@ -54,16 +51,16 @@ std::string name() const override; - std::unique_ptr tune(size_t output_length, - std::chrono::milliseconds msec, - size_t max_memory, - std::chrono::milliseconds tune_msec) const override; + std::unique_ptr tune_params(size_t output_len, + uint64_t desired_runtime_msec, + std::optional max_memory, + uint64_t tune_msec) const override; std::unique_ptr default_params() const override; - std::unique_ptr from_iterations(size_t iter) const override; + std::unique_ptr from_iterations(size_t iterations) const override; - std::unique_ptr from_params(size_t i, size_t, size_t) const override; + std::unique_ptr from_params(size_t iterations, size_t /*unused*/, size_t /*unused*/) const override; }; /** diff -Nru botan3-3.7.1+dfsg/src/lib/pbkdf/pbkdf.h botan3-3.12.0+dfsg/src/lib/pbkdf/pbkdf.h --- botan3-3.7.1+dfsg/src/lib/pbkdf/pbkdf.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pbkdf/pbkdf.h 2026-05-07 01:38:28.000000000 +0000 @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -26,9 +27,11 @@ * and iterated hashing to make brute force attacks harder. * * Starting in 2.8 this functionality is also offered by PasswordHash. -* The PBKDF interface may be removed in a future release. +* +* @warning +* This class will be removed in a future major release. Use PasswordHash */ -class BOTAN_PUBLIC_API(2, 0) PBKDF { +class BOTAN_PUBLIC_API(2, 0) PBKDF /* NOLINT(*-special-member-functions) */ { public: /** * Create an instance based on a name diff -Nru botan3-3.7.1+dfsg/src/lib/pbkdf/pbkdf2/pbkdf2.cpp botan3-3.12.0+dfsg/src/lib/pbkdf/pbkdf2/pbkdf2.cpp --- botan3-3.7.1+dfsg/src/lib/pbkdf/pbkdf2/pbkdf2.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pbkdf/pbkdf2/pbkdf2.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,7 +9,10 @@ #include #include +#include +#include #include +#include #include namespace Botan { @@ -18,7 +21,7 @@ void pbkdf2_set_key(MessageAuthenticationCode& prf, const char* password, size_t password_len) { try { - prf.set_key(cast_char_ptr_to_uint8(password), password_len); + prf.set_key(as_span_of_bytes(password, password_len)); } catch(Invalid_Key_Length&) { throw Invalid_Argument("PBKDF2 cannot accept passphrase of the given size"); } @@ -26,15 +29,15 @@ size_t tune_pbkdf2(MessageAuthenticationCode& prf, size_t output_length, - std::chrono::milliseconds msec, - std::chrono::milliseconds tune_time = std::chrono::milliseconds(10)) { + uint64_t desired_msec, + uint64_t tuning_msec = 10) { if(output_length == 0) { output_length = 1; } const size_t prf_sz = prf.output_length(); BOTAN_ASSERT_NOMSG(prf_sz > 0); - secure_vector U(prf_sz); + const secure_vector U(prf_sz); const size_t trial_iterations = 2000; @@ -42,13 +45,13 @@ prf.set_key(nullptr, 0); - const uint64_t duration_nsec = measure_cost(tune_time, [&]() { + const uint64_t duration_nsec = measure_cost(tuning_msec, [&]() { uint8_t out[12] = {0}; uint8_t salt[12] = {0}; pbkdf2(prf, out, sizeof(out), salt, sizeof(salt), trial_iterations); }); - const uint64_t desired_nsec = static_cast(msec.count()) * 1000000; + const uint64_t desired_nsec = desired_msec * 1000000; if(duration_nsec > desired_nsec) { return trial_iterations; @@ -76,10 +79,10 @@ size_t iterations, std::chrono::milliseconds msec) { if(iterations == 0) { - iterations = tune_pbkdf2(prf, out_len, msec); + iterations = tune_pbkdf2(prf, out_len, msec.count()); } - PBKDF2 pbkdf2(prf, iterations); + const PBKDF2 pbkdf2(prf, iterations); pbkdf2.derive_key(out, out_len, password.data(), password.size(), salt, salt_len); @@ -105,10 +108,14 @@ const size_t prf_sz = prf.output_length(); BOTAN_ASSERT_NOMSG(prf_sz > 0); + // RFC 2898 Section 5.2: derived key length limited to (2^32 - 1) * hLen + const auto blocks_required = ceil_division(out_len, prf_sz); + BOTAN_ARG_CHECK(blocks_required <= 0xFFFFFFFE, "PBKDF2 maximum output length exceeded"); + secure_vector U(prf_sz); uint32_t counter = 1; - while(out_len) { + while(out_len > 0) { const size_t prf_output = std::min(prf_sz, out_len); prf.update(salt, salt_len); @@ -137,10 +144,10 @@ size_t iterations, std::chrono::milliseconds msec) const { if(iterations == 0) { - iterations = tune_pbkdf2(*m_mac, key_len, msec); + iterations = tune_pbkdf2(*m_mac, key_len, msec.count()); } - PBKDF2 pbkdf2(*m_mac, iterations); + const PBKDF2 pbkdf2(*m_mac, iterations); pbkdf2.derive_key(key, key_len, password.data(), password.size(), salt, salt_len); @@ -158,7 +165,7 @@ // PasswordHash interface PBKDF2::PBKDF2(const MessageAuthenticationCode& prf, size_t olen, std::chrono::milliseconds msec) : - m_prf(prf.new_object()), m_iterations(tune_pbkdf2(*m_prf, olen, msec)) {} + m_prf(prf.new_object()), m_iterations(tune_pbkdf2(*m_prf, olen, msec.count())) {} std::string PBKDF2::to_string() const { return fmt("PBKDF2({},{})", m_prf->name(), m_iterations); @@ -178,11 +185,11 @@ return fmt("PBKDF2({})", m_prf->name()); } -std::unique_ptr PBKDF2_Family::tune(size_t output_len, - std::chrono::milliseconds msec, - size_t /*max_memory_usage_mb*/, - std::chrono::milliseconds tune_time) const { - auto iterations = tune_pbkdf2(*m_prf, output_len, msec, tune_time); +std::unique_ptr PBKDF2_Family::tune_params(size_t output_len, + uint64_t desired_runtime_msec, + std::optional /*max_memory*/, + uint64_t tune_msec) const { + auto iterations = tune_pbkdf2(*m_prf, output_len, desired_runtime_msec, tune_msec); return std::make_unique(*m_prf, iterations); } diff -Nru botan3-3.7.1+dfsg/src/lib/pbkdf/pbkdf2/pbkdf2.h botan3-3.12.0+dfsg/src/lib/pbkdf/pbkdf2/pbkdf2.h --- botan3-3.7.1+dfsg/src/lib/pbkdf/pbkdf2/pbkdf2.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pbkdf/pbkdf2/pbkdf2.h 2026-05-07 01:38:28.000000000 +0000 @@ -46,7 +46,7 @@ public: PBKDF2(const MessageAuthenticationCode& prf, size_t iter) : m_prf(prf.new_object()), m_iterations(iter) {} - BOTAN_DEPRECATED("For runtime tuning use PBKDF2_Family::tune") + BOTAN_DEPRECATED("For runtime tuning use PBKDF2_Family::tune_params") PBKDF2(const MessageAuthenticationCode& prf, size_t olen, std::chrono::milliseconds msec); size_t iterations() const override { return m_iterations; } @@ -70,14 +70,14 @@ */ class BOTAN_PUBLIC_API(2, 8) PBKDF2_Family final : public PasswordHashFamily { public: - PBKDF2_Family(std::unique_ptr prf) : m_prf(std::move(prf)) {} + BOTAN_FUTURE_EXPLICIT PBKDF2_Family(std::unique_ptr prf) : m_prf(std::move(prf)) {} std::string name() const override; - std::unique_ptr tune(size_t output_len, - std::chrono::milliseconds msec, - size_t max_memory, - std::chrono::milliseconds tune_msec) const override; + std::unique_ptr tune_params(size_t output_len, + uint64_t desired_runtime_msec, + std::optional max_memory, + uint64_t tune_msec) const override; /** * Return some default parameter set for this PBKDF that should be good @@ -88,7 +88,7 @@ std::unique_ptr from_iterations(size_t iter) const override; - std::unique_ptr from_params(size_t iter, size_t, size_t) const override; + std::unique_ptr from_params(size_t iter, size_t /*unused*/, size_t /*unused*/) const override; private: std::unique_ptr m_prf; diff -Nru botan3-3.7.1+dfsg/src/lib/pbkdf/pgp_s2k/pgp_s2k.cpp botan3-3.12.0+dfsg/src/lib/pbkdf/pgp_s2k/pgp_s2k.cpp --- botan3-3.7.1+dfsg/src/lib/pbkdf/pgp_s2k/pgp_s2k.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pbkdf/pgp_s2k/pgp_s2k.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,7 +9,9 @@ #include #include +#include #include +#include #include #include @@ -31,10 +33,10 @@ secure_vector input_buf(salt_len + password_size); if(salt_len > 0) { - copy_mem(&input_buf[0], salt, salt_len); + copy_mem(input_buf.data(), salt, salt_len); } if(password_size > 0) { - copy_mem(&input_buf[salt_len], cast_char_ptr_to_uint8(password), password_size); + copy_mem(std::span(input_buf).subspan(salt_len), as_span_of_bytes(password, password_size)); } secure_vector hash_buf(hash.output_length()); @@ -50,7 +52,7 @@ hash.update(zero_padding); // The input is always fully processed even if iterations is very small - if(input_buf.empty() == false) { + if(!input_buf.empty()) { size_t left = std::max(iterations, input_buf.size()); while(left > 0) { const size_t input_to_take = std::min(left, input_buf.size()); @@ -74,12 +76,10 @@ const uint8_t salt[], size_t salt_len, size_t iterations, - std::chrono::milliseconds msec) const { - std::unique_ptr pwdhash; - + std::chrono::milliseconds desired_msec) const { if(iterations == 0) { - RFC4880_S2K_Family s2k_params(m_hash->new_object()); - iterations = s2k_params.tune(output_len, msec, 0, std::chrono::milliseconds(10))->iterations(); + const RFC4880_S2K_Family s2k_params(m_hash->new_object()); + iterations = s2k_params.tune_params(output_len, desired_msec.count(), {}, 10)->iterations(); } pgp_s2k(*m_hash, output_buf, output_len, password.data(), password.size(), salt, salt_len, iterations); @@ -91,17 +91,17 @@ return fmt("OpenPGP-S2K({})", m_hash->name()); } -std::unique_ptr RFC4880_S2K_Family::tune(size_t output_len, - std::chrono::milliseconds msec, - size_t /*max_memory_usage_mb*/, - std::chrono::milliseconds tune_time) const { +std::unique_ptr RFC4880_S2K_Family::tune_params(size_t output_len, + uint64_t desired_msec, + std::optional /*max_memory*/, + uint64_t tuning_msec) const { constexpr size_t buf_size = 1024; std::vector buffer(buf_size); - const uint64_t measured_nsec = measure_cost(tune_time, [&]() { m_hash->update(buffer); }); + const uint64_t measured_nsec = measure_cost(tuning_msec, [&]() { m_hash->update(buffer); }); const double hash_bytes_per_second = (buf_size * 1000000000.0) / measured_nsec; - const uint64_t desired_nsec = msec.count() * 1000000; + const uint64_t desired_nsec = desired_msec * 1000000; const size_t hash_size = m_hash->output_length(); const size_t blocks_required = (output_len <= hash_size ? 1 : (output_len + hash_size - 1) / hash_size); @@ -112,16 +112,18 @@ return std::make_unique(m_hash->new_object(), iterations); } -std::unique_ptr RFC4880_S2K_Family::from_params(size_t iter, size_t /*i2*/, size_t /*i3*/) const { - return std::make_unique(m_hash->new_object(), iter); +std::unique_ptr RFC4880_S2K_Family::from_params(size_t iterations, + size_t /*unused*/, + size_t /*unused*/) const { + return std::make_unique(m_hash->new_object(), iterations); } std::unique_ptr RFC4880_S2K_Family::default_params() const { return std::make_unique(m_hash->new_object(), 50331648); } -std::unique_ptr RFC4880_S2K_Family::from_iterations(size_t iter) const { - return std::make_unique(m_hash->new_object(), iter); +std::unique_ptr RFC4880_S2K_Family::from_iterations(size_t iterations) const { + return std::make_unique(m_hash->new_object(), iterations); } RFC4880_S2K::RFC4880_S2K(std::unique_ptr hash, size_t iterations) : diff -Nru botan3-3.7.1+dfsg/src/lib/pbkdf/pgp_s2k/pgp_s2k.h botan3-3.12.0+dfsg/src/lib/pbkdf/pgp_s2k/pgp_s2k.h --- botan3-3.7.1+dfsg/src/lib/pbkdf/pgp_s2k/pgp_s2k.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pbkdf/pgp_s2k/pgp_s2k.h 2026-05-07 01:38:28.000000000 +0000 @@ -103,14 +103,14 @@ class BOTAN_PUBLIC_API(2, 8) RFC4880_S2K_Family final : public PasswordHashFamily { public: - RFC4880_S2K_Family(std::unique_ptr hash) : m_hash(std::move(hash)) {} + BOTAN_FUTURE_EXPLICIT RFC4880_S2K_Family(std::unique_ptr hash) : m_hash(std::move(hash)) {} std::string name() const override; - std::unique_ptr tune(size_t output_len, - std::chrono::milliseconds msec, - size_t max_mem, - std::chrono::milliseconds tune_msec) const override; + std::unique_ptr tune_params(size_t output_len, + uint64_t desired_runtime_msec, + std::optional max_memory, + uint64_t tune_msec) const override; /** * Return some default parameter set for this PBKDF that should be good @@ -119,9 +119,9 @@ */ std::unique_ptr default_params() const override; - std::unique_ptr from_iterations(size_t iter) const override; + std::unique_ptr from_iterations(size_t iterations) const override; - std::unique_ptr from_params(size_t iter, size_t, size_t) const override; + std::unique_ptr from_params(size_t iterations, size_t /*unused*/, size_t /*unused*/) const override; private: std::unique_ptr m_hash; diff -Nru botan3-3.7.1+dfsg/src/lib/pbkdf/pwdhash.h botan3-3.12.0+dfsg/src/lib/pbkdf/pwdhash.h --- botan3-3.7.1+dfsg/src/lib/pbkdf/pwdhash.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pbkdf/pwdhash.h 2026-05-07 01:38:28.000000000 +0000 @@ -8,12 +8,16 @@ #define BOTAN_PWDHASH_H_ #include -#include #include +#include #include #include #include +#if !defined(BOTAN_IS_BEING_BUILT) + #include +#endif + namespace Botan { /** @@ -22,7 +26,7 @@ * Converts a password into a key using a salt and iterated hashing to * make brute force attacks harder. */ -class BOTAN_PUBLIC_API(2, 8) PasswordHash { +class BOTAN_PUBLIC_API(2, 8) PasswordHash /* NOLINT(*-special-member-functions) */ { public: virtual ~PasswordHash() = default; @@ -171,7 +175,7 @@ size_t key_len) const; }; -class BOTAN_PUBLIC_API(2, 8) PasswordHashFamily { +class BOTAN_PUBLIC_API(2, 8) PasswordHashFamily /* NOLINT(*-special-member-functions) */ { public: /** * Create an instance based on a name @@ -213,7 +217,39 @@ * The parameters will be selected to use at most @p max_memory_usage_mb * megabytes of memory, or if left as zero any size is allowed. * - * This function works by runing a short tuning loop to estimate the + * This function works by running a short tuning loop to estimate the + * performance of the algorithm, then scaling the parameters appropriately + * to hit the target size. The length of time the tuning loop runs can be + * controlled using the @p tuning_msec parameter. + * + * @param output_length how long the output length will be + * @param desired_runtime_msec the desired execution time in milliseconds + * + * @param max_memory_usage_mb some password hash functions can use a + * tunable amount of memory, in this case max_memory_usage limits the + * amount of RAM the returned parameters will require, in mebibytes (2**20 + * bytes). It may require some small amount above the request. Set to nullopt + * to place no limit at all. + * @param tuning_msec how long to run the tuning loop + */ + virtual std::unique_ptr tune_params(size_t output_length, + uint64_t desired_runtime_msec, + std::optional max_memory_usage_mb = {}, + uint64_t tuning_msec = 10) const = 0; + +#if !defined(BOTAN_IS_BEING_BUILT) + /** + * Return a new parameter set tuned for this machine + * + * Return a password hash instance tuned to run for approximately @p msec + * milliseconds when producing an output of length @p output_length. + * (Accuracy may vary, use the command line utility ``botan pbkdf_tune`` to + * check.) + * + * The parameters will be selected to use at most @p max_memory_usage_mb + * megabytes of memory, or if left as zero any size is allowed. + * + * This function works by running a short tuning loop to estimate the * performance of the algorithm, then scaling the parameters appropriately * to hit the target size. The length of time the tuning loop runs can be * controlled using the @p tuning_msec parameter. @@ -227,13 +263,25 @@ * bytes). It may require some small amount above the request. Set to zero * to place no limit at all. * @param tuning_msec how long to run the tuning loop + * + * TODO(Botan4) remove this */ - virtual std::unique_ptr tune( - size_t output_length, - std::chrono::milliseconds msec, - size_t max_memory_usage_mb = 0, - std::chrono::milliseconds tuning_msec = std::chrono::milliseconds(10)) const = 0; - + BOTAN_DEPRECATED("Use tune_params instead") + std::unique_ptr tune(size_t output_length, + std::chrono::milliseconds msec, + size_t max_memory_usage_mb = 0, + std::chrono::milliseconds tuning_msec = std::chrono::milliseconds(10)) const { + std::optional max_memory_opt; + if(max_memory_usage_mb > 0) { + max_memory_opt = max_memory_usage_mb; + } + + return this->tune_params(output_length, + static_cast(msec.count()), + max_memory_opt, + static_cast(tuning_msec.count())); + } +#endif /** * Return some default parameter set for this PBKDF that should be good * enough for most users. The value returned may change over time as diff -Nru botan3-3.7.1+dfsg/src/lib/pbkdf/scrypt/scrypt.cpp botan3-3.12.0+dfsg/src/lib/pbkdf/scrypt/scrypt.cpp --- botan3-3.7.1+dfsg/src/lib/pbkdf/scrypt/scrypt.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pbkdf/scrypt/scrypt.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -33,17 +34,15 @@ return std::make_unique(32768, 8, 1); } -std::unique_ptr Scrypt_Family::tune(size_t output_length, - std::chrono::milliseconds msec, - size_t max_memory_usage_mb, - std::chrono::milliseconds tune_time) const { - BOTAN_UNUSED(output_length); - +std::unique_ptr Scrypt_Family::tune_params(size_t /*output_length*/, + uint64_t desired_msec, + std::optional max_memory, + uint64_t tuning_msec) const { /* * Some rough relations between scrypt parameters and runtime. * Denote here by stime(N,r,p) the msec it takes to run scrypt. * - * Emperically for smaller sizes: + * Empirically for smaller sizes: * stime(N,8*r,p) / stime(N,r,p) is ~ 6-7 * stime(N,r,8*p) / stime(N,r,8*p) is ~ 7 * stime(2*N,r,p) / stime(N,r,p) is ~ 2 @@ -51,8 +50,8 @@ * Compute stime(8192,1,1) as baseline and extrapolate */ - // This is zero if max_memory_usage_mb == 0 (unbounded) - const size_t max_memory_usage = max_memory_usage_mb * 1024 * 1024; + // If max_memory is nullopt or zero this becomes zero and is ignored + const size_t max_memory_bytes = max_memory.value_or(0) * 1024 * 1024; // Starting parameters size_t N = 8 * 1024; @@ -61,12 +60,12 @@ auto pwdhash = this->from_params(N, r, p); - const uint64_t measured_time = measure_cost(tune_time, [&]() { + const uint64_t measured_time = measure_cost(tuning_msec, [&]() { uint8_t output[32] = {0}; pwdhash->derive_key(output, sizeof(output), "test", 4, nullptr, 0); }); - const uint64_t target_nsec = msec.count() * static_cast(1000000); + const uint64_t target_nsec = desired_msec * static_cast(1000000); uint64_t est_nsec = measured_time; @@ -75,7 +74,7 @@ // Including p leads to using an N half as large as what the user would expect. // First increase r by 8x if possible - if(max_memory_usage == 0 || scrypt_memory_usage(N, r * 8, 0) <= max_memory_usage) { + if(max_memory_bytes == 0 || scrypt_memory_usage(N, r * 8, 0) <= max_memory_bytes) { if(target_nsec / est_nsec >= 5) { r *= 8; est_nsec *= 5; @@ -83,7 +82,7 @@ } // Now double N as many times as we can - while(max_memory_usage == 0 || scrypt_memory_usage(N * 2, r, 0) <= max_memory_usage) { + while(max_memory_bytes == 0 || scrypt_memory_usage(N * 2, r, 0) <= max_memory_bytes) { if(target_nsec / est_nsec >= 2) { N *= 2; est_nsec *= 2; @@ -198,6 +197,10 @@ size_t password_len, const uint8_t salt[], size_t salt_len) const { + if(output_len == 0) { + return; + } + const size_t N = memory_param(); const size_t p = parallelism(); const size_t r = iterations(); @@ -210,7 +213,7 @@ auto hmac_sha256 = MessageAuthenticationCode::create_or_throw("HMAC(SHA-256)"); try { - hmac_sha256->set_key(cast_char_ptr_to_uint8(password), password_len); + hmac_sha256->set_key(as_span_of_bytes(password, password_len)); } catch(Invalid_Key_Length&) { throw Invalid_Argument("Scrypt cannot accept passphrases of the provided length"); } diff -Nru botan3-3.7.1+dfsg/src/lib/pbkdf/scrypt/scrypt.h botan3-3.12.0+dfsg/src/lib/pbkdf/scrypt/scrypt.h --- botan3-3.7.1+dfsg/src/lib/pbkdf/scrypt/scrypt.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pbkdf/scrypt/scrypt.h 2026-05-07 01:38:28.000000000 +0000 @@ -22,9 +22,6 @@ public: Scrypt(size_t N, size_t r, size_t p); - Scrypt(const Scrypt& other) = default; - Scrypt& operator=(const Scrypt&) = default; - /** * Derive a new key under the current Scrypt parameter set */ @@ -53,10 +50,10 @@ public: std::string name() const override; - std::unique_ptr tune(size_t output_length, - std::chrono::milliseconds msec, - size_t max_memory, - std::chrono::milliseconds tune_msec) const override; + std::unique_ptr tune_params(size_t output_len, + uint64_t desired_runtime_msec, + std::optional max_memory, + uint64_t tune_msec) const override; std::unique_ptr default_params() const override; diff -Nru botan3-3.7.1+dfsg/src/lib/permutations/ascon_perm/ascon_perm.cpp botan3-3.12.0+dfsg/src/lib/permutations/ascon_perm/ascon_perm.cpp --- botan3-3.7.1+dfsg/src/lib/permutations/ascon_perm/ascon_perm.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/permutations/ascon_perm/ascon_perm.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,115 @@ +/* +* Permutation Ascon_p[rounds] as specified in NIST SP.800-232, Section 3 +* (C) 2025 Jack Lloyd +* 2025 René Meusel +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include + +namespace Botan { + +void Ascon_p::absorb(std::span input, std::optional permutation_rounds) { + const auto rounds = permutation_rounds.value_or(m_processing_rounds); + absorb_into_sponge(*this, input, [this, rounds] { permute(rounds); }); +} + +void Ascon_p::squeeze(std::span output) { + squeeze_from_sponge(*this, output); +} + +void Ascon_p::percolate_in(std::span data) { + BufferSlicer input_slicer(data); + BufferStuffer output_stuffer(data); + + process_bytes_in_sponge(*this, data.size(), [&](uint64_t state_word, auto bounds) { + state_word ^= bounds.read_from(input_slicer); + bounds.write_into(output_stuffer, state_word); + return state_word; + }); + + BOTAN_ASSERT_NOMSG(input_slicer.empty()); + BOTAN_ASSERT_NOMSG(output_stuffer.full()); +} + +void Ascon_p::percolate_out(std::span data) { + BufferSlicer input_slicer(data); + BufferStuffer output_stuffer(data); + + process_bytes_in_sponge(*this, data.size(), [&](uint64_t state_word, auto bounds) { + const auto input_word = bounds.read_from(input_slicer); + bounds.write_into(output_stuffer, state_word ^ input_word); + return bounds.masked_assignment(state_word, input_word); + }); + + BOTAN_ASSERT_NOMSG(input_slicer.empty()); + BOTAN_ASSERT_NOMSG(output_stuffer.full()); +} + +void Ascon_p::finish(uint8_t rounds) { + // NIST SP.800-232, Section 2.1 (Algorithm 2 "pad()") + + // The padding is defined as: + // 1. The first padding bit is set to 1 + // 2. The remaining bits are set to 0 + constexpr std::array padding{0x01}; + + // We must always add a padded final input block, if the last verbatim + // input block aligned with the byte rate, the final block may be just + // padding bytes, otherwise the final block is padded as needed. + + absorb(std::span{padding}.first(byte_rate() - cursor()), rounds); + BOTAN_ASSERT_NOMSG(cursor() == 0); +} + +void Ascon_p::permute(uint8_t rounds) { + BOTAN_DEBUG_ASSERT(rounds <= 16); + + auto& S = state(); + + // NIST SP.800-232, Table 5 + constexpr std::array round_constants = { + 0x3c, 0x2d, 0x1e, 0x0f, 0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b}; + + for(uint8_t i = 0; i < rounds; ++i) { + // Constant addition layer p_C + // NIST SP.800-232, Section 3.2 + S[2] ^= round_constants[16 - rounds + i]; + + // Substitution layer p_S + // NIST SP.800-232, Section 3.3, most notably Figure 3 + S[0] ^= S[4]; + S[4] ^= S[3]; + S[2] ^= S[1]; + auto tmp = S; + tmp[0] = ~tmp[0] & S[1]; + tmp[1] = ~tmp[1] & S[2]; + tmp[2] = ~tmp[2] & S[3]; + tmp[3] = ~tmp[3] & S[4]; + tmp[4] = ~tmp[4] & S[0]; + S[0] ^= tmp[1]; + S[1] ^= tmp[2]; + S[2] ^= tmp[3]; + S[3] ^= tmp[4]; + S[4] ^= tmp[0]; + S[1] ^= S[0]; + S[0] ^= S[4]; + S[3] ^= S[2]; + S[2] = ~S[2]; + + // Linear diffusion layer p_L + // NIST SP.800-232, Section 3.4 + S[0] = S[0] ^ rotr<19>(S[0]) ^ rotr<28>(S[0]); + S[1] = S[1] ^ rotr<61>(S[1]) ^ rotr<39>(S[1]); + S[2] = S[2] ^ rotr<1>(S[2]) ^ rotr<6>(S[2]); + S[3] = S[3] ^ rotr<10>(S[3]) ^ rotr<17>(S[3]); + S[4] = S[4] ^ rotr<7>(S[4]) ^ rotr<41>(S[4]); + } +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/permutations/ascon_perm/ascon_perm.h botan3-3.12.0+dfsg/src/lib/permutations/ascon_perm/ascon_perm.h --- botan3-3.7.1+dfsg/src/lib/permutations/ascon_perm/ascon_perm.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/permutations/ascon_perm/ascon_perm.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,74 @@ +/* +* Permutation Ascon_p[rounds] as specified in NIST SP.800-232, Section 3 +* (C) 2025 Jack Lloyd +* 2025 René Meusel +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_ASCON_PERM_H_ +#define BOTAN_ASCON_PERM_H_ + +#include +#include +#include +#include + +namespace Botan { + +/** + * Ascon_p as specified in NIST SP.800-232, Section 3 + */ +class Ascon_p final : public Sponge<5, uint64_t> { + public: + struct Config { + uint8_t init_and_final_rounds; + uint8_t processing_rounds; + uint8_t bit_rate; + state_t initial_state; + }; + + public: + consteval explicit Ascon_p(Config config) : + Sponge({config.bit_rate, config.initial_state}), + m_init_final_rounds(config.init_and_final_rounds), + m_processing_rounds(config.processing_rounds) { + BOTAN_ARG_CHECK(m_init_final_rounds > 0 && m_init_final_rounds <= 16, + "Invalid Ascon initialization/finalization rounds"); + + BOTAN_ARG_CHECK(m_processing_rounds > 0 && m_processing_rounds <= 16, "Invalid Ascon processing rounds"); + } + + std::string provider() const { return "base"; } + + void absorb(std::span input, std::optional permutation_rounds = std::nullopt); + void squeeze(std::span output); + void percolate_in(std::span data); + void percolate_out(std::span data); + + void finish() { finish(m_init_final_rounds); } + + void intermediate_finish() { finish(m_processing_rounds); } + + void permute() { permute(m_processing_rounds); } + + void initial_permute() { permute(m_init_final_rounds); } + + template + requires(offset + count <= state_bytes()) + constexpr auto range_of_state() { + return std::span{state()}.template subspan(); + } + + private: + void finish(uint8_t rounds); + void permute(uint8_t rounds); + + private: + uint8_t m_init_final_rounds; + uint8_t m_processing_rounds; +}; + +} // namespace Botan + +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/permutations/ascon_perm/info.txt botan3-3.12.0+dfsg/src/lib/permutations/ascon_perm/info.txt --- botan3-3.7.1+dfsg/src/lib/permutations/ascon_perm/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/permutations/ascon_perm/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,8 @@ + +name -> "Ascon-permutation" +type -> "Internal" + + + +sponge + diff -Nru botan3-3.7.1+dfsg/src/lib/permutations/keccak_perm/info.txt botan3-3.12.0+dfsg/src/lib/permutations/keccak_perm/info.txt --- botan3-3.7.1+dfsg/src/lib/permutations/keccak_perm/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/permutations/keccak_perm/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,7 +1,11 @@ - + KECCAK_PERM -> 20230613 - + name -> "Keccak-permutation" + + +sponge + diff -Nru botan3-3.7.1+dfsg/src/lib/permutations/keccak_perm/keccak_helpers.cpp botan3-3.12.0+dfsg/src/lib/permutations/keccak_perm/keccak_helpers.cpp --- botan3-3.7.1+dfsg/src/lib/permutations/keccak_perm/keccak_helpers.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/permutations/keccak_perm/keccak_helpers.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -24,14 +24,15 @@ uint8_t encode(std::span out, uint64_t x) { const auto bytes_needed = int_encoding_size(x); + BOTAN_ASSERT_NOMSG(sizeof(x) >= bytes_needed); BOTAN_ASSERT_NOMSG(out.size() >= bytes_needed); - std::array bigendian_x; + const size_t leading_zeros = sizeof(x) - bytes_needed; + + std::array bigendian_x{}; store_be(x, bigendian_x.data()); - auto begin = bigendian_x.begin(); - std::advance(begin, sizeof(x) - bytes_needed); - std::copy(begin, bigendian_x.end(), out.begin()); + std::copy(bigendian_x.begin() + leading_zeros, bigendian_x.end(), out.begin()); return static_cast(bytes_needed); } diff -Nru botan3-3.7.1+dfsg/src/lib/permutations/keccak_perm/keccak_helpers.h botan3-3.12.0+dfsg/src/lib/permutations/keccak_perm/keccak_helpers.h --- botan3-3.7.1+dfsg/src/lib/permutations/keccak_perm/keccak_helpers.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/permutations/keccak_perm/keccak_helpers.h 2026-05-07 01:38:28.000000000 +0000 @@ -78,7 +78,7 @@ /** * This is a combination of the functions encode_string() and bytepad() defined * in NIST SP.800-185 Section 2.3. Additionally, the result is directly streamed - * into the provided XOF to avoid unneccessary memory allocation or a byte vector. + * into the provided XOF to avoid unnecessary memory allocation or a byte vector. * * @param sink the XOF or byte vector to absorb the @p byte_strings into * @param padding_mod the modulus value to create a padding for (NIST calls this 'w') @@ -92,7 +92,7 @@ BOTAN_ASSERT_NOMSG(padding_mod > 0); // used as temporary storage for all integer encodings in this function - std::array int_encoding_buffer; + std::array int_encoding_buffer{}; // absorbs byte strings and counts the number of absorbed bytes size_t bytes_absorbed = 0; diff -Nru botan3-3.7.1+dfsg/src/lib/permutations/keccak_perm/keccak_perm.cpp botan3-3.12.0+dfsg/src/lib/permutations/keccak_perm/keccak_perm.cpp --- botan3-3.7.1+dfsg/src/lib/permutations/keccak_perm/keccak_perm.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/permutations/keccak_perm/keccak_perm.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -2,37 +2,32 @@ * Keccak Permutation * (C) 2010,2016 Jack Lloyd * (C) 2023 Falko Strenzke -* (C) 2023 René Meusel - Rohde & Schwarz Cybersecurity +* (C) 2023,2025 René Meusel - Rohde & Schwarz Cybersecurity * * Botan is released under the Simplified BSD License (see license.txt) */ #include -#include -#include -#include #include -#include -#include +#include -namespace Botan { +#if defined(BOTAN_HAS_CPUID) + #include +#endif -Keccak_Permutation::Keccak_Permutation(size_t capacity, uint64_t custom_padding, uint8_t custom_padding_bit_len) : - m_capacity(capacity), - m_byterate((1600 - capacity) / 8), - m_custom_padding(custom_padding), - m_custom_padding_bit_len(custom_padding_bit_len), - m_S(25), // 1600 bit - m_S_inpos(0), - m_S_outpos(0) { - BOTAN_ARG_CHECK(capacity % 64 == 0, "capacity must be a multiple of 64"); -} +namespace Botan { std::string Keccak_Permutation::provider() const { +#if defined(BOTAN_HAS_KECCAK_PERM_AVX512) + if(auto feat = CPUID::check(CPUID::Feature::AVX512)) { + return *feat; + } +#endif + #if defined(BOTAN_HAS_KECCAK_PERM_BMI2) - if(CPUID::has_bmi2()) { - return "bmi2"; + if(auto feat = CPUID::check(CPUID::Feature::BMI)) { + return *feat; } #endif @@ -40,89 +35,61 @@ } void Keccak_Permutation::clear() { - zeroise(m_S); - m_S_inpos = 0; - m_S_outpos = 0; + state() = {}; + reset_cursor(); } void Keccak_Permutation::absorb(std::span input) { - BufferSlicer input_slicer(input); - - // Block-wise incorporation of the input data into the sponge state until - // all input bytes are processed - while(!input_slicer.empty()) { - const size_t to_take_this_round = std::min(input_slicer.remaining(), m_byterate - m_S_inpos); - BufferSlicer input_this_round(input_slicer.take(to_take_this_round)); - - // If necessary, try to get aligned with the sponge state's 64-bit integer array - for(; !input_this_round.empty() && m_S_inpos % 8; ++m_S_inpos) { - m_S[m_S_inpos / 8] ^= static_cast(input_this_round.take_byte()) << (8 * (m_S_inpos % 8)); - } - - // Process as many aligned 64-bit integer values as possible - for(; input_this_round.remaining() >= 8; m_S_inpos += 8) { - m_S[m_S_inpos / 8] ^= load_le(input_this_round.take(8).data(), 0); - } - - // Read remaining output data, causing misalignment, if necessary - for(; !input_this_round.empty(); ++m_S_inpos) { - m_S[m_S_inpos / 8] ^= static_cast(input_this_round.take_byte()) << (8 * (m_S_inpos % 8)); - } - - // We reached the end of a sponge state block... permute() and start over - if(m_S_inpos == m_byterate) { - permute(); - m_S_inpos = 0; - } - } + absorb_into_sponge(*this, input); } void Keccak_Permutation::squeeze(std::span output) { - BufferStuffer output_stuffer(output); - - // Block-wise readout of the sponge state until enough bytes - // were filled into the output buffer - while(!output_stuffer.full()) { - const size_t bytes_in_this_round = std::min(output_stuffer.remaining_capacity(), m_byterate - m_S_outpos); - BufferStuffer output_this_round(output_stuffer.next(bytes_in_this_round)); - - // If necessary, try to get aligned with the sponge state's 64-bit integer array - for(; !output_this_round.full() && m_S_outpos % 8 != 0; ++m_S_outpos) { - output_this_round.next_byte() = static_cast(m_S[m_S_outpos / 8] >> (8 * (m_S_outpos % 8))); - } - - // Read out as many aligned 64-bit integer values as possible - for(; output_this_round.remaining_capacity() >= 8; m_S_outpos += 8) { - store_le(m_S[m_S_outpos / 8], output_this_round.next(8).data()); - } - - // Read remaining output data, causing misalignment, if necessary - for(; !output_this_round.full(); ++m_S_outpos) { - output_this_round.next_byte() = static_cast(m_S[m_S_outpos / 8] >> (8 * (m_S_outpos % 8))); - } - - // We reached the end of a sponge state block... permute() and start over - if(m_S_outpos == m_byterate) { - permute(); - m_S_outpos = 0; - } - } + squeeze_from_sponge(*this, output); } void Keccak_Permutation::finish() { - // append the first bit of the final padding after the custom padding - uint8_t init_pad = static_cast(m_custom_padding | uint64_t(1) << m_custom_padding_bit_len); - m_S[m_S_inpos / 8] ^= static_cast(init_pad) << (8 * (m_S_inpos % 8)); + // The padding for Keccak[c]-based functions spans the entire remaining + // byterate until the next permute() call. At most that could be an entire + // byterate. First are a few bits of "custom" padding defined by the using + // function (e.g. SHA-3 uses "01"), then the remaining space is filled with + // "pad10*1" (see NIST FIPS 202 Section 5.1) followed by a final permute(). + + auto& S = state(); + + // Apply the custom padding + the left-most 1-bit of "pad10*1" to the current + // (partial) word of the sponge state + + const uint64_t start_of_padding = (m_padding.padding | uint64_t(1) << m_padding.bit_len); + S[cursor() / word_bytes] ^= start_of_padding << (8 * (cursor() % word_bytes)); + + // XOR'ing the 0-bits of "pad10*1" into the state is a NOOP - // final bit of the padding of the last block - m_S[(m_byterate / 8) - 1] ^= static_cast(0x80) << 56; + // If the custom padding + the left-most 1-bit of "pad10*1" had resulted in a + // byte-aligned "partial padding", the final 1-bit of of "pad10*1" could + // potentially override parts of the already-appended "start_of_padding". + // In case we ever introduce a Keccak-based function with such a need, we + // have to modify this padding algorithm. + BOTAN_DEBUG_ASSERT(m_padding.bit_len % 8 != 7); + // Append the final bit of "pad10*1" into the last word of the input range + S[(byte_rate() / word_bytes) - 1] ^= uint64_t(0x8000000000000000); + + // Perform the final permutation and reset the state cursor permute(); + reset_cursor(); + + BOTAN_DEBUG_ASSERT(cursor() == 0); } void Keccak_Permutation::permute() { +#if defined(BOTAN_HAS_KECCAK_PERM_AVX512) + if(CPUID::has(CPUID::Feature::AVX512)) { + return permute_avx512(); + } +#endif + #if defined(BOTAN_HAS_KECCAK_PERM_BMI2) - if(CPUID::has_bmi2()) { + if(CPUID::has(CPUID::Feature::BMI)) { return permute_bmi2(); } #endif @@ -137,8 +104,8 @@ uint64_t T[25]; for(size_t i = 0; i != 24; i += 2) { - Keccak_Permutation_round(T, m_S.data(), RC[i + 0]); - Keccak_Permutation_round(m_S.data(), T, RC[i + 1]); + Keccak_Permutation_round(T, state().data(), RC[i + 0]); + Keccak_Permutation_round(state().data(), T, RC[i + 1]); } } diff -Nru botan3-3.7.1+dfsg/src/lib/permutations/keccak_perm/keccak_perm.h botan3-3.12.0+dfsg/src/lib/permutations/keccak_perm/keccak_perm.h --- botan3-3.7.1+dfsg/src/lib/permutations/keccak_perm/keccak_perm.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/permutations/keccak_perm/keccak_perm.h 2026-05-07 01:38:28.000000000 +0000 @@ -2,7 +2,7 @@ * Keccak Permutation * (C) 2010,2016 Jack Lloyd * (C) 2023 Falko Strenzke -* (C) 2023 René Meusel - Rohde & Schwarz Cybersecurity +* (C) 2023,2025 René Meusel - Rohde & Schwarz Cybersecurity * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -10,12 +10,29 @@ #ifndef BOTAN_KECCAK_PERM_H_ #define BOTAN_KECCAK_PERM_H_ -#include +#include #include #include namespace Botan { +struct KeccakPadding { + uint64_t padding; /// The padding bits in little-endian order + uint8_t bit_len; /// The number of relevant bits in 'padding' + + /// NIST FIPS 202 Section 6.1 + static constexpr KeccakPadding sha3() { return {.padding = 0b10 /* little-endian */, .bit_len = 2}; } + + /// NIST FIPS 202 Section 6.2 + static constexpr KeccakPadding shake() { return {.padding = 0b1111, .bit_len = 4}; } + + /// NIST SP.800-185 Section 3.3 + static constexpr KeccakPadding cshake() { return {.padding = 0b00, .bit_len = 2}; } + + /// Keccak submission, prior to the introduction of an algorithm specific padding + static constexpr KeccakPadding keccak1600() { return {.padding = 0, .bit_len = 0}; } +}; + /** * KECCAK FIPS * @@ -35,24 +52,21 @@ * https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf#page=28 * [2] https://csrc.nist.gov/projects/hash-functions/sha-3-project */ -class Keccak_Permutation final { +class Keccak_Permutation final : public Sponge<25, uint64_t> { + public: + struct Config { + size_t capacity_bits; + KeccakPadding padding; + }; + public: /** * @brief Instantiate a Keccak permutation * - * The @p custom_padding is assumed to be init_pad || 00... || fini_pad - * - * @param capacity_bits Keccak capacity - * @param custom_padding the custom bit padding that is to be appended on the call to finish - * @param custom_padding_bit_len the bit length of the custom_padd + * @param config Keccak parameter configuration */ - Keccak_Permutation(size_t capacity_bits, uint64_t custom_padding, uint8_t custom_padding_bit_len); - - size_t capacity() const { return m_capacity; } - - size_t bit_rate() const { return m_byterate * 8; } - - size_t byte_rate() const { return m_byterate; } + constexpr explicit Keccak_Permutation(Config config) : + Sponge({.bit_rate = state_bits() - config.capacity_bits, .initial_state = {}}), m_padding(config.padding) {} void clear(); std::string provider() const; @@ -80,21 +94,22 @@ */ void finish(); - private: + /** + * The Keccak permutation function + */ void permute(); + private: #if defined(BOTAN_HAS_KECCAK_PERM_BMI2) void permute_bmi2(); #endif +#if defined(BOTAN_HAS_KECCAK_PERM_AVX512) + void permute_avx512(); +#endif + private: - const size_t m_capacity; - const size_t m_byterate; - const uint64_t m_custom_padding; - const uint8_t m_custom_padding_bit_len; - secure_vector m_S; - uint8_t m_S_inpos; - uint8_t m_S_outpos; + KeccakPadding m_padding; }; } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/permutations/keccak_perm/keccak_perm_avx512/info.txt botan3-3.12.0+dfsg/src/lib/permutations/keccak_perm/keccak_perm_avx512/info.txt --- botan3-3.7.1+dfsg/src/lib/permutations/keccak_perm/keccak_perm_avx512/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/permutations/keccak_perm/keccak_perm_avx512/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,15 @@ + +KECCAK_PERM_AVX512 -> 20250524 + + + +name -> "Keccak permutation using AVX512" + + + +avx512 + + + +cpuid + diff -Nru botan3-3.7.1+dfsg/src/lib/permutations/keccak_perm/keccak_perm_avx512/keccak_perm_avx512.cpp botan3-3.12.0+dfsg/src/lib/permutations/keccak_perm/keccak_perm_avx512/keccak_perm_avx512.cpp --- botan3-3.7.1+dfsg/src/lib/permutations/keccak_perm/keccak_perm_avx512/keccak_perm_avx512.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/permutations/keccak_perm/keccak_perm_avx512/keccak_perm_avx512.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,153 @@ +/* +* (C) 2025 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include + +namespace Botan { + +namespace { + +class SIMD_5x64 final { + public: + explicit BOTAN_FN_ISA_AVX512 SIMD_5x64() : SIMD_5x64(_mm512_setzero_si512()) {} + + static BOTAN_FN_ISA_AVX512 SIMD_5x64 rc(uint64_t RC) { + return SIMD_5x64(_mm512_maskz_set1_epi64(0b00000001, RC)); + } + + static BOTAN_FN_ISA_AVX512 SIMD_5x64 load(const uint64_t v[5]) { + return SIMD_5x64(_mm512_maskz_loadu_epi64(0b00011111, v)); + } + + template + inline BOTAN_FN_ISA_AVX512 SIMD_5x64 permute() const { + static_assert(I0 < 5 && I1 < 5 && I2 < 5 && I3 < 5 && I4 < 5); + const __m512i tbl = _mm512_setr_epi64(I0, I1, I2, I3, I4, 0, 0, 0); + return SIMD_5x64(_mm512_permutexvar_epi64(tbl, m_v)); + } + + static BOTAN_FN_ISA_AVX512 void transpose5( + SIMD_5x64& i0, SIMD_5x64& i1, SIMD_5x64& i2, SIMD_5x64& i3, SIMD_5x64& i4) { + // 5x5 u64 transpose using 7 permutex2var, 4 unpack, 1 blend, 5 constants + + const auto lo_01 = _mm512_unpacklo_epi64(i0.m_v, i1.m_v); + const auto lo_23 = _mm512_unpacklo_epi64(i2.m_v, i3.m_v); + + const auto hi_01 = _mm512_unpackhi_epi64(i0.m_v, i1.m_v); + const auto hi_23 = _mm512_unpackhi_epi64(i2.m_v, i3.m_v); + + // Insert the relevant words from i4 into the i0/i1 data + const auto i4_lo_idx = _mm512_setr_epi64(0, 1, 2, 3, 4, 5, 8, 10); + const auto i4_hi_idx = _mm512_setr_epi64(0, 1, 2, 3, -1, -1, 9, 11); + + auto t0 = _mm512_permutex2var_epi64(lo_01, i4_lo_idx, i4.m_v); + auto t2 = _mm512_permutex2var_epi64(hi_01, i4_hi_idx, i4.m_v); + + // Now merge the 0/1/4 and 2/3 vectors using permutes + const auto idx0 = _mm512_setr_epi64(0, 1, 8, 9, 6, -1, -1, -1); + const auto idx1 = _mm512_setr_epi64(2, 3, 10, 11, 7, -1, -1, -1); + const auto idx4 = _mm512_setr_epi64(4, 5, 12, 13, -1, -1, -1, -1); + + i0.m_v = _mm512_permutex2var_epi64(t0, idx0, lo_23); + i1.m_v = _mm512_permutex2var_epi64(t2, idx0, hi_23); + i2.m_v = _mm512_permutex2var_epi64(t0, idx1, lo_23); + i3.m_v = _mm512_permutex2var_epi64(t2, idx1, hi_23); + i4.m_v = _mm512_mask_blend_epi64(0b00010000, _mm512_permutex2var_epi64(t0, idx4, lo_23), i4.m_v); + } + + static BOTAN_FN_ISA_AVX512 SIMD_5x64 chi(const SIMD_5x64& x, const SIMD_5x64& y, const SIMD_5x64& z) { + constexpr uint8_t xor_not_and = 0b11010010; // (x ^ (~y & z)) + return SIMD_5x64(_mm512_ternarylogic_epi64(x.m_v, y.m_v, z.m_v, xor_not_and)); + } + + friend BOTAN_FN_ISA_AVX512 SIMD_5x64 operator^(const SIMD_5x64& x, const SIMD_5x64& y) { + return SIMD_5x64(_mm512_xor_epi64(x.m_v, y.m_v)); + } + + static BOTAN_FN_ISA_AVX512 SIMD_5x64 + xor5(const SIMD_5x64& i0, const SIMD_5x64& i1, const SIMD_5x64& i2, const SIMD_5x64& i3, const SIMD_5x64& i4) { + constexpr uint8_t tern_xor = 0b10010110; + auto t = _mm512_ternarylogic_epi64(i0.m_v, i1.m_v, i2.m_v, tern_xor); + return SIMD_5x64(_mm512_ternarylogic_epi64(i3.m_v, i4.m_v, t, tern_xor)); + } + + BOTAN_FN_ISA_AVX512 SIMD_5x64 rol1() const { return SIMD_5x64(_mm512_rol_epi64(m_v, 1)); } + + template + BOTAN_FN_ISA_AVX512 SIMD_5x64 rolv() const { + static_assert(R0 < 64 && R1 < 64 && R2 < 64 && R3 < 64 && R4 < 64); + const __m512i rot = _mm512_setr_epi64(R0, R1, R2, R3, R4, 0, 0, 0); + return SIMD_5x64(_mm512_rolv_epi64(m_v, rot)); + } + + BOTAN_FN_ISA_AVX512 void store(uint64_t v[5]) const { _mm512_mask_storeu_epi64(v, 0b00011111, m_v); } + + private: + explicit BOTAN_FN_ISA_AVX512 SIMD_5x64(__m512i v) : m_v(v) {} + + __m512i m_v; +}; + +inline void BOTAN_FN_ISA_AVX512 Keccak_Permutation_round_avx512(SIMD_5x64 A[5], uint64_t RC) { + const auto C = SIMD_5x64::xor5(A[0], A[1], A[2], A[3], A[4]); + + const auto D = C.permute<4, 0, 1, 2, 3>() ^ C.permute<1, 2, 3, 4, 0>().rol1(); + + const auto B0 = (A[0] ^ D).permute<0, 3, 1, 4, 2>().rolv<0, 28, 1, 27, 62>(); + const auto B1 = (A[1] ^ D).permute<1, 4, 2, 0, 3>().rolv<44, 20, 6, 36, 55>(); + const auto B2 = (A[2] ^ D).permute<2, 0, 3, 1, 4>().rolv<43, 3, 25, 10, 39>(); + const auto B3 = (A[3] ^ D).permute<3, 1, 4, 2, 0>().rolv<21, 45, 8, 15, 41>(); + const auto B4 = (A[4] ^ D).permute<4, 2, 0, 3, 1>().rolv<14, 61, 18, 56, 2>(); + + auto T0 = SIMD_5x64::chi(B0, B1, B2) ^ SIMD_5x64::rc(RC); + auto T1 = SIMD_5x64::chi(B1, B2, B3); + auto T2 = SIMD_5x64::chi(B2, B3, B4); + auto T3 = SIMD_5x64::chi(B3, B4, B0); + auto T4 = SIMD_5x64::chi(B4, B0, B1); + + SIMD_5x64::transpose5(T0, T1, T2, T3, T4); + + A[0] = T0; + A[1] = T1; + A[2] = T2; + A[3] = T3; + A[4] = T4; +} + +} // namespace + +void BOTAN_FN_ISA_AVX512 Keccak_Permutation::permute_avx512() { + static const uint64_t RC[24] = {0x0000000000000001, 0x0000000000008082, 0x800000000000808A, 0x8000000080008000, + 0x000000000000808B, 0x0000000080000001, 0x8000000080008081, 0x8000000000008009, + 0x000000000000008A, 0x0000000000000088, 0x0000000080008009, 0x000000008000000A, + 0x000000008000808B, 0x800000000000008B, 0x8000000000008089, 0x8000000000008003, + 0x8000000000008002, 0x8000000000000080, 0x000000000000800A, 0x800000008000000A, + 0x8000000080008081, 0x8000000000008080, 0x0000000080000001, 0x8000000080008008}; + + auto& S = state(); + + std::array X{ + SIMD_5x64::load(&S[0]), // NOLINT(*container-data-pointer) + SIMD_5x64::load(&S[5]), + SIMD_5x64::load(&S[10]), + SIMD_5x64::load(&S[15]), + SIMD_5x64::load(&S[20]), + }; + + // NOLINTNEXTLINE(modernize-loop-convert) + for(size_t i = 0; i != 24; ++i) { + Keccak_Permutation_round_avx512(X.data(), RC[i]); + } + + for(size_t i = 0; i != 5; ++i) { + X[i].store(&S[5 * i]); + } +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/permutations/keccak_perm/keccak_perm_bmi2/info.txt botan3-3.12.0+dfsg/src/lib/permutations/keccak_perm/keccak_perm_bmi2/info.txt --- botan3-3.7.1+dfsg/src/lib/permutations/keccak_perm/keccak_perm_bmi2/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/permutations/keccak_perm/keccak_perm_bmi2/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + KECCAK_PERM_BMI2 -> 20230612 - + name -> "KECCAK-permutation BMI2" @@ -15,3 +15,13 @@ x86_64 + + +cpuid + + +# It doesn't make sense to use this on MSVC since it doesn't +# have any way of enabling BMI2 codegen + +!msvc + diff -Nru botan3-3.7.1+dfsg/src/lib/permutations/keccak_perm/keccak_perm_bmi2/keccak_perm_bmi2.cpp botan3-3.12.0+dfsg/src/lib/permutations/keccak_perm/keccak_perm_bmi2/keccak_perm_bmi2.cpp --- botan3-3.7.1+dfsg/src/lib/permutations/keccak_perm/keccak_perm_bmi2/keccak_perm_bmi2.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/permutations/keccak_perm/keccak_perm_bmi2/keccak_perm_bmi2.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,11 +7,12 @@ #include +#include #include namespace Botan { -void Keccak_Permutation::permute_bmi2() { +void BOTAN_FN_ISA_BMI2 Keccak_Permutation::permute_bmi2() { static const uint64_t RC[24] = {0x0000000000000001, 0x0000000000008082, 0x800000000000808A, 0x8000000080008000, 0x000000000000808B, 0x0000000080000001, 0x8000000080008081, 0x8000000000008009, 0x000000000000008A, 0x0000000000000088, 0x0000000080008009, 0x000000008000000A, @@ -22,8 +23,8 @@ uint64_t T[25]; for(size_t i = 0; i != 24; i += 2) { - Keccak_Permutation_round(T, m_S.data(), RC[i + 0]); - Keccak_Permutation_round(m_S.data(), T, RC[i + 1]); + Keccak_Permutation_round(T, state().data(), RC[i + 0]); + Keccak_Permutation_round(state().data(), T, RC[i + 1]); } } diff -Nru botan3-3.7.1+dfsg/src/lib/permutations/keccak_perm/keccak_perm_round.h botan3-3.12.0+dfsg/src/lib/permutations/keccak_perm/keccak_perm_round.h --- botan3-3.7.1+dfsg/src/lib/permutations/keccak_perm/keccak_perm_round.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/permutations/keccak_perm/keccak_perm_round.h 2026-05-07 01:38:28.000000000 +0000 @@ -12,7 +12,7 @@ namespace Botan { -inline void Keccak_Permutation_round(uint64_t T[25], const uint64_t A[25], uint64_t RC) { +BOTAN_FORCE_INLINE void Keccak_Permutation_round(uint64_t T[25], const uint64_t A[25], uint64_t RC) { const uint64_t C0 = A[0] ^ A[5] ^ A[10] ^ A[15] ^ A[20]; const uint64_t C1 = A[1] ^ A[6] ^ A[11] ^ A[16] ^ A[21]; const uint64_t C2 = A[2] ^ A[7] ^ A[12] ^ A[17] ^ A[22]; diff -Nru botan3-3.7.1+dfsg/src/lib/permutations/sponge/info.txt botan3-3.12.0+dfsg/src/lib/permutations/sponge/info.txt --- botan3-3.7.1+dfsg/src/lib/permutations/sponge/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/permutations/sponge/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,4 @@ + +name -> "Sponge Helper" +type -> "Internal" + diff -Nru botan3-3.7.1+dfsg/src/lib/permutations/sponge/sponge.h botan3-3.12.0+dfsg/src/lib/permutations/sponge/sponge.h --- botan3-3.7.1+dfsg/src/lib/permutations/sponge/sponge.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/permutations/sponge/sponge.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,72 @@ +/* +* Base helper class for implementing sponge constructions like Keccak or Ascon +* (C) 2025 Jack Lloyd +* 2025 René Meusel +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_SPONGE_CONSTRUCTION_H_ +#define BOTAN_SPONGE_CONSTRUCTION_H_ + +#include +#include +#include + +namespace Botan { + +/** + * A generic sponge construction with a fixed state size defined in terms of + * "words" of an unsigned integral type. + * + * This is meant to be used as a base class for specific sponge constructions + * like Keccak or Ascon. + */ +template +class Sponge { + public: + using word_t = word; + using state_t = std::array; + constexpr static size_t word_bytes = sizeof(word); + constexpr static size_t word_bits = word_bytes * 8; + + struct Config final { + size_t bit_rate; /// The number of bits that using algorithms can modify between permutations + state_t initial_state; /// The state of the sponge state at initialization + }; + + public: + constexpr explicit Sponge(Config config) : m_S(config.initial_state), m_S_cursor(0), m_bit_rate(config.bit_rate) { + BOTAN_ARG_CHECK(m_bit_rate % word_bits == 0 && m_bit_rate < words * word_bits, "Invalid sponge bit rate"); + } + + constexpr static size_t state_bytes() { return sizeof(state_t); } + + constexpr static size_t state_bits() { return state_bytes() * 8; } + + constexpr size_t bit_rate() const { return m_bit_rate; } + + constexpr size_t byte_rate() const { return m_bit_rate / 8; } + + constexpr size_t bit_capacity() const { return state_bits() - bit_rate(); } + + constexpr size_t byte_capacity() const { return state_bytes() - byte_rate(); } + + constexpr auto& state() { return m_S; } + + size_t cursor() const { return m_S_cursor; } + + size_t& _cursor() { return m_S_cursor; } + + protected: + void reset_cursor() { m_S_cursor = 0; } + + private: + state_t m_S; + size_t m_S_cursor; + size_t m_bit_rate; +}; + +} // namespace Botan + +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/permutations/sponge/sponge_processing.h botan3-3.12.0+dfsg/src/lib/permutations/sponge/sponge_processing.h --- botan3-3.7.1+dfsg/src/lib/permutations/sponge/sponge_processing.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/permutations/sponge/sponge_processing.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,264 @@ +/* +* Byte-oriented Sponge processing helpers +* (C) 2025 Jack Lloyd +* 2025 René Meusel +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_SPONGE_PROCESSING_H_ +#define BOTAN_SPONGE_PROCESSING_H_ + +#include +#include +#include +#include +#include +#include +#include + +namespace Botan { + +namespace detail { + +template +concept SpongeLike = std::unsigned_integral && requires(T a) { + typename T::word_t; + typename T::state_t; + { a.state() } -> std::same_as; + { a._cursor() } -> std::same_as; + { a.byte_rate() } -> std::same_as; +}; + +template +concept SpongeLikeWithTrivialPermute = SpongeLike && requires(T a) { + { a.permute() } -> std::same_as; +}; + +/** +* Represents the bounds of partial byte-oriented data within a word of +* the sponge state. Downstream algorithms can use this to conveniently +* modify the passed in partial state word with data written or read +* from an input or output byte buffer. +*/ +template +class PartialWordBounds final { + public: + size_t offset; // NOLINT(*-non-private-member-*) + size_t length; // NOLINT(*-non-private-member-*) + + private: + using word_t = typename SpongeT::word_t; + constexpr static auto word_bytes = SpongeT::word_bytes; + + public: + /** + * Reads '.length' bytes from the provided slicer and places them + * within a word at the specified '.offset' in little-endian order. + */ + word_t read_from(BufferSlicer& slicer) const { + std::array partial_word_bytes{}; + slicer.copy_into(std::span{partial_word_bytes}.subspan(offset, length)); + return load_le(partial_word_bytes); + } + + /** + * Writes '.length' bytes from the provided word at the specified + * '.offset' into the provided stuffer in little-endian order. + */ + void write_into(BufferStuffer& stuffer, word_t partial_word) const { + const auto partial_word_bytes = store_le(partial_word); + stuffer.append(std::span{partial_word_bytes}.subspan(offset, length)); + } + + /** + * Assigns the bits in 'partial_input_word' to their corresponding + * bits in 'state_word' at the specified '.offset' and '.length' + * while leaving all other bits in 'state_word' unchanged. + */ + word_t masked_assignment(word_t state_word, word_t partial_input_word) const { + BOTAN_DEBUG_ASSERT(length > 0); + const auto mask = ((word_t(0) - 1) >> ((word_bytes - length) * 8)) << (offset * 8); + return (state_word & ~mask) | (partial_input_word & mask); + } +}; + +/** +* A drop-in replacement for `PartialWordBounds` that is optimized for +* handling full words where no masking or offsetting is necessary. +*/ +template +class FullWordBounds final { + private: + using word_t = typename SpongeT::word_t; + constexpr static auto word_bytes = SpongeT::word_bytes; + + public: + word_t read_from(BufferSlicer& slicer) const { return load_le(slicer.take()); } + + void write_into(BufferStuffer& stuffer, word_t full_word) const { stuffer.append(store_le(full_word)); } + + word_t masked_assignment(word_t /*unused*/, word_t full_input_word) const { return full_input_word; } +}; + +template +concept PermutationFn = std::invocable || std::same_as; + +template +concept BaseModifierFn = requires(T fn, typename SpongeT::word_t word, ModifierT bounds) { + { std::invoke(fn, word, bounds) } -> std::same_as; +}; + +template +concept ModifierFn = + BaseModifierFn> || BaseModifierFn>; + +} // namespace detail + +/** +* Performs the core processing loop for ingesting or extracting data into/from +* the sponge state in a byte-oriented manner for the given number of +* @p bytes_to_process. The provided @p word_modifier_fn is called for each +* (partial) word of the sponge state that needs to be modified or read. +* +* The processing loop ensures efficient handling of unaligned input and output +* data. For that, it calls the provided permutation function either with an +* instance of `PartialWordBounds` or `FullWordBounds`. Hence @p word_modifier_fn +* must be able to handle both types of bounds and should use their respective +* methods to read from or write into input or output buffers. +* +* @param sponge the sponge instance to process data into or from +* @param bytes_to_process the number of sponge state bytes to traverse +* @param permutation_fn a function that performs the sponge's permutation +* @param modifier_fn a function that modifies the sponge state words +*/ +template +BOTAN_FORCE_INLINE void process_bytes_in_sponge(SpongeT& sponge, + size_t bytes_to_process, + const detail::PermutationFn auto& permutation_fn, + const detail::ModifierFn auto& modifier_fn) { + if(bytes_to_process == 0) { + return; + } + + constexpr auto word_bytes = SpongeT::word_bytes; + const auto byte_rate = sponge.byte_rate(); + auto& S = sponge.state(); + auto& cursor = sponge._cursor(); + + // If necessary, try to get aligned with the sponge state's words array + const auto bytes_out_of_word_alignment = static_cast(cursor % word_bytes); + if(bytes_out_of_word_alignment > 0) { + const auto bytes_until_word_alignment = word_bytes - bytes_out_of_word_alignment; + const auto bytes_from_input = std::min(bytes_to_process, bytes_until_word_alignment); + BOTAN_DEBUG_ASSERT(bytes_from_input < word_bytes); + + S[cursor / word_bytes] = modifier_fn(S[cursor / word_bytes], + detail::PartialWordBounds{ + .offset = bytes_out_of_word_alignment, + .length = bytes_from_input, + }); + cursor += bytes_from_input; + bytes_to_process -= bytes_from_input; + + if(cursor == byte_rate) { + permutation_fn(); + cursor = 0; + } + } + + // If we didn't exhaust the bytes to process for this invocation, we should + // be word-aligned with the sponge state now + BOTAN_DEBUG_ASSERT(bytes_to_process == 0 || cursor % word_bytes == 0); + + // Block-wise incorporation of the input data into the sponge state until + // all input bytes are processed + while(bytes_to_process >= word_bytes) { + // Process full words until we either run out of data or reach the + // end of the current sponge state block + while(bytes_to_process >= word_bytes && cursor < byte_rate) { + S[cursor / word_bytes] = modifier_fn(S[cursor / word_bytes], detail::FullWordBounds{}); + cursor += word_bytes; + bytes_to_process -= word_bytes; + } + + if(cursor == byte_rate) { + permutation_fn(); + cursor = 0; + } + } + + // Process the remaining bytes that don't fill an entire word. + // Therefore, leaving the sponge state in an unaligned state that won't + // need another permutation until the next call to process(). + BOTAN_DEBUG_ASSERT(bytes_to_process < word_bytes && cursor < byte_rate); + if(bytes_to_process > 0) { + S[cursor / word_bytes] = modifier_fn(S[cursor / word_bytes], + detail::PartialWordBounds{ + .offset = 0, + .length = bytes_to_process, + }); + cursor += bytes_to_process; + } +} + +template +inline void process_bytes_in_sponge(SpongeT& sponge, + size_t bytes_to_process, + const detail::ModifierFn auto& modifier_fn) { + process_bytes_in_sponge( + sponge, bytes_to_process, [&sponge] { sponge.permute(); }, modifier_fn); +} + +/** +* Absorbs @p input data into the @p sponge state. +* +* @param sponge The sponge state to absorb data into. +* @param input The input data to absorb. +* @param permutation_fn The function to call for the sponge's permutation. +*/ +template +inline void absorb_into_sponge(SpongeT& sponge, + std::span input, + const detail::PermutationFn auto& permutation_fn) { + using word_t = typename SpongeT::word_t; + + BufferSlicer input_slicer(input); + process_bytes_in_sponge(sponge, input.size(), permutation_fn, [&](word_t state_word, auto bounds) { + return state_word ^ bounds.read_from(input_slicer); + }); + BOTAN_ASSERT_NOMSG(input_slicer.empty()); +} + +inline void absorb_into_sponge(detail::SpongeLikeWithTrivialPermute auto& sponge, std::span input) { + absorb_into_sponge(sponge, input, [&sponge] { sponge.permute(); }); +} + +/** +* Squeezes @p output data from the @p sponge state. +* +* @param sponge The sponge state to squeeze data from. +* @param output The output buffer to write the squeezed data into. +* @param permutation_fn The function to call for the sponge's permutation. +*/ +template +inline void squeeze_from_sponge(SpongeT& sponge, + std::span output, + const detail::PermutationFn auto& permutation_fn) { + using word_t = typename SpongeT::word_t; + + BufferStuffer output_stuffer(output); + process_bytes_in_sponge(sponge, output.size(), permutation_fn, [&](word_t state_word, auto bounds) { + bounds.write_into(output_stuffer, state_word); + return state_word; + }); + BOTAN_ASSERT_NOMSG(output_stuffer.full()); +} + +inline void squeeze_from_sponge(detail::SpongeLikeWithTrivialPermute auto& sponge, std::span output) { + squeeze_from_sponge(sponge, output, [&sponge] { sponge.permute(); }); +} + +} // namespace Botan + +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/eme.cpp botan3-3.12.0+dfsg/src/lib/pk_pad/eme.cpp --- botan3-3.7.1+dfsg/src/lib/pk_pad/eme.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/eme.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,71 +0,0 @@ -/* -* EME Base Class -* (C) 1999-2008 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include - -#include -#include -#include - -#if defined(BOTAN_HAS_EME_OAEP) - #include -#endif - -#if defined(BOTAN_HAS_EME_PKCS1) - #include -#endif - -#if defined(BOTAN_HAS_EME_RAW) - #include -#endif - -namespace Botan { - -std::unique_ptr EME::create(std::string_view algo_spec) { -#if defined(BOTAN_HAS_EME_RAW) - if(algo_spec == "Raw") { - return std::make_unique(); - } -#endif - -#if defined(BOTAN_HAS_EME_PKCS1) - // TODO(Botan4) Remove all but "PKCS1v15" - if(algo_spec == "PKCS1v15" || algo_spec == "EME-PKCS1-v1_5") { - return std::make_unique(); - } -#endif - -#if defined(BOTAN_HAS_EME_OAEP) - SCAN_Name req(algo_spec); - - // TODO(Botan4) Remove all but "OAEP" - if(req.algo_name() == "OAEP" || req.algo_name() == "EME-OAEP" || req.algo_name() == "EME1") { - if(req.arg_count() == 1 || ((req.arg_count() == 2 || req.arg_count() == 3) && req.arg(1) == "MGF1")) { - if(auto hash = HashFunction::create(req.arg(0))) { - return std::make_unique(std::move(hash), req.arg(2, "")); - } - } else if(req.arg_count() == 2 || req.arg_count() == 3) { - auto mgf_params = parse_algorithm_name(req.arg(1)); - - if(mgf_params.size() == 2 && mgf_params[0] == "MGF1") { - auto hash = HashFunction::create(req.arg(0)); - auto mgf1_hash = HashFunction::create(mgf_params[1]); - - if(hash && mgf1_hash) { - return std::make_unique(std::move(hash), std::move(mgf1_hash), req.arg(2, "")); - } - } - } - } -#endif - - throw Algorithm_Not_Found(algo_spec); -} - -EME::~EME() = default; - -} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/eme.h botan3-3.12.0+dfsg/src/lib/pk_pad/eme.h --- botan3-3.7.1+dfsg/src/lib/pk_pad/eme.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/eme.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,67 +0,0 @@ -/* -* (C) 1999-2007,2024 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#ifndef BOTAN_PUBKEY_EME_H_ -#define BOTAN_PUBKEY_EME_H_ - -#include -#include -#include -#include -#include - -namespace Botan { - -class RandomNumberGenerator; - -/** -* Encoding Method for Encryption -*/ -class BOTAN_TEST_API EME { - public: - virtual ~EME(); - - /** - * Factory method for EME (message-encoding methods for encryption) objects - * @param algo_spec the name of the EME to create - * @return pointer to newly allocated object of that type - */ - static std::unique_ptr create(std::string_view algo_spec); - - /** - * Return the maximum input size in bytes we can support - * @param keybits the size of the key in bits - * @return upper bound of input in bytes - */ - virtual size_t maximum_input_size(size_t keybits) const = 0; - - /** - * Encode an input - * @param output buffer that is written to - * @param input the plaintext - * @param key_length length of the key in bits - * @param rng a random number generator - * @return number of bytes written to output - */ - virtual size_t pad(std::span output, - std::span input, - size_t key_length, - RandomNumberGenerator& rng) const = 0; - - /** - * Decode an input - * @param output buffer where output is placed - * @param input the encoded plaintext - * @return number of bytes written to output if valid, - * or an empty option if invalid. If an empty option is - * returned the contents of output are undefined - */ - virtual CT::Option unpad(std::span output, std::span input) const = 0; -}; - -} // namespace Botan - -#endif diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/eme_oaep/info.txt botan3-3.12.0+dfsg/src/lib/pk_pad/eme_oaep/info.txt --- botan3-3.7.1+dfsg/src/lib/pk_pad/eme_oaep/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/eme_oaep/info.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,12 +0,0 @@ - -EME_OAEP -> 20180305 -OAEP -> 20250130 - - - -name -> "OAEP" - - - -mgf1 - diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/eme_oaep/oaep.cpp botan3-3.12.0+dfsg/src/lib/pk_pad/eme_oaep/oaep.cpp --- botan3-3.7.1+dfsg/src/lib/pk_pad/eme_oaep/oaep.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/eme_oaep/oaep.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,159 +0,0 @@ -/* -* OAEP -* (C) 1999-2010,2015,2018,2024 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include - -#include -#include -#include -#include -#include - -namespace Botan { - -/* -* OAEP Pad Operation -*/ -size_t OAEP::pad(std::span output, - std::span input, - size_t key_length, - RandomNumberGenerator& rng) const { - key_length /= 8; - - if(input.size() > maximum_input_size(key_length * 8)) { - throw Invalid_Argument("OAEP: Input is too large"); - } - - const size_t output_size = key_length; - - output = output.first(output_size); // remainder ignored - - BufferStuffer stuffer(output); - - // We always use a seed len equal to the underlying hash - rng.randomize(stuffer.next(m_Phash.size())); - stuffer.append(m_Phash); - stuffer.append(0x00, stuffer.remaining_capacity() - (1 + input.size())); - stuffer.append(0x01); - stuffer.append(input); - BOTAN_ASSERT_NOMSG(stuffer.full()); - - const size_t hlen = m_Phash.size(); - - mgf1_mask(*m_mgf1_hash, output.first(hlen), output.subspan(hlen)); - - mgf1_mask(*m_mgf1_hash, output.subspan(hlen), output.first(hlen)); - - return key_length; -} - -/* -* OAEP Unpad Operation -*/ -CT::Option OAEP::unpad(std::span output, std::span input) const { - BOTAN_ASSERT_NOMSG(output.size() >= input.size()); - - /* - Must be careful about error messages here; if an attacker can - distinguish them, it is easy to use the differences as an oracle to - find the secret key, as described in "A Chosen Ciphertext Attack on - RSA Optimal Asymmetric Encryption Padding (OAEP) as Standardized in - PKCS #1 v2.0", James Manger, Crypto 2001 - - Also have to be careful about timing attacks! Pointed out by Falko - Strenzke. - - According to the standard (RFC 3447 Section 7.1.1), the encryptor always - creates a message as follows: - i. Concatenate a single octet with hexadecimal value 0x00, - maskedSeed, and maskedDB to form an encoded message EM of - length k octets as - EM = 0x00 || maskedSeed || maskedDB. - where k is the length of the modulus N. - Therefore, the first byte should always be zero. - */ - - if(input.empty()) { - return {}; - } - - auto scope = CT::scoped_poison(input); - - const auto has_leading_0 = CT::Mask::is_zero(input[0]).as_choice(); - - secure_vector decoded(input.begin() + 1, input.end()); - auto buf = std::span{decoded}; - - const size_t hlen = m_Phash.size(); - - mgf1_mask(*m_mgf1_hash, buf.subspan(hlen), buf.first(hlen)); - - mgf1_mask(*m_mgf1_hash, buf.first(hlen), buf.subspan(hlen)); - - auto delim = oaep_find_delim(buf, m_Phash); - - return CT::copy_output(delim.has_value() && has_leading_0, output, buf, delim.value_or(0)); -} - -CT::Option oaep_find_delim(std::span input, std::span phash) { - // Too short to be valid, reject immediately - if(input.size() < 1 + 2 * phash.size()) { - return {}; - } - - size_t delim_idx = 2 * phash.size(); - CT::Mask waiting_for_delim = CT::Mask::set(); - CT::Mask bad_input_m = CT::Mask::cleared(); - - for(uint8_t ib : input.subspan(2 * phash.size())) { - const auto zero_m = CT::Mask::is_zero(ib); - const auto one_m = CT::Mask::is_equal(ib, 1); - - const auto add_m = waiting_for_delim & zero_m; - - bad_input_m |= waiting_for_delim & ~(zero_m | one_m); - - delim_idx += add_m.if_set_return(1); - - waiting_for_delim &= zero_m; - } - - // If we never saw any non-zero byte, then it's not valid input - bad_input_m |= waiting_for_delim; - - // If the P hash is wrong, then it's not valid - bad_input_m |= CT::is_not_equal(&input[phash.size()], phash.data(), phash.size()); - - delim_idx += 1; - - const auto accept = !(bad_input_m.as_choice()); - - return CT::Option(delim_idx, accept); -} - -/* -* Return the max input size for a given key size -*/ -size_t OAEP::maximum_input_size(size_t keybits) const { - if(keybits / 8 > 2 * m_Phash.size() + 1) { - return ((keybits / 8) - 2 * m_Phash.size() - 1); - } else { - return 0; - } -} - -OAEP::OAEP(std::unique_ptr hash, std::string_view P) : m_mgf1_hash(std::move(hash)) { - m_Phash = m_mgf1_hash->process(P); -} - -OAEP::OAEP(std::unique_ptr hash, std::unique_ptr mgf1_hash, std::string_view P) : - m_mgf1_hash(std::move(mgf1_hash)) { - auto phash = std::move(hash); - m_Phash = phash->process(P); -} - -} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/eme_oaep/oaep.h botan3-3.12.0+dfsg/src/lib/pk_pad/eme_oaep/oaep.h --- botan3-3.7.1+dfsg/src/lib/pk_pad/eme_oaep/oaep.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/eme_oaep/oaep.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,55 +0,0 @@ -/* -* OAEP -* (C) 1999-2007,2018,2024 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#ifndef BOTAN_OAEP_H_ -#define BOTAN_OAEP_H_ - -#include - -#include -#include - -namespace Botan { - -/** -* OAEP (called EME1 in IEEE 1363 and in earlier versions of the library) -* as specified in PKCS#1 v2.0 (RFC 2437) or PKCS#1 v2.1 (RFC 3447) -*/ -class OAEP final : public EME { - public: - size_t maximum_input_size(size_t) const override; - - /** - * @param hash function to use for hashing (takes ownership) - * @param P an optional label. Normally empty. - */ - OAEP(std::unique_ptr hash, std::string_view P = ""); - - /** - * @param hash function to use for hashing (takes ownership) - * @param mgf1_hash function to use for MGF1 (takes ownership) - * @param P an optional label. Normally empty. - */ - OAEP(std::unique_ptr hash, std::unique_ptr mgf1_hash, std::string_view P = ""); - - private: - size_t pad(std::span output, - std::span input, - size_t key_length, - RandomNumberGenerator& rng) const override; - - CT::Option unpad(std::span output, std::span input) const override; - - secure_vector m_Phash; - std::unique_ptr m_mgf1_hash; -}; - -BOTAN_FUZZER_API CT::Option oaep_find_delim(std::span input, std::span phash); - -} // namespace Botan - -#endif diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/eme_pkcs1/eme_pkcs.cpp botan3-3.12.0+dfsg/src/lib/pk_pad/eme_pkcs1/eme_pkcs.cpp --- botan3-3.7.1+dfsg/src/lib/pk_pad/eme_pkcs1/eme_pkcs.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/eme_pkcs1/eme_pkcs.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,106 +0,0 @@ -/* -* PKCS #1 v1.5 Type 2 (encryption) padding -* (C) 1999-2007,2015,2016,2024 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include - -#include -#include -#include -#include -#include -#include - -namespace Botan { - -/* -* PKCS1 Pad Operation -*/ -size_t EME_PKCS1v15::pad(std::span output, - std::span input, - size_t key_length, - RandomNumberGenerator& rng) const { - key_length /= 8; - - if(input.size() > maximum_input_size(key_length * 8)) { - throw Invalid_Argument("PKCS1: Input is too large"); - } - - BufferStuffer stuffer(output); - - const size_t padding_bytes = [&]() { - auto d = checked_sub(key_length, input.size() + 2); - BOTAN_ASSERT_NOMSG(d.has_value()); - return *d; - }(); - - stuffer.append(0x02); - for(size_t i = 0; i != padding_bytes; ++i) { - stuffer.append(rng.next_nonzero_byte()); - } - stuffer.append(0x00); - stuffer.append(input); - - return output.size() - stuffer.remaining_capacity(); -} - -/* -* PKCS1 Unpad Operation -*/ -CT::Option EME_PKCS1v15::unpad(std::span output, std::span input) const { - BOTAN_ASSERT_NOMSG(output.size() >= input.size()); - - /* - * RSA decryption pads the ciphertext up to the modulus size, so this only - * occurs with very (!) small keys, or when fuzzing. - * - * 11 bytes == 00,02 + 8 bytes mandatory padding + 00 - */ - if(input.size() < 11) { - return {}; - } - - auto scope = CT::scoped_poison(input); - - CT::Mask bad_input_m = CT::Mask::cleared(); - CT::Mask seen_zero_m = CT::Mask::cleared(); - size_t delim_idx = 2; // initial 0002 - - bad_input_m |= ~CT::Mask::is_equal(input[0], 0); - bad_input_m |= ~CT::Mask::is_equal(input[1], 2); - - for(size_t i = 2; i < input.size(); ++i) { - const auto is_zero_m = CT::Mask::is_zero(input[i]); - delim_idx += seen_zero_m.if_not_set_return(1); - seen_zero_m |= is_zero_m; - } - - // no zero delim -> bad padding - bad_input_m |= ~seen_zero_m; - /* - delim indicates < 8 bytes padding -> bad padding - - We require 11 here because we are counting also the 00 delim byte - */ - bad_input_m |= CT::Mask(CT::Mask::is_lt(delim_idx, 11)); - - const CT::Choice accept = !(bad_input_m.as_choice()); - - return CT::copy_output(accept, output, input, delim_idx); -} - -/* -* Return the max input size for a given key size -*/ -size_t EME_PKCS1v15::maximum_input_size(size_t keybits) const { - if(keybits / 8 > 10) { - return ((keybits / 8) - 10); - } else { - return 0; - } -} - -} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/eme_pkcs1/eme_pkcs.h botan3-3.12.0+dfsg/src/lib/pk_pad/eme_pkcs1/eme_pkcs.h --- botan3-3.7.1+dfsg/src/lib/pk_pad/eme_pkcs1/eme_pkcs.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/eme_pkcs1/eme_pkcs.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -/* -* EME PKCS#1 v1.5 -* (C) 1999-2007 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#ifndef BOTAN_EME_PKCS1_H_ -#define BOTAN_EME_PKCS1_H_ - -#include - -namespace Botan { - -/** -* EME from PKCS #1 v1.5 -*/ -class BOTAN_FUZZER_API EME_PKCS1v15 final : public EME { - private: - size_t maximum_input_size(size_t) const override; - - size_t pad(std::span output, - std::span input, - size_t key_length, - RandomNumberGenerator& rng) const override; - - CT::Option unpad(std::span output, std::span input) const override; -}; - -} // namespace Botan - -#endif diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/eme_pkcs1/info.txt botan3-3.12.0+dfsg/src/lib/pk_pad/eme_pkcs1/info.txt --- botan3-3.7.1+dfsg/src/lib/pk_pad/eme_pkcs1/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/eme_pkcs1/info.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,9 +0,0 @@ - -EME_PKCS1v15 -> 20131128 -EME_PKCS1 -> 20190426 -PKCSV15_ENCRYPTION_PADDING -> 20250126 - - - -name -> "PKCS #1 v1.5 encryption padding" - diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/eme_raw/eme_raw.cpp botan3-3.12.0+dfsg/src/lib/pk_pad/eme_raw/eme_raw.cpp --- botan3-3.7.1+dfsg/src/lib/pk_pad/eme_raw/eme_raw.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/eme_raw/eme_raw.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,40 +0,0 @@ -/* -* (C) 2015,2016,2024 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include - -#include -#include - -namespace Botan { - -size_t EME_Raw::pad(std::span output, - std::span input, - size_t key_length, - RandomNumberGenerator& rng) const { - BOTAN_UNUSED(rng); - BOTAN_ASSERT_NOMSG(input.size() < maximum_input_size(8 * key_length)); - BOTAN_ASSERT_NOMSG(output.size() >= input.size()); - copy_mem(output.first(input.size()), input); - return input.size(); -} - -CT::Option EME_Raw::unpad(std::span output, std::span input) const { - BOTAN_ASSERT_NOMSG(output.size() >= input.size()); - - if(input.empty()) { - return CT::Option(0); - } - - const size_t leading_zeros = CT::count_leading_zero_bytes(input); - return CT::copy_output(CT::Choice::yes(), output, input, leading_zeros); -} - -size_t EME_Raw::maximum_input_size(size_t keybits) const { - return keybits / 8; -} - -} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/eme_raw/eme_raw.h botan3-3.12.0+dfsg/src/lib/pk_pad/eme_raw/eme_raw.h --- botan3-3.7.1+dfsg/src/lib/pk_pad/eme_raw/eme_raw.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/eme_raw/eme_raw.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,31 +0,0 @@ -/* -* (C) 2015 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#ifndef BOTAN_EME_RAW_H_ -#define BOTAN_EME_RAW_H_ - -#include - -namespace Botan { - -class EME_Raw final : public EME { - public: - EME_Raw() = default; - - private: - size_t maximum_input_size(size_t i) const override; - - size_t pad(std::span output, - std::span input, - size_t key_length, - RandomNumberGenerator& rng) const override; - - CT::Option unpad(std::span output, std::span input) const override; -}; - -} // namespace Botan - -#endif diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/eme_raw/info.txt botan3-3.12.0+dfsg/src/lib/pk_pad/eme_raw/info.txt --- botan3-3.7.1+dfsg/src/lib/pk_pad/eme_raw/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/eme_raw/info.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,7 +0,0 @@ - -EME_RAW -> 20150313 - - - -name -> "EME Raw Padding" - diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/emsa.cpp botan3-3.12.0+dfsg/src/lib/pk_pad/emsa.cpp --- botan3-3.7.1+dfsg/src/lib/pk_pad/emsa.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/emsa.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,143 +0,0 @@ -/* -* (C) 2015 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include - -#include -#include -#include - -#if defined(BOTAN_HAS_EMSA_X931) - #include -#endif - -#if defined(BOTAN_HAS_EMSA_PKCS1) - #include -#endif - -#if defined(BOTAN_HAS_EMSA_PSSR) - #include -#endif - -#if defined(BOTAN_HAS_EMSA_RAW) - #include -#endif - -#if defined(BOTAN_HAS_ISO_9796) - #include -#endif - -namespace Botan { - -std::unique_ptr EMSA::create(std::string_view algo_spec) { - SCAN_Name req(algo_spec); - -#if defined(BOTAN_HAS_EMSA_PKCS1) - // TODO(Botan4) Remove all but "PKCS1v15" - if(req.algo_name() == "EMSA_PKCS1" || req.algo_name() == "PKCS1v15" || req.algo_name() == "EMSA-PKCS1-v1_5" || - req.algo_name() == "EMSA3") { - if(req.arg_count() == 2 && req.arg(0) == "Raw") { - return std::make_unique(req.arg(1)); - } else if(req.arg_count() == 1) { - if(req.arg(0) == "Raw") { - return std::make_unique(); - } else { - if(auto hash = HashFunction::create(req.arg(0))) { - return std::make_unique(std::move(hash)); - } - } - } - } -#endif - -#if defined(BOTAN_HAS_EMSA_PSSR) - // TODO(Botan4) Remove all but "PSS_Raw" - if(req.algo_name() == "PSS_Raw" || req.algo_name() == "PSSR_Raw") { - if(req.arg_count_between(1, 3) && req.arg(1, "MGF1") == "MGF1") { - if(auto hash = HashFunction::create(req.arg(0))) { - if(req.arg_count() == 3) { - const size_t salt_size = req.arg_as_integer(2, 0); - return std::make_unique(std::move(hash), salt_size); - } else { - return std::make_unique(std::move(hash)); - } - } - } - } - - // TODO(Botan4) Remove all but "PSS" - if(req.algo_name() == "PSS" || req.algo_name() == "PSSR" || req.algo_name() == "EMSA-PSS" || - req.algo_name() == "PSS-MGF1" || req.algo_name() == "EMSA4") { - if(req.arg_count_between(1, 3) && req.arg(1, "MGF1") == "MGF1") { - if(auto hash = HashFunction::create(req.arg(0))) { - if(req.arg_count() == 3) { - const size_t salt_size = req.arg_as_integer(2, 0); - return std::make_unique(std::move(hash), salt_size); - } else { - return std::make_unique(std::move(hash)); - } - } - } - } -#endif - -#if defined(BOTAN_HAS_ISO_9796) - if(req.algo_name() == "ISO_9796_DS2") { - if(req.arg_count_between(1, 3)) { - if(auto hash = HashFunction::create(req.arg(0))) { - const size_t salt_size = req.arg_as_integer(2, hash->output_length()); - const bool implicit = req.arg(1, "exp") == "imp"; - return std::make_unique(std::move(hash), implicit, salt_size); - } - } - } - //ISO-9796-2 DS 3 is deterministic and DS2 without a salt - if(req.algo_name() == "ISO_9796_DS3") { - if(req.arg_count_between(1, 2)) { - if(auto hash = HashFunction::create(req.arg(0))) { - const bool implicit = req.arg(1, "exp") == "imp"; - return std::make_unique(std::move(hash), implicit); - } - } - } -#endif - -#if defined(BOTAN_HAS_EMSA_X931) - // TODO(Botan4) Remove all but "X9.31" - if(req.algo_name() == "EMSA_X931" || req.algo_name() == "EMSA2" || req.algo_name() == "X9.31") { - if(req.arg_count() == 1) { - if(auto hash = HashFunction::create(req.arg(0))) { - return std::make_unique(std::move(hash)); - } - } - } -#endif - -#if defined(BOTAN_HAS_EMSA_RAW) - if(req.algo_name() == "Raw") { - if(req.arg_count() == 0) { - return std::make_unique(); - } else { - auto hash = HashFunction::create(req.arg(0)); - if(hash) { - return std::make_unique(hash->output_length()); - } - } - } -#endif - - return nullptr; -} - -std::unique_ptr EMSA::create_or_throw(std::string_view algo_spec) { - auto emsa = EMSA::create(algo_spec); - if(emsa) { - return emsa; - } - throw Algorithm_Not_Found(algo_spec); -} - -} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/emsa.h botan3-3.12.0+dfsg/src/lib/pk_pad/emsa.h --- botan3-3.7.1+dfsg/src/lib/pk_pad/emsa.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/emsa.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,88 +0,0 @@ -/* -* EMSA Classes -* (C) 1999-2007 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#ifndef BOTAN_PUBKEY_EMSA_H_ -#define BOTAN_PUBKEY_EMSA_H_ - -#include -#include - -namespace Botan { - -class RandomNumberGenerator; - -/** -* EMSA, from IEEE 1363s Encoding Method for Signatures, Appendix -* -* Any way of encoding/padding signatures -*/ -class BOTAN_TEST_API EMSA { - public: - virtual ~EMSA() = default; - - /** - * Factory method for EMSA (message-encoding methods for signatures - * with appendix) objects - * @param algo_spec the name of the EMSA to create - * @return pointer to newly allocated object of that type, or nullptr - */ - static std::unique_ptr create(std::string_view algo_spec); - - /** - * Factory method for EMSA (message-encoding methods for signatures - * with appendix) objects - * @param algo_spec the name of the EMSA to create - * @return pointer to newly allocated object of that type, or throws - */ - static std::unique_ptr create_or_throw(std::string_view algo_spec); - - /** - * Add more data to the signature computation - * @param input some data - * @param length length of input in bytes - */ - virtual void update(const uint8_t input[], size_t length) = 0; - - /** - * @return raw hash - */ - virtual std::vector raw_data() = 0; - - /** - * Return the encoding of a message - * @param msg the result of raw_data() - * @param output_bits the desired output bit size - * @param rng a random number generator - * @return encoded signature - */ - virtual std::vector encoding_of(const std::vector& msg, - size_t output_bits, - RandomNumberGenerator& rng) = 0; - - /** - * Verify the encoding - * @param coded the received (coded) message representative - * @param raw the computed (local, uncoded) message representative - * @param key_bits the size of the key in bits - * @return true if coded is a valid encoding of raw, otherwise false - */ - virtual bool verify(const std::vector& coded, const std::vector& raw, size_t key_bits) = 0; - - /** - * Return the hash function being used by this padding scheme - */ - virtual std::string hash_function() const = 0; - - /** - * @return the SCAN name of the encoding/padding scheme - */ - virtual std::string name() const = 0; -}; - -} // namespace Botan - -#endif diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/emsa_pkcs1/emsa_pkcs1.cpp botan3-3.12.0+dfsg/src/lib/pk_pad/emsa_pkcs1/emsa_pkcs1.cpp --- botan3-3.7.1+dfsg/src/lib/pk_pad/emsa_pkcs1/emsa_pkcs1.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/emsa_pkcs1/emsa_pkcs1.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,133 +0,0 @@ -/* -* PKCS #1 v1.5 signature padding -* (C) 1999-2008 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include - -#include -#include -#include - -namespace Botan { - -namespace { - -std::vector pkcs1v15_sig_encoding(const std::vector& msg, - size_t output_bits, - std::span hash_id) { - const size_t output_length = output_bits / 8; - - if(output_length < hash_id.size() + msg.size() + 2 + 8) { - throw Encoding_Error("pkcs1v15_sig_encoding: Output length is too small"); - } - - std::vector padded(output_length); - BufferStuffer stuffer(padded); - - stuffer.append(0x01); - stuffer.append(0xFF, stuffer.remaining_capacity() - (1 + hash_id.size() + msg.size())); - stuffer.append(0x00); - stuffer.append(hash_id); - stuffer.append(msg); - BOTAN_ASSERT_NOMSG(stuffer.full()); - - return padded; -} - -} // namespace - -void EMSA_PKCS1v15::update(const uint8_t input[], size_t length) { - m_hash->update(input, length); -} - -std::vector EMSA_PKCS1v15::raw_data() { - return m_hash->final_stdvec(); -} - -std::vector EMSA_PKCS1v15::encoding_of(const std::vector& msg, - size_t output_bits, - RandomNumberGenerator& /*rng*/) { - if(msg.size() != m_hash->output_length()) { - throw Encoding_Error("EMSA_PKCS1v15::encoding_of: Bad input length"); - } - - return pkcs1v15_sig_encoding(msg, output_bits, m_hash_id); -} - -bool EMSA_PKCS1v15::verify(const std::vector& coded, const std::vector& raw, size_t key_bits) { - if(raw.size() != m_hash->output_length()) { - return false; - } - - try { - return coded == pkcs1v15_sig_encoding(raw, key_bits, m_hash_id); - } catch(...) { - return false; - } -} - -EMSA_PKCS1v15::EMSA_PKCS1v15(std::unique_ptr hash) : m_hash(std::move(hash)) { - m_hash_id = pkcs_hash_id(m_hash->name()); -} - -std::string EMSA_PKCS1v15::name() const { - return "PKCS1v15(" + m_hash->name() + ")"; -} - -std::string EMSA_PKCS1v15_Raw::name() const { - if(m_hash_name.empty()) { - return "PKCS1v15(Raw)"; - } else { - return "PKCS1v15(Raw," + m_hash_name + ")"; - } -} - -EMSA_PKCS1v15_Raw::EMSA_PKCS1v15_Raw() { - m_hash_output_len = 0; - // m_hash_id, m_hash_name left empty -} - -EMSA_PKCS1v15_Raw::EMSA_PKCS1v15_Raw(std::string_view hash_algo) { - std::unique_ptr hash(HashFunction::create_or_throw(hash_algo)); - m_hash_id = pkcs_hash_id(hash_algo); - m_hash_name = hash->name(); - m_hash_output_len = hash->output_length(); -} - -void EMSA_PKCS1v15_Raw::update(const uint8_t input[], size_t length) { - m_message += std::make_pair(input, length); -} - -std::vector EMSA_PKCS1v15_Raw::raw_data() { - std::vector ret; - std::swap(ret, m_message); - - if(m_hash_output_len > 0 && ret.size() != m_hash_output_len) { - throw Encoding_Error("EMSA_PKCS1v15_Raw::encoding_of: Bad input length"); - } - - return ret; -} - -std::vector EMSA_PKCS1v15_Raw::encoding_of(const std::vector& msg, - size_t output_bits, - RandomNumberGenerator& /*rng*/) { - return pkcs1v15_sig_encoding(msg, output_bits, m_hash_id); -} - -bool EMSA_PKCS1v15_Raw::verify(const std::vector& coded, const std::vector& raw, size_t key_bits) { - if(m_hash_output_len > 0 && raw.size() != m_hash_output_len) { - return false; - } - - try { - return coded == pkcs1v15_sig_encoding(raw, key_bits, m_hash_id); - } catch(...) { - return false; - } -} - -} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/emsa_pkcs1/emsa_pkcs1.h botan3-3.12.0+dfsg/src/lib/pk_pad/emsa_pkcs1/emsa_pkcs1.h --- botan3-3.7.1+dfsg/src/lib/pk_pad/emsa_pkcs1/emsa_pkcs1.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/emsa_pkcs1/emsa_pkcs1.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,81 +0,0 @@ -/* -* PKCS #1 v1.5 signature padding -* (C) 1999-2008 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#ifndef BOTAN_EMSA_PKCS1_H_ -#define BOTAN_EMSA_PKCS1_H_ - -#include -#include - -namespace Botan { - -/** -* PKCS #1 v1.5 signature padding -* aka PKCS #1 block type 1 -* aka EMSA3 from IEEE 1363 -*/ -class EMSA_PKCS1v15 final : public EMSA { - public: - /** - * @param hash the hash function to use - */ - explicit EMSA_PKCS1v15(std::unique_ptr hash); - - void update(const uint8_t[], size_t) override; - - std::vector raw_data() override; - - std::vector encoding_of(const std::vector&, size_t, RandomNumberGenerator& rng) override; - - bool verify(const std::vector&, const std::vector&, size_t) override; - - std::string name() const override; - - std::string hash_function() const override { return m_hash->name(); } - - private: - std::unique_ptr m_hash; - std::vector m_hash_id; -}; - -/** -* EMSA_PKCS1v15_Raw which is EMSA_PKCS1v15 without a hash or digest id -* (which according to QCA docs is "identical to PKCS#11's CKM_RSA_PKCS -* mechanism", something I have not confirmed) -*/ -class EMSA_PKCS1v15_Raw final : public EMSA { - public: - void update(const uint8_t[], size_t) override; - - std::vector raw_data() override; - - std::vector encoding_of(const std::vector&, size_t, RandomNumberGenerator& rng) override; - - bool verify(const std::vector&, const std::vector&, size_t) override; - - EMSA_PKCS1v15_Raw(); - - /** - * @param hash_algo the digest id for that hash is included in - * the signature. - */ - EMSA_PKCS1v15_Raw(std::string_view hash_algo); - - std::string hash_function() const override { return m_hash_name; } - - std::string name() const override; - - private: - size_t m_hash_output_len = 0; - std::string m_hash_name; - std::vector m_hash_id; - std::vector m_message; -}; - -} // namespace Botan - -#endif diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/emsa_pkcs1/info.txt botan3-3.12.0+dfsg/src/lib/pk_pad/emsa_pkcs1/info.txt --- botan3-3.7.1+dfsg/src/lib/pk_pad/emsa_pkcs1/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/emsa_pkcs1/info.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,12 +0,0 @@ - -EMSA_PKCS1 -> 20140118 -PKCSV15_SIGNATURE_PADDING -> 20250126 - - - -name -> "PKCS #1 v1.5 signature padding" - - - -hash_id - diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/emsa_pssr/info.txt botan3-3.12.0+dfsg/src/lib/pk_pad/emsa_pssr/info.txt --- botan3-3.7.1+dfsg/src/lib/pk_pad/emsa_pssr/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/emsa_pssr/info.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,13 +0,0 @@ - -EMSA_PSSR -> 20131128 -PSS -> 20250130 - - - -name -> "PSS" -brief -> "PSS signature padding from PKCS1v2.0" - - - -mgf1 - diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/emsa_pssr/pssr.cpp botan3-3.12.0+dfsg/src/lib/pk_pad/emsa_pssr/pssr.cpp --- botan3-3.7.1+dfsg/src/lib/pk_pad/emsa_pssr/pssr.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/emsa_pssr/pssr.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,245 +0,0 @@ -/* -* PSSR -* (C) 1999-2007,2017,2023 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace Botan { - -namespace { - -/* -* PSSR Encode Operation -*/ -std::vector pss_encode(HashFunction& hash, - const std::vector& msg, - const std::vector& salt, - size_t output_bits) { - const size_t HASH_SIZE = hash.output_length(); - - if(msg.size() != HASH_SIZE) { - throw Encoding_Error("Cannot encode PSS string, input length invalid for hash"); - } - if(output_bits < 8 * HASH_SIZE + 8 * salt.size() + 9) { - throw Encoding_Error("Cannot encode PSS string, output length too small"); - } - - const size_t output_length = ceil_tobytes(output_bits); - const uint8_t db0_mask = 0xFF >> (8 * output_length - output_bits); - - std::array padding = {0}; - hash.update(padding); - hash.update(msg); - hash.update(salt); - std::vector H = hash.final_stdvec(); - - const size_t db_len = output_length - HASH_SIZE - 1; - std::vector EM(output_length); - - BufferStuffer stuffer(EM); - stuffer.append(0x00, stuffer.remaining_capacity() - (1 + salt.size() + H.size() + 1)); - stuffer.append(0x01); - stuffer.append(salt); - - mgf1_mask(hash, H.data(), H.size(), EM.data(), db_len); - EM[0] &= db0_mask; - - stuffer.append(H); - stuffer.append(0xBC); - BOTAN_ASSERT_NOMSG(stuffer.full()); - - return EM; -} - -bool pss_verify(HashFunction& hash, - const std::vector& pss_repr, - const std::vector& message_hash, - size_t key_bits, - size_t* out_salt_size) { - const size_t HASH_SIZE = hash.output_length(); - const size_t key_bytes = ceil_tobytes(key_bits); - - if(key_bits < 8 * HASH_SIZE + 9) { - return false; - } - - if(message_hash.size() != HASH_SIZE) { - return false; - } - - if(pss_repr.size() > key_bytes || pss_repr.size() <= 1) { - return false; - } - - if(pss_repr[pss_repr.size() - 1] != 0xBC) { - return false; - } - - std::vector coded = pss_repr; - if(coded.size() < key_bytes) { - std::vector temp(key_bytes); - BufferStuffer stuffer(temp); - stuffer.append(0x00, stuffer.remaining_capacity() - coded.size()); - stuffer.append(coded); - coded = temp; - } - - const size_t TOP_BITS = 8 * ((key_bits + 7) / 8) - key_bits; - if(TOP_BITS > 8 - high_bit(coded[0])) { - return false; - } - - uint8_t* DB = coded.data(); - const size_t DB_size = coded.size() - HASH_SIZE - 1; - - const uint8_t* H = &coded[DB_size]; - const size_t H_size = HASH_SIZE; - - mgf1_mask(hash, H, H_size, DB, DB_size); - DB[0] &= 0xFF >> TOP_BITS; - - size_t salt_offset = 0; - for(size_t j = 0; j != DB_size; ++j) { - if(DB[j] == 0x01) { - salt_offset = j + 1; - break; - } - if(DB[j]) { - return false; - } - } - if(salt_offset == 0) { - return false; - } - - const size_t salt_size = DB_size - salt_offset; - - std::array padding = {0}; - hash.update(padding); - hash.update(message_hash); - hash.update(&DB[salt_offset], salt_size); - - const std::vector H2 = hash.final_stdvec(); - - const bool ok = CT::is_equal(H, H2.data(), HASH_SIZE).as_bool(); - - if(out_salt_size && ok) { - *out_salt_size = salt_size; - } - - return ok; -} - -} // namespace - -PSSR::PSSR(std::unique_ptr hash) : - m_hash(std::move(hash)), m_salt_size(m_hash->output_length()), m_required_salt_len(false) {} - -PSSR::PSSR(std::unique_ptr hash, size_t salt_size) : - m_hash(std::move(hash)), m_salt_size(salt_size), m_required_salt_len(true) {} - -/* -* PSSR Update Operation -*/ -void PSSR::update(const uint8_t input[], size_t length) { - m_hash->update(input, length); -} - -/* -* Return the raw (unencoded) data -*/ -std::vector PSSR::raw_data() { - return m_hash->final_stdvec(); -} - -std::vector PSSR::encoding_of(const std::vector& msg, - size_t output_bits, - RandomNumberGenerator& rng) { - const auto salt = rng.random_vec>(m_salt_size); - return pss_encode(*m_hash, msg, salt, output_bits); -} - -/* -* PSSR Decode/Verify Operation -*/ -bool PSSR::verify(const std::vector& coded, const std::vector& raw, size_t key_bits) { - size_t salt_size = 0; - const bool ok = pss_verify(*m_hash, coded, raw, key_bits, &salt_size); - - if(m_required_salt_len && salt_size != m_salt_size) { - return false; - } - - return ok; -} - -std::string PSSR::name() const { - return fmt("PSS({},MGF1,{})", m_hash->name(), m_salt_size); -} - -PSSR_Raw::PSSR_Raw(std::unique_ptr hash) : - m_hash(std::move(hash)), m_salt_size(m_hash->output_length()), m_required_salt_len(false) {} - -PSSR_Raw::PSSR_Raw(std::unique_ptr hash, size_t salt_size) : - m_hash(std::move(hash)), m_salt_size(salt_size), m_required_salt_len(true) {} - -/* -* PSSR_Raw Update Operation -*/ -void PSSR_Raw::update(const uint8_t input[], size_t length) { - m_msg.insert(m_msg.end(), input, input + length); -} - -/* -* Return the raw (unencoded) data -*/ -std::vector PSSR_Raw::raw_data() { - std::vector ret; - std::swap(ret, m_msg); - - if(ret.size() != m_hash->output_length()) { - throw Encoding_Error("PSSR_Raw Bad input length, did not match hash"); - } - - return ret; -} - -std::vector PSSR_Raw::encoding_of(const std::vector& msg, - size_t output_bits, - RandomNumberGenerator& rng) { - const auto salt = rng.random_vec>(m_salt_size); - return pss_encode(*m_hash, msg, salt, output_bits); -} - -/* -* PSSR_Raw Decode/Verify Operation -*/ -bool PSSR_Raw::verify(const std::vector& coded, const std::vector& raw, size_t key_bits) { - size_t salt_size = 0; - const bool ok = pss_verify(*m_hash, coded, raw, key_bits, &salt_size); - - if(m_required_salt_len && salt_size != m_salt_size) { - return false; - } - - return ok; -} - -std::string PSSR_Raw::name() const { - return fmt("PSS_Raw({},MGF1,{})", m_hash->name(), m_salt_size); -} - -} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/emsa_pssr/pssr.h botan3-3.12.0+dfsg/src/lib/pk_pad/emsa_pssr/pssr.h --- botan3-3.7.1+dfsg/src/lib/pk_pad/emsa_pssr/pssr.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/emsa_pssr/pssr.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,92 +0,0 @@ -/* -* PSSR -* (C) 1999-2007 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#ifndef BOTAN_PSSR_H_ -#define BOTAN_PSSR_H_ - -#include -#include - -namespace Botan { - -/** -* PSSR (called EMSA4 in IEEE 1363 and in old versions of the library) -*/ -class PSSR final : public EMSA { - public: - /** - * @param hash the hash function to use - */ - explicit PSSR(std::unique_ptr hash); - - /** - * @param hash the hash function to use - * @param salt_size the size of the salt to use in bytes - */ - PSSR(std::unique_ptr hash, size_t salt_size); - - std::string name() const override; - - std::string hash_function() const override { return m_hash->name(); } - - private: - void update(const uint8_t input[], size_t length) override; - - std::vector raw_data() override; - - std::vector encoding_of(const std::vector& msg, - size_t output_bits, - RandomNumberGenerator& rng) override; - - bool verify(const std::vector& coded, const std::vector& raw, size_t key_bits) override; - - std::unique_ptr m_hash; - size_t m_salt_size; - bool m_required_salt_len; -}; - -/** -* PSSR_Raw -* This accepts a pre-hashed buffer -*/ -class PSSR_Raw final : public EMSA { - public: - /** - * @param hash the hash function to use - */ - explicit PSSR_Raw(std::unique_ptr hash); - - /** - * @param hash the hash function to use - * @param salt_size the size of the salt to use in bytes - */ - PSSR_Raw(std::unique_ptr hash, size_t salt_size); - - std::string hash_function() const override { return m_hash->name(); } - - std::string name() const override; - - private: - void update(const uint8_t input[], size_t length) override; - - std::vector raw_data() override; - - std::vector encoding_of(const std::vector& msg, - size_t output_bits, - RandomNumberGenerator& rng) override; - - bool verify(const std::vector& coded, const std::vector& raw, size_t key_bits) override; - - std::unique_ptr m_hash; - std::vector m_msg; - size_t m_salt_size; - bool m_required_salt_len; -}; - -} // namespace Botan - -#endif diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/emsa_raw/emsa_raw.cpp botan3-3.12.0+dfsg/src/lib/pk_pad/emsa_raw/emsa_raw.cpp --- botan3-3.7.1+dfsg/src/lib/pk_pad/emsa_raw/emsa_raw.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/emsa_raw/emsa_raw.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,92 +0,0 @@ -/* -* EMSA-Raw -* (C) 1999-2007 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include - -#include -#include -#include - -namespace Botan { - -std::string EMSA_Raw::name() const { - if(m_expected_size > 0) { - return "Raw(" + std::to_string(m_expected_size) + ")"; - } - return "Raw"; -} - -/* -* EMSA-Raw Encode Operation -*/ -void EMSA_Raw::update(const uint8_t input[], size_t length) { - m_message += std::make_pair(input, length); -} - -/* -* Return the raw (unencoded) data -*/ -std::vector EMSA_Raw::raw_data() { - if(m_expected_size && m_message.size() != m_expected_size) { - throw Invalid_Argument("EMSA_Raw was configured to use a " + std::to_string(m_expected_size) + - " byte hash but instead was used for a " + std::to_string(m_message.size()) + " hash"); - } - - std::vector output; - std::swap(m_message, output); - return output; -} - -/* -* EMSA-Raw Encode Operation -*/ -std::vector EMSA_Raw::encoding_of(const std::vector& msg, - size_t /*output_bits*/, - RandomNumberGenerator& /*rng*/) { - if(m_expected_size && msg.size() != m_expected_size) { - throw Invalid_Argument("EMSA_Raw was configured to use a " + std::to_string(m_expected_size) + - " byte hash but instead was used for a " + std::to_string(msg.size()) + " hash"); - } - - return msg; -} - -/* -* EMSA-Raw Verify Operation -*/ -bool EMSA_Raw::verify(const std::vector& coded, const std::vector& raw, size_t /*key_bits*/) { - if(m_expected_size && raw.size() != m_expected_size) { - return false; - } - - if(coded.size() == raw.size()) { - return (coded == raw); - } - - if(coded.size() > raw.size()) { - return false; - } - - // handle zero padding differences - const size_t leading_zeros_expected = raw.size() - coded.size(); - - bool same_modulo_leading_zeros = true; - - for(size_t i = 0; i != leading_zeros_expected; ++i) { - if(raw[i]) { - same_modulo_leading_zeros = false; - } - } - - if(!CT::is_equal(coded.data(), raw.data() + leading_zeros_expected, coded.size()).as_bool()) { - same_modulo_leading_zeros = false; - } - - return same_modulo_leading_zeros; -} - -} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/emsa_raw/emsa_raw.h botan3-3.12.0+dfsg/src/lib/pk_pad/emsa_raw/emsa_raw.h --- botan3-3.7.1+dfsg/src/lib/pk_pad/emsa_raw/emsa_raw.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/emsa_raw/emsa_raw.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,41 +0,0 @@ -/* -* EMSA-Raw -* (C) 1999-2007 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#ifndef BOTAN_EMSA_RAW_H_ -#define BOTAN_EMSA_RAW_H_ - -#include - -namespace Botan { - -/** -* EMSA-Raw - sign inputs directly -* Don't use this unless you know what you are doing. -*/ -class EMSA_Raw final : public EMSA { - public: - explicit EMSA_Raw(size_t expected_hash_size = 0) : m_expected_size(expected_hash_size) {} - - std::string hash_function() const override { return "Raw"; } - - std::string name() const override; - - private: - void update(const uint8_t[], size_t) override; - std::vector raw_data() override; - - std::vector encoding_of(const std::vector&, size_t, RandomNumberGenerator&) override; - - bool verify(const std::vector&, const std::vector&, size_t) override; - - const size_t m_expected_size; - std::vector m_message; -}; - -} // namespace Botan - -#endif diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/emsa_raw/info.txt botan3-3.12.0+dfsg/src/lib/pk_pad/emsa_raw/info.txt --- botan3-3.7.1+dfsg/src/lib/pk_pad/emsa_raw/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/emsa_raw/info.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,7 +0,0 @@ - -EMSA_RAW -> 20131128 - - - -name -> "EMSA Raw Padding" - diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/emsa_x931/emsa_x931.cpp botan3-3.12.0+dfsg/src/lib/pk_pad/emsa_x931/emsa_x931.cpp --- botan3-3.7.1+dfsg/src/lib/pk_pad/emsa_x931/emsa_x931.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/emsa_x931/emsa_x931.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,97 +0,0 @@ -/* -* EMSA_X931 -* (C) 1999-2007 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include - -#include -#include -#include -#include - -namespace Botan { - -namespace { - -std::vector emsa2_encoding(const std::vector& msg, - size_t output_bits, - const std::vector& empty_hash, - uint8_t hash_id) { - const size_t HASH_SIZE = empty_hash.size(); - - const size_t output_length = (output_bits + 1) / 8; - - if(msg.size() != HASH_SIZE) { - throw Encoding_Error("EMSA_X931::encoding_of: Bad input length"); - } - if(output_length < HASH_SIZE + 4) { - throw Encoding_Error("EMSA_X931::encoding_of: Output length is too small"); - } - - const bool empty_input = (msg == empty_hash); - - std::vector output(output_length); - BufferStuffer stuffer(output); - - stuffer.append(empty_input ? 0x4B : 0x6B); - stuffer.append(0xBB, stuffer.remaining_capacity() - (1 + msg.size() + 2)); - stuffer.append(0xBA); - stuffer.append(msg); - stuffer.append(hash_id); - stuffer.append(0xCC); - BOTAN_ASSERT_NOMSG(stuffer.full()); - - return output; -} - -} // namespace - -std::string EMSA_X931::name() const { - return fmt("X9.31({})", m_hash->name()); -} - -void EMSA_X931::update(const uint8_t input[], size_t length) { - m_hash->update(input, length); -} - -std::vector EMSA_X931::raw_data() { - return m_hash->final_stdvec(); -} - -/* -* EMSA_X931 Encode Operation -*/ -std::vector EMSA_X931::encoding_of(const std::vector& msg, - size_t output_bits, - RandomNumberGenerator& /*rng*/) { - return emsa2_encoding(msg, output_bits, m_empty_hash, m_hash_id); -} - -/* -* EMSA_X931 Verify Operation -*/ -bool EMSA_X931::verify(const std::vector& coded, const std::vector& raw, size_t key_bits) { - try { - return (coded == emsa2_encoding(raw, key_bits, m_empty_hash, m_hash_id)); - } catch(...) { - return false; - } -} - -/* -* EMSA_X931 Constructor -*/ -EMSA_X931::EMSA_X931(std::unique_ptr hash) : m_hash(std::move(hash)) { - m_empty_hash = m_hash->final_stdvec(); - - m_hash_id = ieee1363_hash_id(m_hash->name()); - - if(!m_hash_id) { - throw Encoding_Error("EMSA_X931 no hash identifier for " + m_hash->name()); - } -} - -} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/emsa_x931/emsa_x931.h botan3-3.12.0+dfsg/src/lib/pk_pad/emsa_x931/emsa_x931.h --- botan3-3.7.1+dfsg/src/lib/pk_pad/emsa_x931/emsa_x931.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/emsa_x931/emsa_x931.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,47 +0,0 @@ -/* -* X9.31 EMSA -* (C) 1999-2007 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#ifndef BOTAN_EMSA_X931_H_ -#define BOTAN_EMSA_X931_H_ - -#include -#include - -namespace Botan { - -/** -* EMSA from X9.31 (EMSA2 in IEEE 1363) -* Useful for Rabin-Williams, also sometimes used with RSA in -* odd protocols. -*/ -class EMSA_X931 final : public EMSA { - public: - /** - * @param hash the hash function to use - */ - explicit EMSA_X931(std::unique_ptr hash); - - std::string name() const override; - - std::string hash_function() const override { return m_hash->name(); } - - private: - void update(const uint8_t[], size_t) override; - std::vector raw_data() override; - - std::vector encoding_of(const std::vector&, size_t, RandomNumberGenerator& rng) override; - - bool verify(const std::vector&, const std::vector&, size_t) override; - - std::vector m_empty_hash; - std::unique_ptr m_hash; - uint8_t m_hash_id; -}; - -} // namespace Botan - -#endif diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/emsa_x931/info.txt botan3-3.12.0+dfsg/src/lib/pk_pad/emsa_x931/info.txt --- botan3-3.7.1+dfsg/src/lib/pk_pad/emsa_x931/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/emsa_x931/info.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,11 +0,0 @@ - -EMSA_X931 -> 20140118 - - - -name -> "X9.31" - - - -hash_id - diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/enc_padding/eme_oaep/info.txt botan3-3.12.0+dfsg/src/lib/pk_pad/enc_padding/eme_oaep/info.txt --- botan3-3.7.1+dfsg/src/lib/pk_pad/enc_padding/eme_oaep/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/enc_padding/eme_oaep/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,12 @@ + +EME_OAEP -> 20180305 +OAEP -> 20250130 + + + +name -> "OAEP" + + + +mgf1 + diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/enc_padding/eme_oaep/oaep.cpp botan3-3.12.0+dfsg/src/lib/pk_pad/enc_padding/eme_oaep/oaep.cpp --- botan3-3.7.1+dfsg/src/lib/pk_pad/enc_padding/eme_oaep/oaep.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/enc_padding/eme_oaep/oaep.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,159 @@ +/* +* OAEP +* (C) 1999-2010,2015,2018,2024 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include +#include + +namespace Botan { + +/* +* OAEP Pad Operation +*/ +size_t OAEP::pad(std::span output, + std::span input, + size_t key_length, + RandomNumberGenerator& rng) const { + key_length /= 8; + + if(input.size() > maximum_input_size(key_length * 8)) { + throw Invalid_Argument("OAEP: Input is too large"); + } + + const size_t output_size = key_length; + + output = output.first(output_size); // remainder ignored + + BufferStuffer stuffer(output); + + // We always use a seed len equal to the underlying hash + rng.randomize(stuffer.next(m_Phash.size())); + stuffer.append(m_Phash); + stuffer.append(0x00, stuffer.remaining_capacity() - (1 + input.size())); + stuffer.append(0x01); + stuffer.append(input); + BOTAN_ASSERT_NOMSG(stuffer.full()); + + const size_t hlen = m_Phash.size(); + + mgf1_mask(*m_mgf1_hash, output.first(hlen), output.subspan(hlen)); + + mgf1_mask(*m_mgf1_hash, output.subspan(hlen), output.first(hlen)); + + return key_length; +} + +/* +* OAEP Unpad Operation +*/ +CT::Option OAEP::unpad(std::span output, std::span input) const { + BOTAN_ASSERT_NOMSG(output.size() >= input.size()); + + /* + Must be careful about error messages here; if an attacker can + distinguish them, it is easy to use the differences as an oracle to + find the secret key, as described in "A Chosen Ciphertext Attack on + RSA Optimal Asymmetric Encryption Padding (OAEP) as Standardized in + PKCS #1 v2.0", James Manger, Crypto 2001 + + Also have to be careful about timing attacks! Pointed out by Falko + Strenzke. + + According to the standard (RFC 3447 Section 7.1.1), the encryptor always + creates a message as follows: + i. Concatenate a single octet with hexadecimal value 0x00, + maskedSeed, and maskedDB to form an encoded message EM of + length k octets as + EM = 0x00 || maskedSeed || maskedDB. + where k is the length of the modulus N. + Therefore, the first byte should always be zero. + */ + + const size_t hlen = m_Phash.size(); + + if(input.size() < 1 + 2 * hlen + 1) { + return {}; + } + + auto scope = CT::scoped_poison(input); + + const auto has_leading_0 = CT::Mask::is_zero(input[0]).as_choice(); + + secure_vector decoded(input.begin() + 1, input.end()); + auto buf = std::span{decoded}; + + mgf1_mask(*m_mgf1_hash, buf.subspan(hlen), buf.first(hlen)); + + mgf1_mask(*m_mgf1_hash, buf.first(hlen), buf.subspan(hlen)); + + auto delim = oaep_find_delim(buf, m_Phash); + + return CT::copy_output(delim.has_value() && has_leading_0, output, buf, delim.value_or(0)); +} + +CT::Option oaep_find_delim(std::span input, std::span phash) { + // Too short to be valid, reject immediately + if(input.size() < 1 + 2 * phash.size()) { + return {}; + } + + size_t delim_idx = 2 * phash.size(); + CT::Mask waiting_for_delim = CT::Mask::set(); + CT::Mask bad_input_m = CT::Mask::cleared(); + + for(const uint8_t ib : input.subspan(2 * phash.size())) { + const auto zero_m = CT::Mask::is_zero(ib); + const auto one_m = CT::Mask::is_equal(ib, 1); + + const auto add_m = waiting_for_delim & zero_m; + + bad_input_m |= waiting_for_delim & ~(zero_m | one_m); + + delim_idx += add_m.if_set_return(1); + + waiting_for_delim &= zero_m; + } + + // If we never saw any non-zero byte, then it's not valid input + bad_input_m |= waiting_for_delim; + + // If the P hash is wrong, then it's not valid + bad_input_m |= CT::is_not_equal(&input[phash.size()], phash.data(), phash.size()); + + delim_idx += 1; + + const auto accept = !(bad_input_m.as_choice()); + + return CT::Option(delim_idx, accept); +} + +/* +* Return the max input size for a given key size +*/ +size_t OAEP::maximum_input_size(size_t keybits) const { + if(keybits / 8 > 2 * m_Phash.size() + 1) { + return ((keybits / 8) - 2 * m_Phash.size() - 1); + } else { + return 0; + } +} + +OAEP::OAEP(std::unique_ptr hash, std::string_view P) : m_mgf1_hash(std::move(hash)) { + m_Phash = m_mgf1_hash->process(P); +} + +OAEP::OAEP(std::unique_ptr hash, std::unique_ptr mgf1_hash, std::string_view P) : + m_mgf1_hash(std::move(mgf1_hash)) { + auto phash = std::move(hash); + m_Phash = phash->process(P); +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/enc_padding/eme_oaep/oaep.h botan3-3.12.0+dfsg/src/lib/pk_pad/enc_padding/eme_oaep/oaep.h --- botan3-3.7.1+dfsg/src/lib/pk_pad/enc_padding/eme_oaep/oaep.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/enc_padding/eme_oaep/oaep.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,55 @@ +/* +* OAEP +* (C) 1999-2007,2018,2024 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_OAEP_H_ +#define BOTAN_OAEP_H_ + +#include + +#include +#include + +namespace Botan { + +/** +* OAEP (called EME1 in IEEE 1363 and in earlier versions of the library) +* as specified in PKCS#1 v2.0 (RFC 2437) or PKCS#1 v2.1 (RFC 3447) +*/ +class OAEP final : public EncryptionPaddingScheme { + public: + size_t maximum_input_size(size_t keybits) const override; + + /** + * @param hash function to use for hashing (takes ownership) + * @param P an optional label. Normally empty. + */ + explicit OAEP(std::unique_ptr hash, std::string_view P = ""); + + /** + * @param hash function to use for hashing (takes ownership) + * @param mgf1_hash function to use for MGF1 (takes ownership) + * @param P an optional label. Normally empty. + */ + OAEP(std::unique_ptr hash, std::unique_ptr mgf1_hash, std::string_view P = ""); + + private: + size_t pad(std::span output, + std::span input, + size_t key_length, + RandomNumberGenerator& rng) const override; + + CT::Option unpad(std::span output, std::span input) const override; + + secure_vector m_Phash; + std::unique_ptr m_mgf1_hash; +}; + +BOTAN_FUZZER_API CT::Option oaep_find_delim(std::span input, std::span phash); + +} // namespace Botan + +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/enc_padding/eme_pkcs1/eme_pkcs.cpp botan3-3.12.0+dfsg/src/lib/pk_pad/enc_padding/eme_pkcs1/eme_pkcs.cpp --- botan3-3.7.1+dfsg/src/lib/pk_pad/enc_padding/eme_pkcs1/eme_pkcs.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/enc_padding/eme_pkcs1/eme_pkcs.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,105 @@ +/* +* PKCS #1 v1.5 Type 2 (encryption) padding +* (C) 1999-2007,2015,2016,2024 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include +#include + +namespace Botan { + +/* +* PKCS1 Pad Operation +*/ +size_t EME_PKCS1v15::pad(std::span output, + std::span input, + size_t key_length, + RandomNumberGenerator& rng) const { + key_length /= 8; + + if(input.size() > maximum_input_size(key_length * 8)) { + throw Invalid_Argument("PKCS1: Input is too large"); + } + + BufferStuffer stuffer(output); + + const size_t padding_bytes = [&]() { + auto d = checked_sub(key_length, input.size() + 2); + BOTAN_ASSERT_NOMSG(d.has_value()); + return *d; + }(); + + stuffer.append(0x02); + for(size_t i = 0; i != padding_bytes; ++i) { + stuffer.append(rng.next_nonzero_byte()); + } + stuffer.append(0x00); + stuffer.append(input); + + return output.size() - stuffer.remaining_capacity(); +} + +/* +* PKCS1 Unpad Operation +*/ +CT::Option EME_PKCS1v15::unpad(std::span output, std::span input) const { + BOTAN_ASSERT_NOMSG(output.size() >= input.size()); + + /* + * RSA decryption pads the ciphertext up to the modulus size, so this only + * occurs with very (!) small keys, or when fuzzing. + * + * 11 bytes == 00,02 + 8 bytes mandatory padding + 00 + */ + if(input.size() < 11) { + return {}; + } + + auto scope = CT::scoped_poison(input); + + CT::Mask bad_input_m = CT::Mask::cleared(); + CT::Mask seen_zero_m = CT::Mask::cleared(); + size_t delim_idx = 2; // initial 0002 + + bad_input_m |= ~CT::Mask::is_equal(input[0], 0); + bad_input_m |= ~CT::Mask::is_equal(input[1], 2); + + for(size_t i = 2; i < input.size(); ++i) { + const auto is_zero_m = CT::Mask::is_zero(input[i]); + delim_idx += seen_zero_m.if_not_set_return(1); + seen_zero_m |= is_zero_m; + } + + // no zero delim -> bad padding + bad_input_m |= ~seen_zero_m; + /* + delim indicates < 8 bytes padding -> bad padding + + We require 11 here because we are counting also the 00 delim byte + */ + bad_input_m |= CT::Mask(CT::Mask::is_lt(delim_idx, 11)); + + const CT::Choice accept = !(bad_input_m.as_choice()); + + return CT::copy_output(accept, output, input, delim_idx); +} + +/* +* Return the max input size for a given key size +*/ +size_t EME_PKCS1v15::maximum_input_size(size_t keybits) const { + if(keybits / 8 > 10) { + return ((keybits / 8) - 10); + } else { + return 0; + } +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/enc_padding/eme_pkcs1/eme_pkcs.h botan3-3.12.0+dfsg/src/lib/pk_pad/enc_padding/eme_pkcs1/eme_pkcs.h --- botan3-3.7.1+dfsg/src/lib/pk_pad/enc_padding/eme_pkcs1/eme_pkcs.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/enc_padding/eme_pkcs1/eme_pkcs.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,32 @@ +/* +* EME PKCS#1 v1.5 +* (C) 1999-2007 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_EME_PKCS1_H_ +#define BOTAN_EME_PKCS1_H_ + +#include + +namespace Botan { + +/** +* EME from PKCS #1 v1.5 +*/ +class BOTAN_FUZZER_API EME_PKCS1v15 final : public EncryptionPaddingScheme { + private: + size_t maximum_input_size(size_t keybits) const override; + + size_t pad(std::span output, + std::span input, + size_t key_length, + RandomNumberGenerator& rng) const override; + + CT::Option unpad(std::span output, std::span input) const override; +}; + +} // namespace Botan + +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/enc_padding/eme_pkcs1/info.txt botan3-3.12.0+dfsg/src/lib/pk_pad/enc_padding/eme_pkcs1/info.txt --- botan3-3.7.1+dfsg/src/lib/pk_pad/enc_padding/eme_pkcs1/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/enc_padding/eme_pkcs1/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,11 @@ + +PKCSV15_ENCRYPTION_PADDING -> 20250126 + +# TODO(Botan4) remove these macro +EME_PKCS1v15 -> 20131128 +EME_PKCS1 -> 20190426 + + + +name -> "PKCS #1 v1.5 encryption padding" + diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/enc_padding/eme_raw/eme_raw.cpp botan3-3.12.0+dfsg/src/lib/pk_pad/enc_padding/eme_raw/eme_raw.cpp --- botan3-3.7.1+dfsg/src/lib/pk_pad/enc_padding/eme_raw/eme_raw.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/enc_padding/eme_raw/eme_raw.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,40 @@ +/* +* (C) 2015,2016,2024 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include + +namespace Botan { + +size_t EME_Raw::pad(std::span output, + std::span input, + size_t key_length, + RandomNumberGenerator& rng) const { + BOTAN_UNUSED(rng); + BOTAN_ASSERT_NOMSG(input.size() < maximum_input_size(8 * key_length)); + BOTAN_ASSERT_NOMSG(output.size() >= input.size()); + copy_mem(output.first(input.size()), input); + return input.size(); +} + +CT::Option EME_Raw::unpad(std::span output, std::span input) const { + BOTAN_ASSERT_NOMSG(output.size() >= input.size()); + + if(input.empty()) { + return CT::Option(0); + } + + const size_t leading_zeros = CT::count_leading_zero_bytes(input); + return CT::copy_output(CT::Choice::yes(), output, input, leading_zeros); +} + +size_t EME_Raw::maximum_input_size(size_t keybits) const { + return keybits / 8; +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/enc_padding/eme_raw/eme_raw.h botan3-3.12.0+dfsg/src/lib/pk_pad/enc_padding/eme_raw/eme_raw.h --- botan3-3.7.1+dfsg/src/lib/pk_pad/enc_padding/eme_raw/eme_raw.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/enc_padding/eme_raw/eme_raw.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,31 @@ +/* +* (C) 2015 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_EME_RAW_H_ +#define BOTAN_EME_RAW_H_ + +#include + +namespace Botan { + +class EME_Raw final : public EncryptionPaddingScheme { + public: + EME_Raw() = default; + + private: + size_t maximum_input_size(size_t i) const override; + + size_t pad(std::span output, + std::span input, + size_t key_length, + RandomNumberGenerator& rng) const override; + + CT::Option unpad(std::span output, std::span input) const override; +}; + +} // namespace Botan + +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/enc_padding/eme_raw/info.txt botan3-3.12.0+dfsg/src/lib/pk_pad/enc_padding/eme_raw/info.txt --- botan3-3.7.1+dfsg/src/lib/pk_pad/enc_padding/eme_raw/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/enc_padding/eme_raw/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,7 @@ + +EME_RAW -> 20150313 + + + +name -> "EME Raw Padding" + diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/enc_padding/enc_padding.cpp botan3-3.12.0+dfsg/src/lib/pk_pad/enc_padding/enc_padding.cpp --- botan3-3.7.1+dfsg/src/lib/pk_pad/enc_padding/enc_padding.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/enc_padding/enc_padding.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,70 @@ +/* +* (C) 1999-2008 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include + +#if defined(BOTAN_HAS_EME_OAEP) + #include +#endif + +#if defined(BOTAN_HAS_EME_PKCS1) + #include +#endif + +#if defined(BOTAN_HAS_EME_RAW) + #include +#endif + +namespace Botan { + +std::unique_ptr EncryptionPaddingScheme::create(std::string_view algo_spec) { +#if defined(BOTAN_HAS_EME_RAW) + if(algo_spec == "Raw") { + return std::make_unique(); + } +#endif + +#if defined(BOTAN_HAS_EME_PKCS1) + // TODO(Botan4) Remove all but "PKCS1v15" + if(algo_spec == "PKCS1v15" || algo_spec == "EME-PKCS1-v1_5") { + return std::make_unique(); + } +#endif + +#if defined(BOTAN_HAS_EME_OAEP) + const SCAN_Name req(algo_spec); + + // TODO(Botan4) Remove all but "OAEP" + if(req.algo_name() == "OAEP" || req.algo_name() == "EME-OAEP" || req.algo_name() == "EME1") { + if(req.arg_count() == 1 || ((req.arg_count() == 2 || req.arg_count() == 3) && req.arg(1) == "MGF1")) { + if(auto hash = HashFunction::create(req.arg(0))) { + return std::make_unique(std::move(hash), req.arg(2, "")); + } + } else if(req.arg_count() == 2 || req.arg_count() == 3) { + auto mgf_params = parse_algorithm_name(req.arg(1)); + + if(mgf_params.size() == 2 && mgf_params[0] == "MGF1") { + auto hash = HashFunction::create(req.arg(0)); + auto mgf1_hash = HashFunction::create(mgf_params[1]); + + if(hash && mgf1_hash) { + return std::make_unique(std::move(hash), std::move(mgf1_hash), req.arg(2, "")); + } + } + } + } +#endif + + throw Algorithm_Not_Found(algo_spec); +} + +EncryptionPaddingScheme::~EncryptionPaddingScheme() = default; + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/enc_padding/enc_padding.h botan3-3.12.0+dfsg/src/lib/pk_pad/enc_padding/enc_padding.h --- botan3-3.7.1+dfsg/src/lib/pk_pad/enc_padding/enc_padding.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/enc_padding/enc_padding.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,68 @@ +/* +* (C) 1999-2007,2024 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_PUBKEY_ENCRYPTION_PADDING_H_ +#define BOTAN_PUBKEY_ENCRYPTION_PADDING_H_ + +#include +#include +#include +#include +#include + +namespace Botan { + +class RandomNumberGenerator; + +/** +* Encoding Method for Encryption +*/ +class BOTAN_TEST_API EncryptionPaddingScheme /* NOLINT(*-special-member-functions) */ { + public: + virtual ~EncryptionPaddingScheme(); + + /** + * Factory method for encryption padding schemes + * + * @param algo_spec the name of the EncryptionPaddingScheme to create + * @return pointer to newly allocated object of that type + */ + static std::unique_ptr create(std::string_view algo_spec); + + /** + * Return the maximum input size in bytes we can support + * @param keybits the size of the key in bits + * @return upper bound of input in bytes + */ + virtual size_t maximum_input_size(size_t keybits) const = 0; + + /** + * Encode an input + * @param output buffer that is written to + * @param input the plaintext + * @param key_length length of the key in bits + * @param rng a random number generator + * @return number of bytes written to output + */ + virtual size_t pad(std::span output, + std::span input, + size_t key_length, + RandomNumberGenerator& rng) const = 0; + + /** + * Decode an input + * @param output buffer where output is placed + * @param input the encoded plaintext + * @return number of bytes written to output if valid, + * or an empty option if invalid. If an empty option is + * returned the contents of output are undefined + */ + virtual CT::Option unpad(std::span output, std::span input) const = 0; +}; + +} // namespace Botan + +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/enc_padding/info.txt botan3-3.12.0+dfsg/src/lib/pk_pad/enc_padding/info.txt --- botan3-3.7.1+dfsg/src/lib/pk_pad/enc_padding/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/enc_padding/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,13 @@ + +RSA_ENCRYPTION_PADDING -> 20250720 + + + +name -> "RSA encryption padding schemes" +brief -> "Implementations of public key encryption padding schemes" + + + +hash +rng + diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/hash_id/info.txt botan3-3.12.0+dfsg/src/lib/pk_pad/hash_id/info.txt --- botan3-3.7.1+dfsg/src/lib/pk_pad/hash_id/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/hash_id/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + HASH_ID -> 20131128 - + name -> "Hash Function Identification" diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/info.txt botan3-3.12.0+dfsg/src/lib/pk_pad/info.txt --- botan3-3.7.1+dfsg/src/lib/pk_pad/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/info.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,13 +0,0 @@ - -PK_PADDING -> 20131128 - - - -name -> "Public Key Paddings" -brief -> "Implementations of public key padding schemes" - - - -hash -rng - diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/iso9796/info.txt botan3-3.12.0+dfsg/src/lib/pk_pad/iso9796/info.txt --- botan3-3.7.1+dfsg/src/lib/pk_pad/iso9796/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/iso9796/info.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,13 +0,0 @@ - -ISO_9796 -> 20161121 - - - -name -> "ISO-9796-2" - - - -mgf1 -hash_id - - diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/iso9796/iso9796.cpp botan3-3.12.0+dfsg/src/lib/pk_pad/iso9796/iso9796.cpp --- botan3-3.7.1+dfsg/src/lib/pk_pad/iso9796/iso9796.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/iso9796/iso9796.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,281 +0,0 @@ -/* - * ISO-9796-2 - Digital signature schemes giving message recovery schemes 2 and 3 - * (C) 2016 Tobias Niemann, Hackmanit GmbH - * - * Botan is released under the Simplified BSD License (see license.txt) - */ - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace Botan { - -namespace { - -std::vector iso9796_encoding(const std::vector& msg, - size_t output_bits, - std::unique_ptr& hash, - size_t SALT_SIZE, - bool implicit, - RandomNumberGenerator& rng) { - const size_t output_length = (output_bits + 7) / 8; - - //set trailer length - const size_t tLength = (implicit) ? 1 : 2; - - const size_t HASH_SIZE = hash->output_length(); - - if(output_length <= HASH_SIZE + SALT_SIZE + tLength) { - throw Encoding_Error("ISO9796-2::encoding_of: Output length is too small"); - } - - //calculate message capacity - const size_t capacity = output_length - HASH_SIZE - SALT_SIZE - tLength - 1; - - //msg1 is the recoverable and hmsg2 is the hash of the unrecoverable message part. - std::vector msg1; - if(msg.size() > capacity) { - msg1 = std::vector(msg.begin(), msg.begin() + capacity); - hash->update(std::span(msg).subspan(capacity)); - } else { - msg1 = msg; - } - const std::vector hmsg2 = hash->final_stdvec(); - - //compute H(C||msg1 ||H(msg2)||S) - const size_t msgLength = msg1.size(); - const auto salt = rng.random_vec>(SALT_SIZE); - hash->update_be(static_cast(msgLength) * 8); - hash->update(msg1); - hash->update(hmsg2); - hash->update(salt); - const std::vector H = hash->final_stdvec(); - - std::vector EM(output_length); - - BufferStuffer stuffer(EM); - stuffer.append(0x00, stuffer.remaining_capacity() - (HASH_SIZE + SALT_SIZE + tLength + msgLength + 1)); - stuffer.append(0x01); - stuffer.append(msg1); - stuffer.append(salt); - - //apply mask - mgf1_mask(*hash, H.data(), HASH_SIZE, EM.data(), output_length - HASH_SIZE - tLength); - - //clear the leftmost bit (confer bouncy castle) - EM[0] &= 0x7F; - - stuffer.append(H); - - // set implicit/ISO trailer - - if(implicit) { - stuffer.append(0xBC); - } else { - const uint8_t hash_id = ieee1363_hash_id(hash->name()); - if(!hash_id) { - throw Encoding_Error("ISO9796-2::encoding_of: no hash identifier for " + hash->name()); - } - stuffer.append(hash_id); - stuffer.append(0xCC); - } - - BOTAN_ASSERT_NOMSG(stuffer.full()); - - return EM; -} - -bool iso9796_verification(const std::vector& const_coded, - const std::vector& raw, - size_t key_bits, - std::unique_ptr& hash, - size_t SALT_SIZE) { - const size_t HASH_SIZE = hash->output_length(); - const size_t KEY_BYTES = (key_bits + 7) / 8; - - if(const_coded.size() != KEY_BYTES) { - return false; - } - //get trailer length - size_t tLength; - if(const_coded[const_coded.size() - 1] == 0xBC) { - tLength = 1; - } else { - uint8_t hash_id = ieee1363_hash_id(hash->name()); - if((!const_coded[const_coded.size() - 2]) || (const_coded[const_coded.size() - 2] != hash_id) || - (const_coded[const_coded.size() - 1] != 0xCC)) { - return false; //in case of wrong ISO trailer. - } - tLength = 2; - } - - std::vector coded = const_coded; - - CT::poison(coded.data(), coded.size()); - //remove mask - uint8_t* DB = coded.data(); - const size_t DB_size = coded.size() - HASH_SIZE - tLength; - - const uint8_t* H = &coded[DB_size]; - - mgf1_mask(*hash, H, HASH_SIZE, DB, DB_size); - //clear the leftmost bit (confer bouncy castle) - DB[0] &= 0x7F; - - //recover msg1 and salt - size_t msg1_offset = 1; - - auto waiting_for_delim = CT::Mask::set(); - auto bad_input = CT::Mask::cleared(); - - for(size_t j = 0; j < DB_size; ++j) { - const auto is_zero = CT::Mask::is_zero(DB[j]); - const auto is_one = CT::Mask::is_equal(DB[j], 0x01); - - const auto add_m = waiting_for_delim & is_zero; - - bad_input |= waiting_for_delim & ~(is_zero | is_one); - msg1_offset += add_m.if_set_return(1); - - waiting_for_delim &= is_zero; - } - - //invalid, if delimiter 0x01 was not found or msg1_offset is too big - bad_input |= waiting_for_delim; - bad_input |= CT::Mask::is_lt(coded.size(), tLength + HASH_SIZE + msg1_offset + SALT_SIZE); - - //in case that msg1_offset is too big, just continue with offset = 0. - msg1_offset = CT::Mask::expand(bad_input.value()).if_not_set_return(msg1_offset); - - CT::unpoison(coded.data(), coded.size()); - CT::unpoison(msg1_offset); - - std::vector msg1(coded.begin() + msg1_offset, coded.end() - tLength - HASH_SIZE - SALT_SIZE); - std::vector salt(coded.begin() + msg1_offset + msg1.size(), coded.end() - tLength - HASH_SIZE); - - //compute H2(C||msg1||H(msg2)||S*). * indicates a recovered value - const size_t capacity = (key_bits - 2 + 7) / 8 - HASH_SIZE - SALT_SIZE - tLength - 1; - std::vector msg1raw; - if(raw.size() > capacity) { - msg1raw = std::vector(raw.begin(), raw.begin() + capacity); - hash->update(std::span(raw).subspan(capacity)); - } else { - msg1raw = raw; - } - const std::vector hmsg2 = hash->final_stdvec(); - - const uint64_t msg1rawLength = msg1raw.size(); - hash->update_be(msg1rawLength * 8); - hash->update(msg1raw); - hash->update(hmsg2); - hash->update(salt); - std::vector H3 = hash->final_stdvec(); - - //compute H3(C*||msg1*||H(msg2)||S*) * indicates a recovered value - const uint64_t msgLength = msg1.size(); - hash->update_be(msgLength * 8); - hash->update(msg1); - hash->update(hmsg2); - hash->update(salt); - std::vector H2 = hash->final_stdvec(); - - //check if H3 == H2 - bad_input |= CT::is_not_equal(H3.data(), H2.data(), HASH_SIZE); - - CT::unpoison(bad_input); - return (bad_input.as_bool() == false); -} - -} // namespace - -/* - * ISO-9796-2 signature scheme 2 - * DS 2 is probabilistic - */ -void ISO_9796_DS2::update(const uint8_t input[], size_t length) { - //need to buffer message completely, before digest - m_msg_buffer.insert(m_msg_buffer.end(), input, input + length); -} - -/* - * Return the raw (unencoded) data - */ -std::vector ISO_9796_DS2::raw_data() { - std::vector retbuffer = m_msg_buffer; - m_msg_buffer.clear(); - return retbuffer; -} - -/* - * ISO-9796-2 scheme 2 encode operation - */ -std::vector ISO_9796_DS2::encoding_of(const std::vector& msg, - size_t output_bits, - RandomNumberGenerator& rng) { - return iso9796_encoding(msg, output_bits, m_hash, m_SALT_SIZE, m_implicit, rng); -} - -/* - * ISO-9796-2 scheme 2 verify operation - */ -bool ISO_9796_DS2::verify(const std::vector& const_coded, const std::vector& raw, size_t key_bits) { - return iso9796_verification(const_coded, raw, key_bits, m_hash, m_SALT_SIZE); -} - -/* - * Return the SCAN name - */ -std::string ISO_9796_DS2::name() const { - return fmt("ISO_9796_DS2({},{},{})", m_hash->name(), (m_implicit ? "imp" : "exp"), m_SALT_SIZE); -} - -/* - * ISO-9796-2 signature scheme 3 - * DS 3 is deterministic and equals DS2 without salt - */ -void ISO_9796_DS3::update(const uint8_t input[], size_t length) { - //need to buffer message completely, before digest - m_msg_buffer.insert(m_msg_buffer.end(), input, input + length); -} - -/* - * Return the raw (unencoded) data - */ -std::vector ISO_9796_DS3::raw_data() { - std::vector retbuffer = m_msg_buffer; - m_msg_buffer.clear(); - return retbuffer; -} - -/* - * ISO-9796-2 scheme 3 encode operation - */ -std::vector ISO_9796_DS3::encoding_of(const std::vector& msg, - size_t output_bits, - RandomNumberGenerator& rng) { - return iso9796_encoding(msg, output_bits, m_hash, 0, m_implicit, rng); -} - -/* - * ISO-9796-2 scheme 3 verify operation - */ -bool ISO_9796_DS3::verify(const std::vector& const_coded, const std::vector& raw, size_t key_bits) { - return iso9796_verification(const_coded, raw, key_bits, m_hash, 0); -} - -/* - * Return the SCAN name - */ -std::string ISO_9796_DS3::name() const { - return fmt("ISO_9796_DS3({},{})", m_hash->name(), (m_implicit ? "imp" : "exp")); -} - -} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/iso9796/iso9796.h botan3-3.12.0+dfsg/src/lib/pk_pad/iso9796/iso9796.h --- botan3-3.7.1+dfsg/src/lib/pk_pad/iso9796/iso9796.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/iso9796/iso9796.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,91 +0,0 @@ -/* - * ISO-9796-2 - Digital signature schemes giving message recovery schemes 2 and 3 - * (C) 2016 Tobias Niemann, Hackmanit GmbH - * - * Botan is released under the Simplified BSD License (see license.txt) - */ - -#ifndef BOTAN_ISO9796_H_ -#define BOTAN_ISO9796_H_ - -#include -#include - -namespace Botan { - -/** -* ISO-9796-2 - Digital signature scheme 2 (probabilistic) -*/ -class ISO_9796_DS2 final : public EMSA { - public: - /** - * @param hash function to use - * @param implicit whether or not the trailer is implicit - */ - explicit ISO_9796_DS2(std::unique_ptr hash, bool implicit = false) : - m_hash(std::move(hash)), m_implicit(implicit), m_SALT_SIZE(hash->output_length()) {} - - /** - * @param hash function to use - * @param implicit whether or not the trailer is implicit - * @param salt_size size of the salt to use in bytes - */ - ISO_9796_DS2(std::unique_ptr hash, bool implicit, size_t salt_size) : - m_hash(std::move(hash)), m_implicit(implicit), m_SALT_SIZE(salt_size) {} - - std::string hash_function() const override { return m_hash->name(); } - - std::string name() const override; - - private: - void update(const uint8_t input[], size_t length) override; - - std::vector raw_data() override; - - std::vector encoding_of(const std::vector& msg, - size_t output_bits, - RandomNumberGenerator& rng) override; - - bool verify(const std::vector& coded, const std::vector& raw, size_t key_bits) override; - - std::unique_ptr m_hash; - bool m_implicit; - size_t m_SALT_SIZE; - std::vector m_msg_buffer; -}; - -/** -* ISO-9796-2 - Digital signature scheme 3 (deterministic) -*/ -class ISO_9796_DS3 final : public EMSA { - public: - /** - * @param hash function to use - * @param implicit whether or not the trailer is implicit - */ - ISO_9796_DS3(std::unique_ptr hash, bool implicit = false) : - m_hash(std::move(hash)), m_implicit(implicit) {} - - std::string name() const override; - - std::string hash_function() const override { return m_hash->name(); } - - private: - void update(const uint8_t input[], size_t length) override; - - std::vector raw_data() override; - - std::vector encoding_of(const std::vector& msg, - size_t output_bits, - RandomNumberGenerator& rng) override; - - bool verify(const std::vector& coded, const std::vector& raw, size_t key_bits) override; - - std::unique_ptr m_hash; - bool m_implicit; - std::vector m_msg_buffer; -}; - -} // namespace Botan - -#endif diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/mgf1/mgf1.cpp botan3-3.12.0+dfsg/src/lib/pk_pad/mgf1/mgf1.cpp --- botan3-3.7.1+dfsg/src/lib/pk_pad/mgf1/mgf1.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/mgf1/mgf1.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,5 @@ /* -* MGF1 -* (C) 1999-2007 Jack Lloyd +* (C) 1999-2007,2025 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -8,23 +7,27 @@ #include #include +#include #include namespace Botan { -void mgf1_mask(HashFunction& hash, const uint8_t in[], size_t in_len, uint8_t out[], size_t out_len) { +void mgf1_mask(HashFunction& hash, std::span input, std::span output) { uint32_t counter = 0; - std::vector buffer(hash.output_length()); - while(out_len) { - hash.update(in, in_len); + const size_t hlen = hash.output_length(); + + BOTAN_ASSERT_NOMSG(hlen > 0); + + std::vector buffer(hlen); + while(!output.empty()) { + hash.update(input); hash.update_be(counter); - hash.final(buffer.data()); + hash.final(buffer); - const size_t xored = std::min(buffer.size(), out_len); - xor_buf(out, buffer.data(), xored); - out += xored; - out_len -= xored; + const size_t xored = std::min(buffer.size(), output.size()); + xor_buf(output.first(xored), std::span{buffer}.first(xored)); + output = output.subspan(xored); ++counter; } diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/mgf1/mgf1.h botan3-3.12.0+dfsg/src/lib/pk_pad/mgf1/mgf1.h --- botan3-3.7.1+dfsg/src/lib/pk_pad/mgf1/mgf1.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/mgf1/mgf1.h 2026-05-07 01:38:28.000000000 +0000 @@ -18,16 +18,10 @@ /** * MGF1 from PKCS #1 v2.0 * @param hash hash function to use -* @param in input buffer -* @param in_len size of the input buffer in bytes -* @param out output buffer. The buffer is XORed with the output of MGF1. -* @param out_len size of the output buffer in bytes +* @param input - the input buffer +* @param output - the output buffer. The buffer is XORed with the output of MGF1. */ -void mgf1_mask(HashFunction& hash, const uint8_t in[], size_t in_len, uint8_t out[], size_t out_len); - -inline void mgf1_mask(HashFunction& hash, std::span input, std::span output) { - mgf1_mask(hash, input.data(), input.size(), output.data(), output.size()); -} +void mgf1_mask(HashFunction& hash, std::span input, std::span output); } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/raw_hash/info.txt botan3-3.12.0+dfsg/src/lib/pk_pad/raw_hash/info.txt --- botan3-3.7.1+dfsg/src/lib/pk_pad/raw_hash/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/raw_hash/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + RAW_HASH_FN -> 20230221 - + name -> "Raw Hash Function" diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/raw_hash/raw_hash.cpp botan3-3.12.0+dfsg/src/lib/pk_pad/raw_hash/raw_hash.cpp --- botan3-3.7.1+dfsg/src/lib/pk_pad/raw_hash/raw_hash.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/raw_hash/raw_hash.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,6 +7,7 @@ #include #include +#include namespace Botan { diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/raw_hash/raw_hash.h botan3-3.12.0+dfsg/src/lib/pk_pad/raw_hash/raw_hash.h --- botan3-3.7.1+dfsg/src/lib/pk_pad/raw_hash/raw_hash.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/raw_hash/raw_hash.h 2026-05-07 01:38:28.000000000 +0000 @@ -22,7 +22,8 @@ */ class RawHashFunction final : public HashFunction { public: - RawHashFunction(std::unique_ptr hash) : RawHashFunction(hash->name(), hash->output_length()) {} + explicit RawHashFunction(std::unique_ptr hash) : + RawHashFunction(hash->name(), hash->output_length()) {} RawHashFunction(std::string_view name, size_t output_length) : m_name(name), m_output_length(output_length) {} diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/sig_padding/emsa_pkcs1/info.txt botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/emsa_pkcs1/info.txt --- botan3-3.7.1+dfsg/src/lib/pk_pad/sig_padding/emsa_pkcs1/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/emsa_pkcs1/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,12 @@ + +EMSA_PKCS1 -> 20140118 +PKCSV15_SIGNATURE_PADDING -> 20250126 + + + +name -> "PKCS #1 v1.5 signature padding" + + + +hash_id + diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/sig_padding/emsa_pkcs1/pkcs1_sig_padding.cpp botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/emsa_pkcs1/pkcs1_sig_padding.cpp --- botan3-3.7.1+dfsg/src/lib/pk_pad/sig_padding/emsa_pkcs1/pkcs1_sig_padding.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/emsa_pkcs1/pkcs1_sig_padding.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,151 @@ +/* +* PKCS #1 v1.5 signature padding +* (C) 1999-2008 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include +#include +#include + +namespace Botan { + +namespace { + +std::vector pkcs1v15_sig_encoding(std::span msg, + size_t output_bits, + std::span hash_id) { + const size_t output_length = output_bits / 8; + + if(output_length < hash_id.size() + msg.size() + 2 + 8) { + throw Encoding_Error("pkcs1v15_sig_encoding: Output length is too small"); + } + + std::vector padded(output_length); + BufferStuffer stuffer(padded); + + stuffer.append(0x01); + stuffer.append(0xFF, stuffer.remaining_capacity() - (1 + hash_id.size() + msg.size())); + stuffer.append(0x00); + stuffer.append(hash_id); + stuffer.append(msg); + BOTAN_ASSERT_NOMSG(stuffer.full()); + + return padded; +} + +} // namespace + +void PKCS1v15_SignaturePaddingScheme::update(const uint8_t input[], size_t length) { + m_hash->update(input, length); +} + +std::vector PKCS1v15_SignaturePaddingScheme::raw_data() { + return m_hash->final_stdvec(); +} + +std::vector PKCS1v15_SignaturePaddingScheme::encoding_of(std::span msg, + size_t output_bits, + RandomNumberGenerator& /*rng*/) { + if(msg.size() != m_hash->output_length()) { + throw Encoding_Error("PKCS1v15_SignaturePaddingScheme::encoding_of: Bad input length"); + } + + return pkcs1v15_sig_encoding(msg, output_bits, m_hash_id); +} + +bool PKCS1v15_SignaturePaddingScheme::verify(std::span coded, + std::span raw, + size_t key_bits) { + if(raw.size() != m_hash->output_length()) { + return false; + } + + try { + const auto pkcs1 = pkcs1v15_sig_encoding(raw, key_bits, m_hash_id); + return constant_time_compare(coded, pkcs1); + } catch(...) { + return false; + } +} + +PKCS1v15_SignaturePaddingScheme::PKCS1v15_SignaturePaddingScheme(std::unique_ptr hash) : + m_hash(std::move(hash)) { + m_hash_id = pkcs_hash_id(m_hash->name()); +} + +std::string PKCS1v15_SignaturePaddingScheme::hash_function() const { + return m_hash->name(); +} + +std::string PKCS1v15_SignaturePaddingScheme::name() const { + return fmt("PKCS1v15({})", m_hash->name()); +} + +std::string PKCS1v15_Raw_SignaturePaddingScheme::name() const { + if(m_hash_name.empty()) { + return "PKCS1v15(Raw)"; + } else { + return fmt("PKCS1v15(Raw,{})", m_hash_name); + } +} + +PKCS1v15_Raw_SignaturePaddingScheme::PKCS1v15_Raw_SignaturePaddingScheme() : m_hash_output_len(0) { + // m_hash_id, m_hash_name left empty +} + +PKCS1v15_Raw_SignaturePaddingScheme::PKCS1v15_Raw_SignaturePaddingScheme(std::string_view hash_algo) { + std::unique_ptr hash(HashFunction::create_or_throw(hash_algo)); + m_hash_id = pkcs_hash_id(hash_algo); + m_hash_name = hash->name(); + m_hash_output_len = hash->output_length(); +} + +void PKCS1v15_Raw_SignaturePaddingScheme::update(const uint8_t input[], size_t length) { + m_message += std::make_pair(input, length); + // A sanity check to prevent someone from accidentally feeding an entire message + // into PKCS1v15(Raw), which would have to be buffered in memory + if(m_message.size() > 16384 / 8) { + throw Invalid_Argument("PKCS1v15(Raw) message too long"); + } +} + +std::vector PKCS1v15_Raw_SignaturePaddingScheme::raw_data() { + std::vector ret; + std::swap(ret, m_message); + + if(m_hash_output_len > 0 && ret.size() != m_hash_output_len) { + throw Encoding_Error("PKCS1v15_Raw_SignaturePaddingScheme::encoding_of: Bad input length"); + } + + return ret; +} + +std::vector PKCS1v15_Raw_SignaturePaddingScheme::encoding_of(std::span msg, + size_t output_bits, + RandomNumberGenerator& /*rng*/) { + return pkcs1v15_sig_encoding(msg, output_bits, m_hash_id); +} + +bool PKCS1v15_Raw_SignaturePaddingScheme::verify(std::span coded, + std::span raw, + size_t key_bits) { + if(m_hash_output_len > 0 && raw.size() != m_hash_output_len) { + return false; + } + + try { + const auto pkcs1 = pkcs1v15_sig_encoding(raw, key_bits, m_hash_id); + return constant_time_compare(coded, pkcs1); + } catch(...) { + return false; + } +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/sig_padding/emsa_pkcs1/pkcs1_sig_padding.h botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/emsa_pkcs1/pkcs1_sig_padding.h --- botan3-3.7.1+dfsg/src/lib/pk_pad/sig_padding/emsa_pkcs1/pkcs1_sig_padding.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/emsa_pkcs1/pkcs1_sig_padding.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,91 @@ +/* +* PKCS #1 v1.5 signature padding +* (C) 1999-2008 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_PKCS1V15_SIGNATURE_PADDING_H_ +#define BOTAN_PKCS1V15_SIGNATURE_PADDING_H_ + +#include + +#include +#include +#include +#include + +namespace Botan { + +class HashFunction; + +/** +* PKCS #1 v1.5 signature padding +* aka PKCS #1 block type 1 +* aka EMSA3 from IEEE 1363 +*/ +class PKCS1v15_SignaturePaddingScheme final : public SignaturePaddingScheme { + public: + /** + * @param hash the hash function to use + */ + explicit PKCS1v15_SignaturePaddingScheme(std::unique_ptr hash); + + void update(const uint8_t input[], size_t length) override; + + std::vector raw_data() override; + + std::vector encoding_of(std::span msg, + size_t output_bits, + RandomNumberGenerator& rng) override; + + bool verify(std::span coded, std::span raw, size_t key_bits) override; + + std::string name() const override; + + std::string hash_function() const override; + + private: + std::unique_ptr m_hash; + std::vector m_hash_id; +}; + +/** +* PKCS1v15_SignaturePaddingScheme_Raw which is PKCS1v15_SignaturePaddingScheme without a hash or digest id +* (which according to QCA docs is "identical to PKCS#11's CKM_RSA_PKCS +* mechanism", something I have not confirmed) +*/ +class PKCS1v15_Raw_SignaturePaddingScheme final : public SignaturePaddingScheme { + public: + void update(const uint8_t input[], size_t length) override; + + std::vector raw_data() override; + + std::vector encoding_of(std::span msg, + size_t output_bits, + RandomNumberGenerator& rng) override; + + bool verify(std::span coded, std::span raw, size_t key_bits) override; + + PKCS1v15_Raw_SignaturePaddingScheme(); + + /** + * @param hash_algo the digest id for that hash is included in + * the signature. + */ + explicit PKCS1v15_Raw_SignaturePaddingScheme(std::string_view hash_algo); + + std::string hash_function() const override { return m_hash_name; } + + std::string name() const override; + + private: + size_t m_hash_output_len = 0; + std::string m_hash_name; + std::vector m_hash_id; + std::vector m_message; +}; + +} // namespace Botan + +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/sig_padding/emsa_pssr/info.txt botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/emsa_pssr/info.txt --- botan3-3.7.1+dfsg/src/lib/pk_pad/sig_padding/emsa_pssr/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/emsa_pssr/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,13 @@ + +EMSA_PSSR -> 20131128 +PSS -> 20250130 + + + +name -> "PSS" +brief -> "PSS signature padding from PKCS1v2.0" + + + +mgf1 + diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/sig_padding/emsa_pssr/pssr.cpp botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/emsa_pssr/pssr.cpp --- botan3-3.7.1+dfsg/src/lib/pk_pad/sig_padding/emsa_pssr/pssr.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/emsa_pssr/pssr.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,256 @@ +/* +* PSSR +* (C) 1999-2007,2017,2023 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Botan { + +namespace { + +/* +* PSSR Encode Operation +*/ +std::vector pss_encode(HashFunction& hash, + std::span msg, + std::span salt, + size_t output_bits) { + const size_t HASH_SIZE = hash.output_length(); + + if(msg.size() != HASH_SIZE) { + throw Encoding_Error("Cannot encode PSS string, input length invalid for hash"); + } + if(output_bits < 8 * HASH_SIZE + 8 * salt.size() + 9) { + throw Encoding_Error("Cannot encode PSS string, output length too small"); + } + + const size_t output_length = ceil_tobytes(output_bits); + const uint8_t db0_mask = 0xFF >> (8 * output_length - output_bits); + + std::array padding = {0}; + hash.update(padding); + hash.update(msg); + hash.update(salt); + std::vector H = hash.final_stdvec(); + + const size_t db_len = output_length - HASH_SIZE - 1; + std::vector EM(output_length); + + BufferStuffer stuffer(EM); + stuffer.append(0x00, stuffer.remaining_capacity() - (1 + salt.size() + H.size() + 1)); + stuffer.append(0x01); + stuffer.append(salt); + + mgf1_mask(hash, H, std::span{EM}.first(db_len)); + EM[0] &= db0_mask; + + stuffer.append(H); + stuffer.append(0xBC); + BOTAN_ASSERT_NOMSG(stuffer.full()); + + return EM; +} + +bool pss_verify(HashFunction& hash, + std::span pss_repr, + std::span message_hash, + size_t key_bits, + size_t* out_salt_size) { + const size_t HASH_SIZE = hash.output_length(); + const size_t key_bytes = ceil_tobytes(key_bits); + + if(key_bits < 8 * HASH_SIZE + 9) { + return false; + } + + if(message_hash.size() != HASH_SIZE) { + return false; + } + + if(pss_repr.size() > key_bytes || pss_repr.size() <= 1) { + return false; + } + + if(pss_repr[pss_repr.size() - 1] != 0xBC) { + return false; + } + + std::vector coded; + if(pss_repr.size() < key_bytes) { + coded.resize(key_bytes); + BufferStuffer stuffer(coded); + stuffer.append(0x00, key_bytes - pss_repr.size()); + stuffer.append(pss_repr); + } else { + coded.assign(pss_repr.begin(), pss_repr.end()); + } + + // We have to check this after potential zero padding above + const size_t top_bits = 8 * ((key_bits + 7) / 8) - key_bits; + if(top_bits > 8 - high_bit(coded[0])) { + return false; + } + + uint8_t* DB = coded.data(); + const size_t DB_size = coded.size() - HASH_SIZE - 1; + + const uint8_t* H = &coded[DB_size]; + const size_t H_size = HASH_SIZE; + + mgf1_mask(hash, {H, H_size}, {DB, DB_size}); + DB[0] &= 0xFF >> top_bits; + + size_t salt_offset = 0; + for(size_t j = 0; j != DB_size; ++j) { + if(DB[j] == 0x01) { + salt_offset = j + 1; + break; + } + if(DB[j] != 0x00) { + return false; + } + } + if(salt_offset == 0) { + return false; + } + + const size_t salt_size = DB_size - salt_offset; + + std::array padding = {0}; + hash.update(padding); + hash.update(message_hash); + hash.update(&DB[salt_offset], salt_size); + + const std::vector H2 = hash.final_stdvec(); + + const bool ok = CT::is_equal(H, H2.data(), HASH_SIZE).as_bool(); + + if(ok && out_salt_size != nullptr) { + *out_salt_size = salt_size; + } + + return ok; +} + +} // namespace + +PSSR::PSSR(std::unique_ptr hash) : + m_hash(std::move(hash)), m_salt_size(m_hash->output_length()), m_required_salt_len(false) {} + +PSSR::PSSR(std::unique_ptr hash, size_t salt_size) : + m_hash(std::move(hash)), m_salt_size(salt_size), m_required_salt_len(true) {} + +/* +* PSSR Update Operation +*/ +void PSSR::update(const uint8_t input[], size_t length) { + m_hash->update(input, length); +} + +/* +* Return the raw (unencoded) data +*/ +std::vector PSSR::raw_data() { + return m_hash->final_stdvec(); +} + +std::vector PSSR::encoding_of(std::span msg, size_t output_bits, RandomNumberGenerator& rng) { + const auto salt = rng.random_vec>(m_salt_size); + return pss_encode(*m_hash, msg, salt, output_bits); +} + +/* +* PSSR Decode/Verify Operation +*/ +bool PSSR::verify(std::span coded, std::span raw, size_t key_bits) { + size_t salt_size = 0; + const bool ok = pss_verify(*m_hash, coded, raw, key_bits, &salt_size); + + if(m_required_salt_len && salt_size != m_salt_size) { + return false; + } + + return ok; +} + +std::string PSSR::hash_function() const { + return m_hash->name(); +} + +std::string PSSR::name() const { + return fmt("PSS({},MGF1,{})", m_hash->name(), m_salt_size); +} + +PSS_Raw::PSS_Raw(std::unique_ptr hash) : + m_hash(std::move(hash)), m_salt_size(m_hash->output_length()), m_required_salt_len(false) {} + +PSS_Raw::PSS_Raw(std::unique_ptr hash, size_t salt_size) : + m_hash(std::move(hash)), m_salt_size(salt_size), m_required_salt_len(true) {} + +/* +* PSS_Raw Update Operation +*/ +void PSS_Raw::update(const uint8_t input[], size_t length) { + m_msg.insert(m_msg.end(), input, input + length); + + if(m_msg.size() > m_hash->output_length()) { + throw Encoding_Error("PSS_Raw: Input length exceeded hash output"); + } +} + +/* +* Return the raw (unencoded) data +*/ +std::vector PSS_Raw::raw_data() { + std::vector ret; + std::swap(ret, m_msg); + + if(ret.size() != m_hash->output_length()) { + throw Encoding_Error("PSS_Raw Bad input length, did not match hash"); + } + + return ret; +} + +std::vector PSS_Raw::encoding_of(std::span msg, + size_t output_bits, + RandomNumberGenerator& rng) { + const auto salt = rng.random_vec>(m_salt_size); + return pss_encode(*m_hash, msg, salt, output_bits); +} + +/* +* PSS_Raw Decode/Verify Operation +*/ +bool PSS_Raw::verify(std::span coded, std::span raw, size_t key_bits) { + size_t salt_size = 0; + const bool ok = pss_verify(*m_hash, coded, raw, key_bits, &salt_size); + + if(m_required_salt_len && salt_size != m_salt_size) { + return false; + } + + return ok; +} + +std::string PSS_Raw::hash_function() const { + return m_hash->name(); +} + +std::string PSS_Raw::name() const { + return fmt("PSS_Raw({},MGF1,{})", m_hash->name(), m_salt_size); +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/sig_padding/emsa_pssr/pssr.h botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/emsa_pssr/pssr.h --- botan3-3.7.1+dfsg/src/lib/pk_pad/sig_padding/emsa_pssr/pssr.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/emsa_pssr/pssr.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,97 @@ +/* +* PSSR +* (C) 1999-2007 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_PSSR_H_ +#define BOTAN_PSSR_H_ + +#include +#include +#include +#include + +namespace Botan { + +class RandomNumberGenerator; +class HashFunction; + +/** +* PSSR (called EMSA4 in IEEE 1363 and in old versions of the library) +*/ +class PSSR final : public SignaturePaddingScheme { + public: + /** + * @param hash the hash function to use + */ + explicit PSSR(std::unique_ptr hash); + + /** + * @param hash the hash function to use + * @param salt_size the size of the salt to use in bytes + */ + PSSR(std::unique_ptr hash, size_t salt_size); + + std::string name() const override; + + std::string hash_function() const override; + + private: + void update(const uint8_t input[], size_t length) override; + + std::vector raw_data() override; + + std::vector encoding_of(std::span msg, + size_t output_bits, + RandomNumberGenerator& rng) override; + + bool verify(std::span coded, std::span raw, size_t key_bits) override; + + std::unique_ptr m_hash; + size_t m_salt_size; + bool m_required_salt_len; +}; + +/** +* PSS_Raw +* This accepts a pre-hashed buffer +*/ +class PSS_Raw final : public SignaturePaddingScheme { + public: + /** + * @param hash the hash function to use + */ + explicit PSS_Raw(std::unique_ptr hash); + + /** + * @param hash the hash function to use + * @param salt_size the size of the salt to use in bytes + */ + PSS_Raw(std::unique_ptr hash, size_t salt_size); + + std::string hash_function() const override; + + std::string name() const override; + + private: + void update(const uint8_t input[], size_t length) override; + + std::vector raw_data() override; + + std::vector encoding_of(std::span msg, + size_t output_bits, + RandomNumberGenerator& rng) override; + + bool verify(std::span coded, std::span raw, size_t key_bits) override; + + std::unique_ptr m_hash; + std::vector m_msg; + size_t m_salt_size; + bool m_required_salt_len; +}; + +} // namespace Botan + +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/sig_padding/emsa_raw/info.txt botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/emsa_raw/info.txt --- botan3-3.7.1+dfsg/src/lib/pk_pad/sig_padding/emsa_raw/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/emsa_raw/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,8 @@ + +EMSA_RAW -> 20131128 +RAW_SIGNATURE_PADDING -> 20250720 + + + +name -> "EMSA Raw Padding" + diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/sig_padding/emsa_raw/raw_sig_padding.cpp botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/emsa_raw/raw_sig_padding.cpp --- botan3-3.7.1+dfsg/src/lib/pk_pad/sig_padding/emsa_raw/raw_sig_padding.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/emsa_raw/raw_sig_padding.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,75 @@ +/* +* (C) 1999-2007,2025 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include + +namespace Botan { + +std::string SignRawBytes::name() const { + if(m_expected_size > 0) { + return fmt("Raw({})", m_expected_size); + } + return "Raw"; +} + +void SignRawBytes::update(const uint8_t input[], size_t length) { + // The input is just accumulated into the buffer + m_message += std::make_pair(input, length); +} + +std::vector SignRawBytes::raw_data() { + /* + * Return the provided data. If a specific length was indicated (eg for a prehash), + * check that. + */ + + if(m_expected_size > 0 && m_message.size() != m_expected_size) { + throw Invalid_Argument( + fmt("SignRawBytes was configured to use a {} byte hash but instead was used for a {} byte hash", + m_expected_size, + m_message.size())); + } + + std::vector output; + std::swap(m_message, output); + return output; +} + +std::vector SignRawBytes::encoding_of(std::span msg, + size_t /*output_bits*/, + RandomNumberGenerator& /*rng*/) { + if(m_expected_size > 0 && msg.size() != m_expected_size) { + throw Invalid_Argument( + fmt("SignRawBytes was configured to use a {} byte hash but instead was used for a {} byte hash", + m_expected_size, + msg.size())); + } + + return std::vector(msg.begin(), msg.end()); +} + +bool SignRawBytes::verify(std::span coded, std::span raw, size_t /*key_bits*/) { + if(m_expected_size > 0 && raw.size() != m_expected_size) { + return false; + } + + if(raw.size() > coded.size()) { + // handle zero padding differences + const size_t expected_lz = raw.size() - coded.size(); + auto zeros_ok = CT::all_zeros(raw.data(), expected_lz); + auto contents_ok = CT::is_equal(coded.data(), raw.data() + expected_lz, coded.size()); + return (zeros_ok & contents_ok).as_bool(); + } + + return constant_time_compare(coded, raw); +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/sig_padding/emsa_raw/raw_sig_padding.h botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/emsa_raw/raw_sig_padding.h --- botan3-3.7.1+dfsg/src/lib/pk_pad/sig_padding/emsa_raw/raw_sig_padding.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/emsa_raw/raw_sig_padding.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,47 @@ +/* +* (C) 1999-2007 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_SIGN_RAW_BYTES_H_ +#define BOTAN_SIGN_RAW_BYTES_H_ + +#include +#include +#include + +namespace Botan { + +class RandomNumberGenerator; + +/** +* This class sign inputs directly with no intermediate hashing or padding. +* +* This is insecure unless used very carefully. +*/ +class SignRawBytes final : public SignaturePaddingScheme { + public: + explicit SignRawBytes(size_t expected_hash_size = 0) : m_expected_size(expected_hash_size) {} + + std::string hash_function() const override { return "Raw"; } + + std::string name() const override; + + private: + void update(const uint8_t input[], size_t length) override; + std::vector raw_data() override; + + std::vector encoding_of(std::span raw, + size_t key_bits, + RandomNumberGenerator& rng) override; + + bool verify(std::span coded, std::span raw, size_t key_bits) override; + + const size_t m_expected_size; + std::vector m_message; +}; + +} // namespace Botan + +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/sig_padding/emsa_x931/info.txt botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/emsa_x931/info.txt --- botan3-3.7.1+dfsg/src/lib/pk_pad/sig_padding/emsa_x931/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/emsa_x931/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,12 @@ + +EMSA_X931 -> 20140118 +X931_SIGNATURE_PADDING -> 20250720 + + + +name -> "X9.31" + + + +hash_id + diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/sig_padding/emsa_x931/x931_sig_padding.cpp botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/emsa_x931/x931_sig_padding.cpp --- botan3-3.7.1+dfsg/src/lib/pk_pad/sig_padding/emsa_x931/x931_sig_padding.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/emsa_x931/x931_sig_padding.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,103 @@ +/* +* (C) 1999-2007 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include +#include +#include + +namespace Botan { + +namespace { + +std::vector x931_encoding(std::span msg, + size_t output_bits, + std::span empty_hash, + uint8_t hash_id) { + const size_t HASH_SIZE = empty_hash.size(); + + const size_t output_length = (output_bits + 1) / 8; + + if(msg.size() != HASH_SIZE) { + throw Encoding_Error("X931_SignaturePadding::encoding_of: Bad input length"); + } + if(output_length < HASH_SIZE + 4) { + throw Encoding_Error("X931_SignaturePadding::encoding_of: Output length is too small"); + } + + const bool empty_input = constant_time_compare(msg, empty_hash); + + std::vector output(output_length); + BufferStuffer stuffer(output); + + stuffer.append(empty_input ? 0x4B : 0x6B); + stuffer.append(0xBB, stuffer.remaining_capacity() - (1 + msg.size() + 2)); + stuffer.append(0xBA); + stuffer.append(msg); + stuffer.append(hash_id); + stuffer.append(0xCC); + BOTAN_ASSERT_NOMSG(stuffer.full()); + + return output; +} + +} // namespace + +std::string X931_SignaturePadding::hash_function() const { + return m_hash->name(); +} + +std::string X931_SignaturePadding::name() const { + return fmt("X9.31({})", m_hash->name()); +} + +void X931_SignaturePadding::update(const uint8_t input[], size_t length) { + m_hash->update(input, length); +} + +std::vector X931_SignaturePadding::raw_data() { + return m_hash->final_stdvec(); +} + +/* +* X931_SignaturePadding Encode Operation +*/ +std::vector X931_SignaturePadding::encoding_of(std::span msg, + size_t output_bits, + RandomNumberGenerator& /*rng*/) { + return x931_encoding(msg, output_bits, m_empty_hash, m_hash_id); +} + +/* +* X931_SignaturePadding Verify Operation +*/ +bool X931_SignaturePadding::verify(std::span coded, std::span raw, size_t key_bits) { + try { + const auto x931 = x931_encoding(raw, key_bits, m_empty_hash, m_hash_id); + return constant_time_compare(coded, x931); + } catch(...) { + return false; + } +} + +/* +* X931_SignaturePadding Constructor +*/ +X931_SignaturePadding::X931_SignaturePadding(std::unique_ptr hash) : m_hash(std::move(hash)) { + m_empty_hash = m_hash->final_stdvec(); + + m_hash_id = ieee1363_hash_id(m_hash->name()); + + if(m_hash_id == 0) { + throw Encoding_Error("X931_SignaturePadding no hash identifier for " + m_hash->name()); + } +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/sig_padding/emsa_x931/x931_sig_padding.h botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/emsa_x931/x931_sig_padding.h --- botan3-3.7.1+dfsg/src/lib/pk_pad/sig_padding/emsa_x931/x931_sig_padding.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/emsa_x931/x931_sig_padding.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,52 @@ +/* +* (C) 1999-2007 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_X931_SIGNATURE_PADDING_SCHEME_H_ +#define BOTAN_X931_SIGNATURE_PADDING_SCHEME_H_ + +#include + +namespace Botan { + +class HashFunction; + +/** +* Padding scheme from X9.31 (aka EMSA2 in IEEE 1363) +* +* Historically used for signature padding with Rabin-Williams, +* which is not implemented by Botan anymore. +* +* Sometimes used with RSA in odd protocols. +*/ +class X931_SignaturePadding final : public SignaturePaddingScheme { + public: + /** + * @param hash the hash function to use + */ + explicit X931_SignaturePadding(std::unique_ptr hash); + + std::string name() const override; + + std::string hash_function() const override; + + private: + void update(const uint8_t input[], size_t length) override; + std::vector raw_data() override; + + std::vector encoding_of(std::span raw, + size_t key_bits, + RandomNumberGenerator& rng) override; + + bool verify(std::span coded, std::span raw, size_t key_bits) override; + + std::vector m_empty_hash; + std::unique_ptr m_hash; + uint8_t m_hash_id; +}; + +} // namespace Botan + +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/sig_padding/info.txt botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/info.txt --- botan3-3.7.1+dfsg/src/lib/pk_pad/sig_padding/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,13 @@ + +RSA_SIGNATURE_PADDING -> 20250720 + + + +name -> "RSA signature padding schemes" +brief -> "Implementations of public key signature padding schemes" + + + +hash +rng + diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/sig_padding/iso9796/info.txt botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/iso9796/info.txt --- botan3-3.7.1+dfsg/src/lib/pk_pad/sig_padding/iso9796/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/iso9796/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,13 @@ + +ISO_9796 -> 20161121 + + + +name -> "ISO-9796-2" + + + +mgf1 +hash_id + + diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/sig_padding/iso9796/iso9796.cpp botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/iso9796/iso9796.cpp --- botan3-3.7.1+dfsg/src/lib/pk_pad/sig_padding/iso9796/iso9796.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/iso9796/iso9796.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,304 @@ +/* + * ISO-9796-2 - Digital signature schemes giving message recovery schemes 2 and 3 + * (C) 2016 Tobias Niemann, Hackmanit GmbH + * 2025 Jack Lloyd + * + * Botan is released under the Simplified BSD License (see license.txt) + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Botan { + +namespace { + +std::vector iso9796_hash(HashFunction& hash, + std::span msg1, + std::span hmsg2, + std::span salt) { + // Compute H(C || msg1 || H(msg2) || S) as described in the ISO text + hash.update_be(static_cast(msg1.size()) * 8); + hash.update(msg1); + hash.update(hmsg2); + hash.update(salt); + return hash.final_stdvec(); +} + +std::vector iso9796_encoding(std::span msg, + size_t output_bits, + std::unique_ptr& hash, + size_t salt_len, + bool implicit, + RandomNumberGenerator& rng) { + const size_t output_length = (output_bits + 7) / 8; + + //set trailer length + const size_t trailer_len = (implicit) ? 1 : 2; + + const size_t hash_len = hash->output_length(); + + if(output_length <= hash_len + salt_len + trailer_len) { + throw Encoding_Error("ISO9796-2::encoding_of: Output length is too small"); + } + + //calculate message capacity + const size_t capacity = output_length - hash_len - salt_len - trailer_len - 1; + + // msg1 is the recoverable part and hmsg2 is the hash of the unrecoverable message part. + const size_t msg1_len = std::min(capacity, msg.size()); + const auto msg1 = msg.first(msg1_len); // the first capacity bytes + const auto msg2 = msg.subspan(msg1_len); // the rest; possibly empty + + const auto hmsg2 = hash->process>(msg2); + const auto salt = rng.random_vec>(salt_len); + + const auto H = iso9796_hash(*hash, msg1, hmsg2, salt); + + std::vector EM(output_length); + + BufferStuffer stuffer(EM); + stuffer.append(0x00, stuffer.remaining_capacity() - (hash_len + salt_len + trailer_len + msg1_len + 1)); + stuffer.append(0x01); + stuffer.append(msg1); + stuffer.append(salt); + + //apply mask + const size_t mgf1_bytes = EM.size() - hash_len - trailer_len; + mgf1_mask(*hash, H, std::span{EM}.first(mgf1_bytes)); + + //clear the leftmost bit (confer bouncy castle) + EM[0] &= 0x7F; + + stuffer.append(H); + + // set implicit/ISO trailer + + if(implicit) { + stuffer.append(0xBC); + } else { + const uint8_t hash_id = ieee1363_hash_id(hash->name()); + if(hash_id == 0) { + throw Encoding_Error("ISO-9796: no hash identifier for " + hash->name()); + } + stuffer.append(hash_id); + stuffer.append(0xCC); + } + + BOTAN_ASSERT_NOMSG(stuffer.full()); + + return EM; +} + +bool iso9796_verification(std::span repr, + std::span raw, + size_t key_bits, + std::unique_ptr& hash, + size_t salt_len) { + if(repr.size() != (key_bits + 7) / 8) { + return false; + } + //get trailer length + + const uint8_t last = repr[repr.size() - 1]; + + if(last != 0xBC && last != 0xCC) { + return false; + } + + const size_t trailer_len = last == 0xBC ? 1 : 2; + + if(trailer_len == 2) { + const uint8_t hash_id = ieee1363_hash_id(hash->name()); + if(hash_id == 0) { + throw Decoding_Error("ISO-9796: no hash identifier for " + hash->name()); + } + + const uint8_t trailer_0 = repr[repr.size() - 2]; + const uint8_t trailer_1 = repr[repr.size() - 1]; + + if(trailer_0 != hash_id || trailer_1 != 0xCC) { + return false; + } + } + + const size_t hash_len = hash->output_length(); + + if(repr.size() < hash_len + trailer_len + salt_len) { + return false; + } + + std::vector coded(repr.begin(), repr.end()); + + CT::poison(coded.data(), coded.size()); + //remove mask + uint8_t* DB = coded.data(); + const size_t DB_size = coded.size() - hash_len - trailer_len; + + const uint8_t* H = &coded[DB_size]; + + mgf1_mask(*hash, {H, hash_len}, {DB, DB_size}); + //clear the leftmost bit (confer bouncy castle) + DB[0] &= 0x7F; + + //recover msg1 and salt + size_t msg1_offset = 1; + + auto waiting_for_delim = CT::Mask::set(); + auto bad_input = CT::Mask::cleared(); + + for(size_t j = 0; j < DB_size; ++j) { + const auto is_zero = CT::Mask::is_zero(DB[j]); + const auto is_one = CT::Mask::is_equal(DB[j], 0x01); + + const auto add_m = waiting_for_delim & is_zero; + + bad_input |= waiting_for_delim & ~(is_zero | is_one); + msg1_offset += add_m.if_set_return(1); + + waiting_for_delim &= is_zero; + } + + //invalid, if delimiter 0x01 was not found or msg1_offset is too big + bad_input |= waiting_for_delim; + + const auto bad_offset = CT::Mask::is_lt(coded.size(), trailer_len + hash_len + msg1_offset + salt_len); + bad_input |= CT::Mask(bad_offset); + + //in case that msg1_offset is too big, just continue with offset = 0. + msg1_offset = CT::Mask::expand(bad_input.value()).if_not_set_return(msg1_offset); + + CT::unpoison(coded.data(), coded.size()); + CT::unpoison(msg1_offset); + + const size_t msg1_len = coded.size() - (trailer_len + hash_len + msg1_offset + salt_len); + + const auto msg1 = std::span(coded).subspan(msg1_offset, msg1_len); + const auto salt = std::span(coded).subspan(msg1_offset + msg1.size(), salt_len); + + //compute H2(C||msg1||H(msg2)||S*). * indicates a recovered value + const size_t capacity = (key_bits - 2 + 7) / 8 - hash_len - salt_len - trailer_len - 1; + + std::span msg1raw = raw; + if(msg1raw.size() > capacity) { + hash->update(msg1raw.subspan(capacity)); + msg1raw = msg1raw.first(capacity); + } + + const auto hmsg2 = hash->final_stdvec(); + + // Compute H(C*||msg1*||H(msg2)||S*) where '*' indicates a recovered value + const auto H2 = iso9796_hash(*hash, msg1, hmsg2, salt); + + // Check if H == H2 + bad_input |= CT::is_not_equal(H, H2.data(), hash_len); + + // Check that msg after MGF1 matches msg in the original + bad_input |= ~CT::Mask(CT::Mask::is_equal(msg1.size(), msg1raw.size())); + bad_input |= ~CT::is_equal(msg1.data(), msg1raw.data(), std::min(msg1.size(), msg1raw.size())); + + CT::unpoison(bad_input); + return (bad_input.as_bool() == false); +} + +} // namespace + +/* + * ISO-9796-2 signature scheme 2 + * DS 2 is probabilistic + */ +void ISO_9796_DS2::update(const uint8_t input[], size_t length) { + //need to buffer message completely, before digest + m_msg_buffer.insert(m_msg_buffer.end(), input, input + length); +} + +/* + * Return the raw (unencoded) data + */ +std::vector ISO_9796_DS2::raw_data() { + std::vector retbuffer = m_msg_buffer; + m_msg_buffer.clear(); + return retbuffer; +} + +/* + * ISO-9796-2 scheme 2 encode operation + */ +std::vector ISO_9796_DS2::encoding_of(std::span msg, + size_t output_bits, + RandomNumberGenerator& rng) { + return iso9796_encoding(msg, output_bits, m_hash, m_salt_len, m_implicit, rng); +} + +/* + * ISO-9796-2 scheme 2 verify operation + */ +bool ISO_9796_DS2::verify(std::span repr, std::span raw, size_t key_bits) { + return iso9796_verification(repr, raw, key_bits, m_hash, m_salt_len); +} + +std::string ISO_9796_DS2::hash_function() const { + return m_hash->name(); +} + +/* + * Return the SCAN name + */ +std::string ISO_9796_DS2::name() const { + return fmt("ISO_9796_DS2({},{},{})", m_hash->name(), (m_implicit ? "imp" : "exp"), m_salt_len); +} + +/* + * ISO-9796-2 signature scheme 3 + * DS 3 is deterministic and equals DS2 without salt + */ +void ISO_9796_DS3::update(const uint8_t input[], size_t length) { + //need to buffer message completely, before digest + m_msg_buffer.insert(m_msg_buffer.end(), input, input + length); +} + +/* + * Return the raw (unencoded) data + */ +std::vector ISO_9796_DS3::raw_data() { + std::vector retbuffer = m_msg_buffer; + m_msg_buffer.clear(); + return retbuffer; +} + +/* + * ISO-9796-2 scheme 3 encode operation + */ +std::vector ISO_9796_DS3::encoding_of(std::span msg, + size_t output_bits, + RandomNumberGenerator& rng) { + return iso9796_encoding(msg, output_bits, m_hash, 0, m_implicit, rng); +} + +/* + * ISO-9796-2 scheme 3 verify operation + */ +bool ISO_9796_DS3::verify(std::span repr, std::span raw, size_t key_bits) { + return iso9796_verification(repr, raw, key_bits, m_hash, 0); +} + +std::string ISO_9796_DS3::hash_function() const { + return m_hash->name(); +} + +/* + * Return the SCAN name + */ +std::string ISO_9796_DS3::name() const { + return fmt("ISO_9796_DS3({},{})", m_hash->name(), (m_implicit ? "imp" : "exp")); +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/sig_padding/iso9796/iso9796.h botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/iso9796/iso9796.h --- botan3-3.7.1+dfsg/src/lib/pk_pad/sig_padding/iso9796/iso9796.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/iso9796/iso9796.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,88 @@ +/* + * ISO-9796-2 - Digital signature schemes giving message recovery schemes 2 and 3 + * (C) 2016 Tobias Niemann, Hackmanit GmbH + * + * Botan is released under the Simplified BSD License (see license.txt) + */ + +#ifndef BOTAN_ISO9796_H_ +#define BOTAN_ISO9796_H_ + +#include +#include +#include +#include + +namespace Botan { + +class HashFunction; + +/** +* ISO-9796-2 - Digital signature scheme 2 (probabilistic) +*/ +class ISO_9796_DS2 final : public SignaturePaddingScheme { + public: + /** + * @param hash function to use + * @param implicit whether or not the trailer is implicit + * @param salt_size size of the salt to use in bytes + */ + ISO_9796_DS2(std::unique_ptr hash, bool implicit, size_t salt_size) : + m_hash(std::move(hash)), m_implicit(implicit), m_salt_len(salt_size) {} + + std::string hash_function() const override; + + std::string name() const override; + + private: + void update(const uint8_t input[], size_t length) override; + + std::vector raw_data() override; + + std::vector encoding_of(std::span msg, + size_t output_bits, + RandomNumberGenerator& rng) override; + + bool verify(std::span coded, std::span raw, size_t key_bits) override; + + std::unique_ptr m_hash; + bool m_implicit; + size_t m_salt_len; + std::vector m_msg_buffer; +}; + +/** +* ISO-9796-2 - Digital signature scheme 3 (deterministic) +*/ +class ISO_9796_DS3 final : public SignaturePaddingScheme { + public: + /** + * @param hash function to use + * @param implicit whether or not the trailer is implicit + */ + explicit ISO_9796_DS3(std::unique_ptr hash, bool implicit = false) : + m_hash(std::move(hash)), m_implicit(implicit) {} + + std::string name() const override; + + std::string hash_function() const override; + + private: + void update(const uint8_t input[], size_t length) override; + + std::vector raw_data() override; + + std::vector encoding_of(std::span msg, + size_t output_bits, + RandomNumberGenerator& rng) override; + + bool verify(std::span coded, std::span raw, size_t key_bits) override; + + std::unique_ptr m_hash; + bool m_implicit; + std::vector m_msg_buffer; +}; + +} // namespace Botan + +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/sig_padding/sig_padding.cpp botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/sig_padding.cpp --- botan3-3.7.1+dfsg/src/lib/pk_pad/sig_padding/sig_padding.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/sig_padding.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,143 @@ +/* +* (C) 2015 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include + +#if defined(BOTAN_HAS_X931_SIGNATURE_PADDING) + #include +#endif + +#if defined(BOTAN_HAS_PKCSV15_SIGNATURE_PADDING) + #include +#endif + +#if defined(BOTAN_HAS_PSS) + #include +#endif + +#if defined(BOTAN_HAS_RAW_SIGNATURE_PADDING) + #include +#endif + +#if defined(BOTAN_HAS_ISO_9796) + #include +#endif + +namespace Botan { + +std::unique_ptr SignaturePaddingScheme::create(std::string_view algo_spec) { + const SCAN_Name req(algo_spec); + +#if defined(BOTAN_HAS_EMSA_PKCS1) + // TODO(Botan4) Remove all but "PKCS1v15" + if(req.algo_name() == "EMSA_PKCS1" || req.algo_name() == "PKCS1v15" || req.algo_name() == "EMSA-PKCS1-v1_5" || + req.algo_name() == "EMSA3") { + if(req.arg_count() == 2 && req.arg(0) == "Raw") { + return std::make_unique(req.arg(1)); + } else if(req.arg_count() == 1) { + if(req.arg(0) == "Raw") { + return std::make_unique(); + } else { + if(auto hash = HashFunction::create(req.arg(0))) { + return std::make_unique(std::move(hash)); + } + } + } + } +#endif + +#if defined(BOTAN_HAS_EMSA_PSSR) + // TODO(Botan4) Remove all but "PSS_Raw" + if(req.algo_name() == "PSS_Raw" || req.algo_name() == "PSSR_Raw") { + if(req.arg_count_between(1, 3) && req.arg(1, "MGF1") == "MGF1") { + if(auto hash = HashFunction::create(req.arg(0))) { + if(req.arg_count() == 3) { + const size_t salt_size = req.arg_as_integer(2, 0); + return std::make_unique(std::move(hash), salt_size); + } else { + return std::make_unique(std::move(hash)); + } + } + } + } + + // TODO(Botan4) Remove all but "PSS" + if(req.algo_name() == "PSS" || req.algo_name() == "PSSR" || req.algo_name() == "EMSA-PSS" || + req.algo_name() == "PSS-MGF1" || req.algo_name() == "EMSA4") { + if(req.arg_count_between(1, 3) && req.arg(1, "MGF1") == "MGF1") { + if(auto hash = HashFunction::create(req.arg(0))) { + if(req.arg_count() == 3) { + const size_t salt_size = req.arg_as_integer(2, 0); + return std::make_unique(std::move(hash), salt_size); + } else { + return std::make_unique(std::move(hash)); + } + } + } + } +#endif + +#if defined(BOTAN_HAS_ISO_9796) + if(req.algo_name() == "ISO_9796_DS2") { + if(req.arg_count_between(1, 3)) { + if(auto hash = HashFunction::create(req.arg(0))) { + const size_t salt_size = req.arg_as_integer(2, hash->output_length()); + const bool implicit = req.arg(1, "exp") == "imp"; + return std::make_unique(std::move(hash), implicit, salt_size); + } + } + } + //ISO-9796-2 DS 3 is deterministic and DS2 without a salt + if(req.algo_name() == "ISO_9796_DS3") { + if(req.arg_count_between(1, 2)) { + if(auto hash = HashFunction::create(req.arg(0))) { + const bool implicit = req.arg(1, "exp") == "imp"; + return std::make_unique(std::move(hash), implicit); + } + } + } +#endif + +#if defined(BOTAN_HAS_X931_SIGNATURE_PADDING) + // TODO(Botan4) Remove all but "X9.31" + if(req.algo_name() == "EMSA_X931" || req.algo_name() == "EMSA2" || req.algo_name() == "X9.31") { + if(req.arg_count() == 1) { + if(auto hash = HashFunction::create(req.arg(0))) { + return std::make_unique(std::move(hash)); + } + } + } +#endif + +#if defined(BOTAN_HAS_RAW_SIGNATURE_PADDING) + if(req.algo_name() == "Raw") { + if(req.arg_count() == 0) { + return std::make_unique(); + } else { + auto hash = HashFunction::create(req.arg(0)); + if(hash) { + return std::make_unique(hash->output_length()); + } + } + } +#endif + + return nullptr; +} + +std::unique_ptr SignaturePaddingScheme::create_or_throw(std::string_view algo_spec) { + if(auto padding = SignaturePaddingScheme::create(algo_spec)) { + return padding; + } else { + throw Algorithm_Not_Found(algo_spec); + } +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pk_pad/sig_padding/sig_padding.h botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/sig_padding.h --- botan3-3.7.1+dfsg/src/lib/pk_pad/sig_padding/sig_padding.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pk_pad/sig_padding/sig_padding.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,90 @@ +/* +* (C) 1999-2007 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_SIGNATURE_PADDING_SCHEME_H_ +#define BOTAN_SIGNATURE_PADDING_SCHEME_H_ + +#include +#include +#include +#include +#include + +namespace Botan { + +class RandomNumberGenerator; + +/** +* RSA Signature Padding Scheme +* +* Previously called 'EMSA' from IEEE 1363's "Encoding Method for Signatures, Appendix" +*/ +class BOTAN_TEST_API SignaturePaddingScheme /* NOLINT(*-special-member-functions) */ { + public: + virtual ~SignaturePaddingScheme() = default; + + /** + * Factory method for SignaturePaddingScheme (message-encoding methods for signatures + * with appendix) objects + * @param algo_spec the name of the SignaturePaddingScheme to create + * @return pointer to newly allocated object of that type, or nullptr + */ + static std::unique_ptr create(std::string_view algo_spec); + + /** + * Factory method for SignaturePaddingScheme (message-encoding methods for signatures + * with appendix) objects + * @param algo_spec the name of the SignaturePaddingScheme to create + * @return pointer to newly allocated object of that type, or throws + */ + static std::unique_ptr create_or_throw(std::string_view algo_spec); + + /** + * Add more data to the signature computation + * @param input some data + * @param length length of input in bytes + */ + virtual void update(const uint8_t input[], size_t length) = 0; + + /** + * @return raw hash + */ + virtual std::vector raw_data() = 0; + + /** + * Return the encoding of a message + * @param msg the result of raw_data() + * @param output_bits the desired output bit size + * @param rng a random number generator + * @return encoded signature + */ + virtual std::vector encoding_of(std::span msg, + size_t output_bits, + RandomNumberGenerator& rng) = 0; + + /** + * Verify the encoding + * @param encoding the received (coded) message representative + * @param raw_hash the computed (local, uncoded) message representative + * @param key_bits the size of the key in bits + * @return true if coded is a valid encoding of raw, otherwise false + */ + virtual bool verify(std::span encoding, std::span raw_hash, size_t key_bits) = 0; + + /** + * Return the hash function being used by this padding scheme + */ + virtual std::string hash_function() const = 0; + + /** + * @return the SCAN name of the encoding/padding scheme + */ + virtual std::string name() const = 0; +}; + +} // namespace Botan + +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/prov/commoncrypto/commoncrypto.h botan3-3.12.0+dfsg/src/lib/prov/commoncrypto/commoncrypto.h --- botan3-3.7.1+dfsg/src/lib/prov/commoncrypto/commoncrypto.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/prov/commoncrypto/commoncrypto.h 2026-05-07 01:38:28.000000000 +0000 @@ -20,7 +20,7 @@ class Cipher_Mode; class BlockCipher; class HashFunction; -enum class Cipher_Dir : int; +enum class Cipher_Dir : uint8_t; typedef int32_t CCCryptorStatus; class BOTAN_PUBLIC_API(2, 0) CommonCrypto_Error final : public Exception { diff -Nru botan3-3.7.1+dfsg/src/lib/prov/commoncrypto/commoncrypto_hash.cpp botan3-3.12.0+dfsg/src/lib/prov/commoncrypto/commoncrypto_hash.cpp --- botan3-3.7.1+dfsg/src/lib/prov/commoncrypto/commoncrypto_hash.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/prov/commoncrypto/commoncrypto_hash.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,8 +8,7 @@ #include #include -#include -#include +#include #include diff -Nru botan3-3.7.1+dfsg/src/lib/prov/commoncrypto/commoncrypto_mode.cpp botan3-3.12.0+dfsg/src/lib/prov/commoncrypto/commoncrypto_mode.cpp --- botan3-3.7.1+dfsg/src/lib/prov/commoncrypto/commoncrypto_mode.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/prov/commoncrypto/commoncrypto_mode.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,6 +7,7 @@ #include +#include #include #include #include @@ -67,11 +68,12 @@ } void CommonCrypto_Cipher_Mode::start_msg(const uint8_t nonce[], size_t nonce_len) { - assert_key_material_set(); - if(!valid_nonce_length(nonce_len)) { throw Invalid_IV_Length(name(), nonce_len); } + + assert_key_material_set(); + if(nonce_len) { CCCryptorStatus status = CCCryptorReset(m_cipher, nonce); if(status != kCCSuccess) { @@ -139,7 +141,7 @@ } size_t CommonCrypto_Cipher_Mode::ideal_granularity() const { - return m_opts.block_size * BOTAN_BLOCK_CIPHER_PAR_MULT; + return m_opts.block_size * BlockCipher::ParallelismMult; } size_t CommonCrypto_Cipher_Mode::minimum_final_size() const { diff -Nru botan3-3.7.1+dfsg/src/lib/prov/tpm/info.txt botan3-3.12.0+dfsg/src/lib/prov/tpm/info.txt --- botan3-3.7.1+dfsg/src/lib/prov/tpm/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/prov/tpm/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -3,8 +3,8 @@ -name -> "TPM" -brief -> "Wrappers and Utilites to interact with TPMs" +name -> "TPM v1 Support (deprecated)" +brief -> "Wrappers and Utilities to interact with TPM v1" lifecycle -> "Deprecated" diff -Nru botan3-3.7.1+dfsg/src/lib/prov/tpm/tpm.cpp botan3-3.12.0+dfsg/src/lib/prov/tpm/tpm.cpp --- botan3-3.7.1+dfsg/src/lib/prov/tpm/tpm.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/prov/tpm/tpm.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,6 +9,7 @@ #include #include +#include #include #include #include diff -Nru botan3-3.7.1+dfsg/src/lib/prov/tpm2/info.txt botan3-3.12.0+dfsg/src/lib/prov/tpm2/info.txt --- botan3-3.7.1+dfsg/src/lib/prov/tpm2/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/prov/tpm2/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -4,7 +4,7 @@ name -> "TPM2" -brief -> "Wrappers and Utilites to interact with TPM2" +brief -> "Wrappers and Utilities to interact with TPM2" load_on vendor diff -Nru botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_algo_mappings.h botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_algo_mappings.h --- botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_algo_mappings.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_algo_mappings.h 2026-05-07 01:38:28.000000000 +0000 @@ -294,7 +294,7 @@ } [[nodiscard]] inline std::optional cipher_botan_to_tss2(std::string_view algo_name) { - SCAN_Name spec(algo_name); + const SCAN_Name spec(algo_name); if(spec.arg_count() == 0) { return std::nullopt; } diff -Nru botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_context.cpp botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_context.cpp --- botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_context.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_context.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -11,12 +11,14 @@ #include #include +#include #include #include #include #include #include #include +#include #include #include @@ -36,8 +38,8 @@ } // namespace struct Context::Impl { - ESYS_CONTEXT* m_ctx; /// m_ctx may be owned by the library user (see m_external) - bool m_external; + ESYS_CONTEXT* m_ctx{}; /// m_ctx may be owned by the library user (see m_external) + bool m_external{}; #if defined(BOTAN_HAS_TPM2_CRYPTO_BACKEND) std::unique_ptr m_crypto_callback_state; @@ -53,11 +55,9 @@ } std::shared_ptr Context::create(const std::string& tcti_nameconf) { - const auto nameconf_ptr = tcti_nameconf.c_str(); - TSS2_TCTI_CONTEXT* tcti_ctx = nullptr; ESYS_CONTEXT* esys_ctx = nullptr; - check_rc("TCTI Initialization", Tss2_TctiLdr_Initialize(nameconf_ptr, &tcti_ctx)); + check_rc("TCTI Initialization", Tss2_TctiLdr_Initialize(tcti_nameconf.c_str(), &tcti_ctx)); BOTAN_ASSERT_NONNULL(tcti_ctx); check_rc("TPM2 Initialization", Esys_Initialize(&esys_ctx, tcti_ctx, nullptr /* ABI version */)); BOTAN_ASSERT_NONNULL(esys_ctx); @@ -67,8 +67,8 @@ } std::shared_ptr Context::create(std::optional tcti, std::optional conf) { - const auto tcti_ptr = tcti.has_value() ? tcti->c_str() : nullptr; - const auto conf_ptr = conf.has_value() ? conf->c_str() : nullptr; + const char* const tcti_ptr = tcti.has_value() ? tcti->c_str() : nullptr; + const char* const conf_ptr = conf.has_value() ? conf->c_str() : nullptr; TSS2_TCTI_CONTEXT* tcti_ctx = nullptr; ESYS_CONTEXT* esys_ctx = nullptr; @@ -345,15 +345,10 @@ // 1. Decide on the location to persist the key to. // This uses either the handle provided by the caller or a free handle. - const TPMI_DH_PERSISTENT new_persistent_handle = [&] { - if(persistent_handle.has_value()) { - return persistent_handle.value(); - } else { - const auto free_persistent_handle = find_free_persistent_handle(); - BOTAN_STATE_CHECK(free_persistent_handle.has_value()); - return free_persistent_handle.value(); - } - }(); + const std::optional new_persistent_handle = + persistent_handle.has_value() ? persistent_handle : find_free_persistent_handle(); + + BOTAN_STATE_CHECK(new_persistent_handle.has_value()); // 2. Persist the transient key in the TPM's NV storage // This will flush the transient key handle and replace it with a new @@ -365,7 +360,7 @@ sessions[0], sessions[1], sessions[2], - new_persistent_handle, + *new_persistent_handle, out_transient_handle(handles))); BOTAN_ASSERT_NOMSG(handles.has_transient_handle()); @@ -384,9 +379,9 @@ Esys_TR_GetTpmHandle(m_impl->m_ctx, handles.transient_handle(), out_persistent_handle(handles))); BOTAN_ASSERT_NOMSG(handles.has_persistent_handle()); - BOTAN_ASSERT_EQUAL(new_persistent_handle, handles.persistent_handle(), "key was persisted at the correct location"); + BOTAN_ASSERT_EQUAL(*new_persistent_handle, handles.persistent_handle(), "key was persisted at the correct location"); - return new_persistent_handle; + return *new_persistent_handle; } void Context::evict(std::unique_ptr key, const SessionBundle& sessions) { @@ -448,7 +443,7 @@ // If the TCTI context was initialized explicitly, Esys_GetTcti() will // return a pointer to the TCTI context that then has to be finalized // explicitly. See ESAPI Specification Section 6.3 "Esys_GetTcti". - TSS2_TCTI_CONTEXT* tcti_ctx = nullptr; + TSS2_TCTI_CONTEXT* tcti_ctx = nullptr; // NOLINT(*-const-correctness) bug in clang-tidy Esys_GetTcti(m_impl->m_ctx, &tcti_ctx); // ignore error in destructor if(tcti_ctx != nullptr) { Tss2_TctiLdr_Finalize(&tcti_ctx); diff -Nru botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_context.h botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_context.h --- botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_context.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_context.h 2026-05-07 01:38:28.000000000 +0000 @@ -33,7 +33,7 @@ /** * Central class for interacting with a TPM2. Additional to managing the - * connection to the TPM, this provides authorative information about the TPM's + * connection to the TPM, this provides authoritative information about the TPM's * capabilities. Also, it allows to persist and evict keys generated by the TPM. */ class BOTAN_PUBLIC_API(3, 6) Context final : public std::enable_shared_from_this { @@ -56,7 +56,7 @@ * Create a TPM2::Context from an externally sourced TPM2-TSS ESYS * Context. Note that the input contexts need to remain alive for the * lifetime of the entire TPM2::Context! This allows to use Botan's TPM2 - * functionality within an exising ESAPI application. + * functionality within an existing ESAPI application. * * Note that Botan won't finalize an externally provided ESYS context, * this responsibility remains with the caller in this case. @@ -101,6 +101,7 @@ /// @return an ESYS_CONTEXT* for use in other TPM2 functions. ESYS_CONTEXT* esys_context() noexcept; + // NOLINTNEXTLINE(*-explicit-conversions) Intentional: enables transparent ESYS_CONTEXT* wrapper usage operator ESYS_CONTEXT*() noexcept { return esys_context(); } /// @return the Vendor of the TPM2 diff -Nru botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_crypto_backend/info.txt botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_crypto_backend/info.txt --- botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_crypto_backend/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_crypto_backend/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -11,7 +11,7 @@ hash hmac modes -pk_pad +enc_padding eme_raw diff -Nru botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_crypto_backend/tpm2_crypto_backend.h botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_crypto_backend/tpm2_crypto_backend.h --- botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_crypto_backend/tpm2_crypto_backend.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_crypto_backend/tpm2_crypto_backend.h 2026-05-07 01:38:28.000000000 +0000 @@ -26,7 +26,7 @@ * ESYS_CONTEXT pointer is valid. */ struct CryptoCallbackState { - std::shared_ptr rng; // NOLINT(misc-non-private-member-variables-in-classes) + std::shared_ptr rng; // NOLINT(*-non-private-member-variable*) }; /** diff -Nru botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_crypto_backend/tpm2_crypto_backend_impl.cpp botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_crypto_backend/tpm2_crypto_backend_impl.cpp --- botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_crypto_backend/tpm2_crypto_backend_impl.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_crypto_backend/tpm2_crypto_backend_impl.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -25,8 +25,9 @@ #include #endif -#include +#include #include +#include #include #include @@ -90,16 +91,11 @@ /// Safely converts the @p userdata to the Botan crypto context object. [[nodiscard]] std::optional> get(void* userdata) noexcept { - if(!userdata) { + if(auto* ccs = reinterpret_cast(userdata)) { + return *ccs; + } else { return std::nullopt; } - - auto ccs = reinterpret_cast(userdata); - if(!ccs) { - return std::nullopt; - } - - return *ccs; } /** @@ -140,13 +136,13 @@ size_t buffer_size, const uint8_t* iv) noexcept { return thunk([&] { - if(!key) { + if(key == nullptr) { return (direction == Botan::Cipher_Dir::Encryption) ? TSS2_ESYS_RC_NO_ENCRYPT_PARAM : TSS2_ESYS_RC_NO_DECRYPT_PARAM; } // nullptr buffer with size 0 is alright - if(!buffer && buffer_size != 0) { + if(buffer == nullptr && buffer_size != 0) { return TSS2_ESYS_RC_BAD_VALUE; } @@ -179,7 +175,7 @@ const auto s_data = std::span{buffer, buffer_size}; const auto s_key = std::span{key, keylength}; const auto s_iv = [&]() -> std::span { - if(iv) { + if(iv != nullptr) { return {iv, cipher->default_nonce_length()}; } else { return {}; @@ -207,7 +203,7 @@ TSS2_RC hash_start(ESYS_CRYPTO_CONTEXT_BLOB** context, TPM2_ALG_ID hash_alg, void* userdata) { BOTAN_UNUSED(userdata); return thunk([&] { - if(!context) { + if(context == nullptr) { return TSS2_ESYS_RC_BAD_REFERENCE; } @@ -217,12 +213,12 @@ } auto hash = Botan::HashFunction::create(hash_name.value()); - if(!hash) { + if(hash == nullptr) { return TSS2_ESYS_RC_NOT_IMPLEMENTED; } // Will be deleted in hash_abort() or hash_finish() - *context = new DigestCallbackState{std::move(hash)}; + *context = new DigestCallbackState{std::move(hash)}; // NOLINT(*-owning-memory) return TSS2_RC_SUCCESS; }); } @@ -247,7 +243,7 @@ } // nullptr buffer with size 0 is alright - if(!buffer && size != 0) { + if(buffer == nullptr && size != 0) { return TSS2_ESYS_RC_BAD_VALUE; } @@ -275,7 +271,7 @@ return thunk([&] { auto hash = get(context); - if(!hash || !buffer) { + if(!hash || buffer == nullptr) { return TSS2_ESYS_RC_BAD_REFERENCE; } @@ -285,7 +281,8 @@ *size = digest_size; } - delete *context; // allocated in hash_start() + // allocated in hash_start() + delete *context; // NOLINT(*-owning-memory) *context = nullptr; return TSS2_RC_SUCCESS; }); @@ -299,8 +296,9 @@ */ void hash_abort(ESYS_CRYPTO_CONTEXT_BLOB** context, void* userdata) { BOTAN_UNUSED(userdata); - if(context) { - delete *context; // allocated in hash_start() + if(context != nullptr) { + // allocated in hash_start() + delete *context; // NOLINT(*-owning-memory) *context = nullptr; } } @@ -321,7 +319,7 @@ ESYS_CRYPTO_CONTEXT_BLOB** context, TPM2_ALG_ID hash_alg, const uint8_t* key, size_t size, void* userdata) { BOTAN_UNUSED(userdata); return thunk([&] { - if(!context || !key) { + if(Botan::any_null_pointers(context, key)) { return TSS2_ESYS_RC_BAD_REFERENCE; } @@ -331,14 +329,14 @@ } auto hmac = Botan::MessageAuthenticationCode::create(Botan::fmt("HMAC({})", hash_name.value())); - if(!hmac) { + if(hmac == nullptr) { return TSS2_ESYS_RC_NOT_IMPLEMENTED; } hmac->set_key(std::span{key, size}); // Will be deleted in hmac_abort() or hmac_finish() - *context = new DigestCallbackState{std::move(hmac)}; + *context = new DigestCallbackState{std::move(hmac)}; // NOLINT(*-owning-memory) return TSS2_RC_SUCCESS; }); } @@ -363,7 +361,7 @@ } // nullptr buffer with size 0 is alright - if(!buffer && size != 0) { + if(buffer == nullptr && size != 0) { return TSS2_ESYS_RC_BAD_VALUE; } @@ -391,7 +389,7 @@ return thunk([&] { auto hmac = get(context); - if(!hmac || !buffer) { + if(!hmac || buffer == nullptr) { return TSS2_ESYS_RC_BAD_REFERENCE; } @@ -401,7 +399,8 @@ *size = digest_size; } - delete *context; // allocated in hmac_start() + // allocated in hmac_start() + delete *context; // NOLINT(*-owning-memory) *context = nullptr; return TSS2_RC_SUCCESS; }); @@ -415,8 +414,9 @@ */ void hmac_abort(ESYS_CRYPTO_CONTEXT_BLOB** context, void* userdata) { BOTAN_UNUSED(userdata); - if(context) { - delete *context; // allocated in hmac_start() + if(context != nullptr) { + // allocated in hmac_start() + delete *context; // NOLINT(*-owning-memory) *context = nullptr; } } @@ -434,7 +434,7 @@ TSS2_RC get_random2b(TPM2B_NONCE* nonce, size_t num_bytes, void* userdata) { return thunk([&] { auto ccs = get(userdata); - if(!ccs || !ccs->get().rng || !nonce) { + if(!ccs || !ccs->get().rng || Botan::any_null_pointers(nonce)) { return TSS2_ESYS_RC_BAD_REFERENCE; } @@ -478,14 +478,14 @@ // // https://github.com/randombit/botan/pull/4318#issuecomment-2297682058 #if defined(BOTAN_HAS_RSA) - auto create_eme = [&]( - const TPMT_RSA_SCHEME& scheme, - [[maybe_unused]] TPM2_ALG_ID name_algo, - [[maybe_unused]] TPMU_ASYM_SCHEME scheme_detail) -> std::optional> { + auto create_eme = [&](const TPMT_RSA_SCHEME& scheme, + [[maybe_unused]] TPM2_ALG_ID name_algo, + [[maybe_unused]] TPMU_ASYM_SCHEME scheme_detail) + -> std::optional> { // OAEP is more complex by requiring a hash function and an optional // label. To avoid marshalling this into Botan's algorithm descriptor // we create an OAEP instance manually. - auto create_oaep = [&]() -> std::optional> { + auto create_oaep = [&]() -> std::optional> { #if defined(BOTAN_HAS_EME_OAEP) // TPM Library, Part 1: Architecture, Annex B.4 // The RSA key's scheme hash algorithm (or, if it is TPM_ALG_NULL, @@ -512,7 +512,7 @@ // TPM Library, Part 1: Architecture, Annex B.4 // [...] is used to compute lhash := H(label), and the null // termination octet is included in the digest. - std::string_view label_with_zero_terminator{label, std::strlen(label) + 1}; + const std::string_view label_with_zero_terminator{label, std::strlen(label) + 1}; return std::make_unique(std::move(H_label), std::move(H_mgf1), label_with_zero_terminator); #else BOTAN_UNUSED(label); @@ -520,14 +520,14 @@ #endif }; - try { // EME::create throws if algorithm is not available + try { // EncryptionPaddingScheme::create throws if algorithm is not available switch(scheme.scheme) { case TPM2_ALG_OAEP: return create_oaep(); case TPM2_ALG_NULL: - return Botan::EME::create("Raw"); + return Botan::EncryptionPaddingScheme::create("Raw"); case TPM2_ALG_RSAES: - return Botan::EME::create("PKCS1v15"); + return Botan::EncryptionPaddingScheme::create("PKCS1v15"); default: return std::nullopt; // -> not supported } @@ -535,12 +535,12 @@ /* ignore */ } - return nullptr; // -> not implemented (EME::create() threw) + return nullptr; // -> not implemented (EncryptionPaddingScheme::create() threw) }; return thunk([&] { auto ccs = get(userdata); - if(!ccs || !pub_tpm_key || !in_buffer || !out_buffer || !ccs->get().rng) { + if(!ccs || !ccs->get().rng || Botan::any_null_pointers(pub_tpm_key, in_buffer, out_buffer)) { return TSS2_ESYS_RC_BAD_REFERENCE; } @@ -588,7 +588,7 @@ // PK_Encryptor_EME does not provide a way to pass in an output buffer. // TODO: provide an `.encrypt()` overload that accepts an output buffer. - Botan::PK_Encryptor_EME encryptor(pubkey, rng, "Raw"); + const Botan::PK_Encryptor_EME encryptor(pubkey, rng, "Raw"); const auto encrypted = encryptor.encrypt({out_buffer, padded_bytes}, rng); BOTAN_DEBUG_ASSERT(encrypted.size() == output_size); @@ -638,7 +638,7 @@ #if defined(BOTAN_HAS_ECDH) return thunk([&] { auto ccs = get(userdata); - if(!ccs || !key || !Z || !Q || !out_buffer | !ccs->get().rng) { + if(!ccs || !ccs->get().rng || Botan::any_null_pointers(key, Z, Q, out_buffer)) { return TSS2_ESYS_RC_BAD_REFERENCE; } @@ -646,7 +646,7 @@ // 1: Get TPM public key const auto [tpm_ec_group, tpm_ec_point] = Botan::TPM2::ecc_pubkey_from_tss2_public(key); - const auto tpm_sw_pubkey = Botan::ECDH_PublicKey(tpm_ec_group, tpm_ec_point.to_legacy_point()); + const auto tpm_sw_pubkey = Botan::ECDH_PublicKey(tpm_ec_group, tpm_ec_point); const auto curve_order_byte_size = tpm_sw_pubkey.domain().get_p_bytes(); @@ -660,7 +660,7 @@ eph_pub_point.serialize_y_to(Botan::TPM2::as_span(Q->y, curve_order_byte_size)); // 3: ECDH Key Agreement - Botan::PK_Key_Agreement ecdh(eph_key, rng, "Raw" /*No KDF used here*/); + const Botan::PK_Key_Agreement ecdh(eph_key, rng, "Raw" /*No KDF used here*/); const auto shared_secret = ecdh.derive_key(0 /*Ignored for raw KDF*/, tpm_sw_pubkey.public_value()).bits_of(); Botan::TPM2::copy_into(*Z, shared_secret); @@ -681,7 +681,7 @@ * @param[in] key key used for AES. * @param[in] tpm_sym_alg AES type in TSS2 notation (must be TPM2_ALG_AES). * @param[in] key_bits Key size in bits. - * @param[in] tpm_mode Block cipher mode of opertion in TSS2 notation (CFB). + * @param[in] tpm_mode Block cipher mode of operation in TSS2 notation (CFB). * For parameter encryption only CFB can be used. * @param[in,out] buffer Data to be encrypted. The encrypted date will be stored * in this buffer. @@ -712,7 +712,7 @@ * @param[in] key key used for AES. * @param[in] tpm_sym_alg AES type in TSS2 notation (must be TPM2_ALG_AES). * @param[in] key_bits Key size in bits. - * @param[in] tpm_mode Block cipher mode of opertion in TSS2 notation (CFB). + * @param[in] tpm_mode Block cipher mode of operation in TSS2 notation (CFB). * For parameter encryption only CFB can be used. * @param[in,out] buffer Data to be decrypted. The decrypted date will be stored * in this buffer. @@ -745,7 +745,7 @@ * @param[in] key key used for SM4. * @param[in] tpm_sym_alg SM4 type in TSS2 notation (must be TPM2_ALG_SM4). * @param[in] key_bits Key size in bits. - * @param[in] tpm_mode Block cipher mode of opertion in TSS2 notation (CFB). + * @param[in] tpm_mode Block cipher mode of operation in TSS2 notation (CFB). * For parameter encryption only CFB can be used. * @param[in,out] buffer Data to be encrypted. The encrypted date will be stored * in this buffer. @@ -776,7 +776,7 @@ * @param[in] key key used for SM4. * @param[in] tpm_sym_alg SM4 type in TSS2 notation (must be TPM2_ALG_SM4). * @param[in] key_bits Key size in bits. - * @param[in] tpm_mode Block cipher mode of opertion in TSS2 notation (CFB). + * @param[in] tpm_mode Block cipher mode of operation in TSS2 notation (CFB). * For parameter encryption only CFB can be used. * @param[in,out] buffer Data to be decrypted. The decrypted date will be stored * in this buffer. diff -Nru botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_ecc/tpm2_ecc.cpp botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_ecc/tpm2_ecc.cpp --- botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_ecc/tpm2_ecc.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_ecc/tpm2_ecc.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,7 +8,7 @@ #include -#include +#include #include #include #include @@ -21,9 +21,11 @@ EC_PublicKey::EC_PublicKey(Object handle, SessionBundle sessions, const TPM2B_PUBLIC* public_blob) : EC_PublicKey(std::move(handle), std::move(sessions), ecc_pubkey_from_tss2_public(public_blob)) {} -EC_PublicKey::EC_PublicKey(Object handle, SessionBundle sessions, std::pair public_key) : +EC_PublicKey::EC_PublicKey(Object handle, + SessionBundle sessions, + const std::pair& public_key) : Botan::TPM2::PublicKey(std::move(handle), std::move(sessions)), - Botan::EC_PublicKey(std::move(public_key.first), public_key.second) {} + Botan::EC_PublicKey(public_key.first, public_key.second) {} EC_PrivateKey::EC_PrivateKey(Object handle, SessionBundle sessions, @@ -33,13 +35,13 @@ EC_PrivateKey::EC_PrivateKey(Object handle, SessionBundle sessions, - std::pair public_key, + const std::pair& public_key, std::span private_blob) : Botan::TPM2::PrivateKey(std::move(handle), std::move(sessions), private_blob), - Botan::EC_PublicKey(std::move(public_key.first), public_key.second) {} + Botan::EC_PublicKey(public_key.first, public_key.second) {} std::unique_ptr EC_PrivateKey::public_key() const { - return std::make_unique(domain(), public_point()); + return std::make_unique(domain(), _public_ec_point()); } std::vector EC_PublicKey::public_key_bits() const { @@ -71,7 +73,7 @@ throw Invalid_Argument("Unsupported ECC curve"); } - TPM2B_SENSITIVE_CREATE sensitive_data = { + const TPM2B_SENSITIVE_CREATE sensitive_data = { .size = 0, // ignored .sensitive = { @@ -84,7 +86,7 @@ }, }; - TPMT_PUBLIC key_template = { + const TPMT_PUBLIC key_template = { .type = TPM2_ALG_ECC, // This is the algorithm for fingerprinting the newly created public key. @@ -171,7 +173,7 @@ }; } -size_t signature_length_for_key_handle(const SessionBundle& sessions, const Object& object) { +size_t signature_length_for_ecdsa_key_handle(const SessionBundle& sessions, const Object& object) { const auto curve_id = object._public_info(sessions, TPM2_ALG_ECDSA).pub->publicArea.parameters.eccDetail.curveID; const auto order_bytes = curve_id_order_byte_size(curve_id); @@ -186,7 +188,9 @@ EC_Signature_Operation(const Object& object, const SessionBundle& sessions, std::string_view hash) : Signature_Operation(object, sessions, make_signature_scheme(hash)) {} - size_t signature_length() const override { return signature_length_for_key_handle(sessions(), key_handle()); } + size_t signature_length() const override { + return signature_length_for_ecdsa_key_handle(sessions(), key_handle()); + } AlgorithmIdentifier algorithm_identifier() const override { // Copied from ECDSA @@ -201,7 +205,7 @@ const auto r = as_span(signature.signature.ecdsa.signatureR); const auto s = as_span(signature.signature.ecdsa.signatureS); - const auto sig_len = signature_length_for_key_handle(sessions(), key_handle()); + const auto sig_len = signature_length_for_ecdsa_key_handle(sessions(), key_handle()); BOTAN_ASSERT_NOMSG(sig_len % 2 == 0); BOTAN_ASSERT_NOMSG(r.size() == sig_len / 2 && s.size() == sig_len / 2); @@ -218,7 +222,7 @@ TPMT_SIGNATURE unmarshal_signature(std::span sig_data) const override { BOTAN_STATE_CHECK(scheme().scheme == TPM2_ALG_ECDSA); - const auto sig_len = signature_length_for_key_handle(sessions(), key_handle()); + const auto sig_len = signature_length_for_ecdsa_key_handle(sessions(), key_handle()); BOTAN_ARG_CHECK(sig_data.size() == sig_len, "Invalid signature length"); BOTAN_ASSERT_NOMSG(sig_len % 2 == 0); diff -Nru botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_ecc/tpm2_ecc.h botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_ecc/tpm2_ecc.h --- botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_ecc/tpm2_ecc.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_ecc/tpm2_ecc.h 2026-05-07 01:38:28.000000000 +0000 @@ -44,7 +44,7 @@ friend class TPM2::PublicKey; EC_PublicKey(Object handle, SessionBundle sessions, const TPM2B_PUBLIC* public_blob); - EC_PublicKey(Object handle, SessionBundle sessions, std::pair public_key); + EC_PublicKey(Object handle, SessionBundle sessions, const std::pair& public_key); }; class BOTAN_PUBLIC_API(3, 6) EC_PrivateKey final : public virtual Botan::TPM2::PrivateKey, @@ -59,7 +59,7 @@ return "ECDSA"; } - std::unique_ptr generate_another(Botan::RandomNumberGenerator&) const override { + std::unique_ptr generate_another(Botan::RandomNumberGenerator& /*rng*/) const override { throw Not_Implemented("Cannot generate a new TPM-based keypair from this asymmetric key"); } @@ -110,7 +110,7 @@ EC_PrivateKey(Object handle, SessionBundle sessions, - std::pair public_key, + const std::pair& public_key, std::span private_blob = {}); }; diff -Nru botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_hash.cpp botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_hash.cpp --- botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_hash.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_hash.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,6 +8,7 @@ #include +#include #include #include #include @@ -71,7 +72,7 @@ const auto auth = init_empty(); const auto rc = check_rc_expecting("Esys_HashSequenceStart", - Esys_HashSequenceStart(*m_handle.context(), + Esys_HashSequenceStart(m_handle.context()->esys_context(), m_sessions[0], m_sessions[1], m_sessions[2], @@ -91,10 +92,13 @@ while(slicer.remaining() > 0) { const size_t chunk = std::min(slicer.remaining(), size_t(TPM2_MAX_DIGEST_BUFFER)); const auto data = copy_into(slicer.take(chunk)); - check_rc( - "Esys_SequenceUpdate", - Esys_SequenceUpdate( - *m_handle.context(), m_handle.transient_handle(), m_sessions[0], m_sessions[1], m_sessions[2], &data)); + check_rc("Esys_SequenceUpdate", + Esys_SequenceUpdate(m_handle.context()->esys_context(), + m_handle.transient_handle(), + m_sessions[0], + m_sessions[1], + m_sessions[2], + &data)); } BOTAN_ASSERT_NOMSG(slicer.empty()); } @@ -106,7 +110,7 @@ const auto nodata = init_empty(); check_rc("Esys_SequenceComplete", - Esys_SequenceComplete(*m_handle.context(), + Esys_SequenceComplete(m_handle.context()->esys_context(), m_handle.transient_handle(), m_sessions[0], m_sessions[1], diff -Nru botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_key.cpp botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_key.cpp --- botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_key.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_key.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -15,6 +15,7 @@ #include #endif +#include #include #include #include @@ -76,16 +77,20 @@ Object object(ctx); check_rc("Esys_TR_FromTPMPublic", - Esys_TR_FromTPMPublic( - *ctx, persistent_object_handle, sessions[0], sessions[1], sessions[2], out_transient_handle(object))); + Esys_TR_FromTPMPublic(ctx->esys_context(), + persistent_object_handle, + sessions[0], + sessions[1], + sessions[2], + out_transient_handle(object))); if(!auth_value.empty()) { const auto user_auth = copy_into(auth_value); - check_rc("Esys_TR_SetAuth", Esys_TR_SetAuth(*ctx, object.transient_handle(), &user_auth)); + check_rc("Esys_TR_SetAuth", Esys_TR_SetAuth(ctx->esys_context(), object.transient_handle(), &user_auth)); } check_rc("Esys_TR_GetTpmHandle", - Esys_TR_GetTpmHandle(*ctx, object.transient_handle(), out_persistent_handle(object))); + Esys_TR_GetTpmHandle(ctx->esys_context(), object.transient_handle(), out_persistent_handle(object))); const auto key_type = object._public_info(sessions).pub->publicArea.type; BOTAN_ARG_CHECK(key_type == TPM2_ALG_RSA || key_type == TPM2_ALG_ECC, @@ -140,7 +145,7 @@ Object handle(ctx); check_rc("Esys_LoadExternal", - Esys_LoadExternal(*ctx, + Esys_LoadExternal(ctx->esys_context(), sessions[0], sessions[1], sessions[2], @@ -197,7 +202,7 @@ const auto private_data = copy_into(private_blob); check_rc("Esys_Load", - Esys_Load(*ctx, + Esys_Load(ctx->esys_context(), parent.handles().transient_handle(), sessions[0], sessions[1], @@ -208,7 +213,7 @@ if(!auth_value.empty()) { const auto user_auth = copy_into(auth_value); - check_rc("Esys_TR_SetAuth", Esys_TR_SetAuth(*ctx, handle.transient_handle(), &user_auth)); + check_rc("Esys_TR_SetAuth", Esys_TR_SetAuth(ctx->esys_context(), handle.transient_handle(), &user_auth)); } return create(std::move(handle), sessions, nullptr /* pull public info from handle */, private_blob); @@ -221,6 +226,8 @@ const TPM2B_SENSITIVE_CREATE& sensitive_data) { BOTAN_ASSERT_NONNULL(ctx); + // NOLINTBEGIN(*-branch-clone) + switch(key_template.type) { case TPM2_ALG_RSA: #if not defined(BOTAN_HAS_TPM2_RSA_ADAPTER) @@ -236,6 +243,8 @@ throw Invalid_Argument("Unsupported key type"); } + // NOLINTEND(*-branch-clone) + const auto marshalled_template = marshal_template(key_template); Object handle(ctx); @@ -250,7 +259,7 @@ // // See the Architecture Document, Section 27.1. check_rc("Esys_CreateLoaded", - Esys_CreateLoaded(*ctx, + Esys_CreateLoaded(ctx->esys_context(), parent, sessions[0], sessions[1], @@ -289,7 +298,7 @@ [[maybe_unused]] const SessionBundle& sessions, [[maybe_unused]] const TPM2B_PUBLIC* public_info, [[maybe_unused]] std::span private_blob) { - if(!public_info) { + if(public_info == nullptr) { public_info = handles._public_info(sessions).pub.get(); } diff -Nru botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_key.h botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_key.h --- botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_key.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_key.h 2026-05-07 01:38:28.000000000 +0000 @@ -87,7 +87,7 @@ const SessionBundle& sessions); public: - std::unique_ptr generate_another(Botan::RandomNumberGenerator&) const override { + std::unique_ptr generate_another(Botan::RandomNumberGenerator& /*rng*/) const override { throw Not_Implemented("Cannot generate a new TPM-based keypair from this asymmetric key"); } diff -Nru botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_object.cpp botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_object.cpp --- botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_object.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_object.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -82,9 +82,9 @@ // Only purely transient objects have to be flushed if(has_transient_handle()) { if(has_persistent_handle()) { - Esys_TR_Close(*m_ctx, &m_handles->transient); + Esys_TR_Close(m_ctx->esys_context(), &m_handles->transient); } else { - Esys_FlushContext(*m_ctx, m_handles->transient); + Esys_FlushContext(m_ctx->esys_context(), m_handles->transient); } } } @@ -136,7 +136,7 @@ m_public_info = std::make_unique(); check_rc("Esys_ReadPublic", - Esys_ReadPublic(*m_ctx, + Esys_ReadPublic(m_ctx->esys_context(), m_handles->transient, sessions[0], sessions[1], diff -Nru botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_pkops.cpp botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_pkops.cpp --- botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_pkops.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_pkops.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -54,7 +54,7 @@ auto do_sign = [this](const TPM2B_DIGEST& digest, const TPMT_TK_HASHCHECK& validation) { unique_esys_ptr signature; check_rc("Esys_Sign", - Esys_Sign(*key_handle().context(), + Esys_Sign(key_handle().context()->esys_context(), key_handle().transient_handle(), sessions()[0], sessions()[1], @@ -70,7 +70,7 @@ }; auto signature = [&] { - if(auto h = dynamic_cast(hash())) { + if(auto* h = dynamic_cast(hash())) { // This is a TPM2-based hash object that calculated the digest on // the TPM. We can use the validation ticket to create the signature. auto [digest, validation] = h->final_with_ticket(); @@ -108,7 +108,7 @@ // If the signature is not valid, this returns TPM2_RC_SIGNATURE. const auto rc = check_rc_expecting("Esys_VerifySignature", - Esys_VerifySignature(*key_handle().context(), + Esys_VerifySignature(key_handle().context()->esys_context(), key_handle().transient_handle(), sessions()[0], sessions()[1], diff -Nru botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_rng.cpp botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_rng.cpp --- botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_rng.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_rng.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,6 +8,8 @@ #include +#include +#include #include #include @@ -29,7 +31,8 @@ const size_t chunk = std::min(in.remaining(), MAX_STIR_RANDOM_SIZE); const auto data = copy_into(in.take(chunk)); - check_rc("Esys_StirRandom", Esys_StirRandom(*m_ctx, m_sessions[0], m_sessions[1], m_sessions[2], &data)); + check_rc("Esys_StirRandom", + Esys_StirRandom(m_ctx->esys_context(), m_sessions[0], m_sessions[1], m_sessions[2], &data)); } BOTAN_ASSERT_NOMSG(in.empty()); @@ -38,7 +41,7 @@ unique_esys_ptr digest = nullptr; const auto requested_bytes = std::min(out.remaining_capacity(), m_max_tpm2_rng_bytes); check_rc("Esys_GetRandom", - Esys_GetRandom(*m_ctx, + Esys_GetRandom(m_ctx->esys_context(), m_sessions[0], m_sessions[1], m_sessions[2], diff -Nru botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_rng.h botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_rng.h --- botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_rng.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_rng.h 2026-05-07 01:38:28.000000000 +0000 @@ -21,7 +21,7 @@ */ class BOTAN_PUBLIC_API(3, 6) RandomNumberGenerator final : public Hardware_RNG { public: - RandomNumberGenerator(std::shared_ptr ctx, SessionBundle sessions = {}); + BOTAN_FUTURE_EXPLICIT RandomNumberGenerator(std::shared_ptr ctx, SessionBundle sessions = {}); bool accepts_input() const override { return true; } diff -Nru botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_rsa/info.txt botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_rsa/info.txt --- botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_rsa/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_rsa/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -9,7 +9,7 @@ rsa -pk_pad +sig_padding diff -Nru botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_rsa/tpm2_rsa.cpp botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_rsa/tpm2_rsa.cpp --- botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_rsa/tpm2_rsa.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_rsa/tpm2_rsa.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -13,9 +13,9 @@ #include #include -#include #include #include +#include #include #include #include @@ -26,17 +26,6 @@ namespace Botan::TPM2 { -std::pair rsa_pubkey_components_from_tss2_public(const TPM2B_PUBLIC* public_area) { - BOTAN_ASSERT_NONNULL(public_area); - const auto& pub = public_area->publicArea; - BOTAN_ARG_CHECK(pub.type == TPM2_ALG_RSA, "Public key is not an RSA key"); - - // TPM2 may report 0 when the exponent is 'the default' (2^16 + 1) - const auto exponent = (pub.parameters.rsaDetail.exponent == 0) ? 65537 : pub.parameters.rsaDetail.exponent; - - return {BigInt(as_span(pub.unique.rsa)), exponent}; -} - RSA_PublicKey::RSA_PublicKey(Object handle, SessionBundle session_bundle, const TPM2B_PUBLIC* public_blob) : Botan::TPM2::RSA_PublicKey( std::move(handle), std::move(session_bundle), rsa_pubkey_components_from_tss2_public(public_blob)) {} @@ -73,7 +62,7 @@ std::optional exponent) { BOTAN_ARG_CHECK(parent.is_parent(), "The passed key cannot be used as a parent key"); - TPM2B_SENSITIVE_CREATE sensitive_data = { + const TPM2B_SENSITIVE_CREATE sensitive_data = { .size = 0, // ignored .sensitive = { @@ -86,7 +75,7 @@ }, }; - TPMT_PUBLIC key_template = { + const TPMT_PUBLIC key_template = { .type = TPM2_ALG_RSA, // This is the algorithm for fingerprinting the newly created public key. @@ -171,7 +160,7 @@ }; } -size_t signature_length_for_key_handle(const SessionBundle& sessions, const Object& key_handle) { +size_t signature_length_for_rsa_key_handle(const SessionBundle& sessions, const Object& key_handle) { return key_handle._public_info(sessions, TPM2_ALG_RSA).pub->publicArea.parameters.rsaDetail.keyBits / 8; } @@ -180,7 +169,7 @@ RSA_Signature_Operation(const Object& object, const SessionBundle& sessions, std::string_view padding) : Signature_Operation(object, sessions, select_signature_algorithms(padding)) {} - size_t signature_length() const override { return signature_length_for_key_handle(sessions(), key_handle()); } + size_t signature_length() const override { return signature_length_for_rsa_key_handle(sessions(), key_handle()); } AlgorithmIdentifier algorithm_identifier() const override { // TODO: This is essentially a copy of the ::algorithm_identifier() @@ -192,21 +181,20 @@ // // TODO: This is a hack, and we should clean this up. BOTAN_STATE_CHECK(padding().has_value()); - const auto emsa = EMSA::create_or_throw(padding().value()); - const std::string emsa_name = emsa->name(); + const std::string padding_name = SignaturePaddingScheme::create_or_throw(padding().value())->name(); try { - const std::string full_name = "RSA/" + emsa_name; + const std::string full_name = "RSA/" + padding_name; const OID oid = OID::from_string(full_name); return AlgorithmIdentifier(oid, AlgorithmIdentifier::USE_EMPTY_PARAM); } catch(Lookup_Error&) {} - if(emsa_name.starts_with("PSS(")) { - auto parameters = PSS_Params::from_emsa_name(emsa_name).serialize(); + if(padding_name.starts_with("PSS(")) { + auto parameters = PSS_Params::from_padding_name(padding_name).serialize(); return AlgorithmIdentifier("RSA/PSS", parameters); } - throw Invalid_Argument(fmt("Signatures using RSA/{} are not supported", emsa_name)); + throw Invalid_Argument(fmt("Signatures using RSA/{} are not supported", padding_name)); } private: @@ -232,7 +220,7 @@ private: TPMT_SIGNATURE unmarshal_signature(std::span signature) const override { - BOTAN_ARG_CHECK(signature.size() == signature_length_for_key_handle(sessions(), key_handle()), + BOTAN_ARG_CHECK(signature.size() == signature_length_for_rsa_key_handle(sessions(), key_handle()), "Unexpected signature byte length"); TPMT_SIGNATURE sig; @@ -279,7 +267,7 @@ unique_esys_ptr ciphertext; check_rc("Esys_RSA_Encrypt", - Esys_RSA_Encrypt(*m_key_handle.context(), + Esys_RSA_Encrypt(m_key_handle.context()->esys_context(), m_key_handle.transient_handle(), m_sessions[0], m_sessions[1], @@ -358,7 +346,7 @@ // all cases here. It passed the test (with a faulty ciphertext), // but I didn't find this to be clearly documented. :-( auto rc = check_rc_expecting("Esys_RSA_Decrypt", - Esys_RSA_Decrypt(*m_key_handle.context(), + Esys_RSA_Decrypt(m_key_handle.context()->esys_context(), m_key_handle.transient_handle(), m_sessions[0], m_sessions[1], diff -Nru botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_rsa/tpm2_rsa.h botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_rsa/tpm2_rsa.h --- botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_rsa/tpm2_rsa.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_rsa/tpm2_rsa.h 2026-05-07 01:38:28.000000000 +0000 @@ -13,15 +13,6 @@ namespace Botan::TPM2 { -/** - * This helper function transforms a @p public_blob in a TPM2B_PUBLIC* format - * into the functional components of an RSA public key. Namely, a pair of - * modulus and exponent as big integers. - * - * @param public_blob The public blob to decompose into RSA pubkey components - */ -std::pair rsa_pubkey_components_from_tss2_public(const TPM2B_PUBLIC* public_blob); - BOTAN_DIAGNOSTIC_PUSH BOTAN_DIAGNOSTIC_IGNORE_INHERITED_VIA_DOMINANCE diff -Nru botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_session.cpp botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_session.cpp --- botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_session.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_session.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -49,7 +49,7 @@ BOTAN_ASSERT_NONNULL(ctx); check_rc("Esys_StartSession", - Esys_StartAuthSession(*ctx, + Esys_StartAuthSession(ctx->esys_context(), ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, @@ -80,7 +80,7 @@ BOTAN_ASSERT_NONNULL(ctx); check_rc("Esys_StartSession", - Esys_StartAuthSession(*ctx, + Esys_StartAuthSession(ctx->esys_context(), tpm_key.handles().transient_handle(), tpm_key.handles().transient_handle(), ESYS_TR_NONE, @@ -105,25 +105,38 @@ } SessionAttributes Session::attributes() const { - TPMA_SESSION attrs; + TPMA_SESSION attrs = 0; check_rc("Esys_TRSess_GetAttributes", - Esys_TRSess_GetAttributes(*m_session.context(), m_session.transient_handle(), &attrs)); + Esys_TRSess_GetAttributes(m_session.context()->esys_context(), m_session.transient_handle(), &attrs)); return SessionAttributes::read(attrs); } void Session::set_attributes(SessionAttributes attributes) { check_rc("Esys_TRSess_SetAttributes", - Esys_TRSess_SetAttributes( - *m_session.context(), m_session.transient_handle(), SessionAttributes::render(attributes), 0xFF)); + Esys_TRSess_SetAttributes(m_session.context()->esys_context(), + m_session.transient_handle(), + SessionAttributes::render(attributes), + 0xFF)); } secure_vector Session::tpm_nonce() const { unique_esys_ptr nonce; check_rc("Esys_TRSess_GetNonceTPM", - Esys_TRSess_GetNonceTPM(*m_session.context(), m_session.transient_handle(), out_ptr(nonce))); + Esys_TRSess_GetNonceTPM(m_session.context()->esys_context(), m_session.transient_handle(), out_ptr(nonce))); return copy_into>(*nonce); } +detail::SessionHandle::SessionHandle(Session& session) : + m_session(session), m_original_attributes(session.attributes()) {} + +detail::SessionHandle::~SessionHandle() { + try { + if(m_session) { + m_session->get().set_attributes(m_original_attributes); + } + } catch(...) {} +} + [[nodiscard]] detail::SessionHandle::operator ESYS_TR() && noexcept { if(m_session) { return m_session->get().transient_handle(); diff -Nru botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_session.h botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_session.h --- botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_session.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_session.h 2026-05-07 01:38:28.000000000 +0000 @@ -69,12 +69,14 @@ SessionHandle& operator=(SessionHandle&&) = delete; ~SessionHandle(); + + // NOLINTNEXTLINE(*-explicit-conversions) Intentional: SessionHandle is a wrapper around ESYS_TR for C API interop [[nodiscard]] operator ESYS_TR() && noexcept; private: friend class Botan::TPM2::Session; - SessionHandle(Session& session); + explicit SessionHandle(Session& session); private: std::optional> m_session; @@ -138,7 +140,7 @@ */ Session(std::shared_ptr ctx, ESYS_TR session_handle) : m_session(std::move(ctx), session_handle) {} - [[nodiscard]] detail::SessionHandle handle() { return *this; } + [[nodiscard]] detail::SessionHandle handle() { return detail::SessionHandle(*this); } SessionAttributes attributes() const; void set_attributes(SessionAttributes attributes); @@ -156,15 +158,6 @@ Object m_session; }; -inline detail::SessionHandle::~SessionHandle() { - if(m_session) { - m_session->get().set_attributes(m_original_attributes); - } -} - -inline detail::SessionHandle::SessionHandle(Session& session) : - m_session(session), m_original_attributes(session.attributes()) {} - /** * This bundles up to three sessions into a single object to be used in a * single TSS2 library function call to simplify passing the sessions around @@ -172,6 +165,7 @@ */ class SessionBundle { public: + // NOLINTNEXTLINE(*-explicit-conversions) Intentional: Allows convenient single-session usage without explicit SessionBundle construction SessionBundle(std::shared_ptr s1 = nullptr, std::shared_ptr s2 = nullptr, std::shared_ptr s3 = nullptr) : diff -Nru botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_util.h botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_util.h --- botan3-3.7.1+dfsg/src/lib/prov/tpm2/tpm2_util.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/prov/tpm2/tpm2_util.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,6 +9,7 @@ #ifndef BOTAN_TPM2_UTIL_H_ #define BOTAN_TPM2_UTIL_H_ +#include #include #include #include @@ -47,9 +48,9 @@ namespace Botan::TPM2 { /** - * Check the return code and throw an exception if some error occured. + * Check the return code and throw an exception if some error occurred. * - * @throws TPM2::Error if an error occured. + * @throws TPM2::Error if an error occurred. */ constexpr void check_rc(std::string_view location, TSS2_RC rc) { if(rc != TSS2_RC_SUCCESS) { @@ -58,13 +59,13 @@ } /** - * Check the return code and throw an exception if an unexpected error occured. + * Check the return code and throw an exception if an unexpected error occurred. * * Errors that are listed in the `expected_errors` parameter are considered * expected and will not cause an exception to be thrown. Instead the error * code is decoded and returned to the caller for further processing. * - * @throws TPM2::Error if an unexpected error occured. + * @throws TPM2::Error if an unexpected error occurred. * @returns TSS2_RC_SUCCESS or one of the expected error codes. */ template @@ -75,7 +76,7 @@ return rc; } - // An error occured, we need to decode it to check if it was expected. + // An error occurred, we need to decode it to check if it was expected. const TSS2_RC decoded_rc = get_raw_rc(rc); // Check if the error is one of the expected and return those to the caller. @@ -122,7 +123,7 @@ /// provided @p data is not larger than the capacity of the buffer type. template constexpr T copy_into(std::span data) { - T result; + T result{}; copy_into(result, data); return result; } @@ -140,7 +141,7 @@ /// Create a TPM2 buffer of a given type and @p length. template constexpr T init_with_size(size_t length) { - T result; + T result{}; BOTAN_ASSERT_NOMSG(length <= sizeof(result.buffer)); result.size = static_cast(length); clear_bytes(result.buffer, length); @@ -181,7 +182,7 @@ */ class ObjectSetter { public: - constexpr ObjectSetter(Object& object, bool persistent = false) : + constexpr explicit ObjectSetter(Object& object, bool persistent = false) : m_object(object), m_persistent(persistent), m_handle(persistent ? 0 : ESYS_TR_NONE) {} constexpr ~ObjectSetter() noexcept { @@ -201,6 +202,7 @@ ObjectSetter& operator=(const ObjectSetter&) = delete; ObjectSetter& operator=(ObjectSetter&&) = delete; + // NOLINTNEXTLINE(*-explicit-conversions) FIXME [[nodiscard]] constexpr operator uint32_t*() && noexcept { return &m_handle; } private: @@ -248,7 +250,7 @@ * * @tparam UnderlyingT the TPMA_* bit field type * @tparam AttributeWrapperT the C++ struct type that wraps the TPMA_* bit field - * @tparam props a bunch of std::pair mappping boolean members of + * @tparam props a bunch of std::pair mapping boolean members of * AttributeWrapperT to the bit masks of the TPMA_* type */ template &> FnT> - static constexpr void for_all(FnT&& fn) { + static constexpr void for_all(const FnT& fn) { (fn(props), ...); } @@ -287,6 +289,28 @@ } }; +#if defined(BOTAN_HAS_RSA) + +/** + * This helper function transforms a @p public_blob in a TPM2B_PUBLIC* format + * into the functional components of an RSA public key. Namely, a pair of + * modulus and exponent as big integers. + * + * @param public_blob The public blob to decompose into RSA pubkey components + */ +inline std::pair rsa_pubkey_components_from_tss2_public(const TPM2B_PUBLIC* public_blob) { + BOTAN_ASSERT_NONNULL(public_blob); + const auto& pub = public_blob->publicArea; + BOTAN_ARG_CHECK(pub.type == TPM2_ALG_RSA, "Public key is not an RSA key"); + + // TPM2 may report 0 when the exponent is 'the default' (2^16 + 1) + const auto exponent = (pub.parameters.rsaDetail.exponent == 0) ? 65537 : pub.parameters.rsaDetail.exponent; + + return {BigInt(as_span(pub.unique.rsa)), exponent}; +} + +#endif + } // namespace Botan::TPM2 #endif diff -Nru botan3-3.7.1+dfsg/src/lib/psk_db/psk_db.cpp botan3-3.12.0+dfsg/src/lib/psk_db/psk_db.cpp --- botan3-3.7.1+dfsg/src/lib/psk_db/psk_db.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/psk_db/psk_db.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -10,18 +10,18 @@ #include #include #include -#include #include +#include namespace Botan { std::string PSK_Database::get_str(std::string_view name) const { - secure_vector psk = this->get(name); - return std::string(cast_uint8_ptr_to_char(psk.data()), psk.size()); + return bytes_to_string(this->get(name)); } void PSK_Database::set_str(std::string_view name, std::string_view psk) { - this->set(name, cast_char_ptr_to_uint8(psk.data()), psk.size()); + auto pskb = as_span_of_bytes(psk); + this->set(name, pskb.data(), pskb.size()); } Encrypted_PSK_Database::Encrypted_PSK_Database(const secure_vector& master_key) { @@ -45,7 +45,7 @@ const secure_vector raw_name = base64_decode(enc_name); const secure_vector name_bits = nist_key_unwrap_padded(raw_name.data(), raw_name.size(), *m_cipher); - std::string pt_name(cast_uint8_ptr_to_char(name_bits.data()), name_bits.size()); + const auto pt_name = bytes_to_string(name_bits); names.insert(pt_name); } catch(Invalid_Authentication_Tag&) {} } @@ -54,15 +54,13 @@ } void Encrypted_PSK_Database::remove(std::string_view name) { - const std::vector wrapped_name = - nist_key_wrap_padded(cast_char_ptr_to_uint8(name.data()), name.size(), *m_cipher); + const auto wrapped_name = nist_key_wrap_padded(as_span_of_bytes(name), *m_cipher); this->kv_del(base64_encode(wrapped_name)); } secure_vector Encrypted_PSK_Database::get(std::string_view name) const { - const std::vector wrapped_name = - nist_key_wrap_padded(cast_char_ptr_to_uint8(name.data()), name.size(), *m_cipher); + const auto wrapped_name = nist_key_wrap_padded(as_span_of_bytes(name), *m_cipher); const std::string val_base64 = kv_get(base64_encode(wrapped_name)); @@ -80,12 +78,11 @@ void Encrypted_PSK_Database::set(std::string_view name, const uint8_t val[], size_t len) { /* - * Both as a basic precaution wrt key seperation, and specifically to prevent + * Both as a basic precaution wrt key separation, and specifically to prevent * cut-and-paste attacks against the database, each PSK is encrypted with a * distinct key which is derived by hashing the wrapped key name with HMAC. */ - const std::vector wrapped_name = - nist_key_wrap_padded(cast_char_ptr_to_uint8(name.data()), name.size(), *m_cipher); + const auto wrapped_name = nist_key_wrap_padded(as_span_of_bytes(name), *m_cipher); auto wrap_cipher = m_cipher->new_object(); wrap_cipher->set_key(m_hmac->process(wrapped_name)); diff -Nru botan3-3.7.1+dfsg/src/lib/psk_db/psk_db.h botan3-3.12.0+dfsg/src/lib/psk_db/psk_db.h --- botan3-3.7.1+dfsg/src/lib/psk_db/psk_db.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/psk_db/psk_db.h 2026-05-07 01:38:28.000000000 +0000 @@ -23,7 +23,7 @@ * It might be implemented as a plaintext storage or via some mechanism * that encrypts the keys and/or values. */ -class BOTAN_PUBLIC_API(2, 4) PSK_Database { +class BOTAN_PUBLIC_API(2, 4) PSK_Database /* NOLINT(*-special-member-functions) */ { public: /** * @returns the set of names for which get() will return a value. @@ -89,7 +89,7 @@ * Subclasses must implement the virtual calls to handle storing and getting raw * (base64 encoded) values. */ -class BOTAN_PUBLIC_API(2, 4) Encrypted_PSK_Database : public PSK_Database { +class BOTAN_PUBLIC_API(2, 4) Encrypted_PSK_Database : public PSK_Database /* NOLINT(*-special-member-functions) */ { public: /** * Initializes or opens a PSK database. The @p master_key is used to secure @@ -113,7 +113,7 @@ * using a password, it is recommended to use Argon2id to derive the database * master key. */ - Encrypted_PSK_Database(const secure_vector& master_key); + BOTAN_FUTURE_EXPLICIT Encrypted_PSK_Database(const secure_vector& master_key); ~Encrypted_PSK_Database() override; @@ -171,6 +171,11 @@ ~Encrypted_PSK_Database_SQL() override; + Encrypted_PSK_Database_SQL(const Encrypted_PSK_Database_SQL& other) = delete; + Encrypted_PSK_Database_SQL(Encrypted_PSK_Database_SQL&& other) = delete; + Encrypted_PSK_Database_SQL& operator=(const Encrypted_PSK_Database_SQL& other) = delete; + Encrypted_PSK_Database_SQL& operator=(Encrypted_PSK_Database_SQL&& other) = delete; + private: void kv_set(std::string_view index, std::string_view value) override; std::string kv_get(std::string_view index) const override; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/blinding/blinding.cpp botan3-3.12.0+dfsg/src/lib/pubkey/blinding/blinding.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/blinding/blinding.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/blinding/blinding.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,7 +9,7 @@ namespace Botan { -Blinder::Blinder(const Modular_Reducer& reducer, +Blinder::Blinder(const Barrett_Reduction& reducer, RandomNumberGenerator& rng, std::function fwd, std::function inv) : @@ -17,9 +17,7 @@ m_rng(rng), m_fwd_fn(std::move(fwd)), m_inv_fn(std::move(inv)), - m_modulus_bits(reducer.get_modulus().bits()), - m_e{}, - m_d{}, + m_modulus_bits(reducer.modulus_bits()), m_counter{} { const BigInt k = blinding_nonce(); m_e = m_fwd_fn(k); @@ -33,7 +31,7 @@ BigInt Blinder::blind(const BigInt& i) const { ++m_counter; - if((BOTAN_BLINDING_REINIT_INTERVAL > 0) && (m_counter > BOTAN_BLINDING_REINIT_INTERVAL)) { + if((ReinitInterval > 0) && (m_counter > ReinitInterval)) { const BigInt k = blinding_nonce(); m_e = m_fwd_fn(k); m_d = m_inv_fn(k); diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/blinding/blinding.h botan3-3.12.0+dfsg/src/lib/pubkey/blinding/blinding.h --- botan3-3.7.1+dfsg/src/lib/pubkey/blinding/blinding.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/blinding/blinding.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,7 +9,7 @@ #define BOTAN_BLINDER_H_ #include -#include +#include #include namespace Botan { @@ -22,12 +22,29 @@ class Blinder final { public: /** + * Normally blinding is performed by choosing a random starting point (plus + * its inverse, of a form appropriate to the algorithm being blinded), and + * then choosing new blinding operands by successive squaring of both + * values. This is much faster than computing a new starting point but + * introduces some possible correlation + * + * To avoid possible leakage problems in long-running processes, the blinder + * periodically reinitializes the sequence. This value specifies how often + * a new sequence should be started. + * + * If set to zero, reinitialization is disabled + */ + static constexpr size_t ReinitInterval = 64; + + /** * Blind a value. - * The blinding nonce k is freshly generated after - * BOTAN_BLINDING_REINIT_INTERVAL calls to blind(). - * BOTAN_BLINDING_REINIT_INTERVAL = 0 means a fresh - * nonce is only generated once. On every other call, - * an updated nonce is used for blinding: k' = k*k mod n. + * + * The blinding nonce k is freshly generated after ReinitInterval + * calls to blind(). + * + * ReinitInterval = 0 means a fresh nonce is only generated once. + * On every other call, the next nonce is derived via modular squaring. + * * @param x value to blind * @return blinded value */ @@ -51,21 +68,23 @@ * @note Lifetime: The rng and reducer arguments are captured by * reference and must live as long as the Blinder does */ - Blinder(const Modular_Reducer& reducer, + Blinder(const Barrett_Reduction& reducer, RandomNumberGenerator& rng, std::function fwd_func, std::function inv_func); Blinder(const Blinder&) = delete; - + Blinder(Blinder&&) = default; Blinder& operator=(const Blinder&) = delete; + Blinder& operator=(Blinder&&) = delete; + ~Blinder() = default; RandomNumberGenerator& rng() const { return m_rng; } private: BigInt blinding_nonce() const; - const Modular_Reducer& m_reducer; + const Barrett_Reduction& m_reducer; RandomNumberGenerator& m_rng; std::function m_fwd_fn; std::function m_inv_fn; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/blinding/info.txt botan3-3.12.0+dfsg/src/lib/pubkey/blinding/info.txt --- botan3-3.7.1+dfsg/src/lib/pubkey/blinding/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/blinding/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,7 +1,3 @@ - -PUBLIC_KEY_BLINDING -> 20250125 - - name -> "Public Key Blinding" brief -> "Helper for BigInt blinding" diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce.cpp botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -11,13 +11,9 @@ #include #include #include -#include #include #include #include -#include - -#include namespace Botan { @@ -69,7 +65,7 @@ return m_public->matrix().bytes(); } -bool Classic_McEliece_PublicKey::check_key(RandomNumberGenerator&, bool) const { +bool Classic_McEliece_PublicKey::check_key(RandomNumberGenerator& /*rng*/, bool /*strong*/) const { return true; } @@ -126,7 +122,7 @@ return m_private->serialize(); } -bool Classic_McEliece_PrivateKey::check_key(RandomNumberGenerator&, bool) const { +bool Classic_McEliece_PrivateKey::check_key(RandomNumberGenerator& /*rng*/, bool /*strong*/) const { return m_private->check_key(); } diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce.h botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce.h --- botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce.h 2026-05-07 01:38:28.000000000 +0000 @@ -73,7 +73,7 @@ std::vector raw_public_key_bits() const override; - bool check_key(RandomNumberGenerator&, bool) const override; + bool check_key(RandomNumberGenerator& rng, bool strong) const override; bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::KeyEncapsulation); @@ -88,8 +88,7 @@ Classic_McEliece_PublicKey() = default; protected: - std::shared_ptr - m_public; // NOLINT(misc-non-private-member-variables-in-classes) + std::shared_ptr m_public; // NOLINT(*-non-private-member-variable*) }; BOTAN_DIAGNOSTIC_PUSH @@ -128,7 +127,7 @@ secure_vector raw_private_key_bits() const override; - bool check_key(RandomNumberGenerator&, bool) const override; + bool check_key(RandomNumberGenerator& rng, bool strong) const override; std::unique_ptr create_kem_decryption_op(RandomNumberGenerator& rng, std::string_view params, diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce_decaps.cpp botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_decaps.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce_decaps.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_decaps.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -11,6 +11,9 @@ #include +#include +#include + namespace Botan { Classic_McEliece_Polynomial Classic_McEliece_Decryptor::compute_goppa_syndrome( @@ -97,9 +100,9 @@ const auto locator = berlekamp_massey(m_key->params(), syndrome); std::vector images; - const auto alphas = m_key->field_ordering().alphas(m_key->params().n()); - std::transform( - alphas.begin(), alphas.end(), std::back_inserter(images), [&](const auto& alpha) { return locator(alpha); }); + for(const auto& alpha : m_key->field_ordering().alphas(m_key->params().n())) { + images.push_back(locator(alpha)); + } // Obtain e and check whether wt(e) = t. locator(alpha_i) = 0 <=> error at position i CmceErrorVector e; @@ -117,7 +120,7 @@ syndromes_are_eq &= GF_Mask::is_equal(syndrome.coef_at(i), syndrome_from_e.coef_at(i)); } - decode_success &= syndromes_are_eq.elem_mask(); + decode_success &= CT::Mask(syndromes_are_eq.elem_mask()); return {decode_success, std::move(e)}; } @@ -153,7 +156,7 @@ hash_func->update(0x02); hash_func->update(e_bytes); const auto c1_p = hash_func->final_stdvec(); - const CT::Mask eq_mask = CT::is_equal(c1.data(), c1_p.data(), c1.size()); + const CT::Mask eq_mask = CT::is_equal(c1, c1_p); eq_mask.select_n(e_bytes.data(), e_bytes.data(), m_key->s().data(), m_key->s().size()); b = eq_mask.select(b, 0); } diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce_decaps.h botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_decaps.h --- botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce_decaps.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_decaps.h 2026-05-07 01:38:28.000000000 +0000 @@ -11,7 +11,6 @@ #include #include -#include #include #include #include diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce_encaps.cpp botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_encaps.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce_encaps.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_encaps.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,9 +8,12 @@ * * Botan is released under the Simplified BSD License (see license.txt) **/ + #include #include +#include +#include namespace Botan { @@ -24,7 +27,7 @@ const Classic_McEliece_Parameters& params, RandomNumberGenerator& rng) const { const auto rand = rng.random_vec((params.sigma1() / 8) * params.tau()); CT::poison(rand); - uint16_t mask_m = (uint32_t(1) << params.m()) - 1; // Only take m least significant bits + const uint16_t mask_m = (uint32_t(1) << params.m()) - 1; // Only take m least significant bits secure_vector a_values; a_values.reserve(params.tau()); BufferSlicer rand_slicer(rand); @@ -37,7 +40,7 @@ // This side channel only leaks which random elements are selected and which are dropped, // but no information about their content is leaked. d &= mask_m; - bool d_in_range = d < params.n(); + const bool d_in_range = d < params.n(); CT::unpoison(d_in_range); if(d_in_range && a_values.size() < params.t()) { a_values.push_back(d); @@ -51,7 +54,7 @@ // Step 4: Restart if not all a_i are distinct for(size_t i = 1; i < params.t(); ++i) { for(size_t j = 0; j < i; ++j) { - bool a_i_j_equal = a_values.at(i) == a_values.at(j); + const bool a_i_j_equal = a_values.at(i) == a_values.at(j); CT::unpoison(a_i_j_equal); if(a_i_j_equal) { return std::nullopt; @@ -95,8 +98,8 @@ const CmceErrorVector e = [&] { // Emergency abort in case unexpected logical error to prevent endless loops // Success probability: >24% per attempt (25% that elements are distinct * 96% enough elements are in range) - // => 203 attempts for 2^(-80) fail probability - constexpr size_t MAX_ATTEMPTS = 203; + // => 647 attempts for 2^(-256) fail probability + constexpr size_t MAX_ATTEMPTS = 647; for(size_t attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) { if(auto maybe_e = fixed_weight_vector_gen(params, rng)) { return maybe_e.value(); diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce_field_ordering.cpp botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_field_ordering.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce_field_ordering.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_field_ordering.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,12 +8,12 @@ * * Botan is released under the Simplified BSD License (see license.txt) **/ + #include -#include -#include +#include #include - +#include #include #include #include @@ -287,7 +287,7 @@ const auto control_bits_as_words = generate_control_bits_internal(m_pi.get()); auto control_bits = secure_bitvector(control_bits_as_words.size()); for(size_t i = 0; i < control_bits.size(); ++i) { - control_bits.at(i) = control_bits_as_words.at(i); + control_bits.at(i) = control_bits_as_words.at(i) != 0; } return control_bits; @@ -303,9 +303,9 @@ std::iota(pi.begin(), pi.end(), static_cast(0)); for(size_t i = 0; i < 2 * params.m() - 1; ++i) { const size_t gap = size_t(1) << std::min(i, 2 * params.m() - 2 - i); - for(size_t j = 0; j < size_t(n / 2); ++j) { + for(size_t j = 0; j < size_t(n) / 2; ++j) { const size_t pos = (j % gap) + 2 * gap * (j / gap); - auto mask = CT::Mask::expand(control_bits[i * n / 2 + j]); + auto mask = CT::Mask::expand_bool(control_bits[i * n / 2 + j]); mask.conditional_swap(pi[pos], pi[pos + gap]); } } @@ -320,8 +320,8 @@ for(size_t p_idx = 1; p_idx <= Classic_McEliece_Parameters::mu(); ++p_idx) { size_t p_counter = 0; for(size_t col = 0; col < Classic_McEliece_Parameters::nu(); ++col) { - auto mask_is_pivot_set = CT::Mask::expand(pivots.at(col)); - p_counter += CT::Mask::expand(pivots.at(col)).if_set_return(1); + auto mask_is_pivot_set = CT::Mask::expand_bool(pivots.at(col)); + p_counter += mask_is_pivot_set.if_set_return(1); auto mask_is_current_pivot = CT::Mask::is_equal(p_idx, p_counter); (mask_is_pivot_set & mask_is_current_pivot) .conditional_swap(m_pi.get().at(col_offset + col), m_pi.get().at(col_offset + p_idx - 1)); diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce_field_ordering.h botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_field_ordering.h --- botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce_field_ordering.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_field_ordering.h 2026-05-07 01:38:28.000000000 +0000 @@ -12,8 +12,6 @@ #include #include -#include - namespace Botan { /** @@ -22,7 +20,7 @@ * Field ordering corresponds to the permutation pi defining the alpha sequence in * the Classic McEliece specification (see Classic McEliece ISO Sec. 8.2.). */ -class BOTAN_TEST_API Classic_McEliece_Field_Ordering { +class BOTAN_TEST_API Classic_McEliece_Field_Ordering final { public: /** * @brief Creates a field ordering from a random bit sequence. Corresponds to diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce_gf.cpp botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_gf.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce_gf.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_gf.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -47,8 +47,8 @@ Classic_McEliece_GF Classic_McEliece_GF::operator*(Classic_McEliece_GF other) const { BOTAN_ASSERT_NOMSG(m_modulus == other.m_modulus); - uint32_t a = m_elem.get(); - uint32_t b = other.m_elem.get(); + const uint32_t a = m_elem.get(); + const uint32_t b = other.m_elem.get(); uint32_t acc = a * (b & CT::value_barrier(1)); diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce_gf.h botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_gf.h --- botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce_gf.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_gf.h 2026-05-07 01:38:28.000000000 +0000 @@ -13,7 +13,6 @@ #include #include #include -#include namespace Botan { @@ -27,7 +26,7 @@ * the coefficient of z^i. For example, the element (z^3 + z^2 + 1) is represented * by the uint16_t 0b1101. */ -class BOTAN_TEST_API Classic_McEliece_GF { +class BOTAN_TEST_API Classic_McEliece_GF final { public: /** * @brief Creates an element of GF(q) from a uint16_t. @@ -171,7 +170,7 @@ static GF_Mask set() { return GF_Mask(CT::Mask::set()); } - GF_Mask(CT::Mask underlying_mask) : m_mask(underlying_mask) {} + explicit GF_Mask(CT::Mask underlying_mask) : m_mask(underlying_mask) {} Classic_McEliece_GF if_set_return(const Classic_McEliece_GF x) const { return Classic_McEliece_GF(CmceGfElem(m_mask.if_set_return(x.elem().get())), x.modulus()); diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce_keys_internal.cpp botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_keys_internal.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce_keys_internal.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_keys_internal.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,6 +8,9 @@ #include +#include +#include + namespace Botan { namespace { diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce_keys_internal.h botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_keys_internal.h --- botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce_keys_internal.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_keys_internal.h 2026-05-07 01:38:28.000000000 +0000 @@ -27,7 +27,7 @@ * - The Classic McEliece parameters * - The public key matrix */ -class BOTAN_TEST_API Classic_McEliece_PublicKeyInternal { +class BOTAN_TEST_API Classic_McEliece_PublicKeyInternal final { public: /** * @brief Construct a Classic McEliece public key. @@ -86,7 +86,7 @@ * - The field ordering alpha * - The seed s for implicit rejection */ -class BOTAN_TEST_API Classic_McEliece_PrivateKeyInternal { +class BOTAN_TEST_API Classic_McEliece_PrivateKeyInternal final { public: /** * @brief Construct a Classic McEliece private key. @@ -187,8 +187,8 @@ * @brief Representation of a Classic McEliece key pair. */ struct BOTAN_TEST_API Classic_McEliece_KeyPair_Internal { - std::shared_ptr private_key; - std::shared_ptr public_key; + std::shared_ptr private_key; // NOLINT(*non-private-member-variable*) + std::shared_ptr public_key; // NOLINT(*non-private-member-variable*) /** * @brief Generate a Classic McEliece key pair using the algorithm described diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce_matrix.cpp botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_matrix.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce_matrix.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_matrix.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -13,6 +13,8 @@ #include #include +#include +#include namespace Botan { @@ -31,10 +33,10 @@ } /// Swaps bit i with bit j in val -void swap_bits(uint64_t& val, size_t i, size_t j) { - uint64_t bit_i = (val >> i) & CT::value_barrier(1); - uint64_t bit_j = (val >> j) & CT::value_barrier(1); - uint64_t xor_sum = bit_i ^ bit_j; +void swap_1_bit(uint64_t& val, size_t i, size_t j) { + const uint64_t bit_i = (val >> i) & CT::value_barrier(1); + const uint64_t bit_j = (val >> j) & CT::value_barrier(1); + const uint64_t xor_sum = bit_i ^ bit_j; val ^= (xor_sum << i); val ^= (xor_sum << j); } @@ -64,8 +66,9 @@ for(size_t i = 0; i < params.t(); ++i) { for(size_t j = 0; j < params.n(); ++j) { + const auto inv_g = inv_g_of_alpha[j].elem().get(); for(size_t alpha_i_j_bit = 0; alpha_i_j_bit < params.m(); ++alpha_i_j_bit) { - mat[i * params.m() + alpha_i_j_bit][j] = (uint16_t(1) << alpha_i_j_bit) & inv_g_of_alpha[j].elem().get(); + mat[i * params.m() + alpha_i_j_bit][j] = static_cast((inv_g >> alpha_i_j_bit) & 1); } } // Update for the next i so that: @@ -97,7 +100,7 @@ // To find which columns need to be swapped to allow for a systematic matrix form, we need to // investigate how a gauss algorithm affects the last mu rows of the swap area. - std::array sub_mat; + std::array sub_mat; // NOLINT(*-member-init) // Extract the bottom mu x nu matrix at offset pos_offset for(size_t i = 0; i < Classic_McEliece_Parameters::mu(); i++) { @@ -126,7 +129,7 @@ // Using the row accumulator we can predict the index of the pivot // bit for the current row, i.e., the first index where we can set // the bit to one row by adding any subsequent row - size_t current_pivot_idx = count_lsb_zeros(row_acc); + const size_t current_pivot_idx = count_lsb_zeros(row_acc); pivot_indices.at(row_idx) = current_pivot_idx; // Add subsequent rows to the current row, until the pivot @@ -155,14 +158,14 @@ for(auto pivot_idx : pivot_indices) { for(size_t i = 0; i < Classic_McEliece_Parameters::nu(); ++i) { auto mask_is_at_current_idx = Botan::CT::Mask::is_equal(i, pivot_idx); - pivots.at(i) = mask_is_at_current_idx.select(1, pivots.at(i).as()); + pivots.at(i) = static_cast(mask_is_at_current_idx.select(1, pivots.at(i).as())); } } // Swap the rows so the matrix can be transformed into systematic form for(size_t mat_row = 0; mat_row < params.pk_no_rows(); ++mat_row) { for(size_t col = 0; col < Classic_McEliece_Parameters::mu(); ++col) { - swap_bits(matrix_swap_area.at(mat_row), col, pivot_indices.at(col)); + swap_1_bit(matrix_swap_area.at(mat_row), col, pivot_indices.at(col)); } } @@ -185,7 +188,7 @@ for(size_t diag_pos = 0; diag_pos < params.pk_no_rows(); ++diag_pos) { if(params.is_f() && diag_pos == params.pk_no_rows() - params.mu()) { auto ret_pivots = move_columns(mat, params); - bool move_columns_failed = !ret_pivots.has_value(); + const bool move_columns_failed = !ret_pivots.has_value(); CT::unpoison(move_columns_failed); if(move_columns_failed) { return std::nullopt; @@ -204,14 +207,14 @@ // If the current bit on the diagonal is not set at this point // the matrix is not systematic. We abort the computation in this case. - bool diag_bit_zero = !mat[diag_pos].at(diag_pos); + const bool diag_bit_zero = !mat[diag_pos].at(diag_pos); CT::unpoison(diag_bit_zero); if(diag_bit_zero) { return std::nullopt; } // Now the new row is added to all other rows, where the - // bit in the column of the current postion on the diagonal + // bit in the column of the current position on the diagonal // is still one for(size_t row = 0; row < params.pk_no_rows(); ++row) { if(row != diag_pos) { @@ -263,7 +266,7 @@ const Classic_McEliece_Minimal_Polynomial& g) { auto pk_matrix_and_pivots = create_matrix(params, field_ordering, g); - bool matrix_creation_failed = !pk_matrix_and_pivots.has_value(); + const bool matrix_creation_failed = !pk_matrix_and_pivots.has_value(); CT::unpoison(matrix_creation_failed); if(matrix_creation_failed) { return std::nullopt; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce_matrix.h botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_matrix.h --- botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce_matrix.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_matrix.h 2026-05-07 01:38:28.000000000 +0000 @@ -23,7 +23,7 @@ * * Only the bytes of the submatrix T are stored. */ -class BOTAN_TEST_API Classic_McEliece_Matrix { +class BOTAN_TEST_API Classic_McEliece_Matrix final { public: /** * @brief Create the matrix H for a Classic McEliece instance given its @@ -90,7 +90,7 @@ // Check padding of mat_bytes rows BOTAN_ASSERT_NOMSG(m_mat_bytes.size() == params.pk_no_rows() * params.pk_row_size_bytes()); for(size_t row = 0; row < params.pk_no_rows(); ++row) { - uint8_t padded_byte = m_mat_bytes[(row + 1) * params.pk_row_size_bytes() - 1]; + const uint8_t padded_byte = m_mat_bytes[(row + 1) * params.pk_row_size_bytes() - 1]; CT::unpoison(padded_byte); BOTAN_ARG_CHECK(padded_byte >> (params.pk_no_cols() % 8) == 0, "Valid padding of unused bytes"); } diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce_parameter_set.cpp botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_parameter_set.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce_parameter_set.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_parameter_set.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -14,7 +14,7 @@ namespace Botan { Classic_McEliece_Parameter_Set Classic_McEliece_Parameter_Set::from_string(std::string_view nm) { - Code code = [&] { + const Code code = [&] { if(nm == "ClassicMcEliece_348864" || nm == "348864") { return ClassicMcEliece_348864; } diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce_parameter_set.h botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_parameter_set.h --- botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce_parameter_set.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_parameter_set.h 2026-05-07 01:38:28.000000000 +0000 @@ -23,9 +23,9 @@ * Instance with 'pc' use plaintext confirmation as defined in the ISO Draft. * Instance with 'f' use matrix reduction with the semi-systematic form. */ -class BOTAN_PUBLIC_API(3, 4) Classic_McEliece_Parameter_Set { +class BOTAN_PUBLIC_API(3, 4) Classic_McEliece_Parameter_Set final { public: - enum class Code { + enum class Code : uint8_t { ClassicMcEliece_348864, // NIST ClassicMcEliece_348864f, // NIST @@ -50,6 +50,7 @@ using enum Code; + // NOLINTNEXTLINE(*-explicit-conversions) Classic_McEliece_Parameter_Set(Code code) : m_code(code) {} /** diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce_parameters.cpp botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_parameters.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce_parameters.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_parameters.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -40,7 +40,7 @@ } Classic_McEliece_Polynomial_Ring determine_poly_ring(Classic_McEliece_Parameter_Set param_set) { - CmceGfMod poly_f = determine_poly_f(param_set); + const CmceGfMod poly_f = determine_poly_f(param_set); switch(param_set.code()) { case Classic_McEliece_Parameter_Set::ClassicMcEliece_348864: diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce_parameters.h botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_parameters.h --- botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce_parameters.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_parameters.h 2026-05-07 01:38:28.000000000 +0000 @@ -20,9 +20,6 @@ namespace Botan { -struct Classic_McEliece_Big_F_Coefficient; -class Classic_McEliece_Polynomial_Ring; - /** * Container for all Classic McEliece parameters. */ @@ -135,7 +132,7 @@ size_t tau() const { // Section 8.4 of ISO: // The integer tau is defined as t if n=q; as 2t if q/2<=n + +#include +#include #include -#include +#include namespace Botan { @@ -19,6 +22,8 @@ BOTAN_DEBUG_ASSERT(a.modulus() == coef_at(0).modulus()); Classic_McEliece_GF r(CmceGfElem(0), a.modulus()); + // TODO(Botan4) use std::ranges::reverse_view here once available (need newer Clang) + // NOLINTNEXTLINE(modernize-loop-convert) for(auto it = m_coef.rbegin(); it != m_coef.rend(); ++it) { r *= a; r += *it; @@ -38,7 +43,7 @@ } for(size_t i = (m_t - 1) * 2; i >= m_t; --i) { - for(auto& [idx, coef] : m_position_map) { + for(const auto& [idx, coef] : m_position_map) { prod.at(i - m_t + idx) += coef * prod.at(i); } } @@ -126,12 +131,12 @@ secure_vector Classic_McEliece_Minimal_Polynomial::serialize() const { BOTAN_ASSERT_NOMSG(!coef().empty()); - auto& all_coeffs = coef(); + const auto& all_coeffs = coef(); // Store all except coef for monomial x^t since polynomial is monic (ISO Spec Section 9.2.9) auto coeffs_to_store = std::span(all_coeffs).first(all_coeffs.size() - 1); secure_vector bytes(sizeof(uint16_t) * coeffs_to_store.size()); BufferStuffer bytes_stuf(bytes); - for(auto& coef : coeffs_to_store) { + for(const auto& coef : coeffs_to_store) { store_le(bytes_stuf.next(), coef.elem().get()); } BOTAN_ASSERT_NOMSG(bytes_stuf.full()); diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce_poly.h botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_poly.h --- botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce_poly.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_poly.h 2026-05-07 01:38:28.000000000 +0000 @@ -36,7 +36,7 @@ * * @param coef The coefficients of the polynomial. The first element is the coefficient of the lowest monomial. */ - Classic_McEliece_Polynomial(std::vector coef) : m_coef(std::move(coef)) {} + explicit Classic_McEliece_Polynomial(std::vector coef) : m_coef(std::move(coef)) {} /** * @brief Evaluate the polynomial P(x) at a given point a, i.e., compute P(a). @@ -78,9 +78,9 @@ * * It represents the monic irreducible degree-t polynomial of the goppa code. */ -class BOTAN_TEST_API Classic_McEliece_Minimal_Polynomial : public Classic_McEliece_Polynomial { +class BOTAN_TEST_API Classic_McEliece_Minimal_Polynomial final : public Classic_McEliece_Polynomial { public: - Classic_McEliece_Minimal_Polynomial(std::vector coef) : + explicit Classic_McEliece_Minimal_Polynomial(std::vector coef) : Classic_McEliece_Polynomial(std::move(coef)) {} /** @@ -101,7 +101,7 @@ * This class contains a modulus polynomial F(y) and the GF(q) modulus f(z). It is used * to create and operate with Classic_McEliece_Polynomials. */ -class BOTAN_TEST_API Classic_McEliece_Polynomial_Ring { +class BOTAN_TEST_API Classic_McEliece_Polynomial_Ring final { public: /** * @brief Represents a non-zero coefficient of the modulus F(y) (which is in GF(q)[y]). diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce_types.h botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_types.h --- botan3-3.7.1+dfsg/src/lib/pubkey/classic_mceliece/cmce_types.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/classic_mceliece/cmce_types.h 2026-05-07 01:38:28.000000000 +0000 @@ -21,7 +21,7 @@ /// Represents a GF(q) modulus using CmceGfMod = Strong; -/// Represents an element of a permuation (pi in spec). Used in field ordering creation. +/// Represents an element of a permutation (pi in spec). Used in field ordering creation. using CmcePermutationElement = Strong; /// Represents a permutation (pi in spec). Used in field ordering creation. diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/curve448/curve448_gf.cpp botan3-3.12.0+dfsg/src/lib/pubkey/curve448/curve448_gf.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/curve448/curve448_gf.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/curve448/curve448_gf.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -12,51 +12,11 @@ #include #include -#include - namespace Botan { namespace { /** - * @brief Compute (a + b). The carry is returned in the carry parameter. - * The carry is not included for the addition. - */ -inline uint64_t u64_add(uint64_t a, uint64_t b, bool* carry) { - // Let the compiler optimize this into fancy instructions - const uint64_t sum = a + b; - *carry = sum < a; - return sum; -} - -/** - * @brief Compute (a + b + carry), where carry is in {0, 1}. The carry of this computation - * is store in the in/out @p carry parameter. - */ -inline uint64_t u64_add_with_carry(uint64_t a, uint64_t b, bool* carry) { - // Let the compiler optimize this into fancy instructions - uint64_t sum = a + b; - const bool carry_a_plus_b = (sum < a); - sum += static_cast(*carry); - *carry = static_cast(carry_a_plus_b) | static_cast(sum < static_cast(*carry)); - return sum; -} - -/** - * @brief Compute (a - (b + borrow)). The borrow is returned in the carry parameter. - * - * I.e. borrow = 1 if a < b + borrow, else 0. - */ -inline uint64_t u64_sub_with_borrow(uint64_t a, uint64_t b, bool* borrow) { - // Let the compiler optimize this into fancy instructions - const uint64_t diff = a - b; - const bool borrow_a_min_b = diff > a; - const uint64_t z = diff - static_cast(*borrow); - *borrow = static_cast(borrow_a_min_b) | static_cast(z > diff); - return z; -} - -/** * @brief Reduce the result of a addition modulo 2^448 - 2^224 - 1. * * Algorithm 1 of paper "Reduction Modulo 2^448 - 2^224 - 1", from line 27. @@ -65,28 +25,30 @@ * @param h_1 Input */ void reduce_after_add(std::span h_3, std::span h_1) { - std::array h_2; - bool carry; + std::array h_2; /* NOLINT(*-member-init) */ + uint64_t carry = 0; - // Line 27+ (of the paper's algorithm 1) - h_2[0] = u64_add(h_1[0], h_1[7], &carry); + constexpr uint64_t zero = 0; - h_2[1] = u64_add(h_1[1], carry, &carry); - h_2[2] = u64_add(h_1[2], carry, &carry); + // Line 27+ (of the paper's algorithm 1) + h_2[0] = word_add(h_1[0], h_1[7], &carry); + h_2[1] = word_add(h_1[1], zero, &carry); + h_2[2] = word_add(h_1[2], zero, &carry); // Line 30 - h_2[3] = u64_add_with_carry(h_1[3], h_1[7] << 32, &carry); + h_2[3] = word_add(h_1[3], h_1[7] << 32, &carry); // Line 31+ - h_2[4] = u64_add(h_1[4], carry, &carry); - h_2[5] = u64_add(h_1[5], carry, &carry); - h_2[6] = u64_add(h_1[6], carry, &carry); + h_2[4] = word_add(h_1[4], zero, &carry); + h_2[5] = word_add(h_1[5], zero, &carry); + h_2[6] = word_add(h_1[6], zero, &carry); h_2[7] = carry; - h_3[0] = u64_add(h_2[0], h_2[7], &carry); - h_3[1] = u64_add(h_2[1], carry, &carry); - h_3[2] = u64_add(h_2[2], carry, &carry); + carry = 0; + h_3[0] = word_add(h_2[0], h_2[7], &carry); + h_3[1] = word_add(h_2[1], zero, &carry); + h_3[2] = word_add(h_2[2], zero, &carry); // Line 37 h_3[3] = h_2[3] + (h_2[7] << 32) + carry; @@ -102,62 +64,88 @@ * Algorithm 1 of paper "Reduction Modulo 2^448 - 2^224 - 1". */ void reduce_after_mul(std::span out, std::span in) { - std::array r; - std::array s; - std::array t_0; - std::array h_1; + std::array r; // NOLINT(*-member-init) + std::array s; // NOLINT(*-member-init) + std::array t_0; // NOLINT(*-member-init) + std::array h_1; // NOLINT(*-member-init) - bool carry; + uint64_t carry = 0; // Line 4 (of the paper's algorithm 1) - r[0] = u64_add(in[0], in[7], &carry); + r[0] = word_add(in[0], in[7], &carry); // Line 5-7 - for(size_t i = 1; i < 7; ++i) { - r[i] = u64_add_with_carry(in[i], in[i + 7], &carry); - } + r[1] = word_add(in[1], in[1 + 7], &carry); + r[2] = word_add(in[2], in[2 + 7], &carry); + r[3] = word_add(in[3], in[3 + 7], &carry); + r[4] = word_add(in[4], in[4 + 7], &carry); + r[5] = word_add(in[5], in[5 + 7], &carry); + r[6] = word_add(in[6], in[6 + 7], &carry); r[7] = carry; s[0] = r[0]; s[1] = r[1]; s[2] = r[2]; // Line 10 - s[3] = u64_add(r[3], in[10] & 0xFFFFFFFF00000000, &carry); + carry = 0; + s[3] = word_add(r[3], in[10] & 0xFFFFFFFF00000000, &carry); // Line 11-13 - for(size_t i = 4; i < 7; ++i) { - s[i] = u64_add_with_carry(r[i], in[i + 7], &carry); - } + s[4] = word_add(r[4], in[4 + 7], &carry); + s[5] = word_add(r[5], in[5 + 7], &carry); + s[6] = word_add(r[6], in[6 + 7], &carry); s[7] = r[7] + carry; // Line 15-17 - for(size_t i = 0; i < 3; ++i) { - t_0[i] = (in[i + 11] << 32) | (in[i + 10] >> 32); - } + t_0[0] = (in[0 + 11] << 32) | (in[0 + 10] >> 32); + t_0[1] = (in[1 + 11] << 32) | (in[1 + 10] >> 32); + t_0[2] = (in[2 + 11] << 32) | (in[2 + 10] >> 32); // Line 18 t_0[3] = (in[7] << 32) | (in[13] >> 32); // Line 19-21 - for(size_t i = 4; i < 7; ++i) { - t_0[i] = (in[i + 4] << 32) | (in[i + 3] >> 32); - } - h_1[0] = u64_add(s[0], t_0[0], &carry); + t_0[4] = (in[4 + 4] << 32) | (in[4 + 3] >> 32); + t_0[5] = (in[5 + 4] << 32) | (in[5 + 3] >> 32); + t_0[6] = (in[6 + 4] << 32) | (in[6 + 3] >> 32); + carry = 0; // Line 23-25 - for(size_t i = 1; i < 7; ++i) { - h_1[i] = u64_add_with_carry(s[i], t_0[i], &carry); - } + h_1[0] = word_add(s[0], t_0[0], &carry); + h_1[1] = word_add(s[1], t_0[1], &carry); + h_1[2] = word_add(s[2], t_0[2], &carry); + h_1[3] = word_add(s[3], t_0[3], &carry); + h_1[4] = word_add(s[4], t_0[4], &carry); + h_1[5] = word_add(s[5], t_0[5], &carry); + h_1[6] = word_add(s[6], t_0[6], &carry); h_1[7] = s[7] + carry; reduce_after_add(out, h_1); } +// Multiply by the Curve448 constant a24 = (a-2)/4 = 39081. +// Uses a 7-word × 1-word multiply (7 muls vs 49 for full comba_mul<7>), +// and the result fits in 8 words so only needs reduce_after_add. +void gf_mul_a24(std::span out, std::span a) { + constexpr uint64_t A24 = 39081; + std::array ws; // NOLINT(*-member-init) + uint64_t carry = 0; + ws[0] = word_madd2(a[0], A24, &carry); + ws[1] = word_madd2(a[1], A24, &carry); + ws[2] = word_madd2(a[2], A24, &carry); + ws[3] = word_madd2(a[3], A24, &carry); + ws[4] = word_madd2(a[4], A24, &carry); + ws[5] = word_madd2(a[5], A24, &carry); + ws[6] = word_madd2(a[6], A24, &carry); + ws[7] = carry; + reduce_after_add(out, ws); +} + void gf_mul(std::span out, std::span a, std::span b) { - std::array ws; + std::array ws; // NOLINT(*-member-init) comba_mul<7>(ws.data(), a.data(), b.data()); reduce_after_mul(out, ws); } void gf_square(std::span out, std::span a) { - std::array ws; + std::array ws; // NOLINT(*-member-init) comba_sqr<7>(ws.data(), a.data()); reduce_after_mul(out, ws); } @@ -165,15 +153,17 @@ void gf_add(std::span out, std::span a, std::span b) { - std::array ws; - copy_mem(std::span(ws).first(), a); - ws[WORDS_448] = 0; + std::array ws; // NOLINT(*-member-init) - bool carry = false; - for(size_t i = 0; i < WORDS_448; ++i) { - ws[i] = u64_add_with_carry(a[i], b[i], &carry); - } - ws[WORDS_448] = carry; + uint64_t carry = 0; + ws[0] = word_add(a[0], b[0], &carry); + ws[1] = word_add(a[1], b[1], &carry); + ws[2] = word_add(a[2], b[2], &carry); + ws[3] = word_add(a[3], b[3], &carry); + ws[4] = word_add(a[4], b[4], &carry); + ws[5] = word_add(a[5], b[5], &carry); + ws[6] = word_add(a[6], b[6], &carry); + ws[7] = carry; reduce_after_add(out, ws); } @@ -186,53 +176,154 @@ void gf_sub(std::span out, std::span a, std::span b) { - std::array h_0; - std::array h_1; + std::array h_0; // NOLINT(*-member-init) + std::array h_1; // NOLINT(*-member-init) - bool borrow = false; - for(size_t i = 0; i < WORDS_448; ++i) { - h_0[i] = u64_sub_with_borrow(a[i], b[i], &borrow); - } + uint64_t borrow = 0; + h_0[0] = word_sub(a[0], b[0], &borrow); + h_0[1] = word_sub(a[1], b[1], &borrow); + h_0[2] = word_sub(a[2], b[2], &borrow); + h_0[3] = word_sub(a[3], b[3], &borrow); + h_0[4] = word_sub(a[4], b[4], &borrow); + h_0[5] = word_sub(a[5], b[5], &borrow); + h_0[6] = word_sub(a[6], b[6], &borrow); uint64_t delta = borrow; uint64_t delta_p = delta << 32; - borrow = false; + borrow = 0; - h_1[0] = u64_sub_with_borrow(h_0[0], delta, &borrow); - h_1[1] = u64_sub_with_borrow(h_0[1], 0, &borrow); - h_1[2] = u64_sub_with_borrow(h_0[2], 0, &borrow); - h_1[3] = u64_sub_with_borrow(h_0[3], delta_p, &borrow); - h_1[4] = u64_sub_with_borrow(h_0[4], 0, &borrow); - h_1[5] = u64_sub_with_borrow(h_0[5], 0, &borrow); - h_1[6] = u64_sub_with_borrow(h_0[6], 0, &borrow); + constexpr uint64_t zero = 0; + + h_1[0] = word_sub(h_0[0], delta, &borrow); + h_1[1] = word_sub(h_0[1], zero, &borrow); + h_1[2] = word_sub(h_0[2], zero, &borrow); + h_1[3] = word_sub(h_0[3], delta_p, &borrow); + h_1[4] = word_sub(h_0[4], zero, &borrow); + h_1[5] = word_sub(h_0[5], zero, &borrow); + h_1[6] = word_sub(h_0[6], zero, &borrow); delta = borrow; delta_p = delta << 32; - borrow = false; + borrow = 0; - out[0] = u64_sub_with_borrow(h_1[0], delta, &borrow); - out[1] = u64_sub_with_borrow(h_1[1], 0, &borrow); - out[2] = u64_sub_with_borrow(h_1[2], 0, &borrow); - out[3] = u64_sub_with_borrow(h_1[3], delta_p, &borrow); + out[0] = word_sub(h_1[0], delta, &borrow); + out[1] = word_sub(h_1[1], zero, &borrow); + out[2] = word_sub(h_1[2], zero, &borrow); + out[3] = word_sub(h_1[3], delta_p, &borrow); out[4] = h_1[4]; out[5] = h_1[5]; out[6] = h_1[6]; } +/// Square a field element n times +void gf_sqr_n(std::span out, std::span a, size_t n) { + gf_square(out, a); + for(size_t i = 1; i < n; ++i) { + gf_square(out, out); + } +} + +/** + * @brief Compute x^(2^222 - 1) using an addition chain. + * + * This is the shared prefix of the addition chains for both + * inversion (x^(p-2)) and square root (x^((p-3)/4)). + * + * Addition chain from addchain tool (cost 446): + * _11 = 1 + _10 + * _111 = 1 + _110 + * _111111 = _111 + _111 << 3 + * x12 = _111111 << 6 + _111111 + * x24 = x12 << 12 + x12 + * x30 = _111111 + x24 << 6 + * x48 = x24 << 6 << 18 + x24 + * x96 = x48 << 48 + x48 + * x192 = x96 << 96 + x96 + * x222 = x192 << 30 + x30 + */ +void gf_pow_2_222m1(std::span x222, + std::span x223, + std::span a) { + std::array t; // NOLINT(*-member-init) + + // _10 = a^2 + std::array a2; // NOLINT(*-member-init) + gf_square(a2, a); + + // _11 = a^3 + std::array a3; // NOLINT(*-member-init) + gf_mul(a3, a, a2); + + // _111 = a^7 + std::array a7; // NOLINT(*-member-init) + gf_square(t, a3); + gf_mul(a7, a, t); + + // _111111 = a^63 + std::array a63; // NOLINT(*-member-init) + gf_sqr_n(t, a7, 3); + gf_mul(a63, a7, t); + + // x12 = a^(2^12 - 1) + std::array x12; // NOLINT(*-member-init) + gf_sqr_n(t, a63, 6); + gf_mul(x12, a63, t); + + // x24 = a^(2^24 - 1) + std::array x24; // NOLINT(*-member-init) + gf_sqr_n(t, x12, 12); + gf_mul(x24, x12, t); + + // i34 = x24 << 6 = a^((2^24 - 1) * 2^6) + std::array i34; // NOLINT(*-member-init) + gf_sqr_n(i34, x24, 6); + + // x30 = a^(2^30 - 1) + std::array x30; // NOLINT(*-member-init) + gf_mul(x30, a63, i34); + + // x48 = a^(2^48 - 1) + std::array x48; // NOLINT(*-member-init) + gf_sqr_n(t, i34, 18); + gf_mul(x48, x24, t); + + // x96 = a^(2^96 - 1) + std::array x96; // NOLINT(*-member-init) + gf_sqr_n(t, x48, 48); + gf_mul(x96, x48, t); + + // x192 = a^(2^192 - 1) + std::array x192; // NOLINT(*-member-init) + gf_sqr_n(t, x96, 96); + gf_mul(x192, x96, t); + + // x222 = a^(2^222 - 1) + gf_sqr_n(t, x192, 30); + gf_mul(x222, x30, t); + + // x223 = a^(2^223 - 1) + gf_square(t, x222); + gf_mul(x223, a, t); +} + /** * @brief Inversion in GF(P) using Fermat's little theorem: * x^-1 = x^(P-2) mod P + * + * Uses an optimized addition chain (cost 460) found by addchain. + * P-2 = 2^448 - 2^224 - 3 + * return = (x223 << 223 + x222) << 2 + 1 */ void gf_inv(std::span out, std::span a) { - clear_mem(out); - out[0] = 1; - // Square and multiply - for(int16_t t = 448; t >= 0; --t) { - gf_square(out, out); - // (P-2) has zero bits at indices 1, 224, 448. All others are one. - if(t != 448 && t != 224 && t != 1) { - gf_mul(out, out, a); - } - } + std::array x222; // NOLINT(*-member-init) + std::array x223; // NOLINT(*-member-init) + gf_pow_2_222m1(x222, x223, a); + + // (x223 << 223 + x222) << 2 + 1 + std::array t; // NOLINT(*-member-init) + gf_sqr_n(t, x223, 223); + gf_mul(t, t, x222); + gf_sqr_n(t, t, 2); + gf_mul(out, t, a); } /** @@ -250,23 +341,23 @@ 0xffffffffffffffff, 0xffffffffffffffff}; - std::array in_minus_p; - bool borrow = false; + std::array in_minus_p; // NOLINT(*-member-init) + uint64_t borrow = 0; for(size_t i = 0; i < WORDS_448; ++i) { - in_minus_p[i] = u64_sub_with_borrow(in[i], p[i], &borrow); + in_minus_p[i] = word_sub(in[i], p[i], &borrow); } - std::array out; + std::array out; // NOLINT(*-member-init) CT::Mask::expand(borrow).select_n(out.data(), in.data(), in_minus_p.data(), WORDS_448); return out; } } // namespace -Gf448Elem::Gf448Elem(std::span x) { +Gf448Elem::Gf448Elem(std::span x) /* NOLINT(*-member-init) */ { load_le(m_x, x); } -Gf448Elem::Gf448Elem(uint64_t least_sig_word) { +Gf448Elem::Gf448Elem(uint64_t least_sig_word) /* NOLINT(*-member-init) */ { clear_mem(m_x); m_x[0] = least_sig_word; } @@ -276,19 +367,19 @@ } std::array Gf448Elem::to_bytes() const { - std::array bytes; + std::array bytes{}; to_bytes(bytes); return bytes; } -void Gf448Elem::ct_cond_swap(bool b, Gf448Elem& other) { +void Gf448Elem::ct_cond_swap(CT::Mask mask, Gf448Elem& other) { for(size_t i = 0; i < WORDS_448; ++i) { - CT::conditional_swap(b, m_x[i], other.m_x[i]); + mask.conditional_swap(m_x[i], other.m_x[i]); } } -void Gf448Elem::ct_cond_assign(bool b, const Gf448Elem& other) { - CT::conditional_assign_mem(static_cast(b), m_x.data(), other.m_x.data(), WORDS_448); +void Gf448Elem::ct_cond_assign(CT::Mask mask, const Gf448Elem& other) { + mask.select_n(m_x.data(), other.m_x.data(), m_x.data(), WORDS_448); } Gf448Elem Gf448Elem::operator+(const Gf448Elem& other) const { @@ -345,6 +436,12 @@ return CT::is_equal(x_words.data(), x_words_canonical.data(), WORDS_448).as_bool(); } +Gf448Elem mul_a24(const Gf448Elem& a) { + Gf448Elem res(0); + gf_mul_a24(res.words(), a.words()); + return res; +} + Gf448Elem square(const Gf448Elem& elem) { Gf448Elem res(0); gf_square(res.words(), elem.words()); @@ -352,16 +449,16 @@ } Gf448Elem root(const Gf448Elem& elem) { - Gf448Elem res(1); - - // (P-3)/4 is an 445 bit integer with one zero bits at 222. All others are one. - for(int16_t t = 445; t >= 0; --t) { - gf_square(res.words(), res.words()); - if(t != 222) { - gf_mul(res.words(), res.words(), elem.words()); - } - } + // Compute elem^((P-3)/4) using an optimized addition chain (cost 457). + // (P-3)/4 = 2^446 - 2^222 - 1 + // return = x223 << 223 + x222 + std::array x222; // NOLINT(*-member-init) + std::array x223; // NOLINT(*-member-init) + gf_pow_2_222m1(x222, x223, elem.words()); + Gf448Elem res(0); + gf_sqr_n(res.words(), x223, 223); + gf_mul(res.words(), res.words(), x222); return res; } diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/curve448/curve448_gf.h botan3-3.12.0+dfsg/src/lib/pubkey/curve448/curve448_gf.h --- botan3-3.7.1+dfsg/src/lib/pubkey/curve448/curve448_gf.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/curve448/curve448_gf.h 2026-05-07 01:38:28.000000000 +0000 @@ -11,13 +11,14 @@ #include #include #include +#include #include #include namespace Botan { -constexpr size_t BYTES_448 = ceil_tobytes(448); +constexpr size_t BYTES_448 = ceil_tobytes(448); /* uint64_t words to store a 448 bit value */ constexpr size_t WORDS_448 = 7; @@ -38,19 +39,29 @@ * @brief Construct a GF element from a 448-bit integer gives as 56 bytes @p x in * little-endian order. */ - Gf448Elem(std::span x); + explicit Gf448Elem(std::span x); /** * @brief Construct a GF element from a 448-bit integer gives as 7 uint64_t words @p x in * little-endian order. */ - Gf448Elem(std::span data) { copy_mem(m_x, data); } + explicit Gf448Elem(std::span data) /* NOLINT(*-member-init) */ { copy_mem(m_x, data); } /** * @brief Construct a GF element by passing the least significant 64 bits as a word. * All other become zero. */ - Gf448Elem(uint64_t least_sig_word); + explicit Gf448Elem(uint64_t least_sig_word); + + /** + * Return the constant value zero + */ + static Gf448Elem zero() { return Gf448Elem(0); } + + /** + * Return the constant value one + */ + static Gf448Elem one() { return Gf448Elem(1); } /** * @brief Store the canonical representation of the GF element as 56 bytes in little-endian @@ -67,14 +78,14 @@ std::array to_bytes() const; /** - * @brief Swap this and other if b == true. Constant time for any b. + * @brief Swap this and @p other if @p mask is set. Constant time. */ - void ct_cond_swap(bool b, Gf448Elem& other); + void ct_cond_swap(CT::Mask mask, Gf448Elem& other); /** - * @brief Set this to @p other if b is true. Constant time for any b. + * @brief Set this to @p other if @p mask is true. Constant time. */ - void ct_cond_assign(bool b, const Gf448Elem& other); + void ct_cond_assign(CT::Mask mask, const Gf448Elem& other); Gf448Elem operator+(const Gf448Elem& other) const; @@ -127,6 +138,11 @@ }; /** + * @brief Multiply a field element by the Curve448 constant a24 = 39081. + */ +Gf448Elem mul_a24(const Gf448Elem& a); + +/** * @brief Computes elem^2. Faster than operator*. */ Gf448Elem square(const Gf448Elem& elem); diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/curve448/curve448_scalar.cpp botan3-3.12.0+dfsg/src/lib/pubkey/curve448/curve448_scalar.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/curve448/curve448_scalar.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/curve448/curve448_scalar.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -24,12 +24,12 @@ copy_mem(std::span(r).template first(), x); return std::make_pair(std::array({0}), r); } else { - std::array r; + std::array r; // NOLINT(*-member-init) copy_mem(r, std::span(x).template first()); // Clear the two most significant bits r[Scalar448::WORDS - 1] &= ~(word(0b11) << (sizeof(word) * 8 - 2)); - std::array q; + std::array q; // NOLINT(*-member-init) bigint_shr2(q.data(), x.data(), x.size(), 446); return std::make_pair(q, r); @@ -56,8 +56,8 @@ /// @return c*x, with c = 0x8335dc163bb124b65129c96fde933d8d723a70aadc873d6d54a7bb0d template std::array mul_c(std::span x) { - std::array res; - std::array ws; + std::array res; // NOLINT(*-member-init) + std::array ws; // NOLINT(*-member-init) constexpr std::array c = c_words(); bigint_mul(res.data(), res.size(), x.data(), x.size(), x.size(), c.data(), c.size(), c.size(), ws.data(), ws.size()); @@ -69,9 +69,9 @@ */ std::array add(std::span x, std::span y) { - std::array res; + std::array res; // NOLINT(*-member-init) copy_mem(res, x); - const word carry = bigint_add2_nc(res.data(), res.size(), y.data(), y.size()); + const word carry = bigint_add2(res.data(), res.size(), y.data(), y.size()); CT::unpoison(carry); BOTAN_ASSERT(carry == 0, "Result fits in output"); return res; @@ -80,10 +80,10 @@ /** * @brief x = (x >= L) ? x - L : x. Constant time. * - * @return true iff a reduction was performed + * @return a CT::Choice that is set iff a reduction was performed */ -bool ct_subtract_L_if_bigger(std::span x) { - std::array tmp; +CT::Choice ct_subtract_L_if_bigger(std::span x) { + std::array tmp; // NOLINT(*-member-init) copy_mem(tmp, x); constexpr auto big_l = big_l_words(); @@ -91,13 +91,13 @@ const auto smaller_than_L = CT::Mask::expand(borrow); smaller_than_L.select_n(x.data(), x.data(), tmp.data(), Scalar448::WORDS); - return !smaller_than_L.as_bool(); + return !smaller_than_L.as_choice(); } template std::array bytes_to_words(std::span x) { constexpr size_t words = words_for_bits(S * 8); - std::array x_word_bytes = {0}; + std::array x_word_bytes{}; copy_mem(std::span(x_word_bytes).template first(), x); return load_le>(x_word_bytes); } @@ -144,6 +144,7 @@ } // namespace +// NOLINTNEXTLINE(*-member-init) Scalar448::Scalar448(std::span in_bytes) { BOTAN_ARG_CHECK(in_bytes.size() <= 114, "Input must be at most 114 bytes long"); std::array max_bytes = {0}; @@ -156,7 +157,32 @@ bool Scalar448::get_bit(size_t bit_pos) const { BOTAN_ARG_CHECK(bit_pos < 446, "Bit position out of range"); constexpr size_t word_sz = sizeof(word) * 8; - return (m_scalar_words[bit_pos / word_sz] >> (bit_pos % word_sz)) & 1; + return (((m_scalar_words[bit_pos / word_sz] >> (bit_pos % word_sz)) & 1) == 1); +} + +uint32_t Scalar448::get_window(size_t starting_pos, size_t width) const { + BOTAN_ARG_CHECK(width <= 32, "Window too wide"); + constexpr size_t word_sz = sizeof(word) * 8; + + // Bits at or beyond position 446 are zero + if(starting_pos >= 446) { + return 0; + } + + // Clamp the effective width so we don't read past bit 445 + const size_t effective_bits = std::min(width, size_t(446) - starting_pos); + + const size_t word_idx = starting_pos / word_sz; + const size_t bit_idx = starting_pos % word_sz; + + const uint64_t mask = (effective_bits >= 64) ? ~uint64_t(0) : (uint64_t(1) << effective_bits) - 1; + + uint64_t val = m_scalar_words[word_idx] >> bit_idx; + if(bit_idx + effective_bits > word_sz && word_idx + 1 < WORDS) { + val |= m_scalar_words[word_idx + 1] << (word_sz - bit_idx); + } + + return static_cast(val & mask); } Scalar448 Scalar448::operator+(const Scalar448& other) const { @@ -188,7 +214,7 @@ const auto leading_zeros = x.subspan(BYTES); const auto leading_zeros_are_zero = CT::all_zeros(leading_zeros.data(), leading_zeros.size()); auto x_sig_words = bytes_to_words(x.first<56>()); - const auto least_56_bytes_smaller_L = CT::Mask::expand(!ct_subtract_L_if_bigger(x_sig_words)); + const auto least_56_bytes_smaller_L = CT::Mask::from_choice(!ct_subtract_L_if_bigger(x_sig_words)); return (leading_zeros_are_zero & least_56_bytes_smaller_L).as_bool(); } diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/curve448/curve448_scalar.h botan3-3.12.0+dfsg/src/lib/pubkey/curve448/curve448_scalar.h --- botan3-3.7.1+dfsg/src/lib/pubkey/curve448/curve448_scalar.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/curve448/curve448_scalar.h 2026-05-07 01:38:28.000000000 +0000 @@ -34,10 +34,10 @@ class BOTAN_TEST_API Scalar448 final { public: constexpr static size_t WORDS = words_for_bits(446); - constexpr static size_t BYTES = ceil_tobytes(446); + constexpr static size_t BYTES = ceil_tobytes(446); /// @brief Construct a new scalar from (max. 114) bytes. Little endian. - Scalar448(std::span x); + explicit Scalar448(std::span x); /// @brief Convert the scalar to bytes in little endian. template @@ -52,6 +52,10 @@ /// @brief Access the i-th bit of the scalar. From 0 (lsb) to 445 (msb). bool get_bit(size_t i) const; + /// @brief Extract a window of @p width bits starting at bit position @p starting_pos. + /// Bits beyond position 445 are treated as zero. + uint32_t get_window(size_t starting_pos, size_t width) const; + /// @brief scalar = (scalar + other) mod L Scalar448 operator+(const Scalar448& other) const; @@ -62,7 +66,8 @@ static bool bytes_are_reduced(std::span x); private: - Scalar448(std::span scalar_words) { copy_mem(m_scalar_words, scalar_words); } + // NOLINTNEXTLINE(*-member-init) + explicit Scalar448(std::span scalar_words) { copy_mem(m_scalar_words, scalar_words); } std::array m_scalar_words; }; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/curve448/ed448/ed448.cpp botan3-3.12.0+dfsg/src/lib/pubkey/curve448/ed448/ed448.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/curve448/ed448/ed448.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/curve448/ed448/ed448.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -57,7 +57,7 @@ Ed448_PrivateKey::Ed448_PrivateKey(const AlgorithmIdentifier& /*unused*/, std::span key_bits) { secure_vector bits; - BER_Decoder(key_bits).decode(bits, ASN1_Type::OctetString).verify_end(); + BER_Decoder(key_bits, BER_Decoder::Limits::DER()).decode(bits, ASN1_Type::OctetString).verify_end(); if(bits.size() != ED448_LEN) { throw Decoding_Error("Invalid size for Ed448 private key"); @@ -88,7 +88,11 @@ } bool Ed448_PrivateKey::check_key(RandomNumberGenerator& /*rng*/, bool /*strong*/) const { - return true; + BOTAN_ASSERT_NOMSG(m_private.size() == ED448_LEN); + auto scope = CT::scoped_poison(m_private); + const auto public_point = create_pk_from_sk(std::span(m_private).first()); + CT::unpoison(public_point); + return public_point == m_public; } namespace { @@ -113,7 +117,7 @@ std::vector get_and_clear() override { return m_hash->final_stdvec(); } - Prehashed_Ed448_Message(std::string_view hash) : m_hash(HashFunction::create_or_throw(hash)) {} + explicit Prehashed_Ed448_Message(std::string_view hash) : m_hash(HashFunction::create_or_throw(hash)) {} private: std::unique_ptr m_hash; @@ -136,9 +140,7 @@ public: explicit Ed448_Verify_Operation(const Ed448_PublicKey& key, std::optional prehash_function = std::nullopt) : - m_prehash_function(std::move(prehash_function)) { - const auto pk_bits = key.public_key_bits(); - copy_mem(m_pk, std::span(pk_bits).first()); + m_pk(key.raw_public_key_bits()), m_prehash_function(std::move(prehash_function)) { if(m_prehash_function) { m_message = std::make_unique(*m_prehash_function); } else { @@ -151,7 +153,7 @@ bool is_valid_signature(std::span sig) override { const auto msg = m_message->get_and_clear(); try { - return verify_signature(m_pk, m_prehash_function.has_value(), {}, sig, msg); + return verify_signature(std::span(m_pk).first(), m_prehash_function.has_value(), {}, sig, msg); } catch(Decoding_Error&) { return false; } @@ -160,7 +162,7 @@ std::string hash_function() const override { return m_prehash_function.value_or("SHAKE-256(912)"); } private: - std::array m_pk; + std::vector m_pk; std::unique_ptr m_message; std::optional m_prehash_function; }; @@ -172,12 +174,9 @@ public: explicit Ed448_Sign_Operation(const Ed448_PrivateKey& key, std::optional prehash_function = std::nullopt) : + m_pk(key.raw_public_key_bits()), + m_sk(key.raw_private_key_bits()), m_prehash_function(std::move(prehash_function)) { - const auto pk_bits = key.public_key_bits(); - copy_mem(m_pk, std::span(pk_bits).first()); - const auto sk_bits = key.raw_private_key_bits(); - BOTAN_ASSERT_NOMSG(sk_bits.size() == ED448_LEN); - m_sk.assign(sk_bits.begin(), sk_bits.end()); if(m_prehash_function) { m_message = std::make_unique(*m_prehash_function); } else { @@ -190,8 +189,11 @@ std::vector sign(RandomNumberGenerator& /*rng*/) override { BOTAN_ASSERT_NOMSG(m_sk.size() == ED448_LEN); auto scope = CT::scoped_poison(m_sk); - const auto sig = sign_message( - std::span(m_sk).first(), m_pk, m_prehash_function.has_value(), {}, m_message->get_and_clear()); + const auto sig = sign_message(std::span(m_sk).first(), + std::span(m_pk).first(), + m_prehash_function.has_value(), + {}, + m_message->get_and_clear()); CT::unpoison(sig); return {sig.begin(), sig.end()}; } @@ -203,7 +205,7 @@ std::string hash_function() const override { return m_prehash_function.value_or("SHAKE-256(912)"); } private: - std::array m_pk; + std::vector m_pk; secure_vector m_sk; std::unique_ptr m_message; std::optional m_prehash_function; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/curve448/ed448/ed448.h botan3-3.12.0+dfsg/src/lib/pubkey/curve448/ed448/ed448.h --- botan3-3.7.1+dfsg/src/lib/pubkey/curve448/ed448/ed448.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/curve448/ed448/ed448.h 2026-05-07 01:38:28.000000000 +0000 @@ -54,7 +54,7 @@ /** * Create a Ed448 Public Key from bytes (57 Bytes). */ - Ed448_PublicKey(std::span key_bits); + BOTAN_FUTURE_EXPLICIT Ed448_PublicKey(std::span key_bits); std::unique_ptr create_verification_op(std::string_view params, std::string_view provider) const override; @@ -64,7 +64,7 @@ protected: Ed448_PublicKey() = default; - std::array m_public; + std::array m_public{}; // NOLINT(*non-private-member-variable*) }; BOTAN_DIAGNOSTIC_PUSH @@ -95,7 +95,7 @@ * * @param key_bits private key bytes (57 Bytes) */ - Ed448_PrivateKey(std::span key_bits); + BOTAN_FUTURE_EXPLICIT Ed448_PrivateKey(std::span key_bits); /** * Generate a new private key. diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/curve448/ed448/ed448_internal.cpp botan3-3.12.0+dfsg/src/lib/pubkey/curve448/ed448/ed448_internal.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/curve448/ed448/ed448_internal.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/curve448/ed448/ed448_internal.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -10,16 +10,16 @@ #include #include +#include +#include +#include +#include #include #include -#include -#include namespace Botan { namespace { -constexpr uint64_t MINUS_D = 39081; - std::vector dom4(uint8_t x, std::span y) { // RFC 8032 2. Notation and Conventions // dom4(x, y) The octet string "SigEd448" || octet(x) || @@ -35,11 +35,11 @@ template std::array shake(bool f, std::span context, Ts... xs) { - auto shake_xof = SHAKE_256_XOF(); - shake_xof.update(dom4(static_cast(f), context)); - (shake_xof.update(std::span(xs)), ...); - std::array res; - shake_xof.output(res); + auto shake_xof = XOF::create_or_throw("SHAKE-256"); + shake_xof->update(dom4(static_cast(f), context)); + (shake_xof->update(std::span(xs)), ...); + std::array res{}; + shake_xof->output(res); return res; } @@ -56,7 +56,7 @@ // 1. Hash the 57-byte private key using SHAKE256(x, 114), storing the // digest in a 114-octet large buffer, denoted h. Only the lower 57 // bytes are used for generating the public key. - std::array raw_s; + std::array raw_s{}; shake_xof.output(raw_s); // 2. Prune the buffer: The two least significant bits of the first // octet are cleared, all eight bits the last octet are cleared, and @@ -96,9 +96,8 @@ // inversion of v and the square root: // (p+1)/4 3 (p-3)/4 // x = (u/v) = u v (u^5 v^3) (mod p) - const auto d = -Gf448Elem(MINUS_D); - const auto u = square(Gf448Elem(y)) - 1; - const auto v = d * square(Gf448Elem(y)) - 1; + const auto u = square(Gf448Elem(y)) - Gf448Elem::one(); + const auto v = -mul_a24(square(Gf448Elem(y))) - Gf448Elem::one(); const auto maybe_x = (u * square(u)) * v * root((square(square(u)) * u) * square(v) * v); // 3. If v * x^2 = u, the recovered x-coordinate is x. Otherwise, no @@ -112,10 +111,10 @@ if(maybe_x.is_zero() && x_distinguisher) { throw Decoding_Error("Square root of zero cannot be odd"); } - bool maybe_x_parity = maybe_x.is_odd(); - std::array x_data; - CT::Mask::expand(maybe_x_parity == x_distinguisher) - .select_n(x_data.data(), maybe_x.words().data(), (-maybe_x).words().data(), 7); + const bool maybe_x_parity = maybe_x.is_odd(); + std::array x_data{}; + CT::Mask::expand_bool(maybe_x_parity == x_distinguisher) + .select_n(x_data.data(), maybe_x.words().data(), (-maybe_x).words().data(), WORDS_448); return {Gf448Elem(x_data), y}; } @@ -161,7 +160,7 @@ const Gf448Elem B = square(A); const Gf448Elem C = m_x * other.m_x; const Gf448Elem D = m_y * other.m_y; - const Gf448Elem E = (-Gf448Elem(MINUS_D)) * C * D; + const Gf448Elem E = -mul_a24(C * D); const Gf448Elem F = B - E; const Gf448Elem G = B + E; const Gf448Elem H = (m_x + m_y) * (other.m_x + other.m_y); @@ -188,33 +187,179 @@ } Ed448Point Ed448Point::scalar_mul(const Scalar448& s) const { - Ed448Point res(0, 1); + // 4-bit windowed scalar multiplication. + std::array table = {Ed448Point::identity(), + *this, + Ed448Point::identity(), + Ed448Point::identity(), + Ed448Point::identity(), + Ed448Point::identity(), + Ed448Point::identity(), + Ed448Point::identity(), + Ed448Point::identity(), + Ed448Point::identity(), + Ed448Point::identity(), + Ed448Point::identity(), + Ed448Point::identity(), + Ed448Point::identity(), + Ed448Point::identity(), + Ed448Point::identity()}; + + for(size_t i = 2; i < 16; ++i) { + if(i % 2 == 0) { + table[i] = table[i / 2].double_point(); + } else { + table[i] = table[i - 1] + *this; + } + } + + // Process 448 bits (446-bit scalar + 2 leading zero bits) in 112 4-bit windows + auto res = Ed448Point::identity(); + + for(int window = 111; window >= 0; --window) { + // Double 4 times + res = res.double_point(); + res = res.double_point(); + res = res.double_point(); + res = res.double_point(); + + // Extract 4-bit window value. Bits at position >= 446 are zero. + const uint64_t w = s.get_window(static_cast(window) * 4, 4); + + // Constant-time table lookup + auto selected = Ed448Point::identity(); + for(size_t i = 0; i < 16; ++i) { + const auto correct_idx = CT::Mask::is_equal(static_cast(i), w); + selected.ct_conditional_assign(correct_idx, table[i]); + } + + res = res + selected; + } + + return res; +} + +Ed448Point Ed448Point::base_point_mul(const Scalar448& scalar) { + /* + Fixed base point multiplication + + Same idea as base point multiply used in pcurves + */ + constexpr size_t W = 4; + constexpr size_t WindowElements = (1 << W) - 1; // 15 + constexpr size_t Windows = (448 + W - 1) / W; // 112 + constexpr size_t TableSize = Windows * WindowElements; // 1680 + + static const auto table = []() { + std::vector tbl(TableSize, Ed448Point::identity()); + + auto accum = Ed448Point::base_point(); + + for(size_t i = 0; i < TableSize; i += WindowElements) { + tbl[i] = accum; + + for(size_t j = 1; j < WindowElements; ++j) { + if(j % 2 == 1) { + tbl[i + j] = tbl[i + j / 2].double_point(); + } else { + tbl[i + j] = tbl[i + j - 1] + tbl[i]; + } + } + + accum = tbl[i + (WindowElements / 2)].double_point(); + } + + return tbl; + }(); + + auto res = Ed448Point::identity(); + + for(size_t i = 0; i != Windows; ++i) { + const uint8_t w = static_cast(scalar.get_window(i * W, W)); + + // Constant-time table lookup from this window's 15-entry subtable + auto selected = Ed448Point::identity(); + for(size_t j = 0; j != WindowElements; ++j) { + const auto assign = CT::Mask::is_equal(j + 1, w); + selected.ct_conditional_assign(assign, table[i * WindowElements + j]); + } + + res = res + selected; + } + + return res; +} - // Square and multiply (double and add) in constant time. - // TODO: Optimization potential. E.g. for a = *this precompute - // 0, a, 2a, 3a, ..., 15a and ct select and add the right one for - // each 4 bit window instead of conditional add. - for(int16_t i = 445; i >= 0; --i) { +Ed448Point Ed448Point::double_scalar_mul_vartime(const Scalar448& s1, + const Ed448Point& p1, + const Scalar448& s2, + const Ed448Point& p2) { + // 2-bit 2-ary Shamir's trick (variable time) + // Process 2 bits from each scalar per iteration, using a 16-entry table. + // table[w1 | (w2 << 2)] = w1*p1 + w2*p2, for w1,w2 in 0..3. + + // Precompute small multiples of each point + const auto p1x2 = p1.double_point(); + const auto p1x3 = p1x2 + p1; + const auto p2x2 = p2.double_point(); + const auto p2x3 = p2x2 + p2; + + // Build table indexed by (w2 << 2) | w1, excluding identity at index 0 + const std::array table = { + p1, // 1*p1 + 0*p2 + p1x2, // 2*p1 + 0*p2 + p1x3, // 3*p1 + 0*p2 + p2, // 0*p1 + 1*p2 + p1 + p2, // 1*p1 + 1*p2 + p1x2 + p2, // 2*p1 + 1*p2 + p1x3 + p2, // 3*p1 + 1*p2 + p2x2, // 0*p1 + 2*p2 + p1 + p2x2, // 1*p1 + 2*p2 + p1x2 + p2x2, // 2*p1 + 2*p2 + p1x3 + p2x2, // 3*p1 + 2*p2 + p2x3, // 0*p1 + 3*p2 + p1 + p2x3, // 1*p1 + 3*p2 + p1x2 + p2x3, // 2*p1 + 3*p2 + p1x3 + p2x3, // 3*p1 + 3*p2 + }; + + auto res = Ed448Point::identity(); + + // 446 bits / 2 = 223 windows, covering bit positions 0..445 + for(int window = 222; window >= 0; --window) { res = res.double_point(); - // Conditional add if bit is set - auto add_sum = res + *this; - res.ct_conditional_assign(s.get_bit(i), add_sum); + res = res.double_point(); + + const size_t bit_pos = static_cast(window) * 2; + const size_t idx = s1.get_window(bit_pos, 2) | (s2.get_window(bit_pos, 2) << 2); + + if(idx > 0) { + res = res + table[idx - 1]; + } } + return res; } bool Ed448Point::operator==(const Ed448Point& other) const { - // Note that the operator== of of Gf448Elem is constant time - const auto mask_x = CT::Mask::expand(x() == other.x()); - const auto mask_y = CT::Mask::expand(y() == other.y()); + // Compare in projective coordinates: (X1:Y1:Z1) == (X2:Y2:Z2) + // iff X1*Z2 == X2*Z1 && Y1*Z2 == Y2*Z1 + // This avoids two field inversions that x() and y() would require. + const auto lhs_x = m_x * other.m_z; + const auto rhs_x = other.m_x * m_z; + const auto lhs_y = m_y * other.m_z; + const auto rhs_y = other.m_y * m_z; + + const auto mask_x = CT::Mask::expand_bool(lhs_x == rhs_x); + const auto mask_y = CT::Mask::expand_bool(lhs_y == rhs_y); return (mask_x & mask_y).as_bool(); } -void Ed448Point::ct_conditional_assign(bool cond, const Ed448Point& other) { - m_x.ct_cond_assign(cond, other.m_x); - m_y.ct_cond_assign(cond, other.m_y); - m_z.ct_cond_assign(cond, other.m_z); +void Ed448Point::ct_conditional_assign(CT::Mask mask, const Ed448Point& other) { + m_x.ct_cond_assign(mask, other.m_x); + m_y.ct_cond_assign(mask, other.m_y); + m_z.ct_cond_assign(mask, other.m_z); } Ed448Point operator*(const Scalar448& lhs, const Ed448Point& rhs) { @@ -224,14 +369,14 @@ std::array create_pk_from_sk(std::span sk) { // 5.2.5. Key Generation // The 57-byte public key is generated by the following steps: - auto shake_xof = SHAKE_256_XOF(); - shake_xof.update(sk); + auto shake_xof = XOF::create_or_throw("SHAKE-256"); + shake_xof->update(sk); - const Scalar448 s = scalar_from_xof(shake_xof); + const Scalar448 s = scalar_from_xof(*shake_xof); // 3. Interpret the buffer as the little-endian integer, forming a // secret scalar s. Perform a known-base-point scalar // multiplication [s]B. - return (s * Ed448Point::base_point()).encode(); + return Ed448Point::base_point_mul(s).encode(); } std::array sign_message(std::span sk, @@ -248,11 +393,11 @@ // the first half of the digest, and the corresponding public key A, // as described in the previous section. Let prefix denote the // second half of the hash digest, h[57],...,h[113]. - auto shake_xof = SHAKE_256_XOF(); - shake_xof.update(sk); - const Scalar448 s = scalar_from_xof(shake_xof); - std::array prefix; - shake_xof.output(prefix); + auto shake_xof = XOF::create_or_throw("SHAKE-256"); + shake_xof->update(sk); + const Scalar448 s = scalar_from_xof(*shake_xof); + std::array prefix{}; + shake_xof->output(prefix); // 2. Compute SHAKE256(dom4(F, C) || prefix || PH(M), 114), where M is // the message to be signed, F is 1 for Ed448ph, 0 for Ed448, and C // is the context to use. Interpret the 114-octet digest as a @@ -261,7 +406,7 @@ // 3. Compute the point [r]B. For efficiency, do this by first // reducing r modulo L, the group order of B. Let the string R be // the encoding of this point. - const auto big_r = (r * Ed448Point::base_point()).encode(); + const auto big_r = Ed448Point::base_point_mul(r).encode(); // 4. Compute SHAKE256(dom4(F, C) || R || A || PH(M), 114), and // interpret the 114-octet digest as a little-endian integer k. const Scalar448 k(shake(pgflag, context, big_r, pk, msg)); @@ -271,7 +416,7 @@ // 6. Form the signature of the concatenation of R (57 octets) and the // little-endian encoding of S (57 octets; the ten most significant // bits of the final octets are always zero). - std::array sig; + std::array sig{}; BufferStuffer stuf(sig); stuf.append(big_r); stuf.append(big_s.to_bytes()); @@ -309,7 +454,9 @@ const Scalar448 k(shake(phflag, context, big_r_bytes, pk, msg)); // 3. Check the group equation [4][S]B = [4]R + [4][k]A’. It’s // sufficient, but not required, to instead check [S]B = R + [k]A’. - return (big_s * Ed448Point::base_point()) == (big_r + k * Ed448Point::decode(pk)); + // Rearranged as [S]B + [k](-A’) = R, computed via Shamir’s trick. + const auto neg_A = Ed448Point::decode(pk).negate(); + return Ed448Point::double_scalar_mul_vartime(big_s, Ed448Point::base_point(), k, neg_A) == big_r; } } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/curve448/ed448/ed448_internal.h botan3-3.12.0+dfsg/src/lib/pubkey/curve448/ed448/ed448_internal.h --- botan3-3.7.1+dfsg/src/lib/pubkey/curve448/ed448/ed448_internal.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/curve448/ed448/ed448_internal.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,6 +9,7 @@ #ifndef BOTAN_ED448_INTERNAL_H_ #define BOTAN_ED448_INTERNAL_H_ +#include #include #include @@ -36,6 +37,9 @@ /// Create a point from its coordinates x, y Ed448Point(const Gf448Elem& x, const Gf448Elem& y) : m_x(x), m_y(y), m_z(1) {} + /// Return the identity element + static Ed448Point identity() { return Ed448Point(Gf448Elem::zero(), Gf448Elem::one()); } + /// Encode the point to its 57-byte representation (RFC 8032 5.2.2) std::array encode() const; @@ -48,6 +52,18 @@ /// Scalar multiplication Ed448Point scalar_mul(const Scalar448& scalar) const; + /// Fixed base point scalar multiplication (precomputed table, no doublings) + static Ed448Point base_point_mul(const Scalar448& scalar); + + /// Variable-time double scalar multiplication using Shamir's trick: [s1]P + [s2]Q + static Ed448Point double_scalar_mul_vartime(const Scalar448& s1, + const Ed448Point& p1, + const Scalar448& s2, + const Ed448Point& p2); + + /// Negate the point + Ed448Point negate() const { return Ed448Point(-m_x, m_y, m_z); } + /// Getter for projective coordinate X Gf448Elem x_proj() const { return m_x; } @@ -66,8 +82,8 @@ /// Check if two points are equal (constant time) bool operator==(const Ed448Point& other) const; - /// Assign other to this if cond is true (constant time) - void ct_conditional_assign(bool cond, const Ed448Point& other); + /// Assign other to this if @p mask is set (constant time) + void ct_conditional_assign(CT::Mask mask, const Ed448Point& other); private: Gf448Elem m_x; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/curve448/info.txt botan3-3.12.0+dfsg/src/lib/pubkey/curve448/info.txt --- botan3-3.7.1+dfsg/src/lib/pubkey/curve448/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/curve448/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,10 +1,6 @@ - -CURVE_448_UTILS -> 20240301 - - -name -> "Curve_448_Utils" -brief -> "Utils for x448 and Ed448" +name -> "Curve448 Arithmetic" +brief -> "x448 and Ed448 Arithmetic" type -> "Internal" diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/curve448/x448/x448.cpp botan3-3.12.0+dfsg/src/lib/pubkey/curve448/x448/x448.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/curve448/x448/x448.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/curve448/x448/x448.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -26,8 +27,10 @@ secure_vector ber_decode_sk(std::span key_bits) { secure_vector decoded_bits; - BER_Decoder(key_bits).decode(decoded_bits, ASN1_Type::OctetString).verify_end(); - BOTAN_ASSERT_NOMSG(decoded_bits.size() == X448_LEN); + BER_Decoder(key_bits, BER_Decoder::Limits::DER()).decode(decoded_bits, ASN1_Type::OctetString).verify_end(); + if(decoded_bits.size() != X448_LEN) { + throw Decoding_Error("Invalid size for X448 private key"); + } return decoded_bits; } @@ -42,11 +45,11 @@ } std::vector X448_PublicKey::raw_public_key_bits() const { - return public_value(); + return {m_public.begin(), m_public.end()}; } std::vector X448_PublicKey::public_key_bits() const { - return public_value(); + return raw_public_key_bits(); } std::unique_ptr X448_PublicKey::generate_another(RandomNumberGenerator& rng) const { @@ -83,7 +86,7 @@ } bool X448_PrivateKey::check_key(RandomNumberGenerator& /*rng*/, bool /*strong*/) const { - std::array public_point; + std::array public_point{}; BOTAN_ASSERT_NOMSG(m_private.size() == X448_LEN); auto scope = CT::scoped_poison(m_private); x448_basepoint_from_data(public_point, std::span(m_private).first()); @@ -107,9 +110,10 @@ secure_vector raw_agree(const uint8_t w_data[], size_t w_len) override { auto scope = CT::scoped_poison(m_sk); - std::span w(w_data, w_len); - BOTAN_ARG_CHECK(w.size() == X448_LEN, "Invalid size for X448 private key"); - BOTAN_ASSERT_NOMSG(m_sk.size() == X448_LEN); + const std::span w(w_data, w_len); + if(w.size() != X448_LEN) { + throw Decoding_Error("Invalid size for X448 public key"); + } const auto k = decode_scalar(m_sk); const auto u = decode_point(w); diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/curve448/x448/x448.h botan3-3.12.0+dfsg/src/lib/pubkey/curve448/x448/x448.h --- botan3-3.7.1+dfsg/src/lib/pubkey/curve448/x448/x448.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/curve448/x448/x448.h 2026-05-07 01:38:28.000000000 +0000 @@ -41,7 +41,9 @@ AlgorithmIdentifier algorithm_identifier() const override; - std::vector public_value() const { return {m_public.begin(), m_public.end()}; } + BOTAN_DEPRECATED("Use raw_public_key_bits") std::vector public_value() const { + return raw_public_key_bits(); + } std::vector raw_public_key_bits() const override; @@ -53,7 +55,7 @@ protected: X448_PublicKey() = default; - std::array m_public; + std::array m_public{}; // NOLINT(*non-private-member-variable*) }; BOTAN_DIAGNOSTIC_PUSH @@ -85,7 +87,7 @@ */ explicit X448_PrivateKey(std::span secret_key); - std::vector public_value() const override { return X448_PublicKey::public_key_bits(); } + std::vector public_value() const override { return raw_public_key_bits(); } secure_vector raw_private_key_bits() const override { return {m_private.begin(), m_private.end()}; } diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/curve448/x448/x448_internal.cpp botan3-3.12.0+dfsg/src/lib/pubkey/curve448/x448/x448_internal.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/curve448/x448/x448_internal.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/curve448/x448/x448_internal.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -46,21 +46,19 @@ // Algorithm see RFC 7748, Section 5: // https://datatracker.ietf.org/doc/html/rfc7748#section-5 Point448 x448(const ScalarX448& k, const Point448& u) { - const Gf448Elem a24 = 39081; - - Gf448Elem x_1 = Gf448Elem(u.get()); - Gf448Elem x_2 = 1; - Gf448Elem z_2 = 0; + const Gf448Elem x_1 = Gf448Elem(u.get()); + Gf448Elem x_2 = Gf448Elem::one(); + Gf448Elem z_2 = Gf448Elem::zero(); Gf448Elem x_3 = Gf448Elem(u.get()); - Gf448Elem z_3 = 1; + Gf448Elem z_3 = Gf448Elem::one(); auto swap = CT::Mask::cleared(); for(int16_t t = 448 - 1; t >= 0; --t) { auto k_t = CT::Mask::expand(get_bit(k, t)); swap ^= k_t; - x_2.ct_cond_swap(swap.as_bool(), x_3); - z_2.ct_cond_swap(swap.as_bool(), z_3); + x_2.ct_cond_swap(swap, x_3); + z_2.ct_cond_swap(swap, z_3); swap = k_t; const auto A = x_2 + z_2; @@ -75,11 +73,11 @@ x_3 = square(DA + CB); z_3 = x_1 * square(DA - CB); x_2 = AA * BB; - z_2 = E * (AA + a24 * E); + z_2 = E * (AA + mul_a24(E)); } - x_2.ct_cond_swap(swap.as_bool(), x_3); - z_2.ct_cond_swap(swap.as_bool(), z_3); + x_2.ct_cond_swap(swap, x_3); + z_2.ct_cond_swap(swap, z_3); const auto res = x_2 / z_2; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/dh/dh.cpp botan3-3.12.0+dfsg/src/lib/pubkey/dh/dh.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/dh/dh.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/dh/dh.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -21,10 +21,6 @@ m_public_key = std::make_shared(group, y); } -std::vector DH_PublicKey::public_value() const { - return m_public_key->public_key_as_bytes(); -} - size_t DH_PublicKey::estimated_strength() const { return m_public_key->estimated_strength(); } @@ -46,7 +42,7 @@ } std::vector DH_PublicKey::raw_public_key_bits() const { - return public_value(); + return m_public_key->public_key_as_bytes(); } std::vector DH_PublicKey::public_key_bits() const { @@ -76,12 +72,16 @@ m_public_key = m_private_key->public_key(); } +bool DH_PrivateKey::check_key(RandomNumberGenerator& rng, bool strong) const { + return m_private_key->check_key(rng, strong); +} + std::unique_ptr DH_PrivateKey::public_key() const { return std::unique_ptr(new DH_PublicKey(m_public_key)); } std::vector DH_PrivateKey::public_value() const { - return DH_PublicKey::public_value(); + return raw_public_key_bits(); } secure_vector DH_PrivateKey::private_key_bits() const { diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/dh/dh.h botan3-3.12.0+dfsg/src/lib/pubkey/dh/dh.h --- botan3-3.7.1+dfsg/src/lib/pubkey/dh/dh.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/dh/dh.h 2026-05-07 01:38:28.000000000 +0000 @@ -48,7 +48,9 @@ size_t estimated_strength() const override; size_t key_length() const override; - std::vector public_value() const; + BOTAN_DEPRECATED("Use raw_public_key_bits") std::vector public_value() const { + return raw_public_key_bits(); + } std::string algo_name() const override { return "DH"; } @@ -65,7 +67,7 @@ DH_PublicKey() = default; - DH_PublicKey(std::shared_ptr key) : m_public_key(std::move(key)) {} + explicit DH_PublicKey(std::shared_ptr key) : m_public_key(std::move(key)) {} std::shared_ptr m_public_key; }; @@ -78,7 +80,7 @@ BOTAN_DIAGNOSTIC_IGNORE_INHERITED_VIA_DOMINANCE class BOTAN_PUBLIC_API(2, 0) DH_PrivateKey final : public DH_PublicKey, - public PK_Key_Agreement_Key, + public virtual PK_Key_Agreement_Key, public virtual Private_Key { public: /** @@ -106,6 +108,8 @@ std::vector public_value() const override; + bool check_key(RandomNumberGenerator& rng, bool strong) const override; + secure_vector private_key_bits() const override; secure_vector raw_private_key_bits() const override; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium.cpp botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -248,7 +249,7 @@ class Dilithium_Verification_Operation final : public PK_Ops::Verification { public: - Dilithium_Verification_Operation(std::shared_ptr pubkey) : + explicit Dilithium_Verification_Operation(std::shared_ptr pubkey) : m_pub_key(std::move(pubkey)), m_A(Dilithium_Algos::expand_A(m_pub_key->rho(), m_pub_key->mode())), m_t1_ntt_shifted(ntt(m_pub_key->t1() << DilithiumConstants::D)), @@ -269,7 +270,7 @@ bool is_valid_signature(std::span sig) override { const auto& mode = m_pub_key->mode(); const auto& sympri = mode.symmetric_primitives(); - StrongSpan sig_bytes(sig); + const StrongSpan sig_bytes(sig); const auto mu = m_h->final(); @@ -365,8 +366,14 @@ return raw_public_key_bits(); } -bool Dilithium_PublicKey::check_key(RandomNumberGenerator&, bool) const { - return true; // ??? +bool Dilithium_PublicKey::check_key(RandomNumberGenerator& /*rng*/, bool /*strong*/) const { + // The public key consists of (rho, t1). Length validation is performed in + // the constructor, and t1 coefficients are decoded via SimpleBitUnpack + // (FIPS 204 Algorithm 18) into a power-of-2 range that exactly covers all + // valid values, so no out-of-range coefficients are possible. For the + // private key, s1/s2 coefficient ranges are validated and t is recomputed + // from (A, s1, s2) and verified against the stored hash during decoding. + return true; } std::unique_ptr Dilithium_PublicKey::generate_another(RandomNumberGenerator& rng) const { @@ -446,6 +453,18 @@ throw Provider_Not_Found(algo_name(), provider); } +bool Dilithium_PrivateKey::check_key(RandomNumberGenerator& rng, bool strong) const { + if(!Dilithium_PublicKey::check_key(rng, strong)) { + return false; + } + + if(strong) { + return KeyPair::signature_consistency_check(rng, *this, ""); + } + + return true; +} + std::unique_ptr Dilithium_PrivateKey::public_key() const { return std::make_unique(*this); } diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium.h botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium.h --- botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium.h 2026-05-07 01:38:28.000000000 +0000 @@ -18,14 +18,14 @@ namespace Botan { -class BOTAN_PUBLIC_API(3, 0) DilithiumMode { +class BOTAN_PUBLIC_API(3, 0) DilithiumMode final { public: - enum Mode { - Dilithium4x4 = 1, + enum Mode : uint8_t /* NOLINT(*-use-enum-class) */ { + Dilithium4x4 BOTAN_DEPRECATED("Dilithium R3 is deprecated - use ML-DSA") = 1, Dilithium4x4_AES BOTAN_DEPRECATED("Dilithium AES mode is deprecated"), - Dilithium6x5, + Dilithium6x5 BOTAN_DEPRECATED("Dilithium R3 is deprecated - use ML-DSA"), Dilithium6x5_AES BOTAN_DEPRECATED("Dilithium AES mode is deprecated"), - Dilithium8x7, + Dilithium8x7 BOTAN_DEPRECATED("Dilithium R3 is deprecated - use ML-DSA"), Dilithium8x7_AES BOTAN_DEPRECATED("Dilithium AES mode is deprecated"), ML_DSA_4x4, ML_DSA_6x5, @@ -33,6 +33,7 @@ }; public: + // NOLINTNEXTLINE(*-explicit-conversions) DilithiumMode(Mode mode) : m_mode(mode) {} explicit DilithiumMode(const OID& oid); @@ -66,10 +67,6 @@ */ class BOTAN_PUBLIC_API(3, 0) Dilithium_PublicKey : public virtual Public_Key { public: - Dilithium_PublicKey& operator=(const Dilithium_PublicKey& other) = default; - - ~Dilithium_PublicKey() override = default; - std::string algo_name() const override; AlgorithmIdentifier algorithm_identifier() const override; @@ -84,7 +81,7 @@ std::vector public_key_bits() const override; - bool check_key(RandomNumberGenerator&, bool) const override; + bool check_key(RandomNumberGenerator& rng, bool strong) const override; bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::Signature); } @@ -106,7 +103,7 @@ friend class Dilithium_Verification_Operation; friend class Dilithium_Signature_Operation; - std::shared_ptr m_public; + std::shared_ptr m_public; // NOLINT(*non-private-member-variable*) }; BOTAN_DIAGNOSTIC_PUSH @@ -117,6 +114,8 @@ public: std::unique_ptr public_key() const override; + bool check_key(RandomNumberGenerator& rng, bool strong) const override; + /** * Generates a new key pair */ @@ -141,7 +140,7 @@ * with "Randomized" or "Deterministic" rhoprime. Pass either of those * strings as @p params. Default (i.e. empty @p params is "Randomized"). */ - std::unique_ptr create_signature_op(RandomNumberGenerator&, + std::unique_ptr create_signature_op(RandomNumberGenerator& rng, std::string_view params, std::string_view provider) const override; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_algos.cpp botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_algos.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_algos.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_algos.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -18,15 +18,14 @@ #include #include +#include +#include #include #include #include -#include #include #include #include -#include - #include namespace Botan::Dilithium_Algos { @@ -113,10 +112,10 @@ using Gamma2 = DilithiumConstants::DilithiumGamma2; auto calculate_b = [](auto gamma2) { return ((DilithiumConstants::Q - 1) / (2 * gamma2)) - 1; }; switch(mode.gamma2()) { - case Gamma2::Qminus1DevidedBy88: - return poly_pack<0, calculate_b(Gamma2::Qminus1DevidedBy88)>(p, stuffer); - case Gamma2::Qminus1DevidedBy32: - return poly_pack<0, calculate_b(Gamma2::Qminus1DevidedBy32)>(p, stuffer); + case Gamma2::Qminus1DividedBy88: + return poly_pack<0, calculate_b(Gamma2::Qminus1DividedBy88)>(p, stuffer); + case Gamma2::Qminus1DividedBy32: + return poly_pack<0, calculate_b(Gamma2::Qminus1DividedBy32)>(p, stuffer); } BOTAN_ASSERT_UNREACHABLE(); @@ -287,8 +286,12 @@ } // Check that the remaining bit positions are all zero (strong unforgeability) - const auto remaining = bit_positions.take(bit_positions.remaining()); - if(!std::all_of(remaining.begin(), remaining.end(), [](auto b) { return b == 0; })) { + uint8_t sum = 0; + for(const uint8_t b : bit_positions.take(bit_positions.remaining())) { + sum |= b; + } + + if(sum != 0) { return std::nullopt; } @@ -366,7 +369,7 @@ * NIST FIPS 204, Algorithm 24 (skEncode) */ DilithiumSerializedPrivateKey encode_keypair(const DilithiumInternalKeypair& keypair) { - auto& [pk, sk] = keypair; + const auto& [pk, sk] = keypair; BOTAN_ASSERT_NONNULL(pk); BOTAN_ASSERT_NONNULL(sk); const auto& mode = sk->mode(); @@ -501,7 +504,7 @@ for(auto& p : response) { poly_unpack_gamma1(p, slicer, mode); } - BOTAN_ASSERT_NOMSG(slicer.remaining() == mode.omega() + mode.k()); + BOTAN_ASSERT_NOMSG(slicer.remaining() == size_t(mode.omega()) + mode.k()); auto hint = hint_unpack(slicer, mode); BOTAN_ASSERT_NOMSG(slicer.empty()); @@ -531,8 +534,8 @@ */ DilithiumPoly sample_in_ball(StrongSpan seed, const DilithiumConstants& mode) { // This generator resembles the while loop in the spec. - auto& xof = mode.symmetric_primitives().H(seed); - auto bounded_xof = Bounded_XOF(xof); + auto xof = mode.symmetric_primitives().H(seed); + auto bounded_xof = Bounded_XOF(*xof); DilithiumPoly c; uint64_t signs = load_le(bounded_xof.next<8>()); @@ -562,8 +565,8 @@ * A generator that returns the next coefficient sampled from the XOF, * according to: NIST FIPS 204, Algorithm 14 (CoeffFromThreeBytes). */ - auto& xof = mode.symmetric_primitives().H(rho, nonce); - auto bounded_xof = Bounded_XOF(xof); + auto xof = mode.symmetric_primitives().H(rho, nonce); + auto bounded_xof = Bounded_XOF(*xof); for(auto& coeff : p) { coeff = @@ -585,7 +588,7 @@ if constexpr(eta == DilithiumConstants::DilithiumEta::_2) { if(CT::driveby_unpoison(b < 15)) { - b = b - (205 * b >> 10) * 5; // b = b mod 5 + b = b - (205U * b >> 10) * 5; // b = b mod 5 return 2 - b; } } else if constexpr(eta == DilithiumConstants::DilithiumEta::_4) { @@ -638,13 +641,13 @@ const DilithiumConstants& mode) { using Eta = DilithiumConstants::DilithiumEta; - auto& xof = mode.symmetric_primitives().H(rhoprime, nonce); + auto xof = mode.symmetric_primitives().H(rhoprime, nonce); switch(mode.eta()) { case Eta::_2: - sample_uniform_eta(p, xof); + sample_uniform_eta(p, *xof); break; case Eta::_4: - sample_uniform_eta(p, xof); + sample_uniform_eta(p, *xof); break; } @@ -663,6 +666,9 @@ * encoding is deferred until the user explicitly invokes the encoding. */ DilithiumInternalKeypair expand_keypair(DilithiumSeedRandomness xi, DilithiumConstants mode) { + if(xi.size() != DilithiumConstants::SEED_RANDOMNESS_BYTES) { + throw Decoding_Error("Invalid ML-DSA seed size"); + } const auto& sympriv = mode.symmetric_primitives(); CT::poison(xi); @@ -684,7 +690,7 @@ CT::unpoison(*keypair.second); return keypair; -}; +} /** * NIST FIPS 204, Algorithm 32 (ExpandA) @@ -730,8 +736,8 @@ const DilithiumConstants& mode) { DilithiumPolyVec s(mode.l()); for(auto& p : s) { - auto& xof = mode.symmetric_primitives().H(rhoprime, nonce++); - poly_unpack_gamma1(p, xof, mode); + auto xof = mode.symmetric_primitives().H(rhoprime, nonce++); + poly_unpack_gamma1(p, *xof, mode); } return s; } @@ -775,10 +781,10 @@ std::pair decompose(int32_t r) { int32_t r1 = (r + 127) >> 7; - if constexpr(gamma2 == DilithiumConstants::DilithiumGamma2::Qminus1DevidedBy32) { + if constexpr(gamma2 == DilithiumConstants::DilithiumGamma2::Qminus1DividedBy32) { r1 = (r1 * 1025 + (1 << 21)) >> 22; r1 &= 15; - } else if constexpr(gamma2 == DilithiumConstants::DilithiumGamma2::Qminus1DevidedBy88) { + } else if constexpr(gamma2 == DilithiumConstants::DilithiumGamma2::Qminus1DividedBy88) { r1 = (r1 * 11275 + (1 << 23)) >> 24; r1 = is_negative_mask(43 - r1).if_not_set_return(r1); } @@ -796,7 +802,7 @@ * optimization given the statically known value of gamma2. */ template -std::pair decompose_all_coefficents(const DilithiumPolyVec& vec) { +std::pair decompose_all_coefficients(const DilithiumPolyVec& vec) { auto result = std::make_pair(DilithiumPolyVec(vec.size()), DilithiumPolyVec(vec.size())); for(size_t i = 0; i < vec.size(); ++i) { @@ -819,11 +825,11 @@ std::pair decompose(const DilithiumPolyVec& vec, const DilithiumConstants& mode) { using Gamma2 = DilithiumConstants::DilithiumGamma2; switch(mode.gamma2()) { - case Gamma2::Qminus1DevidedBy32: - return decompose_all_coefficents(vec); + case Gamma2::Qminus1DividedBy32: + return decompose_all_coefficients(vec); break; - case Gamma2::Qminus1DevidedBy88: - return decompose_all_coefficents(vec); + case Gamma2::Qminus1DividedBy88: + return decompose_all_coefficients(vec); break; } @@ -861,7 +867,7 @@ for(size_t i = 0; i < r.size(); ++i) { for(size_t j = 0; j < r[i].size(); ++j) { - hint[i][j] = make_hint(z[i][j], r[i][j]).as_bool(); + hint[i][j] = static_cast(make_hint(z[i][j], r[i][j]).as_bool()); } } @@ -922,11 +928,11 @@ using Gamma2 = DilithiumConstants::DilithiumGamma2; switch(mode.gamma2()) { - case Gamma2::Qminus1DevidedBy32: - use_hint_on_coefficients(hints, vec); + case Gamma2::Qminus1DividedBy32: + use_hint_on_coefficients(hints, vec); break; - case Gamma2::Qminus1DevidedBy88: - use_hint_on_coefficients(hints, vec); + case Gamma2::Qminus1DividedBy88: + use_hint_on_coefficients(hints, vec); break; } diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_algos.h botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_algos.h --- botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_algos.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_algos.h 2026-05-07 01:38:28.000000000 +0000 @@ -16,6 +16,7 @@ // ML-DSA does encode the private key only by its random seeds. #if defined(BOTAN_HAS_DILITHIUM) || defined(BOTAN_HAS_DILITHIUM_AES) + // NOLINTNEXTLINE(*-macro-usage) #define BOTAN_NEEDS_DILITHIUM_PRIVATE_KEY_ENCODING 1 #endif diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_constants.cpp botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_constants.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_constants.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_constants.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -67,7 +67,7 @@ m_tau = DilithiumTau::_39; m_lambda = DilithiumLambda::_128; m_gamma1 = DilithiumGamma1::ToThe17th; - m_gamma2 = DilithiumGamma2::Qminus1DevidedBy88; + m_gamma2 = DilithiumGamma2::Qminus1DividedBy88; m_k = 4; m_l = 4; m_eta = DilithiumEta::_2; @@ -80,7 +80,7 @@ m_tau = DilithiumTau::_49; m_lambda = DilithiumLambda::_192; m_gamma1 = DilithiumGamma1::ToThe19th; - m_gamma2 = DilithiumGamma2::Qminus1DevidedBy32; + m_gamma2 = DilithiumGamma2::Qminus1DividedBy32; m_k = 6; m_l = 5; m_eta = DilithiumEta::_4; @@ -93,7 +93,7 @@ m_tau = DilithiumTau::_60; m_lambda = DilithiumLambda::_256; m_gamma1 = DilithiumGamma1::ToThe19th; - m_gamma2 = DilithiumGamma2::Qminus1DevidedBy32; + m_gamma2 = DilithiumGamma2::Qminus1DividedBy32; m_k = 8; m_l = 7; m_eta = DilithiumEta::_2; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_constants.h botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_constants.h --- botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_constants.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_constants.h 2026-05-07 01:38:28.000000000 +0000 @@ -70,21 +70,25 @@ /// @} public: - enum DilithiumTau : uint32_t { _39 = 39, _49 = 49, _60 = 60 }; + // NOLINTBEGIN(*-use-enum-class) - enum DilithiumLambda : uint32_t { _128 = 128, _192 = 192, _256 = 256 }; + enum DilithiumTau : uint8_t { _39 = 39, _49 = 49, _60 = 60 }; + + enum DilithiumLambda : uint16_t { _128 = 128, _192 = 192, _256 = 256 }; enum DilithiumGamma1 : uint32_t { ToThe17th = (1 << 17), ToThe19th = (1 << 19) }; - enum DilithiumGamma2 : uint32_t { Qminus1DevidedBy88 = (Q - 1) / 88, Qminus1DevidedBy32 = (Q - 1) / 32 }; + enum DilithiumGamma2 : uint32_t { Qminus1DividedBy88 = (Q - 1) / 88, Qminus1DividedBy32 = (Q - 1) / 32 }; + + enum DilithiumEta : uint8_t { _2 = 2, _4 = 4 }; - enum DilithiumEta : uint32_t { _2 = 2, _4 = 4 }; + enum DilithiumBeta : uint8_t { _78 = 78, _196 = 196, _120 = 120 }; - enum DilithiumBeta : uint32_t { _78 = 78, _196 = 196, _120 = 120 }; + enum DilithiumOmega : uint8_t { _80 = 80, _55 = 55, _75 = 75 }; - enum DilithiumOmega : uint32_t { _80 = 80, _55 = 55, _75 = 75 }; + // NOLINTEND(*-use-enum-class) - DilithiumConstants(DilithiumMode dimension); + explicit DilithiumConstants(DilithiumMode dimension); ~DilithiumConstants(); DilithiumConstants(const DilithiumConstants& other) : DilithiumConstants(other.m_mode) {} diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_keys.h botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_keys.h --- botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_keys.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_keys.h 2026-05-07 01:38:28.000000000 +0000 @@ -20,7 +20,7 @@ namespace Botan { -class Dilithium_Keypair_Codec { +class Dilithium_Keypair_Codec /* NOLINT(*-special-member-functions) */ { public: static std::unique_ptr create(DilithiumMode mode); @@ -30,7 +30,7 @@ DilithiumConstants mode) const = 0; }; -class Dilithium_PublicKeyInternal { +class Dilithium_PublicKeyInternal final { public: static std::shared_ptr decode( DilithiumConstants mode, StrongSpan raw_pk) { @@ -65,7 +65,7 @@ DilithiumHashedPublicKey m_tr; }; -class Dilithium_PrivateKeyInternal { +class Dilithium_PrivateKeyInternal final { public: Dilithium_PrivateKeyInternal(DilithiumConstants mode, std::optional seed, diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_polynomial.h botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_polynomial.h --- botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_polynomial.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_polynomial.h 2026-05-07 01:38:28.000000000 +0000 @@ -25,7 +25,7 @@ static constexpr T montgomery_reduce_coefficient(T2 a) { const T2 t = static_cast(static_cast(static_cast(a)) * Q_inverse); - return (a - static_cast(t) * Q) >> (sizeof(T) * 8); + return static_cast((a - static_cast(t) * Q) >> (sizeof(T) * 8)); } static constexpr T barrett_reduce_coefficient(T a) { @@ -48,7 +48,7 @@ * factors in the coefficients. */ static constexpr void ntt(std::span coeffs) { - size_t j; + size_t j = 0; size_t k = 0; for(size_t len = N / 2; len > 0; len >>= 1) { @@ -56,7 +56,7 @@ const T zeta = zetas[++k]; for(j = start; j < start + len; ++j) { // Zetas contain the montgomery parameter 2^32 mod q - T t = fqmul(zeta, coeffs[j + len]); + const T t = fqmul(zeta, coeffs[j + len]); coeffs[j + len] = coeffs[j] - t; coeffs[j] = coeffs[j] + t; } @@ -76,13 +76,13 @@ * factor of (2^32 mod q) added (!). See above. */ static constexpr void inverse_ntt(std::span coeffs) { - size_t j; + size_t j = 0; size_t k = N; for(size_t len = 1; len < N; len <<= 1) { for(size_t start = 0; start < N; start = j + len) { const T zeta = -zetas[--k]; for(j = start; j < start + len; ++j) { - T t = coeffs[j]; + const T t = coeffs[j]; coeffs[j] = t + coeffs[j + len]; coeffs[j + len] = t - coeffs[j + len]; // Zetas contain the montgomery parameter 2^32 mod q diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_shake/dilithium_shake_xof.cpp botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_shake/dilithium_shake_xof.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_shake/dilithium_shake_xof.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_shake/dilithium_shake_xof.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,25 @@ +/* +* (C) 2026 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include + +namespace Botan { + +DilithiumShakeXOF::~DilithiumShakeXOF() = default; + +//static +std::unique_ptr DilithiumShakeXOF::createXOF(std::string_view name, + std::span seed, + uint16_t nonce) { + auto xof = Botan::XOF::create_or_throw(name); + xof->update(seed); + xof->update(store_le(nonce)); + return xof; +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_shake/dilithium_shake_xof.h botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_shake/dilithium_shake_xof.h --- botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_shake/dilithium_shake_xof.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_shake/dilithium_shake_xof.h 2026-05-07 01:38:28.000000000 +0000 @@ -11,32 +11,31 @@ #include -#include -#include - namespace Botan { class DilithiumShakeXOF final : public DilithiumXOF { public: - Botan::XOF& XOF128(std::span seed, uint16_t nonce) const override { - return XOF(m_xof_128, seed, nonce); - } + DilithiumShakeXOF() = default; + + ~DilithiumShakeXOF() override; - Botan::XOF& XOF256(std::span seed, uint16_t nonce) const override { - return XOF(m_xof_256, seed, nonce); + DilithiumShakeXOF(const DilithiumShakeXOF& other) = delete; + DilithiumShakeXOF(DilithiumShakeXOF&& other) = delete; + DilithiumShakeXOF& operator=(const DilithiumShakeXOF& other) = delete; + DilithiumShakeXOF& operator=(DilithiumShakeXOF&& other) = delete; + + std::unique_ptr XOF128(std::span seed, uint16_t nonce) const override { + return createXOF("SHAKE-128", seed, nonce); } - private: - static Botan::XOF& XOF(Botan::XOF& xof, std::span seed, uint16_t nonce) { - xof.clear(); - xof.update(seed); - xof.update(store_le(nonce)); - return xof; + std::unique_ptr XOF256(std::span seed, uint16_t nonce) const override { + return createXOF("SHAKE-256", seed, nonce); } private: - mutable SHAKE_256_XOF m_xof_256; - mutable SHAKE_128_XOF m_xof_128; + static std::unique_ptr createXOF(std::string_view name, + std::span seed, + uint16_t nonce); }; } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_symmetric_primitives.cpp botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_symmetric_primitives.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_symmetric_primitives.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_symmetric_primitives.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -11,6 +11,8 @@ #include +#include + #if defined(BOTAN_HAS_DILITHIUM) #include #endif @@ -25,6 +27,22 @@ namespace Botan { +DilithiumMessageHash::DilithiumMessageHash(DilithiumHashedPublicKey tr) : + m_tr(std::move(tr)), m_shake(XOF::create_or_throw("SHAKE-256")) {} + +DilithiumMessageHash::~DilithiumMessageHash() = default; + +std::string DilithiumMessageHash::name() const { + return Botan::fmt("{}({})", m_shake->name(), DilithiumConstants::MESSAGE_HASH_BYTES * 8); +} + +Dilithium_Symmetric_Primitives_Base::Dilithium_Symmetric_Primitives_Base(const DilithiumConstants& mode, + std::unique_ptr xof_adapter) : + m_commitment_hash_length_bytes(mode.commitment_hash_full_bytes()), + m_public_key_hash_bytes(mode.public_key_hash_bytes()), + m_mode(mode.mode()), + m_xof_adapter(std::move(xof_adapter)) {} + std::unique_ptr Dilithium_Symmetric_Primitives_Base::create( const DilithiumConstants& mode) { #if defined(BOTAN_HAS_DILITHIUM) diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_symmetric_primitives.h botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_symmetric_primitives.h --- botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_symmetric_primitives.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/dilithium_symmetric_primitives.h 2026-05-07 01:38:28.000000000 +0000 @@ -12,11 +12,8 @@ #define BOTAN_DILITHIUM_ASYM_PRIMITIVES_H_ #include - +#include #include -#include -#include -#include namespace Botan { @@ -28,15 +25,13 @@ * * Namely: mu = H(tr || M) */ -class DilithiumMessageHash { +class DilithiumMessageHash /* NOLINT(*-special-member-functions) */ { public: - DilithiumMessageHash(DilithiumHashedPublicKey tr) : m_tr(std::move(tr)) { clear(); } + explicit DilithiumMessageHash(DilithiumHashedPublicKey tr); - virtual ~DilithiumMessageHash() = default; + virtual ~DilithiumMessageHash(); - std::string name() const { - return Botan::fmt("{}({})", m_shake.name(), DilithiumConstants::MESSAGE_HASH_BYTES * 8); - } + std::string name() const; virtual bool is_valid_user_context(std::span user_context) const { // Only ML-DSA supports user contexts, for all other modes it must be empty. @@ -52,18 +47,18 @@ void update(std::span data) { ensure_started(); - m_shake.update(data); + m_shake->update(data); } DilithiumMessageRepresentative final() { ensure_started(); - scoped_cleanup clean([this]() { clear(); }); - return m_shake.output(DilithiumConstants::MESSAGE_HASH_BYTES); + const scoped_cleanup clean([this]() { clear(); }); + return m_shake->output(DilithiumConstants::MESSAGE_HASH_BYTES); } private: void clear() { - m_shake.clear(); + m_shake->clear(); m_was_started = false; } @@ -76,8 +71,8 @@ private: DilithiumHashedPublicKey m_tr; - bool m_was_started; - SHAKE_256_XOF m_shake; + bool m_was_started = false; + std::unique_ptr m_shake; }; /** @@ -86,12 +81,12 @@ * was not standardized in the FIPS 204; ML-DSA always uses SHAKE. Once we decide * to remove the AES variant, this can be removed. */ -class DilithiumXOF { +class DilithiumXOF /* NOLINT(*-special-member-functions) */ { public: virtual ~DilithiumXOF() = default; - virtual Botan::XOF& XOF128(std::span seed, uint16_t nonce) const = 0; - virtual Botan::XOF& XOF256(std::span seed, uint16_t nonce) const = 0; + virtual std::unique_ptr XOF128(std::span seed, uint16_t nonce) const = 0; + virtual std::unique_ptr XOF256(std::span seed, uint16_t nonce) const = 0; }; /** @@ -100,11 +95,7 @@ */ class Dilithium_Symmetric_Primitives_Base { protected: - Dilithium_Symmetric_Primitives_Base(const DilithiumConstants& mode, std::unique_ptr xof_adapter) : - m_commitment_hash_length_bytes(mode.commitment_hash_full_bytes()), - m_public_key_hash_bytes(mode.public_key_hash_bytes()), - m_mode(mode.mode()), - m_xof_adapter(std::move(xof_adapter)) {} + Dilithium_Symmetric_Primitives_Base(const DilithiumConstants& mode, std::unique_ptr xof_adapter); public: static std::unique_ptr create(const DilithiumConstants& mode); @@ -132,18 +123,18 @@ std::tuple H( StrongSpan seed) const { - m_xof.update(seed); + auto xof = XOF::create_or_throw("SHAKE-256"); + xof->update(seed); if(auto domsep = seed_expansion_domain_separator()) { - m_xof.update(domsep.value()); + xof->update(domsep.value()); } // Note: The order of invocations in an initializer list is not // guaranteed by the C++ standard. Hence, we have to store the // results in variables to ensure the correct order of execution. - auto rho = m_xof.output(DilithiumConstants::SEED_RHO_BYTES); - auto rhoprime = m_xof.output(DilithiumConstants::SEED_RHOPRIME_BYTES); - auto k = m_xof.output(DilithiumConstants::SEED_SIGNING_KEY_BYTES); - m_xof.clear(); + auto rho = xof->output(DilithiumConstants::SEED_RHO_BYTES); + auto rhoprime = xof->output(DilithiumConstants::SEED_RHOPRIME_BYTES); + auto k = xof->output(DilithiumConstants::SEED_SIGNING_KEY_BYTES); return {std::move(rho), std::move(rhoprime), std::move(k)}; } @@ -153,21 +144,17 @@ return H_256(m_commitment_hash_length_bytes, mu, w1); } - SHAKE_256_XOF& H(StrongSpan seed) const { - m_xof_external.clear(); - m_xof_external.update(truncate_commitment_hash(seed)); - return m_xof_external; + std::unique_ptr H(StrongSpan seed) const { + auto xof = XOF::create_or_throw("SHAKE-256"); + xof->update(truncate_commitment_hash(seed)); + return xof; } - // Once Dilithium AES is removed, this could return a SHAKE_256_XOF and - // avoid the virtual method call. - Botan::XOF& H(StrongSpan seed, uint16_t nonce) const { + std::unique_ptr H(StrongSpan seed, uint16_t nonce) const { return m_xof_adapter->XOF128(seed, nonce); } - // Once Dilithium AES is removed, this could return a SHAKE_128_XOF and - // avoid the virtual method call. - Botan::XOF& H(StrongSpan seed, uint16_t nonce) const { + std::unique_ptr H(StrongSpan seed, uint16_t nonce) const { return m_xof_adapter->XOF256(seed, nonce); } @@ -188,10 +175,10 @@ virtual std::optional> seed_expansion_domain_separator() const = 0; template - OutT H_256(size_t outbytes, InTs&&... ins) const { - scoped_cleanup clean([this]() { m_xof.clear(); }); - (m_xof.update(ins), ...); - return m_xof.output(outbytes); + OutT H_256(size_t outbytes, const InTs&... ins) const { + auto xof = XOF::create_or_throw("SHAKE-256"); + (xof->update(ins), ...); + return xof->output(outbytes); } private: @@ -200,8 +187,6 @@ DilithiumMode m_mode; std::unique_ptr m_xof_adapter; - mutable SHAKE_256_XOF m_xof; - mutable SHAKE_256_XOF m_xof_external; }; } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/dilithium_common/info.txt botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/info.txt --- botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/dilithium_common/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_common/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -22,6 +22,7 @@ +keypair pqcrystals pubkey rng diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/dilithium_round3/dilithium/dilithium_round3.h botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_round3/dilithium/dilithium_round3.h --- botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/dilithium_round3/dilithium/dilithium_round3.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_round3/dilithium/dilithium_round3.h 2026-05-07 01:38:28.000000000 +0000 @@ -17,7 +17,7 @@ class Dilithium_Symmetric_Primitives final : public Dilithium_Round3_Symmetric_Primitives { public: - Dilithium_Symmetric_Primitives(const DilithiumConstants& mode) : + explicit Dilithium_Symmetric_Primitives(const DilithiumConstants& mode) : Dilithium_Round3_Symmetric_Primitives(mode, std::make_unique()) {} }; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/dilithium_round3/dilithium_aes/dilithium_aes.cpp botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_round3/dilithium_aes/dilithium_aes.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/dilithium_round3/dilithium_aes/dilithium_aes.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_round3/dilithium_aes/dilithium_aes.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -19,16 +19,17 @@ class AES_XOF final : public DilithiumXOF { public: - Botan::XOF& XOF128(std::span seed, uint16_t nonce) const override { - return XOF(m_aes_xof, seed, nonce); + std::unique_ptr XOF128(std::span seed, uint16_t nonce) const override { + return create_xof(seed, nonce); } - Botan::XOF& XOF256(std::span seed, uint16_t nonce) const override { - return XOF(m_aes_xof, seed, nonce); + std::unique_ptr XOF256(std::span seed, uint16_t nonce) const override { + return create_xof(seed, nonce); } + private: // AES mode always uses AES-256, regardless of the XofType - static Botan::XOF& XOF(Botan::XOF& xof, std::span seed, uint16_t nonce) { + static std::unique_ptr create_xof(std::span seed, uint16_t nonce) { // Algorithm Spec V. 3.1 Section 5.3 // In the AES variant, the first 32 bytes of rhoprime are used as // the key and i is extended to a 12 byte nonce for AES-256 in @@ -41,13 +42,10 @@ const std::array iv{get_byte<1>(nonce), get_byte<0>(nonce), 0}; const auto key = seed.first(32); - xof.clear(); - xof.start(iv, key); + auto xof = std::make_unique(); + xof->start(iv, key); return xof; } - - private: - mutable AES_256_CTR_XOF m_aes_xof; }; } // namespace diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/dilithium_round3/dilithium_aes/dilithium_aes.h botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_round3/dilithium_aes/dilithium_aes.h --- botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/dilithium_round3/dilithium_aes/dilithium_aes.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_round3/dilithium_aes/dilithium_aes.h 2026-05-07 01:38:28.000000000 +0000 @@ -15,7 +15,7 @@ class Dilithium_AES_Symmetric_Primitives final : public Dilithium_Round3_Symmetric_Primitives { public: - Dilithium_AES_Symmetric_Primitives(const DilithiumConstants& mode); + explicit Dilithium_AES_Symmetric_Primitives(const DilithiumConstants& mode); }; } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/dilithium_round3/dilithium_round3_symmetric_primitives.cpp botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_round3/dilithium_round3_symmetric_primitives.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/dilithium_round3/dilithium_round3_symmetric_primitives.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/dilithium_round3/dilithium_round3_symmetric_primitives.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -10,7 +10,6 @@ #include -#include #include namespace Botan { diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/ml_dsa/ml_dsa.h botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/ml_dsa/ml_dsa.h --- botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/ml_dsa/ml_dsa.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/ml_dsa/ml_dsa.h 2026-05-07 01:38:28.000000000 +0000 @@ -24,4 +24,4 @@ } // namespace Botan -#endif \ No newline at end of file +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/ml_dsa/ml_dsa_impl.h botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/ml_dsa/ml_dsa_impl.h --- botan3-3.7.1+dfsg/src/lib/pubkey/dilithium/ml_dsa/ml_dsa_impl.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/dilithium/ml_dsa/ml_dsa_impl.h 2026-05-07 01:38:28.000000000 +0000 @@ -61,7 +61,7 @@ } public: - ML_DSA_Symmetric_Primitives(const DilithiumConstants& mode) : + explicit ML_DSA_Symmetric_Primitives(const DilithiumConstants& mode) : Dilithium_Symmetric_Primitives_Base(mode, std::make_unique()), m_seed_expansion_domain_separator({mode.k(), mode.l()}) {} diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/dl_algo/dl_scheme.cpp botan3-3.12.0+dfsg/src/lib/pubkey/dl_algo/dl_scheme.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/dl_algo/dl_scheme.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/dl_algo/dl_scheme.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -16,13 +16,13 @@ BigInt decode_single_bigint(std::span key_bits) { BigInt x; - BER_Decoder(key_bits).decode(x); + BER_Decoder(key_bits, BER_Decoder::Limits::DER()).decode(x).verify_end(); return x; } BigInt generate_private_dl_key(const DL_Group& group, RandomNumberGenerator& rng) { if(group.has_q() && group.q_bits() >= 160 && group.q_bits() <= 384) { - return BigInt::random_integer(rng, 2, group.get_q()); + return BigInt::random_integer(rng, BigInt::from_s32(2), group.get_q()); } else { return BigInt(rng, group.exponent_bits()); } @@ -35,13 +35,17 @@ } // namespace -DL_PublicKey::DL_PublicKey(const DL_Group& group, const BigInt& public_key) : - m_group(group), m_public_key(public_key) {} +DL_PublicKey::DL_PublicKey(const DL_Group& group, const BigInt& public_key) : m_group(group), m_public_key(public_key) { + // The subgroup check (y^q == 1 mod p) is deferred to check_key() since it can be expensive + BOTAN_ARG_CHECK(m_public_key > 1 && m_public_key < m_group.get_p(), "Invalid DL public key"); +} DL_PublicKey::DL_PublicKey(const AlgorithmIdentifier& alg_id, std::span key_bits, DL_Group_Format format) : - m_group(alg_id.parameters(), format), m_public_key(decode_single_bigint(key_bits)) {} + m_group(alg_id.parameters(), format), m_public_key(decode_single_bigint(key_bits)) { + BOTAN_ARG_CHECK(m_public_key > 1 && m_public_key < m_group.get_p(), "Invalid DL public key"); +} std::vector DL_PublicKey::public_key_as_bytes() const { return m_public_key.serialize(m_group.p_bytes()); @@ -80,7 +84,7 @@ DL_Group_Format format) : m_group(alg_id.parameters(), format), m_private_key(check_dl_private_key_input(decode_single_bigint(key_bits), m_group)), - m_public_key(m_group.power_g_p(m_private_key, m_group.p_bits())) {} + m_public_key(m_group.power_g_p(m_private_key, m_private_key.bits())) {} secure_vector DL_PrivateKey::DER_encode() const { return DER_Encoder().encode(m_private_key).get_contents(); diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/dl_group/dl_group.cpp botan3-3.12.0+dfsg/src/lib/pubkey/dl_group/dl_group.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/dl_group/dl_group.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/dl_group/dl_group.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include #include #include @@ -23,15 +23,50 @@ namespace Botan { +namespace { + +void check_dl_group_params(const BigInt& p, const BigInt& g) { + if(p.signum() <= 0 || p.is_even() || p.bits() < 3 || p.bits() > 16384) { + throw Decoding_Error("Invalid DL group prime"); + } + if(g.signum() <= 0 || g < 2 || g >= p) { + throw Decoding_Error("Invalid DL group generator"); + } +} + +void check_dl_group_params(const BigInt& p, const BigInt& q, const BigInt& g) { + check_dl_group_params(p, g); + if(q.signum() <= 0 || q.is_even() || q.bits() >= p.bits()) { + throw Decoding_Error("Invalid DL group subgroup order"); + } +} + +} // namespace + class DL_Group_Data final { public: + static std::shared_ptr create(const BigInt& p, + const BigInt& q, + const BigInt& g, + DL_Group_Source source) { + check_dl_group_params(p, q, g); + return std::make_shared(p, q, g, source); + } + + static std::shared_ptr create(const BigInt& p, const BigInt& g, DL_Group_Source source) { + check_dl_group_params(p, g); + return std::make_shared(p, g, source); + } + + // This constructor is public because C++ is terrible but all DL_Group_Data should + // be created via DL_Group_Data::create DL_Group_Data(const BigInt& p, const BigInt& q, const BigInt& g, DL_Group_Source source) : m_p(p), m_q(q), m_g(g), - m_mod_p(Modular_Reducer::for_public_modulus(p)), - m_mod_q(Modular_Reducer::for_public_modulus(q)), - m_monty_params(std::make_shared(m_p, m_mod_p)), + m_mod_p(Barrett_Reduction::for_public_modulus(p)), + m_mod_q(Barrett_Reduction::for_public_modulus(q)), + m_monty_params(m_p, m_mod_p), m_monty(monty_precompute(m_monty_params, m_g, /*window bits=*/4)), m_p_bits(p.bits()), m_q_bits(q.bits()), @@ -39,11 +74,13 @@ m_exponent_bits(dl_exponent_size(m_p_bits)), m_source(source) {} + // This constructor is public because C++ is terrible but all DL_Group_Data should + // be created via DL_Group_Data::create DL_Group_Data(const BigInt& p, const BigInt& g, DL_Group_Source source) : m_p(p), m_g(g), - m_mod_p(Modular_Reducer::for_public_modulus(p)), - m_monty_params(std::make_shared(m_p, m_mod_p)), + m_mod_p(Barrett_Reduction::for_public_modulus(p)), + m_monty_params(m_p, m_mod_p), m_monty(monty_precompute(m_monty_params, m_g, /*window bits=*/4)), m_p_bits(p.bits()), m_q_bits(0), @@ -64,14 +101,14 @@ const BigInt& g() const { return m_g; } - const Modular_Reducer& reducer_mod_p() const { return m_mod_p; } + const Barrett_Reduction& reducer_mod_p() const { return m_mod_p; } - const Modular_Reducer& reducer_mod_q() const { + const Barrett_Reduction& reducer_mod_q() const { BOTAN_STATE_CHECK(m_mod_q); return *m_mod_q; } - std::shared_ptr monty_params_p() const { return m_monty_params; } + const Montgomery_Params& monty_params_p() const { return m_monty_params; } size_t p_bits() const { return m_p_bits; } @@ -102,7 +139,7 @@ bool q_is_set() const { return m_q_bits > 0; } void assert_q_is_set(std::string_view function) const { - if(q_is_set() == false) { + if(!q_is_set()) { throw Invalid_State(fmt("DL_Group::{}: q is not set for this group", function)); } } @@ -113,10 +150,10 @@ BigInt m_p; BigInt m_q; // zero if no q set BigInt m_g; - Modular_Reducer m_mod_p; - std::optional m_mod_q; - std::shared_ptr m_monty_params; - std::shared_ptr m_monty; + Barrett_Reduction m_mod_p; + std::optional m_mod_q; + Montgomery_Params m_monty_params; + std::shared_ptr m_monty; size_t m_p_bits; size_t m_q_bits; size_t m_estimated_strength; @@ -125,25 +162,38 @@ }; //static -std::shared_ptr DL_Group::BER_decode_DL_group(const uint8_t data[], - size_t data_len, +std::shared_ptr DL_Group::DER_decode_DL_group(const std::span data, DL_Group_Format format, DL_Group_Source source) { - BER_Decoder decoder(data, data_len); - BER_Decoder ber = decoder.start_sequence(); + BER_Decoder decoder(data, BER_Decoder::Limits::DER()); + BER_Decoder inner = decoder.start_sequence(); if(format == DL_Group_Format::ANSI_X9_57) { - BigInt p, q, g; - ber.decode(p).decode(q).decode(g).verify_end(); - return std::make_shared(p, q, g, source); + /* + This format is p, q, g with no additional data following + */ + BigInt p; + BigInt q; + BigInt g; + inner.decode(p).decode(q).decode(g).verify_end(); + return DL_Group_Data::create(p, q, g, source); } else if(format == DL_Group_Format::ANSI_X9_42) { - BigInt p, g, q; - ber.decode(p).decode(g).decode(q).discard_remaining(); - return std::make_shared(p, q, g, source); + /* + This format is p, g, q with optional cofactor and seed following + */ + BigInt p; + BigInt g; + BigInt q; + inner.decode(p).decode(g).decode(q).discard_remaining(); + return DL_Group_Data::create(p, q, g, source); } else if(format == DL_Group_Format::PKCS_3) { - BigInt p, g; - ber.decode(p).decode(g).discard_remaining(); - return std::make_shared(p, g, source); + /* + This format is p, g followed by optional privateValueLength (recommended exponent size) + */ + BigInt p; + BigInt g; + inner.decode(p).decode(g).discard_remaining(); + return DL_Group_Data::create(p, g, source); } else { throw Invalid_Argument("Unknown DL_Group encoding"); } @@ -156,9 +206,9 @@ const BigInt g(g_str); if(q.is_zero()) { - return std::make_shared(p, g, DL_Group_Source::Builtin); + return DL_Group_Data::create(p, g, DL_Group_Source::Builtin); } else { - return std::make_shared(p, q, g, DL_Group_Source::Builtin); + return DL_Group_Data::create(p, q, g, DL_Group_Source::Builtin); } } @@ -168,7 +218,7 @@ const BigInt q = (p - 1) / 2; const BigInt g(g_str); - return std::make_shared(p, q, g, DL_Group_Source::Builtin); + return DL_Group_Data::create(p, q, g, DL_Group_Source::Builtin); } namespace { @@ -197,10 +247,10 @@ if(m_data == nullptr) { try { std::string label; - const std::vector ber = unlock(PEM_Code::decode(str, label)); - DL_Group_Format format = pem_label_to_dl_format(label); + const std::vector der = unlock(PEM_Code::decode(str, label)); + const DL_Group_Format format = pem_label_to_dl_format(label); - m_data = BER_decode_DL_group(ber.data(), ber.size(), format, DL_Group_Source::ExternalSource); + m_data = DER_decode_DL_group(der, format, DL_Group_Source::ExternalSource); } catch(...) {} } @@ -223,7 +273,7 @@ DL_Group DL_Group::from_PEM(std::string_view pem) { std::string label; const std::vector ber = unlock(PEM_Code::decode(pem, label)); - DL_Group_Format format = pem_label_to_dl_format(label); + const DL_Group_Format format = pem_label_to_dl_format(label); return DL_Group(ber, format); } @@ -233,16 +283,20 @@ * Create generator of the q-sized subgroup (DSA style generator) */ BigInt make_dsa_generator(const BigInt& p, const BigInt& q) { - BigInt e, r; + BigInt e; + BigInt r; vartime_divide(p - 1, q, e, r); if(e == 0 || r > 0) { throw Invalid_Argument("make_dsa_generator q does not divide p-1"); } + // TODO we compute these, then throw them away and recompute in DL_Group_Data + auto mod_p = Barrett_Reduction::for_public_modulus(p); + const Montgomery_Params params(p, mod_p); + for(size_t i = 0; i != PRIME_TABLE_SIZE; ++i) { - // TODO precompute! - BigInt g = power_mod(BigInt::from_word(PRIMES[i]), e, p); + BigInt g = monty_exp_vartime(params, BigInt::from_word(PRIMES[i]), e).value(); if(g > 1) { return g; } @@ -274,46 +328,45 @@ const BigInt q = (p - 1) / 2; /* - Always choose a generator that is quadratic reside mod p, - this forces g to be a generator of the subgroup of size q. + Always choose a generator that is quadratic reside mod p, this forces g to + be a generator of the subgroup of size q. + + We use 2 by default, but if 2 is not a quadratic reside then use 4 which + is always a quadratic reside, being the square of 2 (or p - 2) */ BigInt g = BigInt::from_word(2); if(jacobi(g, p) != 1) { - // prime table does not contain 2 - for(size_t i = 0; i < PRIME_TABLE_SIZE; ++i) { - g = BigInt::from_word(PRIMES[i]); - if(jacobi(g, p) == 1) { - break; - } - } + g = BigInt::from_word(4); } - m_data = std::make_shared(p, q, g, DL_Group_Source::RandomlyGenerated); + m_data = DL_Group_Data::create(p, q, g, DL_Group_Source::RandomlyGenerated); } else if(type == Prime_Subgroup) { if(qbits == 0) { qbits = dl_exponent_size(pbits); } const BigInt q = random_prime(rng, qbits); - auto mod_2q = Modular_Reducer::for_public_modulus(2 * q); + const BigInt q2 = q * 2; BigInt X; BigInt p; while(p.bits() != pbits || !is_prime(p, rng, 128, true)) { X.randomize(rng, pbits); - p = X - mod_2q.reduce(X) + 1; + // Variable time division is OK here since DH groups are public anyway + p = X - (X % q2) + 1; } const BigInt g = make_dsa_generator(p, q); - m_data = std::make_shared(p, q, g, DL_Group_Source::RandomlyGenerated); + m_data = DL_Group_Data::create(p, q, g, DL_Group_Source::RandomlyGenerated); } else if(type == DSA_Kosherizer) { if(qbits == 0) { qbits = ((pbits <= 1024) ? 160 : 256); } - BigInt p, q; + BigInt p; + BigInt q; generate_dsa_primes(rng, p, q, pbits, qbits); const BigInt g = make_dsa_generator(p, q); - m_data = std::make_shared(p, q, g, DL_Group_Source::RandomlyGenerated); + m_data = DL_Group_Data::create(p, q, g, DL_Group_Source::RandomlyGenerated); } else { throw Invalid_Argument("DL_Group unknown PrimeType"); } @@ -323,22 +376,23 @@ * DL_Group Constructor */ DL_Group::DL_Group(RandomNumberGenerator& rng, const std::vector& seed, size_t pbits, size_t qbits) { - BigInt p, q; + BigInt p; + BigInt q; if(!generate_dsa_primes(rng, p, q, pbits, qbits, seed)) { throw Invalid_Argument("DL_Group: The seed given does not generate a DSA group"); } - BigInt g = make_dsa_generator(p, q); + const BigInt g = make_dsa_generator(p, q); - m_data = std::make_shared(p, q, g, DL_Group_Source::RandomlyGenerated); + m_data = DL_Group_Data::create(p, q, g, DL_Group_Source::RandomlyGenerated); } /* * DL_Group Constructor */ DL_Group::DL_Group(const BigInt& p, const BigInt& g) { - m_data = std::make_shared(p, g, DL_Group_Source::ExternalSource); + m_data = DL_Group_Data::create(p, g, DL_Group_Source::ExternalSource); } /* @@ -346,9 +400,9 @@ */ DL_Group::DL_Group(const BigInt& p, const BigInt& q, const BigInt& g) { if(q.is_zero()) { - m_data = std::make_shared(p, g, DL_Group_Source::ExternalSource); + m_data = DL_Group_Data::create(p, g, DL_Group_Source::ExternalSource); } else { - m_data = std::make_shared(p, q, g, DL_Group_Source::ExternalSource); + m_data = DL_Group_Data::create(p, q, g, DL_Group_Source::ExternalSource); } } @@ -368,7 +422,7 @@ return false; } - if(q.is_zero() == false) { + if(!q.is_zero()) { if(data().power_b_p_vartime(y, q) != 1) { return false; } @@ -480,7 +534,7 @@ return data().q(); } -std::shared_ptr DL_Group::monty_params_p() const { +const Montgomery_Params& DL_Group::_monty_params_p() const { return data().monty_params_p(); } @@ -527,7 +581,7 @@ return data().reducer_mod_p().multiply(x, y); } -const Modular_Reducer& DL_Group::_reducer_mod_p() const { +const Barrett_Reduction& DL_Group::_reducer_mod_p() const { return data().reducer_mod_p(); } @@ -618,8 +672,8 @@ } } -DL_Group::DL_Group(const uint8_t ber[], size_t ber_len, DL_Group_Format format) { - m_data = BER_decode_DL_group(ber, ber_len, format, DL_Group_Source::ExternalSource); +DL_Group::DL_Group(std::span der, DL_Group_Format format) { + m_data = DER_decode_DL_group(der, format, DL_Group_Source::ExternalSource); } } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/dl_group/dl_group.h botan3-3.12.0+dfsg/src/lib/pubkey/dl_group/dl_group.h --- botan3-3.7.1+dfsg/src/lib/pubkey/dl_group/dl_group.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/dl_group/dl_group.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,15 +9,16 @@ #define BOTAN_DL_PARAM_H_ #include +#include #include namespace Botan { -class Modular_Reducer; +class Barrett_Reduction; class Montgomery_Params; class DL_Group_Data; -enum class DL_Group_Source { +enum class DL_Group_Source : uint8_t { Builtin, RandomlyGenerated, ExternalSource, @@ -26,10 +27,10 @@ /** * The DL group encoding format variants. */ -enum class DL_Group_Format { - ANSI_X9_42, - ANSI_X9_57, - PKCS_3, +enum class DL_Group_Format : uint8_t { + ANSI_X9_42 = 0, + ANSI_X9_57 = 1, + PKCS_3 = 2, DSA_PARAMETERS = ANSI_X9_57, DH_PARAMETERS = ANSI_X9_42, @@ -47,7 +48,7 @@ /** * Determine the prime creation for DL groups. */ - enum PrimeType { Strong, Prime_Subgroup, DSA_Kosherizer }; + enum PrimeType : uint8_t /* NOLINT(*-use-enum-class) */ { Strong, Prime_Subgroup, DSA_Kosherizer }; using Format = DL_Group_Format; @@ -125,16 +126,14 @@ DL_Group(const BigInt& p, const BigInt& q, const BigInt& g); /** - * Decode a BER-encoded DL group param + * Decode a DER-encoded DL group param */ - DL_Group(const uint8_t ber[], size_t ber_len, DL_Group_Format format); + DL_Group(const uint8_t der[], size_t der_len, DL_Group_Format format) : DL_Group({der, der_len}, format) {} /** - * Decode a BER-encoded DL group param + * Decode a DER-encoded DL group param */ - template - DL_Group(const std::vector& ber, DL_Group_Format format) : - DL_Group(ber.data(), ber.size(), format) {} + DL_Group(std::span der, DL_Group_Format format); /** * Get the prime p. @@ -294,15 +293,12 @@ /** * Multi-exponentiate * Return (g^x * y^z) % p + * + * @warning this function is variable time and should not be used with secret inputs */ BigInt multi_exponentiate(const BigInt& x, const BigInt& y, const BigInt& z) const; /** - * Return parameters for Montgomery reduction/exponentiation mod p - */ - std::shared_ptr monty_params_p() const; - - /** * Return the size of p in bits * Same as get_p().bits() */ @@ -354,15 +350,15 @@ size_t estimated_strength() const; /** - * Decode a DER/BER encoded group into this instance. - * @param ber a vector containing the DER/BER encoded group + * Decode a DER encoded group into this instance. + * @param der a vector containing the DER encoded group * @param format the format of the encoded group * * @warning avoid this. Instead use the DL_Group constructor */ - BOTAN_DEPRECATED("Use DL_Group constructor taking BER encoding") - void BER_decode(const std::vector& ber, DL_Group_Format format) { - *this = DL_Group(ber, format); + BOTAN_DEPRECATED("Use DL_Group constructor taking DER encoding") + void BER_decode(const std::vector& der, DL_Group_Format format) { + *this = DL_Group(der, format); } DL_Group_Source source() const; @@ -373,20 +369,26 @@ */ static std::shared_ptr DL_group_info(std::string_view name); + /** + * Return parameters for Montgomery reduction/exponentiation mod p + * + * For internal use only + */ + const Montgomery_Params& _monty_params_p() const; + /* * For internal use only */ - const Modular_Reducer& _reducer_mod_p() const; + const Barrett_Reduction& _reducer_mod_p() const; private: - DL_Group(std::shared_ptr data) : m_data(std::move(data)) {} + explicit DL_Group(std::shared_ptr data) : m_data(std::move(data)) {} static std::shared_ptr load_DL_group_info(const char* p_str, const char* q_str, const char* g_str); static std::shared_ptr load_DL_group_info(const char* p_str, const char* g_str); - static std::shared_ptr BER_decode_DL_group(const uint8_t data[], - size_t data_len, + static std::shared_ptr DER_decode_DL_group(std::span data, DL_Group_Format format, DL_Group_Source source); diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/dlies/dlies.cpp botan3-3.12.0+dfsg/src/lib/pubkey/dlies/dlies.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/dlies/dlies.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/dlies/dlies.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,9 +7,10 @@ */ #include +#include +#include #include -#include -#include +#include namespace Botan { @@ -27,15 +28,14 @@ size_t cipher_key_len, std::unique_ptr mac, size_t mac_key_length) : - m_other_pub_key(), + m_own_pub_key(own_priv_key.public_value()), m_ka(own_priv_key, rng, "Raw"), m_kdf(std::move(kdf)), m_cipher(std::move(cipher)), m_cipher_key_len(cipher_key_len), m_mac(std::move(mac)), - m_mac_keylen(mac_key_length), - m_iv() { + m_mac_keylen(mac_key_length) { BOTAN_ASSERT_NONNULL(m_kdf); BOTAN_ASSERT_NONNULL(m_mac); } @@ -60,7 +60,7 @@ const size_t cipher_key_len = m_cipher ? m_cipher_key_len : length; if(m_cipher) { - SymmetricKey enc_key(secret_keys.data(), cipher_key_len); + const SymmetricKey enc_key(secret_keys.data(), cipher_key_len); m_cipher->set_key(enc_key); if(m_iv.empty() && !m_cipher->valid_nonce_length(m_iv.size())) { @@ -106,8 +106,7 @@ m_cipher(std::move(cipher)), m_cipher_key_len(cipher_key_len), m_mac(std::move(mac)), - m_mac_keylen(mac_key_length), - m_iv() { + m_mac_keylen(mac_key_length) { BOTAN_ASSERT_NONNULL(m_kdf); BOTAN_ASSERT_NONNULL(m_mac); } @@ -137,7 +136,7 @@ const SymmetricKey secret_value = m_ka.derive_key(0, other_pub_key); const size_t ciphertext_len = length - m_pub_key_size - m_mac->output_length(); - size_t cipher_key_len = m_cipher ? m_cipher_key_len : ciphertext_len; + const size_t cipher_key_len = m_cipher ? m_cipher_key_len : ciphertext_len; // derive secret key from secret value const size_t required_key_length = cipher_key_len + m_mac_keylen; @@ -154,15 +153,15 @@ secure_vector calculated_tag = m_mac->process(ciphertext); // calculated tag == received tag ? - secure_vector tag(msg + m_pub_key_size + ciphertext_len, - msg + m_pub_key_size + ciphertext_len + m_mac->output_length()); - valid_mask = CT::is_equal(tag.data(), calculated_tag.data(), tag.size()).value(); + const std::span tag(msg + m_pub_key_size + ciphertext_len, m_mac->output_length()); + + valid_mask = CT::is_equal(tag, calculated_tag).value(); // decrypt if(m_cipher) { - if(valid_mask) { - SymmetricKey dec_key(secret_keys.data(), cipher_key_len); + if(valid_mask == 0xFF) { + const SymmetricKey dec_key(secret_keys.data(), cipher_key_len); m_cipher->set_key(dec_key); try { diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/dlies/dlies.h botan3-3.12.0+dfsg/src/lib/pubkey/dlies/dlies.h --- botan3-3.7.1+dfsg/src/lib/pubkey/dlies/dlies.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/dlies/dlies.h 2026-05-07 01:38:28.000000000 +0000 @@ -69,7 +69,7 @@ inline void set_initialization_vector(const InitializationVector& iv) { m_iv = iv; } private: - std::vector enc(const uint8_t[], size_t, RandomNumberGenerator&) const override; + std::vector enc(const uint8_t in[], size_t length, RandomNumberGenerator& rng) const override; size_t maximum_input_size() const override; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/dsa/dsa.cpp botan3-3.12.0+dfsg/src/lib/pubkey/dsa/dsa.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/dsa/dsa.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/dsa/dsa.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,6 +8,8 @@ #include +#include +#include #include #include #include @@ -125,10 +127,10 @@ class DSA_Signature_Operation final : public PK_Ops::Signature_with_Hash { public: DSA_Signature_Operation(const std::shared_ptr& key, - std::string_view emsa, + std::string_view hash_fn, RandomNumberGenerator& rng) : - PK_Ops::Signature_with_Hash(emsa), m_key(key) { - m_b = BigInt::random_integer(rng, 2, m_key->group().get_q()); + PK_Ops::Signature_with_Hash(hash_fn), m_key(key) { + m_b = BigInt::random_integer(rng, BigInt::from_s32(2), m_key->group().get_q()); m_b_inv = m_key->group().inverse_mod_q(m_b); } @@ -166,7 +168,7 @@ const BigInt k = BigInt::random_integer(rng, 1, q); #endif - const BigInt k_inv = group.inverse_mod_q(group.mod_q(m_b * k)) * m_b; + const BigInt k_inv = group.multiply_mod_q(group.inverse_mod_q(group.mod_q(m_b * k)), m_b); /* * It may not be strictly necessary for the reduction (g^k mod p) mod q to be @@ -195,7 +197,12 @@ throw Internal_Error("Computed zero r/s during DSA signature"); } - return unlock(BigInt::encode_fixed_length_int_pair(r, s, q.bytes())); + const size_t q_bytes = q.bytes(); + std::vector sig(2 * q_bytes); + BufferStuffer stuffer(sig); + r.serialize_to(stuffer.next(q_bytes)); + s.serialize_to(stuffer.next(q_bytes)); + return sig; } /** @@ -203,8 +210,8 @@ */ class DSA_Verification_Operation final : public PK_Ops::Verification_with_Hash { public: - DSA_Verification_Operation(const std::shared_ptr& key, std::string_view emsa) : - PK_Ops::Verification_with_Hash(emsa), m_key(key) {} + DSA_Verification_Operation(const std::shared_ptr& key, std::string_view hash_fn) : + PK_Ops::Verification_with_Hash(hash_fn), m_key(key) {} DSA_Verification_Operation(const std::shared_ptr& key, const AlgorithmIdentifier& alg_id) : PK_Ops::Verification_with_Hash(alg_id, "DSA"), m_key(key) {} @@ -225,7 +232,7 @@ return false; } - BigInt r(sig.first(q_bytes)); + const BigInt r(sig.first(q_bytes)); BigInt s(sig.last(q_bytes)); if(r == 0 || r >= q || s == 0 || s >= q) { diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/dsa/dsa.h botan3-3.12.0+dfsg/src/lib/pubkey/dsa/dsa.h --- botan3-3.7.1+dfsg/src/lib/pubkey/dsa/dsa.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/dsa/dsa.h 2026-05-07 01:38:28.000000000 +0000 @@ -70,7 +70,7 @@ DSA_PublicKey() = default; - DSA_PublicKey(std::shared_ptr key) : m_public_key(std::move(key)) {} + explicit DSA_PublicKey(std::shared_ptr key) : m_public_key(std::move(key)) {} std::shared_ptr m_public_key; }; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/ec_apoint.cpp botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/ec_apoint.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/ec_apoint.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/ec_apoint.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,6 +9,7 @@ #include #include #include +#include namespace Botan { @@ -82,18 +83,18 @@ EC_AffinePoint EC_AffinePoint::generator(const EC_Group& group) { // TODO it would be nice to improve this (pcurves supports returning generator directly) - try { - return EC_AffinePoint::from_bigint_xy(group, group.get_g_x(), group.get_g_y()).value(); - } catch(...) { + if(auto g = EC_AffinePoint::from_bigint_xy(group, group.get_g_x(), group.get_g_y())) { + return *g; + } else { throw Internal_Error("EC_AffinePoint::generator curve rejected generator"); } } std::optional EC_AffinePoint::from_bigint_xy(const EC_Group& group, const BigInt& x, const BigInt& y) { - if(x.is_negative() || x >= group.get_p()) { + if(x.signum() < 0 || x >= group.get_p()) { return {}; } - if(y.is_negative() || y >= group.get_p()) { + if(y.signum() < 0 || y >= group.get_p()) { return {}; } @@ -122,6 +123,13 @@ return EC_AffinePoint(std::move(pt)); } +EC_AffinePoint EC_AffinePoint::hash_to_curve_ro(const EC_Group& group, + std::string_view hash_fn, + std::span input, + std::string_view domain_sep) { + return EC_AffinePoint::hash_to_curve_ro(group, hash_fn, input, as_span_of_bytes(domain_sep)); +} + EC_AffinePoint EC_AffinePoint::hash_to_curve_nu(const EC_Group& group, std::string_view hash_fn, std::span input, @@ -130,6 +138,13 @@ return EC_AffinePoint(std::move(pt)); } +EC_AffinePoint EC_AffinePoint::hash_to_curve_nu(const EC_Group& group, + std::string_view hash_fn, + std::span input, + std::string_view domain_sep) { + return EC_AffinePoint::hash_to_curve_nu(group, hash_fn, input, as_span_of_bytes(domain_sep)); +} + EC_AffinePoint::~EC_AffinePoint() = default; std::optional EC_AffinePoint::deserialize(const EC_Group& group, std::span bytes) { @@ -140,19 +155,17 @@ } } -EC_AffinePoint EC_AffinePoint::g_mul(const EC_Scalar& scalar, RandomNumberGenerator& rng, std::vector& ws) { - auto pt = scalar._inner().group()->point_g_mul(scalar.inner(), rng, ws); +EC_AffinePoint EC_AffinePoint::g_mul(const EC_Scalar& scalar, RandomNumberGenerator& rng) { + auto pt = scalar._inner().group()->point_g_mul(scalar.inner(), rng); return EC_AffinePoint(std::move(pt)); } -EC_AffinePoint EC_AffinePoint::mul(const EC_Scalar& scalar, RandomNumberGenerator& rng, std::vector& ws) const { - return EC_AffinePoint(inner().mul(scalar._inner(), rng, ws)); +EC_AffinePoint EC_AffinePoint::mul(const EC_Scalar& scalar, RandomNumberGenerator& rng) const { + return EC_AffinePoint(inner().mul(scalar._inner(), rng)); } -secure_vector EC_AffinePoint::mul_x_only(const EC_Scalar& scalar, - RandomNumberGenerator& rng, - std::vector& ws) const { - return inner().mul_x_only(scalar._inner(), rng, ws); +secure_vector EC_AffinePoint::mul_x_only(const EC_Scalar& scalar, RandomNumberGenerator& rng) const { + return inner().mul_x_only(scalar._inner(), rng); } std::optional EC_AffinePoint::mul_px_qy(const EC_AffinePoint& p, diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/ec_apoint.h botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/ec_apoint.h --- botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/ec_apoint.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/ec_apoint.h 2026-05-07 01:38:28.000000000 +0000 @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -32,7 +33,7 @@ /// Elliptic Curve Point in Affine Representation /// -class BOTAN_UNSTABLE_API EC_AffinePoint final { +class BOTAN_PUBLIC_API(3, 6) EC_AffinePoint final { public: /// Point deserialization. Throws if wrong length or not a valid point /// @@ -51,9 +52,7 @@ static std::optional from_bigint_xy(const EC_Group& group, const BigInt& x, const BigInt& y); /// Multiply by the group generator returning a complete point - /// - /// Workspace argument is transitional - static EC_AffinePoint g_mul(const EC_Scalar& scalar, RandomNumberGenerator& rng, std::vector& ws); + static EC_AffinePoint g_mul(const EC_Scalar& scalar, RandomNumberGenerator& rng); /// Return the identity element static EC_AffinePoint identity(const EC_Group& group); @@ -69,6 +68,14 @@ std::span input, std::span domain_sep); + /// Hash to curve (RFC 9380), random oracle variant + /// + /// Only supported for specific groups + static EC_AffinePoint hash_to_curve_ro(const EC_Group& group, + std::string_view hash_fn, + std::span input, + std::string_view domain_sep); + /// Hash to curve (RFC 9380), non uniform variant /// /// Only supported for specific groups @@ -77,17 +84,19 @@ std::span input, std::span domain_sep); - /// Multiply a point by a scalar returning a complete point + /// Hash to curve (RFC 9380), non uniform variant /// - /// Workspace argument is transitional - EC_AffinePoint mul(const EC_Scalar& scalar, RandomNumberGenerator& rng, std::vector& ws) const; + /// Only supported for specific groups + static EC_AffinePoint hash_to_curve_nu(const EC_Group& group, + std::string_view hash_fn, + std::span input, + std::string_view domain_sep); + + /// Multiply a point by a scalar returning a complete point + EC_AffinePoint mul(const EC_Scalar& scalar, RandomNumberGenerator& rng) const; /// Multiply a point by a scalar, returning the byte encoding of the x coordinate only - /// - /// Workspace argument is transitional - secure_vector mul_x_only(const EC_Scalar& scalar, - RandomNumberGenerator& rng, - std::vector& ws) const; + secure_vector mul_x_only(const EC_Scalar& scalar, RandomNumberGenerator& rng) const; /// Compute 2-ary multiscalar multiplication - p*x + q*y /// @@ -107,7 +116,7 @@ /// field inversion. This can be sufficient when implementing protocols /// that just need to perform a few additions. /// - /// In the future a cooresponding EC_ProjectivePoint type may be added + /// In the future a corresponding EC_ProjectivePoint type may be added /// which would avoid the expensive affine conversions EC_AffinePoint add(const EC_AffinePoint& q) const; @@ -232,6 +241,23 @@ EC_Point to_legacy_point() const; #endif + BOTAN_DEPRECATED("Use version without workspace arg") + static EC_AffinePoint g_mul(const EC_Scalar& scalar, RandomNumberGenerator& rng, std::vector& /*ws*/) { + return EC_AffinePoint::g_mul(scalar, rng); + } + + BOTAN_DEPRECATED("Use version without workspace arg") + EC_AffinePoint mul(const EC_Scalar& scalar, RandomNumberGenerator& rng, std::vector& /*ws*/) const { + return this->mul(scalar, rng); + } + + /// Multiply a point by a scalar, returning the byte encoding of the x coordinate only + secure_vector mul_x_only(const EC_Scalar& scalar, + RandomNumberGenerator& rng, + std::vector& /*ws*/) const { + return this->mul_x_only(scalar, rng); + } + ~EC_AffinePoint(); const EC_AffinePoint_Data& _inner() const { return inner(); } @@ -243,7 +269,7 @@ private: friend class EC_Mul2Table; - EC_AffinePoint(std::unique_ptr point); + explicit EC_AffinePoint(std::unique_ptr point); const EC_AffinePoint_Data& inner() const { return *m_point; } diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/ec_group.cpp botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/ec_group.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/ec_group.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/ec_group.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -15,8 +15,8 @@ #include #include #include -#include #include +#include #include #include #include @@ -29,14 +29,30 @@ EC_Group_Data_Map() = default; size_t clear() { - lock_guard_type lock(m_mutex); - size_t count = m_registered_curves.size(); + const lock_guard_type lock(m_mutex); + const size_t count = m_registered_curves.size(); m_registered_curves.clear(); return count; } + bool unregister(const OID& oid) { + // TODO(Botan4) + if(oid.empty()) { + throw Invalid_Argument("OID must not be empty"); + } + + const lock_guard_type lock(m_mutex); + for(size_t i = 0; i < m_registered_curves.size(); i++) { + if(m_registered_curves[i]->oid() == oid) { + m_registered_curves.erase(m_registered_curves.begin() + i); + return true; + } + } + return false; + } + std::shared_ptr lookup(const OID& oid) { - lock_guard_type lock(m_mutex); + const lock_guard_type lock(m_mutex); for(auto i : m_registered_curves) { if(i->oid() == oid) { @@ -48,13 +64,6 @@ std::shared_ptr data = EC_Group::EC_group_info(oid); if(data) { - for(auto curve : m_registered_curves) { - if(curve->oid().empty() == true && curve->params_match(*data)) { - curve->set_oid(oid); - return curve; - } - } - m_registered_curves.push_back(data); return data; } @@ -72,97 +81,139 @@ const BigInt& cofactor, const OID& oid, EC_Group_Source source) { - lock_guard_type lock(m_mutex); - - for(auto i : m_registered_curves) { - /* - * The params may be the same but you are trying to register under a - * different OID than the one we are using, so using a different - * group, since EC_Group's model assumes a single OID per group. - */ - if(!oid.empty() && !i->oid().empty() && i->oid() != oid) { - continue; - } + BOTAN_ASSERT_NOMSG(oid.has_value()); - const bool same_oid = !oid.empty() && i->oid() == oid; - const bool same_params = i->params_match(p, a, b, g_x, g_y, order, cofactor); + const lock_guard_type lock(m_mutex); - /* - * If the params and OID are the same then we are done, just return - * the already registered curve obj. - */ - if(same_params && same_oid) { - return i; - } + for(auto i : m_registered_curves) { + if(i->oid() == oid) { + /* + * If both OID and params are the same then we are done, just return + * the already registered curve obj. + * + * First verify that the params match, to catch an application + * that is attempting to register a EC_Group under the same OID as + * another group currently in use + */ + if(!i->params_match(p, a, b, g_x, g_y, order, cofactor)) { + throw Invalid_Argument("Attempting to register a curve using OID " + oid.to_string() + + " but a distinct curve is already registered using that OID"); + } - /* - * If same params and the new OID is empty, then that's ok too - */ - if(same_params && oid.empty()) { return i; } /* - * Check for someone trying to reuse an already in-use OID - */ - if(same_oid && !same_params) { - throw Invalid_Argument("Attempting to register a curve using OID " + oid.to_string() + - " but a distinct curve is already registered using that OID"); - } - - /* * If the same curve was previously created without an OID but is now * being registered again using an OID, save that OID. + * + * TODO(Botan4) remove this block; this situation won't be possible since + * we will require all groups to have an OID */ - if(same_params && i->oid().empty() && !oid.empty()) { + if(i->oid().empty() && i->params_match(p, a, b, g_x, g_y, order, cofactor)) { i->set_oid(oid); return i; } } /* - Not found in current list, so we need to create a new entry - - If an OID is set, try to look up relative our static tables to detect a duplicate - registration under an OID + * Not found in current list, so we need to create a new entry */ - - auto new_group = EC_Group_Data::create(p, a, b, g_x, g_y, order, cofactor, oid, source); - - if(oid.has_value()) { - std::shared_ptr data = EC_Group::EC_group_info(oid); - if(data != nullptr && !new_group->params_match(*data)) { - throw Invalid_Argument("Attempting to register an EC group under OID of hardcoded group"); - } - } else { - // Here try to use the order as a hint to look up the group id, to identify common groups - const OID oid_from_store = EC_Group::EC_group_identity_from_order(order); - if(oid_from_store.has_value()) { - std::shared_ptr data = EC_Group::EC_group_info(oid_from_store); - + auto new_group = [&] { + if(auto g = EC_Group::EC_group_info(oid); g != nullptr) { /* - If EC_group_identity_from_order returned an OID then looking up that OID - must always return a result. + * This turned out to be the OID of one of the builtin groups. Verify + * that all of the provided parameters match that builtin group. */ - BOTAN_ASSERT_NOMSG(data != nullptr); + BOTAN_ARG_CHECK(g->params_match(p, a, b, g_x, g_y, order, cofactor), + "Attempting to register an EC group under OID of hardcoded group"); + return g; + } else { /* - It is possible (if unlikely) that someone is registering another group - that happens to have an order equal to that of a well known group - - so verify all values before assigning the OID. + * This path is taken for an application registering a new EC_Group with an OID specified */ - if(new_group->params_match(*data)) { - new_group->set_oid(oid_from_store); - } + return EC_Group_Data::create(p, a, b, g_x, g_y, order, cofactor, oid, source); + } + }(); + + m_registered_curves.push_back(new_group); + return new_group; + } + + std::shared_ptr lookup_from_params(const BigInt& p, + const BigInt& a, + const BigInt& b, + std::span base_pt, + const BigInt& order, + const BigInt& cofactor) { + const lock_guard_type lock(m_mutex); + + for(auto i : m_registered_curves) { + if(i->params_match(p, a, b, base_pt, order, cofactor)) { + return i; + } + } + + // Try to use the order as a hint to look up the group id + const OID oid_from_order = EC_Group::EC_group_identity_from_order(order); + if(oid_from_order.has_value()) { + auto new_group = EC_Group::EC_group_info(oid_from_order); + + // Have to check all params in the (unlikely/malicious) event of an order collision + if(new_group && new_group->params_match(p, a, b, base_pt, order, cofactor)) { + m_registered_curves.push_back(new_group); + return new_group; + } + } + + return {}; + } + + // TODO(Botan4) this entire function can be removed since OIDs will be required + std::shared_ptr lookup_or_create_without_oid(const BigInt& p, + const BigInt& a, + const BigInt& b, + const BigInt& g_x, + const BigInt& g_y, + const BigInt& order, + const BigInt& cofactor, + EC_Group_Source source) { + const lock_guard_type lock(m_mutex); + + for(auto i : m_registered_curves) { + if(i->params_match(p, a, b, g_x, g_y, order, cofactor)) { + return i; } } + // Try to use the order as a hint to look up the group id + const OID oid_from_order = EC_Group::EC_group_identity_from_order(order); + if(oid_from_order.has_value()) { + auto new_group = EC_Group::EC_group_info(oid_from_order); + + // Have to check all params in the (unlikely/malicious) event of an order collision + if(new_group && new_group->params_match(p, a, b, g_x, g_y, order, cofactor)) { + m_registered_curves.push_back(new_group); + return new_group; + } + } + + /* + * At this point we have failed to identify the group; it is not any of + * the builtin values, nor is it a group that the user had previously + * registered explicitly. We create the group data without an OID. + * + * TODO(Botan4) remove this; throw an exception instead + */ + auto new_group = EC_Group_Data::create(p, a, b, g_x, g_y, order, cofactor, OID(), source); m_registered_curves.push_back(new_group); return new_group; } private: mutex_type m_mutex; + // TODO(Botan4): Once OID is required we could make this into a map std::vector> m_registered_curves; }; @@ -173,7 +224,7 @@ * which ensures that its destructor runs after ~g_ec_data is complete. */ - static Allocator_Initializer g_init_allocator; + static const Allocator_Initializer g_init_allocator; static EC_Group_Data_Map g_ec_data; return g_ec_data; } @@ -191,6 +242,8 @@ const char* g_y_str, const char* order_str, const OID& oid) { + BOTAN_ARG_CHECK(oid.has_value(), "EC_Group::load_EC_group_info OID must be set"); + const BigInt p(p_str); const BigInt a(a_str); const BigInt b(b_str); @@ -203,15 +256,15 @@ } //static -std::pair, bool> EC_Group::BER_decode_EC_group(std::span bits, +std::pair, bool> EC_Group::DER_decode_EC_group(std::span der, EC_Group_Source source) { - BER_Decoder ber(bits); + BER_Decoder dec(der, BER_Decoder::Limits::DER()); - auto next_obj_type = ber.peek_next_object().type_tag(); + auto next_obj_type = dec.peek_next_object().type_tag(); if(next_obj_type == ASN1_Type::ObjectId) { OID oid; - ber.decode(oid); + dec.decode(oid); auto data = ec_group_data().lookup(oid); if(!data) { @@ -220,20 +273,24 @@ return std::make_pair(data, false); } else if(next_obj_type == ASN1_Type::Sequence) { - BigInt p, a, b, order, cofactor; + BigInt p; + BigInt a; + BigInt b; + BigInt order; + BigInt cofactor; std::vector base_pt; std::vector seed; - ber.start_sequence() + dec.start_sequence() .decode_and_check(1, "Unknown ECC param version code") .start_sequence() - .decode_and_check(OID("1.2.840.10045.1.1"), "Only prime ECC fields supported") + .decode_and_check(OID({1, 2, 840, 10045, 1, 1}), "Only prime ECC fields supported") .decode(p) .end_cons() .start_sequence() .decode_octet_string_bigint(a) .decode_octet_string_bigint(b) - .decode_optional_string(seed, ASN1_Type::BitString, ASN1_Type::BitString) + .decode_optional_string(seed, ASN1_Type::BitString, ASN1_Type::BitString, ASN1_Class::Universal) .end_cons() .decode(base_pt, ASN1_Type::OctetString) .decode(order) @@ -241,34 +298,47 @@ .end_cons() .verify_end(); - if(p.bits() < 112 || p.bits() > 521 || p.is_negative()) { - throw Decoding_Error("ECC p parameter is invalid size"); + // TODO(Botan4) Require cofactor == 1 + if(cofactor <= 0 || cofactor >= 16) { + throw Decoding_Error("Invalid ECC cofactor parameter"); } - auto mod_p = Modular_Reducer::for_public_modulus(p); - if(!is_bailie_psw_probable_prime(p, mod_p)) { - throw Decoding_Error("ECC p parameter is not a prime"); + if(p.bits() < 112 || p.bits() > 521 || p.signum() < 0) { + throw Decoding_Error("ECC p parameter is invalid size"); } - if(a.is_negative() || a >= p) { + // A can be zero + if(a.signum() < 0 || a >= p) { throw Decoding_Error("Invalid ECC a parameter"); } - if(b <= 0 || b >= p) { + // B must be > 0 + if(b.signum() <= 0 || b >= p) { throw Decoding_Error("Invalid ECC b parameter"); } - if(order.is_negative() || order.is_zero() || order >= 2 * p) { + if(order.signum() <= 0 || order >= 2 * p) { throw Decoding_Error("Invalid ECC group order"); } - auto mod_order = Modular_Reducer::for_public_modulus(order); - if(!is_bailie_psw_probable_prime(order, mod_order)) { - throw Decoding_Error("Invalid ECC order parameter"); + if(auto data = ec_group_data().lookup_from_params(p, a, b, base_pt, order, cofactor)) { + return std::make_pair(data, true); } - if(cofactor <= 0 || cofactor >= 16) { - throw Decoding_Error("Invalid ECC cofactor parameter"); + /* + TODO(Botan4) the remaining code is used only to handle the case of decoding an EC_Group + which is neither a builtin group nor a group that was registered by the application. + It can all be removed and replaced with a throw + */ + + auto mod_p = Barrett_Reduction::for_public_modulus(p); + if(!is_bailie_psw_probable_prime(p, mod_p)) { + throw Decoding_Error("ECC p parameter is not a prime"); + } + + auto mod_order = Barrett_Reduction::for_public_modulus(order); + if(!is_bailie_psw_probable_prime(order, mod_order)) { + throw Decoding_Error("Invalid ECC order parameter"); } const size_t p_bytes = p.bytes(); @@ -280,14 +350,15 @@ const uint8_t hdr = base_pt[0]; if(hdr == 0x04 && base_pt.size() == 1 + 2 * p_bytes) { - BigInt x = BigInt::decode(&base_pt[1], p_bytes); - BigInt y = BigInt::decode(&base_pt[p_bytes + 1], p_bytes); + const BigInt x = BigInt::from_bytes(std::span{base_pt}.subspan(1, p_bytes)); + const BigInt y = BigInt::from_bytes(std::span{base_pt}.subspan(1 + p_bytes, p_bytes)); if(x < p && y < p) { return std::make_pair(x, y); } } else if((hdr == 0x02 || hdr == 0x03) && base_pt.size() == 1 + p_bytes) { - BigInt x = BigInt::decode(&base_pt[1], p_bytes); + // TODO(Botan4) remove this branch; we won't support compressed points + const BigInt x = BigInt::from_bytes(std::span{base_pt}.subspan(1, p_bytes)); BigInt y = sqrt_modulo_prime(((x * x + a) * x + b) % p, p); if(x < p && y >= 0) { @@ -303,13 +374,20 @@ throw Decoding_Error("Invalid ECC base point encoding"); }(); + // TODO(Botan4) we can remove this check since we'll only accept pre-registered groups auto y2 = mod_p.square(g_y); auto x3_ax_b = mod_p.reduce(mod_p.cube(g_x) + mod_p.multiply(a, g_x) + b); if(y2 != x3_ax_b) { throw Decoding_Error("Invalid ECC base point"); } - auto data = ec_group_data().lookup_or_create(p, a, b, g_x, g_y, order, cofactor, OID(), source); + /* + * Create the group data without registering it in the global map. + * + * Applications that need persistent custom groups should register them + * via the relevant EC_Group constructor + */ + auto data = EC_Group_Data::create(p, a, b, g_x, g_y, order, cofactor, OID(), source); return std::make_pair(data, true); } else if(next_obj_type == ASN1_Type::Null) { throw Decoding_Error("Decoding ImplicitCA ECC parameters is not supported"); @@ -331,6 +409,33 @@ EC_Group::EC_Group(std::shared_ptr&& data) : m_data(std::move(data)) {} //static +bool EC_Group::supports_named_group(std::string_view name) { + if(name.empty()) { + return false; + } + + // Is it one of the groups compiled into the library? + if(EC_Group::known_named_groups().contains(std::string(name))) { + return true; + } + + // Is it a custom group registered by the application? + if(auto oid = OID::from_name(name)) { + try { + if(ec_group_data().lookup(oid.value()) != nullptr) { + return true; + } + } catch(Not_Implemented&) { + // This would be thrown for example if the group is a known curve + // but the relevant module that enables it is not compiled in + } + } + + // Not known + return false; +} + +//static bool EC_Group::supports_application_specific_group() { #if defined(BOTAN_HAS_LEGACY_EC_POINT) || defined(BOTAN_HAS_PCURVES_GENERIC) return true; @@ -340,6 +445,15 @@ } //static +bool EC_Group::supports_application_specific_group_with_cofactor() { +#if defined(BOTAN_HAS_LEGACY_EC_POINT) + return true; +#else + return false; +#endif +} + +//static EC_Group EC_Group::from_OID(const OID& oid) { auto data = ec_group_data().lookup(oid); @@ -378,11 +492,11 @@ } catch(...) {} if(m_data == nullptr) { - if(str.size() > 30 && str.substr(0, 29) == "-----BEGIN EC PARAMETERS-----") { + if(str.size() > 30 && str.starts_with("-----BEGIN EC PARAMETERS-----")) { // OK try it as PEM ... - const auto ber = PEM_Code::decode_check_label(str, "EC PARAMETERS"); + const auto der = PEM_Code::decode_check_label(str, "EC PARAMETERS"); - auto data = BER_decode_EC_group(ber, EC_Group_Source::ExternalSource); + auto data = DER_decode_EC_group(der, EC_Group_Source::ExternalSource); this->m_data = data.first; this->m_explicit_encoding = data.second; } @@ -395,8 +509,8 @@ //static EC_Group EC_Group::from_PEM(std::string_view pem) { - const auto ber = PEM_Code::decode_check_label(pem, "EC PARAMETERS"); - return EC_Group(ber); + const auto der = PEM_Code::decode_check_label(pem, "EC PARAMETERS"); + return EC_Group(der); } EC_Group::EC_Group(const BigInt& p, @@ -407,8 +521,13 @@ const BigInt& order, const BigInt& cofactor, const OID& oid) { - m_data = - ec_group_data().lookup_or_create(p, a, b, base_x, base_y, order, cofactor, oid, EC_Group_Source::ExternalSource); + if(oid.has_value()) { + m_data = ec_group_data().lookup_or_create( + p, a, b, base_x, base_y, order, cofactor, oid, EC_Group_Source::ExternalSource); + } else { + m_data = ec_group_data().lookup_or_create_without_oid( + p, a, b, base_x, base_y, order, cofactor, EC_Group_Source::ExternalSource); + } } EC_Group::EC_Group(const OID& oid, @@ -457,10 +576,10 @@ BOTAN_ARG_CHECK(base_y >= 0 && base_y < p, "EC_Group base_y is invalid"); BOTAN_ARG_CHECK(p.bits() == order.bits(), "EC_Group p and order must have the same number of bits"); - auto mod_p = Modular_Reducer::for_public_modulus(p); + auto mod_p = Barrett_Reduction::for_public_modulus(p); BOTAN_ARG_CHECK(is_bailie_psw_probable_prime(p, mod_p), "EC_Group p is not prime"); - auto mod_order = Modular_Reducer::for_public_modulus(order); + auto mod_order = Barrett_Reduction::for_public_modulus(order); BOTAN_ARG_CHECK(is_bailie_psw_probable_prime(order, mod_order), "EC_Group order is not prime"); // This catches someone "ignoring" a cofactor and just trying to @@ -468,7 +587,8 @@ BOTAN_ARG_CHECK((p - order).abs().bits() <= (p.bits() / 2) + 1, "Hasse bound invalid"); // Check that 4*a^3 + 27*b^2 != 0 - const auto discriminant = mod_p.reduce(mod_p.multiply(4, mod_p.cube(a)) + mod_p.multiply(27, mod_p.square(b))); + const auto discriminant = mod_p.reduce(mod_p.multiply(BigInt::from_s32(4), mod_p.cube(a)) + + mod_p.multiply(BigInt::from_s32(27), mod_p.square(b))); BOTAN_ARG_CHECK(discriminant != 0, "EC_Group discriminant is invalid"); // Check that the generator (base_x,base_y) is on the curve; y^2 = x^3 + a*x + b @@ -476,18 +596,23 @@ auto x3_ax_b = mod_p.reduce(mod_p.cube(base_x) + mod_p.multiply(a, base_x) + b); BOTAN_ARG_CHECK(y2 == x3_ax_b, "EC_Group generator is not on the curve"); - BigInt cofactor(1); + const BigInt cofactor(1); m_data = ec_group_data().lookup_or_create(p, a, b, base_x, base_y, order, cofactor, oid, EC_Group_Source::ExternalSource); } -EC_Group::EC_Group(std::span ber) { - auto data = BER_decode_EC_group(ber, EC_Group_Source::ExternalSource); +EC_Group::EC_Group(std::span der) { + auto data = DER_decode_EC_group(der, EC_Group_Source::ExternalSource); m_data = data.first; m_explicit_encoding = data.second; } +// static +bool EC_Group::unregister(const OID& oid) { + return ec_group_data().unregister(oid); +} + const EC_Group_Data& EC_Group::data() const { if(m_data == nullptr) { throw Invalid_State("EC_Group uninitialized"); @@ -636,8 +761,8 @@ } } -std::string EC_Group::PEM_encode() const { - const std::vector der = DER_encode(EC_Group_Encoding::Explicit); +std::string EC_Group::PEM_encode(EC_Group_Encoding form) const { + const std::vector der = DER_encode(form); return PEM_Code::encode(der, "EC PARAMETERS"); } @@ -691,9 +816,10 @@ } //compute the discriminant: 4*a^3 + 27*b^2 which must be nonzero - auto mod_p = Modular_Reducer::for_public_modulus(p); + auto mod_p = Barrett_Reduction::for_public_modulus(p); - const BigInt discriminant = mod_p.reduce(mod_p.multiply(4, mod_p.cube(a)) + mod_p.multiply(27, mod_p.square(b))); + const BigInt discriminant = mod_p.reduce(mod_p.multiply(BigInt::from_s32(4), mod_p.cube(a)) + + mod_p.multiply(BigInt::from_s32(27), mod_p.square(b))); if(discriminant == 0) { return false; @@ -727,6 +853,10 @@ return true; } +EC_Group::Mul2Table::Mul2Table(EC_Group::Mul2Table&& other) noexcept = default; + +EC_Group::Mul2Table& EC_Group::Mul2Table::operator=(EC_Group::Mul2Table&& other) noexcept = default; + EC_Group::Mul2Table::Mul2Table(const EC_AffinePoint& h) : m_tbl(h._group()->make_mul2_table(h._inner())) {} EC_Group::Mul2Table::~Mul2Table() = default; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/ec_group.h botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/ec_group.h --- botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/ec_group.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/ec_group.h 2026-05-07 01:38:28.000000000 +0000 @@ -26,24 +26,6 @@ namespace Botan { /** -* This enum indicates the method used to encode the EC parameters -* -* @warning All support for explicit or implicit domain encodings -* will be removed in Botan4. Only named curves will be supported. -* -* TODO(Botan4) remove this enum -*/ -enum class EC_Group_Encoding { - Explicit, - ImplicitCA, - NamedCurve, - - EC_DOMPAR_ENC_EXPLICIT = Explicit, - EC_DOMPAR_ENC_IMPLICITCA = ImplicitCA, - EC_DOMPAR_ENC_OID = NamedCurve -}; - -/** * This enum indicates the source of the elliptic curve parameters * in use. * @@ -53,7 +35,7 @@ * ExternalSource means the curve parameters came from either an explicit * curve encoding or an application defined curve. */ -enum class EC_Group_Source { +enum class EC_Group_Source : uint8_t { Builtin, ExternalSource, }; @@ -63,7 +45,7 @@ * * This is returned by EC_Group::engine */ -enum class EC_Group_Engine { +enum class EC_Group_Engine : uint8_t { /// Using per curve implementation; fastest available Optimized, /// A generic implementation that handles many curves in one implementation @@ -175,13 +157,13 @@ const BigInt& order); /** - * Decode a BER encoded ECC domain parameter set - * @param ber the bytes of the BER encoding + * Decode a DER encoded ECC domain parameter set + * @param der the bytes of the DER encoding */ - explicit EC_Group(std::span ber); + explicit EC_Group(std::span der); BOTAN_DEPRECATED("Use EC_Group(std::span)") - EC_Group(const uint8_t ber[], size_t ber_len) : EC_Group(std::span{ber, ber_len}) {} + EC_Group(const uint8_t der[], size_t der_len) : EC_Group(std::span{der, der_len}) {} /** * Create an EC domain by OID (or throw if unknown) @@ -223,6 +205,16 @@ */ BOTAN_DEPRECATED("Deprecated no replacement") EC_Group(); + /** + * Unregister a previously registered group. + * + * Using this is discouraged for normal use. This is only useful or necessary if + * you are registering a very large number of distinct groups, and need to worry about memory constraints. + * + * Returns true if the group was found and unregistered. + */ + static bool unregister(const OID& oid); + ~EC_Group(); EC_Group(const EC_Group&); @@ -250,7 +242,16 @@ static bool supports_application_specific_group(); /** - * Return true if in this build configuration EC_Group::from_name(name) will succeed + * Return true if in this build configuration it is possible to + * register an application specific elliptic curve with a cofactor + * larger than 1. + */ + static bool supports_application_specific_group_with_cofactor(); + + /** + * Return true if EC_Group::from_name(name) should succeed for this name + * either because it is a group compiled into the library or it is a group + * which has already been registered by the application at runtime. */ static bool supports_named_group(std::string_view name); @@ -272,9 +273,11 @@ /** * Return a set of known named EC groups * - * This returns the set of groups for which from_name should succeed - * Note that the set of included groups can vary based on the - * build configuration. + * This returns a set of groups for which from_name should succeed. + * + * Note that the set of included groups can vary based on the build + * configuration, and that this list does not include any groups registered + * by the application at runtime. */ static const std::set& known_named_groups(); @@ -293,10 +296,15 @@ std::vector DER_encode() const; /** - * Return the PEM encoding (always in explicit form) + * Return the PEM encoding * @return string containing PEM data + * + * @warning In Botan4 the form parameter will be removed and only + * namedCurve will be supported + * + * TODO(Botan4) remove the argument */ - std::string PEM_encode() const; + std::string PEM_encode(EC_Group_Encoding form = EC_Group_Encoding::Explicit) const; /** * Return the size of p in bits (same as get_p().bits()) @@ -324,7 +332,7 @@ /** * Create a table for computing g*x + h*y */ - Mul2Table(const EC_AffinePoint& h); + BOTAN_FUTURE_EXPLICIT Mul2Table(const EC_AffinePoint& h); /** * Return the elliptic curve point g*x + h*y @@ -363,6 +371,10 @@ const EC_Scalar& y) const; ~Mul2Table(); + Mul2Table(const Mul2Table& other) = delete; + Mul2Table(Mul2Table&& other) noexcept; + Mul2Table& operator=(const Mul2Table& other) = delete; + Mul2Table& operator=(Mul2Table&& other) noexcept; private: std::unique_ptr m_tbl; @@ -420,19 +432,23 @@ /* * For internal use only - * TODO(Botan4): Add underscore prefix + * TODO(Botan4): Move this to an internal header */ static std::shared_ptr EC_group_info(const OID& oid); /* * For internal use only - * TODO(Botan4): Add underscore prefix + * + * @warning this invalidates pointers and can cause memory corruption. + * This function exists only to be called in tests. + * + * TODO(Botan4): Move this to an internal header */ static size_t clear_registered_curve_data(); /* * For internal use only - * TODO(Botan4): Add underscore prefix + * TODO(Botan4): Move this to an internal header */ static OID EC_group_identity_from_order(const BigInt& order); @@ -488,7 +504,7 @@ auto y = EC_Scalar::from_bigint(*this, y_bn); auto h = EC_AffinePoint(*this, h_pt); - Mul2Table gh_mul(h); + const Mul2Table gh_mul(h); if(auto r = gh_mul.mul2_vartime(x, y)) { return r->to_legacy_point(); @@ -501,14 +517,14 @@ * Blinded point multiplication, attempts resistance to side channels * @param k_bn the scalar * @param rng a random number generator - * @param ws a temp workspace * @return base_point*k */ BOTAN_DEPRECATED("Use EC_AffinePoint and EC_Scalar") - EC_Point - blinded_base_point_multiply(const BigInt& k_bn, RandomNumberGenerator& rng, std::vector& ws) const { + EC_Point blinded_base_point_multiply(const BigInt& k_bn, + RandomNumberGenerator& rng, + std::vector& /*ws*/) const { auto k = EC_Scalar::from_bigint(*this, k_bn); - auto pt = EC_AffinePoint::g_mul(k, rng, ws); + auto pt = EC_AffinePoint::g_mul(k, rng); return pt.to_legacy_point(); } @@ -518,14 +534,14 @@ * * @param k_bn the scalar * @param rng a random number generator - * @param ws a temp workspace * @return x coordinate of base_point*k */ BOTAN_DEPRECATED("Use EC_AffinePoint and EC_Scalar") - BigInt - blinded_base_point_multiply_x(const BigInt& k_bn, RandomNumberGenerator& rng, std::vector& ws) const { + BigInt blinded_base_point_multiply_x(const BigInt& k_bn, + RandomNumberGenerator& rng, + std::vector& /*ws*/) const { auto k = EC_Scalar::from_bigint(*this, k_bn); - return BigInt(EC_AffinePoint::g_mul(k, rng, ws).x_bytes()); + return BigInt(EC_AffinePoint::g_mul(k, rng).x_bytes()); } /** @@ -533,17 +549,16 @@ * @param point input point * @param k_bn the scalar * @param rng a random number generator - * @param ws a temp workspace * @return point*k */ BOTAN_DEPRECATED("Use EC_AffinePoint and EC_Scalar") EC_Point blinded_var_point_multiply(const EC_Point& point, const BigInt& k_bn, RandomNumberGenerator& rng, - std::vector& ws) const { + std::vector& /*ws*/) const { auto k = EC_Scalar::from_bigint(*this, k_bn); auto pt = EC_AffinePoint(*this, point); - return pt.mul(k, rng, ws).to_legacy_point(); + return pt.mul(k, rng).to_legacy_point(); } /** @@ -561,7 +576,7 @@ * @param hash_fn the hash function to use (typically "SHA-256" or "SHA-512") * @param input the input to hash * @param input_len length of input in bytes - * @param domain_sep a domain seperator + * @param domain_sep a domain separator * @param domain_sep_len length of domain_sep in bytes * @param random_oracle if the mapped point must be uniform (use "true" here unless you know what you are doing) @@ -591,7 +606,7 @@ * @param hash_fn the hash function to use (typically "SHA-256" or "SHA-512") * @param input the input to hash * @param input_len length of input in bytes - * @param domain_sep a domain seperator + * @param domain_sep a domain separator * @param random_oracle if the mapped point must be uniform (use "true" here unless you know what you are doing) */ @@ -602,12 +617,11 @@ std::string_view domain_sep, bool random_oracle = true) const { auto inp = std::span{input, input_len}; - auto dst = std::span{reinterpret_cast(domain_sep.data()), domain_sep.size()}; if(random_oracle) { - return EC_AffinePoint::hash_to_curve_ro(*this, hash_fn, inp, dst).to_legacy_point(); + return EC_AffinePoint::hash_to_curve_ro(*this, hash_fn, inp, domain_sep).to_legacy_point(); } else { - return EC_AffinePoint::hash_to_curve_nu(*this, hash_fn, inp, dst).to_legacy_point(); + return EC_AffinePoint::hash_to_curve_nu(*this, hash_fn, inp, domain_sep).to_legacy_point(); } } @@ -703,9 +717,9 @@ private: static EC_Group_Data_Map& ec_group_data(); - EC_Group(std::shared_ptr&& data); + explicit EC_Group(std::shared_ptr&& data); - static std::pair, bool> BER_decode_EC_group(std::span ber, + static std::pair, bool> DER_decode_EC_group(std::span der, EC_Group_Source source); static std::shared_ptr load_EC_group_info(const char* p, diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/ec_inner_data.cpp botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/ec_inner_data.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/ec_inner_data.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/ec_inner_data.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -10,12 +10,17 @@ #include #include #include +#include #if defined(BOTAN_HAS_LEGACY_EC_POINT) #include #include #endif +#if defined(BOTAN_HAS_XMD) + #include +#endif + namespace Botan { EC_Group_Data::~EC_Group_Data() = default; @@ -38,8 +43,8 @@ m_order(order), m_cofactor(cofactor), #if defined(BOTAN_HAS_LEGACY_EC_POINT) - m_mod_field(Modular_Reducer::for_public_modulus(p)), - m_mod_order(Modular_Reducer::for_public_modulus(order)), + m_mod_field(Barrett_Reduction::for_public_modulus(p)), + m_mod_order(Barrett_Reduction::for_public_modulus(order)), m_monty(m_p, m_mod_field), #endif m_oid(oid), @@ -52,18 +57,29 @@ m_has_cofactor(m_cofactor != 1), m_order_is_less_than_p(m_order < p), m_source(source) { + // TODO(Botan4) we can assume/assert the OID is set if(!m_oid.empty()) { DER_Encoder der(m_der_named_curve); der.encode(m_oid); - if(const auto id = PCurve::PrimeOrderCurveId::from_oid(m_oid)) { - m_pcurve = PCurve::PrimeOrderCurve::from_id(*id); - if(m_pcurve) { - m_engine = EC_Group_Engine::Optimized; - } - // still possibly null, if the curve is supported in general but not - // available in the build + const std::string name = m_oid.human_name_or_empty(); + if(!name.empty()) { + // returns nullptr if unknown or not supported + m_pcurve = PCurve::PrimeOrderCurve::for_named_curve(name); + } + if(m_pcurve) { + m_engine = EC_Group_Engine::Optimized; + } + } + + // Try a generic pcurves instance + if(!m_pcurve && !m_has_cofactor) { + m_pcurve = PCurve::PrimeOrderCurve::from_params(p, a, b, g_x, g_y, order); + if(m_pcurve) { + m_engine = EC_Group_Engine::Generic; } + // possibly still null here, if parameters unsuitable or if the + // pcurves_generic module wasn't included in the build } #if defined(BOTAN_HAS_LEGACY_EC_POINT) @@ -114,8 +130,92 @@ const BigInt& g_y, const BigInt& order, const BigInt& cofactor) const { - return (this->p() == p && this->a() == a && this->b() == b && this->order() == order && - this->cofactor() == cofactor && this->g_x() == g_x && this->g_y() == g_y); + if(p != this->p()) { + return false; + } + if(a != this->a()) { + return false; + } + if(b != this->b()) { + return false; + } + if(order != this->order()) { + return false; + } + if(cofactor != this->cofactor()) { + return false; + } + if(g_x != this->g_x()) { + return false; + } + if(g_y != this->g_y()) { + return false; + } + + return true; +} + +bool EC_Group_Data::params_match(const BigInt& p, + const BigInt& a, + const BigInt& b, + std::span base_pt, + const BigInt& order, + const BigInt& cofactor) const { + if(p != this->p()) { + return false; + } + if(a != this->a()) { + return false; + } + if(b != this->b()) { + return false; + } + if(order != this->order()) { + return false; + } + if(cofactor != this->cofactor()) { + return false; + } + + const size_t field_len = this->p_bytes(); + + if(base_pt.size() == 1 + field_len && (base_pt[0] == 0x02 || base_pt[0] == 0x03)) { + // compressed + + const auto g_x = m_g_x.serialize(field_len); + const auto g_y = m_g_y.is_odd(); + + const auto sec1_x = base_pt.subspan(1, field_len); + const bool sec1_y = (base_pt[0] == 0x03); + + if(!std::ranges::equal(sec1_x, g_x)) { + return false; + } + + if(sec1_y != g_y) { + return false; + } + + return true; + } else if(base_pt.size() == 1 + 2 * field_len && base_pt[0] == 0x04) { + const auto g_x = m_g_x.serialize(field_len); + const auto g_y = m_g_y.serialize(field_len); + + const auto sec1_x = base_pt.subspan(1, field_len); + const auto sec1_y = base_pt.subspan(1 + field_len, field_len); + + if(!std::ranges::equal(sec1_x, g_x)) { + return false; + } + + if(!std::ranges::equal(sec1_y, g_y)) { + return false; + } + + return true; + } else { + throw Decoding_Error("Invalid base point encoding in explicit group"); + } } bool EC_Group_Data::params_match(const EC_Group_Data& other) const { @@ -194,18 +294,6 @@ } } -std::unique_ptr EC_Group_Data::scalar_zero() const { - if(m_pcurve) { - return std::make_unique(shared_from_this(), m_pcurve->scalar_zero()); - } else { -#if defined(BOTAN_HAS_LEGACY_EC_POINT) - return std::make_unique(shared_from_this(), BigInt::zero()); -#else - throw Not_Implemented("Legacy EC interfaces disabled in this build configuration"); -#endif - } -} - std::unique_ptr EC_Group_Data::scalar_one() const { if(m_pcurve) { return std::make_unique(shared_from_this(), m_pcurve->scalar_one()); @@ -235,8 +323,7 @@ } std::unique_ptr EC_Group_Data::gk_x_mod_order(const EC_Scalar_Data& scalar, - RandomNumberGenerator& rng, - std::vector& ws) const { + RandomNumberGenerator& rng) const { if(m_pcurve) { const auto& k = EC_Scalar_Data_PC::checked_ref(scalar); auto gk_x_mod_order = m_pcurve->base_point_mul_x_mod_order(k.value(), rng); @@ -245,15 +332,15 @@ #if defined(BOTAN_HAS_LEGACY_EC_POINT) const auto& k = EC_Scalar_Data_BN::checked_ref(scalar); BOTAN_STATE_CHECK(m_base_mult != nullptr); + std::vector ws; const auto pt = m_base_mult->mul(k.value(), rng, m_order, ws); if(pt.is_zero()) { - return scalar_zero(); + return std::make_unique(shared_from_this(), BigInt::zero()); } else { return std::make_unique(shared_from_this(), m_mod_order.reduce(pt.get_affine_x())); } #else - BOTAN_UNUSED(ws); throw Not_Implemented("Legacy EC interfaces disabled in this build configuration"); #endif } @@ -286,31 +373,70 @@ } std::unique_ptr EC_Group_Data::point_deserialize(std::span bytes) const { + // The deprecated "hybrid" point format + // TODO(Botan4) remove this + if(bytes.size() >= 1 + 2 * 4 && (bytes[0] == 0x06 || bytes[0] == 0x07)) { + const bool hdr_y_is_even = bytes[0] == 0x06; + const bool y_is_even = (bytes.back() & 0x01) == 0; + + if(hdr_y_is_even == y_is_even) { + std::vector sec1(bytes.begin(), bytes.end()); + sec1[0] = 0x04; + return this->point_deserialize(sec1); + } + } + try { if(m_pcurve) { if(auto pt = m_pcurve->deserialize_point(bytes)) { return std::make_unique(shared_from_this(), std::move(*pt)); } else { - return nullptr; + return {}; } } else { #if defined(BOTAN_HAS_LEGACY_EC_POINT) - return std::make_unique(shared_from_this(), bytes); + auto pt = Botan::OS2ECP(bytes, m_curve); + return std::make_unique(shared_from_this(), std::move(pt)); #else throw Not_Implemented("Legacy EC interfaces disabled in this build configuration"); #endif } } catch(...) { - return nullptr; + return {}; + } +} + +namespace { + +std::function)> h2c_expand_message(std::string_view hash_fn, + std::span input, + std::span domain_sep) { + /* + * This could be extended to support expand_message_xof or a MHF like Argon2 + */ + + if(hash_fn.starts_with("SHAKE")) { + throw Not_Implemented("Hash to curve currently does not support expand_message_xof"); } + + return [=](std::span uniform_bytes) { +#if defined(BOTAN_HAS_XMD) + expand_message_xmd(hash_fn, uniform_bytes, input, domain_sep); +#else + BOTAN_UNUSED(hash_fn, uniform_bytes, input, domain_sep); + throw Not_Implemented("Hash to curve is not implemented due to XMD being disabled"); +#endif + }; } +} // namespace + std::unique_ptr EC_Group_Data::point_hash_to_curve_ro(std::string_view hash_fn, std::span input, std::span domain_sep) const { if(m_pcurve) { - auto pt = m_pcurve->hash_to_curve_ro(hash_fn, input, domain_sep); - return std::make_unique(shared_from_this(), pt.to_affine()); + auto pt = m_pcurve->hash_to_curve_ro(h2c_expand_message(hash_fn, input, domain_sep)); + return std::make_unique(shared_from_this(), m_pcurve->point_to_affine(pt)); } else { throw Not_Implemented("Hash to curve is not implemented for this curve"); } @@ -320,7 +446,7 @@ std::span input, std::span domain_sep) const { if(m_pcurve) { - auto pt = m_pcurve->hash_to_curve_nu(hash_fn, input, domain_sep); + auto pt = m_pcurve->hash_to_curve_nu(h2c_expand_message(hash_fn, input, domain_sep)); return std::make_unique(shared_from_this(), std::move(pt)); } else { throw Not_Implemented("Hash to curve is not implemented for this curve"); @@ -328,11 +454,10 @@ } std::unique_ptr EC_Group_Data::point_g_mul(const EC_Scalar_Data& scalar, - RandomNumberGenerator& rng, - std::vector& ws) const { + RandomNumberGenerator& rng) const { if(m_pcurve) { const auto& k = EC_Scalar_Data_PC::checked_ref(scalar); - auto pt = m_pcurve->mul_by_g(k.value(), rng).to_affine(); + auto pt = m_pcurve->point_to_affine(m_pcurve->mul_by_g(k.value(), rng)); return std::make_unique(shared_from_this(), std::move(pt)); } else { #if defined(BOTAN_HAS_LEGACY_EC_POINT) @@ -340,10 +465,10 @@ const auto& bn = EC_Scalar_Data_BN::checked_ref(scalar); BOTAN_STATE_CHECK(group->m_base_mult != nullptr); + std::vector ws; auto pt = group->m_base_mult->mul(bn.value(), rng, m_order, ws); return std::make_unique(shared_from_this(), std::move(pt)); #else - BOTAN_UNUSED(ws); throw Not_Implemented("Legacy EC interfaces disabled in this build configuration"); #endif } @@ -362,7 +487,7 @@ rng); if(pt) { - return std::make_unique(shared_from_this(), pt->to_affine()); + return std::make_unique(shared_from_this(), m_pcurve->point_to_affine(*pt)); } else { return nullptr; } @@ -372,8 +497,8 @@ const auto& group = p.group(); // TODO this could be better! - EC_Point_Var_Point_Precompute p_mul(p.to_legacy_point(), rng, ws); - EC_Point_Var_Point_Precompute q_mul(q.to_legacy_point(), rng, ws); + const EC_Point_Var_Point_Precompute p_mul(p.to_legacy_point(), rng, ws); + const EC_Point_Var_Point_Precompute q_mul(q.to_legacy_point(), rng, ws); const auto order = group->order() * group->cofactor(); // See #3800 @@ -397,11 +522,10 @@ std::unique_ptr EC_Group_Data::affine_add(const EC_AffinePoint_Data& p, const EC_AffinePoint_Data& q) const { if(m_pcurve) { - auto pt = m_pcurve->point_add_mixed( - PCurve::PrimeOrderCurve::ProjectivePoint::from_affine(EC_AffinePoint_Data_PC::checked_ref(p).value()), - EC_AffinePoint_Data_PC::checked_ref(q).value()); + auto pt = m_pcurve->point_add(EC_AffinePoint_Data_PC::checked_ref(p).value(), + EC_AffinePoint_Data_PC::checked_ref(q).value()); - return std::make_unique(shared_from_this(), pt.to_affine()); + return std::make_unique(shared_from_this(), m_pcurve->point_to_affine(pt)); } else { #if defined(BOTAN_HAS_LEGACY_EC_POINT) auto pt = p.to_legacy_point() + q.to_legacy_point(); @@ -432,7 +556,7 @@ return std::make_unique(h); } else { #if defined(BOTAN_HAS_LEGACY_EC_POINT) - EC_AffinePoint_Data_BN g(shared_from_this(), this->base_point()); + const EC_AffinePoint_Data_BN g(shared_from_this(), this->base_point()); return std::make_unique(g, h); #else throw Not_Implemented("Legacy EC interfaces disabled in this build configuration"); diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/ec_inner_data.h botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/ec_inner_data.h --- botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/ec_inner_data.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/ec_inner_data.h 2026-05-07 01:38:28.000000000 +0000 @@ -12,12 +12,11 @@ #include #include #include -#include #include #include #if defined(BOTAN_HAS_LEGACY_EC_POINT) - #include + #include #endif namespace Botan { @@ -34,7 +33,7 @@ class EC_Group_Data; -class EC_Scalar_Data { +class EC_Scalar_Data /* NOLINT(*-special-member-functions) */ { public: virtual ~EC_Scalar_Data() = default; @@ -50,6 +49,8 @@ virtual void assign(const EC_Scalar_Data& y) = 0; + virtual void zeroize() = 0; + virtual void square_self() = 0; virtual std::unique_ptr negate() const = 0; @@ -67,7 +68,7 @@ virtual void serialize_to(std::span bytes) const = 0; }; -class EC_AffinePoint_Data { +class EC_AffinePoint_Data /* NOLINT(*-special-member-functions) */ { public: virtual ~EC_AffinePoint_Data() = default; @@ -97,19 +98,16 @@ virtual void serialize_uncompressed_to(std::span bytes) const = 0; virtual std::unique_ptr mul(const EC_Scalar_Data& scalar, - RandomNumberGenerator& rng, - std::vector& ws) const = 0; + RandomNumberGenerator& rng) const = 0; - virtual secure_vector mul_x_only(const EC_Scalar_Data& scalar, - RandomNumberGenerator& rng, - std::vector& ws) const = 0; + virtual secure_vector mul_x_only(const EC_Scalar_Data& scalar, RandomNumberGenerator& rng) const = 0; #if defined(BOTAN_HAS_LEGACY_EC_POINT) virtual EC_Point to_legacy_point() const = 0; #endif }; -class EC_Mul2Table_Data { +class EC_Mul2Table_Data /* NOLINT(*-special-member-functions) */ { public: virtual ~EC_Mul2Table_Data() = default; @@ -139,6 +137,11 @@ ~EC_Group_Data(); + EC_Group_Data(const EC_Group_Data& other) = delete; + EC_Group_Data(EC_Group_Data&& other) = delete; + EC_Group_Data& operator=(const EC_Group_Data& other) = delete; + EC_Group_Data& operator=(EC_Group_Data&& other) = delete; + bool params_match(const BigInt& p, const BigInt& a, const BigInt& b, @@ -147,6 +150,14 @@ const BigInt& order, const BigInt& cofactor) const; + // Like the other params_match but accepting the base point in encoded form + bool params_match(const BigInt& p, + const BigInt& a, + const BigInt& b, + std::span base_pt, + const BigInt& order, + const BigInt& cofactor) const; + bool params_match(const EC_Group_Data& other) const; void set_oid(const OID& oid); @@ -176,7 +187,7 @@ const BigInt& monty_b() const { return m_b_r; } - const Modular_Reducer& mod_order() const { return m_mod_order; } + const Barrett_Reduction& mod_order() const { return m_mod_order; } #endif bool order_is_less_than_p() const { return m_order_is_less_than_p; } @@ -215,7 +226,7 @@ /// If the input is rejected then nullptr is returned std::unique_ptr scalar_deserialize(std::span bytes) const; - /// Scalar from bytes with ECDSA style trunction + /// Scalar from bytes with ECDSA style truncation /// /// This should always succeed std::unique_ptr scalar_from_bytes_with_trunc(std::span bytes) const; @@ -237,13 +248,9 @@ /// This will be in the range [1,n) where n is the group order std::unique_ptr scalar_random(RandomNumberGenerator& rng) const; - std::unique_ptr scalar_zero() const; - std::unique_ptr scalar_one() const; - std::unique_ptr gk_x_mod_order(const EC_Scalar_Data& scalar, - RandomNumberGenerator& rng, - std::vector& ws) const; + std::unique_ptr gk_x_mod_order(const EC_Scalar_Data& scalar, RandomNumberGenerator& rng) const; /// Deserialize a point /// @@ -258,9 +265,7 @@ std::span input, std::span domain_sep) const; - std::unique_ptr point_g_mul(const EC_Scalar_Data& scalar, - RandomNumberGenerator& rng, - std::vector& ws) const; + std::unique_ptr point_g_mul(const EC_Scalar_Data& scalar, RandomNumberGenerator& rng) const; std::unique_ptr mul_px_qy(const EC_AffinePoint_Data& p, const EC_Scalar_Data& x, @@ -315,8 +320,8 @@ CurveGFp m_curve; EC_Point m_base_point; - Modular_Reducer m_mod_field; - Modular_Reducer m_mod_order; + Barrett_Reduction m_mod_field; + Barrett_Reduction m_mod_order; // Montgomery parameters (only used for legacy EC_Point) Montgomery_Params m_monty; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/ec_inner_pc.cpp botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/ec_inner_pc.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/ec_inner_pc.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/ec_inner_pc.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,24 +6,14 @@ #include -namespace Botan { - -namespace { - -PCurve::PrimeOrderCurve::AffinePoint deserialize_pcurve_pt(const PCurve::PrimeOrderCurve& curve, - std::span bytes) { - if(auto pt = curve.deserialize_point(bytes)) { - return *pt; - } else { - throw Decoding_Error("Invalid elliptic curve point encoding"); - } -} +#include +#include -} // namespace +namespace Botan { const EC_Scalar_Data_PC& EC_Scalar_Data_PC::checked_ref(const EC_Scalar_Data& data) { const auto* p = dynamic_cast(&data); - if(!p) { + if(p == nullptr) { throw Invalid_State("Failed conversion to EC_Scalar_Data_PC"); } return *p; @@ -42,44 +32,50 @@ } bool EC_Scalar_Data_PC::is_zero() const { - return this->value().is_zero(); + const auto& pcurve = this->group()->pcurve(); + return pcurve.scalar_is_zero(m_v); } bool EC_Scalar_Data_PC::is_eq(const EC_Scalar_Data& other) const { - return (value() == checked_ref(other).value()); + const auto& pcurve = group()->pcurve(); + return pcurve.scalar_equal(m_v, checked_ref(other).m_v); } void EC_Scalar_Data_PC::assign(const EC_Scalar_Data& other) { m_v = checked_ref(other).value(); } +void EC_Scalar_Data_PC::zeroize() { + m_v._zeroize(); +} + void EC_Scalar_Data_PC::square_self() { // TODO square in place - m_v = m_v.square(); + m_v = m_group->pcurve().scalar_square(m_v); } std::unique_ptr EC_Scalar_Data_PC::negate() const { - return std::make_unique(m_group, m_v.negate()); + return std::make_unique(m_group, m_group->pcurve().scalar_negate(m_v)); } std::unique_ptr EC_Scalar_Data_PC::invert() const { - return std::make_unique(m_group, m_v.invert()); + return std::make_unique(m_group, m_group->pcurve().scalar_invert(m_v)); } std::unique_ptr EC_Scalar_Data_PC::invert_vartime() const { - return std::make_unique(m_group, m_v.invert_vartime()); + return std::make_unique(m_group, m_group->pcurve().scalar_invert_vartime(m_v)); } std::unique_ptr EC_Scalar_Data_PC::add(const EC_Scalar_Data& other) const { - return std::make_unique(m_group, m_v + checked_ref(other).value()); + return std::make_unique(m_group, group()->pcurve().scalar_add(m_v, checked_ref(other).m_v)); } std::unique_ptr EC_Scalar_Data_PC::sub(const EC_Scalar_Data& other) const { - return std::make_unique(m_group, m_v - checked_ref(other).value()); + return std::make_unique(m_group, group()->pcurve().scalar_sub(m_v, checked_ref(other).m_v)); } std::unique_ptr EC_Scalar_Data_PC::mul(const EC_Scalar_Data& other) const { - return std::make_unique(m_group, m_v * checked_ref(other).value()); + return std::make_unique(m_group, group()->pcurve().scalar_mul(m_v, checked_ref(other).m_v)); } void EC_Scalar_Data_PC::serialize_to(std::span bytes) const { @@ -90,24 +86,17 @@ EC_AffinePoint_Data_PC::EC_AffinePoint_Data_PC(std::shared_ptr group, PCurve::PrimeOrderCurve::AffinePoint pt) : m_group(std::move(group)), m_pt(std::move(pt)) { - if(!m_pt.is_identity()) { - m_xy = m_pt.serialize>(); - BOTAN_ASSERT_NOMSG(m_xy.size() == 1 + 2 * field_element_bytes()); - } -} + const auto& pcurve = m_group->pcurve(); -EC_AffinePoint_Data_PC::EC_AffinePoint_Data_PC(std::shared_ptr group, - std::span bytes) : - m_group(std::move(group)), m_pt(deserialize_pcurve_pt(m_group->pcurve(), bytes)) { - if(!m_pt.is_identity()) { - m_xy = m_pt.serialize>(); - BOTAN_ASSERT_NOMSG(m_xy.size() == 1 + 2 * field_element_bytes()); + if(!pcurve.affine_point_is_identity(m_pt)) { + m_xy.resize(1 + 2 * field_element_bytes()); + pcurve.serialize_point(m_xy, m_pt); } } const EC_AffinePoint_Data_PC& EC_AffinePoint_Data_PC::checked_ref(const EC_AffinePoint_Data& data) { const auto* p = dynamic_cast(&data); - if(!p) { + if(p == nullptr) { throw Invalid_State("Failed conversion to EC_AffinePoint_Data_PC"); } return *p; @@ -122,21 +111,16 @@ } std::unique_ptr EC_AffinePoint_Data_PC::mul(const EC_Scalar_Data& scalar, - RandomNumberGenerator& rng, - std::vector& ws) const { - BOTAN_UNUSED(ws); - + RandomNumberGenerator& rng) const { BOTAN_ARG_CHECK(scalar.group() == m_group, "Curve mismatch"); const auto& k = EC_Scalar_Data_PC::checked_ref(scalar).value(); - auto pt = m_group->pcurve().mul(m_pt, k, rng).to_affine(); + const auto& pcurve = m_group->pcurve(); + auto pt = pcurve.point_to_affine(pcurve.mul(m_pt, k, rng)); return std::make_unique(m_group, std::move(pt)); } secure_vector EC_AffinePoint_Data_PC::mul_x_only(const EC_Scalar_Data& scalar, - RandomNumberGenerator& rng, - std::vector& ws) const { - BOTAN_UNUSED(ws); - + RandomNumberGenerator& rng) const { BOTAN_ARG_CHECK(scalar.group() == m_group, "Curve mismatch"); const auto& k = EC_Scalar_Data_PC::checked_ref(scalar).value(); return m_group->pcurve().mul_x_only(m_pt, k, rng); @@ -217,8 +201,10 @@ const auto& x = EC_Scalar_Data_PC::checked_ref(xd); const auto& y = EC_Scalar_Data_PC::checked_ref(yd); - if(auto pt = m_group->pcurve().mul2_vartime(*m_tbl, x.value(), y.value())) { - return std::make_unique(m_group, pt->to_affine()); + const auto& pcurve = m_group->pcurve(); + + if(auto pt = pcurve.mul2_vartime(*m_tbl, x.value(), y.value())) { + return std::make_unique(m_group, pcurve.point_to_affine(*pt)); } else { return nullptr; } diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/ec_inner_pc.h botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/ec_inner_pc.h --- botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/ec_inner_pc.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/ec_inner_pc.h 2026-05-07 01:38:28.000000000 +0000 @@ -32,6 +32,8 @@ void assign(const EC_Scalar_Data& y) override; + void zeroize() override; + void square_self() override; std::unique_ptr negate() const override; @@ -59,8 +61,6 @@ public: EC_AffinePoint_Data_PC(std::shared_ptr group, PCurve::PrimeOrderCurve::AffinePoint pt); - EC_AffinePoint_Data_PC(std::shared_ptr group, std::span pt); - static const EC_AffinePoint_Data_PC& checked_ref(const EC_AffinePoint_Data& data); const std::shared_ptr& group() const override; @@ -81,13 +81,9 @@ void serialize_uncompressed_to(std::span bytes) const override; - std::unique_ptr mul(const EC_Scalar_Data& scalar, - RandomNumberGenerator& rng, - std::vector& ws) const override; - - secure_vector mul_x_only(const EC_Scalar_Data& scalar, - RandomNumberGenerator& rng, - std::vector& ws) const override; + std::unique_ptr mul(const EC_Scalar_Data& scalar, RandomNumberGenerator& rng) const override; + + secure_vector mul_x_only(const EC_Scalar_Data& scalar, RandomNumberGenerator& rng) const override; const PCurve::PrimeOrderCurve::AffinePoint& value() const { return m_pt; } @@ -103,7 +99,7 @@ class EC_Mul2Table_Data_PC final : public EC_Mul2Table_Data { public: - EC_Mul2Table_Data_PC(const EC_AffinePoint_Data& q); + explicit EC_Mul2Table_Data_PC(const EC_AffinePoint_Data& q); std::unique_ptr mul2_vartime(const EC_Scalar_Data& x, const EC_Scalar_Data& y) const override; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/ec_named.cpp botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/ec_named.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/ec_named.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/ec_named.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,7 @@ /* * ECC Group Info -* This file was automatically generated by ./src/scripts/dev_tools/gen_ec_groups.py on 2023-05-30 +* This file was automatically generated by ./src/scripts/dev_tools/gen_ec_groups.py on 2026-04-24 +* All manual changes will be lost. Edit the script instead. * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -151,7 +152,7 @@ "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD97", "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD94", "0xA6", - "1", + "0x1", "0x8D91E471E0989CDA27DF505A453F2B7635294F2DDF23E3B122ACC99C9E9F1E14", "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6C611070995AD10045841B09B761B893", OID{1, 2, 643, 7, 1, 2, 1, 1, 1}); @@ -163,7 +164,7 @@ "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDC7", "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDC4", "0xE8C2505DEDFC86DDC1BD0B2B6667F1DA34B82574761CB0E879BD081CFD0B6265EE3CB090F30D27614CB4574010DA90DD862EF9D4EBEE4761503190785A71C760", - "3", + "0x3", "0x7503CFE87A836AE3A61B8816E25450E6CE5E1C93ACF1ABC1778064FDCBEFA921DF1626BE4FD036E93D75E6A50E3A41E98028FE5FC235F5B889A589CB5215F2A4", "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF27E69532F48D89116FF22B8D4E0560609B4B38ABFAD2B85DCACDB1411F10B275", oid); @@ -173,8 +174,8 @@ if(oid == OID{1, 3, 132, 0, 9}) { return load_EC_group_info( "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFAC73", - "0", - "7", + "0x0", + "0x7", "0x3B4C382CE37AA192A4019E763036F4F5DD4D7EBB", "0x938CF935318FDCED6BC28286531733C3F03C4FEE", "0x100000000000000000001B8FA16DFAB9ACA16B6B3", @@ -209,8 +210,8 @@ if(oid == OID{1, 3, 132, 0, 31}) { return load_EC_group_info( "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFEE37", - "0", - "3", + "0x0", + "0x3", "0xDB4FF10EC057E9AE26B07D0280B7F4341DA5D1B1EAE06C7D", "0x9B2F2F6D9C5628A7844163D015BE86344082AA88D95E2F9D", "0xFFFFFFFFFFFFFFFFFFFFFFFE26F2FC170F69466A74DEFD8D", @@ -233,8 +234,8 @@ if(oid == OID{1, 3, 132, 0, 32}) { return load_EC_group_info( "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFE56D", - "0", - "5", + "0x0", + "0x5", "0xA1455B334DF099DF30FC28A169A467E9E47075A90F7E650EB6B7A45C", "0x7E089FED7FBA344282CAFBD6F7E319F7C0B0BD59E2CA4BDB556D61A5", "0x10000000000000000000000000001DCE8D2EC6184CAF0A971769FB1F7", @@ -257,8 +258,8 @@ if(oid == OID{1, 3, 132, 0, 10}) { return load_EC_group_info( "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", - "0", - "7", + "0x0", + "0x7", "0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", "0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", @@ -473,46 +474,53 @@ } //static -bool EC_Group::supports_named_group(std::string_view name) { - return EC_Group::known_named_groups().contains(std::string(name)); -} - -//static const std::set& EC_Group::known_named_groups() { static const std::set named_groups = { #if defined(BOTAN_HAS_PCURVES_BRAINPOOL256R1) || defined(BOTAN_HAS_LEGACY_EC_POINT) || defined(BOTAN_HAS_PCURVES_GENERIC) "brainpool256r1", #endif + #if defined(BOTAN_HAS_PCURVES_BRAINPOOL384R1) || defined(BOTAN_HAS_LEGACY_EC_POINT) || defined(BOTAN_HAS_PCURVES_GENERIC) "brainpool384r1", #endif + #if defined(BOTAN_HAS_PCURVES_BRAINPOOL512R1) || defined(BOTAN_HAS_LEGACY_EC_POINT) || defined(BOTAN_HAS_PCURVES_GENERIC) "brainpool512r1", #endif + #if defined(BOTAN_HAS_PCURVES_FRP256V1) || defined(BOTAN_HAS_LEGACY_EC_POINT) || defined(BOTAN_HAS_PCURVES_GENERIC) "frp256v1", #endif + #if defined(BOTAN_HAS_PCURVES_NUMSP512D1) || defined(BOTAN_HAS_LEGACY_EC_POINT) || defined(BOTAN_HAS_PCURVES_GENERIC) "numsp512d1", #endif + #if defined(BOTAN_HAS_PCURVES_SECP192R1) || defined(BOTAN_HAS_LEGACY_EC_POINT) || defined(BOTAN_HAS_PCURVES_GENERIC) "secp192r1", #endif + #if defined(BOTAN_HAS_PCURVES_SECP224R1) || defined(BOTAN_HAS_LEGACY_EC_POINT) - "secp224r1", // not supported by pcurves_generic + // Not supported by pcurves_generic + "secp224r1", #endif + #if defined(BOTAN_HAS_PCURVES_SECP256K1) || defined(BOTAN_HAS_LEGACY_EC_POINT) || defined(BOTAN_HAS_PCURVES_GENERIC) "secp256k1", #endif + #if defined(BOTAN_HAS_PCURVES_SECP256R1) || defined(BOTAN_HAS_LEGACY_EC_POINT) || defined(BOTAN_HAS_PCURVES_GENERIC) "secp256r1", #endif + #if defined(BOTAN_HAS_PCURVES_SECP384R1) || defined(BOTAN_HAS_LEGACY_EC_POINT) || defined(BOTAN_HAS_PCURVES_GENERIC) "secp384r1", #endif + #if defined(BOTAN_HAS_PCURVES_SECP521R1) || defined(BOTAN_HAS_LEGACY_EC_POINT) || defined(BOTAN_HAS_PCURVES_GENERIC) "secp521r1", #endif + #if defined(BOTAN_HAS_PCURVES_SM2P256V1) || defined(BOTAN_HAS_LEGACY_EC_POINT) || defined(BOTAN_HAS_PCURVES_GENERIC) "sm2p256v1", #endif @@ -524,7 +532,6 @@ "gost_256A", "gost_512A", "secp192k1", - "secp192r1", "x962_p192v2", "x962_p192v3", "x962_p239v1", @@ -540,9 +547,10 @@ "secp224k1", #endif }; + return named_groups; } -// clang-format on - } // namespace Botan + +// clang-format on diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/ec_point_format.h botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/ec_point_format.h --- botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/ec_point_format.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/ec_point_format.h 2026-05-07 01:38:28.000000000 +0000 @@ -12,13 +12,29 @@ /* * This header is public, but avoid including it directly. Instead * get the contents via ec_group.h. -* -* TODO(Botan4): Move EC_Point_Format to ec_group.h and delete this file */ namespace Botan { -enum class EC_Point_Format { +/** +* This enum indicates the method used to encode the EC parameters +* +* @warning All support for explicit or implicit domain encodings +* will be removed in Botan4. Only named curves will be supported. +* +* TODO(Botan4) remove this enum +*/ +enum class EC_Group_Encoding : uint8_t { + Explicit = 0, + ImplicitCA = 1, + NamedCurve = 2, + + EC_DOMPAR_ENC_EXPLICIT = Explicit, + EC_DOMPAR_ENC_IMPLICITCA = ImplicitCA, + EC_DOMPAR_ENC_OID = NamedCurve +}; + +enum class EC_Point_Format : uint8_t { Uncompressed = 0, Compressed = 1, diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/ec_scalar.cpp botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/ec_scalar.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/ec_scalar.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/ec_scalar.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,6 +9,10 @@ #include #include +#if defined(BOTAN_HAS_XMD) + #include +#endif + namespace Botan { EC_Scalar EC_Scalar::_from_inner(std::unique_ptr inner) { @@ -76,9 +80,9 @@ return BigInt::from_bytes(bytes); } -EC_Scalar EC_Scalar::gk_x_mod_order(const EC_Scalar& scalar, RandomNumberGenerator& rng, std::vector& ws) { +EC_Scalar EC_Scalar::gk_x_mod_order(const EC_Scalar& scalar, RandomNumberGenerator& rng) { const auto& group = scalar._inner().group(); - return EC_Scalar(group->gk_x_mod_order(scalar.inner(), rng, ws)); + return EC_Scalar(group->gk_x_mod_order(scalar.inner(), rng)); } void EC_Scalar::serialize_to(std::span bytes) const { @@ -162,8 +166,38 @@ m_scalar->assign(x.inner()); } +void EC_Scalar::zeroize() { + m_scalar->zeroize(); +} + bool EC_Scalar::is_eq(const EC_Scalar& x) const { return inner().is_eq(x.inner()); } +//static +EC_Scalar EC_Scalar::hash(const EC_Group& group, + std::string_view hash_fn, + std::span input, + std::span domain_sep) { +#if defined(BOTAN_HAS_XMD) + + /* + * This could be extended to support expand_message_xof or a MHF like Argon2 + */ + if(hash_fn.starts_with("SHAKE")) { + throw Not_Implemented("Hash to scalar currently does not support expand_message_xof"); + } + + const size_t scalar_bits = group.get_order_bits(); + const size_t security_level = (scalar_bits + 1) / 2; + secure_vector uniform_bytes((scalar_bits + security_level + 7) / 8); + expand_message_xmd(hash_fn, uniform_bytes, input, domain_sep); + + return EC_Scalar::from_bytes_mod_order(group, uniform_bytes); +#else + BOTAN_UNUSED(group, hash_fn, input, domain_sep); + throw Not_Implemented("EC_Scalar::hash not available due to missing XMD"); +#endif +} + } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/ec_scalar.h botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/ec_scalar.h --- botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/ec_scalar.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/ec_scalar.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,8 +9,10 @@ #include #include +#include #include #include +#include #include namespace Botan { @@ -18,13 +20,12 @@ class BigInt; class RandomNumberGenerator; class EC_Group; -class EC_Group_Data; class EC_Scalar_Data; /** * Represents an integer modulo the prime group order of an elliptic curve */ -class BOTAN_UNSTABLE_API EC_Scalar final { +class BOTAN_PUBLIC_API(3, 6) EC_Scalar final { public: /** * Deserialize a scalar @@ -54,6 +55,16 @@ static EC_Scalar from_bytes_mod_order(const EC_Group& group, std::span bytes); /** + * Hash to scalar following RFC 9380 + * + * This requires XMD. Unlike hash2curve, any group is supported + */ + static EC_Scalar hash(const EC_Group& group, + std::string_view hash_fn, + std::span input, + std::span domain_sep); + + /** * Convert a bytestring to an EC_Scalar * * This is similar to deserialize but instead of returning nullopt if the input @@ -91,10 +102,14 @@ * Compute the elliptic curve scalar multiplication (g*k) where g is the * standard base point on the curve. Then extract the x coordinate of * the resulting point, and reduce it modulo the group order. - * - * Workspace argument is transitional */ - static EC_Scalar gk_x_mod_order(const EC_Scalar& scalar, RandomNumberGenerator& rng, std::vector& ws); + static EC_Scalar gk_x_mod_order(const EC_Scalar& scalar, RandomNumberGenerator& rng); + + BOTAN_DEPRECATED("Use version without workspace arg") + static EC_Scalar + gk_x_mod_order(const EC_Scalar& scalar, RandomNumberGenerator& rng, std::vector& /*ws*/) { + return EC_Scalar::gk_x_mod_order(scalar, rng); + } /** * Return the byte size of this scalar @@ -189,6 +204,13 @@ void assign(const EC_Scalar& x); /** + * Equivalent to assigning a zero value, but also does so in a way that + * attempts to ensure the write always occurs even if a compiler can deduce + * the assignment is otherwise unnecessary. + */ + void zeroize(); + + /** * Set *this to its own square modulo the group order */ void square_self(); @@ -226,7 +248,7 @@ private: friend class EC_AffinePoint; - EC_Scalar(std::unique_ptr scalar); + explicit EC_Scalar(std::unique_ptr scalar); const EC_Scalar_Data& inner() const { return *m_scalar; } diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/legacy_ec_point/curve_gfp.h botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/legacy_ec_point/curve_gfp.h --- botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/legacy_ec_point/curve_gfp.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/legacy_ec_point/curve_gfp.h 2026-05-07 01:38:28.000000000 +0000 @@ -48,6 +48,9 @@ size_t get_p_words() const; CurveGFp(const CurveGFp&) = default; + CurveGFp(CurveGFp&&) = default; + + ~CurveGFp() = default; private: friend class EC_Point; @@ -58,19 +61,17 @@ */ CurveGFp() = default; - CurveGFp(const EC_Group_Data* group); + BOTAN_FUTURE_EXPLICIT CurveGFp(const EC_Group_Data* group); CurveGFp& operator=(const CurveGFp&) = default; + CurveGFp& operator=(CurveGFp&&) = default; - void swap(CurveGFp& other) { std::swap(m_group, other.m_group); } + void swap(CurveGFp& other) noexcept { std::swap(m_group, other.m_group); } bool operator==(const CurveGFp& other) const { return (m_group == other.m_group); } private: - const EC_Group_Data& group() const { - BOTAN_ASSERT_NONNULL(m_group); - return *m_group; - } + const EC_Group_Data& group() const; /** * Raw pointer diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/legacy_ec_point/ec_inner_bn.cpp botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/legacy_ec_point/ec_inner_bn.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/legacy_ec_point/ec_inner_bn.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/legacy_ec_point/ec_inner_bn.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,13 +6,15 @@ #include +#include +#include #include namespace Botan { const EC_Scalar_Data_BN& EC_Scalar_Data_BN::checked_ref(const EC_Scalar_Data& data) { const auto* p = dynamic_cast(&data); - if(!p) { + if(p == nullptr) { throw Invalid_State("Failed conversion to EC_Scalar_Data_BN"); } return *p; @@ -42,20 +44,35 @@ m_v = checked_ref(other).value(); } +void EC_Scalar_Data_BN::zeroize() { + // BigInt stores its value in a secure_vector, after swapping the existing + // value will go out of scope (inside `zero`) and be wiped properly. + BigInt zero; + std::swap(m_v, zero); +} + void EC_Scalar_Data_BN::square_self() { m_group->mod_order().square(m_v); } std::unique_ptr EC_Scalar_Data_BN::negate() const { - return std::make_unique(m_group, m_group->mod_order().reduce(-m_v)); + return std::make_unique(m_group, m_group->mod_order().reduce(m_group->order() - m_v)); } std::unique_ptr EC_Scalar_Data_BN::invert() const { - return std::make_unique(m_group, inverse_mod_public_prime(m_v, m_group->order())); + if(m_v.is_zero()) { + return std::make_unique(m_group, m_v); + } else { + return std::make_unique(m_group, inverse_mod_public_prime(m_v, m_group->order())); + } } std::unique_ptr EC_Scalar_Data_BN::invert_vartime() const { - return std::make_unique(m_group, inverse_mod_public_prime(m_v, m_group->order())); + if(m_v.is_zero()) { + return std::make_unique(m_group, m_v); + } else { + return std::make_unique(m_group, inverse_mod_public_prime(m_v, m_group->order())); + } } std::unique_ptr EC_Scalar_Data_BN::add(const EC_Scalar_Data& other) const { @@ -63,7 +80,8 @@ } std::unique_ptr EC_Scalar_Data_BN::sub(const EC_Scalar_Data& other) const { - return std::make_unique(m_group, m_group->mod_order().reduce(m_v - checked_ref(other).value())); + return std::make_unique( + m_group, m_group->mod_order().reduce(m_v + (m_group->order() - checked_ref(other).value()))); } std::unique_ptr EC_Scalar_Data_BN::mul(const EC_Scalar_Data& other) const { @@ -83,16 +101,6 @@ } } -EC_AffinePoint_Data_BN::EC_AffinePoint_Data_BN(std::shared_ptr group, - std::span pt) : - m_group(std::move(group)) { - BOTAN_ASSERT_NONNULL(m_group); - m_pt = Botan::OS2ECP(pt, m_group->curve()); - if(!m_pt.is_zero()) { - m_xy = m_pt.xy_bytes(); - } -} - std::unique_ptr EC_AffinePoint_Data_BN::clone() const { return std::make_unique(m_group, m_pt); } @@ -102,12 +110,12 @@ } std::unique_ptr EC_AffinePoint_Data_BN::mul(const EC_Scalar_Data& scalar, - RandomNumberGenerator& rng, - std::vector& ws) const { + RandomNumberGenerator& rng) const { BOTAN_ARG_CHECK(scalar.group() == m_group, "Curve mismatch"); const auto& bn = EC_Scalar_Data_BN::checked_ref(scalar); - EC_Point_Var_Point_Precompute mul(m_pt, rng, ws); + std::vector ws; + const EC_Point_Var_Point_Precompute mul(m_pt, rng, ws); // We pass order*cofactor here to "correctly" handle the case where the // point is on the curve but not in the prime order subgroup. This only @@ -120,12 +128,12 @@ } secure_vector EC_AffinePoint_Data_BN::mul_x_only(const EC_Scalar_Data& scalar, - RandomNumberGenerator& rng, - std::vector& ws) const { + RandomNumberGenerator& rng) const { BOTAN_ARG_CHECK(scalar.group() == m_group, "Curve mismatch"); const auto& bn = EC_Scalar_Data_BN::checked_ref(scalar); - EC_Point_Var_Point_Precompute mul(m_pt, rng, ws); + std::vector ws; + const EC_Point_Var_Point_Precompute mul(m_pt, rng, ws); // We pass order*cofactor here to "correctly" handle the case where the // point is on the curve but not in the prime order subgroup. This only diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/legacy_ec_point/ec_inner_bn.h botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/legacy_ec_point/ec_inner_bn.h --- botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/legacy_ec_point/ec_inner_bn.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/legacy_ec_point/ec_inner_bn.h 2026-05-07 01:38:28.000000000 +0000 @@ -31,6 +31,8 @@ void assign(const EC_Scalar_Data& y) override; + void zeroize() override; + void square_self() override; std::unique_ptr negate() const override; @@ -58,8 +60,6 @@ public: EC_AffinePoint_Data_BN(std::shared_ptr group, EC_Point pt); - EC_AffinePoint_Data_BN(std::shared_ptr group, std::span pt); - const std::shared_ptr& group() const override; std::unique_ptr clone() const override; @@ -78,13 +78,9 @@ void serialize_uncompressed_to(std::span bytes) const override; - std::unique_ptr mul(const EC_Scalar_Data& scalar, - RandomNumberGenerator& rng, - std::vector& ws) const override; - - secure_vector mul_x_only(const EC_Scalar_Data& scalar, - RandomNumberGenerator& rng, - std::vector& ws) const override; + std::unique_ptr mul(const EC_Scalar_Data& scalar, RandomNumberGenerator& rng) const override; + + secure_vector mul_x_only(const EC_Scalar_Data& scalar, RandomNumberGenerator& rng) const override; EC_Point to_legacy_point() const override { return m_pt; } diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/legacy_ec_point/ec_point.cpp botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/legacy_ec_point/ec_point.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/legacy_ec_point/ec_point.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/legacy_ec_point/ec_point.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,14 +9,14 @@ #include +#include #include #include +#include #include #include #include #include -#include -#include namespace Botan { @@ -29,6 +29,11 @@ BOTAN_ASSERT_NONNULL(m_group); } +const EC_Group_Data& CurveGFp::group() const { + BOTAN_ASSERT_NONNULL(m_group); + return *m_group; +} + const BigInt& CurveGFp::get_a() const { return this->group().a(); } @@ -52,23 +57,29 @@ } void from_rep(const EC_Group_Data& group, BigInt& z, secure_vector& ws) { - group.monty().redc_in_place(z, ws); + z = group.monty().redc(z, ws); } BigInt from_rep_to_tmp(const EC_Group_Data& group, const BigInt& x, secure_vector& ws) { return group.monty().redc(x, ws); } -void fe_mul(const EC_Group_Data& group, BigInt& z, const BigInt& x, const BigInt& y, secure_vector& ws) { +inline void fe_mul(const EC_Group_Data& group, BigInt& z, const BigInt& x, const BigInt& y, secure_vector& ws) { group.monty().mul(z, x, y, ws); } -void fe_mul( +inline void fe_mul( const EC_Group_Data& group, BigInt& z, const word x_w[], size_t x_size, const BigInt& y, secure_vector& ws) { group.monty().mul(z, y, std::span{x_w, x_size}, ws); } -BigInt fe_mul(const EC_Group_Data& group, const BigInt& x, const BigInt& y, secure_vector& ws) { +template +inline void fe_smul(BigInt& z, const BigInt& p, secure_vector& ws) { + static_assert(M == 2 || M == 3 || M == 4 || M == 8); + z.mod_mul(M, p, ws); +} + +inline BigInt fe_mul(const EC_Group_Data& group, const BigInt& x, const BigInt& y, secure_vector& ws) { return group.monty().mul(x, y, ws); } @@ -130,7 +141,7 @@ const auto& group = m_curve.group(); - const BigInt mask = BigInt::random_integer(rng, 2, group.p()); + const BigInt mask = BigInt::random_integer(rng, BigInt::from_s32(2), group.p()); /* * No reason to convert this to Montgomery representation first, @@ -161,6 +172,18 @@ } // namespace +void EC_Point::add_affine(const EC_Point& other, std::vector& workspace) { + BOTAN_ASSERT_NOMSG(m_curve == other.m_curve); + BOTAN_DEBUG_ASSERT(other.is_affine()); + + const size_t p_words = m_curve.get_p_words(); + add_affine(other.m_x._data(), + std::min(p_words, other.m_x.size()), + other.m_y._data(), + std::min(p_words, other.m_y.size()), + workspace); +} + void EC_Point::add_affine( const word x_words[], size_t x_size, const word y_words[], size_t y_size, std::vector& ws_bn) { if((CT::all_zeros(x_words, x_size) & CT::all_zeros(y_words, y_size)).as_bool()) { @@ -240,6 +263,20 @@ m_z.swap(T0); } +void EC_Point::add(const EC_Point& other, std::vector& workspace) { + BOTAN_ARG_CHECK(m_curve == other.m_curve, "cannot add points on different curves"); + + const size_t p_words = m_curve.get_p_words(); + + add(other.m_x._data(), + std::min(p_words, other.m_x.size()), + other.m_y._data(), + std::min(p_words, other.m_y.size()), + other.m_z._data(), + std::min(p_words, other.m_z.size()), + workspace); +} + void EC_Point::add(const word x_words[], size_t x_size, const word y_words[], @@ -379,12 +416,12 @@ fe_sqr(group, T0, m_y, ws); fe_mul(group, T1, m_x, T0, ws); - T1.mod_mul(4, p, sub_ws); + fe_smul<4>(T1, p, sub_ws); if(group.a_is_zero()) { // if a == 0 then 3*x^2 + a*z^4 is just 3*x^2 fe_sqr(group, T4, m_x, ws); // x^2 - T4.mod_mul(3, p, sub_ws); // 3*x^2 + fe_smul<3>(T4, p, sub_ws); // 3*x^2 } else if(group.a_is_minus_3()) { /* if a == -3 then @@ -401,14 +438,14 @@ fe_mul(group, T4, T2, T3, ws); // (x-z^2)*(x+z^2) - T4.mod_mul(3, p, sub_ws); // 3*(x-z^2)*(x+z^2) + fe_smul<3>(T4, p, sub_ws); // 3*(x-z^2)*(x+z^2) } else { fe_sqr(group, T3, m_z, ws); // z^2 fe_sqr(group, T4, T3, ws); // z^4 fe_mul(group, T3, group.monty_a(), T4, ws); // a*z^4 fe_sqr(group, T4, m_x, ws); // x^2 - T4.mod_mul(3, p, sub_ws); + fe_smul<3>(T4, p, sub_ws); T4.mod_add(T3, p, sub_ws); // 3*x^2 + a*z^4 } @@ -417,7 +454,7 @@ T2.mod_sub(T1, p, sub_ws); fe_sqr(group, T3, T0, ws); - T3.mod_mul(8, p, sub_ws); + fe_smul<8>(T3, p, sub_ws); T1.mod_sub(T2, p, sub_ws); @@ -427,7 +464,7 @@ m_x.swap(T2); fe_mul(group, T2, m_y, m_z, ws); - T2.mod_mul(2, p, sub_ws); + fe_smul<2>(T2, p, sub_ws); m_y.swap(T0); m_z.swap(T2); @@ -441,7 +478,7 @@ } EC_Point& EC_Point::operator-=(const EC_Point& rhs) { - EC_Point minus_rhs = EC_Point(rhs).negate(); + const EC_Point minus_rhs = EC_Point(rhs).negate(); if(is_zero()) { *this = minus_rhs; @@ -465,12 +502,12 @@ EC_Point R[2] = {this->zero(), *this}; for(size_t i = scalar_bits; i > 0; i--) { - const size_t b = scalar.get_bit(i - 1); + const size_t b = scalar.get_bit(i - 1) ? 1 : 0; R[b ^ 1].add(R[b], ws); R[b].mult2(ws); } - if(scalar.is_negative()) { + if(scalar.signum() < 0) { R[0].negate(); } @@ -519,7 +556,9 @@ BigInt s_inv = invert_element(group, c[c.size() - 1], ws); - BigInt z_inv, z2_inv, z3_inv; + BigInt z_inv; + BigInt z2_inv; + BigInt z3_inv; for(size_t i = points.size() - 1; i != 0; i--) { EC_Point& point = points[i]; @@ -584,7 +623,7 @@ const auto& group = m_curve.group(); const size_t p_bytes = group.p_bytes(); secure_vector b(2 * p_bytes); - BigInt::encode_1363(&b[0], p_bytes, this->get_affine_x()); + BigInt::encode_1363(&b[0], p_bytes, this->get_affine_x()); // NOLINT(*container-data-pointer) BigInt::encode_1363(&b[p_bytes], p_bytes, this->get_affine_y()); return b; } @@ -673,12 +712,23 @@ } bool EC_Point::_is_x_eq_to_v_mod_order(const BigInt& v) const { + BOTAN_ASSERT_NOMSG(v.signum() >= 0); + if(this->is_zero()) { return false; } const auto& group = m_curve.group(); + // In this case v cannot possibly be valid, since it must be in [0..m) where + // m is the smaller of the field modulus or group order + if(v >= group.p()) { + return false; + } + if(v >= group.order()) { + return false; + } + /* * The trick used below doesn't work for curves with cofactors */ @@ -704,7 +754,8 @@ secure_vector ws; BigInt vr = v; to_rep(group, vr, ws); - BigInt z2, v_z2; + BigInt z2; + BigInt v_z2; fe_sqr(group, z2, this->get_z(), ws); fe_mul(group, v_z2, vr, z2, ws); @@ -841,7 +892,8 @@ const uint8_t pc = pt[0]; const size_t p_bytes = p.bytes(); - BigInt x, y; + BigInt x; + BigInt y; if(pc == 2 || pc == 3) { if(pt_len != 1 + p_bytes) { diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/legacy_ec_point/ec_point.h botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/legacy_ec_point/ec_point.h --- botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/legacy_ec_point/ec_point.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/legacy_ec_point/ec_point.h 2026-05-07 01:38:28.000000000 +0000 @@ -14,7 +14,6 @@ #include #include -#include #include namespace Botan { @@ -25,7 +24,7 @@ * Use EC_AffinePoint in new code; this type is no longer used internally at all * except to support very unfortunate (and deprecated) curve types, specifically * those with a cofactor, or with unreasonable sizes (above 521 bits), which -* cannot be accomodated by the new faster EC library in math/pcurves. For +* cannot be accommodated by the new faster EC library in math/pcurves. For * normal curves EC_AffinePoint will typically be 2 or 3 times faster. * * This type will be completely removed in Botan4 @@ -39,7 +38,7 @@ typedef EC_Point_Format Compression_Type; using enum EC_Point_Format; - enum { WORKSPACE_SIZE = 8 }; + enum : uint8_t /* NOLINT(*-use-enum-class) */ { WORKSPACE_SIZE = 8 }; /** * Construct an uninitialized EC_Point @@ -60,7 +59,7 @@ /** * Move Constructor */ - EC_Point(EC_Point&& other) { this->swap(other); } + EC_Point(EC_Point&& other) noexcept { this->swap(other); } /** * Standard Assignment @@ -70,13 +69,15 @@ /** * Move Assignment */ - EC_Point& operator=(EC_Point&& other) { + EC_Point& operator=(EC_Point&& other) noexcept { if(this != &other) { this->swap(other); } return (*this); } + ~EC_Point() = default; + /** * Point multiplication operator * @@ -258,7 +259,7 @@ m_z.swap(new_z); } - friend void swap(EC_Point& x, EC_Point& y) { x.swap(y); } + friend void swap(EC_Point& x, EC_Point& y) noexcept { x.swap(y); } /** * Randomize the point representation @@ -271,19 +272,7 @@ * @param other the point to add to *this * @param workspace temp space, at least WORKSPACE_SIZE elements */ - void add(const EC_Point& other, std::vector& workspace) { - BOTAN_ARG_CHECK(m_curve == other.m_curve, "cannot add points on different curves"); - - const size_t p_words = m_curve.get_p_words(); - - add(other.m_x._data(), - std::min(p_words, other.m_x.size()), - other.m_y._data(), - std::min(p_words, other.m_y.size()), - other.m_z._data(), - std::min(p_words, other.m_z.size()), - workspace); - } + void add(const EC_Point& other, std::vector& workspace); /** * Point addition. Array version. @@ -313,17 +302,7 @@ * @param other affine point to add - assumed to be affine! * @param workspace temp space, at least WORKSPACE_SIZE elements */ - void add_affine(const EC_Point& other, std::vector& workspace) { - BOTAN_ASSERT_NOMSG(m_curve == other.m_curve); - BOTAN_DEBUG_ASSERT(other.is_affine()); - - const size_t p_words = m_curve.get_p_words(); - add_affine(other.m_x._data(), - std::min(p_words, other.m_x.size()), - other.m_y._data(), - std::min(p_words, other.m_y.size()), - workspace); - } + void add_affine(const EC_Point& other, std::vector& workspace); /** * Point addition - mixed J+A. Array version. diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/legacy_ec_point/point_mul.cpp botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/legacy_ec_point/point_mul.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/legacy_ec_point/point_mul.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/legacy_ec_point/point_mul.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,8 +6,10 @@ #include -#include +#include +#include #include +#include #include #include @@ -15,39 +17,41 @@ namespace { -size_t blinding_size(const BigInt& group_order) { - return (group_order.bits() + 1) / 2; +size_t blinding_size(size_t order_bits) { + return (order_bits + 1) / 2; } BigInt blinding_mask(const BigInt& group_order, RandomNumberGenerator& rng) { if(rng.is_seeded()) { - BigInt mask(rng, blinding_size(group_order)); + BigInt mask(rng, blinding_size(group_order.bits())); mask.set_bit(0); return mask; } else { - return 1; + return BigInt::one(); } } } // namespace EC_Point multi_exponentiate(const EC_Point& x, const BigInt& z1, const EC_Point& y, const BigInt& z2) { - EC_Point_Multi_Point_Precompute xy_mul(x, y); + const EC_Point_Multi_Point_Precompute xy_mul(x, y); return xy_mul.multi_exp(z1, z2); } -EC_Point_Base_Point_Precompute::EC_Point_Base_Point_Precompute(const EC_Point& base, const Modular_Reducer& mod_order) : +EC_Point_Base_Point_Precompute::EC_Point_Base_Point_Precompute(const EC_Point& base, + const Barrett_Reduction& mod_order) : m_base_point(base), m_mod_order(mod_order), m_p_words(base.get_curve().get_p_words()) { std::vector ws(EC_Point::WORKSPACE_SIZE); - const size_t order_bits = mod_order.get_modulus().bits(); + const size_t order_bits = mod_order.modulus_bits(); - const size_t T_bits = round_up(order_bits + blinding_size(mod_order.get_modulus()), WINDOW_BITS) / WINDOW_BITS; + const size_t T_bits = round_up(order_bits + blinding_size(order_bits), WindowBits) / WindowBits; - std::vector T(WINDOW_SIZE * T_bits); + std::vector T(WindowSize * T_bits); EC_Point g = base; - EC_Point g2, g4; + EC_Point g2; + EC_Point g4; for(size_t i = 0; i != T_bits; i++) { g2 = g; @@ -71,11 +75,11 @@ m_W.resize(T.size() * 2 * m_p_words); - word* p = &m_W[0]; - for(size_t i = 0; i != T.size(); ++i) { - T[i].get_x().encode_words(p, m_p_words); + word* p = m_W.data(); + for(const auto& pt : T) { + pt.get_x().encode_words(p, m_p_words); p += m_p_words; - T[i].get_y().encode_words(p, m_p_words); + pt.get_y().encode_words(p, m_p_words); p += m_p_words; } } @@ -84,7 +88,7 @@ RandomNumberGenerator& rng, const BigInt& group_order, std::vector& ws) const { - if(k.is_negative()) { + if(k.signum() < 0) { throw Invalid_Argument("EC_Point_Base_Point_Precompute scalar must be positive"); } @@ -108,7 +112,7 @@ BOTAN_DEBUG_ASSERT(scalar.bits() == group_order.bits() + 1); } - const size_t windows = round_up(scalar.bits(), WINDOW_BITS) / WINDOW_BITS; + const size_t windows = round_up(scalar.bits(), WindowBits) / WindowBits; const size_t elem_size = 2 * m_p_words; @@ -125,9 +129,9 @@ for(size_t i = 0; i != windows; ++i) { const size_t window = windows - i - 1; - const size_t base_addr = (WINDOW_SIZE * window) * elem_size; + const size_t base_addr = (WindowSize * window) * elem_size; - const word w = scalar.get_substring(WINDOW_BITS * window, WINDOW_BITS); + const word w = scalar.get_substring(WindowBits * window, WindowBits); const auto w_is_1 = CT::Mask::is_equal(w, 1); const auto w_is_2 = CT::Mask::is_equal(w, 2); @@ -149,7 +153,7 @@ Wt[j] = w1 | w2 | w3 | w4 | w5 | w6 | w7; } - R.add_affine(&Wt[0], m_p_words, &Wt[m_p_words], m_p_words, ws); + R.add_affine(Wt.data(), m_p_words, &Wt[m_p_words], m_p_words, ws); if(i == 0 && rng.is_seeded()) { /* @@ -170,7 +174,7 @@ EC_Point_Var_Point_Precompute::EC_Point_Var_Point_Precompute(const EC_Point& ipoint, RandomNumberGenerator& rng, std::vector& ws) : - m_curve(ipoint.get_curve()), m_p_words(m_curve.get_p_words()), m_window_bits(4) { + m_curve(ipoint.get_curve()), m_p_words(m_curve.get_p_words()) { if(ws.size() < EC_Point::WORKSPACE_SIZE) { ws.resize(EC_Point::WORKSPACE_SIZE); } @@ -178,7 +182,7 @@ auto point = ipoint; point.randomize_repr(rng); - std::vector U(static_cast(1) << m_window_bits); + std::vector U(static_cast(1) << WindowBits); U[0] = point.zero(); U[1] = point; @@ -197,11 +201,11 @@ m_T.resize(U.size() * 3 * m_p_words); - word* p = &m_T[0]; - for(size_t i = 0; i != U.size(); ++i) { - U[i].get_x().encode_words(p, m_p_words); - U[i].get_y().encode_words(p + m_p_words, m_p_words); - U[i].get_z().encode_words(p + 2 * m_p_words, m_p_words); + word* p = m_T.data(); + for(const auto& pt : U) { + pt.get_x().encode_words(p, m_p_words); + pt.get_y().encode_words(p + m_p_words, m_p_words); + pt.get_z().encode_words(p + 2 * m_p_words, m_p_words); p += 3 * m_p_words; } } @@ -210,7 +214,7 @@ RandomNumberGenerator& rng, const BigInt& group_order, std::vector& ws) const { - if(k.is_negative()) { + if(k.signum() < 0) { throw Invalid_Argument("EC_Point_Var_Point_Precompute scalar must be positive"); } if(ws.size() < EC_Point::WORKSPACE_SIZE) { @@ -221,16 +225,16 @@ const BigInt scalar = k + group_order * blinding_mask(group_order, rng); const size_t elem_size = 3 * m_p_words; - const size_t window_elems = static_cast(1) << m_window_bits; + const size_t window_elems = static_cast(1) << WindowBits; - size_t windows = round_up(scalar.bits(), m_window_bits) / m_window_bits; + size_t windows = round_up(scalar.bits(), WindowBits) / WindowBits; EC_Point R(m_curve); secure_vector e(elem_size); if(windows > 0) { windows--; - const uint32_t w = scalar.get_substring(windows * m_window_bits, m_window_bits); + const uint32_t w = scalar.get_substring(windows * WindowBits, WindowBits); clear_mem(e.data(), e.size()); for(size_t i = 1; i != window_elems; ++i) { @@ -241,7 +245,7 @@ } } - R.add(&e[0], m_p_words, &e[m_p_words], m_p_words, &e[2 * m_p_words], m_p_words, ws); + R.add(e.data(), m_p_words, &e[m_p_words], m_p_words, &e[2 * m_p_words], m_p_words, ws); /* Randomize after adding the first nibble as before the addition R @@ -251,10 +255,10 @@ R.randomize_repr(rng, ws[0].get_word_vector()); } - while(windows) { - R.mult2i(m_window_bits, ws); + while(windows > 0) { + R.mult2i(WindowBits, ws); - const uint32_t w = scalar.get_substring((windows - 1) * m_window_bits, m_window_bits); + const uint32_t w = scalar.get_substring((windows - 1) * WindowBits, WindowBits); clear_mem(e.data(), e.size()); for(size_t i = 1; i != window_elems; ++i) { @@ -265,7 +269,7 @@ } } - R.add(&e[0], m_p_words, &e[m_p_words], m_p_words, &e[2 * m_p_words], m_p_words, ws); + R.add(e.data(), m_p_words, &e[m_p_words], m_p_words, &e[2 * m_p_words], m_p_words, ws); windows--; } @@ -276,7 +280,7 @@ } EC_Point_Multi_Point_Precompute::EC_Point_Multi_Point_Precompute(const EC_Point& x, const EC_Point& y) { - if(x.on_the_curve() == false || y.on_the_curve() == false) { + if(!x.on_the_curve() || !y.on_the_curve()) { m_M.push_back(x.zero()); return; } @@ -350,7 +354,7 @@ const uint32_t z12 = (4 * z2_b) + z1_b; // This function is not intended to be const time - if(z12) { + if(z12 != 0) { if(m_no_infinity) { H.add_affine(m_M[z12 - 1], ws); } else { @@ -359,7 +363,7 @@ } } - if(z1.is_negative() != z2.is_negative()) { + if((z1.signum() < 0) != (z2.signum() < 0)) { H.negate(); } diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/legacy_ec_point/point_mul.h botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/legacy_ec_point/point_mul.h --- botan3-3.7.1+dfsg/src/lib/pubkey/ec_group/legacy_ec_point/point_mul.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ec_group/legacy_ec_point/point_mul.h 2026-05-07 01:38:28.000000000 +0000 @@ -11,11 +11,11 @@ namespace Botan { -class Modular_Reducer; +class Barrett_Reduction; class EC_Point_Base_Point_Precompute final { public: - EC_Point_Base_Point_Precompute(const EC_Point& base_point, const Modular_Reducer& mod_order); + EC_Point_Base_Point_Precompute(const EC_Point& base_point, const Barrett_Reduction& mod_order); EC_Point mul(const BigInt& k, RandomNumberGenerator& rng, @@ -24,11 +24,10 @@ private: const EC_Point& m_base_point; - const Modular_Reducer& m_mod_order; + const Barrett_Reduction& m_mod_order; - enum { WINDOW_BITS = 3 }; - - enum { WINDOW_SIZE = (1 << WINDOW_BITS) - 1 }; + static constexpr size_t WindowBits = 3; + static constexpr size_t WindowSize = (1 << WindowBits) - 1; const size_t m_p_words; @@ -48,9 +47,10 @@ std::vector& ws) const; private: + static constexpr size_t WindowBits = 4; + const CurveGFp m_curve; const size_t m_p_words; - const size_t m_window_bits; /* * Table of 2^window_bits * 3*2*p_word words diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ecc_key/ec_key_data.cpp botan3-3.12.0+dfsg/src/lib/pubkey/ecc_key/ec_key_data.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/ecc_key/ec_key_data.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ecc_key/ec_key_data.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,19 +6,28 @@ #include +#include #include namespace Botan { -EC_PublicKey_Data::EC_PublicKey_Data(EC_Group group, std::span bytes) : - m_group(std::move(group)), m_point(m_group, bytes) { +EC_PublicKey_Data::EC_PublicKey_Data(EC_Group group, EC_AffinePoint pt) : + m_group(std::move(group)), m_point(std::move(pt)) { #if defined(BOTAN_HAS_LEGACY_EC_POINT) m_legacy_point = m_point.to_legacy_point(); #endif + + // Checking that the point lies on the curve is done in the deserialization + // of EC_AffinePoint. + BOTAN_ARG_CHECK(!m_point.is_identity(), "ECC public key cannot be point at infinity"); } EC_PrivateKey_Data::EC_PrivateKey_Data(EC_Group group, EC_Scalar x) : - m_group(std::move(group)), m_scalar(std::move(x)), m_legacy_x(m_scalar.to_bigint()) {} + m_group(std::move(group)), m_scalar(std::move(x)), m_legacy_x(m_scalar.to_bigint()) { + // Checking that the scalar is lower than the group order is ensured in the + // deserialization of the EC_Scalar or during the random generation respectively. + BOTAN_ARG_CHECK(m_scalar.is_nonzero(), "ECC private key cannot be zero"); +} namespace { @@ -33,7 +42,7 @@ * not have their high bit set and so can be encoded in 65 bytes, vs 66 * bytes for the full order. * - * To accomodate this, zero prefix the key if we see such a short input + * To accommodate this, zero prefix the key if we see such a short input */ secure_vector padded_sk(order_bytes); copy_mem(std::span{padded_sk}.last(bytes.size()), bytes); @@ -49,19 +58,20 @@ } // namespace -EC_PrivateKey_Data::EC_PrivateKey_Data(EC_Group group, std::span bytes) : - m_group(std::move(group)), - m_scalar(decode_ec_secret_key_scalar(m_group, bytes)), - m_legacy_x(m_scalar.to_bigint()) {} +EC_PrivateKey_Data::EC_PrivateKey_Data(const EC_Group& group, std::span bytes) : + Botan::EC_PrivateKey_Data(group, decode_ec_secret_key_scalar(group, bytes)) {} + +EC_PrivateKey_Data::~EC_PrivateKey_Data() { + m_scalar.zeroize(); +} std::shared_ptr EC_PrivateKey_Data::public_key(RandomNumberGenerator& rng, bool with_modular_inverse) const { auto public_point = [&] { - std::vector ws; if(with_modular_inverse) { - return EC_AffinePoint::g_mul(m_scalar.invert(), rng, ws); + return EC_AffinePoint::g_mul(m_scalar.invert(), rng); } else { - return EC_AffinePoint::g_mul(m_scalar, rng, ws); + return EC_AffinePoint::g_mul(m_scalar, rng); } }; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ecc_key/ec_key_data.h botan3-3.12.0+dfsg/src/lib/pubkey/ecc_key/ec_key_data.h --- botan3-3.7.1+dfsg/src/lib/pubkey/ecc_key/ec_key_data.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ecc_key/ec_key_data.h 2026-05-07 01:38:28.000000000 +0000 @@ -23,13 +23,10 @@ class EC_PublicKey_Data final { public: - EC_PublicKey_Data(EC_Group group, EC_AffinePoint pt) : m_group(std::move(group)), m_point(std::move(pt)) { -#if defined(BOTAN_HAS_LEGACY_EC_POINT) - m_legacy_point = m_point.to_legacy_point(); -#endif - } + EC_PublicKey_Data(EC_Group group, EC_AffinePoint pt); - EC_PublicKey_Data(EC_Group group, std::span bytes); + EC_PublicKey_Data(const EC_Group& group, std::span bytes) : + EC_PublicKey_Data(group, EC_AffinePoint(group, bytes)) {} const EC_Group& group() const { return m_group; } @@ -51,7 +48,13 @@ public: EC_PrivateKey_Data(EC_Group group, EC_Scalar x); - EC_PrivateKey_Data(EC_Group group, std::span bytes); + EC_PrivateKey_Data(const EC_Group& group, std::span bytes); + + EC_PrivateKey_Data(const EC_PrivateKey_Data&) = default; + EC_PrivateKey_Data(EC_PrivateKey_Data&&) = default; + EC_PrivateKey_Data& operator=(const EC_PrivateKey_Data&) = default; + EC_PrivateKey_Data& operator=(EC_PrivateKey_Data&&) = default; + ~EC_PrivateKey_Data(); std::shared_ptr public_key(RandomNumberGenerator& rng, bool with_modular_inverse) const; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ecc_key/ecc_key.cpp botan3-3.12.0+dfsg/src/lib/pubkey/ecc_key/ecc_key.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/ecc_key/ecc_key.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ecc_key/ecc_key.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,5 +1,5 @@ /* -* ECC Key implemenation +* ECC Key implementation * (C) 2007 Manuel Hartl, FlexSecure GmbH * Falko Strenzke, FlexSecure GmbH * 2008-2010 Jack Lloyd @@ -9,6 +9,7 @@ #include +#include #include #include #include @@ -43,21 +44,21 @@ } // namespace #if defined(BOTAN_HAS_LEGACY_EC_POINT) -EC_PublicKey::EC_PublicKey(EC_Group group, const EC_Point& pub_point) { +EC_PublicKey::EC_PublicKey(const EC_Group& group, const EC_Point& pub_point) { auto pt = EC_AffinePoint(group, pub_point); - m_public_key = std::make_shared(std::move(group), std::move(pt)); - m_domain_encoding = default_encoding_for(domain()); + m_public_key = std::make_shared(group, std::move(pt)); + m_domain_encoding = default_encoding_for(domain()); // NOLINT(*-prefer-member-initializer) } #endif -EC_PublicKey::EC_PublicKey(EC_Group group, EC_AffinePoint pub_point) { - m_public_key = std::make_shared(std::move(group), std::move(pub_point)); - m_domain_encoding = default_encoding_for(domain()); +EC_PublicKey::EC_PublicKey(const EC_Group& group, const EC_AffinePoint& pub_point) { + m_public_key = std::make_shared(group, pub_point); + m_domain_encoding = default_encoding_for(domain()); // NOLINT(*-prefer-member-initializer) } EC_PublicKey::EC_PublicKey(const AlgorithmIdentifier& alg_id, std::span key_bits) { m_public_key = std::make_shared(EC_Group(alg_id.parameters()), key_bits); - m_domain_encoding = default_encoding_for(domain()); + m_domain_encoding = default_encoding_for(domain()); // NOLINT(*-prefer-member-initializer) } const EC_Group& EC_PublicKey::domain() const { @@ -128,24 +129,27 @@ * EC_PrivateKey constructor */ EC_PrivateKey::EC_PrivateKey(RandomNumberGenerator& rng, - EC_Group ec_group, + const EC_Group& ec_group, const BigInt& x, - bool with_modular_inverse) { + bool with_modular_inverse) : + m_with_modular_inverse(with_modular_inverse) { auto scalar = (x.is_zero()) ? EC_Scalar::random(ec_group, rng) : EC_Scalar::from_bigint(ec_group, x); - m_private_key = std::make_shared(std::move(ec_group), std::move(scalar)); + m_private_key = std::make_shared(ec_group, std::move(scalar)); m_public_key = m_private_key->public_key(rng, with_modular_inverse); m_domain_encoding = default_encoding_for(domain()); } -EC_PrivateKey::EC_PrivateKey(RandomNumberGenerator& rng, EC_Group ec_group, bool with_modular_inverse) { +EC_PrivateKey::EC_PrivateKey(RandomNumberGenerator& rng, const EC_Group& ec_group, bool with_modular_inverse) : + m_with_modular_inverse(with_modular_inverse) { auto scalar = EC_Scalar::random(ec_group, rng); - m_private_key = std::make_shared(std::move(ec_group), std::move(scalar)); + m_private_key = std::make_shared(ec_group, std::move(scalar)); m_public_key = m_private_key->public_key(rng, with_modular_inverse); m_domain_encoding = default_encoding_for(domain()); } -EC_PrivateKey::EC_PrivateKey(EC_Group ec_group, EC_Scalar x, bool with_modular_inverse) { - m_private_key = std::make_shared(std::move(ec_group), std::move(x)); +EC_PrivateKey::EC_PrivateKey(const EC_Group& ec_group, const EC_Scalar& x, bool with_modular_inverse) : + m_with_modular_inverse(with_modular_inverse) { + m_private_key = std::make_shared(ec_group, x); m_public_key = m_private_key->public_key(with_modular_inverse); m_domain_encoding = default_encoding_for(domain()); } @@ -171,20 +175,22 @@ EC_PrivateKey::EC_PrivateKey(const AlgorithmIdentifier& alg_id, std::span key_bits, - bool with_modular_inverse) { - EC_Group group(alg_id.parameters()); + bool with_modular_inverse) : + m_with_modular_inverse(with_modular_inverse) { + const EC_Group group(alg_id.parameters()); OID key_parameters; secure_vector private_key_bits; secure_vector public_key_bits; - BER_Decoder(key_bits) + BER_Decoder(key_bits, BER_Decoder::Limits::DER()) .start_sequence() .decode_and_check(1, "Unknown version code for ECC key") .decode(private_key_bits, ASN1_Type::OctetString) .decode_optional(key_parameters, ASN1_Type(0), ASN1_Class::ExplicitContextSpecific) .decode_optional_string(public_key_bits, ASN1_Type::BitString, 1, ASN1_Class::ExplicitContextSpecific) - .end_cons(); + .end_cons() + .verify_end(); m_private_key = std::make_shared(group, private_key_bits); @@ -202,7 +208,14 @@ return false; } - return EC_PublicKey::check_key(rng, strong); + if(!EC_PublicKey::check_key(rng, strong)) { + return false; + } + + // Verify that the public key is consistent with the private key. + // For ECKCDSA/ECGDSA the derivation is g^(x^-1), for all others it is g^x. + auto expected = m_private_key->public_key(m_with_modular_inverse); + return expected->public_key() == _public_ec_point(); } const BigInt& EC_PublicKey::get_int_field(std::string_view field) const { diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ecc_key/ecc_key.h botan3-3.12.0+dfsg/src/lib/pubkey/ecc_key/ecc_key.h --- botan3-3.7.1+dfsg/src/lib/pubkey/ecc_key/ecc_key.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ecc_key/ecc_key.h 2026-05-07 01:38:28.000000000 +0000 @@ -10,12 +10,16 @@ #ifndef BOTAN_ECC_PUBLIC_KEY_BASE_H_ #define BOTAN_ECC_PUBLIC_KEY_BASE_H_ -#include +#include #include #include namespace Botan { +class EC_AffinePoint; +class EC_Point; +class EC_Group; +class EC_Scalar; class EC_PublicKey_Data; class EC_PrivateKey_Data; @@ -112,7 +116,7 @@ * @param group EC domain parameters * @param pub_point public point on the curve */ - EC_PublicKey(EC_Group group, const EC_Point& pub_point); + EC_PublicKey(const EC_Group& group, const EC_Point& pub_point); #endif /** @@ -121,7 +125,7 @@ * @param group EC domain parameters * @param public_key public point on the curve */ - EC_PublicKey(EC_Group group, EC_AffinePoint public_key); + EC_PublicKey(const EC_Group& group, const EC_AffinePoint& public_key); /** * Load a public key. @@ -132,9 +136,9 @@ EC_PublicKey() = default; - std::shared_ptr m_public_key; - EC_Group_Encoding m_domain_encoding = EC_Group_Encoding::NamedCurve; - EC_Point_Format m_point_encoding = EC_Point_Format::Uncompressed; + std::shared_ptr m_public_key; // NOLINT(*non-private-member-variable*) + EC_Group_Encoding m_domain_encoding = EC_Group_Encoding::NamedCurve; // NOLINT(*non-private-member-variable*) + EC_Point_Format m_point_encoding = EC_Point_Format::Uncompressed; // NOLINT(*non-private-member-variable*) }; /** @@ -181,7 +185,10 @@ * TODO: Remove, once the respective deprecated constructors of the * concrete ECC algorithms is removed. */ - EC_PrivateKey(RandomNumberGenerator& rng, EC_Group group, const BigInt& x, bool with_modular_inverse = false); + EC_PrivateKey(RandomNumberGenerator& rng, + const EC_Group& group, + const BigInt& x, + bool with_modular_inverse = false); /** * Creates a new private key @@ -190,7 +197,7 @@ * multiplying the base point with the modular inverse of x (as in ECGDSA * and ECKCDSA), otherwise by multiplying directly with x (as in ECDSA). */ - EC_PrivateKey(RandomNumberGenerator& rng, EC_Group group, bool with_modular_inverse = false); + EC_PrivateKey(RandomNumberGenerator& rng, const EC_Group& group, bool with_modular_inverse = false); /** * Load a EC private key from the secret scalar @@ -199,7 +206,7 @@ * multiplying the base point with the modular inverse of x (as in ECGDSA * and ECKCDSA), otherwise by multiplying directly with x (as in ECDSA). */ - EC_PrivateKey(EC_Group group, EC_Scalar scalar, bool with_modular_inverse = false); + EC_PrivateKey(const EC_Group& group, const EC_Scalar& scalar, bool with_modular_inverse = false); /* * Creates a new private key object from the @@ -214,9 +221,10 @@ std::span key_bits, bool with_modular_inverse = false); - EC_PrivateKey() = default; + EC_PrivateKey() : m_with_modular_inverse(false) {} - std::shared_ptr m_private_key; + std::shared_ptr m_private_key; // NOLINT(*non-private-member-variable*) + bool m_with_modular_inverse; // NOLINT(*non-private-member-variable*) }; BOTAN_DIAGNOSTIC_POP diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ecdh/ecdh.cpp botan3-3.12.0+dfsg/src/lib/pubkey/ecdh/ecdh.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/ecdh/ecdh.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ecdh/ecdh.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,5 +1,5 @@ /* -* ECDH implemenation +* ECDH implementation * (C) 2007 Manuel Hartl, FlexSecure GmbH * 2007 Falko Strenzke, FlexSecure GmbH * 2008-2010 Jack Lloyd @@ -9,6 +9,8 @@ #include +#include +#include #include namespace Botan { @@ -33,20 +35,33 @@ size_t agreed_value_size() const override { return m_group.get_p_bytes(); } secure_vector raw_agree(const uint8_t w[], size_t w_len) override { - if(m_group.has_cofactor()) { + const auto input_point = [&] { + if(m_group.has_cofactor()) { #if defined(BOTAN_HAS_LEGACY_EC_POINT) - EC_AffinePoint input_point(m_group, m_group.get_cofactor() * m_group.OS2ECP(w, w_len)); - return input_point.mul_x_only(m_l_times_priv, m_rng, m_ws); + return EC_AffinePoint(m_group, m_group.get_cofactor() * m_group.OS2ECP(w, w_len)); #else - throw Not_Implemented("Support for DH with cofactor adjustment not available in this build configuration"); + throw Not_Implemented( + "Support for DH with cofactor adjustment not available in this build configuration"); #endif - } else { - if(auto input_point = EC_AffinePoint::deserialize(m_group, {w, w_len})) { - return input_point->mul_x_only(m_l_times_priv, m_rng, m_ws); } else { - throw Decoding_Error("ECDH - Invalid elliptic curve point"); + if(auto point = EC_AffinePoint::deserialize(m_group, {w, w_len})) { + return *point; + } else { + throw Decoding_Error("ECDH - Invalid elliptic curve point: not on curve"); + } } + }(); + + // Typical specs (such as BSI's TR-03111 Section 4.3.1) require that + // we check the resulting point of the multiplication to not be the + // point at infinity. However, since we ensure that our ECC private + // scalar can never be zero, checking the peer's input point is + // equivalent. + if(input_point.is_identity()) { + throw Decoding_Error("ECDH - Invalid elliptic curve point: identity"); } + + return input_point.mul_x_only(m_l_times_priv, m_rng); } private: @@ -65,7 +80,6 @@ const EC_Group m_group; const EC_Scalar m_l_times_priv; RandomNumberGenerator& m_rng; - std::vector m_ws; }; } // namespace diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ecdh/ecdh.h botan3-3.12.0+dfsg/src/lib/pubkey/ecdh/ecdh.h --- botan3-3.7.1+dfsg/src/lib/pubkey/ecdh/ecdh.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ecdh/ecdh.h 2026-05-07 01:38:28.000000000 +0000 @@ -75,8 +75,8 @@ BOTAN_DIAGNOSTIC_IGNORE_INHERITED_VIA_DOMINANCE class BOTAN_PUBLIC_API(2, 0) ECDH_PrivateKey final : public ECDH_PublicKey, - public EC_PrivateKey, - public PK_Key_Agreement_Key { + public virtual EC_PrivateKey, + public virtual PK_Key_Agreement_Key { public: /** * Load a private key. @@ -91,14 +91,14 @@ * @param group curve parameters to bu used for this key * @param x the private key */ - ECDH_PrivateKey(EC_Group group, EC_Scalar x) : EC_PrivateKey(std::move(group), std::move(x)) {} + ECDH_PrivateKey(const EC_Group& group, const EC_Scalar& x) : EC_PrivateKey(group, x) {} /** * Create a new private key * @param rng a random number generator * @param group parameters to used for this key */ - ECDH_PrivateKey(RandomNumberGenerator& rng, EC_Group group) : EC_PrivateKey(rng, std::move(group)) {} + ECDH_PrivateKey(RandomNumberGenerator& rng, const EC_Group& group) : EC_PrivateKey(rng, group) {} /** * Generate a new private key @@ -112,10 +112,12 @@ std::unique_ptr public_key() const override; + // NOLINTNEXTLINE(bugprone-derived-method-shadowing-base-method) std::vector public_value() const override { return ECDH_PublicKey::public_value(EC_Point_Format::Uncompressed); } + // NOLINTNEXTLINE(bugprone-derived-method-shadowing-base-method) std::vector public_value(EC_Point_Format type) const { return ECDH_PublicKey::public_value(type); } std::unique_ptr create_key_agreement_op(RandomNumberGenerator& rng, diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ecdsa/ecdsa.cpp botan3-3.12.0+dfsg/src/lib/pubkey/ecdsa/ecdsa.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/ecdsa/ecdsa.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ecdsa/ecdsa.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,5 +1,5 @@ /* -* ECDSA implemenation +* ECDSA implementation * (C) 2007 Manuel Hartl, FlexSecure GmbH * 2007 Falko Strenzke, FlexSecure GmbH * 2008-2010,2015,2016,2018,2024 Jack Lloyd @@ -10,6 +10,7 @@ #include +#include #include #include @@ -38,7 +39,7 @@ } const uint8_t y_odd = v % 2; - const uint8_t add_order = v >> 1; + const bool add_order = (v >> 1) == 0x01; const size_t p_bytes = group.get_p_bytes(); BigInt x = r; @@ -60,7 +61,7 @@ const auto r_inv = EC_Scalar::from_bigint(group, r).invert_vartime(); - EC_Group::Mul2Table GR_mul(R.value()); + const EC_Group::Mul2Table GR_mul(R.value()); if(auto egsr = GR_mul.mul2_vartime(ne * r_inv, ss * r_inv)) { return egsr.value(); } @@ -76,6 +77,10 @@ const EC_Group& group, const std::vector& msg, const BigInt& r, const BigInt& s, uint8_t v) : EC_PublicKey(group, recover_ecdsa_public_key(group, msg, r, s, v)) {} +std::optional ECDSA_PublicKey::_signature_element_size_for_DER_encoding() const { + return domain().get_order_bytes(); +} + std::unique_ptr ECDSA_PublicKey::generate_another(RandomNumberGenerator& rng) const { return std::make_unique(rng, domain()); } @@ -125,8 +130,7 @@ PK_Ops::Signature_with_Hash(padding), m_group(ecdsa.domain()), m_x(ecdsa._private_key()), - m_b(EC_Scalar::random(m_group, rng)), - m_b_inv(m_b.invert()) { + m_b(EC_Scalar::random(m_group, rng)) { #if defined(BOTAN_HAS_RFC6979_GENERATOR) m_rfc6979 = std::make_unique( this->rfc6979_hash_function(), m_group.get_order_bits(), ecdsa._private_key()); @@ -147,10 +151,7 @@ std::unique_ptr m_rfc6979; #endif - std::vector m_ws; - EC_Scalar m_b; - EC_Scalar m_b_inv; }; AlgorithmIdentifier ECDSA_Signature_Operation::algorithm_identifier() const { @@ -168,20 +169,37 @@ const auto k = EC_Scalar::random(m_group, rng); #endif - const auto r = EC_Scalar::gk_x_mod_order(k, rng, m_ws); - - // Blind the inversion of k - const auto k_inv = (m_b * k).invert() * m_b; - /* - * Blind the input message and compute x*r+m as (x*r*b + m*b)/b + * Blind the inputs + * + * Here we are computing (x*r+m)/k + * + * Instead have a random b and compute (k*b)^-1 + * + * Then compute (x*r+m) as (x*r*b + m*b) + * + * Finally (x*r*b + m*b)/(k*b) = (x*r+m)/k + * + * This effectively blinds both the inversion as well as the various scalar + * multiplications. All of these operations should be constant-time anyway but + * blinding is very cheap and may help if either the compiler introduces + * variable-time behavior, or for the case of EM/power side channel attacks [1]. + * + * [1] But note that such attacks are currently outside of Botan's threat model. + * + * NOTE: if you change anything here also update ECDSA_Timing_Test + * in cli/timing_tests.cpp to use the same formulas */ - m_b.square_self(); - m_b_inv.square_self(); + const auto r = EC_Scalar::gk_x_mod_order(k, rng); + + const auto k_inv = (m_b * k).invert(); const auto xr_m = ((m_x * m_b) * r) + (m * m_b); - const auto s = (k_inv * xr_m) * m_b_inv; + const auto s = (k_inv * xr_m); + + // Generate the next blinding value via modular squaring + m_b.square_self(); // With overwhelming probability, a bug rather than actual zero r/s if(r.is_zero() || s.is_zero()) { diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ecdsa/ecdsa.h botan3-3.12.0+dfsg/src/lib/pubkey/ecdsa/ecdsa.h --- botan3-3.7.1+dfsg/src/lib/pubkey/ecdsa/ecdsa.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ecdsa/ecdsa.h 2026-05-07 01:38:28.000000000 +0000 @@ -48,8 +48,8 @@ * See SEC section 4.6.1 * @param group the elliptic curve group * @param msg the message - * @param r the r paramter of the signature - * @param s the s paramter of the signature + * @param r the r parameter of the signature + * @param s the s parameter of the signature * @param v the recovery ID */ ECDSA_PublicKey( @@ -61,9 +61,7 @@ */ std::string algo_name() const override { return "ECDSA"; } - std::optional _signature_element_size_for_DER_encoding() const override { - return domain().get_order_bytes(); - } + std::optional _signature_element_size_for_DER_encoding() const override; bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::Signature); } @@ -104,14 +102,14 @@ * @param group curve parameters to bu used for this key * @param x the private key */ - ECDSA_PrivateKey(EC_Group group, EC_Scalar x) : EC_PrivateKey(std::move(group), std::move(x)) {} + ECDSA_PrivateKey(const EC_Group& group, const EC_Scalar& x) : EC_PrivateKey(group, x) {} /** * Create a new private key * @param rng a random number generator * @param group parameters to used for this key */ - ECDSA_PrivateKey(RandomNumberGenerator& rng, EC_Group group) : EC_PrivateKey(rng, std::move(group)) {} + ECDSA_PrivateKey(RandomNumberGenerator& rng, const EC_Group& group) : EC_PrivateKey(rng, group) {} /** * Create a private key. @@ -123,7 +121,7 @@ ECDSA_PrivateKey(RandomNumberGenerator& rng, const EC_Group& group, const BigInt& x) : EC_PrivateKey(rng, group, x) {} - bool check_key(RandomNumberGenerator& rng, bool) const override; + bool check_key(RandomNumberGenerator& rng, bool strong) const override; std::unique_ptr public_key() const override; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ecgdsa/ecgdsa.cpp botan3-3.12.0+dfsg/src/lib/pubkey/ecgdsa/ecgdsa.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/ecgdsa/ecgdsa.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ecgdsa/ecgdsa.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,6 +8,7 @@ #include +#include #include #include @@ -36,8 +37,8 @@ */ class ECGDSA_Signature_Operation final : public PK_Ops::Signature_with_Hash { public: - ECGDSA_Signature_Operation(const ECGDSA_PrivateKey& ecgdsa, std::string_view emsa) : - PK_Ops::Signature_with_Hash(emsa), m_group(ecgdsa.domain()), m_x(ecgdsa._private_key()) {} + ECGDSA_Signature_Operation(const ECGDSA_PrivateKey& ecgdsa, std::string_view hash_fn) : + PK_Ops::Signature_with_Hash(hash_fn), m_group(ecgdsa.domain()), m_x(ecgdsa._private_key()) {} std::vector raw_sign(std::span msg, RandomNumberGenerator& rng) override; @@ -48,7 +49,6 @@ private: const EC_Group m_group; const EC_Scalar m_x; - std::vector m_ws; }; AlgorithmIdentifier ECGDSA_Signature_Operation::algorithm_identifier() const { @@ -62,7 +62,7 @@ const auto k = EC_Scalar::random(m_group, rng); - const auto r = EC_Scalar::gk_x_mod_order(k, rng, m_ws); + const auto r = EC_Scalar::gk_x_mod_order(k, rng); const auto s = m_x * ((k * r) - m); @@ -113,6 +113,10 @@ } // namespace +std::optional ECGDSA_PublicKey::_signature_element_size_for_DER_encoding() const { + return domain().get_order_bytes(); +} + std::unique_ptr ECGDSA_PublicKey::generate_another(RandomNumberGenerator& rng) const { return std::make_unique(rng, domain()); } diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ecgdsa/ecgdsa.h botan3-3.12.0+dfsg/src/lib/pubkey/ecgdsa/ecgdsa.h --- botan3-3.7.1+dfsg/src/lib/pubkey/ecgdsa/ecgdsa.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ecgdsa/ecgdsa.h 2026-05-07 01:38:28.000000000 +0000 @@ -47,9 +47,7 @@ */ std::string algo_name() const override { return "ECGDSA"; } - std::optional _signature_element_size_for_DER_encoding() const override { - return domain().get_order_bytes(); - } + std::optional _signature_element_size_for_DER_encoding() const override; std::unique_ptr generate_another(RandomNumberGenerator& rng) const final; @@ -88,14 +86,14 @@ * @param group curve parameters to bu used for this key * @param x the private key */ - ECGDSA_PrivateKey(EC_Group group, EC_Scalar x) : EC_PrivateKey(std::move(group), std::move(x), true) {} + ECGDSA_PrivateKey(const EC_Group& group, const EC_Scalar& x) : EC_PrivateKey(group, x, true) {} /** * Create a new private key * @param rng a random number generator * @param group parameters to used for this key */ - ECGDSA_PrivateKey(RandomNumberGenerator& rng, EC_Group group) : EC_PrivateKey(rng, std::move(group), true) {} + ECGDSA_PrivateKey(RandomNumberGenerator& rng, const EC_Group& group) : EC_PrivateKey(rng, group, true) {} /** * Generate a new private key. @@ -109,7 +107,7 @@ std::unique_ptr public_key() const override; - bool check_key(RandomNumberGenerator& rng, bool) const override; + bool check_key(RandomNumberGenerator& rng, bool strong) const override; std::unique_ptr create_signature_op(RandomNumberGenerator& rng, std::string_view params, diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ecies/ecies.cpp botan3-3.12.0+dfsg/src/lib/pubkey/ecies/ecies.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/ecies/ecies.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ecies/ecies.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -13,11 +13,10 @@ #include #include #include -#include #include +#include #include #include -#include namespace Botan { @@ -25,15 +24,18 @@ /** * Private key type for ECIES_ECDH_KA_Operation +* +* TODO(Botan4) this can be removed once cofactor support is removed from ECDH */ BOTAN_DIAGNOSTIC_PUSH BOTAN_DIAGNOSTIC_IGNORE_INHERITED_VIA_DOMINANCE -class ECIES_PrivateKey final : public EC_PrivateKey, - public PK_Key_Agreement_Key { +class ECIES_PrivateKey final : public virtual EC_PrivateKey, + public virtual PK_Key_Agreement_Key { public: explicit ECIES_PrivateKey(const ECDH_PrivateKey& private_key) : + // NOLINTNEXTLINE(*-slicing) EC_PublicKey(private_key), EC_PrivateKey(private_key), PK_Key_Agreement_Key(), m_key(private_key) {} std::vector public_value() const override { return m_key.public_value(); } @@ -60,6 +62,8 @@ /** * Implements ECDH key agreement without using the cofactor mode +* +* TODO(Botan4) this can be removed once cofactor support is removed from ECDH */ class ECIES_ECDH_KA_Operation final : public PK_Ops::Key_Agreement_with_KDF { public: @@ -71,7 +75,7 @@ secure_vector raw_agree(const uint8_t w[], size_t w_len) override { const EC_Group& group = m_key.domain(); if(auto input_point = EC_AffinePoint::deserialize(group, {w, w_len})) { - return input_point->mul(m_key._private_key(), m_rng, m_ws).x_bytes(); + return input_point->mul(m_key._private_key(), m_rng).x_bytes(); } else { throw Decoding_Error("ECIES - Invalid elliptic curve point"); } @@ -80,7 +84,6 @@ private: ECIES_PrivateKey m_key; RandomNumberGenerator& m_rng; - std::vector m_ws; }; std::unique_ptr ECIES_PrivateKey::create_key_agreement_op(RandomNumberGenerator& rng, @@ -97,6 +100,8 @@ * @param ecies_params settings for ecies * @param for_encryption disable cofactor mode if the secret will be used for encryption * (according to ISO 18033 cofactor mode is only used during decryption) +* +* TODO(Botan4) this entire function can be removed once cofactor support is gone */ PK_Key_Agreement create_key_agreement(const PK_Key_Agreement_Key& private_key, const ECIES_KA_Params& ecies_params, @@ -113,7 +118,7 @@ throw Invalid_Argument("ECIES: cofactor, old cofactor and check mode are only supported for ECDH_PrivateKey"); } - if(ecdh_key && (for_encryption || !ecies_params.cofactor_mode())) { + if(ecdh_key != nullptr && (for_encryption || !ecies_params.cofactor_mode())) { // ECDH_KA_Operation uses cofactor mode: use own key agreement method if cofactor should not be used. return PK_Key_Agreement(ECIES_PrivateKey(*ecdh_key), rng, "Raw"); } @@ -138,13 +143,14 @@ throw Invalid_Argument("ECIES: other public key point is zero"); } - auto kdf = KDF::create_or_throw(m_params.kdf_spec()); + auto kdf = KDF::create_or_throw(m_params.kdf()); EC_Point other_point = other_public_key_point; // ISO 18033: step b - if(m_params.old_cofactor_mode() && m_params.domain().has_cofactor()) { - other_point *= m_params.domain().get_cofactor(); + // TODO(Botan4) remove when cofactor support is removed + if(m_params.old_cofactor_mode() && m_params.group().has_cofactor()) { + other_point *= m_params.group().get_cofactor(); } secure_vector derivation_input; @@ -155,11 +161,11 @@ } // ISO 18033: encryption step f / decryption step h - std::vector other_public_key_bin = other_point.encode(m_params.compression_type()); + std::vector other_public_key_bin = other_point.encode(m_params.point_format()); // Note: the argument `m_params.secret_length()` passed for `key_len` will only be used by providers because // "Raw" is passed to the `PK_Key_Agreement` if the implementation of botan is used. const SymmetricKey peh = - m_ka.derive_key(m_params.domain().get_order_bytes(), other_public_key_bin.data(), other_public_key_bin.size()); + m_ka.derive_key(m_params.group().get_order_bytes(), other_public_key_bin.data(), other_public_key_bin.size()); derivation_input.insert(derivation_input.end(), peh.begin(), peh.end()); // ISO 18033: encryption step g / decryption step i @@ -174,16 +180,18 @@ const EC_AffinePoint& other_public_key_point) const { BOTAN_ARG_CHECK(!other_public_key_point.is_identity(), "ECIES: peer public key point is the identity element"); - auto kdf = KDF::create_or_throw(m_params.kdf_spec()); + auto kdf = KDF::create_or_throw(m_params.kdf()); auto other_point = other_public_key_point; + const auto& group = m_params.group(); + // ISO 18033: step b - if(m_params.old_cofactor_mode() && m_params.domain().has_cofactor()) { - std::vector ws; + // TODO(Botan4) remove when cofactor support is removed + if(m_params.old_cofactor_mode() && group.has_cofactor()) { Null_RNG null_rng; - auto cofactor = EC_Scalar::from_bigint(m_params.domain(), m_params.domain().get_cofactor()); - other_point = other_point.mul(cofactor, null_rng, ws); + auto cofactor = EC_Scalar::from_bigint(group, group.get_cofactor()); + other_point = other_point.mul(cofactor, null_rng); } secure_vector derivation_input; @@ -194,33 +202,48 @@ } // ISO 18033: encryption step f / decryption step h - std::vector other_public_key_bin = other_point.serialize(m_params.compression_type()); + std::vector other_public_key_bin = other_point.serialize(m_params.point_format()); // Note: the argument `m_params.secret_length()` passed for `key_len` will only be used by providers because // "Raw" is passed to the `PK_Key_Agreement` if the implementation of botan is used. const SymmetricKey peh = - m_ka.derive_key(m_params.domain().get_order_bytes(), other_public_key_bin.data(), other_public_key_bin.size()); + m_ka.derive_key(m_params.group().get_order_bytes(), other_public_key_bin.data(), other_public_key_bin.size()); derivation_input.insert(derivation_input.end(), peh.begin(), peh.end()); // ISO 18033: encryption step g / decryption step i return SymmetricKey(kdf->derive_key(m_params.secret_length(), derivation_input)); } -ECIES_KA_Params::ECIES_KA_Params(const EC_Group& domain, - std::string_view kdf_spec, - size_t length, - EC_Point_Format compression_type, - ECIES_Flags flags) : - m_domain(domain), m_kdf_spec(kdf_spec), m_length(length), m_compression_mode(compression_type), m_flags(flags) {} +ECIES_KA_Params::ECIES_KA_Params( + const EC_Group& group, std::string_view kdf, size_t length, EC_Point_Format point_format, ECIES_Flags flags) : + m_group(group), + m_kdf(kdf), + m_length(length), + m_point_format(point_format), + m_single_hash_mode((flags & ECIES_Flags::SingleHashMode) == ECIES_Flags::SingleHashMode), + m_check_mode((flags & ECIES_Flags::CheckMode) == ECIES_Flags::CheckMode), + m_cofactor_mode((flags & ECIES_Flags::CofactorMode) == ECIES_Flags::CofactorMode), + m_old_cofactor_mode((flags & ECIES_Flags::OldCofactorMode) == ECIES_Flags::OldCofactorMode) {} + +ECIES_KA_Params::ECIES_KA_Params( + const EC_Group& group, std::string_view kdf, size_t length, EC_Point_Format point_format, bool single_hash_mode) : + m_group(group), + m_kdf(kdf), + m_length(length), + m_point_format(point_format), + m_single_hash_mode(single_hash_mode), + m_check_mode(true), + m_cofactor_mode(false), + m_old_cofactor_mode(false) {} -ECIES_System_Params::ECIES_System_Params(const EC_Group& domain, - std::string_view kdf_spec, +ECIES_System_Params::ECIES_System_Params(const EC_Group& group, + std::string_view kdf, std::string_view dem_algo_spec, size_t dem_key_len, std::string_view mac_spec, size_t mac_key_len, - EC_Point_Format compression_type, + EC_Point_Format point_format, ECIES_Flags flags) : - ECIES_KA_Params(domain, kdf_spec, dem_key_len + mac_key_len, compression_type, flags), + ECIES_KA_Params(group, kdf, dem_key_len + mac_key_len, point_format, flags), m_dem_spec(dem_algo_spec), m_dem_keylen(dem_key_len), m_mac_spec(mac_spec), @@ -231,20 +254,19 @@ } } -ECIES_System_Params::ECIES_System_Params(const EC_Group& domain, - std::string_view kdf_spec, +ECIES_System_Params::ECIES_System_Params(const EC_Group& group, + std::string_view kdf, std::string_view dem_algo_spec, size_t dem_key_len, std::string_view mac_spec, - size_t mac_key_len) : - ECIES_System_Params(domain, - kdf_spec, - dem_algo_spec, - dem_key_len, - mac_spec, - mac_key_len, - EC_Point_Format::Uncompressed, - ECIES_Flags::None) {} + size_t mac_key_len, + EC_Point_Format point_format, + bool single_hash_mode) : + ECIES_KA_Params(group, kdf, dem_key_len + mac_key_len, point_format, single_hash_mode), + m_dem_spec(dem_algo_spec), + m_dem_keylen(dem_key_len), + m_mac_spec(mac_spec), + m_mac_keylen(mac_key_len) {} std::unique_ptr ECIES_System_Params::create_mac() const { return MessageAuthenticationCode::create_or_throw(m_mac_spec); @@ -262,15 +284,12 @@ RandomNumberGenerator& rng) : m_ka(private_key, ecies_params, true, rng), m_params(ecies_params), - m_eph_public_key_bin(private_key.public_value()), // returns the uncompressed public key, see conversion below - m_iv(), - m_other_point(), - m_label() { - if(ecies_params.compression_type() != EC_Point_Format::Uncompressed) { + m_eph_public_key_bin(private_key.public_value()) { + if(ecies_params.point_format() != EC_Point_Format::Uncompressed) { // ISO 18033: step d // convert only if necessary; m_eph_public_key_bin has been initialized with the uncompressed format m_eph_public_key_bin = - EC_AffinePoint(m_params.domain(), m_eph_public_key_bin).serialize(ecies_params.compression_type()); + EC_AffinePoint(m_params.group(), m_eph_public_key_bin).serialize(ecies_params.point_format()); } m_mac = m_params.create_mac(); m_cipher = m_params.create_cipher(Cipher_Dir::Encryption); @@ -280,7 +299,7 @@ * ECIES_Encryptor Constructor */ ECIES_Encryptor::ECIES_Encryptor(RandomNumberGenerator& rng, const ECIES_System_Params& ecies_params) : - ECIES_Encryptor(ECDH_PrivateKey(rng, ecies_params.domain()), ecies_params, rng) {} + ECIES_Encryptor(ECDH_PrivateKey(rng, ecies_params.group()), ecies_params, rng) {} size_t ECIES_Encryptor::maximum_input_size() const { /* @@ -333,14 +352,19 @@ ECIES_Decryptor::ECIES_Decryptor(const PK_Key_Agreement_Key& key, const ECIES_System_Params& ecies_params, RandomNumberGenerator& rng) : - m_ka(key, ecies_params, false, rng), m_params(ecies_params), m_iv(), m_label() { - // ISO 18033: "If v > 1 and CheckMode = 0, then we must have gcd(u, v) = 1." (v = index, u= order) - if(!ecies_params.check_mode()) { - const BigInt& cofactor = m_params.domain().get_cofactor(); - if(cofactor > 1 && gcd(cofactor, m_params.domain().get_order()) != 1) { - throw Invalid_Argument("ECIES: gcd of cofactor and order must be 1 if check_mode is 0"); - } - } + m_ka(key, ecies_params, false, rng), m_params(ecies_params) { + /* + ISO 18033: "If v > 1 and CheckMode = 0, then we must have gcd(u, v) = 1." (v = index, u= order) + + We skip this check because even if CheckMode = 0 we actually do check that + the point is valid. In addition the check from ISO 18033 is pretty odd; u is + the _prime_ order subgroup, and v is the cofactor. For gcd(u, v) > 1 to occur + the cofactor would have to be a multiple of the group order, implying that + the overall group was at least the square of the group order. Such a curve + would also break our assumption that one can check for membership in the + prime order subgroup by multiplying by the group order and checking for the + identity. + */ m_mac = m_params.create_mac(); m_cipher = m_params.create_cipher(Cipher_Dir::Decryption); @@ -360,7 +384,7 @@ } // namespace size_t ECIES_Decryptor::plaintext_length(size_t ctext_len) const { - const size_t point_size = compute_point_size(m_params.domain(), m_params.compression_type()); + const size_t point_size = compute_point_size(m_params.group(), m_params.point_format()); const size_t overhead = point_size + m_mac->output_length(); if(ctext_len < overhead) { @@ -374,7 +398,7 @@ * ECIES Decryption according to ISO 18033-2 */ secure_vector ECIES_Decryptor::do_decrypt(uint8_t& valid_mask, const uint8_t in[], size_t in_len) const { - const size_t point_size = compute_point_size(m_params.domain(), m_params.compression_type()); + const size_t point_size = compute_point_size(m_params.group(), m_params.point_format()); if(in_len < point_size + m_mac->output_length()) { throw Decoding_Error("ECIES decryption: ciphertext is too short"); @@ -386,7 +410,7 @@ const std::vector mac_data(in + in_len - m_mac->output_length(), in + in_len); // ISO 18033: step a - auto other_public_key = EC_AffinePoint(m_params.domain(), other_public_key_bin); + auto other_public_key = EC_AffinePoint(m_params.group(), other_public_key_bin); // ISO 18033: step b would check if other_public_key is on the curve iff check_mode is on // but we ignore this and always check if the point is on the curve @@ -402,9 +426,9 @@ m_mac->update(m_label); } const secure_vector calculated_mac = m_mac->final(); - valid_mask = CT::is_equal(mac_data.data(), calculated_mac.data(), mac_data.size()).value(); + valid_mask = CT::is_equal(mac_data, calculated_mac).value(); - if(valid_mask) { + if(valid_mask == 0xFF) { // decrypt data m_cipher->set_key(SymmetricKey(secret_key.begin(), m_params.dem_keylen())); diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ecies/ecies.h botan3-3.12.0+dfsg/src/lib/pubkey/ecies/ecies.h --- botan3-3.7.1+dfsg/src/lib/pubkey/ecies/ecies.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ecies/ecies.h 2026-05-07 01:38:28.000000000 +0000 @@ -19,6 +19,7 @@ #include #include #include +#include #include #if defined(BOTAN_HAS_LEGACY_EC_POINT) @@ -29,15 +30,29 @@ class RandomNumberGenerator; -enum class ECIES_Flags : uint32_t { +/** +* Flags controlling ECIES operation +* +* Two of the flags are related to how cofactors are handled. +* Support for cofactors is deprecated and will be removed in Botan4. +* +* The CheckMode flag is completely ignored; we always check that the point is +* valid. +* +* TODO(Botan4) remove this enum +*/ +enum class ECIES_Flags : uint8_t { None = 0, /// if set: prefix the input of the (ecdh) key agreement with the encoded (ephemeral) public key SingleHashMode = 1, /// (decryption only) if set: use cofactor multiplication during (ecdh) key agreement + /// This only matters if the curve has a cofactor CofactorMode = 2, - /// if set: use ecdhc instead of ecdh + /// if set: use ecdhc instead of ecdh. + /// This only matters if the curve has a cofactor OldCofactorMode = 4, /// (decryption only) if set: test if the (ephemeral) public key is on the curve + /// Note that we actually ignore this flag and always check the key CheckMode = 8, NONE BOTAN_DEPRECATED("Use None") = None, @@ -62,89 +77,130 @@ class BOTAN_PUBLIC_API(2, 0) ECIES_KA_Params { public: /** - * @param domain ec domain parameters of the involved ec keys + * @param group ec domain parameters of the involved ec keys + * @param kdf_spec name of the key derivation function + * @param length length of the secret to be derived + * @param point_format format of encoded keys (affects the secret derivation if single_hash_mode is used) + * @param single_hash_mode prefix the KDF input with the ephemeral public key (recommended) + */ + ECIES_KA_Params(const EC_Group& group, + std::string_view kdf_spec, + size_t length, + EC_Point_Format point_format = EC_Point_Format::Uncompressed, + bool single_hash_mode = true); + + /** + * @param group ec domain parameters of the involved ec keys * @param kdf_spec name of the key derivation function * @param length length of the secret to be derived - * @param compression_type format of encoded keys (affects the secret derivation if single_hash_mode is used) + * @param point_format format of encoded keys (affects the secret derivation if single_hash_mode is used) * @param flags options, see documentation of ECIES_Flags + * + * This constructor makes sense only if you are using the CofactorMode or + * OldCofactorMode flags. Support for cofactors in EC_Group is deprecated + * and will be removed in Botan4. + * + * TODO(Botan4) remove this constructor when cofactor support is removed */ - ECIES_KA_Params(const EC_Group& domain, + BOTAN_DEPRECATED("Prefer other constructor, see header comment") + ECIES_KA_Params(const EC_Group& group, std::string_view kdf_spec, size_t length, - EC_Point_Format compression_type, + EC_Point_Format point_format, ECIES_Flags flags); ECIES_KA_Params(const ECIES_KA_Params&) = default; + ECIES_KA_Params(ECIES_KA_Params&&) = default; ECIES_KA_Params& operator=(const ECIES_KA_Params&) = delete; + ECIES_KA_Params& operator=(ECIES_KA_Params&&) = delete; virtual ~ECIES_KA_Params() = default; - inline const EC_Group& domain() const { return m_domain; } + const EC_Group& group() const { return m_group; } - inline size_t secret_length() const { return m_length; } + size_t secret_length() const { return m_length; } - inline bool single_hash_mode() const { - return (m_flags & ECIES_Flags::SingleHashMode) == ECIES_Flags::SingleHashMode; - } + bool single_hash_mode() const { return m_single_hash_mode; } - inline bool cofactor_mode() const { return (m_flags & ECIES_Flags::CofactorMode) == ECIES_Flags::CofactorMode; } + // TODO(Botan4) remove this when cofactor support is removed + bool cofactor_mode() const { return m_cofactor_mode; } - inline bool old_cofactor_mode() const { - return (m_flags & ECIES_Flags::OldCofactorMode) == ECIES_Flags::OldCofactorMode; - } + // TODO(Botan4) remove this when cofactor support is removed + bool old_cofactor_mode() const { return m_old_cofactor_mode; } + + // TODO(Botan4) remove this when cofactor support is removed + bool check_mode() const { return m_check_mode; } - inline bool check_mode() const { return (m_flags & ECIES_Flags::CheckMode) == ECIES_Flags::CheckMode; } + EC_Point_Format point_format() const { return m_point_format; } - inline EC_Point_Format compression_type() const { return m_compression_mode; } + const std::string& kdf() const { return m_kdf; } - const std::string& kdf_spec() const { return m_kdf_spec; } + BOTAN_DEPRECATED("Use kdf") const std::string& kdf_spec() const { return kdf(); } + + BOTAN_DEPRECATED("Use group") const EC_Group& domain() const { return group(); } + + BOTAN_DEPRECATED("Use point_format") EC_Point_Format compression_type() const { return point_format(); } private: - const EC_Group m_domain; - const std::string m_kdf_spec; + const EC_Group m_group; + const std::string m_kdf; const size_t m_length; - const EC_Point_Format m_compression_mode; - const ECIES_Flags m_flags; + const EC_Point_Format m_point_format; + const bool m_single_hash_mode; + const bool m_check_mode; // TODO(Botan4) remove this field + const bool m_cofactor_mode; // TODO(Botan4) remove this field + const bool m_old_cofactor_mode; // TODO(Botan4) remove this field }; class BOTAN_PUBLIC_API(2, 0) ECIES_System_Params final : public ECIES_KA_Params { public: /** - * @param domain ec domain parameters of the involved ec keys + * @param group ec domain parameters of the involved ec keys * @param kdf_spec name of the key derivation function * @param dem_algo_spec name of the data encryption method * @param dem_key_len length of the key used for the data encryption method * @param mac_spec name of the message authentication code * @param mac_key_len length of the key used for the message authentication code */ - ECIES_System_Params(const EC_Group& domain, + ECIES_System_Params(const EC_Group& group, std::string_view kdf_spec, std::string_view dem_algo_spec, size_t dem_key_len, std::string_view mac_spec, - size_t mac_key_len); + size_t mac_key_len, + EC_Point_Format point_format = EC_Point_Format::Uncompressed, + bool single_hash_mode = false); /** - * @param domain ec domain parameters of the involved ec keys + * @param group ec domain parameters of the involved ec keys * @param kdf_spec name of the key derivation function * @param dem_algo_spec name of the data encryption method * @param dem_key_len length of the key used for the data encryption method * @param mac_spec name of the message authentication code * @param mac_key_len length of the key used for the message authentication code - * @param compression_type format of encoded keys (affects the secret derivation if single_hash_mode is used) + * @param point_format format of encoded keys (affects the secret derivation if single_hash_mode is used) * @param flags options, see documentation of ECIES_Flags + * + * This constructor makes sense only if you are using the CofactorMode or + * OldCofactorMode flags. Support for cofactors in EC_Group is deprecated + * and will be removed in Botan4. + * + * TODO(Botan4) remove this constructor when cofactor support is removed */ - ECIES_System_Params(const EC_Group& domain, + BOTAN_DEPRECATED("Prefer other constructor, see header comment") + ECIES_System_Params(const EC_Group& group, std::string_view kdf_spec, std::string_view dem_algo_spec, size_t dem_key_len, std::string_view mac_spec, size_t mac_key_len, - EC_Point_Format compression_type, + EC_Point_Format point_format, ECIES_Flags flags); ECIES_System_Params(const ECIES_System_Params&) = default; + ECIES_System_Params(ECIES_System_Params&&) = default; ECIES_System_Params& operator=(const ECIES_System_Params&) = delete; + ECIES_System_Params& operator=(ECIES_System_Params&&) = delete; ~ECIES_System_Params() override = default; /// creates an instance of the message authentication code @@ -154,10 +210,10 @@ std::unique_ptr create_cipher(Cipher_Dir direction) const; /// returns the length of the key used by the data encryption method - inline size_t dem_keylen() const { return m_dem_keylen; } + size_t dem_keylen() const { return m_dem_keylen; } /// returns the length of the key used by the message authentication code - inline size_t mac_keylen() const { return m_mac_keylen; } + size_t mac_keylen() const { return m_mac_keylen; } private: const std::string m_dem_spec; @@ -168,8 +224,6 @@ /** * ECIES secret derivation according to ISO 18033-2 -* -* TODO(Botan4) hide this */ class BOTAN_PUBLIC_API(2, 0) ECIES_KA_Operation { public: @@ -180,7 +234,6 @@ * (according to ISO 18033 cofactor mode is only used during decryption) * @param rng the RNG to use */ - BOTAN_DEPRECATED("Deprecated no replacement") ECIES_KA_Operation(const PK_Key_Agreement_Key& private_key, const ECIES_KA_Params& ecies_params, bool for_encryption, @@ -232,8 +285,8 @@ #if defined(BOTAN_HAS_LEGACY_EC_POINT) /// Set the public key of the other party - inline void set_other_key(const EC_Point& public_point) { - m_other_point = EC_AffinePoint(m_params.domain(), public_point); + void set_other_key(const EC_Point& public_point) { + m_other_point = EC_AffinePoint(m_params.group(), public_point); } #endif @@ -247,7 +300,7 @@ void set_label(std::string_view label) { m_label.assign(label.begin(), label.end()); } private: - std::vector enc(const uint8_t data[], size_t length, RandomNumberGenerator&) const override; + std::vector enc(const uint8_t data[], size_t length, RandomNumberGenerator& rng) const override; size_t maximum_input_size() const override; @@ -278,10 +331,10 @@ RandomNumberGenerator& rng); /// Set the initialization vector for the data encryption method - inline void set_initialization_vector(const InitializationVector& iv) { m_iv = iv; } + void set_initialization_vector(const InitializationVector& iv) { m_iv = iv; } /// Set the label which is appended to the input for the message authentication code - inline void set_label(std::string_view label) { m_label = std::vector(label.begin(), label.end()); } + void set_label(std::string_view label) { m_label = std::vector(label.begin(), label.end()); } private: secure_vector do_decrypt(uint8_t& valid_mask, const uint8_t in[], size_t in_len) const override; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/eckcdsa/eckcdsa.cpp botan3-3.12.0+dfsg/src/lib/pubkey/eckcdsa/eckcdsa.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/eckcdsa/eckcdsa.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/eckcdsa/eckcdsa.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,14 +9,16 @@ #include +#include #include +#include #include +#include #include #include #include #include #include -#include namespace Botan { @@ -43,7 +45,7 @@ return hash; } - SCAN_Name req(padding); + const SCAN_Name req(padding); if(req.algo_name() == "EMSA1" && req.arg_count() == 1) { if(auto hash = HashFunction::create(req.arg(0))) { @@ -150,7 +152,6 @@ const EC_Scalar m_x; std::unique_ptr m_hash; std::vector m_prefix; - std::vector m_ws; bool m_prefix_used; }; @@ -165,7 +166,7 @@ // We cannot use gk_x_mod_order because ECKCDSA, unlike ECDSA or ECGDSA, does // not reduce the x coordinate modulo the group order. - m_hash->update(EC_AffinePoint::g_mul(k, rng, m_ws).x_bytes()); + m_hash->update(EC_AffinePoint::g_mul(k, rng).x_bytes()); auto c = m_hash->final_stdvec(); truncate_hash_if_needed(c, m_group.get_order_bytes()); @@ -260,6 +261,10 @@ } // namespace +std::optional ECKCDSA_PublicKey::_signature_element_size_for_DER_encoding() const { + return domain().get_order_bytes(); +} + std::unique_ptr ECKCDSA_PublicKey::generate_another(RandomNumberGenerator& rng) const { return std::make_unique(rng, domain()); } diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/eckcdsa/eckcdsa.h botan3-3.12.0+dfsg/src/lib/pubkey/eckcdsa/eckcdsa.h --- botan3-3.7.1+dfsg/src/lib/pubkey/eckcdsa/eckcdsa.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/eckcdsa/eckcdsa.h 2026-05-07 01:38:28.000000000 +0000 @@ -46,9 +46,7 @@ */ std::string algo_name() const override { return "ECKCDSA"; } - std::optional _signature_element_size_for_DER_encoding() const override { - return domain().get_order_bytes(); - } + std::optional _signature_element_size_for_DER_encoding() const override; std::unique_ptr generate_another(RandomNumberGenerator& rng) const final; @@ -87,14 +85,14 @@ * @param group curve parameters to bu used for this key * @param x the private key */ - ECKCDSA_PrivateKey(EC_Group group, EC_Scalar x) : EC_PrivateKey(std::move(group), std::move(x), true) {} + ECKCDSA_PrivateKey(const EC_Group& group, const EC_Scalar& x) : EC_PrivateKey(group, x, true) {} /** * Create a new private key * @param rng a random number generator * @param group parameters to used for this key */ - ECKCDSA_PrivateKey(RandomNumberGenerator& rng, EC_Group group) : EC_PrivateKey(rng, std::move(group), true) {} + ECKCDSA_PrivateKey(RandomNumberGenerator& rng, const EC_Group& group) : EC_PrivateKey(rng, group, true) {} /** * Create a private key. @@ -106,7 +104,7 @@ ECKCDSA_PrivateKey(RandomNumberGenerator& rng, const EC_Group& group, const BigInt& x) : EC_PrivateKey(rng, group, x, true) {} - bool check_key(RandomNumberGenerator& rng, bool) const override; + bool check_key(RandomNumberGenerator& rng, bool strong) const override; std::unique_ptr public_key() const override; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ed25519/ed25519.cpp botan3-3.12.0+dfsg/src/lib/pubkey/ed25519/ed25519.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/ed25519/ed25519.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ed25519/ed25519.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -10,26 +10,23 @@ #include -#include -#include +#include #include -#include namespace Botan { -void ed25519_gen_keypair(uint8_t* pk, uint8_t* sk, const uint8_t seed[32]) { +void ed25519_gen_keypair(uint8_t pk[32], uint8_t sk[64], const uint8_t seed[32]) { uint8_t az[64]; - SHA_512 sha; - sha.update(seed, 32); - sha.final(az); + auto sha512 = HashFunction::create_or_throw("SHA-512"); + sha512->update(seed, 32); + sha512->final(az); az[0] &= 248; az[31] &= 63; az[31] |= 64; - ge_scalarmult_base(pk, az); + ed25519_basepoint_mul(std::span{pk, 32}, az); - // todo copy_mem copy_mem(sk, seed, 32); copy_mem(sk + 32, pk, 32); } @@ -44,27 +41,27 @@ uint8_t nonce[64]; uint8_t hram[64]; - SHA_512 sha; + auto sha512 = HashFunction::create_or_throw("SHA-512"); - sha.update(sk, 32); - sha.final(az); + sha512->update(sk, 32); + sha512->final(az); az[0] &= 248; az[31] &= 63; az[31] |= 64; - sha.update(domain_sep, domain_sep_len); - sha.update(az + 32, 32); - sha.update(m, mlen); - sha.final(nonce); + sha512->update(domain_sep, domain_sep_len); + sha512->update(az + 32, 32); + sha512->update(m, mlen); + sha512->final(nonce); sc_reduce(nonce); - ge_scalarmult_base(sig, nonce); + ed25519_basepoint_mul(std::span{sig, 32}, nonce); - sha.update(domain_sep, domain_sep_len); - sha.update(sig, 32); - sha.update(sk + 32, 32); - sha.update(m, mlen); - sha.final(hram); + sha512->update(domain_sep, domain_sep_len); + sha512->update(sig, 32); + sha512->update(sk + 32, 32); + sha512->update(m, mlen); + sha512->final(hram); sc_reduce(hram); sc_muladd(sig + 32, hram, az, nonce); @@ -76,15 +73,7 @@ const uint8_t* pk, const uint8_t domain_sep[], size_t domain_sep_len) { - uint8_t h[64]; - uint8_t rcheck[32]; - ge_p3 A; - SHA_512 sha; - - if(sig[63] & 224) { - return false; - } - if(ge_frombytes_negate_vartime(&A, pk) != 0) { + if((sig[63] & 0xE0) != 0x00) { return false; } @@ -114,16 +103,17 @@ } } - sha.update(domain_sep, domain_sep_len); - sha.update(sig, 32); - sha.update(pk, 32); - sha.update(m, mlen); - sha.final(h); - sc_reduce(h); + uint8_t h[64]; + auto sha512 = HashFunction::create_or_throw("SHA-512"); - ge_double_scalarmult_vartime(rcheck, h, &A, sig + 32); + sha512->update(domain_sep, domain_sep_len); + sha512->update(sig, 32); + sha512->update(pk, 32); + sha512->update(m, mlen); + sha512->final(h); + sc_reduce(h); - return CT::is_equal(rcheck, sig, 32).as_bool(); + return signature_check(std::span{pk, 32}, h, sig, sig + 32); } } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ed25519/ed25519.h botan3-3.12.0+dfsg/src/lib/pubkey/ed25519/ed25519.h --- botan3-3.7.1+dfsg/src/lib/pubkey/ed25519/ed25519.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ed25519/ed25519.h 2026-05-07 01:38:28.000000000 +0000 @@ -1,9 +1,7 @@ /* * Ed25519 * (C) 2017 Ribose Inc -* -* Based on the public domain code from SUPERCOP ref10 by -* Peter Schwabe, Daniel J. Bernstein, Niels Duif, Tanja Lange, Bo-Yin Yang +* 2025 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -12,6 +10,7 @@ #define BOTAN_ED25519_H_ #include +#include namespace Botan { @@ -35,7 +34,9 @@ bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::Signature); } - const std::vector& get_public_key() const { return m_public; } + BOTAN_DEPRECATED("Use raw_public_key_bits") const std::vector& get_public_key() const { + return m_public; + } /** * Create a Ed25519 Public Key. @@ -44,7 +45,8 @@ */ Ed25519_PublicKey(const AlgorithmIdentifier& alg_id, std::span key_bits); - Ed25519_PublicKey(std::span pub) : Ed25519_PublicKey(pub.data(), pub.size()) {} + BOTAN_FUTURE_EXPLICIT Ed25519_PublicKey(std::span pub) : + Ed25519_PublicKey(pub.data(), pub.size()) {} Ed25519_PublicKey(const uint8_t pub_key[], size_t len); @@ -56,7 +58,7 @@ protected: Ed25519_PublicKey() = default; - std::vector m_public; + std::vector m_public; // NOLINT(*non-private-member-variable*) }; BOTAN_DIAGNOSTIC_PUSH @@ -73,16 +75,41 @@ Ed25519_PrivateKey(const AlgorithmIdentifier& alg_id, std::span key_bits); /** - * Generate a private key. + * Generate a new random private key. * @param rng the RNG to use */ explicit Ed25519_PrivateKey(RandomNumberGenerator& rng); /** * Construct a private key from the specified parameters. + * * @param secret_key the private key + * + * The behavior of this function depends on the input length. + * + * If the input is 32 bytes long then it is treated as a seed, and a new + * keypair is generated. + * + * If the input is 64 bytes long then it is treated as a pair of 32 byte + * values, first the private key and then the public key. + * + * This constructor is deprecated since the above behavior is + * quite surprising. If you are relying on it, please comment in #4666. + */ + BOTAN_DEPRECATED("Use from_seed or from_bytes") explicit Ed25519_PrivateKey(std::span secret_key); + + /** + * Generate a new Ed25519_PrivateKey from the provided 32-byte seed + */ + static Ed25519_PrivateKey from_seed(std::span seed); + + /** + * Decode the Ed25519_PrivateKey from the provided 64-byte value + * + * The first 32 bytes are the private key and the last 32 bytes + * are the precomputed public key. */ - explicit Ed25519_PrivateKey(const secure_vector& secret_key); + static Ed25519_PrivateKey from_bytes(std::span bytes); BOTAN_DEPRECATED("Use raw_private_key_bits") const secure_vector& get_private_key() const { return m_private; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ed25519/ed25519_fe.cpp botan3-3.12.0+dfsg/src/lib/pubkey/ed25519/ed25519_fe.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/ed25519/ed25519_fe.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ed25519/ed25519_fe.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -15,66 +15,57 @@ namespace Botan { //static -FE_25519 FE_25519::invert(const FE_25519& z) { - FE_25519 t0; - FE_25519 t1; - FE_25519 t2; - FE_25519 t3; - - fe_sq(t0, z); - fe_sq_iter(t1, t0, 2); - fe_mul(t1, z, t1); - fe_mul(t0, t0, t1); - fe_sq(t2, t0); - fe_mul(t1, t1, t2); - fe_sq_iter(t2, t1, 5); - fe_mul(t1, t2, t1); - fe_sq_iter(t2, t1, 10); - fe_mul(t2, t2, t1); - fe_sq_iter(t3, t2, 20); - fe_mul(t2, t3, t2); - fe_sq_iter(t2, t2, 10); - fe_mul(t1, t2, t1); - fe_sq_iter(t2, t1, 50); - fe_mul(t2, t2, t1); - fe_sq_iter(t3, t2, 100); - fe_mul(t2, t3, t2); - fe_sq_iter(t2, t2, 50); - fe_mul(t1, t2, t1); - fe_sq_iter(t1, t1, 5); +Ed25519_FieldElement Ed25519_FieldElement::invert() const { + auto t0 = this->sqr(); + auto t1 = t0.sqr_iter(2); + t1 = *this * t1; + t0 = t0 * t1; + auto t2 = t0.sqr(); + t1 = t1 * t2; + t2 = t1.sqr_iter(5); + t1 = t2 * t1; + t2 = t1.sqr_iter(10); + t2 = t2 * t1; + auto t3 = t2.sqr_iter(20); + t2 = t3 * t2; + t2 = t2.sqr_iter(10); + t1 = t2 * t1; + t2 = t1.sqr_iter(50); + t2 = t2 * t1; + t3 = t2.sqr_iter(100); + t2 = t3 * t2; + t2 = t2.sqr_iter(50); + t1 = t2 * t1; + t1 = t1.sqr_iter(5); - fe_mul(t0, t1, t0); + t0 = t1 * t0; return t0; } -FE_25519 FE_25519::pow_22523(const fe& z) { - FE_25519 t0; - FE_25519 t1; - FE_25519 t2; - - fe_sq(t0, z); - fe_sq_iter(t1, t0, 2); - fe_mul(t1, z, t1); - fe_mul(t0, t0, t1); - fe_sq(t0, t0); - fe_mul(t0, t1, t0); - fe_sq_iter(t1, t0, 5); - fe_mul(t0, t1, t0); - fe_sq_iter(t1, t0, 10); - fe_mul(t1, t1, t0); - fe_sq_iter(t2, t1, 20); - fe_mul(t1, t2, t1); - fe_sq_iter(t1, t1, 10); - fe_mul(t0, t1, t0); - fe_sq_iter(t1, t0, 50); - fe_mul(t1, t1, t0); - fe_sq_iter(t2, t1, 100); - fe_mul(t1, t2, t1); - fe_sq_iter(t1, t1, 50); - fe_mul(t0, t1, t0); - fe_sq_iter(t0, t0, 2); +Ed25519_FieldElement Ed25519_FieldElement::pow_22523() const { + auto t0 = this->sqr(); + auto t1 = t0.sqr_iter(2); + t1 = (*this) * t1; + t0 = t0 * t1; + t0 = t0.sqr(); + t0 = t1 * t0; + t1 = t0.sqr_iter(5); + t0 = t1 * t0; + t1 = t0.sqr_iter(10); + t1 = t1 * t0; + auto t2 = t1.sqr_iter(20); + t1 = t2 * t1; + t1 = t1.sqr_iter(10); + t0 = t1 * t0; + t1 = t0.sqr_iter(50); + t1 = t1 * t0; + t2 = t1.sqr_iter(100); + t1 = t2 * t1; + t1 = t1.sqr_iter(50); + t0 = t1 * t0; + t0 = t0.sqr_iter(2); - fe_mul(t0, t0, z); + t0 = t0 * (*this); return t0; } @@ -111,28 +102,28 @@ */ //static -FE_25519 FE_25519::mul(const FE_25519& f, const FE_25519& g) { - const int32_t f0 = f[0]; - const int32_t f1 = f[1]; - const int32_t f2 = f[2]; - const int32_t f3 = f[3]; - const int32_t f4 = f[4]; - const int32_t f5 = f[5]; - const int32_t f6 = f[6]; - const int32_t f7 = f[7]; - const int32_t f8 = f[8]; - const int32_t f9 = f[9]; - - const int32_t g0 = g[0]; - const int32_t g1 = g[1]; - const int32_t g2 = g[2]; - const int32_t g3 = g[3]; - const int32_t g4 = g[4]; - const int32_t g5 = g[5]; - const int32_t g6 = g[6]; - const int32_t g7 = g[7]; - const int32_t g8 = g[8]; - const int32_t g9 = g[9]; +Ed25519_FieldElement Ed25519_FieldElement::mul(const Ed25519_FieldElement& f, const Ed25519_FieldElement& g) { + const int32_t f0 = f.m_fe[0]; + const int32_t f1 = f.m_fe[1]; + const int32_t f2 = f.m_fe[2]; + const int32_t f3 = f.m_fe[3]; + const int32_t f4 = f.m_fe[4]; + const int32_t f5 = f.m_fe[5]; + const int32_t f6 = f.m_fe[6]; + const int32_t f7 = f.m_fe[7]; + const int32_t f8 = f.m_fe[8]; + const int32_t f9 = f.m_fe[9]; + + const int32_t g0 = g.m_fe[0]; + const int32_t g1 = g.m_fe[1]; + const int32_t g2 = g.m_fe[2]; + const int32_t g3 = g.m_fe[3]; + const int32_t g4 = g.m_fe[4]; + const int32_t g5 = g.m_fe[5]; + const int32_t g6 = g.m_fe[6]; + const int32_t g7 = g.m_fe[7]; + const int32_t g8 = g.m_fe[8]; + const int32_t g9 = g.m_fe[9]; const int32_t g1_19 = 19 * g1; /* 1.959375*2^29 */ const int32_t g2_19 = 19 * g2; /* 1.959375*2^30; still ok */ @@ -313,7 +304,7 @@ /* |h0| <= 2^25; from now on fits into int32 unchanged */ /* |h1| <= 1.01*2^24 */ - return FE_25519(h0, h1, h2, h3, h4, h5, h6, h7, h8, h9); + return Ed25519_FieldElement(h0, h1, h2, h3, h4, h5, h6, h7, h8, h9); } /* @@ -332,17 +323,17 @@ */ //static -FE_25519 FE_25519::sqr_iter(const FE_25519& f, size_t iter) { - int32_t f0 = f[0]; - int32_t f1 = f[1]; - int32_t f2 = f[2]; - int32_t f3 = f[3]; - int32_t f4 = f[4]; - int32_t f5 = f[5]; - int32_t f6 = f[6]; - int32_t f7 = f[7]; - int32_t f8 = f[8]; - int32_t f9 = f[9]; +Ed25519_FieldElement Ed25519_FieldElement::sqr_iter(size_t iter) const { + int32_t f0 = m_fe[0]; + int32_t f1 = m_fe[1]; + int32_t f2 = m_fe[2]; + int32_t f3 = m_fe[3]; + int32_t f4 = m_fe[4]; + int32_t f5 = m_fe[5]; + int32_t f6 = m_fe[6]; + int32_t f7 = m_fe[7]; + int32_t f8 = m_fe[8]; + int32_t f9 = m_fe[9]; for(size_t i = 0; i != iter; ++i) { const int32_t f0_2 = 2 * f0; @@ -453,7 +444,7 @@ f9 = static_cast(h9); } - return FE_25519(f0, f1, f2, f3, f4, f5, f6, f7, f8, f9); + return Ed25519_FieldElement(f0, f1, f2, f3, f4, f5, f6, f7, f8, f9); } /* @@ -472,17 +463,18 @@ */ //static -FE_25519 FE_25519::sqr2(const FE_25519& f) { - const int32_t f0 = f[0]; - const int32_t f1 = f[1]; - const int32_t f2 = f[2]; - const int32_t f3 = f[3]; - const int32_t f4 = f[4]; - const int32_t f5 = f[5]; - const int32_t f6 = f[6]; - const int32_t f7 = f[7]; - const int32_t f8 = f[8]; - const int32_t f9 = f[9]; +Ed25519_FieldElement Ed25519_FieldElement::sqr2() const { + const int32_t f0 = m_fe[0]; + const int32_t f1 = m_fe[1]; + const int32_t f2 = m_fe[2]; + const int32_t f3 = m_fe[3]; + const int32_t f4 = m_fe[4]; + const int32_t f5 = m_fe[5]; + const int32_t f6 = m_fe[6]; + const int32_t f7 = m_fe[7]; + const int32_t f8 = m_fe[8]; + const int32_t f9 = m_fe[9]; + const int32_t f0_2 = 2 * f0; const int32_t f1_2 = 2 * f1; const int32_t f2_2 = 2 * f2; @@ -590,14 +582,13 @@ carry<25, 19>(h9, h0); carry<26>(h0, h1); - return FE_25519(h0, h1, h2, h3, h4, h5, h6, h7, h8, h9); + return Ed25519_FieldElement(h0, h1, h2, h3, h4, h5, h6, h7, h8, h9); } /* Ignores top bit of h. */ - -void FE_25519::from_bytes(const uint8_t s[32]) { +Ed25519_FieldElement Ed25519_FieldElement::deserialize(const uint8_t s[32]) { int64_t h0 = load_4(s); int64_t h1 = load_3(s + 4) << 6; int64_t h2 = load_3(s + 7) << 5; @@ -621,16 +612,7 @@ carry<26>(h6, h7); carry<26>(h8, h9); - m_fe[0] = static_cast(h0); - m_fe[1] = static_cast(h1); - m_fe[2] = static_cast(h2); - m_fe[3] = static_cast(h3); - m_fe[4] = static_cast(h4); - m_fe[5] = static_cast(h5); - m_fe[6] = static_cast(h6); - m_fe[7] = static_cast(h7); - m_fe[8] = static_cast(h8); - m_fe[9] = static_cast(h9); + return Ed25519_FieldElement(h0, h1, h2, h3, h4, h5, h6, h7, h8, h9); } /* @@ -658,8 +640,8 @@ so floor(2^(-255)(h + 19 2^(-25) h9 + 2^(-1))) = q. */ -void FE_25519::to_bytes(uint8_t s[32]) const { - const int64_t X25 = (1 << 25); +void Ed25519_FieldElement::serialize_to(std::span s) const { + const int32_t X25 = (1 << 25); int32_t h0 = m_fe[0]; int32_t h1 = m_fe[1]; @@ -671,9 +653,8 @@ int32_t h7 = m_fe[7]; int32_t h8 = m_fe[8]; int32_t h9 = m_fe[9]; - int32_t q; - q = (19 * h9 + ((static_cast(1) << 24))) >> 25; + int32_t q = (19 * h9 + ((static_cast(1) << 24))) >> 25; q = (h0 + q) >> 26; q = (h1 + q) >> 25; q = (h2 + q) >> 26; @@ -699,7 +680,7 @@ carry0<25>(h7, h8); carry0<26>(h8, h9); - int32_t carry9 = h9 >> 25; + const int32_t carry9 = h9 >> 25; h9 -= carry9 * X25; /* h10 = carry9 */ diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ed25519/ed25519_fe.h botan3-3.12.0+dfsg/src/lib/pubkey/ed25519/ed25519_fe.h --- botan3-3.7.1+dfsg/src/lib/pubkey/ed25519/ed25519_fe.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ed25519/ed25519_fe.h 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,7 @@ /* * Ed25519 field element * (C) 2017 Ribose Inc +* 2025 Jack Lloyd * * Based on the public domain code from SUPERCOP ref10 by * Peter Schwabe, Daniel J. Bernstein, Niels Duif, Tanja Lange, Bo-Yin Yang @@ -12,45 +13,51 @@ #define BOTAN_ED25519_FE_H_ #include -#include +#include +#include namespace Botan { /** * An element of the field \\Z/(2^255-19) +* +* An element t, entries t[0]...t[9], represents the integer +* t[0]+2^26 t[1]+2^51 t[2]+2^77 t[3]+2^102 t[4]+...+2^230 t[9]. +* Bounds on each t[i] vary depending on context. */ -class FE_25519 { +class Ed25519_FieldElement final { public: - ~FE_25519() { secure_scrub_memory(m_fe, sizeof(m_fe)); } - /** - * Zero element + * Default zero initialization */ - FE_25519(int init = 0) { - if(init != 0 && init != 1) { - throw Invalid_Argument("Invalid FE_25519 initial value"); - } - clear_mem(m_fe, 10); - m_fe[0] = init; + constexpr Ed25519_FieldElement() : m_fe{} {} + + constexpr static Ed25519_FieldElement zero() { return Ed25519_FieldElement(); } + + constexpr static Ed25519_FieldElement one() { + auto o = Ed25519_FieldElement(); + o.m_fe[0] = 1; + return o; } - FE_25519(std::initializer_list x) { - if(x.size() != 10) { - throw Invalid_Argument("Invalid FE_25519 initializer list"); + // NOLINTNEXTLINE(*-member-init) + constexpr explicit Ed25519_FieldElement(std::span fe) { + for(size_t i = 0; i != 10; ++i) { + m_fe[i] = fe[i]; } - copy_mem(m_fe, x.begin(), 10); } - FE_25519(int64_t h0, - int64_t h1, - int64_t h2, - int64_t h3, - int64_t h4, - int64_t h5, - int64_t h6, - int64_t h7, - int64_t h8, - int64_t h9) { + // NOLINTNEXTLINE(*-member-init) + constexpr Ed25519_FieldElement(int64_t h0, + int64_t h1, + int64_t h2, + int64_t h3, + int64_t h4, + int64_t h5, + int64_t h6, + int64_t h7, + int64_t h8, + int64_t h9) { m_fe[0] = static_cast(h0); m_fe[1] = static_cast(h1); m_fe[2] = static_cast(h2); @@ -63,25 +70,14 @@ m_fe[9] = static_cast(h9); } - FE_25519(const FE_25519& other) = default; - FE_25519& operator=(const FE_25519& other) = default; + static Ed25519_FieldElement deserialize(const uint8_t b[32]); - FE_25519(FE_25519&& other) = default; - FE_25519& operator=(FE_25519&& other) = default; - - void from_bytes(const uint8_t b[32]); - void to_bytes(uint8_t b[32]) const; + void serialize_to(std::span b) const; bool is_zero() const { - uint8_t s[32]; - to_bytes(s); - - uint8_t sum = 0; - for(size_t i = 0; i != 32; ++i) { - sum |= s[i]; - } - - return (sum == 0); + std::array value = {}; + this->serialize_to(value); + return CT::all_zeros(value.data(), value.size()).as_bool(); } /* @@ -89,44 +85,48 @@ return 0 if f is in {0,2,4,...,q-1} */ bool is_negative() const { - // TODO could avoid most of the to_bytes computation here - uint8_t s[32]; - to_bytes(s); - return s[0] & 1; + // TODO could avoid most of the serialize computation here + std::array s = {}; + this->serialize_to(s); + return (s[0] & 0x01) == 0x01; } - static FE_25519 add(const FE_25519& a, const FE_25519& b) { - FE_25519 z; + static Ed25519_FieldElement add(const Ed25519_FieldElement& a, const Ed25519_FieldElement& b) { + Ed25519_FieldElement z; for(size_t i = 0; i != 10; ++i) { - z[i] = a[i] + b[i]; + z.m_fe[i] = a.m_fe[i] + b.m_fe[i]; } return z; } - static FE_25519 sub(const FE_25519& a, const FE_25519& b) { - FE_25519 z; + static Ed25519_FieldElement sub(const Ed25519_FieldElement& a, const Ed25519_FieldElement& b) { + Ed25519_FieldElement z; for(size_t i = 0; i != 10; ++i) { - z[i] = a[i] - b[i]; + z.m_fe[i] = a.m_fe[i] - b.m_fe[i]; } return z; } - static FE_25519 negate(const FE_25519& a) { - FE_25519 z; + static Ed25519_FieldElement negate(const Ed25519_FieldElement& a) { + Ed25519_FieldElement z; for(size_t i = 0; i != 10; ++i) { - z[i] = -a[i]; + z.m_fe[i] = -a.m_fe[i]; } return z; } - static FE_25519 mul(const FE_25519& a, const FE_25519& b); - static FE_25519 sqr_iter(const FE_25519& a, size_t iter); + static Ed25519_FieldElement mul(const Ed25519_FieldElement& a, const Ed25519_FieldElement& b); + + Ed25519_FieldElement sqr_iter(size_t iter) const; - static FE_25519 sqr(const FE_25519& a) { return sqr_iter(a, 1); } + Ed25519_FieldElement sqr() const { return sqr_iter(1); } - static FE_25519 sqr2(const FE_25519& a); - static FE_25519 pow_22523(const FE_25519& a); - static FE_25519 invert(const FE_25519& a); + // Return 2*a^2 + Ed25519_FieldElement sqr2() const; + + Ed25519_FieldElement invert() const; + + Ed25519_FieldElement pow_22523() const; // TODO remove int32_t operator[](size_t i) const { return m_fe[i]; } @@ -134,81 +134,23 @@ int32_t& operator[](size_t i) { return m_fe[i]; } private: - int32_t m_fe[10]; + std::array m_fe; }; -typedef FE_25519 fe; - -/* -fe means field element. -Here the field is -An element t, entries t[0]...t[9], represents the integer -t[0]+2^26 t[1]+2^51 t[2]+2^77 t[3]+2^102 t[4]+...+2^230 t[9]. -Bounds on each t[i] vary depending on context. -*/ - -inline void fe_frombytes(fe& x, const uint8_t* b) { - x.from_bytes(b); -} - -inline void fe_tobytes(uint8_t* b, const fe& x) { - x.to_bytes(b); -} - -inline void fe_copy(fe& a, const fe& b) { - a = b; -} - -inline int fe_isnonzero(const fe& x) { - return x.is_zero() ? 0 : 1; -} - -inline int fe_isnegative(const fe& x) { - return x.is_negative(); -} - -inline void fe_0(fe& x) { - x = FE_25519(); -} - -inline void fe_1(fe& x) { - x = FE_25519(1); -} - -inline void fe_add(fe& x, const fe& a, const fe& b) { - x = FE_25519::add(a, b); -} - -inline void fe_sub(fe& x, const fe& a, const fe& b) { - x = FE_25519::sub(a, b); -} - -inline void fe_neg(fe& x, const fe& z) { - x = FE_25519::negate(z); -} - -inline void fe_mul(fe& x, const fe& a, const fe& b) { - x = FE_25519::mul(a, b); -} - -inline void fe_sq(fe& x, const fe& z) { - x = FE_25519::sqr(z); -} - -inline void fe_sq_iter(fe& x, const fe& z, size_t iter) { - x = FE_25519::sqr_iter(z, iter); +inline Ed25519_FieldElement operator+(const Ed25519_FieldElement& x, const Ed25519_FieldElement& y) { + return Ed25519_FieldElement::add(x, y); } -inline void fe_sq2(fe& x, const fe& z) { - x = FE_25519::sqr2(z); +inline Ed25519_FieldElement operator-(const Ed25519_FieldElement& x, const Ed25519_FieldElement& y) { + return Ed25519_FieldElement::sub(x, y); } -inline void fe_invert(fe& x, const fe& z) { - x = FE_25519::invert(z); +inline Ed25519_FieldElement operator*(const Ed25519_FieldElement& x, const Ed25519_FieldElement& y) { + return Ed25519_FieldElement::mul(x, y); } -inline void fe_pow22523(fe& x, const fe& y) { - x = FE_25519::pow_22523(y); +inline Ed25519_FieldElement operator-(const Ed25519_FieldElement& x) { + return Ed25519_FieldElement::negate(x); } } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ed25519/ed25519_internal.h botan3-3.12.0+dfsg/src/lib/pubkey/ed25519/ed25519_internal.h --- botan3-3.7.1+dfsg/src/lib/pubkey/ed25519/ed25519_internal.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ed25519/ed25519_internal.h 2026-05-07 01:38:28.000000000 +0000 @@ -11,16 +11,15 @@ #ifndef BOTAN_ED25519_INT_H_ #define BOTAN_ED25519_INT_H_ -#include #include namespace Botan { -inline uint64_t load_3(const uint8_t in[3]) { - return static_cast(in[0]) | (static_cast(in[1]) << 8) | (static_cast(in[2]) << 16); +inline uint32_t load_3(const uint8_t in[3]) { + return static_cast(in[0]) | (static_cast(in[1]) << 8) | (static_cast(in[2]) << 16); } -inline uint64_t load_4(const uint8_t* in) { +inline uint32_t load_4(const uint8_t* in) { return load_le(in, 0); } @@ -30,7 +29,7 @@ { const int64_t X1 = (static_cast(1) << S); const int64_t X2 = (static_cast(1) << (S - 1)); - int64_t c = (h0 + X2) >> S; + const int64_t c = (h0 + X2) >> S; h1 += c * MUL; h0 -= c * X1; } @@ -40,7 +39,7 @@ requires(S > 0 && S < 64) { const int64_t X1 = (static_cast(1) << S); - int64_t c = h0 >> S; + const int64_t c = h0 >> S; h1 += c; h0 -= c * X1; } @@ -50,7 +49,7 @@ requires(S > 0 && S < 32) { const int32_t X1 = (static_cast(1) << S); - int32_t c = h0 >> S; + const int32_t c = h0 >> S; h1 += c; h0 -= c * X1; } @@ -65,36 +64,17 @@ X = 0; } -/* -ge means group element. - -Here the group is the set of pairs (x,y) of field elements (see fe.h) -satisfying -x^2 + y^2 = 1 + d x^2y^2 -where d = -121665/121666. - -Representations: - ge_p3 (extended): (X:Y:Z:T) satisfying x=X/Z, y=Y/Z, XY=ZT -*/ - -struct ge_p3 { - FE_25519 X; - FE_25519 Y; - FE_25519 Z; - FE_25519 T; -}; - -int ge_frombytes_negate_vartime(ge_p3* v, const uint8_t*); -void ge_scalarmult_base(uint8_t out[32], const uint8_t in[32]); +void ed25519_basepoint_mul(std::span out, const uint8_t in[32]); -void ge_double_scalarmult_vartime(uint8_t out[32], const uint8_t a[], const ge_p3* A, const uint8_t b[]); +bool signature_check(std::span pk, const uint8_t h[32], const uint8_t r[32], const uint8_t s[32]); /* The set of scalars is \Z/l where l = 2^252 + 27742317777372353535851937790883648493. */ -void sc_reduce(uint8_t*); -void sc_muladd(uint8_t*, const uint8_t*, const uint8_t*, const uint8_t*); +void sc_reduce(uint8_t* s); +void sc_muladd(uint8_t* s, const uint8_t* a, const uint8_t* b, const uint8_t* c); } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ed25519/ed25519_key.cpp botan3-3.12.0+dfsg/src/lib/pubkey/ed25519/ed25519_key.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/ed25519/ed25519_key.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ed25519/ed25519_key.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,9 +1,7 @@ /* * Ed25519 * (C) 2017 Ribose Inc -* -* Based on the public domain code from SUPERCOP ref10 by -* Peter Schwabe, Daniel J. Bernstein, Niels Duif, Tanja Lange, Bo-Yin Yang +* 2025 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -49,19 +47,8 @@ copy_mem(pkcopy, m_public.data(), 32); pkcopy[31] ^= (1 << 7); // flip sign - ge_p3 point; - if(ge_frombytes_negate_vartime(&point, pkcopy) != 0) { - return false; - } - - uint8_t result[32]; - ge_double_scalarmult_vartime(result, modm_m, &point, zero); - - if(!CT::is_equal(result, identity_element, 32).as_bool()) { - return false; - } - return true; + return signature_check(pkcopy, modm_m, identity_element, zero); } Ed25519_PublicKey::Ed25519_PublicKey(const uint8_t pub_key[], size_t pub_len) { @@ -91,9 +78,9 @@ return std::make_unique(rng); } -Ed25519_PrivateKey::Ed25519_PrivateKey(const secure_vector& secret_key) { +Ed25519_PrivateKey::Ed25519_PrivateKey(std::span secret_key) { if(secret_key.size() == 64) { - m_private = secret_key; + m_private.assign(secret_key.begin(), secret_key.end()); m_public.assign(m_private.begin() + 32, m_private.end()); } else if(secret_key.size() == 32) { m_public.resize(32); @@ -104,6 +91,18 @@ } } +//static +Ed25519_PrivateKey Ed25519_PrivateKey::from_seed(std::span seed) { + BOTAN_ARG_CHECK(seed.size() == 32, "Ed25519 seed must be exactly 32 bytes long"); + return Ed25519_PrivateKey(seed); +} + +//static +Ed25519_PrivateKey Ed25519_PrivateKey::from_bytes(std::span bytes) { + BOTAN_ARG_CHECK(bytes.size() == 64, "Ed25519 private key must be exactly 64 bytes long"); + return Ed25519_PrivateKey(bytes); +} + Ed25519_PrivateKey::Ed25519_PrivateKey(RandomNumberGenerator& rng) { const secure_vector seed = rng.random_vec(32); m_public.resize(32); @@ -113,7 +112,7 @@ Ed25519_PrivateKey::Ed25519_PrivateKey(const AlgorithmIdentifier& /*unused*/, std::span key_bits) { secure_vector bits; - BER_Decoder(key_bits).decode(bits, ASN1_Type::OctetString).discard_remaining(); + BER_Decoder(key_bits, BER_Decoder::Limits::DER()).decode(bits, ASN1_Type::OctetString).discard_remaining(); if(bits.size() != 32) { throw Decoding_Error("Invalid size for Ed25519 private key"); @@ -128,12 +127,16 @@ } secure_vector Ed25519_PrivateKey::private_key_bits() const { - secure_vector bits(&m_private[0], &m_private[32]); + const secure_vector bits(m_private.data(), &m_private[32]); return DER_Encoder().encode(bits, ASN1_Type::OctetString).get_contents(); } bool Ed25519_PrivateKey::check_key(RandomNumberGenerator& /*rng*/, bool /*strong*/) const { - return true; // ??? + std::vector public_point(32); + secure_vector private_key(64); // discarded + ed25519_gen_keypair(public_point.data(), private_key.data(), m_private.data()); + // Variable time comparison is fine here + return public_point == m_public; } namespace { @@ -149,6 +152,7 @@ bool is_valid_signature(std::span sig) override { if(sig.size() != 64) { + m_msg.clear(); return false; } @@ -168,12 +172,10 @@ /** * Ed25519 verifying operation with pre-hash */ -class Ed25519_Hashed_Verify_Operation final : public PK_Ops::Verification { +class Ed25519_Hashed_Verify_Operation final : public PK_Ops::Verification_with_Hash { public: Ed25519_Hashed_Verify_Operation(const Ed25519_PublicKey& key, std::string_view hash, bool rfc8032) : - m_key(key.get_public_key()) { - m_hash = HashFunction::create_or_throw(hash); - + PK_Ops::Verification_with_Hash(hash), m_key(key.get_public_key()) { if(rfc8032) { m_domain_sep = {0x53, 0x69, 0x67, 0x45, 0x64, 0x32, 0x35, 0x35, 0x31, 0x39, 0x20, 0x6E, 0x6F, 0x20, 0x45, 0x64, 0x32, 0x35, 0x35, 0x31, 0x39, 0x20, 0x63, 0x6F, @@ -181,24 +183,17 @@ } } - void update(std::span msg) override { m_hash->update(msg); } - - bool is_valid_signature(std::span sig) override { + bool verify(std::span ph, std::span sig) override { if(sig.size() != 64) { return false; } - std::vector msg_hash(m_hash->output_length()); - m_hash->final(msg_hash.data()); BOTAN_ASSERT_EQUAL(m_key.size(), 32, "Expected size"); return ed25519_verify( - msg_hash.data(), msg_hash.size(), sig.data(), m_key.data(), m_domain_sep.data(), m_domain_sep.size()); + ph.data(), ph.size(), sig.data(), m_key.data(), m_domain_sep.data(), m_domain_sep.size()); } - std::string hash_function() const override { return m_hash->name(); } - private: - std::unique_ptr m_hash; std::vector m_key; std::vector m_domain_sep; }; @@ -237,12 +232,10 @@ /** * Ed25519 signing operation with pre-hash */ -class Ed25519_Hashed_Sign_Operation final : public PK_Ops::Signature { +class Ed25519_Hashed_Sign_Operation final : public PK_Ops::Signature_with_Hash { public: Ed25519_Hashed_Sign_Operation(const Ed25519_PrivateKey& key, std::string_view hash, bool rfc8032) : - m_key(key.raw_private_key_bits()) { - m_hash = HashFunction::create_or_throw(hash); - + PK_Ops::Signature_with_Hash(hash), m_key(key.raw_private_key_bits()) { if(rfc8032) { m_domain_sep = std::vector{0x53, 0x69, 0x67, 0x45, 0x64, 0x32, 0x35, 0x35, 0x31, 0x39, 0x20, 0x6E, 0x6F, 0x20, 0x45, 0x64, 0x32, 0x35, 0x35, 0x31, 0x39, 0x20, 0x63, 0x6F, @@ -252,21 +245,13 @@ size_t signature_length() const override { return 64; } - void update(std::span msg) override { m_hash->update(msg); } - - std::vector sign(RandomNumberGenerator& /*rng*/) override { + std::vector raw_sign(std::span ph, RandomNumberGenerator& /*rng*/) override { std::vector sig(64); - std::vector msg_hash(m_hash->output_length()); - m_hash->final(msg_hash.data()); - ed25519_sign( - sig.data(), msg_hash.data(), msg_hash.size(), m_key.data(), m_domain_sep.data(), m_domain_sep.size()); + ed25519_sign(sig.data(), ph.data(), ph.size(), m_key.data(), m_domain_sep.data(), m_domain_sep.size()); return sig; } - std::string hash_function() const override { return m_hash->name(); } - private: - std::unique_ptr m_hash; secure_vector m_key; std::vector m_domain_sep; }; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/ed25519/ge.cpp botan3-3.12.0+dfsg/src/lib/pubkey/ed25519/ge.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/ed25519/ge.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/ed25519/ge.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,7 @@ /* * Ed25519 group operations * (C) 2017 Ribose Inc +* 2025 Jack Lloyd * * Based on the public domain code from SUPERCOP ref10 by * Peter Schwabe, Daniel J. Bernstein, Niels Duif, Tanja Lange, Bo-Yin Yang @@ -10,384 +11,281 @@ #include +#include +#include +#include + namespace Botan { namespace { -/* -Representations: - ge_p2 (projective): (X:Y:Z) satisfying x=X/Z, y=Y/Z - ge_p3 (extended): (X:Y:Z:T) satisfying x=X/Z, y=Y/Z, XY=ZT - ge_p1p1 (completed): ((X:Z),(Y:T)) satisfying x=X/Z, y=Y/T - ge_precomp (Duif): (y+x,y-x,2dxy) -*/ -struct ge_p2 { - FE_25519 X; - FE_25519 Y; - FE_25519 Z; -}; - -struct ge_p1p1 { - FE_25519 X; - FE_25519 Y; - FE_25519 Z; - FE_25519 T; -}; - -struct ge_precomp { - FE_25519 yplusx; - FE_25519 yminusx; - FE_25519 xy2d; -}; - -struct ge_cached { - FE_25519 YplusX; - FE_25519 YminusX; - FE_25519 Z; - FE_25519 T2d; -}; +/** +Here the group is the set of pairs (x,y) of field elements (see ed5519_fe.h) +satisfying -x^2 + y^2 = 1 + d x^2y^2 where d = -121665/121666. -/* -r = p + q +Several different point representations are used in this implementation */ -void ge_add(ge_p1p1* r, const ge_p3* p, const ge_cached* q) { - FE_25519 t0; - /* qhasm: YpX1 = Y1+X1 */ - /* asm 1: fe_add(>YpX1=fe#1,YpX1=r->X,Y,X); */ - fe_add(r->X, p->Y, p->X); - - /* qhasm: YmX1 = Y1-X1 */ - /* asm 1: fe_sub(>YmX1=fe#2,YmX1=r->Y,Y,X); */ - fe_sub(r->Y, p->Y, p->X); - - /* qhasm: A = YpX1*YpX2 */ - /* asm 1: fe_mul(>A=fe#3,A=r->Z,X,YplusX); */ - fe_mul(r->Z, r->X, q->YplusX); - - /* qhasm: B = YmX1*YmX2 */ - /* asm 1: fe_mul(>B=fe#2,B=r->Y,Y,YminusX); */ - fe_mul(r->Y, r->Y, q->YminusX); - - /* qhasm: C = T2d2*T1 */ - /* asm 1: fe_mul(>C=fe#4,C=r->T,T2d,T); */ - fe_mul(r->T, q->T2d, p->T); - - /* qhasm: ZZ = Z1*Z2 */ - /* asm 1: fe_mul(>ZZ=fe#1,ZZ=r->X,Z,Z); */ - fe_mul(r->X, p->Z, q->Z); - - /* qhasm: D = 2*ZZ */ - /* asm 1: fe_add(>D=fe#5,D=t0,X,X); */ - fe_add(t0, r->X, r->X); - - /* qhasm: X3 = A-B */ - /* asm 1: fe_sub(>X3=fe#1,X3=r->X,Z,Y); */ - fe_sub(r->X, r->Z, r->Y); - - /* qhasm: Y3 = A+B */ - /* asm 1: fe_add(>Y3=fe#2,Y3=r->Y,Z,Y); */ - fe_add(r->Y, r->Z, r->Y); - - /* qhasm: Z3 = D+C */ - /* asm 1: fe_add(>Z3=fe#3,Z3=r->Z,T); */ - fe_add(r->Z, t0, r->T); - - /* qhasm: T3 = D-C */ - /* asm 1: fe_sub(>T3=fe#4,T3=r->T,T); */ - fe_sub(r->T, t0, r->T); -} - -/* -r = p + q +/** +* Ed25519_Point_Completed +* +* ((X:Z),(Y:T)) satisfying x=X/Z, y=Y/T */ +class Ed25519_Point_Completed final { + public: + Ed25519_FieldElement X; // NOLINT(misc-non-private-member-variables-in-classes) + Ed25519_FieldElement Y; // NOLINT(misc-non-private-member-variables-in-classes) + Ed25519_FieldElement Z; // NOLINT(misc-non-private-member-variables-in-classes) + Ed25519_FieldElement T; // NOLINT(misc-non-private-member-variables-in-classes) +}; -void ge_madd(ge_p1p1* r, const ge_p3* p, const ge_precomp* q) { - FE_25519 t0; - /* qhasm: YpX1 = Y1+X1 */ - fe_add(r->X, p->Y, p->X); - - /* qhasm: YmX1 = Y1-X1 */ - fe_sub(r->Y, p->Y, p->X); - - /* qhasm: A = YpX1*ypx2 */ - fe_mul(r->Z, r->X, q->yplusx); - - /* qhasm: B = YmX1*ymx2 */ - fe_mul(r->Y, r->Y, q->yminusx); - - /* qhasm: C = xy2d2*T1 */ - fe_mul(r->T, q->xy2d, p->T); - - /* qhasm: D = 2*Z1 */ - fe_add(t0, p->Z, p->Z); +/** +* Ed25519_Point_Projective +* +* (X:Y:Z) satisfying x=X/Z, y=Y/Z +*/ +class Ed25519_Point_Projective final { + public: + Ed25519_FieldElement X; // NOLINT(misc-non-private-member-variables-in-classes) + Ed25519_FieldElement Y; // NOLINT(misc-non-private-member-variables-in-classes) + Ed25519_FieldElement Z; // NOLINT(misc-non-private-member-variables-in-classes) + + /* + * Point conversion + */ + static Ed25519_Point_Projective from(const Ed25519_Point_Completed& p) { + Ed25519_Point_Projective r; + r.X = p.X * p.T; + r.Y = p.Y * p.Z; + r.Z = p.Z * p.T; + return r; + } - /* qhasm: X3 = A-B */ - fe_sub(r->X, r->Z, r->Y); + static constexpr Ed25519_Point_Projective identity() { + Ed25519_Point_Projective h; + h.X = Ed25519_FieldElement::zero(); + h.Y = Ed25519_FieldElement::one(); + h.Z = Ed25519_FieldElement::one(); + return h; + } - /* qhasm: Y3 = A+B */ - fe_add(r->Y, r->Z, r->Y); + void serialize_to(std::span s) const { + auto recip = this->Z.invert(); + auto x = this->X * recip; + auto y = this->Y * recip; + y.serialize_to(s); + s[31] ^= x.is_negative() ? 0x80 : 0x00; + } - /* qhasm: Z3 = D+C */ - fe_add(r->Z, t0, r->T); + Ed25519_Point_Completed dbl() const; +}; - /* qhasm: T3 = D-C */ - fe_sub(r->T, t0, r->T); +Ed25519_Point_Completed Ed25519_Point_Projective::dbl() const { + Ed25519_Point_Completed r; + r.X = X.sqr(); // XX=X1^2 + r.Z = Y.sqr(); // YY=Y1^2 + r.T = Z.sqr2(); // B=2*Z1^2 + r.Y = X + Y; // A=X1+Y1 + auto t0 = r.Y.sqr(); // AA=A^2 + r.Y = r.Z + r.X; // Y3=YY+XX + r.Z = r.Z - r.X; // Z3=YY-XX + r.X = t0 - r.Y; // X3=AA-Y3 + r.T = r.T - r.Z; // T3=B-Z3 + return r; } -/* -r = p - q +/** +* Ed25519_Point_Extended +* +* (X:Y:Z:T) satisfying x=X/Z, y=Y/Z, XY=ZT */ +class Ed25519_Point_Extended final { + public: + Ed25519_FieldElement X; // NOLINT(misc-non-private-member-variables-in-classes) + Ed25519_FieldElement Y; // NOLINT(misc-non-private-member-variables-in-classes) + Ed25519_FieldElement Z; // NOLINT(misc-non-private-member-variables-in-classes) + Ed25519_FieldElement T; // NOLINT(misc-non-private-member-variables-in-classes) + + static constexpr Ed25519_Point_Extended identity() { + Ed25519_Point_Extended h; + h.X = Ed25519_FieldElement::zero(); + h.Y = Ed25519_FieldElement::one(); + h.Z = Ed25519_FieldElement::one(); + h.T = Ed25519_FieldElement::zero(); + return h; + } -void ge_msub(ge_p1p1* r, const ge_p3* p, const ge_precomp* q) { - FE_25519 t0; - - /* qhasm: YpX1 = Y1+X1 */ - /* asm 1: fe_add(>YpX1=fe#1,YpX1=r->X,Y,X); */ - fe_add(r->X, p->Y, p->X); - - /* qhasm: YmX1 = Y1-X1 */ - /* asm 1: fe_sub(>YmX1=fe#2,YmX1=r->Y,Y,X); */ - fe_sub(r->Y, p->Y, p->X); - - /* qhasm: A = YpX1*ymx2 */ - /* asm 1: fe_mul(>A=fe#3,A=r->Z,X,yminusx); */ - fe_mul(r->Z, r->X, q->yminusx); - - /* qhasm: B = YmX1*ypx2 */ - /* asm 1: fe_mul(>B=fe#2,B=r->Y,Y,yplusx); */ - fe_mul(r->Y, r->Y, q->yplusx); - - /* qhasm: C = xy2d2*T1 */ - /* asm 1: fe_mul(>C=fe#4,C=r->T,xy2d,T); */ - fe_mul(r->T, q->xy2d, p->T); - - /* qhasm: D = 2*Z1 */ - /* asm 1: fe_add(>D=fe#5,D=t0,Z,Z); */ - fe_add(t0, p->Z, p->Z); - - /* qhasm: X3 = A-B */ - /* asm 1: fe_sub(>X3=fe#1,X3=r->X,Z,Y); */ - fe_sub(r->X, r->Z, r->Y); - - /* qhasm: Y3 = A+B */ - /* asm 1: fe_add(>Y3=fe#2,Y3=r->Y,Z,Y); */ - fe_add(r->Y, r->Z, r->Y); - - /* qhasm: Z3 = D-C */ - /* asm 1: fe_sub(>Z3=fe#3,Z3=r->Z,T); */ - fe_sub(r->Z, t0, r->T); - - /* qhasm: T3 = D+C */ - /* asm 1: fe_add(>T3=fe#4,T3=r->T,T); */ - fe_add(r->T, t0, r->T); -} + Ed25519_Point_Completed dbl() const { + Ed25519_Point_Projective q; + q.X = X; + q.Y = Y; + q.Z = Z; + return q.dbl(); + } -/* -r = p -*/ + /** + * Point conversion + */ + static Ed25519_Point_Extended from(const Ed25519_Point_Completed& p) { + Ed25519_Point_Extended r; + r.X = p.X * p.T; + r.Y = p.Y * p.Z; + r.Z = p.Z * p.T; + r.T = p.X * p.Y; + return r; + } -void ge_p1p1_to_p2(ge_p2* r, const ge_p1p1* p) { - fe_mul(r->X, p->X, p->T); - fe_mul(r->Y, p->Y, p->Z); - fe_mul(r->Z, p->Z, p->T); -} + void serialize_to(std::span out) const { + auto recip = this->Z.invert(); + auto x = this->X * recip; + auto y = this->Y * recip; + y.serialize_to(out); + out[31] ^= x.is_negative() ? 0x80 : 0x00; + } +}; -/* -r = p +/** +* Ed25519 Point in "Niels" coordinates +* +* y + x, y - x, 2d * x * y +* +* where d is the Edwards curve constant. */ +class Ed25519_Point_Niels final { + public: + Ed25519_FieldElement yplusx; // NOLINT(misc-non-private-member-variables-in-classes) + Ed25519_FieldElement yminusx; // NOLINT(misc-non-private-member-variables-in-classes) + Ed25519_FieldElement xy2d; // NOLINT(misc-non-private-member-variables-in-classes) + + static constexpr Ed25519_Point_Niels identity() { + Ed25519_Point_Niels h; + h.yplusx = Ed25519_FieldElement::one(); + h.yminusx = Ed25519_FieldElement::one(); + h.xy2d = Ed25519_FieldElement::zero(); + return h; + } +}; -void ge_p1p1_to_p3(ge_p3* r, const ge_p1p1* p) { - fe_mul(r->X, p->X, p->T); - fe_mul(r->Y, p->Y, p->Z); - fe_mul(r->Z, p->Z, p->T); - fe_mul(r->T, p->X, p->Y); -} +class Ed25519_Point_Cached final { + public: + Ed25519_FieldElement YplusX; // NOLINT(misc-non-private-member-variables-in-classes) + Ed25519_FieldElement YminusX; // NOLINT(misc-non-private-member-variables-in-classes) + Ed25519_FieldElement Z; // NOLINT(misc-non-private-member-variables-in-classes) + Ed25519_FieldElement T2d; // NOLINT(misc-non-private-member-variables-in-classes) + + /** + * Point conversion + */ + static Ed25519_Point_Cached from(const Ed25519_Point_Extended& p) { + static constexpr Ed25519_FieldElement d2 = { + -21827239, -5839606, -30745221, 13898782, 229458, 15978800, -12551817, -6495438, 29715968, 9444199}; + Ed25519_Point_Cached r; + r.YplusX = p.Y + p.X; + r.YminusX = p.Y - p.X; + r.Z = p.Z; + r.T2d = p.T * d2; + return r; + } + + /** + * Point conversion + */ + static Ed25519_Point_Cached from(const Ed25519_Point_Completed& p) { + return Ed25519_Point_Cached::from(Ed25519_Point_Extended::from(p)); + } +}; /* -r = 2 * p +* Point addition */ - -void ge_p2_dbl(ge_p1p1* r, const ge_p2* p) { - FE_25519 t0; - /* qhasm: XX=X1^2 */ - /* asm 1: fe_sq(>XX=fe#1,XX=r->X,X); */ - fe_sq(r->X, p->X); - - /* qhasm: YY=Y1^2 */ - /* asm 1: fe_sq(>YY=fe#3,YY=r->Z,Y); */ - fe_sq(r->Z, p->Y); - - /* qhasm: B=2*Z1^2 */ - /* asm 1: fe_sq2(>B=fe#4,B=r->T,Z); */ - fe_sq2(r->T, p->Z); - - /* qhasm: A=X1+Y1 */ - /* asm 1: fe_add(>A=fe#2,A=r->Y,X,Y); */ - fe_add(r->Y, p->X, p->Y); - - /* qhasm: AA=A^2 */ - /* asm 1: fe_sq(>AA=fe#5,AA=t0,Y); */ - fe_sq(t0, r->Y); - - /* qhasm: Y3=YY+XX */ - /* asm 1: fe_add(>Y3=fe#2,Y3=r->Y,Z,X); */ - fe_add(r->Y, r->Z, r->X); - - /* qhasm: Z3=YY-XX */ - /* asm 1: fe_sub(>Z3=fe#3,Z3=r->Z,Z,X); */ - fe_sub(r->Z, r->Z, r->X); - - /* qhasm: X3=AA-Y3 */ - /* asm 1: fe_sub(>X3=fe#1,X3=r->X,Y); */ - fe_sub(r->X, t0, r->Y); - - /* qhasm: T3=B-Z3 */ - /* asm 1: fe_sub(>T3=fe#4,T3=r->T,T,Z); */ - fe_sub(r->T, r->T, r->Z); -} - -void ge_p3_0(ge_p3* h) { - fe_0(h->X); - fe_1(h->Y); - fe_1(h->Z); - fe_0(h->T); +inline Ed25519_Point_Completed operator+(const Ed25519_Point_Extended& p, const Ed25519_Point_Cached& q) { + Ed25519_Point_Completed r; + r.X = p.Y + p.X; // YpX1 = Y1+X1 + r.Y = p.Y - p.X; // YmX1 = Y1-X1 + r.Z = r.X * q.YplusX; // A = YpX1*YpX2 + r.Y = r.Y * q.YminusX; // B = YmX1*YmX2 + r.T = q.T2d * p.T; // C = T2d2*T1 + r.X = p.Z * q.Z; // ZZ = Z1*Z2 + auto t0 = r.X + r.X; // D = 2*ZZ + r.X = r.Z - r.Y; // X3 = A-B + r.Y = r.Z + r.Y; // Y3 = A+B + r.Z = t0 + r.T; // Z3 = D+C + r.T = t0 - r.T; // T3 = D-C + return r; } /* -r = 2 * p +* Point addition */ - -void ge_p3_dbl(ge_p1p1* r, const ge_p3* p) { - ge_p2 q; - // Convert to p2 rep - q.X = p->X; - q.Y = p->Y; - q.Z = p->Z; - ge_p2_dbl(r, &q); +inline Ed25519_Point_Completed operator+(const Ed25519_Point_Extended& p, const Ed25519_Point_Niels& q) { + Ed25519_Point_Completed r; + r.X = p.Y + p.X; // YpX1 = Y1+X1 + r.Y = p.Y - p.X; // YmX1 = Y1-X1 + r.Z = r.X * q.yplusx; // A = YpX1*ypx2 + r.Y = r.Y * q.yminusx; // B = YmX1*ymx2 + r.T = q.xy2d * p.T; // C = xy2d2*T1 + auto t0 = p.Z + p.Z; // D = 2*Z1 + r.X = r.Z - r.Y; // X3 = A-B + r.Y = r.Z + r.Y; // Y3 = A+B + r.Z = t0 + r.T; // Z3 = D+C + r.T = t0 - r.T; // T3 = D-C + return r; } /* -r = p +* Point subtraction */ - -void ge_p3_to_cached(ge_cached* r, const ge_p3* p) { - static const FE_25519 d2 = { - -21827239, -5839606, -30745221, 13898782, 229458, 15978800, -12551817, -6495438, 29715968, 9444199}; - fe_add(r->YplusX, p->Y, p->X); - fe_sub(r->YminusX, p->Y, p->X); - fe_copy(r->Z, p->Z); - fe_mul(r->T2d, p->T, d2); +inline Ed25519_Point_Completed operator-(const Ed25519_Point_Extended& p, const Ed25519_Point_Niels& q) { + Ed25519_Point_Completed r; + r.X = p.Y + p.X; // YpX1 = Y1+X1 + r.Y = p.Y - p.X; // YmX1 = Y1-X1 + r.Z = r.X * q.yminusx; // A = YpX1*ymx2 + r.Y = r.Y * q.yplusx; // B = YmX1*ypx2 + r.T = q.xy2d * p.T; // C = xy2d2*T1 + auto t0 = p.Z + p.Z; // D = 2*Z1 + r.X = r.Z - r.Y; // X3 = A-B + r.Y = r.Z + r.Y; // Y3 = A+B + r.Z = t0 - r.T; // Z3 = D-C + r.T = t0 + r.T; // T3 = D+C + return r; } /* -r = p - q +* Point subtraction */ - -void ge_sub(ge_p1p1* r, const ge_p3* p, const ge_cached* q) { - FE_25519 t0; - /* qhasm: YpX1 = Y1+X1 */ - /* asm 1: fe_add(>YpX1=fe#1,YpX1=r->X,Y,X); */ - fe_add(r->X, p->Y, p->X); - - /* qhasm: YmX1 = Y1-X1 */ - /* asm 1: fe_sub(>YmX1=fe#2,YmX1=r->Y,Y,X); */ - fe_sub(r->Y, p->Y, p->X); - - /* qhasm: A = YpX1*YmX2 */ - /* asm 1: fe_mul(>A=fe#3,A=r->Z,X,YminusX); */ - fe_mul(r->Z, r->X, q->YminusX); - - /* qhasm: B = YmX1*YpX2 */ - /* asm 1: fe_mul(>B=fe#2,B=r->Y,Y,YplusX); */ - fe_mul(r->Y, r->Y, q->YplusX); - - /* qhasm: C = T2d2*T1 */ - /* asm 1: fe_mul(>C=fe#4,C=r->T,T2d,T); */ - fe_mul(r->T, q->T2d, p->T); - - /* qhasm: ZZ = Z1*Z2 */ - /* asm 1: fe_mul(>ZZ=fe#1,ZZ=r->X,Z,Z); */ - fe_mul(r->X, p->Z, q->Z); - - /* qhasm: D = 2*ZZ */ - /* asm 1: fe_add(>D=fe#5,D=t0,X,X); */ - fe_add(t0, r->X, r->X); - - /* qhasm: X3 = A-B */ - /* asm 1: fe_sub(>X3=fe#1,X3=r->X,Z,Y); */ - fe_sub(r->X, r->Z, r->Y); - - /* qhasm: Y3 = A+B */ - /* asm 1: fe_add(>Y3=fe#2,Y3=r->Y,Z,Y); */ - fe_add(r->Y, r->Z, r->Y); - - /* qhasm: Z3 = D-C */ - /* asm 1: fe_sub(>Z3=fe#3,Z3=r->Z,T); */ - fe_sub(r->Z, t0, r->T); - - /* qhasm: T3 = D+C */ - /* asm 1: fe_add(>T3=fe#4,T3=r->T,T); */ - fe_add(r->T, t0, r->T); +inline Ed25519_Point_Completed operator-(const Ed25519_Point_Extended& p, const Ed25519_Point_Cached& q) { + Ed25519_Point_Completed r; + r.X = p.Y + p.X; // YpX1 = Y1+X1 + r.Y = p.Y - p.X; // YmX1 = Y1-X1 + r.Z = r.X * q.YminusX; // A = YpX1*YmX2 + r.Y = r.Y * q.YplusX; // B = YmX1*YpX2 + r.T = q.T2d * p.T; // C = T2d2*T1 + r.X = p.Z * q.Z; // ZZ = Z1*Z2 + auto t0 = r.X + r.X; // D = 2*ZZ + r.X = r.Z - r.Y; // X3 = A-B + r.Y = r.Z + r.Y; // Y3 = A+B + r.Z = t0 - r.T; // Z3 = D-C + r.T = t0 + r.T; // T3 = D+C + return r; } -void slide(int8_t* r, const uint8_t* a) { +std::array slide(const uint8_t* a) { + std::array r{}; for(size_t i = 0; i < 256; ++i) { r[i] = 1 & (a[i >> 3] >> (i & 7)); } for(size_t i = 0; i < 256; ++i) { - if(r[i]) { + if(r[i] != 0) { for(size_t b = 1; b <= 6 && i + b < 256; ++b) { - if(r[i + b]) { + if(r[i + b] != 0) { if(r[i] + (r[i + b] << b) <= 15) { r[i] += r[i + b] << b; r[i + b] = 0; } else if(r[i] - (r[i + b] << b) >= -15) { r[i] -= r[i + b] << b; for(size_t k = i + b; k < 256; ++k) { - if(!r[k]) { + if(r[k] == 0) { r[k] = 1; break; } @@ -400,74 +298,50 @@ } } } -} - -void ge_tobytes(uint8_t* s, const ge_p2* h) { - FE_25519 recip; - FE_25519 x; - FE_25519 y; - - fe_invert(recip, h->Z); - fe_mul(x, h->X, recip); - fe_mul(y, h->Y, recip); - fe_tobytes(s, y); - s[31] ^= fe_isnegative(x) << 7; -} -void ge_p2_0(ge_p2* h) { - fe_0(h->X); - fe_1(h->Y); - fe_1(h->Z); + return r; } -} // namespace - -int ge_frombytes_negate_vartime(ge_p3* h, const uint8_t* s) { - static const FE_25519 d = { +std::optional frombytes_negate_vartime(std::span s) { + static constexpr Ed25519_FieldElement d = { -10913610, 13857413, -15372611, 6949391, 114729, -8787816, -6275908, -3247719, -18696448, -12055116}; - static const FE_25519 sqrtm1 = { + static constexpr Ed25519_FieldElement sqrtm1 = { -32595792, -7943725, 9377950, 3500415, 12389472, -272473, -25146209, -2005654, 326686, 11406482}; - FE_25519 u; - FE_25519 v; - FE_25519 v3; - FE_25519 vxx; - FE_25519 check; - - fe_frombytes(h->Y, s); - fe_1(h->Z); - fe_sq(u, h->Y); - fe_mul(v, u, d); - fe_sub(u, u, h->Z); /* u = y^2-1 */ - fe_add(v, v, h->Z); /* v = dy^2+1 */ - - fe_sq(v3, v); - fe_mul(v3, v3, v); /* v3 = v^3 */ - fe_sq(h->X, v3); - fe_mul(h->X, h->X, v); - fe_mul(h->X, h->X, u); /* x = uv^7 */ - - fe_pow22523(h->X, h->X); /* x = (uv^7)^((q-5)/8) */ - fe_mul(h->X, h->X, v3); - fe_mul(h->X, h->X, u); /* x = uv^3(uv^7)^((q-5)/8) */ - - fe_sq(vxx, h->X); - fe_mul(vxx, vxx, v); - fe_sub(check, vxx, u); /* vx^2-u */ - if(fe_isnonzero(check)) { - fe_add(check, vxx, u); /* vx^2+u */ - if(fe_isnonzero(check)) { - return -1; + auto h = Ed25519_Point_Extended::identity(); + h.Y = Ed25519_FieldElement::deserialize(s.data()); + h.Z = Ed25519_FieldElement::one(); + auto u = h.Y.sqr(); + auto v = u * d; + u = u - h.Z; /* u = y^2-1 */ + v = v + h.Z; /* v = dy^2+1 */ + + auto v3 = v.sqr() * v; + h.X = v3.sqr(); + h.X = h.X * v; + h.X = h.X * u; /* x = uv^7 */ + + h.X = h.X.pow_22523(); + h.X = h.X * v3; + h.X = h.X * u; /* x = uv^3(uv^7)^((q-5)/8) */ + + auto vxx = h.X.sqr(); + vxx = vxx * v; + auto check = vxx - u; /* vx^2-u */ + if(!check.is_zero()) { + check = vxx + u; /* vx^2+u */ + if(!check.is_zero()) { + return {}; } - fe_mul(h->X, h->X, sqrtm1); + h.X = h.X * sqrtm1; } - if(fe_isnegative(h->X) == (s[31] >> 7)) { - fe_neg(h->X, h->X); + if(h.X.is_negative() == bool(s[31] >> 7)) { + h.X = -h.X; } - fe_mul(h->T, h->X, h->Y); - return 0; + h.T = h.X * h.Y; + return h; } /* @@ -477,8 +351,11 @@ B is the Ed25519 base point (x,4/5) with x positive. */ -void ge_double_scalarmult_vartime(uint8_t out[32], const uint8_t* a, const ge_p3* A, const uint8_t* b) { - static const ge_precomp Bi[8] = { +void ge_double_scalarmult_vartime(std::span out, + const uint8_t* a, + const Ed25519_Point_Extended& A, + const uint8_t* b) { + static constexpr Ed25519_Point_Niels Bi[8] = { { {25967493, -14356035, 29566456, 3660896, -12694345, 4014787, 27544626, -11754271, -6079156, 2047605}, {-12545711, 934262, -2722910, 3049990, -727428, 9406986, 12720692, 5043384, 19500929, -15469378}, @@ -521,78 +398,112 @@ }, }; - int8_t aslide[256]; - int8_t bslide[256]; - ge_cached Ai[8]; /* A,3A,5A,7A,9A,11A,13A,15A */ - ge_p1p1 t; - ge_p3 u; - ge_p3 A2; - ge_p2 r; - int i; - - slide(aslide, a); - slide(bslide, b); - - ge_p3_to_cached(&Ai[0], A); - ge_p3_dbl(&t, A); - ge_p1p1_to_p3(&A2, &t); - ge_add(&t, &A2, &Ai[0]); - ge_p1p1_to_p3(&u, &t); - ge_p3_to_cached(&Ai[1], &u); - ge_add(&t, &A2, &Ai[1]); - ge_p1p1_to_p3(&u, &t); - ge_p3_to_cached(&Ai[2], &u); - ge_add(&t, &A2, &Ai[2]); - ge_p1p1_to_p3(&u, &t); - ge_p3_to_cached(&Ai[3], &u); - ge_add(&t, &A2, &Ai[3]); - ge_p1p1_to_p3(&u, &t); - ge_p3_to_cached(&Ai[4], &u); - ge_add(&t, &A2, &Ai[4]); - ge_p1p1_to_p3(&u, &t); - ge_p3_to_cached(&Ai[5], &u); - ge_add(&t, &A2, &Ai[5]); - ge_p1p1_to_p3(&u, &t); - ge_p3_to_cached(&Ai[6], &u); - ge_add(&t, &A2, &Ai[6]); - ge_p1p1_to_p3(&u, &t); - ge_p3_to_cached(&Ai[7], &u); - - ge_p2_0(&r); - - for(i = 255; i >= 0; --i) { - if(aslide[i] || bslide[i]) { - break; - } + auto aslide = slide(a); + auto bslide = slide(b); + + Ed25519_Point_Cached Ai[8]; /* A,3A,5A,7A,9A,11A,13A,15A */ + Ai[0] = Ed25519_Point_Cached::from(A); + const auto A2 = Ed25519_Point_Extended::from(A.dbl()); + + for(size_t i = 1; i != 8; ++i) { + Ai[i] = Ed25519_Point_Cached::from(A2 + Ai[i - 1]); } + auto r = Ed25519_Point_Projective::identity(); + + int i = [&]() -> int { + int w = 255; + while(w >= 0) { + if(aslide[w] != 0 || bslide[w] != 0) { + return w; + } else { + w--; + } + } + return 0; + }(); + for(; i >= 0; --i) { - ge_p2_dbl(&t, &r); + auto t = r.dbl(); if(aslide[i] > 0) { - ge_p1p1_to_p3(&u, &t); - ge_add(&t, &u, &Ai[aslide[i] >> 1]); + t = Ed25519_Point_Extended::from(t) + Ai[aslide[i] >> 1]; } else if(aslide[i] < 0) { - ge_p1p1_to_p3(&u, &t); - ge_sub(&t, &u, &Ai[(-aslide[i]) >> 1]); + t = Ed25519_Point_Extended::from(t) - Ai[(-aslide[i]) >> 1]; } if(bslide[i] > 0) { - ge_p1p1_to_p3(&u, &t); - ge_madd(&t, &u, &Bi[bslide[i] >> 1]); + t = Ed25519_Point_Extended::from(t) + Bi[bslide[i] >> 1]; } else if(bslide[i] < 0) { - ge_p1p1_to_p3(&u, &t); - ge_msub(&t, &u, &Bi[(-bslide[i]) >> 1]); + t = Ed25519_Point_Extended::from(t) - Bi[(-bslide[i]) >> 1]; } - ge_p1p1_to_p2(&r, &t); + r = Ed25519_Point_Projective::from(t); } - ge_tobytes(out, &r); + r.serialize_to(std::span{out}); +} + +inline uint32_t equal32(uint8_t b, uint8_t c) { + return CT::Mask::is_equal(b, c).value(); +} + +inline uint8_t negative(int8_t b) { + return static_cast(b) >> 7; +} + +Ed25519_Point_Niels select(const Ed25519_Point_Niels base[8], int8_t b) { + const uint8_t bnegative = negative(b); + const uint8_t babs = b - ((-static_cast(bnegative) & b) * 2); + const uint32_t neg_mask = equal32(bnegative, 1); + + const uint32_t mask1 = equal32(babs, 1); + const uint32_t mask2 = equal32(babs, 2); + const uint32_t mask3 = equal32(babs, 3); + const uint32_t mask4 = equal32(babs, 4); + const uint32_t mask5 = equal32(babs, 5); + const uint32_t mask6 = equal32(babs, 6); + const uint32_t mask7 = equal32(babs, 7); + const uint32_t mask8 = equal32(babs, 8); + + auto t = Ed25519_Point_Niels::identity(); + + for(size_t i = 0; i != 10; ++i) { + t.yplusx[i] = t.yplusx[i] ^ ((t.yplusx[i] ^ base[0].yplusx[i]) & mask1) ^ + ((t.yplusx[i] ^ base[1].yplusx[i]) & mask2) ^ ((t.yplusx[i] ^ base[2].yplusx[i]) & mask3) ^ + ((t.yplusx[i] ^ base[3].yplusx[i]) & mask4) ^ ((t.yplusx[i] ^ base[4].yplusx[i]) & mask5) ^ + ((t.yplusx[i] ^ base[5].yplusx[i]) & mask6) ^ ((t.yplusx[i] ^ base[6].yplusx[i]) & mask7) ^ + ((t.yplusx[i] ^ base[7].yplusx[i]) & mask8); + + t.yminusx[i] = t.yminusx[i] ^ ((t.yminusx[i] ^ base[0].yminusx[i]) & mask1) ^ + ((t.yminusx[i] ^ base[1].yminusx[i]) & mask2) ^ ((t.yminusx[i] ^ base[2].yminusx[i]) & mask3) ^ + ((t.yminusx[i] ^ base[3].yminusx[i]) & mask4) ^ ((t.yminusx[i] ^ base[4].yminusx[i]) & mask5) ^ + ((t.yminusx[i] ^ base[5].yminusx[i]) & mask6) ^ ((t.yminusx[i] ^ base[6].yminusx[i]) & mask7) ^ + ((t.yminusx[i] ^ base[7].yminusx[i]) & mask8); + + t.xy2d[i] = t.xy2d[i] ^ ((t.xy2d[i] ^ base[0].xy2d[i]) & mask1) ^ ((t.xy2d[i] ^ base[1].xy2d[i]) & mask2) ^ + ((t.xy2d[i] ^ base[2].xy2d[i]) & mask3) ^ ((t.xy2d[i] ^ base[3].xy2d[i]) & mask4) ^ + ((t.xy2d[i] ^ base[4].xy2d[i]) & mask5) ^ ((t.xy2d[i] ^ base[5].xy2d[i]) & mask6) ^ + ((t.xy2d[i] ^ base[6].xy2d[i]) & mask7) ^ ((t.xy2d[i] ^ base[7].xy2d[i]) & mask8); + } + + auto minus_xy2d = -t.xy2d; + + // If negative have to swap yminusx and yplusx + for(size_t i = 0; i != 10; ++i) { + const int32_t t_yplusx = t.yplusx[i] ^ ((t.yplusx[i] ^ t.yminusx[i]) & neg_mask); + const int32_t t_yminusx = t.yminusx[i] ^ ((t.yminusx[i] ^ t.yplusx[i]) & neg_mask); + + t.yplusx[i] = t_yplusx; + t.yminusx[i] = t_yminusx; + t.xy2d[i] = t.xy2d[i] ^ ((t.xy2d[i] ^ minus_xy2d[i]) & neg_mask); + } + + return t; } /* base[i][j] = (j+1)*256^i*B */ -static const ge_precomp B_precomp[32][8] = { +constexpr Ed25519_Point_Niels B_precomp[32][8] = { { { {25967493, -14356035, 29566456, 3660896, -12694345, 4014787, 27544626, -11754271, -6079156, 2047605}, @@ -1939,96 +1850,6 @@ }, }; -namespace { - -inline uint8_t equal(int8_t b, int8_t c) { - uint8_t ub = b; - uint8_t uc = c; - uint8_t x = ub ^ uc; /* 0: yes; 1..255: no */ - uint32_t y = x; /* 0: yes; 1..255: no */ - y -= 1; /* 4294967295: yes; 0..254: no */ - y >>= 31; /* 1: yes; 0: no */ - return static_cast(y); -} - -inline int32_t equal32(int8_t b, int8_t c) { - return -static_cast(equal(b, c)); -} - -inline uint8_t negative(int8_t b) { - /* 18446744073709551361..18446744073709551615: yes; 0..255: no */ - uint64_t x = b; // NOLINT(bugprone-signed-char-misuse,cert-str34-c) - x >>= 63; /* 1: yes; 0: no */ - return static_cast(x); -} - -inline void ge_precomp_0(ge_precomp* h) { - fe_1(h->yplusx); - fe_1(h->yminusx); - fe_0(h->xy2d); -} - -inline void select(ge_precomp* t, const ge_precomp* base, int8_t b) { - const uint8_t bnegative = negative(b); - const uint8_t babs = b - ((-static_cast(bnegative) & b) * 2); - const int32_t neg_mask = equal32(bnegative, 1); - - const int32_t mask1 = equal32(babs, 1); - const int32_t mask2 = equal32(babs, 2); - const int32_t mask3 = equal32(babs, 3); - const int32_t mask4 = equal32(babs, 4); - const int32_t mask5 = equal32(babs, 5); - const int32_t mask6 = equal32(babs, 6); - const int32_t mask7 = equal32(babs, 7); - const int32_t mask8 = equal32(babs, 8); - - ge_precomp_0(t); - - for(size_t i = 0; i != 10; ++i) { - t->yplusx[i] = t->yplusx[i] ^ ((t->yplusx[i] ^ base[0].yplusx[i]) & mask1) ^ - ((t->yplusx[i] ^ base[1].yplusx[i]) & mask2) ^ ((t->yplusx[i] ^ base[2].yplusx[i]) & mask3) ^ - ((t->yplusx[i] ^ base[3].yplusx[i]) & mask4) ^ ((t->yplusx[i] ^ base[4].yplusx[i]) & mask5) ^ - ((t->yplusx[i] ^ base[5].yplusx[i]) & mask6) ^ ((t->yplusx[i] ^ base[6].yplusx[i]) & mask7) ^ - ((t->yplusx[i] ^ base[7].yplusx[i]) & mask8); - - t->yminusx[i] = t->yminusx[i] ^ ((t->yminusx[i] ^ base[0].yminusx[i]) & mask1) ^ - ((t->yminusx[i] ^ base[1].yminusx[i]) & mask2) ^ ((t->yminusx[i] ^ base[2].yminusx[i]) & mask3) ^ - ((t->yminusx[i] ^ base[3].yminusx[i]) & mask4) ^ ((t->yminusx[i] ^ base[4].yminusx[i]) & mask5) ^ - ((t->yminusx[i] ^ base[5].yminusx[i]) & mask6) ^ ((t->yminusx[i] ^ base[6].yminusx[i]) & mask7) ^ - ((t->yminusx[i] ^ base[7].yminusx[i]) & mask8); - - t->xy2d[i] = t->xy2d[i] ^ ((t->xy2d[i] ^ base[0].xy2d[i]) & mask1) ^ ((t->xy2d[i] ^ base[1].xy2d[i]) & mask2) ^ - ((t->xy2d[i] ^ base[2].xy2d[i]) & mask3) ^ ((t->xy2d[i] ^ base[3].xy2d[i]) & mask4) ^ - ((t->xy2d[i] ^ base[4].xy2d[i]) & mask5) ^ ((t->xy2d[i] ^ base[5].xy2d[i]) & mask6) ^ - ((t->xy2d[i] ^ base[6].xy2d[i]) & mask7) ^ ((t->xy2d[i] ^ base[7].xy2d[i]) & mask8); - } - - FE_25519 minus_xy2d; - fe_neg(minus_xy2d, t->xy2d); - - // If negative have to swap yminusx and yplusx - for(size_t i = 0; i != 10; ++i) { - int32_t t_yplusx = t->yplusx[i] ^ ((t->yplusx[i] ^ t->yminusx[i]) & neg_mask); - int32_t t_yminusx = t->yminusx[i] ^ ((t->yminusx[i] ^ t->yplusx[i]) & neg_mask); - - t->yplusx[i] = t_yplusx; - t->yminusx[i] = t_yminusx; - t->xy2d[i] = t->xy2d[i] ^ ((t->xy2d[i] ^ minus_xy2d[i]) & neg_mask); - } -} - -void ge_p3_tobytes(uint8_t* s, const ge_p3* h) { - FE_25519 recip; - FE_25519 x; - FE_25519 y; - - fe_invert(recip, h->Z); - fe_mul(x, h->X, recip); - fe_mul(y, h->Y, recip); - fe_tobytes(s, y); - s[31] ^= fe_isnegative(x) << 7; -} - } // namespace /* @@ -2039,25 +1860,19 @@ Preconditions: a[31] <= 127 */ +void ed25519_basepoint_mul(std::span out, const uint8_t a[32]) { + std::array e{}; + + CT::poison(a, 32); -void ge_scalarmult_base(uint8_t out[32], const uint8_t a[32]) { - int8_t e[64]; - int8_t carry; - ge_p1p1 r; - ge_p2 s; - ge_p3 h; - ge_precomp t; - int i; - - for(i = 0; i < 32; ++i) { - e[2 * i + 0] = (a[i] >> 0) & 15; - e[2 * i + 1] = (a[i] >> 4) & 15; + // each e[i] is between 0 and 15 except e[63] which is between 0 and 7 + for(size_t i = 0; i != 32; ++i) { + e[2 * i + 0] = (a[i] >> 0) & 0x0F; + e[2 * i + 1] = (a[i] >> 4) & 0x0F; } - /* each e[i] is between 0 and 15 */ - /* e[63] is between 0 and 7 */ - carry = 0; - for(i = 0; i < 63; ++i) { + int8_t carry = 0; + for(size_t i = 0; i < 63; ++i) { e[i] += carry; carry = e[i] + 8; carry >>= 4; @@ -2066,29 +1881,33 @@ e[63] += carry; /* each e[i] is between -8 and 8 */ - ge_p3_0(&h); - for(i = 1; i < 64; i += 2) { - select(&t, B_precomp[i / 2], e[i]); - ge_madd(&r, &h, &t); - ge_p1p1_to_p3(&h, &r); + auto h = Ed25519_Point_Extended::identity(); + for(size_t i = 1; i < 64; i += 2) { + h = Ed25519_Point_Extended::from(h + select(B_precomp[i / 2], e[i])); } - ge_p3_dbl(&r, &h); - ge_p1p1_to_p2(&s, &r); - ge_p2_dbl(&r, &s); - ge_p1p1_to_p2(&s, &r); - ge_p2_dbl(&r, &s); - ge_p1p1_to_p2(&s, &r); - ge_p2_dbl(&r, &s); - ge_p1p1_to_p3(&h, &r); - - for(i = 0; i < 64; i += 2) { - select(&t, B_precomp[i / 2], e[i]); - ge_madd(&r, &h, &t); - ge_p1p1_to_p3(&h, &r); + auto s = Ed25519_Point_Projective::from(h.dbl()); + s = Ed25519_Point_Projective::from(s.dbl()); + s = Ed25519_Point_Projective::from(s.dbl()); + h = Ed25519_Point_Extended::from(s.dbl()); + + for(size_t i = 0; i != 64; i += 2) { + h = Ed25519_Point_Extended::from(h + select(B_precomp[i / 2], e[i])); } - ge_p3_tobytes(out, &h); + h.serialize_to(out); + + CT::unpoison(a, 32); + CT::unpoison(out); +} + +bool signature_check(std::span pk, const uint8_t h[32], const uint8_t r[32], const uint8_t s[32]) { + if(auto A = frombytes_negate_vartime(pk)) { + std::array rcheck{}; + ge_double_scalarmult_vartime(rcheck, h, *A, s); + return CT::is_equal(rcheck.data(), r, 32).as_bool(); + } + return false; } } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/elgamal/elgamal.cpp botan3-3.12.0+dfsg/src/lib/pubkey/elgamal/elgamal.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/elgamal/elgamal.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/elgamal/elgamal.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -105,12 +106,12 @@ /** * ElGamal encryption operation */ -class ElGamal_Encryption_Operation final : public PK_Ops::Encryption_with_EME { +class ElGamal_Encryption_Operation final : public PK_Ops::Encryption_with_Padding { public: - ElGamal_Encryption_Operation(const std::shared_ptr& key, std::string_view eme) : - PK_Ops::Encryption_with_EME(eme), m_key(key) { + ElGamal_Encryption_Operation(const std::shared_ptr& key, std::string_view padding) : + PK_Ops::Encryption_with_Padding(padding), m_key(key) { const size_t powm_window = 4; - m_monty_y_p = monty_precompute(m_key->group().monty_params_p(), m_key->public_key(), powm_window); + m_monty_y_p = monty_precompute(m_key->group()._monty_params_p(), m_key->public_key(), powm_window); } size_t ciphertext_length(size_t /*ptext_len*/) const override { return 2 * m_key->group().p_bytes(); } @@ -121,12 +122,12 @@ private: std::shared_ptr m_key; - std::shared_ptr m_monty_y_p; + std::shared_ptr m_monty_y_p; }; std::vector ElGamal_Encryption_Operation::raw_encrypt(std::span ptext, RandomNumberGenerator& rng) { - BigInt m(ptext); + const BigInt m(ptext); const auto& group = m_key->group(); @@ -148,18 +149,23 @@ const BigInt a = group.power_g_p(k, k_bits); const BigInt b = group.multiply_mod_p(m, monty_execute(*m_monty_y_p, k, k_bits).value()); - return unlock(BigInt::encode_fixed_length_int_pair(a, b, group.p_bytes())); + const size_t p_bytes = group.p_bytes(); + std::vector ctext(2 * p_bytes); + BufferStuffer stuffer(ctext); + a.serialize_to(stuffer.next(p_bytes)); + b.serialize_to(stuffer.next(p_bytes)); + return ctext; } /** * ElGamal decryption operation */ -class ElGamal_Decryption_Operation final : public PK_Ops::Decryption_with_EME { +class ElGamal_Decryption_Operation final : public PK_Ops::Decryption_with_Padding { public: ElGamal_Decryption_Operation(const std::shared_ptr& key, - std::string_view eme, + std::string_view padding, RandomNumberGenerator& rng) : - PK_Ops::Decryption_with_EME(eme), + PK_Ops::Decryption_with_Padding(padding), m_key(key), m_blinder( m_key->group()._reducer_mod_p(), diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/elgamal/elgamal.h botan3-3.12.0+dfsg/src/lib/pubkey/elgamal/elgamal.h --- botan3-3.7.1+dfsg/src/lib/pubkey/elgamal/elgamal.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/elgamal/elgamal.h 2026-05-07 01:38:28.000000000 +0000 @@ -63,7 +63,7 @@ ElGamal_PublicKey() = default; - ElGamal_PublicKey(std::shared_ptr key) : m_public_key(std::move(key)) {} + explicit ElGamal_PublicKey(std::shared_ptr key) : m_public_key(std::move(key)) {} std::shared_ptr m_public_key; }; @@ -99,7 +99,7 @@ */ ElGamal_PrivateKey(const DL_Group& group, const BigInt& private_key); - bool check_key(RandomNumberGenerator& rng, bool) const override; + bool check_key(RandomNumberGenerator& rng, bool strong) const override; std::unique_ptr public_key() const override; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/elgamal/info.txt botan3-3.12.0+dfsg/src/lib/pubkey/elgamal/info.txt --- botan3-3.7.1+dfsg/src/lib/pubkey/elgamal/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/elgamal/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -12,7 +12,7 @@ dl_group keypair numbertheory -pk_pad +enc_padding diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/frodokem/frodokem_aes/frodo_aes_generator.h botan3-3.12.0+dfsg/src/lib/pubkey/frodokem/frodokem_aes/frodo_aes_generator.h --- botan3-3.7.1+dfsg/src/lib/pubkey/frodokem/frodokem_aes/frodo_aes_generator.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/frodokem/frodokem_aes/frodo_aes_generator.h 2026-05-07 01:38:28.000000000 +0000 @@ -12,12 +12,10 @@ #define BOTAN_FRODOKEM_AES_GENERATOR_H_ #include +#include #include #include #include -#include - -#include #include namespace Botan { diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodo_constants.cpp botan3-3.12.0+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodo_constants.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodo_constants.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodo_constants.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -10,6 +10,7 @@ #include +#include #include namespace Botan { @@ -96,9 +97,8 @@ FrodoKEMConstants::~FrodoKEMConstants() = default; -XOF& FrodoKEMConstants::SHAKE_XOF() const { - m_shake_xof->clear(); - return *m_shake_xof; +std::unique_ptr FrodoKEMConstants::create_xof() const { + return m_shake_xof->new_object(); } } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodo_constants.h botan3-3.12.0+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodo_constants.h --- botan3-3.7.1+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodo_constants.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodo_constants.h 2026-05-07 01:38:28.000000000 +0000 @@ -24,7 +24,7 @@ class BOTAN_TEST_API FrodoKEMConstants final { public: - FrodoKEMConstants(FrodoKEMMode mode); + explicit FrodoKEMConstants(FrodoKEMMode mode); ~FrodoKEMConstants(); @@ -76,9 +76,7 @@ FrodoDomainSeparator keygen_domain_separator() const { return FrodoDomainSeparator({0x5F}); } - // TODO: those aren't actually const. We worked around some constness - // issues when playing with the XOFs that are residing in this class. - XOF& SHAKE_XOF() const; + std::unique_ptr create_xof() const; private: FrodoKEMMode m_mode; @@ -93,7 +91,7 @@ std::vector m_cdf_table; // Distribution table T_chi - mutable std::unique_ptr m_shake_xof; + std::unique_ptr m_shake_xof; std::string m_shake; }; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodo_matrix.cpp botan3-3.12.0+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodo_matrix.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodo_matrix.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodo_matrix.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,7 +1,7 @@ /* * FrodoKEM matrix logic * Based on the MIT licensed reference implementation by the designers - * (https://github.com/microsoft/PQCrypto-LWEKE/tree/master/src) + * (https://github.com/microsoft/PQCrypto-LWEKE/tree/master) * * The Fellowship of the FrodoKEM: * (C) 2023 Jack Lloyd @@ -13,14 +13,15 @@ #include #include -#include -#include -#include #include #include +#include #include #include -#include +#include +#include +#include +#include #if defined(BOTAN_HAS_FRODOKEM_AES) #include @@ -30,14 +31,6 @@ #include #endif -#include -#include -#include -#include -#include -#include -#include - namespace Botan { namespace { @@ -130,8 +123,8 @@ std::vector a_row_data(4 * constants.n(), 0); // TODO: maybe use std::as_bytes() instead // (take extra care, as it produces a std::span) - std::span a_row_data_bytes(reinterpret_cast(a_row_data.data()), - sizeof(uint16_t) * a_row_data.size()); + const std::span a_row_data_bytes(reinterpret_cast(a_row_data.data()), + sizeof(uint16_t) * a_row_data.size()); for(size_t i = 0; i < constants.n(); i += 4) { auto a_row = BufferStuffer(a_row_data_bytes); @@ -189,8 +182,8 @@ */ std::vector a_row_data(8 * constants.n(), 0); // TODO: maybe use std::as_bytes() - std::span a_row_data_bytes(reinterpret_cast(a_row_data.data()), - sizeof(uint16_t) * a_row_data.size()); + const std::span a_row_data_bytes(reinterpret_cast(a_row_data.data()), + sizeof(uint16_t) * a_row_data.size()); // Start matrix multiplication for(size_t i = 0; i < constants.n(); i += 8) { @@ -211,7 +204,7 @@ for(size_t j = 0; j < constants.n_bar(); ++j) { uint16_t sum = 0; - std::array sp; + std::array sp{}; for(size_t p = 0; p < 8; ++p) { sp[p] = s.elements_at(j * constants.n() + i + p); } @@ -318,7 +311,7 @@ } FrodoMatrix FrodoMatrix::mul_bs(const FrodoKEMConstants& constants, const FrodoMatrix& b, const FrodoMatrix& s) { - Dimensions dimensions = {constants.n_bar(), constants.n_bar()}; + const Dimensions dimensions = {constants.n_bar(), constants.n_bar()}; auto elements = make_elements_vector(dimensions); for(size_t i = 0; i < constants.n_bar(); ++i) { @@ -453,7 +446,7 @@ while(b < lsb) { const uint8_t nbits = std::min(static_cast(lsb - b), bits); const uint16_t mask = static_cast(1 << nbits) - 1; - uint8_t t = (w >> (bits - nbits)) & mask; // the bits to copy from w to out + const uint8_t t = (w >> (bits - nbits)) & mask; // the bits to copy from w to out elements.at(i) = elements.at(i) + static_cast(t << (lsb - b - nbits)); b += nbits; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodo_matrix.h botan3-3.12.0+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodo_matrix.h --- botan3-3.7.1+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodo_matrix.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodo_matrix.h 2026-05-07 01:38:28.000000000 +0000 @@ -1,7 +1,7 @@ /* * FrodoKEM matrix logic * Based on the MIT licensed reference implementation by the designers - * (https://github.com/microsoft/PQCrypto-LWEKE/tree/master/src) + * (https://github.com/microsoft/PQCrypto-LWEKE/tree/master) * * The Fellowship of the FrodoKEM: * (C) 2023 Jack Lloyd @@ -73,7 +73,7 @@ const Dimensions& dimensions, StrongSpan r); - // Helper function that calls FrodoMatrix::sample on initially provided consts and shake XOF. + // Helper function that calls FrodoMatrix::sample on initially provided constants and shake XOF. // The output function calls shake.output at each invocation. static std::function make_sample_generator( const FrodoKEMConstants& constants, Botan::XOF& shake); diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodo_mode.cpp botan3-3.12.0+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodo_mode.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodo_mode.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodo_mode.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -12,11 +12,6 @@ #include #include -#include - -#include -#include -#include namespace Botan { @@ -76,6 +71,26 @@ return OID::from_string(to_string()); } +bool FrodoKEMMode::is_available() const { + if(is_aes()) { +#if defined(BOTAN_HAS_FRODOKEM_AES) + return true; +#else + return false; +#endif + } + + if(is_shake()) { +#if defined(BOTAN_HAS_FRODOKEM_SHAKE) + return true; +#else + return false; +#endif + } + + return false; +} + std::string FrodoKEMMode::to_string() const { switch(m_mode) { case FrodoKEM640_SHAKE: diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodo_mode.h botan3-3.12.0+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodo_mode.h --- botan3-3.7.1+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodo_mode.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodo_mode.h 2026-05-07 01:38:28.000000000 +0000 @@ -17,9 +17,9 @@ namespace Botan { -class BOTAN_PUBLIC_API(3, 3) FrodoKEMMode { +class BOTAN_PUBLIC_API(3, 3) FrodoKEMMode final { public: - enum Mode { + enum Mode : uint8_t /* NOLINT(*-use-enum-class) */ { FrodoKEM640_SHAKE, FrodoKEM976_SHAKE, FrodoKEM1344_SHAKE, @@ -34,7 +34,9 @@ eFrodoKEM1344_AES }; + // NOLINTNEXTLINE(*-explicit-conversions) FrodoKEMMode(Mode mode); + explicit FrodoKEMMode(const OID& oid); explicit FrodoKEMMode(std::string_view str); @@ -63,18 +65,7 @@ m_mode == FrodoKEM640_AES || m_mode == FrodoKEM976_AES || m_mode == FrodoKEM1344_AES; } - bool is_available() const { - return -#if defined(BOTAN_HAS_FRODOKEM_AES) - is_aes() || -#endif - -#if defined(BOTAN_HAS_FRODOKEM_SHAKE) - is_shake() || -#endif - - false; - } + bool is_available() const; bool operator==(const FrodoKEMMode& other) const { return m_mode == other.m_mode; } diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodo_types.h botan3-3.12.0+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodo_types.h --- botan3-3.7.1+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodo_types.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodo_types.h 2026-05-07 01:38:28.000000000 +0000 @@ -44,7 +44,7 @@ using FrodoSerializedMatrix = Strong, struct FrodoSerializedMatrix_>; // Constant byte 0x5F/0x96 given to SHAKE for domain separation -using FrodoDomainSeparator = Strong, struct FrodoDoaminSeparator_>; +using FrodoDomainSeparator = Strong, struct FrodoDomainSeparator_>; // Bytes of u/u' using FrodoPlaintext = Strong, struct FrodoPlaintext_>; @@ -52,7 +52,7 @@ // Bytes of salt using FrodoSalt = Strong, struct FrodoSalt_>; -// Bytes of k/k' aka intermediate shared secret in FO transform +// Bytes of k/k' aka intermediate shared secret in Fujisaki-Okamoto transform using FrodoIntermediateSharedSecret = Strong, struct FrodoIntermediateSharedSecret_>; } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodokem.cpp botan3-3.12.0+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodokem.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodokem.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodokem.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,7 +1,7 @@ /* - * FrodoKEM implemenation + * FrodoKEM implementation * Based on the MIT licensed reference implementation by the designers - * (https://github.com/microsoft/PQCrypto-LWEKE/tree/master/src) + * (https://github.com/microsoft/PQCrypto-LWEKE/tree/master) * * The Fellowship of the FrodoKEM: * (C) 2023 Jack Lloyd @@ -13,31 +13,31 @@ #include #include -#include +#include #include #include +#include +#include +#include #include #include #include #include -#include #include -#include -#include #include #include #include namespace Botan { -class FrodoKEM_PublicKeyInternal { +class FrodoKEM_PublicKeyInternal final { public: FrodoKEM_PublicKeyInternal(FrodoKEMConstants constants, FrodoSeedA seed_a, FrodoMatrix b) : m_constants(std::move(constants)), m_seed_a(std::move(seed_a)), m_b(std::move(b)) { - auto& shake = m_constants.SHAKE_XOF(); - shake.update(serialize()); - m_hash = shake.output(m_constants.len_sec_bytes()); + auto shake = m_constants.create_xof(); + shake->update(serialize()); + m_hash = shake->output(m_constants.len_sec_bytes()); } const FrodoKEMConstants& constants() const { return m_constants; } @@ -57,7 +57,7 @@ FrodoPublicKeyHash m_hash; }; -class FrodoKEM_PrivateKeyInternal { +class FrodoKEM_PrivateKeyInternal final { public: FrodoKEM_PrivateKeyInternal(FrodoSeedS s, FrodoMatrix s_trans) : m_s(std::move(s)), m_s_trans(std::move(s_trans)) {} @@ -79,6 +79,8 @@ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // +namespace { + class Frodo_KEM_Encryptor final : public PK_Ops::KEM_Encryption_with_KDF { public: Frodo_KEM_Encryptor(std::shared_ptr key, std::string_view kdf) : @@ -91,55 +93,55 @@ void raw_kem_encrypt(std::span out_encapsulated_key, std::span out_shared_key, RandomNumberGenerator& rng) override { - const auto& consts = m_public_key->constants(); - auto& shake = consts.SHAKE_XOF(); - auto sample_generator = FrodoMatrix::make_sample_generator(consts, shake); + const auto& constants = m_public_key->constants(); + auto shake = constants.create_xof(); + auto sample_generator = FrodoMatrix::make_sample_generator(constants, *shake); BufferStuffer out_ct_bs(out_encapsulated_key); - auto c_1 = out_ct_bs.next(consts.len_packed_b_bytes()); - auto c_2 = out_ct_bs.next(consts.len_packed_c_bytes()); - auto salt = out_ct_bs.next(consts.len_salt_bytes()); + auto c_1 = out_ct_bs.next(constants.len_packed_b_bytes()); + auto c_2 = out_ct_bs.next(constants.len_packed_c_bytes()); + auto salt = out_ct_bs.next(constants.len_salt_bytes()); BOTAN_ASSERT_NOMSG(out_ct_bs.full()); - const auto u = rng.random_vec(consts.len_sec_bytes()); + const auto u = rng.random_vec(constants.len_sec_bytes()); rng.randomize(salt); CT::poison(u); - shake.update(m_public_key->hash()); - shake.update(u); - shake.update(salt); - const auto seed_se = shake.output(consts.len_se_bytes()); - const auto k = shake.output(consts.len_sec_bytes()); - shake.clear(); + shake->update(m_public_key->hash()); + shake->update(u); + shake->update(salt); + const auto seed_se = shake->output(constants.len_se_bytes()); + const auto k = shake->output(constants.len_sec_bytes()); + shake->clear(); - shake.update(consts.encapsulation_domain_separator()); - shake.update(seed_se); + shake->update(constants.encapsulation_domain_separator()); + shake->update(seed_se); - const auto s_p = sample_generator(std::tuple(consts.n_bar(), consts.n())); + const auto s_p = sample_generator(std::tuple(constants.n_bar(), constants.n())); - const auto e_p = sample_generator(std::tuple(consts.n_bar(), consts.n())); + const auto e_p = sample_generator(std::tuple(constants.n_bar(), constants.n())); - const auto b_p = FrodoMatrix::mul_add_sa_plus_e(consts, s_p, e_p, m_public_key->seed_a()); + const auto b_p = FrodoMatrix::mul_add_sa_plus_e(constants, s_p, e_p, m_public_key->seed_a()); - b_p.pack(consts, c_1); + b_p.pack(constants, c_1); - const auto e_pp = sample_generator(std::tuple(consts.n_bar(), consts.n_bar())); - shake.clear(); + const auto e_pp = sample_generator(std::tuple(constants.n_bar(), constants.n_bar())); + shake->clear(); - const auto v = FrodoMatrix::mul_add_sb_plus_e(consts, m_public_key->b(), s_p, e_pp); + const auto v = FrodoMatrix::mul_add_sb_plus_e(constants, m_public_key->b(), s_p, e_pp); - const auto encoded = FrodoMatrix::encode(consts, u); + const auto encoded = FrodoMatrix::encode(constants, u); - const auto c = FrodoMatrix::add(consts, v, encoded); + const auto c = FrodoMatrix::add(constants, v, encoded); - c.pack(consts, c_2); + c.pack(constants, c_2); - shake.update(out_encapsulated_key); - shake.update(k); - shake.output(out_shared_key); + shake->update(out_encapsulated_key); + shake->update(k); + shake->output(out_shared_key); CT::unpoison_all(out_shared_key, out_encapsulated_key); } @@ -162,69 +164,69 @@ void raw_kem_decrypt(std::span out_shared_key, std::span encapsulated_key) override { auto scope = CT::scoped_poison(*m_private_key); - const auto& consts = m_public_key->constants(); - auto& shake = consts.SHAKE_XOF(); - auto sample_generator = FrodoMatrix::make_sample_generator(consts, shake); + const auto& constants = m_public_key->constants(); + auto shake = constants.create_xof(); + auto sample_generator = FrodoMatrix::make_sample_generator(constants, *shake); - if(encapsulated_key.size() != consts.len_ct_bytes()) { + if(encapsulated_key.size() != constants.len_ct_bytes()) { throw Invalid_Argument("FrodoKEM ciphertext does not have the correct byte count"); } BufferSlicer ct_bs(encapsulated_key); - auto c_1 = ct_bs.take(consts.len_packed_b_bytes()); - auto c_2 = ct_bs.take(consts.len_packed_c_bytes()); - auto salt = ct_bs.take(consts.len_salt_bytes()); + auto c_1 = ct_bs.take(constants.len_packed_b_bytes()); + auto c_2 = ct_bs.take(constants.len_packed_c_bytes()); + auto salt = ct_bs.take(constants.len_salt_bytes()); BOTAN_ASSERT_NOMSG(ct_bs.empty()); - const auto b_p = FrodoMatrix::unpack(consts, {consts.n_bar(), consts.n()}, c_1); - const auto c = FrodoMatrix::unpack(consts, {consts.n_bar(), consts.n_bar()}, c_2); + const auto b_p = FrodoMatrix::unpack(constants, {constants.n_bar(), constants.n()}, c_1); + const auto c = FrodoMatrix::unpack(constants, {constants.n_bar(), constants.n_bar()}, c_2); - const auto w = FrodoMatrix::mul_bs(consts, b_p, m_private_key->s_trans()); - const auto m = FrodoMatrix::sub(consts, c, w); + const auto w = FrodoMatrix::mul_bs(constants, b_p, m_private_key->s_trans()); + const auto m = FrodoMatrix::sub(constants, c, w); - const auto seed_u_p = m.decode(consts); + const auto seed_u_p = m.decode(constants); - shake.update(m_public_key->hash()); - shake.update(seed_u_p); - shake.update(salt); + shake->update(m_public_key->hash()); + shake->update(seed_u_p); + shake->update(salt); - const auto seed_se_p = shake.output(consts.len_se_bytes()); - const auto k_p = shake.output(consts.len_sec_bytes()); - shake.clear(); + const auto seed_se_p = shake->output(constants.len_se_bytes()); + const auto k_p = shake->output(constants.len_sec_bytes()); + shake->clear(); - shake.update(consts.encapsulation_domain_separator()); - shake.update(seed_se_p); - const auto s_p = sample_generator(std::tuple(consts.n_bar(), consts.n())); + shake->update(constants.encapsulation_domain_separator()); + shake->update(seed_se_p); + const auto s_p = sample_generator(std::tuple(constants.n_bar(), constants.n())); - const auto e_p = sample_generator(std::tuple(consts.n_bar(), consts.n())); + const auto e_p = sample_generator(std::tuple(constants.n_bar(), constants.n())); - auto b_pp = FrodoMatrix::mul_add_sa_plus_e(consts, s_p, e_p, m_public_key->seed_a()); + auto b_pp = FrodoMatrix::mul_add_sa_plus_e(constants, s_p, e_p, m_public_key->seed_a()); - const auto e_pp = sample_generator(std::tuple(consts.n_bar(), consts.n_bar())); - shake.clear(); + const auto e_pp = sample_generator(std::tuple(constants.n_bar(), constants.n_bar())); + shake->clear(); - const auto v = FrodoMatrix::mul_add_sb_plus_e(consts, m_public_key->b(), s_p, e_pp); + const auto v = FrodoMatrix::mul_add_sb_plus_e(constants, m_public_key->b(), s_p, e_pp); - const auto encoded = FrodoMatrix::encode(consts, seed_u_p); - auto c_p = FrodoMatrix::add(consts, v, encoded); + const auto encoded = FrodoMatrix::encode(constants, seed_u_p); + auto c_p = FrodoMatrix::add(constants, v, encoded); // b_p and c are unpacked values that are reduced by definition. // b_pp and c_p are calculated values that need the reduction for // an unambiguous comparison that is required next. - b_pp.reduce(consts); - c_p.reduce(consts); + b_pp.reduce(constants); + c_p.reduce(constants); // The spec concats the matrices b_p and c (b_pp and c_p respectively) // and performs a single CT comparison. For convenience we compare the // matrices individually in CT and CT-&& the resulting masks. const auto cmp = b_p.constant_time_compare(b_pp) & c.constant_time_compare(c_p); - std::vector k_bar(consts.len_sec_bytes(), 0); - CT::conditional_copy_mem(cmp, k_bar.data(), k_p.data(), m_private_key->s().data(), consts.len_sec_bytes()); + secure_vector k_bar(constants.len_sec_bytes(), 0); + CT::conditional_copy_mem(cmp, k_bar.data(), k_p.data(), m_private_key->s().data(), constants.len_sec_bytes()); - shake.update(encapsulated_key); - shake.update(k_bar); - shake.output(out_shared_key); + shake->update(encapsulated_key); + shake->update(k_bar); + shake->output(out_shared_key); CT::unpoison(out_shared_key); } @@ -234,24 +236,26 @@ std::shared_ptr m_private_key; }; +} // namespace + // // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // FrodoKEM_PublicKey::FrodoKEM_PublicKey(std::span pub_key, FrodoKEMMode mode) { - FrodoKEMConstants consts(mode); - if(pub_key.size() != consts.len_public_key_bytes()) { + FrodoKEMConstants constants(mode); + if(pub_key.size() != constants.len_public_key_bytes()) { throw Invalid_Argument("FrodoKEM public key does not have the correct byte count"); } BufferSlicer pk_bs(pub_key); - auto seed_a = pk_bs.copy(consts.len_a_bytes()); - const auto packed_b = pk_bs.take(consts.d() * consts.n() * consts.n_bar() / 8); + auto seed_a = pk_bs.copy(constants.len_a_bytes()); + const auto packed_b = pk_bs.take(constants.d() * constants.n() * constants.n_bar() / 8); BOTAN_ASSERT_NOMSG(pk_bs.empty()); - auto b = FrodoMatrix::unpack(consts, std::tuple(consts.n(), consts.n_bar()), packed_b); + auto b = FrodoMatrix::unpack(constants, std::tuple(constants.n(), constants.n_bar()), packed_b); - m_public = std::make_shared(std::move(consts), std::move(seed_a), std::move(b)); + m_public = std::make_shared(std::move(constants), std::move(seed_a), std::move(b)); } FrodoKEM_PublicKey::FrodoKEM_PublicKey(const AlgorithmIdentifier& alg_id, std::span key_bits) : @@ -296,7 +300,11 @@ return raw_public_key_bits(); } -bool FrodoKEM_PublicKey::check_key(RandomNumberGenerator&, bool) const { +bool FrodoKEM_PublicKey::check_key(RandomNumberGenerator& /*rng*/, bool /*strong*/) const { + // The public key consists of (seed_a, b) where b is a matrix of elements + // mod q = 2^d. Length validation is performed in the constructor, and bit + // unpacking naturally constrains all matrix elements to [0, 2^d - 1] which + // is the full valid range, leaving no further structural checks to perform. return true; } @@ -317,54 +325,54 @@ // FrodoKEM_PrivateKey::FrodoKEM_PrivateKey(RandomNumberGenerator& rng, FrodoKEMMode mode) { - FrodoKEMConstants consts(mode); - auto& shake = consts.SHAKE_XOF(); + FrodoKEMConstants constants(mode); + auto shake = constants.create_xof(); - auto s = rng.random_vec(consts.len_sec_bytes()); - const auto seed_se = rng.random_vec(consts.len_se_bytes()); - const auto z = rng.random_vec(consts.len_a_bytes()); + auto s = rng.random_vec(constants.len_sec_bytes()); + const auto seed_se = rng.random_vec(constants.len_se_bytes()); + const auto z = rng.random_vec(constants.len_a_bytes()); CT::poison_all(s, seed_se); - shake.update(z); - auto seed_a = shake.output(consts.len_a_bytes()); - shake.clear(); - - shake.update(consts.keygen_domain_separator()); - shake.update(seed_se); - - auto sample_generator = FrodoMatrix::make_sample_generator(consts, shake); - auto s_trans = sample_generator(std::tuple(consts.n_bar(), consts.n())); - auto e = sample_generator(std::tuple(consts.n(), consts.n_bar())); - shake.clear(); + shake->update(z); + auto seed_a = shake->output(constants.len_a_bytes()); + shake->clear(); + + shake->update(constants.keygen_domain_separator()); + shake->update(seed_se); + + auto sample_generator = FrodoMatrix::make_sample_generator(constants, *shake); + auto s_trans = sample_generator(std::tuple(constants.n_bar(), constants.n())); + auto e = sample_generator(std::tuple(constants.n(), constants.n_bar())); + shake->clear(); - auto b = FrodoMatrix::mul_add_as_plus_e(consts, s_trans, e, seed_a); + auto b = FrodoMatrix::mul_add_as_plus_e(constants, s_trans, e, seed_a); CT::unpoison_all(s, s_trans, b); - m_public = std::make_shared(std::move(consts), std::move(seed_a), std::move(b)); + m_public = std::make_shared(std::move(constants), std::move(seed_a), std::move(b)); m_private = std::make_shared(std::move(s), std::move(s_trans)); } FrodoKEM_PrivateKey::FrodoKEM_PrivateKey(std::span sk, FrodoKEMMode mode) { - FrodoKEMConstants consts(mode); + FrodoKEMConstants constants(mode); - if(sk.size() != consts.len_private_key_bytes()) { + if(sk.size() != constants.len_private_key_bytes()) { throw Invalid_Argument("FrodoKEM private key does not have the correct byte count"); } BufferSlicer sk_bs(sk); - auto s = sk_bs.copy(consts.len_sec_bytes()); - auto seed_a = sk_bs.copy(consts.len_a_bytes()); - const auto packed_b = sk_bs.take(consts.d() * consts.n() * consts.n_bar() / 8); - const auto s_trans_bytes = sk_bs.take(consts.n_bar() * consts.n() * 2); - const auto pkh = sk_bs.copy(consts.len_sec_bytes()); + auto s = sk_bs.copy(constants.len_sec_bytes()); + auto seed_a = sk_bs.copy(constants.len_a_bytes()); + const auto packed_b = sk_bs.take(constants.d() * constants.n() * constants.n_bar() / 8); + const auto s_trans_bytes = sk_bs.take(constants.n_bar() * constants.n() * 2); + const auto pkh = sk_bs.copy(constants.len_sec_bytes()); BOTAN_ASSERT_NOMSG(sk_bs.empty()); - auto b = FrodoMatrix::unpack(consts, std::tuple(consts.n(), consts.n_bar()), packed_b); - auto s_trans = FrodoMatrix::deserialize({consts.n_bar(), consts.n()}, s_trans_bytes); + auto b = FrodoMatrix::unpack(constants, std::tuple(constants.n(), constants.n_bar()), packed_b); + auto s_trans = FrodoMatrix::deserialize({constants.n_bar(), constants.n()}, s_trans_bytes); - m_public = std::make_shared(std::move(consts), std::move(seed_a), std::move(b)); + m_public = std::make_shared(std::move(constants), std::move(seed_a), std::move(b)); m_private = std::make_shared(std::move(s), std::move(s_trans)); BOTAN_STATE_CHECK(pkh == m_public->hash()); @@ -377,6 +385,22 @@ return std::make_unique(*this); } +bool FrodoKEM_PrivateKey::check_key(RandomNumberGenerator& rng, bool strong) const { + if(!FrodoKEM_PublicKey::check_key(rng, strong)) { + return false; + } + + if(strong) { + PK_KEM_Encryptor enc(*this, "Raw"); + PK_KEM_Decryptor dec(*this, rng, "Raw"); + const auto [c, K] = KEM_Encapsulation::destructure(enc.encrypt(rng)); + const auto K_prime = dec.decrypt(c); + return K == K_prime; + } + + return true; +} + secure_vector FrodoKEM_PrivateKey::private_key_bits() const { return raw_private_key_bits(); // TODO: check if we need to do something else here } diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodokem.h botan3-3.12.0+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodokem.h --- botan3-3.7.1+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodokem.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/frodokem/frodokem_common/frodokem.h 2026-05-07 01:38:28.000000000 +0000 @@ -1,7 +1,7 @@ /* * FrodoKEM implementation * Based on the MIT licensed reference implementation by the designers - * (https://github.com/microsoft/PQCrypto-LWEKE/tree/master/src) + * (https://github.com/microsoft/PQCrypto-LWEKE/tree/master) * * The Fellowship of the FrodoKEM: * (C) 2023 Jack Lloyd @@ -16,7 +16,6 @@ #include #include -#include #include namespace Botan { @@ -58,7 +57,7 @@ std::vector public_key_bits() const override; - bool check_key(RandomNumberGenerator&, bool) const override; + bool check_key(RandomNumberGenerator& rng, bool strong) const override; bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::KeyEncapsulation); @@ -73,7 +72,7 @@ FrodoKEM_PublicKey() = default; protected: - std::shared_ptr m_public; // NOLINT(misc-non-private-member-variables-in-classes) + std::shared_ptr m_public; // NOLINT(*-non-private-member-variable*) }; BOTAN_DIAGNOSTIC_PUSH @@ -90,6 +89,8 @@ std::unique_ptr public_key() const override; + bool check_key(RandomNumberGenerator& rng, bool strong) const override; + secure_vector private_key_bits() const override; secure_vector raw_private_key_bits() const override; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/gost_3410/gost_3410.cpp botan3-3.12.0+dfsg/src/lib/pubkey/gost_3410/gost_3410.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/gost_3410/gost_3410.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/gost_3410/gost_3410.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -29,6 +29,10 @@ } // namespace +std::optional GOST_3410_PublicKey::_signature_element_size_for_DER_encoding() const { + return domain().get_order_bytes(); +} + std::vector GOST_3410_PublicKey::public_key_bits() const { auto bits = _public_ec_point().xy_bytes(); @@ -70,12 +74,17 @@ OID ecc_param_id; // The parameters also includes hash and cipher OIDs - BER_Decoder(alg_id.parameters()).start_sequence().decode(ecc_param_id); + BER_Decoder(alg_id.parameters(), BER_Decoder::Limits::DER()) + .start_sequence() + .decode(ecc_param_id) + .discard_remaining() + .end_cons() + .verify_end(); auto group = check_domain(EC_Group::from_OID(ecc_param_id)); std::vector bits; - BER_Decoder(key_bits).decode(bits, ASN1_Type::OctetString); + BER_Decoder(key_bits, BER_Decoder::Limits::DER()).decode(bits, ASN1_Type::OctetString).verify_end(); if(bits.size() != 2 * (group.get_p_bits() / 8)) { throw Decoding_Error("GOST-34.10-2012 invalid encoding of public key"); @@ -124,8 +133,8 @@ */ class GOST_3410_Signature_Operation final : public PK_Ops::Signature_with_Hash { public: - GOST_3410_Signature_Operation(const GOST_3410_PrivateKey& gost_3410, std::string_view emsa) : - PK_Ops::Signature_with_Hash(emsa), m_group(gost_3410.domain()), m_x(gost_3410._private_key()) {} + GOST_3410_Signature_Operation(const GOST_3410_PrivateKey& gost_3410, std::string_view hash_fn) : + PK_Ops::Signature_with_Hash(hash_fn), m_group(gost_3410.domain()), m_x(gost_3410._private_key()) {} size_t signature_length() const override { return 2 * m_group.get_order_bytes(); } @@ -136,7 +145,6 @@ private: const EC_Group m_group; const EC_Scalar m_x; - std::vector m_ws; }; AlgorithmIdentifier GOST_3410_Signature_Operation::algorithm_identifier() const { @@ -166,7 +174,7 @@ const auto e = gost_msg_to_scalar(m_group, msg); const auto k = EC_Scalar::random(m_group, rng); - const auto r = EC_Scalar::gk_x_mod_order(k, rng, m_ws); + const auto r = EC_Scalar::gk_x_mod_order(k, rng); const auto s = (r * m_x) + (k * e); if(r.is_zero() || s.is_zero()) { diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/gost_3410/gost_3410.h botan3-3.12.0+dfsg/src/lib/pubkey/gost_3410/gost_3410.h --- botan3-3.7.1+dfsg/src/lib/pubkey/gost_3410/gost_3410.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/gost_3410/gost_3410.h 2026-05-07 01:38:28.000000000 +0000 @@ -54,9 +54,7 @@ std::vector public_key_bits() const override; - std::optional _signature_element_size_for_DER_encoding() const override { - return domain().get_order_bytes(); - } + std::optional _signature_element_size_for_DER_encoding() const override; Signature_Format _default_x509_signature_format() const override { return Signature_Format::Standard; } @@ -117,7 +115,9 @@ std::unique_ptr public_key() const override; - AlgorithmIdentifier pkcs8_algorithm_identifier() const override { return EC_PublicKey::algorithm_identifier(); } + AlgorithmIdentifier pkcs8_algorithm_identifier() const override { + return EC_PublicKey::algorithm_identifier(); // NOLINT(bugprone-parent-virtual-call) + } std::unique_ptr create_signature_op(RandomNumberGenerator& rng, std::string_view params, diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/hss_lms/hss.cpp botan3-3.12.0+dfsg/src/lib/pubkey/hss_lms/hss.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/hss_lms/hss.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/hss_lms/hss.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ /** * HSS - Hierarchical Signatures System (RFC 8554) - * (C) 2023 Jack Lloyd + * (C) 2023,2026 Jack Lloyd * 2023 Fabian Albert, Philippe Lieser - Rohde & Schwarz Cybersecurity GmbH * * Botan is released under the Simplified BSD License (see license.txt) @@ -8,13 +8,15 @@ #include +#include +#include +#include +#include #include #include #include #include -#include - -#include +#include #include namespace Botan { @@ -62,7 +64,7 @@ for(int32_t layer_ctr = hss_params.L().get() - 1; layer_ctr >= 0; --layer_ctr) { HSS_Level layer(layer_ctr); const HSS_LMS_Params::LMS_LMOTS_Params_Pair& layer_params = hss_params.params_at_level(layer); - size_t layer_h = layer_params.lms_params().h(); + const size_t layer_h = layer_params.lms_params().h(); q.at(layer.get()) = checked_cast_to(hss_idx.get() % checked_cast_to(1ULL << layer_h)); hss_idx = hss_idx >> layer_h; @@ -80,7 +82,7 @@ "Invalid number of levels"); } -HSS_LMS_Params::HSS_LMS_Params(std::string_view algo_params) { +HSS_LMS_Params::HSS_LMS_Params(std::string_view algo_params) : m_max_sig_count(0) { const auto wrap_in_hss_lms = [&]() { if(algo_params.starts_with("HSS-LMS(")) { return std::string(algo_params); @@ -88,14 +90,14 @@ return fmt("HSS-LMS({})", algo_params); } }(); - SCAN_Name scan(wrap_in_hss_lms); + const SCAN_Name scan(wrap_in_hss_lms); BOTAN_ARG_CHECK(scan.arg_count() >= 2 && scan.arg_count() <= HSS_MAX_LEVELS + 1, "Invalid number of arguments"); - std::string hash = scan.arg(0); + const std::string hash = scan.arg(0); BOTAN_ARG_CHECK(is_supported_hash_function(hash), "Supported HSS-LMS hash function"); for(size_t i = 1; i < scan.arg_count(); ++i) { - SCAN_Name scan_layer(scan.arg(i)); + const SCAN_Name scan_layer(scan.arg(i)); BOTAN_ARG_CHECK(scan_layer.algo_name() == "HW", "Invalid name for layer parameters"); BOTAN_ARG_CHECK(scan_layer.arg_count() == 2, "Invalid number of layer parameters"); const auto h = @@ -119,9 +121,26 @@ } HSS_LMS_PrivateKeyInternal::HSS_LMS_PrivateKeyInternal(const HSS_LMS_Params& hss_params, RandomNumberGenerator& rng) : - m_hss_params(hss_params), m_current_idx(0), m_sig_size(HSS_Signature::size(m_hss_params)) { - m_hss_seed = rng.random_vec(m_hss_params.params_at_level(HSS_Level(0)).lms_params().m()); - m_identifier = rng.random_vec(LMS_IDENTIFIER_LEN); + m_hss_params(hss_params), + m_hss_seed(rng.random_vec(m_hss_params.params_at_level(HSS_Level(0)).lms_params().m())), + m_identifier(rng.random_vec(LMS_IDENTIFIER_LEN)), + // LMS doesn't have a single unique parameter code that we can easily use, + // so algo_params is left as 0 + m_keyid("HSS-LMS", 0, m_hss_seed, m_identifier), + m_sig_size(HSS_Signature::size(m_hss_params)) {} + +HSS_LMS_PrivateKeyInternal::HSS_LMS_PrivateKeyInternal(HSS_LMS_Params hss_params, + LMS_Seed hss_seed, + LMS_Identifier identifier) : + m_hss_params(std::move(hss_params)), + m_hss_seed(std::move(hss_seed)), + m_identifier(std::move(identifier)), + // LMS doesn't have a single unique parameter code that we can easily use, so algo_params is left as 0 + m_keyid("HSS-LMS", 0, m_hss_seed, m_identifier), + m_sig_size(HSS_Signature::size(m_hss_params)) { + BOTAN_ARG_CHECK(m_hss_seed.size() == m_hss_params.params_at_level(HSS_Level(0)).lms_params().m(), + "Invalid HSS-LMS seed size"); + BOTAN_ARG_CHECK(m_identifier.size() == LMS_IDENTIFIER_LEN, "Invalid HSS-LMS identifier size"); } std::shared_ptr HSS_LMS_PrivateKeyInternal::from_bytes_or_throw( @@ -147,13 +166,14 @@ const auto lmots_type = load_be(slicer.take()); params.push_back({LMS_Params::create_or_throw(lms_type), LMOTS_Params::create_or_throw(lmots_type)}); } - std::string hash_name = params.at(0).lms_params().hash_name(); - if(std::any_of(params.begin(), params.end(), [&hash_name](HSS_LMS_Params::LMS_LMOTS_Params_Pair& lms_lmots_params) { - bool invalid_lmots_hash = lms_lmots_params.lmots_params().hash_name() != hash_name; - bool invalid_lms_hash = lms_lmots_params.lms_params().hash_name() != hash_name; - return invalid_lmots_hash || invalid_lms_hash; - })) { - throw Decoding_Error("Inconsistent hash functions are not allowed."); + const auto& hash_name = params.at(0).lms_params().hash_name(); + + for(const auto& param : params) { + const bool invalid_lmots_hash = param.lmots_params().hash_name() != hash_name; + const bool invalid_lms_hash = param.lms_params().hash_name() != hash_name; + if(invalid_lmots_hash || invalid_lms_hash) { + throw Decoding_Error("Inconsistent hash functions are not allowed."); + } } if(slicer.remaining() < params.at(0).lms_params().m() + LMS_IDENTIFIER_LEN) { @@ -176,8 +196,10 @@ secure_vector sk_bytes(size()); BufferStuffer stuffer(sk_bytes); + const uint64_t current_index = Stateful_Key_Index_Registry::global().current_index(m_keyid); + stuffer.append(store_be(hss_params().L())); - stuffer.append(store_be(get_idx())); + stuffer.append(store_be(current_index)); for(HSS_Level layer(1); layer <= hss_params().L(); ++layer) { const auto& params = hss_params().params_at_level(layer - 1); @@ -191,17 +213,20 @@ return sk_bytes; } +HSS_Sig_Idx HSS_LMS_PrivateKeyInternal::remaining_operations(HSS_Sig_Idx idx) const { + return HSS_Sig_Idx(Stateful_Key_Index_Registry::global().remaining_operations(m_keyid, idx.get())); +} + void HSS_LMS_PrivateKeyInternal::set_idx(HSS_Sig_Idx idx) { - m_current_idx = idx; + Stateful_Key_Index_Registry::global().set_index_lower_bound(m_keyid, idx.get()); } HSS_Sig_Idx HSS_LMS_PrivateKeyInternal::reserve_next_idx() { - HSS_Sig_Idx next_idx = m_current_idx; - if(next_idx >= m_hss_params.max_sig_count()) { + const auto idx = HSS_Sig_Idx(Stateful_Key_Index_Registry::global().reserve_next_index(m_keyid)); + if(idx >= m_hss_params.max_sig_count()) { throw Decoding_Error("HSS private key is exhausted"); } - set_idx(m_current_idx + 1); - return next_idx; + return idx; } size_t HSS_LMS_PrivateKeyInternal::size() const { @@ -212,19 +237,6 @@ return sk_size; } -HSS_LMS_PrivateKeyInternal::HSS_LMS_PrivateKeyInternal(HSS_LMS_Params hss_params, - LMS_Seed hss_seed, - LMS_Identifier identifier) : - m_hss_params(std::move(hss_params)), - m_hss_seed(std::move(hss_seed)), - m_identifier(std::move(identifier)), - m_current_idx(0), - m_sig_size(HSS_Signature::size(m_hss_params)) { - BOTAN_ARG_CHECK(m_hss_seed.size() == m_hss_params.params_at_level(HSS_Level(0)).lms_params().m(), - "Invalid seed size"); - BOTAN_ARG_CHECK(m_identifier.size() == LMS_IDENTIFIER_LEN, "Invalid identifier size"); -} - std::vector HSS_LMS_PrivateKeyInternal::sign(std::span msg) { std::vector sig(HSS_Signature::size(hss_params())); BufferStuffer sig_stuffer(sig); @@ -273,7 +285,7 @@ } LMS_PrivateKey HSS_LMS_PrivateKeyInternal::hss_derive_root_lms_private_key() const { - auto& top_params = hss_params().params_at_level(HSS_Level(0)); + const auto& top_params = hss_params().params_at_level(HSS_Level(0)); return LMS_PrivateKey(top_params.lms_params(), top_params.lmots_params(), m_identifier, m_hss_seed); } @@ -303,7 +315,7 @@ } HSS_LMS_PublicKeyInternal HSS_LMS_PublicKeyInternal::create(const HSS_LMS_PrivateKeyInternal& hss_sk) { - auto& hss_params = hss_sk.hss_params(); + const auto& hss_params = hss_sk.hss_params(); const auto root_sk = hss_sk.hss_derive_root_lms_private_key(); LMS_PublicKey top_pub_key = LMS_PublicKey(root_sk); diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/hss_lms/hss.h botan3-3.12.0+dfsg/src/lib/pubkey/hss_lms/hss.h --- botan3-3.7.1+dfsg/src/lib/pubkey/hss_lms/hss.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/hss_lms/hss.h 2026-05-07 01:38:28.000000000 +0000 @@ -10,11 +10,10 @@ #define BOTAN_HSS_H_ #include -#include #include #include #include - +#include #include #include #include @@ -23,6 +22,8 @@ namespace Botan { +class RandomNumberGenerator; + /** * @brief The index of a node within a specific LMS tree layer */ @@ -75,7 +76,7 @@ * @brief Construct the HSS-LMS parameters form an algorithm parameter string. * * The HSS/LMS instance to use for creating new keys is defined using an algorithm parameter string, - * i.e. to define which hash function (hash), LMS tree hights (h) + * i.e. to define which hash function (hash), LMS tree height (h) * and OTS Winternitz coefficient widths (w) to use. The syntax is the following: * * HSS-LMS(,HW(,),HW(,),...) @@ -155,13 +156,14 @@ /** * @brief Get the idx of the next signature to generate. */ - HSS_Sig_Idx get_idx() const { return m_current_idx; } + HSS_Sig_Idx remaining_operations(HSS_Sig_Idx idx) const; /** * @brief Set the idx of the next signature to generate. * * Note that creating two signatures with the same index is insecure. * The index must be lower than hss_params().max_sig_count(). + * The index will never go backward (highest value wins). */ void set_idx(HSS_Sig_Idx idx); @@ -230,7 +232,7 @@ HSS_LMS_Params m_hss_params; LMS_Seed m_hss_seed; LMS_Identifier m_identifier; - HSS_Sig_Idx m_current_idx; + Stateful_Key_Index_Registry::KeyId m_keyid; const size_t m_sig_size; }; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/hss_lms/hss_lms.cpp botan3-3.12.0+dfsg/src/lib/pubkey/hss_lms/hss_lms.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/hss_lms/hss_lms.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/hss_lms/hss_lms.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -43,7 +43,7 @@ return m_public->object_identifier(); } -bool HSS_LMS_PublicKey::check_key(RandomNumberGenerator&, bool) const { +bool HSS_LMS_PublicKey::check_key(RandomNumberGenerator& /*rng*/, bool /*strong*/) const { // Nothing to check. Only useful checks are already done during parsing. return true; } @@ -58,9 +58,11 @@ return raw_public_key_bits(); } +namespace { + class HSS_LMS_Verification_Operation final : public PK_Ops::Verification { public: - HSS_LMS_Verification_Operation(std::shared_ptr pub_key) : + explicit HSS_LMS_Verification_Operation(std::shared_ptr pub_key) : m_public(std::move(pub_key)) {} void update(std::span msg) override { @@ -85,6 +87,8 @@ std::vector m_msg_buffer; }; +} // namespace + std::unique_ptr HSS_LMS_PublicKey::create_verification_op(std::string_view /*params*/, std::string_view provider) const { if(provider.empty() || provider == "base") { @@ -108,7 +112,7 @@ return op == PublicKeyOperation::Signature; } -std::unique_ptr HSS_LMS_PublicKey::generate_another(RandomNumberGenerator&) const { +std::unique_ptr HSS_LMS_PublicKey::generate_another(RandomNumberGenerator& /*rng*/) const { // For this key type we cannot derive all required parameters from just // the public key. It is however possible to call HSS_LMS_PrivateKey::generate_another(). throw Not_Implemented("Cannot generate a new HSS/LMS keypair from a public key"); @@ -122,7 +126,7 @@ } HSS_LMS_PrivateKey::HSS_LMS_PrivateKey(RandomNumberGenerator& rng, std::string_view algo_params) { - HSS_LMS_Params hss_params(algo_params); + const HSS_LMS_Params hss_params(algo_params); m_private = std::make_shared(hss_params, rng); auto scope = CT::scoped_poison(*m_private); m_public = std::make_shared(HSS_LMS_PublicKeyInternal::create(*m_private)); @@ -158,7 +162,7 @@ } std::optional HSS_LMS_PrivateKey::remaining_operations() const { - return (m_private->hss_params().max_sig_count() - m_private->get_idx()).get(); + return m_private->remaining_operations(m_private->hss_params().max_sig_count()).get(); } std::unique_ptr HSS_LMS_PrivateKey::generate_another(RandomNumberGenerator& rng) const { @@ -167,6 +171,8 @@ new HSS_LMS_PrivateKey(std::make_shared(m_private->hss_params(), rng))); } +namespace { + class HSS_LMS_Signature_Operation final : public PK_Ops::Signature { public: HSS_LMS_Signature_Operation(std::shared_ptr private_key, @@ -177,7 +183,7 @@ m_msg_buffer.insert(m_msg_buffer.end(), msg.begin(), msg.end()); } - std::vector sign(RandomNumberGenerator&) override { + std::vector sign(RandomNumberGenerator& /*rng*/) override { std::vector message_to_sign = std::exchange(m_msg_buffer, {}); auto scope = CT::scoped_poison(*m_private); return CT::driveby_unpoison(m_private->sign(message_to_sign)); @@ -195,6 +201,8 @@ std::vector m_msg_buffer; }; +} // namespace + std::unique_ptr HSS_LMS_PrivateKey::create_signature_op(RandomNumberGenerator& rng, std::string_view params, std::string_view provider) const { diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/hss_lms/hss_lms.h botan3-3.12.0+dfsg/src/lib/pubkey/hss_lms/hss_lms.h --- botan3-3.7.1+dfsg/src/lib/pubkey/hss_lms/hss_lms.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/hss_lms/hss_lms.h 2026-05-07 01:38:28.000000000 +0000 @@ -36,9 +36,13 @@ /** * @brief Load an existing public key using its bytes. */ - HSS_LMS_PublicKey(std::span pub_key_bytes); + BOTAN_FUTURE_EXPLICIT HSS_LMS_PublicKey(std::span pub_key_bytes); ~HSS_LMS_PublicKey() override; + HSS_LMS_PublicKey(const HSS_LMS_PublicKey& other) = default; + HSS_LMS_PublicKey(HSS_LMS_PublicKey&& other) = default; + HSS_LMS_PublicKey& operator=(const HSS_LMS_PublicKey& other) = delete; + HSS_LMS_PublicKey& operator=(HSS_LMS_PublicKey&& other) = delete; size_t key_length() const override; @@ -67,7 +71,7 @@ protected: HSS_LMS_PublicKey() = default; - std::shared_ptr m_public; + std::shared_ptr m_public; // NOLINT(*non-private-member-variable*) }; BOTAN_DIAGNOSTIC_PUSH @@ -104,7 +108,7 @@ * HSS-LMS(,HW(,),HW(,),...) * * e.g. 'HSS-LMS(SHA-256,HW(5,1),HW(5,1))' to use SHA-256 in a two-layer HSS instance - * with a LMS tree hights 5 and w=1. The following parameters are allowed (which are + * with a LMS tree height 5 and w=1. The following parameters are allowed (which are * specified in RFC 8554 and draft-fluhrer-lms-more-parm-sets-11): * * hash: 'SHA-256', 'Truncated(SHA-256,192)', 'SHAKE-256(256)', SHAKE-256(192) @@ -119,7 +123,7 @@ /** * @brief Load an existing LMS private key using its bytes */ - HSS_LMS_PrivateKey(std::span private_key_bytes); + BOTAN_FUTURE_EXPLICIT HSS_LMS_PrivateKey(std::span private_key_bytes); /** * @brief Construct a new hss lms privatekey object. @@ -130,6 +134,10 @@ HSS_LMS_PrivateKey(RandomNumberGenerator& rng, std::string_view algo_params); ~HSS_LMS_PrivateKey() override; + HSS_LMS_PrivateKey(const HSS_LMS_PrivateKey& other) = delete; + HSS_LMS_PrivateKey(HSS_LMS_PrivateKey&& other) = default; + HSS_LMS_PrivateKey& operator=(const HSS_LMS_PrivateKey& other) = delete; + HSS_LMS_PrivateKey& operator=(HSS_LMS_PrivateKey&& other) = delete; secure_vector private_key_bits() const override; secure_vector raw_private_key_bits() const override; @@ -151,7 +159,7 @@ std::string_view provider) const override; private: - HSS_LMS_PrivateKey(std::shared_ptr sk); + explicit HSS_LMS_PrivateKey(std::shared_ptr sk); std::shared_ptr m_private; }; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/hss_lms/hss_lms_utils.cpp botan3-3.12.0+dfsg/src/lib/pubkey/hss_lms/hss_lms_utils.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/hss_lms/hss_lms_utils.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/hss_lms/hss_lms_utils.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,8 +8,6 @@ #include -#include - namespace Botan { // The magic numbers in the initializer list below reflect the structure of the diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/hss_lms/hss_lms_utils.h botan3-3.12.0+dfsg/src/lib/pubkey/hss_lms/hss_lms_utils.h --- botan3-3.7.1+dfsg/src/lib/pubkey/hss_lms/hss_lms_utils.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/hss_lms/hss_lms_utils.h 2026-05-07 01:38:28.000000000 +0000 @@ -30,7 +30,7 @@ /** * @brief Create a PseudorandomKeyGeneration instance for a fixed @p identifier */ - PseudorandomKeyGeneration(std::span identifier); + explicit PseudorandomKeyGeneration(std::span identifier); /** * @brief Specify the value for the u32str(q) hash input field diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/hss_lms/info.txt botan3-3.12.0+dfsg/src/lib/pubkey/hss_lms/info.txt --- botan3-3.7.1+dfsg/src/lib/pubkey/hss_lms/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/hss_lms/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -21,6 +21,7 @@ rng sha2_32 shake +stateful_key_index trunc_hash tree_hash diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/hss_lms/lm_ots.cpp botan3-3.12.0+dfsg/src/lib/pubkey/hss_lms/lm_ots.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/hss_lms/lm_ots.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/hss_lms/lm_ots.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include #include #include @@ -97,6 +99,10 @@ } // namespace +std::unique_ptr LMOTS_Params::hash() const { + return HashFunction::create_or_throw(hash_name()); +} + LMOTS_Params LMOTS_Params::create_or_throw(LMOTS_Algorithm_Type type) { auto [hash_name, w] = [](const LMOTS_Algorithm_Type& lmots_type) -> std::pair { switch(lmots_type) { @@ -145,7 +151,7 @@ if(w != 1 && w != 2 && w != 4 && w != 8) { throw Decoding_Error("Invalid Winternitz parameter"); } - LMOTS_Algorithm_Type type = [](std::string_view hash, uint8_t w_p) -> LMOTS_Algorithm_Type { + const LMOTS_Algorithm_Type type = [](std::string_view hash, uint8_t w_p) -> LMOTS_Algorithm_Type { if(hash == "SHA-256") { switch(w_p) { case 1: @@ -223,7 +229,7 @@ std::vector C, std::vector y_buffer) : m_algorithm_type(lmots_type), m_C(std::move(C)), m_y_buffer(std::move(y_buffer)) { - LMOTS_Params params = LMOTS_Params::create_or_throw(m_algorithm_type); + const LMOTS_Params params = LMOTS_Params::create_or_throw(m_algorithm_type); BufferSlicer y_slicer(m_y_buffer); for(uint16_t i = 0; i < params.p(); ++i) { @@ -233,7 +239,7 @@ } LMOTS_Signature LMOTS_Signature::from_bytes_or_throw(BufferSlicer& slicer) { - size_t total_remaining_bytes = slicer.remaining(); + const size_t total_remaining_bytes = slicer.remaining(); // Alg. 6a. 1. (last 4 bytes) / Alg. 4b. 1. if(total_remaining_bytes < sizeof(LMOTS_Algorithm_Type)) { throw Decoding_Error("Too few signature bytes while parsing LMOTS signature."); @@ -242,7 +248,7 @@ auto algorithm_type = load_be(slicer.take()); // Alg. 6a. 2.d. / Alg. 4b. 2.c. - LMOTS_Params params = LMOTS_Params::create_or_throw(algorithm_type); + const LMOTS_Params params = LMOTS_Params::create_or_throw(algorithm_type); if(total_remaining_bytes < size(params)) { throw Decoding_Error("Too few signature bytes while parsing LMOTS signature."); @@ -307,7 +313,7 @@ gen.gen(out, hash, m_seed); } -LMOTS_Public_Key::LMOTS_Public_Key(const LMOTS_Private_Key& lmots_sk) : OTS_Instance(lmots_sk) { +LMOTS_Public_Key::LMOTS_Public_Key(const LMOTS_Private_Key& lmots_sk) : /* NOLINT(*-slicing) */ OTS_Instance(lmots_sk) { const auto pk_hash = lmots_sk.params().hash(); pk_hash->update(lmots_sk.identifier()); pk_hash->update(store_be(lmots_sk.q())); diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/hss_lms/lm_ots.h botan3-3.12.0+dfsg/src/lib/pubkey/hss_lms/lm_ots.h --- botan3-3.7.1+dfsg/src/lib/pubkey/hss_lms/lm_ots.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/hss_lms/lm_ots.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,9 +9,9 @@ #ifndef BOTAN_LM_OTS_H_ #define BOTAN_LM_OTS_H_ -#include -#include - +#include +#include +#include #include #include #include @@ -19,6 +19,9 @@ namespace Botan { +class BufferSlicer; +class HashFunction; + /** * @brief Seed of the LMS tree, used to generate the LM-OTS private keys. */ @@ -62,7 +65,7 @@ * introduced in RFC 8554 Section 3.2. and their format specified in * Section 3.3. */ -enum class LMOTS_Algorithm_Type : uint32_t { +enum class LMOTS_Algorithm_Type : uint32_t /* NOLINT(*-enum-size) */ { // --- RFC 8554 --- RESERVED = 0x00, @@ -108,7 +111,7 @@ /** * @brief Create the LM-OTS parameters from a hash function and width. * - * @param hash_name tha name of the hash function to use. + * @param hash_name the name of the hash function to use. * @param w the width (in bits) of the Winternitz coefficients. * @throws Decoding_Error If the algorithm type is unknown */ @@ -152,7 +155,7 @@ /** * @brief Construct a new hash instance for the OTS instance. */ - std::unique_ptr hash() const { return HashFunction::create_or_throw(hash_name()); } + std::unique_ptr hash() const; private: /** @@ -309,7 +312,7 @@ * @brief Derivivation of an LMOTS public key using an LMOTS_Private_Key as defined * in RFC 8554 4.3 */ - LMOTS_Public_Key(const LMOTS_Private_Key& lmots_sk); + explicit LMOTS_Public_Key(const LMOTS_Private_Key& lmots_sk); /** * @brief Construct a new LMOTS public key object using the bytes. diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/hss_lms/lms.cpp botan3-3.12.0+dfsg/src/lib/pubkey/hss_lms/lms.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/hss_lms/lms.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/hss_lms/lms.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,7 +8,11 @@ #include -#include +#include +#include +#include +#include +#include #include #include @@ -109,6 +113,10 @@ } // namespace +std::unique_ptr LMS_Params::hash() const { + return HashFunction::create_or_throw(hash_name()); +} + LMS_Params LMS_Params::create_or_throw(LMS_Algorithm_Type type) { auto [hash_name, height] = [](const LMS_Algorithm_Type& lms_type) -> std::pair { switch(lms_type) { @@ -161,7 +169,7 @@ } LMS_Params LMS_Params::create_or_throw(std::string_view hash_name, uint8_t height) { - LMS_Algorithm_Type type = [](std::string_view hash, uint8_t h) -> LMS_Algorithm_Type { + const LMS_Algorithm_Type type = [](std::string_view hash, uint8_t h) -> LMS_Algorithm_Type { if(hash == "SHA-256") { switch(h) { case 5: @@ -253,7 +261,7 @@ BOTAN_ASSERT_NOMSG(sig_stuffer.full()); - TreeAddress lms_tree_address(lms_params().h()); + const TreeAddress lms_tree_address(lms_params().h()); LMS_Tree_Node pk_buffer(lms_params().m()); lms_treehash(StrongSpan(pk_buffer.get()), auth_path_buffer, q, *this); @@ -262,7 +270,7 @@ } LMS_PublicKey LMS_PublicKey::from_bytes_or_throw(BufferSlicer& slicer) { - size_t total_remaining_bytes = slicer.remaining(); + const size_t total_remaining_bytes = slicer.remaining(); // Alg. 6. 1. (4 bytes are sufficient until the next check) if(total_remaining_bytes < sizeof(LMS_Algorithm_Type)) { throw Decoding_Error("Too few bytes while parsing LMS public key."); @@ -315,7 +323,7 @@ } LMS_Signature LMS_Signature::from_bytes_or_throw(BufferSlicer& slicer) { - size_t total_remaining_bytes = slicer.remaining(); + const size_t total_remaining_bytes = slicer.remaining(); // Alg. 6a 1. (next 4 bytes are checked in LMOTS_Signature::from_bytes_or_throw) if(total_remaining_bytes < sizeof(LMS_Tree_Node_Idx)) { throw Decoding_Error("Too few signature bytes while parsing LMS signature."); @@ -325,7 +333,7 @@ // Alg. 6a 2.b.-e. auto lmots_sig = LMOTS_Signature::from_bytes_or_throw(slicer); - LMOTS_Params lmots_params = LMOTS_Params::create_or_throw(lmots_sig.algorithm_type()); + const LMOTS_Params lmots_params = LMOTS_Params::create_or_throw(lmots_sig.algorithm_type()); if(slicer.remaining() < sizeof(LMS_Algorithm_Type)) { throw Decoding_Error("Too few signature bytes while parsing LMS signature."); @@ -333,7 +341,7 @@ // Alg. 6a 2.f. auto lms_type = load_be(slicer.take()); // Alg. 6a 2.h. - LMS_Params lms_params = LMS_Params::create_or_throw(lms_type); + const LMS_Params lms_params = LMS_Params::create_or_throw(lms_type); // Alg. 6a 2.i. (signature is not exactly [...] bytes long) if(total_remaining_bytes < size(lms_params, lmots_params)) { throw Decoding_Error("Too few signature bytes while parsing LMS signature."); @@ -345,7 +353,8 @@ return LMS_Signature(q, std::move(lmots_sig), lms_type, std::move(auth_path)); } -LMS_PublicKey::LMS_PublicKey(const LMS_PrivateKey& sk) : LMS_Instance(sk), m_lms_root(sk.lms_params().m()) { +LMS_PublicKey::LMS_PublicKey(const LMS_PrivateKey& sk) : + /* NOLINT(*-slicing) */ LMS_Instance(sk), m_lms_root(sk.lms_params().m()) { lms_treehash(StrongSpan(m_lms_root), std::nullopt, std::nullopt, sk); } @@ -395,7 +404,7 @@ auto lms_address = TreeAddress(lms_params.h()); lms_address.set_address(LMS_TreeLayerIndex(0), LMS_Tree_Node_Idx(sig.q().get())); - LMOTS_Public_Key pk_candidate(lmots_params, identifier(), sig.q(), Kc); + const LMOTS_Public_Key pk_candidate(lmots_params, identifier(), sig.q(), Kc); LMS_Tree_Node tmp(lms_params.m()); lms_gen_leaf(tmp, pk_candidate, lms_address, *hash); diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/hss_lms/lms.h botan3-3.12.0+dfsg/src/lib/pubkey/hss_lms/lms.h --- botan3-3.7.1+dfsg/src/lib/pubkey/hss_lms/lms.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/hss_lms/lms.h 2026-05-07 01:38:28.000000000 +0000 @@ -19,6 +19,8 @@ namespace Botan { +class BufferSlicer; + /** * @brief Enum of available LMS algorithm types. * @@ -27,7 +29,7 @@ * introduced in RFC 8554 Section 3.2. and their format specified in * Section 3.3. */ -enum class LMS_Algorithm_Type : uint32_t { +enum class LMS_Algorithm_Type : uint32_t /* NOLINT(*-enum-size) */ { // --- RFC 8554 --- RESERVED = 0x00, @@ -104,7 +106,7 @@ static LMS_Params create_or_throw(std::string_view hash_name, uint8_t h); /** - * @brief Retuns the LMS algorithm type. + * @brief Returns the LMS algorithm type. */ LMS_Algorithm_Type algorithm_type() const { return m_algorithm_type; } @@ -126,7 +128,7 @@ /** * @brief Construct a new hash instance for the LMS instance. */ - std::unique_ptr hash() const { return HashFunction::create_or_throw(hash_name()); } + std::unique_ptr hash() const; private: /** @@ -241,7 +243,7 @@ /** * @brief Construct a new public key from a given LMS private key (RFC 8554 5.3). */ - LMS_PublicKey(const LMS_PrivateKey& sk); + explicit LMS_PublicKey(const LMS_PrivateKey& sk); /** * @brief Bytes of the full lms public key according to 8554 5.3 diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/hybrid_kem/hybrid_kem.cpp botan3-3.12.0+dfsg/src/lib/pubkey/hybrid_kem/hybrid_kem.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/hybrid_kem/hybrid_kem.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/hybrid_kem/hybrid_kem.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,82 @@ +/** +* Abstraction for a combined KEM public and private key. +* +* (C) 2024 Jack Lloyd +* 2024 Fabian Albert, René Meusel - Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ +#include + +#include +#include +#include + +namespace Botan { + +Hybrid_PublicKey::Hybrid_PublicKey(std::vector> pks) : + m_pks(std::move(pks)), m_key_length(0), m_estimated_strength(0) { + BOTAN_ARG_CHECK(m_pks.size() >= 2, "List of public keys must include at least two keys"); + for(const auto& pk : m_pks) { + BOTAN_ARG_CHECK(pk != nullptr, "List of public keys contains a nullptr"); + BOTAN_ARG_CHECK(pk->supports_operation(PublicKeyOperation::KeyEncapsulation), + fmt("Public key type '{}' does not support key encapsulation", pk->algo_name()).c_str()); + m_key_length = std::max(m_key_length, pk->key_length()); + m_estimated_strength = std::max(m_estimated_strength, pk->estimated_strength()); + } +} + +bool Hybrid_PublicKey::check_key(RandomNumberGenerator& rng, bool strong) const { + return reduce(public_keys(), true, [&](bool ckr, const auto& key) { return ckr && key->check_key(rng, strong); }); +} + +std::vector Hybrid_PublicKey::raw_public_key_bits() const { + return reduce(public_keys(), std::vector(), [](auto pkb, const auto& key) { + return concat(pkb, key->raw_public_key_bits()); + }); +} + +bool Hybrid_PublicKey::supports_operation(PublicKeyOperation op) const { + return PublicKeyOperation::KeyEncapsulation == op; +} + +std::vector> Hybrid_PublicKey::generate_other_sks_from_pks( + RandomNumberGenerator& rng) const { + std::vector> new_private_keys; + new_private_keys.reserve(public_keys().size()); + for(const auto& pk : public_keys()) { + new_private_keys.push_back(pk->generate_another(rng)); + } + return new_private_keys; +} + +Hybrid_PrivateKey::Hybrid_PrivateKey(std::vector> private_keys) : + m_sks(std::move(private_keys)) { + BOTAN_ARG_CHECK(m_sks.size() >= 2, "List of secret keys must include at least two keys"); + for(const auto& sk : m_sks) { + BOTAN_ARG_CHECK(sk != nullptr, "List of secret keys contains a nullptr"); + BOTAN_ARG_CHECK(sk->supports_operation(PublicKeyOperation::KeyEncapsulation), + "Some provided secret key is not compatible with this hybrid wrapper"); + } +} + +secure_vector Hybrid_PrivateKey::private_key_bits() const { + throw Not_Implemented("Hybrid private keys cannot be serialized"); +} + +bool Hybrid_PrivateKey::check_key(RandomNumberGenerator& rng, bool strong) const { + return reduce(private_keys(), true, [&](bool ckr, const auto& key) { return ckr && key->check_key(rng, strong); }); +} + +std::vector> Hybrid_PrivateKey::extract_public_keys( + const std::vector>& private_keys) { + std::vector> public_keys; + public_keys.reserve(private_keys.size()); + for(const auto& sk : private_keys) { + BOTAN_ARG_CHECK(sk != nullptr, "List of private keys contains a nullptr"); + public_keys.push_back(sk->public_key()); + } + return public_keys; +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/hybrid_kem/hybrid_kem.h botan3-3.12.0+dfsg/src/lib/pubkey/hybrid_kem/hybrid_kem.h --- botan3-3.7.1+dfsg/src/lib/pubkey/hybrid_kem/hybrid_kem.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/hybrid_kem/hybrid_kem.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,135 @@ +/** +* Abstraction for a combined KEM public and private key. +* +* (C) 2024 Jack Lloyd +* 2024 Fabian Albert, René Meusel - Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_HYBRID_KEM_H_ +#define BOTAN_HYBRID_KEM_H_ + +#include +#include +#include + +#include +#include + +namespace Botan { + +/** + * @brief Abstraction for a combined KEM public key. + * + * Two or more KEM public keys are combined into a single KEM public key. Derived classes + * must implement the abstract methods to provide the encryption operation, e.g. by + * specifying how encryption results are combined to the ciphertext and how a KEM combiner + * is applied to derive the shared secret using the individual shared secrets, ciphertexts, + * and other context information. + */ +class BOTAN_TEST_API Hybrid_PublicKey : public virtual Public_Key { + public: + /** + * @brief Constructor for a list of multiple KEM public keys. + * + * To use KEX algorithms use the KEX_to_KEM_Adapter_PublicKey. + * @param public_keys List of public keys to combine + */ + explicit Hybrid_PublicKey(std::vector> public_keys); + + Hybrid_PublicKey(Hybrid_PublicKey&&) = default; + Hybrid_PublicKey(const Hybrid_PublicKey&) = delete; + Hybrid_PublicKey& operator=(Hybrid_PublicKey&&) = default; + Hybrid_PublicKey& operator=(const Hybrid_PublicKey&) = delete; + ~Hybrid_PublicKey() override = default; + + size_t estimated_strength() const override { return m_estimated_strength; } + + size_t key_length() const override { return m_key_length; } + + bool check_key(RandomNumberGenerator& rng, bool strong) const override; + + std::vector raw_public_key_bits() const override; + + /** + * @brief Return the public key bits of this hybrid key as the concatenated + * bytes of the individual public keys (without encoding). + * + * @return the public key bytes + */ + std::vector public_key_bits() const override { return raw_public_key_bits(); } + + bool supports_operation(PublicKeyOperation op) const override; + + /// @returns the public keys combined in this hybrid key + const std::vector>& public_keys() const { return m_pks; } + + protected: + // Default constructor used for virtual inheritance to prevent, that the derived class + // calls the constructor twice. + Hybrid_PublicKey() = default; + + /** + * @brief Helper function for generate_another. Generate a new private key for each + * public key in this hybrid key. + */ + std::vector> generate_other_sks_from_pks(RandomNumberGenerator& rng) const; + + private: + std::vector> m_pks; + + size_t m_key_length = 0; + size_t m_estimated_strength = 0; +}; + +BOTAN_DIAGNOSTIC_PUSH +BOTAN_DIAGNOSTIC_IGNORE_INHERITED_VIA_DOMINANCE + +/** + * @brief Abstraction for a combined KEM private key. + * + * Two or more KEM private keys are combined into a single KEM private key. Derived classes + * must implement the abstract methods to provide the decryption operation, e.g. by + * specifying how a KEM combiner is applied to derive the shared secret using the + * individual shared secrets, ciphertexts, and other context information. + */ +class BOTAN_TEST_API Hybrid_PrivateKey : virtual public Private_Key { + public: + Hybrid_PrivateKey(const Hybrid_PrivateKey&) = delete; + Hybrid_PrivateKey& operator=(const Hybrid_PrivateKey&) = delete; + + Hybrid_PrivateKey(Hybrid_PrivateKey&&) = default; + Hybrid_PrivateKey& operator=(Hybrid_PrivateKey&&) = default; + + ~Hybrid_PrivateKey() override = default; + + /** + * @brief Constructor for a list of multiple KEM private keys. + * + * To use KEX algorithms use the KEX_to_KEM_Adapter_PrivateKey. + * @param private_keys List of private keys to combine + */ + explicit Hybrid_PrivateKey(std::vector> private_keys); + + /// Disabled by default + secure_vector private_key_bits() const override; + + /// @returns the private keys combined in this hybrid key + const std::vector>& private_keys() const { return m_sks; } + + bool check_key(RandomNumberGenerator& rng, bool strong) const override; + + protected: + static std::vector> extract_public_keys( + const std::vector>& private_keys); + + private: + std::vector> m_sks; +}; + +BOTAN_DIAGNOSTIC_POP + +} // namespace Botan + +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/hybrid_kem/hybrid_kem_ops.cpp botan3-3.12.0+dfsg/src/lib/pubkey/hybrid_kem/hybrid_kem_ops.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/hybrid_kem/hybrid_kem_ops.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/hybrid_kem/hybrid_kem_ops.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,110 @@ +/** +* Abstraction for a combined KEM encryptors and decryptors. +* +* (C) 2024 Jack Lloyd +* 2024 Fabian Albert, René Meusel - Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ +#include + +#include +#include + +namespace Botan { + +KEM_Encryption_with_Combiner::KEM_Encryption_with_Combiner(const std::vector>& public_keys, + std::string_view provider) : + m_encapsulated_key_length(0) { + m_encryptors.reserve(public_keys.size()); + for(const auto& pk : public_keys) { + const auto& newenc = m_encryptors.emplace_back(*pk, "Raw", provider); + m_encapsulated_key_length += newenc.encapsulated_key_length(); + } +} + +void KEM_Encryption_with_Combiner::kem_encrypt(std::span out_encapsulated_key, + std::span out_shared_key, + RandomNumberGenerator& rng, + size_t desired_shared_key_len, + std::span salt) { + BOTAN_ARG_CHECK(out_encapsulated_key.size() == encapsulated_key_length(), + "Encapsulated key output buffer has wrong size"); + BOTAN_ARG_CHECK(out_shared_key.size() == shared_key_length(desired_shared_key_len), + "Shared key output buffer has wrong size"); + + std::vector> shared_secrets; + shared_secrets.reserve(m_encryptors.size()); + + std::vector> ciphertexts; + ciphertexts.reserve(m_encryptors.size()); + + for(auto& encryptor : m_encryptors) { + auto [ct, ss] = KEM_Encapsulation::destructure(encryptor.encrypt(rng, 0 /* no KDF */)); + shared_secrets.push_back(std::move(ss)); + ciphertexts.push_back(std::move(ct)); + } + combine_ciphertexts(out_encapsulated_key, ciphertexts, salt); + combine_shared_secrets(out_shared_key, shared_secrets, ciphertexts, desired_shared_key_len, salt); +} + +void KEM_Encryption_with_Combiner::combine_ciphertexts(std::span out_ciphertext, + const std::vector>& ciphertexts, + std::span salt) { + BOTAN_ARG_CHECK(salt.empty(), "Salt not supported by this KEM"); + BOTAN_ARG_CHECK(ciphertexts.size() == m_encryptors.size(), "Invalid number of ciphertexts"); + BOTAN_ARG_CHECK(out_ciphertext.size() == encapsulated_key_length(), "Invalid output buffer size"); + BufferStuffer ct_stuffer(out_ciphertext); + for(size_t idx = 0; idx < ciphertexts.size(); idx++) { + BOTAN_ARG_CHECK(ciphertexts.at(idx).size() == m_encryptors.at(idx).encapsulated_key_length(), + "Invalid ciphertext length"); + ct_stuffer.append(ciphertexts.at(idx)); + } + BOTAN_ASSERT_NOMSG(ct_stuffer.full()); +} + +KEM_Decryption_with_Combiner::KEM_Decryption_with_Combiner( + const std::vector>& private_keys, + RandomNumberGenerator& rng, + std::string_view provider) : + m_encapsulated_key_length(0) { + m_decryptors.reserve(private_keys.size()); + for(const auto& sk : private_keys) { + const auto& newenc = m_decryptors.emplace_back(*sk, rng, "Raw", provider); + m_encapsulated_key_length += newenc.encapsulated_key_length(); + } +} + +void KEM_Decryption_with_Combiner::kem_decrypt(std::span out_shared_key, + std::span encapsulated_key, + size_t desired_shared_key_len, + std::span salt) { + BOTAN_ARG_CHECK(encapsulated_key.size() == encapsulated_key_length(), "Invalid encapsulated key length"); + BOTAN_ARG_CHECK(out_shared_key.size() == shared_key_length(desired_shared_key_len), "Invalid output buffer size"); + + std::vector> shared_secrets; + shared_secrets.reserve(m_decryptors.size()); + auto ciphertexts = split_ciphertexts(encapsulated_key); + BOTAN_ASSERT(ciphertexts.size() == m_decryptors.size(), "Correct number of ciphertexts"); + + for(size_t idx = 0; idx < m_decryptors.size(); idx++) { + shared_secrets.push_back(m_decryptors.at(idx).decrypt(ciphertexts.at(idx), 0 /* no KDF */)); + } + + combine_shared_secrets(out_shared_key, shared_secrets, ciphertexts, desired_shared_key_len, salt); +} + +std::vector> KEM_Decryption_with_Combiner::split_ciphertexts( + std::span concat_ciphertext) { + BOTAN_ARG_CHECK(concat_ciphertext.size() == encapsulated_key_length(), "Wrong ciphertext length"); + std::vector> ciphertexts; + ciphertexts.reserve(m_decryptors.size()); + BufferSlicer ct_slicer(concat_ciphertext); + for(const auto& decryptor : m_decryptors) { + ciphertexts.push_back(ct_slicer.copy_as_vector(decryptor.encapsulated_key_length())); + } + BOTAN_ASSERT_NOMSG(ct_slicer.empty()); + return ciphertexts; +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/hybrid_kem/hybrid_kem_ops.h botan3-3.12.0+dfsg/src/lib/pubkey/hybrid_kem/hybrid_kem_ops.h --- botan3-3.7.1+dfsg/src/lib/pubkey/hybrid_kem/hybrid_kem_ops.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/hybrid_kem/hybrid_kem_ops.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,140 @@ +/** +* Abstraction for a combined KEM encryptors and decryptors. +* +* (C) 2024 Jack Lloyd +* 2024 Fabian Albert, René Meusel - Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_HYBRID_KEM_OPS_H_ +#define BOTAN_HYBRID_KEM_OPS_H_ + +#include +#include +#include + +#include +#include + +namespace Botan { + +/** + * @brief Abstract interface for a KEM encryption operation for KEM combiners. + * + * Multiple public keys are used to encapsulate shared secrets. These shared + * secrets (and maybe the ciphertexts and public keys) are combined using the + * KEM combiner to derive the final shared secret. + * + */ +class KEM_Encryption_with_Combiner : public PK_Ops::KEM_Encryption { + public: + KEM_Encryption_with_Combiner(const std::vector>& public_keys, + std::string_view provider); + + void kem_encrypt(std::span out_encapsulated_key, + std::span out_shared_key, + RandomNumberGenerator& rng, + size_t desired_shared_key_len, + std::span salt) final; + + /// The default implementation returns the sum of the encapsulated key lengths of the underlying KEMs. + size_t encapsulated_key_length() const override { return m_encapsulated_key_length; } + + protected: + /** + * @brief Defines how multiple ciphertexts are combined into a single ciphertext. + * + * The default implementation concatenates the ciphertexts. + * + * @param out_ciphertext The output buffer for the combined ciphertext + * @param ciphertexts The ciphertexts to combine + * @param salt The salt. In this default implementation the salt must be empty. + */ + virtual void combine_ciphertexts(std::span out_ciphertext, + const std::vector>& ciphertexts, + std::span salt); + + /** + * @brief Describes how the shared secrets are combined to derive the final shared secret. + * + * @param out_shared_secret the output buffer for the shared secret + * @param shared_secrets a list of shared secrets corresponding to the public keys + * @param ciphertexts a list of encapsulated shared secrets + * @param desired_shared_key_len the desired shared key length + * @param salt the salt (input of kem_encrypt) + */ + virtual void combine_shared_secrets(std::span out_shared_secret, + const std::vector>& shared_secrets, + const std::vector>& ciphertexts, + size_t desired_shared_key_len, + std::span salt) = 0; + + std::vector& encryptors() { return m_encryptors; } + + const std::vector& encryptors() const { return m_encryptors; } + + private: + std::vector m_encryptors; + size_t m_encapsulated_key_length; +}; + +/** + * @brief Abstract interface for a KEM decryption operation for KEM combiners. + * + * Multiple private keys are used to decapsulate shared secrets from a combined + * ciphertext (concatenated in most cases). These shared + * secrets (and maybe the ciphertexts and public keys) are combined using the + * KEM combiner to derive the final shared secret. + */ +class KEM_Decryption_with_Combiner : public PK_Ops::KEM_Decryption { + public: + KEM_Decryption_with_Combiner(const std::vector>& private_keys, + RandomNumberGenerator& rng, + std::string_view provider); + + void kem_decrypt(std::span out_shared_key, + std::span encapsulated_key, + size_t desired_shared_key_len, + std::span salt) final; + + /// The default implementation returns the sum of the encapsulated key lengths of the underlying KEMs. + size_t encapsulated_key_length() const override { return m_encapsulated_key_length; } + + protected: + /** + * @brief Defines how the individual ciphertexts are extracted from the combined ciphertext. + * + * The default implementation splits concatenated ciphertexts. + * @param concat_ciphertext The combined ciphertext + * @returns The individual ciphertexts + */ + virtual std::vector> split_ciphertexts(std::span concat_ciphertext); + + /** + * @brief Describes how the shared secrets are combined to derive the final shared secret. + * + * @param out_shared_secret the output buffer for the shared secret + * @param shared_secrets a list of shared secrets corresponding to the public keys + * @param ciphertexts the list of encapsulated shared secrets + * @param desired_shared_key_len the desired shared key length + * @param salt the salt (input of kem_decrypt) + */ + virtual void combine_shared_secrets(std::span out_shared_secret, + const std::vector>& shared_secrets, + const std::vector>& ciphertexts, + size_t desired_shared_key_len, + std::span salt) = 0; + + std::vector& decryptors() { return m_decryptors; } + + const std::vector& decryptors() const { return m_decryptors; } + + private: + std::vector m_decryptors; + size_t m_encapsulated_key_length; +}; + +} // namespace Botan + +#endif // BOTAN_HYBRID_KEM_OPS_H_ diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/hybrid_kem/info.txt botan3-3.12.0+dfsg/src/lib/pubkey/hybrid_kem/info.txt --- botan3-3.7.1+dfsg/src/lib/pubkey/hybrid_kem/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/hybrid_kem/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,17 @@ + +HYBRID_KEM -> 20240425 + + + +name -> "Hybrid KEM" +type -> "Internal" + + + +hybrid_kem.h +hybrid_kem_ops.h + + + + + diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/info.txt botan3-3.12.0+dfsg/src/lib/pubkey/info.txt --- botan3-3.7.1+dfsg/src/lib/pubkey/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -25,9 +25,9 @@ asn1 bigint +enc_padding kdf pem -pk_pad numbertheory rng hash diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/kex_to_kem_adapter/info.txt botan3-3.12.0+dfsg/src/lib/pubkey/kex_to_kem_adapter/info.txt --- botan3-3.7.1+dfsg/src/lib/pubkey/kex_to_kem_adapter/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/kex_to_kem_adapter/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,18 @@ + +KEX_TO_KEM_ADAPTER -> 20240504 + + + +name -> "KEX to KEM adapter" +brief -> "Basic KEX to KEM key transformation" +type -> "Internal" + + + + +kex_to_kem_adapter.h + + + + + diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/kex_to_kem_adapter/kex_to_kem_adapter.cpp botan3-3.12.0+dfsg/src/lib/pubkey/kex_to_kem_adapter/kex_to_kem_adapter.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/kex_to_kem_adapter/kex_to_kem_adapter.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/kex_to_kem_adapter/kex_to_kem_adapter.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,270 @@ +/** + * Adapter that allows using a KEX key as a KEM, using an ephemeral + * key in the KEM encapsulation. + * + * (C) 2023 Jack Lloyd + * 2023,2024 Fabian Albert, René Meusel - Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + */ + +#include + +#include +#include +#include + +#if defined(BOTAN_HAS_DIFFIE_HELLMAN) + #include + #include +#endif + +#if defined(BOTAN_HAS_ECDH) + #include + #include +#endif + +#if defined(BOTAN_HAS_X25519) + #include +#endif + +#if defined(BOTAN_HAS_X448) + #include +#endif + +namespace Botan { + +namespace { + +/** + * This helper determines the length of the agreed-upon value depending + * on the key agreement public key's algorithm type. It would be better + * to get this value via PK_Key_Agreement::agreed_value_size(), but + * instantiating a PK_Key_Agreement object requires a PrivateKey object + * which we don't have (yet) in the context this is used. + * + * TODO: Find a way to get this information without duplicating those + * implementation details of the key agreement algorithms. + */ +size_t kex_shared_key_length(const Public_Key& kex_public_key) { + BOTAN_ASSERT_NOMSG(kex_public_key.supports_operation(PublicKeyOperation::KeyAgreement)); + +#if defined(BOTAN_HAS_ECDH) + if(const auto* ecdh = dynamic_cast(&kex_public_key)) { + return ecdh->domain().get_p_bytes(); + } +#endif + +#if defined(BOTAN_HAS_DIFFIE_HELLMAN) + if(const auto* dh = dynamic_cast(&kex_public_key)) { + return dh->group().p_bytes(); + } +#endif + +#if defined(BOTAN_HAS_X25519) + if(const auto* curve = dynamic_cast(&kex_public_key)) { + BOTAN_UNUSED(curve); + return 32; /* TODO: magic number */ + } +#endif + +#if defined(BOTAN_HAS_X448) + if(const auto* curve = dynamic_cast(&kex_public_key)) { + BOTAN_UNUSED(curve); + return 56; /* TODO: magic number */ + } +#endif + + throw Not_Implemented( + fmt("Cannot get shared kex key length from unknown key agreement public key of type '{}' in the hybrid KEM key", + kex_public_key.algo_name())); +} + +/** + * This helper generates an ephemeral key agreement private key given a + * public key instance of a certain key agreement algorithm. + */ +std::unique_ptr generate_key_agreement_private_key(const Public_Key& kex_public_key, + RandomNumberGenerator& rng) { + BOTAN_ASSERT_NOMSG(kex_public_key.supports_operation(PublicKeyOperation::KeyAgreement)); + + auto new_kex_key = [&] { + auto new_private_key = kex_public_key.generate_another(rng); + auto* const kex_key = dynamic_cast(new_private_key.get()); + if(kex_key != nullptr) [[likely]] { + // Intentionally leak new_private_key since we hold an alias of it in kex_key, + // which is captured in a unique_ptr below + // NOLINTNEXTLINE(*-unused-return-value) + (void)new_private_key.release(); + } + return std::unique_ptr(kex_key); + }(); + + BOTAN_ASSERT(new_kex_key, "Keys wrapped in this adapter are always key-agreement keys"); + return new_kex_key; +} + +std::unique_ptr maybe_get_public_key(const std::unique_ptr& private_key) { + BOTAN_ARG_CHECK(private_key != nullptr, "Private key is a nullptr"); + return private_key->public_key(); +} + +class KEX_to_KEM_Adapter_Encryption_Operation final : public PK_Ops::KEM_Encryption_with_KDF { + public: + KEX_to_KEM_Adapter_Encryption_Operation(const Public_Key& key, std::string_view kdf, std::string_view provider) : + PK_Ops::KEM_Encryption_with_KDF(kdf), m_provider(provider), m_public_key(key) {} + + size_t raw_kem_shared_key_length() const override { return kex_shared_key_length(m_public_key); } + + size_t encapsulated_key_length() const override { + // Serializing the public value into a short-lived heap-allocated + // vector is not ideal. + // + // TODO: Find a way to get the public value length without copying + // the public value into a vector. See GH #3706 (point 5). + return m_public_key.raw_public_key_bits().size(); + } + + void raw_kem_encrypt(std::span out_encapsulated_key, + std::span raw_shared_key, + Botan::RandomNumberGenerator& rng) override { + const auto sk = generate_key_agreement_private_key(m_public_key, rng); + const auto shared_key = PK_Key_Agreement(*sk, rng, "Raw", m_provider) + .derive_key(0 /* no KDF */, m_public_key.raw_public_key_bits()) + .bits_of(); + + const auto public_value = sk->public_value(); + + // TODO: perhaps avoid these copies by providing std::span out-params + // for `PK_Key_Agreement::derive_key()` and + // `PK_Key_Agreement_Key::public_value()` + BOTAN_ASSERT_EQUAL(public_value.size(), + out_encapsulated_key.size(), + "KEX-to-KEM Adapter: encapsulated key out-param has correct length"); + BOTAN_ASSERT_EQUAL( + shared_key.size(), raw_shared_key.size(), "KEX-to-KEM Adapter: shared key out-param has correct length"); + std::copy(public_value.begin(), public_value.end(), out_encapsulated_key.begin()); + std::copy(shared_key.begin(), shared_key.end(), raw_shared_key.begin()); + } + + private: + std::string m_provider; + const Public_Key& m_public_key; +}; + +class KEX_to_KEM_Decryption_Operation final : public PK_Ops::KEM_Decryption_with_KDF { + public: + KEX_to_KEM_Decryption_Operation(const PK_Key_Agreement_Key& key, + RandomNumberGenerator& rng, + const std::string_view kdf, + const std::string_view provider) : + PK_Ops::KEM_Decryption_with_KDF(kdf), + m_operation(key, rng, "Raw", provider), + m_encapsulated_key_length(key.public_value().size()) {} + + void raw_kem_decrypt(std::span out_shared_key, std::span encap_key) override { + secure_vector shared_secret = m_operation.derive_key(0 /* no KDF */, encap_key).bits_of(); + BOTAN_ASSERT_EQUAL( + shared_secret.size(), out_shared_key.size(), "KEX-to-KEM Adapter: shared key out-param has correct length"); + std::copy(shared_secret.begin(), shared_secret.end(), out_shared_key.begin()); + } + + size_t encapsulated_key_length() const override { return m_encapsulated_key_length; } + + size_t raw_kem_shared_key_length() const override { return m_operation.agreed_value_size(); } + + private: + PK_Key_Agreement m_operation; + size_t m_encapsulated_key_length; +}; + +} // namespace + +KEX_to_KEM_Adapter_PublicKey::KEX_to_KEM_Adapter_PublicKey(std::unique_ptr public_key) : + m_public_key(std::move(public_key)) { + BOTAN_ARG_CHECK(m_public_key != nullptr, "Public key is a nullptr"); + BOTAN_ARG_CHECK(m_public_key->supports_operation(PublicKeyOperation::KeyAgreement), "Public key is no KEX key"); +} + +std::string KEX_to_KEM_Adapter_PublicKey::algo_name() const { + return fmt("KEX-to-KEM({})", m_public_key->algo_name()); +} + +size_t KEX_to_KEM_Adapter_PublicKey::estimated_strength() const { + return m_public_key->estimated_strength(); +} + +size_t KEX_to_KEM_Adapter_PublicKey::key_length() const { + return m_public_key->key_length(); +} + +bool KEX_to_KEM_Adapter_PublicKey::check_key(RandomNumberGenerator& rng, bool strong) const { + return m_public_key->check_key(rng, strong); +} + +AlgorithmIdentifier KEX_to_KEM_Adapter_PublicKey::algorithm_identifier() const { + return m_public_key->algorithm_identifier(); +} + +std::vector KEX_to_KEM_Adapter_PublicKey::raw_public_key_bits() const { + return m_public_key->raw_public_key_bits(); +} + +std::vector KEX_to_KEM_Adapter_PublicKey::public_key_bits() const { + return m_public_key->public_key_bits(); +} + +std::unique_ptr KEX_to_KEM_Adapter_PublicKey::generate_another(RandomNumberGenerator& rng) const { + return std::make_unique(generate_key_agreement_private_key(*m_public_key, rng)); +} + +bool KEX_to_KEM_Adapter_PublicKey::supports_operation(PublicKeyOperation op) const { + return op == PublicKeyOperation::KeyEncapsulation; +} + +namespace { + +std::unique_ptr capture_as_ka_key(std::unique_ptr private_key) { + auto* raw_ptr = private_key.release(); + if(auto* sk = dynamic_cast(raw_ptr)) { + return std::unique_ptr(sk); + } else { + delete raw_ptr; // NOLINT(*-owning-memory) + throw_invalid_argument( + "Private key must implement PK_Key_Agreement_Key", "KEX_to_KEM_Adapter_PrivateKey", __FILE__); + } +} + +} // namespace + +KEX_to_KEM_Adapter_PrivateKey::KEX_to_KEM_Adapter_PrivateKey(std::unique_ptr private_key) : + KEX_to_KEM_Adapter_PublicKey(maybe_get_public_key(private_key)), + m_private_key(capture_as_ka_key(std::move(private_key))) {} + +secure_vector KEX_to_KEM_Adapter_PrivateKey::private_key_bits() const { + return m_private_key->private_key_bits(); +} + +secure_vector KEX_to_KEM_Adapter_PrivateKey::raw_private_key_bits() const { + return m_private_key->raw_private_key_bits(); +} + +std::unique_ptr KEX_to_KEM_Adapter_PrivateKey::public_key() const { + return std::make_unique(m_private_key->public_key()); +} + +bool KEX_to_KEM_Adapter_PrivateKey::check_key(RandomNumberGenerator& rng, bool strong) const { + return m_private_key->check_key(rng, strong); +} + +std::unique_ptr KEX_to_KEM_Adapter_PublicKey::create_kem_encryption_op( + std::string_view kdf, std::string_view provider) const { + return std::make_unique(*m_public_key, kdf, provider); +} + +std::unique_ptr KEX_to_KEM_Adapter_PrivateKey::create_kem_decryption_op( + RandomNumberGenerator& rng, std::string_view kdf, std::string_view provider) const { + return std::make_unique(*m_private_key, rng, kdf, provider); +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/kex_to_kem_adapter/kex_to_kem_adapter.h botan3-3.12.0+dfsg/src/lib/pubkey/kex_to_kem_adapter/kex_to_kem_adapter.h --- botan3-3.7.1+dfsg/src/lib/pubkey/kex_to_kem_adapter/kex_to_kem_adapter.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/kex_to_kem_adapter/kex_to_kem_adapter.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,92 @@ +/** + * Adapter that allows using a KEX key as a KEM, using an ephemeral + * key in the KEM encapsulation. + * + * (C) 2023 Jack Lloyd + * 2023,2024 Fabian Albert, René Meusel - Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + */ + +#ifndef BOTAN_TLS_13_KEX_TO_KEM_ADAPTER_H_ +#define BOTAN_TLS_13_KEX_TO_KEM_ADAPTER_H_ + +#include + +#include + +namespace Botan { + +/** + * Adapter to use a key agreement key pair (e.g. ECDH) as a key encapsulation + * mechanism. + */ +class BOTAN_TEST_API KEX_to_KEM_Adapter_PublicKey : public virtual Public_Key { + public: + explicit KEX_to_KEM_Adapter_PublicKey(std::unique_ptr public_key); + + std::string algo_name() const override; + size_t estimated_strength() const override; + size_t key_length() const override; + bool check_key(RandomNumberGenerator& rng, bool strong) const override; + AlgorithmIdentifier algorithm_identifier() const override; + std::vector raw_public_key_bits() const override; + std::vector public_key_bits() const override; + std::unique_ptr generate_another(RandomNumberGenerator& rng) const final; + + bool supports_operation(PublicKeyOperation op) const override; + + std::unique_ptr create_kem_encryption_op( + std::string_view kdf, std::string_view provider = "base") const override; + + private: + std::unique_ptr m_public_key; +}; + +BOTAN_DIAGNOSTIC_PUSH +BOTAN_DIAGNOSTIC_IGNORE_INHERITED_VIA_DOMINANCE + +/** + * Adapter to use a key agreement key pair (e.g. ECDH) as a key encapsulation + * mechanism. This works by generating an ephemeral key pair during the + * encapsulation. The following Botan key types are supported: + * ECDH, DH, X25519 and X448. + * + * The abstract interface of a key exchange mechanism (KEX) is mapped like so: + * + * * KEM-generate(rng) -> tuple[PublicKey, PrivateKey] + * => KEX-generate(rng) -> tuple[PublicKey, PrivateKey] + * + * * KEM-encapsulate(PublicKey, rng) -> tuple[SharedSecret, EncapsulatedSharedSecret] + * => eph_pk, eph_sk = KEX-generate(rng) + * secret = KEX-agree(eph_sk, PublicKey) + * [secret, eph_pk] + * + * * KEM-decapsulate(PrivateKey, EncapsulatedSharedSecret) -> SharedSecret + * => KEX-agree(PrivateKey, EncapsulatedSharedSecret) + */ +class BOTAN_TEST_API KEX_to_KEM_Adapter_PrivateKey final : public KEX_to_KEM_Adapter_PublicKey, + public virtual Private_Key { + public: + explicit KEX_to_KEM_Adapter_PrivateKey(std::unique_ptr private_key); + + secure_vector private_key_bits() const override; + + secure_vector raw_private_key_bits() const override; + + std::unique_ptr public_key() const override; + + bool check_key(RandomNumberGenerator& rng, bool strong) const override; + + std::unique_ptr create_kem_decryption_op( + RandomNumberGenerator& rng, std::string_view kdf, std::string_view provider = "base") const override; + + private: + std::unique_ptr m_private_key; +}; + +BOTAN_DIAGNOSTIC_POP + +} // namespace Botan + +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/keypair/keypair.cpp botan3-3.12.0+dfsg/src/lib/pubkey/keypair/keypair.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/keypair/keypair.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/keypair/keypair.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -19,8 +19,8 @@ const Private_Key& private_key, const Public_Key& public_key, std::string_view padding) { - PK_Encryptor_EME encryptor(public_key, rng, padding); - PK_Decryptor_EME decryptor(private_key, rng, padding); + const PK_Encryptor_EME encryptor(public_key, rng, padding); + const PK_Decryptor_EME decryptor(private_key, rng, padding); /* Weird corner case, if the key is too small to encrypt anything at @@ -38,7 +38,7 @@ return false; } - std::vector decrypted = unlock(decryptor.decrypt(ciphertext)); + const std::vector decrypted = unlock(decryptor.decrypt(ciphertext)); return (plaintext == decrypted); } diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/kyber/kyber_common/kyber.cpp botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_common/kyber.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/kyber/kyber_common/kyber.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_common/kyber.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -15,27 +15,14 @@ #include #include -#include #include #include #include - -#include #include #include #include #include -#include #include -#include - -#if defined(BOTAN_HAS_KYBER) - #include -#endif - -#if defined(BOTAN_HAS_KYBER_90S) - #include -#endif #if defined(BOTAN_HAS_KYBER) || defined(BOTAN_HAS_KYBER_90S) #include @@ -209,7 +196,7 @@ return m_public->mode().canonical_parameter_set_identifier(); } -bool Kyber_PublicKey::check_key(RandomNumberGenerator&, bool) const { +bool Kyber_PublicKey::check_key(RandomNumberGenerator& /*rng*/, bool /*strong*/) const { // The length checks described in FIPS 203, Section 7.2 are already performed // while decoding the public key. See constructor of Kyber_PublicKeyInternal. // The decoding function KyberAlgos::byte_decode() also checks the range of @@ -242,12 +229,15 @@ Kyber_PrivateKey::Kyber_PrivateKey(std::span sk, KyberMode m) { KyberConstants mode(m); - if(mode.private_key_bytes() != sk.size()) { + if(mode.mode().is_ml_kem() && sk.size() == mode.seed_private_key_bytes()) { + std::tie(m_public, m_private) = Seed_Expanding_Keypair_Codec().decode_keypair(sk, std::move(mode)); + } else if(sk.size() == mode.expanded_private_key_bytes()) { + std::tie(m_public, m_private) = Expanded_Keypair_Codec().decode_keypair(sk, std::move(mode)); + } else if(!mode.mode().is_ml_kem() && sk.size() == mode.seed_private_key_bytes()) { + throw Invalid_Argument("Kyber round 3 private keys do not support the seed format"); + } else { throw Invalid_Argument("Private key does not have the correct byte count"); } - - const auto& codec = mode.keypair_codec(); - std::tie(m_public, m_private) = codec.decode_keypair(sk, std::move(mode)); } std::unique_ptr Kyber_PrivateKey::public_key() const { @@ -259,7 +249,7 @@ } secure_vector Kyber_PrivateKey::private_key_bits() const { - return m_private->mode().keypair_codec().encode_keypair({m_public, m_private}); + return private_key_bits_with_format(private_key_format()); } bool Kyber_PrivateKey::check_key(RandomNumberGenerator& rng, bool strong) const { @@ -325,4 +315,26 @@ throw Provider_Not_Found(algo_name(), provider); } +MlPrivateKeyFormat Kyber_PrivateKey::private_key_format() const { + if(mode().is_ml_kem() && m_private->seed().d.has_value()) { + return MlPrivateKeyFormat::Seed; + } + return MlPrivateKeyFormat::Expanded; +} + +secure_vector Kyber_PrivateKey::private_key_bits_with_format(MlPrivateKeyFormat format) const { + if(format == MlPrivateKeyFormat::Seed && private_key_format() != MlPrivateKeyFormat::Seed) { + throw Encoding_Error("Expanded private keys do not support the seed format"); + } + const auto codec = [&]() -> std::unique_ptr { + switch(format) { + case MlPrivateKeyFormat::Seed: + return std::make_unique(); + case MlPrivateKeyFormat::Expanded: + return std::make_unique(); + } + BOTAN_ASSERT_UNREACHABLE(); + }(); + return codec->encode_keypair({m_public, m_private}); +} } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/kyber/kyber_common/kyber.h botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_common/kyber.h --- botan3-3.7.1+dfsg/src/lib/pubkey/kyber/kyber_common/kyber.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_common/kyber.h 2026-05-07 01:38:28.000000000 +0000 @@ -14,11 +14,7 @@ #ifndef BOTAN_KYBER_COMMON_H_ #define BOTAN_KYBER_COMMON_H_ -#include -#include -#include #include - #include #if !defined(BOTAN_HAS_KYBER_90S) && !defined(BOTAN_HAS_KYBER) && !defined(BOTAN_HAS_ML_KEM) @@ -29,30 +25,32 @@ namespace Botan { -class BOTAN_PUBLIC_API(3, 0) KyberMode { +class BOTAN_PUBLIC_API(3, 0) KyberMode final { public: - enum Mode { + enum Mode : uint8_t /* NOLINT(*-use-enum-class) */ { // Kyber512 as proposed in round 3 of the NIST competition - Kyber512_R3, + Kyber512_R3 BOTAN_DEPRECATED("Kyber R3 is deprecated - use ML-KEM") = 0, // Kyber768 as proposed in round 3 of the NIST competition - Kyber768_R3, + Kyber768_R3 BOTAN_DEPRECATED("Kyber R3 is deprecated - use ML-KEM") = 1, // Kyber1024 as proposed in round 3 of the NIST competition - Kyber1024_R3, + Kyber1024_R3 BOTAN_DEPRECATED("Kyber R3 is deprecated - use ML-KEM") = 2, - Kyber512 BOTAN_DEPRECATED("Use Kyber512_R3") = Kyber512_R3, - Kyber768 BOTAN_DEPRECATED("Use Kyber768_R3") = Kyber768_R3, - Kyber1024 BOTAN_DEPRECATED("Use Kyber1024_R3") = Kyber1024_R3, - - ML_KEM_512, - ML_KEM_768, - ML_KEM_1024, - - Kyber512_90s BOTAN_DEPRECATED("Kyber 90s mode is deprecated"), - Kyber768_90s BOTAN_DEPRECATED("Kyber 90s mode is deprecated"), - Kyber1024_90s BOTAN_DEPRECATED("Kyber 90s mode is deprecated"), + Kyber512 BOTAN_DEPRECATED("Kyber R3 is deprecated - use ML-KEM") = 0, + Kyber768 BOTAN_DEPRECATED("Kyber R3 is deprecated - use ML-KEM") = 1, + Kyber1024 BOTAN_DEPRECATED("Kyber R3 is deprecated - use ML-KEM") = 2, + + ML_KEM_512 = 3, + ML_KEM_768 = 4, + ML_KEM_1024 = 5, + + Kyber512_90s BOTAN_DEPRECATED("Kyber 90s mode is deprecated") = 6, + Kyber768_90s BOTAN_DEPRECATED("Kyber 90s mode is deprecated") = 7, + Kyber1024_90s BOTAN_DEPRECATED("Kyber 90s mode is deprecated") = 8, }; + // NOLINTNEXTLINE(*-explicit-conversions) KyberMode(Mode mode); + explicit KyberMode(const OID& oid); explicit KyberMode(std::string_view str); @@ -79,6 +77,17 @@ Mode m_mode; }; +/// Byte encoding format of ML-KEM and ML-DSA the private key +enum class MlPrivateKeyFormat : uint8_t { + /// Only supported for ML-KEM/ML-DSA keys: + /// - ML-KEM: 64-byte seed: d || z + /// - ML-DSA: 32-byte seed: xi (private_key_bits_with_format not yet + /// yet supported for ML-DSA) + Seed, + /// The expanded format, i.e., the format specified in FIPS-203/204. + Expanded, +}; + class Kyber_PublicKeyInternal; class Kyber_PrivateKeyInternal; @@ -89,8 +98,9 @@ Kyber_PublicKey(const AlgorithmIdentifier& alg_id, std::span key_bits); Kyber_PublicKey(const Kyber_PublicKey& other); - Kyber_PublicKey& operator=(const Kyber_PublicKey& other) = default; + Kyber_PublicKey(Kyber_PublicKey&& other) = default; + Kyber_PublicKey& operator=(Kyber_PublicKey&& other) = default; ~Kyber_PublicKey() override = default; @@ -131,7 +141,7 @@ friend class Kyber_KEM_Encryptor; friend class Kyber_KEM_Decryptor; - std::shared_ptr m_public; + std::shared_ptr m_public; // NOLINT(*non-private-member-variable*) }; BOTAN_DIAGNOSTIC_PUSH @@ -140,10 +150,28 @@ class BOTAN_PUBLIC_API(3, 0) Kyber_PrivateKey final : public virtual Kyber_PublicKey, public virtual Private_Key { public: + /** + * Create a new private key. The private key will be encoded as the 64 byte + * seed. + */ Kyber_PrivateKey(RandomNumberGenerator& rng, KyberMode mode); + /** + * Import a private key using its key bytes. Supported are key bytes as + * 64-byte seeds (not supported for Kyber Round 3 instances), + * as well as the expanded encoding specified by FIPS 203. Note that the + * encoding used in this constructor is reflected by the calls for + * private_key_bits, private_key_info, etc. + */ Kyber_PrivateKey(std::span sk, KyberMode mode); + /** + * Import a private key using its key bytes. Supported are key bytes as + * 64-byte seeds (not supported for Kyber Round 3 instances), + * as well as the expanded encoding specified by FIPS 203. Note that the + * encoding used in this constructor is reflected by the calls for + * private_key_bits, private_key_info, etc. + */ Kyber_PrivateKey(const AlgorithmIdentifier& alg_id, std::span key_bits); std::unique_ptr public_key() const override; @@ -158,6 +186,26 @@ std::string_view params, std::string_view provider) const override; + /** + * The private key format from which the key was loaded. It is the format + * used for the private_key_bits(), raw_private_key_bits() andFIPS + * private_key_info() methods. + * + * Note that keys in Seed format can be serialized to Expanded format + * using the method private_key_bits_with_format but NOT the other way + * around. + */ + MlPrivateKeyFormat private_key_format() const; + + /** + * Encode the private key in the specified format. Note that the seed + * format is only available for new ML-KEM keys and those loaded from + * seeds. + * @throws Encoding_Error if the private key cannot be encoded in the + * requested format. + */ + secure_vector private_key_bits_with_format(MlPrivateKeyFormat format) const; + private: friend class Kyber_KEM_Decryptor; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_algos.cpp botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_algos.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_algos.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_algos.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -14,6 +14,8 @@ #include +#include +#include #include #include #include @@ -183,7 +185,7 @@ void encode_polynomial_vector(std::span out, const KyberPolyVecNTT& vec) { BufferStuffer bs(out); - for(auto& v : vec) { + for(const auto& v : vec) { byte_encode(bs, v); } BOTAN_ASSERT_NOMSG(bs.full()); @@ -382,10 +384,14 @@ KyberPolyMat mat(mode.k(), mode.k()); + const auto& sym = mode.symmetric_primitives(); + std::unique_ptr xof; + for(uint8_t i = 0; i < mode.k(); ++i) { for(uint8_t j = 0; j < mode.k(); ++j) { const auto pos = (transposed) ? std::tuple(i, j) : std::tuple(j, i); - sample_ntt_uniform(mat[i][j], mode.symmetric_primitives().XOF(seed, pos)); + sym.setup_XOF(xof, seed, pos); + sample_ntt_uniform(mat[i][j], *xof); } } diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_algos.h botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_algos.h --- botan3-3.7.1+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_algos.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_algos.h 2026-05-07 01:38:28.000000000 +0000 @@ -15,11 +15,8 @@ #ifndef BOTAN_KYBER_ALGOS_H_ #define BOTAN_KYBER_ALGOS_H_ -#include -#include #include #include -#include namespace Botan::Kyber_Algos { @@ -94,7 +91,12 @@ } private: - KyberSamplingRandomness prf(size_t bytes) { return m_mode.symmetric_primitives().PRF(m_seed, m_nonce++, bytes); } + KyberSamplingRandomness prf(size_t bytes) { + const auto& sym = m_mode.symmetric_primitives(); + auto seed_span = m_seed.get(); + sym.setup_PRF(m_prf_xof, seed_span, m_nonce++); + return m_prf_xof->output(bytes); + } void sample_poly_cbd(KyberPoly& poly, KyberConstants::KyberEta eta) { const auto randomness = [&] { @@ -115,6 +117,7 @@ StrongSpan m_seed; const KyberConstants& m_mode; uint8_t m_nonce; + std::unique_ptr m_prf_xof; }; template diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_constants.cpp botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_constants.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_constants.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_constants.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -21,10 +21,6 @@ #include #endif -#if defined(BOTAN_HAS_KYBER) || defined(BOTAN_HAS_KYBER_90S) - #include -#endif - #if defined(BOTAN_HAS_ML_KEM) #include #endif @@ -70,21 +66,18 @@ #ifdef BOTAN_HAS_KYBER_90S if(mode.is_kyber_round3() && mode.is_90s()) { m_symmetric_primitives = std::make_unique(); - m_keypair_codec = std::make_unique(); } #endif #ifdef BOTAN_HAS_KYBER if(mode.is_kyber_round3() && mode.is_modern()) { m_symmetric_primitives = std::make_unique(); - m_keypair_codec = std::make_unique(); } #endif #ifdef BOTAN_HAS_ML_KEM if(mode.is_ml_kem()) { m_symmetric_primitives = std::make_unique(); - m_keypair_codec = std::make_unique(); } #endif @@ -92,14 +85,9 @@ m_polynomial_vector_bytes = (bitlen(Q) * (N / 8)) * k(); m_polynomial_vector_compressed_bytes = d_u() * k() * (N / 8); m_polynomial_compressed_bytes = d_v() * (N / 8); - m_private_key_bytes = static_cast([this]() -> size_t { - if(m_mode.is_ml_kem()) { - // ML-KEM's private keys are simply expanded from their seeds. - return 2 * SEED_BYTES; - } else { - return m_polynomial_vector_bytes + public_key_bytes() + PUBLIC_KEY_HASH_BYTES + SEED_BYTES; - } - }()); + m_expanded_private_key_bytes = + static_cast(m_polynomial_vector_bytes + public_key_bytes() + PUBLIC_KEY_HASH_BYTES + SEED_BYTES); + m_seed_private_key_bytes = 2 * SEED_BYTES; if(!m_symmetric_primitives) { throw Not_Implemented("requested Kyber mode is not enabled in this build"); diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_constants.h botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_constants.h --- botan3-3.7.1+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_constants.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_constants.h 2026-05-07 01:38:28.000000000 +0000 @@ -17,7 +17,6 @@ namespace Botan { class Kyber_Symmetric_Primitives; -class Kyber_Keypair_Codec; class KyberConstants final { public: @@ -48,16 +47,20 @@ static constexpr uint16_t SAMPLE_NTT_POLY_FROM_XOF_BOUND = 280 * 3 /* XOF bytes per while iteration */; public: + // NOLINTBEGIN(*-use-enum-class) + enum KyberEta : uint8_t { _2 = 2, _3 = 3 }; enum KyberDu : uint8_t { _10 = 10, _11 = 11 }; enum KyberDv : uint8_t { _4 = 4, _5 = 5 }; - enum KyberStrength : uint32_t { _128 = 128, _192 = 192, _256 = 256 }; + enum KyberStrength : uint16_t { _128 = 128, _192 = 192, _256 = 256 }; + + // NOLINTEND(*-use-enum-class) public: - KyberConstants(KyberMode mode); + /* NOLINT(*-explicit-conversions) */ KyberConstants(KyberMode mode); ~KyberConstants(); @@ -110,15 +113,17 @@ /// byte length of an encoded public key size_t public_key_bytes() const { return polynomial_vector_bytes() + SEED_BYTES; } - /// byte length of an encoded private key - size_t private_key_bytes() const { return m_private_key_bytes; } + /// byte length of a private key with expanded encoding as defined + // in FIPS 203 + size_t expanded_private_key_bytes() const { return m_expanded_private_key_bytes; } + + /// byte length of an private key encoded as the seed: d || z + size_t seed_private_key_bytes() const { return m_seed_private_key_bytes; } /// @} Kyber_Symmetric_Primitives& symmetric_primitives() const { return *m_symmetric_primitives; } - Kyber_Keypair_Codec& keypair_codec() const { return *m_keypair_codec; } - private: KyberMode m_mode; @@ -131,9 +136,10 @@ uint32_t m_polynomial_vector_bytes; uint32_t m_polynomial_vector_compressed_bytes; uint32_t m_polynomial_compressed_bytes; - uint32_t m_private_key_bytes; - std::unique_ptr m_keypair_codec; + uint32_t m_expanded_private_key_bytes; + uint32_t m_seed_private_key_bytes; + std::unique_ptr m_symmetric_primitives; }; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_encaps_base.h botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_encaps_base.h --- botan3-3.7.1+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_encaps_base.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_encaps_base.h 2026-05-07 01:38:28.000000000 +0000 @@ -17,15 +17,19 @@ class Kyber_KEM_Operation_Base { protected: - Kyber_KEM_Operation_Base(const Kyber_PublicKeyInternal& pk) : - m_At(Kyber_Algos::sample_matrix(pk.rho(), true /* transposed */, pk.mode())) {} + explicit Kyber_KEM_Operation_Base(const Kyber_PublicKeyInternal& pk) : + m_mode(pk.mode()), m_At(Kyber_Algos::sample_matrix(pk.rho(), true /* transposed */, m_mode)) {} + + const KyberConstants& mode() const { return m_mode; } const KyberPolyMat& precomputed_matrix_At() const { return m_At; } private: + const KyberConstants& m_mode; + // The public key's matrix is pre-computed to avoid redundant work when // encapsulating multiple keys. This matrix is needed for encapsulation as - // well as for the FO transform in the decapsulation. + // well as for the Fujisaki-Okamoto transform in the decapsulation. KyberPolyMat m_At; }; @@ -51,8 +55,6 @@ virtual void encapsulate(StrongSpan out_encapsulated_key, StrongSpan out_shared_key, RandomNumberGenerator& rng) = 0; - - virtual const KyberConstants& mode() const = 0; }; class Kyber_KEM_Decryptor_Base : public PK_Ops::KEM_Decryption_with_KDF, @@ -73,8 +75,6 @@ virtual void decapsulate(StrongSpan out_shared_key, StrongSpan encapsulated_key) = 0; - - virtual const KyberConstants& mode() const = 0; }; } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_helpers.h botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_helpers.h --- botan3-3.7.1+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_helpers.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_helpers.h 2026-05-07 01:38:28.000000000 +0000 @@ -54,7 +54,7 @@ constexpr size_t p = 33; constexpr unsigned_T mask = (1 << d) - 1; return static_cast((n * m) >> p) & mask; -}; +} /** * NIST FIPS 203, Formula 4.8 (Decompress) diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_keys.cpp botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_keys.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_keys.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_keys.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -11,8 +11,9 @@ #include +#include +#include #include -#include namespace Botan { @@ -27,6 +28,80 @@ } // namespace +/** + * Key decoding as specified in Crystals Kyber (Version 3.01), + * Algorithms 4 (CPAPKE.KeyGen()), and 7 (CCAKEM.KeyGen()) + * + * Public Key: pk := (encode(t) || rho) + * Secret Key: sk' := encode(s) + * + * Expanded Secret Key: sk := (sk' || pk || H(pk) || z) + */ +KyberInternalKeypair Expanded_Keypair_Codec::decode_keypair(std::span sk, KyberConstants mode) const { + auto scope = CT::scoped_poison(sk); + BufferSlicer s(sk); + + auto skpv = Kyber_Algos::decode_polynomial_vector(s.take(mode.polynomial_vector_bytes()), mode); + auto pub_key = s.copy(mode.public_key_bytes()); + auto puk_key_hash = s.take(KyberConstants::PUBLIC_KEY_HASH_BYTES); + auto z = s.copy(KyberConstants::SEED_BYTES); + + BOTAN_ASSERT_NOMSG(s.empty()); + + CT::unpoison_all(pub_key, puk_key_hash, skpv, z); + + KyberInternalKeypair keypair{ + std::make_shared(mode, std::move(pub_key)), + std::make_shared( + std::move(mode), + std::move(skpv), + KyberPrivateKeySeed{std::nullopt, // Reading from an expanded and encoded + // private key cannot reconstruct the + // original seed from key generation. + std::move(z)}), + }; + + BOTAN_ASSERT(keypair.first && keypair.second, "reading private key encoding"); + BOTAN_ARG_CHECK(keypair.first->H_public_key_bits_raw().size() == puk_key_hash.size() && + std::equal(keypair.first->H_public_key_bits_raw().begin(), + keypair.first->H_public_key_bits_raw().end(), + puk_key_hash.begin()), + "public key's hash does not match the stored hash"); + + return keypair; +} + +secure_vector Expanded_Keypair_Codec::encode_keypair(KyberInternalKeypair keypair) const { + BOTAN_ASSERT_NONNULL(keypair.first); + BOTAN_ASSERT_NONNULL(keypair.second); + const auto& mode = keypair.first->mode(); + auto scope = CT::scoped_poison(*keypair.second); + auto result = concat(Kyber_Algos::encode_polynomial_vector(keypair.second->s().reduce(), mode), + keypair.first->public_key_bits_raw(), + keypair.first->H_public_key_bits_raw(), + keypair.second->z()); + CT::unpoison(result); + return result; +} + +KyberInternalKeypair Seed_Expanding_Keypair_Codec::decode_keypair(std::span private_key, + KyberConstants mode) const { + BufferSlicer s(private_key); + auto seed = KyberPrivateKeySeed{ + s.copy(KyberConstants::SEED_BYTES), + s.copy(KyberConstants::SEED_BYTES), + }; + BOTAN_ASSERT_NOMSG(s.empty()); + return Kyber_Algos::expand_keypair(std::move(seed), std::move(mode)); +} + +secure_vector Seed_Expanding_Keypair_Codec::encode_keypair(KyberInternalKeypair keypair) const { + BOTAN_ASSERT_NONNULL(keypair.second); + const auto& seed = keypair.second->seed(); + BOTAN_ARG_CHECK(seed.d.has_value(), "Cannot encode keypair without the full private seed"); + return concat>(seed.d.value(), seed.z); +} + Kyber_PublicKeyInternal::Kyber_PublicKeyInternal(KyberConstants mode, KyberSerializedPublicKey public_key) : m_mode(std::move(mode)), m_public_key_bits_raw(validate_public_key_length(std::move(public_key), m_mode.public_key_bytes())), @@ -55,9 +130,10 @@ void Kyber_PublicKeyInternal::indcpa_encrypt(StrongSpan out_ct, StrongSpan m, StrongSpan r, - const KyberPolyMat& At) const { + const KyberPolyMat& At, + const KyberConstants& mode) const { // The nonce N is handled internally by the PolynomialSampler - Kyber_Algos::PolynomialSampler ps(r, m_mode); + Kyber_Algos::PolynomialSampler ps(r, mode); const auto y = ntt(ps.sample_polynomial_vector_cbd_eta1()); const auto e1 = ps.sample_polynomial_vector_cbd_eta2(); const auto e2 = ps.sample_polynomial_cbd_eta2(); diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_keys.h botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_keys.h --- botan3-3.7.1+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_keys.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_keys.h 2026-05-07 01:38:28.000000000 +0000 @@ -19,14 +19,28 @@ namespace Botan { -class Kyber_Keypair_Codec { +class Kyber_Keypair_Codec /* NOLINT(*-special-member-functions) */ { public: virtual ~Kyber_Keypair_Codec() = default; virtual secure_vector encode_keypair(KyberInternalKeypair keypair) const = 0; virtual KyberInternalKeypair decode_keypair(std::span private_key, KyberConstants mode) const = 0; }; -class Kyber_PublicKeyInternal { +/// Codec for expanded private keys (as specified in FIPS 203) +class Expanded_Keypair_Codec final : public Kyber_Keypair_Codec { + public: + KyberInternalKeypair decode_keypair(std::span buffer, KyberConstants mode) const override; + secure_vector encode_keypair(KyberInternalKeypair private_key) const override; +}; + +/// Codec for private keys as 64-byte seeds: d || z +class Seed_Expanding_Keypair_Codec final : public Kyber_Keypair_Codec { + public: + KyberInternalKeypair decode_keypair(std::span buffer, KyberConstants mode) const override; + secure_vector encode_keypair(KyberInternalKeypair keypair) const override; +}; + +class Kyber_PublicKeyInternal final { public: Kyber_PublicKeyInternal(KyberConstants mode, KyberSerializedPublicKey public_key); Kyber_PublicKeyInternal(KyberConstants mode, KyberPolyVecNTT polynomials, KyberSeedRho seed); @@ -34,13 +48,15 @@ void indcpa_encrypt(StrongSpan out_ct, StrongSpan m, StrongSpan r, - const KyberPolyMat& At) const; + const KyberPolyMat& At, + const KyberConstants& mode) const; KyberCompressedCiphertext indcpa_encrypt(const KyberMessage& m, const KyberEncryptionRandomness& r, - const KyberPolyMat& At) const { + const KyberPolyMat& At, + const KyberConstants& mode) const { KyberCompressedCiphertext ct(m_mode.ciphertext_bytes()); - indcpa_encrypt(ct, m, r, At); + indcpa_encrypt(ct, m, r, At, mode); return ct; } @@ -64,7 +80,7 @@ const KyberSeedRho m_rho; }; -class Kyber_PrivateKeyInternal { +class Kyber_PrivateKeyInternal final { public: Kyber_PrivateKeyInternal(KyberConstants mode, KyberPolyVecNTT s, KyberPrivateKeySeed seed) : m_mode(std::move(mode)), m_s(std::move(s)), m_seed(std::move(seed)) {} diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_polynomial.h botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_polynomial.h --- botan3-3.7.1+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_polynomial.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_polynomial.h 2026-05-07 01:38:28.000000000 +0000 @@ -27,7 +27,7 @@ friend class CRYSTALS::Trait_Base; constexpr static T montgomery_reduce_coefficient(T2 a) { - const T u = static_cast(a) * Q_inverse; + const T u = static_cast(static_cast(a) * Q_inverse); auto t = static_cast(u) * Q; t = a - t; t >>= sizeof(T) * 8; @@ -36,8 +36,8 @@ constexpr static T barrett_reduce_coefficient(T a) { constexpr T2 v = ((1U << 26) + Q / 2) / Q; - const T t = (v * a >> 26) * Q; - return a - t; + const T t = static_cast(((v * a) >> 26) * Q); + return static_cast(a - t); } public: @@ -54,8 +54,8 @@ const auto zeta = zetas[++i]; for(j = start; j < start + len; ++j) { const auto t = fqmul(zeta, p[j + len]); - p[j + len] = p[j] - t; - p[j] = p[j] + t; + p[j + len] = static_cast(p[j] - t); + p[j] = static_cast(p[j] + t); } } } @@ -80,8 +80,8 @@ const auto zeta = zetas[i--]; for(j = start; j < start + len; ++j) { const auto t = p[j]; - p[j] = barrett_reduce_coefficient(t + p[j + len]); - p[j + len] = fqmul(zeta, p[j + len] - t); + p[j] = barrett_reduce_coefficient(static_cast(t + p[j + len])); + p[j + len] = fqmul(zeta, static_cast(p[j + len] - t)); } } } @@ -119,9 +119,10 @@ }; for(size_t i = 0; i < Tq_elem_count(result) / 2; ++i) { - const auto zeta = zetas[64 + i]; + const T zeta = zetas[64 + i]; + const T nzeta = static_cast(-zeta); Tq_elem(result, 2 * i) = basemul(Tq_elem(lhs, 2 * i), Tq_elem(rhs, 2 * i), zeta); - Tq_elem(result, 2 * i + 1) = basemul(Tq_elem(lhs, 2 * i + 1), Tq_elem(rhs, 2 * i + 1), -zeta); + Tq_elem(result, 2 * i + 1) = basemul(Tq_elem(lhs, 2 * i + 1), Tq_elem(rhs, 2 * i + 1), nzeta); } } }; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_symmetric_primitives.h botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_symmetric_primitives.h --- botan3-3.7.1+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_symmetric_primitives.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_common/kyber_symmetric_primitives.h 2026-05-07 01:38:28.000000000 +0000 @@ -11,12 +11,10 @@ #define BOTAN_KYBER_SYMMETRIC_PRIMITIVES_H_ #include -#include #include - +#include #include #include -#include #include #include @@ -27,20 +25,20 @@ * Adapter class that uses polymorphy to distinguish * Kyber "modern" from Kyber "90s" modes. */ -class Kyber_Symmetric_Primitives { +class Kyber_Symmetric_Primitives /* NOLINT(*-special-member-functions) */ { public: virtual ~Kyber_Symmetric_Primitives() = default; // TODO: remove this once Kyber-R3 is removed - KyberMessage H(StrongSpan m) const { return get_H().process(m); } + KyberMessage H(StrongSpan m) const { return create_H()->process(m); } // TODO: remove this once Kyber-R3 is removed KyberHashedCiphertext H(StrongSpan r) const { - return get_H().process(r); + return create_H()->process(r); } KyberHashedPublicKey H(StrongSpan pk) const { - return get_H().process(pk); + return create_H()->process(pk); } std::pair G(StrongSpan seed, @@ -59,41 +57,57 @@ KyberSharedSecret J(StrongSpan rejection_value, StrongSpan ciphertext) const { - auto& j = get_J(); - j.update(rejection_value); - j.update(ciphertext); - return j.final(); + auto j = create_J(); + j->update(rejection_value); + j->update(ciphertext); + return j->final(); } // TODO: remove this once Kyber-R3 is removed void KDF(StrongSpan out, StrongSpan shared_secret, StrongSpan hashed_ciphertext) const { - auto& kdf = get_KDF(); - kdf.update(shared_secret); - kdf.update(hashed_ciphertext); - kdf.final(out); + auto kdf = create_KDF(); + kdf->update(shared_secret); + kdf->update(hashed_ciphertext); + kdf->final(out); } KyberSamplingRandomness PRF(KyberSigmaOrEncryptionRandomness seed, const uint8_t nonce, const size_t outlen) const { auto bare_seed_span = std::visit([&](const auto s) { return s.get(); }, seed); - return get_PRF(bare_seed_span, nonce).output(outlen); + return create_PRF(bare_seed_span, nonce)->output(outlen); } - Botan::XOF& XOF(StrongSpan seed, std::tuple matrix_position) const { - return get_XOF(seed, matrix_position); + /// Setup an XOF object for matrix sampling + void setup_XOF(std::unique_ptr& xof, + StrongSpan seed, + std::tuple matrix_position) const { + if(!xof) { + xof = create_XOF(seed, matrix_position); + } else { + init_XOF(*xof, seed, matrix_position); + } + } + + /// Setup a seeded PRF XOF for polynomial sampling + void setup_PRF(std::unique_ptr& xof, std::span seed, uint8_t nonce) const { + if(!xof) { + xof = create_PRF(seed, nonce); + } else { + init_PRF(*xof, seed, nonce); + } } private: template - std::pair G_split(InputTs&&... inputs) const { - auto& g = get_G(); - (g.update(inputs), ...); - auto s = g.final(); + std::pair G_split(const InputTs&... inputs) const { + auto g = create_G(); + (g->update(inputs), ...); + const auto s = g->final(); BufferSlicer bs(s); std::pair result; @@ -107,13 +121,19 @@ virtual std::optional> seed_expansion_domain_separator( const KyberConstants& mode) const = 0; - virtual HashFunction& get_G() const = 0; - virtual HashFunction& get_H() const = 0; - virtual HashFunction& get_J() const = 0; - virtual HashFunction& get_KDF() const = 0; - virtual Botan::XOF& get_PRF(std::span seed, uint8_t nonce) const = 0; - virtual Botan::XOF& get_XOF(std::span seed, - std::tuple matrix_position) const = 0; + virtual std::unique_ptr create_G() const = 0; + virtual std::unique_ptr create_H() const = 0; + virtual std::unique_ptr create_J() const = 0; + virtual std::unique_ptr create_KDF() const = 0; + + virtual std::unique_ptr create_PRF(std::span seed, uint8_t nonce) const = 0; + virtual void init_PRF(Botan::XOF& xof, std::span seed, uint8_t nonce) const = 0; + + virtual std::unique_ptr create_XOF(std::span seed, + std::tuple matrix_position) const = 0; + virtual void init_XOF(Botan::XOF& xof, + std::span seed, + std::tuple matrix_position) const = 0; }; } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/kyber/kyber_round3/kyber/kyber_modern.h botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_round3/kyber/kyber_modern.h --- botan3-3.7.1+dfsg/src/lib/pubkey/kyber/kyber_round3/kyber/kyber_modern.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_round3/kyber/kyber_modern.h 2026-05-07 01:38:28.000000000 +0000 @@ -10,58 +10,60 @@ #ifndef BOTAN_KYBER_MODERN_H_ #define BOTAN_KYBER_MODERN_H_ -#include -#include - #include -#include +#include +#include +#include #include namespace Botan { class Kyber_Modern_Symmetric_Primitives final : public Kyber_Symmetric_Primitives { - public: - Kyber_Modern_Symmetric_Primitives() : - m_sha3_512(HashFunction::create_or_throw("SHA-3(512)")), - m_sha3_256(HashFunction::create_or_throw("SHA-3(256)")), - m_shake256_256(HashFunction::create_or_throw("SHAKE-256(256)")), - m_shake128(Botan::XOF::create_or_throw("SHAKE-128")), - m_shake256(Botan::XOF::create_or_throw("SHAKE-256")) {} - protected: - std::optional> seed_expansion_domain_separator(const KyberConstants&) const override { + std::optional> seed_expansion_domain_separator( + const KyberConstants& /*constants*/) const override { return {}; } - HashFunction& get_G() const override { return *m_sha3_512; } + std::unique_ptr create_G() const override { return HashFunction::create_or_throw("SHA-3(512)"); } - HashFunction& get_H() const override { return *m_sha3_256; } + std::unique_ptr create_H() const override { return HashFunction::create_or_throw("SHA-3(256)"); } - HashFunction& get_J() const override { throw Invalid_State("Kyber-R3 does not support J()"); } + std::unique_ptr create_J() const override { throw Invalid_State("Kyber-R3 does not support J()"); } - HashFunction& get_KDF() const override { return *m_shake256_256; } + std::unique_ptr create_KDF() const override { + return HashFunction::create_or_throw("SHAKE-256(256)"); + } + + std::unique_ptr create_PRF(std::span seed, const uint8_t nonce) const override { + auto xof = Botan::XOF::create_or_throw("SHAKE-256"); + init_PRF(*xof, seed, nonce); + return xof; + } - Botan::XOF& get_PRF(std::span seed, const uint8_t nonce) const override { - m_shake256->clear(); - m_shake256->update(seed); - m_shake256->update(store_be(nonce)); - return *m_shake256; + void init_PRF(Botan::XOF& xof, std::span seed, const uint8_t nonce) const override { + xof.clear(); + xof.update(seed); + xof.update({&nonce, 1}); } - Botan::XOF& get_XOF(std::span seed, std::tuple matrix_position) const override { - m_shake128->clear(); - m_shake128->update(seed); - m_shake128->update(store_be(make_uint16(std::get<0>(matrix_position), std::get<1>(matrix_position)))); - return *m_shake128; + std::unique_ptr create_XOF(std::span seed, + std::tuple matrix_position) const override { + auto xof = Botan::XOF::create_or_throw("SHAKE-128"); + init_XOF(*xof, seed, matrix_position); + return xof; } - private: - std::unique_ptr m_sha3_512; - std::unique_ptr m_sha3_256; - std::unique_ptr m_shake256_256; - std::unique_ptr m_shake128; - std::unique_ptr m_shake256; + void init_XOF(Botan::XOF& xof, + std::span seed, + std::tuple matrix_position) const override { + xof.clear(); + xof.update(seed); + + const std::array pos = {std::get<0>(matrix_position), std::get<1>(matrix_position)}; + xof.update(pos); + } }; } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/kyber/kyber_round3/kyber_90s/kyber_90s.h botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_round3/kyber_90s/kyber_90s.h --- botan3-3.7.1+dfsg/src/lib/pubkey/kyber/kyber_round3/kyber_90s/kyber_90s.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_round3/kyber_90s/kyber_90s.h 2026-05-07 01:38:28.000000000 +0000 @@ -21,43 +21,45 @@ namespace Botan { class Kyber_90s_Symmetric_Primitives final : public Kyber_Symmetric_Primitives { - public: - Kyber_90s_Symmetric_Primitives() : - m_sha512(HashFunction::create_or_throw("SHA-512")), - m_sha256(HashFunction::create_or_throw("SHA-256")), - m_aes256_xof(std::make_unique()) {} - protected: - std::optional> seed_expansion_domain_separator(const KyberConstants&) const override { + std::optional> seed_expansion_domain_separator( + const KyberConstants& /*constants*/) const override { return {}; } - HashFunction& get_G() const override { return *m_sha512; } + std::unique_ptr create_G() const override { return HashFunction::create_or_throw("SHA-512"); } - HashFunction& get_H() const override { return *m_sha256; } + std::unique_ptr create_H() const override { return HashFunction::create_or_throw("SHA-256"); } - HashFunction& get_J() const override { throw Invalid_State("Kyber-R3 in 90s mode does not support J()"); } + std::unique_ptr create_J() const override { + throw Invalid_State("Kyber-R3 in 90s mode does not support J()"); + } - HashFunction& get_KDF() const override { return *m_sha256; } + std::unique_ptr create_KDF() const override { return HashFunction::create_or_throw("SHA-256"); } - Botan::XOF& get_PRF(std::span seed, const uint8_t nonce) const override { - m_aes256_xof->clear(); - const std::array nonce_buffer{nonce, 0}; - m_aes256_xof->start(nonce_buffer, seed); - return *m_aes256_xof; + std::unique_ptr create_PRF(std::span seed, const uint8_t nonce) const override { + auto xof = std::make_unique(); + init_PRF(*xof, seed, nonce); + return xof; } - Botan::XOF& get_XOF(std::span seed, std::tuple mpos) const override { - m_aes256_xof->clear(); - const std::array iv{std::get<0>(mpos), std::get<1>(mpos), 0}; - m_aes256_xof->start(iv, seed); - return *m_aes256_xof; + void init_PRF(Botan::XOF& xof, std::span seed, const uint8_t nonce) const override { + xof.clear(); + dynamic_cast(xof).start(std::array{nonce, 0}, seed); } - private: - std::unique_ptr m_sha512; - std::unique_ptr m_sha256; - mutable std::unique_ptr m_aes256_xof; + std::unique_ptr create_XOF(std::span seed, + std::tuple mpos) const override { + auto xof = std::make_unique(); + init_XOF(*xof, seed, mpos); + return xof; + } + + void init_XOF(Botan::XOF& xof, std::span seed, std::tuple mpos) const override { + xof.clear(); + dynamic_cast(xof).start(std::array{std::get<0>(mpos), std::get<1>(mpos), 0}, + seed); + } }; } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/kyber/kyber_round3/kyber_round3_impl.cpp botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_round3/kyber_round3_impl.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/kyber/kyber_round3/kyber_round3_impl.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_round3/kyber_round3_impl.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -22,14 +22,14 @@ void Kyber_KEM_Encryptor::encapsulate(StrongSpan out_encapsulated_key, StrongSpan out_shared_key, RandomNumberGenerator& rng) { - const auto& sym = m_public_key->mode().symmetric_primitives(); + const auto& sym = mode().symmetric_primitives(); const auto seed_m = rng.random_vec(KyberConstants::SEED_BYTES); CT::poison(seed_m); const auto m = sym.H(seed_m); const auto [K_bar, r] = sym.G(m, m_public_key->H_public_key_bits_raw()); - m_public_key->indcpa_encrypt(out_encapsulated_key, m, r, precomputed_matrix_At()); + m_public_key->indcpa_encrypt(out_encapsulated_key, m, r, precomputed_matrix_At(), mode()); sym.KDF(out_shared_key, K_bar, sym.H(out_encapsulated_key)); CT::unpoison_all(out_shared_key, out_encapsulated_key); @@ -42,7 +42,7 @@ StrongSpan encapsulated_key) { auto scope = CT::scoped_poison(*m_private_key); - const auto& sym = m_public_key->mode().symmetric_primitives(); + const auto& sym = mode().symmetric_primitives(); const auto& h = m_public_key->H_public_key_bits_raw(); const auto& z = m_private_key->z(); @@ -50,73 +50,16 @@ const auto m_prime = m_private_key->indcpa_decrypt(encapsulated_key); const auto [K_bar_prime, r_prime] = sym.G(m_prime, h); - const auto c_prime = m_public_key->indcpa_encrypt(m_prime, r_prime, precomputed_matrix_At()); + const auto c_prime = m_public_key->indcpa_encrypt(m_prime, r_prime, precomputed_matrix_At(), mode()); KyberSharedSecret K(KyberConstants::SEED_BYTES); BOTAN_ASSERT_NOMSG(encapsulated_key.size() == c_prime.size()); BOTAN_ASSERT_NOMSG(K_bar_prime.size() == K.size()); - const auto reencrypt_success = CT::is_equal(encapsulated_key.data(), c_prime.data(), encapsulated_key.size()); + const auto reencrypt_success = CT::is_equal(encapsulated_key, c_prime); CT::conditional_copy_mem(reencrypt_success, K.data(), K_bar_prime.data(), z.data(), K_bar_prime.size()); sym.KDF(out_shared_key, K, sym.H(encapsulated_key)); CT::unpoison(out_shared_key); } -/** - * Key decoding as specified in Crystals Kyber (Version 3.01), - * Algorithms 4 (CPAPKE.KeyGen()), and 7 (CCAKEM.KeyGen()) - * - * Public Key: pk := (encode(t) || rho) - * Secret Key: sk' := encode(s) - * - * Expanded Secret Key: sk := (sk' || pk || H(pk) || z) - */ -KyberInternalKeypair Kyber_Expanded_Keypair_Codec::decode_keypair(std::span sk, - KyberConstants mode) const { - auto scope = CT::scoped_poison(sk); - BufferSlicer s(sk); - - auto skpv = Kyber_Algos::decode_polynomial_vector(s.take(mode.polynomial_vector_bytes()), mode); - auto pub_key = s.copy(mode.public_key_bytes()); - auto puk_key_hash = s.take(KyberConstants::PUBLIC_KEY_HASH_BYTES); - auto z = s.copy(KyberConstants::SEED_BYTES); - - BOTAN_ASSERT_NOMSG(s.empty()); - - CT::unpoison_all(pub_key, puk_key_hash, skpv, z); - - KyberInternalKeypair keypair{ - std::make_shared(mode, std::move(pub_key)), - std::make_shared( - std::move(mode), - std::move(skpv), - KyberPrivateKeySeed{std::nullopt, // Reading from an expanded and encoded - // private key cannot reconstruct the - // original seed from key generation. - std::move(z)}), - }; - - BOTAN_ASSERT(keypair.first && keypair.second, "reading private key encoding"); - BOTAN_ARG_CHECK(keypair.first->H_public_key_bits_raw().size() == puk_key_hash.size() && - std::equal(keypair.first->H_public_key_bits_raw().begin(), - keypair.first->H_public_key_bits_raw().end(), - puk_key_hash.begin()), - "public key's hash does not match the stored hash"); - - return keypair; -} - -secure_vector Kyber_Expanded_Keypair_Codec::encode_keypair(KyberInternalKeypair keypair) const { - BOTAN_ASSERT_NONNULL(keypair.first); - BOTAN_ASSERT_NONNULL(keypair.second); - const auto& mode = keypair.first->mode(); - auto scope = CT::scoped_poison(*keypair.second); - auto result = concat(Kyber_Algos::encode_polynomial_vector(keypair.second->s().reduce(), mode), - keypair.first->public_key_bits_raw(), - keypair.first->H_public_key_bits_raw(), - keypair.second->z()); - CT::unpoison(result); - return result; -} - } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/kyber/kyber_round3/kyber_round3_impl.h botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_round3/kyber_round3_impl.h --- botan3-3.7.1+dfsg/src/lib/pubkey/kyber/kyber_round3/kyber_round3_impl.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/kyber/kyber_round3/kyber_round3_impl.h 2026-05-07 01:38:28.000000000 +0000 @@ -27,8 +27,6 @@ StrongSpan out_shared_key, RandomNumberGenerator& rng) override; - const KyberConstants& mode() const override { return m_public_key->mode(); } - private: std::shared_ptr m_public_key; }; @@ -46,19 +44,11 @@ void decapsulate(StrongSpan out_shared_key, StrongSpan encapsulated_key) override; - const KyberConstants& mode() const override { return m_private_key->mode(); } - private: std::shared_ptr m_public_key; std::shared_ptr m_private_key; }; -class Kyber_Expanded_Keypair_Codec final : public Kyber_Keypair_Codec { - public: - KyberInternalKeypair decode_keypair(std::span buffer, KyberConstants mode) const override; - secure_vector encode_keypair(KyberInternalKeypair private_key) const override; -}; - } // namespace Botan #endif diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/kyber/ml_kem/ml_kem_impl.cpp botan3-3.12.0+dfsg/src/lib/pubkey/kyber/ml_kem/ml_kem_impl.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/kyber/ml_kem/ml_kem_impl.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/kyber/ml_kem/ml_kem_impl.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -10,7 +10,6 @@ #include #include -#include #include #include @@ -25,13 +24,13 @@ void ML_KEM_Encryptor::encapsulate(StrongSpan out_encapsulated_key, StrongSpan out_shared_key, RandomNumberGenerator& rng) { - const auto& sym = m_public_key->mode().symmetric_primitives(); + const auto& sym = mode().symmetric_primitives(); const auto m = rng.random_vec(KyberConstants::SEED_BYTES); auto scope = CT::scoped_poison(m); const auto [K, r] = sym.G(m, m_public_key->H_public_key_bits_raw()); - m_public_key->indcpa_encrypt(out_encapsulated_key, m, r, precomputed_matrix_At()); + m_public_key->indcpa_encrypt(out_encapsulated_key, m, r, precomputed_matrix_At(), mode()); // TODO: avoid this copy by letting sym.G() directly write to the span. copy_mem(out_shared_key, K); @@ -49,7 +48,7 @@ StrongSpan c) { auto scope = CT::scoped_poison(*m_private_key); - const auto& sym = m_public_key->mode().symmetric_primitives(); + const auto& sym = mode().symmetric_primitives(); const auto& h = m_public_key->H_public_key_bits_raw(); const auto& z = m_private_key->z(); @@ -58,32 +57,13 @@ const auto [K_prime, r_prime] = sym.G(m_prime, h); const auto K_bar = sym.J(z, c); - const auto c_prime = m_public_key->indcpa_encrypt(m_prime, r_prime, precomputed_matrix_At()); + const auto c_prime = m_public_key->indcpa_encrypt(m_prime, r_prime, precomputed_matrix_At(), mode()); BOTAN_ASSERT_NOMSG(c.size() == c_prime.size()); BOTAN_ASSERT_NOMSG(K_prime.size() == K_bar.size() && out_shared_key.size() == K_bar.size()); - const auto reencrypt_success = CT::is_equal(c.data(), c_prime.data(), c.size()); + const auto reencrypt_success = CT::is_equal(c, c_prime); CT::conditional_copy_mem(reencrypt_success, out_shared_key.data(), K_prime.data(), K_bar.data(), K_prime.size()); CT::unpoison(out_shared_key); } - -KyberInternalKeypair ML_KEM_Expanding_Keypair_Codec::decode_keypair(std::span private_key, - KyberConstants mode) const { - BufferSlicer s(private_key); - auto seed = KyberPrivateKeySeed{ - s.copy(KyberConstants::SEED_BYTES), - s.copy(KyberConstants::SEED_BYTES), - }; - BOTAN_ASSERT_NOMSG(s.empty()); - return Kyber_Algos::expand_keypair(std::move(seed), std::move(mode)); -} - -secure_vector ML_KEM_Expanding_Keypair_Codec::encode_keypair(KyberInternalKeypair keypair) const { - BOTAN_ASSERT_NONNULL(keypair.second); - const auto& seed = keypair.second->seed(); - BOTAN_ARG_CHECK(seed.d.has_value(), "Cannot encode keypair without the full private seed"); - return concat>(seed.d.value(), seed.z); -}; - } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/kyber/ml_kem/ml_kem_impl.h botan3-3.12.0+dfsg/src/lib/pubkey/kyber/ml_kem/ml_kem_impl.h --- botan3-3.7.1+dfsg/src/lib/pubkey/kyber/ml_kem/ml_kem_impl.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/kyber/ml_kem/ml_kem_impl.h 2026-05-07 01:38:28.000000000 +0000 @@ -18,6 +18,7 @@ #include #include #include +#include namespace Botan { @@ -31,8 +32,6 @@ StrongSpan out_shared_key, RandomNumberGenerator& rng) override; - const KyberConstants& mode() const override { return m_public_key->mode(); } - private: std::shared_ptr m_public_key; }; @@ -50,22 +49,12 @@ void decapsulate(StrongSpan out_shared_key, StrongSpan encapsulated_key) override; - const KyberConstants& mode() const override { return m_private_key->mode(); } - private: std::shared_ptr m_public_key; std::shared_ptr m_private_key; }; class ML_KEM_Symmetric_Primitives final : public Kyber_Symmetric_Primitives { - public: - ML_KEM_Symmetric_Primitives() : - m_sha3_512(HashFunction::create_or_throw("SHA-3(512)")), - m_sha3_256(HashFunction::create_or_throw("SHA-3(256)")), - m_shake256_256(HashFunction::create_or_throw("SHAKE-256(256)")), - m_shake128(Botan::XOF::create_or_throw("SHAKE-128")), - m_shake256(Botan::XOF::create_or_throw("SHAKE-256")) {} - protected: std::optional> seed_expansion_domain_separator(const KyberConstants& mode) const override { // NIST FIPS 203, Algorithm 13 (K-PKE.KeyGen) @@ -75,40 +64,44 @@ return std::array{mode.k()}; } - HashFunction& get_G() const override { return *m_sha3_512; } + std::unique_ptr create_G() const override { return HashFunction::create_or_throw("SHA-3(512)"); } - HashFunction& get_H() const override { return *m_sha3_256; } + std::unique_ptr create_H() const override { return HashFunction::create_or_throw("SHA-3(256)"); } - HashFunction& get_J() const override { return *m_shake256_256; } + std::unique_ptr create_J() const override { + return HashFunction::create_or_throw("SHAKE-256(256)"); + } - HashFunction& get_KDF() const override { throw Invalid_State("ML-KEM does not support KDF()"); } + std::unique_ptr create_KDF() const override { + throw Invalid_State("ML-KEM does not support KDF()"); + } - Botan::XOF& get_PRF(std::span seed, const uint8_t nonce) const override { - m_shake256->clear(); - m_shake256->update(seed); - m_shake256->update(store_be(nonce)); - return *m_shake256; + std::unique_ptr create_PRF(std::span seed, const uint8_t nonce) const override { + auto xof = Botan::XOF::create_or_throw("SHAKE-256"); + init_PRF(*xof, seed, nonce); + return xof; } - Botan::XOF& get_XOF(std::span seed, std::tuple matrix_position) const override { - m_shake128->clear(); - m_shake128->update(seed); - m_shake128->update(store_be(make_uint16(std::get<0>(matrix_position), std::get<1>(matrix_position)))); - return *m_shake128; + void init_PRF(Botan::XOF& xof, std::span seed, const uint8_t nonce) const override { + xof.clear(); + xof.update(seed); + xof.update(store_be(nonce)); } - private: - std::unique_ptr m_sha3_512; - std::unique_ptr m_sha3_256; - std::unique_ptr m_shake256_256; - std::unique_ptr m_shake128; - std::unique_ptr m_shake256; -}; + std::unique_ptr create_XOF(std::span seed, + std::tuple matrix_position) const override { + auto xof = Botan::XOF::create_or_throw("SHAKE-128"); + init_XOF(*xof, seed, matrix_position); + return xof; + } -class ML_KEM_Expanding_Keypair_Codec final : public Kyber_Keypair_Codec { - public: - KyberInternalKeypair decode_keypair(std::span buffer, KyberConstants mode) const override; - secure_vector encode_keypair(KyberInternalKeypair keypair) const override; + void init_XOF(Botan::XOF& xof, + std::span seed, + std::tuple matrix_position) const override { + xof.clear(); + xof.update(seed); + xof.update(store_be(make_uint16(std::get<0>(matrix_position), std::get<1>(matrix_position)))); + } }; } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/mce/code_based_key_gen.cpp botan3-3.12.0+dfsg/src/lib/pubkey/mce/code_based_key_gen.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/mce/code_based_key_gen.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/mce/code_based_key_gen.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -54,10 +54,7 @@ std::vector m_elem; }; -binary_matrix::binary_matrix(size_t rown, size_t coln) { - m_coln = coln; - m_rown = rown; - m_rwdcnt = 1 + ((m_coln - 1) / 32); +binary_matrix::binary_matrix(size_t rown, size_t coln) : m_rown(rown), m_coln(coln), m_rwdcnt(1 + ((m_coln - 1) / 32)) { m_elem = std::vector(m_rown * m_rwdcnt); } @@ -81,7 +78,7 @@ bool found_row = false; for(size_t j = i; !found_row && j != m_rown; j++) { - if(coef(j, max)) { + if(coef(j, max) > 0) { if(i != j) //not needed as ith row is 0 and jth row is 1. { row_xor(i, j); //xor to the row.(swap)? @@ -95,7 +92,7 @@ if(!found_row) { perm[m_coln - m_rown - 1 - failcnt] = static_cast(max); failcnt++; - if(!max) { + if(max == 0) { perm.clear(); } i--; @@ -103,14 +100,14 @@ perm[i + m_coln - m_rown] = max; for(size_t j = i + 1; j < m_rown; j++) //fill the column downwards with 0's { - if(coef(j, max)) { + if(coef(j, max) > 0) { row_xor(j, i); //check the arg. order. } } //fill the column with 0's upwards too. for(size_t j = i; j != 0; --j) { - if(coef(j - 1, max)) { + if(coef(j - 1, max) > 0) { row_xor(j - 1, i); } } @@ -121,7 +118,7 @@ void randomize_support(std::vector& L, RandomNumberGenerator& rng) { for(size_t i = 0; i != L.size(); ++i) { - gf2m rnd = random_gf2m(rng); + const gf2m rnd = random_gf2m(rng); // no rejection sampling, but for useful code-based parameters with n <= 13 this seem tolerable std::swap(L[i], L[rnd % L.size()]); @@ -146,7 +143,7 @@ gf2m y = x; for(size_t j = 0; j < t; j++) { for(size_t k = 0; k < sp_field.get_extension_degree(); k++) { - if(y & (1 << k)) { + if((y & (1 << k)) != 0) { //the co-eff. are set in 2^0,...,2^11 ; 2^0,...,2^11 format along the rows/cols? H.set_coef_to_one(j * sp_field.get_extension_degree() + k, i); } @@ -163,7 +160,7 @@ auto result = std::make_unique(code_length - r, r); for(size_t i = 0; i < result->rows(); ++i) { for(size_t j = 0; j < result->columns(); ++j) { - if(H.coef(j, perm[i])) { + if(H.coef(j, perm[i]) > 0) { result->toggle_coeff(i, j); } } @@ -202,6 +199,7 @@ bool success = false; std::unique_ptr R; + // NOLINTNEXTLINE(*-avoid-do-while) do { // create a random irreducible polynomial g = polyn_gf2m(t, rng, sp_field); @@ -212,7 +210,7 @@ } catch(const Invalid_State&) {} } while(!success); - std::vector sqrtmod = polyn_gf2m::sqrt_mod_init(g); + const std::vector sqrtmod = polyn_gf2m::sqrt_mod_init(g); std::vector F = syndrome_init(g, L, static_cast(code_length)); // Each F[i] is the (precomputed) syndrome of the error vector with diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/mce/gf2m_rootfind_dcmp.cpp botan3-3.12.0+dfsg/src/lib/pubkey/mce/gf2m_rootfind_dcmp.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/mce/gf2m_rootfind_dcmp.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/mce/gf2m_rootfind_dcmp.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -10,7 +10,6 @@ #include #include -#include #include namespace Botan { @@ -19,7 +18,7 @@ void patch_root_array(gf2m res_root_arr[], size_t res_root_arr_len, size_t root_pos) { volatile gf2m patch_elem = 0x01; - volatile gf2m cond_mask = (root_pos == res_root_arr_len); + volatile gf2m cond_mask = static_cast(root_pos == res_root_arr_len); cond_mask = expand_mask_16bit(cond_mask); cond_mask = ~cond_mask; /* now cond = 1 if not enough roots */ patch_elem = patch_elem & cond_mask; @@ -55,11 +54,10 @@ * calculates ceil((t-4)/5) = outer_summands - 1 */ uint32_t brootf_decomp_calc_sum_limit(uint32_t t) { - uint32_t result; if(t < 4) { return 0; } - result = t - 4; + uint32_t result = t - 4; result += 4; result /= 5; return result; @@ -67,16 +65,14 @@ gf2m_decomp_rootfind_state::gf2m_decomp_rootfind_state(const polyn_gf2m& polyn, size_t code_length) : m_code_length(code_length), m_j(0), m_j_gray(0) { - gf2m coeff_3; - gf2m coeff_head; - std::shared_ptr sp_field = polyn.get_sp_field(); - int deg_sigma = polyn.get_degree(); + const std::shared_ptr sp_field = polyn.get_sp_field(); + const int deg_sigma = polyn.get_degree(); if(deg_sigma <= 3) { throw Internal_Error("Unexpected degree in gf2m_decomp_rootfind_state"); } - coeff_3 = polyn.get_coef(3); - coeff_head = polyn.get_coef(deg_sigma); /* dummy value for SCA CM */ + const gf2m coeff_3 = polyn.get_coef(3); + const gf2m coeff_head = polyn.get_coef(deg_sigma); /* dummy value for SCA CM */ if(coeff_3 != 0) { this->m_sigma_3_l = sp_field->gf_l_from_n(coeff_3); this->m_sigma_3_neq_0_mask = 0xFFFF; @@ -92,11 +88,10 @@ } void gf2m_decomp_rootfind_state::calc_Ai_zero(const polyn_gf2m& sigma) { - uint32_t i; /* * this function assumes this the first gray code element is zero */ - for(i = 0; i < this->m_outer_summands; i++) { + for(uint32_t i = 0; i < this->m_outer_summands; i++) { this->m_Aij[i] = sigma.get_coef(5 * i); } this->m_j = 0; @@ -109,32 +104,29 @@ * first thing, we declare Aij Aij_minusone and increase j. * Case j=0 upon function entry also included, then Aij contains A_{i,j=0}. */ - uint32_t i; - gf2m diff, new_j_gray; - uint32_t Lik_pos_base; + uint32_t Lik_pos_base = 0; this->m_j++; - new_j_gray = lex_to_gray(this->m_j); + const gf2m new_j_gray = lex_to_gray(this->m_j); - if(this->m_j & 1) /* half of the times */ - { + if((this->m_j & 1) != 0) { + /* half of the times */ Lik_pos_base = 0; - } else if(this->m_j & 2) /* one quarter of the times */ - { + } else if((this->m_j & 2) != 0) { + /* one quarter of the times */ Lik_pos_base = this->m_outer_summands; - } else if(this->m_j & 4) /* one eighth of the times */ - { + } else if((this->m_j & 4) != 0) { + /* one eighth of the times */ Lik_pos_base = this->m_outer_summands * 2; - } else if(this->m_j & 8) /* one sixteenth of the times */ - { + } else if((this->m_j & 8) != 0) { + /* one sixteenth of the times */ Lik_pos_base = this->m_outer_summands * 3; - } else if(this->m_j & 16) /* ... */ - { + } else if((this->m_j & 16) != 0) { Lik_pos_base = this->m_outer_summands * 4; } else { gf2m delta_offs = 5; - diff = this->m_j_gray ^ new_j_gray; + const gf2m diff = this->m_j_gray ^ new_j_gray; while(((static_cast(1) << delta_offs) & diff) == 0) { delta_offs++; } @@ -142,38 +134,34 @@ } this->m_j_gray = new_j_gray; - i = 0; - for(; i < this->m_outer_summands; i++) { + for(uint32_t i = 0; i < this->m_outer_summands; i++) { this->m_Aij[i] ^= this->m_Lik[Lik_pos_base + i]; } } void gf2m_decomp_rootfind_state::calc_LiK(const polyn_gf2m& sigma) { - std::shared_ptr sp_field = sigma.get_sp_field(); - uint32_t i, k, d; - d = sigma.get_degree(); - for(k = 0; k < sp_field->get_extension_degree(); k++) { - uint32_t Lik_pos_base = k * this->m_outer_summands; + const std::shared_ptr sp_field = sigma.get_sp_field(); + const uint32_t d = sigma.get_degree(); + for(uint32_t k = 0; k < sp_field->get_extension_degree(); k++) { + const uint32_t Lik_pos_base = k * this->m_outer_summands; gf2m alpha_l_k_tt2_ttj[4]; alpha_l_k_tt2_ttj[0] = sp_field->gf_l_from_n(static_cast(1) << k); alpha_l_k_tt2_ttj[1] = sp_field->gf_mul_rrr(alpha_l_k_tt2_ttj[0], alpha_l_k_tt2_ttj[0]); alpha_l_k_tt2_ttj[2] = sp_field->gf_mul_rrr(alpha_l_k_tt2_ttj[1], alpha_l_k_tt2_ttj[1]); alpha_l_k_tt2_ttj[3] = sp_field->gf_mul_rrr(alpha_l_k_tt2_ttj[2], alpha_l_k_tt2_ttj[2]); - for(i = 0; i < this->m_outer_summands; i++) { - uint32_t j; - uint32_t five_i = 5 * i; - uint32_t Lik_pos = Lik_pos_base + i; + for(uint32_t i = 0; i < this->m_outer_summands; i++) { + const uint32_t five_i = 5 * i; + const uint32_t Lik_pos = Lik_pos_base + i; this->m_Lik[Lik_pos] = 0; - for(j = 0; j <= 3; j++) { - gf2m f, x; - uint32_t f_ind = five_i + (static_cast(1) << j); + for(size_t j = 0; j <= 3; j++) { + const uint32_t f_ind = five_i + (static_cast(1) << j); if(f_ind > d) { break; } - f = sigma.get_coef(f_ind); + const gf2m f = sigma.get_coef(f_ind); - x = sp_field->gf_mul_zrz(alpha_l_k_tt2_ttj[j], f); + const gf2m x = sp_field->gf_mul_zrz(alpha_l_k_tt2_ttj[j], f); this->m_Lik[Lik_pos] ^= x; } } @@ -183,11 +171,10 @@ gf2m gf2m_decomp_rootfind_state::calc_Fxj_j_neq_0(const polyn_gf2m& sigma, gf2m j_gray) { //needs the A_{ij} to compute F(x)_j gf2m sum = 0; - uint32_t i; - std::shared_ptr sp_field = sigma.get_sp_field(); + const std::shared_ptr sp_field = sigma.get_sp_field(); const gf2m jl_gray = sp_field->gf_l_from_n(j_gray); gf2m xl_j_tt_5 = sp_field->gf_square_rr(jl_gray); - gf2m xl_gray_tt_3 = sp_field->gf_mul_rrr(xl_j_tt_5, jl_gray); + const gf2m xl_gray_tt_3 = sp_field->gf_mul_rrr(xl_j_tt_5, jl_gray); xl_j_tt_5 = sp_field->gf_mul_rrr(xl_j_tt_5, xl_gray_tt_3); sum = sp_field->gf_mul_nrr(xl_gray_tt_3, this->m_sigma_3_l); @@ -200,18 +187,16 @@ /* treat i = 1 special also */ if(this->m_outer_summands > 1) { - gf2m x; - x = sp_field->gf_mul_zrz(xl_j_tt_5, this->m_Aij[1]); /* x_j^{5i} A_i^j */ + const gf2m x = sp_field->gf_mul_zrz(xl_j_tt_5, this->m_Aij[1]); /* x_j^{5i} A_i^j */ sum ^= x; } gf2m xl_j_tt_5i = xl_j_tt_5; - for(i = 2; i < this->m_outer_summands; i++) { - gf2m x; + for(uint32_t i = 2; i < this->m_outer_summands; i++) { xl_j_tt_5i = sp_field->gf_mul_rrr(xl_j_tt_5i, xl_j_tt_5); // now x_j_tt_5i lives up to its name - x = sp_field->gf_mul_zrz(xl_j_tt_5i, this->m_Aij[i]); /* x_j^{5i} A_i^(j) */ + const gf2m x = sp_field->gf_mul_zrz(xl_j_tt_5i, this->m_Aij[i]); /* x_j^{5i} A_i^(j) */ sum ^= x; } return sum; @@ -226,7 +211,7 @@ this->calc_Ai_zero(sigma); this->calc_LiK(sigma); for(;;) { - gf2m eval_result; + gf2m eval_result = 0; if(this->m_j_gray == 0) { eval_result = sigma.get_coef(0); diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/mce/gf2m_small_m.cpp botan3-3.12.0+dfsg/src/lib/pubkey/mce/gf2m_small_m.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/mce/gf2m_small_m.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/mce/gf2m_small_m.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -11,6 +11,7 @@ #include #include +#include #include namespace Botan { @@ -99,8 +100,7 @@ } gf2m decode_gf2m(const uint8_t* mem) { - gf2m result; - result = mem[0] << 8; + gf2m result = mem[0] << 8; result |= mem[1]; return result; } @@ -114,8 +114,8 @@ gf2m GF2m_Field::gf_div(gf2m x, gf2m y) const { const int32_t sub_res = static_cast(gf_log(x) - static_cast(gf_log(y))); const gf2m modq_res = _gf_modq_1(sub_res); - const int32_t div_res = static_cast(x) ? static_cast(gf_exp(modq_res)) : 0; - return static_cast(div_res); + const gf2m div = gf_exp(modq_res); + return (~CT::Mask::is_zero(x)).if_set_return(div); } } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/mce/gf2m_small_m.h botan3-3.12.0+dfsg/src/lib/pubkey/mce/gf2m_small_m.h --- botan3-3.7.1+dfsg/src/lib/pubkey/mce/gf2m_small_m.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/mce/gf2m_small_m.h 2026-05-07 01:38:28.000000000 +0000 @@ -26,13 +26,13 @@ public: explicit GF2m_Field(size_t extdeg); - gf2m gf_mul(gf2m x, gf2m y) const { return ((x) ? gf_mul_fast(x, y) : 0); } + gf2m gf_mul(gf2m x, gf2m y) const { return ((x != 0) ? gf_mul_fast(x, y) : 0); } - gf2m gf_square(gf2m x) const { return ((x) ? gf_exp(_gf_modq_1(gf_log(x) << 1)) : 0); } + gf2m gf_square(gf2m x) const { return ((x != 0) ? gf_exp(_gf_modq_1(gf_log(x) << 1)) : 0); } gf2m square_rr(gf2m x) const { return _gf_modq_1(x << 1); } - gf2m gf_mul_fast(gf2m x, gf2m y) const { return ((y) ? gf_exp(_gf_modq_1(gf_log(x) + gf_log(y))) : 0); } + gf2m gf_mul_fast(gf2m x, gf2m y) const { return ((y != 0) ? gf_exp(_gf_modq_1(gf_log(x) + gf_log(y))) : 0); } /* naming convention of GF(2^m) field operations: @@ -70,7 +70,9 @@ */ gf2m gf_mul_nnr(gf2m y, gf2m a) const { return gf_mul_nrn(a, y); } - gf2m gf_sqrt(gf2m x) const { return ((x) ? gf_exp(_gf_modq_1(gf_log(x) << (get_extension_degree() - 1))) : 0); } + gf2m gf_sqrt(gf2m x) const { + return ((x != 0) ? gf_exp(_gf_modq_1(gf_log(x) << (get_extension_degree() - 1))) : 0); + } gf2m gf_div_rnn(gf2m x, gf2m y) const { return _gf_modq_1(gf_log(x) - gf_log(y)); } @@ -78,7 +80,7 @@ gf2m gf_div_nrr(gf2m a, gf2m b) const { return gf_exp(_gf_modq_1(a - b)); } - gf2m gf_div_zzr(gf2m x, gf2m b) const { return ((x) ? gf_exp(_gf_modq_1(gf_log(x) - b)) : 0); } + gf2m gf_div_zzr(gf2m x, gf2m b) const { return ((x != 0) ? gf_exp(_gf_modq_1(gf_log(x) - b)) : 0); } gf2m gf_inv(gf2m x) const { return gf_exp(gf_ord() - gf_log(x)); } diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/mce/goppa_code.cpp botan3-3.12.0+dfsg/src/lib/pubkey/mce/goppa_code.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/mce/goppa_code.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/mce/goppa_code.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -25,7 +25,7 @@ uint32_t output_vec[], size_t output_vec_len) { for(size_t j = 0; j < numo_rows; j++) { - if((input_vec[j / 8] >> (j % 8)) & 1) { + if(((input_vec[j / 8] >> (j % 8)) & 1) != 0) { for(size_t i = 0; i < output_vec_len; i++) { output_vec[i] ^= matrix[j * (words_per_row) + i]; } @@ -36,21 +36,20 @@ /** * returns the error vector to the syndrome */ -secure_vector goppa_decode(const polyn_gf2m& syndrom_polyn, +secure_vector goppa_decode(const polyn_gf2m& syndrome_polyn, const polyn_gf2m& g, const std::vector& sqrtmod, const std::vector& Linv) { const size_t code_length = Linv.size(); - gf2m a; - uint32_t t = g.get_degree(); + const uint32_t t = g.get_degree(); - std::shared_ptr sp_field = g.get_sp_field(); + const std::shared_ptr sp_field = g.get_sp_field(); - std::pair h_aux = polyn_gf2m::eea_with_coefficients(syndrom_polyn, g, 1); + std::pair h_aux = polyn_gf2m::eea_with_coefficients(syndrome_polyn, g, 1); polyn_gf2m& h = h_aux.first; - polyn_gf2m& aux = h_aux.second; - a = sp_field->gf_inv(aux.get_coef(0)); - gf2m log_a = sp_field->gf_log(a); + const polyn_gf2m& aux = h_aux.second; + gf2m a = sp_field->gf_inv(aux.get_coef(0)); + const gf2m log_a = sp_field->gf_log(a); for(int i = 0; i <= h.get_degree(); ++i) { h.set_coef(i, sp_field->gf_mul_zrz(log_a, h.get_coef(i))); } @@ -63,7 +62,7 @@ for(uint32_t i = 0; i < t; i++) { a = sp_field->gf_sqrt(h.get_coef(i)); - if(i & 1) { + if((i & 1) != 0) { for(uint32_t j = 0; j < t; j++) { S.add_to_coef(j, sp_field->gf_mul(a, sqrtmod[i / 2].get_coef(j))); } @@ -74,9 +73,9 @@ S.get_degree(); - std::pair v_u = polyn_gf2m::eea_with_coefficients(S, g, t / 2 + 1); - polyn_gf2m& u = v_u.second; - polyn_gf2m& v = v_u.first; + const std::pair v_u = polyn_gf2m::eea_with_coefficients(S, g, t / 2 + 1); + const polyn_gf2m& u = v_u.second; + const polyn_gf2m& v = v_u.first; // sigma = u^2+z*v^2 polyn_gf2m sigma(t, g.get_sp_field()); @@ -94,14 +93,13 @@ } secure_vector res = find_roots_gf2m_decomp(sigma, code_length); - size_t d = res.size(); + const size_t d = res.size(); secure_vector result(d); for(uint32_t i = 0; i < d; ++i) { - gf2m current = res[i]; + const gf2m current = res[i]; - gf2m tmp; - tmp = gray_to_lex(current); + const gf2m tmp = gray_to_lex(current); /// XXX double assignment, possible bug? if(tmp >= code_length) /* invalid root */ { @@ -191,7 +189,7 @@ copy_mem(cleartext.data(), ciphertext, cleartext_len); for(size_t i = 0; i < nb_err; i++) { - gf2m current = error_pos[i]; + const gf2m current = error_pos[i]; if(current >= cleartext_len * 8) { // an invalid position, this shouldn't happen @@ -200,7 +198,7 @@ cleartext[current / 8] ^= (1 << (current % 8)); } - if(unused_pt_bits) { + if(unused_pt_bits > 0) { cleartext[cleartext_len - 1] &= unused_pt_bits_mask; } diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/mce/mce_workfactor.cpp botan3-3.12.0+dfsg/src/lib/pubkey/mce/mce_workfactor.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/mce/mce_workfactor.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/mce/mce_workfactor.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -92,7 +92,7 @@ double min = cout_total(n, k, t, 0, 0); // correspond a p=1 for(size_t p = 0; p != t / 2; ++p) { - double lwf = best_wf(n, k + 1, t, p); + const double lwf = best_wf(n, k + 1, t, p); if(lwf < 0) { break; } diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/mce/mceliece.cpp botan3-3.12.0+dfsg/src/lib/pubkey/mce/mceliece.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/mce/mceliece.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/mce/mceliece.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -30,10 +30,10 @@ if(final_bits == 0) { const size_t dim_bytes = bit_size_to_byte_size(dimension); - copy_mem(&x[0], a.data(), dim_bytes); + copy_mem(&x[0], a.data(), dim_bytes); // NOLINT(*container-data-pointer) copy_mem(&x[dim_bytes], b.data(), bit_size_to_byte_size(codimension)); } else { - copy_mem(&x[0], a.data(), (dimension / 8)); + copy_mem(&x[0], a.data(), (dimension / 8)); // NOLINT(*container-data-pointer) size_t l = dimension / 8; x[l] = static_cast(a[l] & ((1 << final_bits) - 1)); @@ -61,7 +61,7 @@ for(size_t i = 0; i < dimension / 8; ++i) { for(size_t j = 0; j < 8; ++j) { - if(cleartext[i] & (1 << j)) { + if((cleartext[i] & (1 << j)) != 0) { xor_buf(cR.data(), pt, cR.size()); } pt += cR.size(); @@ -69,7 +69,7 @@ } for(size_t i = 0; i < dimension % 8; ++i) { - if(cleartext[dimension / 8] & (1 << i)) { + if((cleartext[dimension / 8] & (1 << i)) != 0) { xor_buf(cR.data(), pt, cR.size()); } pt += cR.size(); @@ -86,14 +86,14 @@ size_t bits_set = 0; while(bits_set < error_weight) { - gf2m x = random_code_element(static_cast(code_length), rng); + const gf2m x = random_code_element(static_cast(code_length), rng); const size_t byte_pos = x / 8; const size_t bit_pos = x % 8; const uint8_t mask = (1 << bit_pos); - if(result[byte_pos] & mask) { + if((result[byte_pos] & mask) != 0) { continue; // already set this bit } diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/mce/mceliece.h botan3-3.12.0+dfsg/src/lib/pubkey/mce/mceliece.h --- botan3-3.7.1+dfsg/src/lib/pubkey/mce/mceliece.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/mce/mceliece.h 2026-05-07 01:38:28.000000000 +0000 @@ -22,7 +22,7 @@ class polyn_gf2m; -class BOTAN_PUBLIC_API(2, 0) McEliece_PublicKey : public virtual Public_Key { +class BOTAN_PUBLIC_API(2, 0) McEliece_PublicKey : public virtual Public_Key /* NOLINT(*-special-member-functions) */ { public: explicit McEliece_PublicKey(std::span key_bits); @@ -45,7 +45,7 @@ std::vector raw_public_key_bits() const override; std::vector public_key_bits() const override; - bool check_key(RandomNumberGenerator&, bool) const override { return true; } + bool check_key(RandomNumberGenerator& /*rng*/, bool /*strong*/) const override { return true; } size_t get_t() const { return m_t; } @@ -71,9 +71,9 @@ protected: McEliece_PublicKey() : m_t(0), m_code_length(0) {} - std::vector m_public_matrix; - size_t m_t; - size_t m_code_length; + std::vector m_public_matrix; // NOLINT(*non-private-member-variable*) + size_t m_t; // NOLINT(*non-private-member-variable*) + size_t m_code_length; // NOLINT(*non-private-member-variable*) }; BOTAN_DIAGNOSTIC_PUSH diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/mce/mceliece_key.cpp botan3-3.12.0+dfsg/src/lib/pubkey/mce/mceliece_key.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/mce/mceliece_key.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/mce/mceliece_key.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -16,12 +16,12 @@ #include #include #include +#include #include #include #include #include #include -#include namespace Botan { @@ -44,8 +44,9 @@ m_codimension(static_cast(ceil_log2(inverse_support.size())) * goppa_polyn.get_degree()), m_dimension(inverse_support.size() - m_codimension) {} +// NOLINTNEXTLINE(*-member-init) McEliece_PrivateKey::McEliece_PrivateKey(RandomNumberGenerator& rng, size_t code_length, size_t t) { - uint32_t ext_deg = ceil_log2(code_length); + const uint32_t ext_deg = ceil_log2(code_length); *this = generate_mceliece_key(rng, ext_deg, code_length, t); } @@ -54,7 +55,7 @@ } size_t McEliece_PublicKey::get_message_word_bit_length() const { - size_t codimension = ceil_log2(m_code_length) * m_t; + const size_t codimension = ceil_log2(m_code_length) * m_t; return m_code_length - codimension; } @@ -65,7 +66,7 @@ rng.randomize(plaintext.data(), plaintext.size()); // unset unused bits in the last plaintext byte - if(uint32_t used = bits % 8) { + if(const uint32_t used = bits % 8) { const uint8_t mask = (1 << used) - 1; plaintext[plaintext.size() - 1] &= mask; } @@ -103,16 +104,48 @@ } McEliece_PublicKey::McEliece_PublicKey(std::span key_bits) { - BER_Decoder dec(key_bits); - size_t n; - size_t t; + BER_Decoder dec(key_bits, BER_Decoder::Limits::DER()); + size_t n = 0; + size_t t = 0; dec.start_sequence() .start_sequence() .decode(n) .decode(t) .end_cons() .decode(m_public_matrix, ASN1_Type::OctetString) - .end_cons(); + .end_cons() + .verify_end(); + + if(n == 0 || t == 0) { + throw Decoding_Error("Invalid McEliece parameters"); + } + + // GF(2^m) field requires extension degree in [2, 16] + const size_t ext_deg = ceil_log2(n); + if(ext_deg < 2 || ext_deg > 16) { + throw Decoding_Error("McEliece code length out of supported range"); + } + + // Since ext_deg >= 2, t >= n already implies ext_deg * t > n + if(t >= n) { + throw Decoding_Error("McEliece parameters are inconsistent"); + } + + const size_t codimension = ext_deg * t; + + // codimension must be strictly less than n, otherwise the code has no message bits + if(codimension >= n) { + throw Decoding_Error("McEliece parameters are inconsistent"); + } + + const size_t dimension = n - codimension; + + // public matrix is a dimension x codimension binary matrix stored as uint32_t rows + const size_t expected_pubmat_size = dimension * bit_size_to_32bit_size(codimension) * sizeof(uint32_t); + if(m_public_matrix.size() != expected_pubmat_size) { + throw Decoding_Error("McEliece public matrix size does not match parameters"); + } + m_t = t; m_code_length = n; } @@ -127,19 +160,19 @@ .encode(m_public_matrix, ASN1_Type::OctetString) .encode(m_g[0].encode(), ASN1_Type::OctetString); // g as octet string enc.start_sequence(); - for(size_t i = 0; i < m_sqrtmod.size(); i++) { - enc.encode(m_sqrtmod[i].encode(), ASN1_Type::OctetString); + for(const auto& x : m_sqrtmod) { + enc.encode(x.encode(), ASN1_Type::OctetString); } enc.end_cons(); secure_vector enc_support; - for(uint16_t Linv : m_Linv) { + for(const uint16_t Linv : m_Linv) { enc_support.push_back(get_byte<0>(Linv)); enc_support.push_back(get_byte<1>(Linv)); } enc.encode(enc_support, ASN1_Type::OctetString); secure_vector enc_H; - for(uint32_t coef : m_coeffs) { + for(const uint32_t coef : m_coeffs) { enc_H.push_back(get_byte<0>(coef)); enc_H.push_back(get_byte<1>(coef)); enc_H.push_back(get_byte<2>(coef)); @@ -169,26 +202,45 @@ } McEliece_PrivateKey::McEliece_PrivateKey(std::span key_bits) { - size_t n, t; + size_t n = 0; + size_t t = 0; secure_vector enc_g; - BER_Decoder dec_base(key_bits); - BER_Decoder dec = dec_base.start_sequence() - .start_sequence() - .decode(n) - .decode(t) - .end_cons() - .decode(m_public_matrix, ASN1_Type::OctetString) - .decode(enc_g, ASN1_Type::OctetString); + BER_Decoder dec_base(key_bits, BER_Decoder::Limits::DER()); + BER_Decoder dec = dec_base.start_sequence(); + dec.start_sequence().decode(n).decode(t).end_cons(); + dec.decode(m_public_matrix, ASN1_Type::OctetString).decode(enc_g, ASN1_Type::OctetString); if(t == 0 || n == 0) { throw Decoding_Error("invalid McEliece parameters"); } - uint32_t ext_deg = ceil_log2(n); + const uint32_t ext_deg = ceil_log2(n); + + if(ext_deg < 2 || ext_deg > 16) { + throw Decoding_Error("McEliece code length out of supported range"); + } + + // Since ext_deg >= 2, t >= n already implies ext_deg * t > n + if(t >= n) { + throw Decoding_Error("McEliece parameters are inconsistent"); + } + + const size_t codimension = ext_deg * t; + + if(codimension >= n) { + throw Decoding_Error("McEliece parameters are inconsistent"); + } + + const size_t dimension = n - codimension; + const size_t expected_pubmat_size = dimension * bit_size_to_32bit_size(codimension) * sizeof(uint32_t); + if(m_public_matrix.size() != expected_pubmat_size) { + throw Decoding_Error("McEliece public matrix size does not match parameters"); + } + m_code_length = n; m_t = t; - m_codimension = (ext_deg * t); - m_dimension = (n - m_codimension); + m_codimension = codimension; + m_dimension = dimension; auto sp_field = std::make_shared(ext_deg); m_g = {polyn_gf2m(enc_g, sp_field)}; @@ -210,20 +262,21 @@ m_sqrtmod.push_back(polyn_gf2m(sqrt_enc, sp_field)); } secure_vector enc_support; - BER_Decoder dec3 = dec2.end_cons().decode(enc_support, ASN1_Type::OctetString); - if(enc_support.size() % 2) { + dec2.end_cons(); + dec.decode(enc_support, ASN1_Type::OctetString); + if(enc_support.size() % 2 != 0) { throw Decoding_Error("encoded support has odd length"); } if(enc_support.size() / 2 != n) { throw Decoding_Error("encoded support has length different from code length"); } for(uint32_t i = 0; i < n * 2; i += 2) { - gf2m el = (enc_support[i] << 8) | enc_support[i + 1]; + const gf2m el = (enc_support[i] << 8) | enc_support[i + 1]; m_Linv.push_back(el); } secure_vector enc_H; - dec3.decode(enc_H, ASN1_Type::OctetString).end_cons(); - if(enc_H.size() % 4) { + dec.decode(enc_H, ASN1_Type::OctetString).end_cons().verify_end(); + if(enc_H.size() % 4 != 0) { throw Decoding_Error("encoded parity check matrix has length which is not a multiple of four"); } if(enc_H.size() / 4 != bit_size_to_32bit_size(m_codimension) * m_code_length) { @@ -231,7 +284,7 @@ } for(uint32_t i = 0; i < enc_H.size(); i += 4) { - uint32_t coeff = (enc_H[i] << 24) | (enc_H[i + 1] << 16) | (enc_H[i + 2] << 8) | enc_H[i + 3]; + const uint32_t coeff = (enc_H[i] << 24) | (enc_H[i + 1] << 16) | (enc_H[i + 2] << 8) | enc_H[i + 3]; m_coeffs.push_back(coeff); } } @@ -299,7 +352,8 @@ RandomNumberGenerator& rng) override { secure_vector plaintext = m_key.random_plaintext_element(rng); - secure_vector ciphertext, error_mask; + secure_vector ciphertext; + secure_vector error_mask; mceliece_encrypt(ciphertext, error_mask, plaintext, m_key, rng); // TODO: Perhaps avoid the copies below @@ -330,7 +384,8 @@ size_t encapsulated_key_length() const override { return (m_key.get_code_length() + 7) / 8; } void raw_kem_decrypt(std::span out_shared_key, std::span encapsulated_key) override { - secure_vector plaintext, error_mask; + secure_vector plaintext; + secure_vector error_mask; mceliece_decrypt(plaintext, error_mask, encapsulated_key.data(), encapsulated_key.size(), m_key); // TODO: perhaps avoid the copies below diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/mce/polyn_gf2m.cpp botan3-3.12.0+dfsg/src/lib/pubkey/mce/polyn_gf2m.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/mce/polyn_gf2m.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/mce/polyn_gf2m.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -14,16 +14,17 @@ #include #include -#include #include #include namespace Botan { +// NOLINTBEGIN(*-implicit-bool-conversion) + namespace { gf2m generate_gf2m_mask(gf2m a) { - gf2m result = (a != 0); + const gf2m result = (a != 0); return ~(result - 1); } @@ -31,11 +32,10 @@ * number of leading zeros */ unsigned nlz_16bit(uint16_t x) { - unsigned n; if(x == 0) { return 16; } - n = 0; + unsigned n = 0; if(x <= 0x00FF) { n = n + 8; x = x << 8; @@ -53,6 +53,7 @@ } return n; } + } // namespace int polyn_gf2m::calc_degree_secure() const { @@ -84,12 +85,12 @@ const unsigned nlz = nlz_16bit(code_length - 1); const gf2m mask = (1 << (16 - nlz)) - 1; - gf2m result; + gf2m result = random_gf2m(rng) & mask; - do { - result = random_gf2m(rng); - result &= mask; - } while(result >= code_length); // rejection sampling + while(result >= code_length) { + // rejection sampling + result = random_gf2m(rng) & mask; + } return result; } @@ -112,7 +113,7 @@ throw Decoding_Error("illegal length of memory to decode "); } - uint32_t size = (mem_len / sizeof(this->m_coeff[0])); + const uint32_t size = (mem_len / sizeof(this->m_coeff[0])); this->m_coeff = secure_vector(size); this->m_deg = -1; for(uint32_t i = 0; i < size; i++) { @@ -134,20 +135,17 @@ size_t mem_byte_len, const std::shared_ptr& sp_field) : m_sp_field(sp_field) { - uint32_t j, k, l; - gf2m a; - uint32_t polyn_size; - polyn_size = degree + 1; + const uint32_t polyn_size = degree + 1; if(polyn_size * sp_field->get_extension_degree() > 8 * mem_byte_len) { throw Decoding_Error("memory vector for polynomial has wrong size"); } this->m_coeff = secure_vector(degree + 1); - gf2m ext_deg = static_cast(this->m_sp_field->get_extension_degree()); - for(l = 0; l < polyn_size; l++) { - k = (l * ext_deg) / 8; + const gf2m ext_deg = static_cast(this->m_sp_field->get_extension_degree()); + for(uint32_t l = 0; l < polyn_size; l++) { + const uint32_t k = (l * ext_deg) / 8; - j = (l * ext_deg) % 8; - a = mem[k] >> j; + const uint32_t j = (l * ext_deg) % 8; + gf2m a = mem[k] >> j; if(j + ext_deg > 8) { a ^= mem[k + 1] << (8 - j); } @@ -162,7 +160,7 @@ } void polyn_gf2m::set_to_zero() { - clear_mem(&this->m_coeff[0], this->m_coeff.size()); + clear_mem(this->m_coeff.data(), this->m_coeff.size()); this->m_deg = -1; } @@ -178,8 +176,7 @@ namespace { gf2m eval_aux(const gf2m* /*restrict*/ coeff, gf2m a, int d, const std::shared_ptr& sp_field) { - gf2m b; - b = coeff[d--]; + gf2m b = coeff[d--]; for(; d >= 0; --d) { if(b != 0) { b = sp_field->gf_mul(b, a) ^ coeff[d]; @@ -193,16 +190,17 @@ } // namespace gf2m polyn_gf2m::eval(gf2m a) { - return eval_aux(&this->m_coeff[0], a, this->m_deg, this->m_sp_field); + return eval_aux(this->m_coeff.data(), a, this->m_deg, this->m_sp_field); } // p will contain it's remainder modulo g void polyn_gf2m::remainder(polyn_gf2m& p, const polyn_gf2m& g) { - int i, j, d; - std::shared_ptr m_sp_field = g.m_sp_field; - d = p.get_degree() - g.get_degree(); + int i = 0; + int j = 0; + const std::shared_ptr m_sp_field = g.m_sp_field; + int d = p.get_degree() - g.get_degree(); if(d >= 0) { - gf2m la = m_sp_field->gf_inv_rn(g.get_lead_coef()); + const gf2m la = m_sp_field->gf_inv_rn(g.get_lead_coef()); const int p_degree = p.get_degree(); @@ -210,7 +208,7 @@ for(i = p_degree; d >= 0; --i, --d) { if(p[i] != 0) { - gf2m lb = m_sp_field->gf_mul_rrn(la, p[i]); + const gf2m lb = m_sp_field->gf_mul_rrn(la, p[i]); for(j = 0; j < g.get_degree(); ++j) { p[j + d] ^= m_sp_field->gf_mul_zrz(lb, g[j]); } @@ -232,9 +230,9 @@ } const uint32_t d = static_cast(signed_deg); - uint32_t t = g.m_deg; + const uint32_t t = g.m_deg; // create t zero polynomials - uint32_t i; + uint32_t i = 0; for(i = 0; i < t; ++i) { sq.push_back(polyn_gf2m(t + 1, g.get_sp_field())); } @@ -244,8 +242,8 @@ } for(; i < d; ++i) { - clear_mem(&sq[i].m_coeff[0], 2); - copy_mem(&sq[i].m_coeff[0] + 2, &sq[i - 1].m_coeff[0], d); + clear_mem(sq[i].m_coeff.data(), 2); + copy_mem(sq[i].m_coeff.data() + 2, sq[i - 1].m_coeff.data(), d); sq[i].set_degree(sq[i - 1].get_degree() + 2); polyn_gf2m::remainder(sq[i], g); } @@ -256,9 +254,8 @@ Modulo g of the base canonical polynomials of degree < d, where d is the degree of G. The table sq[] will be calculated by polyn_gf2m_sqmod_init*/ polyn_gf2m polyn_gf2m::sqmod(const std::vector& sq, int d) { - int i, j; - gf2m la; - std::shared_ptr sp_field = this->m_sp_field; + int i = 0; + const std::shared_ptr sp_field = this->m_sp_field; polyn_gf2m result(d - 1, sp_field); // terms of low degree @@ -271,8 +268,8 @@ gf2m lpi = (*this)[i]; if(lpi != 0) { lpi = sp_field->gf_log(lpi); - la = sp_field->gf_mul_rrr(lpi, lpi); - for(j = 0; j < d; ++j) { + const gf2m la = sp_field->gf_mul_rrr(lpi, lpi); + for(int j = 0; j < d; ++j) { result[j] ^= sp_field->gf_mul_zrz(la, sq[i][j]); } } @@ -312,7 +309,7 @@ const size_t ext_deg = g.m_sp_field->get_extension_degree(); const int d = g.get_degree(); - std::vector u = polyn_gf2m::sqmod_init(g); + const std::vector u = polyn_gf2m::sqmod_init(g); polyn_gf2m p(d - 1, g.m_sp_field); @@ -343,15 +340,13 @@ } void polyn_gf2m::patchup_deg_secure(uint32_t trgt_deg, gf2m patch_elem) { - uint32_t i; if(this->m_coeff.size() < trgt_deg) { return; } - for(i = 0; i < this->m_coeff.size(); i++) { - uint32_t equal, equal_mask; + for(uint32_t i = 0; i < this->m_coeff.size(); i++) { this->m_coeff[i] |= patch_elem; - equal = (i == trgt_deg); - equal_mask = expand_mask_16bit(equal); + const uint32_t equal = (i == trgt_deg); + const uint32_t equal_mask = expand_mask_16bit(equal); patch_elem &= ~equal_mask; } this->calc_degree_secure(); @@ -362,14 +357,12 @@ std::pair polyn_gf2m::eea_with_coefficients(const polyn_gf2m& p, const polyn_gf2m& g, int break_deg) { - std::shared_ptr m_sp_field = g.m_sp_field; - int i, j, dr, du, delta; - gf2m a; + const std::shared_ptr m_sp_field = g.m_sp_field; polyn_gf2m aux; // initialisation of the local variables // r0 <- g, r1 <- p, u0 <- 0, u1 <- 1 - dr = g.get_degree(); + int dr = g.get_degree(); BOTAN_ASSERT(dr > 3, "Valid polynomial"); @@ -391,15 +384,17 @@ // and m_deg(u1) = m_deg(g) - m_deg(r0) // It stops when m_deg (r1) = t) // And therefore m_deg (u1) = m_deg (g) - m_deg (r0) = break_deg) { for(j = delta; j >= 0; --j) { - a = m_sp_field->gf_div(r0[dr + j], r1[dr]); + const gf2m a = m_sp_field->gf_div(r0[dr + j], r1[dr]); if(a != 0) { - gf2m la = m_sp_field->gf_log(a); + const gf2m la = m_sp_field->gf_log(a); // u0(z) <- u0(z) + a * u1(z) * z^j for(i = 0; i <= du; ++i) { u0[i + j] ^= m_sp_field->gf_mul_zrz(la, u1[i]); @@ -418,8 +413,9 @@ * */ volatile gf2m fake_elem = 0x01; - volatile gf2m cond1, cond2; - int trgt_deg = r1.get_degree() - 1; + volatile gf2m cond1 = 0; + volatile gf2m cond2 = 0; + const int trgt_deg = r1.get_degree() - 1; r0.calc_degree_secure(); u0.calc_degree_secure(); if(!(g.get_degree() % 2)) { @@ -432,7 +428,7 @@ cond1 = cond1 & cond2; } /* expand cond1 to a full mask */ - gf2m mask = generate_gf2m_mask(cond1); + const gf2m mask = generate_gf2m_mask(cond1); fake_elem = fake_elem & mask; r0.patchup_deg_secure(trgt_deg, fake_elem); } @@ -465,18 +461,18 @@ int cond_r = r0.get_degree() == 0; /** * Now come the conditions for all odd coefficients of this sigma - * candiate. If they are all fulfilled, then we know that we have a low + * candidate. If they are all fulfilled, then we know that we have a low * weight error vector, since the key-equation solving EEA is skipped if - * the degree of tau^2 is low (=m_deg(u0)) and all its odd cofficients are + * the degree of tau^2 is low (=m_deg(u0)) and all its odd coefficients are * zero (they would cause "full-length" contributions from the square * root computation). */ // Condition for the coefficient to Y to be cancelled out by the // addition of Y before the square root computation: - int cond_u1 = m_sp_field->gf_mul(u0.m_coeff[1], m_sp_field->gf_inv(r0.m_coeff[0])) == 1; + const int cond_u1 = m_sp_field->gf_mul(u0.m_coeff[1], m_sp_field->gf_inv(r0.m_coeff[0])) == 1; // Condition sigma_3 = 0: - int cond_u3 = u0.m_coeff[3] == 0; + const int cond_u3 = u0.m_coeff[3] == 0; // combine the conditions: cond_r &= (cond_u1 & cond_u3); // mask generation: @@ -486,10 +482,10 @@ } else if(u0.get_degree() == 6) { uint32_t mask = 0; int cond_r = r0.get_degree() == 0; - int cond_u1 = m_sp_field->gf_mul(u0.m_coeff[1], m_sp_field->gf_inv(r0.m_coeff[0])) == 1; - int cond_u3 = u0.m_coeff[3] == 0; + const int cond_u1 = m_sp_field->gf_mul(u0.m_coeff[1], m_sp_field->gf_inv(r0.m_coeff[0])) == 1; + const int cond_u3 = u0.m_coeff[3] == 0; - int cond_u5 = u0.m_coeff[5] == 0; + const int cond_u5 = u0.m_coeff[5] == 0; cond_r &= (cond_u1 & cond_u3 & cond_u5); mask = expand_mask_16bit(cond_r); @@ -498,12 +494,12 @@ } else if(u0.get_degree() == 8) { uint32_t mask = 0; int cond_r = r0.get_degree() == 0; - int cond_u1 = m_sp_field->gf_mul(u0[1], m_sp_field->gf_inv(r0[0])) == 1; - int cond_u3 = u0.m_coeff[3] == 0; + const int cond_u1 = m_sp_field->gf_mul(u0[1], m_sp_field->gf_inv(r0[0])) == 1; + const int cond_u3 = u0.m_coeff[3] == 0; - int cond_u5 = u0.m_coeff[5] == 0; + const int cond_u5 = u0.m_coeff[5] == 0; - int cond_u7 = u0.m_coeff[7] == 0; + const int cond_u7 = u0.m_coeff[7] == 0; cond_r &= (cond_u1 & cond_u3 & cond_u5 & cond_u7); mask = expand_mask_16bit(cond_r); @@ -555,10 +551,10 @@ if(g.get_degree() <= 1) { throw Invalid_Argument("shiftmod cannot be called on polynomials of degree 1 or less"); } - std::shared_ptr field = g.m_sp_field; + const std::shared_ptr field = g.m_sp_field; - int t = g.get_degree(); - gf2m a = field->gf_div(this->m_coeff[t - 1], g.m_coeff[t]); + const int t = g.get_degree(); + const gf2m a = field->gf_div(this->m_coeff[t - 1], g.m_coeff[t]); for(int i = t - 1; i > 0; --i) { this->m_coeff[i] = this->m_coeff[i - 1] ^ this->m_sp_field->gf_mul(a, g.m_coeff[i]); } @@ -566,14 +562,15 @@ } std::vector polyn_gf2m::sqrt_mod_init(const polyn_gf2m& g) { - uint32_t i, t; - uint32_t nb_polyn_sqrt_mat; - std::shared_ptr m_sp_field = g.m_sp_field; + uint32_t i = 0; + uint32_t t = 0; + uint32_t nb_polyn_sqrt_mat = 0; + const std::shared_ptr m_sp_field = g.m_sp_field; std::vector result; t = g.get_degree(); nb_polyn_sqrt_mat = t / 2; - std::vector sq_aux = polyn_gf2m::sqmod_init(g); + const std::vector sq_aux = polyn_gf2m::sqmod_init(g); polyn_gf2m p(t - 1, g.get_sp_field()); p.set_degree(1); @@ -584,7 +581,7 @@ // q(z) <- p(z)^2 mod g(z) polyn_gf2m q = p.sqmod(sq_aux, t); // q(z) <-> p(z) - polyn_gf2m aux = q; + const polyn_gf2m aux = q; q = p; p = aux; } @@ -606,10 +603,12 @@ } std::vector syndrome_init(const polyn_gf2m& generator, const std::vector& support, int n) { - int i, j, t; - gf2m a; + int i = 0; + int j = 0; + int t = 0; + gf2m a = 0; - std::shared_ptr m_sp_field = generator.get_sp_field(); + const std::shared_ptr m_sp_field = generator.get_sp_field(); std::vector result; t = generator.get_degree(); @@ -638,7 +637,7 @@ throw Decoding_Error("encoded polynomial has odd length"); } for(uint32_t i = 0; i < encoded.size(); i += 2) { - gf2m el = (encoded[i] << 8) | encoded[i + 1]; + const gf2m el = (encoded[i] << 8) | encoded[i + 1]; m_coeff.push_back(el); } get_degree(); @@ -653,7 +652,7 @@ return result; } - uint32_t len = m_deg + 1; + const uint32_t len = m_deg + 1; for(unsigned i = 0; i < len; i++) { // "big endian" encoding of the GF(2^m) elements result.push_back(get_byte<0>(m_coeff[i])); @@ -669,10 +668,9 @@ } bool polyn_gf2m::operator==(const polyn_gf2m& other) const { - if(m_deg != other.m_deg || m_coeff != other.m_coeff) { - return false; - } - return true; + return m_deg == other.m_deg && m_coeff == other.m_coeff; } +// NOLINTEND(*-implicit-bool-conversion) + } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/mce/polyn_gf2m.h botan3-3.12.0+dfsg/src/lib/pubkey/mce/polyn_gf2m.h --- botan3-3.7.1+dfsg/src/lib/pubkey/mce/polyn_gf2m.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/mce/polyn_gf2m.h 2026-05-07 01:38:28.000000000 +0000 @@ -13,6 +13,7 @@ #define BOTAN_POLYN_GF2M_H_ #include +#include #include namespace Botan { @@ -34,6 +35,8 @@ polyn_gf2m(const secure_vector& encoded, const std::shared_ptr& sp_field); + ~polyn_gf2m() = default; + polyn_gf2m& operator=(const polyn_gf2m&) = default; /** @@ -129,7 +132,7 @@ static polyn_gf2m gcd_aux(polyn_gf2m& p1, polyn_gf2m& p2); private: - int m_deg; + int m_deg = -1; secure_vector m_coeff; std::shared_ptr m_sp_field; }; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/pbes2/pbes2.cpp botan3-3.12.0+dfsg/src/lib/pubkey/pbes2/pbes2.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/pbes2/pbes2.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/pbes2/pbes2.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -30,10 +30,11 @@ size_t default_key_size) { if(kdf_algo.oid() == OID::from_string("PKCS5.PBKDF2")) { secure_vector salt; - size_t iterations = 0, key_length = 0; + size_t iterations = 0; + size_t key_length = 0; AlgorithmIdentifier prf_algo; - BER_Decoder(kdf_algo.parameters()) + BER_Decoder(kdf_algo.parameters(), BER_Decoder::Limits::DER()) .start_sequence() .decode(salt, ASN1_Type::OctetString) .decode(iterations) @@ -42,7 +43,12 @@ ASN1_Type::Sequence, ASN1_Class::Constructed, AlgorithmIdentifier("HMAC(SHA-1)", AlgorithmIdentifier::USE_NULL_PARAM)) - .end_cons(); + .end_cons() + .verify_end(); + + if(iterations == 0) { + throw Decoding_Error("PBE-PKCS5 v2.0: Iteration count must be positive"); + } if(salt.size() < 8) { throw Decoding_Error("PBE-PKCS5 v2.0: Encoded salt is too small"); @@ -65,18 +71,25 @@ return derived_key; } else if(kdf_algo.oid() == OID::from_string("Scrypt")) { secure_vector salt; - size_t N = 0, r = 0, p = 0; + size_t N = 0; + size_t r = 0; + size_t p = 0; size_t key_length = 0; - AlgorithmIdentifier prf_algo; - BER_Decoder(kdf_algo.parameters()) + const AlgorithmIdentifier prf_algo; + BER_Decoder(kdf_algo.parameters(), BER_Decoder::Limits::DER()) .start_sequence() .decode(salt, ASN1_Type::OctetString) .decode(N) .decode(r) .decode(p) .decode_optional(key_length, ASN1_Type::Integer, ASN1_Class::Universal) - .end_cons(); + .end_cons() + .verify_end(); + + if(N == 0 || r == 0 || p == 0) { + throw Decoding_Error("PBE-PKCS5 v2.0: Invalid Scrypt parameters"); + } if(key_length == 0) { key_length = default_key_size; @@ -110,9 +123,8 @@ std::unique_ptr pwhash; - if(msec_in_iterations_out) { - const std::chrono::milliseconds msec(*msec_in_iterations_out); - pwhash = pwhash_fam->tune(key_length, msec); + if(msec_in_iterations_out != nullptr) { + pwhash = pwhash_fam->tune_params(key_length, *msec_in_iterations_out); } else { pwhash = pwhash_fam->from_iterations(iterations_if_msec_null); } @@ -124,7 +136,7 @@ const size_t r = pwhash->iterations(); const size_t p = pwhash->parallelism(); - if(msec_in_iterations_out) { + if(msec_in_iterations_out != nullptr) { *msec_in_iterations_out = 0; } @@ -151,9 +163,8 @@ std::unique_ptr pwhash; - if(msec_in_iterations_out) { - const std::chrono::milliseconds msec(*msec_in_iterations_out); - pwhash = pwhash_fam->tune(key_length, msec); + if(msec_in_iterations_out != nullptr) { + pwhash = pwhash_fam->tune_params(key_length, *msec_in_iterations_out); } else { pwhash = pwhash_fam->from_iterations(iterations_if_msec_null); } @@ -165,7 +176,7 @@ const size_t iterations = pwhash->iterations(); - if(msec_in_iterations_out) { + if(msec_in_iterations_out != nullptr) { *msec_in_iterations_out = iterations; } @@ -232,7 +243,7 @@ .encode(AlgorithmIdentifier(cipher, encoded_iv)) .end_cons(); - AlgorithmIdentifier id(OID::from_string("PBE-PKCS5v20"), pbes2_params); + const AlgorithmIdentifier id(OID::from_string("PBE-PKCS5v20"), pbes2_params); return std::make_pair(id, unlock(ctext)); } @@ -261,7 +272,7 @@ auto ret = pbes2_encrypt_shared(key_bits, passphrase, &msec_in_iterations_out, 0, cipher, digest, rng); - if(out_iterations_if_nonnull) { + if(out_iterations_if_nonnull != nullptr) { *out_iterations_if_nonnull = msec_in_iterations_out; } @@ -280,9 +291,15 @@ secure_vector pbes2_decrypt(std::span key_bits, std::string_view passphrase, const std::vector& params) { - AlgorithmIdentifier kdf_algo, enc_algo; + AlgorithmIdentifier kdf_algo; + AlgorithmIdentifier enc_algo; - BER_Decoder(params).start_sequence().decode(kdf_algo).decode(enc_algo).end_cons(); + BER_Decoder(params, BER_Decoder::Limits::DER()) + .start_sequence() + .decode(kdf_algo) + .decode(enc_algo) + .end_cons() + .verify_end(); const std::string cipher = enc_algo.oid().human_name_or_empty(); const auto cipher_spec = split_on(cipher, '/'); @@ -291,7 +308,7 @@ } secure_vector iv; - BER_Decoder(enc_algo.parameters()).decode(iv, ASN1_Type::OctetString).verify_end(); + BER_Decoder(enc_algo.parameters(), BER_Decoder::Limits::DER()).decode(iv, ASN1_Type::OctetString).verify_end(); auto dec = Cipher_Mode::create(cipher, Cipher_Dir::Decryption); if(!dec) { diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/pbes2/pbes2.h botan3-3.12.0+dfsg/src/lib/pubkey/pbes2/pbes2.h --- botan3-3.7.1+dfsg/src/lib/pubkey/pbes2/pbes2.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/pbes2/pbes2.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,6 +9,7 @@ #define BOTAN_PBE_PKCS_V20_H_ #include +#include #include #include diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/pem/pem.cpp botan3-3.12.0+dfsg/src/lib/pubkey/pem/pem.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/pem/pem.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/pem/pem.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -69,11 +69,12 @@ size_t position = 0; while(position != PEM_HEADER1.length()) { - uint8_t b; - if(!source.read_byte(b)) { + auto b = source.read_byte(); + + if(!b) { throw Decoding_Error("PEM: No PEM header found"); } - if(static_cast(b) == PEM_HEADER1[position]) { + if(static_cast(*b) == PEM_HEADER1[position]) { ++position; } else if(position >= RANDOM_CHAR_LIMIT) { throw Decoding_Error("PEM: Malformed PEM header"); @@ -83,18 +84,22 @@ } position = 0; while(position != PEM_HEADER2.length()) { - uint8_t b; - if(!source.read_byte(b)) { + auto b = source.read_byte(); + + if(!b) { throw Decoding_Error("PEM: No PEM header found"); } - if(static_cast(b) == PEM_HEADER2[position]) { + if(static_cast(*b) == PEM_HEADER2[position]) { ++position; - } else if(position) { + } else if(position > 0) { throw Decoding_Error("PEM: Malformed PEM header"); } if(position == 0) { - label += static_cast(b); + if(label.size() >= 128) { + throw Decoding_Error("PEM: Label too long"); + } + label += static_cast(*b); } } @@ -103,18 +108,19 @@ const std::string PEM_TRAILER = fmt("-----END {}-----", label); position = 0; while(position != PEM_TRAILER.length()) { - uint8_t b; - if(!source.read_byte(b)) { + auto b = source.read_byte(); + + if(!b) { throw Decoding_Error("PEM: No PEM trailer found"); } - if(static_cast(b) == PEM_TRAILER[position]) { + if(static_cast(*b) == PEM_TRAILER[position]) { ++position; - } else if(position) { + } else if(position > 0) { throw Decoding_Error("PEM: Malformed PEM trailer"); } if(position == 0) { - b64.push_back(b); + b64.push_back(*b); } } diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/pk_algs.cpp botan3-3.12.0+dfsg/src/lib/pubkey/pk_algs.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/pk_algs.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/pk_algs.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,7 +7,9 @@ #include +#include #include +#include #include #include @@ -35,6 +37,10 @@ #include #endif +#if defined(BOTAN_HAS_ECC_GROUP) + #include +#endif + #if defined(BOTAN_HAS_ECDSA) #include #endif @@ -125,7 +131,7 @@ [[maybe_unused]] std::span key_bits) { const std::string oid_str = alg_id.oid().to_formatted_string(); const std::vector alg_info = split_on(oid_str, '/'); - std::string_view alg_name = alg_info[0]; + const std::string_view alg_name = alg_info[0]; #if defined(BOTAN_HAS_RSA) if(alg_name == "RSA") { @@ -284,7 +290,7 @@ [[maybe_unused]] std::span key_bits) { const std::string oid_str = alg_id.oid().to_formatted_string(); const std::vector alg_info = split_on(oid_str, '/'); - std::string_view alg_name = alg_info[0]; + const std::string_view alg_name = alg_info[0]; #if defined(BOTAN_HAS_RSA) if(alg_name == "RSA") { @@ -489,7 +495,7 @@ std::string_view params, std::string_view provider) { /* - * Default paramaters are chosen for work factor > 2**128 where possible + * Default parameters are chosen for work factor > 2**128 where possible */ #if defined(BOTAN_HAS_X25519) @@ -634,7 +640,7 @@ if(params.empty()) { return XMSS_Parameters::XMSS_SHA2_10_512; } - return XMSS_Parameters(params).oid(); + return XMSS_Parameters::from_name(params).oid(); }(); return std::make_unique(xmss_oid, rng); diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/pk_algs.h botan3-3.12.0+dfsg/src/lib/pubkey/pk_algs.h --- botan3-3.7.1+dfsg/src/lib/pubkey/pk_algs.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/pk_algs.h 2026-05-07 01:38:28.000000000 +0000 @@ -8,12 +8,21 @@ #ifndef BOTAN_PK_KEY_FACTORY_H_ #define BOTAN_PK_KEY_FACTORY_H_ -#include -#include +#include #include +#include +#include +#include +#include namespace Botan { +class Public_Key; +class Private_Key; +class AlgorithmIdentifier; +class EC_Group; +class RandomNumberGenerator; + BOTAN_PUBLIC_API(2, 0) std::unique_ptr load_public_key(const AlgorithmIdentifier& alg_id, std::span key_bits); @@ -34,8 +43,6 @@ std::string_view algo_params = "", std::string_view provider = ""); -class EC_Group; - /** * Create a new ECC key */ diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/pk_keys.cpp botan3-3.12.0+dfsg/src/lib/pubkey/pk_keys.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/pk_keys.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/pk_keys.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -35,19 +35,26 @@ } } -std::string create_hex_fingerprint(const uint8_t bits[], size_t bits_len, std::string_view hash_name) { +std::string create_hex_fingerprint(std::span bits, std::string_view hash_name) { auto hash_fn = HashFunction::create_or_throw(hash_name); - const std::string hex_hash = hex_encode(hash_fn->process(bits, bits_len)); + hash_fn->update(bits); + auto digest = hash_fn->final_stdvec(); + return format_hex_fingerprint(digest); +} + +std::string format_hex_fingerprint(std::span bits) { + const std::string hex = hex_encode(bits); std::string fprint; + fprint.reserve(3 * bits.size()); - for(size_t i = 0; i != hex_hash.size(); i += 2) { + for(size_t i = 0; i != hex.size(); i += 2) { if(i != 0) { fprint.push_back(':'); } - fprint.push_back(hex_hash[i]); - fprint.push_back(hex_hash[i + 1]); + fprint.push_back(hex[i]); + fprint.push_back(hex[i + 1]); } return fprint; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/pk_keys.h botan3-3.12.0+dfsg/src/lib/pubkey/pk_keys.h --- botan3-3.7.1+dfsg/src/lib/pubkey/pk_keys.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/pk_keys.h 2026-05-07 01:38:28.000000000 +0000 @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -28,9 +29,9 @@ * This is mostly used for requesting DER encoding of ECDSA signatures; * most other algorithms only support "standard". */ -enum class Signature_Format { - Standard, - DerSequence, +enum class Signature_Format : uint8_t { + Standard = 0, + DerSequence = 1, IEEE_1363 BOTAN_DEPRECATED("Use Standard") = Standard, DER_SEQUENCE BOTAN_DEPRECATED("Use DerSequence") = DerSequence, @@ -42,7 +43,7 @@ * It is possible to query if a key supports a particular operation * type using Asymmetric_Key::supports_operation() */ -enum class PublicKeyOperation { +enum class PublicKeyOperation : uint8_t { Encryption, Signature, KeyEncapsulation, @@ -56,7 +57,7 @@ * * This is derived for both public and private keys */ -class BOTAN_PUBLIC_API(3, 0) Asymmetric_Key { +class BOTAN_PUBLIC_API(3, 0) Asymmetric_Key /* NOLINT(*special-member-functions) */ { public: virtual ~Asymmetric_Key() = default; @@ -116,9 +117,16 @@ /* * Test the key values for consistency. - * @param rng rng to use - * @param strong whether to perform strong and lengthy version of the test - * @return true if the test is passed + * + * Note this function is always "best effort"; for many algorithms it is + * not computationally possible to ensure the key is correctly formed in + * all respects. There is always the possibility a malformed key will be + * accepted; this is especially the case for public keys. + * + * @param rng rng to use for randomized testing (may be ignored) + * @param strong whether to perform strong and lengthy version of the test, + * however for many algorithms this has no effect + * @return true if the tests passed */ virtual bool check_key(RandomNumberGenerator& rng, bool strong) const = 0; @@ -209,7 +217,7 @@ } /** - * Returns how large each of the message parts refered to + * Returns how large each of the message parts referred to * by message_parts() is * * This function is public but applications should have few @@ -221,6 +229,8 @@ return _signature_element_size_for_DER_encoding().value_or(0); } + // NOLINTBEGIN(bugprone-virtual-near-miss) + /* * Return the format normally used by this algorithm for X.509 signatures */ @@ -228,6 +238,8 @@ return _default_x509_signature_format(); } + // NOLINTEND(bugprone-virtual-near-miss) + /** * This is an internal library function exposed on key types. * In almost all cases applications should use wrappers in pubkey.h @@ -418,10 +430,21 @@ virtual std::vector public_value() const = 0; }; -std::string BOTAN_PUBLIC_API(2, 4) create_hex_fingerprint(const uint8_t bits[], size_t len, std::string_view hash_name); +/** +* Hex encode the data and separate them in blocks with `:` characters +*/ +std::string BOTAN_PUBLIC_API(3, 12) format_hex_fingerprint(std::span bits); + +/** +* Hash the input then format that hash using format_hex_fingerprint +*/ +std::string BOTAN_PUBLIC_API(3, 0) create_hex_fingerprint(std::span bits, std::string_view hash_name); -inline std::string create_hex_fingerprint(std::span vec, std::string_view hash_name) { - return create_hex_fingerprint(vec.data(), vec.size(), hash_name); +/** +* Old interface for create_hex_fingerprint added in 2.4 pre-span +*/ +inline std::string create_hex_fingerprint(const uint8_t bits[], size_t len, std::string_view hash_name) { + return create_hex_fingerprint({bits, len}, hash_name); } } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/pk_ops.cpp botan3-3.12.0+dfsg/src/lib/pubkey/pk_ops.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/pk_ops.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/pk_ops.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,11 +7,12 @@ #include +#include #include #include #include -#include -#include +#include +#include #include #include #include @@ -26,33 +27,36 @@ throw Not_Implemented("This signature scheme does not have an algorithm identifier available"); } -PK_Ops::Encryption_with_EME::Encryption_with_EME(std::string_view eme) : m_eme(EME::create(eme)) {} +PK_Ops::Encryption_with_Padding::Encryption_with_Padding(std::string_view padding) : + m_padding(EncryptionPaddingScheme::create(padding)) {} -PK_Ops::Encryption_with_EME::~Encryption_with_EME() = default; +PK_Ops::Encryption_with_Padding::~Encryption_with_Padding() = default; -size_t PK_Ops::Encryption_with_EME::max_input_bits() const { - return 8 * m_eme->maximum_input_size(max_ptext_input_bits()); +size_t PK_Ops::Encryption_with_Padding::max_input_bits() const { + return 8 * m_padding->maximum_input_size(max_ptext_input_bits()); } -std::vector PK_Ops::Encryption_with_EME::encrypt(std::span msg, RandomNumberGenerator& rng) { +std::vector PK_Ops::Encryption_with_Padding::encrypt(std::span msg, + RandomNumberGenerator& rng) { const size_t max_input_bits = max_ptext_input_bits(); const size_t max_input_bytes = (max_input_bits + 7) / 8; BOTAN_ARG_CHECK(msg.size() <= max_input_bytes, "Plaintext too large"); - secure_vector eme_output(max_input_bits); - const size_t written = m_eme->pad(eme_output, msg, max_input_bits, rng); - return raw_encrypt(std::span{eme_output}.first(written), rng); + secure_vector padded_ptext(max_input_bits); + const size_t written = m_padding->pad(padded_ptext, msg, max_input_bits, rng); + return raw_encrypt(std::span{padded_ptext}.first(written), rng); } -PK_Ops::Decryption_with_EME::Decryption_with_EME(std::string_view eme) : m_eme(EME::create(eme)) {} +PK_Ops::Decryption_with_Padding::Decryption_with_Padding(std::string_view padding) : + m_padding(EncryptionPaddingScheme::create(padding)) {} -PK_Ops::Decryption_with_EME::~Decryption_with_EME() = default; +PK_Ops::Decryption_with_Padding::~Decryption_with_Padding() = default; -secure_vector PK_Ops::Decryption_with_EME::decrypt(uint8_t& valid_mask, std::span ctext) { +secure_vector PK_Ops::Decryption_with_Padding::decrypt(uint8_t& valid_mask, std::span ctext) { const secure_vector raw = raw_decrypt(ctext); secure_vector ptext(raw.size()); - auto len = m_eme->unpad(ptext, raw); + auto len = m_padding->unpad(ptext, raw); valid_mask = CT::Mask::from_choice(len.has_value()).if_set_return(0xFF); @@ -99,7 +103,7 @@ return hash; } - SCAN_Name req(padding); + const SCAN_Name req(padding); if(req.algo_name() == "EMSA1" && req.arg_count() == 1) { if(auto hash = HashFunction::create(req.arg(0))) { diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/pk_ops.h botan3-3.12.0+dfsg/src/lib/pubkey/pk_ops.h --- botan3-3.7.1+dfsg/src/lib/pubkey/pk_ops.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/pk_ops.h 2026-05-07 01:38:28.000000000 +0000 @@ -30,9 +30,6 @@ namespace Botan { class RandomNumberGenerator; -class EME; -class KDF; -class EMSA; } // namespace Botan @@ -41,7 +38,7 @@ /** * Public key encryption interface */ -class BOTAN_UNSTABLE_API Encryption { +class BOTAN_UNSTABLE_API Encryption /* NOLINT(*special-member-functions) */ { public: /** * Encrypt a message returning the ciphertext @@ -65,7 +62,7 @@ /** * Public key decryption interface */ -class BOTAN_UNSTABLE_API Decryption { +class BOTAN_UNSTABLE_API Decryption /* NOLINT(*special-member-functions) */ { public: virtual secure_vector decrypt(uint8_t& valid_mask, std::span ctext) = 0; @@ -77,7 +74,7 @@ /** * Public key signature verification interface */ -class BOTAN_UNSTABLE_API Verification { +class BOTAN_UNSTABLE_API Verification /* NOLINT(*special-member-functions) */ { public: /** * Add more data to the message currently being signed @@ -102,7 +99,7 @@ /** * Public key signature creation interface */ -class BOTAN_UNSTABLE_API Signature { +class BOTAN_UNSTABLE_API Signature /* NOLINT(*special-member-functions) */ { public: /** * Add more data to the message currently being signed @@ -139,7 +136,7 @@ /** * A generic key agreement operation (eg DH or ECDH) */ -class BOTAN_UNSTABLE_API Key_Agreement { +class BOTAN_UNSTABLE_API Key_Agreement /* NOLINT(*special-member-functions) */ { public: virtual secure_vector agree(size_t key_len, std::span other_key, @@ -153,7 +150,7 @@ /** * KEM (key encapsulation) */ -class BOTAN_UNSTABLE_API KEM_Encryption { +class BOTAN_UNSTABLE_API KEM_Encryption /* NOLINT(*special-member-functions) */ { public: virtual void kem_encrypt(std::span out_encapsulated_key, std::span out_shared_key, @@ -168,7 +165,7 @@ virtual ~KEM_Encryption() = default; }; -class BOTAN_UNSTABLE_API KEM_Decryption { +class BOTAN_UNSTABLE_API KEM_Decryption /* NOLINT(*special-member-functions) */ { public: virtual void kem_decrypt(std::span out_shared_key, std::span encapsulated_key, diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/pk_ops_impl.h botan3-3.12.0+dfsg/src/lib/pubkey/pk_ops_impl.h --- botan3-3.7.1+dfsg/src/lib/pubkey/pk_ops_impl.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/pk_ops_impl.h 2026-05-07 01:38:28.000000000 +0000 @@ -14,42 +14,44 @@ class HashFunction; class KDF; -class EME; +class EncryptionPaddingScheme; } // namespace Botan namespace Botan::PK_Ops { -class Encryption_with_EME : public Encryption { +// NOLINTBEGIN(*-special-member-functions) + +class Encryption_with_Padding : public Encryption { public: - ~Encryption_with_EME() override; + ~Encryption_with_Padding() override; size_t max_input_bits() const override; std::vector encrypt(std::span ptext, RandomNumberGenerator& rng) override; protected: - explicit Encryption_with_EME(std::string_view eme); + explicit Encryption_with_Padding(std::string_view padding); private: virtual size_t max_ptext_input_bits() const = 0; virtual std::vector raw_encrypt(std::span msg, RandomNumberGenerator& rng) = 0; - std::unique_ptr m_eme; + std::unique_ptr m_padding; }; -class Decryption_with_EME : public Decryption { +class Decryption_with_Padding : public Decryption { public: - ~Decryption_with_EME() override; + ~Decryption_with_Padding() override; secure_vector decrypt(uint8_t& valid_mask, std::span ctext) override; protected: - explicit Decryption_with_EME(std::string_view eme); + explicit Decryption_with_Padding(std::string_view padding); private: virtual secure_vector raw_decrypt(std::span ctext) = 0; - std::unique_ptr m_eme; + std::unique_ptr m_padding; }; class Verification_with_Hash : public Verification { @@ -167,6 +169,8 @@ std::unique_ptr m_kdf; }; +// NOLINTEND(*-special-member-functions) + } // namespace Botan::PK_Ops #endif diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/pkcs8.cpp botan3-3.12.0+dfsg/src/lib/pubkey/pkcs8.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/pkcs8.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/pkcs8.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -30,7 +31,11 @@ secure_vector PKCS8_extract(DataSource& source, AlgorithmIdentifier& pbe_alg_id) { secure_vector key_data; - BER_Decoder(source).start_sequence().decode(pbe_alg_id).decode(key_data, ASN1_Type::OctetString).verify_end(); + BER_Decoder(source, BER_Decoder::Limits::DER()) + .start_sequence() + .decode(pbe_alg_id) + .decode(key_data, ASN1_Type::OctetString) + .verify_end(); return key_data; } @@ -43,7 +48,8 @@ AlgorithmIdentifier& pk_alg_id, bool is_encrypted) { AlgorithmIdentifier pbe_alg_id; - secure_vector key_data, key; + secure_vector key_data; + secure_vector key; try { if(ASN1::maybe_BER(source) && !PEM_Code::matches(source)) { @@ -51,12 +57,8 @@ key_data = PKCS8_extract(source, pbe_alg_id); } else { // todo read more efficiently - while(!source.end_of_data()) { - uint8_t b; - size_t read = source.read_byte(b); - if(read) { - key_data.push_back(b); - } + while(auto b = source.read_byte()) { + key_data.push_back(*b); } } } else { @@ -97,13 +99,14 @@ key = key_data; } - BER_Decoder(key) + BER_Decoder(key, BER_Decoder::Limits::DER()) .start_sequence() .decode_and_check(0, "Unknown PKCS #8 version number") .decode(pk_alg_id) .decode(key, ASN1_Type::OctetString) .discard_remaining() - .end_cons(); + .end_cons() + .verify_end(); } catch(std::exception& e) { throw Decoding_Error("PKCS #8 private key decoding", e); } @@ -144,7 +147,7 @@ return std::make_pair("AES-256/CBC", "SHA-256"); } - SCAN_Name request(pbe_algo); + const SCAN_Name request(pbe_algo); if(request.arg_count() != 2 || (request.algo_name() != "PBE-PKCS5v20" && request.algo_name() != "PBES2")) { throw Invalid_Argument(fmt("Unsupported PBE '{}'", pbe_algo)); diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/pkcs8.h botan3-3.12.0+dfsg/src/lib/pubkey/pkcs8.h --- botan3-3.7.1+dfsg/src/lib/pubkey/pkcs8.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/pkcs8.h 2026-05-07 01:38:28.000000000 +0000 @@ -93,7 +93,7 @@ * @param key the key to encode * @param rng the rng to use * @param pass the password to use for encryption -* @param pbkdf_iter number of interations to run PBKDF2 +* @param pbkdf_iter number of iterations to run PBKDF2 * @param cipher if non-empty specifies the cipher to use. CBC and GCM modes * are supported, for example "AES-128/CBC", "AES-256/GCM", "Serpent/CBC". * If empty a suitable default is chosen. diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/pqcrystals/info.txt botan3-3.12.0+dfsg/src/lib/pubkey/pqcrystals/info.txt --- botan3-3.7.1+dfsg/src/lib/pubkey/pqcrystals/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/pqcrystals/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + PQCRYSTALS -> 20240228 - + name -> "CRYSTALS" diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/pqcrystals/pqcrystals.h botan3-3.12.0+dfsg/src/lib/pubkey/pqcrystals/pqcrystals.h --- botan3-3.7.1+dfsg/src/lib/pubkey/pqcrystals/pqcrystals.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/pqcrystals/pqcrystals.h 2026-05-07 01:38:28.000000000 +0000 @@ -25,7 +25,7 @@ namespace Botan::CRYSTALS { -enum class Domain { Normal, NTT }; +enum class Domain : uint8_t { Normal, NTT }; template concept crystals_constants = @@ -75,6 +75,8 @@ /// @} protected: + Trait_Base() = default; // NOLINT(*crtp-constructor-accessibility) + /// @returns the number of polynomials in the polynomial vector @p polyvec. static constexpr size_t polys_in_polyvec(std::span polyvec) { BOTAN_DEBUG_ASSERT(polyvec.size() % N == 0); @@ -128,7 +130,7 @@ std::span u, std::span v) { clear_mem(w); - std::array t; + std::array t{}; for(size_t i = 0; i < polys_in_polyvec(u); ++i) { DerivedT::poly_pointwise_montgomery(t, poly_in_polyvec(u, i), poly_in_polyvec(v, i)); poly_add(w, w, t); @@ -232,7 +234,7 @@ */ template requires(D != OtherD) - explicit Polynomial(Polynomial&& other) noexcept : + explicit Polynomial(Polynomial&& other) noexcept : // NOLINT(*-rvalue-reference-param-not-moved) m_coeffs_storage(std::move(other.m_coeffs_storage)), m_coeffs(owns_storage() ? std::span(m_coeffs_storage) : other.m_coeffs) {} @@ -372,7 +374,8 @@ */ template requires(D != OtherD) - explicit PolynomialVector(PolynomialVector&& other) noexcept : + /* NOLINTNEXTLINE(*rvalue-reference-param-not-moved) */ explicit PolynomialVector( + PolynomialVector&& other) noexcept : m_polys_storage(std::move(other.m_polys_storage)) { BOTAN_DEBUG_ASSERT(m_polys_storage.size() % Trait::N == 0); const size_t vecsize = m_polys_storage.size() / Trait::N; @@ -395,7 +398,7 @@ } public: - PolynomialVector(size_t vecsize) : m_polys_storage(vecsize * Trait::N) { + explicit PolynomialVector(size_t vecsize) : m_polys_storage(vecsize * Trait::N) { for(size_t i = 0; i < vecsize; ++i) { m_vec.emplace_back( Polynomial(std::span{m_polys_storage}.subspan(i * Trait::N).template first())); @@ -497,7 +500,7 @@ std::vector> m_mat; public: - PolynomialMatrix(std::vector> mat) : m_mat(std::move(mat)) {} + explicit PolynomialMatrix(std::vector> mat) : m_mat(std::move(mat)) {} PolynomialMatrix(const ThisPolynomialMatrix& other) = delete; PolynomialMatrix(ThisPolynomialMatrix&& other) noexcept = default; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/pqcrystals/pqcrystals_encoding.h botan3-3.12.0+dfsg/src/lib/pubkey/pqcrystals/pqcrystals_encoding.h --- botan3-3.7.1+dfsg/src/lib/pubkey/pqcrystals/pqcrystals_encoding.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/pqcrystals/pqcrystals_encoding.h 2026-05-07 01:38:28.000000000 +0000 @@ -15,10 +15,11 @@ #include #include +#include +#include #include #include #include -#include #if defined(BOTAN_HAS_XOF) #include @@ -75,7 +76,7 @@ constexpr static size_t bits_per_pack = [] { // Ensure that the bit-packing is byte-aligned and scale it // to utilize the collector's bit-width as much as possible. - size_t smallest_aligned_pack = std::lcm(bits_per_coeff, size_t(8)); + const size_t smallest_aligned_pack = std::lcm(bits_per_coeff, size_t(8)); return (smallest_aligned_pack < bits_in_collector) ? (bits_in_collector / smallest_aligned_pack) * smallest_aligned_pack : smallest_aligned_pack; @@ -107,7 +108,7 @@ * * Note that this bit-packing algorithm is inefficient if the bit-length of the * coefficients is a multiple of 8. In that case, a byte-level encoding (that - * might need to take endianess into account) would be more efficient. However, + * might need to take endianness into account) would be more efficient. However, * neither Kyber nor Dilithium instantiate bit-packings with such a value range. * * @tparam range the upper bound of the coefficient range. diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/pqcrystals/pqcrystals_helpers.h botan3-3.12.0+dfsg/src/lib/pubkey/pqcrystals/pqcrystals_helpers.h --- botan3-3.7.1+dfsg/src/lib/pubkey/pqcrystals/pqcrystals_helpers.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/pqcrystals/pqcrystals_helpers.h 2026-05-07 01:38:28.000000000 +0000 @@ -40,7 +40,7 @@ // clang-format on template - requires(size_t(sizeof(T)) <= 4) + requires(sizeof(T) <= 4) consteval T montgomery_R(T q) { using T_unsigned = std::make_unsigned_t; using T2 = next_longer_uint_t; @@ -48,7 +48,7 @@ } template - requires(size_t(sizeof(T)) <= 4) + requires(sizeof(T) <= 4) consteval T montgomery_R2(T q) { using T2 = next_longer_int_t; return (static_cast(montgomery_R(q)) * static_cast(montgomery_R(q))) % q; @@ -71,7 +71,10 @@ std::swap(a, b); } - T u1 = 0, v1 = 1, u2 = 1, v2 = 0; + T u1 = 0; + T v1 = 1; + T u2 = 1; + T v2 = 0; if(a != b) { while(a != 0) { @@ -97,7 +100,7 @@ constexpr auto bitlen(size_t x) { return ceil_log2(x + 1); -}; +} /** * Precompute the zeta-values for the NTT. Note that the pre-computed values @@ -158,7 +161,7 @@ } template - constexpr static bool default_predicate(T) { + constexpr static bool default_predicate(T /*v*/) { return true; } @@ -188,8 +191,8 @@ typename PredicateFnT = decltype(default_predicate>)> requires std::invocable> && std::invocable> - constexpr auto next(MapFnT&& transformer = default_transformer, - PredicateFnT&& predicate = default_predicate>) { + constexpr auto next(const MapFnT& transformer = default_transformer, + const PredicateFnT& predicate = default_predicate>) { while(true) { auto output = transformer(take()); if(predicate(output)) { diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/pubkey.cpp botan3-3.12.0+dfsg/src/lib/pubkey/pubkey.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/pubkey.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/pubkey.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,14 +9,12 @@ #include #include #include -#include #include -#include #include +#include #include #include -#include -#include +#include namespace Botan { @@ -110,8 +108,8 @@ return m_op->ciphertext_length(ptext_len); } -std::vector PK_Encryptor_EME::enc(const uint8_t in[], size_t length, RandomNumberGenerator& rng) const { - return m_op->encrypt(std::span{in, length}, rng); +std::vector PK_Encryptor_EME::enc(const uint8_t ptext[], size_t len, RandomNumberGenerator& rng) const { + return m_op->encrypt(std::span{ptext, len}, rng); } size_t PK_Encryptor_EME::maximum_input_size() const { @@ -235,33 +233,32 @@ const uint8_t peer_key[], size_t peer_key_len, std::string_view salt) const { - return this->derive_key(key_len, peer_key, peer_key_len, cast_char_ptr_to_uint8(salt.data()), salt.length()); + return this->derive_key(key_len, {peer_key, peer_key_len}, as_span_of_bytes(salt)); } SymmetricKey PK_Key_Agreement::derive_key(size_t key_len, const std::span peer_key, std::string_view salt) const { - return this->derive_key( - key_len, peer_key.data(), peer_key.size(), cast_char_ptr_to_uint8(salt.data()), salt.length()); + return this->derive_key(key_len, peer_key, as_span_of_bytes(salt)); } -SymmetricKey PK_Key_Agreement::derive_key( - size_t key_len, const uint8_t peer_key[], size_t peer_key_len, const uint8_t salt[], size_t salt_len) const { - return SymmetricKey(m_op->agree(key_len, {peer_key, peer_key_len}, {salt, salt_len})); +SymmetricKey PK_Key_Agreement::derive_key(size_t key_len, + std::span peer_key, + std::span salt) const { + return SymmetricKey(m_op->agree(key_len, peer_key, salt)); } PK_Signer::PK_Signer(const Private_Key& key, RandomNumberGenerator& rng, - std::string_view emsa, + std::string_view padding, Signature_Format format, std::string_view provider) : - m_sig_format(format) { + m_sig_format(format), m_sig_element_size(key._signature_element_size_for_DER_encoding()) { if(m_sig_format == Signature_Format::DerSequence) { - m_sig_element_size = key._signature_element_size_for_DER_encoding(); BOTAN_ARG_CHECK(m_sig_element_size.has_value(), "This key does not support DER signatures"); } - m_op = key.create_signature_op(rng, emsa, provider); + m_op = key.create_signature_op(rng, padding, provider); if(!m_op) { throw Invalid_Argument(fmt("Key type {} does not support signature generation", key.algo_name())); } @@ -281,7 +278,7 @@ PK_Signer& PK_Signer::operator=(PK_Signer&&) noexcept = default; void PK_Signer::update(std::string_view in) { - this->update(cast_char_ptr_to_uint8(in.data()), in.size()); + this->update(as_span_of_bytes(in)); } void PK_Signer::update(const uint8_t in[], size_t length) { @@ -313,9 +310,9 @@ if(m_sig_format == Signature_Format::Standard) { return m_op->signature_length(); } else if(m_sig_format == Signature_Format::DerSequence) { - size_t sig_len = m_op->signature_length(); + const size_t sig_len = m_op->signature_length(); - size_t der_overhead = [sig_len]() { + const size_t der_overhead = [sig_len]() { /* This was computed by DER encoding of some maximal value signatures (since DER is variable length) @@ -367,10 +364,10 @@ } PK_Verifier::PK_Verifier(const Public_Key& key, - std::string_view emsa, + std::string_view padding, Signature_Format format, std::string_view provider) { - m_op = key.create_verification_op(emsa, provider); + m_op = key.create_verification_op(padding, provider); if(!m_op) { throw Invalid_Argument(fmt("Key type {} does not support signature verification", key.algo_name())); } @@ -417,7 +414,7 @@ } void PK_Verifier::update(std::string_view in) { - this->update(cast_char_ptr_to_uint8(in.data()), in.size()); + this->update(as_span_of_bytes(in)); } void PK_Verifier::update(const uint8_t in[], size_t length) { @@ -426,32 +423,27 @@ namespace { -std::vector decode_der_signature(const uint8_t sig[], size_t length, size_t sig_parts, size_t sig_part_size) { - std::vector real_sig; - BER_Decoder decoder(sig, length); - BER_Decoder ber_sig = decoder.start_sequence(); +std::vector decode_der_signature_pair(std::span der_sig, size_t sig_part_size) { + BOTAN_ASSERT_NOMSG(sig_part_size > 0); - BOTAN_ASSERT_NOMSG(sig_parts != 0 && sig_part_size != 0); + BigInt r; + BigInt s; - size_t count = 0; + // TODO should be able to just get the integer bytes directly from + // BER_Decoder without using BigInt here + BER_Decoder(der_sig, BER_Decoder::Limits::DER()).start_sequence().decode(r).decode(s).end_cons().verify_end(); - while(ber_sig.more_items()) { - BigInt sig_part; - ber_sig.decode(sig_part); - real_sig += sig_part.serialize(sig_part_size); - ++count; - } + const bool invalid_r = r.is_negative() || r.bytes() > sig_part_size; + const bool invalid_s = s.is_negative() || s.bytes() > sig_part_size; - if(count != sig_parts) { - throw Decoding_Error("PK_Verifier: signature size invalid"); + if(invalid_r || invalid_s) { + throw Decoding_Error("Invalid DER encoding of signature"); } - const std::vector reencoded = der_encode_signature(real_sig, sig_parts, sig_part_size); - - if(reencoded.size() != length || CT::is_equal(reencoded.data(), sig, reencoded.size()).as_bool() == false) { - throw Decoding_Error("PK_Verifier: signature is not the canonical DER encoding"); - } - return real_sig; + std::vector sig(2 * sig_part_size); + r.serialize_to(std::span{sig}.first(sig_part_size)); + s.serialize_to(std::span{sig}.last(sig_part_size)); + return sig; } } // namespace @@ -467,11 +459,13 @@ BOTAN_ASSERT_NOMSG(m_sig_element_size.has_value()); try { - real_sig = decode_der_signature(sig, length, 2, m_sig_element_size.value()); + real_sig = decode_der_signature_pair({sig, length}, m_sig_element_size.value()); decoding_success = true; - } catch(Decoding_Error&) {} + } catch(...) {} - bool accept = m_op->is_valid_signature(real_sig); + // It is critical that is_valid_signature is called even if DER decoding failed, since + // that is what resets the internal state (message hashes, etc) + const bool accept = m_op->is_valid_signature(real_sig); return accept && decoding_success; } else { diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/pubkey.h botan3-3.12.0+dfsg/src/lib/pubkey/pubkey.h --- botan3-3.7.1+dfsg/src/lib/pubkey/pubkey.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/pubkey.h 2026-05-07 01:38:28.000000000 +0000 @@ -8,7 +8,6 @@ #ifndef BOTAN_PUBKEY_H_ #define BOTAN_PUBKEY_H_ -#include #include #include #include @@ -420,12 +419,22 @@ * Perform Key Agreement Operation * @param key_len the desired key output size (ignored if "Raw" KDF is used) * @param peer_key the other parties key + * @param salt extra derivation salt + */ + SymmetricKey derive_key(size_t key_len, std::span peer_key, std::span salt) const; + + /** + * Perform Key Agreement Operation + * @param key_len the desired key output size (ignored if "Raw" KDF is used) + * @param peer_key the other parties key * @param peer_key_len the length of peer_key in bytes * @param salt extra derivation salt * @param salt_len the length of salt in bytes */ SymmetricKey derive_key( - size_t key_len, const uint8_t peer_key[], size_t peer_key_len, const uint8_t salt[], size_t salt_len) const; + size_t key_len, const uint8_t peer_key[], size_t peer_key_len, const uint8_t salt[], size_t salt_len) const { + return this->derive_key(key_len, {peer_key, peer_key_len}, {salt, salt_len}); + } /** * Perform Key Agreement Operation @@ -507,13 +516,15 @@ size_t ciphertext_length(size_t ptext_len) const override; private: - std::vector enc(const uint8_t[], size_t, RandomNumberGenerator& rng) const override; + std::vector enc(const uint8_t ptext[], size_t len, RandomNumberGenerator& rng) const override; std::unique_ptr m_op; }; /** -* Decryption with an MR algorithm and an EME. +* Decryption with a padding scheme. +* +* This is typically only used with RSA */ class BOTAN_PUBLIC_API(2, 0) PK_Decryptor_EME final : public PK_Decryptor { public: @@ -521,12 +532,12 @@ * Construct an instance. * @param key the key to use inside the decryptor * @param rng the random generator to use - * @param eme the EME to use + * @param padding the padding scheme to use * @param provider the provider to use */ PK_Decryptor_EME(const Private_Key& key, RandomNumberGenerator& rng, - std::string_view eme, + std::string_view padding, std::string_view provider = ""); size_t plaintext_length(size_t ptext_len) const override; @@ -566,7 +577,8 @@ /** * @returns the pair (encapsulated key, key) extracted from @p kem */ - static std::pair, secure_vector> destructure(KEM_Encapsulation&& kem) { + static std::pair, secure_vector> destructure( + KEM_Encapsulation&& kem) /* NOLINT(*param-not-moved*) */ { return std::make_pair(std::exchange(kem.m_encapsulated_shared_key, {}), std::exchange(kem.m_shared_key, {})); } @@ -592,7 +604,9 @@ * @param kem_param additional KEM parameters * @param provider the provider to use */ - PK_KEM_Encryptor(const Public_Key& key, std::string_view kem_param = "", std::string_view provider = ""); + BOTAN_FUTURE_EXPLICIT PK_KEM_Encryptor(const Public_Key& key, + std::string_view kem_param = "", + std::string_view provider = ""); /** * Construct an instance. @@ -703,7 +717,7 @@ this->encrypt(out_encapsulated_key, out_shared_key, rng, desired_shared_key_len, {salt, salt_len}); } - BOTAN_DEPRECATED("use overload where rng comes after the out-paramters") + BOTAN_DEPRECATED("use overload where rng comes after the out-parameters") void encrypt(secure_vector& out_encapsulated_key, secure_vector& out_shared_key, size_t desired_shared_key_len, diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/rfc6979/info.txt botan3-3.12.0+dfsg/src/lib/pubkey/rfc6979/info.txt --- botan3-3.7.1+dfsg/src/lib/pubkey/rfc6979/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/rfc6979/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + RFC6979_GENERATOR -> 20140321 - + name -> "RFC 6979" diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/rfc6979/rfc6979.cpp botan3-3.12.0+dfsg/src/lib/pubkey/rfc6979/rfc6979.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/rfc6979/rfc6979.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/rfc6979/rfc6979.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,12 +7,16 @@ #include +#include #include #include #include namespace Botan { +RFC6979_Nonce_Generator::RFC6979_Nonce_Generator(RFC6979_Nonce_Generator&& other) noexcept = default; +RFC6979_Nonce_Generator& RFC6979_Nonce_Generator::operator=(RFC6979_Nonce_Generator&& other) noexcept = default; + RFC6979_Nonce_Generator::~RFC6979_Nonce_Generator() = default; RFC6979_Nonce_Generator::RFC6979_Nonce_Generator(std::string_view hash, size_t order_bits, const BigInt& x) : @@ -34,14 +38,18 @@ BigInt k; - do { + for(;;) { m_hmac_drbg->randomize(m_rng_out); k._assign_from_bytes(m_rng_out); if(shift > 0) { k >>= shift; } - } while(k == 0 || k >= order); + + if(k > 0 && k < order) { + break; + } + } return k; } diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/rfc6979/rfc6979.h botan3-3.12.0+dfsg/src/lib/pubkey/rfc6979/rfc6979.h --- botan3-3.7.1+dfsg/src/lib/pubkey/rfc6979/rfc6979.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/rfc6979/rfc6979.h 2026-05-07 01:38:28.000000000 +0000 @@ -10,7 +10,6 @@ #include #include -#include #include #if defined(BOTAN_HAS_ECC_GROUP) @@ -33,6 +32,11 @@ EC_Scalar nonce_for(const EC_Group& group, const EC_Scalar& m); #endif + RFC6979_Nonce_Generator(const RFC6979_Nonce_Generator& other) = delete; + RFC6979_Nonce_Generator& operator=(const RFC6979_Nonce_Generator& other) = delete; + + RFC6979_Nonce_Generator(RFC6979_Nonce_Generator&& other) noexcept; + RFC6979_Nonce_Generator& operator=(RFC6979_Nonce_Generator&& other) noexcept; ~RFC6979_Nonce_Generator(); private: diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/rsa/info.txt botan3-3.12.0+dfsg/src/lib/pubkey/rsa/info.txt --- botan3-3.7.1+dfsg/src/lib/pubkey/rsa/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/rsa/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -10,7 +10,8 @@ blinding keypair numbertheory -pk_pad +sig_padding +enc_padding diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/rsa/rsa.cpp botan3-3.12.0+dfsg/src/lib/pubkey/rsa/rsa.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/rsa/rsa.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/rsa/rsa.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -11,17 +11,19 @@ #include #include #include -#include +#include #include #include -#include #include #include #include #include #include +#include #include #include +#include +#include #include #if defined(BOTAN_HAS_THREAD_UTILS) @@ -35,8 +37,8 @@ RSA_Public_Data(BigInt&& n, BigInt&& e) : m_n(std::move(n)), m_e(std::move(e)), - m_mod_n(Modular_Reducer::for_public_modulus(m_n)), - m_monty_n(std::make_shared(m_n, m_mod_n)), + m_mod_n(Barrett_Reduction::for_public_modulus(m_n)), + m_monty_n(m_n, m_mod_n), m_public_modulus_bits(m_n.bits()), m_public_modulus_bytes(m_n.bytes()) {} @@ -54,15 +56,15 @@ size_t public_modulus_bytes() const { return m_public_modulus_bytes; } - const std::shared_ptr& monty_n() const { return m_monty_n; } + const Montgomery_Params& monty_n() const { return m_monty_n; } - const Modular_Reducer& reducer_mod_n() const { return m_mod_n; } + const Barrett_Reduction& reducer_mod_n() const { return m_mod_n; } private: BigInt m_n; BigInt m_e; - Modular_Reducer m_mod_n; - std::shared_ptr m_monty_n; + Barrett_Reduction m_mod_n; + const Montgomery_Params m_monty_n; size_t m_public_modulus_bits; size_t m_public_modulus_bytes; }; @@ -76,8 +78,8 @@ m_d1(std::move(d1)), m_d2(std::move(d2)), m_c(std::move(c)), - m_monty_p(std::make_shared(m_p)), - m_monty_q(std::make_shared(m_q)), + m_monty_p(m_p), + m_monty_q(m_q), m_c_monty(m_monty_p, m_c), m_p_bits(m_p.bits()), m_q_bits(m_q.bits()) {} @@ -92,13 +94,17 @@ const BigInt& get_d2() const { return m_d2; } + BigInt blinded_d1(const BigInt& m) const { return m_d1 + m * (m_p - 1); } + + BigInt blinded_d2(const BigInt& m) const { return m_d2 + m * (m_q - 1); } + const BigInt& get_c() const { return m_c; } const Montgomery_Int& get_c_monty() const { return m_c_monty; } - const std::shared_ptr& monty_p() const { return m_monty_p; } + const Montgomery_Params& monty_p() const { return m_monty_p; } - const std::shared_ptr& monty_q() const { return m_monty_q; } + const Montgomery_Params& monty_q() const { return m_monty_q; } size_t p_bits() const { return m_p_bits; } @@ -114,8 +120,8 @@ BigInt m_d2; BigInt m_c; - std::shared_ptr m_monty_p; - std::shared_ptr m_monty_q; + const Montgomery_Params m_monty_p; + const Montgomery_Params m_monty_q; Montgomery_Int m_c_monty; size_t m_p_bits; size_t m_q_bits; @@ -136,7 +142,7 @@ } std::unique_ptr RSA_PublicKey::generate_another(RandomNumberGenerator& rng) const { - return std::make_unique(rng, m_public->public_modulus_bits(), m_public->get_e().to_u32bit()); + return std::make_unique(rng, m_public->public_modulus_bits(), 65537); } const BigInt& RSA_PublicKey::get_n() const { @@ -148,15 +154,19 @@ } void RSA_PublicKey::init(BigInt&& n, BigInt&& e) { - if(n.is_negative() || n.is_even() || n.bits() < 5 /* n >= 3*5 */ || e.is_negative() || e.is_even()) { - throw Decoding_Error("Invalid RSA public key parameters"); + if(n.signum() <= 0 || n.is_even() || n.bits() < 384 || n.bits() > 16384) { + throw Decoding_Error("Invalid RSA public key modulus"); + } + if(e.is_even() || e <= 1 || e >= n || e.bits() > 256) { + throw Decoding_Error("Invalid RSA public key exponent"); } m_public = std::make_shared(std::move(n), std::move(e)); } RSA_PublicKey::RSA_PublicKey(const AlgorithmIdentifier& /*unused*/, std::span key_bits) { - BigInt n, e; - BER_Decoder(key_bits).start_sequence().decode(n).decode(e).end_cons(); + BigInt n; + BigInt e; + BER_Decoder(key_bits, BER_Decoder::Limits::DER()).start_sequence().decode(n).decode(e).end_cons().verify_end(); init(std::move(n), std::move(e)); } @@ -251,14 +261,27 @@ } void RSA_PrivateKey::init(BigInt&& d, BigInt&& p, BigInt&& q, BigInt&& d1, BigInt&& d2, BigInt&& c) { + if(d < 2 || p < 3 || q < 3 || p == q) { + throw Decoding_Error("Invalid RSA private key parameters"); + } + if(p * q != get_n()) { + throw Decoding_Error("Invalid RSA private key: p * q != n"); + } m_private = std::make_shared( std::move(d), std::move(p), std::move(q), std::move(d1), std::move(d2), std::move(c)); } RSA_PrivateKey::RSA_PrivateKey(const AlgorithmIdentifier& /*unused*/, std::span key_bits) { - BigInt n, e, d, p, q, d1, d2, c; + BigInt n; + BigInt e; + BigInt d; + BigInt p; + BigInt q; + BigInt d1; + BigInt d2; + BigInt c; - BER_Decoder(key_bits) + BER_Decoder(key_bits, BER_Decoder::Limits::DER()) .start_sequence() .decode_and_check(0, "Unknown PKCS #1 key format version") .decode(n) @@ -269,7 +292,8 @@ .decode(d1) .decode(d2) .decode(c) - .end_cons(); + .end_cons() + .verify_end(); RSA_PublicKey::init(std::move(n), std::move(e)); @@ -310,8 +334,18 @@ * Create a RSA private key */ RSA_PrivateKey::RSA_PrivateKey(RandomNumberGenerator& rng, size_t bits, size_t exp) { - if(bits < 1024) { - throw Invalid_Argument(fmt("Cannot create an RSA key only {} bits long", bits)); + constexpr size_t MIN_RSA_BITS = 1024; + constexpr size_t MAX_RSA_BITS = 16384; + constexpr size_t MOD_RSA_BITS = 8; + + if(bits < MIN_RSA_BITS) { + throw Invalid_Argument(fmt("Cannot create an RSA key of {} bits: must be at least {} bits", bits, MIN_RSA_BITS)); + } else if(bits > MAX_RSA_BITS) { + throw Invalid_Argument( + fmt("Cannot create an RSA key of {} bits: must be no more than {} bits", bits, MAX_RSA_BITS)); + } else if(bits % MOD_RSA_BITS != 0) { + throw Invalid_Argument( + fmt("Cannot create an RSA key of {} bits: must be a multiple of {} bits", bits, MOD_RSA_BITS)); } if(exp < 3 || exp % 2 == 0) { @@ -321,7 +355,9 @@ const size_t p_bits = (bits + 1) / 2; const size_t q_bits = bits - p_bits; - BigInt p, q, n; + BigInt p; + BigInt q; + BigInt n; BigInt e = BigInt::from_u64(exp); for(size_t attempt = 0;; ++attempt) { @@ -444,6 +480,58 @@ namespace { +/* +* To recover the final value from the CRT representation (j1,j2) +* we use Garner's algorithm: +* c = q^-1 mod p (this is precomputed) +* h = c*(j1-j2) mod p +* r = h*q + j2 +*/ +BigInt crt_recombine(const Montgomery_Int& j1, + const Montgomery_Int& j2_p, + const BigInt& j2, + const Montgomery_Int& c_monty, + const BigInt& p, + const BigInt& q) { + // We skip CRT entirely if the primes are not balanced (same bitlength) so q is also of this size + const size_t p_words = p.sig_words(); + BOTAN_ASSERT_NOMSG(p_words == q.sig_words()); + + const size_t n_words = 2 * p_words; + + // Ensure sufficient storage + BOTAN_ASSERT_NOMSG(j1.repr().size() >= p_words); + BOTAN_ASSERT_NOMSG(j2_p.repr().size() >= p_words); + BOTAN_ASSERT_NOMSG(j2.size() >= p_words); + + /* + * Compute h = (j1 - j2) * c mod p + * + * This doesn't quite match up with the "Smooth-CRT" proposal; there we would + * multiply by a precomputed c * R2, which would have the effect of both + * multiplying by c and immediately converting from Montgomery to standard form. + */ + secure_vector ws(2 * p_words); + + const Montgomery_Int h_monty = (j1 - j2_p).mul(c_monty, ws); + + const BigInt h = h_monty.value(); + // Montgomery_Int always returns values sized to the modulus + BOTAN_ASSERT_NOMSG(h.size() >= p_words); + BOTAN_DEBUG_ASSERT(h.sig_words() <= p_words); + + // Compute r = h * q + secure_vector r(2 * p_words); + + bigint_mul(r.data(), r.size(), h._data(), h.size(), p_words, q._data(), q.size(), p_words, ws.data(), ws.size()); + + // r += j2 + const word carry = bigint_add2(r.data(), n_words, j2._data(), p_words); + BOTAN_ASSERT_NOMSG(carry == 0); // should not be possible since it would imply r > the public modulus + + return BigInt::_from_words(r); +} + /** * RSA private (decrypt/sign) operation */ @@ -470,8 +558,8 @@ throw Decoding_Error("RSA input is too long for this key"); } const BigInt input_bn(input.data(), input.size()); - if(input_bn >= m_public->get_n()) { - throw Decoding_Error("RSA input is too large for this key"); + if(input_bn.is_zero() || input_bn >= m_public->get_n()) { + throw Decoding_Error("RSA input is not in the valid range"); } // TODO: This should be a function on blinder // BigInt Blinder::run_blinded_function(std::function fn, const BigInt& input); @@ -504,14 +592,14 @@ #if defined(BOTAN_RSA_USE_ASYNC) /* * Precompute m.sig_words in the main thread before calling async. Otherwise - * the two threads race (during Modular_Reducer::reduce) and while the output + * the two threads race (during Barrett_Reduction::reduce) and while the output * is correct in both threads, helgrind warns. */ m.sig_words(); auto future_j1 = Thread_Pool::global_instance().run([this, &m, &d1_mask]() { #endif - const BigInt masked_d1 = m_private->get_d1() + (d1_mask * (m_private->get_p() - 1)); + const BigInt masked_d1 = m_private->blinded_d1(d1_mask); auto powm_d1_p = monty_precompute(Montgomery_Int::from_wide_int(m_private->monty_p(), m), powm_window); auto j1 = monty_execute(*powm_d1_p, masked_d1, m_max_d1_bits); @@ -521,7 +609,7 @@ #endif const BigInt d2_mask(m_blinder.rng(), m_blinding_bits); - const BigInt masked_d2 = m_private->get_d2() + (d2_mask * (m_private->get_q() - 1)); + const BigInt masked_d2 = m_private->blinded_d2(d2_mask); auto powm_d2_q = monty_precompute(Montgomery_Int::from_wide_int(m_private->monty_q(), m), powm_window); const auto j2 = monty_execute(*powm_d2_q, masked_d2, m_max_d2_bits).value(); @@ -529,23 +617,10 @@ auto j1 = future_j1.get(); #endif - /* - * To recover the final value from the CRT representation (j1,j2) - * we use Garner's algorithm: - * c = q^-1 mod p (this is precomputed) - * h = c*(j1-j2) mod p - * m = j2 + h*q - */ - + // Reduce j2 modulo p const auto j2_p = Montgomery_Int::from_wide_int(m_private->monty_p(), j2); - /** - * This doesn't quite match up with the "Smooth-CRT" proposal; there we - * would multiply by c * R2 so would have the effect of both multiplying - * by c and immediately converting from Montgomery to standard form. - */ - j1 = (j1 - j2_p) * m_private->get_c_monty(); - return j1.value() * m_private->get_q() + j2; + return crt_recombine(j1, j2_p, j2, m_private->get_c_monty(), m_private->get_p(), m_private->get_q()); } std::shared_ptr m_public; @@ -561,12 +636,12 @@ class RSA_Signature_Operation final : public PK_Ops::Signature, private RSA_Private_Operation { public: - void update(std::span msg) override { m_emsa->update(msg.data(), msg.size()); } + void update(std::span msg) override { m_padding->update(msg.data(), msg.size()); } std::vector sign(RandomNumberGenerator& rng) override { const size_t max_input_bits = public_modulus_bits() - 1; - const auto msg = m_emsa->raw_data(); - const auto padded = m_emsa->encoding_of(msg, max_input_bits, rng); + const auto msg = m_padding->raw_data(); + const auto padded = m_padding->encoding_of(msg, max_input_bits, rng); std::vector out(public_modulus_bytes()); raw_op(out, padded); @@ -577,37 +652,37 @@ AlgorithmIdentifier algorithm_identifier() const override; - std::string hash_function() const override { return m_emsa->hash_function(); } + std::string hash_function() const override { return m_padding->hash_function(); } RSA_Signature_Operation(const RSA_PrivateKey& rsa, std::string_view padding, RandomNumberGenerator& rng) : - RSA_Private_Operation(rsa, rng), m_emsa(EMSA::create_or_throw(padding)) {} + RSA_Private_Operation(rsa, rng), m_padding(SignaturePaddingScheme::create_or_throw(padding)) {} private: - std::unique_ptr m_emsa; + std::unique_ptr m_padding; }; AlgorithmIdentifier RSA_Signature_Operation::algorithm_identifier() const { - const std::string emsa_name = m_emsa->name(); + const std::string padding_name = m_padding->name(); try { - const std::string full_name = "RSA/" + emsa_name; + const std::string full_name = "RSA/" + padding_name; const OID oid = OID::from_string(full_name); return AlgorithmIdentifier(oid, AlgorithmIdentifier::USE_EMPTY_PARAM); } catch(Lookup_Error&) {} - if(emsa_name.starts_with("PSS(")) { - auto parameters = PSS_Params::from_emsa_name(m_emsa->name()).serialize(); + if(padding_name.starts_with("PSS(")) { + auto parameters = PSS_Params::from_padding_name(m_padding->name()).serialize(); return AlgorithmIdentifier("RSA/PSS", parameters); } - throw Invalid_Argument(fmt("Signatures using RSA/{} are not supported", emsa_name)); + throw Invalid_Argument(fmt("Signatures using RSA/{} are not supported", padding_name)); } -class RSA_Decryption_Operation final : public PK_Ops::Decryption_with_EME, +class RSA_Decryption_Operation final : public PK_Ops::Decryption_with_Padding, private RSA_Private_Operation { public: - RSA_Decryption_Operation(const RSA_PrivateKey& rsa, std::string_view eme, RandomNumberGenerator& rng) : - PK_Ops::Decryption_with_EME(eme), RSA_Private_Operation(rsa, rng) {} + RSA_Decryption_Operation(const RSA_PrivateKey& rsa, std::string_view padding, RandomNumberGenerator& rng) : + PK_Ops::Decryption_with_Padding(padding), RSA_Private_Operation(rsa, rng) {} size_t plaintext_length(size_t /*ctext_len*/) const override { return public_modulus_bytes(); } @@ -659,18 +734,18 @@ std::shared_ptr m_public; }; -class RSA_Encryption_Operation final : public PK_Ops::Encryption_with_EME, +class RSA_Encryption_Operation final : public PK_Ops::Encryption_with_Padding, private RSA_Public_Operation { public: - RSA_Encryption_Operation(const RSA_PublicKey& rsa, std::string_view eme) : - PK_Ops::Encryption_with_EME(eme), RSA_Public_Operation(rsa) {} + RSA_Encryption_Operation(const RSA_PublicKey& rsa, std::string_view padding) : + PK_Ops::Encryption_with_Padding(padding), RSA_Public_Operation(rsa) {} size_t ciphertext_length(size_t /*ptext_len*/) const override { return public_modulus_bytes(); } size_t max_ptext_input_bits() const override { return public_modulus_bits() - 1; } std::vector raw_encrypt(std::span input, RandomNumberGenerator& /*rng*/) override { - BigInt input_bn(input); + const BigInt input_bn(input); return public_op(input_bn).serialize(public_modulus_bytes()); } }; @@ -678,29 +753,29 @@ class RSA_Verify_Operation final : public PK_Ops::Verification, private RSA_Public_Operation { public: - void update(std::span msg) override { m_emsa->update(msg.data(), msg.size()); } + void update(std::span msg) override { m_padding->update(msg.data(), msg.size()); } bool is_valid_signature(std::span sig) override { - const auto msg = m_emsa->raw_data(); + const auto msg = m_padding->raw_data(); const auto message_repr = recover_message_repr(sig.data(), sig.size()); - return m_emsa->verify(message_repr, msg, public_modulus_bits() - 1); + return m_padding->verify(message_repr, msg, public_modulus_bits() - 1); } RSA_Verify_Operation(const RSA_PublicKey& rsa, std::string_view padding) : - RSA_Public_Operation(rsa), m_emsa(EMSA::create_or_throw(padding)) {} + RSA_Public_Operation(rsa), m_padding(SignaturePaddingScheme::create_or_throw(padding)) {} - std::string hash_function() const override { return m_emsa->hash_function(); } + std::string hash_function() const override { return m_padding->hash_function(); } private: std::vector recover_message_repr(const uint8_t input[], size_t input_len) { if(input_len > public_modulus_bytes()) { throw Decoding_Error("RSA signature too large to be valid for this key"); } - BigInt input_bn(input, input_len); + const BigInt input_bn(input, input_len); return public_op(input_bn).serialize(); } - std::unique_ptr m_emsa; + std::unique_ptr m_padding; }; class RSA_KEM_Encryption_Operation final : public PK_Ops::KEM_Encryption_with_KDF, @@ -717,7 +792,7 @@ void raw_kem_encrypt(std::span out_encapsulated_key, std::span raw_shared_key, RandomNumberGenerator& rng) override { - const BigInt r = BigInt::random_integer(rng, 1, get_n()); + const BigInt r = BigInt::random_integer(rng, BigInt::one(), get_n()); const BigInt c = public_op(r); c.serialize_to(out_encapsulated_key); @@ -764,13 +839,19 @@ std::string padding = sig_info[1]; + if(padding != "PSS") { + if(!alg_id.parameters_are_null_or_empty()) { + throw Decoding_Error("Non-PSS RSA signature algorithm OID has unexpected parameters"); + } + } + if(padding == "PSS") { // "MUST contain RSASSA-PSS-params" if(alg_id.parameters().empty()) { throw Decoding_Error("PSS params must be provided"); } - PSS_Params pss_params(alg_id.parameters()); + const PSS_Params pss_params(alg_id.parameters()); // hash_algo must be SHA1, SHA2-224, SHA2-256, SHA2-384 or SHA2-512 // We also support SHA-3 (is also supported by e.g. OpenSSL and bouncycastle) diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/rsa/rsa.h botan3-3.12.0+dfsg/src/lib/pubkey/rsa/rsa.h --- botan3-3.7.1+dfsg/src/lib/pubkey/rsa/rsa.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/rsa/rsa.h 2026-05-07 01:38:28.000000000 +0000 @@ -40,7 +40,7 @@ std::string algo_name() const override { return "RSA"; } - bool check_key(RandomNumberGenerator& rng, bool) const override; + bool check_key(RandomNumberGenerator& rng, bool strong) const override; AlgorithmIdentifier algorithm_identifier() const override; @@ -88,7 +88,7 @@ void init(BigInt&& n, BigInt&& e); - std::shared_ptr m_public; + std::shared_ptr m_public; // NOLINT(*non-private-member-variable*) }; /** @@ -98,8 +98,8 @@ BOTAN_DIAGNOSTIC_PUSH BOTAN_DIAGNOSTIC_IGNORE_INHERITED_VIA_DOMINANCE -class BOTAN_PUBLIC_API(2, 0) RSA_PrivateKey final : public Private_Key, - public RSA_PublicKey { +class BOTAN_PUBLIC_API(2, 0) RSA_PrivateKey final : public virtual Private_Key, + public virtual RSA_PublicKey { public: /** * Load a private key. @@ -135,7 +135,7 @@ std::unique_ptr public_key() const override; - bool check_key(RandomNumberGenerator& rng, bool) const override; + bool check_key(RandomNumberGenerator& rng, bool strong) const override; const BigInt& get_int_field(std::string_view field) const override; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/sm2/sm2.cpp botan3-3.12.0+dfsg/src/lib/pubkey/sm2/sm2.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/sm2/sm2.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/sm2/sm2.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,10 +8,10 @@ #include +#include #include #include #include -#include #include namespace Botan { @@ -20,6 +20,10 @@ return "SM2"; } +std::optional SM2_PublicKey::_signature_element_size_for_DER_encoding() const { + return domain().get_order_bytes(); +} + std::unique_ptr SM2_PrivateKey::public_key() const { return std::make_unique(domain(), _public_ec_point()); } @@ -50,21 +54,31 @@ m_da_inv((this->_private_key() + EC_Scalar::one(domain())).invert()), m_da_inv_legacy(m_da_inv.to_bigint()) {} -SM2_PrivateKey::SM2_PrivateKey(EC_Group group, EC_Scalar x) : - EC_PrivateKey(std::move(group), std::move(x)), +SM2_PrivateKey::SM2_PrivateKey(const EC_Group& group, const EC_Scalar& x) : + EC_PrivateKey(group, x), m_da_inv((this->_private_key() + EC_Scalar::one(domain())).invert()), m_da_inv_legacy(m_da_inv.to_bigint()) {} -SM2_PrivateKey::SM2_PrivateKey(RandomNumberGenerator& rng, EC_Group group) : - EC_PrivateKey(rng, std::move(group)), +SM2_PrivateKey::SM2_PrivateKey(RandomNumberGenerator& rng, const EC_Group& group) : + EC_PrivateKey(rng, group), m_da_inv((this->_private_key() + EC_Scalar::one(domain())).invert()), m_da_inv_legacy(m_da_inv.to_bigint()) {} -SM2_PrivateKey::SM2_PrivateKey(RandomNumberGenerator& rng, EC_Group group, const BigInt& x) : - EC_PrivateKey(rng, std::move(group), x), +SM2_PrivateKey::SM2_PrivateKey(RandomNumberGenerator& rng, const EC_Group& group, const BigInt& x) : + EC_PrivateKey(rng, group, x), m_da_inv((this->_private_key() + EC_Scalar::one(domain())).invert()), m_da_inv_legacy(m_da_inv.to_bigint()) {} +#if defined(BOTAN_HAS_LEGACY_EC_POINT) +std::vector sm2_compute_za(HashFunction& hash, + std::string_view user_id, + const EC_Group& group, + const EC_Point& pubkey) { + auto apoint = EC_AffinePoint(group, pubkey); + return sm2_compute_za(hash, user_id, group, apoint); +} +#endif + std::vector sm2_compute_za(HashFunction& hash, std::string_view user_id, const EC_Group& group, @@ -131,7 +145,6 @@ std::vector m_za; secure_vector m_digest; std::unique_ptr m_hash; - std::vector m_ws; }; std::vector SM2_Signature_Operation::sign(RandomNumberGenerator& rng) { @@ -150,9 +163,14 @@ const auto k = EC_Scalar::random(m_group, rng); - const auto r = EC_Scalar::gk_x_mod_order(k, rng, m_ws) + e; + const auto r = EC_Scalar::gk_x_mod_order(k, rng) + e; const auto s = (k - r * m_x) * m_da_inv; + // With overwhelming probability, a bug rather than actual zero r/s + if(r.is_zero() || s.is_zero()) { + throw Internal_Error("During SM2 signature generated zero r/s"); + } + return EC_Scalar::serialize_pair(r, s); } @@ -253,7 +271,8 @@ std::unique_ptr SM2_PublicKey::create_verification_op(std::string_view params, std::string_view provider) const { if(provider == "base" || provider.empty()) { - std::string userid, hash; + std::string userid; + std::string hash; parse_sm2_param_string(params, userid, hash); return std::make_unique(*this, userid, hash); } @@ -265,7 +284,8 @@ std::string_view params, std::string_view provider) const { if(provider == "base" || provider.empty()) { - std::string userid, hash; + std::string userid; + std::string hash; parse_sm2_param_string(params, userid, hash); return std::make_unique(*this, userid, hash); } diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/sm2/sm2.h botan3-3.12.0+dfsg/src/lib/pubkey/sm2/sm2.h --- botan3-3.7.1+dfsg/src/lib/pubkey/sm2/sm2.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/sm2/sm2.h 2026-05-07 01:38:28.000000000 +0000 @@ -8,6 +8,8 @@ #ifndef BOTAN_SM2_KEY_H_ #define BOTAN_SM2_KEY_H_ +#include +#include #include namespace Botan { @@ -53,9 +55,7 @@ return (op == PublicKeyOperation::Signature || op == PublicKeyOperation::Encryption); } - std::optional _signature_element_size_for_DER_encoding() const override { - return domain().get_order_bytes(); - } + std::optional _signature_element_size_for_DER_encoding() const override; std::unique_ptr create_verification_op(std::string_view params, std::string_view provider) const override; @@ -90,14 +90,14 @@ * @param group curve parameters to bu used for this key * @param x the private key */ - SM2_PrivateKey(EC_Group group, EC_Scalar x); + SM2_PrivateKey(const EC_Group& group, const EC_Scalar& x); /** * Create a new private key * @param rng a random number generator * @param group parameters to used for this key */ - SM2_PrivateKey(RandomNumberGenerator& rng, EC_Group group); + SM2_PrivateKey(RandomNumberGenerator& rng, const EC_Group& group); /** * Create a private key. @@ -106,9 +106,9 @@ * @param x the private key (if zero, generate a new random key) */ BOTAN_DEPRECATED("Use one of the other constructors") - SM2_PrivateKey(RandomNumberGenerator& rng, EC_Group group, const BigInt& x); + SM2_PrivateKey(RandomNumberGenerator& rng, const EC_Group& group, const BigInt& x); - bool check_key(RandomNumberGenerator& rng, bool) const override; + bool check_key(RandomNumberGenerator& rng, bool strong) const override; std::unique_ptr public_key() const override; @@ -120,6 +120,7 @@ std::string_view params, std::string_view provider) const override; + // TODO(Botan4) remove this and the member variable BOTAN_DEPRECATED("Deprecated no replacement") const BigInt& get_da_inv() const { return m_da_inv_legacy; } const EC_Scalar& _get_da_inv() const { return m_da_inv; } @@ -152,10 +153,7 @@ inline std::vector sm2_compute_za(HashFunction& hash, std::string_view user_id, const EC_Group& group, - const EC_Point& pubkey) { - auto apoint = EC_AffinePoint(group, pubkey); - return sm2_compute_za(hash, user_id, group, apoint); -} + const EC_Point& pubkey); #endif // For compat with versions 2.2 - 2.7 diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/sm2/sm2_enc.cpp botan3-3.12.0+dfsg/src/lib/pubkey/sm2/sm2_enc.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/sm2/sm2_enc.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/sm2/sm2_enc.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,8 +9,10 @@ #include #include +#include #include #include +#include #include #include #include @@ -42,9 +44,9 @@ std::vector encrypt(std::span msg, RandomNumberGenerator& rng) override { const auto k = EC_Scalar::random(m_group, rng); - const EC_AffinePoint C1 = EC_AffinePoint::g_mul(k, rng, m_ws); + const EC_AffinePoint C1 = EC_AffinePoint::g_mul(k, rng); - const EC_AffinePoint kPB = m_peer.mul(k, rng, m_ws); + const EC_AffinePoint kPB = m_peer.mul(k, rng); const auto x2_bytes = kPB.x_bytes(); const auto y2_bytes = kPB.y_bytes(); @@ -80,7 +82,6 @@ const EC_AffinePoint m_peer; std::unique_ptr m_hash; std::unique_ptr m_kdf; - std::vector m_ws; }; class SM2_Decryption_Operation final : public PK_Ops::Decryption { @@ -119,10 +120,12 @@ return secure_vector(); } - BigInt x1, y1; - secure_vector C3, masked_msg; + BigInt x1; + BigInt y1; + secure_vector C3; + secure_vector masked_msg; - BER_Decoder(ctext) + BER_Decoder(ctext, BER_Decoder::Limits::DER()) .start_sequence() .decode(x1) .decode(y1) @@ -131,31 +134,19 @@ .end_cons() .verify_end(); - std::vector recode_ctext; - DER_Encoder(recode_ctext) - .start_sequence() - .encode(x1) - .encode(y1) - .encode(C3, ASN1_Type::OctetString) - .encode(masked_msg, ASN1_Type::OctetString) - .end_cons(); - - if(recode_ctext.size() != ctext.size()) { - return secure_vector(); - } - - if(CT::is_equal(recode_ctext.data(), ctext.data(), ctext.size()).as_bool() == false) { + // Wrong length so certainly invalid, reject immediately + if(C3.size() != m_hash->output_length()) { return secure_vector(); } auto C1 = EC_AffinePoint::from_bigint_xy(m_group, x1, y1); - // Here C1 is publically invalid, so no problem with early return: + // Here C1 is publicly invalid, so no problem with early return: if(!C1) { return secure_vector(); } - const auto dbC1 = C1->mul(m_x, m_rng, m_ws); + const auto dbC1 = C1->mul(m_x, m_rng); const auto x2_bytes = dbC1.x_bytes(); const auto y2_bytes = dbC1.y_bytes(); @@ -168,11 +159,13 @@ m_hash->update(y2_bytes); const auto u = m_hash->final(); - if(!CT::is_equal(u.data(), C3.data(), m_hash->output_length()).as_bool()) { - return secure_vector(); - } + const auto mac_ok = CT::is_equal(u, C3); + valid_mask = mac_ok.if_set_return(0xFF); - valid_mask = 0xFF; + // Zero the plaintext if the MAC check failed + (~mac_ok).if_set_zero_out(masked_msg.data(), masked_msg.size()); + const size_t output_len = CT::Mask::expand(mac_ok).if_set_return(masked_msg.size()); + masked_msg.resize(output_len); return masked_msg; } @@ -180,7 +173,6 @@ const EC_Group m_group; const EC_Scalar m_x; RandomNumberGenerator& m_rng; - std::vector m_ws; std::unique_ptr m_hash; std::unique_ptr m_kdf; }; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_address.h botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_address.h --- botan3-3.7.1+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_address.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_address.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,15 +9,13 @@ #ifndef BOTAN_SPHINCS_PLUS_ADDRESS_H_ #define BOTAN_SPHINCS_PLUS_ADDRESS_H_ -#include - -#include #include #include +#include namespace Botan { -enum class Sphincs_Address_Type : uint32_t { +enum class Sphincs_Address_Type : uint32_t /* NOLINT(*-enum-size) */ { WotsHash = 0, WotsPublicKeyCompression = 1, HashTree = 2, @@ -46,12 +44,11 @@ public: using enum Sphincs_Address_Type; - Sphincs_Address(Sphincs_Address_Type type) { - m_address.fill(0); - set_type(type); - } + explicit Sphincs_Address(Sphincs_Address_Type type) : m_address{} { set_type(type); } - Sphincs_Address(std::array address) { std::copy(address.begin(), address.end(), m_address.begin()); } + explicit Sphincs_Address(std::array address) : m_address{} { + std::copy(address.begin(), address.end(), m_address.begin()); + } /* Setter member functions as specified in FIPS 205, Section 4.3 */ @@ -137,7 +134,7 @@ Sphincs_Address_Type get_type() const { return Sphincs_Address_Type(m_address[type_offset]); } std::array to_bytes() const { - std::array result; + std::array result{}; for(unsigned int i = 0; i < m_address.size(); ++i) { store_be(m_address[i], result.data() + (i * 4)); } @@ -145,7 +142,7 @@ } std::array to_bytes_compressed() const { - std::array result; + std::array result{}; result[0] = static_cast(m_address[layer_offset]); store_be(m_address[tree_offset + 1], &result[1]); diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_fors.cpp botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_fors.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_fors.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_fors.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -11,14 +11,14 @@ #include #include -#include #include +#include +#include #include #include #include #include -#include namespace Botan { @@ -78,7 +78,7 @@ BufferStuffer roots(roots_buffer); BufferStuffer sig(sig_out); - // Buffer to hold the FORS leafs during tree traversal + // Buffer to hold the FORS leaves during tree traversal // (Avoids a secure_vector allocation/deallocation in the hot path) ForsLeafSecret fors_leaf_secret(params.n()); @@ -86,7 +86,7 @@ // and the trees' root and append the signature respectively BOTAN_ASSERT_NOMSG(indices.size() == params.k()); for(uint32_t i = 0; i < params.k(); ++i) { - uint32_t idx_offset = i * (1 << params.a()); + const uint32_t idx_offset = i * (1 << params.a()); // Compute the secret leaf given by the chunk of the message and append it to the signature fors_tree_addr.set_type(Sphincs_Address_Type::ForsKeyGeneration) @@ -98,7 +98,8 @@ // Compute the authentication path and root for this leaf node fors_tree_addr.set_type(Sphincs_Address_Type::ForsTree); - GenerateLeafFunction fors_gen_leaf = [&](StrongSpan out_root, TreeNodeIndex address_index) { + const GenerateLeafFunction fors_gen_leaf = [&](StrongSpan out_root, + TreeNodeIndex address_index) { fors_tree_addr.set_tree_index(address_index); fors_tree_addr.set_type(Sphincs_Address_Type::ForsKeyGeneration); @@ -145,7 +146,7 @@ // leaf and the authentication path offered in the FORS signature. BOTAN_ASSERT_NOMSG(indices.size() == params.k()); for(uint32_t i = 0; i < params.k(); ++i) { - uint32_t idx_offset = i * (1 << params.a()); + const uint32_t idx_offset = i * (1 << params.a()); // Compute the FORS leaf by using the secret leaf contained in the signature fors_tree_addr.set_tree_height(TreeLayerIndex(0)).set_tree_index(indices[i] + idx_offset); diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_hash.cpp botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_hash.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_hash.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_hash.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,12 +8,12 @@ #include -#include - #include #include #include #include +#include +#include #if defined(BOTAN_HAS_SPHINCS_PLUS_SHAKE_BASE) #include @@ -23,8 +23,6 @@ #include #endif -#include - namespace Botan { Sphincs_Hash_Functions::Sphincs_Hash_Functions(const Sphincs_Parameters& sphincs_params, diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_hash.h botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_hash.h --- botan3-3.7.1+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_hash.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_hash.h 2026-05-07 01:38:28.000000000 +0000 @@ -20,7 +20,7 @@ * A collection of pseudorandom hash functions required for SLH-DSA * computations. See FIPS 205, Section 11.2.1 and 11.2.2. **/ -class BOTAN_TEST_API Sphincs_Hash_Functions { +class BOTAN_TEST_API Sphincs_Hash_Functions /* NOLINT(*-special-member-functions) */ { public: virtual ~Sphincs_Hash_Functions() = default; @@ -54,16 +54,16 @@ const SphincsMessageInternal& msg) = 0; template - void T(std::span out, const Sphincs_Address& address, BufferTs&&... in) { - auto& hash = tweak_hash(address, (std::forward(in).size() + ...)); - (hash.update(std::forward(in)), ...); + void T(std::span out, const Sphincs_Address& address, const BufferTs&... in) { + auto& hash = tweak_hash(address, (in.size() + ...)); + (hash.update(in), ...); hash.final(out); } template , typename... BufferTs> - OutT T(const Sphincs_Address& address, BufferTs&&... in) { + OutT T(const Sphincs_Address& address, const BufferTs&... in) { OutT t(m_sphincs_params.n()); - T(t, address, std::forward(in)...); + T(t, address, in...); return t; } @@ -99,8 +99,8 @@ const SphincsTreeNode& root, const SphincsMessageInternal& message) = 0; - const Sphincs_Parameters& m_sphincs_params; - const SphincsPublicSeed& m_pub_seed; + const Sphincs_Parameters& m_sphincs_params; // NOLINT(*non-private-member-variable*) + const SphincsPublicSeed& m_pub_seed; // NOLINT(*non-private-member-variable*) }; } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_hypertree.cpp botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_hypertree.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_hypertree.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_hypertree.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,12 +9,13 @@ #include #include +#include +#include #include #include #include #include #include -#include namespace Botan { diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_hypertree.h botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_hypertree.h --- botan3-3.7.1+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_hypertree.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_hypertree.h 2026-05-07 01:38:28.000000000 +0000 @@ -15,7 +15,6 @@ namespace Botan { -class Sphincs_Address; class Sphincs_Hash_Functions; class Sphincs_Parameters; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_parameters.cpp botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_parameters.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_parameters.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_parameters.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,13 +8,11 @@ #include -#include +#include #include #include #include -#include - namespace Botan { namespace { diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_parameters.h botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_parameters.h --- botan3-3.7.1+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_parameters.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_parameters.h 2026-05-07 01:38:28.000000000 +0000 @@ -15,13 +15,13 @@ namespace Botan { -enum class Sphincs_Hash_Type { +enum class Sphincs_Hash_Type : uint8_t { Shake256, Sha256, Haraka BOTAN_DEPRECATED("Haraka is not and will not be supported"), ///< Haraka is currently not supported }; -enum class Sphincs_Parameter_Set { +enum class Sphincs_Parameter_Set : uint8_t { Sphincs128Small, Sphincs128Fast, Sphincs192Small, diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_treehash.cpp botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_treehash.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_treehash.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_treehash.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,9 +8,9 @@ #include +#include #include #include -#include namespace Botan { diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_types.h botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_types.h --- botan3-3.7.1+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_types.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_types.h 2026-05-07 01:38:28.000000000 +0000 @@ -99,4 +99,4 @@ } // namespace Botan -#endif \ No newline at end of file +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_wots.cpp botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_wots.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_wots.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_wots.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,8 +8,9 @@ #include +#include +#include #include -#include namespace Botan { namespace { @@ -78,7 +79,7 @@ // Convert checksum to base_w. csum = csum << ((8 - ((params.wots_len_2() * params.log_w()) % 8)) % 8); - std::array csum_bytes; + std::array csum_bytes{}; store_be(csum, csum_bytes.data()); const size_t csum_bytes_size = params.wots_checksum_bytes(); @@ -143,7 +144,7 @@ BOTAN_ASSERT_NOMSG(!sign_leaf_idx.has_value() || wots_steps.size() == params.wots_len()); BOTAN_ASSERT_NOMSG(pk_addr.get_type() == Sphincs_Address_Type::WotsPublicKeyCompression); - secure_vector wots_sig; + const secure_vector wots_sig; WotsPublicKey wots_pk_buffer(params.wots_bytes()); BufferStuffer wots_pk(wots_pk_buffer); @@ -154,7 +155,7 @@ for(WotsChainIndex i(0); i < params.wots_len(); i++) { // If the current leaf is part of the signature wots_k stores the chain index - // of the value neccessary for the signature. Otherwise: nullopt (no signature) + // of the value necessary for the signature. Otherwise: nullopt (no signature) const auto wots_k = [&]() -> std::optional { if(sign_leaf_idx.has_value() && leaf_idx == sign_leaf_idx.value()) { return wots_steps[i.get()]; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_xmss.cpp botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_xmss.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_xmss.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_xmss.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,10 +8,10 @@ #include +#include #include #include #include -#include #include namespace Botan { @@ -43,7 +43,7 @@ pk_addr.set_type(Sphincs_Address_Type::WotsPublicKeyCompression); - GenerateLeafFunction xmss_gen_leaf = [&](StrongSpan out_root, TreeNodeIndex address_index) { + const GenerateLeafFunction xmss_gen_leaf = [&](StrongSpan out_root, TreeNodeIndex address_index) { wots_sign_and_pkgen( wots_bytes_s, out_root, secret_seed, address_index, idx_leaf, steps, leaf_addr, pk_addr, params, hashes); }; @@ -62,7 +62,7 @@ // code to have just one treehash routine that computes both root and path // in one function. SphincsXmssSignature dummy_sig(params.xmss_tree_height() * params.n() + params.wots_bytes()); - SphincsTreeNode dummy_root(params.n()); + const SphincsTreeNode dummy_root(params.n()); Sphincs_Address top_tree_addr(Sphincs_Address_Type::HashTree); Sphincs_Address wots_addr(Sphincs_Address_Type::WotsPublicKeyCompression); diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus.cpp botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,6 +9,9 @@ #include #include +#include +#include +#include #include #include #include @@ -18,7 +21,6 @@ #include #include #include -#include #include @@ -159,8 +161,10 @@ return m_public->parameters().object_identifier(); } -bool SphincsPlus_PublicKey::check_key(RandomNumberGenerator&, bool) const { - // Nothing to check. It's literally just hashes. :-) +bool SphincsPlus_PublicKey::check_key(RandomNumberGenerator& /*rng*/, bool /*strong*/) const { + // Nothing to check for the public key. It's literally just hashes. :-) + // A sign/verify roundtrip for the private key could be added for strong + // validation, but SLH-DSA signing is very expensive. return true; } @@ -178,9 +182,11 @@ return std::make_unique(rng, m_public->parameters()); } +namespace { + class SphincsPlus_Verification_Operation final : public PK_Ops::Verification { public: - SphincsPlus_Verification_Operation(std::shared_ptr pub_key) : + explicit SphincsPlus_Verification_Operation(std::shared_ptr pub_key) : m_public(std::move(pub_key)), m_hashes(Botan::Sphincs_Hash_Functions::create(m_public->parameters(), m_public->seed())), m_context(/* TODO: Add API */ {}) { @@ -241,6 +247,8 @@ SphincsContext m_context; }; +} // namespace + std::unique_ptr SphincsPlus_PublicKey::create_verification_op(std::string_view /*params*/, std::string_view provider) const { if(provider.empty() || provider == "base") { @@ -338,6 +346,8 @@ return std::make_unique(*this); } +namespace { + class SphincsPlus_Signature_Operation final : public PK_Ops::Signature { public: SphincsPlus_Signature_Operation(std::shared_ptr private_key, @@ -427,6 +437,8 @@ SphincsContext m_context; }; +} // namespace + std::unique_ptr SphincsPlus_PrivateKey::create_signature_op(RandomNumberGenerator& rng, std::string_view params, std::string_view provider) const { diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus.h botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus.h --- botan3-3.7.1+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus.h 2026-05-07 01:38:28.000000000 +0000 @@ -36,6 +36,11 @@ ~SphincsPlus_PublicKey() override; + SphincsPlus_PublicKey(const SphincsPlus_PublicKey& other) = default; + SphincsPlus_PublicKey(SphincsPlus_PublicKey&& other) = default; + SphincsPlus_PublicKey& operator=(const SphincsPlus_PublicKey& other) = default; + SphincsPlus_PublicKey& operator=(SphincsPlus_PublicKey&& other) = default; + size_t key_length() const override; std::string algo_name() const override; @@ -60,7 +65,7 @@ protected: SphincsPlus_PublicKey() = default; - std::shared_ptr m_public; + std::shared_ptr m_public; // NOLINT(*non-private-member-variable*) }; BOTAN_DIAGNOSTIC_PUSH @@ -98,6 +103,11 @@ ~SphincsPlus_PrivateKey() override; + SphincsPlus_PrivateKey(const SphincsPlus_PrivateKey& other) = default; + SphincsPlus_PrivateKey(SphincsPlus_PrivateKey&& other) = default; + SphincsPlus_PrivateKey& operator=(const SphincsPlus_PrivateKey& other) = delete; + SphincsPlus_PrivateKey& operator=(SphincsPlus_PrivateKey&& other) = delete; + secure_vector private_key_bits() const override; secure_vector raw_private_key_bits() const override; std::unique_ptr public_key() const override; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus_sha2_base/sp_hash_sha2.h botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus_sha2_base/sp_hash_sha2.h --- botan3-3.7.1+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus_sha2_base/sp_hash_sha2.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus_sha2_base/sp_hash_sha2.h 2026-05-07 01:38:28.000000000 +0000 @@ -11,11 +11,11 @@ #include +#include #include #include #include #include -#include #include namespace Botan { @@ -55,7 +55,7 @@ std::vector mgf1_input = concat>(r, m_pub_seed, r_pk_buffer); std::vector digest(m_sphincs_params.h_msg_digest_bytes()); - mgf1_mask(*m_sha_x_full, mgf1_input.data(), mgf1_input.size(), digest.data(), digest.size()); + mgf1_mask(*m_sha_x_full, mgf1_input, digest); return digest; } diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus_shake_base/sp_hash_shake.h botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus_shake_base/sp_hash_shake.h --- botan3-3.7.1+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus_shake_base/sp_hash_shake.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus_shake_base/sp_hash_shake.h 2026-05-07 01:38:28.000000000 +0000 @@ -42,11 +42,8 @@ public: Sphincs_Hash_Functions_Shake(const Sphincs_Parameters& sphincs_params, const SphincsPublicSeed& pub_seed) : Sphincs_Hash_Functions(sphincs_params, pub_seed), - m_seeded_hash(sphincs_params.n() * 8), m_hash(sphincs_params.n() * 8), - m_h_msg_hash(8 * sphincs_params.h_msg_digest_bytes()) { - m_seeded_hash.update(m_pub_seed); - } + m_h_msg_hash(8 * sphincs_params.h_msg_digest_bytes()) {} void PRF_msg(StrongSpan out, StrongSpan sk_prf, @@ -62,7 +59,6 @@ std::string msg_hash_function_name() const override { return m_h_msg_hash.name(); } private: - SHAKE_256 m_seeded_hash; SHAKE_256 m_hash; SHAKE_256 m_h_msg_hash; }; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/stateful_key_index/info.txt botan3-3.12.0+dfsg/src/lib/pubkey/stateful_key_index/info.txt --- botan3-3.7.1+dfsg/src/lib/pubkey/stateful_key_index/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/stateful_key_index/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,9 @@ + +name -> "Stateful Key Index" +brief -> "Tracks updates for stateful signing algorithms" +type -> "Internal" + + + +sha2_32 + diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/stateful_key_index/stateful_key_index_registry.cpp botan3-3.12.0+dfsg/src/lib/pubkey/stateful_key_index/stateful_key_index_registry.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/stateful_key_index/stateful_key_index_registry.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/stateful_key_index/stateful_key_index_registry.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,82 @@ +/* + * (C) 2016 Matthias Gierlings + * 2026 Jack Lloyd + * + * Botan is released under the Simplified BSD License (see license.txt) + */ + +#include + +#include +#include + +namespace Botan { + +Stateful_Key_Index_Registry& Stateful_Key_Index_Registry::global() { + static Stateful_Key_Index_Registry g_registry; + return g_registry; +} + +Stateful_Key_Index_Registry::Stateful_Key_Index_Registry() = default; + +Stateful_Key_Index_Registry::~Stateful_Key_Index_Registry() = default; + +Stateful_Key_Index_Registry::KeyId::KeyId(std::string_view algo_name, + uint32_t algo_params, + std::span key_material_1, + std::span key_material_2) : + m_val() { + auto hash = HashFunction::create_or_throw("SHA-256"); + + hash->update("Botan Stateful_Key_Index_Registry KeyID"); + hash->update_be(static_cast(algo_name.size())); + hash->update(algo_name); + hash->update_be(algo_params); + hash->update_be(static_cast(key_material_1.size())); + hash->update(key_material_1); + hash->update_be(static_cast(key_material_2.size())); + hash->update(key_material_2); + + BOTAN_ASSERT_NOMSG(hash->output_length() == m_val.size()); + + hash->final(m_val); +} + +// Lock must be held while this function is called +Stateful_Key_Index_Registry::RegistryMap::iterator Stateful_Key_Index_Registry::lookup(const KeyId& key_id) { + auto [i, _inserted] = m_registry.emplace(key_id, 0); + return i; +} + +uint64_t Stateful_Key_Index_Registry::current_index(const KeyId& key_id) { + const lock_guard_type lock(m_mutex); + auto idx = this->lookup(key_id); + return idx->second; +} + +uint64_t Stateful_Key_Index_Registry::reserve_next_index(const KeyId& key_id) { + const lock_guard_type lock(m_mutex); + auto idx = this->lookup(key_id); + const uint64_t cur = idx->second; + idx->second += 1; + return cur; +} + +void Stateful_Key_Index_Registry::set_index_lower_bound(const KeyId& key_id, uint64_t min) { + const lock_guard_type lock(m_mutex); + auto idx = this->lookup(key_id); + idx->second = std::max(idx->second, min); +} + +uint64_t Stateful_Key_Index_Registry::remaining_operations(const KeyId& key_id, uint64_t max) { + const lock_guard_type lock(m_mutex); + const uint64_t idx = this->lookup(key_id)->second; + + if(idx >= max) { + return 0; + } else { + return max - idx; + } +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/stateful_key_index/stateful_key_index_registry.h botan3-3.12.0+dfsg/src/lib/pubkey/stateful_key_index/stateful_key_index_registry.h --- botan3-3.7.1+dfsg/src/lib/pubkey/stateful_key_index/stateful_key_index_registry.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/stateful_key_index/stateful_key_index_registry.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,97 @@ +/* + * (C) 2016 Matthias Gierlings + * 2026 Jack Lloyd + * + * Botan is released under the Simplified BSD License (see license.txt) + */ + +#ifndef BOTAN_STATEFUL_KEY_INDEX_REGISTRY_H_ +#define BOTAN_STATEFUL_KEY_INDEX_REGISTRY_H_ + +#include +#include +#include +#include +#include +#include + +namespace Botan { + +/** + * A process-wide registry mapping stateful key identity to a shared + * atomic counter. Ensures that independent copies of the same key + * material (e.g. deserialized separately) share a single leaf index, + * preventing catastrophic one-time signature reuse. + * + * Used by XMSS and HSS-LMS. + */ +class Stateful_Key_Index_Registry final { + public: + class KeyId final { + public: + /** + * Create a KeyId for some kind of key material + * + * @param algo_name Algorithm name (ex "XMSS", "HSS-LMS") + * @param algo_params Algorithm specific parameters + * @param key_material_1 First part of key identifying material + * @param key_material_2 Second part of key identifying material (can be omitted) + */ + KeyId(std::string_view algo_name, + uint32_t algo_params, + std::span key_material_1, + std::span key_material_2); + + KeyId() = default; + + auto operator<=>(const KeyId& other) const = default; + + private: + std::array m_val; + }; + + Stateful_Key_Index_Registry(const Stateful_Key_Index_Registry&) = delete; + Stateful_Key_Index_Registry(Stateful_Key_Index_Registry&&) = delete; + Stateful_Key_Index_Registry& operator=(const Stateful_Key_Index_Registry&) = delete; + Stateful_Key_Index_Registry& operator=(Stateful_Key_Index_Registry&&) = delete; + ~Stateful_Key_Index_Registry(); + + /** + * Retrieve the process-wide instance + */ + static Stateful_Key_Index_Registry& global(); + + /** + * Return the current counter + */ + uint64_t current_index(const KeyId& key_id); + + /** + * Return a new counter + */ + uint64_t reserve_next_index(const KeyId& key_id); + + /** + * Set the counter to at least min (but if already higher it will retain its current value) + */ + void set_index_lower_bound(const KeyId& key_id, uint64_t min); + + /** + * If the current counter is >= max returns 0, otherwise max - counter + */ + uint64_t remaining_operations(const KeyId& key_id, uint64_t max); + + private: + typedef std::map RegistryMap; + + RegistryMap::iterator lookup(const KeyId& key_id); + + Stateful_Key_Index_Registry(); + + mutex_type m_mutex; + RegistryMap m_registry; +}; + +} // namespace Botan + +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/workfactor.cpp botan3-3.12.0+dfsg/src/lib/pubkey/workfactor.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/workfactor.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/workfactor.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,11 +1,13 @@ /* * Public Key Work Factor Functions -* (C) 1999-2007,2012 Jack Lloyd +* (C) 1999-2007,2012,2026 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ #include + +#include #include #include @@ -19,7 +21,7 @@ size_t nfs_workfactor(size_t bits, double log2_k) { // approximates natural logarithm of an integer of given bitsize - const double log_p = bits / std::numbers::log2e; + const double log_p = static_cast(bits) / std::numbers::log2e; const double log_log_p = std::log(log_p); @@ -48,26 +50,54 @@ return if_work_factor(bits); } -size_t dl_exponent_size(size_t bits) { - if(bits == 0) { - return 0; - } - if(bits <= 256) { - return bits - 1; - } - if(bits <= 1024) { +size_t dl_exponent_size(size_t p_bits) { + BOTAN_ARG_CHECK(p_bits > 1, "Invalid prime length"); + + /* + For relevant sizes we follow the suggestions in + NIST SP 800-56B Rev 2 Appendix D + "Maximum Security Strength Estimates for IFC Modulus Lengths" + + For sizes outside the range considered in the SP we use some sensible values + + Note that we return twice the value given in Table 4 since we are choosing + the exponent size as twice the estimated security strength. + + See also NIST SP 800-56A Rev 3 Appendix D, Tables 25 and 26 + */ + + if(p_bits <= 256) { + /* + * For stupidly small groups we might return a value larger than the group + * size if we fell into the conditionals below. Just use the maximum + * possible exponent size - for all the good it will do you with a group + * this weak. + */ + return p_bits - 1; + } else if(p_bits <= 1024) { + /* + Not in the SP, but general estimates are that a 1024 bit group provides at + most 80 bits security, so using an exponent appropriate for 96 bit security + is more than sufficient. + */ return 192; + } else if(p_bits <= 2048) { + return 224; // SP 800-56B + } else if(p_bits <= 3072) { + return 256; // SP 800-56B + } else if(p_bits <= 4096) { + return 304; // SP 800-56B + } else if(p_bits <= 6144) { + return 352; // SP 800-56B + } else if(p_bits <= 8192) { + return 400; // SP 800-56B + } else { + // For values larger than we know about, just saturate to 256 bit security + // which is Good Enough for FFDH + // + // NIST puts 15360 bit groups at exactly 256 bits security + return 512; } - if(bits <= 1536) { - return 224; - } - if(bits <= 2048) { - return 256; - } - if(bits <= 4096) { - return 384; - } - return 512; } } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/x25519/donna.cpp botan3-3.12.0+dfsg/src/lib/pubkey/x25519/donna.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/x25519/donna.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/x25519/donna.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -37,7 +37,6 @@ #include #include #include -#include namespace Botan { @@ -113,11 +112,11 @@ * On return, out[i] < 2**52 */ inline void fmul(uint64_t out[5], const uint64_t in[5], const uint64_t in2[5]) { - const uint128_t s0 = in2[0]; - const uint128_t s1 = in2[1]; - const uint128_t s2 = in2[2]; - const uint128_t s3 = in2[3]; - const uint128_t s4 = in2[4]; + const auto s0 = uint128_t(in2[0]); + const auto s1 = uint128_t(in2[1]); + const auto s2 = uint128_t(in2[2]); + const auto s3 = uint128_t(in2[3]); + const auto s4 = uint128_t(in2[4]); uint64_t r0 = in[0]; uint64_t r1 = in[1]; @@ -153,10 +152,10 @@ uint64_t c = carry_shift(t4, 51); r0 += c * 19; - c = r0 >> 51; + c = r0 >> 51U; r0 = r0 & MASK_63; r1 += c; - c = r1 >> 51; + c = r1 >> 51U; r1 = r1 & MASK_63; r2 += c; @@ -181,7 +180,7 @@ const uint64_t d419 = r4 * 19; const uint64_t d4 = d419 * 2; - uint128_t t0 = uint128_t(r0) * r0 + uint128_t(d4) * r1 + uint128_t(d2) * (r3); + const uint128_t t0 = uint128_t(r0) * r0 + uint128_t(d4) * r1 + uint128_t(d2) * (r3); uint128_t t1 = uint128_t(d0) * r1 + uint128_t(d4) * r2 + uint128_t(r3) * (r3 * 19); uint128_t t2 = uint128_t(d0) * r2 + uint128_t(r1) * r1 + uint128_t(d4) * (r3); uint128_t t3 = uint128_t(d0) * r3 + uint128_t(d1) * r2 + uint128_t(r4) * (d419); @@ -199,10 +198,10 @@ uint64_t c = carry_shift(t4, 51); r0 += c * 19; - c = r0 >> 51; + c = r0 >> 51U; r0 = r0 & MASK_63; r1 += c; - c = r1 >> 51; + c = r1 >> 51U; r1 = r1 & MASK_63; r2 += c; } @@ -227,22 +226,22 @@ * little-endian, 32-byte array */ inline void fcontract(uint8_t* out, const uint64_t input[5]) { - uint128_t t0 = input[0]; - uint128_t t1 = input[1]; - uint128_t t2 = input[2]; - uint128_t t3 = input[3]; - uint128_t t4 = input[4]; + auto t0 = uint128_t(input[0]); + auto t1 = uint128_t(input[1]); + auto t2 = uint128_t(input[2]); + auto t3 = uint128_t(input[3]); + auto t4 = uint128_t(input[4]); for(size_t i = 0; i != 2; ++i) { - t1 += t0 >> 51; + t1 += t0 >> 51U; t0 &= MASK_63; - t2 += t1 >> 51; + t2 += t1 >> 51U; t1 &= MASK_63; - t3 += t2 >> 51; + t3 += t2 >> 51U; t2 &= MASK_63; - t4 += t3 >> 51; + t4 += t3 >> 51U; t3 &= MASK_63; - t0 += (t4 >> 51) * 19; + t0 += (t4 >> 51U) * 19; t4 &= MASK_63; } @@ -251,15 +250,15 @@ t0 += 19; - t1 += t0 >> 51; + t1 += t0 >> 51U; t0 &= MASK_63; - t2 += t1 >> 51; + t2 += t1 >> 51U; t1 &= MASK_63; - t3 += t2 >> 51; + t3 += t2 >> 51U; t2 &= MASK_63; - t4 += t3 >> 51; + t4 += t3 >> 51U; t3 &= MASK_63; - t0 += (t4 >> 51) * 19; + t0 += (t4 >> 51U) * 19; t4 &= MASK_63; /* now between 19 and 2^255-1 in both cases, and offset by 19. */ @@ -272,13 +271,13 @@ /* now between 2^255 and 2^256-20, and offset by 2^255. */ - t1 += t0 >> 51; + t1 += t0 >> 51U; t0 &= MASK_63; - t2 += t1 >> 51; + t2 += t1 >> 51U; t1 &= MASK_63; - t3 += t2 >> 51; + t3 += t2 >> 51U; t2 &= MASK_63; - t4 += t3 >> 51; + t4 += t3 >> 51U; t3 &= MASK_63; t4 &= MASK_63; @@ -455,7 +454,10 @@ CT::poison(secret, 32); CT::poison(basepoint, 32); - uint64_t bp[5], x[5], z[5], zmone[5]; + uint64_t bp[5]; + uint64_t x[5]; + uint64_t z[5]; + uint64_t zmone[5]; uint8_t e[32]; copy_mem(e, secret, 32); diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/x25519/x25519.cpp botan3-3.12.0+dfsg/src/lib/pubkey/x25519/x25519.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/x25519/x25519.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/x25519/x25519.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -66,13 +66,13 @@ return std::make_unique(rng); } -X25519_PrivateKey::X25519_PrivateKey(const secure_vector& secret_key) { +X25519_PrivateKey::X25519_PrivateKey(std::span secret_key) { if(secret_key.size() != 32) { throw Decoding_Error("Invalid size for X25519 private key"); } m_public.resize(32); - m_private = secret_key; + m_private.assign(secret_key.begin(), secret_key.end()); curve25519_basepoint(m_public.data(), m_private.data()); } @@ -83,7 +83,7 @@ } X25519_PrivateKey::X25519_PrivateKey(const AlgorithmIdentifier& /*unused*/, std::span key_bits) { - BER_Decoder(key_bits).decode(m_private, ASN1_Type::OctetString).discard_remaining(); + BER_Decoder(key_bits, BER_Decoder::Limits::DER()).decode(m_private, ASN1_Type::OctetString).discard_remaining(); size_check(m_private.size(), "private key"); m_public.resize(32); diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/x25519/x25519.h botan3-3.12.0+dfsg/src/lib/pubkey/x25519/x25519.h --- botan3-3.7.1+dfsg/src/lib/pubkey/x25519/x25519.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/x25519/x25519.h 2026-05-07 01:38:28.000000000 +0000 @@ -27,7 +27,9 @@ std::vector public_key_bits() const override; - std::vector public_value() const { return m_public; } + BOTAN_DEPRECATED("Use raw_public_key_bits") std::vector public_value() const { + return raw_public_key_bits(); + } bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::KeyAgreement); } @@ -48,7 +50,7 @@ protected: X25519_PublicKey() = default; - std::vector m_public; + std::vector m_public; // NOLINT(*non-private-member-variable*) }; BOTAN_DIAGNOSTIC_PUSH @@ -75,9 +77,9 @@ * Construct a private key from the specified parameters. * @param secret_key the private key */ - explicit X25519_PrivateKey(const secure_vector& secret_key); + explicit X25519_PrivateKey(std::span secret_key); - std::vector public_value() const override { return X25519_PublicKey::public_value(); } + std::vector public_value() const override { return raw_public_key_bits(); } secure_vector agree(const uint8_t w[], size_t w_len) const; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/x509_key.cpp botan3-3.12.0+dfsg/src/lib/pubkey/x509_key.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/x509_key.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/x509_key.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -31,11 +31,21 @@ std::vector key_bits; if(ASN1::maybe_BER(source) && !PEM_Code::matches(source)) { - BER_Decoder(source).start_sequence().decode(alg_id).decode(key_bits, ASN1_Type::BitString).end_cons(); + BER_Decoder(source, BER_Decoder::Limits::DER()) + .start_sequence() + .decode(alg_id) + .decode(key_bits, ASN1_Type::BitString) + .end_cons() + .verify_end(); } else { DataSource_Memory ber(PEM_Code::decode_check_label(source, "PUBLIC KEY")); - BER_Decoder(ber).start_sequence().decode(alg_id).decode(key_bits, ASN1_Type::BitString).end_cons(); + BER_Decoder(ber, BER_Decoder::Limits::DER()) + .start_sequence() + .decode(alg_id) + .decode(key_bits, ASN1_Type::BitString) + .end_cons() + .verify_end(); } if(key_bits.empty()) { diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/x509_key.h botan3-3.12.0+dfsg/src/lib/pubkey/x509_key.h --- botan3-3.7.1+dfsg/src/lib/pubkey/x509_key.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/x509_key.h 2026-05-07 01:38:28.000000000 +0000 @@ -10,6 +10,7 @@ #include #include +#include #include #include diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/xmss/atomic.h botan3-3.12.0+dfsg/src/lib/pubkey/xmss/atomic.h --- botan3-3.7.1+dfsg/src/lib/pubkey/xmss/atomic.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/xmss/atomic.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -/* - * Atomic - * (C) 2016 Matthias Gierlings - * - * Botan is released under the Simplified BSD License (see license.txt) - **/ - -#ifndef BOTAN_ATOMIC_H_ -#define BOTAN_ATOMIC_H_ - -#include -#include -#include - -namespace Botan { - -template -/** - * Simple helper class to expand std::atomic with copy constructor and copy - * assignment operator, i.e. for use as element in a container like - * std::vector. The construction of instances of this wrapper is NOT atomic - * and needs to be properly guarded. - **/ -class Atomic final { - public: - Atomic() = default; - - Atomic(const Atomic& data) : m_data(data.m_data.load()) {} - - Atomic(const std::atomic& data) : m_data(data.load()) {} - - ~Atomic() = default; - - Atomic& operator=(const Atomic& other) { - if(this != &other) { - m_data.store(other.m_data.load()); - } - return *this; - } - - Atomic& operator=(const std::atomic& a) { - m_data.store(a.load()); - return *this; - } - - operator std::atomic&() { return m_data; } - - operator T() { return m_data.load(); } - - private: - std::atomic m_data; -}; - -} // namespace Botan - -#endif diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/xmss/info.txt botan3-3.12.0+dfsg/src/lib/pubkey/xmss/info.txt --- botan3-3.7.1+dfsg/src/lib/pubkey/xmss/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/xmss/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -12,11 +12,9 @@ -atomic.h xmss_address.h xmss_common_ops.h xmss_hash.h -xmss_index_registry.h xmss_signature.h xmss_signature_operation.h xmss_tools.h @@ -28,10 +26,6 @@ asn1 rng hash -sha2_32 +stateful_key_index trunc_hash - - -atomics - diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss.h botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss.h --- botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,18 +9,17 @@ #ifndef BOTAN_XMSS_H_ #define BOTAN_XMSS_H_ -#include -#include - -#include #include #include +#include +#include namespace Botan { class RandomNumberGenerator; class XMSS_Address; class XMSS_Hash; +class XMSS_PublicKey_Internal; class XMSS_PrivateKey_Internal; class XMSS_Verification_Operation; class XMSS_WOTS_PublicKey; @@ -55,7 +54,7 @@ * * @param key_bits DER encoded public key bits */ - XMSS_PublicKey(std::span key_bits); + BOTAN_FUTURE_EXPLICIT XMSS_PublicKey(std::span key_bits); /** * Creates a new XMSS public key for a chosen XMSS signature method as @@ -75,11 +74,11 @@ return AlgorithmIdentifier(object_identifier(), AlgorithmIdentifier::USE_EMPTY_PARAM); } - bool check_key(RandomNumberGenerator&, bool) const override { return true; } + bool check_key(RandomNumberGenerator& rng, bool strong) const override; - size_t estimated_strength() const override { return m_xmss_params.estimated_strength(); } + size_t estimated_strength() const override; - size_t key_length() const override { return m_xmss_params.estimated_strength(); } + size_t key_length() const override; /** * Generates a byte sequence representing the XMSS @@ -113,18 +112,16 @@ protected: friend class XMSS_Verification_Operation; - const secure_vector& public_seed() const { return m_public_seed; } + const secure_vector& public_seed() const; - const secure_vector& root() const { return m_root; } + const secure_vector& root() const; - const XMSS_Parameters& xmss_parameters() const { return m_xmss_params; } + const XMSS_Parameters& xmss_parameters() const; - protected: - std::vector m_raw_key; - XMSS_Parameters m_xmss_params; - XMSS_WOTS_Parameters m_wots_params; - secure_vector m_root; - secure_vector m_public_seed; + void set_root(secure_vector root); + + private: + std::shared_ptr m_public_key; }; template @@ -135,7 +132,7 @@ /** * Determines how WOTS+ private keys are derived from the XMSS private key */ -enum class WOTS_Derivation_Method { +enum class WOTS_Derivation_Method : uint8_t { /// This roughly followed the suggestions in RFC 8391 but is vulnerable /// to a multi-target attack. For new private keys, we recommend using /// the derivation as suggested in NIST SP.800-208. @@ -186,7 +183,7 @@ * * @param raw_key An XMSS private key serialized using raw_private_key(). **/ - XMSS_PrivateKey(std::span raw_key); + BOTAN_FUTURE_EXPLICIT XMSS_PrivateKey(std::span raw_key); /** * Creates a new XMSS private key for the chosen XMSS signature method @@ -232,19 +229,20 @@ std::optional remaining_operations() const override; - std::unique_ptr create_signature_op(RandomNumberGenerator&, - std::string_view, + std::unique_ptr create_signature_op(RandomNumberGenerator& rng, + std::string_view params, std::string_view provider) const override; secure_vector private_key_bits() const override; /** - * Generates a non standartized byte sequence representing the XMSS + * Generates a non standardized byte sequence representing the XMSS * private key. * * @return byte sequence consisting of the following elements in order: * 4-byte OID, n-byte root node, n-byte public seed, - * 8-byte unused leaf index, n-byte prf seed, n-byte private seed. + * 4-byte unused leaf index, n-byte prf seed, n-byte private seed. + * At last 1-byte that encodes the WOTS+ key derivation method. **/ secure_vector raw_private_key() const; @@ -257,8 +255,8 @@ const secure_vector& prf_value() const; - XMSS_WOTS_PublicKey wots_public_key_for(XMSS_Address& adrs, XMSS_Hash& hash) const; - XMSS_WOTS_PrivateKey wots_private_key_for(XMSS_Address& adrs, XMSS_Hash& hash) const; + XMSS_WOTS_PublicKey wots_public_key_for(const XMSS_Address& adrs, XMSS_Hash& hash) const; + XMSS_WOTS_PrivateKey wots_private_key_for(const XMSS_Address& adrs, XMSS_Hash& hash) const; /** * Algorithm 9: "treeHash" @@ -267,26 +265,22 @@ * @param start_idx The start index. * @param target_node_height Height of the target node. * @param adrs Address of the tree containing the target node. + * @param hash The hash function to use * * @return The root node of a tree of height target_node height with the * leftmost leaf being the hash of the WOTS+ pk with index * start_idx. **/ - secure_vector tree_hash(size_t start_idx, size_t target_node_height, XMSS_Address& adrs); + secure_vector tree_hash(size_t start_idx, + size_t target_node_height, + const XMSS_Address& adrs, + XMSS_Hash& hash) const; void tree_hash_subtree(secure_vector& result, size_t start_idx, size_t target_node_height, - XMSS_Address& adrs); - - /** - * Helper for multithreaded tree hashing. - */ - void tree_hash_subtree(secure_vector& result, - size_t start_idx, - size_t target_node_height, XMSS_Address& adrs, - XMSS_Hash& hash); + XMSS_Hash& hash) const; std::shared_ptr m_private; }; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_address.h botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_address.h --- botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_address.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_address.h 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,7 @@ /* * XMSS Address * (C) 2016 Matthias Gierlings + * 2026 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) **/ @@ -9,8 +10,9 @@ #define BOTAN_XMSS_ADDRESS_H_ #include -#include #include +#include +#include namespace Botan { @@ -277,29 +279,25 @@ set_hi32(3, value); } - const secure_vector& bytes() const { return m_data; } - - secure_vector& bytes() { return m_data; } + std::span bytes() const { return std::span{m_data}; } /** * @return the size of an XMSS_Address **/ size_t size() const { return m_data.size(); } - XMSS_Address() : m_data(m_address_size) { set_type(Type::None); } + XMSS_Address() : m_data{} { set_type(Type::None); } - XMSS_Address(Type type) : m_data(m_address_size) { set_type(type); } + ~XMSS_Address() = default; + XMSS_Address(const XMSS_Address& other) = default; + XMSS_Address(XMSS_Address&& other) = default; - XMSS_Address(secure_vector data) : m_data(std::move(data)) { - BOTAN_ASSERT(m_data.size() == m_address_size, "XMSS_Address must be of 256 bits size."); - } + XMSS_Address& operator=(const XMSS_Address& other) = default; + XMSS_Address& operator=(XMSS_Address&& other) = default; - protected: - secure_vector m_data; + explicit XMSS_Address(Type type) : m_data() { set_type(type); } private: - static const size_t m_address_size = 32; - inline uint32_t get_hi32(size_t offset) const { return ((0x000000FF & m_data[8 * offset + 3]) | (0x000000FF & m_data[8 * offset + 2]) << 8 | (0x000000FF & m_data[8 * offset + 1]) << 16 | (0x000000FF & m_data[8 * offset]) << 24); @@ -323,6 +321,8 @@ m_data[offset * 8 + 6] = ((value >> 8) & 0xFF); m_data[offset * 8 + 7] = ((value) & 0xFF); } + + std::array m_data; // NOLINT(*non-private-member-variable*) }; } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_common_ops.cpp botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_common_ops.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_common_ops.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_common_ops.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -15,7 +15,7 @@ void XMSS_Common_Ops::randomize_tree_hash(secure_vector& result, const secure_vector& left, const secure_vector& right, - XMSS_Address& adrs, + XMSS_Address adrs, const secure_vector& seed, XMSS_Hash& hash, const XMSS_Parameters& params) { @@ -45,7 +45,7 @@ void XMSS_Common_Ops::create_l_tree(secure_vector& result, wots_keysig_t pk, - XMSS_Address& adrs, + XMSS_Address adrs, const secure_vector& seed, XMSS_Hash& hash, const XMSS_Parameters& params) { @@ -57,7 +57,7 @@ adrs.set_tree_index(static_cast(i)); randomize_tree_hash(pk[i], pk[2 * i], pk[2 * i + 1], adrs, seed, hash, params); } - if(l & 0x01) { + if((l & 0x01) == 0x01) { pk[l >> 1] = pk[l - 1]; } l = (l >> 1) + (l & 0x01); diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_common_ops.h botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_common_ops.h --- botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_common_ops.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_common_ops.h 2026-05-07 01:38:28.000000000 +0000 @@ -30,13 +30,13 @@ * Generates a randomized hash. * * This overload is used in multithreaded scenarios, where it is - * required to provide seperate instances of XMSS_Hash to each + * required to provide separate instances of XMSS_Hash to each * thread. * * @param[out] result The resulting randomized hash. * @param[in] left Left half of the hash function input. * @param[in] right Right half of the hash function input. - * @param[in] adrs Adress of the hash function call. + * @param[in] adrs Address of the hash function call. * @param[in] seed The seed for G. * @param[in] hash Instance of XMSS_Hash, that may only by the thread * executing generate_public_key. @@ -45,7 +45,7 @@ static void randomize_tree_hash(secure_vector& result, const secure_vector& left, const secure_vector& right, - XMSS_Address& adrs, + XMSS_Address adrs, const secure_vector& seed, XMSS_Hash& hash, const XMSS_Parameters& params); @@ -56,7 +56,7 @@ * Takes a WOTS+ public key and compresses it to a single n-byte value. * * This overload is used in multithreaded scenarios, where it is - * required to provide seperate instances of XMSS_Hash to each thread. + * required to provide separate instances of XMSS_Hash to each thread. * * @param[out] result Public key compressed to a single n-byte value * pk[0]. @@ -69,7 +69,7 @@ **/ static void create_l_tree(secure_vector& result, wots_keysig_t pk, - XMSS_Address& adrs, + XMSS_Address adrs, const secure_vector& seed, XMSS_Hash& hash, const XMSS_Parameters& params); diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_hash.cpp botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_hash.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_hash.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_hash.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,6 +9,7 @@ #include +#include #include #include #include diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_hash.h botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_hash.h --- botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_hash.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_hash.h 2026-05-07 01:38:28.000000000 +0000 @@ -22,7 +22,7 @@ **/ class XMSS_Hash final { public: - XMSS_Hash(const XMSS_Parameters& params); + explicit XMSS_Hash(const XMSS_Parameters& params); XMSS_Hash(const XMSS_Hash& hash); XMSS_Hash(XMSS_Hash&& hash) = default; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_index_registry.cpp botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_index_registry.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_index_registry.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_index_registry.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,71 +0,0 @@ -/* - * XMSS Index Registry - * A registry for XMSS private keys, keeps track of the leaf index for - * independend copies of the same key. - * (C) 2016 Matthias Gierlings - * - * Botan is released under the Simplified BSD License (see license.txt) - **/ - -#include - -#include -#include - -namespace Botan { - -const std::string XMSS_Index_Registry::m_index_hash_function = "SHA-256"; - -//static -uint64_t XMSS_Index_Registry::make_key_id(const secure_vector& private_seed, - const secure_vector& prf) { - std::unique_ptr hash = HashFunction::create(m_index_hash_function); - BOTAN_ASSERT(hash != nullptr, "XMSS_Index_Registry requires SHA-256"); - hash->update(private_seed); - hash->update(prf); - secure_vector result = hash->final(); - uint64_t key_id = 0; - for(size_t i = 0; i < sizeof(key_id); i++) { - key_id = ((key_id << 8) | result[i]); - } - - return key_id; -} - -std::shared_ptr> XMSS_Index_Registry::get(const secure_vector& private_seed, - const secure_vector& prf) { - size_t pos = get(make_key_id(private_seed, prf)); - - if(pos < std::numeric_limits::max()) { - return m_leaf_indices[pos]; - } else { - return m_leaf_indices[add(make_key_id(private_seed, prf))]; - } -} - -size_t XMSS_Index_Registry::get(uint64_t id) const { - for(size_t i = 0; i < m_key_ids.size(); i++) { - if(m_key_ids[i] == id) { - return i; - } - } - - return std::numeric_limits::max(); -} - -size_t XMSS_Index_Registry::add(uint64_t id, size_t last_unused) { - lock_guard_type lock(m_mutex); - size_t pos = get(id); - if(pos < m_key_ids.size()) { - if(last_unused > *(m_leaf_indices[pos])) { - m_leaf_indices[pos] = std::make_shared>(last_unused); - } - return pos; - } - - m_key_ids.push_back(id); - m_leaf_indices.push_back(std::make_shared>(last_unused)); - return m_key_ids.size() - 1; -} - -} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_index_registry.h botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_index_registry.h --- botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_index_registry.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_index_registry.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,99 +0,0 @@ -/* - * XMSS Index Registry - * (C) 2016 Matthias Gierlings - * - * Botan is released under the Simplified BSD License (see license.txt) - **/ - -#ifndef BOTAN_XMSS_INDEX_REGISTRY_H_ -#define BOTAN_XMSS_INDEX_REGISTRY_H_ - -#include - -#include -#include -#include - -namespace Botan { - -/** - * A registry for XMSS private keys, keeps track of the leaf index for - * independend copies of the same key. - **/ -class XMSS_Index_Registry final { - public: - XMSS_Index_Registry(const XMSS_Index_Registry&) = delete; - XMSS_Index_Registry& operator=(const XMSS_Index_Registry&) = delete; - - /** - * Retrieves a handle to the process-wide unique XMSS index registry. - * - * @return Reference to unique XMSS index registry. - **/ - static XMSS_Index_Registry& get_instance() { - static XMSS_Index_Registry self; - return self; - } - - /** - * Retrieves the last unused leaf index for the private key identified - * by private_seed and prf. The leaf index will be updated properly - * across independent copies of private_key. - * - * @param private_seed Part of the unique identifier for an - * XMSS_PrivateKey. - * @param prf Part of the unique identifier for an XMSS_PrivateKey. - * - * @return last unused leaf index for private_key. - **/ - std::shared_ptr> get(const secure_vector& private_seed, - const secure_vector& prf); - - private: - XMSS_Index_Registry() = default; - - static const std::string m_index_hash_function; - - /** - * Creates a unique 64-bit id for an XMSS_Private key, by interpreting - * the first 64-bit of HASH(PRIVATE_SEED || PRF) as 64 bit integer - * value. - * - * @return unique integral identifier for an XMSS private key. - **/ - static uint64_t make_key_id(const secure_vector& private_seed, const secure_vector& prf); - - /** - * Retrieves the index position of a key within the registry or - * max(size_t) if key has not been found. - * - * @param id unique id of the XMSS private key (see make_key_id()). - * - * @return index position of key or max(size_t) if key not found. - **/ - size_t get(uint64_t id) const; - - /** - * If XMSS_PrivateKey identified by id is already registered, the - * position of the according registry entry is returned. If last_unused - * is bigger than the last unused index stored for the key identified by - * id the unused leaf index for this key is set to last_unused. If no key - * matching id is registed yet, an entry of id is added, with the last - * unused leaf index initialized to the value of last_unused. - * - * @last_unused Initial value for the last unused leaf index of the - * registered key. - * - * @return positon of leaf index registry entry for key identified - * by id. - **/ - size_t add(uint64_t id, size_t last_unused = 0); - - std::vector m_key_ids; - std::vector>> m_leaf_indices; - mutex_type m_mutex; -}; - -} // namespace Botan - -#endif diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_parameters.cpp botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_parameters.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_parameters.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_parameters.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ /* * XMSS Parameters - * Descibes a signature method for XMSS, as defined in: + * Describes a signature method for XMSS, as defined in: * [1] XMSS: Extended Hash-Based Signatures, * Request for Comments: 8391 * Release: May 2018. @@ -13,6 +13,7 @@ #include +#include #include #include @@ -86,242 +87,197 @@ throw Lookup_Error(fmt("Unknown XMSS algorithm param '{}'", param_set)); } -XMSS_Parameters::XMSS_Parameters(std::string_view param_set) : - XMSS_Parameters(XMSS_Parameters::xmss_id_from_string(param_set)) {} +std::string_view XMSS_Parameters::hash_function_name() const { + switch(m_oid) { + case XMSS_SHA2_10_256: + case XMSS_SHA2_16_256: + case XMSS_SHA2_20_256: + return "SHA-256"; + + case XMSS_SHA2_10_512: + case XMSS_SHA2_16_512: + case XMSS_SHA2_20_512: + return "SHA-512"; + + case XMSS_SHAKE_10_256: + case XMSS_SHAKE_16_256: + case XMSS_SHAKE_20_256: + return "SHAKE-128(256)"; + + case XMSS_SHAKE_10_512: + case XMSS_SHAKE_16_512: + case XMSS_SHAKE_20_512: + return "SHAKE-256(512)"; + + case XMSS_SHA2_10_192: + case XMSS_SHA2_16_192: + case XMSS_SHA2_20_192: + return "Truncated(SHA-256,192)"; + + case XMSS_SHAKE256_10_256: + case XMSS_SHAKE256_16_256: + case XMSS_SHAKE256_20_256: + return "SHAKE-256(256)"; + + case XMSS_SHAKE256_10_192: + case XMSS_SHAKE256_16_192: + case XMSS_SHAKE256_20_192: + return "SHAKE-256(192)"; + + default: + BOTAN_ASSERT_UNREACHABLE(); + } +} + +std::string_view XMSS_Parameters::name() const { + switch(m_oid) { + case XMSS_SHA2_10_256: + return "XMSS-SHA2_10_256"; + + case XMSS_SHA2_16_256: + return "XMSS-SHA2_16_256"; + + case XMSS_SHA2_20_256: + return "XMSS-SHA2_20_256"; + + case XMSS_SHA2_10_512: + return "XMSS-SHA2_10_512"; + + case XMSS_SHA2_16_512: + return "XMSS-SHA2_16_512"; + + case XMSS_SHA2_20_512: + return "XMSS-SHA2_20_512"; + + case XMSS_SHAKE_10_256: + return "XMSS-SHAKE_10_256"; + + case XMSS_SHAKE_16_256: + return "XMSS-SHAKE_16_256"; + + case XMSS_SHAKE_20_256: + return "XMSS-SHAKE_20_256"; + + case XMSS_SHAKE_10_512: + return "XMSS-SHAKE_10_512"; + + case XMSS_SHAKE_16_512: + return "XMSS-SHAKE_16_512"; + + case XMSS_SHAKE_20_512: + return "XMSS-SHAKE_20_512"; + + case XMSS_SHA2_10_192: + return "XMSS-SHA2_10_192"; + + case XMSS_SHA2_16_192: + return "XMSS-SHA2_16_192"; + + case XMSS_SHA2_20_192: + return "XMSS-SHA2_20_192"; + + case XMSS_SHAKE256_10_256: + return "XMSS-SHAKE256_10_256"; + + case XMSS_SHAKE256_16_256: + return "XMSS-SHAKE256_16_256"; + + case XMSS_SHAKE256_20_256: + return "XMSS-SHAKE256_20_256"; + + case XMSS_SHAKE256_10_192: + return "XMSS-SHAKE256_10_192"; + + case XMSS_SHAKE256_16_192: + return "XMSS-SHAKE256_16_192"; + + case XMSS_SHAKE256_20_192: + return "XMSS-SHAKE256_20_192"; + + default: + BOTAN_ASSERT_UNREACHABLE(); + } +} + +// NOLINTBEGIN(*-member-init) +XMSS_Parameters::XMSS_Parameters(std::string_view algo_name) { + *this = XMSS_Parameters::from_name(algo_name); +} + +XMSS_Parameters::XMSS_Parameters(xmss_algorithm_t oid) { + *this = XMSS_Parameters::from_id(oid); +} + +// NOLINTEND(*-member-init) -XMSS_Parameters::XMSS_Parameters(xmss_algorithm_t oid) : m_oid(oid) { +XMSS_Parameters XMSS_Parameters::from_name(std::string_view param_set) { + return XMSS_Parameters::from_id(XMSS_Parameters::xmss_id_from_string(param_set)); +} + +XMSS_Parameters XMSS_Parameters::from_id(xmss_algorithm_t oid) { switch(oid) { case XMSS_SHA2_10_256: - m_element_size = 32; - m_hash_id_size = 32; - m_w = 16; - m_len = 67; - m_tree_height = 10; - m_name = "XMSS-SHA2_10_256"; - m_hash_name = "SHA-256"; - m_strength = 256; - m_wots_oid = XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHA2_256; - break; + return XMSS_Parameters(oid, XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHA2_256, 32, 32, 10, 67); + case XMSS_SHA2_16_256: - m_element_size = 32; - m_hash_id_size = 32; - m_w = 16; - m_len = 67; - m_tree_height = 16; - m_name = "XMSS-SHA2_16_256"; - m_hash_name = "SHA-256"; - m_strength = 256; - m_wots_oid = XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHA2_256; - break; + return XMSS_Parameters(oid, XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHA2_256, 32, 32, 16, 67); + case XMSS_SHA2_20_256: - m_element_size = 32; - m_hash_id_size = 32; - m_w = 16; - m_len = 67; - m_tree_height = 20; - m_name = "XMSS-SHA2_20_256"; - m_hash_name = "SHA-256"; - m_strength = 256; - m_wots_oid = XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHA2_256; - break; + return XMSS_Parameters(oid, XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHA2_256, 32, 32, 20, 67); + case XMSS_SHA2_10_512: - m_element_size = 64; - m_hash_id_size = 64; - m_w = 16; - m_len = 131; - m_tree_height = 10; - m_name = "XMSS-SHA2_10_512"; - m_hash_name = "SHA-512"; - m_strength = 512; - m_wots_oid = XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHA2_512; - break; + return XMSS_Parameters(oid, XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHA2_512, 64, 64, 10, 131); + case XMSS_SHA2_16_512: - m_element_size = 64; - m_hash_id_size = 64; - m_w = 16; - m_len = 131; - m_tree_height = 16; - m_name = "XMSS-SHA2_16_512"; - m_hash_name = "SHA-512"; - m_strength = 512; - m_wots_oid = XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHA2_512; - break; + return XMSS_Parameters(oid, XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHA2_512, 64, 64, 16, 131); + case XMSS_SHA2_20_512: - m_element_size = 64; - m_hash_id_size = 64; - m_w = 16; - m_len = 131; - m_tree_height = 20; - m_name = "XMSS-SHA2_20_512"; - m_hash_name = "SHA-512"; - m_strength = 512; - m_wots_oid = XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHA2_512; - break; + return XMSS_Parameters(oid, XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHA2_512, 64, 64, 20, 131); + case XMSS_SHAKE_10_256: - m_element_size = 32; - m_hash_id_size = 32; - m_w = 16; - m_len = 67; - m_tree_height = 10; - m_name = "XMSS-SHAKE_10_256"; - m_hash_name = "SHAKE-128(256)"; - m_strength = 256; - m_wots_oid = XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHAKE_256; - break; + return XMSS_Parameters(oid, XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHAKE_256, 32, 32, 10, 67); + case XMSS_SHAKE_16_256: - m_element_size = 32; - m_hash_id_size = 32; - m_w = 16; - m_len = 67; - m_tree_height = 16; - m_name = "XMSS-SHAKE_16_256"; - m_hash_name = "SHAKE-128(256)"; - m_strength = 256; - m_wots_oid = XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHAKE_256; - break; + return XMSS_Parameters(oid, XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHAKE_256, 32, 32, 16, 67); + case XMSS_SHAKE_20_256: - m_element_size = 32; - m_hash_id_size = 32; - m_w = 16; - m_len = 67; - m_tree_height = 20; - m_name = "XMSS-SHAKE_20_256"; - m_hash_name = "SHAKE-128(256)"; - m_strength = 256; - m_wots_oid = XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHAKE_256; - break; + return XMSS_Parameters(oid, XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHAKE_256, 32, 32, 20, 67); + case XMSS_SHAKE_10_512: - m_element_size = 64; - m_hash_id_size = 64; - m_w = 16; - m_len = 131; - m_tree_height = 10; - m_name = "XMSS-SHAKE_10_512"; - m_hash_name = "SHAKE-256(512)"; - m_strength = 512; - m_wots_oid = XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHAKE_512; - break; + return XMSS_Parameters(oid, XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHAKE_512, 64, 64, 10, 131); + case XMSS_SHAKE_16_512: - m_element_size = 64; - m_hash_id_size = 64; - m_w = 16; - m_len = 131; - m_tree_height = 16; - m_name = "XMSS-SHAKE_16_512"; - m_hash_name = "SHAKE-256(512)"; - m_strength = 512; - m_wots_oid = XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHAKE_512; - break; + return XMSS_Parameters(oid, XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHAKE_512, 64, 64, 16, 131); + case XMSS_SHAKE_20_512: - m_element_size = 64; - m_hash_id_size = 64; - m_w = 16; - m_len = 131; - m_tree_height = 20; - m_name = "XMSS-SHAKE_20_512"; - m_hash_name = "SHAKE-256(512)"; - m_strength = 512; - m_wots_oid = XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHAKE_512; - break; + return XMSS_Parameters(oid, XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHAKE_512, 64, 64, 20, 131); + case XMSS_SHA2_10_192: - m_element_size = 24; - m_hash_id_size = 4; - m_w = 16; - m_len = 51; - m_tree_height = 10; - m_name = "XMSS-SHA2_10_192"; - m_hash_name = "Truncated(SHA-256,192)"; - m_strength = 192; - m_wots_oid = XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHA2_192; - break; + return XMSS_Parameters(oid, XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHA2_192, 24, 4, 10, 51); + case XMSS_SHA2_16_192: - m_element_size = 24; - m_hash_id_size = 4; - m_w = 16; - m_len = 51; - m_tree_height = 16; - m_name = "XMSS-SHA2_16_192"; - m_hash_name = "Truncated(SHA-256,192)"; - m_strength = 192; - m_wots_oid = XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHA2_192; - break; + return XMSS_Parameters(oid, XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHA2_192, 24, 4, 16, 51); + case XMSS_SHA2_20_192: - m_element_size = 24; - m_hash_id_size = 4; - m_w = 16; - m_len = 51; - m_tree_height = 20; - m_name = "XMSS-SHA2_20_192"; - m_hash_name = "Truncated(SHA-256,192)"; - m_strength = 192; - m_wots_oid = XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHA2_192; - break; + return XMSS_Parameters(oid, XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHA2_192, 24, 4, 20, 51); + case XMSS_SHAKE256_10_256: - m_element_size = 32; - m_hash_id_size = 32; - m_w = 16; - m_len = 67; - m_tree_height = 10; - m_name = "XMSS-SHAKE256_10_256"; - m_hash_name = "SHAKE-256(256)"; - m_strength = 256; - m_wots_oid = XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHAKE_256_256; - break; + return XMSS_Parameters(oid, XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHAKE_256_256, 32, 32, 10, 67); + case XMSS_SHAKE256_16_256: - m_element_size = 32; - m_hash_id_size = 32; - m_w = 16; - m_len = 67; - m_tree_height = 16; - m_name = "XMSS-SHAKE256_16_256"; - m_hash_name = "SHAKE-256(256)"; - m_strength = 256; - m_wots_oid = XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHAKE_256_256; - break; + return XMSS_Parameters(oid, XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHAKE_256_256, 32, 32, 16, 67); + case XMSS_SHAKE256_20_256: - m_element_size = 32; - m_hash_id_size = 32; - m_w = 16; - m_len = 67; - m_tree_height = 20; - m_name = "XMSS-SHAKE256_20_256"; - m_hash_name = "SHAKE-256(256)"; - m_strength = 256; - m_wots_oid = XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHAKE_256_256; - break; + return XMSS_Parameters(oid, XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHAKE_256_256, 32, 32, 20, 67); + case XMSS_SHAKE256_10_192: - m_element_size = 24; - m_hash_id_size = 4; - m_w = 16; - m_len = 51; - m_tree_height = 10; - m_name = "XMSS-SHAKE256_10_192"; - m_hash_name = "SHAKE-256(192)"; - m_strength = 192; - m_wots_oid = XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHAKE_256_192; - break; + return XMSS_Parameters(oid, XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHAKE_256_192, 24, 4, 10, 51); + case XMSS_SHAKE256_16_192: - m_element_size = 24; - m_hash_id_size = 4; - m_w = 16; - m_len = 51; - m_tree_height = 16; - m_name = "XMSS-SHAKE256_16_192"; - m_hash_name = "SHAKE-256(192)"; - m_strength = 192; - m_wots_oid = XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHAKE_256_192; - break; + return XMSS_Parameters(oid, XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHAKE_256_192, 24, 4, 16, 51); + case XMSS_SHAKE256_20_192: - m_element_size = 24; - m_hash_id_size = 4; - m_w = 16; - m_len = 51; - m_tree_height = 20; - m_name = "XMSS-SHAKE256_20_192"; - m_hash_name = "SHAKE-256(192)"; - m_strength = 192; - m_wots_oid = XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHAKE_256_192; - break; + return XMSS_Parameters(oid, XMSS_WOTS_Parameters::ots_algorithm_t::WOTSP_SHAKE_256_192, 24, 4, 20, 51); default: throw Not_Implemented("Algorithm id does not match any known XMSS algorithm id:" + std::to_string(oid)); diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_parameters.h botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_parameters.h --- botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_parameters.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_parameters.h 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,7 @@ /* * XMSS Parameters * (C) 2016,2018 Matthias Gierlings + * 2026 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) **/ @@ -8,16 +9,19 @@ #ifndef BOTAN_XMSS_PARAMETERS_H_ #define BOTAN_XMSS_PARAMETERS_H_ -#include -#include - #include #include +#include namespace Botan { +/* +* TODO(Botan4) this header is only needed by xmss.h due to xmss_algorithm_t +* Split xmss_algorithm_t out somehow, and make this header internal +*/ + /** - * Descibes a signature method for XMSS Winternitz One Time Signatures, + * Describes a signature method for XMSS Winternitz One Time Signatures, * as defined in: * [1] XMSS: Extended Hash-Based Signatures, * Request for Comments: 8391 @@ -30,7 +34,7 @@ **/ class BOTAN_PUBLIC_API(2, 0) XMSS_WOTS_Parameters final { public: - enum ots_algorithm_t { + enum ots_algorithm_t : uint32_t /* NOLINT(*-enum-size,*-use-enum-class) */ { // from RFC 8391 WOTSP_SHA2_256 = 0x00000001, @@ -46,29 +50,13 @@ WOTSP_SHAKE_256_192 = 0x00000007, }; - explicit XMSS_WOTS_Parameters(std::string_view algo_name); - XMSS_WOTS_Parameters(ots_algorithm_t ots_spec); + static XMSS_WOTS_Parameters from_id(ots_algorithm_t id); - static ots_algorithm_t xmss_wots_id_from_string(std::string_view param_set); - - /** - * Algorithm 1: convert input string to base. - * - * @param msg Input string (referred to as X in [1]). - * @param out_size size of message in base w. - * - * @return Input string converted to the given base. - **/ - secure_vector base_w(const secure_vector& msg, size_t out_size) const; - - secure_vector base_w(size_t value) const; - - void append_checksum(secure_vector& data) const; - - /** - * @return XMSS WOTS registry name for the chosen parameter set. - **/ - const std::string& name() const { return m_name; } + XMSS_WOTS_Parameters(const XMSS_WOTS_Parameters& other) = default; + XMSS_WOTS_Parameters(XMSS_WOTS_Parameters&& other) noexcept = default; + XMSS_WOTS_Parameters& operator=(const XMSS_WOTS_Parameters& other) = default; + XMSS_WOTS_Parameters& operator=(XMSS_WOTS_Parameters&& other) noexcept = default; + ~XMSS_WOTS_Parameters() = default; /** * Retrieves the uniform length of a message, and the size of @@ -82,10 +70,16 @@ /** * The Winternitz parameter. * - * @return numeric base used for internal representation of - * data. + * @return numeric base used for internal representation of data. + * + * Fixed at 16 for this implementation. **/ - size_t wots_parameter() const { return m_w; } + size_t wots_parameter() const { return 16; } + + /** + * The log2 of wots_parameter + */ + size_t lg_w() const { return 4; } size_t len() const { return m_len; } @@ -93,30 +87,28 @@ size_t len_2() const { return m_len_2; } - size_t lg_w() const { return m_lg_w; } - - ots_algorithm_t oid() const { return m_oid; } + ots_algorithm_t oid() const { return m_id; } - size_t estimated_strength() const { return m_strength; } + // Return estimated workfactor in bits + size_t estimated_strength() const { return 8 * m_element_size; } - bool operator==(const XMSS_WOTS_Parameters& p) const { return m_oid == p.m_oid; } + bool operator==(const XMSS_WOTS_Parameters& p) const { return m_id == p.m_id; } private: - static const std::map m_oid_name_lut; - ots_algorithm_t m_oid; - std::string m_name; - std::string m_hash_name; + static XMSS_WOTS_Parameters from_hash_len(ots_algorithm_t id, size_t hash_len); + + XMSS_WOTS_Parameters(ots_algorithm_t id, size_t hash_len, size_t len, size_t len1, size_t len2) : + m_id(id), m_element_size(hash_len), m_len(len), m_len_1(len1), m_len_2(len2) {} + + ots_algorithm_t m_id{}; size_t m_element_size; - size_t m_w; + size_t m_len; size_t m_len_1; size_t m_len_2; - size_t m_len; - size_t m_strength; - uint8_t m_lg_w; }; /** - * Descibes a signature method for XMSS, as defined in: + * Describes a signature method for XMSS, as defined in: * [1] XMSS: Extended Hash-Based Signatures, * Request for Comments: 8391 * Release: May 2018. @@ -126,9 +118,9 @@ * Release: October 2020. * https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-208.pdf **/ -class BOTAN_PUBLIC_API(2, 0) XMSS_Parameters { +class BOTAN_PUBLIC_API(2, 0) XMSS_Parameters final { public: - enum xmss_algorithm_t { + enum xmss_algorithm_t : uint32_t /* NOLINT(*-enum-size,*-use-enum-class) */ { // from RFC 8391 XMSS_SHA2_10_256 = 0x00000001, XMSS_SHA2_16_256 = 0x00000002, @@ -160,15 +152,26 @@ static xmss_algorithm_t xmss_id_from_string(std::string_view algo_name); - explicit XMSS_Parameters(std::string_view algo_name); - explicit XMSS_Parameters(xmss_algorithm_t oid); + BOTAN_DEPRECATED("Use XMSS_Parameters::from_name") explicit XMSS_Parameters(std::string_view algo_name); + + BOTAN_DEPRECATED("Use XMSS_Parameters::from_id") explicit XMSS_Parameters(xmss_algorithm_t oid); + + static XMSS_Parameters from_name(std::string_view algo_name); + + static XMSS_Parameters from_id(xmss_algorithm_t id); + + XMSS_Parameters(const XMSS_Parameters& other) = default; + XMSS_Parameters(XMSS_Parameters&& other) noexcept = default; + XMSS_Parameters& operator=(const XMSS_Parameters& other) = default; + XMSS_Parameters& operator=(XMSS_Parameters&& other) noexcept = default; + ~XMSS_Parameters() = default; /** * @return XMSS registry name for the chosen parameter set. **/ - const std::string& name() const { return m_name; } + std::string_view name() const; - const std::string& hash_function_name() const { return m_hash_name; } + std::string_view hash_function_name() const; /** * Retrieves the uniform length of a message, and the size of @@ -197,7 +200,7 @@ /** * @returns total number of signatures allowed for this XMSS instance */ - size_t total_number_of_signatures() const { return size_t(1) << tree_height(); } + size_t total_number_of_signatures() const { return static_cast(1) << tree_height(); } /** * The Winternitz parameter. @@ -205,7 +208,7 @@ * @return numeric base used for internal representation of * data. **/ - size_t wots_parameter() const { return m_w; } + size_t wots_parameter() const { return 16; } size_t len() const { return m_len; } @@ -213,11 +216,13 @@ XMSS_WOTS_Parameters::ots_algorithm_t ots_oid() const { return m_wots_oid; } + XMSS_WOTS_Parameters wots_parameters() const { return XMSS_WOTS_Parameters::from_id(m_wots_oid); } + /** * Returns the estimated pre-quantum security level of * the chosen algorithm. **/ - size_t estimated_strength() const { return m_strength; } + size_t estimated_strength() const { return 8 * m_element_size; } size_t raw_public_key_size() const { return sizeof(uint32_t) + 2 * element_size(); } @@ -232,16 +237,25 @@ bool operator==(const XMSS_Parameters& p) const { return m_oid == p.m_oid; } private: - xmss_algorithm_t m_oid; + XMSS_Parameters(xmss_algorithm_t oid, + XMSS_WOTS_Parameters::ots_algorithm_t wots_oid, + size_t hash_len, + size_t hash_id_size, + size_t tree_height, + size_t len) : + m_oid(oid), + m_wots_oid(wots_oid), + m_element_size(hash_len), + m_hash_id_size(hash_id_size), + m_tree_height(tree_height), + m_len(len) {} + + xmss_algorithm_t m_oid{}; XMSS_WOTS_Parameters::ots_algorithm_t m_wots_oid; - std::string m_name; - std::string m_hash_name; size_t m_element_size; size_t m_hash_id_size; size_t m_tree_height; - size_t m_w; size_t m_len; - size_t m_strength; }; } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_privatekey.cpp botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_privatekey.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_privatekey.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_privatekey.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ /* * XMSS Private Key - * An XMSS: Extended Hash-Based Siganture private key. + * An XMSS: Extended Hash-Based Signature private key. * The XMSS private key does not support the X509 and PKCS7 standard. Instead * the raw format described in [1] is used. * @@ -10,7 +10,7 @@ * https://datatracker.ietf.org/doc/rfc8391/ * * (C) 2016,2017,2018 Matthias Gierlings - * (C) 2019 Jack Lloyd + * (C) 2019,2026 Jack Lloyd * (C) 2023 René Meusel - Rohde & Schwarz Cybersecurity * * Botan is released under the Simplified BSD License (see license.txt) @@ -20,12 +20,15 @@ #include #include +#include +#include +#include +#include #include -#include +#include #include -#include +#include #include -#include #if defined(BOTAN_HAS_THREAD_UTILS) #include @@ -45,8 +48,7 @@ key_bits.size() == xmss_params.raw_legacy_private_key_size()) { raw_key.assign(key_bits.begin(), key_bits.end()); } else { - DataSource_Memory src(key_bits); - BER_Decoder(src).decode(raw_key, ASN1_Type::OctetString).verify_end(); + BER_Decoder(key_bits, BER_Decoder::Limits::DER()).decode(raw_key, ASN1_Type::OctetString).verify_end(); } return raw_key; @@ -54,40 +56,33 @@ } // namespace -class XMSS_PrivateKey_Internal { +class XMSS_PrivateKey_Internal final { public: - XMSS_PrivateKey_Internal(const XMSS_Parameters& xmss_params, - const XMSS_WOTS_Parameters& wots_params, + XMSS_PrivateKey_Internal(XMSS_Parameters::xmss_algorithm_t xmss_algo_id, WOTS_Derivation_Method wots_derivation_method, RandomNumberGenerator& rng) : - m_xmss_params(xmss_params), - m_wots_params(wots_params), + m_xmss_params(XMSS_Parameters::from_id(xmss_algo_id)), + m_wots_params(m_xmss_params.wots_parameters()), m_wots_derivation_method(wots_derivation_method), - m_hash(xmss_params), - m_prf(rng.random_vec(xmss_params.element_size())), - m_private_seed(rng.random_vec(xmss_params.element_size())), - m_index_reg(XMSS_Index_Registry::get_instance()) {} + m_prf(rng.random_vec(m_xmss_params.element_size())), + m_private_seed(rng.random_vec(m_xmss_params.element_size())), + m_keyid(Stateful_Key_Index_Registry::KeyId("XMSS", m_xmss_params.oid(), m_private_seed, m_prf)) {} - XMSS_PrivateKey_Internal(const XMSS_Parameters& xmss_params, - const XMSS_WOTS_Parameters& wots_params, + XMSS_PrivateKey_Internal(XMSS_Parameters::xmss_algorithm_t xmss_algo_id, WOTS_Derivation_Method wots_derivation_method, secure_vector private_seed, secure_vector prf) : - m_xmss_params(xmss_params), - m_wots_params(wots_params), + m_xmss_params(XMSS_Parameters::from_id(xmss_algo_id)), + m_wots_params(m_xmss_params.wots_parameters()), m_wots_derivation_method(wots_derivation_method), - m_hash(m_xmss_params), m_prf(std::move(prf)), m_private_seed(std::move(private_seed)), - m_index_reg(XMSS_Index_Registry::get_instance()) {} + m_keyid(Stateful_Key_Index_Registry::KeyId("XMSS", m_xmss_params.oid(), m_private_seed, m_prf)) {} - XMSS_PrivateKey_Internal(const XMSS_Parameters& xmss_params, - const XMSS_WOTS_Parameters& wots_params, - std::span key_bits) : - m_xmss_params(xmss_params), - m_wots_params(wots_params), - m_hash(m_xmss_params), - m_index_reg(XMSS_Index_Registry::get_instance()) { + XMSS_PrivateKey_Internal(XMSS_Parameters::xmss_algorithm_t xmss_algo_id, std::span key_bits) : + m_xmss_params(XMSS_Parameters::from_id(xmss_algo_id)), + m_wots_params(m_xmss_params.wots_parameters()), + m_keyid(/* initialized later*/) { /* The code requires sizeof(size_t) >= ceil(tree_height / 8) @@ -98,7 +93,7 @@ */ static_assert(sizeof(size_t) >= 4, "size_t is big enough to support leaf index"); - const secure_vector raw_key = extract_raw_private_key(key_bits, xmss_params); + const secure_vector raw_key = extract_raw_private_key(key_bits, m_xmss_params); if(raw_key.size() != m_xmss_params.raw_private_key_size() && raw_key.size() != m_xmss_params.raw_legacy_private_key_size()) { @@ -111,13 +106,17 @@ s.skip(m_xmss_params.raw_public_key_size()); auto unused_leaf_bytes = s.take(sizeof(uint32_t)); - size_t unused_leaf = load_be(unused_leaf_bytes.data(), 0); + const size_t unused_leaf = load_be(unused_leaf_bytes.data(), 0); if(unused_leaf >= (1ULL << m_xmss_params.tree_height())) { throw Decoding_Error("XMSS private key leaf index out of bounds"); } m_prf = s.copy_as_secure_vector(m_xmss_params.element_size()); m_private_seed = s.copy_as_secure_vector(m_xmss_params.element_size()); + + m_keyid = Stateful_Key_Index_Registry::KeyId("XMSS", m_xmss_params.oid(), m_private_seed, m_prf); + + // Note m_keyid must be initialized before set_unused_leaf_index is called! set_unused_leaf_index(unused_leaf); // Legacy keys generated prior to Botan 3.x don't feature a @@ -139,8 +138,6 @@ raw_public_key, unused_index, m_prf, m_private_seed, wots_derivation_method); } - XMSS_Hash& hash() { return m_hash; } - const secure_vector& prf_value() const { return m_prf; } const secure_vector& private_seed() { return m_private_seed; } @@ -149,43 +146,31 @@ WOTS_Derivation_Method wots_derivation_method() const { return m_wots_derivation_method; } - XMSS_Index_Registry& index_registry() { return m_index_reg; } - - std::shared_ptr> recover_global_leaf_index() const { - BOTAN_ASSERT( - m_private_seed.size() == m_xmss_params.element_size() && m_prf.size() == m_xmss_params.element_size(), - "Trying to retrieve index for partially initialized key"); - return m_index_reg.get(m_private_seed, m_prf); - } - void set_unused_leaf_index(size_t idx) { if(idx >= (1ULL << m_xmss_params.tree_height())) { throw Decoding_Error("XMSS private key leaf index out of bounds"); } else { - std::atomic& index = static_cast&>(*recover_global_leaf_index()); - size_t current = 0; - - do { - current = index.load(); - if(current > idx) { - return; - } - } while(!index.compare_exchange_strong(current, idx)); + Stateful_Key_Index_Registry::global().set_index_lower_bound(m_keyid, idx); } } size_t reserve_unused_leaf_index() { - size_t idx = (static_cast&>(*recover_global_leaf_index())).fetch_add(1); + const uint64_t idx = Stateful_Key_Index_Registry::global().reserve_next_index(m_keyid); if(idx >= m_xmss_params.total_number_of_signatures()) { - throw Decoding_Error("XMSS private key, one time signatures exhaused"); + throw Decoding_Error("XMSS private key, one time signatures exhausted"); } - return idx; + // Cast is safe even on 32 bit since total_number_of_signatures will be less + return static_cast(idx); } - size_t unused_leaf_index() const { return *recover_global_leaf_index(); } + size_t unused_leaf_index() const { + const uint64_t idx = Stateful_Key_Index_Registry::global().current_index(m_keyid); + return checked_cast_to(idx); + } - size_t remaining_signatures() const { - return m_xmss_params.total_number_of_signatures() - *recover_global_leaf_index(); + uint64_t remaining_signatures() const { + const size_t max = m_xmss_params.total_number_of_signatures(); + return Stateful_Key_Index_Registry::global().remaining_operations(m_keyid, max); } private: @@ -193,23 +178,23 @@ XMSS_WOTS_Parameters m_wots_params; WOTS_Derivation_Method m_wots_derivation_method; - XMSS_Hash m_hash; secure_vector m_prf; secure_vector m_private_seed; - XMSS_Index_Registry& m_index_reg; + Stateful_Key_Index_Registry::KeyId m_keyid; }; XMSS_PrivateKey::XMSS_PrivateKey(std::span key_bits) : XMSS_PublicKey(key_bits), - m_private(std::make_shared(m_xmss_params, m_wots_params, key_bits)) {} + m_private(std::make_shared(xmss_parameters().oid(), key_bits)) {} XMSS_PrivateKey::XMSS_PrivateKey(XMSS_Parameters::xmss_algorithm_t xmss_algo_id, RandomNumberGenerator& rng, WOTS_Derivation_Method wots_derivation_method) : XMSS_PublicKey(xmss_algo_id, rng), - m_private(std::make_shared(m_xmss_params, m_wots_params, wots_derivation_method, rng)) { - XMSS_Address adrs; - m_root = tree_hash(0, XMSS_PublicKey::m_xmss_params.tree_height(), adrs); + m_private(std::make_shared(xmss_algo_id, wots_derivation_method, rng)) { + const XMSS_Address adrs; + XMSS_Hash hash(xmss_parameters()); + set_root(tree_hash(0, xmss_parameters().tree_height(), adrs, hash)); } XMSS_PrivateKey::XMSS_PrivateKey(XMSS_Parameters::xmss_algorithm_t xmss_algo_id, @@ -221,21 +206,24 @@ WOTS_Derivation_Method wots_derivation_method) : XMSS_PublicKey(xmss_algo_id, std::move(root), std::move(public_seed)), m_private(std::make_shared( - m_xmss_params, m_wots_params, wots_derivation_method, std::move(wots_priv_seed), std::move(prf))) { + xmss_algo_id, wots_derivation_method, std::move(wots_priv_seed), std::move(prf))) { m_private->set_unused_leaf_index(idx_leaf); - BOTAN_ARG_CHECK(m_private->prf_value().size() == m_xmss_params.element_size(), + BOTAN_ARG_CHECK(m_private->prf_value().size() == xmss_parameters().element_size(), "XMSS: unexpected byte length of PRF value"); - BOTAN_ARG_CHECK(m_private->private_seed().size() == m_xmss_params.element_size(), + BOTAN_ARG_CHECK(m_private->private_seed().size() == xmss_parameters().element_size(), "XMSS: unexpected byte length of private seed"); } -secure_vector XMSS_PrivateKey::tree_hash(size_t start_idx, size_t target_node_height, XMSS_Address& adrs) { +secure_vector XMSS_PrivateKey::tree_hash(size_t start_idx, + size_t target_node_height, + const XMSS_Address& adrs, + XMSS_Hash& hash) const { BOTAN_ASSERT_NOMSG(target_node_height <= 30); BOTAN_ASSERT((start_idx % (static_cast(1) << target_node_height)) == 0, "Start index must be divisible by 2^{target node height}."); #if defined(BOTAN_HAS_THREAD_UTILS) - // dertermine number of parallel tasks to split the tree_hashing into. + // determine number of parallel tasks to split the tree_hashing into. Thread_Pool& thread_pool = Thread_Pool::global_instance(); @@ -244,7 +232,8 @@ // skip parallelization overhead for leaf nodes. if(split_level == 0) { secure_vector result; - tree_hash_subtree(result, start_idx, target_node_height, adrs); + XMSS_Address subtree_addr(adrs); + tree_hash_subtree(result, start_idx, target_node_height, subtree_addr, hash); return result; } @@ -258,18 +247,17 @@ "Number of worker threads in tree_hash need to divide range " "of calculated nodes."); - std::vector> nodes(subtrees, - secure_vector(XMSS_PublicKey::m_xmss_params.element_size())); + std::vector> nodes(subtrees, secure_vector(xmss_parameters().element_size())); std::vector node_addresses(subtrees, adrs); - std::vector xmss_hash(subtrees, m_private->hash()); + std::vector xmss_hash(subtrees, hash); std::vector> work; // Calculate multiple subtrees in parallel. for(size_t i = 0; i < subtrees; i++) { using tree_hash_subtree_fn_t = - void (XMSS_PrivateKey::*)(secure_vector&, size_t, size_t, XMSS_Address&, XMSS_Hash&); + void (XMSS_PrivateKey::*)(secure_vector&, size_t, size_t, XMSS_Address&, XMSS_Hash&) const; - tree_hash_subtree_fn_t work_fn = &XMSS_PrivateKey::tree_hash_subtree; + const tree_hash_subtree_fn_t work_fn = &XMSS_PrivateKey::tree_hash_subtree; work.push_back(thread_pool.run(work_fn, this, @@ -300,10 +288,10 @@ std::ref(nodes[i]), std::cref(ro_nodes[2 * i]), std::cref(ro_nodes[2 * i + 1]), - std::ref(node_addresses[i]), + node_addresses[i], std::cref(this->public_seed()), std::ref(xmss_hash[i]), - std::cref(m_xmss_params))); + std::cref(xmss_parameters()))); } for(auto& w : work) { @@ -316,11 +304,12 @@ node_addresses[0].set_tree_height(static_cast(target_node_height - 1)); node_addresses[0].set_tree_index((node_addresses[1].get_tree_index() - 1) >> 1); XMSS_Common_Ops::randomize_tree_hash( - nodes[0], nodes[0], nodes[1], node_addresses[0], this->public_seed(), m_private->hash(), m_xmss_params); + nodes[0], nodes[0], nodes[1], node_addresses[0], this->public_seed(), hash, xmss_parameters()); return nodes[0]; #else secure_vector result; - tree_hash_subtree(result, start_idx, target_node_height, adrs, m_private->hash()); + XMSS_Address subtree_addr(adrs); + tree_hash_subtree(result, start_idx, target_node_height, subtree_addr, hash); return result; #endif } @@ -328,16 +317,12 @@ void XMSS_PrivateKey::tree_hash_subtree(secure_vector& result, size_t start_idx, size_t target_node_height, - XMSS_Address& adrs) { - return tree_hash_subtree(result, start_idx, target_node_height, adrs, m_private->hash()); -} - -void XMSS_PrivateKey::tree_hash_subtree( - secure_vector& result, size_t start_idx, size_t target_node_height, XMSS_Address& adrs, XMSS_Hash& hash) { + XMSS_Address& adrs, + XMSS_Hash& hash) const { const secure_vector& seed = this->public_seed(); std::vector> nodes(target_node_height + 1, - secure_vector(XMSS_PublicKey::m_xmss_params.element_size())); + secure_vector(xmss_parameters().element_size())); // node stack, holds all nodes on stack and one extra "pending" node. This // temporary node referred to as "node" in the XMSS standard document stays @@ -352,11 +337,11 @@ adrs.set_type(XMSS_Address::Type::OTS_Hash_Address); adrs.set_ots_address(static_cast(i)); - XMSS_WOTS_PublicKey pk = this->wots_public_key_for(adrs, hash); + const XMSS_WOTS_PublicKey pk = this->wots_public_key_for(adrs, hash); adrs.set_type(XMSS_Address::Type::LTree_Address); adrs.set_ltree_address(static_cast(i)); - XMSS_Common_Ops::create_l_tree(nodes[level], pk.key_data(), adrs, seed, hash, m_xmss_params); + XMSS_Common_Ops::create_l_tree(nodes[level], pk.key_data(), adrs, seed, hash, xmss_parameters()); node_levels[level] = 0; adrs.set_type(XMSS_Address::Type::Hash_Tree_Address); @@ -366,7 +351,7 @@ while(level > 0 && node_levels[level] == node_levels[level - 1]) { adrs.set_tree_index(((adrs.get_tree_index() - 1) >> 1)); XMSS_Common_Ops::randomize_tree_hash( - nodes[level - 1], nodes[level - 1], nodes[level], adrs, seed, hash, m_xmss_params); + nodes[level - 1], nodes[level - 1], nodes[level], adrs, seed, hash, xmss_parameters()); node_levels[level - 1]++; level--; //Pop stack top element adrs.set_tree_height(adrs.get_tree_height() + 1); @@ -376,16 +361,16 @@ result = nodes[level - 1]; } -XMSS_WOTS_PublicKey XMSS_PrivateKey::wots_public_key_for(XMSS_Address& adrs, XMSS_Hash& hash) const { +XMSS_WOTS_PublicKey XMSS_PrivateKey::wots_public_key_for(const XMSS_Address& adrs, XMSS_Hash& hash) const { const auto private_key = wots_private_key_for(adrs, hash); - return XMSS_WOTS_PublicKey(m_private->wots_parameters(), m_public_seed, private_key, adrs, hash); + return XMSS_WOTS_PublicKey(m_private->wots_parameters(), public_seed(), private_key, adrs, hash); } -XMSS_WOTS_PrivateKey XMSS_PrivateKey::wots_private_key_for(XMSS_Address& adrs, XMSS_Hash& hash) const { +XMSS_WOTS_PrivateKey XMSS_PrivateKey::wots_private_key_for(const XMSS_Address& adrs, XMSS_Hash& hash) const { switch(wots_derivation_method()) { case WOTS_Derivation_Method::NIST_SP800_208: return XMSS_WOTS_PrivateKey( - m_private->wots_parameters(), m_public_seed, m_private->private_seed(), adrs, hash); + m_private->wots_parameters(), public_seed(), m_private->private_seed(), adrs, hash); case WOTS_Derivation_Method::Botan2x: return XMSS_WOTS_PrivateKey(m_private->wots_parameters(), m_private->private_seed(), adrs, hash); } @@ -406,7 +391,7 @@ } size_t XMSS_PrivateKey::remaining_signatures() const { - return m_private->remaining_signatures(); + return checked_cast_to(m_private->remaining_signatures()); } std::optional XMSS_PrivateKey::remaining_operations() const { diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_publickey.cpp botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_publickey.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_publickey.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_publickey.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ /* * XMSS Public Key - * An XMSS: Extended Hash-Based Siganture public key. + * An XMSS: Extended Hash-Based Signature public key. * The XMSS public key does not support the X509 standard. Instead the * raw format described in [1] is used. * @@ -18,12 +18,12 @@ #include #include +#include +#include +#include #include -#include #include -#include - namespace Botan { namespace { @@ -46,13 +46,12 @@ std::vector extract_raw_public_key(std::span key_bits) { std::vector raw_key; try { - DataSource_Memory src(key_bits); - BER_Decoder(src).decode(raw_key, ASN1_Type::OctetString).verify_end(); + BER_Decoder(key_bits, BER_Decoder::Limits::DER()).decode(raw_key, ASN1_Type::OctetString).verify_end(); - // Smoke check the decoded key. Valid raw keys might be decodeable as BER + // Smoke check the decoded key. Valid raw keys might be decodable as BER // and they might be either a sole public key or a concatenation of public // and private key (with the optional WOTS+ derivation identifier). - XMSS_Parameters params(deserialize_xmss_oid(raw_key)); + const XMSS_Parameters params = XMSS_Parameters::from_id(deserialize_xmss_oid(raw_key)); if(raw_key.size() != params.raw_public_key_size() && raw_key.size() != params.raw_private_key_size() && raw_key.size() != params.raw_legacy_private_key_size()) { throw Decoding_Error("unpacked XMSS key does not have the correct length"); @@ -68,36 +67,100 @@ } // namespace -XMSS_PublicKey::XMSS_PublicKey(XMSS_Parameters::xmss_algorithm_t xmss_oid, RandomNumberGenerator& rng) : - m_xmss_params(xmss_oid), - m_wots_params(m_xmss_params.ots_oid()), - m_root(m_xmss_params.element_size()), - m_public_seed(rng.random_vec(m_xmss_params.element_size())) {} - -XMSS_PublicKey::XMSS_PublicKey(std::span key_bits) : - m_raw_key(extract_raw_public_key(key_bits)), - m_xmss_params(deserialize_xmss_oid(m_raw_key)), - m_wots_params(m_xmss_params.ots_oid()) { - if(m_raw_key.size() < m_xmss_params.raw_public_key_size()) { +class XMSS_PublicKey_Internal final { + public: + XMSS_PublicKey_Internal(const XMSS_Parameters& params, + secure_vector root, + secure_vector public_seed) : + m_xmss_params(params), + m_wots_params(m_xmss_params.wots_parameters()), + m_root(std::move(root)), + m_public_seed(std::move(public_seed)) {} + + const XMSS_Parameters& xmss_parameters() const { return m_xmss_params; } + + const XMSS_WOTS_Parameters& wots_parameters() const { return m_wots_params; } + + const secure_vector& root() const { return m_root; } + + const secure_vector& public_seed() const { return m_public_seed; } + + void set_root(secure_vector root) { m_root = std::move(root); } + + std::vector raw_public_key_bits() const { + return concat>( + store_be(static_cast(m_xmss_params.oid())), m_root, m_public_seed); + } + + private: + XMSS_Parameters m_xmss_params; + XMSS_WOTS_Parameters m_wots_params; + secure_vector m_root; + secure_vector m_public_seed; +}; + +XMSS_PublicKey::XMSS_PublicKey(XMSS_Parameters::xmss_algorithm_t xmss_oid, RandomNumberGenerator& rng) { + const auto params = XMSS_Parameters::from_id(xmss_oid); + m_public_key = std::make_shared( + params, secure_vector(params.element_size()), rng.random_vec(params.element_size())); +} + +XMSS_PublicKey::XMSS_PublicKey(std::span key_bits) { + const auto raw_key = extract_raw_public_key(key_bits); + const auto xmss_oid = deserialize_xmss_oid(raw_key); + const auto params = XMSS_Parameters::from_id(xmss_oid); + if(raw_key.size() < params.raw_public_key_size()) { throw Decoding_Error("Invalid XMSS public key size detected"); } - BufferSlicer s(m_raw_key); + BufferSlicer s(raw_key); s.skip(4 /* algorithm ID -- already consumed by `deserialize_xmss_oid()` */); - m_root = s.copy_as_secure_vector(m_xmss_params.element_size()); - m_public_seed = s.copy_as_secure_vector(m_xmss_params.element_size()); + auto root = s.copy_as_secure_vector(params.element_size()); + auto public_seed = s.copy_as_secure_vector(params.element_size()); + + m_public_key = std::make_shared(params, std::move(root), std::move(public_seed)); } XMSS_PublicKey::XMSS_PublicKey(XMSS_Parameters::xmss_algorithm_t xmss_oid, secure_vector root, - secure_vector public_seed) : - m_xmss_params(xmss_oid), - m_wots_params(m_xmss_params.ots_oid()), - m_root(std::move(root)), - m_public_seed(std::move(public_seed)) { - BOTAN_ARG_CHECK(m_root.size() == m_xmss_params.element_size(), "XMSS: unexpected byte length of root hash"); - BOTAN_ARG_CHECK(m_public_seed.size() == m_xmss_params.element_size(), "XMSS: unexpected byte length of public seed"); + secure_vector public_seed) { + const auto params = XMSS_Parameters::from_id(xmss_oid); + BOTAN_ARG_CHECK(root.size() == params.element_size(), "XMSS: unexpected byte length of root hash"); + BOTAN_ARG_CHECK(public_seed.size() == params.element_size(), "XMSS: unexpected byte length of public seed"); + m_public_key = std::make_shared(params, std::move(root), std::move(public_seed)); +} + +const secure_vector& XMSS_PublicKey::public_seed() const { + return m_public_key->public_seed(); +} + +const secure_vector& XMSS_PublicKey::root() const { + return m_public_key->root(); +} + +const XMSS_Parameters& XMSS_PublicKey::xmss_parameters() const { + return m_public_key->xmss_parameters(); +} + +void XMSS_PublicKey::set_root(secure_vector root) { + m_public_key->set_root(std::move(root)); +} + +size_t XMSS_PublicKey::estimated_strength() const { + return xmss_parameters().estimated_strength(); +} + +size_t XMSS_PublicKey::key_length() const { + return xmss_parameters().estimated_strength(); +} + +bool XMSS_PublicKey::check_key(RandomNumberGenerator& /*rng*/, bool /*strong*/) const { + // The public key consists of (OID, root hash, public seed). The OID is + // validated and the byte lengths of root and public_seed are verified + // against the parameter set during deserialization. These are opaque + // hash outputs with no further structural invariants to check. + return true; } std::unique_ptr XMSS_PublicKey::create_verification_op(std::string_view /*params*/, @@ -120,7 +183,7 @@ } std::vector XMSS_PublicKey::raw_public_key_bits() const { - return concat>(store_be(static_cast(m_xmss_params.oid())), m_root, m_public_seed); + return m_public_key->raw_public_key_bits(); } std::vector XMSS_PublicKey::public_key_bits() const { @@ -137,7 +200,7 @@ // Note: Given only an XMSS public key we cannot know which WOTS key // derivation method was used to build the XMSS tree. Hence, we have to // use the default here. - return std::make_unique(m_xmss_params.oid(), rng); + return std::make_unique(xmss_parameters().oid(), rng); } } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_signature.cpp botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_signature.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_signature.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_signature.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -12,10 +12,9 @@ XMSS_Signature::XMSS_Signature(XMSS_Parameters::xmss_algorithm_t oid, std::span raw_sig) : m_leaf_idx(0), m_randomness(0, 0x00) { - XMSS_Parameters xmss_params(oid); + const auto params = XMSS_Parameters::from_id(oid); - if(raw_sig.size() != - (xmss_params.len() + xmss_params.tree_height() + 1) * xmss_params.element_size() + sizeof(uint32_t)) { + if(raw_sig.size() != (params.len() + params.tree_height() + 1) * params.element_size() + sizeof(uint32_t)) { throw Decoding_Error("XMSS signature size invalid."); } @@ -23,27 +22,27 @@ m_leaf_idx = ((m_leaf_idx << 8) | raw_sig[i]); } - if(m_leaf_idx >= xmss_params.total_number_of_signatures()) { + if(m_leaf_idx >= params.total_number_of_signatures()) { throw Decoding_Error("XMSS signature leaf index out of bounds."); } auto begin = raw_sig.begin() + sizeof(uint32_t); - auto end = begin + xmss_params.element_size(); + auto end = begin + params.element_size(); std::copy(begin, end, std::back_inserter(m_randomness)); - for(size_t i = 0; i < xmss_params.len(); i++) { + for(size_t i = 0; i < params.len(); i++) { begin = end; - end = begin + xmss_params.element_size(); + end = begin + params.element_size(); m_tree_sig.ots_signature.push_back(secure_vector(0)); - m_tree_sig.ots_signature.back().reserve(xmss_params.element_size()); + m_tree_sig.ots_signature.back().reserve(params.element_size()); std::copy(begin, end, std::back_inserter(m_tree_sig.ots_signature.back())); } - for(size_t i = 0; i < xmss_params.tree_height(); i++) { + for(size_t i = 0; i < params.tree_height(); i++) { begin = end; - end = begin + xmss_params.element_size(); + end = begin + params.element_size(); m_tree_sig.authentication_path.push_back(secure_vector(0)); - m_tree_sig.authentication_path.back().reserve(xmss_params.element_size()); + m_tree_sig.authentication_path.back().reserve(params.element_size()); std::copy(begin, end, std::back_inserter(m_tree_sig.authentication_path.back())); } } diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_signature.h botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_signature.h --- botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_signature.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_signature.h 2026-05-07 01:38:28.000000000 +0000 @@ -13,7 +13,6 @@ #include #include #include -#include namespace Botan { diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_signature_operation.cpp botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_signature_operation.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_signature_operation.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_signature_operation.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,6 +9,7 @@ * https://datatracker.ietf.org/doc/rfc8391/ * * (C) 2016,2017,2018 Matthias Gierlings + * 2026 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) **/ @@ -26,56 +27,44 @@ m_leaf_idx(0), m_is_initialized(false) {} -XMSS_Signature::TreeSignature XMSS_Signature_Operation::generate_tree_signature(const secure_vector& msg, - XMSS_PrivateKey& xmss_priv_key, - XMSS_Address& adrs) { - XMSS_Signature::TreeSignature result; - - result.authentication_path = build_auth_path(xmss_priv_key, adrs); - adrs.set_type(XMSS_Address::Type::OTS_Hash_Address); - adrs.set_ots_address(m_leaf_idx); - - result.ots_signature = - xmss_priv_key.wots_private_key_for(adrs, m_hash).sign(msg, xmss_priv_key.public_seed(), adrs, m_hash); - - return result; -} - -XMSS_Signature XMSS_Signature_Operation::sign(const secure_vector& msg_hash, XMSS_PrivateKey& xmss_priv_key) { - XMSS_Address adrs; - XMSS_Signature sig(m_leaf_idx, m_randomness, generate_tree_signature(msg_hash, xmss_priv_key, adrs)); - return sig; -} - size_t XMSS_Signature_Operation::signature_length() const { const auto& params = m_priv_key.xmss_parameters(); return sizeof(uint64_t) + // size of leaf index params.element_size() + params.len() * params.element_size() + params.tree_height() * params.element_size(); } -wots_keysig_t XMSS_Signature_Operation::build_auth_path(XMSS_PrivateKey& priv_key, XMSS_Address& adrs) { +void XMSS_Signature_Operation::update(std::span input) { + initialize(); + m_hash.h_msg_update(input); +} + +std::vector XMSS_Signature_Operation::sign(RandomNumberGenerator& /*rng*/) { + initialize(); + + const auto msg_hash = m_hash.h_msg_final(); + const auto& params = m_priv_key.xmss_parameters(); wots_keysig_t auth_path(params.tree_height()); + + XMSS_Address adrs; adrs.set_type(XMSS_Address::Type::Hash_Tree_Address); for(size_t j = 0; j < params.tree_height(); j++) { - size_t k = (m_leaf_idx / (static_cast(1) << j)) ^ 0x01; - auth_path[j] = priv_key.tree_hash(k * (static_cast(1) << j), j, adrs); + const size_t k = (m_leaf_idx / (static_cast(1) << j)) ^ 0x01; + auth_path[j] = m_priv_key.tree_hash(k * (static_cast(1) << j), j, adrs, m_hash); } - return auth_path; -} + adrs.set_type(XMSS_Address::Type::OTS_Hash_Address); + adrs.set_ots_address(m_leaf_idx); -void XMSS_Signature_Operation::update(std::span input) { - initialize(); - m_hash.h_msg_update(input); -} + XMSS_Signature::TreeSignature tree_sig; + tree_sig.authentication_path = auth_path; + tree_sig.ots_signature = + m_priv_key.wots_private_key_for(adrs, m_hash).sign(msg_hash, m_priv_key.public_seed(), adrs, m_hash); -std::vector XMSS_Signature_Operation::sign(RandomNumberGenerator& /*rng*/) { - initialize(); - auto sig = sign(m_hash.h_msg_final(), m_priv_key).bytes(); + const XMSS_Signature sig(m_leaf_idx, m_randomness, tree_sig); m_is_initialized = false; - return sig; + return sig.bytes(); } void XMSS_Signature_Operation::initialize() { @@ -90,10 +79,10 @@ m_leaf_idx = static_cast(m_priv_key.reserve_unused_leaf_index()); // write prefix for message hashing into buffer. - XMSS_Tools::concat(index_bytes, m_leaf_idx, 32); + xmss_concat(index_bytes, m_leaf_idx, 32); m_hash.prf(m_randomness, m_priv_key.prf_value(), index_bytes); index_bytes.clear(); - XMSS_Tools::concat(index_bytes, m_leaf_idx, m_priv_key.xmss_parameters().element_size()); + xmss_concat(index_bytes, m_leaf_idx, m_priv_key.xmss_parameters().element_size()); m_hash.h_msg_init(m_randomness, m_priv_key.root(), index_bytes); m_is_initialized = true; } diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_signature_operation.h botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_signature_operation.h --- botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_signature_operation.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_signature_operation.h 2026-05-07 01:38:28.000000000 +0000 @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -27,7 +28,7 @@ **/ class XMSS_Signature_Operation final : public virtual PK_Ops::Signature { public: - XMSS_Signature_Operation(const XMSS_PrivateKey& private_key); + explicit XMSS_Signature_Operation(const XMSS_PrivateKey& private_key); /** * Creates an XMSS signature for the message provided through call to @@ -35,7 +36,7 @@ * * @return serialized XMSS signature. **/ - std::vector sign(RandomNumberGenerator&) override; + std::vector sign(RandomNumberGenerator& rng) override; void update(std::span input) override; @@ -46,32 +47,6 @@ std::string hash_function() const override { return m_hash.hash_function(); } private: - /** - * Algorithm 11: "treeSig" - * Generate a WOTS+ signature on a message with corresponding auth path. - * - * @param msg A message. - * @param xmss_priv_key A XMSS private key. - * @param adrs A XMSS Address. - **/ - XMSS_Signature::TreeSignature generate_tree_signature(const secure_vector& msg, - XMSS_PrivateKey& xmss_priv_key, - XMSS_Address& adrs); - - /** - * Algorithm 12: "XMSS_sign" - * Generate an XMSS signature and update the XMSS secret key - * - * @param msg A message to sign of arbitrary length. - * @param [out] xmss_priv_key A XMSS private key. The private key will be - * updated during the signing process. - * - * @return The signature of msg signed using xmss_priv_key. - **/ - XMSS_Signature sign(const secure_vector& msg, XMSS_PrivateKey& xmss_priv_key); - - wots_keysig_t build_auth_path(XMSS_PrivateKey& priv_key, XMSS_Address& adrs); - void initialize(); XMSS_PrivateKey m_priv_key; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_tools.h botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_tools.h --- botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_tools.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_tools.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,71 +9,56 @@ #define BOTAN_XMSS_TOOLS_H_ #include -#include +#include +#include +#include #include -#include namespace Botan { /** - * Helper tools for low level byte operations required - * for the XMSS implementation. - **/ -class XMSS_Tools final { - public: - XMSS_Tools() = delete; - XMSS_Tools(const XMSS_Tools&) = delete; - void operator=(const XMSS_Tools&) = delete; - - /** - * Concatenates the byte representation in big-endian order of any - * integral value to a secure_vector. - * - * @param target Vector to concatenate the byte representation of the - * integral value to. - * @param src integral value to concatenate. - **/ - template ::value, void>::type> - static void concat(secure_vector& target, const T& src); - - /** - * Concatenates the last n bytes of the byte representation in big-endian - * order of any integral value to a to a secure_vector. - * - * @param target Vector to concatenate the byte representation of the - * integral value to. - * @param src Integral value to concatenate. - * @param len number of bytes to concatenate. This value must be smaller - * or equal to the size of type T. - **/ - template ::value, void>::type> - static void concat(secure_vector& target, const T& src, size_t len); -}; - -template -void XMSS_Tools::concat(secure_vector& target, const T& src) { +* Concatenates the byte representation in big-endian order of any +* integral value to a secure_vector. +* +* @param target Vector to concatenate the byte representation of the +* integral value to. +* @param src integral value to concatenate. +**/ +template +void xmss_concat(secure_vector& target, const T& src) { const uint8_t* src_bytes = reinterpret_cast(&src); - if(CPUID::is_little_endian()) { + if constexpr(std::endian::native == std::endian::little) { std::reverse_copy(src_bytes, src_bytes + sizeof(src), std::back_inserter(target)); } else { std::copy(src_bytes, src_bytes + sizeof(src), std::back_inserter(target)); } } -template -void XMSS_Tools::concat(secure_vector& target, const T& src, size_t len) { - size_t c = static_cast(std::min(len, sizeof(src))); +/** +* Concatenates the last n bytes of the byte representation in big-endian +* order of any integral value to a to a secure_vector. +* +* @param target Vector to concatenate the byte representation of the +* integral value to. +* @param src Integral value to concatenate. +* @param len number of bytes to concatenate. This value must be smaller +* or equal to the size of type T. +**/ +template +void xmss_concat(secure_vector& target, const T& src, size_t len) { + const size_t c = static_cast(std::min(len, sizeof(src))); if(len > sizeof(src)) { target.resize(target.size() + len - sizeof(src), 0); } const uint8_t* src_bytes = reinterpret_cast(&src); - if(CPUID::is_little_endian()) { + if constexpr(std::endian::native == std::endian::little) { std::reverse_copy(src_bytes, src_bytes + c, std::back_inserter(target)); } else { std::copy(src_bytes + sizeof(src) - c, src_bytes + sizeof(src), std::back_inserter(target)); } } + } // namespace Botan #endif diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_verification_operation.cpp botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_verification_operation.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_verification_operation.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_verification_operation.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -21,15 +21,16 @@ secure_vector XMSS_Verification_Operation::root_from_signature(const XMSS_Signature& sig, const secure_vector& msg, - XMSS_Address& adrs, const secure_vector& seed) { const auto& params = m_pub_key.xmss_parameters(); const uint32_t next_index = static_cast(sig.unused_leaf_index()); + XMSS_Address adrs; adrs.set_type(XMSS_Address::Type::OTS_Hash_Address); adrs.set_ots_address(next_index); - XMSS_WOTS_PublicKey pub_key_ots(params.ots_oid(), seed, sig.tree().ots_signature, msg, adrs, m_hash); + const XMSS_WOTS_Parameters wots_params = params.wots_parameters(); + const XMSS_WOTS_PublicKey pub_key_ots(wots_params, seed, sig.tree().ots_signature, msg, adrs, m_hash); adrs.set_type(XMSS_Address::Type::LTree_Address); adrs.set_ltree_address(next_index); @@ -59,12 +60,11 @@ bool XMSS_Verification_Operation::verify(const XMSS_Signature& sig, const secure_vector& msg, const XMSS_PublicKey& public_key) { - XMSS_Address adrs; secure_vector index_bytes; - XMSS_Tools::concat(index_bytes, sig.unused_leaf_index(), m_pub_key.xmss_parameters().element_size()); - secure_vector msg_digest = m_hash.h_msg(sig.randomness(), public_key.root(), index_bytes, msg); + xmss_concat(index_bytes, sig.unused_leaf_index(), m_pub_key.xmss_parameters().element_size()); + const secure_vector msg_digest = m_hash.h_msg(sig.randomness(), public_key.root(), index_bytes, msg); - secure_vector node = root_from_signature(sig, msg_digest, adrs, public_key.public_seed()); + const secure_vector node = root_from_signature(sig, msg_digest, public_key.public_seed()); return (node == public_key.root()); } @@ -82,8 +82,8 @@ bool XMSS_Verification_Operation::is_valid_signature(std::span sig) { try { - XMSS_Signature signature(m_pub_key.xmss_parameters().oid(), sig); - bool result = verify(signature, m_msg_buf, m_pub_key); + const XMSS_Signature signature(m_pub_key.xmss_parameters().oid(), sig); + const bool result = verify(signature, m_msg_buf, m_pub_key); m_msg_buf.clear(); return result; } catch(...) { diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_verification_operation.h botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_verification_operation.h --- botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_verification_operation.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_verification_operation.h 2026-05-07 01:38:28.000000000 +0000 @@ -10,6 +10,7 @@ #include #include +#include #include namespace Botan { @@ -20,7 +21,7 @@ **/ class XMSS_Verification_Operation final : public virtual PK_Ops::Verification { public: - XMSS_Verification_Operation(const XMSS_PublicKey& public_key); + explicit XMSS_Verification_Operation(const XMSS_PublicKey& public_key); bool is_valid_signature(std::span sign) override; @@ -35,7 +36,6 @@ * * @param msg A message. * @param sig The XMSS signature for msg. - * @param ards A XMSS tree address. * @param seed A seed. * * @return An n-byte string holding the value of the root of a tree @@ -43,7 +43,6 @@ **/ secure_vector root_from_signature(const XMSS_Signature& sig, const secure_vector& msg, - XMSS_Address& ards, const secure_vector& seed); /** diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_wots.cpp botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_wots.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_wots.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_wots.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,19 +1,22 @@ /* * XMSS WOTS Public and Private Key - + * * A Winternitz One Time Signature public/private key for use with * Extended Hash-Based Signatures. * * (C) 2016,2017,2018 Matthias Gierlings * 2023 René Meusel - Rohde & Schwarz Cybersecurity + * 2026 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) **/ #include -#include +#include +#include #include +#include #include namespace Botan { @@ -21,6 +24,47 @@ namespace { /** +* Algorithm 1 (base_w) followed by the WOTS+ checksum, as used by the +* signing and verification routines in RFC 8391. The result is a single +* buffer of length params.len() holding the len_1 base-w digits of the +* message followed by the len_2 base-w digits of the checksum. +*/ +secure_vector base_w_with_checksum(const XMSS_WOTS_Parameters& params, std::span input) { + const size_t len_1 = params.len_1(); + const size_t len_2 = params.len_2(); + const size_t lg_w = params.lg_w(); + const uint8_t mask = static_cast(params.wots_parameter() - 1); + + BOTAN_ASSERT_NOMSG(input.size() * 8 >= len_1 * lg_w); + + secure_vector result(len_1 + len_2); + + size_t in = 0; + size_t total = 0; + size_t bits = 0; + for(size_t i = 0; i < len_1; ++i) { + if(bits == 0) { + total = input[in++]; + bits = 8; + } + bits -= lg_w; + result[i] = static_cast((total >> bits) & mask); + } + + size_t csum = 0; + for(size_t i = 0; i < len_1; ++i) { + csum += params.wots_parameter() - 1 - result[i]; + } + + for(size_t i = 0; i < len_2; ++i) { + const size_t shift = lg_w * (len_2 - 1 - i); + result[len_1 + i] = static_cast((csum >> shift) & mask); + } + + return result; +} + +/** * Algorithm 2: Chaining Function. * * Takes an n-byte input string and transforms it into a the function @@ -28,7 +72,7 @@ * the input x using the outputs of the PRNG "G". * * This overload is used in multithreaded scenarios, where it is - * required to provide seperate instances of XMSS_Hash to each + * required to provide separate instances of XMSS_Hash to each * thread. * * @param params The WOTS parameters to use @@ -45,7 +89,7 @@ secure_vector& result, size_t start_idx, size_t steps, - XMSS_Address& adrs, + XMSS_Address adrs, std::span seed, XMSS_Hash& hash) { BOTAN_ASSERT_NOMSG(result.size() == hash.output_length()); @@ -79,9 +123,9 @@ XMSS_WOTS_PublicKey::XMSS_WOTS_PublicKey(XMSS_WOTS_Parameters params, std::span public_seed, const XMSS_WOTS_PrivateKey& private_key, - XMSS_Address& adrs, + XMSS_Address adrs, XMSS_Hash& hash) : - XMSS_WOTS_Base(std::move(params), private_key.key_data()) { + XMSS_WOTS_Base(params, private_key.key_data()) { for(size_t i = 0; i < m_params.len(); ++i) { adrs.set_chain_address(static_cast(i)); chain(m_params, m_key_data[i], 0, m_params.wots_parameter() - 1, adrs, public_seed, hash); @@ -92,12 +136,10 @@ std::span public_seed, wots_keysig_t signature, const secure_vector& msg, - XMSS_Address& adrs, + XMSS_Address adrs, XMSS_Hash& hash) : - XMSS_WOTS_Base(std::move(params), std::move(signature)) { - secure_vector msg_digest{m_params.base_w(msg, m_params.len_1())}; - - m_params.append_checksum(msg_digest); + XMSS_WOTS_Base(params, std::move(signature)) { + const secure_vector msg_digest = base_w_with_checksum(m_params, msg); for(size_t i = 0; i < m_params.len(); i++) { adrs.set_chain_address(static_cast(i)); @@ -113,11 +155,9 @@ wots_keysig_t XMSS_WOTS_PrivateKey::sign(const secure_vector& msg, std::span public_seed, - XMSS_Address& adrs, + XMSS_Address adrs, XMSS_Hash& hash) { - secure_vector msg_digest{m_params.base_w(msg, m_params.len_1())}; - - m_params.append_checksum(msg_digest); + const secure_vector msg_digest = base_w_with_checksum(m_params, msg); auto sig = this->key_data(); for(size_t i = 0; i < m_params.len(); i++) { @@ -133,7 +173,7 @@ std::span private_seed, XMSS_Address adrs, XMSS_Hash& hash) : - XMSS_WOTS_Base(std::move(params)) { + XMSS_WOTS_Base(params) { m_key_data.resize(m_params.len()); for(size_t i = 0; i < m_params.len(); ++i) { adrs.set_chain_address(static_cast(i)); @@ -147,14 +187,14 @@ std::span private_seed, XMSS_Address adrs, XMSS_Hash& hash) : - XMSS_WOTS_Base(std::move(params)) { + XMSS_WOTS_Base(params) { m_key_data.resize(m_params.len()); secure_vector r; hash.prf(r, private_seed, adrs.bytes()); for(size_t i = 0; i < m_params.len(); ++i) { - XMSS_Tools::concat(m_key_data[i], i, 32); + xmss_concat(m_key_data[i], i, 32); hash.prf(m_key_data[i], r, m_key_data[i]); } } diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_wots.h botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_wots.h --- botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_wots.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_wots.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,37 +9,30 @@ #ifndef BOTAN_XMSS_WOTS_H_ #define BOTAN_XMSS_WOTS_H_ -#include -#include -#include -#include #include #include -#include -#include -#include -#include +#include #include namespace Botan { -class XMSS_Address; +class XMSS_Hash; class XMSS_WOTS_PrivateKey; typedef std::vector> wots_keysig_t; class XMSS_WOTS_Base { public: - XMSS_WOTS_Base(XMSS_WOTS_Parameters params) : m_params(std::move(params)) {} + explicit XMSS_WOTS_Base(XMSS_WOTS_Parameters params) : m_params(params) {} XMSS_WOTS_Base(XMSS_WOTS_Parameters params, wots_keysig_t key_data) : - m_params(std::move(params)), m_key_data(std::move(key_data)) {} + m_params(params), m_key_data(std::move(key_data)) {} const wots_keysig_t& key_data() const { return m_key_data; } protected: - XMSS_WOTS_Parameters m_params; - wots_keysig_t m_key_data; + XMSS_WOTS_Parameters m_params; // NOLINT(*non-private-member-variable*) + wots_keysig_t m_key_data; // NOLINT(*non-private-member-variable*) }; /** @@ -55,7 +48,7 @@ * function. * * This overload is used in multithreaded scenarios, where it is - * required to provide seperate instances of XMSS_Hash to each + * required to provide separate instances of XMSS_Hash to each * thread. * * @param params The WOTS parameters to use @@ -68,7 +61,7 @@ XMSS_WOTS_PublicKey(XMSS_WOTS_Parameters params, std::span public_seed, const XMSS_WOTS_PrivateKey& private_key, - XMSS_Address& adrs, + XMSS_Address adrs, XMSS_Hash& hash); /** @@ -88,7 +81,7 @@ std::span public_seed, wots_keysig_t signature, const secure_vector& msg, - XMSS_Address& adrs, + XMSS_Address adrs, XMSS_Hash& hash); }; @@ -108,7 +101,7 @@ * recommendation. * * This overload is used in multithreaded scenarios, where it is - * required to provide seperate instances of XMSS_Hash to each thread. + * required to provide separate instances of XMSS_Hash to each thread. * * @param params The WOTS parameters to use * @param public_seed The public seed for the private key generation @@ -146,7 +139,7 @@ * Generates a signature from a private key and a message. * * This overload is used in multithreaded scenarios, where it is - * required to provide seperate instances of XMSS_Hash to each + * required to provide separate instances of XMSS_Hash to each * thread. * * @param msg A message to sign. @@ -160,7 +153,7 @@ **/ wots_keysig_t sign(const secure_vector& msg, std::span public_seed, - XMSS_Address& adrs, + XMSS_Address adrs, XMSS_Hash& hash); }; diff -Nru botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_wots_parameters.cpp botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_wots_parameters.cpp --- botan3-3.7.1+dfsg/src/lib/pubkey/xmss/xmss_wots_parameters.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/pubkey/xmss/xmss_wots_parameters.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ /* * XMSS WOTS Parameters - * Descibes a signature method for XMSS Winternitz One Time Signatures, + * Describes a signature method for XMSS Winternitz One Time Signatures, * as defined in: * [1] XMSS: Extended Hash-Based Signatures, * Request for Comments: 8391 @@ -8,155 +8,53 @@ * https://datatracker.ietf.org/doc/rfc8391/ * * (C) 2016,2017,2018 Matthias Gierlings + * 2026 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) **/ -#include +#include +#include #include -#include -#include -#include namespace Botan { -XMSS_WOTS_Parameters::ots_algorithm_t XMSS_WOTS_Parameters::xmss_wots_id_from_string(std::string_view param_set) { - if(param_set == "WOTSP-SHA2_256") { - return WOTSP_SHA2_256; - } - if(param_set == "WOTSP-SHA2_512") { - return WOTSP_SHA2_512; - } - if(param_set == "WOTSP-SHAKE_256") { - return WOTSP_SHAKE_256; - } - if(param_set == "WOTSP-SHAKE_512") { - return WOTSP_SHAKE_512; - } - if(param_set == "WOTSP-SHA2_192") { - return WOTSP_SHA2_192; - } - if(param_set == "WOTSP-SHAKE_256_256") { - return WOTSP_SHAKE_256_256; - } - if(param_set == "WOTSP-SHAKE_256_192") { - return WOTSP_SHAKE_256_192; - } - - throw Lookup_Error(fmt("Unknown XMSS-WOTS algorithm param '{}'", param_set)); +XMSS_WOTS_Parameters XMSS_WOTS_Parameters::from_hash_len(ots_algorithm_t id, size_t hash_len) { + BOTAN_ASSERT_NOMSG(hash_len == 24 || hash_len == 32 || hash_len == 64); + const size_t len1 = 2 * hash_len; + // Theoretically this is a computed parameter based on the hash length and the Winternitz parameter + // We always use W=16, so len2 = 3 is correct for all hash lengths between 18 and 270 bytes. + const size_t len2 = 3; + return XMSS_WOTS_Parameters(id, hash_len, len1 + len2, len1, len2); } -XMSS_WOTS_Parameters::XMSS_WOTS_Parameters(std::string_view param_set) : - XMSS_WOTS_Parameters(xmss_wots_id_from_string(param_set)) {} - -XMSS_WOTS_Parameters::XMSS_WOTS_Parameters(ots_algorithm_t oid) : m_oid(oid) { - switch(oid) { +XMSS_WOTS_Parameters XMSS_WOTS_Parameters::from_id(ots_algorithm_t id) { + switch(id) { case WOTSP_SHA2_256: - m_element_size = 32; - m_w = 16; - m_len = 67; - m_name = "WOTSP-SHA2_256"; - m_hash_name = "SHA-256"; - m_strength = 256; - break; + return XMSS_WOTS_Parameters::from_hash_len(WOTSP_SHA2_256, 32); + case WOTSP_SHA2_512: - m_element_size = 64; - m_w = 16; - m_len = 131; - m_name = "WOTSP-SHA2_512"; - m_hash_name = "SHA-512"; - m_strength = 512; - break; + return XMSS_WOTS_Parameters::from_hash_len(WOTSP_SHA2_512, 64); + case WOTSP_SHAKE_256: - m_element_size = 32; - m_w = 16; - m_len = 67; - m_name = "WOTSP-SHAKE_256"; - m_hash_name = "SHAKE-128(256)"; - m_strength = 256; - break; + return XMSS_WOTS_Parameters::from_hash_len(WOTSP_SHAKE_256, 32); + case WOTSP_SHAKE_512: - m_element_size = 64; - m_w = 16; - m_len = 131; - m_name = "WOTSP-SHAKE_512"; - m_hash_name = "SHAKE-256(512)"; - m_strength = 512; - break; + return XMSS_WOTS_Parameters::from_hash_len(WOTSP_SHAKE_512, 64); + case WOTSP_SHA2_192: - m_element_size = 24; - m_w = 16; - m_len = 51; - m_name = "WOTSP-SHA2_192"; - m_hash_name = "Truncated(SHA-256,192)"; - m_strength = 192; - break; + return XMSS_WOTS_Parameters::from_hash_len(WOTSP_SHA2_192, 24); + case WOTSP_SHAKE_256_256: - m_element_size = 32; - m_w = 16; - m_len = 67; - m_name = "WOTSP-SHAKE_256_256"; - m_hash_name = "SHAKE-256(256)"; - m_strength = 256; - break; + return XMSS_WOTS_Parameters::from_hash_len(WOTSP_SHAKE_256_256, 32); + case WOTSP_SHAKE_256_192: - m_element_size = 24; - m_w = 16; - m_len = 51; - m_name = "WOTSP-SHAKE_256_192"; - m_hash_name = "SHAKE-256(192)"; - m_strength = 192; - break; + return XMSS_WOTS_Parameters::from_hash_len(WOTSP_SHAKE_256_192, 24); + default: throw Not_Implemented("Algorithm id does not match any known XMSS WOTS algorithm id."); } - - m_lg_w = (m_w == 16) ? 4 : 2; - m_len_1 = static_cast(std::ceil((8 * element_size()) / m_lg_w)); - m_len_2 = static_cast(floor(log2(m_len_1 * (wots_parameter() - 1)) / m_lg_w) + 1); - BOTAN_ASSERT(m_len == m_len_1 + m_len_2, - "Invalid XMSS WOTS parameter " - "\"len\" detected."); -} - -secure_vector XMSS_WOTS_Parameters::base_w(const secure_vector& msg, size_t out_size) const { - secure_vector result; - result.reserve(out_size); - - size_t in = 0; - size_t total = 0; - size_t bits = 0; - - for(size_t i = 0; i < out_size; i++) { - if(bits == 0) { - total = msg[in]; - in++; - bits += 8; - } - bits -= m_lg_w; - result.push_back(static_cast((total >> bits) & (m_w - 1))); - } - return result; -} - -secure_vector XMSS_WOTS_Parameters::base_w(size_t value) const { - value <<= (8 - ((m_len_2 * m_lg_w) % 8)); - size_t len_2_bytes = static_cast(std::ceil(static_cast(m_len_2 * m_lg_w) / 8.0)); - secure_vector result; - XMSS_Tools::concat(result, value, len_2_bytes); - return base_w(result, m_len_2); -} - -void XMSS_WOTS_Parameters::append_checksum(secure_vector& data) const { - size_t csum = 0; - - for(size_t i = 0; i < data.size(); i++) { - csum += wots_parameter() - 1 - data[i]; - } - - secure_vector csum_bytes = base_w(csum); - std::move(csum_bytes.begin(), csum_bytes.end(), std::back_inserter(data)); } } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/rng/auto_rng/auto_rng.cpp botan3-3.12.0+dfsg/src/lib/rng/auto_rng/auto_rng.cpp --- botan3-3.7.1+dfsg/src/lib/rng/auto_rng/auto_rng.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/rng/auto_rng/auto_rng.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,8 +6,10 @@ #include +#include +#include #include -#include +#include #if defined(BOTAN_HAS_ENTROPY_SOURCE) #include @@ -39,6 +41,8 @@ } // namespace +AutoSeeded_RNG::AutoSeeded_RNG(AutoSeeded_RNG&& other) noexcept = default; + AutoSeeded_RNG::~AutoSeeded_RNG() = default; AutoSeeded_RNG::AutoSeeded_RNG(RandomNumberGenerator& underlying_rng, size_t reseed_interval) { @@ -95,8 +99,8 @@ return m_rng->name(); } -size_t AutoSeeded_RNG::reseed(Entropy_Sources& srcs, size_t poll_bits, std::chrono::milliseconds poll_timeout) { - return m_rng->reseed(srcs, poll_bits, poll_timeout); +size_t AutoSeeded_RNG::reseed_from_sources(Entropy_Sources& srcs, size_t poll_bits) { + return m_rng->reseed_from_sources(srcs, poll_bits); } void AutoSeeded_RNG::fill_bytes_with_input(std::span out, std::span in) { diff -Nru botan3-3.7.1+dfsg/src/lib/rng/auto_rng/auto_rng.h botan3-3.12.0+dfsg/src/lib/rng/auto_rng/auto_rng.h --- botan3-3.7.1+dfsg/src/lib/rng/auto_rng/auto_rng.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/rng/auto_rng/auto_rng.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,6 +9,7 @@ #define BOTAN_AUTO_SEEDING_RNG_H_ #include +#include namespace Botan { @@ -28,9 +29,8 @@ */ void force_reseed(); - size_t reseed(Entropy_Sources& srcs, - size_t poll_bits = BOTAN_RNG_RESEED_POLL_BITS, - std::chrono::milliseconds poll_timeout = BOTAN_RNG_RESEED_DEFAULT_TIMEOUT) override; + size_t reseed_from_sources(Entropy_Sources& srcs, + size_t poll_bits = RandomNumberGenerator::DefaultPollBits) override; std::string name() const override; @@ -43,7 +43,7 @@ * @param reseed_interval specifies a limit of how many times * the RNG will be called before automatic reseeding is performed */ - AutoSeeded_RNG(size_t reseed_interval = BOTAN_RNG_DEFAULT_RESEED_INTERVAL); + BOTAN_FUTURE_EXPLICIT AutoSeeded_RNG(size_t reseed_interval = RandomNumberGenerator::DefaultReseedInterval); /** * Create an AutoSeeded_RNG which will get seed material from some other @@ -55,7 +55,8 @@ * @param reseed_interval specifies a limit of how many times * the RNG will be called before automatic reseeding is performed */ - AutoSeeded_RNG(RandomNumberGenerator& underlying_rng, size_t reseed_interval = BOTAN_RNG_DEFAULT_RESEED_INTERVAL); + BOTAN_FUTURE_EXPLICIT AutoSeeded_RNG(RandomNumberGenerator& underlying_rng, + size_t reseed_interval = RandomNumberGenerator::DefaultReseedInterval); /** * Create an AutoSeeded_RNG which will get seed material from a set of @@ -65,7 +66,8 @@ * @param reseed_interval specifies a limit of how many times * the RNG will be called before automatic reseeding is performed */ - AutoSeeded_RNG(Entropy_Sources& entropy_sources, size_t reseed_interval = BOTAN_RNG_DEFAULT_RESEED_INTERVAL); + BOTAN_FUTURE_EXPLICIT AutoSeeded_RNG(Entropy_Sources& entropy_sources, + size_t reseed_interval = RandomNumberGenerator::DefaultReseedInterval); /** * Create an AutoSeeded_RNG which will get seed material from both an @@ -79,7 +81,12 @@ */ AutoSeeded_RNG(RandomNumberGenerator& underlying_rng, Entropy_Sources& entropy_sources, - size_t reseed_interval = BOTAN_RNG_DEFAULT_RESEED_INTERVAL); + size_t reseed_interval = RandomNumberGenerator::DefaultReseedInterval); + + AutoSeeded_RNG(const AutoSeeded_RNG& other) = delete; + AutoSeeded_RNG(AutoSeeded_RNG&& other) noexcept; + AutoSeeded_RNG& operator=(const AutoSeeded_RNG& other) = delete; + AutoSeeded_RNG& operator=(AutoSeeded_RNG&& other) = delete; ~AutoSeeded_RNG() override; diff -Nru botan3-3.7.1+dfsg/src/lib/rng/auto_rng/info.txt botan3-3.12.0+dfsg/src/lib/rng/auto_rng/info.txt --- botan3-3.7.1+dfsg/src/lib/rng/auto_rng/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/rng/auto_rng/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,8 @@ -AUTO_SEEDING_RNG -> 20160821 AUTO_RNG -> 20161126 + +# TODO(Botan4) remove this +AUTO_SEEDING_RNG -> 20160821 diff -Nru botan3-3.7.1+dfsg/src/lib/rng/chacha_rng/chacha_rng.cpp botan3-3.12.0+dfsg/src/lib/rng/chacha_rng/chacha_rng.cpp --- botan3-3.7.1+dfsg/src/lib/rng/chacha_rng/chacha_rng.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/rng/chacha_rng/chacha_rng.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,15 +7,17 @@ #include +#include + namespace Botan { -ChaCha_RNG::ChaCha_RNG() : Stateful_RNG() { +ChaCha_RNG::ChaCha_RNG() { m_hmac = MessageAuthenticationCode::create_or_throw("HMAC(SHA-256)"); m_chacha = StreamCipher::create_or_throw("ChaCha(20)"); clear(); } -ChaCha_RNG::ChaCha_RNG(std::span seed) : Stateful_RNG() { +ChaCha_RNG::ChaCha_RNG(std::span seed) { m_hmac = MessageAuthenticationCode::create_or_throw("HMAC(SHA-256)"); m_chacha = StreamCipher::create_or_throw("ChaCha(20)"); clear(); diff -Nru botan3-3.7.1+dfsg/src/lib/rng/chacha_rng/chacha_rng.h botan3-3.12.0+dfsg/src/lib/rng/chacha_rng/chacha_rng.h --- botan3-3.7.1+dfsg/src/lib/rng/chacha_rng/chacha_rng.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/rng/chacha_rng/chacha_rng.h 2026-05-07 01:38:28.000000000 +0000 @@ -61,7 +61,7 @@ * * @param seed the seed material, should be at least 256 bits */ - ChaCha_RNG(std::span seed); + BOTAN_FUTURE_EXPLICIT ChaCha_RNG(std::span seed); /** * Automatic reseeding from @p underlying_rng will take place after @@ -72,7 +72,8 @@ * @param reseed_interval specifies a limit of how many times * the RNG will be called before automatic reseeding is performed */ - ChaCha_RNG(RandomNumberGenerator& underlying_rng, size_t reseed_interval = BOTAN_RNG_DEFAULT_RESEED_INTERVAL); + BOTAN_FUTURE_EXPLICIT ChaCha_RNG(RandomNumberGenerator& underlying_rng, + size_t reseed_interval = RandomNumberGenerator::DefaultReseedInterval); /** * Automatic reseeding from @p entropy_sources will take place after @@ -82,7 +83,8 @@ * @param reseed_interval specifies a limit of how many times * the RNG will be called before automatic reseeding is performed. */ - ChaCha_RNG(Entropy_Sources& entropy_sources, size_t reseed_interval = BOTAN_RNG_DEFAULT_RESEED_INTERVAL); + BOTAN_FUTURE_EXPLICIT ChaCha_RNG(Entropy_Sources& entropy_sources, + size_t reseed_interval = RandomNumberGenerator::DefaultReseedInterval); /** * Automatic reseeding from @p underlying_rng and @p entropy_sources @@ -97,7 +99,7 @@ */ ChaCha_RNG(RandomNumberGenerator& underlying_rng, Entropy_Sources& entropy_sources, - size_t reseed_interval = BOTAN_RNG_DEFAULT_RESEED_INTERVAL); + size_t reseed_interval = RandomNumberGenerator::DefaultReseedInterval); std::string name() const override { return "ChaCha_RNG"; } diff -Nru botan3-3.7.1+dfsg/src/lib/rng/esdm_rng/esdm_rng.cpp botan3-3.12.0+dfsg/src/lib/rng/esdm_rng/esdm_rng.cpp --- botan3-3.7.1+dfsg/src/lib/rng/esdm_rng/esdm_rng.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/rng/esdm_rng/esdm_rng.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,6 +7,7 @@ #include +#include #include #include @@ -19,7 +20,7 @@ * as soon as all instances of ESDM_RNG are destructed. This may * happen multiple times in the lifetime of the process. */ -class ESDM_Context { +class ESDM_Context final { public: [[nodiscard]] static std::shared_ptr instance() { static ESDM_Context g_instance; diff -Nru botan3-3.7.1+dfsg/src/lib/rng/esdm_rng/esdm_rng.h botan3-3.12.0+dfsg/src/lib/rng/esdm_rng/esdm_rng.h --- botan3-3.7.1+dfsg/src/lib/rng/esdm_rng/esdm_rng.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/rng/esdm_rng/esdm_rng.h 2026-05-07 01:38:28.000000000 +0000 @@ -34,7 +34,7 @@ * esdm_rpcc_get_random_bytes_full (fully seeded) calls have to be used. * * Configurable modes: -* - fully seeded (-> fast): provide entropy from a DRBG/PRNG after beeing fully seeded, +* - fully seeded (-> fast): provide entropy from a DRBG/PRNG after being fully seeded, * block until this point is reached, reseed from after a time * and/or invocation limit, block again if reseeding is not possible * - prediction resistance (-> slow): reseed ESDM with fresh entropy after each invocation @@ -96,7 +96,7 @@ private: /** - * tracks if predicition resistant or fully seeded interface should be queried + * tracks if prediction resistant or fully seeded interface should be queried */ bool m_prediction_resistance; diff -Nru botan3-3.7.1+dfsg/src/lib/rng/hmac_drbg/hmac_drbg.cpp botan3-3.12.0+dfsg/src/lib/rng/hmac_drbg/hmac_drbg.cpp --- botan3-3.7.1+dfsg/src/lib/rng/hmac_drbg/hmac_drbg.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/rng/hmac_drbg/hmac_drbg.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,6 +7,10 @@ #include +#include +#include +#include +#include #include #include @@ -22,6 +26,10 @@ // SHA-256, SHA-512/256, SHA-384, SHA-512: >= 256 bits // NIST SP 800-90A only supports up to 256 bits though + if(mac_output_length < 20) { + throw Invalid_Argument(fmt("HMAC_DRBG MAC output length {} is too small", mac_output_length)); + } + if(mac_output_length < 32) { return (mac_output_length - 4) * 8; } else { @@ -41,18 +49,24 @@ } } +template +std::unique_ptr check_not_null(std::unique_ptr obj) { + BOTAN_ARG_CHECK(obj != nullptr, "Argument must not be null"); + return obj; +} + } // namespace +HMAC_DRBG::~HMAC_DRBG() = default; + HMAC_DRBG::HMAC_DRBG(std::unique_ptr prf, RandomNumberGenerator& underlying_rng, size_t reseed_interval, size_t max_number_of_bytes_per_request) : Stateful_RNG(underlying_rng, reseed_interval), - m_mac(std::move(prf)), + m_mac(check_not_null(std::move(prf))), m_max_number_of_bytes_per_request(max_number_of_bytes_per_request), m_security_level(hmac_drbg_security_level(m_mac->output_length())) { - BOTAN_ASSERT_NONNULL(m_mac); - check_limits(reseed_interval, max_number_of_bytes_per_request); clear(); @@ -64,11 +78,9 @@ size_t reseed_interval, size_t max_number_of_bytes_per_request) : Stateful_RNG(underlying_rng, entropy_sources, reseed_interval), - m_mac(std::move(prf)), + m_mac(check_not_null(std::move(prf))), m_max_number_of_bytes_per_request(max_number_of_bytes_per_request), m_security_level(hmac_drbg_security_level(m_mac->output_length())) { - BOTAN_ASSERT_NONNULL(m_mac); - check_limits(reseed_interval, max_number_of_bytes_per_request); clear(); @@ -79,27 +91,22 @@ size_t reseed_interval, size_t max_number_of_bytes_per_request) : Stateful_RNG(entropy_sources, reseed_interval), - m_mac(std::move(prf)), + m_mac(check_not_null(std::move(prf))), m_max_number_of_bytes_per_request(max_number_of_bytes_per_request), m_security_level(hmac_drbg_security_level(m_mac->output_length())) { - BOTAN_ASSERT_NONNULL(m_mac); - check_limits(reseed_interval, max_number_of_bytes_per_request); clear(); } HMAC_DRBG::HMAC_DRBG(std::unique_ptr prf) : - Stateful_RNG(), - m_mac(std::move(prf)), + m_mac(check_not_null(std::move(prf))), m_max_number_of_bytes_per_request(64 * 1024), m_security_level(hmac_drbg_security_level(m_mac->output_length())) { - BOTAN_ASSERT_NONNULL(m_mac); clear(); } HMAC_DRBG::HMAC_DRBG(std::string_view hmac_hash) : - Stateful_RNG(), m_mac(MessageAuthenticationCode::create_or_throw(fmt("HMAC({})", hmac_hash))), m_max_number_of_bytes_per_request(64 * 1024), m_security_level(hmac_drbg_security_level(m_mac->output_length())) { @@ -113,9 +120,7 @@ m_T.resize(output_length); } - for(size_t i = 0; i != m_V.size(); ++i) { - m_V[i] = 0x01; - } + std::fill(m_V.begin(), m_V.end(), 0x01); m_mac->set_key(std::vector(m_V.size(), 0x00)); } @@ -128,6 +133,7 @@ * See NIST SP800-90A section 10.1.2.5 */ void HMAC_DRBG::generate_output(std::span output, std::span input) { + // This is an internal function, callers should have validated this beforehand BOTAN_ASSERT_NOMSG(!output.empty()); if(!input.empty()) { diff -Nru botan3-3.7.1+dfsg/src/lib/rng/hmac_drbg/hmac_drbg.h botan3-3.12.0+dfsg/src/lib/rng/hmac_drbg/hmac_drbg.h --- botan3-3.7.1+dfsg/src/lib/rng/hmac_drbg/hmac_drbg.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/rng/hmac_drbg/hmac_drbg.h 2026-05-07 01:38:28.000000000 +0000 @@ -8,11 +8,12 @@ #ifndef BOTAN_HMAC_DRBG_H_ #define BOTAN_HMAC_DRBG_H_ -#include #include +#include namespace Botan { +class MessageAuthenticationCode; class Entropy_Sources; /** @@ -62,7 +63,7 @@ */ HMAC_DRBG(std::unique_ptr prf, RandomNumberGenerator& underlying_rng, - size_t reseed_interval = BOTAN_RNG_DEFAULT_RESEED_INTERVAL, + size_t reseed_interval = RandomNumberGenerator::DefaultReseedInterval, size_t max_number_of_bytes_per_request = 64 * 1024); /** @@ -89,7 +90,7 @@ */ HMAC_DRBG(std::unique_ptr prf, Entropy_Sources& entropy_sources, - size_t reseed_interval = BOTAN_RNG_DEFAULT_RESEED_INTERVAL, + size_t reseed_interval = RandomNumberGenerator::DefaultReseedInterval, size_t max_number_of_bytes_per_request = 64 * 1024); /** @@ -120,9 +121,17 @@ HMAC_DRBG(std::unique_ptr prf, RandomNumberGenerator& underlying_rng, Entropy_Sources& entropy_sources, - size_t reseed_interval = BOTAN_RNG_DEFAULT_RESEED_INTERVAL, + size_t reseed_interval = RandomNumberGenerator::DefaultReseedInterval, size_t max_number_of_bytes_per_request = 64 * 1024); + ~HMAC_DRBG() override; + + HMAC_DRBG(const HMAC_DRBG& rng) = delete; + HMAC_DRBG& operator=(const HMAC_DRBG& rng) = delete; + + HMAC_DRBG(HMAC_DRBG&& rng) = delete; + HMAC_DRBG& operator=(HMAC_DRBG&& rng) = delete; + std::string name() const override; size_t security_level() const override; diff -Nru botan3-3.7.1+dfsg/src/lib/rng/jitter_rng/jitter_rng.cpp botan3-3.12.0+dfsg/src/lib/rng/jitter_rng/jitter_rng.cpp --- botan3-3.7.1+dfsg/src/lib/rng/jitter_rng/jitter_rng.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/rng/jitter_rng/jitter_rng.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,6 +7,9 @@ #include +#include +#include + #include namespace Botan { @@ -21,16 +24,21 @@ }; Jitter_RNG_Internal::Jitter_RNG_Internal() { - static int result = jent_entropy_init(); + constexpr unsigned int oversampling_rate = 0; // use default oversampling + constexpr unsigned int flags = JENT_FORCE_FIPS; // enable health tests - // no further details documented regarding the return value - BOTAN_ASSERT(result == 0, "JitterRNG: initialization successful"); + // if flags and osr are used, use the same values for init and alloc + static int result = jent_entropy_init_ex(oversampling_rate, flags); - constexpr unsigned int oversampling_rate = 0; // use default oversampling - constexpr unsigned int flags = 0; + // no further details documented regarding the return value + if(result != 0) { + throw Internal_Error("Jitter_RNG_Internal initialization failed"); + } m_rand_data = jent_entropy_collector_alloc(oversampling_rate, flags); - BOTAN_ASSERT_NONNULL(m_rand_data); + if(m_rand_data == nullptr) { + throw Internal_Error("Jitter_RNG_Internal collector allocation failed"); + } } Jitter_RNG_Internal::~Jitter_RNG_Internal() { @@ -93,4 +101,5 @@ m_jitter->collect_into_buffer(out); } -}; // namespace Botan + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/rng/jitter_rng/jitter_rng.h botan3-3.12.0+dfsg/src/lib/rng/jitter_rng/jitter_rng.h --- botan3-3.7.1+dfsg/src/lib/rng/jitter_rng/jitter_rng.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/rng/jitter_rng/jitter_rng.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,6 +9,8 @@ #define BOTAN_JITTER_RNG_H_ #include +#include +#include namespace Botan { @@ -35,6 +37,7 @@ std::unique_ptr m_jitter; }; + } // namespace Botan #endif diff -Nru botan3-3.7.1+dfsg/src/lib/rng/processor_rng/info.txt botan3-3.12.0+dfsg/src/lib/rng/processor_rng/info.txt --- botan3-3.7.1+dfsg/src/lib/rng/processor_rng/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/rng/processor_rng/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -17,8 +17,6 @@ processor_rng.h - -x86_32:rdrand -x86_64:rdrand -ppc64:power9 - + +cpuid + diff -Nru botan3-3.7.1+dfsg/src/lib/rng/processor_rng/processor_rng.cpp botan3-3.12.0+dfsg/src/lib/rng/processor_rng/processor_rng.cpp --- botan3-3.7.1+dfsg/src/lib/rng/processor_rng/processor_rng.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/rng/processor_rng/processor_rng.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,10 +6,13 @@ #include +#include #include +#include #include +#include -#if defined(BOTAN_TARGET_CPU_IS_X86_FAMILY) +#if defined(BOTAN_TARGET_ARCH_IS_X86_FAMILY) && !defined(BOTAN_USE_GCC_INLINE_ASM) #include #endif @@ -17,14 +20,14 @@ namespace { -#if defined(BOTAN_TARGET_CPU_IS_X86_FAMILY) +#if defined(BOTAN_TARGET_ARCH_IS_X86_FAMILY) /* * According to Intel, RDRAND is guaranteed to generate a random * number within 10 retries on a working CPU */ const size_t HWRNG_RETRIES = 10; -#elif defined(BOTAN_TARGET_CPU_IS_PPC_FAMILY) +#elif defined(BOTAN_TARGET_ARCH_IS_PPC_FAMILY) /** * PowerISA 3.0 p.78: * When the error value is obtained, software is expected to repeat the @@ -47,14 +50,15 @@ typedef uint64_t hwrng_output; #endif -hwrng_output read_hwrng(bool& success) { - hwrng_output output = 0; +hwrng_output BOTAN_FN_ISA_RNG read_hwrng(bool& success) { + hwrng_output output = 0; // NOLINT(*-const-correctness) clang-tidy doesn't understand inline asm success = false; -#if defined(BOTAN_TARGET_CPU_IS_X86_FAMILY) - int cf = 0; +#if defined(BOTAN_TARGET_ARCH_IS_X86_FAMILY) + int cf = 0; // NOLINT(*-const-correctness) clang-tidy doesn't understand inline asm #if defined(BOTAN_USE_GCC_INLINE_ASM) // same asm seq works for 32 and 64 bit + // NOLINTNEXTLINE(*-no-assembler) asm volatile("rdrand %0; adcl $0,%1" : "=r"(output), "=r"(cf) : "0"(output), "1"(cf) : "cc"); #elif defined(BOTAN_TARGET_ARCH_IS_X86_32) cf = _rdrand32_step(&output); @@ -63,17 +67,17 @@ #endif success = (1 == cf); -#elif defined(BOTAN_TARGET_CPU_IS_PPC_FAMILY) +#elif defined(BOTAN_TARGET_ARCH_IS_PPC_FAMILY) /* DARN indicates error by returning 0xFF..FF, ie is biased. Which is crazy. Avoid the bias by invoking it twice and, assuming both succeed, returning the XOR of the two results, which should unbias the output. */ - uint64_t output2 = 0; + uint64_t output2 = 0; // NOLINT(*-const-correctness) clang-tidy doesn't understand inline asm // DARN codes are 0: 32-bit conditioned, 1: 64-bit conditioned, 2: 64-bit raw (ala RDSEED) - asm volatile("darn %0, 1" : "=r"(output)); - asm volatile("darn %0, 1" : "=r"(output2)); + asm volatile("darn %0, 1" : "=r"(output)); // NOLINT(*-no-assembler) + asm volatile("darn %0, 1" : "=r"(output2)); // NOLINT(*-no-assembler) if((~output) != 0 && (~output2) != 0) { output ^= output2; @@ -92,7 +96,7 @@ hwrng_output read_hwrng() { for(size_t i = 0; i < HWRNG_RETRIES; ++i) { bool success = false; - hwrng_output output = read_hwrng(success); + const hwrng_output output = read_hwrng(success); if(success) { return output; @@ -106,19 +110,19 @@ //static bool Processor_RNG::available() { -#if defined(BOTAN_TARGET_CPU_IS_X86_FAMILY) - return CPUID::has_rdrand(); -#elif defined(BOTAN_TARGET_CPU_IS_PPC_FAMILY) - return CPUID::has_darn_rng(); +#if defined(BOTAN_TARGET_ARCH_IS_X86_FAMILY) + return CPUID::has(CPUID::Feature::RDRAND); +#elif defined(BOTAN_TARGET_ARCH_IS_PPC_FAMILY) + return CPUID::has(CPUID::Feature::DARN); #else return false; #endif } std::string Processor_RNG::name() const { -#if defined(BOTAN_TARGET_CPU_IS_X86_FAMILY) +#if defined(BOTAN_TARGET_ARCH_IS_X86_FAMILY) return "rdrand"; -#elif defined(BOTAN_TARGET_CPU_IS_PPC_FAMILY) +#elif defined(BOTAN_TARGET_ARCH_IS_PPC_FAMILY) return "darn"; #else return "hwrng"; @@ -153,11 +157,4 @@ } } -size_t Processor_RNG::reseed(Entropy_Sources& /*srcs*/, - size_t /*poll_bits*/, - std::chrono::milliseconds /*poll_timeout*/) { - /* no way to add entropy */ - return 0; -} - } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/rng/processor_rng/processor_rng.h botan3-3.12.0+dfsg/src/lib/rng/processor_rng/processor_rng.h --- botan3-3.7.1+dfsg/src/lib/rng/processor_rng/processor_rng.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/rng/processor_rng/processor_rng.h 2026-05-07 01:38:28.000000000 +0000 @@ -32,11 +32,6 @@ bool is_seeded() const override { return true; } - /* - * No way to reseed processor provided generator, so reseed is ignored - */ - size_t reseed(Entropy_Sources&, size_t, std::chrono::milliseconds) override; - std::string name() const override; private: diff -Nru botan3-3.7.1+dfsg/src/lib/rng/rng.cpp botan3-3.12.0+dfsg/src/lib/rng/rng.cpp --- botan3-3.7.1+dfsg/src/lib/rng/rng.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/rng/rng.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,6 +6,7 @@ #include +#include #include #if defined(BOTAN_HAS_ENTROPY_SOURCE) @@ -48,12 +49,12 @@ } } -size_t RandomNumberGenerator::reseed(Entropy_Sources& srcs, size_t poll_bits, std::chrono::milliseconds poll_timeout) { +size_t RandomNumberGenerator::reseed_from_sources(Entropy_Sources& srcs, size_t poll_bits) { if(this->accepts_input()) { #if defined(BOTAN_HAS_ENTROPY_SOURCE) - return srcs.poll(*this, poll_bits, poll_timeout); + return srcs.poll(*this, poll_bits); #else - BOTAN_UNUSED(srcs, poll_bits, poll_timeout); + BOTAN_UNUSED(srcs, poll_bits); #endif } diff -Nru botan3-3.7.1+dfsg/src/lib/rng/rng.h botan3-3.12.0+dfsg/src/lib/rng/rng.h --- botan3-3.7.1+dfsg/src/lib/rng/rng.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/rng/rng.h 2026-05-07 01:38:28.000000000 +0000 @@ -10,17 +10,25 @@ #define BOTAN_RANDOM_NUMBER_GENERATOR_H_ #include -#include -#include #include #include -#include #include #include #include #include +/* +* We only include in downstream applications to avoid +* breaking semver wrt RandomNumberGenerator::reseed. Within the +* library we avoid it because it slows down compilation significantly. +* +* TODO(Botan4): remove this entirely +*/ +#if !defined(BOTAN_IS_BEING_BUILT) + #include +#endif + namespace Botan { class Entropy_Sources; @@ -30,6 +38,17 @@ */ class BOTAN_PUBLIC_API(2, 0) RandomNumberGenerator { public: + /** + * Userspace RNGs like HMAC_DRBG will reseed after a specified number + * of outputs are generated. Set to zero to disable automatic reseeding. + */ + static constexpr size_t DefaultReseedInterval = 1024; + + /** + * Number of entropy bits polled for reseeding userspace RNGs like HMAC_DRBG + */ + static constexpr size_t DefaultPollBits = 256; + virtual ~RandomNumberGenerator() = default; RandomNumberGenerator() = default; @@ -40,6 +59,9 @@ RandomNumberGenerator(const RandomNumberGenerator& rng) = delete; RandomNumberGenerator& operator=(const RandomNumberGenerator& rng) = delete; + RandomNumberGenerator(RandomNumberGenerator&& rng) = default; + RandomNumberGenerator& operator=(RandomNumberGenerator&& rng) = default; + /** * Randomize a byte array. * @@ -81,7 +103,7 @@ * Incorporate some additional data into the RNG state. */ template - requires std::is_standard_layout::value && std::is_trivial::value + requires std::is_standard_layout_v && std::is_trivial_v void add_entropy_T(const T& t) { this->add_entropy(reinterpret_cast(&t), sizeof(T)); } @@ -148,15 +170,15 @@ virtual bool is_seeded() const = 0; /** - * Poll provided sources for up to poll_bits bits of entropy - * or until the timeout expires. Returns estimate of the number - * of bits collected. - * + * Poll provided sources for up to poll_bits bits of entropy. + * Returns estimate of the number of bits collected. * Sets the seeded state to true if enough entropy was added. + * + * @throws Exception if RNG accepts input but reseeding failed. */ - virtual size_t reseed(Entropy_Sources& srcs, - size_t poll_bits = BOTAN_RNG_RESEED_POLL_BITS, - std::chrono::milliseconds poll_timeout = BOTAN_RNG_RESEED_DEFAULT_TIMEOUT); + size_t reseed_from(Entropy_Sources& srcs, size_t poll_bits = RandomNumberGenerator::DefaultPollBits) { + return reseed_from_sources(srcs, poll_bits); + } /** * Reseed by reading specified bits from the RNG @@ -165,7 +187,9 @@ * * @throws Exception if RNG accepts input but reseeding failed. */ - virtual void reseed_from_rng(RandomNumberGenerator& rng, size_t poll_bits = BOTAN_RNG_RESEED_POLL_BITS); + void reseed_from(RandomNumberGenerator& rng, size_t poll_bits = RandomNumberGenerator::DefaultPollBits) { + return reseed_from_rng(rng, poll_bits); + } // Some utility functions built on the interface above: @@ -214,7 +238,7 @@ */ template std::array random_array() { - std::array result; + std::array result{}; random_vec(result); return result; } @@ -226,7 +250,7 @@ * @throws Exception if the RNG fails */ uint8_t next_byte() { - uint8_t b; + uint8_t b = 0; this->fill_bytes_with_input(std::span(&b, 1), {}); return b; } @@ -244,8 +268,50 @@ return b; } + /** + * Reseed by reading specified bits from the RNG + * + * Sets the seeded state to true if enough entropy was added. + * + * @throws Exception if RNG accepts input but reseeding failed. + */ + virtual void reseed_from_rng(RandomNumberGenerator& rng, + size_t poll_bits = RandomNumberGenerator::DefaultPollBits); + +#if !defined(BOTAN_IS_BEING_BUILT) + /** + * Default poll timeout + */ + static constexpr auto DefaultPollTimeout = std::chrono::milliseconds(50); + + /** + * Poll provided sources for up to poll_bits bits of entropy. + * Returns estimate of the number of bits collected. + * + * Sets the seeded state to true if enough entropy was added. + * + * TODO(Botan4) remove this function + */ + BOTAN_DEPRECATED("Use reseed_from_sources") + inline size_t reseed(Entropy_Sources& srcs, + size_t poll_bits = RandomNumberGenerator::DefaultPollBits, + std::chrono::milliseconds /*unused_timeout*/ = DefaultPollTimeout) { + return reseed_from(srcs, poll_bits); + } +#endif + protected: /** + * Poll provided sources for up to poll_bits bits of entropy. + * Returns estimate of the number of bits collected. + * Sets the seeded state to true if enough entropy was added. + * + * @throws Exception if RNG accepts input but reseeding failed. + */ + virtual size_t reseed_from_sources(Entropy_Sources& srcs, + size_t poll_bits = RandomNumberGenerator::DefaultPollBits); + + /** * Generic interface to provide entropy to a concrete implementation and to * fill a given buffer with random output. Both @p output and @p input may * be empty and should be ignored in that case. If both buffers are diff -Nru botan3-3.7.1+dfsg/src/lib/rng/stateful_rng/stateful_rng.cpp botan3-3.12.0+dfsg/src/lib/rng/stateful_rng/stateful_rng.cpp --- botan3-3.7.1+dfsg/src/lib/rng/stateful_rng/stateful_rng.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/rng/stateful_rng/stateful_rng.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,30 +6,31 @@ #include -#include +#include +#include #include namespace Botan { void Stateful_RNG::clear() { - lock_guard_type lock(m_mutex); + const lock_guard_type lock(m_mutex); m_reseed_counter = 0; m_last_pid = 0; clear_state(); } void Stateful_RNG::force_reseed() { - lock_guard_type lock(m_mutex); + const lock_guard_type lock(m_mutex); m_reseed_counter = 0; } bool Stateful_RNG::is_seeded() const { - lock_guard_type lock(m_mutex); + const lock_guard_type lock(m_mutex); return m_reseed_counter > 0; } void Stateful_RNG::initialize_with(std::span input) { - lock_guard_type lock(m_mutex); + const lock_guard_type lock(m_mutex); clear(); add_entropy(input); @@ -60,7 +61,7 @@ } void Stateful_RNG::fill_bytes_with_input(std::span output, std::span input) { - lock_guard_type lock(m_mutex); + const lock_guard_type lock(m_mutex); if(output.empty()) { // Special case for exclusively adding entropy to the stateful RNG. @@ -74,10 +75,10 @@ } } -size_t Stateful_RNG::reseed(Entropy_Sources& srcs, size_t poll_bits, std::chrono::milliseconds poll_timeout) { - lock_guard_type lock(m_mutex); +size_t Stateful_RNG::reseed_from_sources(Entropy_Sources& srcs, size_t poll_bits) { + const lock_guard_type lock(m_mutex); - const size_t bits_collected = RandomNumberGenerator::reseed(srcs, poll_bits, poll_timeout); + const size_t bits_collected = RandomNumberGenerator::reseed_from_sources(srcs, poll_bits); if(bits_collected >= security_level()) { reset_reseed_counter(); @@ -87,7 +88,7 @@ } void Stateful_RNG::reseed_from_rng(RandomNumberGenerator& rng, size_t poll_bits) { - lock_guard_type lock(m_mutex); + const lock_guard_type lock(m_mutex); RandomNumberGenerator::reseed_from_rng(rng, poll_bits); @@ -112,12 +113,12 @@ m_reseed_counter = 0; m_last_pid = cur_pid; - if(m_underlying_rng) { + if(m_underlying_rng != nullptr) { reseed_from_rng(*m_underlying_rng, security_level()); } - if(m_entropy_sources) { - reseed(*m_entropy_sources, security_level()); + if(m_entropy_sources != nullptr) { + reseed_from_sources(*m_entropy_sources, security_level()); } if(!is_seeded()) { diff -Nru botan3-3.7.1+dfsg/src/lib/rng/stateful_rng/stateful_rng.h botan3-3.12.0+dfsg/src/lib/rng/stateful_rng/stateful_rng.h --- botan3-3.7.1+dfsg/src/lib/rng/stateful_rng/stateful_rng.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/rng/stateful_rng/stateful_rng.h 2026-05-07 01:38:28.000000000 +0000 @@ -75,16 +75,14 @@ */ void force_reseed(); - void reseed_from_rng(RandomNumberGenerator& rng, size_t poll_bits = BOTAN_RNG_RESEED_POLL_BITS) final; + void reseed_from_rng(RandomNumberGenerator& rng, size_t poll_bits = RandomNumberGenerator::DefaultPollBits) final; /** - * Poll provided sources for up to poll_bits bits of entropy - * or until the timeout expires. Returns estimate of the number - * of bits collected. + * Poll provided sources for up to poll_bits bits of entropy. + * Returns estimate of the number of bits collected. */ - size_t reseed(Entropy_Sources& srcs, - size_t poll_bits = BOTAN_RNG_RESEED_POLL_BITS, - std::chrono::milliseconds poll_timeout = BOTAN_RNG_RESEED_DEFAULT_TIMEOUT) override; + size_t reseed_from_sources(Entropy_Sources& srcs, + size_t poll_bits = RandomNumberGenerator::DefaultPollBits) final; /** * @return intended security level of this DRBG diff -Nru botan3-3.7.1+dfsg/src/lib/rng/system_rng/info.txt botan3-3.12.0+dfsg/src/lib/rng/system_rng/info.txt --- botan3-3.7.1+dfsg/src/lib/rng/system_rng/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/rng/system_rng/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -8,8 +8,11 @@ +# Any one of these feature sets is sufficient dev_random,posix1 +ccrandom arc4random +getrandom rtlgenrandom crypto_ng diff -Nru botan3-3.7.1+dfsg/src/lib/rng/system_rng/system_rng.cpp botan3-3.12.0+dfsg/src/lib/rng/system_rng/system_rng.cpp --- botan3-3.7.1+dfsg/src/lib/rng/system_rng/system_rng.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/rng/system_rng/system_rng.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,6 +8,10 @@ #include +#include +#include +#include + #if defined(BOTAN_TARGET_OS_HAS_WIN32) #define NOMINMAX 1 #define _WINSOCKAPI_ // stop windows.h including winsock.h @@ -16,6 +20,7 @@ #if defined(BOTAN_TARGET_OS_HAS_RTLGENRANDOM) #include + #include #elif defined(BOTAN_TARGET_OS_HAS_CRYPTO_NG) #include #include @@ -226,6 +231,10 @@ throw System_Error("System_RNG getrandom failed", errno); } + if(got == 0) { + throw System_Error("System_RNG getrandom unexpectedly returned 0"); + } + buf += got; len -= got; } diff -Nru botan3-3.7.1+dfsg/src/lib/stream/chacha/chacha.cpp botan3-3.12.0+dfsg/src/lib/stream/chacha/chacha.cpp --- botan3-3.7.1+dfsg/src/lib/stream/chacha/chacha.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/stream/chacha/chacha.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,11 +8,14 @@ #include #include -#include #include #include #include +#if defined(BOTAN_HAS_CPUID) + #include +#endif + namespace Botan { namespace { @@ -38,9 +41,22 @@ void hchacha(uint32_t output[8], const uint32_t input[16], size_t rounds) { BOTAN_ASSERT(rounds % 2 == 0, "Valid rounds"); - uint32_t x00 = input[0], x01 = input[1], x02 = input[2], x03 = input[3], x04 = input[4], x05 = input[5], - x06 = input[6], x07 = input[7], x08 = input[8], x09 = input[9], x10 = input[10], x11 = input[11], - x12 = input[12], x13 = input[13], x14 = input[14], x15 = input[15]; + uint32_t x00 = input[0]; + uint32_t x01 = input[1]; + uint32_t x02 = input[2]; + uint32_t x03 = input[3]; + uint32_t x04 = input[4]; + uint32_t x05 = input[5]; + uint32_t x06 = input[6]; + uint32_t x07 = input[7]; + uint32_t x08 = input[8]; + uint32_t x09 = input[9]; + uint32_t x10 = input[10]; + uint32_t x11 = input[11]; + uint32_t x12 = input[12]; + uint32_t x13 = input[13]; + uint32_t x14 = input[14]; + uint32_t x15 = input[15]; for(size_t i = 0; i != rounds / 2; ++i) { chacha_quarter_round(x00, x04, x08, x12); @@ -72,13 +88,13 @@ size_t ChaCha::parallelism() { #if defined(BOTAN_HAS_CHACHA_AVX512) - if(CPUID::has_avx512()) { + if(CPUID::has(CPUID::Feature::AVX512)) { return 16; } #endif #if defined(BOTAN_HAS_CHACHA_AVX2) - if(CPUID::has_avx2()) { + if(CPUID::has(CPUID::Feature::AVX2)) { return 8; } #endif @@ -88,20 +104,20 @@ std::string ChaCha::provider() const { #if defined(BOTAN_HAS_CHACHA_AVX512) - if(CPUID::has_avx512()) { - return "avx512"; + if(auto feat = CPUID::check(CPUID::Feature::AVX512)) { + return *feat; } #endif #if defined(BOTAN_HAS_CHACHA_AVX2) - if(CPUID::has_avx2()) { - return "avx2"; + if(auto feat = CPUID::check(CPUID::Feature::AVX2)) { + return *feat; } #endif #if defined(BOTAN_HAS_CHACHA_SIMD32) - if(CPUID::has_simd_32()) { - return "simd32"; + if(auto feat = CPUID::check(CPUID::Feature::SIMD_4X32)) { + return *feat; } #endif @@ -112,7 +128,7 @@ BOTAN_ASSERT(rounds % 2 == 0, "Valid rounds"); #if defined(BOTAN_HAS_CHACHA_AVX512) - if(CPUID::has_avx512()) { + if(CPUID::has(CPUID::Feature::AVX512)) { while(output_blocks >= 16) { ChaCha::chacha_avx512_x16(output, state, rounds); output += 16 * 64; @@ -122,7 +138,7 @@ #endif #if defined(BOTAN_HAS_CHACHA_AVX2) - if(CPUID::has_avx2()) { + if(CPUID::has(CPUID::Feature::AVX2)) { while(output_blocks >= 8) { ChaCha::chacha_avx2_x8(output, state, rounds); output += 8 * 64; @@ -132,7 +148,7 @@ #endif #if defined(BOTAN_HAS_CHACHA_SIMD32) - if(CPUID::has_simd_32()) { + if(CPUID::has(CPUID::Feature::SIMD_4X32)) { while(output_blocks >= 4) { ChaCha::chacha_simd32_x4(output, state, rounds); output += 4 * 64; @@ -143,9 +159,22 @@ // TODO interleave rounds for(size_t i = 0; i != output_blocks; ++i) { - uint32_t x00 = state[0], x01 = state[1], x02 = state[2], x03 = state[3], x04 = state[4], x05 = state[5], - x06 = state[6], x07 = state[7], x08 = state[8], x09 = state[9], x10 = state[10], x11 = state[11], - x12 = state[12], x13 = state[13], x14 = state[14], x15 = state[15]; + uint32_t x00 = state[0]; + uint32_t x01 = state[1]; + uint32_t x02 = state[2]; + uint32_t x03 = state[3]; + uint32_t x04 = state[4]; + uint32_t x05 = state[5]; + uint32_t x06 = state[6]; + uint32_t x07 = state[7]; + uint32_t x08 = state[8]; + uint32_t x09 = state[9]; + uint32_t x10 = state[10]; + uint32_t x11 = state[11]; + uint32_t x12 = state[12]; + uint32_t x13 = state[13]; + uint32_t x14 = state[14]; + uint32_t x15 = state[15]; for(size_t r = 0; r != rounds / 2; ++r) { chacha_quarter_round(x00, x04, x08, x12); @@ -194,7 +223,9 @@ store_le(x15, output + 64 * i + 4 * 15); state[12]++; - state[13] += (state[12] == 0); + if(state[12] == 0) { + state[13] += 1; + } } } diff -Nru botan3-3.7.1+dfsg/src/lib/stream/chacha/chacha_avx2/chacha_avx2.cpp botan3-3.12.0+dfsg/src/lib/stream/chacha/chacha_avx2/chacha_avx2.cpp --- botan3-3.7.1+dfsg/src/lib/stream/chacha/chacha_avx2/chacha_avx2.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/stream/chacha/chacha_avx2/chacha_avx2.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,19 +6,20 @@ #include +#include #include namespace Botan { //static -BOTAN_AVX2_FN -void ChaCha::chacha_avx2_x8(uint8_t output[64 * 8], uint32_t state[16], size_t rounds) { +void BOTAN_FN_ISA_AVX2 ChaCha::chacha_avx2_x8(uint8_t output[64 * 8], uint32_t state[16], size_t rounds) { SIMD_8x32::reset_registers(); BOTAN_ASSERT(rounds % 2 == 0, "Valid rounds"); const SIMD_8x32 CTR0 = SIMD_8x32(0, 1, 2, 3, 4, 5, 6, 7); const uint32_t C = 0xFFFFFFFF - state[12]; + // NOLINTNEXTLINE(*-implicit-bool-conversion) const SIMD_8x32 CTR1 = SIMD_8x32(0, C < 1, C < 2, C < 3, C < 4, C < 5, C < 6, C < 7); SIMD_8x32 R00 = SIMD_8x32::splat(state[0]); diff -Nru botan3-3.7.1+dfsg/src/lib/stream/chacha/chacha_avx2/info.txt botan3-3.12.0+dfsg/src/lib/stream/chacha/chacha_avx2/info.txt --- botan3-3.7.1+dfsg/src/lib/stream/chacha/chacha_avx2/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/stream/chacha/chacha_avx2/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + CHACHA_AVX2 -> 20180418 - + name -> "ChaCha20 AVX2" @@ -13,4 +13,5 @@ simd_avx2 +cpuid diff -Nru botan3-3.7.1+dfsg/src/lib/stream/chacha/chacha_avx512/chacha_avx512.cpp botan3-3.12.0+dfsg/src/lib/stream/chacha/chacha_avx512/chacha_avx512.cpp --- botan3-3.7.1+dfsg/src/lib/stream/chacha/chacha_avx512/chacha_avx512.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/stream/chacha/chacha_avx512/chacha_avx512.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -5,19 +5,24 @@ */ #include + +#include #include namespace Botan { //static -BOTAN_AVX512_FN -void ChaCha::chacha_avx512_x16(uint8_t output[64 * 16], uint32_t state[16], size_t rounds) { +void BOTAN_FN_ISA_AVX512 ChaCha::chacha_avx512_x16(uint8_t output[64 * 16], uint32_t state[16], size_t rounds) { BOTAN_ASSERT(rounds % 2 == 0, "Valid rounds"); const SIMD_16x32 CTR0 = SIMD_16x32(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); const uint32_t C = 0xFFFFFFFF - state[12]; + + // clang-format off const SIMD_16x32 CTR1 = SIMD_16x32( + // NOLINTNEXTLINE(*-implicit-bool-conversion) 0, C < 1, C < 2, C < 3, C < 4, C < 5, C < 6, C < 7, C < 8, C < 9, C < 10, C < 11, C < 12, C < 13, C < 14, C < 15); + // clang-format on SIMD_16x32 R00 = SIMD_16x32::splat(state[0]); SIMD_16x32 R01 = SIMD_16x32::splat(state[1]); diff -Nru botan3-3.7.1+dfsg/src/lib/stream/chacha/chacha_avx512/info.txt botan3-3.12.0+dfsg/src/lib/stream/chacha/chacha_avx512/info.txt --- botan3-3.7.1+dfsg/src/lib/stream/chacha/chacha_avx512/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/stream/chacha/chacha_avx512/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + CHACHA_AVX512 -> 20230101 - + name -> "ChaCha20 AVX512" @@ -13,4 +13,5 @@ simd_avx512 +cpuid diff -Nru botan3-3.7.1+dfsg/src/lib/stream/chacha/chacha_simd32/chacha_simd32.cpp botan3-3.12.0+dfsg/src/lib/stream/chacha/chacha_simd32/chacha_simd32.cpp --- botan3-3.7.1+dfsg/src/lib/stream/chacha/chacha_simd32/chacha_simd32.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/stream/chacha/chacha_simd32/chacha_simd32.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,16 +6,19 @@ #include -#include +#include +#include namespace Botan { //static -void ChaCha::chacha_simd32_x4(uint8_t output[64 * 4], uint32_t state[16], size_t rounds) { +void BOTAN_FN_ISA_SIMD_4X32 ChaCha::chacha_simd32_x4(uint8_t output[64 * 4], uint32_t state[16], size_t rounds) { BOTAN_ASSERT(rounds % 2 == 0, "Valid rounds"); const SIMD_4x32 CTR0 = SIMD_4x32(0, 1, 2, 3); const uint32_t C = 0xFFFFFFFF - state[12]; + + // NOLINTNEXTLINE(*-implicit-bool-conversion) const SIMD_4x32 CTR1 = SIMD_4x32(0, C < 1, C < 2, C < 3); SIMD_4x32 R00 = SIMD_4x32::splat(state[0]); diff -Nru botan3-3.7.1+dfsg/src/lib/stream/chacha/chacha_simd32/info.txt botan3-3.12.0+dfsg/src/lib/stream/chacha/chacha_simd32/info.txt --- botan3-3.7.1+dfsg/src/lib/stream/chacha/chacha_simd32/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/stream/chacha/chacha_simd32/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -1,6 +1,6 @@ - + CHACHA_SIMD32 -> 20181104 - + name -> "ChaCha20 SIMD" @@ -8,5 +8,18 @@ -simd +simd_4x32 +cpuid + + +x86_32:ssse3 +x86_64:ssse3 +x32:ssse3 +arm32:neon +arm64:neon +ppc32:altivec +ppc64:altivec +loongarch64:lsx +wasm:simd128 + diff -Nru botan3-3.7.1+dfsg/src/lib/stream/ctr/ctr.cpp botan3-3.12.0+dfsg/src/lib/stream/ctr/ctr.cpp --- botan3-3.7.1+dfsg/src/lib/stream/ctr/ctr.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/stream/ctr/ctr.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -12,6 +12,10 @@ #include #include +#if defined(BOTAN_HAS_CTR_BE_AVX2) || defined(BOTAN_HAS_CTR_BE_SIMD32) + #include +#endif + namespace Botan { CTR_BE::CTR_BE(std::unique_ptr cipher) : @@ -84,9 +88,11 @@ void CTR_BE::cipher_bytes(const uint8_t in[], uint8_t out[], size_t length) { assert_key_material_set(); - const uint8_t* pad_bits = &m_pad[0]; + const uint8_t* pad_bits = m_pad.data(); const size_t pad_size = m_pad.size(); + /* Consume any already computed keystream in m_pad */ + if(m_pad_pos > 0) { const size_t avail = pad_size - m_pad_pos; const size_t take = std::min(length, avail); @@ -103,6 +109,28 @@ } } + /* Bulk processing */ + + [[maybe_unused]] const bool can_use_bs16_ctr4_fastpath = m_block_size == 16 && m_ctr_size == 4 && pad_size % 64 == 0; + +#if defined(BOTAN_HAS_CTR_BE_AVX2) + if(length >= pad_size && can_use_bs16_ctr4_fastpath && CPUID::has(CPUID::Feature::AVX2)) { + const size_t consumed = ctr_proc_bs16_ctr4_avx2(in, out, length); + in += consumed; + out += consumed; + length -= consumed; + } +#endif + +#if defined(BOTAN_HAS_CTR_BE_SIMD32) + if(length >= pad_size && can_use_bs16_ctr4_fastpath && CPUID::has(CPUID::Feature::SIMD_4X32)) { + const size_t consumed = ctr_proc_bs16_ctr4_simd32(in, out, length); + in += consumed; + out += consumed; + length -= consumed; + } +#endif + while(length >= pad_size) { xor_buf(out, in, pad_bits, pad_size); length -= pad_size; @@ -113,8 +141,11 @@ m_cipher->encrypt_n(m_counter.data(), m_pad.data(), m_ctr_blocks); } - xor_buf(out, in, pad_bits, length); - m_pad_pos += length; + /* Now if length > 0 then we have some remaining text, and m_pad is full - consume as required */ + if(length > 0) { + xor_buf(out, in, pad_bits, length); + m_pad_pos = length; + } } void CTR_BE::generate_keystream(uint8_t out[], size_t length) { @@ -141,7 +172,7 @@ m_pad_pos = 0; } - copy_mem(out, &m_pad[0], length); + copy_mem(out, m_pad.data(), length); m_pad_pos += length; BOTAN_ASSERT_NOMSG(m_pad_pos < m_pad.size()); } @@ -153,7 +184,7 @@ m_iv.resize(m_block_size); zeroise(m_iv); - copy_mem(&m_iv[0], iv, iv_len); + copy_mem(m_iv.data(), iv, iv_len); seek(0); } @@ -188,13 +219,15 @@ store_be(b0, &m_counter[i * BS + off]); store_be(b1, &m_counter[i * BS + off + 8]); b1 += 1; - b0 += (b1 == 0); // carry + if(b1 == 0) { + b0 += 1; // carry + } } } else { for(size_t i = 0; i != ctr_blocks; ++i) { uint64_t local_counter = counter; uint16_t carry = static_cast(local_counter); - for(size_t j = 0; (carry || local_counter) && j != ctr_size; ++j) { + for(size_t j = 0; (carry > 0 || local_counter > 0) && j != ctr_size; ++j) { const size_t off = i * BS + (BS - 1 - j); const uint16_t cnt = static_cast(m_counter[off]) + carry; m_counter[off] = static_cast(cnt); @@ -212,7 +245,7 @@ zeroise(m_counter); BOTAN_ASSERT_NOMSG(m_counter.size() >= m_iv.size()); - copy_mem(&m_counter[0], &m_iv[0], m_iv.size()); + copy_mem(m_counter.data(), m_iv.data(), m_iv.size()); const size_t BS = m_block_size; @@ -224,12 +257,12 @@ if(m_ctr_blocks >= 4 && is_power_of_2(m_ctr_blocks)) { size_t written = 1; while(written < m_ctr_blocks) { - copy_mem(&m_counter[written * BS], &m_counter[0], BS * written); + copy_mem(&m_counter[written * BS], &m_counter[0], BS * written); // NOLINT(*container-data-pointer) written *= 2; } } else { for(size_t i = 1; i != m_ctr_blocks; ++i) { - copy_mem(&m_counter[i * BS], &m_counter[0], BS - 4); + copy_mem(&m_counter[i * BS], &m_counter[0], BS - 4); // NOLINT(*container-data-pointer) } } @@ -243,7 +276,9 @@ copy_mem(&m_counter[i * BS], &m_counter[(i - 1) * BS], BS); for(size_t j = 0; j != m_ctr_size; ++j) { - if(++m_counter[i * BS + (BS - 1 - j)]) { + uint8_t& c = m_counter[i * BS + (BS - 1 - j)]; + c += 1; + if(c > 0) { break; } } diff -Nru botan3-3.7.1+dfsg/src/lib/stream/ctr/ctr.h botan3-3.12.0+dfsg/src/lib/stream/ctr/ctr.h --- botan3-3.7.1+dfsg/src/lib/stream/ctr/ctr.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/stream/ctr/ctr.h 2026-05-07 01:38:28.000000000 +0000 @@ -50,6 +50,14 @@ void set_iv_bytes(const uint8_t iv[], size_t iv_len) override; void add_counter(uint64_t counter); +#if defined(BOTAN_HAS_CTR_BE_AVX2) + size_t ctr_proc_bs16_ctr4_avx2(const uint8_t in[], uint8_t out[], size_t length); +#endif + +#if defined(BOTAN_HAS_CTR_BE_SIMD32) + size_t ctr_proc_bs16_ctr4_simd32(const uint8_t in[], uint8_t out[], size_t length); +#endif + std::unique_ptr m_cipher; const size_t m_block_size; diff -Nru botan3-3.7.1+dfsg/src/lib/stream/ctr/ctr_avx2/ctr_avx2.cpp botan3-3.12.0+dfsg/src/lib/stream/ctr/ctr_avx2/ctr_avx2.cpp --- botan3-3.7.1+dfsg/src/lib/stream/ctr/ctr_avx2/ctr_avx2.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/stream/ctr/ctr_avx2/ctr_avx2.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,83 @@ +/* +* (C) 2026 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include + +namespace Botan { + +BOTAN_FN_ISA_AVX2 +size_t CTR_BE::ctr_proc_bs16_ctr4_avx2(const uint8_t* in, uint8_t* out, size_t length) { + BOTAN_ASSERT_NOMSG(m_pad.size() % 64 == 0); + BOTAN_DEBUG_ASSERT(m_counter.size() == m_pad.size()); + + const size_t pad_size = m_pad.size(); + if(length < pad_size) { + return 0; + } + + const size_t ctr_blocks = m_ctr_blocks; + + /* + * Byte swap table that swaps only the counter bytes and not the nonce bytes + */ + const SIMD_8x32 bswap_ctr( + 0x03020100, 0x07060504, 0x0B0A0908, 0x0C0D0E0F, 0x03020100, 0x07060504, 0x0B0A0908, 0x0C0D0E0F); + + // Load the starting counter value, bswap the counter field itself so we can add + const SIMD_8x32 starting_ctr = SIMD_8x32::byte_shuffle(SIMD_8x32::load_le128(m_counter.data()), bswap_ctr); + + // Counter is incremented 4 blocks at a time (2 per register, 2 registers) + const SIMD_8x32 inc4(0, 0, 0, 4); + + const uint32_t N = static_cast(ctr_blocks); + SIMD_8x32 batch_ctr0 = starting_ctr + SIMD_8x32(0, 0, 0, N, 0, 0, 0, N + 1); + SIMD_8x32 batch_ctr1 = starting_ctr + SIMD_8x32(0, 0, 0, N + 2, 0, 0, 0, N + 3); + const uint8_t* pad_buf = m_pad.data(); + uint8_t* ctr_buf = m_counter.data(); + + const size_t ctr_block_quads = ctr_blocks / 4; + + size_t processed = 0; + + while(length >= pad_size) { + for(size_t i = 0; i != ctr_block_quads; ++i) { + const size_t off = i * 64; + + // Store and update the counters + SIMD_8x32::byte_shuffle(batch_ctr0, bswap_ctr).store_le(ctr_buf + off); + SIMD_8x32::byte_shuffle(batch_ctr1, bswap_ctr).store_le(ctr_buf + off + 32); + batch_ctr0 += inc4; + batch_ctr1 += inc4; + + const auto p0 = SIMD_8x32::load_le(pad_buf + off); + const auto p1 = SIMD_8x32::load_le(pad_buf + off + 32); + + auto i0 = SIMD_8x32::load_le(in + off); + auto i1 = SIMD_8x32::load_le(in + off + 32); + + i0 ^= p0; + i1 ^= p1; + + i0.store_le(out + off); + i1.store_le(out + off + 32); + } + + in += pad_size; + out += pad_size; + length -= pad_size; + processed += pad_size; + + // Regenerate the pad buffer + m_cipher->encrypt_n(m_counter.data(), m_pad.data(), ctr_blocks); + } + + return processed; +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/stream/ctr/ctr_avx2/info.txt botan3-3.12.0+dfsg/src/lib/stream/ctr/ctr_avx2/info.txt --- botan3-3.7.1+dfsg/src/lib/stream/ctr/ctr_avx2/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/stream/ctr/ctr_avx2/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,17 @@ + +CTR_BE_AVX2 -> 20260321 + + + +name -> "CTR-BE AVX2" +brief -> "AVX2-accelerated CTR-BE counter management and XOR" + + + +avx2 + + + +cpuid +simd_avx2 + diff -Nru botan3-3.7.1+dfsg/src/lib/stream/ctr/ctr_simd32/ctr_simd32.cpp botan3-3.12.0+dfsg/src/lib/stream/ctr/ctr_simd32/ctr_simd32.cpp --- botan3-3.7.1+dfsg/src/lib/stream/ctr/ctr_simd32/ctr_simd32.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/stream/ctr/ctr_simd32/ctr_simd32.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,89 @@ +/* +* (C) 2026 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include + +namespace Botan { + +BOTAN_FN_ISA_SIMD_4X32 +size_t CTR_BE::ctr_proc_bs16_ctr4_simd32(const uint8_t* in, uint8_t* out, size_t length) { + BOTAN_ASSERT_NOMSG(m_pad.size() % 64 == 0); + BOTAN_DEBUG_ASSERT(m_counter.size() == m_pad.size()); + + const size_t pad_size = m_pad.size(); + if(length < pad_size) { + return 0; + } + + const size_t ctr_blocks = m_ctr_blocks; + + // Load the starting counter as big-endian 32-bit words. + // Word 3 (bytes 12-15) contains the counter value in native form. + const SIMD_4x32 starting_ctr = SIMD_4x32::load_be(m_counter.data()); + + const uint32_t N = static_cast(ctr_blocks); + + // Initialize 4 counter registers for 4-way unrolled processing + SIMD_4x32 ctr0 = starting_ctr + SIMD_4x32(0, 0, 0, N); + SIMD_4x32 ctr1 = starting_ctr + SIMD_4x32(0, 0, 0, N + 1); + SIMD_4x32 ctr2 = starting_ctr + SIMD_4x32(0, 0, 0, N + 2); + SIMD_4x32 ctr3 = starting_ctr + SIMD_4x32(0, 0, 0, N + 3); + const SIMD_4x32 inc4 = SIMD_4x32(0, 0, 0, 4); + + const uint8_t* pad_buf = m_pad.data(); + uint8_t* ctr_buf = m_counter.data(); + + const size_t ctr_block_quads = ctr_blocks / 4; + + size_t processed = 0; + + while(length >= pad_size) { + for(size_t i = 0; i != ctr_block_quads; ++i) { + const size_t off = i * 64; + + // Store and update the counter + ctr0.store_be(ctr_buf + off); + ctr1.store_be(ctr_buf + off + 16); + ctr2.store_be(ctr_buf + off + 32); + ctr3.store_be(ctr_buf + off + 48); + ctr0 += inc4; + ctr1 += inc4; + ctr2 += inc4; + ctr3 += inc4; + + // Load and XOR the pad with the input blocks + auto p0 = SIMD_4x32::load_le(pad_buf + off); + auto p1 = SIMD_4x32::load_le(pad_buf + off + 16); + auto p2 = SIMD_4x32::load_le(pad_buf + off + 32); + auto p3 = SIMD_4x32::load_le(pad_buf + off + 48); + + p0 ^= SIMD_4x32::load_le(in + off); + p1 ^= SIMD_4x32::load_le(in + off + 16); + p2 ^= SIMD_4x32::load_le(in + off + 32); + p3 ^= SIMD_4x32::load_le(in + off + 48); + + p0.store_le(out + off); + p1.store_le(out + off + 16); + p2.store_le(out + off + 32); + p3.store_le(out + off + 48); + } + + in += pad_size; + out += pad_size; + length -= pad_size; + processed += pad_size; + + // Regenerate the pad buffer + m_cipher->encrypt_n(m_counter.data(), m_pad.data(), ctr_blocks); + } + + return processed; +} + +} // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/stream/ctr/ctr_simd32/info.txt botan3-3.12.0+dfsg/src/lib/stream/ctr/ctr_simd32/info.txt --- botan3-3.7.1+dfsg/src/lib/stream/ctr/ctr_simd32/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/stream/ctr/ctr_simd32/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,26 @@ + +CTR_BE_SIMD32 -> 20260323 + + + +name -> "CTR-BE SIMD" +brief -> "SIMD-accelerated CTR-BE counter management and XOR" + + + +cpuid +simd_4x32 + + + +x86_32:ssse3 +x86_64:ssse3 +x32:ssse3 +arm32:neon +arm64:neon + +# disabled until tested +#ppc64:altivec +#loongarch64:lsx +#wasm:simd128 + diff -Nru botan3-3.7.1+dfsg/src/lib/stream/ofb/ofb.cpp botan3-3.12.0+dfsg/src/lib/stream/ofb/ofb.cpp --- botan3-3.7.1+dfsg/src/lib/stream/ofb/ofb.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/stream/ofb/ofb.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,6 +8,7 @@ #include #include +#include #include namespace Botan { @@ -76,7 +77,7 @@ zeroise(m_buffer); BOTAN_ASSERT_NOMSG(m_buffer.size() >= iv_len); - copy_mem(&m_buffer[0], iv, iv_len); + copy_mem(m_buffer.data(), iv, iv_len); m_cipher->encrypt(m_buffer); m_buf_pos = 0; diff -Nru botan3-3.7.1+dfsg/src/lib/stream/rc4/rc4.cpp botan3-3.12.0+dfsg/src/lib/stream/rc4/rc4.cpp --- botan3-3.7.1+dfsg/src/lib/stream/rc4/rc4.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/stream/rc4/rc4.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -51,11 +51,10 @@ * Generate cipher stream */ void RC4::generate() { - uint8_t SX, SY; for(size_t i = 0; i != m_buffer.size(); i += 4) { - SX = m_state[m_X + 1]; + uint8_t SX = m_state[m_X + 1]; m_Y = (m_Y + SX) % 256; - SY = m_state[m_Y]; + uint8_t SY = m_state[m_Y]; m_state[m_X + 1] = SY; m_state[m_Y] = SX; m_buffer[i] = m_state[(SX + SY) % 256]; diff -Nru botan3-3.7.1+dfsg/src/lib/stream/rc4/rc4.h botan3-3.12.0+dfsg/src/lib/stream/rc4/rc4.h --- botan3-3.7.1+dfsg/src/lib/stream/rc4/rc4.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/stream/rc4/rc4.h 2026-05-07 01:38:28.000000000 +0000 @@ -36,8 +36,6 @@ */ explicit RC4(size_t skip = 0); - ~RC4() override { clear(); } - private: void key_schedule(std::span key) override; void cipher_bytes(const uint8_t in[], uint8_t out[], size_t length) override; diff -Nru botan3-3.7.1+dfsg/src/lib/stream/salsa20/salsa20.cpp botan3-3.12.0+dfsg/src/lib/stream/salsa20/salsa20.cpp --- botan3-3.7.1+dfsg/src/lib/stream/salsa20/salsa20.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/stream/salsa20/salsa20.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -29,9 +29,22 @@ */ //static void Salsa20::hsalsa20(uint32_t output[8], const uint32_t input[16]) { - uint32_t x00 = input[0], x01 = input[1], x02 = input[2], x03 = input[3], x04 = input[4], x05 = input[5], - x06 = input[6], x07 = input[7], x08 = input[8], x09 = input[9], x10 = input[10], x11 = input[11], - x12 = input[12], x13 = input[13], x14 = input[14], x15 = input[15]; + uint32_t x00 = input[0]; + uint32_t x01 = input[1]; + uint32_t x02 = input[2]; + uint32_t x03 = input[3]; + uint32_t x04 = input[4]; + uint32_t x05 = input[5]; + uint32_t x06 = input[6]; + uint32_t x07 = input[7]; + uint32_t x08 = input[8]; + uint32_t x09 = input[9]; + uint32_t x10 = input[10]; + uint32_t x11 = input[11]; + uint32_t x12 = input[12]; + uint32_t x13 = input[13]; + uint32_t x14 = input[14]; + uint32_t x15 = input[15]; for(size_t i = 0; i != 10; ++i) { salsa20_quarter_round(x00, x04, x08, x12); @@ -62,9 +75,22 @@ void Salsa20::salsa_core(uint8_t output[64], const uint32_t input[16], size_t rounds) { BOTAN_ASSERT_NOMSG(rounds % 2 == 0); - uint32_t x00 = input[0], x01 = input[1], x02 = input[2], x03 = input[3], x04 = input[4], x05 = input[5], - x06 = input[6], x07 = input[7], x08 = input[8], x09 = input[9], x10 = input[10], x11 = input[11], - x12 = input[12], x13 = input[13], x14 = input[14], x15 = input[15]; + uint32_t x00 = input[0]; + uint32_t x01 = input[1]; + uint32_t x02 = input[2]; + uint32_t x03 = input[3]; + uint32_t x04 = input[4]; + uint32_t x05 = input[5]; + uint32_t x06 = input[6]; + uint32_t x07 = input[7]; + uint32_t x08 = input[8]; + uint32_t x09 = input[9]; + uint32_t x10 = input[10]; + uint32_t x11 = input[11]; + uint32_t x12 = input[12]; + uint32_t x13 = input[13]; + uint32_t x14 = input[14]; + uint32_t x15 = input[15]; for(size_t i = 0; i != rounds / 2; ++i) { salsa20_quarter_round(x00, x04, x08, x12); @@ -109,7 +135,9 @@ salsa_core(m_buffer.data(), m_state.data(), 20); ++m_state[8]; - m_state[9] += (m_state[8] == 0); + if(m_state[8] == 0) { + m_state[9] += 1; + } length -= available; in += available; @@ -229,7 +257,9 @@ salsa_core(m_buffer.data(), m_state.data(), 20); ++m_state[8]; - m_state[9] += (m_state[8] == 0); + if(m_state[8] == 0) { + m_state[9] += 1; + } m_position = 0; } @@ -278,7 +308,9 @@ salsa_core(m_buffer.data(), m_state.data(), 20); ++m_state[8]; - m_state[9] += (m_state[8] == 0); + if(m_state[8] == 0) { + m_state[9] += 1; + } m_position = offset % 64; } diff -Nru botan3-3.7.1+dfsg/src/lib/stream/shake_cipher/shake_cipher.cpp botan3-3.12.0+dfsg/src/lib/stream/shake_cipher/shake_cipher.cpp --- botan3-3.7.1+dfsg/src/lib/stream/shake_cipher/shake_cipher.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/stream/shake_cipher/shake_cipher.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -14,7 +14,7 @@ namespace Botan { SHAKE_Cipher::SHAKE_Cipher(size_t keccak_capacity) : - m_keccak(keccak_capacity, 0xF, 4), + m_keccak({.capacity_bits = keccak_capacity, .padding = KeccakPadding::shake()}), m_has_keying_material(false), m_keystream_buffer(buffer_size()), m_bytes_generated(0) {} diff -Nru botan3-3.7.1+dfsg/src/lib/stream/stream_cipher.cpp botan3-3.12.0+dfsg/src/lib/stream/stream_cipher.cpp --- botan3-3.7.1+dfsg/src/lib/stream/stream_cipher.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/stream/stream_cipher.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -75,7 +75,7 @@ if(provider.empty() || provider == "base") { auto cipher = BlockCipher::create(req.arg(0)); if(cipher) { - size_t ctr_size = req.arg_as_integer(1, cipher->block_size()); + const size_t ctr_size = req.arg_as_integer(1, cipher->block_size()); return std::make_unique(std::move(cipher), ctr_size); } } @@ -130,6 +130,11 @@ return probe_providers_of(algo_spec); } +void StreamCipher::cipher(std::span in, std::span out) { + BOTAN_ARG_CHECK(in.size() <= out.size(), "Output buffer of stream cipher must be at least as long as input buffer"); + cipher_bytes(in.data(), out.data(), in.size()); +} + size_t StreamCipher::default_iv_length() const { return 0; } diff -Nru botan3-3.7.1+dfsg/src/lib/stream/stream_cipher.h botan3-3.12.0+dfsg/src/lib/stream/stream_cipher.h --- botan3-3.7.1+dfsg/src/lib/stream/stream_cipher.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/stream/stream_cipher.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,6 +9,7 @@ #define BOTAN_STREAM_CIPHER_H_ #include +#include #include #include #include @@ -22,8 +23,6 @@ */ class BOTAN_PUBLIC_API(2, 0) StreamCipher : public SymmetricAlgorithm { public: - ~StreamCipher() override = default; - /** * Create an instance based on a name * If provider is empty then best available is chosen. @@ -65,11 +64,7 @@ * @param out the byte array to hold the output, i.e. the ciphertext * with at least the same size as @p in */ - void cipher(std::span in, std::span out) { - BOTAN_ARG_CHECK(in.size() <= out.size(), - "Output buffer of stream cipher must be at least as long as input buffer"); - cipher_bytes(in.data(), out.data(), in.size()); - } + void cipher(std::span in, std::span out); /** * Write keystream bytes to a buffer @@ -93,7 +88,7 @@ /** * Get @p bytes from the keystream * - * The bytes are written into a continous byte buffer of your choosing. + * The bytes are written into a continuous byte buffer of your choosing. * * @param bytes The number of bytes to be produced */ diff -Nru botan3-3.7.1+dfsg/src/lib/tls/asio/asio_async_ops.h botan3-3.12.0+dfsg/src/lib/tls/asio/asio_async_ops.h --- botan3-3.7.1+dfsg/src/lib/tls/asio/asio_async_ops.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/asio/asio_async_ops.h 2026-05-07 01:38:28.000000000 +0000 @@ -91,8 +91,8 @@ m_handler(std::forward(args)...); } - Handler m_handler; - boost::asio::executor_work_guard m_work_guard_1; + Handler m_handler; // NOLINT(*-non-private-member-variable*) + boost::asio::executor_work_guard m_work_guard_1; // NOLINT(*-non-private-member-variable*) }; template > @@ -119,7 +119,11 @@ this->operator()(ec, std::size_t(0), false); } - AsyncReadOperation(AsyncReadOperation&&) = default; + AsyncReadOperation(const AsyncReadOperation& other) = delete; + AsyncReadOperation(AsyncReadOperation&& other) = default; + AsyncReadOperation& operator=(const AsyncReadOperation& other) = delete; + AsyncReadOperation& operator=(AsyncReadOperation&& other) = delete; + ~AsyncReadOperation() = default; /** * Read and decrypt application data from the peer. Note, that this is @@ -148,7 +152,7 @@ // an application data record, the "input buffer" will become // non-empty. if(!ec && bytes_transferred > 0) { - boost::asio::const_buffer read_buffer{m_stream.input_buffer().data(), bytes_transferred}; + const boost::asio::const_buffer read_buffer{m_stream.input_buffer().data(), bytes_transferred}; m_stream.process_encrypted_data(read_buffer); } @@ -218,7 +222,11 @@ this->operator()(ec, std::size_t(0), false); } - AsyncWriteOperation(AsyncWriteOperation&&) = default; + AsyncWriteOperation(const AsyncWriteOperation& other) = delete; + AsyncWriteOperation(AsyncWriteOperation&& other) = default; + AsyncWriteOperation& operator=(const AsyncWriteOperation& other) = delete; + AsyncWriteOperation& operator=(AsyncWriteOperation&& other) = delete; + ~AsyncWriteOperation() = default; /** * Write (encrypted) TLS record data generated by us and bound to the peer @@ -288,7 +296,11 @@ this->operator()(ec, std::size_t(0), false); } - AsyncHandshakeOperation(AsyncHandshakeOperation&&) = default; + AsyncHandshakeOperation(const AsyncHandshakeOperation& other) = delete; + AsyncHandshakeOperation(AsyncHandshakeOperation&& other) = default; + AsyncHandshakeOperation& operator=(const AsyncHandshakeOperation& other) = delete; + AsyncHandshakeOperation& operator=(AsyncHandshakeOperation&& other) = delete; + ~AsyncHandshakeOperation() = default; /** * Perform a TLS handshake with the peer. @@ -316,7 +328,7 @@ // result in the advancement of the handshake state and produce data // in the output buffer. if(!ec && bytesTransferred > 0) { - boost::asio::const_buffer read_buffer{m_stream.input_buffer().data(), bytesTransferred}; + const boost::asio::const_buffer read_buffer{m_stream.input_buffer().data(), bytesTransferred}; m_stream.process_encrypted_data(read_buffer); } @@ -328,9 +340,9 @@ // operation will eventually call `*this` as its own handler, passing the 0 back to this call operator. // This is necessary because the check of `bytesTransferred > 0` assumes that `bytesTransferred` bytes // were just read and are available in input_buffer for further processing. - AsyncWriteOperation::type, Stream, Allocator>, - Stream, - Allocator> + const AsyncWriteOperation, Stream, Allocator>, + Stream, + Allocator> op{std::move(*this), m_stream, 0}; return; } diff -Nru botan3-3.7.1+dfsg/src/lib/tls/asio/asio_compat.h botan3-3.12.0+dfsg/src/lib/tls/asio/asio_compat.h --- botan3-3.7.1+dfsg/src/lib/tls/asio/asio_compat.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/asio/asio_compat.h 2026-05-07 01:38:28.000000000 +0000 @@ -13,6 +13,8 @@ #if defined(BOTAN_HAS_BOOST_ASIO) + // NOLINTBEGIN(*-macro-usage) + #include /** @brief minimum supported boost version for the TLS ASIO wrapper @@ -43,5 +45,8 @@ #endif + // NOLINTEND(*-macro-usage) + #endif + #endif diff -Nru botan3-3.7.1+dfsg/src/lib/tls/asio/asio_context.cpp botan3-3.12.0+dfsg/src/lib/tls/asio/asio_context.cpp --- botan3-3.7.1+dfsg/src/lib/tls/asio/asio_context.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/asio/asio_context.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,15 +8,16 @@ #include -#if defined(BOTAN_HAS_HAS_DEFAULT_TLS_CONTEXT) +#if defined(BOTAN_HAS_DEFAULT_TLS_CONTEXT) #include #include + #include #include #endif namespace Botan::TLS { -#if defined(BOTAN_HAS_HAS_DEFAULT_TLS_CONTEXT) +#if defined(BOTAN_HAS_DEFAULT_TLS_CONTEXT) namespace { @@ -34,7 +35,8 @@ } } - std::vector trusted_certificate_authorities(const std::string&, const std::string&) override { + std::vector trusted_certificate_authorities(const std::string& /*type*/, + const std::string& /*context*/) override { if(m_cert_store) { return {m_cert_store.get()}; } else { diff -Nru botan3-3.7.1+dfsg/src/lib/tls/asio/asio_context.h botan3-3.12.0+dfsg/src/lib/tls/asio/asio_context.h --- botan3-3.7.1+dfsg/src/lib/tls/asio/asio_context.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/asio/asio_context.h 2026-05-07 01:38:28.000000000 +0000 @@ -23,6 +23,9 @@ #include #if defined(BOTAN_HAS_AUTO_SEEDING_RNG) && defined(BOTAN_HAS_CERTSTOR_SYSTEM) + #define BOTAN_HAS_DEFAULT_TLS_CONTEXT + + // TODO(Botan4) remove this #define BOTAN_HAS_HAS_DEFAULT_TLS_CONTEXT #endif @@ -51,13 +54,13 @@ */ using Verify_Callback = detail::fn_signature_helper::type; - #if defined(BOTAN_HAS_HAS_DEFAULT_TLS_CONTEXT) + #if defined(BOTAN_HAS_DEFAULT_TLS_CONTEXT) /** * @brief Construct a TLS stream context with typical defaults * * @param server_info Basic information about the host to connect to (SNI) */ - Context(Server_Information server_info = Server_Information()); + BOTAN_FUTURE_EXPLICIT Context(Server_Information server_info = Server_Information()); #endif Context(std::shared_ptr credentials_manager, @@ -65,10 +68,10 @@ std::shared_ptr session_manager, std::shared_ptr policy, Server_Information server_info = Server_Information()) : - m_credentials_manager(credentials_manager), - m_rng(rng), - m_session_manager(session_manager), - m_policy(policy), + m_credentials_manager(std::move(credentials_manager)), + m_rng(std::move(rng)), + m_session_manager(std::move(session_manager)), + m_policy(std::move(policy)), m_server_info(std::move(server_info)) {} virtual ~Context() = default; @@ -95,10 +98,16 @@ void set_server_info(Server_Information server_info) { m_server_info = std::move(server_info); } + void set_app_protocols(std::vector app_protocols = {}) { + m_app_protocols = std::move(app_protocols); + } + protected: template friend class Stream; + friend class StreamCallbacks; + // NOLINTBEGIN(*-non-private-member-variable*) std::shared_ptr m_credentials_manager; std::shared_ptr m_rng; std::shared_ptr m_session_manager; @@ -106,6 +115,8 @@ Server_Information m_server_info; Verify_Callback m_verify_callback; + std::vector m_app_protocols; + // NOLINTEND(*-non-private-member-variable*) }; } // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/asio/asio_error.h botan3-3.12.0+dfsg/src/lib/tls/asio/asio_error.h --- botan3-3.7.1+dfsg/src/lib/tls/asio/asio_error.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/asio/asio_error.h 2026-05-07 01:38:28.000000000 +0000 @@ -26,14 +26,30 @@ */ namespace Botan { + +/** +* Generic base class wrapping boost::system::error_category and +* adding a (bizarrely missing) virtual destructor. +*/ +class BoostErrorCategory : public boost::system::error_category { + public: + virtual ~BoostErrorCategory() = default; + + BoostErrorCategory() = default; + BoostErrorCategory(const BoostErrorCategory& other) = delete; + BoostErrorCategory(BoostErrorCategory&& other) = delete; + BoostErrorCategory& operator=(const BoostErrorCategory& other) = delete; + BoostErrorCategory& operator=(BoostErrorCategory&& other) = delete; +}; + namespace TLS { -enum StreamError { StreamTruncated = 1 }; +// NOLINTNEXTLINE(*-use-enum-class) +enum StreamError : uint8_t { StreamTruncated = 1 }; //! @brief An error category for errors from the TLS::Stream -struct StreamCategory : public boost::system::error_category { - virtual ~StreamCategory() = default; - +class StreamCategory final : public BoostErrorCategory { + public: const char* name() const noexcept override { return "Botan TLS Stream"; } std::string message(int value) const override { @@ -46,7 +62,7 @@ }; inline const StreamCategory& botan_stream_category() { - static StreamCategory category; + static const StreamCategory category; return category; } @@ -55,19 +71,18 @@ } //! @brief An error category for TLS alerts -struct BotanAlertCategory : boost::system::error_category { - virtual ~BotanAlertCategory() = default; - +class BotanAlertCategory final : public BoostErrorCategory { + public: const char* name() const noexcept override { return "Botan TLS Alert"; } std::string message(int ev) const override { - Botan::TLS::Alert alert(static_cast(ev)); + const Botan::TLS::Alert alert(static_cast(ev)); return alert.type_string(); } }; inline const BotanAlertCategory& botan_alert_category() noexcept { - static BotanAlertCategory category; + static const BotanAlertCategory category; return category; } @@ -78,16 +93,15 @@ } // namespace TLS //! @brief An error category for errors from Botan (other than TLS alerts) -struct BotanErrorCategory : boost::system::error_category { - virtual ~BotanErrorCategory() = default; - +class BotanErrorCategory : public BoostErrorCategory { + public: const char* name() const noexcept override { return "Botan"; } std::string message(int ev) const override { return Botan::to_string(static_cast(ev)); } }; inline const BotanErrorCategory& botan_category() noexcept { - static BotanErrorCategory category; + static const BotanErrorCategory category; return category; } diff -Nru botan3-3.7.1+dfsg/src/lib/tls/asio/asio_stream.h botan3-3.12.0+dfsg/src/lib/tls/asio/asio_stream.h --- botan3-3.7.1+dfsg/src/lib/tls/asio/asio_stream.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/asio/asio_stream.h 2026-05-07 01:38:28.000000000 +0000 @@ -31,7 +31,6 @@ #include #include - #include #include #include @@ -57,14 +56,14 @@ */ class StreamCallbacks : public Callbacks { public: - StreamCallbacks() {} + StreamCallbacks() = default; void tls_emit_data(std::span data) final { m_send_buffer.commit(boost::asio::buffer_copy(m_send_buffer.prepare(data.size()), boost::asio::buffer(data.data(), data.size()))); } - void tls_record_received(uint64_t, std::span data) final { + void tls_record_received(uint64_t /*record_number*/, std::span data) final { m_receive_buffer.commit(boost::asio::buffer_copy(m_receive_buffer.prepare(data.size()), boost::asio::const_buffer(data.data(), data.size()))); } @@ -108,6 +107,29 @@ } } + std::string tls_server_choose_app_protocol(const std::vector& client_protos) override { + if(client_protos.empty()) { + return ""; + } + auto ctx = m_context.lock(); + + if(!ctx || ctx->m_app_protocols.empty()) { + return ""; + } + // Priority is to the server. + for(const auto& server_proto : ctx->m_app_protocols) { + for(const auto& client_proto : client_protos) { + if(server_proto == client_proto) { + return server_proto; + } + } + } + // No match. + throw TLS_Exception( + Alert::NoApplicationProtocol, + "Rejecting ALPN request: no overlap between client-offered and server-configured application protocols"); + } + private: // The members below are meant for the tightly-coupled Stream class only template @@ -214,7 +236,7 @@ std::shared_ptr callbacks = std::make_shared()) : Stream(std::move(context), std::move(callbacks), std::forward(arg)) {} - #if defined(BOTAN_HAS_HAS_DEFAULT_TLS_CONTEXT) + #if defined(BOTAN_HAS_DEFAULT_TLS_CONTEXT) /** * @brief Conveniently construct a new Stream with default settings * @@ -242,7 +264,7 @@ //! \name boost::asio accessor methods //! @{ - using next_layer_type = typename std::remove_reference::type; + using next_layer_type = std::remove_reference_t; const next_layer_type& next_layer() const { return m_nextLayer; } @@ -258,10 +280,12 @@ executor_type get_executor() noexcept { return m_nextLayer.get_executor(); } - using native_handle_type = typename std::add_pointer::type; + using native_handle_type = std::add_pointer_t; native_handle_type native_handle() { - BOTAN_STATE_CHECK(m_native_handle != nullptr); + if(m_native_handle == nullptr) { + throw Botan::Invalid_State("ASIO native handle unexpectedly null"); + } return m_native_handle.get(); } @@ -288,8 +312,7 @@ * @param callback the callback implementation * @param ec This parameter is unused. */ - void set_verify_callback(Context::Verify_Callback callback, boost::system::error_code& ec) { - BOTAN_UNUSED(ec); + void set_verify_callback(Context::Verify_Callback callback, [[maybe_unused]] boost::system::error_code& ec) { m_context->set_verify_callback(std::move(callback)); } @@ -298,8 +321,7 @@ * @param depth the desired verification depth * @throws Not_Implemented todo */ - void set_verify_depth(int depth) { - BOTAN_UNUSED(depth); + void set_verify_depth([[maybe_unused]] int depth) { throw Not_Implemented("set_verify_depth is not implemented"); } @@ -308,8 +330,7 @@ * @param depth the desired verification depth * @param ec Will be set to `Botan::ErrorType::NotImplemented` */ - void set_verify_depth(int depth, boost::system::error_code& ec) { - BOTAN_UNUSED(depth); + void set_verify_depth([[maybe_unused]] int depth, boost::system::error_code& ec) { ec = ErrorType::NotImplemented; } @@ -319,8 +340,7 @@ * @throws Not_Implemented todo */ template - void set_verify_mode(verify_mode v) { - BOTAN_UNUSED(v); + void set_verify_mode([[maybe_unused]] verify_mode v) { throw Not_Implemented("set_verify_mode is not implemented"); } @@ -330,8 +350,7 @@ * @param ec Will be set to `Botan::ErrorType::NotImplemented` */ template - void set_verify_mode(verify_mode v, boost::system::error_code& ec) { - BOTAN_UNUSED(v); + void set_verify_mode([[maybe_unused]] verify_mode v, boost::system::error_code& ec) { ec = ErrorType::NotImplemented; } @@ -345,7 +364,7 @@ * The function call will block until handshaking is complete or an error occurs. * * @param side The type of handshaking to be performed, i.e. as a client or as a server. - * @throws boost::system::system_error if error occured + * @throws boost::system::system_error if error occurred */ void handshake(Connection_Side side) { boost::system::error_code ec; @@ -365,7 +384,7 @@ setup_native_handle(side, ec); // We write to the socket if we have data to send and read from it - // otherwise, until either some error occured or we have successfully + // otherwise, until either some error occurred or we have successfully // performed the handshake. while(!ec) { // Send pending data to the peer and abort the handshake if that @@ -393,8 +412,6 @@ // handled by `handle_tls_protocol_errors()` in the next iteration. read_and_process_encrypted_data_from_peer(ec); } - - BOTAN_ASSERT_NOMSG(ec.failed()); } /** @@ -408,6 +425,7 @@ */ template auto async_handshake(Botan::TLS::Connection_Side side, + // NOLINTNEXTLINE(*-missing-std-forward) CompletionToken&& completion_token = default_completion_token{}) { return boost::asio::async_initiate( [this](auto&& completion_handler, TLS::Connection_Side connection_side) { @@ -416,7 +434,7 @@ boost::system::error_code ec; setup_native_handle(connection_side, ec); - detail::AsyncHandshakeOperation op{ + const detail::AsyncHandshakeOperation op{ std::forward(completion_handler), *this, ec}; }, completion_token, @@ -428,10 +446,9 @@ * @throws Not_Implemented todo */ template - auto async_handshake(Connection_Side side, - const ConstBufferSequence& buffers, - BufferedHandshakeHandler&& handler) { - BOTAN_UNUSED(side, buffers, handler); + auto async_handshake([[maybe_unused]] Connection_Side side, + [[maybe_unused]] const ConstBufferSequence& buffers, + [[maybe_unused]] BufferedHandshakeHandler&& handler /* NOLINT(*missing-std-forward) */) { throw Not_Implemented("buffered async handshake is not implemented"); } @@ -447,7 +464,7 @@ * * Note that this can be used in reaction of a received shutdown alert from the peer. * - * @param ec Set to indicate what error occured, if any. + * @param ec Set to indicate what error occurred, if any. */ void shutdown(boost::system::error_code& ec) { try_with_error_code([&] { native_handle()->close(); }, ec); @@ -463,7 +480,7 @@ * * Note that this can be used in reaction of a received shutdown alert from the peer. * - * @throws boost::system::system_error if error occured + * @throws boost::system::system_error if error occurred */ void shutdown() { boost::system::error_code ec; @@ -481,7 +498,7 @@ */ template struct Wrapper { - void operator()(boost::system::error_code ec, std::size_t) { handler(ec); } + void operator()(boost::system::error_code ec, std::size_t /*unused*/) { handler(ec); } using executor_type = boost::asio::associated_executor_t; @@ -493,8 +510,8 @@ allocator_type get_allocator() const noexcept { return boost::asio::get_associated_allocator(handler); } - Handler handler; - Executor io_executor; + Handler handler; // NOLINT(*-non-private-member-variable*) + Executor io_executor; // NOLINT(*-non-private-member-variable*) }; public: @@ -509,6 +526,7 @@ * The completion signature of the handler must be: void(boost::system::error_code). */ template + // NOLINTNEXTLINE(*-missing-std-forward) auto async_shutdown(CompletionToken&& completion_token = default_completion_token{}) { return boost::asio::async_initiate( [this](auto&& completion_handler) { @@ -519,7 +537,7 @@ using write_handler_t = Wrapper; - TLS::detail::AsyncWriteOperation op{ + const TLS::detail::AsyncWriteOperation op{ write_handler_t{std::forward(completion_handler), get_executor()}, *this, boost::asio::buffer_size(send_buffer()), @@ -545,7 +563,7 @@ */ template std::size_t read_some(const MutableBufferSequence& buffers, boost::system::error_code& ec) { - // We read from the socket until either some error occured or we have + // We read from the socket until either some error occurred or we have // decrypted at least one byte of application data. while(!ec) { // Some previous invocation of process_encrypted_data() generated @@ -570,7 +588,6 @@ read_and_process_encrypted_data_from_peer(ec); } - BOTAN_ASSERT_NOMSG(ec.failed()); return 0; } @@ -582,7 +599,7 @@ * * @param buffers The buffers into which the data will be read. * @return The number of bytes read. Returns 0 if an error occurred. - * @throws boost::system::system_error if error occured + * @throws boost::system::system_error if error occurred */ template std::size_t read_some(const MutableBufferSequence& buffers) { @@ -617,7 +634,7 @@ * * @param buffers The data to be written. * @return The number of bytes written. - * @throws boost::system::system_error if error occured + * @throws boost::system::system_error if error occurred */ template std::size_t write_some(const ConstBufferSequence& buffers) { @@ -638,6 +655,7 @@ template auto async_write_some(const ConstBufferSequence& buffers, + // NOLINTNEXTLINE(*-missing-std-forward) CompletionToken&& completion_token = default_completion_token{}) { return boost::asio::async_initiate( [this](auto&& completion_handler, const auto& bufs) { @@ -652,7 +670,7 @@ m_core->send_buffer().consume(m_core->send_buffer().size()); } - detail::AsyncWriteOperation op{ + const detail::AsyncWriteOperation op{ std::forward(completion_handler), *this, ec ? 0 : boost::asio::buffer_size(bufs), @@ -674,12 +692,13 @@ template auto async_read_some(const MutableBufferSequence& buffers, + // NOLINTNEXTLINE(*-missing-std-forward) CompletionToken&& completion_token = default_completion_token{}) { return boost::asio::async_initiate( [this](auto&& completion_handler, const auto& bufs) { using completion_handler_t = std::decay_t; - detail::AsyncReadOperation op{ + const detail::AsyncReadOperation op{ std::forward(completion_handler), *this, bufs}; }, completion_token, @@ -739,8 +758,10 @@ void setup_native_handle(Connection_Side side, boost::system::error_code& ec) { // Do not attempt to instantiate the native_handle when a custom (mocked) channel type template parameter has // been specified. This allows mocking the native_handle in test code. - if constexpr(std::is_same::value) { - BOTAN_STATE_CHECK(m_native_handle == nullptr); + if constexpr(std::is_same_v) { + if(m_native_handle != nullptr) { + throw Botan::Invalid_State("ASIO native handle unexpectedly set"); + } try_with_error_code( [&] { @@ -752,7 +773,8 @@ m_context->m_policy, m_context->m_rng, m_context->m_server_info, - m_context->m_policy->latest_supported_version(false /* no DTLS */))); + m_context->m_policy->latest_supported_version(false /* no DTLS */), + m_context->m_app_protocols)); } else { m_native_handle = std::unique_ptr(new Server(m_core, m_context->m_session_manager, @@ -829,14 +851,19 @@ // If we have received application data in a previous invocation, this // data needs to be passed to the application first. Otherwise, it // might get overwritten. - BOTAN_ASSERT(!has_received_data(), "receive buffer is empty"); - BOTAN_ASSERT(!error_from_us() && !alert_from_peer(), "TLS session is healthy"); + if(has_received_data()) { + throw Botan::Invalid_State("ASIO receive buffer not empty"); + } + + if(error_from_us() || alert_from_peer()) { + throw Botan::Invalid_State("ASIO TLS session no longer healthy"); + } // If there's no existing error condition, read and process data from // the peer and report any sort of network error. TLS related errors do // not immediately cause an abort, they are checked in the invocation // via `error_from_us()`. - boost::asio::const_buffer read_buffer{input_buffer().data(), m_nextLayer.read_some(input_buffer(), ec)}; + const boost::asio::const_buffer read_buffer{input_buffer().data(), m_nextLayer.read_some(input_buffer(), ec)}; if(!ec) { process_encrypted_data(read_buffer); } else if(ec == boost::asio::error::eof) { @@ -913,8 +940,9 @@ * @param read_buffer Input buffer containing the encrypted data. */ void process_encrypted_data(const boost::asio::const_buffer& read_buffer) { - BOTAN_ASSERT(!alert_from_peer() && !error_from_us(), - "no one sent an alert before (no data allowed after that)"); + if(alert_from_peer() || error_from_us()) { + throw Botan::Invalid_State("ASIO TLS session no longer healthy"); + } // If the local TLS implementation generates an alert, we are notified // with an exception that is caught in try_with_error_code(). The error @@ -958,16 +986,16 @@ boost::system::error_code error_from_us() const { return m_ec_from_last_read; } protected: - std::shared_ptr m_context; - StreamLayer m_nextLayer; + std::shared_ptr m_context; // NOLINT(*-non-private-member-variable*) + StreamLayer m_nextLayer; // NOLINT(*-non-private-member-variable*) - std::shared_ptr m_core; - std::unique_ptr m_native_handle; - boost::system::error_code m_ec_from_last_read; + std::shared_ptr m_core; // NOLINT(*-non-private-member-variable*) + std::unique_ptr m_native_handle; // NOLINT(*-non-private-member-variable*) + boost::system::error_code m_ec_from_last_read; // NOLINT(*-non-private-member-variable*) // Buffer space used to read input intended for the core - std::vector m_input_buffer_space; - const boost::asio::mutable_buffer m_input_buffer; + std::vector m_input_buffer_space; // NOLINT(*-non-private-member-variable*) + const boost::asio::mutable_buffer m_input_buffer; // NOLINT(*-non-private-member-variable*) }; // deduction guides for convenient construction from an existing diff -Nru botan3-3.7.1+dfsg/src/lib/tls/credentials_manager.cpp botan3-3.12.0+dfsg/src/lib/tls/credentials_manager.cpp --- botan3-3.7.1+dfsg/src/lib/tls/credentials_manager.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/credentials_manager.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,6 +8,8 @@ #include #include +#include +#include #include namespace Botan { diff -Nru botan3-3.7.1+dfsg/src/lib/tls/credentials_manager.h botan3-3.12.0+dfsg/src/lib/tls/credentials_manager.h --- botan3-3.7.1+dfsg/src/lib/tls/credentials_manager.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/credentials_manager.h 2026-05-07 01:38:28.000000000 +0000 @@ -8,20 +8,26 @@ #ifndef BOTAN_CREDENTIALS_MANAGER_H_ #define BOTAN_CREDENTIALS_MANAGER_H_ -#include -#include -#include -#include #include -#include #include -#include +#include +#include #include namespace Botan { +class AlgorithmIdentifier; +class Certificate_Store; +class Public_Key; +class Private_Key; +class X509_Certificate; class X509_DN; -class BigInt; + +namespace TLS { + +class ExternalPSK; + +} /** * Interface for a credentials manager. @@ -31,7 +37,7 @@ * and "tls-server". Context represents a hostname, email address, * username, or other identifier. */ -class BOTAN_PUBLIC_API(2, 0) Credentials_Manager { +class BOTAN_PUBLIC_API(2, 0) Credentials_Manager /* NOLINT(*-special-member-functions) */ { public: virtual ~Credentials_Manager() = default; @@ -236,6 +242,8 @@ * dtls_cookie_secret() respectively. New applications should implement * those methods and rely on the default implementation of psk(). * + * TODO(Botan4) remove this interface + * * @param type specifies the type of operation occurring * @param context specifies a context relative to type. * @param identity is a PSK identity previously returned by diff -Nru botan3-3.7.1+dfsg/src/lib/tls/info.txt botan3-3.12.0+dfsg/src/lib/tls/info.txt --- botan3-3.7.1+dfsg/src/lib/tls/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -27,6 +27,7 @@ tls_server.h tls_server_info.h tls_session.h +tls_session_id.h tls_session_manager.h tls_session_manager_noop.h tls_session_manager_memory.h @@ -39,10 +40,15 @@ tls_channel_impl.h tls_handshake_transitions.h +tls_messages_internal.h tls_reader.h +aead +aes +asn1 +dh ecdh ecdsa gcm @@ -50,11 +56,7 @@ rng sha2_32 sha2_64 -tls12 x509 -pcurves_secp256r1 -pcurves_secp384r1 -pcurves_secp521r1 diff -Nru botan3-3.7.1+dfsg/src/lib/tls/msg_cert_req.cpp botan3-3.12.0+dfsg/src/lib/tls/msg_cert_req.cpp --- botan3-3.7.1+dfsg/src/lib/tls/msg_cert_req.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/msg_cert_req.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,155 +0,0 @@ -/* -* Certificate Request Message -* (C) 2004-2006,2012 Jack Lloyd -* 2021 Elektrobit Automotive GmbH -* 2022 René Meusel, Hannes Rantzsch - neXenio GmbH -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include - -#include -#include -#include -#include -#include -#include - -namespace Botan::TLS { - -Handshake_Type Certificate_Request_12::type() const { - return Handshake_Type::CertificateRequest; -} - -namespace { - -std::string cert_type_code_to_name(uint8_t code) { - switch(code) { - case 1: - return "RSA"; - case 64: - return "ECDSA"; - default: - return ""; // DH or something else - } -} - -uint8_t cert_type_name_to_code(std::string_view name) { - if(name == "RSA") { - return 1; - } - if(name == "ECDSA") { - return 64; - } - - throw Invalid_Argument(fmt("Unknown/unhandled TLS cert type {}", name)); -} - -} // namespace - -/** -* Create a new Certificate Request message -*/ -Certificate_Request_12::Certificate_Request_12(Handshake_IO& io, - Handshake_Hash& hash, - const Policy& policy, - const std::vector& ca_certs) : - m_names(ca_certs), m_cert_key_types({"RSA", "ECDSA"}) { - m_schemes = policy.acceptable_signature_schemes(); - hash.update(io.send(*this)); -} - -/** -* Deserialize a Certificate Request message -*/ -Certificate_Request_12::Certificate_Request_12(const std::vector& buf) { - if(buf.size() < 4) { - throw Decoding_Error("Certificate_Req: Bad certificate request"); - } - - TLS_Data_Reader reader("CertificateRequest", buf); - - const auto cert_type_codes = reader.get_range_vector(1, 1, 255); - - for(const auto cert_type_code : cert_type_codes) { - const std::string cert_type_name = cert_type_code_to_name(cert_type_code); - - if(cert_type_name.empty()) { // something we don't know - continue; - } - - m_cert_key_types.emplace_back(cert_type_name); - } - - const std::vector algs = reader.get_range_vector(2, 2, 65534); - - if(algs.size() % 2 != 0) { - throw Decoding_Error("Bad length for signature IDs in certificate request"); - } - - for(size_t i = 0; i != algs.size(); i += 2) { - m_schemes.emplace_back(make_uint16(algs[i], algs[i + 1])); - } - - const uint16_t purported_size = reader.get_uint16_t(); - - if(reader.remaining_bytes() != purported_size) { - throw Decoding_Error("Inconsistent length in certificate request"); - } - - while(reader.has_remaining()) { - std::vector name_bits = reader.get_range_vector(2, 0, 65535); - - BER_Decoder decoder(name_bits.data(), name_bits.size()); - X509_DN name; - decoder.decode(name); - m_names.emplace_back(name); - } -} - -const std::vector& Certificate_Request_12::acceptable_cert_types() const { - return m_cert_key_types; -} - -const std::vector& Certificate_Request_12::acceptable_CAs() const { - return m_names; -} - -const std::vector& Certificate_Request_12::signature_schemes() const { - return m_schemes; -} - -/** -* Serialize a Certificate Request message -*/ -std::vector Certificate_Request_12::serialize() const { - std::vector buf; - - std::vector cert_types; - - cert_types.reserve(m_cert_key_types.size()); - for(const auto& cert_key_type : m_cert_key_types) { - cert_types.push_back(cert_type_name_to_code(cert_key_type)); - } - - append_tls_length_value(buf, cert_types, 1); - - if(!m_schemes.empty()) { - buf += Signature_Algorithms(m_schemes).serialize(Connection_Side::Server); - } - - std::vector encoded_names; - - for(const auto& name : m_names) { - DER_Encoder encoder; - encoder.encode(name); - - append_tls_length_value(encoded_names, encoder.get_contents(), 2); - } - - append_tls_length_value(buf, encoded_names, 2); - - return buf; -} -} // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/msg_cert_status.cpp botan3-3.12.0+dfsg/src/lib/tls/msg_cert_status.cpp --- botan3-3.7.1+dfsg/src/lib/tls/msg_cert_status.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/msg_cert_status.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,54 @@ +/* +* Certificate Status +* (C) 2016 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include + +namespace Botan::TLS { + +Certificate_Status::Certificate_Status(const std::vector& buf, const Connection_Side /*side*/) { + if(buf.size() < 5) { + throw Decoding_Error("Invalid Certificate_Status message: too small"); + } + + if(buf[0] != 1) { // not OCSP + throw Decoding_Error("Unexpected Certificate_Status message: unexpected response type"); + } + + const size_t len = make_uint32(0, buf[1], buf[2], buf[3]); + + // Verify the redundant length field... + if(buf.size() != len + 4) { + throw Decoding_Error("Invalid Certificate_Status: invalid length field"); + } + + m_response.assign(buf.begin() + 4, buf.end()); +} + +Certificate_Status::Certificate_Status(std::vector raw_response_bytes) : + m_response(std::move(raw_response_bytes)) {} + +std::vector Certificate_Status::serialize() const { + if(m_response.size() > 0xFFFFFF) { // unlikely + throw Encoding_Error("OCSP response too long to encode in TLS"); + } + + const uint32_t response_len = static_cast(m_response.size()); + + std::vector buf; + buf.reserve(1 + 3 + m_response.size()); + buf.push_back(1); // type OCSP + for(size_t i = 1; i < 4; ++i) { + buf.push_back(get_byte_var(i, response_len)); + } + + buf.insert(buf.end(), m_response.begin(), m_response.end()); + return buf; +} + +} // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/msg_cert_verify.cpp botan3-3.12.0+dfsg/src/lib/tls/msg_cert_verify.cpp --- botan3-3.7.1+dfsg/src/lib/tls/msg_cert_verify.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/msg_cert_verify.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -10,42 +10,18 @@ #include -#include -#include -#include -#include -#include -#include -#include #include namespace Botan::TLS { /* -* Create a new Certificate Verify message for TLS 1.2 -*/ -Certificate_Verify_12::Certificate_Verify_12(Handshake_IO& io, - Handshake_State& state, - const Policy& policy, - RandomNumberGenerator& rng, - const Private_Key* priv_key) { - BOTAN_ASSERT_NONNULL(priv_key); - - std::pair format = state.choose_sig_format(*priv_key, m_scheme, true, policy); - - m_signature = - state.callbacks().tls_sign_message(*priv_key, rng, format.first, format.second, state.hash().get_contents()); - - state.hash().update(io.send(*this)); -} - -/* * Deserialize a Certificate Verify message */ Certificate_Verify::Certificate_Verify(const std::vector& buf) { TLS_Data_Reader reader("CertificateVerify", buf); m_scheme = Signature_Scheme(reader.get_uint16_t()); + // Somewhat oddly, the signature really is allowed to be empty in a CertificateVerify m_signature = reader.get_range(2, 0, 65535); reader.assert_done(); @@ -78,131 +54,4 @@ return buf; } -bool Certificate_Verify_12::verify(const X509_Certificate& cert, - const Handshake_State& state, - const Policy& policy) const { - auto key = cert.subject_public_key(); - - policy.check_peer_key_acceptable(*key); - - std::pair format = - state.parse_sig_format(*key, m_scheme, state.client_hello()->signature_schemes(), true, policy); - - const bool signature_valid = - state.callbacks().tls_verify_message(*key, format.first, format.second, state.hash().get_contents(), m_signature); - -#if defined(BOTAN_UNSAFE_FUZZER_MODE) - BOTAN_UNUSED(signature_valid); - return true; - -#else - return signature_valid; - -#endif -} - -#if defined(BOTAN_HAS_TLS_13) - -namespace { - -std::vector message(Connection_Side side, const Transcript_Hash& hash) { - std::vector msg(64, 0x20); - msg.reserve(64 + 33 + 1 + hash.size()); - - const std::string context_string = (side == TLS::Connection_Side::Server) ? "TLS 1.3, server CertificateVerify" - : "TLS 1.3, client CertificateVerify"; - - msg.insert(msg.end(), context_string.cbegin(), context_string.cend()); - msg.push_back(0x00); - - msg.insert(msg.end(), hash.cbegin(), hash.cend()); - return msg; -} - -Signature_Scheme choose_signature_scheme(const Private_Key& key, - const std::vector& allowed_schemes, - const std::vector& peer_allowed_schemes) { - for(Signature_Scheme scheme : allowed_schemes) { - if(scheme.is_available() && scheme.is_suitable_for(key) && value_exists(peer_allowed_schemes, scheme)) { - return scheme; - } - } - - throw TLS_Exception(Alert::HandshakeFailure, "Failed to agree on a signature algorithm"); -} - -} // namespace - -/* -* Create a new Certificate Verify message for TLS 1.3 -*/ -Certificate_Verify_13::Certificate_Verify_13(const Certificate_13& certificate_msg, - const std::vector& peer_allowed_schemes, - std::string_view hostname, - const Transcript_Hash& hash, - Connection_Side whoami, - Credentials_Manager& creds_mgr, - const Policy& policy, - Callbacks& callbacks, - RandomNumberGenerator& rng) : - m_side(whoami) { - BOTAN_ASSERT_NOMSG(!certificate_msg.empty()); - - const auto op_type = (m_side == Connection_Side::Client) ? "tls-client" : "tls-server"; - const auto context = std::string(hostname); - - const auto private_key = (certificate_msg.has_certificate_chain()) - ? creds_mgr.private_key_for(certificate_msg.leaf(), op_type, context) - : creds_mgr.private_key_for(*certificate_msg.public_key(), op_type, context); - if(!private_key) { - throw TLS_Exception(Alert::InternalError, "Application did not provide a private key for its credential"); - } - - m_scheme = choose_signature_scheme(*private_key, policy.allowed_signature_schemes(), peer_allowed_schemes); - BOTAN_ASSERT_NOMSG(m_scheme.is_available()); - BOTAN_ASSERT_NOMSG(m_scheme.is_compatible_with(Protocol_Version::TLS_V13)); - - m_signature = callbacks.tls_sign_message( - *private_key, rng, m_scheme.padding_string(), m_scheme.format().value(), message(m_side, hash)); -} - -Certificate_Verify_13::Certificate_Verify_13(const std::vector& buf, const Connection_Side side) : - Certificate_Verify(buf), m_side(side) { - if(!m_scheme.is_available()) { - throw TLS_Exception(Alert::IllegalParameter, "Peer sent unknown signature scheme"); - } - - if(!m_scheme.is_compatible_with(Protocol_Version::TLS_V13)) { - throw TLS_Exception(Alert::IllegalParameter, "Peer sent signature algorithm that is not suitable for TLS 1.3"); - } -} - -/* -* Verify a Certificate Verify message -*/ -bool Certificate_Verify_13::verify(const Public_Key& public_key, - Callbacks& callbacks, - const Transcript_Hash& transcript_hash) const { - BOTAN_ASSERT_NOMSG(m_scheme.is_available()); - - // RFC 8446 4.2.3 - // The keys found in certificates MUST [...] be of appropriate type for - // the signature algorithms they are used with. - if(m_scheme.key_algorithm_identifier() != public_key.algorithm_identifier()) { - throw TLS_Exception(Alert::IllegalParameter, "Signature algorithm does not match certificate's public key"); - } - - const bool signature_valid = callbacks.tls_verify_message( - public_key, m_scheme.padding_string(), m_scheme.format().value(), message(m_side, transcript_hash), m_signature); - - #if defined(BOTAN_UNSAFE_FUZZER_MODE) - BOTAN_UNUSED(signature_valid); - return true; - #else - return signature_valid; - #endif -} - -#endif // BOTAN_HAS_TLS_13 - } // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/msg_client_hello.cpp botan3-3.12.0+dfsg/src/lib/tls/msg_client_hello.cpp --- botan3-3.7.1+dfsg/src/lib/tls/msg_client_hello.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/msg_client_hello.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -1,37 +1,24 @@ /* -* TLS Hello Request and Client Hello Messages +* TLS Client Hello Messages * (C) 2004-2011,2015,2016 Jack Lloyd * 2016 Matthias Gierlings * 2017 Harry Reimann, Rohde & Schwarz Cybersecurity * 2021 Elektrobit Automotive GmbH * 2022 René Meusel, Hannes Rantzsch - neXenio GmbH +* 2026 René Meusel - Rohde & Schwarz Cybersecurity GmbH * * Botan is released under the Simplified BSD License (see license.txt) */ #include +#include -#include #include #include #include -#include -#include - -#include -#include -#include -#include +#include #include -#include - -#ifdef BOTAN_HAS_TLS_13 - #include - #include -#endif - -#include -#include +#include namespace Botan::TLS { @@ -56,112 +43,82 @@ return buf; } -/** - * Version-agnostic internal client hello data container that allows - * parsing Client_Hello messages without prior knowledge of the contained - * protocol version. - */ -class Client_Hello_Internal { - public: - Client_Hello_Internal() : m_comp_methods({0}) {} - - Client_Hello_Internal(const std::vector& buf) { - if(buf.size() < 41) { - throw Decoding_Error("Client_Hello: Packet corrupted"); - } - - TLS_Data_Reader reader("ClientHello", buf); - - const uint8_t major_version = reader.get_byte(); - const uint8_t minor_version = reader.get_byte(); - - m_legacy_version = Protocol_Version(major_version, minor_version); - m_random = reader.get_fixed(32); - m_session_id = Session_ID(reader.get_range(1, 0, 32)); - - if(m_legacy_version.is_datagram_protocol()) { - auto sha256 = HashFunction::create_or_throw("SHA-256"); - sha256->update(reader.get_data_read_so_far()); - - m_hello_cookie = reader.get_range(1, 0, 255); - - sha256->update(reader.get_remaining()); - m_cookie_input_bits = sha256->final_stdvec(); - } - - m_suites = reader.get_range_vector(2, 1, 32767); - m_comp_methods = reader.get_range_vector(1, 1, 255); - - m_extensions.deserialize(reader, Connection_Side::Client, Handshake_Type::ClientHello); - } - - /** - * This distinguishes between a TLS 1.3 compliant Client Hello (containing - * the "supported_version" extension) and legacy Client Hello messages. - * - * @return TLS 1.3 if the Client Hello contains "supported_versions", or - * the content of the "legacy_version" version field if it - * indicates (D)TLS 1.2 or older, or - * (D)TLS 1.2 if the "legacy_version" was some other odd value. - */ - Protocol_Version version() const { - // RFC 8446 4.2.1 - // If [the "supported_versions"] extension is not present, servers - // which are compliant with this specification and which also support - // TLS 1.2 MUST negotiate TLS 1.2 or prior as specified in [RFC5246], - // even if ClientHello.legacy_version is 0x0304 or later. - // - // RFC 8446 4.2.1 - // Servers MUST be prepared to receive ClientHellos that include - // [the supported_versions] extension but do not include 0x0304 in - // the list of versions. - // - // RFC 8446 4.1.2 - // TLS 1.3 ClientHellos are identified as having a legacy_version of - // 0x0303 and a supported_versions extension present with 0x0304 as - // the highest version indicated therein. - if(!extensions().has() || - !extensions().get()->supports(Protocol_Version::TLS_V13)) { - // The exact legacy_version is ignored we just inspect it to - // distinguish TLS and DTLS. - return (m_legacy_version.is_datagram_protocol()) ? Protocol_Version::DTLS_V12 : Protocol_Version::TLS_V12; - } - - // Note: The Client_Hello_13 class will make sure that legacy_version - // is exactly 0x0303 (aka ossified TLS 1.2) - return Protocol_Version::TLS_V13; - } - - Protocol_Version legacy_version() const { return m_legacy_version; } - - const Session_ID& session_id() const { return m_session_id; } - - const std::vector& random() const { return m_random; } - - const std::vector& ciphersuites() const { return m_suites; } - - const std::vector& comp_methods() const { return m_comp_methods; } - - const std::vector& hello_cookie() const { return m_hello_cookie; } - - const std::vector& hello_cookie_input_bits() const { return m_cookie_input_bits; } - - const Extensions& extensions() const { return m_extensions; } - - Extensions& extensions() { return m_extensions; } - - public: - Protocol_Version m_legacy_version; // NOLINT(*-non-private-member-variables-in-classes) - Session_ID m_session_id; // NOLINT(*-non-private-member-variables-in-classes) - std::vector m_random; // NOLINT(*-non-private-member-variables-in-classes) - std::vector m_suites; // NOLINT(*-non-private-member-variables-in-classes) - std::vector m_comp_methods; // NOLINT(*-non-private-member-variables-in-classes) - Extensions m_extensions; // NOLINT(*-non-private-member-variables-in-classes) - - // These fields are only for DTLS: - std::vector m_hello_cookie; // NOLINT(*-non-private-member-variables-in-classes) - std::vector m_cookie_input_bits; // NOLINT(*-non-private-member-variables-in-classes) -}; +Client_Hello_Internal::Client_Hello_Internal(const std::vector& buf) { + /* + Minimum possible client hello + + version: 2 bytes + random: 32 bytes + session_id len: 1 byte + ciphersuite_len: 2 + ciphersuite (single): 2 + compression_len: 1 + compression (single): 1 + */ + + constexpr size_t MinimumClientHelloBytes = 2 + 32 + 1 + 2 + 2 + 1 + 1; + if(buf.size() < MinimumClientHelloBytes) { + throw Decoding_Error("Client_Hello: Packet corrupted"); + } + + TLS_Data_Reader reader("ClientHello", buf); + + const uint8_t major_version = reader.get_byte(); + const uint8_t minor_version = reader.get_byte(); + + m_legacy_version = Protocol_Version(major_version, minor_version); + + // DTLS has an additional 1 byte cookie length field + if(m_legacy_version.is_datagram_protocol() && buf.size() < MinimumClientHelloBytes + 1) { + throw Decoding_Error("Client_Hello: DTLS packet corrupted"); + } + + m_random = reader.get_fixed(32); + m_session_id = Session_ID(reader.get_range(1, 0, 32)); + + if(m_legacy_version.is_datagram_protocol()) { + auto sha256 = HashFunction::create_or_throw("SHA-256"); + sha256->update(reader.get_data_read_so_far()); + + m_hello_cookie = reader.get_range(1, 0, 255); + + sha256->update(reader.get_remaining()); + m_cookie_input_bits = sha256->final_stdvec(); + } + + m_suites = reader.get_range_vector(2, 1, 32767); + m_comp_methods = reader.get_range_vector(1, 1, 255); + + m_extensions.deserialize(reader, Connection_Side::Client, Handshake_Type::ClientHello); +} + +Protocol_Version Client_Hello_Internal::version() const { + // RFC 8446 4.2.1 + // If [the "supported_versions"] extension is not present, servers + // which are compliant with this specification and which also support + // TLS 1.2 MUST negotiate TLS 1.2 or prior as specified in [RFC5246], + // even if ClientHello.legacy_version is 0x0304 or later. + // + // RFC 8446 4.2.1 + // Servers MUST be prepared to receive ClientHellos that include + // [the supported_versions] extension but do not include 0x0304 in + // the list of versions. + // + // RFC 8446 4.1.2 + // TLS 1.3 ClientHellos are identified as having a legacy_version of + // 0x0303 and a supported_versions extension present with 0x0304 as + // the highest version indicated therein. + if(!extensions().has() || + !extensions().get()->supports(Protocol_Version::TLS_V13)) { + // The exact legacy_version is ignored we just inspect it to + // distinguish TLS and DTLS. + return (m_legacy_version.is_datagram_protocol()) ? Protocol_Version::DTLS_V12 : Protocol_Version::TLS_V12; + } + + // Note: The Client_Hello_13 class will make sure that legacy_version + // is exactly 0x0303 (aka ossified TLS 1.2) + return Protocol_Version::TLS_V13; +} Client_Hello::Client_Hello(Client_Hello&&) noexcept = default; Client_Hello& Client_Hello::operator=(Client_Hello&&) noexcept = default; @@ -209,12 +166,6 @@ return m_data->extensions(); } -void Client_Hello_12::update_hello_cookie(const Hello_Verify_Request& hello_verify) { - BOTAN_STATE_CHECK(m_data->legacy_version().is_datagram_protocol()); - - m_data->m_hello_cookie = hello_verify.cookie(); -} - /* * Serialize a Client Hello message */ @@ -261,7 +212,7 @@ } std::vector Client_Hello::signature_schemes() const { - if(Signature_Algorithms* sigs = m_data->extensions().get()) { + if(const Signature_Algorithms* sigs = m_data->extensions().get()) { return sigs->supported_schemes(); } return {}; @@ -272,7 +223,7 @@ // If no "signature_algorithms_cert" extension is present, then the // "signature_algorithms" extension also applies to signatures appearing // in certificates. - if(Signature_Algorithms_Cert* sigs = m_data->extensions().get()) { + if(const Signature_Algorithms_Cert* sigs = m_data->extensions().get()) { return sigs->supported_schemes(); } else { return signature_schemes(); @@ -280,105 +231,50 @@ } std::vector Client_Hello::supported_ecc_curves() const { - if(Supported_Groups* groups = m_data->extensions().get()) { + if(const Supported_Groups* groups = m_data->extensions().get()) { return groups->ec_groups(); } return {}; } std::vector Client_Hello::supported_dh_groups() const { - if(Supported_Groups* groups = m_data->extensions().get()) { + if(const Supported_Groups* groups = m_data->extensions().get()) { return groups->dh_groups(); } return std::vector(); } -bool Client_Hello_12::prefers_compressed_ec_points() const { - if(Supported_Point_Formats* ecc_formats = m_data->extensions().get()) { - return ecc_formats->prefers_compressed(); - } - return false; -} - std::string Client_Hello::sni_hostname() const { - if(Server_Name_Indicator* sni = m_data->extensions().get()) { + if(const Server_Name_Indicator* sni = m_data->extensions().get()) { return sni->host_name(); } return ""; } -bool Client_Hello_12::secure_renegotiation() const { - return m_data->extensions().has(); -} - -std::vector Client_Hello_12::renegotiation_info() const { - if(Renegotiation_Extension* reneg = m_data->extensions().get()) { - return reneg->renegotiation_info(); - } - return {}; -} - std::vector Client_Hello::supported_versions() const { - if(Supported_Versions* versions = m_data->extensions().get()) { + if(const Supported_Versions* versions = m_data->extensions().get()) { return versions->versions(); } return {}; } -bool Client_Hello_12::supports_session_ticket() const { - return m_data->extensions().has(); -} - -Session_Ticket Client_Hello_12::session_ticket() const { - if(auto* ticket = m_data->extensions().get()) { - return ticket->contents(); - } - return {}; -} - -std::optional Client_Hello_12::session_handle() const { - // RFC 5077 3.4 - // If a ticket is presented by the client, the server MUST NOT attempt - // to use the Session ID in the ClientHello for stateful session - // resumption. - if(auto ticket = session_ticket(); !ticket.empty()) { - return ticket; - } else if(const auto& id = session_id(); !id.empty()) { - return id; - } else { - return std::nullopt; - } -} - bool Client_Hello::supports_alpn() const { return m_data->extensions().has(); } -bool Client_Hello_12::supports_extended_master_secret() const { - return m_data->extensions().has(); -} - -bool Client_Hello_12::supports_cert_status_message() const { - return m_data->extensions().has(); -} - -bool Client_Hello_12::supports_encrypt_then_mac() const { - return m_data->extensions().has(); -} - bool Client_Hello::sent_signature_algorithms() const { return m_data->extensions().has(); } std::vector Client_Hello::next_protocols() const { - if(auto alpn = m_data->extensions().get()) { + if(auto* alpn = m_data->extensions().get()) { return alpn->protocols(); } return {}; } std::vector Client_Hello::srtp_profiles() const { - if(SRTP_Protection_Profiles* srtp = m_data->extensions().get()) { + if(const SRTP_Protection_Profiles* srtp = m_data->extensions().get()) { return srtp->profiles(); } return {}; @@ -388,685 +284,10 @@ return m_data->hello_cookie(); } -/* -* Create a new Hello Request message -*/ -Hello_Request::Hello_Request(Handshake_IO& io) { - io.send(*this); -} - -/* -* Deserialize a Hello Request message -*/ -Hello_Request::Hello_Request(const std::vector& buf) { - if(!buf.empty()) { - throw Decoding_Error("Bad Hello_Request, has non-zero size"); - } -} - -/* -* Serialize a Hello Request message -*/ -std::vector Hello_Request::serialize() const { - return std::vector(); -} - -void Client_Hello_12::add_tls12_supported_groups_extensions(const Policy& policy) { - // RFC 7919 3. - // A client that offers a group MUST be able and willing to perform a DH - // key exchange using that group. - // - // We don't support hybrid key exchange in TLS 1.2 - const std::vector kex_groups = policy.key_exchange_groups(); - std::vector compatible_kex_groups; - std::copy_if(kex_groups.begin(), kex_groups.end(), std::back_inserter(compatible_kex_groups), [](const auto group) { - return !group.is_post_quantum(); - }); - - auto supported_groups = std::make_unique(std::move(compatible_kex_groups)); - - if(!supported_groups->ec_groups().empty()) { - m_data->extensions().add(new Supported_Point_Formats(policy.use_ecc_point_compression())); - } - - m_data->extensions().add(std::move(supported_groups)); -} - -Client_Hello_12::Client_Hello_12(std::unique_ptr data) : Client_Hello(std::move(data)) { - const uint16_t TLS_EMPTY_RENEGOTIATION_INFO_SCSV = 0x00FF; - - if(offered_suite(static_cast(TLS_EMPTY_RENEGOTIATION_INFO_SCSV))) { - if(Renegotiation_Extension* reneg = m_data->extensions().get()) { - if(!reneg->renegotiation_info().empty()) { - throw TLS_Exception(Alert::HandshakeFailure, "Client sent renegotiation SCSV and non-empty extension"); - } - } else { - // add fake extension - m_data->extensions().add(new Renegotiation_Extension()); - } - } -} - -namespace { - -// Avoid sending an IPv4/IPv6 address in SNI as this is prohibitied -bool hostname_acceptable_for_sni(std::string_view hostname) { - if(hostname.empty()) { - return false; - } - - if(string_to_ipv4(hostname).has_value()) { - return false; - } - - // IPv6? Anyway ':' is not valid in DNS - if(hostname.find(':') != std::string_view::npos) { - return false; - } - - return true; -} - -} // namespace - -// Note: This delegates to the Client_Hello_12 constructor to take advantage -// of the sanity checks there. -Client_Hello_12::Client_Hello_12(const std::vector& buf) : - Client_Hello_12(std::make_unique(buf)) {} - -/* -* Create a new Client Hello message -*/ -Client_Hello_12::Client_Hello_12(Handshake_IO& io, - Handshake_Hash& hash, - const Policy& policy, - Callbacks& cb, - RandomNumberGenerator& rng, - const std::vector& reneg_info, - const Client_Hello_12::Settings& client_settings, - const std::vector& next_protocols) { - m_data->m_legacy_version = client_settings.protocol_version(); - m_data->m_random = make_hello_random(rng, cb, policy); - m_data->m_suites = policy.ciphersuite_list(client_settings.protocol_version()); - - if(!policy.acceptable_protocol_version(m_data->legacy_version())) { - throw Internal_Error("Offering " + m_data->legacy_version().to_string() + - " but our own policy does not accept it"); - } - - /* - * Place all empty extensions in front to avoid a bug in some systems - * which reject hellos when the last extension in the list is empty. - */ - - // EMS must always be used with TLS 1.2, regardless of the policy used. - m_data->extensions().add(new Extended_Master_Secret); - - if(policy.negotiate_encrypt_then_mac()) { - m_data->extensions().add(new Encrypt_then_MAC); - } - - m_data->extensions().add(new Session_Ticket_Extension()); - - m_data->extensions().add(new Renegotiation_Extension(reneg_info)); - - m_data->extensions().add(new Supported_Versions(m_data->legacy_version(), policy)); - - if(hostname_acceptable_for_sni(client_settings.hostname())) { - m_data->extensions().add(new Server_Name_Indicator(client_settings.hostname())); - } - - if(policy.support_cert_status_message()) { - m_data->extensions().add(new Certificate_Status_Request({}, {})); - } - - add_tls12_supported_groups_extensions(policy); - - m_data->extensions().add(new Signature_Algorithms(policy.acceptable_signature_schemes())); - if(auto cert_signing_prefs = policy.acceptable_certificate_signature_schemes()) { - // RFC 8446 4.2.3 - // TLS 1.2 implementations SHOULD also process this extension. - // Implementations which have the same policy in both cases MAY omit - // the "signature_algorithms_cert" extension. - m_data->extensions().add(new Signature_Algorithms_Cert(std::move(cert_signing_prefs.value()))); - } - - if(reneg_info.empty() && !next_protocols.empty()) { - m_data->extensions().add(new Application_Layer_Protocol_Notification(next_protocols)); - } - - if(m_data->legacy_version().is_datagram_protocol()) { - m_data->extensions().add(new SRTP_Protection_Profiles(policy.srtp_profiles())); - } - - cb.tls_modify_extensions(m_data->extensions(), Connection_Side::Client, type()); - - hash.update(io.send(*this)); -} - -/* -* Create a new Client Hello message (session resumption case) -*/ -Client_Hello_12::Client_Hello_12(Handshake_IO& io, - Handshake_Hash& hash, - const Policy& policy, - Callbacks& cb, - RandomNumberGenerator& rng, - const std::vector& reneg_info, - const Session_with_Handle& session, - const std::vector& next_protocols) { - m_data->m_legacy_version = session.session.version(); - m_data->m_random = make_hello_random(rng, cb, policy); - - // RFC 5077 3.4 - // When presenting a ticket, the client MAY generate and include a - // Session ID in the TLS ClientHello. [...] If a ticket is presented by - // the client, the server MUST NOT attempt to use the Session ID in the - // ClientHello for stateful session resumption. - m_data->m_session_id = session.handle.id().value_or(Session_ID(make_hello_random(rng, cb, policy))); - m_data->m_suites = policy.ciphersuite_list(m_data->legacy_version()); - - if(!policy.acceptable_protocol_version(session.session.version())) { - throw Internal_Error("Offering " + m_data->legacy_version().to_string() + - " but our own policy does not accept it"); - } - - if(!value_exists(m_data->ciphersuites(), session.session.ciphersuite_code())) { - m_data->m_suites.push_back(session.session.ciphersuite_code()); - } - - /* - * As EMS must always be used with TLS 1.2, add it even if it wasn't used - * in the original session. If the server understands it and follows the - * RFC it should reject our resume attempt and upgrade us to a new session - * with the EMS protection. - */ - m_data->extensions().add(new Extended_Master_Secret); - - if(session.session.supports_encrypt_then_mac()) { - m_data->extensions().add(new Encrypt_then_MAC); - } - - if(session.handle.is_ticket()) { - m_data->extensions().add(new Session_Ticket_Extension(session.handle.ticket().value())); - } - - m_data->extensions().add(new Renegotiation_Extension(reneg_info)); - - const std::string hostname = session.session.server_info().hostname(); - - if(hostname_acceptable_for_sni(hostname)) { - m_data->extensions().add(new Server_Name_Indicator(hostname)); - } - - if(policy.support_cert_status_message()) { - m_data->extensions().add(new Certificate_Status_Request({}, {})); - } - - add_tls12_supported_groups_extensions(policy); - - m_data->extensions().add(new Signature_Algorithms(policy.acceptable_signature_schemes())); - if(auto cert_signing_prefs = policy.acceptable_certificate_signature_schemes()) { - // RFC 8446 4.2.3 - // TLS 1.2 implementations SHOULD also process this extension. - // Implementations which have the same policy in both cases MAY omit - // the "signature_algorithms_cert" extension. - m_data->extensions().add(new Signature_Algorithms_Cert(std::move(cert_signing_prefs.value()))); - } - - if(reneg_info.empty() && !next_protocols.empty()) { - m_data->extensions().add(new Application_Layer_Protocol_Notification(next_protocols)); - } - - cb.tls_modify_extensions(m_data->extensions(), Connection_Side::Client, type()); - - hash.update(io.send(*this)); -} - -#if defined(BOTAN_HAS_TLS_13) - -Client_Hello_13::Client_Hello_13(std::unique_ptr data) : Client_Hello(std::move(data)) { - const auto& exts = m_data->extensions(); - - // RFC 8446 4.1.2 - // TLS 1.3 ClientHellos are identified as having a legacy_version of - // 0x0303 and a "supported_versions" extension present with 0x0304 as the - // highest version indicated therein. - // - // Note that we already checked for "supported_versions" before entering this - // c'tor in `Client_Hello_13::parse()`. This is just to be doubly sure. - BOTAN_ASSERT_NOMSG(exts.has()); - - // RFC 8446 4.2.1 - // Servers MAY abort the handshake upon receiving a ClientHello with - // legacy_version 0x0304 or later. - if(m_data->legacy_version().is_tls_13_or_later()) { - throw TLS_Exception(Alert::DecodeError, "TLS 1.3 Client Hello has invalid legacy_version"); - } - - // RFC 8446 4.1.2 - // For every TLS 1.3 ClientHello, [the compression method] MUST contain - // exactly one byte, set to zero, [...]. If a TLS 1.3 ClientHello is - // received with any other value in this field, the server MUST abort the - // handshake with an "illegal_parameter" alert. - if(m_data->comp_methods().size() != 1 || m_data->comp_methods().front() != 0) { - throw TLS_Exception(Alert::IllegalParameter, "Client did not offer NULL compression"); - } - - // RFC 8446 4.2.9 - // A client MUST provide a "psk_key_exchange_modes" extension if it - // offers a "pre_shared_key" extension. If clients offer "pre_shared_key" - // without a "psk_key_exchange_modes" extension, servers MUST abort - // the handshake. - if(exts.has()) { - if(!exts.has()) { - throw TLS_Exception(Alert::MissingExtension, - "Client Hello offered a PSK without a psk_key_exchange_modes extension"); - } - - // RFC 8446 4.2.11 - // The "pre_shared_key" extension MUST be the last extension in the - // ClientHello [...]. Servers MUST check that it is the last extension - // and otherwise fail the handshake with an "illegal_parameter" alert. - if(exts.all().back()->type() != Extension_Code::PresharedKey) { - throw TLS_Exception(Alert::IllegalParameter, "PSK extension was not at the very end of the Client Hello"); - } - } - - // RFC 8446 9.2 - // [A TLS 1.3 ClientHello] message MUST meet the following requirements: - // - // - If not containing a "pre_shared_key" extension, it MUST contain - // both a "signature_algorithms" extension and a "supported_groups" - // extension. - // - // - If containing a "supported_groups" extension, it MUST also contain - // a "key_share" extension, and vice versa. An empty - // KeyShare.client_shares vector is permitted. - // - // Servers receiving a ClientHello which does not conform to these - // requirements MUST abort the handshake with a "missing_extension" - // alert. - if(!exts.has()) { - if(!exts.has() || !exts.has()) { - throw TLS_Exception( - Alert::MissingExtension, - "Non-PSK Client Hello did not contain supported_groups and signature_algorithms extensions"); - } - } - if(exts.has() != exts.has()) { - throw TLS_Exception(Alert::MissingExtension, - "Client Hello must either contain both key_share and supported_groups extensions or neither"); - } - - if(exts.has()) { - const auto supported_ext = exts.get(); - BOTAN_ASSERT_NONNULL(supported_ext); - const auto supports = supported_ext->groups(); - const auto offers = exts.get()->offered_groups(); - - // RFC 8446 4.2.8 - // Each KeyShareEntry value MUST correspond to a group offered in the - // "supported_groups" extension and MUST appear in the same order. - // [...] - // Clients MUST NOT offer any KeyShareEntry values for groups not - // listed in the client's "supported_groups" extension. - // - // Note: We can assume that both `offers` and `supports` are unique lists - // as this is ensured in the parsing code of the extensions. - auto found_in_supported_groups = [&supports, support_offset = -1](auto group) mutable { - const auto i = std::find(supports.begin(), supports.end(), group); - if(i == supports.end()) { - return false; - } - - const auto found_at = std::distance(supports.begin(), i); - if(found_at <= support_offset) { - return false; // The order that groups appear in "key_share" and - // "supported_groups" must be the same - } - - support_offset = static_cast(found_at); - return true; - }; - - for(const auto offered : offers) { - // RFC 8446 4.2.8 - // Servers MAY check for violations of these rules and abort the - // handshake with an "illegal_parameter" alert if one is violated. - if(!found_in_supported_groups(offered)) { - throw TLS_Exception(Alert::IllegalParameter, - "Offered key exchange groups do not align with claimed supported groups"); - } - } - } - - // TODO: Reject oid_filters extension if found (which is the only known extension that - // must not occur in the TLS 1.3 client hello. - // RFC 8446 4.2.5 - // [The oid_filters extension] MUST only be sent in the CertificateRequest message. -} - -/* -* Create a new Client Hello message -*/ -Client_Hello_13::Client_Hello_13(const Policy& policy, - Callbacks& cb, - RandomNumberGenerator& rng, - std::string_view hostname, - const std::vector& next_protocols, - std::optional& session, - std::vector psks) { - // RFC 8446 4.1.2 - // In TLS 1.3, the client indicates its version preferences in the - // "supported_versions" extension (Section 4.2.1) and the - // legacy_version field MUST be set to 0x0303, which is the version - // number for TLS 1.2. - m_data->m_legacy_version = Protocol_Version::TLS_V12; - m_data->m_random = make_hello_random(rng, cb, policy); - m_data->m_suites = policy.ciphersuite_list(Protocol_Version::TLS_V13); - - if(policy.allow_tls12()) { - // Note: DTLS 1.3 is NYI, hence dtls_12 is not checked - const auto legacy_suites = policy.ciphersuite_list(Protocol_Version::TLS_V12); - m_data->m_suites.insert(m_data->m_suites.end(), legacy_suites.cbegin(), legacy_suites.cend()); - } - - if(policy.tls_13_middlebox_compatibility_mode()) { - // RFC 8446 4.1.2 - // In compatibility mode (see Appendix D.4), this field MUST be non-empty, - // so a client not offering a pre-TLS 1.3 session MUST generate a new - // 32-byte value. - // - // Note: we won't ever offer a TLS 1.2 session. In such a case we would - // have instantiated a TLS 1.2 client in the first place. - m_data->m_session_id = Session_ID(make_hello_random(rng, cb, policy)); - } - - if(hostname_acceptable_for_sni(hostname)) { - m_data->extensions().add(new Server_Name_Indicator(hostname)); - } - - m_data->extensions().add(new Supported_Groups(policy.key_exchange_groups())); - - m_data->extensions().add(new Key_Share(policy, cb, rng)); - - m_data->extensions().add(new Supported_Versions(Protocol_Version::TLS_V13, policy)); - - m_data->extensions().add(new Signature_Algorithms(policy.acceptable_signature_schemes())); - if(auto cert_signing_prefs = policy.acceptable_certificate_signature_schemes()) { - // RFC 8446 4.2.3 - // Implementations which have the same policy in both cases MAY omit - // the "signature_algorithms_cert" extension. - m_data->extensions().add(new Signature_Algorithms_Cert(std::move(cert_signing_prefs.value()))); - } - - // TODO: Support for PSK-only mode without a key exchange. - // This should be configurable in TLS::Policy and should allow no PSK - // support at all (e.g. to disable support for session resumption). - m_data->extensions().add(new PSK_Key_Exchange_Modes({PSK_Key_Exchange_Mode::PSK_DHE_KE})); - - if(policy.support_cert_status_message()) { - m_data->extensions().add(new Certificate_Status_Request({}, {})); - } - - // We currently support "record_size_limit" for TLS 1.3 exclusively. Hence, - // when TLS 1.2 is advertised as a supported protocol, we must not offer this - // extension. - if(policy.record_size_limit().has_value() && !policy.allow_tls12()) { - m_data->extensions().add(new Record_Size_Limit(policy.record_size_limit().value())); - } - - if(!next_protocols.empty()) { - m_data->extensions().add(new Application_Layer_Protocol_Notification(next_protocols)); - } - - // RFC 7250 4.1 - // In order to indicate the support of raw public keys, clients include - // the client_certificate_type and/or the server_certificate_type - // extensions in an extended client hello message. - m_data->extensions().add(new Client_Certificate_Type(policy.accepted_client_certificate_types())); - m_data->extensions().add(new Server_Certificate_Type(policy.accepted_server_certificate_types())); - - if(policy.allow_tls12()) { - m_data->extensions().add(new Renegotiation_Extension()); - m_data->extensions().add(new Session_Ticket_Extension()); - - // EMS must always be used with TLS 1.2, regardless of the policy - m_data->extensions().add(new Extended_Master_Secret); - - if(policy.negotiate_encrypt_then_mac()) { - m_data->extensions().add(new Encrypt_then_MAC); - } - - if(m_data->extensions().has() && - !m_data->extensions().get()->ec_groups().empty()) { - m_data->extensions().add(new Supported_Point_Formats(policy.use_ecc_point_compression())); - } - } - - if(session.has_value() || !psks.empty()) { - m_data->extensions().add(new PSK(session, std::move(psks), cb)); - } - - cb.tls_modify_extensions(m_data->extensions(), Connection_Side::Client, type()); - - if(m_data->extensions().has()) { - // RFC 8446 4.2.11 - // The "pre_shared_key" extension MUST be the last extension in the - // ClientHello (this facilitates implementation [...]). - if(m_data->extensions().all().back()->type() != Extension_Code::PresharedKey) { - throw TLS_Exception(Alert::InternalError, - "Application modified extensions of Client Hello, PSK is not last anymore"); - } - calculate_psk_binders({}); - } -} - -std::variant Client_Hello_13::parse(const std::vector& buf) { - auto data = std::make_unique(buf); - const auto version = data->version(); - - if(version.is_pre_tls_13()) { - return Client_Hello_12(std::move(data)); - } else { - return Client_Hello_13(std::move(data)); - } -} - -void Client_Hello_13::retry(const Hello_Retry_Request& hrr, - const Transcript_Hash_State& transcript_hash_state, - Callbacks& cb, - RandomNumberGenerator& rng) { - BOTAN_STATE_CHECK(m_data->extensions().has()); - BOTAN_STATE_CHECK(m_data->extensions().has()); - - auto hrr_ks = hrr.extensions().get(); - const auto& supported_groups = m_data->extensions().get()->groups(); - - if(hrr.extensions().has()) { - m_data->extensions().get()->retry_offer(*hrr_ks, supported_groups, cb, rng); - } - - // RFC 8446 4.2.2 - // When sending the new ClientHello, the client MUST copy - // the contents of the extension received in the HelloRetryRequest into - // a "cookie" extension in the new ClientHello. - // - // RFC 8446 4.2.2 - // Clients MUST NOT use cookies in their initial ClientHello in subsequent - // connections. - if(hrr.extensions().has()) { - BOTAN_STATE_CHECK(!m_data->extensions().has()); - m_data->extensions().add(new Cookie(hrr.extensions().get()->get_cookie())); - } - - // Note: the consumer of the TLS implementation won't be able to distinguish - // invocations to this callback due to the first Client_Hello or the - // retried Client_Hello after receiving a Hello_Retry_Request. We assume - // that the user keeps and detects this state themselves. - cb.tls_modify_extensions(m_data->extensions(), Connection_Side::Client, type()); - - auto psk = m_data->extensions().get(); - if(psk) { - // Cipher suite should always be a known suite as this is checked upstream - const auto cipher = Ciphersuite::by_id(hrr.ciphersuite()); - BOTAN_ASSERT_NOMSG(cipher.has_value()); - - // RFC 8446 4.1.4 - // In [...] its updated ClientHello, the client SHOULD NOT offer - // any pre-shared keys associated with a hash other than that of the - // selected cipher suite. - psk->filter(cipher.value()); - - // RFC 8446 4.2.11.2 - // If the server responds with a HelloRetryRequest and the client - // then sends ClientHello2, its binder will be computed over: [...]. - calculate_psk_binders(transcript_hash_state.clone()); - } -} - -void Client_Hello_13::validate_updates(const Client_Hello_13& new_ch) { - // RFC 8446 4.1.2 - // The client will also send a ClientHello when the server has responded - // to its ClientHello with a HelloRetryRequest. In that case, the client - // MUST send the same ClientHello without modification, except as follows: - - if(m_data->session_id() != new_ch.m_data->session_id() || m_data->random() != new_ch.m_data->random() || - m_data->ciphersuites() != new_ch.m_data->ciphersuites() || - m_data->comp_methods() != new_ch.m_data->comp_methods()) { - throw TLS_Exception(Alert::IllegalParameter, "Client Hello core values changed after Hello Retry Request"); - } - - const auto oldexts = extension_types(); - const auto newexts = new_ch.extension_types(); - - // Check that extension omissions are justified - for(const auto oldext : oldexts) { - if(!newexts.contains(oldext)) { - const auto ext = extensions().get(oldext); - - // We don't make any assumptions about unimplemented extensions. - if(!ext->is_implemented()) { - continue; - } - - // RFC 8446 4.1.2 - // Removing the "early_data" extension (Section 4.2.10) if one was - // present. Early data is not permitted after a HelloRetryRequest. - if(oldext == EarlyDataIndication::static_type()) { - continue; - } - - // RFC 8446 4.1.2 - // Optionally adding, removing, or changing the length of the - // "padding" extension. - // - // TODO: implement the Padding extension - // if(oldext == Padding::static_type()) - // continue; - - throw TLS_Exception(Alert::IllegalParameter, "Extension removed in updated Client Hello"); - } - } - - // Check that extension additions are justified - for(const auto newext : newexts) { - if(!oldexts.contains(newext)) { - const auto ext = new_ch.extensions().get(newext); - - // We don't make any assumptions about unimplemented extensions. - if(!ext->is_implemented()) { - continue; - } - - // RFC 8446 4.1.2 - // Including a "cookie" extension if one was provided in the - // HelloRetryRequest. - if(newext == Cookie::static_type()) { - continue; - } - - // RFC 8446 4.1.2 - // Optionally adding, removing, or changing the length of the - // "padding" extension. - // - // TODO: implement the Padding extension - // if(newext == Padding::static_type()) - // continue; - - throw TLS_Exception(Alert::UnsupportedExtension, "Added an extension in updated Client Hello"); - } - } - - // RFC 8446 4.1.2 - // Removing the "early_data" extension (Section 4.2.10) if one was - // present. Early data is not permitted after a HelloRetryRequest. - if(new_ch.extensions().has()) { - throw TLS_Exception(Alert::IllegalParameter, "Updated Client Hello indicates early data"); - } - - // TODO: Contents of extensions are not checked for update compatibility, see: - // - // RFC 8446 4.1.2 - // If a "key_share" extension was supplied in the HelloRetryRequest, - // replacing the list of shares with a list containing a single - // KeyShareEntry from the indicated group. - // - // Updating the "pre_shared_key" extension if present by recomputing - // the "obfuscated_ticket_age" and binder values and (optionally) - // removing any PSKs which are incompatible with the server's - // indicated cipher suite. - // - // Optionally adding, removing, or changing the length of the - // "padding" extension. -} - -void Client_Hello_13::calculate_psk_binders(Transcript_Hash_State ths) { - auto psk = m_data->extensions().get(); - if(!psk || psk->empty()) { - return; - } - - // RFC 8446 4.2.11.2 - // Each entry in the binders list is computed as an HMAC over a - // transcript hash (see Section 4.4.1) containing a partial ClientHello - // [...]. - // - // Therefore we marshal the entire message prematurely to obtain the - // (truncated) transcript hash, calculate the PSK binders with it, update - // the Client Hello thus finalizing the message. Down the road, it will be - // re-marshalled with the correct binders and sent over the wire. - Handshake_Layer::prepare_message(*this, ths); - psk->calculate_binders(ths); -} - -std::optional Client_Hello_13::highest_supported_version(const Policy& policy) const { - // RFC 8446 4.2.1 - // The "supported_versions" extension is used by the client to indicate - // which versions of TLS it supports and by the server to indicate which - // version it is using. The extension contains a list of supported - // versions in preference order, with the most preferred version first. - const auto supvers = m_data->extensions().get(); - BOTAN_ASSERT_NONNULL(supvers); - - std::optional result; - - for(const auto& v : supvers->versions()) { - // RFC 8446 4.2.1 - // Servers MUST only select a version of TLS present in that extension - // and MUST ignore any unknown versions that are present in that - // extension. - if(!v.known_version() || !policy.acceptable_protocol_version(v)) { - continue; - } - - result = (result.has_value()) ? std::optional(std::max(result.value(), v)) : std::optional(v); - } - - return result; -} +Client_Hello_12_Shim::Client_Hello_12_Shim(std::unique_ptr data) : + Client_Hello(std::move(data)) {} -#endif // BOTAN_HAS_TLS_13 +Client_Hello_12_Shim::Client_Hello_12_Shim(const std::vector& buf) : + Client_Hello_12_Shim(std::make_unique(buf)) {} } // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/msg_finished.cpp botan3-3.12.0+dfsg/src/lib/tls/msg_finished.cpp --- botan3-3.7.1+dfsg/src/lib/tls/msg_finished.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/msg_finished.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,88 +0,0 @@ -/* -* Finished Message -* (C) 2004-2006,2012 Jack Lloyd -* 2021 Elektrobit Automotive GmbH -* 2022 René Meusel, Hannes Rantzsch - neXenio GmbH -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include - -#include -#include -#include -#include - -#if defined(BOTAN_HAS_TLS_13) - #include -#endif - -namespace Botan::TLS { - -namespace { - -/* -* Compute the verify_data for TLS 1.2 -*/ -std::vector finished_compute_verify_12(const Handshake_State& state, Connection_Side side) { - const uint8_t TLS_CLIENT_LABEL[] = { - 0x63, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x20, 0x66, 0x69, 0x6E, 0x69, 0x73, 0x68, 0x65, 0x64}; - - const uint8_t TLS_SERVER_LABEL[] = { - 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x66, 0x69, 0x6E, 0x69, 0x73, 0x68, 0x65, 0x64}; - - auto prf = state.protocol_specific_prf(); - - std::vector input; - std::vector label; - label += (side == Connection_Side::Client) ? std::make_pair(TLS_CLIENT_LABEL, sizeof(TLS_CLIENT_LABEL)) - : std::make_pair(TLS_SERVER_LABEL, sizeof(TLS_SERVER_LABEL)); - - input += state.hash().final(state.ciphersuite().prf_algo()); - - return unlock(prf->derive_key(12, state.session_keys().master_secret(), input, label)); -} - -} // namespace - -std::vector Finished::serialize() const { - return m_verification_data; -} - -Finished::Finished(const std::vector& buf) : m_verification_data(buf) {} - -std::vector Finished::verify_data() const { - return m_verification_data; -} - -Finished_12::Finished_12(Handshake_IO& io, Handshake_State& state, Connection_Side side) { - m_verification_data = finished_compute_verify_12(state, side); - state.hash().update(io.send(*this)); -} - -bool Finished_12::verify(const Handshake_State& state, Connection_Side side) const { - std::vector computed_verify = finished_compute_verify_12(state, side); - -#if defined(BOTAN_UNSAFE_FUZZER_MODE) - return true; -#else - // first check the size: - if(m_verification_data.size() != computed_verify.size()) { - return false; - } - - return CT::is_equal(m_verification_data.data(), computed_verify.data(), computed_verify.size()).as_bool(); -#endif -} - -#if defined(BOTAN_HAS_TLS_13) -Finished_13::Finished_13(Cipher_State* cipher_state, const Transcript_Hash& transcript_hash) { - m_verification_data = cipher_state->finished_mac(transcript_hash); -} - -bool Finished_13::verify(Cipher_State* cipher_state, const Transcript_Hash& transcript_hash) const { - return cipher_state->verify_peer_finished_mac(transcript_hash, m_verification_data); -} -#endif -} // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/msg_server_hello.cpp botan3-3.12.0+dfsg/src/lib/tls/msg_server_hello.cpp --- botan3-3.7.1+dfsg/src/lib/tls/msg_server_hello.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/msg_server_hello.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -5,47 +5,26 @@ * 2017 Harry Reimann, Rohde & Schwarz Cybersecurity * 2021 Elektrobit Automotive GmbH * 2022 René Meusel, Hannes Rantzsch - neXenio GmbH +* 2026 René Meusel - Rohde & Schwarz Cybersecurity GmbH * * Botan is released under the Simplified BSD License (see license.txt) */ #include +#include -#include -#include +#include #include -#include -#include +#include #include -#include -#include -#include #include -#include - -#include namespace Botan::TLS { -namespace { - -const uint64_t DOWNGRADE_TLS11 = 0x444F574E47524400; -const uint64_t DOWNGRADE_TLS12 = 0x444F574E47524401; - -// SHA-256("HelloRetryRequest") -const std::vector HELLO_RETRY_REQUEST_MARKER = { - 0xCF, 0x21, 0xAD, 0x74, 0xE5, 0x9A, 0x61, 0x11, 0xBE, 0x1D, 0x8C, 0x02, 0x1E, 0x65, 0xB8, 0x91, - 0xC2, 0xA2, 0x11, 0x16, 0x7A, 0xBB, 0x8C, 0x5E, 0x07, 0x9E, 0x09, 0xE2, 0xC8, 0xA8, 0x33, 0x9C}; - -bool random_signals_hello_retry_request(const std::vector& random) { - return CT::is_equal(random.data(), HELLO_RETRY_REQUEST_MARKER.data(), HELLO_RETRY_REQUEST_MARKER.size()).as_bool(); -} - std::vector make_server_hello_random(RandomNumberGenerator& rng, Protocol_Version offered_version, Callbacks& cb, const Policy& policy) { - BOTAN_UNUSED(offered_version); auto random = make_hello_random(rng, cb, policy); // RFC 8446 4.1.3 @@ -59,111 +38,58 @@ if(offered_version.is_pre_tls_13() && policy.allow_tls13()) { constexpr size_t downgrade_signal_length = sizeof(DOWNGRADE_TLS12); BOTAN_ASSERT_NOMSG(random.size() >= downgrade_signal_length); - auto lastbytes = random.data() + random.size() - downgrade_signal_length; + const auto lastbytes = std::span{random}.last(downgrade_signal_length); store_be(DOWNGRADE_TLS12, lastbytes); } return random; } -} // namespace +Server_Hello_Internal::Server_Hello_Internal(const std::vector& buf) { + if(buf.size() < 38) { + throw Decoding_Error("Server_Hello: Packet corrupted"); + } -/** -* Version-agnostic internal server hello data container that allows -* parsing Server_Hello messages without prior knowledge of the contained -* protocol version. -*/ -class Server_Hello_Internal { - public: - /** - * Deserialize a Server Hello message - */ - Server_Hello_Internal(const std::vector& buf) { - if(buf.size() < 38) { - throw Decoding_Error("Server_Hello: Packet corrupted"); - } - - TLS_Data_Reader reader("ServerHello", buf); - - const uint8_t major_version = reader.get_byte(); - const uint8_t minor_version = reader.get_byte(); - - m_legacy_version = Protocol_Version(major_version, minor_version); - - // RFC 8446 4.1.3 - // Upon receiving a message with type server_hello, implementations MUST - // first examine the Random value and, if it matches this value, process - // it as described in Section 4.1.4 [Hello Retry Request]). - m_random = reader.get_fixed(32); - m_is_hello_retry_request = random_signals_hello_retry_request(m_random); - - m_session_id = Session_ID(reader.get_range(1, 0, 32)); - m_ciphersuite = reader.get_uint16_t(); - m_comp_method = reader.get_byte(); - - // Note that this code path might parse a TLS 1.2 (or older) server hello message that - // is nevertheless marked as being a 'hello retry request' (potentially maliciously). - // Extension parsing will however not be affected by the associated flag. - // Only after parsing the extensions will the upstream code be able to decide - // whether we're dealing with TLS 1.3 or older. - m_extensions.deserialize( - reader, - Connection_Side::Server, - m_is_hello_retry_request ? Handshake_Type::HelloRetryRequest : Handshake_Type::ServerHello); - } - - Server_Hello_Internal(Protocol_Version lv, - Session_ID sid, - std::vector r, - const uint16_t cs, - const uint8_t cm, - bool is_hrr = false) : - m_legacy_version(lv), - m_session_id(std::move(sid)), - m_random(std::move(r)), - m_is_hello_retry_request(is_hrr), - m_ciphersuite(cs), - m_comp_method(cm) {} - - Protocol_Version version() const { - // RFC 8446 4.2.1 - // A server which negotiates a version of TLS prior to TLS 1.3 MUST set - // ServerHello.version and MUST NOT send the "supported_versions" - // extension. A server which negotiates TLS 1.3 MUST respond by sending - // a "supported_versions" extension containing the selected version - // value (0x0304). - // - // Note: Here we just take a message parsing decision, further validation of - // the extension's contents is done later. - return (extensions().has()) ? Protocol_Version::TLS_V13 : m_legacy_version; - } - - Protocol_Version legacy_version() const { return m_legacy_version; } - - const Session_ID& session_id() const { return m_session_id; } - - const std::vector& random() const { return m_random; } - - uint16_t ciphersuite() const { return m_ciphersuite; } - - uint8_t comp_method() const { return m_comp_method; } - - bool is_hello_retry_request() const { return m_is_hello_retry_request; } - - const Extensions& extensions() const { return m_extensions; } - - Extensions& extensions() { return m_extensions; } - - private: - Protocol_Version m_legacy_version; - Session_ID m_session_id; - std::vector m_random; - bool m_is_hello_retry_request; - uint16_t m_ciphersuite; - uint8_t m_comp_method; + TLS_Data_Reader reader("ServerHello", buf); + + const uint8_t major_version = reader.get_byte(); + const uint8_t minor_version = reader.get_byte(); + + m_legacy_version = Protocol_Version(major_version, minor_version); + + // RFC 8446 4.1.3 + // Upon receiving a message with type server_hello, implementations MUST + // first examine the Random value and, if it matches this value, process + // it as described in Section 4.1.4 [Hello Retry Request]). + m_random = reader.get_fixed(32); + m_is_hello_retry_request = CT::is_equal(m_random, HELLO_RETRY_REQUEST_MARKER).as_bool(); - Extensions m_extensions; -}; + m_session_id = Session_ID(reader.get_range(1, 0, 32)); + m_ciphersuite = reader.get_uint16_t(); + m_comp_method = reader.get_byte(); + + // Note that this code path might parse a TLS 1.2 (or older) server hello message that + // is nevertheless marked as being a 'hello retry request' (potentially maliciously). + // Extension parsing will however not be affected by the associated flag. + // Only after parsing the extensions will the upstream code be able to decide + // whether we're dealing with TLS 1.3 or older. + m_extensions.deserialize(reader, + Connection_Side::Server, + m_is_hello_retry_request ? Handshake_Type::HelloRetryRequest : Handshake_Type::ServerHello); +} + +Protocol_Version Server_Hello_Internal::version() const { + // RFC 8446 4.2.1 + // A server which negotiates a version of TLS prior to TLS 1.3 MUST set + // ServerHello.version and MUST NOT send the "supported_versions" + // extension. A server which negotiates TLS 1.3 MUST respond by sending + // a "supported_versions" extension containing the selected version + // value (0x0304). + // + // Note: Here we just take a message parsing decision, further validation of + // the extension's contents is done later. + return (extensions().has()) ? Protocol_Version::TLS_V13 : m_legacy_version; +} Server_Hello::Server_Hello(std::unique_ptr data) : m_data(std::move(data)) {} @@ -227,195 +153,21 @@ return m_data->extensions(); } -// New session case -Server_Hello_12::Server_Hello_12(Handshake_IO& io, - Handshake_Hash& hash, - const Policy& policy, - Callbacks& cb, - RandomNumberGenerator& rng, - const std::vector& reneg_info, - const Client_Hello_12& client_hello, - const Server_Hello_12::Settings& server_settings, - std::string_view next_protocol) : - Server_Hello(std::make_unique( - server_settings.protocol_version(), - server_settings.session_id(), - make_server_hello_random(rng, server_settings.protocol_version(), cb, policy), - server_settings.ciphersuite(), - uint8_t(0))) { - if(client_hello.supports_extended_master_secret()) { - m_data->extensions().add(new Extended_Master_Secret); - } - - // Sending the extension back does not commit us to sending a stapled response - if(client_hello.supports_cert_status_message() && policy.support_cert_status_message()) { - m_data->extensions().add(new Certificate_Status_Request); - } - - if(!next_protocol.empty() && client_hello.supports_alpn()) { - m_data->extensions().add(new Application_Layer_Protocol_Notification(next_protocol)); - } - - const auto c = Ciphersuite::by_id(m_data->ciphersuite()); - - if(c && c->cbc_ciphersuite() && client_hello.supports_encrypt_then_mac() && policy.negotiate_encrypt_then_mac()) { - m_data->extensions().add(new Encrypt_then_MAC); - } - - if(c && c->ecc_ciphersuite() && client_hello.extension_types().contains(Extension_Code::EcPointFormats)) { - m_data->extensions().add(new Supported_Point_Formats(policy.use_ecc_point_compression())); - } - - if(client_hello.secure_renegotiation()) { - m_data->extensions().add(new Renegotiation_Extension(reneg_info)); - } - - if(client_hello.supports_session_ticket() && server_settings.offer_session_ticket()) { - m_data->extensions().add(new Session_Ticket_Extension()); - } - - if(m_data->legacy_version().is_datagram_protocol()) { - const std::vector server_srtp = policy.srtp_profiles(); - const std::vector client_srtp = client_hello.srtp_profiles(); - - if(!server_srtp.empty() && !client_srtp.empty()) { - uint16_t shared = 0; - // always using server preferences for now - for(auto s_srtp : server_srtp) { - for(auto c_srtp : client_srtp) { - if(shared == 0 && s_srtp == c_srtp) { - shared = s_srtp; - } - } - } - - if(shared) { - m_data->extensions().add(new SRTP_Protection_Profiles(shared)); - } - } - } - - cb.tls_modify_extensions(m_data->extensions(), Connection_Side::Server, type()); - - hash.update(io.send(*this)); -} - -// Resuming -Server_Hello_12::Server_Hello_12(Handshake_IO& io, - Handshake_Hash& hash, - const Policy& policy, - Callbacks& cb, - RandomNumberGenerator& rng, - const std::vector& reneg_info, - const Client_Hello_12& client_hello, - const Session& resumed_session, - bool offer_session_ticket, - std::string_view next_protocol) : - Server_Hello(std::make_unique(resumed_session.version(), - client_hello.session_id(), - make_hello_random(rng, cb, policy), - resumed_session.ciphersuite_code(), - uint8_t(0))) { - if(client_hello.supports_extended_master_secret()) { - m_data->extensions().add(new Extended_Master_Secret); - } - - if(!next_protocol.empty() && client_hello.supports_alpn()) { - m_data->extensions().add(new Application_Layer_Protocol_Notification(next_protocol)); - } - - if(client_hello.supports_encrypt_then_mac() && policy.negotiate_encrypt_then_mac()) { - Ciphersuite c = resumed_session.ciphersuite(); - if(c.cbc_ciphersuite()) { - m_data->extensions().add(new Encrypt_then_MAC); - } - } - - if(resumed_session.ciphersuite().ecc_ciphersuite() && - client_hello.extension_types().contains(Extension_Code::EcPointFormats)) { - m_data->extensions().add(new Supported_Point_Formats(policy.use_ecc_point_compression())); - } - - if(client_hello.secure_renegotiation()) { - m_data->extensions().add(new Renegotiation_Extension(reneg_info)); - } - - if(client_hello.supports_session_ticket() && offer_session_ticket) { - m_data->extensions().add(new Session_Ticket_Extension()); - } - - cb.tls_modify_extensions(m_data->extensions(), Connection_Side::Server, type()); - - hash.update(io.send(*this)); -} - -Server_Hello_12::Server_Hello_12(const std::vector& buf) : - Server_Hello_12(std::make_unique(buf)) {} +Server_Hello_12_Shim::Server_Hello_12_Shim(const std::vector& buf) : + Server_Hello_12_Shim(std::make_unique(buf)) {} -Server_Hello_12::Server_Hello_12(std::unique_ptr data) : Server_Hello(std::move(data)) { +Server_Hello_12_Shim::Server_Hello_12_Shim(std::unique_ptr data) : + Server_Hello(std::move(data)) { if(!m_data->version().is_pre_tls_13()) { throw TLS_Exception(Alert::ProtocolVersion, "Expected server hello of (D)TLS 1.2 or lower"); } } -Protocol_Version Server_Hello_12::selected_version() const { +Protocol_Version Server_Hello_12_Shim::selected_version() const { return legacy_version(); } -bool Server_Hello_12::secure_renegotiation() const { - return m_data->extensions().has(); -} - -std::vector Server_Hello_12::renegotiation_info() const { - if(Renegotiation_Extension* reneg = m_data->extensions().get()) { - return reneg->renegotiation_info(); - } - return std::vector(); -} - -bool Server_Hello_12::supports_extended_master_secret() const { - return m_data->extensions().has(); -} - -bool Server_Hello_12::supports_encrypt_then_mac() const { - return m_data->extensions().has(); -} - -bool Server_Hello_12::supports_certificate_status_message() const { - return m_data->extensions().has(); -} - -bool Server_Hello_12::supports_session_ticket() const { - return m_data->extensions().has(); -} - -uint16_t Server_Hello_12::srtp_profile() const { - if(auto srtp = m_data->extensions().get()) { - auto prof = srtp->profiles(); - if(prof.size() != 1 || prof[0] == 0) { - throw Decoding_Error("Server sent malformed DTLS-SRTP extension"); - } - return prof[0]; - } - - return 0; -} - -std::string Server_Hello_12::next_protocol() const { - if(auto alpn = m_data->extensions().get()) { - return alpn->single_protocol(); - } - return ""; -} - -bool Server_Hello_12::prefers_compressed_ec_points() const { - if(auto ecc_formats = m_data->extensions().get()) { - return ecc_formats->prefers_compressed(); - } - return false; -} - -std::optional Server_Hello_12::random_signals_downgrade() const { +std::optional Server_Hello_12_Shim::random_signals_downgrade() const { const uint64_t last8 = load_be(m_data->random().data(), 3); if(last8 == DOWNGRADE_TLS11) { return Protocol_Version::TLS_V11; @@ -427,404 +179,4 @@ return std::nullopt; } -/* -* Create a new Server Hello Done message -*/ -Server_Hello_Done::Server_Hello_Done(Handshake_IO& io, Handshake_Hash& hash) { - hash.update(io.send(*this)); -} - -/* -* Deserialize a Server Hello Done message -*/ -Server_Hello_Done::Server_Hello_Done(const std::vector& buf) { - if(!buf.empty()) { - throw Decoding_Error("Server_Hello_Done: Must be empty, and is not"); - } -} - -/* -* Serialize a Server Hello Done message -*/ -std::vector Server_Hello_Done::serialize() const { - return std::vector(); -} - -#if defined(BOTAN_HAS_TLS_13) - -const Server_Hello_13::Server_Hello_Tag Server_Hello_13::as_server_hello; -const Server_Hello_13::Hello_Retry_Request_Tag Server_Hello_13::as_hello_retry_request; -const Server_Hello_13::Hello_Retry_Request_Creation_Tag Server_Hello_13::as_new_hello_retry_request; - -std::variant Server_Hello_13::create(const Client_Hello_13& ch, - bool hello_retry_request_allowed, - Session_Manager& session_mgr, - Credentials_Manager& credentials_mgr, - RandomNumberGenerator& rng, - const Policy& policy, - Callbacks& cb) { - const auto& exts = ch.extensions(); - - // RFC 8446 4.2.9 - // [With PSK with (EC)DHE key establishment], the client and server MUST - // supply "key_share" values [...]. - // - // Note: We currently do not support PSK without (EC)DHE, hence, we can - // assume that those extensions are available. - BOTAN_ASSERT_NOMSG(exts.has() && exts.has()); - const auto& supported_by_client = exts.get()->groups(); - const auto& offered_by_client = exts.get()->offered_groups(); - const auto selected_group = policy.choose_key_exchange_group(supported_by_client, offered_by_client); - - // RFC 8446 4.1.1 - // If there is no overlap between the received "supported_groups" and the - // groups supported by the server, then the server MUST abort the - // handshake with a "handshake_failure" or an "insufficient_security" alert. - if(selected_group == Named_Group::NONE) { - throw TLS_Exception(Alert::HandshakeFailure, "Client did not offer any acceptable group"); - } - - // RFC 8446 4.2.8: - // Servers MUST NOT send a KeyShareEntry for any group not indicated in the - // client's "supported_groups" extension [...] - if(!value_exists(supported_by_client, selected_group)) { - throw TLS_Exception(Alert::InternalError, "Application selected a group that is not supported by the client"); - } - - // RFC 8446 4.1.4 - // The server will send this message in response to a ClientHello - // message if it is able to find an acceptable set of parameters but the - // ClientHello does not contain sufficient information to proceed with - // the handshake. - // - // In this case, the Client Hello did not contain a key share offer for - // the group selected by the application. - if(!value_exists(offered_by_client, selected_group)) { - // RFC 8446 4.1.4 - // If a client receives a second HelloRetryRequest in the same - // connection (i.e., where the ClientHello was itself in response to a - // HelloRetryRequest), it MUST abort the handshake with an - // "unexpected_message" alert. - BOTAN_STATE_CHECK(hello_retry_request_allowed); - return Hello_Retry_Request(ch, selected_group, policy, cb); - } else { - return Server_Hello_13(ch, selected_group, session_mgr, credentials_mgr, rng, cb, policy); - } -} - -std::variant Server_Hello_13::parse( - const std::vector& buf) { - auto data = std::make_unique(buf); - const auto version = data->version(); - - // server hello that appears to be pre-TLS 1.3, takes precedence over... - if(version.is_pre_tls_13()) { - return Server_Hello_12(std::move(data)); - } - - // ... the TLS 1.3 "special case" aka. Hello_Retry_Request - if(version == Protocol_Version::TLS_V13) { - if(data->is_hello_retry_request()) { - return Hello_Retry_Request(std::move(data)); - } - - return Server_Hello_13(std::move(data)); - } - - throw TLS_Exception(Alert::ProtocolVersion, "unexpected server hello version: " + version.to_string()); -} - -/** - * Validation that applies to both Server Hello and Hello Retry Request - */ -void Server_Hello_13::basic_validation() const { - BOTAN_ASSERT_NOMSG(m_data->version() == Protocol_Version::TLS_V13); - - // Note: checks that cannot be performed without contextual information - // are done in the specific TLS client implementation. - // Note: The Supported_Version extension makes sure internally that - // exactly one entry is provided. - - // Note: Hello Retry Request basic validation is equivalent with the - // basic validations required for Server Hello - // - // RFC 8446 4.1.4 - // Upon receipt of a HelloRetryRequest, the client MUST check the - // legacy_version, [...], and legacy_compression_method as specified in - // Section 4.1.3 and then process the extensions, starting with determining - // the version using "supported_versions". - - // RFC 8446 4.1.3 - // In TLS 1.3, [...] the legacy_version field MUST be set to 0x0303 - if(legacy_version() != Protocol_Version::TLS_V12) { - throw TLS_Exception(Alert::ProtocolVersion, - "legacy_version '" + legacy_version().to_string() + "' is not allowed"); - } - - // RFC 8446 4.1.3 - // legacy_compression_method: A single byte which MUST have the value 0. - if(compression_method() != 0x00) { - throw TLS_Exception(Alert::DecodeError, "compression is not supported in TLS 1.3"); - } - - // RFC 8446 4.1.3 - // All TLS 1.3 ServerHello messages MUST contain the "supported_versions" extension. - if(!extensions().has()) { - throw TLS_Exception(Alert::MissingExtension, "server hello did not contain 'supported version' extension"); - } - - // RFC 8446 4.2.1 - // A server which negotiates TLS 1.3 MUST respond by sending - // a "supported_versions" extension containing the selected version - // value (0x0304). - if(selected_version() != Protocol_Version::TLS_V13) { - throw TLS_Exception(Alert::IllegalParameter, "TLS 1.3 Server Hello selected a different version"); - } -} - -Server_Hello_13::Server_Hello_13(std::unique_ptr data, Server_Hello_13::Server_Hello_Tag) : - Server_Hello(std::move(data)) { - BOTAN_ASSERT_NOMSG(!m_data->is_hello_retry_request()); - basic_validation(); - - const auto& exts = extensions(); - - // RFC 8446 4.1.3 - // The ServerHello MUST only include extensions which are required to - // establish the cryptographic context and negotiate the protocol version. - // [...] - // Other extensions (see Section 4.2) are sent separately in the - // EncryptedExtensions message. - // - // Note that further validation dependent on the client hello is done in the - // TLS client implementation. - const std::set allowed = { - Extension_Code::KeyShare, - Extension_Code::SupportedVersions, - Extension_Code::PresharedKey, - }; - - // As the ServerHello shall only contain essential extensions, we don't give - // any slack for extensions not implemented by Botan here. - if(exts.contains_other_than(allowed)) { - throw TLS_Exception(Alert::UnsupportedExtension, "Server Hello contained an extension that is not allowed"); - } - - // RFC 8446 4.1.3 - // Current ServerHello messages additionally contain - // either the "pre_shared_key" extension or the "key_share" - // extension, or both [...]. - if(!exts.has() && !exts.has()) { - throw TLS_Exception(Alert::MissingExtension, "server hello must contain key exchange information"); - } -} - -Server_Hello_13::Server_Hello_13(std::unique_ptr data, - Server_Hello_13::Hello_Retry_Request_Tag) : - Server_Hello(std::move(data)) { - BOTAN_ASSERT_NOMSG(m_data->is_hello_retry_request()); - basic_validation(); - - const auto& exts = extensions(); - - // RFC 8446 4.1.4 - // The HelloRetryRequest extensions defined in this specification are: - // - supported_versions (see Section 4.2.1) - // - cookie (see Section 4.2.2) - // - key_share (see Section 4.2.8) - const std::set allowed = { - Extension_Code::Cookie, - Extension_Code::SupportedVersions, - Extension_Code::KeyShare, - }; - - // As the Hello Retry Request shall only contain essential extensions, we - // don't give any slack for extensions not implemented by Botan here. - if(exts.contains_other_than(allowed)) { - throw TLS_Exception(Alert::UnsupportedExtension, - "Hello Retry Request contained an extension that is not allowed"); - } - - // RFC 8446 4.1.4 - // Clients MUST abort the handshake with an "illegal_parameter" alert if - // the HelloRetryRequest would not result in any change in the ClientHello. - if(!exts.has() && !exts.has()) { - throw TLS_Exception(Alert::IllegalParameter, "Hello Retry Request does not request any changes to Client Hello"); - } -} - -Server_Hello_13::Server_Hello_13(std::unique_ptr data, Hello_Retry_Request_Creation_Tag) : - Server_Hello(std::move(data)) {} - -namespace { - -uint16_t choose_ciphersuite(const Client_Hello_13& ch, const Policy& policy) { - auto pref_list = ch.ciphersuites(); - // TODO: DTLS might need to make this version dynamic - auto other_list = policy.ciphersuite_list(Protocol_Version::TLS_V13); - - if(policy.server_uses_own_ciphersuite_preferences()) { - std::swap(pref_list, other_list); - } - - for(auto suite_id : pref_list) { - // TODO: take potentially available PSKs into account to select a - // compatible ciphersuite. - // - // Assuming the client sent one or more PSKs, we would first need to find - // the hash functions they are associated to. For session tickets, that - // would mean decrypting the ticket and comparing the cipher suite used in - // those tickets. For (currently not yet supported) pre-assigned PSKs, the - // hash function needs to be specified along with them. - // - // Then we could refine the ciphersuite selection using the required hash - // function for the PSK(s) we are wishing to use down the road. - // - // For now, we just negotiate the cipher suite blindly and hope for the - // best. As long as PSKs are used for session resumption only, this has a - // high chance of success. Previous handshakes with this client have very - // likely selected the same ciphersuite anyway. - // - // See also RFC 8446 4.2.11 - // When session resumption is the primary use case of PSKs, the most - // straightforward way to implement the PSK/cipher suite matching - // requirements is to negotiate the cipher suite first [...]. - if(value_exists(other_list, suite_id)) { - return suite_id; - } - } - - // RFC 8446 4.1.1 - // If the server is unable to negotiate a supported set of parameters - // [...], it MUST abort the handshake with either a "handshake_failure" - // or "insufficient_security" fatal alert [...]. - throw TLS_Exception(Alert::HandshakeFailure, "Can't agree on a ciphersuite with client"); -} -} // namespace - -Server_Hello_13::Server_Hello_13(const Client_Hello_13& ch, - std::optional key_exchange_group, - Session_Manager& session_mgr, - Credentials_Manager& credentials_mgr, - RandomNumberGenerator& rng, - Callbacks& cb, - const Policy& policy) : - Server_Hello(std::make_unique( - Protocol_Version::TLS_V12, - ch.session_id(), - make_server_hello_random(rng, Protocol_Version::TLS_V13, cb, policy), - choose_ciphersuite(ch, policy), - uint8_t(0) /* compression method */ - )) { - // RFC 8446 4.2.1 - // A server which negotiates TLS 1.3 MUST respond by sending a - // "supported_versions" extension containing the selected version - // value (0x0304). It MUST set the ServerHello.legacy_version field to - // 0x0303 (TLS 1.2). - // - // Note that the legacy version (TLS 1.2) is set in this constructor's - // initializer list, accordingly. - m_data->extensions().add(new Supported_Versions(Protocol_Version::TLS_V13)); - - if(key_exchange_group.has_value()) { - BOTAN_ASSERT_NOMSG(ch.extensions().has()); - m_data->extensions().add(Key_Share::create_as_encapsulation( - key_exchange_group.value(), *ch.extensions().get(), policy, cb, rng)); - } - - auto& ch_exts = ch.extensions(); - - if(ch_exts.has()) { - const auto cs = Ciphersuite::by_id(m_data->ciphersuite()); - BOTAN_ASSERT_NOMSG(cs); - - // RFC 8446 4.2.9 - // A client MUST provide a "psk_key_exchange_modes" extension if it - // offers a "pre_shared_key" extension. - // - // Note: Client_Hello_13 constructor already performed a graceful check. - const auto psk_modes = ch_exts.get(); - BOTAN_ASSERT_NONNULL(psk_modes); - - // TODO: also support PSK_Key_Exchange_Mode::PSK_KE - // (PSK-based handshake without an additional ephemeral key exchange) - if(value_exists(psk_modes->modes(), PSK_Key_Exchange_Mode::PSK_DHE_KE)) { - if(auto server_psk = ch_exts.get()->select_offered_psk( - ch.sni_hostname(), cs.value(), session_mgr, credentials_mgr, cb, policy)) { - // RFC 8446 4.2.11 - // In order to accept PSK key establishment, the server sends a - // "pre_shared_key" extension indicating the selected identity. - m_data->extensions().add(std::move(server_psk)); - } - } - } - - cb.tls_modify_extensions(m_data->extensions(), Connection_Side::Server, type()); -} - -std::optional Server_Hello_13::random_signals_downgrade() const { - const uint64_t last8 = load_be(m_data->random().data(), 3); - if(last8 == DOWNGRADE_TLS11) { - return Protocol_Version::TLS_V11; - } - if(last8 == DOWNGRADE_TLS12) { - return Protocol_Version::TLS_V12; - } - - return std::nullopt; -} - -Protocol_Version Server_Hello_13::selected_version() const { - const auto versions_ext = m_data->extensions().get(); - BOTAN_ASSERT_NOMSG(versions_ext); - const auto& versions = versions_ext->versions(); - BOTAN_ASSERT_NOMSG(versions.size() == 1); - return versions.front(); -} - -Hello_Retry_Request::Hello_Retry_Request(std::unique_ptr data) : - Server_Hello_13(std::move(data), Server_Hello_13::as_hello_retry_request) {} - -Hello_Retry_Request::Hello_Retry_Request(const Client_Hello_13& ch, - Named_Group selected_group, - const Policy& policy, - Callbacks& cb) : - Server_Hello_13(std::make_unique(Protocol_Version::TLS_V12 /* legacy_version */, - ch.session_id(), - HELLO_RETRY_REQUEST_MARKER, - choose_ciphersuite(ch, policy), - uint8_t(0) /* compression method */, - true /* is Hello Retry Request */ - ), - as_new_hello_retry_request) { - // RFC 8446 4.1.4 - // As with the ServerHello, a HelloRetryRequest MUST NOT contain any - // extensions that were not first offered by the client in its - // ClientHello, with the exception of optionally the "cookie" [...] - // extension. - BOTAN_STATE_CHECK(ch.extensions().has()); - BOTAN_STATE_CHECK(ch.extensions().has()); - - BOTAN_STATE_CHECK(!value_exists(ch.extensions().get()->offered_groups(), selected_group)); - - // RFC 8446 4.1.4 - // The server's extensions MUST contain "supported_versions". - // - // RFC 8446 4.2.1 - // A server which negotiates TLS 1.3 MUST respond by sending a - // "supported_versions" extension containing the selected version - // value (0x0304). It MUST set the ServerHello.legacy_version field to - // 0x0303 (TLS 1.2). - // - // Note that the legacy version (TLS 1.2) is set in this constructor's - // initializer list, accordingly. - m_data->extensions().add(new Supported_Versions(Protocol_Version::TLS_V13)); - - m_data->extensions().add(new Key_Share(selected_group)); - - cb.tls_modify_extensions(m_data->extensions(), Connection_Side::Server, type()); -} - -#endif // BOTAN_HAS_TLS_13 - } // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/msg_session_ticket.cpp botan3-3.12.0+dfsg/src/lib/tls/msg_session_ticket.cpp --- botan3-3.7.1+dfsg/src/lib/tls/msg_session_ticket.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/msg_session_ticket.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,140 +0,0 @@ -/* -* Session Tickets -* (C) 2012 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -namespace Botan::TLS { - -New_Session_Ticket_12::New_Session_Ticket_12(Handshake_IO& io, - Handshake_Hash& hash, - Session_Ticket ticket, - std::chrono::seconds lifetime) : - m_ticket_lifetime_hint(lifetime), m_ticket(std::move(ticket)) { - hash.update(io.send(*this)); -} - -New_Session_Ticket_12::New_Session_Ticket_12(Handshake_IO& io, Handshake_Hash& hash) { - hash.update(io.send(*this)); -} - -New_Session_Ticket_12::New_Session_Ticket_12(const std::vector& buf) { - if(buf.size() < 6) { - throw Decoding_Error("Session ticket message too short to be valid"); - } - - TLS_Data_Reader reader("SessionTicket", buf); - - m_ticket_lifetime_hint = std::chrono::seconds(reader.get_uint32_t()); - m_ticket = Session_Ticket(reader.get_range(2, 0, 65535)); - reader.assert_done(); -} - -namespace { - -template -void store_lifetime(std::span sink, std::chrono::seconds lifetime) { - BOTAN_ARG_CHECK(lifetime.count() >= 0 && lifetime.count() <= std::numeric_limits::max(), - "Ticket lifetime is out of range"); - store_be(static_cast(lifetime.count()), sink.data()); -} - -} // namespace - -std::vector New_Session_Ticket_12::serialize() const { - std::vector buf(4); - store_be(static_cast(m_ticket_lifetime_hint.count()), buf.data()); - append_tls_length_value(buf, m_ticket.get(), 2); - return buf; -} - -#if defined(BOTAN_HAS_TLS_13) - -New_Session_Ticket_13::New_Session_Ticket_13(Ticket_Nonce nonce, - const Session& session, - const Session_Handle& handle, - Callbacks& callbacks) : - m_ticket_lifetime_hint(session.lifetime_hint()), - m_ticket_age_add(session.session_age_add()), - m_ticket_nonce(std::move(nonce)), - m_handle(handle.opaque_handle()) { - callbacks.tls_modify_extensions(m_extensions, Connection_Side::Server, type()); -} - -New_Session_Ticket_13::New_Session_Ticket_13(const std::vector& buf, Connection_Side from) { - TLS_Data_Reader reader("New_Session_Ticket_13", buf); - - m_ticket_lifetime_hint = std::chrono::seconds(reader.get_uint32_t()); - - // RFC 8446 4.6.1 - // Servers MUST NOT use any value [of ticket_lifetime] greater than 604800 - // seconds (7 days). - if(m_ticket_lifetime_hint > std::chrono::days(7)) { - throw TLS_Exception(Alert::IllegalParameter, "Received a session ticket with lifetime longer than one week."); - } - - m_ticket_age_add = reader.get_uint32_t(); - m_ticket_nonce = Ticket_Nonce(reader.get_tls_length_value(1)); - m_handle = Opaque_Session_Handle(reader.get_tls_length_value(2)); - - m_extensions.deserialize(reader, from, type()); - - // RFC 8446 4.6.1 - // The sole extension currently defined for NewSessionTicket is - // "early_data", indicating that the ticket may be used to send 0-RTT - // data [...]. Clients MUST ignore unrecognized extensions. - if(m_extensions.contains_implemented_extensions_other_than({Extension_Code::EarlyData})) { - throw TLS_Exception(Alert::IllegalParameter, "NewSessionTicket message contained unexpected extension"); - } - - reader.assert_done(); -} - -std::optional New_Session_Ticket_13::early_data_byte_limit() const { - if(!m_extensions.has()) { - return std::nullopt; - } - - const EarlyDataIndication* ext = m_extensions.get(); - BOTAN_ASSERT_NOMSG(ext->max_early_data_size().has_value()); - return ext->max_early_data_size(); -} - -std::vector New_Session_Ticket_13::serialize() const { - std::vector result(8); - - store_lifetime(std::span(result.data(), 4), m_ticket_lifetime_hint); - store_be(m_ticket_age_add, result.data() + 4); - append_tls_length_value(result, m_ticket_nonce.get(), 1); - append_tls_length_value(result, m_handle.get(), 2); - - // TODO: re-evaluate this construction when reworking message marshalling - if(m_extensions.empty()) { - result.push_back(0x00); - result.push_back(0x00); - } else { - result += m_extensions.serialize(Connection_Side::Server); - } - - return result; -} - -#endif - -} // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/sessions_sql/tls_session_manager_sql.cpp botan3-3.12.0+dfsg/src/lib/tls/sessions_sql/tls_session_manager_sql.cpp --- botan3-3.7.1+dfsg/src/lib/tls/sessions_sql/tls_session_manager_sql.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/sessions_sql/tls_session_manager_sql.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -11,8 +11,8 @@ #include #include #include +#include #include -#include namespace Botan::TLS { @@ -93,11 +93,11 @@ secure_vector derived_key(32 + 2); - const auto pbkdf_name = "PBKDF2(SHA-512)"; + const std::string pbkdf_name = "PBKDF2(SHA-512)"; auto pbkdf_fam = PasswordHashFamily::create_or_throw(pbkdf_name); - auto desired_runtime = std::chrono::milliseconds(100); - auto pbkdf = pbkdf_fam->tune(derived_key.size(), desired_runtime); + constexpr uint32_t desired_runtime_msec = 100; + auto pbkdf = pbkdf_fam->tune_params(derived_key.size(), desired_runtime_msec); pbkdf->derive_key( derived_key.data(), derived_key.size(), passphrase.data(), passphrase.size(), salt.data(), salt.size()); @@ -123,7 +123,7 @@ throw Internal_Error("Failed to initialize TLS session database"); } - std::pair salt = stmt->get_blob(0); + const std::pair salt = stmt->get_blob(0); const size_t iterations = stmt->get_size_t(1); const size_t check_val_db = stmt->get_size_t(2); const std::string pbkdf_name = stmt->get_str(3); @@ -188,7 +188,7 @@ stmt->bind(1, hex_encode(session_id->get())); while(stmt->step()) { - std::pair blob = stmt->get_blob(0); + const std::pair blob = stmt->get_blob(0); try { return Session::decrypt(blob.first, blob.second, m_session_key); @@ -221,13 +221,13 @@ auto handle = [&]() -> Session_Handle { auto ticket_blob = stmt->get_blob(1); if(ticket_blob.second > 0) { - return Session_Ticket(std::span(ticket_blob.first, ticket_blob.second)); + return Session_Handle(Session_Ticket(std::span(ticket_blob.first, ticket_blob.second))); } else { - return Session_ID(Botan::hex_decode(stmt->get_str(0))); + return Session_Handle(Session_ID(Botan::hex_decode(stmt->get_str(0)))); } }(); - std::pair blob = stmt->get_blob(2); + const std::pair blob = stmt->get_blob(2); try { found_sessions.emplace_back( @@ -241,7 +241,7 @@ size_t Session_Manager_SQL::remove(const Session_Handle& handle) { // The number of deleted rows is taken globally from the database connection, // therefore we need to serialize this implementation. - lock_guard_type lk(mutex()); + const lock_guard_type lk(mutex()); if(const auto id = handle.id()) { auto stmt = m_db->new_statement("DELETE FROM tls_sessions WHERE session_id = ?1"); @@ -262,7 +262,7 @@ size_t Session_Manager_SQL::remove_all() { // The number of deleted rows is taken globally from the database connection, // therefore we need to serialize this implementation. - lock_guard_type lk(mutex()); + const lock_guard_type lk(mutex()); m_db->exec("DELETE FROM tls_sessions"); return m_db->rows_changed_by_last_statement(); diff -Nru botan3-3.7.1+dfsg/src/lib/tls/sessions_sql/tls_session_manager_sql.h botan3-3.12.0+dfsg/src/lib/tls/sessions_sql/tls_session_manager_sql.h --- botan3-3.7.1+dfsg/src/lib/tls/sessions_sql/tls_session_manager_sql.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/sessions_sql/tls_session_manager_sql.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,6 +9,7 @@ #define BOTAN_TLS_SQL_SESSION_MANAGER_H_ #include +#include #include namespace Botan { @@ -43,6 +44,9 @@ Session_Manager_SQL(const Session_Manager_SQL&) = delete; Session_Manager_SQL& operator=(const Session_Manager_SQL&) = delete; + Session_Manager_SQL(Session_Manager_SQL&&) = delete; + Session_Manager_SQL& operator=(Session_Manager_SQL&&) = delete; + ~Session_Manager_SQL() override = default; void store(const Session& session, const Session_Handle& handle) override; size_t remove(const Session_Handle& handle) override; @@ -69,7 +73,7 @@ // 20120609 - older (Botan 2.0) database scheme // 20230113 - adapt to Botan 3.0 Session_Manager API // (Session objects don't contain Session_ID, Session_Ticket) - enum Schema_Revision { + enum Schema_Revision /* NOLINT(*-use-enum-class) */ { EMPTY = 0, CORRUPTED = 1, PRE_BOTAN_3_0 = 20120609, diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/info.txt botan3-3.12.0+dfsg/src/lib/tls/tls12/info.txt --- botan3-3.7.1+dfsg/src/lib/tls/tls12/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -8,10 +8,13 @@ +tls_messages_12.h +tls_extensions_12.h tls_channel_impl_12.h +tls_connection_state_12.h tls_client_impl_12.h tls_record.h tls_server_impl_12.h @@ -23,17 +26,9 @@ -aead -aes -asn1 -dh eme_pkcs1 emsa_pkcs1 -gcm -hmac prf_tls -rng rsa -x509 tls diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/msg_cert_status.cpp botan3-3.12.0+dfsg/src/lib/tls/tls12/msg_cert_status.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls12/msg_cert_status.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/msg_cert_status.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,72 +0,0 @@ -/* -* Certificate Status -* (C) 2016 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include - -#include -#include -#include -#include -#include -#include -#include - -namespace Botan::TLS { - -Certificate_Status::Certificate_Status(const std::vector& buf, const Connection_Side) { - if(buf.size() < 5) { - throw Decoding_Error("Invalid Certificate_Status message: too small"); - } - - if(buf[0] != 1) { // not OCSP - throw Decoding_Error("Unexpected Certificate_Status message: unexpected response type"); - } - - size_t len = make_uint32(0, buf[1], buf[2], buf[3]); - - // Verify the redundant length field... - if(buf.size() != len + 4) { - throw Decoding_Error("Invalid Certificate_Status: invalid length field"); - } - - m_response.assign(buf.begin() + 4, buf.end()); -} - -Certificate_Status::Certificate_Status(Handshake_IO& io, Handshake_Hash& hash, const OCSP::Response& ocsp) : - m_response(ocsp.raw_bits()) { - hash.update(io.send(*this)); -} - -Certificate_Status::Certificate_Status(Handshake_IO& io, - Handshake_Hash& hash, - std::vector raw_response_bytes) : - Certificate_Status(std::move(raw_response_bytes)) { - hash.update(io.send(*this)); -} - -Certificate_Status::Certificate_Status(std::vector raw_response_bytes) : - m_response(std::move(raw_response_bytes)) {} - -std::vector Certificate_Status::serialize() const { - if(m_response.size() > 0xFFFFFF) { // unlikely - throw Encoding_Error("OCSP response too long to encode in TLS"); - } - - const uint32_t response_len = static_cast(m_response.size()); - - std::vector buf; - buf.reserve(1 + 3 + m_response.size()); - buf.push_back(1); // type OCSP - for(size_t i = 1; i < 4; ++i) { - buf.push_back(get_byte_var(i, response_len)); - } - - buf += m_response; - return buf; -} - -} // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/msg_cert_status_12.cpp botan3-3.12.0+dfsg/src/lib/tls/tls12/msg_cert_status_12.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls12/msg_cert_status_12.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/msg_cert_status_12.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,22 @@ +/* +* Certificate Status +* (C) 2016 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include + +namespace Botan::TLS { + +Certificate_Status_12::Certificate_Status_12(Handshake_IO& io, + Handshake_Hash& hash, + std::vector raw_response_bytes) : + Certificate_Status(std::move(raw_response_bytes)) { + hash.update(io.send(*this)); +} + +} // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/msg_cert_verify_12.cpp botan3-3.12.0+dfsg/src/lib/tls/tls12/msg_cert_verify_12.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls12/msg_cert_verify_12.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/msg_cert_verify_12.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,61 @@ +/* +* Certificate Verify Message +* (C) 2004,2006,2011,2012 Jack Lloyd +* 2017 Harry Reimann, Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include +#include +#include + +namespace Botan::TLS { + +/* +* Create a new Certificate Verify message for TLS 1.2 +*/ +Certificate_Verify_12::Certificate_Verify_12(Handshake_IO& io, + Handshake_State& state, + const Policy& policy, + RandomNumberGenerator& rng, + const Private_Key* priv_key) { + BOTAN_ASSERT_NONNULL(priv_key); + + const std::pair format = state.choose_sig_format(*priv_key, m_scheme, true, policy); + + m_signature = + state.callbacks().tls_sign_message(*priv_key, rng, format.first, format.second, state.hash().get_contents()); + + state.hash().update(io.send(*this)); +} + +bool Certificate_Verify_12::verify(const X509_Certificate& cert, + const Handshake_State& state, + const Policy& policy) const { + auto key = cert.subject_public_key(); + + policy.check_peer_key_acceptable(*key); + + const std::pair format = + state.parse_sig_format(*key, m_scheme, state.client_hello()->signature_schemes(), true, policy); + + const bool signature_valid = + state.callbacks().tls_verify_message(*key, format.first, format.second, state.hash().get_contents(), m_signature); + +#if defined(BOTAN_UNSAFE_FUZZER_MODE) + BOTAN_UNUSED(signature_valid); + return true; + +#else + return signature_valid; + +#endif +} + +} // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/msg_certificate_12.cpp botan3-3.12.0+dfsg/src/lib/tls/tls12/msg_certificate_12.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls12/msg_certificate_12.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/msg_certificate_12.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -5,19 +5,22 @@ * Botan is released under the Simplified BSD License (see license.txt) */ -#include +#include #include #include #include #include +#include +#include #include #include #include -#include namespace Botan::TLS { +Certificate_12::~Certificate_12() = default; + /** * Create a new Certificate message */ @@ -47,7 +50,7 @@ const uint8_t* certs = buf.data() + 3; - while(size_t remaining_bytes = buf.data() + buf.size() - certs) { + while(const size_t remaining_bytes = buf.data() + buf.size() - certs) { if(remaining_bytes < 3) { throw Decoding_Error("Certificate: Message malformed"); } @@ -59,7 +62,12 @@ } DataSource_Memory cert_buf(&certs[3], cert_size); - m_certs.push_back(X509_Certificate(cert_buf)); + try { + m_certs.push_back(X509_Certificate(cert_buf)); + } catch(Exception& e) { + // bad_certificate would make more sense but BoGo expects decoding_error + throw TLS_Exception(Alert::DecodeError, e.what()); + } certs += cert_size + 3; } @@ -99,4 +107,8 @@ return buf; } +size_t Certificate_12::count() const { + return m_certs.size(); +} + } // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/msg_certificate_req_12.cpp botan3-3.12.0+dfsg/src/lib/tls/tls12/msg_certificate_req_12.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls12/msg_certificate_req_12.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/msg_certificate_req_12.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,164 @@ +/* +* Certificate Request Message +* (C) 2004-2006,2012 Jack Lloyd +* 2021 Elektrobit Automotive GmbH +* 2022 René Meusel, Hannes Rantzsch - neXenio GmbH +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Botan::TLS { + +Certificate_Request_12::~Certificate_Request_12() = default; + +Handshake_Type Certificate_Request_12::type() const { + return Handshake_Type::CertificateRequest; +} + +namespace { + +std::string cert_type_code_to_name(uint8_t code) { + switch(code) { + case 1: + return "RSA"; + case 64: + return "ECDSA"; + default: + return ""; // DH or something else + } +} + +uint8_t cert_type_name_to_code(std::string_view name) { + if(name == "RSA") { + return 1; + } + if(name == "ECDSA") { + return 64; + } + + throw Invalid_Argument(fmt("Unknown/unhandled TLS cert type {}", name)); +} + +} // namespace + +/** +* Create a new Certificate Request message +*/ +Certificate_Request_12::Certificate_Request_12(Handshake_IO& io, + Handshake_Hash& hash, + const Policy& policy, + const std::vector& ca_certs) : + m_names(ca_certs), m_cert_key_types({"RSA", "ECDSA"}) { + m_schemes = policy.acceptable_signature_schemes(); + // RFC 5246 7.4.4: supported_signature_algorithms<2..2^16-2> + if(m_schemes.empty()) { + throw Internal_Error("Policy returned no acceptable signature schemes for CertificateRequest"); + } + hash.update(io.send(*this)); +} + +/** +* Deserialize a Certificate Request message +*/ +Certificate_Request_12::Certificate_Request_12(const std::vector& buf) { + if(buf.size() < 4) { + throw Decoding_Error("Certificate_Req: Bad certificate request"); + } + + TLS_Data_Reader reader("CertificateRequest", buf); + + const auto cert_type_codes = reader.get_range_vector(1, 1, 255); + + for(const auto cert_type_code : cert_type_codes) { + const std::string cert_type_name = cert_type_code_to_name(cert_type_code); + + if(cert_type_name.empty()) { // something we don't know + continue; + } + + m_cert_key_types.emplace_back(cert_type_name); + } + + const std::vector algs = reader.get_range_vector(2, 2, 65534); + + if(algs.size() % 2 != 0) { + throw Decoding_Error("Bad length for signature IDs in certificate request"); + } + + for(size_t i = 0; i != algs.size(); i += 2) { + m_schemes.emplace_back(make_uint16(algs[i], algs[i + 1])); + } + + const uint16_t purported_size = reader.get_uint16_t(); + + if(reader.remaining_bytes() != purported_size) { + throw Decoding_Error("Inconsistent length in certificate request"); + } + + while(reader.has_remaining()) { + // RFC 5246 7.4.4: opaque DistinguishedName<1..2^16-1> + std::vector name_bits = reader.get_range_vector(2, 1, 65535); + + BER_Decoder decoder(name_bits, BER_Decoder::Limits::DER()); + X509_DN name; + decoder.decode(name).verify_end(); + m_names.emplace_back(name); + } +} + +const std::vector& Certificate_Request_12::acceptable_cert_types() const { + return m_cert_key_types; +} + +const std::vector& Certificate_Request_12::acceptable_CAs() const { + return m_names; +} + +const std::vector& Certificate_Request_12::signature_schemes() const { + return m_schemes; +} + +/** +* Serialize a Certificate Request message +*/ +std::vector Certificate_Request_12::serialize() const { + std::vector buf; + + std::vector cert_types; + + cert_types.reserve(m_cert_key_types.size()); + for(const auto& cert_key_type : m_cert_key_types) { + cert_types.push_back(cert_type_name_to_code(cert_key_type)); + } + + append_tls_length_value(buf, cert_types, 1); + + // RFC 5246 7.4.4: supported_signature_algorithms<2..2^16-2> + buf += Signature_Algorithms(m_schemes).serialize(Connection_Side::Server); + + std::vector encoded_names; + + for(const auto& name : m_names) { + DER_Encoder encoder; + encoder.encode(name); + + append_tls_length_value(encoded_names, encoder.get_contents(), 2); + } + + append_tls_length_value(buf, encoded_names, 2); + + return buf; +} +} // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/msg_client_hello_12.cpp botan3-3.12.0+dfsg/src/lib/tls/tls12/msg_client_hello_12.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls12/msg_client_hello_12.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/msg_client_hello_12.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,297 @@ +/* +* TLS Hello Request and Client Hello Messages +* (C) 2004-2011,2015,2016 Jack Lloyd +* 2016 Matthias Gierlings +* 2017 Harry Reimann, Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Botan::TLS { + +void Client_Hello_12::update_hello_cookie(const Hello_Verify_Request& hello_verify) { + BOTAN_STATE_CHECK(m_data->legacy_version().is_datagram_protocol()); + + m_data->m_hello_cookie = hello_verify.cookie(); +} + +bool Client_Hello_12::prefers_compressed_ec_points() const { + if(const Supported_Point_Formats* ecc_formats = m_data->extensions().get()) { + return ecc_formats->prefers_compressed(); + } + return false; +} + +bool Client_Hello_12::secure_renegotiation() const { + return m_data->extensions().has(); +} + +std::vector Client_Hello_12::renegotiation_info() const { + if(const Renegotiation_Extension* reneg = m_data->extensions().get()) { + return reneg->renegotiation_info(); + } + return {}; +} + +bool Client_Hello_12::supports_session_ticket() const { + return m_data->extensions().has(); +} + +Session_Ticket Client_Hello_12::session_ticket() const { + if(auto* ticket = m_data->extensions().get()) { + return ticket->contents(); + } + return {}; +} + +std::optional Client_Hello_12::session_handle() const { + // RFC 5077 3.4 + // If a ticket is presented by the client, the server MUST NOT attempt + // to use the Session ID in the ClientHello for stateful session + // resumption. + if(auto ticket = session_ticket(); !ticket.empty()) { + return Session_Handle(ticket); + } else if(const auto& id = session_id(); !id.empty()) { + return Session_Handle(id); + } else { + return std::nullopt; + } +} + +bool Client_Hello_12::supports_extended_master_secret() const { + return m_data->extensions().has(); +} + +bool Client_Hello_12::supports_cert_status_message() const { + return m_data->extensions().has(); +} + +bool Client_Hello_12::supports_encrypt_then_mac() const { + return m_data->extensions().has(); +} + +void Client_Hello_12::add_tls12_supported_groups_extensions(const Policy& policy) { + // RFC 7919 3. + // A client that offers a group MUST be able and willing to perform a DH + // key exchange using that group. + // + // We don't support hybrid key exchange in TLS 1.2 + + std::vector compatible_kex_groups; + for(const auto& group : policy.key_exchange_groups()) { + if(!group.is_post_quantum()) { + compatible_kex_groups.push_back(group); + } + } + + auto supported_groups = std::make_unique(std::move(compatible_kex_groups)); + + if(!supported_groups->ec_groups().empty()) { + // NOLINTNEXTLINE(*-owning-memory) + m_data->extensions().add(new Supported_Point_Formats(policy.use_ecc_point_compression())); + } + + m_data->extensions().add(std::move(supported_groups)); +} + +/* +* Create a new Client Hello message +*/ +Client_Hello_12::Client_Hello_12(Handshake_IO& io, + Handshake_Hash& hash, + const Policy& policy, + Callbacks& cb, + RandomNumberGenerator& rng, + const std::vector& reneg_info, + const Client_Hello_12::Settings& client_settings, + const std::vector& next_protocols) { + m_data->m_legacy_version = client_settings.protocol_version(); + m_data->m_random = make_hello_random(rng, cb, policy); + m_data->m_suites = policy.ciphersuite_list(client_settings.protocol_version()); + + if(!policy.acceptable_protocol_version(m_data->legacy_version())) { + throw Internal_Error("Offering " + m_data->legacy_version().to_string() + + " but our own policy does not accept it"); + } + + /* + * Place all empty extensions in front to avoid a bug in some systems + * which reject hellos when the last extension in the list is empty. + */ + + // NOLINTBEGIN(*-owning-memory) + + // EMS must always be used with TLS 1.2, regardless of the policy used. + + m_data->extensions().add(new Extended_Master_Secret); + + if(policy.negotiate_encrypt_then_mac()) { + m_data->extensions().add(new Encrypt_then_MAC); + } + + m_data->extensions().add(new Session_Ticket_Extension()); + + m_data->extensions().add(new Renegotiation_Extension(reneg_info)); + + m_data->extensions().add(new Supported_Versions(m_data->legacy_version(), policy)); + + if(Server_Name_Indicator::hostname_acceptable_for_sni(client_settings.hostname())) { + m_data->extensions().add(new Server_Name_Indicator(client_settings.hostname())); + } + + if(policy.support_cert_status_message()) { + m_data->extensions().add(new Certificate_Status_Request({}, {})); + } + + add_tls12_supported_groups_extensions(policy); + + m_data->extensions().add(new Signature_Algorithms(policy.acceptable_signature_schemes())); + if(auto cert_signing_prefs = policy.acceptable_certificate_signature_schemes()) { + // RFC 8446 4.2.3 + // TLS 1.2 implementations SHOULD also process this extension. + // Implementations which have the same policy in both cases MAY omit + // the "signature_algorithms_cert" extension. + m_data->extensions().add(new Signature_Algorithms_Cert(std::move(cert_signing_prefs.value()))); + } + + if(reneg_info.empty() && !next_protocols.empty()) { + m_data->extensions().add(new Application_Layer_Protocol_Notification(next_protocols)); + } + + if(m_data->legacy_version().is_datagram_protocol()) { + m_data->extensions().add(new SRTP_Protection_Profiles(policy.srtp_profiles())); + } + + // NOLINTEND(*-owning-memory) + + cb.tls_modify_extensions(m_data->extensions(), Connection_Side::Client, type()); + + hash.update(io.send(*this)); +} + +/* +* Create a new Client Hello message (session resumption case) +*/ +Client_Hello_12::Client_Hello_12(Handshake_IO& io, + Handshake_Hash& hash, + const Policy& policy, + Callbacks& cb, + RandomNumberGenerator& rng, + const std::vector& reneg_info, + const Session_with_Handle& session, + const std::vector& next_protocols) { + m_data->m_legacy_version = session.session.version(); + m_data->m_random = make_hello_random(rng, cb, policy); + + // RFC 5077 3.4 + // When presenting a ticket, the client MAY generate and include a + // Session ID in the TLS ClientHello. [...] If a ticket is presented by + // the client, the server MUST NOT attempt to use the Session ID in the + // ClientHello for stateful session resumption. + m_data->m_session_id = session.handle.id().value_or(Session_ID(make_hello_random(rng, cb, policy))); + m_data->m_suites = policy.ciphersuite_list(m_data->legacy_version()); + + if(!policy.acceptable_protocol_version(session.session.version())) { + throw Internal_Error("Offering " + m_data->legacy_version().to_string() + + " but our own policy does not accept it"); + } + + if(!value_exists(m_data->ciphersuites(), session.session.ciphersuite_code())) { + m_data->m_suites.push_back(session.session.ciphersuite_code()); + } + + /* + * As EMS must always be used with TLS 1.2, add it even if it wasn't used + * in the original session. If the server understands it and follows the + * RFC it should reject our resume attempt and upgrade us to a new session + * with the EMS protection. + */ + // NOLINTBEGIN(*-owning-memory) + m_data->extensions().add(new Extended_Master_Secret); + + if(session.session.supports_encrypt_then_mac()) { + m_data->extensions().add(new Encrypt_then_MAC); + } + + if(session.handle.is_ticket()) { + m_data->extensions().add(new Session_Ticket_Extension(session.handle.ticket().value())); + } + + m_data->extensions().add(new Renegotiation_Extension(reneg_info)); + + const std::string hostname = session.session.server_info().hostname(); + + if(Server_Name_Indicator::hostname_acceptable_for_sni(hostname)) { + m_data->extensions().add(new Server_Name_Indicator(hostname)); + } + + if(policy.support_cert_status_message()) { + m_data->extensions().add(new Certificate_Status_Request({}, {})); + } + + add_tls12_supported_groups_extensions(policy); + + m_data->extensions().add(new Signature_Algorithms(policy.acceptable_signature_schemes())); + if(auto cert_signing_prefs = policy.acceptable_certificate_signature_schemes()) { + // RFC 8446 4.2.3 + // TLS 1.2 implementations SHOULD also process this extension. + // Implementations which have the same policy in both cases MAY omit + // the "signature_algorithms_cert" extension. + m_data->extensions().add(new Signature_Algorithms_Cert(std::move(cert_signing_prefs.value()))); + } + + if(reneg_info.empty() && !next_protocols.empty()) { + m_data->extensions().add(new Application_Layer_Protocol_Notification(next_protocols)); + } + + // NOLINTEND(*-owning-memory) + + cb.tls_modify_extensions(m_data->extensions(), Connection_Side::Client, type()); + + hash.update(io.send(*this)); +} + +Client_Hello_12::Client_Hello_12(const std::vector& buf) : + Client_Hello_12(std::make_unique(buf)) {} + +Client_Hello_12::Client_Hello_12(std::unique_ptr data) : Client_Hello_12_Shim(std::move(data)) { + const uint16_t TLS_EMPTY_RENEGOTIATION_INFO_SCSV = 0x00FF; + + if(offered_suite(static_cast(TLS_EMPTY_RENEGOTIATION_INFO_SCSV))) { + if(const Renegotiation_Extension* reneg = m_data->extensions().get()) { + if(!reneg->renegotiation_info().empty()) { + throw TLS_Exception(Alert::HandshakeFailure, "Client sent renegotiation SCSV and non-empty extension"); + } + } else { + // add fake extension + m_data->extensions().add(new Renegotiation_Extension()); // NOLINT(*-owning-memory) + } + } +} + +Hello_Request::Hello_Request(Handshake_IO& io) { + io.send(*this); +} + +Hello_Request::Hello_Request(const std::vector& buf) { + if(!buf.empty()) { + throw Decoding_Error("Bad Hello_Request, has non-zero size"); + } +} + +std::vector Hello_Request::serialize() const { + return std::vector(); +} + +} // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/msg_client_kex.cpp botan3-3.12.0+dfsg/src/lib/tls/tls12/msg_client_kex.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls12/msg_client_kex.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/msg_client_kex.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,24 +6,51 @@ * Botan is released under the Simplified BSD License (see license.txt) */ -#include +#include +#include +#include #include +#include +#include #include - -#include +#include #include +#include #include #include #include #include #include -#include -#include - namespace Botan::TLS { +namespace { + +/* +* If the (p, g) pair the server sent corresponds to a known group +* (RFC 7919 or RFC 3526), return that group. +*/ +std::optional match_well_known_dh_group(const BigInt& p, const BigInt& g) { + const size_t p_bits = p.bits(); + + if(p_bits != 2048 && p_bits != 3072 && p_bits != 4096 && p_bits != 6144 && p_bits != 8192) { + return {}; + } + + for(const char* prefix : {"ffdhe/ietf", "modp/ietf"}) { + const std::string name = fmt("{}/{}", prefix, p_bits); + auto candidate = DL_Group::from_name(name); + if(candidate.get_p() == p && candidate.get_g() == g) { + return candidate; + } + } + + return std::nullopt; +} + +} // namespace + /* * Create a new Client Key Exchange message */ @@ -39,34 +66,42 @@ if(kex_algo == Kex_Algo::PSK) { std::string identity_hint; - if(state.server_kex()) { + if(state.server_kex() != nullptr) { TLS_Data_Reader reader("ClientKeyExchange", state.server_kex()->params()); identity_hint = reader.get_string(2, 0, 65535); } m_psk_identity = creds.psk_identity("tls-client", std::string(hostname), identity_hint); - append_tls_length_value(m_key_material, to_byte_vector(m_psk_identity.value()), 2); + append_tls_length_value(m_key_material, as_span_of_bytes(m_psk_identity.value()), 2); - SymmetricKey psk = creds.psk("tls-client", std::string(hostname), m_psk_identity.value()); + const SymmetricKey psk = creds.psk("tls-client", std::string(hostname), m_psk_identity.value()); - std::vector zeros(psk.length()); + if(psk.empty()) { + throw TLS_Exception(Alert::InternalError, "Application did not provide a PSK for the negotiated identity"); + } + + const std::vector zeros(psk.length()); append_tls_length_value(m_pre_master, zeros, 2); append_tls_length_value(m_pre_master, psk.bits_of(), 2); - } else if(state.server_kex()) { + } else if(state.server_kex() != nullptr) { TLS_Data_Reader reader("ClientKeyExchange", state.server_kex()->params()); SymmetricKey psk; if(kex_algo == Kex_Algo::ECDHE_PSK) { - std::string identity_hint = reader.get_string(2, 0, 65535); + const std::string identity_hint = reader.get_string(2, 0, 65535); m_psk_identity = creds.psk_identity("tls-client", std::string(hostname), identity_hint); - append_tls_length_value(m_key_material, to_byte_vector(m_psk_identity.value()), 2); + append_tls_length_value(m_key_material, as_span_of_bytes(m_psk_identity.value()), 2); psk = creds.psk("tls-client", std::string(hostname), m_psk_identity.value()); + + if(psk.empty()) { + throw TLS_Exception(Alert::InternalError, "Application did not provide a PSK for the negotiated identity"); + } } if(kex_algo == Kex_Algo::DH) { @@ -74,27 +109,36 @@ const auto generator = BigInt::from_bytes(reader.get_range(2, 1, 65535)); const std::vector peer_public_value = reader.get_range(2, 1, 65535); - if(reader.remaining_bytes()) { + if(reader.remaining_bytes() > 0) { throw Decoding_Error("Bad params size for DH key exchange"); } - DL_Group group(modulus, generator); - - if(!group.verify_group(rng, false)) { - throw TLS_Exception(Alert::InsufficientSecurity, "DH group validation failed"); + if(modulus.bits() < policy.minimum_dh_group_size()) { + throw TLS_Exception(Alert::InsufficientSecurity, "DH prime too small for policy"); } + if(modulus.bits() > policy.maximum_dh_group_size()) { + throw TLS_Exception(Alert::IllegalParameter, "DH prime too large for policy"); + } + + const auto group = [&] { + if(auto matched = match_well_known_dh_group(modulus, generator)) { + return std::move(*matched); + } else { + /* + * Even if we sent ffdhe groups in the supported_groups extension + * a server may have replied with some other group. + */ + DL_Group ad_hoc(modulus, generator); + if(!ad_hoc.verify_group(rng, false)) { + throw TLS_Exception(Alert::InsufficientSecurity, "DH group validation failed"); + } + return ad_hoc; + } + }(); const auto private_key = state.callbacks().tls_generate_ephemeral_key(group, rng); - auto shared_secret = CT::strip_leading_zeros( + m_pre_master = CT::strip_leading_zeros( state.callbacks().tls_ephemeral_key_agreement(group, *private_key, peer_public_value, rng, policy)); - - if(kex_algo == Kex_Algo::DH) { - m_pre_master = std::move(shared_secret); - } else { - append_tls_length_value(m_pre_master, shared_secret, 2); - append_tls_length_value(m_pre_master, psk.bits_of(), 2); - } - append_tls_length_value(m_key_material, private_key->public_value(), 2); } else if(kex_algo == Kex_Algo::ECDH || kex_algo == Kex_Algo::ECDHE_PSK) { const uint8_t curve_type = reader.get_byte(); @@ -110,11 +154,34 @@ "Server selected a group that is not compatible with the negotiated ciphersuite"); } + // RFC 8422 5.1: the server MUST select a curve from the + // supported_groups list the client offered. Check against the actual + // offered list (which may be a strict subset of the policy's + // key_exchange_groups() if the application narrowed it via + // tls_modify_extensions) rather than just the policy. + if(!value_exists(state.client_hello()->supported_ecc_curves(), curve_id)) { + throw TLS_Exception(Alert::IllegalParameter, "Server selected a curve we did not offer"); + } + if(policy.choose_key_exchange_group({curve_id}, {}) != curve_id) { throw TLS_Exception(Alert::HandshakeFailure, "Server sent ECC curve prohibited by policy"); } - const auto private_key = state.callbacks().tls_generate_ephemeral_key(curve_id, rng); + const auto private_key = [&] { + if(curve_id.is_ecdh_named_curve()) { + const auto pubkey_point_format = state.server_hello()->prefers_compressed_ec_points() + ? EC_Point_Format::Compressed + : EC_Point_Format::Uncompressed; + return state.callbacks().tls12_generate_ephemeral_ecdh_key(curve_id, rng, pubkey_point_format); + } else { + return state.callbacks().tls_generate_ephemeral_key(curve_id, rng); + } + }(); + + if(!private_key) { + throw TLS_Exception(Alert::InternalError, "Application did not provide an EC key"); + } + auto shared_secret = state.callbacks().tls_ephemeral_key_agreement(curve_id, *private_key, peer_public_value, rng, policy); @@ -125,19 +192,10 @@ append_tls_length_value(m_pre_master, psk.bits_of(), 2); } - if(curve_id.is_ecdh_named_curve()) { - auto ecdh_key = dynamic_cast(private_key.get()); - if(!ecdh_key) { - throw TLS_Exception(Alert::InternalError, "Application did not provide a ECDH_PublicKey"); - } - append_tls_length_value(m_key_material, - ecdh_key->public_value(state.server_hello()->prefers_compressed_ec_points() - ? EC_Point_Format::Compressed - : EC_Point_Format::Uncompressed), - 1); - } else { - append_tls_length_value(m_key_material, private_key->public_value(), 1); - } + // Note: In contrast to public_value(), raw_public_key_bits() takes the + // point format (compressed vs. uncompressed) into account that was set + // in its construction within tls_generate_ephemeral_key(). + append_tls_length_value(m_key_material, private_key->raw_public_key_bits(), 1); } else { throw Internal_Error("Client_Key_Exchange: Unknown key exchange method was negotiated"); } @@ -150,18 +208,18 @@ throw Unexpected_Message("No server kex message, but negotiated a key exchange that required it"); } - if(!server_public_key) { + if(server_public_key == nullptr) { throw Internal_Error("No server public key for RSA exchange"); } - if(auto rsa_pub = dynamic_cast(server_public_key)) { + if(const auto* rsa_pub = dynamic_cast(server_public_key)) { const Protocol_Version offered_version = state.client_hello()->legacy_version(); rng.random_vec(m_pre_master, 48); m_pre_master[0] = offered_version.major_version(); m_pre_master[1] = offered_version.minor_version(); - PK_Encryptor_EME encryptor(*rsa_pub, rng, "PKCS1v15"); + const PK_Encryptor_EME encryptor(*rsa_pub, rng, "PKCS1v15"); const std::vector encrypted_key = encryptor.encrypt(m_pre_master, rng); @@ -190,7 +248,7 @@ BOTAN_ASSERT(state.server_certs() && !state.server_certs()->cert_chain().empty(), "RSA key exchange negotiated so server sent a certificate"); - if(!server_rsa_kex_key) { + if(server_rsa_kex_key == nullptr) { throw Internal_Error("Expected RSA kex but no server kex key set"); } @@ -199,10 +257,11 @@ } TLS_Data_Reader reader("ClientKeyExchange", contents); - const std::vector encrypted_pre_master = reader.get_range(2, 0, 65535); + // RFC 5246 7.4.7.1: encrypted_pre_master_secret<1..2^16-1>. + const std::vector encrypted_pre_master = reader.get_range(2, 1, 65535); reader.assert_done(); - PK_Decryptor_EME decryptor(*server_rsa_kex_key, rng, "PKCS1v15"); + const PK_Decryptor_EME decryptor(*server_rsa_kex_key, rng, "PKCS1v15"); const uint8_t client_major = state.client_hello()->legacy_version().major_version(); const uint8_t client_minor = state.client_hello()->legacy_version().minor_version(); @@ -233,7 +292,12 @@ if(key_exchange_is_psk(kex_algo)) { m_psk_identity = reader.get_string(2, 0, 65535); - psk = creds.psk("tls-server", state.client_hello()->sni_hostname(), m_psk_identity.value()); + try { + psk = creds.psk("tls-server", state.client_hello()->sni_hostname(), m_psk_identity.value()); + } catch(...) { + // Treat any lookup failure for the identity sent by the client as + // "no PSK for this identity" and let the logic below handle it + } if(psk.empty()) { if(policy.hide_unknown_users()) { @@ -245,14 +309,15 @@ } if(kex_algo == Kex_Algo::PSK) { - std::vector zeros(psk.length()); + reader.assert_done(); + const std::vector zeros(psk.length()); append_tls_length_value(m_pre_master, zeros, 2); append_tls_length_value(m_pre_master, psk.bits_of(), 2); } else if(kex_algo == Kex_Algo::DH || kex_algo == Kex_Algo::ECDH || kex_algo == Kex_Algo::ECDHE_PSK) { const PK_Key_Agreement_Key& ka_key = state.server_kex()->server_kex_key(); const std::vector client_pubkey = (ka_key.algo_name() == "DH") - ? reader.get_range(2, 0, 65535) + ? reader.get_range(2, 1, 65535) : reader.get_range(1, 1, 255); const auto shared_group = state.server_kex()->shared_group(); @@ -274,9 +339,8 @@ } } catch(Invalid_Argument& e) { throw TLS_Exception(Alert::IllegalParameter, e.what()); - } catch(TLS_Exception& e) { - // NOLINTNEXTLINE(cert-err60-cpp) - throw e; + } catch(TLS_Exception&) { + throw; // rethrow } catch(std::exception&) { /* * Something failed in the DH/ECDH computation. To avoid possible diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/msg_finished_12.cpp botan3-3.12.0+dfsg/src/lib/tls/tls12/msg_finished_12.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls12/msg_finished_12.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/msg_finished_12.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,59 @@ +/* +* Finished Message +* (C) 2004-2006,2012 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include +#include + +namespace Botan::TLS { + +namespace { + +/* +* Compute the verify_data for TLS 1.2 +*/ +std::vector finished_compute_verify_12(const Handshake_State& state, Connection_Side side) { + const uint8_t TLS_CLIENT_LABEL[] = { + 0x63, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x20, 0x66, 0x69, 0x6E, 0x69, 0x73, 0x68, 0x65, 0x64}; + + const uint8_t TLS_SERVER_LABEL[] = { + 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x66, 0x69, 0x6E, 0x69, 0x73, 0x68, 0x65, 0x64}; + + auto prf = state.protocol_specific_prf(); + + std::vector input; + std::vector label; + label += (side == Connection_Side::Client) ? std::make_pair(TLS_CLIENT_LABEL, sizeof(TLS_CLIENT_LABEL)) + : std::make_pair(TLS_SERVER_LABEL, sizeof(TLS_SERVER_LABEL)); + + input += state.hash().final(state.ciphersuite().prf_algo()); + + return unlock(prf->derive_key(12, state.session_keys().master_secret(), input, label)); +} + +} // namespace + +Finished_12::Finished_12(Handshake_IO& io, Handshake_State& state, Connection_Side side) { + m_verification_data = finished_compute_verify_12(state, side); + state.hash().update(io.send(*this)); +} + +bool Finished_12::verify(const Handshake_State& state, Connection_Side side) const { + std::vector computed_verify = finished_compute_verify_12(state, side); + +#if defined(BOTAN_UNSAFE_FUZZER_MODE) + return true; +#else + return CT::is_equal(m_verification_data, computed_verify).as_bool(); +#endif +} + +} // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/msg_hello_verify.cpp botan3-3.12.0+dfsg/src/lib/tls/tls12/msg_hello_verify.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls12/msg_hello_verify.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/msg_hello_verify.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,6 +8,7 @@ #include #include +#include namespace Botan::TLS { @@ -16,7 +17,7 @@ throw Decoding_Error("Hello verify request too small"); } - Protocol_Version version(buf[0], buf[1]); + const Protocol_Version version(buf[0], buf[1]); if(!version.is_datagram_protocol()) { throw Decoding_Error("Unknown version from server in hello verify request"); @@ -50,13 +51,12 @@ negotiated (RFC 6347, section 4.2.1) */ - Protocol_Version format_version(254, 255); // DTLS 1.0 + const Protocol_Version format_version(254, 255); // DTLS 1.0 std::vector bits; bits.push_back(format_version.major_version()); bits.push_back(format_version.minor_version()); - bits.push_back(static_cast(m_cookie.size())); - bits += m_cookie; + append_tls_length_value(bits, m_cookie, 1); return bits; } diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/msg_server_hello_12.cpp botan3-3.12.0+dfsg/src/lib/tls/tls12/msg_server_hello_12.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls12/msg_server_hello_12.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/msg_server_hello_12.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,229 @@ +/* +* TLS Server Hello and Server Hello Done +* (C) 2004-2011,2015,2016,2019 Jack Lloyd +* 2016 Matthias Gierlings +* 2017 Harry Reimann, Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include +#include +#include + +namespace Botan::TLS { + +// New session case +Server_Hello_12::Server_Hello_12(Handshake_IO& io, + Handshake_Hash& hash, + const Policy& policy, + Callbacks& cb, + RandomNumberGenerator& rng, + const std::vector& reneg_info, + const Client_Hello_12& client_hello, + const Server_Hello_12::Settings& server_settings, + std::string_view next_protocol) : + Server_Hello_12(std::make_unique( + server_settings.protocol_version(), + server_settings.session_id(), + make_server_hello_random(rng, server_settings.protocol_version(), cb, policy), + server_settings.ciphersuite(), + uint8_t(0))) { + // NOLINTBEGIN(*-owning-memory) + if(client_hello.supports_extended_master_secret()) { + m_data->extensions().add(new Extended_Master_Secret); + } + + // Sending the extension back does not commit us to sending a stapled response + if(client_hello.supports_cert_status_message() && policy.support_cert_status_message()) { + m_data->extensions().add(new Certificate_Status_Request); + } + + if(!next_protocol.empty() && client_hello.supports_alpn()) { + m_data->extensions().add(new Application_Layer_Protocol_Notification(next_protocol)); + } + + const auto c = Ciphersuite::by_id(m_data->ciphersuite()); + + if(c && c->cbc_ciphersuite() && client_hello.supports_encrypt_then_mac() && policy.negotiate_encrypt_then_mac()) { + m_data->extensions().add(new Encrypt_then_MAC); + } + + if(c && c->ecc_ciphersuite() && client_hello.extension_types().contains(Extension_Code::EcPointFormats)) { + m_data->extensions().add(new Supported_Point_Formats(policy.use_ecc_point_compression())); + } + + if(client_hello.secure_renegotiation()) { + m_data->extensions().add(new Renegotiation_Extension(reneg_info)); + } + + if(client_hello.supports_session_ticket() && server_settings.offer_session_ticket()) { + m_data->extensions().add(new Session_Ticket_Extension()); + } + + if(m_data->legacy_version().is_datagram_protocol()) { + const std::vector server_srtp = policy.srtp_profiles(); + const std::vector client_srtp = client_hello.srtp_profiles(); + + if(!server_srtp.empty() && !client_srtp.empty()) { + uint16_t shared = 0; + // always using server preferences for now + for(auto s_srtp : server_srtp) { + for(auto c_srtp : client_srtp) { + if(shared == 0 && s_srtp == c_srtp) { + shared = s_srtp; + } + } + } + + if(shared != 0) { + m_data->extensions().add(new SRTP_Protection_Profiles(shared)); + } + } + } + // NOLINTEND(*-owning-memory) + + cb.tls_modify_extensions(m_data->extensions(), Connection_Side::Server, type()); + + hash.update(io.send(*this)); +} + +// Resuming +Server_Hello_12::Server_Hello_12(Handshake_IO& io, + Handshake_Hash& hash, + const Policy& policy, + Callbacks& cb, + RandomNumberGenerator& rng, + const std::vector& reneg_info, + const Client_Hello_12& client_hello, + const Session& resumed_session, + bool offer_session_ticket, + std::string_view next_protocol) : + Server_Hello_12( + std::make_unique(resumed_session.version(), + client_hello.session_id(), + make_server_hello_random(rng, resumed_session.version(), cb, policy), + resumed_session.ciphersuite_code(), + uint8_t(0))) { + // NOLINTBEGIN(*-owning-memory) + if(client_hello.supports_extended_master_secret()) { + m_data->extensions().add(new Extended_Master_Secret); + } + + if(!next_protocol.empty() && client_hello.supports_alpn()) { + m_data->extensions().add(new Application_Layer_Protocol_Notification(next_protocol)); + } + + if(client_hello.supports_encrypt_then_mac() && policy.negotiate_encrypt_then_mac()) { + const Ciphersuite c = resumed_session.ciphersuite(); + if(c.cbc_ciphersuite()) { + m_data->extensions().add(new Encrypt_then_MAC); + } + } + + if(resumed_session.ciphersuite().ecc_ciphersuite() && + client_hello.extension_types().contains(Extension_Code::EcPointFormats)) { + m_data->extensions().add(new Supported_Point_Formats(policy.use_ecc_point_compression())); + } + + if(client_hello.secure_renegotiation()) { + m_data->extensions().add(new Renegotiation_Extension(reneg_info)); + } + + if(client_hello.supports_session_ticket() && offer_session_ticket) { + m_data->extensions().add(new Session_Ticket_Extension()); + } + // NOLINTEND(*-owning-memory) + + cb.tls_modify_extensions(m_data->extensions(), Connection_Side::Server, type()); + + hash.update(io.send(*this)); +} + +Server_Hello_12::Server_Hello_12(const std::vector& buf) : + Server_Hello_12(std::make_unique(buf)) {} + +Server_Hello_12::Server_Hello_12(std::unique_ptr data) : Server_Hello_12_Shim(std::move(data)) {} + +bool Server_Hello_12::secure_renegotiation() const { + return m_data->extensions().has(); +} + +std::vector Server_Hello_12::renegotiation_info() const { + if(const Renegotiation_Extension* reneg = m_data->extensions().get()) { + return reneg->renegotiation_info(); + } + return std::vector(); +} + +bool Server_Hello_12::supports_extended_master_secret() const { + return m_data->extensions().has(); +} + +bool Server_Hello_12::supports_encrypt_then_mac() const { + return m_data->extensions().has(); +} + +bool Server_Hello_12::supports_certificate_status_message() const { + return m_data->extensions().has(); +} + +bool Server_Hello_12::supports_session_ticket() const { + return m_data->extensions().has(); +} + +uint16_t Server_Hello_12::srtp_profile() const { + if(auto* srtp = m_data->extensions().get()) { + auto prof = srtp->profiles(); + if(prof.size() != 1 || prof[0] == 0) { + throw Decoding_Error("Server sent malformed DTLS-SRTP extension"); + } + return prof[0]; + } + + return 0; +} + +std::string Server_Hello_12::next_protocol() const { + if(auto* alpn = m_data->extensions().get()) { + return alpn->single_protocol(); + } + return ""; +} + +bool Server_Hello_12::prefers_compressed_ec_points() const { + if(auto* ecc_formats = m_data->extensions().get()) { + return ecc_formats->prefers_compressed(); + } + return false; +} + +/* +* Create a new Server Hello Done message +*/ +Server_Hello_Done::Server_Hello_Done(Handshake_IO& io, Handshake_Hash& hash) { + hash.update(io.send(*this)); +} + +/* +* Deserialize a Server Hello Done message +*/ +Server_Hello_Done::Server_Hello_Done(const std::vector& buf) { + if(!buf.empty()) { + throw Decoding_Error("Server_Hello_Done: Must be empty, and is not"); + } +} + +/* +* Serialize a Server Hello Done message +*/ +std::vector Server_Hello_Done::serialize() const { + return std::vector(); +} + +} // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/msg_server_kex.cpp botan3-3.12.0+dfsg/src/lib/tls/tls12/msg_server_kex.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls12/msg_server_kex.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/msg_server_kex.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,29 +6,25 @@ * Botan is released under the Simplified BSD License (see license.txt) */ -#include +#include +#include #include -#include -#include +#include +#include +#include #include +#include #include #include #include #include -#include -#include - -#if defined(BOTAN_HAS_X25519) - #include -#endif -#if defined(BOTAN_HAS_X448) - #include -#endif namespace Botan::TLS { +Server_Key_Exchange::~Server_Key_Exchange() = default; + /** * Create a new Server Key Exchange message */ @@ -42,7 +38,7 @@ const Kex_Algo kex_algo = state.ciphersuite().kex_method(); if(kex_algo == Kex_Algo::PSK || kex_algo == Kex_Algo::ECDHE_PSK) { - std::string identity_hint = creds.psk_identity_hint("tls-server", hostname); + const std::string identity_hint = creds.psk_identity_hint("tls-server", hostname); append_tls_length_value(m_params, identity_hint, 2); } @@ -83,8 +79,8 @@ // `Policy::default_dh_group()` could return a `std::variant`, allowing it to define arbitrary groups. m_kex_key = state.callbacks().tls_generate_ephemeral_key(m_shared_group.value(), rng); - auto dh = dynamic_cast(m_kex_key.get()); - if(!dh) { + auto* dh = dynamic_cast(m_kex_key.get()); + if(dh == nullptr) { throw TLS_Exception(Alert::InternalError, "Application did not provide a Diffie-Hellman key"); } @@ -104,25 +100,19 @@ throw TLS_Exception(Alert::HandshakeFailure, "No shared ECC group with client"); } - std::vector ecdh_public_val; - - if(m_shared_group.value() == Group_Params::X25519 || m_shared_group.value() == Group_Params::X448) { - m_kex_key = state.callbacks().tls_generate_ephemeral_key(m_shared_group.value(), rng); - if(!m_kex_key) { - throw TLS_Exception(Alert::InternalError, "Application did not provide an EC key"); - } - ecdh_public_val = m_kex_key->public_value(); - } else { - m_kex_key = state.callbacks().tls_generate_ephemeral_key(m_shared_group.value(), rng); - auto ecdh = dynamic_cast(m_kex_key.get()); - if(!ecdh) { - throw TLS_Exception(Alert::InternalError, "Application did not provide a EC-Diffie-Hellman key"); + m_kex_key = [&] { + if(m_shared_group->is_ecdh_named_curve()) { + const auto pubkey_point_format = state.client_hello()->prefers_compressed_ec_points() + ? EC_Point_Format::Compressed + : EC_Point_Format::Uncompressed; + return state.callbacks().tls12_generate_ephemeral_ecdh_key(*m_shared_group, rng, pubkey_point_format); + } else { + return state.callbacks().tls_generate_ephemeral_key(*m_shared_group, rng); } + }(); - // follow client's preference for point compression - ecdh_public_val = - ecdh->public_value(state.client_hello()->prefers_compressed_ec_points() ? EC_Point_Format::Compressed - : EC_Point_Format::Uncompressed); + if(!m_kex_key) { + throw TLS_Exception(Alert::InternalError, "Application did not provide an EC key"); } const uint16_t named_curve_id = m_shared_group.value().wire_code(); @@ -130,7 +120,10 @@ m_params.push_back(get_byte<0>(named_curve_id)); m_params.push_back(get_byte<1>(named_curve_id)); - append_tls_length_value(m_params, ecdh_public_val, 1); + // Note: In contrast to public_value(), raw_public_key_bits() takes the + // point format (compressed vs. uncompressed) into account that was set + // in its construction within tls_generate_ephemeral_key(). + append_tls_length_value(m_params, m_kex_key->raw_public_key_bits(), 1); } else if(kex_algo != Kex_Algo::PSK) { throw Internal_Error("Server_Key_Exchange: Unknown kex type " + kex_method_to_string(kex_algo)); } @@ -138,7 +131,8 @@ if(state.ciphersuite().signature_used()) { BOTAN_ASSERT(signing_key, "Signing key was set"); - std::pair format = state.choose_sig_format(*signing_key, m_scheme, false, policy); + const std::pair format = + state.choose_sig_format(*signing_key, m_scheme, false, policy); std::vector buf = state.client_hello()->random(); @@ -189,7 +183,9 @@ if(auth_method != Auth_Method::IMPLICIT) { m_scheme = Signature_Scheme(reader.get_uint16_t()); - m_signature = reader.get_range(2, 0, 65535); + // RFC 5246 4.7: digitally-signed signatures are opaque<1..2^16-1>. + // Matches the parallel check in Certificate_Verify. + m_signature = reader.get_range(2, 1, 65535); } reader.assert_done(); @@ -221,7 +217,7 @@ const Policy& policy) const { policy.check_peer_key_acceptable(server_key); - std::pair format = + const std::pair format = state.parse_sig_format(server_key, m_scheme, state.client_hello()->signature_schemes(), false, policy); std::vector buf = state.client_hello()->random(); diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/msg_session_ticket_12.cpp botan3-3.12.0+dfsg/src/lib/tls/tls12/msg_session_ticket_12.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls12/msg_session_ticket_12.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/msg_session_ticket_12.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,46 @@ +/* +* Session Tickets +* (C) 2012 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include + +namespace Botan::TLS { + +New_Session_Ticket_12::New_Session_Ticket_12(Handshake_IO& io, + Handshake_Hash& hash, + Session_Ticket ticket, + uint32_t lifetime) : + m_ticket_lifetime_hint(lifetime), m_ticket(std::move(ticket)) { + hash.update(io.send(*this)); +} + +New_Session_Ticket_12::New_Session_Ticket_12(Handshake_IO& io, Handshake_Hash& hash) { + hash.update(io.send(*this)); +} + +New_Session_Ticket_12::New_Session_Ticket_12(const std::vector& buf) { + if(buf.size() < 6) { + throw Decoding_Error("Session ticket message too short to be valid"); + } + + TLS_Data_Reader reader("SessionTicket", buf); + + m_ticket_lifetime_hint = reader.get_uint32_t(); + m_ticket = Session_Ticket(reader.get_range(2, 0, 65535)); + reader.assert_done(); +} + +std::vector New_Session_Ticket_12::serialize() const { + auto buf = store_be>(m_ticket_lifetime_hint); + append_tls_length_value(buf, m_ticket.get(), 2); + return buf; +} + +} // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_cbc/tls_cbc.cpp botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_cbc/tls_cbc.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_cbc/tls_cbc.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_cbc/tls_cbc.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -10,16 +10,20 @@ #include -#include - +#include +#include #include #include +#include +#include #include #include #include namespace Botan::TLS { +TLS_CBC_HMAC_AEAD_Mode::~TLS_CBC_HMAC_AEAD_Mode() = default; + /* * TLS_CBC_HMAC_AEAD_Mode Constructor */ @@ -28,21 +32,20 @@ std::unique_ptr mac, size_t cipher_keylen, size_t mac_keylen, - Protocol_Version version, + const Protocol_Version& version, bool use_encrypt_then_mac) : + m_mac(std::move(mac)), m_cipher_name(cipher->name()), - m_mac_name(mac->name()), + m_mac_name(m_mac->name()), m_cipher_keylen(cipher_keylen), + m_block_size(cipher->block_size()), + m_iv_size(m_block_size), m_mac_keylen(mac_keylen), - m_use_encrypt_then_mac(use_encrypt_then_mac) { - m_tag_size = mac->output_length(); - m_block_size = cipher->block_size(); - - m_iv_size = m_block_size; - - m_is_datagram = version.is_datagram_protocol(); - - m_mac = std::move(mac); + m_tag_size(m_mac->output_length()), + m_use_encrypt_then_mac(use_encrypt_then_mac), + m_is_datagram(version.is_datagram_protocol()) { + BOTAN_ASSERT_NOMSG(m_mac->valid_keylength(m_mac_keylen)); + BOTAN_ASSERT_NOMSG(cipher->valid_keylength(m_cipher_keylen)); auto null_padding = std::make_unique(); if(dir == Cipher_Dir::Encryption) { @@ -135,6 +138,20 @@ m_ad.assign(ad.begin(), ad.end()); } +TLS_CBC_HMAC_AEAD_Encryption::TLS_CBC_HMAC_AEAD_Encryption(std::unique_ptr cipher, + std::unique_ptr mac, + const size_t cipher_keylen, + const size_t mac_keylen, + const Protocol_Version& version, + bool use_encrypt_then_mac) : + TLS_CBC_HMAC_AEAD_Mode(Cipher_Dir::Encryption, + std::move(cipher), + std::move(mac), + cipher_keylen, + mac_keylen, + version, + use_encrypt_then_mac) {} + void TLS_CBC_HMAC_AEAD_Encryption::set_associated_data_n(size_t idx, std::span ad) { TLS_CBC_HMAC_AEAD_Mode::set_associated_data_n(idx, ad); @@ -262,6 +279,20 @@ return pad_invalid.if_not_set_return(pad_bytes); } +TLS_CBC_HMAC_AEAD_Decryption::TLS_CBC_HMAC_AEAD_Decryption(std::unique_ptr cipher, + std::unique_ptr mac, + const size_t cipher_keylen, + const size_t mac_keylen, + const Protocol_Version& version, + bool use_encrypt_then_mac) : + TLS_CBC_HMAC_AEAD_Mode(Cipher_Dir::Decryption, + std::move(cipher), + std::move(mac), + cipher_keylen, + mac_keylen, + version, + use_encrypt_then_mac) {} + void TLS_CBC_HMAC_AEAD_Decryption::cbc_decrypt_record(uint8_t record_contents[], size_t record_len) { if(record_len == 0 || record_len % block_size() != 0) { throw Decoding_Error("Received TLS CBC ciphertext with invalid length"); @@ -323,15 +354,10 @@ * */ void TLS_CBC_HMAC_AEAD_Decryption::perform_additional_compressions(size_t plen, size_t padlen) { - uint16_t block_size; - uint16_t max_bytes_in_first_block; - if(mac().name() == "HMAC(SHA-384)") { - block_size = 128; - max_bytes_in_first_block = 111; - } else { - block_size = 64; - max_bytes_in_first_block = 55; - } + const bool is_sha384 = mac().name() == "HMAC(SHA-384)"; + const uint16_t block_size = is_sha384 ? 128 : 64; + const uint16_t max_bytes_in_first_block = is_sha384 ? 111 : 55; + // number of maximum MACed bytes const uint16_t L1 = static_cast(13 + plen - tag_size()); // number of current MACed bytes (L1 - padlen) diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_cbc/tls_cbc.h botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_cbc/tls_cbc.h --- botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_cbc/tls_cbc.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_cbc/tls_cbc.h 2026-05-07 01:38:28.000000000 +0000 @@ -10,12 +10,18 @@ #define BOTAN_TLS_CBC_HMAC_AEAD_H_ #include -#include -#include -#include + +namespace Botan { + +class BlockCipher; +class MessageAuthenticationCode; + +} // namespace Botan namespace Botan::TLS { +class Protocol_Version; + /** * TLS CBC+HMAC AEAD base class (GenericBlockCipher in TLS spec) * This is the weird TLS-specific mode, not for general consumption. @@ -44,13 +50,21 @@ bool has_keying_material() const final; + ~TLS_CBC_HMAC_AEAD_Mode() override; + + TLS_CBC_HMAC_AEAD_Mode(const TLS_CBC_HMAC_AEAD_Mode& other) = delete; + TLS_CBC_HMAC_AEAD_Mode(TLS_CBC_HMAC_AEAD_Mode&& other) = delete; + + TLS_CBC_HMAC_AEAD_Mode& operator=(const TLS_CBC_HMAC_AEAD_Mode& other) = delete; + TLS_CBC_HMAC_AEAD_Mode& operator=(TLS_CBC_HMAC_AEAD_Mode&& other) = delete; + protected: TLS_CBC_HMAC_AEAD_Mode(Cipher_Dir direction, std::unique_ptr cipher, std::unique_ptr mac, size_t cipher_keylen, size_t mac_keylen, - Protocol_Version version, + const Protocol_Version& version, bool use_encrypt_then_mac); size_t cipher_keylen() const { return m_cipher_keylen; } @@ -67,10 +81,7 @@ Cipher_Mode& cbc() const { return *m_cbc; } - MessageAuthenticationCode& mac() const { - BOTAN_ASSERT_NONNULL(m_mac); - return *m_mac; - } + MessageAuthenticationCode& mac() const { return *m_mac; } secure_vector& cbc_state() { return m_cbc_state; } @@ -86,19 +97,19 @@ void key_schedule(std::span key) final; + std::unique_ptr m_cbc; + std::unique_ptr m_mac; + const std::string m_cipher_name; const std::string m_mac_name; size_t m_cipher_keylen; - size_t m_mac_keylen; + size_t m_block_size; size_t m_iv_size; + size_t m_mac_keylen; size_t m_tag_size; - size_t m_block_size; bool m_use_encrypt_then_mac; bool m_is_datagram; - std::unique_ptr m_cbc; - std::unique_ptr m_mac; - secure_vector m_cbc_state; std::vector m_ad; secure_vector m_msg; @@ -113,17 +124,10 @@ */ TLS_CBC_HMAC_AEAD_Encryption(std::unique_ptr cipher, std::unique_ptr mac, - const size_t cipher_keylen, - const size_t mac_keylen, - const Protocol_Version version, - bool use_encrypt_then_mac) : - TLS_CBC_HMAC_AEAD_Mode(Cipher_Dir::Encryption, - std::move(cipher), - std::move(mac), - cipher_keylen, - mac_keylen, - version, - use_encrypt_then_mac) {} + size_t cipher_keylen, + size_t mac_keylen, + const Protocol_Version& version, + bool use_encrypt_then_mac); void set_associated_data_n(size_t idx, std::span ad) override; @@ -145,17 +149,10 @@ */ TLS_CBC_HMAC_AEAD_Decryption(std::unique_ptr cipher, std::unique_ptr mac, - const size_t cipher_keylen, - const size_t mac_keylen, - const Protocol_Version version, - bool use_encrypt_then_mac) : - TLS_CBC_HMAC_AEAD_Mode(Cipher_Dir::Decryption, - std::move(cipher), - std::move(mac), - cipher_keylen, - mac_keylen, - version, - use_encrypt_then_mac) {} + size_t cipher_keylen, + size_t mac_keylen, + const Protocol_Version& version, + bool use_encrypt_then_mac); size_t output_length(size_t input_length) const override; diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_channel_impl_12.cpp botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_channel_impl_12.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_channel_impl_12.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_channel_impl_12.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,10 +9,14 @@ #include #include -#include +#include +#include #include #include +#include +#include #include +#include #include #include #include @@ -66,7 +70,7 @@ m_read_cipher_states[0] = nullptr; if(m_sequence_numbers) { - m_sequence_numbers->reset(); + m_sequence_numbers->reset(); // NOLINT(*-ambiguous-smartptr-reset-call) } } @@ -94,28 +98,29 @@ } std::vector Channel_Impl_12::peer_cert_chain() const { - if(auto active = active_state()) { - return get_peer_cert_chain(*active); + if(m_active_state.has_value()) { + return m_active_state->peer_certs(); } return std::vector(); } std::optional Channel_Impl_12::external_psk_identity() const { - const auto* state = (active_state() != nullptr) ? active_state() : pending_state(); - if(state) { + if(m_active_state.has_value()) { + return m_active_state->psk_identity(); + } + if(const auto* state = pending_state()) { return state->psk_identity(); - } else { - return std::nullopt; } + return std::nullopt; } Handshake_State& Channel_Impl_12::create_handshake_state(Protocol_Version version) { - if(pending_state()) { + if(pending_state() != nullptr) { throw Internal_Error("create_handshake_state called during handshake"); } - if(auto active = active_state()) { - Protocol_Version active_version = active->version(); + if(m_active_state.has_value()) { + const Protocol_Version active_version = m_active_state->version(); if(active_version.is_datagram_protocol() != version.is_datagram_protocol()) { throw TLS_Exception(Alert::ProtocolVersion, @@ -136,20 +141,30 @@ std::unique_ptr io; if(version.is_datagram_protocol()) { - io = - std::make_unique(std::bind(&Channel_Impl_12::send_record_under_epoch, this, _1, _2, _3), - sequence_numbers(), - static_cast(policy().dtls_default_mtu()), - policy().dtls_initial_timeout(), - policy().dtls_maximum_timeout()); + const uint16_t mtu = static_cast(policy().dtls_default_mtu()); + const size_t initial_timeout_ms = policy().dtls_initial_timeout(); + const size_t max_timeout_ms = policy().dtls_maximum_timeout(); + + auto send_record_f = [this](uint16_t epoch, Record_Type record_type, const std::vector& record) { + send_record_under_epoch(epoch, record_type, record); + }; + io = std::make_unique(send_record_f, + sequence_numbers(), + mtu, + initial_timeout_ms, + max_timeout_ms, + policy().maximum_handshake_message_size()); } else { - io = std::make_unique(std::bind(&Channel_Impl_12::send_record, this, _1, _2)); + auto send_record_f = [this](Record_Type rec_type, const std::vector& record) { + send_record(rec_type, record); + }; + io = std::make_unique(send_record_f); } m_pending_state = new_handshake_state(std::move(io)); - if(auto active = active_state()) { - m_pending_state->set_version(active->version()); + if(m_active_state.has_value()) { + m_pending_state->set_version(m_active_state->version()); } return *m_pending_state; @@ -165,27 +180,27 @@ } void Channel_Impl_12::renegotiate(bool force_full_renegotiation) { - if(pending_state()) { // currently in handshake? + if(pending_state() != nullptr) { // currently in handshake? return; } - if(auto active = active_state()) { - if(force_full_renegotiation == false) { + if(m_active_state.has_value()) { + if(!force_full_renegotiation) { force_full_renegotiation = !policy().allow_resumption_for_renegotiation(); } - initiate_handshake(create_handshake_state(active->version()), force_full_renegotiation); + initiate_handshake(create_handshake_state(m_active_state->version()), force_full_renegotiation); } else { throw Invalid_State("Cannot renegotiate on inactive connection"); } } -void Channel_Impl_12::update_traffic_keys(bool) { +void Channel_Impl_12::update_traffic_keys(bool /*update_requested*/) { throw Invalid_Argument("cannot update traffic keys on a TLS 1.2 channel"); } void Channel_Impl_12::change_cipher_spec_reader(Connection_Side side) { - auto pending = pending_state(); + const auto* pending = pending_state(); BOTAN_ASSERT(pending && pending->server_hello(), "Have received server hello"); @@ -200,19 +215,19 @@ BOTAN_ASSERT(!m_read_cipher_states.contains(epoch), "No read cipher state currently set for next epoch"); // flip side as we are reading - std::shared_ptr read_state( - new Connection_Cipher_State(pending->version(), - (side == Connection_Side::Client) ? Connection_Side::Server : Connection_Side::Client, - false, - pending->ciphersuite(), - pending->session_keys(), - pending->server_hello()->supports_encrypt_then_mac())); + auto read_state = std::make_shared( + pending->version(), + (side == Connection_Side::Client) ? Connection_Side::Server : Connection_Side::Client, + false, + pending->ciphersuite(), + pending->session_keys(), + pending->server_hello()->supports_encrypt_then_mac()); m_read_cipher_states[epoch] = read_state; } void Channel_Impl_12::change_cipher_spec_writer(Connection_Side side) { - auto pending = pending_state(); + const auto* pending = pending_state(); BOTAN_ASSERT(pending && pending->server_hello(), "Have received server hello"); @@ -226,19 +241,18 @@ BOTAN_ASSERT(!m_write_cipher_states.contains(epoch), "No write cipher state currently set for next epoch"); - std::shared_ptr write_state( - new Connection_Cipher_State(pending->version(), - side, - true, - pending->ciphersuite(), - pending->session_keys(), - pending->server_hello()->supports_encrypt_then_mac())); + auto write_state = std::make_shared(pending->version(), + side, + true, + pending->ciphersuite(), + pending->session_keys(), + pending->server_hello()->supports_encrypt_then_mac()); m_write_cipher_states[epoch] = write_state; } bool Channel_Impl_12::is_handshake_complete() const { - return (active_state() != nullptr); + return m_active_state.has_value(); } bool Channel_Impl_12::is_active() const { @@ -250,10 +264,11 @@ } void Channel_Impl_12::activate_session() { - std::swap(m_active_state, m_pending_state); - m_pending_state.reset(); + BOTAN_ASSERT_NONNULL(m_pending_state); + + const auto& state = *m_pending_state; - if(!m_active_state->version().is_datagram_protocol()) { + if(!state.version().is_datagram_protocol()) { // TLS is easy just remove all but the current state const uint16_t current_epoch = sequence_numbers().current_write_epoch(); @@ -263,17 +278,26 @@ map_remove_if(not_current_epoch, m_read_cipher_states); } + // For DTLS, keep the handshake IO for last-flight retransmission. + if(m_is_datagram) { + m_active_state = Active_Connection_State_12(state, application_protocol(), m_pending_state->take_handshake_io()); + } else { + m_active_state = Active_Connection_State_12(state, application_protocol()); + } + + m_pending_state.reset(); + callbacks().tls_session_activated(); } size_t Channel_Impl_12::from_peer(std::span data) { const bool allow_epoch0_restart = m_is_datagram && m_is_server && policy().allow_dtls_epoch0_restart(); - auto input = data.data(); + const auto* input = data.data(); auto input_size = data.size(); try { - while(input_size) { + while(input_size > 0) { size_t consumed = 0; auto get_epoch = [this](uint16_t epoch) { return read_cipher_state_epoch(epoch); }; @@ -312,13 +336,13 @@ throw TLS_Exception(Alert::RecordOverflow, "TLS plaintext record is larger than allowed maximum"); } - const bool epoch0_restart = m_is_datagram && record.epoch() == 0 && active_state(); + const bool epoch0_restart = m_is_datagram && record.epoch() == 0 && m_active_state.has_value(); BOTAN_ASSERT_IMPLICATION(epoch0_restart, allow_epoch0_restart, "Allowed state"); - const bool initial_record = epoch0_restart || (!pending_state() && !active_state()); + const bool initial_record = epoch0_restart || (pending_state() == nullptr && !m_active_state.has_value()); bool initial_handshake_message = false; if(record.type() == Record_Type::Handshake && !m_record_buf.empty()) { - Handshake_Type type = static_cast(m_record_buf[0]); + const Handshake_Type type = static_cast(m_record_buf[0]); initial_handshake_message = (type == Handshake_Type::ClientHello); } @@ -328,13 +352,13 @@ if(record.version().major_version() != 3 && record.version().major_version() != 0xFE) { throw TLS_Exception(Alert::ProtocolVersion, "Received unexpected record version in initial record"); } - } else if(auto pending = pending_state()) { + } else if(const auto* pending = pending_state()) { if(pending->server_hello() != nullptr && !initial_handshake_message && record.version() != pending->version()) { throw TLS_Exception(Alert::ProtocolVersion, "Received unexpected record version"); } - } else if(auto active = active_state()) { - if(record.version() != active->version() && !initial_handshake_message) { + } else if(m_active_state.has_value()) { + if(record.version() != m_active_state->version() && !initial_handshake_message) { throw TLS_Exception(Alert::ProtocolVersion, "Received unexpected record version"); } } @@ -394,11 +418,14 @@ const uint16_t epoch = record_sequence >> 48; - if(epoch == sequence_numbers().current_read_epoch()) { + const uint16_t current_epoch = sequence_numbers().current_read_epoch(); + if(epoch == current_epoch) { create_handshake_state(record_version); - } else if(epoch == sequence_numbers().current_read_epoch() - 1) { - BOTAN_ASSERT(m_active_state, "Have active state here"); - m_active_state->handshake_io().add_record(record.data(), record.size(), record_type, record_sequence); + } else if(current_epoch > 0 && epoch == current_epoch - 1) { + BOTAN_ASSERT(m_active_state.has_value() && m_active_state->dtls_handshake_io(), + "Have DTLS handshake IO for retransmission"); + m_active_state->dtls_handshake_io()->add_record( + record.data(), record.size(), record_type, record_sequence); } } else { create_handshake_state(record_version); @@ -412,14 +439,14 @@ if(m_pending_state) { m_pending_state->handshake_io().add_record(record.data(), record.size(), record_type, record_sequence); - while(auto pending = m_pending_state.get()) { - auto msg = pending->get_next_handshake_msg(); + while(auto* pending = m_pending_state.get()) { + auto msg = pending->get_next_handshake_msg(policy().maximum_handshake_message_size()); if(msg.first == Handshake_Type::None) { // no full handshake yet break; } - process_handshake_msg(active_state(), *pending, msg.first, msg.second, epoch0_restart); + process_handshake_msg(*pending, msg.first, msg.second, epoch0_restart); if(!m_pending_state) { break; @@ -429,28 +456,38 @@ } void Channel_Impl_12::process_application_data(uint64_t seq_no, const secure_vector& record) { - if(!active_state()) { + if(!m_active_state.has_value()) { throw Unexpected_Message("Application data before handshake done"); } + // ApplicationData must arrive under a non-zero read epoch + const uint16_t read_epoch = + m_is_datagram ? static_cast(seq_no >> 48) : sequence_numbers().current_read_epoch(); + if(read_epoch == 0) { + throw Unexpected_Message("Application data received in unexpected read epoch"); + } + callbacks().tls_record_received(seq_no, record); } void Channel_Impl_12::process_alert(const secure_vector& record) { - Alert alert_msg(record); + const Alert alert_msg(record); - if(alert_msg.type() == Alert::NoRenegotiation) { + // RFC 5246 7.2.2: + // no_renegotiation + // Sent by the client in response to a hello request or by the + // server in response to a client hello after initial handshaking. + if(alert_msg.type() == Alert::NoRenegotiation && m_active_state.has_value()) { m_pending_state.reset(); } callbacks().tls_alert(alert_msg); - if(alert_msg.is_fatal()) { - if(auto active = active_state()) { - const auto& session_id = active->server_hello()->session_id(); - if(!session_id.empty()) { - session_manager().remove(session_id); - } + // If the alert is fatal on an active session, prevent later resumptions + if(alert_msg.is_fatal() && m_active_state.has_value()) { + const auto& sid = m_active_state->session_id(); + if(!sid.empty()) { + session_manager().remove(Session_Handle(sid)); } } @@ -471,10 +508,9 @@ Record_Type record_type, const uint8_t input[], size_t length) { - BOTAN_ASSERT(m_pending_state || m_active_state, "Some connection state exists"); + BOTAN_ASSERT(m_pending_state || m_active_state.has_value(), "Some connection state exists"); - const Protocol_Version record_version = - (m_pending_state) ? (m_pending_state->version()) : (m_active_state->version()); + const Protocol_Version record_version = (m_pending_state) ? (m_pending_state->version()) : m_active_state->version(); const uint64_t next_seq = sequence_numbers().next_write_sequence(epoch); @@ -494,7 +530,7 @@ auto cipher_state = write_cipher_state_epoch(epoch); - while(length) { + while(length > 0) { const size_t sending = std::min(length, MAX_PLAINTEXT_SIZE); write_record(cipher_state.get(), epoch, type, input, sending); @@ -530,15 +566,15 @@ } } - if(alert.type() == Alert::NoRenegotiation) { + if(alert.type() == Alert::NoRenegotiation && m_active_state.has_value()) { m_pending_state.reset(); } if(alert.is_fatal()) { - if(auto active = active_state()) { - const auto& session_id = active->server_hello()->session_id(); - if(!session_id.empty()) { - session_manager().remove(Session_ID(session_id)); + if(m_active_state.has_value()) { + const auto& sid = m_active_state->session_id(); + if(!sid.empty()) { + session_manager().remove(Session_Handle(sid)); } } reset_state(); @@ -550,69 +586,63 @@ } void Channel_Impl_12::secure_renegotiation_check(const Client_Hello_12* client_hello) { + BOTAN_ASSERT_NONNULL(client_hello); const bool secure_renegotiation = client_hello->secure_renegotiation(); - if(auto active = active_state()) { - const bool active_sr = active->client_hello()->secure_renegotiation(); - - if(active_sr != secure_renegotiation) { - throw TLS_Exception(Alert::HandshakeFailure, "Client changed its mind about secure renegotiation"); - } + if(m_active_state && m_active_state->client_supports_secure_renegotiation() != secure_renegotiation) { + throw TLS_Exception(Alert::HandshakeFailure, "Client changed its mind about secure renegotiation"); } if(secure_renegotiation) { const std::vector& data = client_hello->renegotiation_info(); - if(data != secure_renegotiation_data_for_client_hello()) { + const auto expected = secure_renegotiation_data_for_client_hello(); + if(!CT::is_equal(data, expected).as_bool()) { throw TLS_Exception(Alert::HandshakeFailure, "Client sent bad values for secure renegotiation"); } } } void Channel_Impl_12::secure_renegotiation_check(const Server_Hello_12* server_hello) { + BOTAN_ASSERT_NONNULL(server_hello); const bool secure_renegotiation = server_hello->secure_renegotiation(); - if(auto active = active_state()) { - const bool active_sr = active->server_hello()->secure_renegotiation(); - - if(active_sr != secure_renegotiation) { - throw TLS_Exception(Alert::HandshakeFailure, "Server changed its mind about secure renegotiation"); - } + if(m_active_state && m_active_state->server_supports_secure_renegotiation() != secure_renegotiation) { + throw TLS_Exception(Alert::HandshakeFailure, "Server changed its mind about secure renegotiation"); } if(secure_renegotiation) { const std::vector& data = server_hello->renegotiation_info(); - if(data != secure_renegotiation_data_for_server_hello()) { + const auto expected = secure_renegotiation_data_for_server_hello(); + if(!CT::is_equal(data, expected).as_bool()) { throw TLS_Exception(Alert::HandshakeFailure, "Server sent bad values for secure renegotiation"); } } } std::vector Channel_Impl_12::secure_renegotiation_data_for_client_hello() const { - if(auto active = active_state()) { - return active->client_finished()->verify_data(); + if(m_active_state.has_value()) { + return m_active_state->client_finished_verify_data(); } return std::vector(); } std::vector Channel_Impl_12::secure_renegotiation_data_for_server_hello() const { - if(auto active = active_state()) { - std::vector buf = active->client_finished()->verify_data(); - buf += active->server_finished()->verify_data(); - return buf; + if(m_active_state.has_value()) { + return concat(m_active_state->client_finished_verify_data(), m_active_state->server_finished_verify_data()); + } else { + return {}; } - - return std::vector(); } bool Channel_Impl_12::secure_renegotiation_supported() const { - if(auto active = active_state()) { - return active->server_hello()->secure_renegotiation(); + if(m_active_state.has_value()) { + return m_active_state->server_supports_secure_renegotiation(); } - if(auto pending = pending_state()) { - if(auto hello = pending->server_hello()) { + if(const auto* pending = pending_state()) { + if(const auto* hello = pending->server_hello()) { return hello->secure_renegotiation(); } } @@ -623,33 +653,28 @@ SymmetricKey Channel_Impl_12::key_material_export(std::string_view label, std::string_view context, size_t length) const { - if(auto active = active_state()) { - if(pending_state() != nullptr) { - throw Invalid_State("Channel_Impl_12::key_material_export cannot export during renegotiation"); - } - - auto prf = active->protocol_specific_prf(); + if(!m_active_state.has_value()) { + throw Invalid_State("Channel_Impl_12::key_material_export connection not active"); + } - const secure_vector& master_secret = active->session_keys().master_secret(); + if(pending_state() != nullptr) { + throw Invalid_State("Channel_Impl_12::key_material_export cannot export during renegotiation"); + } - std::vector salt; - salt += active->client_hello()->random(); - salt += active->server_hello()->random(); + auto prf = callbacks().tls12_protocol_specific_kdf(m_active_state->prf_algo()); - if(!context.empty()) { - size_t context_size = context.length(); - if(context_size > 0xFFFF) { - throw Invalid_Argument("key_material_export context is too long"); - } - salt.push_back(get_byte<0>(static_cast(context_size))); - salt.push_back(get_byte<1>(static_cast(context_size))); - salt += to_byte_vector(context); + const auto salt = [&] { + if(context.empty()) { + return concat(m_active_state->client_random(), m_active_state->server_random()); + } else { + return concat(m_active_state->client_random(), + m_active_state->server_random(), + store_be(static_cast(context.size())), + as_span_of_bytes(context)); } + }(); - return SymmetricKey(prf->derive_key(length, master_secret, salt, to_byte_vector(label))); - } else { - throw Invalid_State("Channel_Impl_12::key_material_export connection not active"); - } + return SymmetricKey(prf->derive_key(length, m_active_state->master_secret(), salt, as_span_of_bytes(label))); } } // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_channel_impl_12.h botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_channel_impl_12.h --- botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_channel_impl_12.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_channel_impl_12.h 2026-05-07 01:38:28.000000000 +0000 @@ -10,11 +10,9 @@ #define BOTAN_TLS_CHANNEL_IMPL_12_H_ #include -#include -#include #include #include -#include +#include #include #include #include @@ -26,6 +24,7 @@ namespace TLS { +class Callbacks; class Connection_Cipher_State; class Connection_Sequence_Numbers; class Handshake_State; @@ -39,12 +38,6 @@ */ class Channel_Impl_12 : public Channel_Impl { public: - typedef std::function output_fn; - typedef std::function data_cb; - typedef std::function alert_cb; - typedef std::function handshake_cb; - typedef std::function handshake_msg_cb; - /** * Set up a new TLS session * @@ -67,9 +60,10 @@ bool is_datagram, size_t io_buf_sz = TLS::Channel::IO_BUF_DEFAULT_SIZE); - explicit Channel_Impl_12(const Channel_Impl_12&) = delete; - - Channel_Impl_12& operator=(const Channel_Impl_12&) = delete; + Channel_Impl_12(const Channel_Impl_12& other) = delete; + Channel_Impl_12(Channel_Impl_12&& other) = delete; + Channel_Impl_12& operator=(const Channel_Impl_12& other) = delete; + Channel_Impl_12& operator=(Channel_Impl_12&& other) = delete; ~Channel_Impl_12() override; @@ -156,8 +150,9 @@ bool timeout_check() override; protected: - virtual void process_handshake_msg(const Handshake_State* active_state, - Handshake_State& pending_state, + const std::optional& active_state() const { return m_active_state; } + + virtual void process_handshake_msg(Handshake_State& pending_state, Handshake_Type type, const std::vector& contents, bool epoch0_restart) = 0; @@ -193,8 +188,6 @@ virtual void initiate_handshake(Handshake_State& state, bool force_full_renegotiation) = 0; - virtual std::vector get_peer_cert_chain(const Handshake_State& state) const = 0; - private: void send_record(Record_Type record_type, const std::vector& record); @@ -213,8 +206,6 @@ std::shared_ptr write_cipher_state_epoch(uint16_t epoch) const; - const Handshake_State* active_state() const { return m_active_state.get(); } - const Handshake_State* pending_state() const { return m_pending_state.get(); } /* methods to handle incoming traffic through Channel_Impl_12::receive_data. */ @@ -242,8 +233,7 @@ /* sequence number state */ std::unique_ptr m_sequence_numbers; - /* pending and active connection states */ - std::unique_ptr m_active_state; + /* pending handshake state (null when no handshake is in progress) */ std::unique_ptr m_pending_state; /* cipher states for each epoch */ @@ -256,6 +246,8 @@ secure_vector m_record_buf; bool m_has_been_closed; + + std::optional m_active_state; }; } // namespace TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_client_impl_12.cpp botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_client_impl_12.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_client_impl_12.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_client_impl_12.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -10,11 +10,12 @@ #include #include -#include -#include +#include +#include +#include #include #include - +#include #include #include #include @@ -68,6 +69,21 @@ return m_resumed_session->supports_extended_master_secret(); } + uint16_t resumed_session_ciphersuite_code() const { + BOTAN_STATE_CHECK(is_a_resumption()); + return m_resumed_session->ciphersuite_code(); + } + + std::vector peer_cert_chain() const override { + if(is_a_resumption()) { + return resume_peer_certs(); + } + if(server_certs() != nullptr) { + return server_certs()->cert_chain(); + } + return {}; + } + private: std::unique_ptr m_server_public_key; @@ -114,11 +130,11 @@ if(!downgrade_info.client_hello_message.empty()) { // Downgrade detected after receiving a TLS 1.2 server hello. We need to // recreate the state as if this implementation issued the client hello. - std::vector client_hello_msg( + const std::vector client_hello_msg( downgrade_info.client_hello_message.begin() + 4 /* handshake header length */, downgrade_info.client_hello_message.end()); - state.client_hello(new Client_Hello_12(client_hello_msg)); + state.client_hello(std::make_unique(client_hello_msg)); state.hash().update(downgrade_info.client_hello_message); secure_renegotiation_check(state.client_hello()); @@ -140,19 +156,6 @@ return std::make_unique(std::move(io), callbacks()); } -std::vector Client_Impl_12::get_peer_cert_chain(const Handshake_State& state) const { - const Client_Handshake_State_12& cstate = dynamic_cast(state); - - if(cstate.is_a_resumption()) { - return cstate.resume_peer_certs(); - } - - if(state.server_certs()) { - return state.server_certs()->cert_chain(); - } - return std::vector(); -} - /* * Send a new client hello to renegotiate */ @@ -195,31 +198,31 @@ const bool session_version_ok = policy().only_resume_with_exact_version() ? exact_version : ok_version; if(policy().acceptable_ciphersuite(session_info.ciphersuite()) && session_version_ok) { - state.client_hello(new Client_Hello_12(state.handshake_io(), - state.hash(), - policy(), - callbacks(), - rng(), - secure_renegotiation_data_for_client_hello(), - session_and_handle.value(), - next_protocols)); + state.client_hello(std::make_unique(state.handshake_io(), + state.hash(), + policy(), + callbacks(), + rng(), + secure_renegotiation_data_for_client_hello(), + session_and_handle.value(), + next_protocols)); state.record_resumption_info(std::move(session_info)); } } } - if(!state.client_hello()) { + if(state.client_hello() == nullptr) { // not resuming - Client_Hello_12::Settings client_settings(version, m_info.hostname()); - state.client_hello(new Client_Hello_12(state.handshake_io(), - state.hash(), - policy(), - callbacks(), - rng(), - secure_renegotiation_data_for_client_hello(), - client_settings, - next_protocols)); + const Client_Hello_12::Settings client_settings(version, m_info.hostname()); + state.client_hello(std::make_unique(state.handshake_io(), + state.hash(), + policy(), + callbacks(), + rng(), + secure_renegotiation_data_for_client_hello(), + client_settings, + next_protocols)); } secure_renegotiation_check(state.client_hello()); @@ -244,8 +247,7 @@ /* * Process a handshake message */ -void Client_Impl_12::process_handshake_msg(const Handshake_State* active_state, - Handshake_State& state_base, +void Client_Impl_12::process_handshake_msg(Handshake_State& state_base, Handshake_Type type, const std::vector& contents, bool epoch0_restart) { @@ -253,10 +255,10 @@ Client_Handshake_State_12& state = dynamic_cast(state_base); - if(type == Handshake_Type::HelloRequest && active_state) { - Hello_Request hello_request(contents); + if(type == Handshake_Type::HelloRequest && active_state().has_value()) { + const Hello_Request hello_request(contents); - if(state.client_hello()) { + if(state.client_hello() != nullptr) { throw TLS_Exception(Alert::HandshakeFailure, "Cannot renegotiate during a handshake"); } @@ -290,10 +292,10 @@ state.set_expected_next(Handshake_Type::ServerHello); state.set_expected_next(Handshake_Type::HelloVerifyRequest); // might get it again - Hello_Verify_Request hello_verify_request(contents); + const Hello_Verify_Request hello_verify_request(contents); state.hello_verify_request(hello_verify_request); } else if(type == Handshake_Type::ServerHello) { - state.server_hello(new Server_Hello_12(contents)); + state.server_hello(std::make_unique(contents)); if(!state.server_hello()->legacy_version().valid()) { throw TLS_Exception(Alert::ProtocolVersion, "Server replied with an invalid version"); @@ -362,7 +364,7 @@ throw TLS_Exception(Alert::UnsupportedExtension, msg.str()); } - if(uint16_t srtp = state.server_hello()->srtp_profile()) { + if(const uint16_t srtp = state.server_hello()->srtp_profile()) { if(!value_exists(state.client_hello()->srtp_profiles(), srtp)) { throw TLS_Exception(Alert::HandshakeFailure, "Server replied with DTLS-SRTP alg we did not send"); } @@ -372,10 +374,27 @@ state.server_hello()->extensions(), Connection_Side::Server, Handshake_Type::ServerHello); state.set_version(state.server_hello()->legacy_version()); + + if(state.server_hello()->extensions().has()) { + const auto* server_alpn = state.server_hello()->extensions().get(); + const auto selected = server_alpn->single_protocol(); + const auto* client_alpn = state.client_hello()->extensions().get(); + BOTAN_ASSERT_NONNULL(client_alpn); + const auto& offered = client_alpn->protocols(); + if(!value_exists(offered, selected)) { + throw TLS_Exception(Alert::IllegalParameter, "Server selected an ALPN protocol not offered by the client"); + } + } m_application_protocol = state.server_hello()->next_protocol(); secure_renegotiation_check(state.server_hello()); + // RFC 7627 / RFC 9325 4.4: optionally require Extended Master Secret. + if(policy().require_extended_master_secret() && !state.server_hello()->supports_extended_master_secret()) { + throw TLS_Exception(Alert::HandshakeFailure, + "Policy requires the Extended Master Secret extension but the server did not send it"); + } + const bool server_returned_same_session_id = !state.server_hello()->session_id().empty() && (state.server_hello()->session_id() == state.client_hello()->session_id()); @@ -392,6 +411,12 @@ throw TLS_Exception(Alert::HandshakeFailure, "Server resumed session but with wrong version"); } + // RFC 5246 7.4.1.2: when resuming a session, the server MUST use + // the same cipher suite that was negotiated in the original session. + if(state.server_hello()->ciphersuite() != state.resumed_session_ciphersuite_code()) { + throw TLS_Exception(Alert::HandshakeFailure, "Server resumed session with a different ciphersuite"); + } + if(state.server_hello()->supports_extended_master_secret() && !state.resumed_session_supports_extended_master_secret()) { throw TLS_Exception(Alert::HandshakeFailure, "Server resumed session but added extended master secret"); @@ -420,17 +445,17 @@ } else { // new session - if(active_state) { - // Here we are testing things that should not change during a renegotation, - // even if the server creates a new session. Howerver they might change + if(active_state().has_value()) { + // Here we are testing things that should not change during a renegotiation, + // even if the server creates a new session. However they might change // in a resumption scenario. - if(active_state->version() != state.server_hello()->legacy_version()) { + if(active_state()->version() != state.server_hello()->legacy_version()) { throw TLS_Exception(Alert::ProtocolVersion, "Server changed version after renegotiation"); } if(state.server_hello()->supports_extended_master_secret() != - active_state->server_hello()->supports_extended_master_secret()) { + active_state()->supports_extended_master_secret()) { throw TLS_Exception(Alert::HandshakeFailure, "Server changed its mind about extended master secret"); } } @@ -455,7 +480,7 @@ "Server version " + state.version().to_string() + " is unacceptable by policy"); } - if(state.ciphersuite().signature_used() || state.ciphersuite().kex_method() == Kex_Algo::STATIC_RSA) { + if(state.ciphersuite().is_certificate_required()) { state.set_expected_next(Handshake_Type::Certificate); } else if(state.ciphersuite().kex_method() == Kex_Algo::PSK) { /* PSK is anonymous so no certificate/cert req message is @@ -468,15 +493,17 @@ state.set_expected_next(Handshake_Type::ServerKeyExchange); state.set_expected_next(Handshake_Type::ServerHelloDone); - } else if(state.ciphersuite().kex_method() != Kex_Algo::STATIC_RSA) { - state.set_expected_next(Handshake_Type::ServerKeyExchange); } else { - state.set_expected_next(Handshake_Type::CertificateRequest); // optional - state.set_expected_next(Handshake_Type::ServerHelloDone); + // ECDHE_PSK ServerKeyExchange carries the ECDH parameters and + // immediately follows ServerHello. + // + // Suites using RSA key exchange or signature-authenticated ECDH + // were already routed to expect Certificate above. + state.set_expected_next(Handshake_Type::ServerKeyExchange); } } } else if(type == Handshake_Type::Certificate) { - state.server_certs(new Certificate_12(contents, policy())); + state.server_certs(std::make_unique(contents, policy())); const std::vector& server_certs = state.server_certs()->cert_chain(); @@ -490,10 +517,10 @@ in case an OCSP response was also available */ - X509_Certificate server_cert = server_certs[0]; + const X509_Certificate server_cert = server_certs[0]; - if(active_state && active_state->server_certs()) { - X509_Certificate current_cert = active_state->server_certs()->cert_chain().at(0); + if(active_state().has_value() && !active_state()->peer_certs().empty()) { + const X509_Certificate& current_cert = active_state()->peer_certs().at(0); if(current_cert != server_cert) { throw TLS_Exception(Alert::BadCertificate, "Server certificate changed during renegotiation"); @@ -537,7 +564,7 @@ } } } else if(type == Handshake_Type::CertificateStatus) { - state.server_cert_status(new Certificate_Status(contents, Connection_Side::Server)); + state.server_cert_status(std::make_unique(contents, Connection_Side::Server)); if(state.ciphersuite().kex_method() != Kex_Algo::STATIC_RSA) { state.set_expected_next(Handshake_Type::ServerKeyExchange); @@ -546,12 +573,12 @@ state.set_expected_next(Handshake_Type::ServerHelloDone); } } else if(type == Handshake_Type::ServerKeyExchange) { - if(state.ciphersuite().psk_ciphersuite() == false) { + if(!state.ciphersuite().psk_ciphersuite()) { state.set_expected_next(Handshake_Type::CertificateRequest); // optional } state.set_expected_next(Handshake_Type::ServerHelloDone); - state.server_kex(new Server_Key_Exchange( + state.server_kex(std::make_unique( contents, state.ciphersuite().kex_method(), state.ciphersuite().auth_method(), state.version())); if(state.ciphersuite().signature_used()) { @@ -563,9 +590,9 @@ } } else if(type == Handshake_Type::CertificateRequest) { state.set_expected_next(Handshake_Type::ServerHelloDone); - state.cert_req(new Certificate_Request_12(contents)); + state.cert_req(std::make_unique(contents)); } else if(type == Handshake_Type::ServerHelloDone) { - state.server_hello_done(new Server_Hello_Done(contents)); + state.server_hello_done(std::make_unique(contents)); if(state.handshake_io().have_more_data()) { throw TLS_Exception(Alert::UnexpectedMessage, "Have data remaining in buffer after ServerHelloDone"); @@ -596,13 +623,13 @@ if(state.received_handshake_msg(Handshake_Type::CertificateRequest)) { const auto& types = state.cert_req()->acceptable_cert_types(); - std::vector client_certs = + const std::vector client_certs = m_creds->find_cert_chain(types, {}, state.cert_req()->acceptable_CAs(), "tls-client", m_info.hostname()); - state.client_certs(new Certificate_12(state.handshake_io(), state.hash(), client_certs)); + state.client_certs(std::make_unique(state.handshake_io(), state.hash(), client_certs)); } - state.client_kex(new Client_Key_Exchange( + state.client_kex(std::make_unique( state.handshake_io(), state, policy(), *m_creds, state.maybe_server_public_key(), m_info.hostname(), rng())); state.compute_session_keys(); @@ -624,14 +651,14 @@ } state.client_verify( - new Certificate_Verify_12(state.handshake_io(), state, policy(), rng(), private_key.get())); + std::make_unique(state.handshake_io(), state, policy(), rng(), private_key.get())); } state.handshake_io().send(Change_Cipher_Spec()); change_cipher_spec_writer(Connection_Side::Client); - state.client_finished(new Finished_12(state.handshake_io(), state, Connection_Side::Client)); + state.client_finished(std::make_unique(state.handshake_io(), state, Connection_Side::Client)); if(state.server_hello()->supports_session_ticket()) { state.set_expected_next(Handshake_Type::NewSessionTicket); @@ -639,7 +666,7 @@ state.set_expected_next(Handshake_Type::HandshakeCCS); } } else if(type == Handshake_Type::NewSessionTicket) { - state.new_session_ticket(new New_Session_Ticket_12(contents)); + state.new_session_ticket(std::make_unique(contents)); state.set_expected_next(Handshake_Type::HandshakeCCS); } else if(type == Handshake_Type::HandshakeCCS) { @@ -651,7 +678,7 @@ throw TLS_Exception(Alert::UnexpectedMessage, "Have data remaining in buffer after Finished"); } - state.server_finished(new Finished_12(contents)); + state.server_finished(std::make_unique(contents)); if(!state.server_finished()->verify(state, Connection_Side::Server)) { throw TLS_Exception(Alert::DecryptError, "Finished message didn't verify"); @@ -659,40 +686,56 @@ state.hash().update(state.handshake_io().format(contents, type)); - if(!state.client_finished()) { + if(state.client_finished() == nullptr) { // session resume case state.handshake_io().send(Change_Cipher_Spec()); change_cipher_spec_writer(Connection_Side::Client); - state.client_finished(new Finished_12(state.handshake_io(), state, Connection_Side::Client)); + state.client_finished(std::make_unique(state.handshake_io(), state, Connection_Side::Client)); } + // Session Tickets (as defined in RFC 5077) contain a lifetime_hint, + // sessions identified via a Session_ID do not. + const std::chrono::seconds session_lifetime_hint = [&] { + if(state.new_session_ticket() != nullptr) { + return std::chrono::seconds(state.new_session_ticket()->ticket_lifetime_hint()); + } else { + return std::chrono::seconds::max(); + } + }(); + Session session_info(state.session_keys().master_secret(), state.server_hello()->legacy_version(), state.server_hello()->ciphersuite(), Connection_Side::Client, state.server_hello()->supports_extended_master_secret(), state.server_hello()->supports_encrypt_then_mac(), - get_peer_cert_chain(state), + state.peer_cert_chain(), m_info, state.server_hello()->srtp_profile(), callbacks().tls_current_timestamp(), - - // Session Tickets (as defined in RFC 5077) contain a lifetime_hint, - // sessions identified via a Session_ID do not. - ((state.new_session_ticket()) ? state.new_session_ticket()->ticket_lifetime_hint() - : std::chrono::seconds::max())); + session_lifetime_hint); // RFC 5077 3.4 // If the client receives a session ticket from the server, then it // discards any Session ID that was sent in the ServerHello. const auto handle = [&]() -> std::optional { - if(const auto& session_ticket = state.session_ticket(); !session_ticket.empty()) { - return session_ticket; - } else if(const auto& session_id = state.server_hello()->session_id(); !session_id.empty()) { - return session_id; - } else { - return std::nullopt; + /* + On successful resumption an empty (or absent) NewSessionTicket means "keep using + the old ticket" so we inherit it from the ClientHello. On a fresh negotiation + an empty NewSessionTicket means "no ticket for this session", so inheriting the + ClientHello's old ticket would store the new master secret under a ticket the + server has discarded. + */ + if(const auto* nst = state.new_session_ticket(); nst != nullptr && !nst->ticket().empty()) { + return Session_Handle(nst->ticket()); + } + if(state.is_a_resumption() && !state.client_hello()->session_ticket().empty()) { + return Session_Handle(state.client_hello()->session_ticket()); + } + if(const auto& session_id = state.server_hello()->session_id(); !session_id.empty()) { + return Session_Handle(session_id); } + return std::nullopt; }(); // Give the application a chance for a final veto before fully @@ -700,7 +743,7 @@ callbacks().tls_session_established([&, this] { Session_Summary summary(session_info, state.is_a_resumption(), external_psk_identity()); summary.set_session_id(state.server_hello()->session_id()); - if(auto nst = state.new_session_ticket()) { + if(const auto* nst = state.new_session_ticket()) { summary.set_session_ticket(nst->ticket()); } return summary; @@ -720,7 +763,7 @@ should_save) { // renew the session ticket by removing the one we used to establish // this connection and replace it with the one we just received - session_manager().remove(state.client_hello()->session_ticket()); + session_manager().remove(Session_Handle(state.client_hello()->session_ticket())); session_manager().store(session_info, handle.value()); } diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_client_impl_12.h botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_client_impl_12.h --- botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_client_impl_12.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_client_impl_12.h 2026-05-07 01:38:28.000000000 +0000 @@ -11,7 +11,6 @@ #include #include -#include #include #include #include @@ -21,7 +20,7 @@ /** * SSL/TLS Client 1.2 implementation */ -class Client_Impl_12 : public Channel_Impl_12 { +class Client_Impl_12 final : public Channel_Impl_12 { public: /** * Set up a new TLS client session @@ -65,8 +64,6 @@ std::string application_protocol() const override { return m_application_protocol; } private: - std::vector get_peer_cert_chain(const Handshake_State& state) const override; - void initiate_handshake(Handshake_State& state, bool force_full_renegotiation) override; void send_client_hello(Handshake_State& state, @@ -75,8 +72,7 @@ std::optional session_and_handle = std::nullopt, const std::vector& next_protocols = {}); - void process_handshake_msg(const Handshake_State* active_state, - Handshake_State& pending_state, + void process_handshake_msg(Handshake_State& pending_state, Handshake_Type type, const std::vector& contents, bool epoch0_restart) override; diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_connection_state_12.cpp botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_connection_state_12.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_connection_state_12.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_connection_state_12.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,47 @@ +/* +* (C) 2026 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include + +namespace Botan::TLS { + +Active_Connection_State_12::~Active_Connection_State_12() = default; +Active_Connection_State_12::Active_Connection_State_12(Active_Connection_State_12&&) noexcept = default; +Active_Connection_State_12& Active_Connection_State_12::operator=(Active_Connection_State_12&&) noexcept = default; + +Active_Connection_State_12::Active_Connection_State_12(const Handshake_State& state, std::string application_protocol) : + m_version(state.version()), + m_ciphersuite_code(state.server_hello()->ciphersuite()), + m_application_protocol(std::move(application_protocol)), + m_peer_certs(state.peer_cert_chain()), + m_client_random(state.client_hello()->random()), + m_psk_identity(state.psk_identity()), + m_server_random(state.server_hello()->random()), + m_session_id(state.server_hello()->session_id()), + m_master_secret(state.session_keys().master_secret()), + m_prf_algo(state.ciphersuite().prf_algo()), + m_client_supports_secure_renegotiation(state.client_hello()->secure_renegotiation()), + m_server_supports_secure_renegotiation(state.server_hello()->secure_renegotiation()), + m_client_finished_verify_data(state.client_finished()->verify_data()), + m_server_finished_verify_data(state.server_finished()->verify_data()), + m_supports_extended_master_secret(state.server_hello()->supports_extended_master_secret()) {} + +Active_Connection_State_12::Active_Connection_State_12(const Handshake_State& state, + std::string application_protocol, + std::unique_ptr io) : + Active_Connection_State_12(state, std::move(application_protocol)) { + BOTAN_ASSERT_NOMSG(m_version.is_datagram_protocol()); + auto* dtls_io = dynamic_cast(io.get()); + BOTAN_ASSERT_NOMSG(dtls_io != nullptr); + m_dtls_handshake_io.reset(dtls_io); + io.release(); // NOLINT(*-unused-return-value) +} + +} // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_connection_state_12.h botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_connection_state_12.h --- botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_connection_state_12.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_connection_state_12.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,105 @@ +/* +* (C) 2026 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_TLS_CONNECTION_STATE_12_H_ +#define BOTAN_TLS_CONNECTION_STATE_12_H_ + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace Botan::TLS { + +class Handshake_IO; +class Datagram_Handshake_IO; +class Handshake_State; + +/** +* Captures the state of a completed TLS 1.2 handshake that is needed +* for the lifetime of an active connection. + */ +class Active_Connection_State_12 final { + public: + ~Active_Connection_State_12(); + + Active_Connection_State_12(Active_Connection_State_12&&) noexcept; + Active_Connection_State_12& operator=(Active_Connection_State_12&&) noexcept; + + Active_Connection_State_12(const Active_Connection_State_12&) = delete; + Active_Connection_State_12& operator=(const Active_Connection_State_12&) = delete; + + Active_Connection_State_12(const Handshake_State& state, std::string application_protocol); + + // DTLS variant: takes the handshake IO for replay of final flight + Active_Connection_State_12(const Handshake_State& state, + std::string application_protocol, + std::unique_ptr io); + + Protocol_Version version() const { return m_version; } + + uint16_t ciphersuite_code() const { return m_ciphersuite_code; } + + const std::string& application_protocol() const { return m_application_protocol; } + + const std::vector& peer_certs() const { return m_peer_certs; } + + const std::vector& client_random() const { return m_client_random; } + + const std::optional& psk_identity() const { return m_psk_identity; } + + const std::vector& server_random() const { return m_server_random; } + + const Session_ID& session_id() const { return m_session_id; } + + const secure_vector& master_secret() const { return m_master_secret; } + + const std::string& prf_algo() const { return m_prf_algo; } + + bool client_supports_secure_renegotiation() const { return m_client_supports_secure_renegotiation; } + + bool server_supports_secure_renegotiation() const { return m_server_supports_secure_renegotiation; } + + const std::vector& client_finished_verify_data() const { return m_client_finished_verify_data; } + + const std::vector& server_finished_verify_data() const { return m_server_finished_verify_data; } + + bool supports_extended_master_secret() const { return m_supports_extended_master_secret; } + + /** + * For DTLS: the handshake IO from the completed handshake, needed + * to retransmit the last flight when records arrive under the + * previous epoch. Null for stream TLS. + */ + Datagram_Handshake_IO* dtls_handshake_io() { return m_dtls_handshake_io.get(); } + + private: + Protocol_Version m_version; + uint16_t m_ciphersuite_code = 0; + std::string m_application_protocol; + std::vector m_peer_certs; + std::vector m_client_random; + std::optional m_psk_identity; + std::vector m_server_random; + Session_ID m_session_id; + secure_vector m_master_secret; + std::string m_prf_algo; + bool m_client_supports_secure_renegotiation = false; + bool m_server_supports_secure_renegotiation = false; + std::vector m_client_finished_verify_data; + std::vector m_server_finished_verify_data; + bool m_supports_extended_master_secret = false; + std::unique_ptr m_dtls_handshake_io; +}; + +} // namespace Botan::TLS + +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_extensions_12.cpp botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_extensions_12.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_extensions_12.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_extensions_12.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,118 @@ +/* +* TLS 1.2 Specific Extensions +* (C) 2011,2012,2015,2016 Jack Lloyd +* 2016 Juraj Somorovsky +* 2021 Elektrobit Automotive GmbH +* 2022 René Meusel, Hannes Rantzsch - neXenio GmbH +* 2023 Mateusz Berezecki +* 2023 Fabian Albert, René Meusel - Rohde & Schwarz Cybersecurity +* 2026 René Meusel - Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include + +namespace Botan::TLS { + +Renegotiation_Extension::Renegotiation_Extension(TLS_Data_Reader& reader, uint16_t extension_size) : + m_reneg_data(reader.get_range(1, 0, 255)) { + if(m_reneg_data.size() + 1 != extension_size) { + throw Decoding_Error("Bad encoding for secure renegotiation extn"); + } +} + +std::vector Renegotiation_Extension::serialize(Connection_Side /*whoami*/) const { + std::vector buf; + append_tls_length_value(buf, m_reneg_data, 1); + return buf; +} + +std::vector Supported_Point_Formats::serialize(Connection_Side /*whoami*/) const { + // if this extension is sent, it MUST include uncompressed (RFC 4492, section 5.1) + if(m_prefers_compressed) { + return std::vector{2, ANSIX962_COMPRESSED_PRIME, UNCOMPRESSED}; + } else { + return std::vector{1, UNCOMPRESSED}; + } +} + +Supported_Point_Formats::Supported_Point_Formats(TLS_Data_Reader& reader, uint16_t extension_size) { + const uint8_t len = reader.get_byte(); + + if(len + 1 != extension_size) { + throw Decoding_Error("Inconsistent length field in supported point formats list"); + } + + bool includes_uncompressed = false; + for(size_t i = 0; i != len; ++i) { + const uint8_t format = reader.get_byte(); + + if(static_cast(format) == UNCOMPRESSED) { + m_prefers_compressed = false; + reader.discard_next(len - i - 1); + return; + } else if(static_cast(format) == ANSIX962_COMPRESSED_PRIME) { + m_prefers_compressed = true; + std::vector remaining_formats = reader.get_fixed(len - i - 1); + includes_uncompressed = + std::any_of(std::begin(remaining_formats), std::end(remaining_formats), [](uint8_t remaining_format) { + return static_cast(remaining_format) == UNCOMPRESSED; + }); + break; + } + + // ignore ANSIX962_COMPRESSED_CHAR2, we don't support these curves + } + + // RFC 4492 5.1.: + // If the Supported Point Formats Extension is indeed sent, it MUST contain the value 0 (uncompressed) + // as one of the items in the list of point formats. + // Note: + // RFC 8422 5.1.2. explicitly requires this check, + // but only if the Supported Groups extension was sent. + if(!includes_uncompressed) { + throw TLS_Exception(Alert::IllegalParameter, + "Supported Point Formats Extension must contain the uncompressed point format"); + } +} + +Session_Ticket_Extension::Session_Ticket_Extension(TLS_Data_Reader& reader, + uint16_t extension_size, + Connection_Side from) { + // RFC 5077 3.2: in a ServerHello the SessionTicket extension is just a + // flag indicating that a NewSessionTicket handshake message will follow; + // its extension_data MUST be empty. A ticket body is only valid in a + // ClientHello. + if(from == Connection_Side::Server && extension_size != 0) { + throw Decoding_Error("Server sent a non-empty SessionTicket extension"); + } + m_ticket = Session_Ticket(reader.get_elem>(extension_size)); +} + +Extended_Master_Secret::Extended_Master_Secret(TLS_Data_Reader& /*unused*/, uint16_t extension_size) { + if(extension_size != 0) { + throw Decoding_Error("Invalid extended_master_secret extension"); + } +} + +std::vector Extended_Master_Secret::serialize(Connection_Side /*whoami*/) const { + return std::vector(); +} + +Encrypt_then_MAC::Encrypt_then_MAC(TLS_Data_Reader& /*unused*/, uint16_t extension_size) { + if(extension_size != 0) { + throw Decoding_Error("Invalid encrypt_then_mac extension"); + } +} + +std::vector Encrypt_then_MAC::serialize(Connection_Side /*whoami*/) const { + return std::vector(); +} + +} // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_extensions_12.h botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_extensions_12.h --- botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_extensions_12.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_extensions_12.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,155 @@ +/* +* TLS 1.2 Specific Extensions +* (C) 2011,2012,2016,2018,2019 Jack Lloyd +* (C) 2016 Juraj Somorovsky +* (C) 2016 Matthias Gierlings +* (C) 2021 Elektrobit Automotive GmbH +* (C) 2022 René Meusel, Hannes Rantzsch - neXenio GmbH +* (C) 2023 Fabian Albert, René Meusel - Rohde & Schwarz Cybersecurity +* (C) 2026 René Meusel - Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_TLS_EXTENSIONS_12_H_ +#define BOTAN_TLS_EXTENSIONS_12_H_ + +#include +#include + +#include + +namespace Botan::TLS { + +class TLS_Data_Reader; + +/** +* Renegotiation Indication Extension (RFC 5746) +*/ +class BOTAN_UNSTABLE_API Renegotiation_Extension final : public Extension { + public: + static Extension_Code static_type() { return Extension_Code::SafeRenegotiation; } + + Extension_Code type() const override { return static_type(); } + + Renegotiation_Extension() = default; + + explicit Renegotiation_Extension(const std::vector& bits) : m_reneg_data(bits) {} + + Renegotiation_Extension(TLS_Data_Reader& reader, uint16_t extension_size); + + const std::vector& renegotiation_info() const { return m_reneg_data; } + + std::vector serialize(Connection_Side whoami) const override; + + bool empty() const override { return false; } // always send this + + private: + std::vector m_reneg_data; +}; + +/** +* Session Ticket Extension (RFC 5077) +*/ +class BOTAN_UNSTABLE_API Session_Ticket_Extension final : public Extension { + public: + static Extension_Code static_type() { return Extension_Code::SessionTicket; } + + Extension_Code type() const override { return static_type(); } + + /** + * @return contents of the session ticket + */ + const Session_Ticket& contents() const { return m_ticket; } + + /** + * Create empty extension, used by both client and server + */ + Session_Ticket_Extension() = default; + + /** + * Extension with ticket, used by client + */ + explicit Session_Ticket_Extension(Session_Ticket session_ticket) : m_ticket(std::move(session_ticket)) {} + + /** + * Deserialize a session ticket + */ + Session_Ticket_Extension(TLS_Data_Reader& reader, uint16_t extension_size, Connection_Side from); + + std::vector serialize(Connection_Side /*whoami*/) const override { return m_ticket.get(); } + + bool empty() const override { return false; } + + private: + Session_Ticket m_ticket; +}; + +/** +* Supported Point Formats Extension (RFC 4492) +*/ +class BOTAN_UNSTABLE_API Supported_Point_Formats final : public Extension { + public: + enum ECPointFormat : uint8_t /* NOLINT(*-use-enum-class) */ { + UNCOMPRESSED = 0, + ANSIX962_COMPRESSED_PRIME = 1, + ANSIX962_COMPRESSED_CHAR2 = 2, // don't support these curves + }; + + static Extension_Code static_type() { return Extension_Code::EcPointFormats; } + + Extension_Code type() const override { return static_type(); } + + std::vector serialize(Connection_Side whoami) const override; + + explicit Supported_Point_Formats(bool prefer_compressed) : m_prefers_compressed(prefer_compressed) {} + + Supported_Point_Formats(TLS_Data_Reader& reader, uint16_t extension_size); + + bool empty() const override { return false; } + + bool prefers_compressed() const { return m_prefers_compressed; } + + private: + bool m_prefers_compressed = false; +}; + +/** +* Extended Master Secret Extension (RFC 7627) +*/ +class BOTAN_UNSTABLE_API Extended_Master_Secret final : public Extension { + public: + static Extension_Code static_type() { return Extension_Code::ExtendedMasterSecret; } + + Extension_Code type() const override { return static_type(); } + + std::vector serialize(Connection_Side whoami) const override; + + bool empty() const override { return false; } + + Extended_Master_Secret() = default; + + Extended_Master_Secret(TLS_Data_Reader& reader, uint16_t extension_size); +}; + +/** +* Encrypt-then-MAC Extension (RFC 7366) +*/ +class BOTAN_UNSTABLE_API Encrypt_then_MAC final : public Extension { + public: + static Extension_Code static_type() { return Extension_Code::EncryptThenMac; } + + Extension_Code type() const override { return static_type(); } + + std::vector serialize(Connection_Side whoami) const override; + + bool empty() const override { return false; } + + Encrypt_then_MAC() = default; + + Encrypt_then_MAC(TLS_Data_Reader& reader, uint16_t extension_size); +}; + +} // namespace Botan::TLS + +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_handshake_io.cpp botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_handshake_io.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_handshake_io.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_handshake_io.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,7 +8,9 @@ #include #include -#include +#include +#include +#include #include #include #include @@ -22,6 +24,18 @@ return make_uint32(0, q[0], q[1], q[2]); } +// Reject handshake type values that are internal sentinels, not wire values +void verify_is_expected_wire_handshake_type(Handshake_Type type) { + switch(type) { + case Handshake_Type::HelloRetryRequest: + case Handshake_Type::HandshakeCCS: + case Handshake_Type::None: + throw TLS_Exception(Alert::UnexpectedMessage, "Invalid handshake message type"); + default: + break; + } +} + void store_be24(uint8_t out[3], size_t val) { out[0] = get_byte<1>(static_cast(val)); out[1] = get_byte<2>(static_cast(val)); @@ -59,18 +73,36 @@ } } -std::pair> Stream_Handshake_IO::get_next_record(bool /*expecting_ccs*/) { +std::pair> Stream_Handshake_IO::get_next_record(bool expecting_ccs, + size_t max_message_size) { if(m_queue.size() >= 4) { - const size_t length = 4 + make_uint32(0, m_queue[1], m_queue[2], m_queue[3]); + const Handshake_Type type = static_cast(m_queue[0]); - if(m_queue.size() >= length) { - Handshake_Type type = static_cast(m_queue[0]); + const size_t rec_length = make_uint32(0, m_queue[1], m_queue[2], m_queue[3]); - if(type == Handshake_Type::None) { - throw Decoding_Error("Invalid handshake message type"); + // If we are expecting a CCS but the next queued message is not a CCS, + // the peer has skipped the CCS message. This can happen when the peer + // sends an encrypted Finished without the preceding CCS, in which case + // the encrypted bytes are misinterpreted as a handshake message. + if(expecting_ccs) { + const bool is_ccs = (type == Handshake_Type::HandshakeCCS && rec_length == 0); + if(!is_ccs) { + throw TLS_Exception(Alert::UnexpectedMessage, "Expected ChangeCipherSpec but got a handshake message"); } + } else { + verify_is_expected_wire_handshake_type(type); + + if(max_message_size > 0 && rec_length > max_message_size) { + throw TLS_Exception( + Alert::HandshakeFailure, + Botan::fmt("Handshake message is {} bytes, policy maximum is {}", rec_length, max_message_size)); + } + } - std::vector contents(m_queue.begin() + 4, m_queue.begin() + length); + const size_t length = 4 + rec_length; + + if(m_queue.size() >= length) { + const std::vector contents(m_queue.begin() + 4, m_queue.begin() + length); m_queue.erase(m_queue.begin(), m_queue.begin() + length); @@ -135,7 +167,7 @@ if(msg.epoch != epoch) { // Epoch gap: insert the CCS - std::vector ccs(1, 1); + const std::vector ccs(1, 1); m_send_hs(epoch, Record_Type::ChangeCipherSpec, ccs); } @@ -187,13 +219,23 @@ const size_t DTLS_HANDSHAKE_HEADER_LEN = 12; - while(record_len) { + while(record_len > 0) { if(record_len < DTLS_HANDSHAKE_HEADER_LEN) { return; // completely bogus? at least degenerate/weird } const Handshake_Type msg_type = static_cast(record[0]); + + verify_is_expected_wire_handshake_type(msg_type); + const size_t msg_len = load_be24(&record[1]); + + if(m_max_handshake_msg_size > 0 && msg_len > m_max_handshake_msg_size) { + throw TLS_Exception( + Alert::HandshakeFailure, + Botan::fmt("Handshake message is {} bytes, policy maximum is {}", msg_len, m_max_handshake_msg_size)); + } + const uint16_t message_seq = load_be(&record[4], 0); const size_t fragment_offset = load_be24(&record[6]); const size_t fragment_length = load_be24(&record[9]); @@ -204,8 +246,24 @@ throw Decoding_Error("Bad lengths in DTLS header"); } - if(message_seq >= m_in_message_seq) { - m_messages[message_seq].add_fragment( + // Bound the out-of-order reassembly window. + constexpr uint16_t reassembly_window = 16; + + // Independently cap total bytes committed to in-flight reassembly slots + const size_t max_pending = 4 * m_max_handshake_msg_size; + + if(message_seq >= m_in_message_seq && (message_seq - m_in_message_seq) < reassembly_window) { + auto [it, inserted] = m_messages.try_emplace(message_seq); + if(inserted) { + if(m_max_handshake_msg_size > 0 && m_pending_reassembly_bytes + msg_len > max_pending) { + m_messages.erase(it); + record += total_size; + record_len -= total_size; + continue; + } + m_pending_reassembly_bytes += msg_len; + } + it->second.add_fragment( &record[DTLS_HANDSHAKE_HEADER_LEN], fragment_length, fragment_offset, epoch, msg_type, msg_len); } else { // TODO: detect retransmitted flight @@ -216,7 +274,8 @@ } } -std::pair> Datagram_Handshake_IO::get_next_record(bool expecting_ccs) { +std::pair> Datagram_Handshake_IO::get_next_record(bool expecting_ccs, + size_t /*max_message_size*/) { // Expecting a message means the last flight is concluded if(!m_flights.rbegin()->empty()) { m_flights.push_back(std::vector()); @@ -241,7 +300,24 @@ m_in_message_seq += 1; - return i->second.message(); + auto result = i->second.message(); + + // Free the reassembly buffer for this delivered slot and uncommit its + // bytes against the cap. The entry itself stays in m_messages because + // the expecting_ccs branch above uses m_messages.begin()->second.epoch() + // as an epoch-0 sentinel; it only needs the metadata, not the buffers. + BOTAN_ASSERT_NOMSG(m_pending_reassembly_bytes >= i->second.msg_length()); + m_pending_reassembly_bytes -= i->second.msg_length(); + i->second.release_buffers(); + + return result; +} + +void Datagram_Handshake_IO::Handshake_Reassembly::release_buffers() { + m_received_mask.clear(); + m_received_mask.shrink_to_fit(); + m_message.clear(); + m_message.shrink_to_fit(); } void Datagram_Handshake_IO::Handshake_Reassembly::add_fragment(const uint8_t fragment[], @@ -250,18 +326,21 @@ uint16_t epoch, Handshake_Type msg_type, size_t msg_length) { - if(complete()) { - return; // already have entire message, ignore this - } - if(m_msg_type == Handshake_Type::None) { + // First fragment for this message_seq m_epoch = epoch; m_msg_type = msg_type; m_msg_length = msg_length; - } + m_message.resize(msg_length); + m_received_mask.assign(msg_length, 0); + } else { + if(msg_type != m_msg_type || msg_length != m_msg_length || epoch != m_epoch) { + throw Decoding_Error("Inconsistent values in fragmented DTLS handshake header"); + } - if(msg_type != m_msg_type || msg_length != m_msg_length || epoch != m_epoch) { - throw Decoding_Error("Inconsistent values in fragmented DTLS handshake header"); + if(complete()) { + return; // already have entire message, ignore this + } } if(fragment_offset > m_msg_length) { @@ -272,34 +351,26 @@ throw Decoding_Error("Fragment overlaps past end of message"); } - if(fragment_offset == 0 && fragment_length == m_msg_length) { - m_fragments.clear(); - m_message.assign(fragment, fragment + fragment_length); - } else { - /* - * FIXME. This is a pretty lame way to do defragmentation, huge - * overhead with a tree node per byte. - * - * Also should confirm that all overlaps have no changes, - * otherwise we expose ourselves to the classic fingerprinting - * and IDS evasion attacks on IP fragmentation. - */ - for(size_t i = 0; i != fragment_length; ++i) { - m_fragments[fragment_offset + i] = fragment[i]; - } + BOTAN_ASSERT_NOMSG(m_received_mask.size() == m_msg_length); - if(m_fragments.size() == m_msg_length) { - m_message.resize(m_msg_length); - for(size_t i = 0; i != m_msg_length; ++i) { - m_message[i] = m_fragments[i]; + for(size_t i = 0; i != fragment_length; ++i) { + const size_t off = fragment_offset + i; + if(m_received_mask[off] != 0) { + // RFC 6347 4.2.3 permits overlapping retransmissions, but the + // overlapping bytes must agree. + if(m_message[off] != fragment[i]) { + throw Decoding_Error("Inconsistent overlapping DTLS handshake fragment"); } - m_fragments.clear(); + } else { + m_message[off] = fragment[i]; + m_received_mask[off] = 1; + ++m_bytes_received; } } } bool Datagram_Handshake_IO::Handshake_Reassembly::complete() const { - return (m_msg_type != Handshake_Type::None && m_message.size() == m_msg_length); + return (m_msg_type != Handshake_Type::None && m_bytes_received == m_msg_length); } std::pair> Datagram_Handshake_IO::Handshake_Reassembly::message() const { @@ -312,8 +383,8 @@ std::vector Datagram_Handshake_IO::format_fragment(const uint8_t fragment[], size_t frag_len, - uint16_t frag_offset, - uint16_t msg_len, + uint32_t frag_offset, + uint32_t msg_len, Handshake_Type type, uint16_t msg_sequence) const { std::vector send_buf(12 + frag_len); @@ -337,10 +408,11 @@ std::vector Datagram_Handshake_IO::format_w_seq(const std::vector& msg, Handshake_Type type, uint16_t msg_sequence) const { - return format_fragment(msg.data(), msg.size(), 0, static_cast(msg.size()), type, msg_sequence); + return format_fragment(msg.data(), msg.size(), 0, static_cast(msg.size()), type, msg_sequence); } std::vector Datagram_Handshake_IO::format(const std::vector& msg, Handshake_Type type) const { + BOTAN_ASSERT_NOMSG(m_in_message_seq > 0); return format_w_seq(msg, type, m_in_message_seq - 1); } @@ -407,8 +479,8 @@ const std::vector frag = format_fragment(&msg_bits[frag_offset], frag_len, - static_cast(frag_offset), - static_cast(msg_bits.size()), + static_cast(frag_offset), + static_cast(msg_bits.size()), msg_type, msg_seq); diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_handshake_io.h botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_handshake_io.h --- botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_handshake_io.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_handshake_io.h 2026-05-07 01:38:28.000000000 +0000 @@ -10,7 +10,6 @@ #include #include -#include #include #include #include @@ -64,13 +63,15 @@ /** * Returns (HANDSHAKE_NONE, std::vector<>()) if no message currently available */ - virtual std::pair> get_next_record(bool expecting_ccs) = 0; + virtual std::pair> get_next_record(bool expecting_ccs, + size_t max_message_size) = 0; Handshake_IO() = default; Handshake_IO(const Handshake_IO&) = delete; - + Handshake_IO(Handshake_IO&&) = delete; Handshake_IO& operator=(const Handshake_IO&) = delete; + Handshake_IO& operator=(Handshake_IO&&) = delete; virtual ~Handshake_IO() = default; }; @@ -82,13 +83,13 @@ public: typedef std::function&)> writer_fn; - explicit Stream_Handshake_IO(writer_fn writer) : m_send_hs(writer) {} + explicit Stream_Handshake_IO(writer_fn writer) : m_send_hs(std::move(writer)) {} Protocol_Version initial_record_version() const override; bool timeout_check() override { return false; } - bool have_more_data() const override { return m_queue.empty() == false; } + bool have_more_data() const override { return !m_queue.empty(); } std::vector send(const Handshake_Message& msg) override; @@ -99,7 +100,8 @@ void add_record(const uint8_t record[], size_t record_len, Record_Type type, uint64_t sequence_number) override; - std::pair> get_next_record(bool expecting_ccs) override; + std::pair> get_next_record(bool expecting_ccs, + size_t max_message_size) override; private: std::deque m_queue; @@ -117,13 +119,15 @@ class Connection_Sequence_Numbers& seq, uint16_t mtu, uint64_t initial_timeout_ms, - uint64_t max_timeout_ms) : + uint64_t max_timeout_ms, + size_t max_handshake_msg_size) : m_seqs(seq), m_flights(1), m_initial_timeout(initial_timeout_ms), m_max_timeout(max_timeout_ms), - m_send_hs(writer), - m_mtu(mtu) {} + m_send_hs(std::move(writer)), + m_mtu(mtu), + m_max_handshake_msg_size(max_handshake_msg_size) {} Protocol_Version initial_record_version() const override; @@ -140,7 +144,8 @@ void add_record(const uint8_t record[], size_t record_len, Record_Type type, uint64_t sequence_number) override; - std::pair> get_next_record(bool expecting_ccs) override; + std::pair> get_next_record(bool expecting_ccs, + size_t max_message_size) override; private: void retransmit_flight(size_t flight); @@ -148,8 +153,8 @@ std::vector format_fragment(const uint8_t fragment[], size_t fragment_len, - uint16_t frag_offset, - uint16_t msg_len, + uint32_t frag_offset, + uint32_t msg_len, Handshake_Type type, uint16_t msg_sequence) const; @@ -175,16 +180,23 @@ uint16_t epoch() const { return m_epoch; } + // 0 until the first fragment has set the declared msg_length. + size_t msg_length() const { return m_msg_length; } + std::pair> message() const; + // Release the memory buffers; called after reassembly has completed + void release_buffers(); + private: Handshake_Type m_msg_type = Handshake_Type::None; size_t m_msg_length = 0; + size_t m_bytes_received = 0; uint16_t m_epoch = 0; - // vector m_seen; - // vector m_fragments - std::map m_fragments; + // Reassembly buffer (sized to m_msg_length once known) and a parallel + // byte-mask marking which positions have already been seen. + std::vector m_received_mask; std::vector m_message; }; @@ -194,13 +206,14 @@ Message_Info() : epoch(0xFFFF), msg_type(Handshake_Type::None) {} - uint16_t epoch; - Handshake_Type msg_type; - std::vector msg_bits; + uint16_t epoch; // NOLINT(*non-private-member-variable*) + Handshake_Type msg_type; // NOLINT(*non-private-member-variable*) + std::vector msg_bits; // NOLINT(*non-private-member-variable*) }; class Connection_Sequence_Numbers& m_seqs; std::map m_messages; + size_t m_pending_reassembly_bytes = 0; std::set m_ccs_epochs; std::vector> m_flights; std::map m_flight_data; @@ -216,6 +229,7 @@ writer_fn m_send_hs; uint16_t m_mtu; + size_t m_max_handshake_msg_size; }; } // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_handshake_state.cpp botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_handshake_state.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_handshake_state.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_handshake_state.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,10 +9,12 @@ #include #include -#include +#include +#include +#include +#include #include #include -#include namespace Botan::TLS { @@ -20,73 +22,6 @@ return handshake_type_to_string(type()); } -const char* handshake_type_to_string(Handshake_Type type) { - switch(type) { - case Handshake_Type::HelloVerifyRequest: - return "hello_verify_request"; - - case Handshake_Type::HelloRequest: - return "hello_request"; - - case Handshake_Type::ClientHello: - return "client_hello"; - - case Handshake_Type::ServerHello: - return "server_hello"; - - case Handshake_Type::HelloRetryRequest: - return "hello_retry_request"; - - case Handshake_Type::Certificate: - return "certificate"; - - case Handshake_Type::CertificateUrl: - return "certificate_url"; - - case Handshake_Type::CertificateStatus: - return "certificate_status"; - - case Handshake_Type::ServerKeyExchange: - return "server_key_exchange"; - - case Handshake_Type::CertificateRequest: - return "certificate_request"; - - case Handshake_Type::ServerHelloDone: - return "server_hello_done"; - - case Handshake_Type::CertificateVerify: - return "certificate_verify"; - - case Handshake_Type::ClientKeyExchange: - return "client_key_exchange"; - - case Handshake_Type::NewSessionTicket: - return "new_session_ticket"; - - case Handshake_Type::HandshakeCCS: - return "change_cipher_spec"; - - case Handshake_Type::Finished: - return "finished"; - - case Handshake_Type::EndOfEarlyData: - return "end_of_early_data"; - - case Handshake_Type::EncryptedExtensions: - return "encrypted_extensions"; - - case Handshake_Type::KeyUpdate: - return "key_update"; - - case Handshake_Type::None: - return "invalid"; - } - - throw TLS_Exception(Alert::UnexpectedMessage, - "Unknown TLS handshake message type " + std::to_string(static_cast(type))); -} - /* * Initialize the SSL/TLS Handshake State */ @@ -102,85 +37,100 @@ void Handshake_State::hello_verify_request(const Hello_Verify_Request& hello_verify) { note_message(hello_verify); + BOTAN_ASSERT_NONNULL(m_client_hello); m_client_hello->update_hello_cookie(hello_verify); hash().reset(); hash().update(handshake_io().send(*m_client_hello)); note_message(*m_client_hello); } -void Handshake_State::client_hello(Client_Hello_12* client_hello) { +void Handshake_State::client_hello(std::unique_ptr client_hello) { + // Legacy behavior (exception to the rule): Allow client_hello to be nullptr to reset state. if(client_hello == nullptr) { m_client_hello.reset(); hash().reset(); } else { - m_client_hello.reset(client_hello); + m_client_hello = std::move(client_hello); note_message(*m_client_hello); } } -void Handshake_State::server_hello(Server_Hello_12* server_hello) { - m_server_hello.reset(server_hello); +void Handshake_State::server_hello(std::unique_ptr server_hello) { + BOTAN_ASSERT_NONNULL(server_hello); + m_server_hello = std::move(server_hello); m_ciphersuite = Ciphersuite::by_id(m_server_hello->ciphersuite()); note_message(*m_server_hello); } -void Handshake_State::server_certs(Certificate_12* server_certs) { - m_server_certs.reset(server_certs); +void Handshake_State::server_certs(std::unique_ptr server_certs) { + BOTAN_ASSERT_NONNULL(server_certs); + m_server_certs = std::move(server_certs); note_message(*m_server_certs); } -void Handshake_State::server_cert_status(Certificate_Status* server_cert_status) { - m_server_cert_status.reset(server_cert_status); +void Handshake_State::server_cert_status(std::unique_ptr server_cert_status) { + BOTAN_ASSERT_NONNULL(server_cert_status); + m_server_cert_status = std::move(server_cert_status); note_message(*m_server_cert_status); } -void Handshake_State::server_kex(Server_Key_Exchange* server_kex) { - m_server_kex.reset(server_kex); +void Handshake_State::server_kex(std::unique_ptr server_kex) { + BOTAN_ASSERT_NONNULL(server_kex); + m_server_kex = std::move(server_kex); note_message(*m_server_kex); } -void Handshake_State::cert_req(Certificate_Request_12* cert_req) { - m_cert_req.reset(cert_req); +void Handshake_State::cert_req(std::unique_ptr cert_req) { + BOTAN_ASSERT_NONNULL(cert_req); + m_cert_req = std::move(cert_req); note_message(*m_cert_req); } -void Handshake_State::server_hello_done(Server_Hello_Done* server_hello_done) { - m_server_hello_done.reset(server_hello_done); +void Handshake_State::server_hello_done(std::unique_ptr server_hello_done) { + BOTAN_ASSERT_NONNULL(server_hello_done); + m_server_hello_done = std::move(server_hello_done); note_message(*m_server_hello_done); } -void Handshake_State::client_certs(Certificate_12* client_certs) { - m_client_certs.reset(client_certs); +void Handshake_State::client_certs(std::unique_ptr client_certs) { + BOTAN_ASSERT_NONNULL(client_certs); + m_client_certs = std::move(client_certs); note_message(*m_client_certs); } -void Handshake_State::client_kex(Client_Key_Exchange* client_kex) { - m_client_kex.reset(client_kex); +void Handshake_State::client_kex(std::unique_ptr client_kex) { + BOTAN_ASSERT_NONNULL(client_kex); + m_client_kex = std::move(client_kex); note_message(*m_client_kex); } -void Handshake_State::client_verify(Certificate_Verify_12* client_verify) { - m_client_verify.reset(client_verify); +void Handshake_State::client_verify(std::unique_ptr client_verify) { + BOTAN_ASSERT_NONNULL(client_verify); + m_client_verify = std::move(client_verify); note_message(*m_client_verify); } -void Handshake_State::server_verify(Certificate_Verify_12* server_verify) { - m_server_verify.reset(server_verify); +void Handshake_State::server_verify(std::unique_ptr server_verify) { + BOTAN_ASSERT_NONNULL(server_verify); + m_server_verify = std::move(server_verify); note_message(*m_server_verify); } -void Handshake_State::new_session_ticket(New_Session_Ticket_12* new_session_ticket) { - m_new_session_ticket.reset(new_session_ticket); +void Handshake_State::new_session_ticket(std::unique_ptr new_session_ticket) { + BOTAN_ASSERT_NONNULL(new_session_ticket); + m_new_session_ticket = std::move(new_session_ticket); note_message(*m_new_session_ticket); } -void Handshake_State::server_finished(Finished_12* server_finished) { - m_server_finished.reset(server_finished); +void Handshake_State::server_finished(std::unique_ptr server_finished) { + BOTAN_ASSERT_NONNULL(server_finished); + m_server_finished = std::move(server_finished); note_message(*m_server_finished); } -void Handshake_State::client_finished(Finished_12* client_finished) { - m_client_finished.reset(client_finished); +void Handshake_State::client_finished(std::unique_ptr client_finished) { + BOTAN_ASSERT_NONNULL(client_finished); + m_client_finished = std::move(client_finished); note_message(*m_client_finished); } @@ -203,6 +153,7 @@ } void Handshake_State::compute_session_keys() { + BOTAN_ASSERT_NONNULL(client_kex()); m_session_keys = Session_Keys(this, client_kex()->pre_master_secret(), false); } @@ -222,26 +173,24 @@ return m_transitions.received_handshake_msg(handshake_msg); } -std::pair> Handshake_State::get_next_handshake_msg() { - return m_handshake_io->get_next_record(m_transitions.change_cipher_spec_expected()); +std::pair> Handshake_State::get_next_handshake_msg(size_t max_handshake_msg_size) { + return m_handshake_io->get_next_record(m_transitions.change_cipher_spec_expected(), max_handshake_msg_size); } Session_Ticket Handshake_State::session_ticket() const { - if(new_session_ticket() && !new_session_ticket()->ticket().empty()) { - return new_session_ticket()->ticket(); + if(const auto* nst = new_session_ticket()) { + const auto& ticket = nst->ticket(); + if(!ticket.empty()) { + return ticket; + } } + BOTAN_ASSERT_NONNULL(client_hello()); return client_hello()->session_ticket(); } std::unique_ptr Handshake_State::protocol_specific_prf() const { - const std::string prf_algo = ciphersuite().prf_algo(); - - if(prf_algo == "MD5" || prf_algo == "SHA-1") { - return KDF::create_or_throw("TLS-12-PRF(SHA-256)"); - } - - return KDF::create_or_throw("TLS-12-PRF(" + prf_algo + ")"); + return m_callbacks.tls12_protocol_specific_kdf(ciphersuite().prf_algo()); } std::pair Handshake_State::choose_sig_format(const Private_Key& key, @@ -252,10 +201,16 @@ const std::vector allowed = policy.allowed_signature_schemes(); + if(for_client_auth) { + BOTAN_ASSERT_NONNULL(cert_req()); + } else { + BOTAN_ASSERT_NONNULL(client_hello()); + } + std::vector requested = (for_client_auth) ? cert_req()->signature_schemes() : client_hello()->signature_schemes(); - for(Signature_Scheme scheme : allowed) { + for(const Signature_Scheme scheme : allowed) { if(!scheme.is_available()) { continue; } @@ -268,6 +223,11 @@ } } + if(!chosen_scheme.is_set()) { + throw TLS_Exception(Alert::HandshakeFailure, + "Could not agree on a signature scheme with peer for " + sig_algo + " key"); + } + const std::string hash = chosen_scheme.hash_function_name(); if(!policy.allowed_signature_hash(hash)) { @@ -283,11 +243,9 @@ namespace { -bool supported_algos_include(const std::vector& schemes, - std::string_view key_type, - std::string_view hash_type) { - for(Signature_Scheme scheme : schemes) { - if(scheme.is_available() && hash_type == scheme.hash_function_name() && key_type == scheme.algorithm_name()) { +bool supported_algos_include(const std::vector& schemes, Signature_Scheme received_scheme) { + for(const Signature_Scheme scheme : schemes) { + if(scheme == received_scheme && scheme.is_available()) { return true; } } @@ -317,8 +275,8 @@ throw Decoding_Error("Counterparty sent inconsistent key and sig types"); } - if(for_client_auth && !cert_req()) { - throw TLS_Exception(Alert::HandshakeFailure, "No certificate verify set"); + if(for_client_auth && cert_req() == nullptr) { + throw TLS_Exception(Alert::HandshakeFailure, "No CertificateVerify message received"); } /* @@ -329,15 +287,13 @@ const std::vector supported_algos = for_client_auth ? cert_req()->signature_schemes() : offered_schemes; - const std::string hash_algo = scheme.hash_function_name(); - if(!scheme.is_compatible_with(Protocol_Version::TLS_V12)) { - throw TLS_Exception(Alert::IllegalParameter, "Peer sent unexceptable signature scheme"); + throw TLS_Exception(Alert::IllegalParameter, "Peer sent unacceptable signature scheme"); } - if(!supported_algos_include(supported_algos, key_type, hash_algo)) { + if(!supported_algos_include(supported_algos, scheme)) { throw TLS_Exception(Alert::IllegalParameter, - "TLS signature extension did not allow for " + key_type + "/" + hash_algo + " signature"); + "TLS signature extension did not allow for " + scheme.to_string() + " signature"); } if(!scheme.format().has_value()) { diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_handshake_state.h botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_handshake_state.h --- botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_handshake_state.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_handshake_state.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,21 +9,23 @@ #ifndef BOTAN_TLS_HANDSHAKE_STATE_H_ #define BOTAN_TLS_HANDSHAKE_STATE_H_ -#include -#include -#include #include #include +#include #include +#include +#include #include #include #include #include -#include #include +#include namespace Botan { +enum class Signature_Format : uint8_t; +class Public_Key; class KDF; namespace TLS { @@ -61,11 +63,15 @@ Handshake_State(std::unique_ptr io, Callbacks& callbacks); virtual ~Handshake_State(); - Handshake_State(const Handshake_State&) = delete; - Handshake_State& operator=(const Handshake_State&) = delete; + Handshake_State(const Handshake_State& other) = delete; + Handshake_State(Handshake_State&& other) = delete; + Handshake_State& operator=(const Handshake_State& other) = delete; + Handshake_State& operator=(Handshake_State&& other) = delete; Handshake_IO& handshake_io() { return *m_handshake_io; } + std::unique_ptr take_handshake_io() { return std::move(m_handshake_io); } + /** * Return true iff we have received a particular message already * @param msg_type the message type @@ -84,7 +90,7 @@ */ void set_expected_next(Handshake_Type msg_type); - std::pair> get_next_handshake_msg(); + std::pair> get_next_handshake_msg(size_t max_handshake_msg_size); Session_Ticket session_ticket() const; @@ -107,26 +113,24 @@ void hello_verify_request(const Hello_Verify_Request& hello_verify); - // TODO: take unique_ptr instead of raw pointers for all of these, as - // we're taking the ownership - void client_hello(Client_Hello_12* client_hello); - void server_hello(Server_Hello_12* server_hello); - void server_cert_status(Certificate_Status* server_cert_status); - void server_kex(Server_Key_Exchange* server_kex); - void cert_req(Certificate_Request_12* cert_req); - void server_hello_done(Server_Hello_Done* server_hello_done); - void client_kex(Client_Key_Exchange* client_kex); - - void client_certs(Certificate_12* client_certs); - void server_certs(Certificate_12* server_certs); + void client_hello(std::unique_ptr client_hello); + void server_hello(std::unique_ptr server_hello); + void server_cert_status(std::unique_ptr server_cert_status); + void server_kex(std::unique_ptr server_kex); + void cert_req(std::unique_ptr cert_req); + void server_hello_done(std::unique_ptr server_hello_done); + void client_kex(std::unique_ptr client_kex); + + void client_certs(std::unique_ptr client_certs); + void server_certs(std::unique_ptr server_certs); - void client_verify(Certificate_Verify_12* client_verify); - void server_verify(Certificate_Verify_12* server_verify); + void client_verify(std::unique_ptr client_verify); + void server_verify(std::unique_ptr server_verify); - void server_finished(Finished_12* server_finished); - void client_finished(Finished_12* client_finished); + void server_finished(std::unique_ptr server_finished); + void client_finished(std::unique_ptr client_finished); - void new_session_ticket(New_Session_Ticket_12* new_session_ticket); + void new_session_ticket(std::unique_ptr new_session_ticket); const Client_Hello_12* client_hello() const { return m_client_hello.get(); } @@ -156,6 +160,8 @@ const Finished_12* client_finished() const { return m_client_finished.get(); } + virtual std::vector peer_cert_chain() const = 0; + const Ciphersuite& ciphersuite() const; std::optional psk_identity() const; diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_messages_12.h botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_messages_12.h --- botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_messages_12.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_messages_12.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,424 @@ +/* +* TLS Messages +* (C) 2004-2011,2015 Jack Lloyd +* 2016 Matthias Gierlings +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_TLS_MESSAGES_12_H_ +#define BOTAN_TLS_MESSAGES_12_H_ + +#include +#include + +namespace Botan { + +class PK_Key_Agreement_Key; + +namespace TLS { + +class BOTAN_UNSTABLE_API Client_Hello_12 final : public Client_Hello_12_Shim { + public: + class Settings final { + public: + explicit Settings(const Protocol_Version version, std::string_view hostname = "") : + m_new_session_version(version), m_hostname(hostname) {} + + Protocol_Version protocol_version() const { return m_new_session_version; } + + const std::string& hostname() const { return m_hostname; } + + private: + const Protocol_Version m_new_session_version; + const std::string m_hostname; + }; + + public: + Client_Hello_12(Handshake_IO& io, + Handshake_Hash& hash, + const Policy& policy, + Callbacks& cb, + RandomNumberGenerator& rng, + const std::vector& reneg_info, + const Settings& client_settings, + const std::vector& next_protocols); + + Client_Hello_12(Handshake_IO& io, + Handshake_Hash& hash, + const Policy& policy, + Callbacks& cb, + RandomNumberGenerator& rng, + const std::vector& reneg_info, + const Session_with_Handle& session_and_handle, + const std::vector& next_protocols); + + explicit Client_Hello_12(const std::vector& buf); + + private: + explicit Client_Hello_12(std::unique_ptr data); + + public: + using Client_Hello::compression_methods; + using Client_Hello::random; + + bool prefers_compressed_ec_points() const; + + bool secure_renegotiation() const; + + std::vector renegotiation_info() const; + + bool supports_session_ticket() const; + + Session_Ticket session_ticket() const; + + std::optional session_handle() const; + + bool supports_extended_master_secret() const; + + bool supports_cert_status_message() const; + + bool supports_encrypt_then_mac() const; + + void update_hello_cookie(const Hello_Verify_Request& hello_verify); + + private: + void add_tls12_supported_groups_extensions(const Policy& policy); +}; + +class BOTAN_UNSTABLE_API Server_Hello_12 final : public Server_Hello_12_Shim { + public: + class Settings final { + public: + Settings(Session_ID new_session_id, + Protocol_Version new_session_version, + uint16_t ciphersuite, + bool offer_session_ticket) : + m_new_session_id(std::move(new_session_id)), + m_new_session_version(new_session_version), + m_ciphersuite(ciphersuite), + m_offer_session_ticket(offer_session_ticket) {} + + const Session_ID& session_id() const { return m_new_session_id; } + + Protocol_Version protocol_version() const { return m_new_session_version; } + + uint16_t ciphersuite() const { return m_ciphersuite; } + + bool offer_session_ticket() const { return m_offer_session_ticket; } + + private: + const Session_ID m_new_session_id; + Protocol_Version m_new_session_version; + uint16_t m_ciphersuite; + bool m_offer_session_ticket; + }; + + Server_Hello_12(Handshake_IO& io, + Handshake_Hash& hash, + const Policy& policy, + Callbacks& cb, + RandomNumberGenerator& rng, + const std::vector& secure_reneg_info, + const Client_Hello_12& client_hello, + const Settings& settings, + std::string_view next_protocol); + + Server_Hello_12(Handshake_IO& io, + Handshake_Hash& hash, + const Policy& policy, + Callbacks& cb, + RandomNumberGenerator& rng, + const std::vector& secure_reneg_info, + const Client_Hello_12& client_hello, + const Session& resumed_session, + bool offer_session_ticket, + std::string_view next_protocol); + + explicit Server_Hello_12(const std::vector& buf); + + private: + explicit Server_Hello_12(std::unique_ptr data); + + public: + using Server_Hello::compression_method; + using Server_Hello::extension_types; + using Server_Hello::legacy_version; + using Server_Hello::random; + + bool secure_renegotiation() const; + + std::vector renegotiation_info() const; + + std::string next_protocol() const; + + bool supports_extended_master_secret() const; + + bool supports_encrypt_then_mac() const; + + bool supports_certificate_status_message() const; + + bool supports_session_ticket() const; + + uint16_t srtp_profile() const; + bool prefers_compressed_ec_points() const; +}; + +/** +* Client Key Exchange Message +*/ +class BOTAN_UNSTABLE_API Client_Key_Exchange final : public Handshake_Message { + public: + Handshake_Type type() const override { return Handshake_Type::ClientKeyExchange; } + + const secure_vector& pre_master_secret() const { return m_pre_master; } + + /** + * @returns the agreed upon PSK identity or std::nullopt if not applicable + */ + const std::optional& psk_identity() const { return m_psk_identity; } + + Client_Key_Exchange(Handshake_IO& io, + Handshake_State& state, + const Policy& policy, + Credentials_Manager& creds, + const Public_Key* server_public_key, + std::string_view hostname, + RandomNumberGenerator& rng); + + Client_Key_Exchange(const std::vector& buf, + const Handshake_State& state, + const Private_Key* server_rsa_kex_key, + Credentials_Manager& creds, + const Policy& policy, + RandomNumberGenerator& rng); + + private: + std::vector serialize() const override { return m_key_material; } + + std::vector m_key_material; + secure_vector m_pre_master; + std::optional m_psk_identity; +}; + +/** +* Certificate Message of TLS 1.2 +*/ +class BOTAN_UNSTABLE_API Certificate_12 final : public Handshake_Message /* NOLINT(*-special-member-functions) */ { + public: + Handshake_Type type() const override { return Handshake_Type::Certificate; } + + const std::vector& cert_chain() const { return m_certs; } + + size_t count() const; + + bool empty() const { return m_certs.empty(); } + + Certificate_12(Handshake_IO& io, Handshake_Hash& hash, const std::vector& certs); + + Certificate_12(const std::vector& buf, const Policy& policy); + + ~Certificate_12() override; + + std::vector serialize() const override; + + private: + std::vector m_certs; +}; + +/** +* Certificate Request Message (TLS 1.2) +*/ +class BOTAN_UNSTABLE_API Certificate_Request_12 final : public Handshake_Message { + public: + Handshake_Type type() const override; + + const std::vector& acceptable_cert_types() const; + + const std::vector& acceptable_CAs() const; + + const std::vector& signature_schemes() const; + + Certificate_Request_12(Handshake_IO& io, + Handshake_Hash& hash, + const Policy& policy, + const std::vector& allowed_cas); + + explicit Certificate_Request_12(const std::vector& buf); + + ~Certificate_Request_12() override; + + Certificate_Request_12(const Certificate_Request_12&) = delete; + Certificate_Request_12(Certificate_Request_12&&) = delete; + Certificate_Request_12& operator=(const Certificate_Request_12& other) = delete; + Certificate_Request_12& operator=(Certificate_Request_12&& other) = delete; + + std::vector serialize() const override; + + private: + std::vector m_names; + std::vector m_cert_key_types; + std::vector m_schemes; +}; + +/** +* Certificate Verify Message +*/ +class BOTAN_UNSTABLE_API Certificate_Verify_12 final : public Certificate_Verify { + public: + using Certificate_Verify::Certificate_Verify; + + Certificate_Verify_12(Handshake_IO& io, + Handshake_State& state, + const Policy& policy, + RandomNumberGenerator& rng, + const Private_Key* key); + + /** + * Check the signature on a certificate verify message + * @param cert the purported certificate + * @param state the handshake state + * @param policy the TLS policy + */ + bool verify(const X509_Certificate& cert, const Handshake_State& state, const Policy& policy) const; +}; + +/** +* Certificate Status (RFC 6066) +*/ +class BOTAN_UNSTABLE_API Certificate_Status_12 final : public Certificate_Status { + public: + /* + * Create a Certificate_Status message using an already DER encoded OCSP response. + */ + Certificate_Status_12(Handshake_IO& io, Handshake_Hash& hash, std::vector raw_response_bytes); +}; + +class BOTAN_UNSTABLE_API Finished_12 final : public Finished { + public: + using Finished::Finished; + Finished_12(Handshake_IO& io, Handshake_State& state, Connection_Side side); + + bool verify(const Handshake_State& state, Connection_Side side) const; +}; + +/** +* Hello Request Message +*/ +class BOTAN_UNSTABLE_API Hello_Request final : public Handshake_Message { + public: + Handshake_Type type() const override { return Handshake_Type::HelloRequest; } + + explicit Hello_Request(Handshake_IO& io); + explicit Hello_Request(const std::vector& buf); + + private: + std::vector serialize() const override; +}; + +/** +* Server Key Exchange Message +*/ +class BOTAN_UNSTABLE_API Server_Key_Exchange final : public Handshake_Message { + public: + Handshake_Type type() const override { return Handshake_Type::ServerKeyExchange; } + + const std::vector& params() const { return m_params; } + + bool verify(const Public_Key& server_key, const Handshake_State& state, const Policy& policy) const; + + // Only valid for certain kex types + const PK_Key_Agreement_Key& server_kex_key() const; + + /** + * @returns the agreed upon KEX group or std::nullopt if the KEX type does + * not depend on a group + */ + const std::optional& shared_group() const { return m_shared_group; } + + Server_Key_Exchange(Handshake_IO& io, + Handshake_State& state, + const Policy& policy, + Credentials_Manager& creds, + RandomNumberGenerator& rng, + const Private_Key* signing_key = nullptr); + + Server_Key_Exchange(const std::vector& buf, + Kex_Algo kex_alg, + Auth_Method sig_alg, + Protocol_Version version); + + ~Server_Key_Exchange() override; + + Server_Key_Exchange(const Server_Key_Exchange& other) = delete; + Server_Key_Exchange(Server_Key_Exchange&& other) = delete; + Server_Key_Exchange& operator=(const Server_Key_Exchange& other) = delete; + Server_Key_Exchange& operator=(Server_Key_Exchange&& other) = delete; + + private: + std::vector serialize() const override; + + std::unique_ptr m_kex_key; + std::optional m_shared_group; + + std::vector m_params; + + std::vector m_signature; + Signature_Scheme m_scheme; +}; + +/** +* Server Hello Done Message +*/ +class BOTAN_UNSTABLE_API Server_Hello_Done final : public Handshake_Message { + public: + Handshake_Type type() const override { return Handshake_Type::ServerHelloDone; } + + explicit Server_Hello_Done(Handshake_IO& io, Handshake_Hash& hash); + explicit Server_Hello_Done(const std::vector& buf); + + private: + std::vector serialize() const override; +}; + +/** +* New Session Ticket Message +*/ +class BOTAN_UNSTABLE_API New_Session_Ticket_12 final : public Handshake_Message { + public: + Handshake_Type type() const override { return Handshake_Type::NewSessionTicket; } + + uint32_t ticket_lifetime_hint() const { return m_ticket_lifetime_hint; } + + const Session_Ticket& ticket() const { return m_ticket; } + + New_Session_Ticket_12(Handshake_IO& io, + Handshake_Hash& hash, + Session_Ticket ticket, + uint32_t lifetime_in_seconds); + + New_Session_Ticket_12(Handshake_IO& io, Handshake_Hash& hash); + + explicit New_Session_Ticket_12(const std::vector& buf); + + std::vector serialize() const override; + + private: + uint32_t m_ticket_lifetime_hint = 0; + Session_Ticket m_ticket; +}; + +/** +* Change Cipher Spec +*/ +class BOTAN_UNSTABLE_API Change_Cipher_Spec final : public Handshake_Message { + public: + Handshake_Type type() const override { return Handshake_Type::HandshakeCCS; } + + std::vector serialize() const override { return std::vector(1, 1); } +}; + +} // namespace TLS +} // namespace Botan + +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_null/info.txt botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_null/info.txt --- botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_null/info.txt 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_null/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,19 @@ +# Disable this module by default +load_on request + + +TLS_NULL -> 20240830 + + + +name -> "TLS 1.2 Null cipher" +brief -> "Null cipher + HMAC AEAD mode of operation for TLS 1.2" + + + +tls_null.h + + + +hmac + diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_null/tls_null.cpp botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_null/tls_null.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_null/tls_null.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_null/tls_null.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,154 @@ +/* +* TLS Null Cipher Handling +* (C) 2024 Sebastian Ahrens, Dirk Dobkowitz, André Schomburg (Volkswagen AG) +* (C) 2024 Lars Dürkop (CARIAD SE) +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include +#include + +namespace Botan::TLS { + +/* +* TLS_NULL_HMAC_AEAD_Mode Constructor +*/ +TLS_NULL_HMAC_AEAD_Mode::TLS_NULL_HMAC_AEAD_Mode(std::unique_ptr mac, size_t mac_keylen) : + m_mac_name(mac->name()), m_mac_keylen(mac_keylen), m_tag_size(mac->output_length()), m_mac(std::move(mac)) {} + +void TLS_NULL_HMAC_AEAD_Mode::clear() { + m_key.clear(); + m_ad.clear(); + mac().clear(); +} + +void TLS_NULL_HMAC_AEAD_Mode::reset() { + m_ad.clear(); + // The base AEAD_Mode contract permits reset() before the first key has + // been set; only re-key the MAC if there is a key to re-key with. + if(!m_key.empty()) { + mac().set_key(m_key); + } +} + +std::string TLS_NULL_HMAC_AEAD_Mode::name() const { + return fmt("TLS_NULL({})", m_mac_name); +} + +size_t TLS_NULL_HMAC_AEAD_Mode::update_granularity() const { + return 1; +} + +size_t TLS_NULL_HMAC_AEAD_Mode::ideal_granularity() const { + return 1; +} + +bool TLS_NULL_HMAC_AEAD_Mode::valid_nonce_length(size_t nl) const { + return nl == 0; +} + +Key_Length_Specification TLS_NULL_HMAC_AEAD_Mode::key_spec() const { + return Key_Length_Specification(m_mac_keylen); +} + +bool TLS_NULL_HMAC_AEAD_Mode::has_keying_material() const { + return mac().has_keying_material(); +} + +size_t TLS_NULL_HMAC_AEAD_Mode::mac_keylen() const { + return m_mac_keylen; +} + +MessageAuthenticationCode& TLS_NULL_HMAC_AEAD_Mode::mac() const { + BOTAN_ASSERT_NONNULL(m_mac); + return *m_mac; +} + +void TLS_NULL_HMAC_AEAD_Mode::key_schedule(std::span key) { + if(key.size() != m_mac_keylen) { + throw Invalid_Key_Length(name(), key.size()); + } + m_key.assign(key.begin(), key.end()); + reset(); +} + +void TLS_NULL_HMAC_AEAD_Mode::start_msg(const uint8_t nonce[], size_t nonce_len) { + BOTAN_UNUSED(nonce); + + if(!valid_nonce_length(nonce_len)) { + throw Invalid_IV_Length(name(), nonce_len); + } + + m_processed = false; + + // AEAD_Mode contract: AD set via set_associated_data persists across + // messages until reset. finish_msg calls mac().final() which clears the + // internal state, so we re-feed the cached AD at the start of each + // message rather than once at set_associated_data time. + if(!m_ad.empty()) { + mac().update(m_ad); + } +} + +size_t TLS_NULL_HMAC_AEAD_Mode::process_msg(uint8_t buf[], size_t sz) { + // The TLS record code path MACs each record in a single call (via + // finish_msg -> process). A second invocation between start_msg and + // finish_msg would feed additional bytes into the same HMAC instance, + // producing a tag covering more than the intended record body. + BOTAN_ASSERT_NOMSG(!m_processed); + m_processed = true; + + mac().update(buf, sz); + return sz; +} + +void TLS_NULL_HMAC_AEAD_Mode::set_associated_data_n(size_t idx, std::span ad) { + BOTAN_ARG_CHECK(idx == 0, "TLS 1.2 NULL/HMAC: cannot handle non-zero index in set_associated_data_n"); + BOTAN_ARG_CHECK(ad.size() == 13, "TLS 1.2 NULL/HMAC: invalid TLS AEAD associated data length"); + + // Cache the AD; the actual MAC update happens at start_msg so the AD + // persists across messages per the AEAD_Mode contract. + m_ad.assign(ad.begin(), ad.end()); +} + +void TLS_NULL_HMAC_AEAD_Encryption::set_associated_data_n(size_t idx, std::span ad) { + TLS_NULL_HMAC_AEAD_Mode::set_associated_data_n(idx, ad); +} + +size_t TLS_NULL_HMAC_AEAD_Encryption::output_length(size_t input_length) const { + return input_length + tag_size(); +} + +void TLS_NULL_HMAC_AEAD_Encryption::finish_msg(secure_vector& buffer, size_t offset) { + process(std::span{buffer}.subspan(offset)); + buffer.resize(buffer.size() + tag_size()); + mac().final(std::span{buffer}.last(tag_size())); +} + +size_t TLS_NULL_HMAC_AEAD_Decryption::output_length(size_t input_length) const { + return input_length - tag_size(); +} + +void TLS_NULL_HMAC_AEAD_Decryption::finish_msg(secure_vector& buffer, size_t offset) { + BOTAN_ARG_CHECK(buffer.size() >= tag_size() + offset, + "TLS_NULL_HMAC_AEAD_Decryption needs at least tag_size() bytes in final buffer"); + + const auto data_and_tag = std::span{buffer}.subspan(offset); + const auto data = data_and_tag.first(data_and_tag.size() - tag_size()); + const auto tag = data_and_tag.subspan(data.size()); + + process(data); + if(!mac().verify_mac(tag)) { + throw TLS_Exception(Alert::BadRecordMac, "Message authentication failure"); + } + + buffer.resize(buffer.size() - tag_size()); +} + +} // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_null/tls_null.h botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_null/tls_null.h --- botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_null/tls_null.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_null/tls_null.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,112 @@ +/* +* TLS Null Cipher Handling +* (C) 2024 Sebastian Ahrens, Dirk Dobkowitz, André Schomburg (Volkswagen AG) +* (C) 2024 Lars Dürkop (CARIAD SE) +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_TLS_NULL_HMAC_AEAD_H_ +#define BOTAN_TLS_NULL_HMAC_AEAD_H_ + +#include +#include +#include + +namespace Botan::TLS { + +/** +* TLS NULL+HMAC AEAD base class (GenericStreamCipher in TLS spec) +*/ +class BOTAN_TEST_API TLS_NULL_HMAC_AEAD_Mode : public AEAD_Mode { + public: + std::string name() const final; + + void set_associated_data_n(size_t idx, std::span ad) override; + + size_t update_granularity() const final; + + size_t ideal_granularity() const final; + + Key_Length_Specification key_spec() const final; + + bool valid_nonce_length(size_t nl) const final; + + size_t tag_size() const final { return m_tag_size; } + + void clear() final; + + void reset() final; + + bool has_keying_material() const final; + + protected: + TLS_NULL_HMAC_AEAD_Mode(std::unique_ptr mac, size_t mac_keylen); + + size_t mac_keylen() const; + + MessageAuthenticationCode& mac() const; + + private: + void start_msg(const uint8_t nonce[], size_t nonce_len) final; + size_t process_msg(uint8_t buf[], size_t sz) final; + + void key_schedule(std::span key) final; + + const std::string m_mac_name; + size_t m_mac_keylen; + size_t m_tag_size; + + secure_vector m_key; + std::unique_ptr m_mac; + + // Per the AEAD_Mode contract, associated data set via + // set_associated_data persists across messages until reset. finish_msg + // calls mac().final() which clears the internal state, so we cache the + // AD here and re-feed it at start_msg time. + std::vector m_ad; + + // Single-call contract for process_msg: the TLS record code path is + // expected to MAC the entire record in one shot via finish_msg. A second + // process_msg call between start_msg and finish_msg would re-feed bytes + // to the MAC and produce a tag covering them twice; this flag is + // asserted to catch any such future misuse. + bool m_processed = false; +}; + +/** +* TLS_NULL_HMAC_AEAD Encryption +*/ +class BOTAN_TEST_API TLS_NULL_HMAC_AEAD_Encryption final : public TLS_NULL_HMAC_AEAD_Mode { + public: + TLS_NULL_HMAC_AEAD_Encryption(std::unique_ptr mac, const size_t mac_keylen) : + TLS_NULL_HMAC_AEAD_Mode(std::move(mac), mac_keylen) {} + + void set_associated_data_n(size_t idx, std::span ad) override; + + size_t output_length(size_t input_length) const override; + + size_t minimum_final_size() const override { return 0; } + + private: + void finish_msg(secure_vector& final_block, size_t offset = 0) override; +}; + +/** +* TLS_NULL_HMAC_AEAD Decryption +*/ +class BOTAN_TEST_API TLS_NULL_HMAC_AEAD_Decryption final : public TLS_NULL_HMAC_AEAD_Mode { + public: + TLS_NULL_HMAC_AEAD_Decryption(std::unique_ptr mac, const size_t mac_keylen) : + TLS_NULL_HMAC_AEAD_Mode(std::move(mac), mac_keylen) {} + + size_t output_length(size_t input_length) const override; + + size_t minimum_final_size() const override { return tag_size(); } + + void finish_msg(secure_vector& final_block, size_t offset = 0) override; +}; + +} // namespace Botan::TLS + +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_record.cpp botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_record.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_record.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_record.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,41 +9,50 @@ #include +#include +#include +#include #include -#include #include #include -#include +#include #include #include #include -#include #if defined(BOTAN_HAS_TLS_CBC) #include #endif +#if defined(BOTAN_HAS_TLS_NULL) + #include +#endif + namespace Botan::TLS { +Connection_Cipher_State::~Connection_Cipher_State() = default; + Connection_Cipher_State::Connection_Cipher_State(Protocol_Version version, Connection_Side side, bool our_side, const Ciphersuite& suite, const Session_Keys& keys, bool uses_encrypt_then_mac) { + // NOLINTBEGIN(*-prefer-member-initializer) m_nonce_format = suite.nonce_format(); m_nonce_bytes_from_record = suite.nonce_bytes_from_record(version); m_nonce_bytes_from_handshake = suite.nonce_bytes_from_handshake(); const secure_vector& aead_key = keys.aead_key(side); m_nonce = keys.nonce(side); + // NOLINTEND(*-prefer-member-initializer) BOTAN_ASSERT_NOMSG(m_nonce.size() == m_nonce_bytes_from_handshake); if(nonce_format() == Nonce_Format::CBC_MODE) { #if defined(BOTAN_HAS_TLS_CBC) // legacy CBC+HMAC mode - auto mac = MessageAuthenticationCode::create_or_throw("HMAC(" + suite.mac_algo() + ")"); + auto mac = MessageAuthenticationCode::create_or_throw(fmt("HMAC({})", suite.mac_algo())); auto cipher = BlockCipher::create_or_throw(suite.cipher_algo()); if(our_side) { @@ -66,6 +75,18 @@ BOTAN_UNUSED(uses_encrypt_then_mac); throw Internal_Error("Negotiated disabled TLS CBC+HMAC ciphersuite"); #endif + } else if(nonce_format() == Nonce_Format::NULL_CIPHER) { +#if defined(BOTAN_HAS_TLS_NULL) + auto mac = MessageAuthenticationCode::create_or_throw(fmt("HMAC({})", suite.mac_algo())); + + if(our_side) { + m_aead = std::make_unique(std::move(mac), suite.mac_keylen()); + } else { + m_aead = std::make_unique(std::move(mac), suite.mac_keylen()); + } +#else + throw Internal_Error("Negotiated disabled TLS NULL ciphersuite"); +#endif } else { m_aead = AEAD_Mode::create_or_throw(suite.cipher_algo(), our_side ? Cipher_Dir::Encryption : Cipher_Dir::Decryption); @@ -76,17 +97,16 @@ std::vector Connection_Cipher_State::aead_nonce(uint64_t seq, RandomNumberGenerator& rng) { switch(m_nonce_format) { + case Nonce_Format::NULL_CIPHER: { + return std::vector{}; + } case Nonce_Format::CBC_MODE: { - if(!m_nonce.empty()) { - std::vector nonce; - nonce.swap(m_nonce); - return nonce; - } std::vector nonce(nonce_bytes_from_record()); rng.randomize(nonce.data(), nonce.size()); return nonce; } case Nonce_Format::AEAD_XOR_12: { + BOTAN_ASSERT_NOMSG(m_nonce.size() == 12); std::vector nonce(12); store_be(seq, nonce.data() + 4); xor_buf(nonce, m_nonce.data(), m_nonce.size()); @@ -95,7 +115,7 @@ case Nonce_Format::AEAD_IMPLICIT_4: { BOTAN_ASSERT_NOMSG(m_nonce.size() == 4); std::vector nonce(12); - copy_mem(&nonce[0], m_nonce.data(), 4); + copy_mem(&nonce[0], m_nonce.data(), 4); // NOLINT(*container-data-pointer) store_be(seq, &nonce[nonce_bytes_from_handshake()]); return nonce; } @@ -106,12 +126,10 @@ std::vector Connection_Cipher_State::aead_nonce(const uint8_t record[], size_t record_len, uint64_t seq) { switch(m_nonce_format) { + case Nonce_Format::NULL_CIPHER: { + return std::vector{}; + } case Nonce_Format::CBC_MODE: { - if(nonce_bytes_from_record() == 0 && !m_nonce.empty()) { - std::vector nonce; - nonce.swap(m_nonce); - return nonce; - } if(record_len < nonce_bytes_from_record()) { throw Decoding_Error("Invalid CBC packet too short to be valid"); } @@ -119,6 +137,7 @@ return nonce; } case Nonce_Format::AEAD_XOR_12: { + BOTAN_ASSERT_NOMSG(m_nonce.size() == 12); std::vector nonce(12); store_be(seq, nonce.data() + 4); xor_buf(nonce, m_nonce.data(), m_nonce.size()); @@ -130,7 +149,7 @@ throw Decoding_Error("Invalid AEAD packet too short to be valid"); } std::vector nonce(12); - copy_mem(&nonce[0], m_nonce.data(), 4); + copy_mem(&nonce[0], m_nonce.data(), 4); // NOLINT(*container-data-pointer) copy_mem(&nonce[nonce_bytes_from_handshake()], record, nonce_bytes_from_record()); return nonce; } @@ -145,7 +164,7 @@ uint16_t msg_length) { std::vector ad(13); - store_be(msg_sequence, &ad[0]); + store_be(msg_sequence, &ad[0]); // NOLINT(*container-data-pointer) ad[8] = static_cast(msg_type); ad[9] = version.major_version(); ad[10] = version.minor_version(); @@ -299,7 +318,7 @@ const get_cipherstate_fn& get_cipherstate) { if(readbuf.size() < TLS_HEADER_SIZE) { // header incomplete - if(size_t needed = fill_buffer_to(readbuf, input, input_len, consumed, TLS_HEADER_SIZE)) { + if(const size_t needed = fill_buffer_to(readbuf, input, input_len, consumed, TLS_HEADER_SIZE)) { return Record_Header(needed); } @@ -310,16 +329,15 @@ Verify that the record type and record version are within some expected range, so we can quickly reject totally invalid packets. - The version check is a little hacky but given how TLS 1.3 versioning works - this is probably safe + Unfortunately we cannot be more strict about the record number than just + checking the major version, at least at this level, due to this requirement + in RFC 7568 - - The first byte is the record version which in TLS 1.2 is always in [20..23) - - The second byte is the TLS major version which is effectively fossilized at 3 - - The third byte is the TLS minor version which (due to TLS 1.3 versioning changes) - will never be more than 3 (signifying TLS 1.2) + TLS servers MUST accept any value {03,XX} (including {03,00}) as + the record layer version number for ClientHello */ const bool bad_record_type = readbuf[0] < 20 || readbuf[0] > 23; - const bool bad_record_version = readbuf[1] != 3 || readbuf[2] >= 4; + const bool bad_record_version = readbuf[1] != 3; if(bad_record_type || bad_record_version) { // We know we read up to at least the 5 byte TLS header @@ -359,7 +377,7 @@ throw TLS_Exception(Alert::DecodeError, "Received a completely empty record"); } - if(size_t needed = fill_buffer_to(readbuf, input, input_len, consumed, TLS_HEADER_SIZE + record_size)) { + if(const size_t needed = fill_buffer_to(readbuf, input, input_len, consumed, TLS_HEADER_SIZE + record_size)) { return Record_Header(needed); } @@ -370,7 +388,7 @@ uint16_t epoch = 0; uint64_t sequence = 0; - if(sequence_numbers) { + if(sequence_numbers != nullptr) { sequence = sequence_numbers->next_read_sequence(); epoch = sequence_numbers->current_read_epoch(); } else { @@ -392,7 +410,7 @@ decrypt_record(recbuf, &readbuf[TLS_HEADER_SIZE], record_size, sequence, version, type, *cs); - if(sequence_numbers) { + if(sequence_numbers != nullptr) { sequence_numbers->read_accept(sequence); } @@ -410,7 +428,7 @@ bool allow_epoch0_restart) { if(readbuf.size() < DTLS_HEADER_SIZE) { // header incomplete - if(fill_buffer_to(readbuf, input, input_len, consumed, DTLS_HEADER_SIZE)) { + if(fill_buffer_to(readbuf, input, input_len, consumed, DTLS_HEADER_SIZE) != 0) { readbuf.clear(); return Record_Header(0); } @@ -433,7 +451,7 @@ return Record_Header(0); } - if(fill_buffer_to(readbuf, input, input_len, consumed, DTLS_HEADER_SIZE + record_size)) { + if(fill_buffer_to(readbuf, input, input_len, consumed, DTLS_HEADER_SIZE + record_size) != 0) { // Truncated packet? readbuf.clear(); return Record_Header(0); @@ -446,7 +464,7 @@ const uint64_t sequence = load_be(&readbuf[3], 0); const uint16_t epoch = (sequence >> 48); - const bool already_seen = sequence_numbers && sequence_numbers->already_seen(sequence); + const bool already_seen = sequence_numbers != nullptr && sequence_numbers->already_seen(sequence); if(already_seen && !(epoch == 0 && allow_epoch0_restart)) { readbuf.clear(); @@ -457,7 +475,7 @@ // Unencrypted initial handshake recbuf.assign(readbuf.begin() + DTLS_HEADER_SIZE, readbuf.begin() + DTLS_HEADER_SIZE + record_size); readbuf.clear(); - if(sequence_numbers) { + if(sequence_numbers != nullptr) { sequence_numbers->read_accept(sequence); } return Record_Header(sequence, version, type); @@ -475,7 +493,7 @@ return Record_Header(0); } - if(sequence_numbers) { + if(sequence_numbers != nullptr) { sequence_numbers->read_accept(sequence); } diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_record.h botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_record.h --- botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_record.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_record.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,18 +9,24 @@ #ifndef BOTAN_TLS_RECORDS_H_ #define BOTAN_TLS_RECORDS_H_ -#include +#include +#include #include #include #include -#include -#include #include +#include #include +namespace Botan { + +class AEAD_Mode; +class RandomNumberGenerator; + +} // namespace Botan + namespace Botan::TLS { -class Callbacks; class Ciphersuite; class Session_Keys; @@ -41,6 +47,14 @@ const Session_Keys& keys, bool uses_encrypt_then_mac); + ~Connection_Cipher_State(); + + Connection_Cipher_State(const Connection_Cipher_State& other) = delete; + Connection_Cipher_State(Connection_Cipher_State&& other) = delete; + + Connection_Cipher_State& operator=(const Connection_Cipher_State& other) = delete; + Connection_Cipher_State& operator=(Connection_Cipher_State&& other) = delete; + AEAD_Mode& aead() { BOTAN_ASSERT_NONNULL(m_aead.get()); return *m_aead; @@ -72,8 +86,7 @@ Record_Header(uint64_t sequence, Protocol_Version version, Record_Type type) : m_needed(0), m_sequence(sequence), m_version(version), m_type(type) {} - Record_Header(size_t needed) : - m_needed(needed), m_sequence(0), m_version(Protocol_Version()), m_type(Record_Type::Invalid) {} + explicit Record_Header(size_t needed) : m_needed(needed), m_sequence(0), m_type(Record_Type::Invalid) {} size_t needed() const { return m_needed; } diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_seq_numbers.h botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_seq_numbers.h --- botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_seq_numbers.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_seq_numbers.h 2026-05-07 01:38:28.000000000 +0000 @@ -8,12 +8,14 @@ #ifndef BOTAN_TLS_SEQ_NUMBERS_H_ #define BOTAN_TLS_SEQ_NUMBERS_H_ +#include #include +#include #include namespace Botan::TLS { -class Connection_Sequence_Numbers { +class Connection_Sequence_Numbers /* NOLINT(*-special-member-functions) */ { public: virtual ~Connection_Sequence_Numbers() = default; @@ -34,7 +36,7 @@ class Stream_Sequence_Numbers final : public Connection_Sequence_Numbers { public: - Stream_Sequence_Numbers() { Stream_Sequence_Numbers::reset(); } + Stream_Sequence_Numbers() : m_write_seq_no(0), m_read_seq_no(0), m_read_epoch(0), m_write_epoch(0) {} void reset() override { m_write_seq_no = 0; @@ -57,13 +59,23 @@ uint16_t current_write_epoch() const override { return m_write_epoch; } - uint64_t next_write_sequence(uint16_t) override { return m_write_seq_no++; } + uint64_t next_write_sequence(uint16_t /*epoch*/) override { + if(m_write_seq_no == std::numeric_limits::max()) { + throw Invalid_State("TLS 1.2 write sequence number overflow"); + } + return m_write_seq_no++; + } uint64_t next_read_sequence() override { return m_read_seq_no; } - bool already_seen(uint64_t) const override { return false; } + bool already_seen(uint64_t /*seq*/) const override { return false; } - void read_accept(uint64_t) override { m_read_seq_no++; } + void read_accept(uint64_t /*seq*/) override { + if(m_read_seq_no == std::numeric_limits::max()) { + throw Invalid_State("TLS 1.2 read sequence number overflow"); + } + m_read_seq_no++; + } private: uint64_t m_write_seq_no; @@ -99,6 +111,9 @@ uint64_t next_write_sequence(uint16_t epoch) override { auto i = m_write_seqs.find(epoch); BOTAN_ASSERT(i != m_write_seqs.end(), "Found epoch"); + if(i->second > 0x0000FFFFFFFFFFFF) { + throw Invalid_State("DTLS write sequence number overflow"); + } return (static_cast(epoch) << 48) | i->second++; } diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_server_impl_12.cpp botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_server_impl_12.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_server_impl_12.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_server_impl_12.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,13 +8,18 @@ #include +#include #include +#include #include -#include -#include +#include +#include #include +#include #include #include +#include +#include namespace Botan::TLS { @@ -40,6 +45,16 @@ bool is_a_resumption() const { return m_is_a_resumption; } + std::vector peer_cert_chain() const override { + if(!m_resume_peer_certs.empty()) { + return m_resume_peer_certs; + } + if(client_certs() != nullptr) { + return client_certs()->cert_chain(); + } + return {}; + } + private: // Used by the server only, in case of RSA key exchange. std::shared_ptr m_server_rsa_kex_key; @@ -130,18 +145,47 @@ const bool have_shared_dh_group = (policy.choose_key_exchange_group(client_hello.supported_dh_groups(), {}) != Group_Params::NONE); + const std::unordered_set client_suite_set(client_suites.begin(), client_suites.end()); + + const std::vector allowed_sig_schemes = policy.allowed_signature_schemes(); + const std::vector client_sig_methods = client_hello.signature_schemes(); + + // Algorithm names (eg "RSA", "ECDSA") for which the client offered at least + // one signature_scheme that is available, is in our policy, and uses a hash we accept. + const std::unordered_set client_sig_algs = [&] { + std::unordered_set allowed_codes; + allowed_codes.reserve(allowed_sig_schemes.size()); + for(auto s : allowed_sig_schemes) { + allowed_codes.insert(static_cast(s.wire_code())); + } + std::unordered_set result; + for(const Signature_Scheme scheme : client_sig_methods) { + if(!scheme.is_available()) { + continue; + } + if(!allowed_codes.contains(static_cast(scheme.wire_code()))) { + continue; + } + if(!policy.allowed_signature_hash(scheme.hash_function_name())) { + continue; + } + result.insert(scheme.algorithm_name()); + } + return result; + }(); + /* Walk down one list in preference order */ - std::vector pref_list = server_suites; - std::vector other_list = client_suites; + const std::vector& pref_list = our_choice ? server_suites : client_suites; - if(!our_choice) { - std::swap(pref_list, other_list); - } + auto in_other_list = [&](uint16_t suite_id) { + // server_suites is small and policy-controlled + return our_choice ? client_suite_set.contains(suite_id) : value_exists(server_suites, suite_id); + }; for(auto suite_id : pref_list) { - if(!value_exists(other_list, suite_id)) { + if(!in_other_list(suite_id)) { continue; } @@ -160,39 +204,24 @@ } // For non-anon ciphersuites - if(suite->signature_used()) { - const std::string sig_algo = suite->sig_algo(); + if(suite->is_certificate_required()) { + const std::string cert_algo = suite->signature_used() ? suite->sig_algo() : "RSA"; // Do we have any certificates for this sig? - if(!cert_chains.contains(sig_algo)) { + if(!cert_chains.contains(cert_algo)) { continue; } + } - const std::vector allowed = policy.allowed_signature_schemes(); - - std::vector client_sig_methods = client_hello.signature_schemes(); - - /* - Contrary to the wording of draft-ietf-tls-md5-sha1-deprecate we do - not enforce that clients do not offer support SHA-1 or MD5 - signatures; we just ignore it. - */ - bool we_support_some_hash_by_client = false; - - for(Signature_Scheme scheme : client_sig_methods) { - if(!scheme.is_available()) { - continue; - } - - if(scheme.algorithm_name() == suite->sig_algo() && - policy.allowed_signature_hash(scheme.hash_function_name())) { - we_support_some_hash_by_client = true; - } - } - - if(we_support_some_hash_by_client == false) { - throw TLS_Exception(Alert::HandshakeFailure, - "Policy does not accept any hash function supported by client"); + if(suite->signature_used()) { + // The client's signature_algorithms list might not include a scheme + // matching this suite's sig_algo (e.g. the client offered ECDSA + // schemes but we're considering an RSA suite). That's just a + // mismatch on this candidate, not a handshake-fatal condition - try + // the next suite. The final "Can't agree on a ciphersuite" throw + // below fires only if no candidate works. + if(!client_sig_algs.contains(suite->sig_algo())) { + continue; } } @@ -214,16 +243,16 @@ std::map> get_server_certs( std::string_view hostname, const std::vector& cert_sig_schemes, Credentials_Manager& creds) { - const char* cert_types[] = {"RSA", "ECDSA", "DSA", nullptr}; + const std::vector cert_types = {"RSA", "ECDSA"}; std::map> cert_chains; - for(size_t i = 0; cert_types[i]; ++i) { + for(const auto& cert_type : cert_types) { const std::vector certs = creds.cert_chain_single_type( - cert_types[i], to_algorithm_identifiers(cert_sig_schemes), "tls-server", std::string(hostname)); + cert_type, to_algorithm_identifiers(cert_sig_schemes), "tls-server", std::string(hostname)); if(!certs.empty()) { - cert_chains[cert_types[i]] = certs; + cert_chains[cert_type] = certs; } } @@ -259,25 +288,13 @@ return state; } -std::vector Server_Impl_12::get_peer_cert_chain(const Handshake_State& state_base) const { - const Server_Handshake_State& state = dynamic_cast(state_base); - if(!state.resume_peer_certs().empty()) { - return state.resume_peer_certs(); - } - - if(state.client_certs()) { - return state.client_certs()->cert_chain(); - } - return std::vector(); -} - /* * Send a hello request to the client */ void Server_Impl_12::initiate_handshake(Handshake_State& state, bool force_full_renegotiation) { dynamic_cast(state).set_allow_session_resumption(!force_full_renegotiation); - Hello_Request hello_req(state.handshake_io()); + const Hello_Request hello_req(state.handshake_io()); } namespace { @@ -338,13 +355,12 @@ /* * Process a Client Hello Message */ -void Server_Impl_12::process_client_hello_msg(const Handshake_State* active_state, - Server_Handshake_State& pending_state, +void Server_Impl_12::process_client_hello_msg(Server_Handshake_State& pending_state, const std::vector& contents, bool epoch0_restart) { - BOTAN_ASSERT_IMPLICATION(epoch0_restart, active_state != nullptr, "Can't restart with a dead connection"); + BOTAN_ASSERT_IMPLICATION(epoch0_restart, active_state().has_value(), "Can't restart with a dead connection"); - const bool initial_handshake = epoch0_restart || !active_state; + const bool initial_handshake = epoch0_restart || !active_state().has_value(); if(initial_handshake == false && policy().allow_client_initiated_renegotiation() == false) { if(policy().abort_connection_on_undesired_renegotiation()) { @@ -364,7 +380,7 @@ throw TLS_Exception(Alert::UnexpectedMessage, "Have data remaining in buffer after ClientHello"); } - pending_state.client_hello(new Client_Hello_12(contents)); + pending_state.client_hello(std::make_unique(contents)); const Protocol_Version client_offer = pending_state.client_hello()->legacy_version(); const bool datagram = client_offer.is_datagram_protocol(); @@ -394,7 +410,7 @@ const Protocol_Version negotiated_version = select_version(policy(), client_offer, - active_state ? active_state->version() : Protocol_Version(), + active_state().has_value() ? active_state()->version() : Protocol_Version(), pending_state.client_hello()->supported_versions()); pending_state.set_version(negotiated_version); @@ -413,16 +429,17 @@ if(!cookie_secret.empty()) { const std::string client_identity = callbacks().tls_peer_network_identity(); - Hello_Verify_Request verify(pending_state.client_hello()->cookie_input_data(), client_identity, cookie_secret); + const Hello_Verify_Request verify( + pending_state.client_hello()->cookie_input_data(), client_identity, cookie_secret); - if(pending_state.client_hello()->cookie() != verify.cookie()) { + if(!CT::is_equal(pending_state.client_hello()->cookie(), verify.cookie()).as_bool()) { if(epoch0_restart) { pending_state.handshake_io().send_under_epoch(verify, 0); } else { pending_state.handshake_io().send(verify); } - pending_state.client_hello(static_cast(nullptr)); + pending_state.client_hello(nullptr); pending_state.set_expected_next(Handshake_Type::ClientHello); return; } @@ -438,6 +455,29 @@ secure_renegotiation_check(pending_state.client_hello()); + // RFC 7627 / RFC 9325 4.4: optionally require Extended Master Secret + if(policy().require_extended_master_secret() && !pending_state.client_hello()->supports_extended_master_secret()) { + throw TLS_Exception(Alert::HandshakeFailure, + "Policy requires the Extended Master Secret extension but the client did not send it"); + } + + // RFC 7627 5.3 has an explicit MUST regarding EMS mismatch on resumption + // + // "If the original session used the 'extended_master_secret' + // extension but the new ClientHello does not contain it, the + // server MUST abort the abbreviated handshake." + // + // There is apparently no RFC requirement that a client must not drop EMS between the + // initial negotiation and a renegotiation... but there is also no RFC requirement + // that we must accept it. So we don't. + if(const auto& active = active_state()) { + const bool ems_pending = pending_state.client_hello()->supports_extended_master_secret(); + if(active->supports_extended_master_secret() == true && ems_pending == false) { + throw TLS_Exception(Alert::HandshakeFailure, + "Renegotiation ClientHello dropped the Extended Master Secret extension"); + } + } + callbacks().tls_examine_extensions( pending_state.client_hello()->extensions(), Connection_Side::Client, Handshake_Type::ClientHello); @@ -451,7 +491,14 @@ m_next_protocol = ""; if(pending_state.client_hello()->supports_alpn()) { - m_next_protocol = callbacks().tls_server_choose_app_protocol(pending_state.client_hello()->next_protocols()); + const auto offered = pending_state.client_hello()->next_protocols(); + m_next_protocol = callbacks().tls_server_choose_app_protocol(offered); + // RFC 7301 3.2: if a protocol is selected, the server MUST select one + // of the protocols advertised by the client. An empty return signals + // "no ALPN" and is allowed. + if(!m_next_protocol.empty() && !value_exists(offered, m_next_protocol)) { + throw TLS_Exception(Alert::InternalError, "Application chose an ALPN protocol that the client did not offer"); + } } if(session_info.has_value()) { @@ -464,7 +511,7 @@ void Server_Impl_12::process_certificate_msg(Server_Handshake_State& pending_state, const std::vector& contents) { - pending_state.client_certs(new Certificate_12(contents, policy())); + pending_state.client_certs(std::make_unique(contents, policy())); // CERTIFICATE_REQUIRED would make more sense but BoGo expects handshake failure alert if(pending_state.client_certs()->empty() && policy().require_client_certificate_authentication()) { @@ -482,8 +529,8 @@ pending_state.set_expected_next(Handshake_Type::HandshakeCCS); } - pending_state.client_kex( - new Client_Key_Exchange(contents, pending_state, pending_state.server_rsa_kex_key(), *m_creds, policy(), rng())); + pending_state.client_kex(std::make_unique( + contents, pending_state, pending_state.server_rsa_kex_key(), *m_creds, policy(), rng())); pending_state.compute_session_keys(); if(policy().allow_ssl_key_log_file()) { @@ -504,7 +551,7 @@ void Server_Impl_12::process_certificate_verify_msg(Server_Handshake_State& pending_state, Handshake_Type type, const std::vector& contents) { - pending_state.client_verify(new Certificate_Verify_12(contents)); + pending_state.client_verify(std::make_unique(contents)); const std::vector& client_certs = pending_state.client_certs()->cert_chain(); @@ -512,8 +559,11 @@ throw TLS_Exception(Alert::DecodeError, "No client certificate sent"); } - if(!client_certs[0].allowed_usage(Key_Constraints::DigitalSignature)) { - throw TLS_Exception(Alert::BadCertificate, "Client certificate does not support signing"); + const auto cert_constraints = client_certs[0].constraints(); + if(!cert_constraints.empty()) { + if(!cert_constraints.includes_any(Key_Constraints::DigitalSignature, Key_Constraints::NonRepudiation)) { + throw TLS_Exception(Alert::BadCertificate, "Client certificate does not support signing"); + } } const bool sig_valid = pending_state.client_verify()->verify(client_certs[0], pending_state, policy()); @@ -555,13 +605,13 @@ throw TLS_Exception(Alert::UnexpectedMessage, "Have data remaining in buffer after Finished"); } - pending_state.client_finished(new Finished_12(contents)); + pending_state.client_finished(std::make_unique(contents)); if(!pending_state.client_finished()->verify(pending_state, Connection_Side::Client)) { throw TLS_Exception(Alert::DecryptError, "Finished message didn't verify"); } - if(!pending_state.server_finished()) { + if(pending_state.server_finished() == nullptr) { // already sent finished if resuming, so this is a new session pending_state.hash().update(pending_state.handshake_io().format(contents, type)); @@ -572,7 +622,7 @@ Connection_Side::Server, pending_state.server_hello()->supports_extended_master_secret(), pending_state.server_hello()->supports_encrypt_then_mac(), - get_peer_cert_chain(pending_state), + pending_state.peer_cert_chain(), Server_Information(pending_state.client_hello()->sni_hostname()), pending_state.server_hello()->srtp_profile(), callbacks().tls_current_timestamp()); @@ -591,16 +641,17 @@ !pending_state.server_hello()->supports_session_ticket()); if(pending_state.server_hello()->supports_session_ticket() && handle.has_value() && handle->is_ticket()) { - pending_state.new_session_ticket(new New_Session_Ticket_12(pending_state.handshake_io(), - pending_state.hash(), - handle->ticket().value(), - policy().session_ticket_lifetime())); + pending_state.new_session_ticket(std::make_unique( + pending_state.handshake_io(), + pending_state.hash(), + handle->ticket().value(), + static_cast(policy().session_ticket_lifetime().count()))); } } - if(!pending_state.new_session_ticket() && pending_state.server_hello()->supports_session_ticket()) { + if(pending_state.new_session_ticket() == nullptr && pending_state.server_hello()->supports_session_ticket()) { pending_state.new_session_ticket( - new New_Session_Ticket_12(pending_state.handshake_io(), pending_state.hash())); + std::make_unique(pending_state.handshake_io(), pending_state.hash())); } pending_state.handshake_io().send(Change_Cipher_Spec()); @@ -608,7 +659,7 @@ change_cipher_spec_writer(Connection_Side::Server); pending_state.server_finished( - new Finished_12(pending_state.handshake_io(), pending_state, Connection_Side::Server)); + std::make_unique(pending_state.handshake_io(), pending_state, Connection_Side::Server)); } activate_session(); @@ -617,8 +668,7 @@ /* * Process a handshake message */ -void Server_Impl_12::process_handshake_msg(const Handshake_State* active_state, - Handshake_State& state_base, +void Server_Impl_12::process_handshake_msg(Handshake_State& state_base, Handshake_Type type, const std::vector& contents, bool epoch0_restart) { @@ -639,7 +689,7 @@ switch(type) { case Handshake_Type::ClientHello: - return this->process_client_hello_msg(active_state, state, contents, epoch0_restart); + return this->process_client_hello_msg(state, contents, epoch0_restart); case Handshake_Type::Certificate: return this->process_certificate_msg(state, contents); @@ -669,16 +719,16 @@ pending_state.client_hello()->session_ticket().empty() && session_manager().emits_session_tickets(); - pending_state.server_hello(new Server_Hello_12(pending_state.handshake_io(), - pending_state.hash(), - policy(), - callbacks(), - rng(), - secure_renegotiation_data_for_server_hello(), - *pending_state.client_hello(), - session.session, - offer_new_session_ticket, - m_next_protocol)); + pending_state.server_hello(std::make_unique(pending_state.handshake_io(), + pending_state.hash(), + policy(), + callbacks(), + rng(), + secure_renegotiation_data_for_server_hello(), + *pending_state.client_hello(), + session.session, + offer_new_session_ticket, + m_next_protocol)); secure_renegotiation_check(pending_state.server_hello()); @@ -716,13 +766,12 @@ if(pending_state.server_hello()->supports_session_ticket()) { if(new_handle.has_value() && new_handle->is_ticket()) { - pending_state.new_session_ticket(new New_Session_Ticket_12(pending_state.handshake_io(), - pending_state.hash(), - new_handle->ticket().value(), - policy().session_ticket_lifetime())); + const uint32_t lifetime = static_cast(policy().session_ticket_lifetime().count()); + pending_state.new_session_ticket(std::make_unique( + pending_state.handshake_io(), pending_state.hash(), new_handle->ticket().value(), lifetime)); } else { pending_state.new_session_ticket( - new New_Session_Ticket_12(pending_state.handshake_io(), pending_state.hash())); + std::make_unique(pending_state.handshake_io(), pending_state.hash())); } } @@ -730,7 +779,8 @@ change_cipher_spec_writer(Connection_Side::Server); - pending_state.server_finished(new Finished_12(pending_state.handshake_io(), pending_state, Connection_Side::Server)); + pending_state.server_finished( + std::make_unique(pending_state.handshake_io(), pending_state, Connection_Side::Server)); pending_state.set_expected_next(Handshake_Type::HandshakeCCS); } @@ -766,20 +816,20 @@ const uint16_t ciphersuite = choose_ciphersuite(policy(), pending_state.version(), cert_chains, *pending_state.client_hello()); - Server_Hello_12::Settings srv_settings(Session_ID(make_hello_random(rng(), callbacks(), policy())), - pending_state.version(), - ciphersuite, - session_manager().emits_session_tickets()); - - pending_state.server_hello(new Server_Hello_12(pending_state.handshake_io(), - pending_state.hash(), - policy(), - callbacks(), - rng(), - secure_renegotiation_data_for_server_hello(), - *pending_state.client_hello(), - srv_settings, - m_next_protocol)); + const Server_Hello_12::Settings srv_settings(Session_ID(make_hello_random(rng(), callbacks(), policy())), + pending_state.version(), + ciphersuite, + session_manager().emits_session_tickets()); + + pending_state.server_hello(std::make_unique(pending_state.handshake_io(), + pending_state.hash(), + policy(), + callbacks(), + rng(), + secure_renegotiation_data_for_server_hello(), + *pending_state.client_hello(), + srv_settings, + m_next_protocol)); secure_renegotiation_check(pending_state.server_hello()); @@ -787,13 +837,13 @@ std::shared_ptr private_key; - if(pending_suite.signature_used() || pending_suite.kex_method() == Kex_Algo::STATIC_RSA) { + if(pending_suite.is_certificate_required()) { const std::string algo_used = pending_suite.signature_used() ? pending_suite.sig_algo() : "RSA"; BOTAN_ASSERT(!cert_chains[algo_used].empty(), "Attempting to send empty certificate chain"); pending_state.server_certs( - new Certificate_12(pending_state.handshake_io(), pending_state.hash(), cert_chains[algo_used])); + std::make_unique(pending_state.handshake_io(), pending_state.hash(), cert_chains[algo_used])); if(pending_state.client_hello()->supports_cert_status_message() && pending_state.is_a_resumption() == false) { auto* csr = pending_state.client_hello()->extensions().get(); @@ -802,7 +852,7 @@ const auto resp_bytes = callbacks().tls_provide_cert_status(cert_chains[algo_used], *csr); if(!resp_bytes.empty()) { pending_state.server_cert_status( - new Certificate_Status(pending_state.handshake_io(), pending_state.hash(), resp_bytes)); + std::make_unique(pending_state.handshake_io(), pending_state.hash(), resp_bytes)); } } @@ -816,7 +866,7 @@ if(pending_suite.kex_method() == Kex_Algo::STATIC_RSA) { pending_state.set_server_rsa_kex_key(private_key); } else { - pending_state.server_kex(new Server_Key_Exchange( + pending_state.server_kex(std::make_unique( pending_state.handshake_io(), pending_state, policy(), *m_creds, rng(), private_key.get())); } @@ -831,9 +881,14 @@ const bool request_cert = (client_auth_CAs.empty() == false) || policy().request_client_certificate_authentication(); - if(request_cert && pending_state.ciphersuite().signature_used()) { - pending_state.cert_req( - new Certificate_Request_12(pending_state.handshake_io(), pending_state.hash(), policy(), client_auth_CAs)); + // RFC 5246 7.4.4: supported_signature_algorithms<2..2^16-2> + // Without at least one acceptable scheme we cannot construct a valid + // CertificateRequest, so client cert auth is unreachable regardless. + const bool can_request_cert = !policy().acceptable_signature_schemes().empty(); + + if(request_cert && can_request_cert && pending_state.ciphersuite().is_certificate_required()) { + pending_state.cert_req(std::make_unique( + pending_state.handshake_io(), pending_state.hash(), policy(), client_auth_CAs)); /* SSLv3 allowed clients to skip the Certificate message entirely @@ -845,6 +900,7 @@ pending_state.set_expected_next(Handshake_Type::ClientKeyExchange); } - pending_state.server_hello_done(new Server_Hello_Done(pending_state.handshake_io(), pending_state.hash())); + pending_state.server_hello_done( + std::make_unique(pending_state.handshake_io(), pending_state.hash())); } } // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_server_impl_12.h botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_server_impl_12.h --- botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_server_impl_12.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_server_impl_12.h 2026-05-07 01:38:28.000000000 +0000 @@ -10,21 +10,19 @@ #define BOTAN_TLS_SERVER_IMPL_12_H_ #include -#include #include #include namespace Botan::TLS { +class Policy; class Server_Handshake_State; /** * SSL/TLS Server 1.2 implementation */ -class Server_Impl_12 : public Channel_Impl_12 { +class Server_Impl_12 final : public Channel_Impl_12 { public: - typedef std::function)> next_protocol_fn; - /** * Server initialization * @@ -65,18 +63,14 @@ */ std::string application_protocol() const override { return m_next_protocol; } - std::vector get_peer_cert_chain(const Handshake_State& state) const override; - void initiate_handshake(Handshake_State& state, bool force_full_renegotiation) override; - void process_handshake_msg(const Handshake_State* active_state, - Handshake_State& pending_state, + void process_handshake_msg(Handshake_State& pending_state, Handshake_Type type, const std::vector& contents, bool epoch0_restart) override; - void process_client_hello_msg(const Handshake_State* active_state, - Server_Handshake_State& pending_state, + void process_client_hello_msg(Server_Handshake_State& pending_state, const std::vector& contents, bool epoch0_restart); diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_session_key.cpp botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_session_key.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls12/tls_session_key.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls12/tls_session_key.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,7 +8,8 @@ #include #include -#include +#include +#include #include namespace Botan::TLS { @@ -19,9 +20,16 @@ Session_Keys::Session_Keys(const Handshake_State* state, const secure_vector& pre_master_secret, bool resuming) { - const size_t cipher_keylen = state->ciphersuite().cipher_keylen(); - const size_t mac_keylen = state->ciphersuite().mac_keylen(); - const size_t cipher_nonce_bytes = state->ciphersuite().nonce_bytes_from_handshake(); + BOTAN_ASSERT_NONNULL(state); + BOTAN_ASSERT_NONNULL(state->client_hello()); + BOTAN_ASSERT_NONNULL(state->server_hello()); + + const auto& suite = state->ciphersuite(); + BOTAN_STATE_CHECK(suite.valid()); + + const size_t cipher_keylen = suite.cipher_keylen(); + const size_t mac_keylen = suite.mac_keylen(); + const size_t cipher_nonce_bytes = suite.nonce_bytes_from_handshake(); const bool extended_master_secret = state->server_hello()->supports_extended_master_secret(); @@ -44,7 +52,7 @@ std::vector label; if(extended_master_secret) { label.assign(EXT_MASTER_SECRET_MAGIC, EXT_MASTER_SECRET_MAGIC + sizeof(EXT_MASTER_SECRET_MAGIC)); - salt += state->hash().final(state->ciphersuite().prf_algo()); + salt += state->hash().final(suite.prf_algo()); } else { label.assign(MASTER_SECRET_MAGIC, MASTER_SECRET_MAGIC + sizeof(MASTER_SECRET_MAGIC)); salt += state->client_hello()->random(); @@ -68,17 +76,26 @@ m_c_aead.resize(mac_keylen + cipher_keylen); m_s_aead.resize(mac_keylen + cipher_keylen); + // NOLINTBEGIN(readability-container-data-pointer) copy_mem(&m_c_aead[0], key_data, mac_keylen); copy_mem(&m_s_aead[0], key_data + mac_keylen, mac_keylen); + // NOLINTEND(readability-container-data-pointer) - copy_mem(&m_c_aead[mac_keylen], key_data + 2 * mac_keylen, cipher_keylen); - copy_mem(&m_s_aead[mac_keylen], key_data + 2 * mac_keylen + cipher_keylen, cipher_keylen); + // Key is not used for NULL suites + if(cipher_keylen > 0) { + copy_mem(&m_c_aead[mac_keylen], key_data + 2 * mac_keylen, cipher_keylen); + copy_mem(&m_s_aead[mac_keylen], key_data + 2 * mac_keylen + cipher_keylen, cipher_keylen); + } else { + BOTAN_STATE_CHECK(suite.null_ciphersuite()); + } - m_c_nonce.resize(cipher_nonce_bytes); - m_s_nonce.resize(cipher_nonce_bytes); + if(cipher_nonce_bytes > 0) { + const uint8_t* c_nonce_bytes = key_data + 2 * (mac_keylen + cipher_keylen); + m_c_nonce.assign(c_nonce_bytes, c_nonce_bytes + cipher_nonce_bytes); - copy_mem(&m_c_nonce[0], key_data + 2 * (mac_keylen + cipher_keylen), cipher_nonce_bytes); - copy_mem(&m_s_nonce[0], key_data + 2 * (mac_keylen + cipher_keylen) + cipher_nonce_bytes, cipher_nonce_bytes); + const uint8_t* s_nonce_bytes = key_data + 2 * (mac_keylen + cipher_keylen) + cipher_nonce_bytes; + m_s_nonce.assign(s_nonce_bytes, s_nonce_bytes + cipher_nonce_bytes); + } } } // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/info.txt botan3-3.12.0+dfsg/src/lib/tls/tls13/info.txt --- botan3-3.7.1+dfsg/src/lib/tls/tls13/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -8,11 +8,15 @@ +tls_messages_13.h +tls_extensions_13.h +tls_psk_13.h tls_psk_identity_13.h tls_channel_impl_13.h +tls_connection_state_13.h tls_cipher_state.h tls_client_impl_13.h tls_handshake_layer_13.h @@ -25,5 +29,4 @@ hkdf tls -tls12 diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/msg_cert_verify_13.cpp botan3-3.12.0+dfsg/src/lib/tls/tls13/msg_cert_verify_13.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls13/msg_cert_verify_13.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/msg_cert_verify_13.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,123 @@ +/* +* Certificate Verify Message +* (C) 2021-2022 Jack Lloyd +* 2021 Elektrobit Automotive GmbH +* 2022 René Meusel, Hannes Rantzsch - neXenio GmbH +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include +#include +#include + +namespace Botan::TLS { + +namespace { + +std::vector message(Connection_Side side, const Transcript_Hash& hash) { + std::vector msg(64, 0x20); + msg.reserve(64 + 33 + 1 + hash.size()); + + const std::string context_string = (side == TLS::Connection_Side::Server) ? "TLS 1.3, server CertificateVerify" + : "TLS 1.3, client CertificateVerify"; + + msg.insert(msg.end(), context_string.cbegin(), context_string.cend()); + msg.push_back(0x00); + + msg.insert(msg.end(), hash.cbegin(), hash.cend()); + return msg; +} + +Signature_Scheme choose_signature_scheme(const Private_Key& key, + const std::vector& allowed_schemes, + const std::vector& peer_allowed_schemes) { + for(Signature_Scheme scheme : allowed_schemes) { + // RFC 8446 4.4.3 forbids the rsa_pkcs1_* schemes in CertificateVerify, those are TLS 1.2 only. + if(scheme.is_available() && scheme.is_compatible_with(Protocol_Version::TLS_V13) && scheme.is_suitable_for(key) && + value_exists(peer_allowed_schemes, scheme)) { + return scheme; + } + } + + throw TLS_Exception(Alert::HandshakeFailure, "Failed to agree on a signature algorithm"); +} + +} // namespace + +/* +* Create a new Certificate Verify message for TLS 1.3 +*/ +Certificate_Verify_13::Certificate_Verify_13(const Certificate_13& certificate_msg, + const std::vector& peer_allowed_schemes, + std::string_view hostname, + const Transcript_Hash& hash, + Connection_Side whoami, + Credentials_Manager& creds_mgr, + const Policy& policy, + Callbacks& callbacks, + RandomNumberGenerator& rng) : + m_side(whoami) { + BOTAN_ASSERT_NOMSG(!certificate_msg.empty()); + + const std::string op_type((m_side == Connection_Side::Client) ? "tls-client" : "tls-server"); + const auto context = std::string(hostname); + + const auto private_key = (certificate_msg.has_certificate_chain()) + ? creds_mgr.private_key_for(certificate_msg.leaf(), op_type, context) + : creds_mgr.private_key_for(*certificate_msg.public_key(), op_type, context); + if(!private_key) { + throw TLS_Exception(Alert::InternalError, "Application did not provide a private key for its credential"); + } + + m_scheme = choose_signature_scheme(*private_key, policy.allowed_signature_schemes(), peer_allowed_schemes); + BOTAN_ASSERT_NOMSG(m_scheme.is_available()); + BOTAN_ASSERT_NOMSG(m_scheme.is_compatible_with(Protocol_Version::TLS_V13)); + + m_signature = callbacks.tls_sign_message( + *private_key, rng, m_scheme.padding_string(), m_scheme.format().value(), message(m_side, hash)); +} + +Certificate_Verify_13::Certificate_Verify_13(const std::vector& buf, const Connection_Side side) : + Certificate_Verify(buf), m_side(side) { + if(!m_scheme.is_available()) { + throw TLS_Exception(Alert::IllegalParameter, "Peer sent unknown signature scheme"); + } + + if(!m_scheme.is_compatible_with(Protocol_Version::TLS_V13)) { + throw TLS_Exception(Alert::IllegalParameter, "Peer sent signature algorithm that is not suitable for TLS 1.3"); + } +} + +/* +* Verify a Certificate Verify message +*/ +bool Certificate_Verify_13::verify(const Public_Key& public_key, + Callbacks& callbacks, + const Transcript_Hash& transcript_hash) const { + BOTAN_ASSERT_NOMSG(m_scheme.is_available()); + + // RFC 8446 4.2.3 + // The keys found in certificates MUST [...] be of appropriate type for + // the signature algorithms they are used with. + if(m_scheme.key_algorithm_identifier() != public_key.algorithm_identifier()) { + throw TLS_Exception(Alert::IllegalParameter, "Signature algorithm does not match certificate's public key"); + } + + const bool signature_valid = callbacks.tls_verify_message( + public_key, m_scheme.padding_string(), m_scheme.format().value(), message(m_side, transcript_hash), m_signature); + +#if defined(BOTAN_UNSAFE_FUZZER_MODE) + BOTAN_UNUSED(signature_valid); + return true; +#else + return signature_valid; +#endif +} + +} // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/msg_certificate_13.cpp botan3-3.12.0+dfsg/src/lib/tls/tls13/msg_certificate_13.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls13/msg_certificate_13.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/msg_certificate_13.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,22 +7,18 @@ * Botan is released under the Simplified BSD License (see license.txt) */ -#include +#include #include -#include #include #include #include #include #include +#include #include -#include -#include -#include -#include #include - +#include #include #include @@ -161,7 +157,7 @@ for(size_t i = 0; i < cert_chain.size(); ++i) { auto& entry = m_entries.emplace_back(cert_chain[i]); if(!ocsp_responses[i].empty()) { - entry.extensions().add(new Certificate_Status_Request(ocsp_responses[i])); + entry.extensions().add(new Certificate_Status_Request(ocsp_responses[i])); // NOLINT(*-owning-memory) } // This will call the modification callback multiple times. Once for @@ -192,7 +188,7 @@ Certificate_Type cert_type) : m_request_context(cert_request.context()), m_side(Connection_Side::Client) { const auto key_types = filter_signature_schemes(cert_request.signature_schemes()); - const auto op_type = "tls-client"; + const std::string op_type = "tls-client"; if(cert_type == Certificate_Type::X509) { setup_entries( @@ -227,14 +223,24 @@ Callbacks& callbacks, Certificate_Type cert_type) : // RFC 8446 4.4.2: - // [In the case of server authentication], this field + // [In the case of server authentication], the request context // SHALL be zero length - m_request_context(), m_side(Connection_Side::Server) { - BOTAN_ASSERT_NOMSG(client_hello.extensions().has()); + m_request_context(/* NOLINT(*-redundant-member-init) */), m_side(Connection_Side::Server) { + /* + RFC 8446 4.2.3: + Clients which desire the server to authenticate itself via a + certificate MUST send the "signature_algorithms" extension. If a + server is authenticating via a certificate and the client has not sent + a "signature_algorithms" extension, then the server MUST abort the + handshake with a "missing_extension" alert. + */ + if(!client_hello.extensions().has()) { + throw TLS_Exception(Alert::MissingExtension, "Client Hello is missing required signature_algorithms extension"); + } const auto key_types = filter_signature_schemes(client_hello.signature_schemes()); - const auto op_type = "tls-server"; - const auto context = client_hello.sni_hostname(); + const std::string op_type = "tls-server"; + const std::string context = client_hello.sni_hostname(); if(cert_type == Certificate_Type::X509) { auto cert_chain = credentials_manager.find_cert_chain( @@ -264,31 +270,38 @@ } Certificate_13::Certificate_Entry::Certificate_Entry(TLS_Data_Reader& reader, - const Connection_Side side, - const Certificate_Type cert_type) { - switch(cert_type) { - case Certificate_Type::X509: - // RFC 8446 4.2.2 - // [...] each CertificateEntry contains a DER-encoded X.509 - // certificate. - m_certificate = X509_Certificate(reader.get_tls_length_value(3)); + Connection_Side side, + Certificate_Type cert_type) { + if(cert_type == Certificate_Type::X509) { + // RFC 8446 4.2.2 + // [...] each CertificateEntry contains a DER-encoded X.509 + // certificate. + const auto cert_bytes = reader.get_tls_length_value(3); + try { + m_certificate = std::make_unique(cert_bytes); m_raw_public_key = m_certificate->subject_public_key(); - break; - case Certificate_Type::RawPublicKey: - // RFC 7250 3. - // This specification uses raw public keys whereby the already - // available encoding used in a PKIX certificate in the form of a - // SubjectPublicKeyInfo structure is reused. + } catch(Exception& e) { + // bad_certificate would make more sense but BoGo expects decoding_error + throw TLS_Exception(Alert::DecodeError, e.what()); + } + } else if(cert_type == Certificate_Type::RawPublicKey) { + // RFC 7250 3. + // This specification uses raw public keys whereby the already + // available encoding used in a PKIX certificate in the form of a + // SubjectPublicKeyInfo structure is reused. + try { m_raw_public_key = X509::load_key(reader.get_tls_length_value(3)); - break; - default: - throw TLS_Exception(Alert::InternalError, "Unknown certificate type"); + } catch(Exception& e) { + throw TLS_Exception(Alert::DecodeError, e.what()); + } + } else { + throw TLS_Exception(Alert::InternalError, "Unknown certificate type"); } // Extensions are simply tacked at the end of the certificate entry. This // is a departure from the typical "tag-length-value" in a sense that the // Extensions deserializer needs the length value of the extensions. - const auto extensions_length = reader.peek_uint16_t(); + const size_t extensions_length = reader.peek_uint16_t(); const auto exts_buf = reader.get_fixed(extensions_length + 2); TLS_Data_Reader exts_reader("extensions reader", exts_buf); m_extensions.deserialize(exts_reader, side, Handshake_Type::Certificate); @@ -317,17 +330,23 @@ } } -Certificate_13::Certificate_Entry::Certificate_Entry(X509_Certificate cert) : - m_certificate(std::move(cert)), m_raw_public_key(m_certificate->subject_public_key()) {} +Certificate_13::Certificate_Entry::~Certificate_Entry() = default; + +Certificate_13::Certificate_Entry::Certificate_Entry(Certificate_13::Certificate_Entry&& other) noexcept = default; +Certificate_13::Certificate_Entry& Certificate_13::Certificate_Entry::operator=( + Certificate_13::Certificate_Entry&& other) noexcept = default; + +Certificate_13::Certificate_Entry::Certificate_Entry(const X509_Certificate& cert) : + m_certificate(std::make_unique(cert)), m_raw_public_key(m_certificate->subject_public_key()) {} Certificate_13::Certificate_Entry::Certificate_Entry(std::shared_ptr raw_public_key) : - m_certificate(std::nullopt), m_raw_public_key(std::move(raw_public_key)) { + m_raw_public_key(std::move(raw_public_key)) { BOTAN_ASSERT_NONNULL(m_raw_public_key); } const X509_Certificate& Certificate_13::Certificate_Entry::certificate() const { BOTAN_STATE_CHECK(has_certificate()); - return m_certificate.value(); + return *m_certificate; } std::shared_ptr Certificate_13::Certificate_Entry::public_key() const { diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/msg_certificate_req_13.cpp botan3-3.12.0+dfsg/src/lib/tls/tls13/msg_certificate_req_13.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls13/msg_certificate_req_13.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/msg_certificate_req_13.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -5,11 +5,15 @@ * Botan is released under the Simplified BSD License (see license.txt) */ -#include +#include +#include #include +#include #include #include +#include +#include #include namespace Botan::TLS { @@ -50,7 +54,7 @@ // For Certificate Request said table states: // "status_request", "signature_algorithms", "signed_certificate_timestamp", // "certificate_authorities", "oid_filters", "signature_algorithms_cert", - std::set allowed_extensions = { + const std::set allowed_extensions = { Extension_Code::CertificateStatusRequest, Extension_Code::SignatureAlgorithms, // Extension_Code::SignedCertificateTimestamp, // NYI @@ -62,9 +66,11 @@ if(m_extensions.contains_implemented_extensions_other_than(allowed_extensions)) { throw TLS_Exception(Alert::IllegalParameter, "Certificate Request contained an extension that is not allowed"); } + + reader.assert_done(); } -Certificate_Request_13::Certificate_Request_13(std::vector acceptable_CAs, +Certificate_Request_13::Certificate_Request_13(const std::vector& acceptable_CAs, const Policy& policy, Callbacks& callbacks) { // RFC 8446 4.3.2 @@ -93,12 +99,18 @@ } if(!acceptable_CAs.empty()) { - m_extensions.add(std::make_unique(std::move(acceptable_CAs))); + m_extensions.add(std::make_unique(acceptable_CAs)); } // TODO: Support cert_status_request for OCSP stapling callbacks.tls_modify_extensions(m_extensions, Connection_Side::Server, type()); + + if(!m_extensions.has()) { + throw TLS_Exception( + Alert::InternalError, + "Application tls_modify_extensions callback removed Signature_Algorithms from the CertificateRequest"); + } } std::optional Certificate_Request_13::maybe_create(const Client_Hello_13& client_hello, @@ -108,7 +120,7 @@ const auto trusted_CAs = cred_mgr.trusted_certificate_authorities("tls-server", client_hello.sni_hostname()); std::vector client_auth_CAs; - for(const auto store : trusted_CAs) { + for(auto* const store : trusted_CAs) { const auto subjects = store->all_subjects(); client_auth_CAs.insert(client_auth_CAs.end(), subjects.begin(), subjects.end()); } @@ -117,7 +129,7 @@ return std::nullopt; } - return Certificate_Request_13(std::move(client_auth_CAs), policy, callbacks); + return Certificate_Request_13(client_auth_CAs, policy, callbacks); } std::vector Certificate_Request_13::acceptable_CAs() const { @@ -140,7 +152,7 @@ // If no "signature_algorithms_cert" extension is present, then the // "signature_algorithms" extension also applies to signatures appearing // in certificates. - if(auto sig_schemes_cert = m_extensions.get()) { + if(auto* sig_schemes_cert = m_extensions.get()) { return sig_schemes_cert->supported_schemes(); } else { return signature_schemes(); diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/msg_client_hello_13.cpp botan3-3.12.0+dfsg/src/lib/tls/tls13/msg_client_hello_13.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls13/msg_client_hello_13.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/msg_client_hello_13.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,512 @@ +/* +* TLS Client Hello Messages +* (C) 2004-2011,2015,2016 Jack Lloyd +* 2021 Elektrobit Automotive GmbH +* 2022 René Meusel, Hannes Rantzsch - neXenio GmbH +* 2026 René Meusel - Rohde & Schwarz Cybersecurity GmbH +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(BOTAN_HAS_TLS_12) + #include +#endif + +namespace Botan::TLS { + +Client_Hello_13::Client_Hello_13(std::unique_ptr data) : Client_Hello(std::move(data)) { + const auto& exts = m_data->extensions(); + + // RFC 8446 4.1.2 + // TLS 1.3 ClientHellos are identified as having a legacy_version of + // 0x0303 and a "supported_versions" extension present with 0x0304 as the + // highest version indicated therein. + // + // Note that we already checked for "supported_versions" before entering this + // c'tor in `Client_Hello_13::parse()`. This is just to be doubly sure. + BOTAN_ASSERT_NOMSG(exts.has()); + + // RFC 8446 4.2.1 + // Servers MAY abort the handshake upon receiving a ClientHello with + // legacy_version 0x0304 or later. + if(m_data->legacy_version().is_tls_13_or_later()) { + throw TLS_Exception(Alert::DecodeError, "TLS 1.3 Client Hello has invalid legacy_version"); + } + + // RFC 8446 D.5 + // Any endpoint receiving a Hello message with ClientHello.legacy_version [...] + // set to 0x0300 MUST abort the handshake with a "protocol_version" alert. + if(m_data->legacy_version().major_version() == 3 && m_data->legacy_version().minor_version() == 0) { + throw TLS_Exception(Alert::ProtocolVersion, "TLS 1.3 Client Hello has invalid legacy_version"); + } + + // RFC 8446 4.1.2 + // For every TLS 1.3 ClientHello, [the compression method] MUST contain + // exactly one byte, set to zero, [...]. If a TLS 1.3 ClientHello is + // received with any other value in this field, the server MUST abort the + // handshake with an "illegal_parameter" alert. + if(m_data->comp_methods().size() != 1 || m_data->comp_methods().front() != 0) { + throw TLS_Exception(Alert::IllegalParameter, "Client did not offer NULL compression"); + } + + // RFC 8446 4.2.9 + // A client MUST provide a "psk_key_exchange_modes" extension if it + // offers a "pre_shared_key" extension. If clients offer "pre_shared_key" + // without a "psk_key_exchange_modes" extension, servers MUST abort + // the handshake. + if(exts.has()) { + if(!exts.has()) { + throw TLS_Exception(Alert::MissingExtension, + "Client Hello offered a PSK without a psk_key_exchange_modes extension"); + } + + // RFC 8446 4.2.11 + // The "pre_shared_key" extension MUST be the last extension in the + // ClientHello [...]. Servers MUST check that it is the last extension + // and otherwise fail the handshake with an "illegal_parameter" alert. + if(exts.last_added() != Extension_Code::PresharedKey) { + throw TLS_Exception(Alert::IllegalParameter, "PSK extension was not at the very end of the Client Hello"); + } + } + + // RFC 8446 9.2 + // [A TLS 1.3 ClientHello] message MUST meet the following requirements: + // + // - If not containing a "pre_shared_key" extension, it MUST contain + // both a "signature_algorithms" extension and a "supported_groups" + // extension. + // + // - If containing a "supported_groups" extension, it MUST also contain + // a "key_share" extension, and vice versa. An empty + // KeyShare.client_shares vector is permitted. + // + // Servers receiving a ClientHello which does not conform to these + // requirements MUST abort the handshake with a "missing_extension" + // alert. + if(!exts.has()) { + if(!exts.has() || !exts.has()) { + throw TLS_Exception( + Alert::MissingExtension, + "Non-PSK Client Hello did not contain supported_groups and signature_algorithms extensions"); + } + } + if(exts.has() != exts.has()) { + throw TLS_Exception(Alert::MissingExtension, + "Client Hello must either contain both key_share and supported_groups extensions or neither"); + } + + if(exts.has()) { + auto* const supported_ext = exts.get(); + BOTAN_ASSERT_NONNULL(supported_ext); + const auto supports = supported_ext->groups(); + const auto offers = exts.get()->offered_groups(); + + // RFC 8446 4.2.8 + // Each KeyShareEntry value MUST correspond to a group offered in the + // "supported_groups" extension and MUST appear in the same order. + // [...] + // Clients MUST NOT offer any KeyShareEntry values for groups not + // listed in the client's "supported_groups" extension. + // + // Servers MAY check for violations of these rules and abort the + // handshake with an "illegal_parameter" alert if one is violated. + // + // Note: We can assume that both `offers` and `supports` are unique lists + // as this is ensured in the parsing code of the extensions. + // + // Since offers must appear in the same order as supports, a single + // forward sweep of `supports` suffices: after finding each offered group + // we advance past its position so the next offered group is searched for + // only in the remaining suffix. + auto supports_it = supports.begin(); + for(const auto offered : offers) { + supports_it = std::find(supports_it, supports.end(), offered); + if(supports_it == supports.end()) { + throw TLS_Exception(Alert::IllegalParameter, + "Offered key exchange groups do not align with claimed supported groups"); + } + ++supports_it; + } + } + + // TODO: Reject oid_filters extension if found (which is the only known extension that + // must not occur in the TLS 1.3 client hello. + // RFC 8446 4.2.5 + // [The oid_filters extension] MUST only be sent in the CertificateRequest message. +} + +/* + * Create a new Client Hello message + */ +Client_Hello_13::Client_Hello_13(const Policy& policy, + Callbacks& cb, + RandomNumberGenerator& rng, + std::string_view hostname, + const std::vector& next_protocols, + std::optional& session, + std::vector psks) { + // RFC 8446 4.1.2 + // In TLS 1.3, the client indicates its version preferences in the + // "supported_versions" extension (Section 4.2.1) and the + // legacy_version field MUST be set to 0x0303, which is the version + // number for TLS 1.2. + m_data->m_legacy_version = Protocol_Version::TLS_V12; + m_data->m_random = make_hello_random(rng, cb, policy); + m_data->m_suites = policy.ciphersuite_list(Protocol_Version::TLS_V13); + + if(policy.allow_tls12()) { + // Note: DTLS 1.3 is NYI, hence dtls_12 is not checked + const auto legacy_suites = policy.ciphersuite_list(Protocol_Version::TLS_V12); + m_data->m_suites.insert(m_data->m_suites.end(), legacy_suites.cbegin(), legacy_suites.cend()); + } + + if(policy.tls_13_middlebox_compatibility_mode()) { + // RFC 8446 4.1.2 + // In compatibility mode (see Appendix D.4), this field MUST be non-empty, + // so a client not offering a pre-TLS 1.3 session MUST generate a new + // 32-byte value. + // + // Note: we won't ever offer a TLS 1.2 session. In such a case we would + // have instantiated a TLS 1.2 client in the first place. + m_data->m_session_id = Session_ID(make_hello_random(rng, cb, policy)); + } + + // NOLINTBEGIN(*-owning-memory) + if(Server_Name_Indicator::hostname_acceptable_for_sni(hostname)) { + m_data->extensions().add(new Server_Name_Indicator(hostname)); + } + + m_data->extensions().add(new Supported_Groups(policy.key_exchange_groups())); + + m_data->extensions().add(new Key_Share(policy, cb, rng)); + + m_data->extensions().add(new Supported_Versions(Protocol_Version::TLS_V13, policy)); + + m_data->extensions().add(new Signature_Algorithms(policy.acceptable_signature_schemes())); + if(auto cert_signing_prefs = policy.acceptable_certificate_signature_schemes()) { + // RFC 8446 4.2.3 + // Implementations which have the same policy in both cases MAY omit + // the "signature_algorithms_cert" extension. + m_data->extensions().add(new Signature_Algorithms_Cert(std::move(cert_signing_prefs.value()))); + } + + // TODO: Support for PSK-only mode without a key exchange. + // This should be configurable in TLS::Policy and should allow no PSK + // support at all (e.g. to disable support for session resumption). + m_data->extensions().add(new PSK_Key_Exchange_Modes({PSK_Key_Exchange_Mode::PSK_DHE_KE})); + + if(policy.support_cert_status_message()) { + m_data->extensions().add(new Certificate_Status_Request({}, {})); + } + + // We currently support "record_size_limit" for TLS 1.3 exclusively. Hence, + // when TLS 1.2 is advertised as a supported protocol, we must not offer this + // extension. + if(policy.record_size_limit().has_value() && !policy.allow_tls12()) { + m_data->extensions().add(new Record_Size_Limit(policy.record_size_limit().value())); + } + + /* + * Right now raw public key support is not implemented for TLS 1.2, so we only offer + * certificate_types (which is used to request raw public key) if additionally TLS 1.2 + * support is disabled. Otherwise a peer might reply with a 1.2 server hello + a certificate_type + * extension indicating it wishes to use RPK, which would lead to errors later. + */ + if(!policy.allow_tls12()) { + m_data->extensions().add(new Client_Certificate_Type(policy.accepted_client_certificate_types())); + m_data->extensions().add(new Server_Certificate_Type(policy.accepted_server_certificate_types())); + } + + if(!next_protocols.empty()) { + m_data->extensions().add(new Application_Layer_Protocol_Notification(next_protocols)); + } + +#if defined(BOTAN_HAS_TLS_12) + if(policy.allow_tls12()) { + m_data->extensions().add(new Renegotiation_Extension()); + m_data->extensions().add(new Session_Ticket_Extension()); + + // EMS must always be used with TLS 1.2, regardless of the policy + m_data->extensions().add(new Extended_Master_Secret); + + if(policy.negotiate_encrypt_then_mac()) { + m_data->extensions().add(new Encrypt_then_MAC); + } + + if(m_data->extensions().has() && + !m_data->extensions().get()->ec_groups().empty()) { + m_data->extensions().add(new Supported_Point_Formats(policy.use_ecc_point_compression())); + } + } +#endif + + if(session.has_value() || !psks.empty()) { + m_data->extensions().add(new PSK(session, std::move(psks), cb)); + } + // NOLINTEND(*-owning-memory) + + cb.tls_modify_extensions(m_data->extensions(), Connection_Side::Client, type()); + + // The application's tls_modify_extensions callback could have stripped + // Supported_Groups or Key_Share, which must be there. + if(!m_data->extensions().has()) { + throw TLS_Exception(Alert::InternalError, + "Application tls_modify_extensions callback removed Supported_Groups from the ClientHello"); + } + if(!m_data->extensions().has()) { + throw TLS_Exception(Alert::InternalError, + "Application tls_modify_extensions callback removed Key_Share from the ClientHello"); + } + + if(m_data->extensions().has()) { + // RFC 8446 4.2.11 + // The "pre_shared_key" extension MUST be the last extension in the + // ClientHello (this facilitates implementation [...]). + if(m_data->extensions().last_added() != Extension_Code::PresharedKey) { + throw TLS_Exception(Alert::InternalError, + "Application modified extensions of Client Hello, PSK is not last anymore"); + } + calculate_psk_binders({}); + } +} + +std::variant Client_Hello_13::parse(const std::vector& buf) { + auto data = std::make_unique(buf); + const auto version = data->version(); + + if(version.is_pre_tls_13()) { + return Client_Hello_12_Shim(std::move(data)); + } else { + return Client_Hello_13(std::move(data)); + } +} + +void Client_Hello_13::retry(const Hello_Retry_Request& hrr, + const Transcript_Hash_State& transcript_hash_state, + Callbacks& cb, + RandomNumberGenerator& rng) { + BOTAN_STATE_CHECK(m_data->extensions().has()); + BOTAN_STATE_CHECK(m_data->extensions().has()); + + auto* hrr_ks = hrr.extensions().get(); + const auto& supported_groups = m_data->extensions().get()->groups(); + + if(hrr.extensions().has()) { + m_data->extensions().get()->retry_offer(*hrr_ks, supported_groups, cb, rng); + } + + // RFC 8446 4.2.2 + // When sending the new ClientHello, the client MUST copy + // the contents of the extension received in the HelloRetryRequest into + // a "cookie" extension in the new ClientHello. + // + // RFC 8446 4.2.2 + // Clients MUST NOT use cookies in their initial ClientHello in subsequent + // connections. + if(hrr.extensions().has()) { + BOTAN_STATE_CHECK(!m_data->extensions().has()); + m_data->extensions().add(new Cookie(hrr.extensions().get()->get_cookie())); // NOLINT(*-owning-memory) + } + + // Note: the consumer of the TLS implementation won't be able to distinguish + // invocations to this callback due to the first Client_Hello or the + // retried Client_Hello after receiving a Hello_Retry_Request. We assume + // that the user keeps and detects this state themselves. + cb.tls_modify_extensions(m_data->extensions(), Connection_Side::Client, type()); + + // Same invariants as in the constructor: the callback must not strip + // Supported_Groups or Key_Share + if(!m_data->extensions().has()) { + throw TLS_Exception( + Alert::InternalError, + "Application tls_modify_extensions callback removed Supported_Groups from the retried ClientHello"); + } + if(!m_data->extensions().has()) { + throw TLS_Exception(Alert::InternalError, + "Application tls_modify_extensions callback removed Key_Share from the retried ClientHello"); + } + + auto* psk = m_data->extensions().get(); + if(psk != nullptr) { + // RFC 8446 4.2.11 + // The "pre_shared_key" extension MUST be the last extension in the + // ClientHello (this facilitates implementation [...]). + m_data->extensions().reorder({Extension_Code::PresharedKey}); + + // Cipher suite should always be a known suite as this is checked upstream + const auto cipher = Ciphersuite::by_id(hrr.ciphersuite()); + BOTAN_ASSERT_NOMSG(cipher.has_value()); + + // RFC 8446 4.1.4 + // In [...] its updated ClientHello, the client SHOULD NOT offer + // any pre-shared keys associated with a hash other than that of the + // selected cipher suite. + psk->filter(cipher.value()); + + // RFC 8446 4.2.11.2 + // If the server responds with a HelloRetryRequest and the client + // then sends ClientHello2, its binder will be computed over: [...]. + calculate_psk_binders(transcript_hash_state.clone()); + } +} + +void Client_Hello_13::validate_updates(const Client_Hello_13& new_ch) { + // RFC 8446 4.1.2 + // The client will also send a ClientHello when the server has responded + // to its ClientHello with a HelloRetryRequest. In that case, the client + // MUST send the same ClientHello without modification, except as follows: + + if(m_data->session_id() != new_ch.m_data->session_id() || m_data->random() != new_ch.m_data->random() || + m_data->ciphersuites() != new_ch.m_data->ciphersuites() || + m_data->comp_methods() != new_ch.m_data->comp_methods()) { + throw TLS_Exception(Alert::IllegalParameter, "Client Hello core values changed after Hello Retry Request"); + } + + const auto oldexts = extension_types(); + const auto newexts = new_ch.extension_types(); + + // Check that extension omissions are justified. RFC 8446 4.1.2 lists the + // only mutations the client may make between CH1 and CH2; any other + // extension removal is an illegal parameter regardless of whether the + // extension is one this implementation recognizes. + for(const auto oldext : oldexts) { + if(!newexts.contains(oldext)) { + // RFC 8446 4.1.2 + // Removing the "early_data" extension (Section 4.2.10) if one was + // present. Early data is not permitted after a HelloRetryRequest. + if(oldext == EarlyDataIndication::static_type()) { + continue; + } + + // RFC 8446 4.1.2 + // Optionally adding, removing, or changing the length of the + // "padding" extension. + if(oldext == Extension_Code::Padding) { + continue; + } + + throw TLS_Exception(Alert::IllegalParameter, "Extension removed in updated Client Hello"); + } + } + + // Check that extension additions are justified. Same reasoning: only the + // RFC-listed mutations are allowed, including for unknown extension codes. + for(const auto newext : newexts) { + if(!oldexts.contains(newext)) { + // RFC 8446 4.1.2 + // Including a "cookie" extension if one was provided in the + // HelloRetryRequest. + if(newext == Cookie::static_type()) { + continue; + } + + // RFC 8446 4.1.2 + // Optionally adding, removing, or changing the length of the + // "padding" extension. + if(newext == Extension_Code::Padding) { + continue; + } + + throw TLS_Exception(Alert::UnsupportedExtension, "Added an extension in updated Client Hello"); + } + } + + // RFC 8446 4.1.2 + // Removing the "early_data" extension (Section 4.2.10) if one was + // present. Early data is not permitted after a HelloRetryRequest. + if(new_ch.extensions().has()) { + throw TLS_Exception(Alert::IllegalParameter, "Updated Client Hello indicates early data"); + } + + // RFC 8446 4.1.2 + // The client MUST send the same ClientHello without modification, + // except as follows: [key_share, pre_shared_key, early_data, cookie, padding] + // + // Verify that extensions whose content must not change between the + // initial and retried Client Hello have identical wire encodings. + const std::set extensions_allowed_to_change = { + Extension_Code::KeyShare, + Extension_Code::PresharedKey, + Extension_Code::EarlyData, + Extension_Code::Cookie, + Extension_Code::Padding, + }; + + for(const auto ext_type : oldexts) { + if(extensions_allowed_to_change.contains(ext_type)) { + continue; + } + + const auto old_bytes = extensions().extension_raw_bytes(ext_type); + const auto new_bytes = new_ch.extensions().extension_raw_bytes(ext_type); + + // Both Client Hellos validated here are received from the peer and went + // through Extensions::deserialize, which records raw bytes for every + // parsed extension. A missing raw_bytes on either side would mean an + // extension was added by us programmatically - which shouldn't happen + BOTAN_ASSERT_NOMSG(old_bytes.has_value() && new_bytes.has_value()); + if(old_bytes.value() != new_bytes.value()) { + throw TLS_Exception(Alert::IllegalParameter, "Extension content changed in updated Client Hello"); + } + } +} + +void Client_Hello_13::calculate_psk_binders(Transcript_Hash_State transcript_hash) { + auto* psk = m_data->extensions().get(); + if(psk == nullptr || psk->empty()) { + return; + } + + // RFC 8446 4.2.11.2 + // Each entry in the binders list is computed as an HMAC over a + // transcript hash (see Section 4.4.1) containing a partial ClientHello + // [...]. + // + // Therefore we marshal the entire message prematurely to obtain the + // (truncated) transcript hash, calculate the PSK binders with it, update + // the Client Hello thus finalizing the message. Down the road, it will be + // re-marshalled with the correct binders and sent over the wire. + Handshake_Layer::prepare_message(*this, transcript_hash); + psk->calculate_binders(transcript_hash); +} + +std::optional Client_Hello_13::highest_supported_version(const Policy& policy) const { + // RFC 8446 4.2.1 + // The "supported_versions" extension is used by the client to indicate + // which versions of TLS it supports and by the server to indicate which + // version it is using. The extension contains a list of supported + // versions in preference order, with the most preferred version first. + auto* const supvers = m_data->extensions().get(); + BOTAN_ASSERT_NONNULL(supvers); + + std::optional result; + + for(const auto& v : supvers->versions()) { + // RFC 8446 4.2.1 + // Servers MUST only select a version of TLS present in that extension + // and MUST ignore any unknown versions that are present in that + // extension. + if(!v.known_version() || !policy.acceptable_protocol_version(v)) { + continue; + } + + result = (result.has_value()) ? std::optional(std::max(result.value(), v)) : std::optional(v); + } + + return result; +} + +} // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/msg_encrypted_extensions.cpp botan3-3.12.0+dfsg/src/lib/tls/tls13/msg_encrypted_extensions.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls13/msg_encrypted_extensions.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/msg_encrypted_extensions.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,17 +6,25 @@ * Botan is released under the Simplified BSD License (see license.txt) */ -#include +#include #include #include +#include +#include #include namespace Botan::TLS { -Encrypted_Extensions::Encrypted_Extensions(const Client_Hello_13& client_hello, const Policy& policy, Callbacks& cb) { +Encrypted_Extensions::Encrypted_Extensions(const Client_Hello_13& client_hello, + const Policy& policy, + Callbacks& cb, + bool is_resumption, + bool requesting_client_auth) { const auto& exts = client_hello.extensions(); + // NOLINTBEGIN(*-owning-memory) + // RFC 8446 4.2.7 // As of TLS 1.3, servers are permitted to send the "supported_groups" // extension to the client. Clients [...] MAY use the information @@ -52,8 +60,14 @@ // If the server does not send a certificate_request payload [...], // then the client_certificate_type payload in the server hello MUST be // omitted. - if(auto ch_client_cert_types = exts.get(); - ch_client_cert_types && policy.request_client_certificate_authentication()) { + // + // Note: requesting_client_auth tracks whether the caller will actually + // emit a CertificateRequest. The server-side decision in + // Certificate_Request_13::maybe_create depends on both the policy flag + // *and* the credentials manager's CA list, so re-checking just the + // policy flag here would miss the trusted-CAs-only configuration. + if(auto* ch_client_cert_types = exts.get(); + ch_client_cert_types != nullptr && requesting_client_auth) { m_extensions.add(new Client_Certificate_Type(*ch_client_cert_types, policy)); } @@ -63,7 +77,7 @@ // the server in a subsequent certificate payload. [...] With the // server_certificate_type extension in the server hello, the TLS server // indicates the certificate type carried in the Certificate payload. - if(auto ch_server_cert_types = exts.get()) { + if(auto* ch_server_cert_types = exts.get()) { m_extensions.add(new Server_Certificate_Type(*ch_server_cert_types, policy)); } @@ -72,21 +86,49 @@ // extension [...] SHALL include an extension of type "server_name" in the // (extended) server hello. The "extension_data" field of this extension // SHALL be empty. - if(exts.has()) { + // + // When resuming a session, the server MUST NOT include a server_name + // extension in the server hello. + if(exts.has() && !is_resumption) { m_extensions.add(new Server_Name_Indicator("")); } - if(auto alpn_ext = exts.get()) { - const auto next_protocol = cb.tls_server_choose_app_protocol(alpn_ext->protocols()); + if(auto* alpn_ext = exts.get()) { + const auto& offered = alpn_ext->protocols(); + const auto next_protocol = cb.tls_server_choose_app_protocol(offered); if(!next_protocol.empty()) { + // RFC 7301 3.2: if a protocol is selected, the server MUST select + // one of the protocols advertised by the client. + if(!value_exists(offered, next_protocol)) { + throw TLS_Exception(Alert::InternalError, + "Application chose an ALPN protocol that the client did not offer"); + } m_extensions.add(new Application_Layer_Protocol_Notification(next_protocol)); } } + // NOLINTEND(*-owning-memory) + // TODO: Implement handling for (at least) // * SRTP cb.tls_modify_extensions(m_extensions, Connection_Side::Server, type()); + + // After the application's tls_modify_extensions callback runs, re-check the + // RFC-MUST invariants we just established above. The application can add or + // reorder extensions, but shouldn't remove ones required direct response to + // ClientHello extensions would put us out of spec. + if(exts.has() && !m_extensions.has()) { + throw TLS_Exception( + Alert::InternalError, + "Application tls_modify_extensions callback removed Server_Certificate_Type from EncryptedExtensions"); + } + + if(requesting_client_auth && exts.has() && !m_extensions.has()) { + throw TLS_Exception( + Alert::InternalError, + "Application tls_modify_extensions callback removed Client_Certificate_Type from EncryptedExtensions"); + } } Encrypted_Extensions::Encrypted_Extensions(const std::vector& buf) { @@ -127,9 +169,19 @@ if(m_extensions.contains_implemented_extensions_other_than(allowed_exts)) { throw TLS_Exception(Alert::IllegalParameter, "Encrypted Extensions contained an extension that is not allowed"); } + + reader.assert_done(); } std::vector Encrypted_Extensions::serialize() const { + // RFC 8446 4.3.1: EncryptedExtensions carries Extension extensions<0..2^16-1>; + // an empty list still requires a 2-byte length-prefix on the wire. + // Extensions::serialize collapses empty to {} to suit other contexts, so + // emit the explicit length here. Mirrors the same fallback in + // New_Session_Ticket_13::serialize. + if(m_extensions.empty()) { + return {0x00, 0x00}; + } return m_extensions.serialize(Connection_Side::Server); } diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/msg_finished_13.cpp botan3-3.12.0+dfsg/src/lib/tls/tls13/msg_finished_13.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls13/msg_finished_13.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/msg_finished_13.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,24 @@ +/* +* Finished Message +* (C) 2021-2022 Jack Lloyd +* 2021 Elektrobit Automotive GmbH +* 2022 René Meusel, Hannes Rantzsch - neXenio GmbH +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include + +namespace Botan::TLS { + +Finished_13::Finished_13(Cipher_State* cipher_state, const Transcript_Hash& transcript_hash) { + m_verification_data = cipher_state->finished_mac(transcript_hash); +} + +bool Finished_13::verify(Cipher_State* cipher_state, const Transcript_Hash& transcript_hash) const { + return cipher_state->verify_peer_finished_mac(transcript_hash, m_verification_data); +} + +} // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/msg_key_update.cpp botan3-3.12.0+dfsg/src/lib/tls/tls13/msg_key_update.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls13/msg_key_update.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/msg_key_update.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,7 +6,7 @@ * Botan is released under the Simplified BSD License (see license.txt) */ -#include +#include #include diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/msg_server_hello_13.cpp botan3-3.12.0+dfsg/src/lib/tls/tls13/msg_server_hello_13.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls13/msg_server_hello_13.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/msg_server_hello_13.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,412 @@ +/* +* TLS Server Hello and Server Hello Done +* (C) 2004-2011,2015,2016,2019 Jack Lloyd +* 2021 Elektrobit Automotive GmbH +* 2022 René Meusel, Hannes Rantzsch - neXenio GmbH +* 2026 René Meusel - Rohde & Schwarz Cybersecurity GmbH +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Botan::TLS { + +const Server_Hello_13::Server_Hello_Tag Server_Hello_13::as_server_hello; +const Server_Hello_13::Hello_Retry_Request_Tag Server_Hello_13::as_hello_retry_request; +const Server_Hello_13::Hello_Retry_Request_Creation_Tag Server_Hello_13::as_new_hello_retry_request; + +std::variant Server_Hello_13::create(const Client_Hello_13& ch, + bool hello_retry_request_allowed, + Session_Manager& session_mgr, + Credentials_Manager& credentials_mgr, + RandomNumberGenerator& rng, + const Policy& policy, + Callbacks& cb) { + const auto& exts = ch.extensions(); + + // RFC 8446 4.2.9 + // [With PSK with (EC)DHE key establishment], the client and server MUST + // supply "key_share" values [...]. + // + // Note: We currently do not support PSK without (EC)DHE, hence, we can + // assume that those extensions are available. + BOTAN_ASSERT_NOMSG(exts.has() && exts.has()); + const auto& supported_by_client = exts.get()->groups(); + const auto& offered_by_client = exts.get()->offered_groups(); + const auto selected_group = policy.choose_key_exchange_group(supported_by_client, offered_by_client); + + // RFC 8446 4.1.1 + // If there is no overlap between the received "supported_groups" and the + // groups supported by the server, then the server MUST abort the + // handshake with a "handshake_failure" or an "insufficient_security" alert. + if(selected_group == Named_Group::NONE) { + throw TLS_Exception(Alert::HandshakeFailure, "Client did not offer any acceptable group"); + } + + // RFC 8446 4.2.8: + // Servers MUST NOT send a KeyShareEntry for any group not indicated in the + // client's "supported_groups" extension [...] + if(!value_exists(supported_by_client, selected_group)) { + throw TLS_Exception(Alert::InternalError, "Application selected a group that is not supported by the client"); + } + + // RFC 8446 4.1.4 + // The server will send this message in response to a ClientHello + // message if it is able to find an acceptable set of parameters but the + // ClientHello does not contain sufficient information to proceed with + // the handshake. + // + // In this case, the Client Hello did not contain a key share offer for + // the group selected by the application. + if(!value_exists(offered_by_client, selected_group)) { + // RFC 8446 4.1.4 + // If a client receives a second HelloRetryRequest in the same + // connection (i.e., where the ClientHello was itself in response to a + // HelloRetryRequest), it MUST abort the handshake with an + // "unexpected_message" alert. + BOTAN_STATE_CHECK(hello_retry_request_allowed); + return Hello_Retry_Request(ch, selected_group, policy, cb); + } else { + return Server_Hello_13(ch, selected_group, session_mgr, credentials_mgr, rng, cb, policy); + } +} + +std::variant Server_Hello_13::parse( + const std::vector& buf) { + auto data = std::make_unique(buf); + const auto version = data->version(); + + // server hello that appears to be pre-TLS 1.3, takes precedence over... + if(version.is_pre_tls_13()) { + return Server_Hello_12_Shim(std::move(data)); + } + + // ... the TLS 1.3 "special case" aka. Hello_Retry_Request + if(version == Protocol_Version::TLS_V13) { + if(data->is_hello_retry_request()) { + return Hello_Retry_Request(std::move(data)); + } + + return Server_Hello_13(std::move(data)); + } + + throw TLS_Exception(Alert::ProtocolVersion, "unexpected server hello version: " + version.to_string()); +} + +/** + * Validation that applies to both Server Hello and Hello Retry Request + */ +void Server_Hello_13::basic_validation() const { + BOTAN_ASSERT_NOMSG(m_data->version() == Protocol_Version::TLS_V13); + + // Note: checks that cannot be performed without contextual information + // are done in the specific TLS client implementation. + // Note: The Supported_Version extension makes sure internally that + // exactly one entry is provided. + + // Note: Hello Retry Request basic validation is equivalent with the + // basic validations required for Server Hello + // + // RFC 8446 4.1.4 + // Upon receipt of a HelloRetryRequest, the client MUST check the + // legacy_version, [...], and legacy_compression_method as specified in + // Section 4.1.3 and then process the extensions, starting with determining + // the version using "supported_versions". + + // RFC 8446 4.1.3 + // In TLS 1.3, [...] the legacy_version field MUST be set to 0x0303 + if(legacy_version() != Protocol_Version::TLS_V12) { + throw TLS_Exception(Alert::ProtocolVersion, + "legacy_version '" + legacy_version().to_string() + "' is not allowed"); + } + + // RFC 8446 4.1.3 + // legacy_compression_method: A single byte which MUST have the value 0. + if(compression_method() != 0x00) { + throw TLS_Exception(Alert::DecodeError, "compression is not supported in TLS 1.3"); + } + + // RFC 8446 4.1.3 + // All TLS 1.3 ServerHello messages MUST contain the "supported_versions" extension. + if(!extensions().has()) { + throw TLS_Exception(Alert::MissingExtension, "server hello did not contain 'supported version' extension"); + } + + // RFC 8446 4.2.1 + // A server which negotiates TLS 1.3 MUST respond by sending + // a "supported_versions" extension containing the selected version + // value (0x0304). + if(selected_version() != Protocol_Version::TLS_V13) { + throw TLS_Exception(Alert::IllegalParameter, "TLS 1.3 Server Hello selected a different version"); + } +} + +Server_Hello_13::Server_Hello_13(std::unique_ptr data, + Server_Hello_13::Server_Hello_Tag /*tag*/) : + Server_Hello(std::move(data)) { + BOTAN_ASSERT_NOMSG(!m_data->is_hello_retry_request()); + basic_validation(); + + const auto& exts = extensions(); + + // RFC 8446 4.1.3 + // The ServerHello MUST only include extensions which are required to + // establish the cryptographic context and negotiate the protocol version. + // [...] + // Other extensions (see Section 4.2) are sent separately in the + // EncryptedExtensions message. + // + // Note that further validation dependent on the client hello is done in the + // TLS client implementation. + const std::set allowed = { + Extension_Code::KeyShare, + Extension_Code::SupportedVersions, + Extension_Code::PresharedKey, + }; + + // As the ServerHello shall only contain essential extensions, we don't give + // any slack for extensions not implemented by Botan here. + if(exts.contains_other_than(allowed)) { + throw TLS_Exception(Alert::UnsupportedExtension, "Server Hello contained an extension that is not allowed"); + } + + // RFC 8446 4.1.3 + // Current ServerHello messages additionally contain + // either the "pre_shared_key" extension or the "key_share" + // extension, or both [...]. + if(!exts.has() && !exts.has()) { + throw TLS_Exception(Alert::MissingExtension, "server hello must contain key exchange information"); + } +} + +Server_Hello_13::Server_Hello_13(std::unique_ptr data, + Server_Hello_13::Hello_Retry_Request_Tag /*tag*/) : + Server_Hello(std::move(data)) { + BOTAN_ASSERT_NOMSG(m_data->is_hello_retry_request()); + basic_validation(); + + const auto& exts = extensions(); + + // RFC 8446 4.1.4 + // The HelloRetryRequest extensions defined in this specification are: + // - supported_versions (see Section 4.2.1) + // - cookie (see Section 4.2.2) + // - key_share (see Section 4.2.8) + const std::set allowed = { + Extension_Code::Cookie, + Extension_Code::SupportedVersions, + Extension_Code::KeyShare, + }; + + // As the Hello Retry Request shall only contain essential extensions, we + // don't give any slack for extensions not implemented by Botan here. + if(exts.contains_other_than(allowed)) { + throw TLS_Exception(Alert::UnsupportedExtension, + "Hello Retry Request contained an extension that is not allowed"); + } + + // RFC 8446 4.1.4 + // Clients MUST abort the handshake with an "illegal_parameter" alert if + // the HelloRetryRequest would not result in any change in the ClientHello. + if(!exts.has() && !exts.has()) { + throw TLS_Exception(Alert::IllegalParameter, "Hello Retry Request does not request any changes to Client Hello"); + } +} + +Server_Hello_13::Server_Hello_13(std::unique_ptr data, + Hello_Retry_Request_Creation_Tag /*tag*/) : + Server_Hello(std::move(data)) {} + +namespace { + +uint16_t choose_ciphersuite(const Client_Hello_13& ch, const Policy& policy) { + auto pref_list = ch.ciphersuites(); + // TODO: DTLS might need to make this version dynamic + auto other_list = policy.ciphersuite_list(Protocol_Version::TLS_V13); + + if(policy.server_uses_own_ciphersuite_preferences()) { + std::swap(pref_list, other_list); + } + + for(auto suite_id : pref_list) { + // TODO: take potentially available PSKs into account to select a + // compatible ciphersuite. + // + // Assuming the client sent one or more PSKs, we would first need to find + // the hash functions they are associated to. For session tickets, that + // would mean decrypting the ticket and comparing the cipher suite used in + // those tickets. For (currently not yet supported) pre-assigned PSKs, the + // hash function needs to be specified along with them. + // + // Then we could refine the ciphersuite selection using the required hash + // function for the PSK(s) we are wishing to use down the road. + // + // For now, we just negotiate the cipher suite blindly and hope for the + // best. As long as PSKs are used for session resumption only, this has a + // high chance of success. Previous handshakes with this client have very + // likely selected the same ciphersuite anyway. + // + // See also RFC 8446 4.2.11 + // When session resumption is the primary use case of PSKs, the most + // straightforward way to implement the PSK/cipher suite matching + // requirements is to negotiate the cipher suite first [...]. + if(value_exists(other_list, suite_id)) { + return suite_id; + } + } + + // RFC 8446 4.1.1 + // If the server is unable to negotiate a supported set of parameters + // [...], it MUST abort the handshake with either a "handshake_failure" + // or "insufficient_security" fatal alert [...]. + throw TLS_Exception(Alert::HandshakeFailure, "Can't agree on a ciphersuite with client"); +} +} // namespace + +Server_Hello_13::Server_Hello_13(const Client_Hello_13& ch, + std::optional key_exchange_group, + Session_Manager& session_mgr, + Credentials_Manager& credentials_mgr, + RandomNumberGenerator& rng, + Callbacks& cb, + const Policy& policy) : + Server_Hello(std::make_unique( + Protocol_Version::TLS_V12, + ch.session_id(), + make_server_hello_random(rng, Protocol_Version::TLS_V13, cb, policy), + choose_ciphersuite(ch, policy), + uint8_t(0) /* compression method */ + )) { + // RFC 8446 4.2.1 + // A server which negotiates TLS 1.3 MUST respond by sending a + // "supported_versions" extension containing the selected version + // value (0x0304). It MUST set the ServerHello.legacy_version field to + // 0x0303 (TLS 1.2). + // + // Note that the legacy version (TLS 1.2) is set in this constructor's + // initializer list, accordingly. + m_data->extensions().add(new Supported_Versions(Protocol_Version::TLS_V13)); // NOLINT(*-owning-memory) + + if(key_exchange_group.has_value()) { + BOTAN_ASSERT_NOMSG(ch.extensions().has()); + m_data->extensions().add(Key_Share::create_as_encapsulation( + key_exchange_group.value(), *ch.extensions().get(), policy, cb, rng)); + } + + const auto& ch_exts = ch.extensions(); + + if(ch_exts.has()) { + const auto cs = Ciphersuite::by_id(m_data->ciphersuite()); + BOTAN_ASSERT_NOMSG(cs); + + // RFC 8446 4.2.9 + // A client MUST provide a "psk_key_exchange_modes" extension if it + // offers a "pre_shared_key" extension. + // + // Note: Client_Hello_13 constructor already performed a graceful check. + auto* const psk_modes = ch_exts.get(); + BOTAN_ASSERT_NONNULL(psk_modes); + + // TODO: also support PSK_Key_Exchange_Mode::PSK_KE + // (PSK-based handshake without an additional ephemeral key exchange) + if(value_exists(psk_modes->modes(), PSK_Key_Exchange_Mode::PSK_DHE_KE)) { + if(auto server_psk = ch_exts.get()->select_offered_psk( + ch.sni_hostname(), cs.value(), session_mgr, credentials_mgr, cb, policy)) { + // RFC 8446 4.2.11 + // In order to accept PSK key establishment, the server sends a + // "pre_shared_key" extension indicating the selected identity. + m_data->extensions().add(std::move(server_psk)); + } + } + } + + cb.tls_modify_extensions(m_data->extensions(), Connection_Side::Server, type()); + + if(!m_data->extensions().has()) { + throw TLS_Exception(Alert::InternalError, + "Application tls_modify_extensions callback removed Key_Share from the ServerHello"); + } +} + +std::optional Server_Hello_13::random_signals_downgrade() const { + const uint64_t last8 = load_be(m_data->random().data(), 3); + if(last8 == DOWNGRADE_TLS11) { + return Protocol_Version::TLS_V11; + } + if(last8 == DOWNGRADE_TLS12) { + return Protocol_Version::TLS_V12; + } + + return std::nullopt; +} + +Protocol_Version Server_Hello_13::selected_version() const { + auto* const versions_ext = m_data->extensions().get(); + BOTAN_ASSERT_NOMSG(versions_ext); + const auto& versions = versions_ext->versions(); + BOTAN_ASSERT_NOMSG(versions.size() == 1); + return versions.front(); +} + +Hello_Retry_Request::Hello_Retry_Request(std::unique_ptr data) : + Server_Hello_13(std::move(data), Server_Hello_13::as_hello_retry_request) {} + +Hello_Retry_Request::Hello_Retry_Request(const Client_Hello_13& ch, + Named_Group selected_group, + const Policy& policy, + Callbacks& cb) : + Server_Hello_13(std::make_unique( + Protocol_Version::TLS_V12 /* legacy_version */, + ch.session_id(), + std::vector(HELLO_RETRY_REQUEST_MARKER.begin(), HELLO_RETRY_REQUEST_MARKER.end()), + choose_ciphersuite(ch, policy), + uint8_t(0) /* compression method */, + true /* is Hello Retry Request */ + ), + as_new_hello_retry_request) { + // RFC 8446 4.1.4 + // As with the ServerHello, a HelloRetryRequest MUST NOT contain any + // extensions that were not first offered by the client in its + // ClientHello, with the exception of optionally the "cookie" [...] + // extension. + BOTAN_STATE_CHECK(ch.extensions().has()); + BOTAN_STATE_CHECK(ch.extensions().has()); + + BOTAN_STATE_CHECK(!value_exists(ch.extensions().get()->offered_groups(), selected_group)); + + // RFC 8446 4.1.4 + // The server's extensions MUST contain "supported_versions". + // + // RFC 8446 4.2.1 + // A server which negotiates TLS 1.3 MUST respond by sending a + // "supported_versions" extension containing the selected version + // value (0x0304). It MUST set the ServerHello.legacy_version field to + // 0x0303 (TLS 1.2). + // + // Note that the legacy version (TLS 1.2) is set in this constructor's + // initializer list, accordingly. + // NOLINTBEGIN(*-owning-memory) + m_data->extensions().add(new Supported_Versions(Protocol_Version::TLS_V13)); + + m_data->extensions().add(new Key_Share(selected_group)); + // NOLINTEND(*-owning-memory) + + cb.tls_modify_extensions(m_data->extensions(), Connection_Side::Server, type()); + + if(!m_data->extensions().has()) { + throw TLS_Exception(Alert::InternalError, + "Application tls_modify_extensions callback removed Key_Share from the HelloRetryRequest"); + } +} + +} // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/msg_session_ticket_13.cpp botan3-3.12.0+dfsg/src/lib/tls/tls13/msg_session_ticket_13.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls13/msg_session_ticket_13.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/msg_session_ticket_13.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,104 @@ +/* +* Session Tickets +* (C) 2021-2022 Jack Lloyd +* 2021 Elektrobit Automotive GmbH +* 2022 René Meusel, Hannes Rantzsch - neXenio GmbH +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include +#include +#include + +#include + +namespace Botan::TLS { + +namespace { + +template +void store_lifetime(std::span sink, std::chrono::seconds lifetime) { + BOTAN_ARG_CHECK(lifetime.count() >= 0 && lifetime.count() <= std::numeric_limits::max(), + "Ticket lifetime is out of range"); + store_be(static_cast(lifetime.count()), sink.data()); +} + +} // namespace + +New_Session_Ticket_13::New_Session_Ticket_13(Ticket_Nonce nonce, + const Session& session, + const Session_Handle& handle, + Callbacks& callbacks) : + m_ticket_lifetime_hint(session.lifetime_hint()), + m_ticket_age_add(session.session_age_add()), + m_ticket_nonce(std::move(nonce)), + m_handle(handle.opaque_handle()) { + callbacks.tls_modify_extensions(m_extensions, Connection_Side::Server, type()); +} + +New_Session_Ticket_13::New_Session_Ticket_13(const std::vector& buf, Connection_Side from) { + TLS_Data_Reader reader("New_Session_Ticket_13", buf); + + m_ticket_lifetime_hint = std::chrono::seconds(reader.get_uint32_t()); + + // RFC 8446 4.6.1 + // Servers MUST NOT use any value [of ticket_lifetime] greater than 604800 + // seconds (7 days). + if(m_ticket_lifetime_hint > std::chrono::days(7)) { + throw TLS_Exception(Alert::IllegalParameter, "Received a session ticket with lifetime longer than one week."); + } + + m_ticket_age_add = reader.get_uint32_t(); + m_ticket_nonce = Ticket_Nonce(reader.get_tls_length_value(1)); + // RFC 8446 4.6.1: opaque ticket<1..2^16-1> + m_handle = Opaque_Session_Handle(reader.get_range(2, 1, 65535)); + + m_extensions.deserialize(reader, from, type()); + + // RFC 8446 4.6.1 + // The sole extension currently defined for NewSessionTicket is + // "early_data", indicating that the ticket may be used to send 0-RTT + // data [...]. Clients MUST ignore unrecognized extensions. + if(m_extensions.contains_implemented_extensions_other_than({Extension_Code::EarlyData})) { + throw TLS_Exception(Alert::IllegalParameter, "NewSessionTicket message contained unexpected extension"); + } + + reader.assert_done(); +} + +std::optional New_Session_Ticket_13::early_data_byte_limit() const { + if(!m_extensions.has()) { + return std::nullopt; + } + + const EarlyDataIndication* ext = m_extensions.get(); + BOTAN_ASSERT_NOMSG(ext->max_early_data_size().has_value()); + return ext->max_early_data_size(); +} + +std::vector New_Session_Ticket_13::serialize() const { + std::vector result(8); + + store_lifetime(std::span(result.data(), 4), m_ticket_lifetime_hint); + store_be(m_ticket_age_add, result.data() + 4); + append_tls_length_value(result, m_ticket_nonce.get(), 1); + append_tls_length_value(result, m_handle.get(), 2); + + // TODO: re-evaluate this construction when reworking message marshalling + if(m_extensions.empty()) { + result.push_back(0x00); + result.push_back(0x00); + } else { + result += m_extensions.serialize(Connection_Side::Server); + } + + return result; +} + +} // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_channel_impl_13.cpp botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_channel_impl_13.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_channel_impl_13.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_channel_impl_13.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -9,15 +9,15 @@ #include -#include -#include -#include +#include +#include +#include +#include +#include #include -#include -#include -#include #include +#include namespace { bool is_user_canceled_alert(const Botan::TLS::Alert& alert) { @@ -124,7 +124,7 @@ // Note: Server_Hello_12 was deliberately not included in the check below because in TLS 1.2 Server Hello and // other handshake messages can be legally coalesced in a single record. // - if(holds_any_ofcan_decrypt_application_traffic()) { + throw Unexpected_Message("Application data received before handshake completion"); + } + /* + The record sequence number is set in Record_Layer::next_record only when + the record contents are decrypted under the current set of traffic keys + */ + if(!record.seq_no.has_value()) { + throw Unexpected_Message("Application data must have a sequence number"); + } callbacks().tls_record_received(record.seq_no.value(), record.fragment); } else if(record.type == Record_Type::Alert) { process_alert(record.fragment); @@ -198,10 +208,20 @@ throw Unexpected_Message("Unexpected additional post-handshake message data found in record"); } - m_cipher_state->update_read_keys(*this); + if(const uint64_t min_interval = policy().minimum_key_update_interval_ms(); min_interval > 0) { + const uint64_t now = + std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()) + .count(); + + if(m_last_key_update_ms != 0 && (now - m_last_key_update_ms) < min_interval) { + throw TLS_Exception(Alert::UnexpectedMessage, "Peer is requesting KeyUpdates too frequently"); + } - // TODO: introduce some kind of rate limit of key updates, otherwise we - // might be forced into an endless loop of key updates. + m_last_key_update_ms = now; + } + + BOTAN_ASSERT_NONNULL(m_cipher_state); + m_cipher_state->update_read_keys(*this); // RFC 8446 4.6.3 // If the request_update field is set to "update_requested", then the @@ -315,8 +335,7 @@ } void Channel_Impl_13::update_traffic_keys(bool request_peer_update) { - BOTAN_STATE_CHECK(!is_downgrading()); - BOTAN_STATE_CHECK(is_handshake_complete()); + BOTAN_STATE_CHECK(!is_downgrading() && is_handshake_complete() && is_active()); BOTAN_ASSERT_NONNULL(m_cipher_state); send_post_handshake_message(Key_Update(request_peer_update)); m_cipher_state->update_write_keys(*this); @@ -349,7 +368,7 @@ } void Channel_Impl_13::process_alert(const secure_vector& record) { - Alert alert(record); + const Alert alert(record); if(is_close_notify_alert(alert)) { m_can_read = false; @@ -397,6 +416,7 @@ m_can_read = false; m_can_write = false; m_cipher_state.reset(); + m_active_state.reset(); } void Channel_Impl_13::expect_downgrade(const Server_Information& server_info, diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_channel_impl_13.h botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_channel_impl_13.h --- botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_channel_impl_13.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_channel_impl_13.h 2026-05-07 01:38:28.000000000 +0000 @@ -10,8 +10,10 @@ #ifndef BOTAN_TLS_CHANNEL_IMPL_13_H_ #define BOTAN_TLS_CHANNEL_IMPL_13_H_ +#include #include #include +#include #include #include #include @@ -27,7 +29,7 @@ * * The class is split from the rest of the Channel_Impl_13 for mockability. */ -class Secret_Logger { +class Secret_Logger /* NOLINT(*-special-member-functions) */ { public: virtual ~Secret_Logger() = default; @@ -78,10 +80,10 @@ bool contains_messages() const { return !m_message_buffer.empty(); } protected: - std::vector m_message_buffer; + std::vector m_message_buffer; // NOLINT(*non-private-member-variable*) - Channel_Impl_13& m_channel; - Handshake_Layer& m_handshake_layer; + Channel_Impl_13& m_channel; // NOLINT(*non-private-member-variable*) + Handshake_Layer& m_handshake_layer; // NOLINT(*non-private-member-variable*) }; /** @@ -136,9 +138,10 @@ const std::shared_ptr& policy, bool is_server); - explicit Channel_Impl_13(const Channel_Impl_13&) = delete; - - Channel_Impl_13& operator=(const Channel_Impl_13&) = delete; + Channel_Impl_13(const Channel_Impl_13& other) = delete; + Channel_Impl_13(Channel_Impl_13&& other) = delete; + Channel_Impl_13& operator=(const Channel_Impl_13& other) = delete; + Channel_Impl_13& operator=(Channel_Impl_13&& other) = delete; ~Channel_Impl_13() override; @@ -283,9 +286,10 @@ void shutdown(); protected: - const Connection_Side m_side; - Transcript_Hash_State m_transcript_hash; - std::unique_ptr m_cipher_state; + const Connection_Side m_side; // NOLINT(*non-private-member-variable*) + Transcript_Hash_State m_transcript_hash; // NOLINT(*non-private-member-variable*) + std::unique_ptr m_cipher_state; // NOLINT(*non-private-member-variable*) + std::optional m_active_state; // NOLINT(*non-private-member-variable*) /** * Indicate that we have to expect a downgrade to TLS 1.2. In which case the current @@ -336,6 +340,8 @@ bool m_opportunistic_key_update; bool m_first_message_sent; bool m_first_message_received; + + uint64_t m_last_key_update_ms = 0; }; } // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_cipher_state.cpp botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_cipher_state.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_cipher_state.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_cipher_state.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -14,7 +14,7 @@ * v * PSK -> HKDF-Extract = Early Secret * | - * +-----> Derive-Secret(., "ext binder" | "res binder", "") + * +-----> Derive-Secret(., "ext binder" | "res binder" | "imp binder", "") * | = binder_key * STATE PSK BINDER * This state is reached by constructing the Cipher_State using init_with_psk(). @@ -122,10 +122,10 @@ secure_vector&& shared_secret, const Ciphersuite& cipher, const Transcript_Hash& transcript_hash, - const Secret_Logger& loggger) { + const Secret_Logger& logger) { auto cs = std::unique_ptr(new Cipher_State(side, cipher.prf_algo())); cs->advance_without_psk(); - cs->advance_with_server_hello(cipher, std::move(shared_secret), transcript_hash, loggger); + cs->advance_with_server_hello(cipher, std::move(shared_secret), transcript_hash, logger); return cs; } @@ -138,7 +138,7 @@ return cs; } -void Cipher_State::advance_with_client_hello(const Transcript_Hash& transcript_hash, const Secret_Logger& loggger) { +void Cipher_State::advance_with_client_hello(const Transcript_Hash& transcript_hash, const Secret_Logger& logger) { BOTAN_ASSERT_NOMSG(m_state == State::PskBinder); zap(m_binder_key); @@ -155,7 +155,7 @@ // An implementation of TLS 1.3 use the label // "EARLY_EXPORTER_MASTER_SECRET" to identify the secret that is using for // early exporters - loggger.maybe_log_secret("EARLY_EXPORTER_MASTER_SECRET", m_exporter_master_secret); + logger.maybe_log_secret("EARLY_EXPORTER_MASTER_SECRET", m_exporter_master_secret); m_salt = derive_secret(m_early_secret, "derived", empty_hash()); zap(m_early_secret); @@ -163,7 +163,7 @@ m_state = State::EarlyTraffic; } -void Cipher_State::advance_with_server_finished(const Transcript_Hash& transcript_hash, const Secret_Logger& loggger) { +void Cipher_State::advance_with_server_finished(const Transcript_Hash& transcript_hash, const Secret_Logger& logger) { BOTAN_ASSERT_NOMSG(m_state == State::HandshakeTraffic); const auto master_secret = hkdf_extract(secure_vector(m_hash->output_length(), 0x00)); @@ -175,8 +175,8 @@ // An implementation of TLS 1.3 use the label "CLIENT_TRAFFIC_SECRET_0" // and "SERVER_TRAFFIC_SECRET_0" to identify the secrets are using to // protect the connection. - loggger.maybe_log_secret("CLIENT_TRAFFIC_SECRET_0", client_application_traffic_secret); - loggger.maybe_log_secret("SERVER_TRAFFIC_SECRET_0", server_application_traffic_secret); + logger.maybe_log_secret("CLIENT_TRAFFIC_SECRET_0", client_application_traffic_secret); + logger.maybe_log_secret("SERVER_TRAFFIC_SECRET_0", server_application_traffic_secret); // Note: the secrets for processing client's application data // are not derived before the client's Finished message @@ -197,7 +197,7 @@ // An implementation of TLS 1.3 use the label "EXPORTER_SECRET" to // identify the secret that is used in generating exporters(rfc8446 // Section 7.5). - loggger.maybe_log_secret("EXPORTER_SECRET", m_exporter_master_secret); + logger.maybe_log_secret("EXPORTER_SECRET", m_exporter_master_secret); m_state = State::ServerApplicationTraffic; } @@ -248,7 +248,12 @@ uint64_t Cipher_State::encrypt_record_fragment(const std::vector& header, secure_vector& fragment) { BOTAN_ASSERT_NONNULL(m_encrypt); - m_encrypt->set_key(m_write_key); + // RFC 8446 5.3 + // Sequence numbers MUST NOT wrap. + if(m_write_seq_no == std::numeric_limits::max()) { + throw Invalid_State("TLS write sequence number overflow"); + } + m_encrypt->set_associated_data(header); m_encrypt->start(current_nonce(m_write_seq_no, m_write_iv)); m_encrypt->finish(fragment); @@ -261,7 +266,12 @@ BOTAN_ASSERT_NONNULL(m_decrypt); BOTAN_ARG_CHECK(encrypted_fragment.size() >= m_decrypt->minimum_final_size(), "fragment too short to decrypt"); - m_decrypt->set_key(m_read_key); + // RFC 8446 5.3 + // Sequence numbers MUST NOT wrap. + if(m_read_seq_no == std::numeric_limits::max()) { + throw Invalid_State("TLS read sequence number overflow"); + } + m_decrypt->set_associated_data(header); m_decrypt->start(current_nonce(m_read_seq_no, m_read_iv)); @@ -357,12 +367,17 @@ } BOTAN_ASSERT_NOMSG((m_encrypt == nullptr) == (m_decrypt == nullptr)); - // TODO: Find a better way to check that the instantiated cipher algorithm - // is compatible with the one required by the cipher suite. - // AEAD_Mode::create() sets defaults the tag length to 16 which is then - // reported via AEAD_Mode::name() and hinders the trivial string comparison. - if(m_encrypt && m_encrypt->name() != cipher.cipher_algo() && m_encrypt->name() != cipher.cipher_algo() + "(16)") { - return false; + // Compare canonical AEAD names rather than substring-matching cipher_algo + // against m_encrypt->name(). starts_with() is both too permissive (an + // AES-128/CCM-8 instance starts with "AES-128/CCM" so it would accept the + // CCM-16 suite) and too restrictive (cipher_algo "AES-128/CCM(8)" does not + // prefix the canonical "AES-128/CCM(8,3)"). Re-instantiating the AEAD from + // cipher_algo yields the same canonical name() the suite would produce. + if(m_encrypt) { + auto canonical = AEAD_Mode::create(cipher.cipher_algo(), Cipher_Dir::Encryption); + if(!canonical || canonical->name() != m_encrypt->name()) { + return false; + } } return true; @@ -409,12 +424,17 @@ Ticket_Nonce Cipher_State::next_ticket_nonce() { BOTAN_STATE_CHECK(m_state == State::Completed); - if(m_ticket_nonce == std::numeric_limits::max()) { + if(m_ticket_nonce_exhausted) { throw Botan::Invalid_State("ticket nonce pool exhausted"); } - Ticket_Nonce retval(std::vector(sizeof(m_ticket_nonce))); - store_be(m_ticket_nonce++, retval.data()); + auto retval = store_be(m_ticket_nonce); + + if(m_ticket_nonce == std::numeric_limits::max()) { + m_ticket_nonce_exhausted = true; + } else { + ++m_ticket_nonce; + } return retval; } @@ -468,7 +488,18 @@ m_early_secret = hkdf_extract(std::move(psk)); - const char* binder_label = (type == PSK_Type::Resumption) ? "res binder" : "ext binder"; + // RFC 8446 and RFC 9258 specify these strings + const char* binder_label = [type]() -> const char* { + switch(type) { + case PSK_Type::Resumption: + return "res binder"; + case PSK_Type::External: + return "ext binder"; + case PSK_Type::Imported: + return "imp binder"; + } + BOTAN_ASSERT_UNREACHABLE(); + }(); // RFC 8446 4.2.11.2 // The PskBinderEntry is computed in the same way as the Finished message @@ -485,7 +516,7 @@ void Cipher_State::advance_with_server_hello(const Ciphersuite& cipher, secure_vector&& shared_secret, const Transcript_Hash& transcript_hash, - const Secret_Logger& loggger) { + const Secret_Logger& logger) { BOTAN_ASSERT_NOMSG(m_state == State::EarlyTraffic); BOTAN_ASSERT_NOMSG(!m_encrypt); BOTAN_ASSERT_NOMSG(!m_decrypt); @@ -503,8 +534,8 @@ // An implementation of TLS 1.3 use the label // "CLIENT_HANDSHAKE_TRAFFIC_SECRET" and "SERVER_HANDSHAKE_TRAFFIC_SECRET" // to identify the secrets are using to protect handshake messages. - loggger.maybe_log_secret("CLIENT_HANDSHAKE_TRAFFIC_SECRET", client_handshake_traffic_secret); - loggger.maybe_log_secret("SERVER_HANDSHAKE_TRAFFIC_SECRET", server_handshake_traffic_secret); + logger.maybe_log_secret("CLIENT_HANDSHAKE_TRAFFIC_SECRET", client_handshake_traffic_secret); + logger.maybe_log_secret("SERVER_HANDSHAKE_TRAFFIC_SECRET", server_handshake_traffic_secret); if(m_connection_side == Connection_Side::Server) { derive_read_traffic_key(client_handshake_traffic_secret, true); @@ -527,6 +558,8 @@ m_write_iv = hkdf_expand_label(traffic_secret, "iv", {}, NONCE_LENGTH); m_write_seq_no = 0; + m_encrypt->set_key(m_write_key); + if(handshake_traffic_secret) { // Key derivation for the MAC in the "Finished" handshake message as described in RFC 8446 4.4.4 // (will be cleared in advance_with_server_finished()) @@ -536,12 +569,14 @@ void Cipher_State::derive_read_traffic_key(const secure_vector& traffic_secret, const bool handshake_traffic_secret) { - BOTAN_ASSERT_NONNULL(m_encrypt); + BOTAN_ASSERT_NONNULL(m_decrypt); - m_read_key = hkdf_expand_label(traffic_secret, "key", {}, m_encrypt->minimum_keylength()); + m_read_key = hkdf_expand_label(traffic_secret, "key", {}, m_decrypt->minimum_keylength()); m_read_iv = hkdf_expand_label(traffic_secret, "iv", {}, NONCE_LENGTH); m_read_seq_no = 0; + m_decrypt->set_key(m_read_key); + if(handshake_traffic_secret) { // Key derivation for the MAC in the "Finished" handshake message as described in RFC 8446 4.4.4 // (will be cleared in advance_with_client_finished()) diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_cipher_state.h botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_cipher_state.h --- botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_cipher_state.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_cipher_state.h 2026-05-07 01:38:28.000000000 +0000 @@ -11,7 +11,6 @@ #include #include -#include #include @@ -61,14 +60,20 @@ */ class BOTAN_TEST_API Cipher_State { public: - enum class PSK_Type { - Resumption, - External, // currently not implemented + enum class PSK_Type : uint8_t { + Resumption, // RFC 8446 + External, // RFC 8446 + Imported, // RFC 9258 PSK importer - uses "imp binder" label }; public: ~Cipher_State(); + Cipher_State(const Cipher_State& other) = delete; + Cipher_State(Cipher_State&& other) = delete; + Cipher_State& operator=(const Cipher_State& other) = delete; + Cipher_State& operator=(Cipher_State&& other) = delete; + /** * Construct a Cipher_State from a Pre-Shared-Key. */ @@ -295,7 +300,7 @@ std::vector empty_hash() const; private: - enum class State { + enum class State : uint8_t { Uninitialized, PskBinder, EarlyTraffic, @@ -332,6 +337,7 @@ uint32_t m_read_key_update_count; uint16_t m_ticket_nonce; + bool m_ticket_nonce_exhausted = false; secure_vector m_finished_key; secure_vector m_peer_finished_key; diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_client_impl_13.cpp botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_client_impl_13.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_client_impl_13.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_client_impl_13.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,18 +6,19 @@ * * Botan is released under the Simplified BSD License (see license.txt) */ + #include #include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include #include -#include #include namespace Botan::TLS { @@ -31,7 +32,7 @@ const std::vector& next_protocols) : Channel_Impl_13(callbacks, session_manager, creds, rng, policy, false /* is_server */), m_info(std::move(info)), - m_should_send_ccs(false) { + m_handshake(std::make_unique()) { #if defined(BOTAN_HAS_TLS_12) if(policy->allow_tls12()) { expect_downgrade(m_info, next_protocols); @@ -40,7 +41,7 @@ if(auto session = find_session_for_resumption()) { if(!session->session.version().is_pre_tls_13()) { - m_resumed_session = std::move(session); + m_handshake->resumed_session = std::move(session); } else if(expects_downgrade()) { // If we found a session that was created with TLS 1.2, we downgrade // the implementation right away, before even issuing a Client Hello. @@ -49,13 +50,13 @@ } } - auto msg = send_handshake_message(m_handshake_state.sending( + auto msg = send_handshake_message(m_handshake->state.sending( Client_Hello_13(*policy, *callbacks, *rng, m_info.hostname(), next_protocols, - m_resumed_session, + m_handshake->resumed_session, creds->find_preshared_keys(m_info.hostname(), Connection_Side::Client)))); if(expects_downgrade()) { @@ -69,17 +70,19 @@ // // TODO: don't schedule ccs here when early data is used if(policy->tls_13_middlebox_compatibility_mode()) { - m_should_send_ccs = true; + m_handshake->should_send_ccs = true; } - m_transitions.set_expected_next({Handshake_Type::ServerHello, Handshake_Type::HelloRetryRequest}); + m_handshake->transitions.set_expected_next({Handshake_Type::ServerHello, Handshake_Type::HelloRetryRequest}); } void Client_Impl_13::process_handshake_msg(Handshake_Message_13 message) { + BOTAN_STATE_CHECK(m_handshake != nullptr); + std::visit( [&](auto msg) { // first verify that the message was expected by the state machine... - m_transitions.confirm_transition_to(msg.get().type()); + m_handshake->transitions.confirm_transition_to(msg.get().type()); // ... then allow the library user to abort on their discretion callbacks().tls_inspect_handshake_msg(msg.get()); @@ -87,13 +90,18 @@ // ... finally handle the message handle(msg.get()); }, - m_handshake_state.received(std::move(message))); + m_handshake->state.received(std::move(message))); } void Client_Impl_13::process_post_handshake_msg(Post_Handshake_Message_13 message) { BOTAN_STATE_CHECK(is_handshake_complete()); - std::visit([&](auto msg) { handle(msg); }, m_handshake_state.received(std::move(message))); + const auto msg = specialize_to(std::move(message)); + if(!msg) { + throw TLS_Exception(Alert::UnexpectedMessage, "received an unexpected post-handshake message"); + } + + std::visit([&](auto&& m) { handle(m); }, *msg); } void Client_Impl_13::process_dummy_change_cipher_spec() { @@ -101,7 +109,7 @@ // If an implementation detects a change_cipher_spec record received before // the first ClientHello message or after the peer's Finished message, it MUST be // treated as an unexpected record type [("unexpected_message" alert)]. - if(!m_handshake_state.has_client_hello() || m_handshake_state.has_server_finished()) { + if(!m_handshake || !m_handshake->state.has_client_hello() || m_handshake->state.has_server_finished()) { throw TLS_Exception(Alert::UnexpectedMessage, "Received an unexpected dummy Change Cipher Spec"); } @@ -115,7 +123,7 @@ } bool Client_Impl_13::is_handshake_complete() const { - return m_handshake_state.handshake_finished(); + return m_active_state.has_value(); } std::optional Client_Impl_13::find_session_for_resumption() { @@ -147,8 +155,10 @@ return std::move(session_to_resume); } -void Client_Impl_13::handle(const Server_Hello_12& server_hello_msg) { - if(m_handshake_state.has_hello_retry_request()) { +void Client_Impl_13::handle(const Server_Hello_12_Shim& server_hello_msg) { + BOTAN_ASSERT_NONNULL(m_handshake); + + if(m_handshake->state.has_hello_retry_request()) { throw TLS_Exception(Alert::UnexpectedMessage, "Version downgrade received after Hello Retry"); } @@ -179,7 +189,7 @@ // MUST NOT send the "supported_versions" extension. // // Note that this condition should never happen, as the Server_Hello parsing - // code decides to create a Server_Hello_12 based on the absense of this extension. + // code decides to create a Server_Hello_12 based on the absence of this extension. if(server_hello_msg.extensions().has()) { throw TLS_Exception(Alert::IllegalParameter, "Unexpected extension received"); } @@ -188,14 +198,14 @@ // If the version chosen by the server is not supported by the client // (or is not acceptable), the client MUST abort the handshake with a // "protocol_version" alert. - const auto& client_hello_exts = m_handshake_state.client_hello().extensions(); + const auto& client_hello_exts = m_handshake->state.client_hello().extensions(); BOTAN_ASSERT_NOMSG(client_hello_exts.has()); if(!client_hello_exts.get()->supports(server_hello_msg.selected_version())) { throw TLS_Exception(Alert::ProtocolVersion, "Protocol version was not offered"); } if(policy().tls_13_middlebox_compatibility_mode() && - m_handshake_state.client_hello().session_id() == server_hello_msg.session_id()) { + m_handshake->state.client_hello().session_id() == server_hello_msg.session_id()) { // In compatibility mode, the server will reflect the session ID we sent in the client hello. // However, a TLS 1.2 server that wants to downgrade cannot have found the random session ID // we sent. Therefore, we have to consider this as an attack. @@ -207,7 +217,7 @@ // After this, no further messages are expected here because this instance will be replaced // by a Client_Impl_12. - m_transitions.set_expected_next({}); + m_handshake->transitions.set_expected_next({}); } namespace { @@ -243,11 +253,22 @@ void Client_Impl_13::handle(const Server_Hello_13& sh) { // Note: Basic checks (that do not require contextual information) were already // performed during the construction of the Server_Hello_13 object. + BOTAN_ASSERT_NONNULL(m_handshake); - const auto& ch = m_handshake_state.client_hello(); + const auto& ch = m_handshake->state.client_hello(); validate_server_hello_ish(ch, sh); + // RFC 8446 4.1.3: TLS 1.3 servers downgrading to TLS 1.2 or below set + // the last 8 bytes of ServerHello.random to a magic value so the client + // can detect a stripped-supported_versions downgrade attack. The Shim + // path (Server_Hello_12_Shim) already enforces this; catch it here too + // as defense in depth in case a misbehaving server writes the sentinel + // into an actual TLS 1.3 ServerHello. + if(sh.random_signals_downgrade().has_value()) { + throw TLS_Exception(Alert::IllegalParameter, "Downgrade attack detected"); + } + // RFC 8446 4.2 // Implementations MUST NOT send extension responses if the remote // endpoint did not send the corresponding extension requests, [...]. Upon @@ -257,8 +278,8 @@ throw TLS_Exception(Alert::UnsupportedExtension, "Unsupported extension found in Server Hello"); } - if(m_handshake_state.has_hello_retry_request()) { - const auto& hrr = m_handshake_state.hello_retry_request(); + if(m_handshake->state.has_hello_retry_request()) { + const auto& hrr = m_handshake->state.hello_retry_request(); // RFC 8446 4.1.4 // Upon receiving the ServerHello, clients MUST check that the cipher suite @@ -303,20 +324,20 @@ throw TLS_Exception(Alert::IllegalParameter, "Server Hello did not contain a key share extension"); } - auto my_keyshare = ch.extensions().get(); + auto* my_keyshare = ch.extensions().get(); auto shared_secret = my_keyshare->decapsulate(*sh.extensions().get(), policy(), callbacks(), rng()); m_transcript_hash.set_algorithm(cipher.value().prf_algo()); if(sh.extensions().has()) { - std::tie(m_psk_identity, m_cipher_state) = + std::tie(m_handshake->psk_identity, m_cipher_state) = ch.extensions().get()->take_selected_psk_info(*sh.extensions().get(), cipher.value()); // If we offered a session for resumption *and* an externally provided PSK // and the latter was chosen by the server over the offered resumption, we - // want to invalidate the now-outdated session in m_resumed_session. - if(m_psk_identity.has_value() && m_resumed_session.has_value()) { - m_resumed_session.reset(); + // want to invalidate the now-outdated session in m_handshake->resumed_session. + if(m_handshake->psk_identity.has_value() && m_handshake->resumed_session.has_value()) { + m_handshake->resumed_session.reset(); } // TODO: When implementing early data, `advance_with_client_hello` must @@ -326,22 +347,23 @@ m_cipher_state->advance_with_server_hello( cipher.value(), std::move(shared_secret), m_transcript_hash.current(), *this); } else { - m_resumed_session.reset(); // might have been set if we attempted a resumption + m_handshake->resumed_session.reset(); // might have been set if we attempted a resumption m_cipher_state = Cipher_State::init_with_server_hello( m_side, std::move(shared_secret), cipher.value(), m_transcript_hash.current(), *this); } callbacks().tls_examine_extensions(sh.extensions(), Connection_Side::Server, Handshake_Type::ServerHello); - m_transitions.set_expected_next(Handshake_Type::EncryptedExtensions); + m_handshake->transitions.set_expected_next(Handshake_Type::EncryptedExtensions); } void Client_Impl_13::handle(const Hello_Retry_Request& hrr) { // Note: Basic checks (that do not require contextual information) were already // performed during the construction of the Hello_Retry_Request object as // a subclass of Server_Hello_13. + BOTAN_ASSERT_NONNULL(m_handshake); - auto& ch = m_handshake_state.client_hello(); + auto& ch = m_handshake->state.client_hello(); validate_server_hello_ish(ch, hrr); @@ -358,6 +380,13 @@ auto cipher = Ciphersuite::by_id(hrr.ciphersuite()); BOTAN_ASSERT_NOMSG(cipher.has_value()); // should work, since we offered this suite + // RFC 8446 4.1.4 / Appendix B.4 + // Similarly, cipher suites for TLS 1.2 and lower cannot be used with + // TLS 1.3. + if(!cipher->usable_in_version(Protocol_Version::TLS_V13)) { + throw TLS_Exception(Alert::IllegalParameter, "HelloRetryRequest selected a cipher suite not usable in TLS 1.3"); + } + m_transcript_hash = Transcript_Hash_State::recreate_after_hello_retry_request(cipher.value().prf_algo(), m_transcript_hash); @@ -370,10 +399,12 @@ // RFC 8446 4.1.4 // If a client receives a second HelloRetryRequest in the same connection [...], // it MUST abort the handshake with an "unexpected_message" alert. - m_transitions.set_expected_next(Handshake_Type::ServerHello); + m_handshake->transitions.set_expected_next(Handshake_Type::ServerHello); } void Client_Impl_13::handle(const Encrypted_Extensions& encrypted_extensions_msg) { + BOTAN_ASSERT_NONNULL(m_handshake); + const auto& exts = encrypted_extensions_msg.extensions(); // RFC 8446 4.2 @@ -381,7 +412,7 @@ // endpoint did not send the corresponding extension requests, [...]. Upon // receiving such an extension, an endpoint MUST abort the handshake // with an "unsupported_extension" alert. - const auto& requested_exts = m_handshake_state.client_hello().extensions().extension_types(); + const auto& requested_exts = m_handshake->state.client_hello().extensions().extension_types(); if(exts.contains_other_than(requested_exts)) { throw TLS_Exception(Alert::UnsupportedExtension, "Encrypted Extensions contained an extension that was not offered"); @@ -390,7 +421,23 @@ // Note: As per RFC 6066 3. we can check for an empty SNI extensions to // determine if the server used the SNI we sent here. - if(exts.has() && m_handshake_state.client_hello().extensions().has()) { + if(exts.has()) { + // RFC 7301 3.2 + // The "extension_data" field of the [...] "application_layer_protocol_negotiation" + // extension [...] SHALL include the server's selection of a protocol from among + // the list that was advertised by the client. + const auto* server_alpn = exts.get(); + const auto selected = server_alpn->single_protocol(); + const auto* client_alpn = + m_handshake->state.client_hello().extensions().get(); + BOTAN_ASSERT_NONNULL(client_alpn); // unrequested extension check above ensures this + const auto& offered = client_alpn->protocols(); + if(!value_exists(offered, selected)) { + throw TLS_Exception(Alert::IllegalParameter, "Server selected an ALPN protocol not offered by the client"); + } + } + + if(exts.has() && m_handshake->state.client_hello().extensions().has()) { // RFC 8449 4. // The record size limit only applies to records sent toward the // endpoint that advertises the limit. An endpoint can send records @@ -398,15 +445,16 @@ // // Hence, the "outgoing" limit is what the server requested and the // "incoming" limit is what we requested in the Client Hello. - const auto outgoing_limit = exts.get(); - const auto incoming_limit = m_handshake_state.client_hello().extensions().get(); + auto* const outgoing_limit = exts.get(); + auto* const incoming_limit = m_handshake->state.client_hello().extensions().get(); set_record_size_limits(outgoing_limit->limit(), incoming_limit->limit()); } - if(exts.has() && - m_handshake_state.client_hello().extensions().has()) { + if(exts.has()) { + // The unrequested-extension check above ensures the client offered this. + BOTAN_ASSERT_NOMSG(m_handshake->state.client_hello().extensions().has()); const auto* server_cert_type = exts.get(); - const auto* our_server_cert_types = m_handshake_state.client_hello().extensions().get(); + const auto* our_server_cert_types = m_handshake->state.client_hello().extensions().get(); our_server_cert_types->validate_selection(*server_cert_type); // RFC 7250 4.2 @@ -421,17 +469,19 @@ callbacks().tls_examine_extensions(exts, Connection_Side::Server, Handshake_Type::EncryptedExtensions); - if(m_handshake_state.server_hello().extensions().has()) { + if(m_handshake->state.server_hello().extensions().has()) { // RFC 8446 2.2 // As the server is authenticating via a PSK, it does not send a // Certificate or a CertificateVerify message. - m_transitions.set_expected_next(Handshake_Type::Finished); + m_handshake->transitions.set_expected_next(Handshake_Type::Finished); } else { - m_transitions.set_expected_next({Handshake_Type::Certificate, Handshake_Type::CertificateRequest}); + m_handshake->transitions.set_expected_next({Handshake_Type::Certificate, Handshake_Type::CertificateRequest}); } } void Client_Impl_13::handle(const Certificate_Request_13& certificate_request_msg) { + BOTAN_ASSERT_NONNULL(m_handshake); + // RFC 8446 4.3.2 // [The 'context' field] SHALL be zero length unless used for the // post-handshake authentication exchanges described in Section 4.6.2. @@ -441,10 +491,12 @@ callbacks().tls_examine_extensions( certificate_request_msg.extensions(), Connection_Side::Server, Handshake_Type::CertificateRequest); - m_transitions.set_expected_next(Handshake_Type::Certificate); + m_handshake->transitions.set_expected_next(Handshake_Type::Certificate); } void Client_Impl_13::handle(const Certificate_13& certificate_msg) { + BOTAN_ASSERT_NONNULL(m_handshake); + // RFC 8446 4.4.2 // certificate_request_context: [...] In the case of server authentication, // this field SHALL be zero length. @@ -455,17 +507,19 @@ // RFC 8446 4.4.2 // Extensions in the Certificate message from the server MUST correspond // to ones from the ClientHello message. - certificate_msg.validate_extensions(m_handshake_state.client_hello().extensions().extension_types(), callbacks()); + certificate_msg.validate_extensions(m_handshake->state.client_hello().extensions().extension_types(), callbacks()); certificate_msg.verify(callbacks(), policy(), credentials_manager(), m_info.hostname(), - m_handshake_state.client_hello().extensions().has()); + m_handshake->state.client_hello().extensions().has()); - m_transitions.set_expected_next(Handshake_Type::CertificateVerify); + m_handshake->transitions.set_expected_next(Handshake_Type::CertificateVerify); } void Client_Impl_13::handle(const Certificate_Verify_13& certificate_verify_msg) { + BOTAN_ASSERT_NONNULL(m_handshake); + // RFC 8446 4.4.3 // If the CertificateVerify message is sent by a server, the signature // algorithm MUST be one offered in the client's "signature_algorithms" @@ -474,31 +528,33 @@ // // Note: if the server failed to produce a certificate chain without using // an unsupported signature scheme, we opt to abort the handshake. - const auto offered = m_handshake_state.client_hello().signature_schemes(); + const auto offered = m_handshake->state.client_hello().signature_schemes(); if(!value_exists(offered, certificate_verify_msg.signature_scheme())) { throw TLS_Exception(Alert::IllegalParameter, "We did not offer the usage of " + certificate_verify_msg.signature_scheme().to_string() + " as a signature scheme"); } - bool sig_valid = certificate_verify_msg.verify( - *m_handshake_state.server_certificate().public_key(), callbacks(), m_transcript_hash.previous()); + const bool sig_valid = certificate_verify_msg.verify( + *m_handshake->state.server_certificate().public_key(), callbacks(), m_transcript_hash.previous()); if(!sig_valid) { throw TLS_Exception(Alert::DecryptError, "Server certificate verification failed"); } - m_transitions.set_expected_next(Handshake_Type::Finished); + m_handshake->transitions.set_expected_next(Handshake_Type::Finished); } void Client_Impl_13::send_client_authentication(Channel_Impl_13::AggregatedHandshakeMessages& flight) { - BOTAN_ASSERT_NOMSG(m_handshake_state.has_certificate_request()); - const auto& cert_request = m_handshake_state.certificate_request(); + BOTAN_ASSERT_NOMSG(m_handshake->state.has_certificate_request()); + const auto& cert_request = m_handshake->state.certificate_request(); const auto cert_type = [&] { - const auto& exts = m_handshake_state.encrypted_extensions().extensions(); - const auto& chexts = m_handshake_state.client_hello().extensions(); - if(exts.has() && chexts.has()) { + const auto& exts = m_handshake->state.encrypted_extensions().extensions(); + const auto& chexts = m_handshake->state.client_hello().extensions(); + if(exts.has()) { + // The unrequested-extension check in handle(Encrypted_Extensions) ensures the client offered this. + BOTAN_ASSERT_NOMSG(chexts.has()); const auto* client_cert_type = exts.get(); chexts.get()->validate_selection(*client_cert_type); @@ -523,7 +579,7 @@ // certificate_request_context: If this message is in response to a // CertificateRequest, the value of certificate_request_context in // that message. - flight.add(m_handshake_state.sending( + flight.add(m_handshake->state.sending( Certificate_13(cert_request, m_info.hostname(), credentials_manager(), callbacks(), cert_type))); // RFC 8446 4.4.2 @@ -532,20 +588,22 @@ // certificates. // // In that case, no Certificate Verify message will be sent. - if(!m_handshake_state.client_certificate().empty()) { - flight.add(m_handshake_state.sending(Certificate_Verify_13(m_handshake_state.client_certificate(), - cert_request.signature_schemes(), - m_info.hostname(), - m_transcript_hash.current(), - Connection_Side::Client, - credentials_manager(), - policy(), - callbacks(), - rng()))); + if(!m_handshake->state.client_certificate().empty()) { + flight.add(m_handshake->state.sending(Certificate_Verify_13(m_handshake->state.client_certificate(), + cert_request.signature_schemes(), + m_info.hostname(), + m_transcript_hash.current(), + Connection_Side::Client, + credentials_manager(), + policy(), + callbacks(), + rng()))); } } void Client_Impl_13::handle(const Finished_13& finished_msg) { + BOTAN_ASSERT_NONNULL(m_handshake); + // RFC 8446 4.4.4 // Recipients of Finished messages MUST verify that the contents are // correct and if incorrect MUST terminate the connection with a @@ -554,14 +612,16 @@ throw TLS_Exception(Alert::DecryptError, "Finished message didn't verify"); } + m_handshake->state.confirm_peer_finished_verified(); + // Give the application a chance for a final veto before fully // establishing the connection. - callbacks().tls_session_established(Session_Summary(m_handshake_state.server_hello(), - Connection_Side::Server, + callbacks().tls_session_established(Session_Summary(m_handshake->state.server_hello(), + Connection_Side::Client, peer_cert_chain(), peer_raw_public_key(), external_psk_identity(), - m_resumed_session.has_value(), + m_handshake->resumed_session.has_value(), m_info, callbacks().tls_current_timestamp())); @@ -574,12 +634,12 @@ // RFC 8446 4.4.2 // The client MUST send a Certificate message if and only if the server // has requested client authentication via a CertificateRequest message. - if(m_handshake_state.has_certificate_request()) { + if(m_handshake->state.has_certificate_request()) { send_client_authentication(flight); } // send client finished handshake message (still using handshake traffic secrets) - flight.add(m_handshake_state.sending(Finished_13(m_cipher_state.get(), m_transcript_hash.current()))); + flight.add(m_handshake->state.sending(Finished_13(m_cipher_state.get(), m_transcript_hash.current()))); flight.send(); @@ -591,77 +651,145 @@ // callback's doc string. // no more handshake messages expected - m_transitions.set_expected_next({}); + m_handshake->transitions.set_expected_next({}); + // Extract post-handshake state before signaling activation. + // After this point, only m_active_state should be consulted + // for connection properties. + { + auto extract_certs = [&]() -> std::vector { + if(m_handshake->state.has_server_certificate_msg() && + m_handshake->state.server_certificate().has_certificate_chain()) { + return m_handshake->state.server_certificate().cert_chain(); + } + if(m_handshake->resumed_session.has_value()) { + return m_handshake->resumed_session->session.peer_certs(); + } + return {}; + }; + + auto extract_raw_pk = [&]() -> std::shared_ptr { + if(m_handshake->state.has_server_certificate_msg() && + m_handshake->state.server_certificate().is_raw_public_key()) { + return m_handshake->state.server_certificate().public_key(); + } + if(m_handshake->resumed_session.has_value()) { + return m_handshake->resumed_session->session.peer_raw_public_key(); + } + return nullptr; + }; + + m_active_state = Active_Connection_State_13(m_handshake->state, + extract_certs(), + extract_raw_pk(), + m_handshake->psk_identity, + m_info.hostname(), + false /* peer_supports_psk_dhe_ke - client doesn't need this */); + } + + m_handshake.reset(); + m_transcript_hash = Transcript_Hash_State(); callbacks().tls_session_activated(); } void TLS::Client_Impl_13::handle(const New_Session_Ticket_13& new_session_ticket) { + BOTAN_STATE_CHECK(m_active_state.has_value()); + + if(const size_t max_tickets = policy().maximum_session_tickets_per_connection(); + max_tickets > 0 && m_session_tickets_received >= max_tickets) { + // Silently ignore excess tickets rather than terminating the connection, + // since the server may have legitimate reasons to send many tickets. + return; + } + ++m_session_tickets_received; + callbacks().tls_examine_extensions( new_session_ticket.extensions(), Connection_Side::Server, Handshake_Type::NewSessionTicket); - Session session(m_cipher_state->psk(new_session_ticket.nonce()), - new_session_ticket.early_data_byte_limit(), - new_session_ticket.ticket_age_add(), - new_session_ticket.lifetime_hint(), - m_handshake_state.server_hello().selected_version(), - m_handshake_state.server_hello().ciphersuite(), - Connection_Side::Client, - peer_cert_chain(), - peer_raw_public_key(), - m_info, - callbacks().tls_current_timestamp()); + const Session session(m_cipher_state->psk(new_session_ticket.nonce()), + new_session_ticket.early_data_byte_limit(), + new_session_ticket.ticket_age_add(), + new_session_ticket.lifetime_hint(), + m_active_state->version(), + m_active_state->ciphersuite_code(), + Connection_Side::Client, + peer_cert_chain(), + peer_raw_public_key(), + m_info, + callbacks().tls_current_timestamp()); if(callbacks().tls_should_persist_resumption_information(session)) { - session_manager().store(session, new_session_ticket.handle()); + session_manager().store(session, Session_Handle(new_session_ticket.handle())); } } std::vector Client_Impl_13::peer_cert_chain() const { - if(m_handshake_state.has_server_certificate_msg() && - m_handshake_state.server_certificate().has_certificate_chain()) { - return m_handshake_state.server_certificate().cert_chain(); + if(m_active_state.has_value()) { + return m_active_state->peer_certs(); } - if(m_resumed_session.has_value()) { - return m_resumed_session->session.peer_certs(); + // During handshake, before m_active_state is populated + if(m_handshake) { + if(m_handshake->state.has_server_certificate_msg() && + m_handshake->state.server_certificate().has_certificate_chain()) { + return m_handshake->state.server_certificate().cert_chain(); + } + + if(m_handshake->resumed_session.has_value()) { + return m_handshake->resumed_session->session.peer_certs(); + } } return {}; } std::shared_ptr Client_Impl_13::peer_raw_public_key() const { - if(m_handshake_state.has_server_certificate_msg() && m_handshake_state.server_certificate().is_raw_public_key()) { - return m_handshake_state.server_certificate().public_key(); + if(m_active_state.has_value()) { + return m_active_state->peer_raw_public_key(); } - if(m_resumed_session.has_value()) { - return m_resumed_session->session.peer_raw_public_key(); + // During handshake, before m_active_state is populated + if(m_handshake) { + if(m_handshake->state.has_server_certificate_msg() && + m_handshake->state.server_certificate().is_raw_public_key()) { + return m_handshake->state.server_certificate().public_key(); + } + + if(m_handshake->resumed_session.has_value()) { + return m_handshake->resumed_session->session.peer_raw_public_key(); + } } return nullptr; } std::optional Client_Impl_13::external_psk_identity() const { - return m_psk_identity; + if(m_active_state.has_value()) { + return m_active_state->psk_identity(); + } + if(m_handshake) { + return m_handshake->psk_identity; + } + return std::nullopt; } bool Client_Impl_13::prepend_ccs() { - return std::exchange(m_should_send_ccs, false); // test-and-set + return m_handshake && std::exchange(m_handshake->should_send_ccs, false); } void Client_Impl_13::maybe_log_secret(std::string_view label, std::span secret) const { if(policy().allow_ssl_key_log_file()) { - callbacks().tls_ssl_key_log_data(label, m_handshake_state.client_hello().random(), secret); + if(m_active_state.has_value()) { + callbacks().tls_ssl_key_log_data(label, m_active_state->client_random(), secret); + } else { + callbacks().tls_ssl_key_log_data(label, m_handshake->state.client_hello().random(), secret); + } } } std::string Client_Impl_13::application_protocol() const { - if(is_handshake_complete()) { - const auto& eee = m_handshake_state.encrypted_extensions().extensions(); - if(eee.has()) { - return eee.get()->single_protocol(); - } + if(m_active_state.has_value()) { + return m_active_state->application_protocol(); } return ""; diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_client_impl_13.h botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_client_impl_13.h --- botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_client_impl_13.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_client_impl_13.h 2026-05-07 01:38:28.000000000 +0000 @@ -25,7 +25,7 @@ /** * SSL/TLS Client 1.3 implementation */ -class Client_Impl_13 : public Channel_Impl_13 { +class Client_Impl_13 final : public Channel_Impl_13 { public: /** * Set up a new TLS client session @@ -88,7 +88,7 @@ bool prepend_ccs() override; using Channel_Impl_13::handle; - void handle(const Server_Hello_12& server_hello_msg); + void handle(const Server_Hello_12_Shim& server_hello_msg); void handle(const Server_Hello_13& server_hello_msg); void handle(const Hello_Retry_Request& hrr_msg); void handle(const Encrypted_Extensions& encrypted_extensions_msg); @@ -104,13 +104,16 @@ private: const Server_Information m_info; - Client_Handshake_State_13 m_handshake_state; - Handshake_Transitions m_transitions; + struct Pending_Handshake { + Client_Handshake_State_13 state; + Handshake_Transitions transitions; + bool should_send_ccs = false; + std::optional resumed_session; + std::optional psk_identity; + }; - bool m_should_send_ccs; - - std::optional m_resumed_session; - std::optional m_psk_identity; + std::unique_ptr m_handshake; + size_t m_session_tickets_received = 0; }; } // namespace TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_connection_state_13.cpp botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_connection_state_13.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_connection_state_13.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_connection_state_13.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,46 @@ +/* +* (C) 2026 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include + +namespace Botan::TLS { + +namespace { + +std::string extract_alpn(const Internal::Handshake_State_13_Base& state) { + const auto& eee = state.encrypted_extensions().extensions(); + if(const auto* alpn = eee.get()) { + return alpn->single_protocol(); + } + return {}; +} + +} // namespace + +Active_Connection_State_13::~Active_Connection_State_13() = default; +Active_Connection_State_13::Active_Connection_State_13(Active_Connection_State_13&&) noexcept = default; +Active_Connection_State_13& Active_Connection_State_13::operator=(Active_Connection_State_13&&) noexcept = default; + +Active_Connection_State_13::Active_Connection_State_13(const Internal::Handshake_State_13_Base& state, + std::vector peer_certs, + std::shared_ptr peer_raw_public_key, + std::optional psk_identity, + std::string sni_hostname, + bool peer_supports_psk_dhe_ke) : + m_version(state.server_hello().selected_version()), + m_ciphersuite_code(state.server_hello().ciphersuite()), + m_application_protocol(extract_alpn(state)), + m_peer_certs(std::move(peer_certs)), + m_client_random(state.client_hello().random()), + m_psk_identity(std::move(psk_identity)), + m_peer_raw_public_key(std::move(peer_raw_public_key)), + m_sni_hostname(std::move(sni_hostname)), + m_peer_supports_psk_dhe_ke(peer_supports_psk_dhe_ke) {} + +} // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_connection_state_13.h botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_connection_state_13.h --- botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_connection_state_13.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_connection_state_13.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,82 @@ +/* +* (C) 2026 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_TLS_CONNECTION_STATE_13_H_ +#define BOTAN_TLS_CONNECTION_STATE_13_H_ + +#include +#include + +#include +#include +#include +#include + +namespace Botan { + +class Public_Key; + +} // namespace Botan + +namespace Botan::TLS { + +namespace Internal { +class Handshake_State_13_Base; +} + +/** +* Captures the state of a completed TLS 1.3 handshake that is needed +* for the lifetime of an active connection. +*/ +class Active_Connection_State_13 final { + public: + Active_Connection_State_13(const Internal::Handshake_State_13_Base& state, + std::vector peer_certs, + std::shared_ptr peer_raw_public_key, + std::optional psk_identity, + std::string sni_hostname, + bool peer_supports_psk_dhe_ke); + + ~Active_Connection_State_13(); + Active_Connection_State_13(Active_Connection_State_13&&) noexcept; + Active_Connection_State_13& operator=(Active_Connection_State_13&&) noexcept; + + Active_Connection_State_13(const Active_Connection_State_13&) = delete; + Active_Connection_State_13& operator=(const Active_Connection_State_13&) = delete; + + Protocol_Version version() const { return m_version; } + + uint16_t ciphersuite_code() const { return m_ciphersuite_code; } + + const std::string& application_protocol() const { return m_application_protocol; } + + const std::vector& peer_certs() const { return m_peer_certs; } + + const std::vector& client_random() const { return m_client_random; } + + const std::optional& psk_identity() const { return m_psk_identity; } + + const std::shared_ptr& peer_raw_public_key() const { return m_peer_raw_public_key; } + + const std::string& sni_hostname() const { return m_sni_hostname; } + + bool peer_supports_psk_dhe_ke() const { return m_peer_supports_psk_dhe_ke; } + + private: + Protocol_Version m_version; + uint16_t m_ciphersuite_code = 0; + std::string m_application_protocol; + std::vector m_peer_certs; + std::vector m_client_random; + std::optional m_psk_identity; + std::shared_ptr m_peer_raw_public_key; + std::string m_sni_hostname; + bool m_peer_supports_psk_dhe_ke = false; +}; + +} // namespace Botan::TLS + +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_extensions_13.cpp botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_extensions_13.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_extensions_13.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_extensions_13.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,170 @@ +/* +* TLS 1.3 Specific Extensions +* (C) 2011,2012,2015,2016 Jack Lloyd +* 2016 Juraj Somorovsky +* 2021 Elektrobit Automotive GmbH +* 2022 René Meusel, Hannes Rantzsch - neXenio GmbH +* 2023 Mateusz Berezecki +* 2023 Fabian Albert, René Meusel - Rohde & Schwarz Cybersecurity +* 2026 René Meusel - Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include +#include + +namespace Botan::TLS { + +Cookie::Cookie(const std::vector& cookie) : m_cookie(cookie) {} + +Cookie::Cookie(TLS_Data_Reader& reader, uint16_t extension_size) { + // RFC 8446 4.2.2 + // struct { + // opaque cookie<1..2^16-1>; + // } Cookie; + // + // The wire form requires a 2-byte length field plus at least one byte of + // cookie data, so the minimum extension size is 3 bytes. + if(extension_size < 3) { + throw Decoding_Error("Empty cookie extension is illegal"); + } + + const uint16_t len = reader.get_uint16_t(); + + if(static_cast(len) + 2 != extension_size) { + throw Decoding_Error("Inconsistent length in cookie extension"); + } + + m_cookie = reader.get_fixed(len); +} + +std::vector Cookie::serialize(Connection_Side /*whoami*/) const { + std::vector buf; + append_tls_length_value(buf, m_cookie, 2); + return buf; +} + +std::vector PSK_Key_Exchange_Modes::serialize(Connection_Side /*whoami*/) const { + std::vector buf; + + BOTAN_ASSERT_NOMSG(m_modes.size() < 256); + buf.push_back(static_cast(m_modes.size())); + for(const auto& mode : m_modes) { + buf.push_back(static_cast(mode)); + } + + return buf; +} + +PSK_Key_Exchange_Modes::PSK_Key_Exchange_Modes(TLS_Data_Reader& reader, uint16_t extension_size) { + // RFC 8446 4.2.9 + // struct { + // PskKeyExchangeMode ke_modes<1..255>; + // } PskKeyExchangeModes; + // + // The wire form is a 1-byte length followed by mode_count mode bytes, + // with mode_count in [1, 255], so the extension size is in [2, 256]. + if(extension_size < 2) { + throw Decoding_Error("Empty psk_key_exchange_modes extension is illegal"); + } + + const auto mode_count = reader.get_byte(); + if(static_cast(mode_count) + 1 != extension_size) { + throw Decoding_Error("Inconsistent length in psk_key_exchange_modes extension"); + } + + for(uint16_t i = 0; i < mode_count; ++i) { + const auto mode = static_cast(reader.get_byte()); + if(mode == PSK_Key_Exchange_Mode::PSK_KE || mode == PSK_Key_Exchange_Mode::PSK_DHE_KE) { + m_modes.push_back(mode); + } + } +} + +std::vector Certificate_Authorities::serialize(Connection_Side /*whoami*/) const { + std::vector out; + std::vector dn_list; + + for(const auto& dn : m_distinguished_names) { + std::vector encoded_dn; + auto encoder = DER_Encoder(encoded_dn); + dn.encode_into(encoder); + append_tls_length_value(dn_list, encoded_dn, 2); + } + + append_tls_length_value(out, dn_list, 2); + + return out; +} + +Certificate_Authorities::Certificate_Authorities(TLS_Data_Reader& reader, uint16_t extension_size) { + if(extension_size < 2) { + throw Decoding_Error("Empty certificate_authorities extension is illegal"); + } + + const uint16_t purported_size = reader.get_uint16_t(); + + if(reader.remaining_bytes() != purported_size) { + throw Decoding_Error("Inconsistent length in certificate_authorities extension"); + } + + // RFC 8446 4.2.4: DistinguishedName authorities<3..2^16-1>; + if(purported_size < 3) { + throw Decoding_Error("Empty certificate_authorities list is illegal"); + } + + while(reader.has_remaining()) { + // RFC 8446 4.2.4: opaque DistinguishedName<1..2^16-1> + const std::vector name_bits = reader.get_range(2, 1, 65535); + + BER_Decoder decoder(name_bits, BER_Decoder::Limits::DER()); + m_distinguished_names.emplace_back(); + decoder.decode(m_distinguished_names.back()).verify_end(); + } +} + +Certificate_Authorities::Certificate_Authorities(std::vector acceptable_DNs) : + m_distinguished_names(std::move(acceptable_DNs)) {} + +std::vector EarlyDataIndication::serialize(Connection_Side /*whoami*/) const { + std::vector result; + if(m_max_early_data_size.has_value()) { + const auto max_data = m_max_early_data_size.value(); + result.push_back(get_byte<0>(max_data)); + result.push_back(get_byte<1>(max_data)); + result.push_back(get_byte<2>(max_data)); + result.push_back(get_byte<3>(max_data)); + } + return result; +} + +EarlyDataIndication::EarlyDataIndication(TLS_Data_Reader& reader, + uint16_t extension_size, + Handshake_Type message_type) { + if(message_type == Handshake_Type::NewSessionTicket) { + if(extension_size != 4) { + throw TLS_Exception(Alert::DecodeError, + "Received an early_data extension in a NewSessionTicket message " + "without maximum early data size indication"); + } + + m_max_early_data_size = reader.get_uint32_t(); + } else if(extension_size != 0) { + throw TLS_Exception(Alert::DecodeError, + "Received an early_data extension containing an unexpected data " + "size indication"); + } +} + +bool EarlyDataIndication::empty() const { + // This extension may be empty by definition but still carry information + return false; +} + +} // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_extensions_13.h botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_extensions_13.h --- botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_extensions_13.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_extensions_13.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,334 @@ +/* +* TLS 1.3 Specific Extensions +* (C) 2011,2012,2016,2018,2019 Jack Lloyd +* (C) 2016 Juraj Somorovsky +* (C) 2016 Matthias Gierlings +* (C) 2021 Elektrobit Automotive GmbH +* (C) 2022 René Meusel, Hannes Rantzsch - neXenio GmbH +* (C) 2023 Fabian Albert, René Meusel - Rohde & Schwarz Cybersecurity +* (C) 2026 René Meusel - Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_TLS_EXTENSIONS_13_H_ +#define BOTAN_TLS_EXTENSIONS_13_H_ + +#include +#include +#include +#include + +namespace Botan { + +class RandomNumberGenerator; +class Credentials_Manager; + +namespace TLS { + +class Callbacks; +class Cipher_State; +class Ciphersuite; +class Policy; +class Session_Manager; +class TLS_Data_Reader; +class Transcript_Hash_State; + +enum class PSK_Key_Exchange_Mode : uint8_t { PSK_KE = 0, PSK_DHE_KE = 1 }; + +/** +* Cookie from RFC 8446 4.2.2 +*/ +class BOTAN_UNSTABLE_API Cookie final : public Extension { + public: + static Extension_Code static_type() { return Extension_Code::Cookie; } + + Extension_Code type() const override { return static_type(); } + + std::vector serialize(Connection_Side whoami) const override; + + bool empty() const override { return m_cookie.empty(); } + + const std::vector& get_cookie() const { return m_cookie; } + + explicit Cookie(const std::vector& cookie); + + explicit Cookie(TLS_Data_Reader& reader, uint16_t extension_size); + + private: + std::vector m_cookie; +}; + +/** +* Pre-Shared Key Exchange Modes from RFC 8446 4.2.9 +*/ +class BOTAN_UNSTABLE_API PSK_Key_Exchange_Modes final : public Extension { + public: + static Extension_Code static_type() { return Extension_Code::PskKeyExchangeModes; } + + Extension_Code type() const override { return static_type(); } + + std::vector serialize(Connection_Side whoami) const override; + + bool empty() const override { return m_modes.empty(); } + + const std::vector& modes() const { return m_modes; } + + explicit PSK_Key_Exchange_Modes(std::vector modes) : m_modes(std::move(modes)) {} + + explicit PSK_Key_Exchange_Modes(TLS_Data_Reader& reader, uint16_t extension_size); + + private: + std::vector m_modes; +}; + +/** + * Certificate Authorities Extension from RFC 8446 4.2.4 + */ +class BOTAN_UNSTABLE_API Certificate_Authorities final : public Extension { + public: + static Extension_Code static_type() { return Extension_Code::CertificateAuthorities; } + + Extension_Code type() const override { return static_type(); } + + std::vector serialize(Connection_Side whoami) const override; + + bool empty() const override { return m_distinguished_names.empty(); } + + const std::vector& distinguished_names() const { return m_distinguished_names; } + + Certificate_Authorities(TLS_Data_Reader& reader, uint16_t extension_size); + explicit Certificate_Authorities(std::vector acceptable_DNs); + + private: + std::vector m_distinguished_names; +}; + +/** + * Pre-Shared Key extension from RFC 8446 4.2.11 + */ +class BOTAN_UNSTABLE_API PSK final : public Extension /* NOLINT(*-special-member-functions) */ { + public: + static Extension_Code static_type() { return Extension_Code::PresharedKey; } + + Extension_Code type() const override { return static_type(); } + + std::vector serialize(Connection_Side side) const override; + + /** + * Returns the PSK identity (in case of an externally provided PSK) and + * the cipher state representing the PSK selected by the server. Note that + * this destructs the list of offered PSKs and its cipher states and must + * therefore not be called more than once. + * + * @note Technically, PSKs used for resumption also carry an identity. + * Though, typically, this is an opaque value meaningful only to the + * peer and of no authoritative value for the user. We therefore + * report the identity of externally provided PSKs only. + */ + std::pair, std::unique_ptr> take_selected_psk_info( + const PSK& server_psk, const Ciphersuite& cipher); + + /** + * Selects one of the offered PSKs that is compatible with \p cipher. + * @retval PSK extension object that can be added to the Server Hello response + * @retval std::nullptr if no PSK offered by the client is convenient + */ + std::unique_ptr select_offered_psk(std::string_view host, + const Ciphersuite& cipher, + Session_Manager& session_mgr, + Credentials_Manager& credentials_mgr, + Callbacks& callbacks, + const Policy& policy); + + /** + * Remove PSK identities from the list in \p m_psk that are not compatible + * with the passed in \p cipher suite. + * This is useful to react to Hello Retry Requests. See RFC 8446 4.1.4. + */ + void filter(const Ciphersuite& cipher); + + /** + * Pulls the preshared key or the Session to resume from a PSK extension + * in Server Hello. + */ + std::variant take_session_to_resume_or_psk(); + + bool empty() const override; + + PSK(TLS_Data_Reader& reader, uint16_t extension_size, Handshake_Type message_type); + + /** + * Creates a PSK extension with a TLS 1.3 session object containing a + * master_secret. Note that it will extract that secret from the session, + * and won't create a copy of it. + * + * @param session_to_resume the session to be resumed; note that the + * master secret will be taken away from the + * session object. + * @param psks a list of non-resumption PSKs that should be + * offered to the server + * @param callbacks the application's callbacks + */ + PSK(std::optional& session_to_resume, std::vector psks, Callbacks& callbacks); + + ~PSK() override; + + void calculate_binders(const Transcript_Hash_State& truncated_transcript_hash); + bool validate_binder(const PSK& server_psk, const std::vector& binder) const; + + // TODO: Implement pure PSK negotiation that is not used for session + // resumption. + + private: + /** + * Creates a PSK extension that specifies the server's selection of an + * offered client PSK. The @p session_to_resume is kept internally + * and used later for the initialization of the Cipher_State object. + * + * Note: This constructor is called internally in PSK::select_offered_psk(). + */ + PSK(Session session_to_resume, uint16_t psk_index); + + /** + * Creates a PSK extension that specifies the server's selection of an + * externally provided PSK offered by the client. The @p psk is kept + * internally and used later for the initialization of the Cipher_State object. + * + * Note: This constructor is called internally in PSK::select_offered_psk(). + */ + PSK(ExternalPSK psk, uint16_t psk_index); + + private: + class PSK_Internal; + std::unique_ptr m_impl; +}; + +/** +* Key_Share from RFC 8446 4.2.8 +*/ +class BOTAN_UNSTABLE_API Key_Share final : public Extension /* NOLINT(*-special-member-functions) */ { + public: + static Extension_Code static_type() { return Extension_Code::KeyShare; } + + Extension_Code type() const override { return static_type(); } + + std::vector serialize(Connection_Side whoami) const override; + + bool empty() const override; + + /** + * Creates a Key_Share extension meant for the Server Hello that + * performs a key encapsulation with the selected public key from + * the client. + * + * @note This will retain the shared secret in the Key_Share extension + * until it is retrieved via take_shared_secret(). + */ + static std::unique_ptr create_as_encapsulation(Group_Params selected_group, + const Key_Share& client_keyshare, + const Policy& policy, + Callbacks& cb, + RandomNumberGenerator& rng); + + /** + * Decapsulate the shared secret with the peer's key share. This method + * can be called on a ClientHello's Key_Share with a ServerHello's + * Key_Share. + * + * @note After the decapsulation the client's private key is destroyed. + * Multiple calls will result in an exception. + */ + secure_vector decapsulate(const Key_Share& server_keyshare, + const Policy& policy, + Callbacks& cb, + RandomNumberGenerator& rng); + + /** + * Update a ClientHello's Key_Share to comply with a HelloRetryRequest. + * + * This will create new Key_Share_Entries and should only be called on a ClientHello Key_Share with a HelloRetryRequest Key_Share. + */ + void retry_offer(const Key_Share& retry_request_keyshare, + const std::vector& supported_groups, + Callbacks& cb, + RandomNumberGenerator& rng); + + /** + * @return key exchange groups the peer offered key share entries for + */ + std::vector offered_groups() const; + + /** + * @return key exchange group that was selected by a Hello Retry Request + */ + Named_Group selected_group() const; + + /** + * @returns the shared secret that was obtained by constructing this + * Key_Share object with the peer's. + * + * @note the shared secret value is std:move'd out. Multiple calls will + * result in an exception. + */ + secure_vector take_shared_secret(); + + Key_Share(TLS_Data_Reader& reader, uint16_t extension_size, Handshake_Type message_type); + + // constructor used for ClientHello msg + Key_Share(const Policy& policy, Callbacks& cb, RandomNumberGenerator& rng); + + // constructor used for HelloRetryRequest msg + explicit Key_Share(Named_Group selected_group); + + // destructor implemented in .cpp to hide Key_Share_Impl + ~Key_Share() override; + + private: + // constructor used for ServerHello + // (called via create_as_encapsulation()) + Key_Share(Group_Params selected_group, + const Key_Share& client_keyshare, + const Policy& policy, + Callbacks& cb, + RandomNumberGenerator& rng); + + private: + class Key_Share_Impl; + std::unique_ptr m_impl; +}; + +/** + * Indicates usage or support of early data as described in RFC 8446 4.2.10. + */ +class BOTAN_UNSTABLE_API EarlyDataIndication final : public Extension { + public: + static Extension_Code static_type() { return Extension_Code::EarlyData; } + + Extension_Code type() const override { return static_type(); } + + std::vector serialize(Connection_Side whoami) const override; + + bool empty() const override; + + std::optional max_early_data_size() const { return m_max_early_data_size; } + + EarlyDataIndication(TLS_Data_Reader& reader, uint16_t extension_size, Handshake_Type message_type); + + /** + * The max_early_data_size is exclusively provided by servers when using + * this extension in the NewSessionTicket message! Otherwise it stays + * std::nullopt and results in an empty extension. (RFC 8446 4.2.10). + */ + explicit EarlyDataIndication(std::optional max_early_data_size = std::nullopt) : + m_max_early_data_size(max_early_data_size) {} + + private: + std::optional m_max_early_data_size; +}; + +} // namespace TLS + +} // namespace Botan + +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_extensions_key_share.cpp botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_extensions_key_share.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_extensions_key_share.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_extensions_key_share.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,47 +8,52 @@ * Botan is released under the Simplified BSD License (see license.txt) */ -#include +#include -#include +#include #include #include #include -#include +#include #include #include - -#include -#include +#include +#include #include -#if defined(BOTAN_HAS_X25519) - #include -#endif - -#if defined(BOTAN_HAS_X448) - #include -#endif - -#include -#include -#include - namespace Botan::TLS { namespace { +// RFC 8446 4.2.8.2: TLS 1.3 removes ec_point_formats negotiation and +// requires that ECDH key shares are uncompressed. +// +// This logic happens to also work for the existing PQ shares since they +// place the ECDH part of the key share first +void check_ecdh_uncompressed_format(Group_Params group, std::span bytes) { + const auto hybrid_ecc = group.pqc_hybrid_ecc(); + const bool has_ecdh = + group.is_ecdh_named_curve() || (hybrid_ecc.has_value() && Group_Params(hybrid_ecc.value()).is_ecdh_named_curve()); + if(!has_ecdh) { + return; + } + if(bytes.empty() || bytes[0] != 0x04) { + throw TLS_Exception(Alert::IllegalParameter, "TLS 1.3 ECDH key share must use uncompressed point format"); + } +} + class Key_Share_Entry { public: - Key_Share_Entry(TLS_Data_Reader& reader) { + explicit Key_Share_Entry(TLS_Data_Reader& reader) { // TODO check that the group actually exists before casting... m_group = static_cast(reader.get_uint16_t()); - m_key_exchange = reader.get_tls_length_value(2); + // RFC 8446 4.2.8: opaque key_exchange<1..2^16-1> + m_key_exchange = reader.get_range(2, 1, 65535); } // Create an empty Key_Share_Entry with the selected group // but don't pre-generate a keypair, yet. - Key_Share_Entry(const TLS::Group_Params group) : m_group(group) {} + explicit Key_Share_Entry(const TLS::Group_Params group) : m_group(group) {} Key_Share_Entry(const TLS::Group_Params group, Callbacks& cb, RandomNumberGenerator& rng) : m_group(group), m_private_key(cb.tls_kem_generate_key(group, rng)) { @@ -59,8 +64,8 @@ if(group.is_kem()) { m_key_exchange = m_private_key->public_key_bits(); } else if(group.is_ecdh_named_curve()) { - auto pkey = dynamic_cast(m_private_key.get()); - if(!pkey) { + auto* pkey = dynamic_cast(m_private_key.get()); + if(pkey == nullptr) { throw TLS_Exception(Alert::InternalError, "Application did not provide a ECDH_PublicKey"); } @@ -74,8 +79,8 @@ // ClientHello::prefers_compressed_ec_points() into account here. m_key_exchange = pkey->public_value(EC_Point_Format::Uncompressed); } else { - auto pkey = dynamic_cast(m_private_key.get()); - if(!pkey) { + auto* pkey = dynamic_cast(m_private_key.get()); + if(pkey == nullptr) { throw TLS_Exception(Alert::InternalError, "Application did not provide a key-agreement key"); } @@ -103,6 +108,7 @@ const Policy& policy, Callbacks& cb, RandomNumberGenerator& rng) { + check_ecdh_uncompressed_format(m_group, client_share.m_key_exchange); auto [encapsulated_shared_key, shared_key] = KEM_Encapsulation::destructure(cb.tls_kem_encapsulate(m_group, client_share.m_key_exchange, rng, policy)); m_key_exchange = std::move(encapsulated_shared_key); @@ -122,6 +128,7 @@ auto scope = scoped_cleanup([&] { m_private_key.reset(); }); BOTAN_ASSERT_NOMSG(m_group == received.m_group); BOTAN_STATE_CHECK(m_private_key != nullptr); + check_ecdh_uncompressed_format(m_group, received.m_key_exchange); return cb.tls_kem_decapsulate(m_group, *m_private_key, received.m_key_exchange, rng, policy); } @@ -135,7 +142,7 @@ class Key_Share_ServerHello { public: - Key_Share_ServerHello(TLS_Data_Reader& reader, uint16_t) : m_server_share(reader) {} + Key_Share_ServerHello(TLS_Data_Reader& reader, uint16_t /*len*/) : m_server_share(reader) {} Key_Share_ServerHello(Named_Group group, const Key_Share_ClientHello& client_keyshare, @@ -176,25 +183,22 @@ class Key_Share_ClientHello { public: Key_Share_ClientHello(TLS_Data_Reader& reader, uint16_t /* extension_size */) { - // This construction is a crutch to make working with the incoming - // TLS_Data_Reader bearable. Currently, this reader spans the entire - // Client_Hello message. Hence, if offset or length fields are skewed - // or maliciously fabricated, it is possible to read further than the - // bounds of the current extension. - // Note that this aplies to many locations in the code base. - // - // TODO: Overhaul the TLS_Data_Reader to allow for cheap "sub-readers" - // that enforce read bounds of sub-structures while parsing. + // The reader is per-extension (Extensions::deserialize binds it to + // exactly extension_size bytes). Enforce that the inner + // client_shares length matches what the outer extension has left, + // then let the entry loop consume everything; extn_reader's + // assert_done() at the deserialize call site catches any leftover. const auto client_key_share_length = reader.get_uint16_t(); - const auto read_bytes_so_far_begin = reader.read_so_far(); - auto remaining = [&] { - const auto read_so_far = reader.read_so_far() - read_bytes_so_far_begin; - BOTAN_STATE_CHECK(read_so_far <= client_key_share_length); - return client_key_share_length - read_so_far; - }; + if(reader.remaining_bytes() != client_key_share_length) { + throw TLS_Exception(Alert::DecodeError, "Inconsistent length in client KeyShare extension"); + } - while(reader.has_remaining() && remaining() > 0) { - if(remaining() < 4) { + std::unordered_set seen_groups; + while(reader.has_remaining()) { + // Each KeyShareEntry is at least 4 bytes (group + 2-byte length). + // Cleaner failure than the reader underflow we'd otherwise hit + // when the inner buffer ends mid-entry. + if(reader.remaining_bytes() < 4) { throw TLS_Exception(Alert::DecodeError, "Not enough data to read another KeyShareEntry"); } @@ -205,18 +209,12 @@ // group. [...] // Servers MAY check for violations of these rules and abort the // handshake with an "illegal_parameter" alert if one is violated. - if(std::find_if(m_client_shares.begin(), m_client_shares.end(), [&](const auto& entry) { - return entry.group() == new_entry.group(); - }) != m_client_shares.end()) { + if(!seen_groups.insert(new_entry.group().wire_code()).second) { throw TLS_Exception(Alert::IllegalParameter, "Received multiple key share entries for the same group"); } m_client_shares.emplace_back(std::move(new_entry)); } - - if((reader.read_so_far() - read_bytes_so_far_begin) != client_key_share_length) { - throw Decoding_Error("Read bytes are not equal client KeyShare length"); - } } Key_Share_ClientHello(const Policy& policy, Callbacks& cb, RandomNumberGenerator& rng) { @@ -362,7 +360,7 @@ m_selected_group = static_cast(reader.get_uint16_t()); } - Key_Share_HelloRetryRequest(Named_Group selected_group) : m_selected_group(selected_group) {} + explicit Key_Share_HelloRetryRequest(Named_Group selected_group) : m_selected_group(selected_group) {} ~Key_Share_HelloRetryRequest() = default; @@ -395,10 +393,9 @@ public: using Key_Share_Type = std::variant; - Key_Share_Impl(Key_Share_Type ks) : key_share(std::move(ks)) {} + explicit Key_Share_Impl(Key_Share_Type ks) : key_share(std::move(ks)) {} - // NOLINTNEXTLINE(*-non-private-member-variables-in-classes) - Key_Share_Type key_share; + Key_Share_Type key_share; // NOLINT(*-non-private-member-variable*) }; Key_Share::Key_Share(TLS_Data_Reader& reader, uint16_t extension_size, Handshake_Type message_type) { diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_extensions_psk.cpp botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_extensions_psk.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_extensions_psk.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_extensions_psk.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,22 +7,22 @@ * Botan is released under the Simplified BSD License (see license.txt) */ -#include +#include #include +#include #include #include +#include #include #include +#include #include #include #include - #include #include -#if defined(BOTAN_HAS_TLS_13) - namespace Botan::TLS { namespace { @@ -46,11 +46,11 @@ Cipher_State::PSK_Type::Resumption) {} // NOLINTNEXTLINE(*-rvalue-reference-param-not-moved) - Client_PSK(ExternalPSK&& psk) : + explicit Client_PSK(ExternalPSK&& psk) : Client_PSK(PskIdentity(PresharedKeyID(psk.identity())), psk.prf_algo(), psk.extract_master_secret(), - Cipher_State::PSK_Type::External) {} + psk.is_imported() ? Cipher_State::PSK_Type::Imported : Cipher_State::PSK_Type::External) {} Client_PSK(PskIdentity id, std::vector bndr) : m_identity(std::move(id)), m_binder(std::move(bndr)), m_is_resumption(false) {} @@ -106,7 +106,7 @@ class Server_PSK { public: - Server_PSK(uint16_t id) : m_selected_identity(id), m_session_to_resume_or_psk(std::monostate()) {} + explicit Server_PSK(uint16_t id) : m_selected_identity(id), m_session_to_resume_or_psk(std::monostate()) {} Server_PSK(uint16_t id, Session session) : m_selected_identity(id), m_session_to_resume_or_psk(std::move(session)) {} @@ -131,11 +131,11 @@ class PSK::PSK_Internal { public: - PSK_Internal(Server_PSK srv_psk) : psk(std::move(srv_psk)) {} + explicit PSK_Internal(Server_PSK srv_psk) : psk(std::move(srv_psk)) {} - PSK_Internal(std::vector clt_psks) : psk(std::move(clt_psks)) {} + explicit PSK_Internal(std::vector clt_psks) : psk(std::move(clt_psks)) {} - // NOLINTNEXTLINE(*-non-private-member-variables-in-classes) + // NOLINTNEXTLINE(*-non-private-member-variable*) std::variant, Server_PSK> psk; }; @@ -153,6 +153,17 @@ std::vector psk_identities; while(reader.has_remaining() && (reader.read_so_far() - identities_offset) < identities_length) { + /* Per RFC 8446 PskIdentity is + + struct { + opaque identity<1..2^16-1>; + uint32 obfuscated_ticket_age; + } PskIdentity; + + so we should reject an empty identity. However BoGo seems to expect + being able to send us such an identity, so for now we accept it. + */ + auto identity = reader.get_tls_length_value(2); const auto obfuscated_ticket_age = reader.get_uint32_t(); psk_identities.emplace_back(std::move(identity), obfuscated_ticket_age); @@ -179,6 +190,11 @@ throw TLS_Exception(Alert::IllegalParameter, "Not enough PSK binders"); } + // RFC 8446 4.2.11 declares PskBinderEntry opaque<32..255>, but we accept any + // 0..255 length here and let validate_binder reject, which yields a bad_record_mac + // alert rather than decode_error. BoringSSL behaves the same way and BoGo has + // tests that specifically expect this. + psks.emplace_back(std::move(psk_identity), reader.get_tls_length_value(1)); } @@ -206,10 +222,10 @@ m_impl = std::make_unique(std::move(cpsk)); } -PSK::PSK(Session session_to_resume, const uint16_t psk_index) : +PSK::PSK(Session session_to_resume, uint16_t psk_index) : m_impl(std::make_unique(Server_PSK(psk_index, std::move(session_to_resume)))) {} -PSK::PSK(ExternalPSK psk, const uint16_t psk_index) : +PSK::PSK(ExternalPSK psk, uint16_t psk_index) : m_impl(std::make_unique(Server_PSK(psk_index, std::move(psk)))) {} PSK::~PSK() = default; @@ -287,16 +303,22 @@ session_mgr.choose_from_offered_tickets(psk_identities, cipher.prf_algo(), callbacks, policy)) { auto& [session, psk_index] = selected_session.value(); - // RFC 8446 4.6.1 - // Any ticket MUST only be resumed with a cipher suite that has the - // same KDF hash algorithm as that used to establish the original - // connection. - if(session.ciphersuite().prf_algo() != cipher.prf_algo()) { - throw TLS_Exception(Alert::InternalError, - "Application chose a ticket that is not compatible with the negotiated ciphersuite"); - } + // Refuse to resume a ticket across SNI: a session minted for one + // virtual host must not be presentable against another. Treat as a + // cache miss and fall through to the external PSK path rather than + // failing the connection. + if(session.server_info().hostname() == host) { + // RFC 8446 4.6.1 + // Any ticket MUST only be resumed with a cipher suite that has the + // same KDF hash algorithm as that used to establish the original + // connection. + if(session.ciphersuite().prf_algo() != cipher.prf_algo()) { + throw TLS_Exception(Alert::InternalError, + "Application chose a ticket that is not compatible with the negotiated ciphersuite"); + } - return std::unique_ptr(new PSK(std::move(session), psk_index)); + return std::unique_ptr(new PSK(std::move(session), psk_index)); + } } // @@ -428,9 +450,8 @@ const auto& psks = std::get>(m_impl->psk); BOTAN_STATE_CHECK(index < psks.size()); - return psks[index].binder() == binder; + const auto& expected_binder = psks[index].binder(); + return CT::is_equal(binder, expected_binder).as_bool(); } } // namespace Botan::TLS - -#endif // HAS_TLS_13 diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_handshake_layer_13.cpp botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_handshake_layer_13.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_handshake_layer_13.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_handshake_layer_13.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -10,6 +10,9 @@ #include #include +#include +#include +#include #include #include #include @@ -17,6 +20,13 @@ namespace Botan::TLS { void Handshake_Layer::copy_data(std::span data_from_peer) { + // Compact consumed data before appending new data + BOTAN_ASSERT_NOMSG(m_read_offset <= m_read_buffer.size()); + if(m_read_offset > 0) { + m_read_buffer.erase(m_read_buffer.begin(), m_read_buffer.begin() + m_read_offset); + m_read_offset = 0; + } + m_read_buffer.insert(m_read_buffer.end(), data_from_peer.begin(), data_from_peer.end()); } @@ -54,6 +64,13 @@ } } +void verify_handshake_message_size(size_t msg_len, size_t max_size) { + if(max_size > 0 && msg_len > max_size) { + throw TLS_Exception(Alert::HandshakeFailure, + Botan::fmt("Handshake message is {} bytes, policy maximum is {}", msg_len, max_size)); + } +} + template std::optional parse_message(TLS::TLS_Data_Reader& reader, const Policy& policy, @@ -64,10 +81,14 @@ return std::nullopt; } - Handshake_Type type = handshake_type_from_byte(reader.get_byte()); + const Handshake_Type type = handshake_type_from_byte(reader.get_byte()); // make sure we have received the full message const size_t msg_len = reader.get_uint24_t(); + + // TODO(Botan4) this is split out due to a GCC 11 ICE, can be inlined + verify_handshake_message_size(msg_len, policy.maximum_handshake_message_size()); + if(reader.remaining_bytes() < msg_len) { return std::nullopt; } @@ -118,24 +139,39 @@ std::optional Handshake_Layer::next_message(const Policy& policy, Transcript_Hash_State& transcript_hash) { - TLS::TLS_Data_Reader reader("handshake message", m_read_buffer); + BOTAN_ASSERT_NOMSG(m_read_offset <= m_read_buffer.size()); + auto pending = std::span{m_read_buffer}.subspan(m_read_offset); + TLS::TLS_Data_Reader reader("handshake message", pending); auto msg = parse_message(reader, policy, m_peer, m_certificate_type); if(msg.has_value()) { - BOTAN_ASSERT_NOMSG(m_read_buffer.size() >= reader.read_so_far()); - transcript_hash.update(std::span{m_read_buffer.data(), reader.read_so_far()}); - m_read_buffer.erase(m_read_buffer.cbegin(), m_read_buffer.cbegin() + reader.read_so_far()); + transcript_hash.update(pending.first(reader.read_so_far())); + m_read_offset += reader.read_so_far(); + BOTAN_ASSERT_NOMSG(m_read_offset <= m_read_buffer.size()); + + if(m_read_offset == m_read_buffer.size()) { + m_read_buffer.clear(); + m_read_offset = 0; + } } return msg; } std::optional Handshake_Layer::next_post_handshake_message(const Policy& policy) { - TLS::TLS_Data_Reader reader("post handshake message", m_read_buffer); + BOTAN_ASSERT_NOMSG(m_read_offset <= m_read_buffer.size()); + auto pending = std::span{m_read_buffer}.subspan(m_read_offset); + TLS::TLS_Data_Reader reader("post handshake message", pending); auto msg = parse_message(reader, policy, m_peer, m_certificate_type); if(msg.has_value()) { - m_read_buffer.erase(m_read_buffer.cbegin(), m_read_buffer.cbegin() + reader.read_so_far()); + m_read_offset += reader.read_so_far(); + BOTAN_ASSERT_NOMSG(m_read_offset <= m_read_buffer.size()); + + if(m_read_offset == m_read_buffer.size()) { + m_read_buffer.clear(); + m_read_offset = 0; + } } return msg; diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_handshake_layer_13.h botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_handshake_layer_13.h --- botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_handshake_layer_13.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_handshake_layer_13.h 2026-05-07 01:38:28.000000000 +0000 @@ -13,7 +13,7 @@ #include #include -#include +#include namespace Botan::TLS { @@ -27,7 +27,7 @@ */ class BOTAN_TEST_API Handshake_Layer { public: - Handshake_Layer(Connection_Side whoami) : + explicit Handshake_Layer(Connection_Side whoami) : m_peer(whoami == Connection_Side::Server ? Connection_Side::Client : Connection_Side::Server) // RFC 8446 4.4.2 // If the corresponding certificate type extension @@ -68,7 +68,7 @@ std::optional next_post_handshake_message(const Policy& policy); /** - * Marshalls one handshake message for sending in an (encrypted) record and updates the + * Marshals one handshake message for sending in an (encrypted) record and updates the * provided transcript hash state accordingly. * * @param message the handshake message to be marshalled @@ -80,7 +80,7 @@ Transcript_Hash_State& transcript_hash); /** - * Marshalls one post-handshake message for sending in an (encrypted) record. + * Marshals one post-handshake message for sending in an (encrypted) record. * * @param message the post handshake message to be marshalled * @@ -92,7 +92,7 @@ * Check if the Handshake_Layer has stored a partial message in its internal buffer. * This can happen if a handshake message spans multiple records. */ - bool has_pending_data() const { return !m_read_buffer.empty(); } + bool has_pending_data() const { return m_read_offset < m_read_buffer.size(); } /** * Set the certificate_type used for parsing Certificate messages. This @@ -113,6 +113,7 @@ private: std::vector m_read_buffer; + size_t m_read_offset = 0; Connection_Side m_peer; Certificate_Type m_certificate_type; }; diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_handshake_state_13.cpp botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_handshake_state_13.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_handshake_state_13.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_handshake_state_13.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,9 +8,11 @@ #include +#include + namespace Botan::TLS::Internal { -Client_Hello_13& Handshake_State_13_Base::store(Client_Hello_13 client_hello, const bool) { +Client_Hello_13& Handshake_State_13_Base::store(Client_Hello_13 client_hello, const bool /*from_peer*/) { if(m_client_hello) { // Make sure that the updated Client Hello is compatible to the initial one. BOTAN_STATE_CHECK(has_hello_retry_request()); @@ -21,32 +23,34 @@ return m_client_hello.value(); } -Client_Hello_12& Handshake_State_13_Base::store(Client_Hello_12 client_hello, const bool) { +Client_Hello_12_Shim& Handshake_State_13_Base::store(Client_Hello_12_Shim client_hello, const bool /*from_peer*/) { m_client_hello_12 = std::move(client_hello); return m_client_hello_12.value(); } -Server_Hello_13& Handshake_State_13_Base::store(Server_Hello_13 server_hello, const bool) { +Server_Hello_13& Handshake_State_13_Base::store(Server_Hello_13 server_hello, const bool /*from_peer*/) { m_server_hello = std::move(server_hello); return m_server_hello.value(); } -Server_Hello_12& Handshake_State_13_Base::store(Server_Hello_12 server_hello, const bool) { +Server_Hello_12_Shim& Handshake_State_13_Base::store(Server_Hello_12_Shim server_hello, const bool /*from_peer*/) { m_server_hello_12 = std::move(server_hello); return m_server_hello_12.value(); } -Hello_Retry_Request& Handshake_State_13_Base::store(Hello_Retry_Request hello_retry_request, const bool) { +Hello_Retry_Request& Handshake_State_13_Base::store(Hello_Retry_Request hello_retry_request, const bool /*from_peer*/) { m_hello_retry_request = std::move(hello_retry_request); return m_hello_retry_request.value(); } -Encrypted_Extensions& Handshake_State_13_Base::store(Encrypted_Extensions encrypted_extensions, const bool) { +Encrypted_Extensions& Handshake_State_13_Base::store(Encrypted_Extensions encrypted_extensions, + const bool /*from_peer*/) { m_encrypted_extensions = std::move(encrypted_extensions); return m_encrypted_extensions.value(); } -Certificate_Request_13& Handshake_State_13_Base::store(Certificate_Request_13 certificate_request, const bool) { +Certificate_Request_13& Handshake_State_13_Base::store(Certificate_Request_13 certificate_request, + const bool /*from_peer*/) { m_certificate_request = std::move(certificate_request); return m_certificate_request.value(); } diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_handshake_state_13.h botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_handshake_state_13.h --- botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_handshake_state_13.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_handshake_state_13.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,15 +9,12 @@ #ifndef BOTAN_TLS_HANDSHAKE_STATE_13_H_ #define BOTAN_TLS_HANDSHAKE_STATE_13_H_ -#include -#include -#include -#include - #include #include -#include +#include #include +#include +#include namespace Botan::TLS { @@ -40,7 +37,15 @@ bool has_client_finished() const { return m_client_finished.has_value(); } - bool handshake_finished() const { return has_server_finished() && has_client_finished(); } + bool handshake_finished() const { + return has_server_finished() && has_client_finished() && m_peer_finished_verified; + } + + /** + * Once the implementation has successfully verified the peer's Finished + * message, the handshake is considered complete and successful. + */ + void confirm_peer_finished_verified() { m_peer_finished_verified = true; } // Client_Hello_13 cannot be const because it might need modification due to a Hello_Retry_Request Client_Hello_13& client_hello() { return get(m_client_hello); } @@ -68,12 +73,12 @@ const Finished_13& client_finished() const { return get(m_client_finished); } protected: - Handshake_State_13_Base(Connection_Side whoami) : m_side(whoami) {} + explicit Handshake_State_13_Base(Connection_Side whoami) : m_side(whoami) {} Client_Hello_13& store(Client_Hello_13 client_hello, bool from_peer); - Client_Hello_12& store(Client_Hello_12 client_hello, bool from_peer); + Client_Hello_12_Shim& store(Client_Hello_12_Shim client_hello, bool from_peer); Server_Hello_13& store(Server_Hello_13 server_hello, bool from_peer); - Server_Hello_12& store(Server_Hello_12 server_hello, bool from_peer); + Server_Hello_12_Shim& store(Server_Hello_12_Shim server_hello, bool from_peer); Hello_Retry_Request& store(Hello_Retry_Request hello_retry_request, bool from_peer); Encrypted_Extensions& store(Encrypted_Extensions encrypted_extensions, bool from_peer); Certificate_Request_13& store(Certificate_Request_13 certificate_request, bool from_peer); @@ -99,11 +104,12 @@ } Connection_Side m_side; + bool m_peer_finished_verified = false; std::optional m_client_hello; - std::optional m_client_hello_12; + std::optional m_client_hello_12; std::optional m_server_hello; - std::optional m_server_hello_12; + std::optional m_server_hello_12; std::optional m_hello_retry_request; std::optional m_encrypted_extensions; std::optional m_certificate_request; @@ -147,13 +153,15 @@ requires(is_generalizable_to(message)) { return std::visit( - [&](auto msg) -> as_wrapped_references_t> { return sending(std::move(msg)); }, + [&](auto msg) -> detail::as_wrapped_references_t> { + return sending(std::move(msg)); + }, std::move(message)); } decltype(auto) received(Handshake_Message_13 message) { return std::visit( - [&](auto msg) -> as_wrapped_references_t { + [&](auto msg) -> detail::as_wrapped_references_t { if constexpr(std::is_constructible_v) { return std::reference_wrapper(store(std::move(msg), true)); } else { diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_messages_13.h botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_messages_13.h --- botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_messages_13.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_messages_13.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,475 @@ +/* +* TLS Messages +* (C) 2021-2022 Jack Lloyd +* 2021 Elektrobit Automotive GmbH +* 2022 René Meusel, Hannes Rantzsch - neXenio GmbH +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_TLS_MESSAGES_13_H_ +#define BOTAN_TLS_MESSAGES_13_H_ + +#include +#include +#include +#include + +namespace Botan { + +enum class Usage_Type : uint8_t; +class X509_Certificate; + +} // namespace Botan + +namespace Botan::TLS { + +class Transcript_Hash_State; + +class BOTAN_UNSTABLE_API Client_Hello_13 final : public Client_Hello { + public: + /** + * Creates a client hello which might optionally use the passed-in + * @p session for resumption. In that case, this will "extract" the + * master secret from the passed-in @p session. + */ + Client_Hello_13(const Policy& policy, + Callbacks& cb, + RandomNumberGenerator& rng, + std::string_view hostname, + const std::vector& next_protocols, + std::optional& session, + std::vector psks); + + static std::variant parse(const std::vector& buf); + + void retry(const Hello_Retry_Request& hrr, + const Transcript_Hash_State& transcript_hash_state, + Callbacks& cb, + RandomNumberGenerator& rng); + + /** + * Select the highest protocol version from the list of versions + * supported by the client. If no such version can be determined this + * returns std::nullopt. + */ + std::optional highest_supported_version(const Policy& policy) const; + + /** + * This validates that a Client Hello received after sending a Hello + * Retry Request was updated in accordance with RFC 8446 4.1.2. If issues + * are found, this method throws accordingly. + */ + void validate_updates(const Client_Hello_13& new_ch); + + private: + explicit Client_Hello_13(std::unique_ptr data); + + /** + * If the Client Hello contains a PSK extensions with identities this will + * generate the PSK binders as described in RFC 8446 4.2.11.2. + * Note that the passed in \p transcript_hash_state might be virgin for + * the initial Client Hello and should be primed with ClientHello1 and + * HelloRetryRequest for an updated Client Hello. + */ + void calculate_psk_binders(Transcript_Hash_State transcript_hash_state); +}; + +class Hello_Retry_Request; + +class BOTAN_UNSTABLE_API Server_Hello_13 : public Server_Hello { + protected: + static const struct Server_Hello_Tag { + } as_server_hello; + + static const struct Hello_Retry_Request_Tag { + } as_hello_retry_request; + + static const struct Hello_Retry_Request_Creation_Tag { + } as_new_hello_retry_request; + + // These constructors are meant for instantiating Server Hellos + // after parsing a peer's message. They perform basic validation + // and are therefore not suitable for constructing a message to + // be sent to a client. + explicit Server_Hello_13(std::unique_ptr data, Server_Hello_Tag tag = as_server_hello); + explicit Server_Hello_13(std::unique_ptr data, Hello_Retry_Request_Tag tag); + void basic_validation() const; + + // Instantiate a Server Hello as response to a client's Client Hello + // (called from Server_Hello_13::create()) + Server_Hello_13(const Client_Hello_13& ch, + std::optional key_exchange_group, + Session_Manager& session_mgr, + Credentials_Manager& credentials_mgr, + RandomNumberGenerator& rng, + Callbacks& cb, + const Policy& policy); + + explicit Server_Hello_13(std::unique_ptr data, Hello_Retry_Request_Creation_Tag tag); + + public: + static std::variant create(const Client_Hello_13& ch, + bool hello_retry_request_allowed, + Session_Manager& session_mgr, + Credentials_Manager& credentials_mgr, + RandomNumberGenerator& rng, + const Policy& policy, + Callbacks& cb); + + static std::variant parse( + const std::vector& buf); + + /** + * Return desired downgrade version indicated by hello random, if any. + */ + std::optional random_signals_downgrade() const; + + /** + * @returns the selected version as indicated by the supported_versions extension + */ + Protocol_Version selected_version() const final; +}; + +class BOTAN_UNSTABLE_API Hello_Retry_Request final : public Server_Hello_13 { + protected: + friend class Server_Hello_13; // to allow construction by Server_Hello_13::parse() and ::create() + explicit Hello_Retry_Request(std::unique_ptr data); + Hello_Retry_Request(const Client_Hello_13& ch, Named_Group selected_group, const Policy& policy, Callbacks& cb); + + public: + Handshake_Type type() const override { return Handshake_Type::HelloRetryRequest; } + + Handshake_Type wire_type() const override { return Handshake_Type::ServerHello; } +}; + +class BOTAN_UNSTABLE_API Encrypted_Extensions final : public Handshake_Message { + public: + explicit Encrypted_Extensions(const std::vector& buf); + Encrypted_Extensions(const Client_Hello_13& client_hello, + const Policy& policy, + Callbacks& cb, + bool is_resumption, + bool requesting_client_auth); + + Handshake_Type type() const override { return Handshake_Type::EncryptedExtensions; } + + const Extensions& extensions() const { return m_extensions; } + + std::vector serialize() const override; + + private: + Extensions m_extensions; +}; + +class Certificate_Request_13; + +/** +* Certificate Message of TLS 1.3 +*/ +class BOTAN_UNSTABLE_API Certificate_13 final : public Handshake_Message { + public: + class Certificate_Entry { + public: + Certificate_Entry(TLS_Data_Reader& reader, Connection_Side side, Certificate_Type cert_type); + explicit Certificate_Entry(const X509_Certificate& cert); + explicit Certificate_Entry(std::shared_ptr raw_public_key); + + bool has_certificate() const { return m_certificate != nullptr; } + + const X509_Certificate& certificate() const; + std::shared_ptr public_key() const; + + std::vector serialize() const; + + Extensions& extensions() { return m_extensions; } + + const Extensions& extensions() const { return m_extensions; } + + Certificate_Entry(const Certificate_Entry& other) = delete; + Certificate_Entry& operator=(const Certificate_Entry& other) = delete; + + Certificate_Entry(Certificate_Entry&& other) noexcept; + Certificate_Entry& operator=(Certificate_Entry&& other) noexcept; + + ~Certificate_Entry(); + + private: + std::unique_ptr m_certificate; // possibly null if raw public key in use + std::shared_ptr m_raw_public_key; + Extensions m_extensions; + }; + + public: + Handshake_Type type() const override { return Handshake_Type::Certificate; } + + std::vector cert_chain() const; + + bool has_certificate_chain() const; + bool is_raw_public_key() const; + + size_t count() const { return m_entries.size(); } + + bool empty() const { return m_entries.empty(); } + + std::shared_ptr public_key() const; + const X509_Certificate& leaf() const; + + const std::vector& request_context() const { return m_request_context; } + + /** + * Create a Client Certificate message + * ... in response to a Certificate Request message. + */ + Certificate_13(const Certificate_Request_13& cert_request, + std::string_view hostname, + Credentials_Manager& credentials_manager, + Callbacks& callbacks, + Certificate_Type cert_type); + + /** + * Create a Server Certificate message + * ... in response to a Client Hello indicating the need to authenticate + * with a server certificate. + */ + Certificate_13(const Client_Hello_13& client_hello, + Credentials_Manager& credentials_manager, + Callbacks& callbacks, + Certificate_Type cert_type); + + /** + * Deserialize a Certificate message + * @param buf the serialized message + * @param policy the TLS policy + * @param side is this a Connection_Side::Server or Connection_Side::Client certificate message + * @param cert_type is the certificate type that was negotiated during the handshake + */ + Certificate_13(const std::vector& buf, + const Policy& policy, + Connection_Side side, + Certificate_Type cert_type); + + /** + * Validate a Certificate message regarding what extensions are expected based on + * previous handshake messages. Also call the tls_examine_extensions() callback + * for each entry. + * + * @param requested_extensions Extensions of Client_Hello or Certificate_Request messages + * @param cb Callback that will be called for each extension. + */ + void validate_extensions(const std::set& requested_extensions, Callbacks& cb) const; + + /** + * Verify the certificate chain + * + * @throws if verification fails. + */ + void verify(Callbacks& callbacks, + const Policy& policy, + Credentials_Manager& creds, + std::string_view hostname, + bool use_ocsp) const; + + std::vector serialize() const override; + + private: + void setup_entries(std::vector cert_chain, + const Certificate_Status_Request* csr, + Callbacks& callbacks); + void setup_entry(std::shared_ptr raw_public_key, Callbacks& callbacks); + + void verify_certificate_chain(Callbacks& callbacks, + const Policy& policy, + Credentials_Manager& creds, + std::string_view hostname, + bool use_ocsp, + Usage_Type usage_type) const; + + private: + std::vector m_request_context; + std::vector m_entries; + Connection_Side m_side; +}; + +class BOTAN_UNSTABLE_API Certificate_Request_13 final : public Handshake_Message { + public: + Handshake_Type type() const override; + + Certificate_Request_13(const std::vector& buf, Connection_Side side); + + //! Creates a Certificate_Request message if it is required by the configuration + //! @return std::nullopt if configuration does not require client authentication + static std::optional maybe_create(const Client_Hello_13& sni_hostname, + Credentials_Manager& cred_mgr, + Callbacks& callbacks, + const Policy& policy); + + std::vector acceptable_CAs() const; + const std::vector& signature_schemes() const; + const std::vector& certificate_signature_schemes() const; + + const Extensions& extensions() const { return m_extensions; } + + std::vector serialize() const override; + + const std::vector& context() const { return m_context; } + + private: + Certificate_Request_13(const std::vector& acceptable_CAs, const Policy& policy, Callbacks& callbacks); + + private: + std::vector m_context; + Extensions m_extensions; +}; + +/** +* Certificate Verify Message +*/ +class BOTAN_UNSTABLE_API Certificate_Verify_13 final : public Certificate_Verify { + public: + /** + * Deserialize a Certificate message + * @param buf the serialized message + * @param side is this a Connection_Side::Server or Connection_Side::Client certificate message + */ + Certificate_Verify_13(const std::vector& buf, Connection_Side side); + + Certificate_Verify_13(const Certificate_13& certificate_message, + const std::vector& peer_allowed_schemes, + std::string_view hostname, + const Transcript_Hash& hash, + Connection_Side whoami, + Credentials_Manager& creds_mgr, + const Policy& policy, + Callbacks& callbacks, + RandomNumberGenerator& rng); + + bool verify(const Public_Key& public_key, Callbacks& callbacks, const Transcript_Hash& transcript_hash) const; + + private: + Connection_Side m_side; +}; + +class BOTAN_UNSTABLE_API Finished_13 final : public Finished { + public: + using Finished::Finished; + Finished_13(Cipher_State* cipher_state, const Transcript_Hash& transcript_hash); + + bool verify(Cipher_State* cipher_state, const Transcript_Hash& transcript_hash) const; +}; + +class BOTAN_UNSTABLE_API New_Session_Ticket_13 final : public Handshake_Message { + public: + Handshake_Type type() const override { return Handshake_Type::NewSessionTicket; } + + New_Session_Ticket_13(Ticket_Nonce nonce, + const Session& session, + const Session_Handle& handle, + Callbacks& callbacks); + + New_Session_Ticket_13(const std::vector& buf, Connection_Side from); + + std::vector serialize() const override; + + const Extensions& extensions() const { return m_extensions; } + + const Opaque_Session_Handle& handle() const { return m_handle; } + + const Ticket_Nonce& nonce() const { return m_ticket_nonce; } + + uint32_t ticket_age_add() const { return m_ticket_age_add; } + + std::chrono::seconds lifetime_hint() const { return m_ticket_lifetime_hint; } + + /** + * @return the number of bytes allowed for early data or std::nullopt + * when early data is not allowed at all + */ + std::optional early_data_byte_limit() const; + + private: + // RFC 8446 4.6.1 + // Clients MUST NOT cache tickets for longer than 7 days, regardless of + // the ticket_lifetime, and MAY delete tickets earlier based on local + // policy. A server MAY treat a ticket as valid for a shorter period + // of time than what is stated in the ticket_lifetime. + // + // ... hence we call it 'lifetime hint'. + std::chrono::seconds m_ticket_lifetime_hint{}; + uint32_t m_ticket_age_add; + Ticket_Nonce m_ticket_nonce; + Opaque_Session_Handle m_handle; + Extensions m_extensions; +}; + +class BOTAN_UNSTABLE_API Key_Update final : public Handshake_Message { + public: + Handshake_Type type() const override { return Handshake_Type::KeyUpdate; } + + explicit Key_Update(bool request_peer_update); + explicit Key_Update(const std::vector& buf); + + std::vector serialize() const override; + + bool expects_reciprocation() const { return m_update_requested; } + + private: + bool m_update_requested; +}; + +namespace detail { +template +struct as_wrapped_references {}; + +template +struct as_wrapped_references> { + using type = std::variant...>; +}; + +template +using as_wrapped_references_t = typename as_wrapped_references::type; +} // namespace detail + +// Handshake message types from RFC 8446 4. +using Handshake_Message_13 = std::variant; +using Handshake_Message_13_Ref = detail::as_wrapped_references_t; + +using Post_Handshake_Message_13 = std::variant; + +// Key_Update is handled generically by the Channel. The messages assigned +// to those variants are the ones that need to be handled by the specific +// client and/or server implementations. +using Server_Post_Handshake_13_Message = std::variant; +using Client_Post_Handshake_13_Message = std::variant; + +using Server_Handshake_13_Message = std::variant; +using Server_Handshake_13_Message_Ref = detail::as_wrapped_references_t; + +using Client_Handshake_13_Message = + std::variant; +using Client_Handshake_13_Message_Ref = detail::as_wrapped_references_t; + +} // namespace Botan::TLS + +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_psk_13.h botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_psk_13.h --- botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_psk_13.h 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_psk_13.h 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,120 @@ +/** + * TLS 1.3 Preshared Key identity and importer + * (C) 2023 Jack Lloyd + * 2023 René Meusel - Rohde & Schwarz Cybersecurity + * 2023 Fabian Albert, René Meusel - Rohde & Schwarz Cybersecurity + * 2025,2026 Jack Lloyd + * + * Botan is released under the Simplified BSD License (see license.txt) + */ + +#ifndef BOTAN_TLS_PSK_13_H_ +#define BOTAN_TLS_PSK_13_H_ + +#include +#include +#include // TODO remove this dep +#include +#include +#include +#include +#include +#include +#include + +namespace Botan::TLS { + +/// @brief holds a PSK identity as used in TLS 1.3 +using PresharedKeyID = Strong; + +/** + * Represents a TLS 1.3 PSK identity as found in the Preshared Key extension + * with an opaque identity and an associated (obfuscated) ticket age. The latter + * is not applicable for externally provided PSKs. + */ +class BOTAN_PUBLIC_API(3, 1) PskIdentity { + public: + /** + * Construct from information provided in the peer's ClientHello + */ + PskIdentity(std::vector identity, const uint32_t obfuscated_age) : + m_identity(std::move(identity)), m_obfuscated_age(obfuscated_age) {} + + /** + * Construct from a session stored by the client + */ + PskIdentity(Opaque_Session_Handle identity, std::chrono::milliseconds age, uint32_t ticket_age_add); + + /** + * Construct from an externally provided PSK in the client + */ + BOTAN_FUTURE_EXPLICIT PskIdentity(PresharedKeyID identity); + + const std::vector& identity() const { return m_identity; } + + std::string identity_as_string() const; + + /** + * If this represents a PSK for session resumption, it returns the + * session's age given the de-obfuscation parameter @p ticket_age_add. For + * externally provided PSKs this method does not provide any meaningful + * information. + */ + std::chrono::milliseconds age(uint32_t ticket_age_add) const; + + uint32_t obfuscated_age() const { return m_obfuscated_age; } + + private: + std::vector m_identity; + uint32_t m_obfuscated_age; +}; + +/** + * Botan 3.0.0 used the class name "Ticket". In Botan 3.1.0 we decided to + * re-name it to the more generic term "PskIdentity" to better reflect its dual + * use case for resumption and externally provided PSKs. + */ +BOTAN_DEPRECATED("Use PskIdentity") typedef PskIdentity Ticket; + +/** + * RFC 9258 PSK Importer. + * + * Holds the base key material and identity for a pre-shared key and + * derives imported PSKs for specific TLS protocol versions and cipher + * suite hash algorithms using the PSK importer mechanism + */ +class BOTAN_PUBLIC_API(3, 12) PSKImporter { + public: + /** + * @param key the base pre-shared key + * @param identity the external PSK identity + * @param context optional importer context + * @param hash the hash algorithm provisioned with this PSK ("SHA-256" or "SHA-384") + * which defaults to SHA-256 due to RFC 9258's "If the EPSK does not have [...] + * an associated hash function, SHA-256 SHOULD be used." + */ + PSKImporter(std::span key, + std::span identity, + std::span context, + std::string_view hash = "SHA-256"); + + /** + * Derive an imported PSK for the given target protocol version and + * cipher suite hash algorithm. + * + * @param version target TLS protocol version (must be TLS 1.3) + * @param target_hash hash algorithm of the target cipher suite ("SHA-256" or "SHA-384") + * @return an ExternalPSK ready for use in a TLS 1.3 handshake + */ + ExternalPSK derive_imported_psk(Protocol_Version version, std::string_view target_hash) const; + + private: + secure_vector m_key; + std::vector m_identity; + std::vector m_context; + std::string m_hash; +}; + +} // namespace Botan::TLS + +#endif diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_psk_identity_13.cpp botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_psk_identity_13.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_psk_identity_13.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_psk_identity_13.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,9 +6,9 @@ * Botan is released under the Simplified BSD License (see license.txt) */ -#include +#include -#include +#include namespace Botan::TLS { @@ -23,6 +23,10 @@ return static_cast(in + ticket_age_add); } +inline std::vector to_byte_vector(std::string_view s) { + return std::vector(s.cbegin(), s.cend()); +} + } // namespace PskIdentity::PskIdentity(Opaque_Session_Handle identity, @@ -39,11 +43,12 @@ m_obfuscated_age(0) {} std::chrono::milliseconds PskIdentity::age(const uint32_t ticket_age_add) const { - return std::chrono::milliseconds(obfuscate_ticket_age(m_obfuscated_age, ticket_age_add)); + // De-obfuscate: subtract ticket_age_add (inverse of obfuscate_ticket_age) + return std::chrono::milliseconds(static_cast(m_obfuscated_age - ticket_age_add)); } std::string PskIdentity::identity_as_string() const { - return Botan::to_string(m_identity); + return bytes_to_string(m_identity); } } // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_psk_identity_13.h botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_psk_identity_13.h --- botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_psk_identity_13.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_psk_identity_13.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,68 +9,8 @@ #ifndef BOTAN_TLS_13_TICKET_H_ #define BOTAN_TLS_13_TICKET_H_ -#include -#include -#include +#include -#include -#include -#include - -namespace Botan::TLS { - -/// @brief holds a PSK identity as used in TLS 1.3 -using PresharedKeyID = Strong; - -/** - * Represents a TLS 1.3 PSK identity as found in the Preshared Key extension - * with an opaque identity and an associated (obfuscated) ticket age. The latter - * is not applicable for externally provided PSKs. - */ -class BOTAN_PUBLIC_API(3, 1) PskIdentity { - public: - /** - * Construct from information provided in the peer's ClientHello - */ - PskIdentity(std::vector identity, const uint32_t obfuscated_age) : - m_identity(std::move(identity)), m_obfuscated_age(obfuscated_age) {} - - /** - * Construct from a session stored by the client - */ - PskIdentity(Opaque_Session_Handle identity, std::chrono::milliseconds age, uint32_t ticket_age_add); - - /** - * Construct from an externally provided PSK in the client - */ - PskIdentity(PresharedKeyID identity); - - const std::vector& identity() const { return m_identity; } - - std::string identity_as_string() const; - - /** - * If this represents a PSK for session resumption, it returns the - * session's age given the de-obfuscation parameter @p ticket_age_add. For - * externally provided PSKs this method does not provide any meaningful - * information. - */ - std::chrono::milliseconds age(uint32_t ticket_age_add) const; - - uint32_t obfuscated_age() const { return m_obfuscated_age; } - - private: - std::vector m_identity; - uint32_t m_obfuscated_age; -}; - -/** - * Botan 3.0.0 used the class name "Ticket". In Botan 3.1.0 we decided to - * re-name it to the more generic term "PskIdentity" to better reflect its dual - * use case for resumption and externally provided PSKs. - */ -BOTAN_DEPRECATED("Use PskIdentity") typedef PskIdentity Ticket; - -} // namespace Botan::TLS +BOTAN_DEPRECATED_HEADER("tls_psk_identity_13.h") #endif diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_psk_importer_13.cpp botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_psk_importer_13.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_psk_importer_13.cpp 1970-01-01 00:00:00.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_psk_importer_13.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -0,0 +1,121 @@ +/* +* (C) 2026 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace Botan::TLS { + +PSKImporter::PSKImporter(std::span key, + std::span identity, + std::span context, + std::string_view hash) : + m_key(key.begin(), key.end()), + m_identity(identity.begin(), identity.end()), + m_context(context.begin(), context.end()), + m_hash(hash) { + BOTAN_ARG_CHECK(m_hash == "SHA-256" || m_hash == "SHA-384", "PSK importer hash must be SHA-256 or SHA-384"); + // RFC 9258 5.1: + // struct { + // opaque external_identity<1...2^16-1>; + // opaque context<0..2^16-1>; + // uint16 target_protocol; + // uint16 target_kdf; + // } ImportedIdentity; + + BOTAN_ARG_CHECK(!m_identity.empty(), "PSK importer identity must not be empty"); + + // The derived imported PSK identity (above) ends up as a TLS PSK identity + // (opaque<1..2^16-1>), so the whole assembled value must fit in. + BOTAN_ARG_CHECK(m_identity.size() + m_context.size() + 8 <= std::numeric_limits::max(), + "PSK importer identity + context too long for a TLS PSK identity"); +} + +ExternalPSK PSKImporter::derive_imported_psk(Protocol_Version version, std::string_view target_hash) const { + BOTAN_ARG_CHECK(version == Protocol_Version::TLS_V13, "PSK importer is only defined for TLS 1.3"); + BOTAN_ARG_CHECK(target_hash == "SHA-256" || target_hash == "SHA-384", + "PSK importer target hash must be SHA-256 or SHA-384"); + + // TODO(DTLS1.3): This duplicates Cipher_State::hkdf_expand_label + + const uint16_t target_protocol = version.version_code(); + const uint16_t target_kdf = (target_hash == "SHA-256") ? uint16_t(0x0001) : uint16_t(0x0002); + + // Build imported PSK identity (RFC 9258, Section 5.1): + // external_identity (length-prefixed) || context (length-prefixed) || + // target_protocol (2 bytes) || target_kdf (2 bytes) + const auto id_len = static_cast(m_identity.size()); + const auto ctx_len = static_cast(m_context.size()); + + const auto imported_identity = concat>( + store_be(id_len), m_identity, store_be(ctx_len), m_context, store_be(target_protocol), store_be(target_kdf)); + + // RFC 9258 5.1: "The hash function used for HKDF is that which is + // associated with the EPSK. It is not the hash function associated + // with ImportedIdentity.target_kdf." + auto hash_fn = HashFunction::create_or_throw(m_hash); + hash_fn->update(imported_identity); + const auto identity_hash = hash_fn->final_stdvec(); + + // HKDF-Extract(0, epsk) -- using the EPSK's hash per above + const size_t psk_hash_len = hash_fn->output_length(); + auto hkdf_extract = KDF::create_or_throw("HKDF-Extract(" + m_hash + ")"); + + const std::vector salt(psk_hash_len, 0); + const auto epskx = hkdf_extract->derive_key(psk_hash_len, m_key, salt, {}); + + // HKDF-Expand-Label(epskx, "derived psk", Hash(ImportedIdentity), L) + // + // Two distinct hashes are in play here and it is easy to conflate them: + // + // * The HKDF used for Extract and Expand is the one associated with the + // EPSK (m_hash). RFC 9258 5.1: "The hash function used for HKDF is + // that which is associated with the EPSK. It is not the hash function + // associated with ImportedIdentity.target_kdf." + // + // * The output length L, by contrast, is taken from the *target* KDF, + // not the EPSK's hash. RFC 9258 5.1: "L corresponds to the KDF + // output length of ImportedIdentity.target_kdf [...] For hash-based + // KDFs, such as HKDF_SHA256 (0x0001), this is the length of the + // hash function output, e.g., 32 octets for SHA256." + // + // So e.g. a SHA-256 EPSK imported for a SHA-384 target cipher suite runs + // HKDF-SHA-256 (driven by m_hash) and emits 48 bytes (driven by target_hash). + const std::string target_hash_str(target_hash); + auto target_hash_fn = HashFunction::create_or_throw(target_hash_str); + const size_t target_hash_len = target_hash_fn->output_length(); + const auto expand_out_len = static_cast(target_hash_len); + + auto hkdf_expand = KDF::create_or_throw("HKDF-Expand(" + m_hash + ")"); + // "tls13 derived psk" as bytes + const std::array prefixed_label = { + 't', 'l', 's', '1', '3', ' ', 'd', 'e', 'r', 'i', 'v', 'e', 'd', ' ', 'p', 's', 'k'}; + + // TLS 1.3 HkdfLabel: length (2) || label length (1) || label || context length (1) || context + + const auto prefixed_label_len = static_cast(prefixed_label.size()); + const auto identity_hash_len = static_cast(identity_hash.size()); + const auto hkdf_label = concat>(store_be(expand_out_len), + store_be(prefixed_label_len), + prefixed_label, + store_be(identity_hash_len), + identity_hash); + + secure_vector ipskx(target_hash_len); + hkdf_expand->derive_key(ipskx, epskx, hkdf_label, {}); + + const std::string wire_identity(imported_identity.begin(), imported_identity.end()); + return ExternalPSK(wire_identity, target_hash_str, std::move(ipskx), true); +} + +} // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_record_layer_13.cpp botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_record_layer_13.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_record_layer_13.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_record_layer_13.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -11,8 +11,10 @@ #include #include #include +#include +#include #include -#include +#include namespace Botan::TLS { @@ -51,10 +53,12 @@ class TLSPlaintext_Header final { public: TLSPlaintext_Header(std::vector hdr, const bool check_tls13_version) { + // NOLINTBEGIN(*-prefer-member-initializer) m_type = read_record_type(hdr[0]); m_legacy_version = Protocol_Version(make_uint16(hdr[1], hdr[2])); m_fragment_length = make_uint16(hdr[3], hdr[4]); m_serialized = std::move(hdr); + // NOLINTEND(*-prefer-member-initializer) // If no full version check is requested, we just verify the practically // ossified major version number. @@ -168,6 +172,13 @@ m_receiving_compat_mode(true) {} void Record_Layer::copy_data(std::span data) { + // Compact consumed data before appending new data + BOTAN_ASSERT_NOMSG(m_read_offset <= m_read_buffer.size()); + if(m_read_offset > 0) { + m_read_buffer.erase(m_read_buffer.begin(), m_read_buffer.begin() + m_read_offset); + m_read_offset = 0; + } + m_read_buffer.insert(m_read_buffer.end(), data.begin(), data.end()); } @@ -236,6 +247,7 @@ // even if the plaintext size is zero. This happens only for Application // Data types. BOTAN_ASSERT_NOMSG(to_process != 0 || protect); + // NOLINTNEXTLINE(*-avoid-do-while) do { const size_t pt_size = std::min(to_process, max_plaintext_size); const size_t ct_size = @@ -277,17 +289,19 @@ } Record_Layer::ReadResult Record_Layer::next_record(Cipher_State* cipher_state) { - if(m_read_buffer.size() < TLS_HEADER_SIZE) { - return TLS_HEADER_SIZE - m_read_buffer.size(); + const auto remaining = m_read_buffer.size() - m_read_offset; + + if(remaining < TLS_HEADER_SIZE) { + return TLS_HEADER_SIZE - remaining; } - const auto header_begin = m_read_buffer.cbegin(); + const auto header_begin = m_read_buffer.cbegin() + m_read_offset; const auto header_end = header_begin + TLS_HEADER_SIZE; // The first received record(s) are likely a client or server hello. To be able to // perform protocol downgrades we must be less vigorous with the record's // legacy version. Hence, `check_tls13_version` is `false` for the first record(s). - TLSPlaintext_Header plaintext_header({header_begin, header_end}, !m_receiving_compat_mode); + const TLSPlaintext_Header plaintext_header({header_begin, header_end}, !m_receiving_compat_mode); // After the key exchange phase of the handshake is completed and record protection is engaged, // cipher_state is set. At this point, only protected traffic (and CCS) is allowed. @@ -304,8 +318,8 @@ throw TLS_Exception(Alert::UnexpectedMessage, "unprotected record received where protected traffic was expected"); } - if(m_read_buffer.size() < TLS_HEADER_SIZE + plaintext_header.fragment_length()) { - return TLS_HEADER_SIZE + plaintext_header.fragment_length() - m_read_buffer.size(); + if(remaining < TLS_HEADER_SIZE + plaintext_header.fragment_length()) { + return TLS_HEADER_SIZE + plaintext_header.fragment_length() - remaining; } const auto fragment_begin = header_end; @@ -317,7 +331,14 @@ } Record record(plaintext_header.type(), secure_vector(fragment_begin, fragment_end)); - m_read_buffer.erase(header_begin, fragment_end); + m_read_offset += TLS_HEADER_SIZE + plaintext_header.fragment_length(); + + // If all buffered data has been consumed, release the buffer memory + // to avoid retaining peak allocation on idle connections. + if(m_read_offset == m_read_buffer.size()) { + zap(m_read_buffer); + m_read_offset = 0; + } if(record.type == Record_Type::ApplicationData) { if(cipher_state == nullptr) { @@ -336,11 +357,22 @@ record.seq_no = cipher_state->decrypt_record_fragment(plaintext_header.serialized(), record.fragment); - // Remove record padding (RFC 8446 5.4). - const auto end_of_content = - std::find_if(record.fragment.crbegin(), record.fragment.crend(), [](auto byte) { return byte != 0x00; }); + // Remove record padding (RFC 8446 5.4). The TLSInnerPlaintext layout is + // content || content_type || zero_padding + auto seen_nonzero = CT::Mask::cleared(); + uint8_t content_type_byte = 0; + size_t content_index = 0; + for(size_t i = record.fragment.size(); i-- > 0;) { + const uint8_t b = record.fragment[i]; + const auto byte_is_nonzero = CT::Mask::expand(b); + // Set on the first non-zero byte we encounter scanning right-to-left. + const auto first_nonzero = byte_is_nonzero & ~seen_nonzero; + content_type_byte = first_nonzero.select(b, content_type_byte); + content_index = CT::Mask::expand(first_nonzero.value()).select(i, content_index); + seen_nonzero |= byte_is_nonzero; + } - if(end_of_content == record.fragment.crend()) { + if(!seen_nonzero.as_bool()) { // RFC 8446 5.4 // If a receiving implementation does not // find a non-zero octet in the cleartext, it MUST terminate the @@ -349,7 +381,7 @@ } // hydrate the actual content type from TLSInnerPlaintext - record.type = read_record_type(*end_of_content); + record.type = read_record_type(content_type_byte); if(record.type == Record_Type::ChangeCipherSpec) { // RFC 8446 5 @@ -358,8 +390,20 @@ throw TLS_Exception(Alert::UnexpectedMessage, "protected change cipher spec received"); } - // erase content type and padding - record.fragment.erase((end_of_content + 1).base(), record.fragment.cend()); + // Truncate to drop the content_type byte and padding. resize() on a + // vector of trivially-destructible elements is bookkeeping-only and + // does not allocate or iterate over the dropped suffix. + record.fragment.resize(content_index); + + // RFC 8446 5.4 + // Implementations MUST NOT send Handshake and Alert records that have + // a zero-length TLSInnerPlaintext.content; if such a message is + // received, the receiving implementation MUST terminate the connection + // with an "unexpected_message" alert. + if(record.fragment.empty() && record.type != Record_Type::ApplicationData) { + throw TLS_Exception(Alert::UnexpectedMessage, + "Received a protected record with empty TLSInnerPlaintext content"); + } } return record; diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_record_layer_13.h botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_record_layer_13.h --- botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_record_layer_13.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_record_layer_13.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,15 +9,13 @@ #ifndef BOTAN_TLS_RECORD_LAYER_13_H_ #define BOTAN_TLS_RECORD_LAYER_13_H_ +#include +#include #include #include #include #include -#include -#include -#include - namespace Botan::TLS { /** @@ -25,9 +23,11 @@ * minus the record protocol specifics and ossified bytes. */ struct Record { - Record_Type type; - secure_vector fragment; - std::optional seq_no; // unprotected records have no sequence number + Record_Type type; // NOLINT(*non-private-member-variable*) + secure_vector fragment; // NOLINT(*non-private-member-variable*) + + // unprotected records have no sequence number + std::optional seq_no; // NOLINT(*non-private-member-variable*) Record(Record_Type record_type, secure_vector frgmnt) : type(record_type), fragment(std::move(frgmnt)), seq_no(std::nullopt) {} @@ -45,7 +45,7 @@ */ class BOTAN_TEST_API Record_Layer { public: - Record_Layer(Connection_Side side); + explicit Record_Layer(Connection_Side side); template using ReadResult = std::variant; @@ -79,7 +79,10 @@ * Clears any data currently stored in the read buffer. This is typically * used for memory cleanup when the peer sent a CloseNotify alert. */ - void clear_read_buffer() { zap(m_read_buffer); } + void clear_read_buffer() { + zap(m_read_buffer); + m_read_offset = 0; + } /** * Set the record size limits as negotiated by the "record_size_limit" @@ -102,6 +105,7 @@ private: std::vector m_read_buffer; + size_t m_read_offset = 0; Connection_Side m_side; // Those are either the limits set by the TLS 1.3 specification (RFC 8446), diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_server_impl_13.cpp botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_server_impl_13.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_server_impl_13.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_server_impl_13.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -10,6 +10,10 @@ #include #include +#include +#include +#include +#include #include #include #include @@ -21,54 +25,71 @@ const std::shared_ptr& credentials_manager, const std::shared_ptr& policy, const std::shared_ptr& rng) : - Channel_Impl_13(callbacks, session_manager, credentials_manager, rng, policy, true /* is_server */) { + Channel_Impl_13(callbacks, session_manager, credentials_manager, rng, policy, true /* is_server */), + m_handshake(std::make_unique()) { #if defined(BOTAN_HAS_TLS_12) if(policy->allow_tls12()) { expect_downgrade({}, {}); } #endif - m_transitions.set_expected_next(Handshake_Type::ClientHello); + m_handshake->transitions.set_expected_next(Handshake_Type::ClientHello); } std::string Server_Impl_13::application_protocol() const { - if(is_handshake_complete()) { - const auto& eee = m_handshake_state.encrypted_extensions().extensions(); - if(const auto alpn = eee.get()) { - return alpn->single_protocol(); - } + if(m_active_state.has_value()) { + return m_active_state->application_protocol(); } return ""; } std::vector Server_Impl_13::peer_cert_chain() const { - if(m_handshake_state.has_client_certificate_msg() && - m_handshake_state.client_certificate().has_certificate_chain()) { - return m_handshake_state.client_certificate().cert_chain(); + if(m_active_state.has_value()) { + return m_active_state->peer_certs(); } - if(m_resumed_session.has_value()) { - return m_resumed_session->peer_certs(); + if(m_handshake) { + if(m_handshake->state.has_client_certificate_msg() && + m_handshake->state.client_certificate().has_certificate_chain()) { + return m_handshake->state.client_certificate().cert_chain(); + } + + if(m_handshake->resumed_session.has_value()) { + return m_handshake->resumed_session->peer_certs(); + } } return {}; } std::shared_ptr Server_Impl_13::peer_raw_public_key() const { - if(m_handshake_state.has_client_certificate_msg() && m_handshake_state.client_certificate().is_raw_public_key()) { - return m_handshake_state.client_certificate().public_key(); + if(m_active_state.has_value()) { + return m_active_state->peer_raw_public_key(); } - if(m_resumed_session.has_value()) { - return m_resumed_session->peer_raw_public_key(); + if(m_handshake) { + if(m_handshake->state.has_client_certificate_msg() && + m_handshake->state.client_certificate().is_raw_public_key()) { + return m_handshake->state.client_certificate().public_key(); + } + + if(m_handshake->resumed_session.has_value()) { + return m_handshake->resumed_session->peer_raw_public_key(); + } } return nullptr; } std::optional Server_Impl_13::external_psk_identity() const { - return m_psk_identity; + if(m_active_state.has_value()) { + return m_active_state->psk_identity(); + } else if(m_handshake) { + return m_handshake->psk_identity; + } else { + return std::nullopt; + } } bool Server_Impl_13::new_session_ticket_supported() const { @@ -82,13 +103,12 @@ // regardless of this method indicating no support for tickets. // // TODO: Implement other PSK KE modes than PSK_DHE_KE - return is_handshake_complete() && m_handshake_state.client_hello().extensions().has() && - value_exists(m_handshake_state.client_hello().extensions().get()->modes(), - PSK_Key_Exchange_Mode::PSK_DHE_KE); + return is_handshake_complete() && m_active_state.has_value() && m_active_state->peer_supports_psk_dhe_ke(); } size_t Server_Impl_13::send_new_session_tickets(const size_t tickets) { BOTAN_STATE_CHECK(is_handshake_complete()); + BOTAN_STATE_CHECK(m_cipher_state != nullptr); if(tickets == 0) { return 0; @@ -97,17 +117,22 @@ auto flight = aggregate_post_handshake_messages(); size_t tickets_created = 0; + BOTAN_STATE_CHECK(m_active_state.has_value()); + for(size_t i = 0; i < tickets; ++i) { auto nonce = m_cipher_state->next_ticket_nonce(); + const uint32_t ticket_age_add = load_be(rng().random_array<4>()); const Session session(m_cipher_state->psk(nonce), std::nullopt, // early data not yet implemented + ticket_age_add, policy().session_ticket_lifetime(), + m_active_state->version(), + m_active_state->ciphersuite_code(), + Connection_Side::Server, peer_cert_chain(), peer_raw_public_key(), - m_handshake_state.client_hello(), - m_handshake_state.server_hello(), - callbacks(), - rng()); + Server_Information(m_active_state->sni_hostname()), + callbacks().tls_current_timestamp()); if(callbacks().tls_should_persist_resumption_information(session)) { if(auto handle = session_manager().establish(session)) { @@ -125,10 +150,12 @@ } void Server_Impl_13::process_handshake_msg(Handshake_Message_13 message) { + BOTAN_STATE_CHECK(m_handshake != nullptr); + std::visit( [&](auto msg) { // first verify that the message was expected by the state machine... - m_transitions.confirm_transition_to(msg.get().type()); + m_handshake->transitions.confirm_transition_to(msg.get().type()); // ... then allow the library user to abort on their discretion callbacks().tls_inspect_handshake_msg(msg.get()); @@ -136,13 +163,18 @@ // ... finally handle the message handle(msg.get()); }, - m_handshake_state.received(std::move(message))); + m_handshake->state.received(std::move(message))); } void Server_Impl_13::process_post_handshake_msg(Post_Handshake_Message_13 message) { BOTAN_STATE_CHECK(is_handshake_complete()); - std::visit([&](auto msg) { handle(msg); }, m_handshake_state.received(std::move(message))); + const auto msg = specialize_to(std::move(message)); + if(!msg) { + throw TLS_Exception(Alert::UnexpectedMessage, "Received an unexpected post-handshake message"); + } + + std::visit([&](auto&& m) { handle(m); }, *msg); } void Server_Impl_13::process_dummy_change_cipher_spec() { @@ -150,7 +182,7 @@ // If an implementation detects a change_cipher_spec record received before // the first ClientHello message or after the peer's Finished message, it MUST be // treated as an unexpected record type [("unexpected_message" alert)]. - if(!m_handshake_state.has_client_hello() || m_handshake_state.has_client_finished()) { + if(!m_handshake || !m_handshake->state.has_client_hello() || m_handshake->state.has_client_finished()) { throw TLS_Exception(Alert::UnexpectedMessage, "Received an unexpected dummy Change Cipher Spec"); } @@ -164,12 +196,16 @@ } bool Server_Impl_13::is_handshake_complete() const { - return m_handshake_state.handshake_finished(); + return m_active_state.has_value() || (m_handshake != nullptr && m_handshake->state.has_client_finished()); } void Server_Impl_13::maybe_log_secret(std::string_view label, std::span secret) const { if(policy().allow_ssl_key_log_file()) { - callbacks().tls_ssl_key_log_data(label, m_handshake_state.client_hello().random(), secret); + if(m_active_state.has_value()) { + callbacks().tls_ssl_key_log_data(label, m_active_state->client_random(), secret); + } else { + callbacks().tls_ssl_key_log_data(label, m_handshake->state.client_hello().random(), secret); + } } } @@ -180,12 +216,12 @@ // After this, no further messages are expected here because this instance // will be replaced by a Server_Impl_12. - m_transitions.set_expected_next({}); + m_handshake->transitions.set_expected_next({}); } void Server_Impl_13::maybe_handle_compatibility_mode() { - BOTAN_ASSERT_NOMSG(m_handshake_state.has_client_hello()); - BOTAN_ASSERT_NOMSG(m_handshake_state.has_hello_retry_request() || m_handshake_state.has_server_hello()); + BOTAN_ASSERT_NOMSG(m_handshake->state.has_client_hello()); + BOTAN_ASSERT_NOMSG(m_handshake->state.has_hello_retry_request() || m_handshake->state.has_server_hello()); // RFC 8446 Appendix D.4 (Middlebox Compatibility Mode) // The server sends a dummy change_cipher_spec record immediately after @@ -208,8 +244,8 @@ // after Hello Retry Request (exclusively) or after a Server Hello that was // not preseded by a Hello Retry Request. const bool just_after_first_handshake_message = - m_handshake_state.has_hello_retry_request() ^ m_handshake_state.has_server_hello(); - const bool client_requested_compatibility_mode = !m_handshake_state.client_hello().session_id().empty(); + m_handshake->state.has_hello_retry_request() ^ m_handshake->state.has_server_hello(); + const bool client_requested_compatibility_mode = !m_handshake->state.client_hello().session_id().empty(); if(just_after_first_handshake_message && (policy().tls_13_middlebox_compatibility_mode() || client_requested_compatibility_mode)) { @@ -218,7 +254,7 @@ } void Server_Impl_13::handle_reply_to_client_hello(Server_Hello_13 server_hello) { - const auto& client_hello = m_handshake_state.client_hello(); + const auto& client_hello = m_handshake->state.client_hello(); const auto& exts = client_hello.extensions(); const bool uses_psk = server_hello.extensions().has(); @@ -230,24 +266,24 @@ std::unique_ptr psk_cipher_state; if(uses_psk) { - auto psk_extension = server_hello.extensions().get(); + auto* psk_extension = server_hello.extensions().get(); - psk_cipher_state = - std::visit(overloaded{[&, this](Session session) { - m_resumed_session = std::move(session); - return Cipher_State::init_with_psk(Connection_Side::Server, - Cipher_State::PSK_Type::Resumption, - m_resumed_session->extract_master_secret(), - cipher.prf_algo()); - }, - [&, this](ExternalPSK psk) { - m_psk_identity = psk.identity(); - return Cipher_State::init_with_psk(Connection_Side::Server, - Cipher_State::PSK_Type::External, - psk.extract_master_secret(), - cipher.prf_algo()); - }}, - psk_extension->take_session_to_resume_or_psk()); + psk_cipher_state = std::visit( + overloaded{[&, this](Session session) { + m_handshake->resumed_session = std::move(session); + return Cipher_State::init_with_psk(Connection_Side::Server, + Cipher_State::PSK_Type::Resumption, + m_handshake->resumed_session->extract_master_secret(), + cipher.prf_algo()); + }, + [&, this](ExternalPSK psk) { + m_handshake->psk_identity = psk.identity(); + const auto psk_type = + psk.is_imported() ? Cipher_State::PSK_Type::Imported : Cipher_State::PSK_Type::External; + return Cipher_State::init_with_psk( + Connection_Side::Server, psk_type, psk.extract_master_secret(), cipher.prf_algo()); + }}, + psk_extension->take_session_to_resume_or_psk()); // RFC 8446 4.2.11 // Prior to accepting PSK key establishment, the server MUST validate @@ -282,13 +318,13 @@ // NOTE: the server_hello variable is moved into the handshake state. Later // references to the Server Hello will need to consult the handshake // state object! - send_handshake_message(m_handshake_state.sending(std::move(server_hello))); + send_handshake_message(m_handshake->state.sending(std::move(server_hello))); maybe_handle_compatibility_mode(); // Setup encryption for all the remaining handshake messages m_cipher_state = [&] { // Currently, PSK without DHE is not implemented... - const auto my_keyshare = m_handshake_state.server_hello().extensions().get(); + auto* const my_keyshare = m_handshake->state.server_hello().extensions().get(); BOTAN_ASSERT_NONNULL(my_keyshare); if(uses_psk) { @@ -304,20 +340,30 @@ } }(); + // Decide up front whether we will request client authentication so the + // EncryptedExtensions can attach client_certificate_type when applicable + // (RFC 7250 4.2 requires the two messages to agree). + auto certificate_request = + uses_psk ? std::nullopt + : Certificate_Request_13::maybe_create(client_hello, credentials_manager(), callbacks(), policy()); + auto flight = aggregate_handshake_messages(); - flight.add(m_handshake_state.sending(Encrypted_Extensions(client_hello, policy(), callbacks()))); + const bool is_resumption = m_handshake->resumed_session.has_value(); + const bool requesting_client_auth = certificate_request.has_value(); + + flight.add(m_handshake->state.sending( + Encrypted_Extensions(client_hello, policy(), callbacks(), is_resumption, requesting_client_auth))); if(!uses_psk) { // RFC 8446 4.3.2 // A server which is authenticating with a certificate MAY optionally // request a certificate from the client. This message, if sent, MUST // follow EncryptedExtensions. - if(auto certificate_request = - Certificate_Request_13::maybe_create(client_hello, credentials_manager(), callbacks(), policy())) { - flight.add(m_handshake_state.sending(std::move(certificate_request.value()))); + if(certificate_request.has_value()) { + flight.add(m_handshake->state.sending(std::move(certificate_request.value()))); } - const auto& enc_exts = m_handshake_state.encrypted_extensions().extensions(); + const auto& enc_exts = m_handshake->state.encrypted_extensions().extensions(); // RFC 7250 4.2 // This client_certificate_type extension in the server hello then @@ -326,7 +372,7 @@ // // Note: TLS 1.3 carries this extension in the Encrypted Extensions // message instead of the Server Hello. - if(auto client_cert_type = enc_exts.get()) { + if(auto* client_cert_type = enc_exts.get()) { set_selected_certificate_type(client_cert_type->selected_certificate_type()); } @@ -336,29 +382,30 @@ // was negotiated, then each CertificateEntry contains a DER-encoded // X.509 certificate. const auto cert_type = [&] { - if(auto server_cert_type = enc_exts.get()) { + if(auto* server_cert_type = enc_exts.get()) { return server_cert_type->selected_certificate_type(); } else { return Certificate_Type::X509; } }(); - flight.add(m_handshake_state.sending(Certificate_13(client_hello, credentials_manager(), callbacks(), cert_type))) - .add(m_handshake_state.sending(Certificate_Verify_13(m_handshake_state.server_certificate(), - client_hello.signature_schemes(), - client_hello.sni_hostname(), - m_transcript_hash.current(), - Connection_Side::Server, - credentials_manager(), - policy(), - callbacks(), - rng()))); + flight + .add(m_handshake->state.sending(Certificate_13(client_hello, credentials_manager(), callbacks(), cert_type))) + .add(m_handshake->state.sending(Certificate_Verify_13(m_handshake->state.server_certificate(), + client_hello.signature_schemes(), + client_hello.sni_hostname(), + m_transcript_hash.current(), + Connection_Side::Server, + credentials_manager(), + policy(), + callbacks(), + rng()))); } - flight.add(m_handshake_state.sending(Finished_13(m_cipher_state.get(), m_transcript_hash.current()))); + flight.add(m_handshake->state.sending(Finished_13(m_cipher_state.get(), m_transcript_hash.current()))); if(client_hello.extensions().has() && - m_handshake_state.encrypted_extensions().extensions().has()) { + m_handshake->state.encrypted_extensions().extensions().has()) { // RFC 8449 4. // When the "record_size_limit" extension is negotiated, an endpoint // MUST NOT generate a protected record with plaintext that is larger @@ -374,8 +421,8 @@ // // Hence, the "outgoing" limit is what the client requested and the // "incoming" limit is what we will request in the Encrypted Extensions. - const auto outgoing_limit = client_hello.extensions().get(); - const auto incoming_limit = m_handshake_state.encrypted_extensions().extensions().get(); + auto* const outgoing_limit = client_hello.extensions().get(); + auto* const incoming_limit = m_handshake->state.encrypted_extensions().extensions().get(); set_record_size_limits(outgoing_limit->limit(), incoming_limit->limit()); } @@ -383,16 +430,16 @@ m_cipher_state->advance_with_server_finished(m_transcript_hash.current(), *this); - if(m_handshake_state.has_certificate_request()) { + if(m_handshake->state.has_certificate_request()) { // RFC 8446 4.4.2 // The client MUST send a Certificate message if and only if the server // has requested client authentication via a CertificateRequest message // [...]. If the server requests client authentication but no // suitable certificate is available, the client MUST send a Certificate // message containing no certificates [...]. - m_transitions.set_expected_next(Handshake_Type::Certificate); + m_handshake->transitions.set_expected_next(Handshake_Type::Certificate); } else { - m_transitions.set_expected_next(Handshake_Type::Finished); + m_handshake->transitions.set_expected_next(Handshake_Type::Finished); } } @@ -400,21 +447,22 @@ auto cipher = Ciphersuite::by_id(hello_retry_request.ciphersuite()); BOTAN_ASSERT_NOMSG(cipher.has_value()); // should work, since we chose that suite - send_handshake_message(m_handshake_state.sending(std::move(hello_retry_request))); + send_handshake_message(m_handshake->state.sending(std::move(hello_retry_request))); maybe_handle_compatibility_mode(); m_transcript_hash = Transcript_Hash_State::recreate_after_hello_retry_request(cipher->prf_algo(), m_transcript_hash); - m_transitions.set_expected_next(Handshake_Type::ClientHello); + m_handshake->transitions.set_expected_next(Handshake_Type::ClientHello); } -void Server_Impl_13::handle(const Client_Hello_12& ch) { +void Server_Impl_13::handle(const Client_Hello_12_Shim& ch) { // The detailed handling of the TLS 1.2 compliant Client Hello is left to // the TLS 1.2 server implementation. BOTAN_UNUSED(ch); + BOTAN_ASSERT_NONNULL(m_handshake); // After we sent a Hello Retry Request we must not accept a downgrade. - if(m_handshake_state.has_hello_retry_request()) { + if(m_handshake->state.has_hello_retry_request()) { throw TLS_Exception(Alert::UnexpectedMessage, "Received a TLS 1.2 Client Hello after Hello Retry Request"); } @@ -432,9 +480,11 @@ } void Server_Impl_13::handle(const Client_Hello_13& client_hello) { + BOTAN_ASSERT_NONNULL(m_handshake); + const auto& exts = client_hello.extensions(); - const bool is_initial_client_hello = !m_handshake_state.has_hello_retry_request(); + const bool is_initial_client_hello = !m_handshake->state.has_hello_retry_request(); if(is_initial_client_hello) { const auto preferred_version = client_hello.highest_supported_version(policy()); @@ -464,9 +514,11 @@ BOTAN_ASSERT_NOMSG(exts.has()); if(!is_initial_client_hello) { - const auto& hrr_exts = m_handshake_state.hello_retry_request().extensions(); + const auto& hrr_exts = m_handshake->state.hello_retry_request().extensions(); const auto offered_groups = exts.get()->offered_groups(); - const auto selected_group = hrr_exts.get()->selected_group(); + const auto* hrr_key_share = hrr_exts.get(); + BOTAN_ASSERT_NONNULL(hrr_key_share); + const auto selected_group = hrr_key_share->selected_group(); if(offered_groups.size() != 1 || offered_groups.at(0) != selected_group) { throw TLS_Exception(Alert::IllegalParameter, "Client did not comply with the requested key exchange group"); } @@ -484,6 +536,8 @@ } void Server_Impl_13::handle(const Certificate_13& certificate_msg) { + BOTAN_ASSERT_NONNULL(m_handshake); + // RFC 8446 4.3.2 // certificate_request_context: [...] This field SHALL be zero length // unless used for the post-handshake authentication exchanges [...]. @@ -494,7 +548,7 @@ // RFC 8446 4.4.2 // Extensions in the Certificate message from the client MUST correspond // to extensions in the CertificateRequest message from the server. - certificate_msg.validate_extensions(m_handshake_state.certificate_request().extensions().extension_types(), + certificate_msg.validate_extensions(m_handshake->state.certificate_request().extensions().extension_types(), callbacks()); // RFC 8446 4.4.2.4 @@ -510,7 +564,7 @@ // RFC 8446 4.4.2 // A Finished message MUST be sent regardless of whether the // Certificate message is empty. - m_transitions.set_expected_next(Handshake_Type::Finished); + m_handshake->transitions.set_expected_next(Handshake_Type::Finished); } else { // RFC 8446 4.4.2.4 // [...], if some aspect of the certificate chain was unacceptable @@ -521,38 +575,39 @@ // TODO: We could make this dependent on Policy::require_client_auth(). // Though, apps may also override Callbacks::tls_verify_cert_chain() // and 'ignore' validation issues to a certain extent. - certificate_msg.verify(callbacks(), - policy(), - credentials_manager(), - m_handshake_state.client_hello().sni_hostname(), - m_handshake_state.client_hello().extensions().has()); + + const bool use_ocsp = m_handshake->state.certificate_request().extensions().has(); + certificate_msg.verify( + callbacks(), policy(), credentials_manager(), m_handshake->state.client_hello().sni_hostname(), use_ocsp); // RFC 8446 4.4.3 // Clients MUST send this message whenever authenticating via a // certificate (i.e., when the Certificate message // is non-empty). When sent, this message MUST appear immediately after // the Certificate message [...]. - m_transitions.set_expected_next(Handshake_Type::CertificateVerify); + m_handshake->transitions.set_expected_next(Handshake_Type::CertificateVerify); } } void Server_Impl_13::handle(const Certificate_Verify_13& certificate_verify_msg) { + BOTAN_ASSERT_NONNULL(m_handshake); + // RFC 8446 4.4.3 // If sent by a client, the signature algorithm used in the signature // MUST be one of those present in the supported_signature_algorithms // field of the "signature_algorithms" extension in the // CertificateRequest message. - const auto offered = m_handshake_state.certificate_request().signature_schemes(); + const auto offered = m_handshake->state.certificate_request().signature_schemes(); if(!value_exists(offered, certificate_verify_msg.signature_scheme())) { throw TLS_Exception(Alert::IllegalParameter, "We did not offer the usage of " + certificate_verify_msg.signature_scheme().to_string() + " as a signature scheme"); } - BOTAN_ASSERT_NOMSG(m_handshake_state.has_client_certificate_msg() && - !m_handshake_state.client_certificate().empty()); - bool sig_valid = certificate_verify_msg.verify( - *m_handshake_state.client_certificate().public_key(), callbacks(), m_transcript_hash.previous()); + BOTAN_ASSERT_NOMSG(m_handshake->state.has_client_certificate_msg() && + !m_handshake->state.client_certificate().empty()); + const bool sig_valid = certificate_verify_msg.verify( + *m_handshake->state.client_certificate().public_key(), callbacks(), m_transcript_hash.previous()); // RFC 8446 4.4.3 // If the verification fails, the receiver MUST terminate the handshake @@ -561,10 +616,12 @@ throw TLS_Exception(Alert::DecryptError, "Client certificate verification failed"); } - m_transitions.set_expected_next(Handshake_Type::Finished); + m_handshake->transitions.set_expected_next(Handshake_Type::Finished); } void Server_Impl_13::handle(const Finished_13& finished_msg) { + BOTAN_ASSERT_NONNULL(m_handshake); + // RFC 8446 4.4.4 // Recipients of Finished messages MUST verify that the contents are // correct and if incorrect MUST terminate the connection with a @@ -573,23 +630,64 @@ throw TLS_Exception(Alert::DecryptError, "Finished message didn't verify"); } + m_handshake->state.confirm_peer_finished_verified(); + // Give the application a chance for a final veto before fully // establishing the connection. callbacks().tls_session_established( - Session_Summary(m_handshake_state.server_hello(), + Session_Summary(m_handshake->state.server_hello(), Connection_Side::Server, peer_cert_chain(), peer_raw_public_key(), - m_psk_identity, - m_resumed_session.has_value(), - Server_Information(m_handshake_state.client_hello().sni_hostname()), + m_handshake->psk_identity, + m_handshake->resumed_session.has_value(), + Server_Information(m_handshake->state.client_hello().sni_hostname()), callbacks().tls_current_timestamp())); m_cipher_state->advance_with_client_finished(m_transcript_hash.current()); // no more handshake messages expected - m_transitions.set_expected_next({}); + m_handshake->transitions.set_expected_next({}); + + // Extract post-handshake state before signaling activation. + { + auto extract_certs = [&]() -> std::vector { + if(m_handshake->state.has_client_certificate_msg() && + m_handshake->state.client_certificate().has_certificate_chain()) { + return m_handshake->state.client_certificate().cert_chain(); + } + if(m_handshake->resumed_session.has_value()) { + return m_handshake->resumed_session->peer_certs(); + } + return {}; + }; + + auto extract_raw_pk = [&]() -> std::shared_ptr { + if(m_handshake->state.has_client_certificate_msg() && + m_handshake->state.client_certificate().is_raw_public_key()) { + return m_handshake->state.client_certificate().public_key(); + } + if(m_handshake->resumed_session.has_value()) { + return m_handshake->resumed_session->peer_raw_public_key(); + } + return nullptr; + }; + + const bool supports_psk_dhe = + m_handshake->state.client_hello().extensions().has() && + value_exists(m_handshake->state.client_hello().extensions().get()->modes(), + PSK_Key_Exchange_Mode::PSK_DHE_KE); + + m_active_state = Active_Connection_State_13(m_handshake->state, + extract_certs(), + extract_raw_pk(), + m_handshake->psk_identity, + m_handshake->state.client_hello().sni_hostname(), + supports_psk_dhe); + } + m_handshake.reset(); + m_transcript_hash = Transcript_Hash_State(); callbacks().tls_session_activated(); if(new_session_ticket_supported()) { diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_server_impl_13.h botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_server_impl_13.h --- botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_server_impl_13.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_server_impl_13.h 2026-05-07 01:38:28.000000000 +0000 @@ -19,7 +19,7 @@ /** * SSL/TLS Server 1.3 implementation */ -class Server_Impl_13 : public Channel_Impl_13 { +class Server_Impl_13 final : public Channel_Impl_13 { public: explicit Server_Impl_13(const std::shared_ptr& callbacks, const std::shared_ptr& session_manager, @@ -43,7 +43,7 @@ void process_dummy_change_cipher_spec() override; using Channel_Impl_13::handle; - void handle(const Client_Hello_12& client_hello_msg); + void handle(const Client_Hello_12_Shim& client_hello_msg); void handle(const Client_Hello_13& client_hello_msg); void handle(const Certificate_13& certificate_msg); void handle(const Certificate_Verify_13& certificate_verify_msg); @@ -58,11 +58,14 @@ void downgrade(); private: - Server_Handshake_State_13 m_handshake_state; - Handshake_Transitions m_transitions; + struct Pending_Handshake { + Server_Handshake_State_13 state; + Handshake_Transitions transitions; + std::optional resumed_session; + std::optional psk_identity; + }; - std::optional m_resumed_session; - std::optional m_psk_identity; + std::unique_ptr m_handshake; }; } // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_transcript_hash_13.cpp botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_transcript_hash_13.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_transcript_hash_13.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_transcript_hash_13.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -8,8 +8,9 @@ #include +#include #include -#include +#include #include #include @@ -20,6 +21,13 @@ set_algorithm(algo_spec); } +Transcript_Hash_State::Transcript_Hash_State() = default; + +Transcript_Hash_State::~Transcript_Hash_State() = default; + +Transcript_Hash_State::Transcript_Hash_State(Transcript_Hash_State&& other) noexcept = default; +Transcript_Hash_State& Transcript_Hash_State::operator=(Transcript_Hash_State&& other) noexcept = default; + Transcript_Hash_State::Transcript_Hash_State(const Transcript_Hash_State& other) : m_hash((other.m_hash != nullptr) ? other.m_hash->copy_state() : nullptr), m_unprocessed_transcript(other.m_unprocessed_transcript), @@ -34,12 +42,12 @@ BOTAN_STATE_CHECK(prev_transcript_hash_state.m_hash == nullptr); BOTAN_STATE_CHECK(prev_transcript_hash_state.m_unprocessed_transcript.size() == 2); - Transcript_Hash_State ths(algo_spec); + Transcript_Hash_State transcript_hash(algo_spec); const auto& client_hello_1 = prev_transcript_hash_state.m_unprocessed_transcript.front(); const auto& hello_retry_request = prev_transcript_hash_state.m_unprocessed_transcript.back(); - const size_t hash_length = ths.m_hash->output_length(); + const size_t hash_length = transcript_hash.m_hash->output_length(); BOTAN_ASSERT_NOMSG(hash_length < 256); // RFC 8446 4.4.1 @@ -52,12 +60,12 @@ message_hash.push_back(0x00); message_hash.push_back(0x00); message_hash.push_back(static_cast(hash_length)); - message_hash += ths.m_hash->process(client_hello_1); + message_hash += transcript_hash.m_hash->process(client_hello_1); - ths.update(message_hash); - ths.update(hello_retry_request); + transcript_hash.update(message_hash); + transcript_hash.update(hello_retry_request); - return ths; + return transcript_hash; } namespace { @@ -145,7 +153,7 @@ } // namespace void Transcript_Hash_State::update(std::span serialized_message_s) { - auto serialized_message = serialized_message_s.data(); + const auto* serialized_message = serialized_message_s.data(); auto serialized_message_length = serialized_message_s.size(); if(m_hash != nullptr) { auto truncation_mark = serialized_message_length; diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_transcript_hash_13.h botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_transcript_hash_13.h --- botan3-3.7.1+dfsg/src/lib/tls/tls13/tls_transcript_hash_13.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13/tls_transcript_hash_13.h 2026-05-07 01:38:28.000000000 +0000 @@ -9,14 +9,18 @@ #ifndef BOTAN_TLS_TRANSCRIPT_HASH_13_H_ #define BOTAN_TLS_TRANSCRIPT_HASH_13_H_ -#include #include - #include #include -#include +#include #include +namespace Botan { + +class HashFunction; + +} // namespace Botan + namespace Botan::TLS { /** @@ -27,9 +31,9 @@ */ class BOTAN_TEST_API Transcript_Hash_State { public: - Transcript_Hash_State() = default; - Transcript_Hash_State(std::string_view algo_spec); - ~Transcript_Hash_State() = default; + Transcript_Hash_State(); + explicit Transcript_Hash_State(std::string_view algo_spec); + ~Transcript_Hash_State(); /** * Recreates a Transcript_Hash_State after receiving a Hello Retry Request. @@ -45,8 +49,8 @@ Transcript_Hash_State& operator=(const Transcript_Hash_State&) = delete; - Transcript_Hash_State(Transcript_Hash_State&&) = default; - Transcript_Hash_State& operator=(Transcript_Hash_State&&) = default; + Transcript_Hash_State(Transcript_Hash_State&& other) noexcept; + Transcript_Hash_State& operator=(Transcript_Hash_State&& other) noexcept; void update(std::span serialized_message_s); @@ -79,6 +83,7 @@ Transcript_Hash_State clone() const; private: + // called by clone Transcript_Hash_State(const Transcript_Hash_State& other); private: diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13_pqc/hybrid_public_key.cpp botan3-3.12.0+dfsg/src/lib/tls/tls13_pqc/hybrid_public_key.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls13_pqc/hybrid_public_key.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13_pqc/hybrid_public_key.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -10,12 +10,16 @@ #include +#include #include - -#include +#include +#include +#include +#include #include -#include #include +#include +#include namespace Botan::TLS { @@ -40,6 +44,8 @@ return {{"ML-KEM", "ML-KEM-768"}, {"X25519", "X25519"}}; case Group_Params::HYBRID_SECP256R1_ML_KEM_768: return {{"ECDH", "secp256r1"}, {"ML-KEM", "ML-KEM-768"}}; + case Group_Params::HYBRID_SECP384R1_ML_KEM_1024: + return {{"ECDH", "secp384r1"}, {"ML-KEM", "ML-KEM-1024"}}; case Group_Params::HYBRID_X25519_eFRODOKEM_640_SHAKE_OQS: return {{"X25519", "X25519"}, {"FrodoKEM", "eFrodoKEM-640-SHAKE"}}; @@ -85,13 +91,17 @@ // TODO: This is inconvenient, confusing and error-prone. Find a better way // to load arbitrary public keys. for(const auto& spec : specs) { - result.push_back(AlgorithmIdentifier(spec.second, AlgorithmIdentifier::USE_EMPTY_PARAM)); + if(spec.first == "ECDH") { + result.push_back(AlgorithmIdentifier("ECDH", EC_Group::from_name(spec.second).DER_encode())); + } else { + result.push_back(AlgorithmIdentifier(spec.second, AlgorithmIdentifier::USE_EMPTY_PARAM)); + } } return result; } -std::vector public_value_lengths_for_group(Group_Params group) { +std::vector public_key_lengths_for_group(Group_Params group) { BOTAN_ASSERT_NOMSG(group.is_pqc_hybrid()); // This duplicates information of the algorithm internals. @@ -102,113 +112,171 @@ case Group_Params::HYBRID_X25519_ML_KEM_768: return {1184, 32}; case Group_Params::HYBRID_SECP256R1_ML_KEM_768: - return {32, 1184}; + return {65, 1184}; + case Group_Params::HYBRID_SECP384R1_ML_KEM_1024: + return {97, 1568}; case Group_Params::HYBRID_X25519_eFRODOKEM_640_SHAKE_OQS: - return {32, 9616}; case Group_Params::HYBRID_X25519_eFRODOKEM_640_AES_OQS: return {32, 9616}; + case Group_Params::HYBRID_X448_eFRODOKEM_976_SHAKE_OQS: - return {56, 15632}; case Group_Params::HYBRID_X448_eFRODOKEM_976_AES_OQS: return {56, 15632}; case Group_Params::HYBRID_SECP256R1_eFRODOKEM_640_SHAKE_OQS: - return {32, 9616}; case Group_Params::HYBRID_SECP256R1_eFRODOKEM_640_AES_OQS: - return {32, 9616}; + return {65, 9616}; case Group_Params::HYBRID_SECP384R1_eFRODOKEM_976_SHAKE_OQS: - return {48, 15632}; case Group_Params::HYBRID_SECP384R1_eFRODOKEM_976_AES_OQS: - return {48, 15632}; + return {97, 15632}; case Group_Params::HYBRID_SECP521R1_eFRODOKEM_1344_SHAKE_OQS: - return {66, 21520}; case Group_Params::HYBRID_SECP521R1_eFRODOKEM_1344_AES_OQS: - return {66, 21520}; + return {133, 21520}; default: return {}; } } +std::vector> convert_kex_to_kem_pks(std::vector> pks) { + std::vector> result; + std::transform(pks.begin(), pks.end(), std::back_inserter(result), [](auto& key) -> std::unique_ptr { + BOTAN_ARG_CHECK(key != nullptr, "Public key list contains a nullptr"); + if(key->supports_operation(PublicKeyOperation::KeyAgreement) && + !key->supports_operation(PublicKeyOperation::KeyEncapsulation)) { + return std::make_unique(std::move(key)); + } else { + return std::move(key); + } + }); + return result; +} + +std::vector> convert_kex_to_kem_sks(std::vector> sks) { + std::vector> result; + std::transform(sks.begin(), sks.end(), std::back_inserter(result), [](auto& key) -> std::unique_ptr { + BOTAN_ARG_CHECK(key != nullptr, "Private key list contains a nullptr"); + if(key->supports_operation(PublicKeyOperation::KeyAgreement) && + !key->supports_operation(PublicKeyOperation::KeyEncapsulation)) { + auto* ka_key = dynamic_cast(key.get()); + BOTAN_ASSERT_NONNULL(ka_key); + (void)key.release(); + return std::make_unique(std::unique_ptr(ka_key)); + } else { + return std::move(key); + } + }); + return result; +} + +template +void concat_secret_combiner(KEM_Operation& op, + std::span out_shared_secret, + const std::vector>& shared_secrets, + size_t desired_shared_key_len) { + BOTAN_ARG_CHECK(out_shared_secret.size() == op.shared_key_length(desired_shared_key_len), + "Invalid output buffer size"); + + BufferStuffer shared_secret_stuffer(out_shared_secret); + for(const auto& ss : shared_secrets) { + shared_secret_stuffer.append(ss); + } + BOTAN_ASSERT_NOMSG(shared_secret_stuffer.full()); +} + +template +size_t concat_shared_key_length(const std::vector& operation) { + return reduce( + operation, size_t(0), [](size_t acc, const auto& op) { return acc + op.shared_key_length(0 /*no KDF*/); }); +} + +/// Encryptor that simply concatenates the multiple shared secrets +class Hybrid_TLS_KEM_Encryptor final : public KEM_Encryption_with_Combiner { + public: + Hybrid_TLS_KEM_Encryptor(const std::vector>& public_keys, std::string_view provider) : + KEM_Encryption_with_Combiner(public_keys, provider) {} + + void combine_shared_secrets(std::span out_shared_secret, + const std::vector>& shared_secrets, + const std::vector>& /*ciphertexts*/, + size_t desired_shared_key_len, + std::span /*salt*/) override { + concat_secret_combiner(*this, out_shared_secret, shared_secrets, desired_shared_key_len); + } + + size_t shared_key_length(size_t /*desired_shared_key_len*/) const override { + return concat_shared_key_length(encryptors()); + } +}; + +/// Decryptor that simply concatenates the multiple shared secrets +class Hybrid_TLS_KEM_Decryptor final : public KEM_Decryption_with_Combiner { + public: + Hybrid_TLS_KEM_Decryptor(const std::vector>& private_keys, + RandomNumberGenerator& rng, + const std::string_view provider) : + KEM_Decryption_with_Combiner(private_keys, rng, provider) {} + + void combine_shared_secrets(std::span out_shared_secret, + const std::vector>& shared_secrets, + const std::vector>& /*ciphertexts*/, + size_t desired_shared_key_len, + std::span /*salt*/) override { + concat_secret_combiner(*this, out_shared_secret, shared_secrets, desired_shared_key_len); + } + + size_t shared_key_length(size_t /*desired_shared_key_len*/) const override { + return concat_shared_key_length(decryptors()); + } +}; + } // namespace std::unique_ptr Hybrid_KEM_PublicKey::load_for_group( - Group_Params group, std::span concatenated_public_values) { - const auto public_value_lengths = public_value_lengths_for_group(group); + Group_Params group, std::span concatenated_public_keys) { + const auto public_key_lengths = public_key_lengths_for_group(group); auto alg_ids = algorithm_identifiers_for_group(group); - BOTAN_ASSERT_NOMSG(public_value_lengths.size() == alg_ids.size()); + BOTAN_ASSERT_NOMSG(public_key_lengths.size() == alg_ids.size()); - const auto expected_public_values_length = - reduce(public_value_lengths, size_t(0), [](size_t acc, size_t len) { return acc + len; }); - if(expected_public_values_length != concatenated_public_values.size()) { + const auto expected_public_keys_length = + reduce(public_key_lengths, size_t(0), [](size_t acc, size_t len) { return acc + len; }); + if(expected_public_keys_length != concatenated_public_keys.size()) { throw Decoding_Error("Concatenated public values have an unexpected length"); } - BufferSlicer public_value_slicer(concatenated_public_values); + BufferSlicer public_key_slicer(concatenated_public_keys); std::vector> pks; pks.reserve(alg_ids.size()); for(size_t idx = 0; idx < alg_ids.size(); ++idx) { - pks.emplace_back(load_public_key(alg_ids[idx], public_value_slicer.take(public_value_lengths[idx]))); + pks.emplace_back(load_public_key(alg_ids[idx], public_key_slicer.take(public_key_lengths[idx]))); } - BOTAN_ASSERT_NOMSG(public_value_slicer.empty()); + BOTAN_ASSERT_NOMSG(public_key_slicer.empty()); return std::make_unique(std::move(pks)); } -Hybrid_KEM_PublicKey::Hybrid_KEM_PublicKey(std::vector> pks) { - BOTAN_ARG_CHECK(pks.size() >= 2, "List of public keys must include at least two keys"); - BOTAN_ARG_CHECK(std::all_of(pks.begin(), pks.end(), [](const auto& pk) { return pk != nullptr; }), - "List of public keys contains a nullptr"); - BOTAN_ARG_CHECK(std::all_of(pks.begin(), - pks.end(), - [](const auto& pk) { - return pk->supports_operation(PublicKeyOperation::KeyEncapsulation) || - pk->supports_operation(PublicKeyOperation::KeyAgreement); - }), - "Some provided public key is not compatible with this hybrid wrapper"); - - std::transform( - pks.begin(), pks.end(), std::back_inserter(m_public_keys), [](auto& key) -> std::unique_ptr { - if(key->supports_operation(PublicKeyOperation::KeyAgreement) && - !key->supports_operation(PublicKeyOperation::KeyEncapsulation)) { - return std::make_unique(std::move(key)); - } else { - return std::move(key); - } - }); - - m_key_length = - reduce(m_public_keys, size_t(0), [](size_t kl, const auto& key) { return std::max(kl, key->key_length()); }); - m_estimated_strength = reduce( - m_public_keys, size_t(0), [](size_t es, const auto& key) { return std::max(es, key->estimated_strength()); }); -} +Hybrid_KEM_PublicKey::Hybrid_KEM_PublicKey(std::vector> pks) : + Hybrid_PublicKey(convert_kex_to_kem_pks(std::move(pks))) {} + +Hybrid_KEM_PrivateKey::Hybrid_KEM_PrivateKey(std::vector> sks) : + Hybrid_PublicKey(convert_kex_to_kem_pks(extract_public_keys(sks))), + Hybrid_PrivateKey(convert_kex_to_kem_sks(std::move(sks))) {} std::string Hybrid_KEM_PublicKey::algo_name() const { - std::ostringstream algo_name("Hybrid("); - for(size_t i = 0; i < m_public_keys.size(); ++i) { + std::ostringstream algo_name; + algo_name << "Hybrid("; + for(size_t i = 0; i < public_keys().size(); ++i) { if(i > 0) { algo_name << ","; } - algo_name << m_public_keys[i]->algo_name(); + algo_name << public_keys().at(i)->algo_name(); } algo_name << ")"; return algo_name.str(); } -size_t Hybrid_KEM_PublicKey::estimated_strength() const { - return m_estimated_strength; -} - -size_t Hybrid_KEM_PublicKey::key_length() const { - return m_key_length; -} - -bool Hybrid_KEM_PublicKey::check_key(RandomNumberGenerator& rng, bool strong) const { - return reduce(m_public_keys, true, [&](bool ckr, const auto& key) { return ckr && key->check_key(rng, strong); }); -} - AlgorithmIdentifier Hybrid_KEM_PublicKey::algorithm_identifier() const { throw Botan::Not_Implemented("Hybrid keys don't have an algorithm identifier"); } @@ -225,87 +293,23 @@ // to be used with values that are not fixed-length, a length prefix or // other unambiguous encoding must be used to ensure that the composition // of the two values is injective. - return reduce(m_public_keys, std::vector(), [](auto pkb, const auto& key) { + return reduce(public_keys(), std::vector(), [](auto pkb, const auto& key) { return concat(pkb, key->raw_public_key_bits()); }); } std::unique_ptr Hybrid_KEM_PublicKey::generate_another(RandomNumberGenerator& rng) const { - std::vector> new_private_keys; - std::transform( - m_public_keys.begin(), m_public_keys.end(), std::back_inserter(new_private_keys), [&](const auto& public_key) { - return public_key->generate_another(rng); - }); - return std::make_unique(std::move(new_private_keys)); -} - -bool Hybrid_KEM_PublicKey::supports_operation(PublicKeyOperation op) const { - return PublicKeyOperation::KeyEncapsulation == op; + return std::make_unique(generate_other_sks_from_pks(rng)); } -namespace { - -class Hybrid_KEM_Encryption_Operation final : public PK_Ops::KEM_Encryption_with_KDF { - public: - Hybrid_KEM_Encryption_Operation(const Hybrid_KEM_PublicKey& key, - std::string_view kdf, - std::string_view provider) : - PK_Ops::KEM_Encryption_with_KDF(kdf), m_raw_kem_shared_key_length(0), m_encapsulated_key_length(0) { - m_kem_encryptors.reserve(key.public_keys().size()); - for(const auto& k : key.public_keys()) { - const auto& newenc = m_kem_encryptors.emplace_back(*k, "Raw", provider); - m_raw_kem_shared_key_length += newenc.shared_key_length(0 /* no KDF */); - m_encapsulated_key_length += newenc.encapsulated_key_length(); - } - } - - size_t raw_kem_shared_key_length() const override { return m_raw_kem_shared_key_length; } - - size_t encapsulated_key_length() const override { return m_encapsulated_key_length; } - - void raw_kem_encrypt(std::span out_encapsulated_key, - std::span raw_shared_key, - Botan::RandomNumberGenerator& rng) override { - BOTAN_ASSERT_NOMSG(out_encapsulated_key.size() == encapsulated_key_length()); - BOTAN_ASSERT_NOMSG(raw_shared_key.size() == raw_kem_shared_key_length()); - - BufferStuffer encaps_key_stuffer(out_encapsulated_key); - BufferStuffer shared_key_stuffer(raw_shared_key); - - for(auto& kem_enc : m_kem_encryptors) { - kem_enc.encrypt(encaps_key_stuffer.next(kem_enc.encapsulated_key_length()), - shared_key_stuffer.next(kem_enc.shared_key_length(0 /* no KDF */)), - rng); - } - } - - private: - std::vector m_kem_encryptors; - size_t m_raw_kem_shared_key_length; - size_t m_encapsulated_key_length; -}; - -} // namespace - std::unique_ptr Hybrid_KEM_PublicKey::create_kem_encryption_op( - std::string_view kdf, std::string_view provider) const { - return std::make_unique(*this, kdf, provider); -} - -namespace { - -auto extract_public_keys(const std::vector>& private_keys) { - std::vector> public_keys; - public_keys.reserve(private_keys.size()); - for(const auto& private_key : private_keys) { - BOTAN_ARG_CHECK(private_key != nullptr, "List of private keys contains a nullptr"); - public_keys.push_back(private_key->public_key()); + std::string_view params, std::string_view provider) const { + if(params != "Raw" && !params.empty()) { + throw Botan::Invalid_Argument("Hybrid KEM encryption does not support KDFs"); } - return public_keys; + return std::make_unique(public_keys(), provider); } -} // namespace - std::unique_ptr Hybrid_KEM_PrivateKey::generate_from_group(Group_Params group, RandomNumberGenerator& rng) { const auto algo_spec = algorithm_specs_for_group(group); @@ -317,88 +321,12 @@ return std::make_unique(std::move(private_keys)); } -Hybrid_KEM_PrivateKey::Hybrid_KEM_PrivateKey(std::vector> sks) : - Hybrid_KEM_PublicKey(extract_public_keys(sks)) { - BOTAN_ARG_CHECK(sks.size() >= 2, "List of private keys must include at least two keys"); - BOTAN_ARG_CHECK(std::all_of(sks.begin(), - sks.end(), - [](const auto& sk) { - return sk->supports_operation(PublicKeyOperation::KeyEncapsulation) || - sk->supports_operation(PublicKeyOperation::KeyAgreement); - }), - "Some provided private key is not compatible with this hybrid wrapper"); - - std::transform( - sks.begin(), sks.end(), std::back_inserter(m_private_keys), [](auto& key) -> std::unique_ptr { - if(key->supports_operation(PublicKeyOperation::KeyAgreement) && - !key->supports_operation(PublicKeyOperation::KeyEncapsulation)) { - auto ka_key = dynamic_cast(key.get()); - BOTAN_ASSERT_NONNULL(ka_key); - (void)key.release(); - return std::make_unique(std::unique_ptr(ka_key)); - } else { - return std::move(key); - } - }); -} - -secure_vector Hybrid_KEM_PrivateKey::private_key_bits() const { - throw Not_Implemented("Hybrid private keys cannot be serialized"); -} - -std::unique_ptr Hybrid_KEM_PrivateKey::public_key() const { - return std::make_unique(extract_public_keys(m_private_keys)); -} - -bool Hybrid_KEM_PrivateKey::check_key(RandomNumberGenerator& rng, bool strong) const { - return reduce(m_public_keys, true, [&](bool ckr, const auto& key) { return ckr && key->check_key(rng, strong); }); -} - -namespace { - -class Hybrid_KEM_Decryption final : public PK_Ops::KEM_Decryption_with_KDF { - public: - Hybrid_KEM_Decryption(const Hybrid_KEM_PrivateKey& key, - RandomNumberGenerator& rng, - const std::string_view kdf, - const std::string_view provider) : - PK_Ops::KEM_Decryption_with_KDF(kdf), m_encapsulated_key_length(0), m_raw_kem_shared_key_length(0) { - m_decryptors.reserve(key.private_keys().size()); - for(const auto& private_key : key.private_keys()) { - const auto& newdec = m_decryptors.emplace_back(*private_key, rng, "Raw", provider); - m_encapsulated_key_length += newdec.encapsulated_key_length(); - m_raw_kem_shared_key_length += newdec.shared_key_length(0 /* no KDF */); - } - } - - void raw_kem_decrypt(std::span out_shared_key, std::span encap_key) override { - BOTAN_ASSERT_NOMSG(out_shared_key.size() == raw_kem_shared_key_length()); - BOTAN_ASSERT_NOMSG(encap_key.size() == encapsulated_key_length()); - - BufferSlicer encap_key_slicer(encap_key); - BufferStuffer shared_secret_stuffer(out_shared_key); - - for(auto& decryptor : m_decryptors) { - decryptor.decrypt(shared_secret_stuffer.next(decryptor.shared_key_length(0 /* no KDF */)), - encap_key_slicer.take(decryptor.encapsulated_key_length())); - } - } - - size_t encapsulated_key_length() const override { return m_encapsulated_key_length; } - - size_t raw_kem_shared_key_length() const override { return m_raw_kem_shared_key_length; } - - private: - std::vector m_decryptors; - size_t m_encapsulated_key_length; - size_t m_raw_kem_shared_key_length; -}; - -} // namespace - std::unique_ptr Hybrid_KEM_PrivateKey::create_kem_decryption_op( - RandomNumberGenerator& rng, std::string_view kdf, std::string_view provider) const { - return std::make_unique(*this, rng, kdf, provider); + RandomNumberGenerator& rng, std::string_view params, std::string_view provider) const { + if(params != "Raw" && !params.empty()) { + throw Botan::Invalid_Argument("Hybrid KEM decryption does not support KDFs"); + } + return std::make_unique(private_keys(), rng, provider); } } // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13_pqc/hybrid_public_key.h botan3-3.12.0+dfsg/src/lib/tls/tls13_pqc/hybrid_public_key.h --- botan3-3.7.1+dfsg/src/lib/tls/tls13_pqc/hybrid_public_key.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13_pqc/hybrid_public_key.h 2026-05-07 01:38:28.000000000 +0000 @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -37,7 +38,7 @@ * serializes and parses keys and ciphertexts as described in the * above-mentioned IETF draft for a post-quantum TLS 1.3. */ -class BOTAN_TEST_API Hybrid_KEM_PublicKey : public virtual Public_Key { +class BOTAN_TEST_API Hybrid_KEM_PublicKey : public virtual Hybrid_PublicKey { public: static std::unique_ptr load_for_group(Group_Params group, std::span concatenated_public_values); @@ -45,34 +46,18 @@ public: explicit Hybrid_KEM_PublicKey(std::vector> pks); - Hybrid_KEM_PublicKey(Hybrid_KEM_PublicKey&&) = default; - Hybrid_KEM_PublicKey(const Hybrid_KEM_PublicKey&) = delete; - Hybrid_KEM_PublicKey& operator=(Hybrid_KEM_PublicKey&&) = default; - Hybrid_KEM_PublicKey& operator=(const Hybrid_KEM_PublicKey&) = delete; - ~Hybrid_KEM_PublicKey() = default; - std::string algo_name() const override; - size_t estimated_strength() const override; - size_t key_length() const override; - bool check_key(RandomNumberGenerator& rng, bool strong) const override; AlgorithmIdentifier algorithm_identifier() const override; std::vector raw_public_key_bits() const override; std::vector public_key_bits() const override; std::unique_ptr generate_another(RandomNumberGenerator& rng) const final; - bool supports_operation(PublicKeyOperation op) const override; - + // no KDF support std::unique_ptr create_kem_encryption_op( - std::string_view kdf, std::string_view provider = "base") const override; - - const auto& public_keys() const { return m_public_keys; } + std::string_view params, std::string_view provider = "base") const override; protected: - std::vector> m_public_keys; - - private: - size_t m_key_length; - size_t m_estimated_strength; + Hybrid_KEM_PublicKey() = default; }; BOTAN_DIAGNOSTIC_PUSH @@ -82,8 +67,8 @@ * Composes a number of private keys for hybrid key agreement as defined in this * IETF draft: https://datatracker.ietf.org/doc/html/draft-ietf-tls-hybrid-design-04 */ -class BOTAN_TEST_API Hybrid_KEM_PrivateKey final : public Private_Key, - public Hybrid_KEM_PublicKey { +class BOTAN_TEST_API Hybrid_KEM_PrivateKey final : public virtual Hybrid_KEM_PublicKey, + public virtual Hybrid_PrivateKey { public: /** * Generate a hybrid private key for the given TLS code point. @@ -91,25 +76,21 @@ static std::unique_ptr generate_from_group(Group_Params group, RandomNumberGenerator& rng); public: - Hybrid_KEM_PrivateKey(std::vector> private_keys); + explicit Hybrid_KEM_PrivateKey(std::vector> private_keys); - secure_vector private_key_bits() const override; + std::unique_ptr public_key() const override { + return std::make_unique(extract_public_keys(private_keys())); + } - std::unique_ptr public_key() const override; - - bool check_key(RandomNumberGenerator& rng, bool strong) const override; + bool check_key(RandomNumberGenerator& rng, bool strong) const override { + return Hybrid_PrivateKey::check_key(rng, strong); + } + // no KDF support std::unique_ptr create_kem_decryption_op( - RandomNumberGenerator& rng, std::string_view kdf, std::string_view provider = "base") const override; - - const auto& private_keys() const { return m_private_keys; } - - private: - std::vector> m_private_keys; + RandomNumberGenerator& rng, std::string_view params, std::string_view provider = "base") const override; }; -BOTAN_DIAGNOSTIC_POP - } // namespace Botan::TLS #endif diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13_pqc/info.txt botan3-3.12.0+dfsg/src/lib/tls/tls13_pqc/info.txt --- botan3-3.7.1+dfsg/src/lib/tls/tls13_pqc/info.txt 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13_pqc/info.txt 2026-05-07 01:38:28.000000000 +0000 @@ -12,9 +12,10 @@ hybrid_public_key.h -kex_to_kem_adapter.h tls13 +hybrid_kem +kex_to_kem_adapter diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13_pqc/kex_to_kem_adapter.cpp botan3-3.12.0+dfsg/src/lib/tls/tls13_pqc/kex_to_kem_adapter.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls13_pqc/kex_to_kem_adapter.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13_pqc/kex_to_kem_adapter.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,251 +0,0 @@ -/** - * Adapter that allows using a KEX key as a KEM, using an ephemeral - * key in the KEM encapsulation. - * - * (C) 2023 Jack Lloyd - * 2023 Fabian Albert, René Meusel - Rohde & Schwarz Cybersecurity - * - * Botan is released under the Simplified BSD License (see license.txt) - */ - -#include - -#include -#include -#include - -#if defined(BOTAN_HAS_DIFFIE_HELLMAN) - #include - #include -#endif - -#if defined(BOTAN_HAS_ECDH) - #include -#endif - -#if defined(BOTAN_HAS_X25519) - #include -#endif - -#if defined(BOTAN_HAS_X448) - #include -#endif - -namespace Botan::TLS { - -namespace { - -/** - * This helper determines the length of the agreed-upon value depending - * on the key agreement public key's algorithm type. It would be better - * to get this value via PK_Key_Agreement::agreed_value_size(), but - * instantiating a PK_Key_Agreement object requires a PrivateKey object - * which we don't have (yet) in the context this is used. - * - * TODO: Find a way to get this information without duplicating those - * implementation details of the key agreement algorithms. - */ -size_t kex_shared_key_length(const Public_Key& kex_public_key) { - BOTAN_ASSERT_NOMSG(kex_public_key.supports_operation(PublicKeyOperation::KeyAgreement)); - -#if defined(BOTAN_HAS_ECDH) - if(const auto* ecdh = dynamic_cast(&kex_public_key)) { - return ecdh->domain().get_p_bytes(); - } -#endif - -#if defined(BOTAN_HAS_DIFFIE_HELLMAN) - if(const auto* dh = dynamic_cast(&kex_public_key)) { - return dh->group().p_bytes(); - } -#endif - -#if defined(BOTAN_HAS_X25519) - if(const auto* curve = dynamic_cast(&kex_public_key)) { - BOTAN_UNUSED(curve); - return 32; /* TODO: magic number */ - } -#endif - -#if defined(BOTAN_HAS_X448) - if(const auto* curve = dynamic_cast(&kex_public_key)) { - BOTAN_UNUSED(curve); - return 56; /* TODO: magic number */ - } -#endif - - throw Not_Implemented( - fmt("Cannot get shared kex key length from unknown key agreement public key of type '{}' in the hybrid KEM key", - kex_public_key.algo_name())); -} - -/** - * This helper generates an ephemeral key agreement private key given a - * public key instance of a certain key agreement algorithm. - */ -std::unique_ptr generate_key_agreement_private_key(const Public_Key& kex_public_key, - RandomNumberGenerator& rng) { - BOTAN_ASSERT_NOMSG(kex_public_key.supports_operation(PublicKeyOperation::KeyAgreement)); - - auto new_kex_key = [&] { - auto new_private_key = kex_public_key.generate_another(rng); - const auto kex_key = dynamic_cast(new_private_key.get()); - if(kex_key) [[likely]] { - // Intentionally leak new_private_key since we hold an alias of it in kex_key, - // which is captured in a unique_ptr below - // NOLINTNEXTLINE(*-unused-return-value) - (void)new_private_key.release(); - } - return std::unique_ptr(kex_key); - }(); - - BOTAN_ASSERT(new_kex_key, "Keys wrapped in this adapter are always key-agreement keys"); - return new_kex_key; -} - -std::unique_ptr maybe_get_public_key(const std::unique_ptr& private_key) { - BOTAN_ARG_CHECK(private_key != nullptr, "Private key is a nullptr"); - return private_key->public_key(); -} - -class KEX_to_KEM_Adapter_Encryption_Operation final : public PK_Ops::KEM_Encryption_with_KDF { - public: - KEX_to_KEM_Adapter_Encryption_Operation(const Public_Key& key, std::string_view kdf, std::string_view provider) : - PK_Ops::KEM_Encryption_with_KDF(kdf), m_provider(provider), m_public_key(key) {} - - size_t raw_kem_shared_key_length() const override { return kex_shared_key_length(m_public_key); } - - size_t encapsulated_key_length() const override { - // Serializing the public value into a short-lived heap-allocated - // vector is not ideal. - // - // TODO: Find a way to get the public value length without copying - // the public value into a vector. See GH #3706 (point 5). - return m_public_key.raw_public_key_bits().size(); - } - - void raw_kem_encrypt(std::span out_encapsulated_key, - std::span raw_shared_key, - Botan::RandomNumberGenerator& rng) override { - const auto sk = generate_key_agreement_private_key(m_public_key, rng); - const auto shared_key = PK_Key_Agreement(*sk, rng, "Raw", m_provider) - .derive_key(0 /* no KDF */, m_public_key.raw_public_key_bits()) - .bits_of(); - - const auto public_value = sk->public_value(); - - // TODO: perhaps avoid these copies by providing std::span out-params - // for `PK_Key_Agreement::derive_key()` and - // `PK_Key_Agreement_Key::public_value()` - BOTAN_ASSERT_EQUAL(public_value.size(), - out_encapsulated_key.size(), - "KEX-to-KEM Adapter: encapsulated key out-param has correct length"); - BOTAN_ASSERT_EQUAL( - shared_key.size(), raw_shared_key.size(), "KEX-to-KEM Adapter: shared key out-param has correct length"); - std::copy(public_value.begin(), public_value.end(), out_encapsulated_key.begin()); - std::copy(shared_key.begin(), shared_key.end(), raw_shared_key.begin()); - } - - private: - std::string m_provider; - const Public_Key& m_public_key; -}; - -class KEX_to_KEM_Decryption_Operation final : public PK_Ops::KEM_Decryption_with_KDF { - public: - KEX_to_KEM_Decryption_Operation(const PK_Key_Agreement_Key& key, - RandomNumberGenerator& rng, - const std::string_view kdf, - const std::string_view provider) : - PK_Ops::KEM_Decryption_with_KDF(kdf), - m_operation(key, rng, "Raw", provider), - m_encapsulated_key_length(key.public_value().size()) {} - - void raw_kem_decrypt(std::span out_shared_key, std::span encap_key) override { - secure_vector shared_secret = m_operation.derive_key(0 /* no KDF */, encap_key).bits_of(); - BOTAN_ASSERT_EQUAL( - shared_secret.size(), out_shared_key.size(), "KEX-to-KEM Adapter: shared key out-param has correct length"); - std::copy(shared_secret.begin(), shared_secret.end(), out_shared_key.begin()); - } - - size_t encapsulated_key_length() const override { return m_encapsulated_key_length; } - - size_t raw_kem_shared_key_length() const override { return m_operation.agreed_value_size(); } - - private: - PK_Key_Agreement m_operation; - size_t m_encapsulated_key_length; -}; - -} // namespace - -KEX_to_KEM_Adapter_PublicKey::KEX_to_KEM_Adapter_PublicKey(std::unique_ptr public_key) : - m_public_key(std::move(public_key)) { - BOTAN_ARG_CHECK(m_public_key != nullptr, "Public key is a nullptr"); - BOTAN_ARG_CHECK(m_public_key->supports_operation(PublicKeyOperation::KeyAgreement), "Public key is no KEX key"); -} - -std::string KEX_to_KEM_Adapter_PublicKey::algo_name() const { - return fmt("KEX-to-KEM({})", m_public_key->algo_name()); -} - -size_t KEX_to_KEM_Adapter_PublicKey::estimated_strength() const { - return m_public_key->estimated_strength(); -} - -size_t KEX_to_KEM_Adapter_PublicKey::key_length() const { - return m_public_key->key_length(); -} - -bool KEX_to_KEM_Adapter_PublicKey::check_key(RandomNumberGenerator& rng, bool strong) const { - return m_public_key->check_key(rng, strong); -} - -AlgorithmIdentifier KEX_to_KEM_Adapter_PublicKey::algorithm_identifier() const { - return m_public_key->algorithm_identifier(); -} - -std::vector KEX_to_KEM_Adapter_PublicKey::raw_public_key_bits() const { - return m_public_key->raw_public_key_bits(); -} - -std::vector KEX_to_KEM_Adapter_PublicKey::public_key_bits() const { - throw Not_Implemented("The KEX-to-KEM adapter does not support ASN.1-based public key serialization"); -} - -std::unique_ptr KEX_to_KEM_Adapter_PublicKey::generate_another(RandomNumberGenerator& rng) const { - return std::make_unique(generate_key_agreement_private_key(*m_public_key, rng)); -} - -bool KEX_to_KEM_Adapter_PublicKey::supports_operation(PublicKeyOperation op) const { - return op == PublicKeyOperation::KeyEncapsulation; -} - -KEX_to_KEM_Adapter_PrivateKey::KEX_to_KEM_Adapter_PrivateKey(std::unique_ptr private_key) : - KEX_to_KEM_Adapter_PublicKey(maybe_get_public_key(private_key)), m_private_key(std::move(private_key)) { - BOTAN_ARG_CHECK(m_private_key->supports_operation(PublicKeyOperation::KeyAgreement), "Private key is no KEX key"); -} - -secure_vector KEX_to_KEM_Adapter_PrivateKey::private_key_bits() const { - return m_private_key->private_key_bits(); -} - -std::unique_ptr KEX_to_KEM_Adapter_PrivateKey::public_key() const { - return std::make_unique(m_private_key->public_key()); -} - -bool KEX_to_KEM_Adapter_PrivateKey::check_key(RandomNumberGenerator& rng, bool strong) const { - return m_private_key->check_key(rng, strong); -} - -std::unique_ptr KEX_to_KEM_Adapter_PublicKey::create_kem_encryption_op( - std::string_view kdf, std::string_view provider) const { - return std::make_unique(*m_public_key, kdf, provider); -} - -std::unique_ptr KEX_to_KEM_Adapter_PrivateKey::create_kem_decryption_op( - RandomNumberGenerator& rng, std::string_view kdf, std::string_view provider) const { - return std::make_unique(*m_private_key, rng, kdf, provider); -} - -} // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls13_pqc/kex_to_kem_adapter.h botan3-3.12.0+dfsg/src/lib/tls/tls13_pqc/kex_to_kem_adapter.h --- botan3-3.7.1+dfsg/src/lib/tls/tls13_pqc/kex_to_kem_adapter.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls13_pqc/kex_to_kem_adapter.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,89 +0,0 @@ -/** - * Adapter that allows using a KEX key as a KEM, using an ephemeral - * key in the KEM encapsulation. - * - * (C) 2023 Jack Lloyd - * 2023 Fabian Albert, René Meusel - Rohde & Schwarz Cybersecurity - * - * Botan is released under the Simplified BSD License (see license.txt) - */ - -#ifndef BOTAN_TLS_13_KEX_TO_KEM_ADAPTER_H_ -#define BOTAN_TLS_13_KEX_TO_KEM_ADAPTER_H_ - -#include - -#include - -namespace Botan::TLS { - -/** - * Adapter to use a key agreement key pair (e.g. ECDH) as a key encapsulation - * mechanism. - */ -class BOTAN_TEST_API KEX_to_KEM_Adapter_PublicKey : public virtual Public_Key { - public: - KEX_to_KEM_Adapter_PublicKey(std::unique_ptr public_key); - - std::string algo_name() const override; - size_t estimated_strength() const override; - size_t key_length() const override; - bool check_key(RandomNumberGenerator& rng, bool strong) const override; - AlgorithmIdentifier algorithm_identifier() const override; - std::vector raw_public_key_bits() const override; - std::vector public_key_bits() const override; - std::unique_ptr generate_another(RandomNumberGenerator& rng) const final; - - bool supports_operation(PublicKeyOperation op) const override; - - std::unique_ptr create_kem_encryption_op( - std::string_view kdf, std::string_view provider = "base") const override; - - private: - std::unique_ptr m_public_key; -}; - -BOTAN_DIAGNOSTIC_PUSH -BOTAN_DIAGNOSTIC_IGNORE_INHERITED_VIA_DOMINANCE - -/** - * Adapter to use a key agreement key pair (e.g. ECDH) as a key encapsulation - * mechanism. This works by generating an ephemeral key pair during the - * encapsulation. - * - * The abstract interface of a key exchange mechanism (KEX) is mapped like so: - * - * * KEM-generate(rng) -> tuple[PublicKey, PrivateKey] - * => KEX-generate(rng) -> tuple[PublicKey, PrivateKey] - * - * * KEM-encapsulate(PublicKey, rng) -> tuple[SharedSecret, EncapsulatedSharedSecret] - * => eph_pk, eph_sk = KEX-generate(rng) - * secret = KEX-agree(eph_sk, PublicKey) - * [secret, eph_pk] - * - * * KEM-decapsulate(PrivateKey, EncapsulatedSharedSecret) -> SharedSecret - * => KEX-agree(PrivateKey, EncapsulatedSharedSecret) - */ -class BOTAN_TEST_API KEX_to_KEM_Adapter_PrivateKey final : public KEX_to_KEM_Adapter_PublicKey, - public virtual Private_Key { - public: - KEX_to_KEM_Adapter_PrivateKey(std::unique_ptr private_key); - - secure_vector private_key_bits() const override; - - std::unique_ptr public_key() const override; - - bool check_key(RandomNumberGenerator& rng, bool strong) const override; - - std::unique_ptr create_kem_decryption_op( - RandomNumberGenerator& rng, std::string_view kdf, std::string_view provider = "base") const override; - - private: - std::unique_ptr m_private_key; -}; - -BOTAN_DIAGNOSTIC_POP - -} // namespace Botan::TLS - -#endif diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls_alert.h botan3-3.12.0+dfsg/src/lib/tls/tls_alert.h --- botan3-3.7.1+dfsg/src/lib/tls/tls_alert.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls_alert.h 2026-05-07 01:38:28.000000000 +0000 @@ -18,7 +18,7 @@ * * The enumeration value matches the wire encoding */ -enum class AlertType { +enum class AlertType : uint16_t { CloseNotify = 0, UnexpectedMessage = 10, BadRecordMac = 20, @@ -58,6 +58,7 @@ None = 256, // Compat enum variants, will be removed in a future major release + // TODO(Botan4): remove these CLOSE_NOTIFY BOTAN_DEPRECATED("Use CloseNotify") = CloseNotify, NO_APPLICATION_PROTOCOL BOTAN_DEPRECATED("Use NoApplicationProtocol") = NoApplicationProtocol, PROTOCOL_VERSION BOTAN_DEPRECATED("Use ProtocolVersion") = ProtocolVersion, @@ -121,7 +122,8 @@ * @param type_code the type of alert * @param fatal specifies if this is a fatal alert */ - Alert(Type type_code, bool fatal = false) : m_fatal(fatal), m_type_code(type_code) {} + Alert(Type type_code, bool fatal = false) : // NOLINT(*-explicit-conversions) + m_fatal(fatal), m_type_code(type_code) {} Alert() : m_fatal(false), m_type_code(AlertType::None) {} diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls_algos.cpp botan3-3.12.0+dfsg/src/lib/tls/tls_algos.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls_algos.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls_algos.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -6,10 +6,12 @@ #include -#include #include #include +#include +#include + namespace Botan::TLS { std::string kdf_algo_to_string(KDF_Algo algo) { @@ -134,43 +136,108 @@ throw Invalid_Argument(fmt("Unknown TLS signature method '{}'", str)); } -bool Group_Params::is_available() const { -#if !defined(BOTAN_HAS_X25519) - if(is_x25519()) { - return false; - } - if(is_pqc_hybrid() && pqc_hybrid_ecc() == Group_Params_Code::X25519) { - return false; - } +namespace { + +consteval auto available_group_params() { + auto codes = std::array { +#if defined(BOTAN_HAS_PCURVES_SECP256R1) || defined(BOTAN_HAS_PCURVES_GENERIC) + Group_Params_Code::SECP256R1, #endif -#if !defined(BOTAN_HAS_X448) - if(is_x448()) { - return false; - } - if(is_pqc_hybrid() && pqc_hybrid_ecc() == Group_Params_Code::X448) { - return false; - } +#if defined(BOTAN_HAS_PCURVES_SECP384R1) || defined(BOTAN_HAS_PCURVES_GENERIC) + Group_Params_Code::SECP384R1, #endif -#if !defined(BOTAN_HAS_DIFFIE_HELLMAN) - if(is_in_ffdhe_range()) { - return false; - } +#if defined(BOTAN_HAS_PCURVES_SECP521R1) || defined(BOTAN_HAS_PCURVES_GENERIC) + Group_Params_Code::SECP521R1, #endif -#if !defined(BOTAN_HAS_ML_KEM) - if(is_pure_ml_kem() || is_pqc_hybrid_ml_kem()) { - return false; - } +#if defined(BOTAN_HAS_PCURVES_BRAINPOOL256R1) || defined(BOTAN_HAS_PCURVES_GENERIC) + Group_Params_Code::BRAINPOOL256R1, #endif -#if !defined(BOTAN_HAS_FRODOKEM) - if(is_pure_frodokem() || is_pqc_hybrid_frodokem()) { - return false; - } +#if defined(BOTAN_HAS_PCURVES_BRAINPOOL384R1) || defined(BOTAN_HAS_PCURVES_GENERIC) + Group_Params_Code::BRAINPOOL384R1, #endif +#if defined(BOTAN_HAS_PCURVES_BRAINPOOL512R1) || defined(BOTAN_HAS_PCURVES_GENERIC) + Group_Params_Code::BRAINPOOL512R1, +#endif + +#if defined(BOTAN_HAS_X25519) + Group_Params_Code::X25519, +#endif + +#if defined(BOTAN_HAS_X448) + Group_Params_Code::X448, +#endif + +#if defined(BOTAN_HAS_DIFFIE_HELLMAN) + Group_Params_Code::FFDHE_2048, Group_Params_Code::FFDHE_3072, Group_Params_Code::FFDHE_4096, + Group_Params_Code::FFDHE_6144, Group_Params_Code::FFDHE_8192, +#endif + +#if defined(BOTAN_HAS_ML_KEM) + Group_Params_Code::ML_KEM_512, Group_Params_Code::ML_KEM_768, Group_Params_Code::ML_KEM_1024, + + #if defined(BOTAN_HAS_PCURVES_SECP256R1) || defined(BOTAN_HAS_PCURVES_GENERIC) + Group_Params_Code::HYBRID_SECP256R1_ML_KEM_768, + #endif + + #if defined(BOTAN_HAS_PCURVES_SECP384R1) || defined(BOTAN_HAS_PCURVES_GENERIC) + Group_Params_Code::HYBRID_SECP384R1_ML_KEM_1024, + #endif + + #if defined(BOTAN_HAS_X25519) + Group_Params_Code::HYBRID_X25519_ML_KEM_768, + #endif +#endif + +#if defined(BOTAN_HAS_FRODOKEM) + Group_Params_Code::eFRODOKEM_640_SHAKE_OQS, Group_Params_Code::eFRODOKEM_976_SHAKE_OQS, + Group_Params_Code::eFRODOKEM_1344_SHAKE_OQS, Group_Params_Code::eFRODOKEM_640_AES_OQS, + Group_Params_Code::eFRODOKEM_976_AES_OQS, Group_Params_Code::eFRODOKEM_1344_AES_OQS, + + #if defined(BOTAN_HAS_PCURVES_SECP256R1) || defined(BOTAN_HAS_PCURVES_GENERIC) + Group_Params_Code::HYBRID_SECP256R1_eFRODOKEM_640_SHAKE_OQS, + Group_Params_Code::HYBRID_SECP256R1_eFRODOKEM_640_AES_OQS, + #endif + + #if defined(BOTAN_HAS_PCURVES_SECP384R1) || defined(BOTAN_HAS_PCURVES_GENERIC) + Group_Params_Code::HYBRID_SECP384R1_eFRODOKEM_976_SHAKE_OQS, + Group_Params_Code::HYBRID_SECP384R1_eFRODOKEM_976_AES_OQS, + #endif + + #if defined(BOTAN_HAS_PCURVES_SECP521R1) || defined(BOTAN_HAS_PCURVES_GENERIC) + Group_Params_Code::HYBRID_SECP521R1_eFRODOKEM_1344_SHAKE_OQS, + Group_Params_Code::HYBRID_SECP521R1_eFRODOKEM_1344_AES_OQS, + #endif + + #if defined(BOTAN_HAS_X25519) + Group_Params_Code::HYBRID_X25519_eFRODOKEM_640_SHAKE_OQS, + Group_Params_Code::HYBRID_X25519_eFRODOKEM_640_AES_OQS, + #endif + + #if defined(BOTAN_HAS_X448) + Group_Params_Code::HYBRID_X448_eFRODOKEM_976_SHAKE_OQS, Group_Params_Code::HYBRID_X448_eFRODOKEM_976_AES_OQS, + #endif +#endif + }; + + std::sort(codes.begin(), codes.end()); + + return codes; +} + +} // namespace + +bool Group_Params::is_available() const { + // For group codes we recognize, check the build-time availability table. + // Unknown codes may be user-supplied custom groups handled via callbacks. + if(to_string().has_value()) { + static constexpr auto codes = available_group_params(); + return std::binary_search(codes.begin(), codes.end(), this->code()); + } return true; } @@ -191,6 +258,7 @@ case Group_Params_Code::HYBRID_SECP256R1_eFRODOKEM_640_AES_OQS: return Group_Params_Code::SECP256R1; + case Group_Params_Code::HYBRID_SECP384R1_ML_KEM_1024: case Group_Params_Code::HYBRID_SECP384R1_eFRODOKEM_976_SHAKE_OQS: case Group_Params_Code::HYBRID_SECP384R1_eFRODOKEM_976_AES_OQS: return Group_Params_Code::SECP384R1; @@ -281,6 +349,9 @@ if(group_name == "secp256r1/ML-KEM-768") { return Group_Params::HYBRID_SECP256R1_ML_KEM_768; } + if(group_name == "secp384r1/ML-KEM-1024") { + return Group_Params::HYBRID_SECP384R1_ML_KEM_1024; + } if(group_name == "x25519/eFrodoKEM-640-SHAKE") { return Group_Params::HYBRID_X25519_eFRODOKEM_640_SHAKE_OQS; @@ -394,10 +465,33 @@ return "x25519/ML-KEM-768"; case Group_Params::HYBRID_SECP256R1_ML_KEM_768: return "secp256r1/ML-KEM-768"; + case Group_Params::HYBRID_SECP384R1_ML_KEM_1024: + return "secp384r1/ML-KEM-1024"; default: return std::nullopt; } } +std::string certificate_type_to_string(Certificate_Type type) { + switch(type) { + case Certificate_Type::X509: + return "X509"; + case Certificate_Type::RawPublicKey: + return "RawPublicKey"; + } + + return "Unknown"; +} + +Certificate_Type certificate_type_from_string(const std::string& type_str) { + if(type_str == "X509") { + return Certificate_Type::X509; + } else if(type_str == "RawPublicKey") { + return Certificate_Type::RawPublicKey; + } else { + throw Decoding_Error("Unknown certificate type: " + type_str); + } +} + } // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls_algos.h botan3-3.12.0+dfsg/src/lib/tls/tls_algos.h --- botan3-3.7.1+dfsg/src/lib/tls/tls_algos.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls_algos.h 2026-05-07 01:38:28.000000000 +0000 @@ -7,18 +7,15 @@ #ifndef BOTAN_TLS_ALGO_IDS_H_ #define BOTAN_TLS_ALGO_IDS_H_ -#include -#include #include #include #include -#include //BOTAN_FUTURE_INTERNAL_HEADER(tls_algos.h) namespace Botan::TLS { -enum class Cipher_Algo { +enum class Cipher_Algo : uint8_t { CHACHA20_POLY1305, AES_128_GCM, @@ -46,7 +43,7 @@ DES_EDE_CBC_HMAC_SHA1, }; -enum class KDF_Algo { +enum class KDF_Algo : uint8_t { SHA_1, SHA_256, SHA_384, @@ -54,20 +51,21 @@ std::string BOTAN_DLL kdf_algo_to_string(KDF_Algo algo); -enum class Nonce_Format { +enum class Nonce_Format : uint8_t { CBC_MODE, AEAD_IMPLICIT_4, AEAD_XOR_12, + NULL_CIPHER, }; // TODO encoding should match signature_algorithms extension // TODO this should include hash etc as in TLS v1.3 -enum class Auth_Method { - RSA, - ECDSA, +enum class Auth_Method : uint32_t { + RSA = 0, + ECDSA = 1, // To support TLS 1.3 ciphersuites, which do not determine the auth method - UNDEFINED, + UNDEFINED = 2, // These are placed outside the encodable range IMPLICIT = 0x10000 @@ -105,32 +103,35 @@ // libOQS defines those in: // https://github.com/open-quantum-safe/oqs-provider/blob/main/ALGORITHMS.md - eFRODOKEM_640_SHAKE_OQS = 0xFE01, - eFRODOKEM_976_SHAKE_OQS = 0x0203, - eFRODOKEM_1344_SHAKE_OQS = 0x0205, + // (last update: 6th June 2025 - matching oqs commit 9447f68) + eFRODOKEM_640_SHAKE_OQS = 0xFE03, + eFRODOKEM_976_SHAKE_OQS = 0xFE09, + eFRODOKEM_1344_SHAKE_OQS = 0xFE0E, eFRODOKEM_640_AES_OQS = 0xFE00, - eFRODOKEM_976_AES_OQS = 0xFE02, - eFRODOKEM_1344_AES_OQS = 0x0204, + eFRODOKEM_976_AES_OQS = 0xFE06, + eFRODOKEM_1344_AES_OQS = 0xFE0C, // https://datatracker.ietf.org/doc/draft-kwiatkowski-tls-ecdhe-mlkem/03/ HYBRID_SECP256R1_ML_KEM_768 = 0x11EB, + HYBRID_SECP384R1_ML_KEM_1024 = 0x11ED, HYBRID_X25519_ML_KEM_768 = 0x11EC, // https://github.com/open-quantum-safe/oqs-provider/blob/main/ALGORITHMS.md - HYBRID_X25519_eFRODOKEM_640_SHAKE_OQS = 0x2F81, - HYBRID_X25519_eFRODOKEM_640_AES_OQS = 0x2F80, + // (last update: 6th June 2025 - matching oqs commit 9447f68) + HYBRID_X25519_eFRODOKEM_640_SHAKE_OQS = 0xFE05, + HYBRID_X25519_eFRODOKEM_640_AES_OQS = 0xFE02, - HYBRID_X448_eFRODOKEM_976_SHAKE_OQS = 0x2F83, - HYBRID_X448_eFRODOKEM_976_AES_OQS = 0x2F82, + HYBRID_X448_eFRODOKEM_976_SHAKE_OQS = 0xFE0B, + HYBRID_X448_eFRODOKEM_976_AES_OQS = 0xFE08, - HYBRID_SECP256R1_eFRODOKEM_640_SHAKE_OQS = 0x2F01, - HYBRID_SECP256R1_eFRODOKEM_640_AES_OQS = 0x2F00, + HYBRID_SECP256R1_eFRODOKEM_640_SHAKE_OQS = 0xFE04, + HYBRID_SECP256R1_eFRODOKEM_640_AES_OQS = 0xFE01, - HYBRID_SECP384R1_eFRODOKEM_976_SHAKE_OQS = 0x2F03, - HYBRID_SECP384R1_eFRODOKEM_976_AES_OQS = 0x2F02, + HYBRID_SECP384R1_eFRODOKEM_976_SHAKE_OQS = 0xFE0A, + HYBRID_SECP384R1_eFRODOKEM_976_AES_OQS = 0xFE07, - HYBRID_SECP521R1_eFRODOKEM_1344_SHAKE_OQS = 0x2F05, - HYBRID_SECP521R1_eFRODOKEM_1344_AES_OQS = 0x2F04, + HYBRID_SECP521R1_eFRODOKEM_1344_SHAKE_OQS = 0xFE0F, + HYBRID_SECP521R1_eFRODOKEM_1344_AES_OQS = 0xFE0D, }; class BOTAN_PUBLIC_API(3, 2) Group_Params final { @@ -139,8 +140,10 @@ constexpr Group_Params() : m_code(Group_Params_Code::NONE) {} + // NOLINTNEXTLINE(*-explicit-conversions) constexpr Group_Params(Group_Params_Code code) : m_code(code) {} + // NOLINTNEXTLINE(*-explicit-conversions) constexpr Group_Params(uint16_t code) : m_code(static_cast(code)) {} /** @@ -211,6 +214,7 @@ constexpr bool is_pqc_hybrid_ml_kem() const { return m_code == Group_Params_Code::HYBRID_SECP256R1_ML_KEM_768 || + m_code == Group_Params_Code::HYBRID_SECP384R1_ML_KEM_1024 || m_code == Group_Params_Code::HYBRID_X25519_ML_KEM_768; } @@ -248,7 +252,7 @@ Group_Params_Code m_code; }; -enum class Kex_Algo { +enum class Kex_Algo : uint8_t { STATIC_RSA, DH, ECDH, @@ -271,6 +275,12 @@ return (m == Kex_Algo::PSK || m == Kex_Algo::ECDHE_PSK || m == Kex_Algo::DHE_PSK); } +// As defined in RFC 8446 4.4.2 +enum class Certificate_Type : uint8_t { X509 = 0, RawPublicKey = 2 }; + +std::string certificate_type_to_string(Certificate_Type type); +Certificate_Type certificate_type_from_string(const std::string& type_str); + } // namespace Botan::TLS #endif diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls_callbacks.cpp botan3-3.12.0+dfsg/src/lib/tls/tls_callbacks.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls_callbacks.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls_callbacks.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -12,14 +12,16 @@ #include #include +#include #include #include #include #include #include #include +#include #include -#include +#include #include #if defined(BOTAN_HAS_X25519) @@ -93,17 +95,24 @@ throw Invalid_Argument("Certificate chain was empty"); } - Path_Validation_Restrictions restrictions(policy.require_cert_revocation_info(), - policy.minimum_signature_strength()); + const Path_Validation_Restrictions restrictions(policy.require_cert_revocation_info(), + policy.minimum_signature_strength()); - Path_Validation_Result result = x509_path_validate(cert_chain, - restrictions, - trusted_roots, - hostname, - usage, - tls_current_timestamp(), - tls_verify_cert_chain_ocsp_timeout(), - ocsp_responses); + /* + Hostname is always provided in order to allow host-specific logic if required, + but it should not be passed to x509_path_validate unless we are verifying + the server. + */ + const std::string_view name_to_match = (usage == Usage_Type::TLS_CLIENT_AUTH) ? std::string_view{} : hostname; + + const Path_Validation_Result result = x509_path_validate(cert_chain, + restrictions, + trusted_roots, + name_to_match, + usage, + tls_current_timestamp(), + tls_verify_cert_chain_ocsp_timeout(), + ocsp_responses); if(!result.successful_validation()) { throw TLS_Exception(Alert::BadCertificate, "Certificate validation failure: " + result.result_string()); @@ -277,6 +286,8 @@ // This exception means that the public key was invalid. However, // TLS' DecodeError would imply that a protocol message was invalid. throw TLS_Exception(Alert::IllegalParameter, ex.what()); + } catch(const Invalid_Argument& ex) { + throw TLS_Exception(Alert::IllegalParameter, ex.what()); } }(); @@ -285,6 +296,8 @@ try { return PK_KEM_Encryptor(*kem_pub_key, "Raw").encrypt(rng); + } catch(const Decoding_Error& ex) { + throw TLS_Exception(Alert::IllegalParameter, ex.what()); } catch(const Invalid_Argument& ex) { throw TLS_Exception(Alert::IllegalParameter, ex.what()); } @@ -309,11 +322,17 @@ if(encapsulated_bytes.size() != kemdec.encapsulated_key_length()) { throw TLS_Exception(Alert::IllegalParameter, "Invalid encapsulated key length"); } - return kemdec.decrypt(encapsulated_bytes, 0, {}); + try { + return kemdec.decrypt(encapsulated_bytes, 0, {}); + } catch(const Decoding_Error& ex) { + throw TLS_Exception(Alert::IllegalParameter, ex.what()); + } catch(const Invalid_Argument& ex) { + throw TLS_Exception(Alert::IllegalParameter, ex.what()); + } } try { - auto& key_agreement_key = dynamic_cast(private_key); + const auto& key_agreement_key = dynamic_cast(private_key); return tls_ephemeral_key_agreement(group, key_agreement_key, encapsulated_bytes, rng, policy); } catch(const std::bad_cast&) { throw Invalid_Argument("provided ephemeral key is not a PK_Key_Agreement_Key"); @@ -354,6 +373,25 @@ throw TLS_Exception(Alert::DecodeError, "cannot create a key offering without a group definition"); } +std::unique_ptr TLS::Callbacks::tls12_generate_ephemeral_ecdh_key( + TLS::Group_Params group, RandomNumberGenerator& rng, EC_Point_Format tls12_ecc_pubkey_encoding_format) { + // Delegating to the "universal" callback to obtain an ECDH key pair + auto key = tls_generate_ephemeral_key(group, rng); + + // For ordinary ECDH key pairs (that are derived from `ECDH_PublicKey`), we + // set the internal point encoding flag for the key before passing it on into + // the TLS 1.2 implementation. For user-defined keypair types (e.g. to + // offload to some crypto hardware) inheriting from Botan's `ECDH_PublicKey` + // might not be feasible. Such users should consider overriding this + // ECDH-specific callback and ensure that their custom class handles the + // public point encoding as requested by `tls12_ecc_pubkey_encoding_format`. + if(auto* ecc_key = dynamic_cast(key.get())) { + ecc_key->set_point_encoding(tls12_ecc_pubkey_encoding_format); + } + + return key; +} + secure_vector TLS::Callbacks::tls_ephemeral_key_agreement( const std::variant& group, const PK_Key_Agreement_Key& private_key, @@ -367,6 +405,8 @@ // This exception means that the public key was invalid. However, // TLS' DecodeError would imply that a protocol message was invalid. throw TLS_Exception(Alert::IllegalParameter, ex.what()); + } catch(const Invalid_Argument& ex) { + throw TLS_Exception(Alert::IllegalParameter, ex.what()); } }(); @@ -381,11 +421,47 @@ // This is done within the key agreement operation and throws // an Invalid_Argument exception if the shared secret is all-zero. try { - PK_Key_Agreement ka(private_key, rng, "Raw"); + const PK_Key_Agreement ka(private_key, rng, "Raw"); return ka.derive_key(0, kex_pub_key->raw_public_key_bits()).bits_of(); } catch(const Invalid_Argument& ex) { throw TLS_Exception(Alert::IllegalParameter, ex.what()); } } +void TLS::Callbacks::tls_session_established(const Session_Summary& session) { + BOTAN_UNUSED(session); +} + +std::vector TLS::Callbacks::tls_provide_cert_status(const std::vector& chain, + const Certificate_Status_Request& csr) { + BOTAN_UNUSED(chain, csr); + return std::vector(); +} + +void TLS::Callbacks::tls_log_error(const char* err) { + BOTAN_UNUSED(err); +} + +void TLS::Callbacks::tls_log_debug(const char* what) { + BOTAN_UNUSED(what); +} + +void TLS::Callbacks::tls_log_debug_bin(const char* descr, const uint8_t val[], size_t val_len) { + BOTAN_UNUSED(descr, val, val_len); +} + +void TLS::Callbacks::tls_ssl_key_log_data(std::string_view label, + std::span client_random, + std::span secret) const { + BOTAN_UNUSED(label, client_random, secret); +} + +std::unique_ptr TLS::Callbacks::tls12_protocol_specific_kdf(std::string_view prf_algo) const { + if(prf_algo == "MD5" || prf_algo == "SHA-1") { + return KDF::create_or_throw("TLS-12-PRF(SHA-256)"); + } + + return KDF::create_or_throw(Botan::fmt("TLS-12-PRF({})", prf_algo)); +} + } // namespace Botan diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls_callbacks.h botan3-3.12.0+dfsg/src/lib/tls/tls_callbacks.h --- botan3-3.7.1+dfsg/src/lib/tls/tls_callbacks.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls_callbacks.h 2026-05-07 01:38:28.000000000 +0000 @@ -4,6 +4,7 @@ * 2016 Jack Lloyd * 2017 Harry Reimann, Rohde & Schwarz Cybersecurity * 2022 René Meusel, Rohde & Schwarz Cybersecurity +* 2025 Frederik Dornemann, CARIAD SE * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -11,18 +12,28 @@ #ifndef BOTAN_TLS_CALLBACKS_H_ #define BOTAN_TLS_CALLBACKS_H_ -#include -#include +#include +#include #include #include -#include +#include +#include #include +#include #include +#include namespace Botan { +enum class Signature_Format : uint8_t; +enum class Usage_Type : uint8_t; +class DL_Group; +class PK_Key_Agreement_Key; class Certificate_Store; class X509_Certificate; +class Public_Key; +class Private_Key; +class RandomNumberGenerator; namespace OCSP { @@ -36,12 +47,14 @@ class Policy; class Extensions; class Certificate_Status_Request; +class Session_Summary; +class Session; /** * Encapsulates the callbacks that a TLS channel will make which are due to * channel specific operations. */ -class BOTAN_PUBLIC_API(2, 0) Callbacks { +class BOTAN_PUBLIC_API(2, 0) Callbacks /* NOLINT(*-special-member-functions) */ { public: virtual ~Callbacks() = default; @@ -128,7 +141,7 @@ * * @param session the session descriptor */ - virtual void tls_session_established(const Session_Summary& session) { BOTAN_UNUSED(session); } + virtual void tls_session_established(const Session_Summary& session); /** * Optional callback: session activated @@ -256,6 +269,8 @@ * * This function should not be "const" since the implementation might need * to perform some side effecting operation to compute the result. + * + * TODO(Botan4) change return type to uint64_t */ virtual std::chrono::milliseconds tls_verify_cert_chain_ocsp_timeout() const { return std::chrono::milliseconds(0); @@ -270,13 +285,11 @@ * indicates the revocation status of the server certificate. Return an * empty vector to indicate that no response is available, and thus * suppress the Certificate_Status message. + * + * Default implementation returns an empty vector, disabling certificate status */ virtual std::vector tls_provide_cert_status(const std::vector& chain, - const Certificate_Status_Request& csr) { - BOTAN_UNUSED(chain); - BOTAN_UNUSED(csr); - return std::vector(); - } + const Certificate_Status_Request& csr); /** * Called by TLS 1.3 client or server whenever the peer indicated that @@ -343,7 +356,7 @@ * * If deserialization fails, the default implementation throws a * Botan::Decoding_Error exception that will be translated into a - * TLS_Exception with an Alert::IllegalParamter. + * TLS_Exception with an Alert::IllegalParameter. * * @param group the group identifier or (in case of TLS 1.2) an explicit * discrete-log group of the public key @@ -404,6 +417,10 @@ * * @returns the shared secret both in plaintext and encapsulated with * @p encoded_public_key. + * + * TODO(Botan4) change this return type to something else so the pubkey.h + * dependency is removed + * TODO(Botan4) change encoded_public_key to a span */ virtual KEM_Encapsulation tls_kem_encapsulate(TLS::Group_Params group, const std::vector& encoded_public_key, @@ -434,6 +451,8 @@ * * @returns the plaintext shared secret from @p encapsulated_bytes after * decapsulation with @p private_key. + * + * TODO(Botan4) change encapsulated_bytes to a std::span */ virtual secure_vector tls_kem_decapsulate(TLS::Group_Params group, const Private_Key& private_key, @@ -466,6 +485,36 @@ const std::variant& group, RandomNumberGenerator& rng); /** + * Generate an ECDH key pair for the TLS 1.2 handshake. + * + * Note that this callback is called exclusively by TLS 1.2 to handle the + * ECDH public key serialization format explicitly. TLS 1.3 fixes this + * format to 'uncompressed' and does not allow negotiating anything else. + * X25519 and X448 feature a defined and fixed public key encoding and are + * therefore not explicitly handled by this callback either. + * + * Users may override this if they want to provide a custom keypair type + * to offload TLS 1.2's ECDH handling to custom hardware, for instance. It + * is worth noting that support for compressed points in Botan is + * deprecated and this callback will disappear when it is removed in a + * future release. + * + * Typical use cases of the library don't need to do that and serious + * security risks are associated with customizing TLS's key exchange + * mechanism. + * + * @throws TLS_Exception(Alert::DecodeError) if the @p group is not known. + * + * @param group ECDH group identifier to generate an ephemeral keypair for + * @param rng a random number generator + * @param tls12_ecc_pubkey_encoding_format the key's serialization format + * + * @return an ECDH private key of an algorithm usable for key agreement + */ + virtual std::unique_ptr tls12_generate_ephemeral_ecdh_key( + TLS::Group_Params group, RandomNumberGenerator& rng, EC_Point_Format tls12_ecc_pubkey_encoding_format); + + /** * Agree on a shared secret with the peer's ephemeral public key for * the TLS handshake. * @@ -488,6 +537,8 @@ * @param policy a TLS policy object * * @return the shared secret derived from public_value and private_key + * + * TODO(Botan4) change public_value to a std::span */ virtual secure_vector tls_ephemeral_key_agreement(const std::variant& group, const PK_Key_Agreement_Key& private_key, @@ -581,6 +632,8 @@ * * @param raw_response raw OCSP response buffer * @returns the parsed OCSP response or std::nullopt on error + * + * TODO(Botan4) change raw_response to a std::span */ virtual std::optional tls_parse_ocsp_response(const std::vector& raw_response); @@ -604,30 +657,36 @@ * * Note that typical usages will not need to override this callback but it * is useful for testing purposes to allow for deterministic test outcomes. + * + * TODO(Botan4) change return type to uint64_t */ virtual std::chrono::system_clock::time_point tls_current_timestamp(); /** * Optional callback: error logging. (not currently called) * @param err An error message related to this connection. + * + * TODO(Botan4) remove this */ - virtual void tls_log_error(const char* err) { BOTAN_UNUSED(err); } + virtual void tls_log_error(const char* err); /** * Optional callback: debug logging. (not currently called) * @param what Some hopefully informative string + * + * TODO(Botan4) remove this */ - virtual void tls_log_debug(const char* what) { BOTAN_UNUSED(what); } + virtual void tls_log_debug(const char* what); /** * Optional callback: debug logging taking a buffer. (not currently called) * @param descr What this buffer is * @param val the bytes * @param val_len length of val + * + * TODO(Botan4) remove this */ - virtual void tls_log_debug_bin(const char* descr, const uint8_t val[], size_t val_len) { - BOTAN_UNUSED(descr, val, val_len); - } + virtual void tls_log_debug_bin(const char* descr, const uint8_t val[], size_t val_len); /** * Optional callback: Allows access to a connection's secret data @@ -647,9 +706,21 @@ */ virtual void tls_ssl_key_log_data(std::string_view label, std::span client_random, - std::span secret) const { - BOTAN_UNUSED(label, client_random, secret); - } + std::span secret) const; + + /** + * Returns the key derivation function to be used for TLS 1.2 + * + * The default implementation can be overridden to provide a user-defined + * key derivation function, for example to delegate key derivation to a + * hardware-protected environment when a pre-shared key must remain + * inaccessible to the non-secure world. + * + * @param prf_algo name of the hash function (e.g. "SHA-256") + * + * @return TLS 1.2 KDF implementation + */ + virtual std::unique_ptr tls12_protocol_specific_kdf(std::string_view prf_algo) const; }; } // namespace TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls_channel.h botan3-3.12.0+dfsg/src/lib/tls/tls_channel.h --- botan3-3.7.1+dfsg/src/lib/tls/tls_channel.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls_channel.h 2026-05-07 01:38:28.000000000 +0000 @@ -11,17 +11,22 @@ #ifndef BOTAN_TLS_CHANNEL_H_ #define BOTAN_TLS_CHANNEL_H_ +#include #include -#include -#include -#include -#include - +#include +#include #include #include #include #include +namespace Botan { + +class Public_Key; +class X509_Certificate; + +} // namespace Botan + namespace Botan::TLS { /** @@ -33,7 +38,14 @@ virtual ~Channel() = default; + Channel(const Channel& other) = delete; + Channel(Channel&& other) = default; + Channel& operator=(const Channel& other) = delete; + Channel& operator=(Channel&& other) = delete; + protected: + Channel() = default; + virtual size_t from_peer(std::span data) = 0; virtual void to_peer(std::span data) = 0; @@ -59,7 +71,7 @@ * Inject plaintext intended for counterparty * Throws an exception if is_active() is false */ - void send(std::string_view val) { this->send(std::span(cast_char_ptr_to_uint8(val.data()), val.size())); } + void send(std::string_view s) { this->send({reinterpret_cast(s.data()), s.size()}); } /** * Inject plaintext intended for counterparty diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls_channel_impl.h botan3-3.12.0+dfsg/src/lib/tls/tls_channel_impl.h --- botan3-3.7.1+dfsg/src/lib/tls/tls_channel_impl.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls_channel_impl.h 2026-05-07 01:38:28.000000000 +0000 @@ -11,10 +11,12 @@ #ifndef BOTAN_TLS_CHANNEL_IMPL_H_ #define BOTAN_TLS_CHANNEL_IMPL_H_ +#include #include #include +#include // TODO remove this dep +#include #include - #include #include #include @@ -29,21 +31,15 @@ class Client; class Server; -enum class Record_Type : uint8_t { - Invalid = 0, // RFC 8446 (TLS 1.3) - - ChangeCipherSpec = 20, - Alert = 21, - Handshake = 22, - ApplicationData = 23, - - Heartbeat = 24, // RFC 6520 (TLS 1.3) -}; - class Channel_Impl { public: virtual ~Channel_Impl() = default; + Channel_Impl(const Channel_Impl& other) = delete; + Channel_Impl(Channel_Impl&& other) = default; + Channel_Impl& operator=(const Channel_Impl& other) = delete; + Channel_Impl& operator=(Channel_Impl&& other) = delete; + /** * Inject TLS traffic received from counterparty * @return a hint as the how many more bytes we need to q the @@ -186,6 +182,8 @@ virtual std::string application_protocol() const = 0; protected: + Channel_Impl() = default; + /** * This struct collect all information required to perform a downgrade from TLS 1.3 to TLS 1.2. * @@ -221,7 +219,7 @@ bool will_downgrade; }; - std::unique_ptr m_downgrade_info; + std::unique_ptr m_downgrade_info; // NOLINT(*non-private-member-variable*) void preserve_peer_transcript(std::span input) { BOTAN_STATE_CHECK(m_downgrade_info); @@ -244,7 +242,7 @@ /** * Implementations use this to signal that the peer indicated a protocol * version downgrade. After calling `request_downgrade()` no further - * state changes must be perfomed by the implementation. Particularly, no + * state changes must be performed by the implementation. Particularly, no * further handshake messages must be emitted. Instead, they must yield * control flow back to the underlying Channel implementation to perform * the protocol version downgrade. diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls_ciphersuite.cpp botan3-3.12.0+dfsg/src/lib/tls/tls_ciphersuite.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls_ciphersuite.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls_ciphersuite.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -7,28 +7,22 @@ #include -#include +#include #include -#include -#include -#include #include namespace Botan::TLS { size_t Ciphersuite::nonce_bytes_from_handshake() const { switch(m_nonce_format) { - case Nonce_Format::CBC_MODE: { - if(cipher_algo() == "3DES") { - return 8; - } else { - return 16; - } - } + case Nonce_Format::CBC_MODE: + return 0; case Nonce_Format::AEAD_IMPLICIT_4: return 4; case Nonce_Format::AEAD_XOR_12: return 12; + case Nonce_Format::NULL_CIPHER: + return 0; } throw Invalid_State("In Ciphersuite::nonce_bytes_from_handshake invalid enum value"); @@ -42,6 +36,7 @@ case Nonce_Format::AEAD_IMPLICIT_4: return 8; case Nonce_Format::AEAD_XOR_12: + case Nonce_Format::NULL_CIPHER: return 0; } @@ -49,6 +44,24 @@ } bool Ciphersuite::is_scsv(uint16_t suite) { + // Both signaling cipher suite values - skip them when iterating + // negotiable ciphersuites. The two callers are: + // + // - 0x00FF: TLS_EMPTY_RENEGOTIATION_INFO_SCSV (RFC 5746). Consumed by + // Client_Hello_12::Client_Hello_12 to set secure_renegotiation when + // the renegotiation_info extension is absent. + // + // - 0x5600: TLS_FALLBACK_SCSV (RFC 7507). Recognized so it is filtered + // out of negotiation, but the inappropriate_fallback enforcement is + // intentionally not implemented: + // * Botan does not support TLS 1.0 / 1.1, so the 1.2 -> 1.0/1.1 + // fallback that SCSV was originally designed to detect cannot + // occur here. + // * The 1.3 -> 1.2 downgrade is already protected by the + // ServerHello.random sentinel (RFC 8446 4.1.3, DOWNGRADE_TLS12), + // which Botan's TLS 1.3 client enforces at + // tls_client_impl_13.cpp via random_signals_downgrade(). + // // TODO: derive from IANA file in script return (suite == 0x00FF || suite == 0x5600); } @@ -76,7 +89,11 @@ } bool Ciphersuite::cbc_ciphersuite() const { - return (mac_algo() != "AEAD"); + return (mac_algo() != "AEAD" && cipher_algo() != "NULL"); +} + +bool Ciphersuite::null_ciphersuite() const { + return (cipher_algo() == "NULL"); } bool Ciphersuite::aead_ciphersuite() const { @@ -87,6 +104,10 @@ return auth_method() != Auth_Method::IMPLICIT; } +bool Ciphersuite::is_certificate_required() const { + return signature_used() || kex_method() == Kex_Algo::STATIC_RSA; +} + std::optional Ciphersuite::by_id(uint16_t suite) { const std::vector& all_suites = all_known_ciphersuites(); auto s = std::lower_bound(all_suites.begin(), all_suites.end(), suite); @@ -101,7 +122,7 @@ std::optional Ciphersuite::from_name(std::string_view name) { const std::vector& all_suites = all_known_ciphersuites(); - for(auto suite : all_suites) { + for(const auto& suite : all_suites) { if(suite.to_string() == name) { return suite; } @@ -110,95 +131,4 @@ return std::nullopt; // some unknown ciphersuite } -namespace { - -bool have_hash(std::string_view prf) { - return (!HashFunction::providers(prf).empty()); -} - -bool have_cipher(std::string_view cipher) { - return (!BlockCipher::providers(cipher).empty()) || (!StreamCipher::providers(cipher).empty()); -} - -} // namespace - -bool Ciphersuite::is_usable() const { - if(!m_cipher_keylen) { // uninitialized object - return false; - } - - if(!have_hash(prf_algo())) { - return false; - } - -#if !defined(BOTAN_HAS_TLS_CBC) - if(cbc_ciphersuite()) - return false; -#endif - - if(mac_algo() == "AEAD") { - if(cipher_algo() == "ChaCha20Poly1305") { -#if !defined(BOTAN_HAS_AEAD_CHACHA20_POLY1305) - return false; -#endif - } else { - auto cipher_and_mode = split_on(cipher_algo(), '/'); - BOTAN_ASSERT(cipher_and_mode.size() == 2, "Expected format for AEAD algo"); - if(!have_cipher(cipher_and_mode[0])) { - return false; - } - - const auto& mode = cipher_and_mode[1]; - -#if !defined(BOTAN_HAS_AEAD_CCM) - if(mode == "CCM" || mode == "CCM-8") - return false; -#endif - -#if !defined(BOTAN_HAS_AEAD_GCM) - if(mode == "GCM") - return false; -#endif - -#if !defined(BOTAN_HAS_AEAD_OCB) - if(mode == "OCB(12)" || mode == "OCB") - return false; -#endif - - // Potentially unused if all AEADs are available - BOTAN_UNUSED(mode); - } - } else { - // Old non-AEAD schemes - if(!have_cipher(cipher_algo())) { - return false; - } - if(!have_hash(mac_algo())) { // HMAC - return false; - } - } - - if(kex_method() == Kex_Algo::ECDH || kex_method() == Kex_Algo::ECDHE_PSK) { -#if !defined(BOTAN_HAS_ECDH) - return false; -#endif - } else if(kex_method() == Kex_Algo::DH) { -#if !defined(BOTAN_HAS_DIFFIE_HELLMAN) - return false; -#endif - } - - if(auth_method() == Auth_Method::ECDSA) { -#if !defined(BOTAN_HAS_ECDSA) - return false; -#endif - } else if(auth_method() == Auth_Method::RSA) { -#if !defined(BOTAN_HAS_RSA) - return false; -#endif - } - - return true; -} - } // namespace Botan::TLS diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls_ciphersuite.h botan3-3.12.0+dfsg/src/lib/tls/tls_ciphersuite.h --- botan3-3.7.1+dfsg/src/lib/tls/tls_ciphersuite.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls_ciphersuite.h 2026-05-07 01:38:28.000000000 +0000 @@ -54,7 +54,7 @@ * e.g "RSA_WITH_RC4_128_SHA" or "ECDHE_RSA_WITH_AES_128_GCM_SHA256" * @return RFC ciphersuite string identifier */ - std::string to_string() const { return (!m_iana_id) ? "unknown cipher suite" : m_iana_id; } + std::string to_string() const { return (m_iana_id == nullptr) ? "unknown cipher suite" : m_iana_id; } /** * @return ciphersuite number @@ -77,6 +77,11 @@ bool cbc_ciphersuite() const; /** + * @return true if this suite uses a NULL cipher + */ + bool null_ciphersuite() const; + + /** * @return true if this suite uses a AEAD cipher */ bool aead_ciphersuite() const; @@ -84,6 +89,14 @@ bool signature_used() const; /** + * @return true if this ciphersuite requires the server to present + * a certificate. True for both signature-authenticated suites and + * static RSA key exchange (which uses the server's RSA cert for + * key transport). + */ + bool is_certificate_required() const; + + /** * @return key exchange algorithm used by this ciphersuite */ std::string kex_algo() const { return kex_method_to_string(kex_method()); } @@ -134,7 +147,7 @@ bool operator<(const uint16_t c) const { return ciphersuite_code() < c; } private: - bool is_usable() const; + static bool is_known_usable(uint16_t code); Ciphersuite(uint16_t ciphersuite_code, const char* iana_id, @@ -155,9 +168,8 @@ m_cipher_algo(cipher_algo), m_mac_algo(mac_algo), m_cipher_keylen(cipher_keylen), - m_mac_keylen(mac_keylen) { - m_usable = is_usable(); - } + m_mac_keylen(mac_keylen), + m_usable(is_known_usable(ciphersuite_code)) {} uint16_t m_ciphersuite_code = 0; diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls_client.cpp botan3-3.12.0+dfsg/src/lib/tls/tls_client.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls_client.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls_client.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -10,18 +10,18 @@ #include -#include -#include -#include +#include +#include +#include + +#if defined(BOTAN_HAS_TLS_12) + #include +#endif -#include #if defined(BOTAN_HAS_TLS_13) #include #endif -#include -#include - namespace Botan::TLS { /* @@ -58,15 +58,23 @@ } #endif - m_impl = std::make_unique(callbacks, - session_manager, - creds, - policy, - rng, - std::move(info), - offer_version.is_datagram_protocol(), - next_protocols, - io_buf_sz); +#if defined(BOTAN_HAS_TLS_12) + if(offer_version.is_pre_tls_13()) { + m_impl = std::make_unique(callbacks, + session_manager, + creds, + policy, + rng, + std::move(info), + offer_version.is_datagram_protocol(), + next_protocols, + io_buf_sz); + return; + } +#endif + + BOTAN_UNUSED(callbacks, session_manager, creds, policy, rng, info, offer_version, next_protocols, io_buf_sz); + throw Not_Implemented("Requested TLS version to be offered is not available in this build"); } Client::~Client() = default; @@ -74,6 +82,7 @@ size_t Client::downgrade() { BOTAN_ASSERT_NOMSG(m_impl->is_downgrading()); +#if defined(BOTAN_HAS_TLS_12) auto info = m_impl->extract_downgrade_info(); m_impl = std::make_unique(*info); @@ -85,6 +94,11 @@ // before any data was transferred return 0; } +#else + // If TLS 1.2 is not available, we will never downgrade, the downgrade info + // won't even be created and `is_downgrading()` would always return false. + BOTAN_ASSERT_UNREACHABLE(); +#endif } size_t Client::from_peer(std::span data) { diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls_client.h botan3-3.12.0+dfsg/src/lib/tls/tls_client.h --- botan3-3.7.1+dfsg/src/lib/tls/tls_client.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls_client.h 2026-05-07 01:38:28.000000000 +0000 @@ -12,15 +12,20 @@ #define BOTAN_TLS_CLIENT_H_ #include +#include // TODO(Botan4) not necessary here, remove #include -#include +#include // TODO(Botan4) not necessary here, remove +#include +#include #include #include namespace Botan::TLS { +class Callbacks; +class Session_Manager; class Channel_Impl; -class Handshake_IO; +class Policy; /** * SSL/TLS Client @@ -124,6 +129,11 @@ bool timeout_check() override; + Client(const Client& other) = delete; + Client(Client&& other) = default; + Client& operator=(const Client& other) = delete; + Client& operator=(Client&& other) = delete; + private: size_t downgrade(); diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls_exceptn.h botan3-3.12.0+dfsg/src/lib/tls/tls_exceptn.h --- botan3-3.7.1+dfsg/src/lib/tls/tls_exceptn.h 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls_exceptn.h 2026-05-07 01:38:28.000000000 +0000 @@ -20,7 +20,7 @@ public: Alert::Type type() const { return m_alert_type; } - TLS_Exception(Alert::Type type, std::string_view err_msg = "Unknown error") : + explicit TLS_Exception(Alert::Type type, std::string_view err_msg = "Unknown error") : Exception(err_msg), m_alert_type(type) {} int error_code() const noexcept override { return static_cast(m_alert_type); } diff -Nru botan3-3.7.1+dfsg/src/lib/tls/tls_extensions.cpp botan3-3.12.0+dfsg/src/lib/tls/tls_extensions.cpp --- botan3-3.7.1+dfsg/src/lib/tls/tls_extensions.cpp 2025-02-05 07:30:54.000000000 +0000 +++ botan3-3.12.0+dfsg/src/lib/tls/tls_extensions.cpp 2026-05-07 01:38:28.000000000 +0000 @@ -12,14 +12,22 @@ #include -#include -#include #include #include +#include +#include #include #include +#include +#include -#include +#if defined(BOTAN_HAS_TLS_13) + #include +#endif + +#if defined(BOTAN_HAS_TLS_12) + #include +#endif namespace Botan::TLS { @@ -34,7 +42,7 @@ const uint16_t size = static_cast(reader.remaining_bytes()); switch(code) { case Extension_Code::ServerNameIndication: - return std::make_unique(reader, size); + return std::make_unique(reader, size, from); case Extension_Code::SupportedGroups: return std::make_unique(reader, size); @@ -42,12 +50,6 @@ case Extension_Code::CertificateStatusRequest: return std::make_unique(reader, size, message_type, from); - case Extension_Code::EcPointFormats: - return std::make_unique(reader, size); - - case Extension_Code::SafeRenegotiation: - return std::make_unique(reader, size); - case Extension_Code::SignatureAlgorithms: return std::make_unique(reader, size); @@ -66,20 +68,38 @@ case Extension_Code::ServerCertificateType: return std::make_unique(reader, size, from); - case Extension_Code::ExtendedMasterSecret: - return std::make_unique(reader, size); - case Extension_Code::RecordSizeLimit: return std::make_unique(reader, size, from); + case Extension_Code::SupportedVersions: + return std::make_unique(reader, size, from); + + case Extension_Code::Padding: + break; // RFC 7685, recognized but not implemented; falls through to Unknown_Extension + +#if defined(BOTAN_HAS_TLS_12) + case Extension_Code::EcPointFormats: + return std::make_unique(reader, size); + + case Extension_Code::SafeRenegotiation: + return std::make_unique(reader, size); + + case Extension_Code::ExtendedMasterSecret: + return std::make_unique(reader, size); + case Extension_Code::EncryptThenMac: return std::make_unique(reader, size); case Extension_Code::SessionTicket: - return std::make_unique(reader, size); - - case Extension_Code::SupportedVersions: - return std::make_unique(reader, size, from); + return std::make_unique(reader, size, from); +#else + case Extension_Code::EcPointFormats: + case Extension_Code::SafeRenegotiation: + case Extension_Code::ExtendedMasterSecret: + case Extension_Code::EncryptThenMac: + case Extension_Code::SessionTicket: + break; // considered as 'unknown extension' +#endif #if defined(BOTAN_HAS_TLS_13) case Extension_Code::PresharedKey: @@ -99,6 +119,14 @@ case Extension_Code::KeyShare: return std::make_unique(reader, size, message_type); +#else + case Extension_Code::PresharedKey: + case Extension_Code::EarlyData: + case Extension_Code::Cookie: + case Extension_Code::PskKeyExchangeModes: + case Extension_Code::CertificateAuthorities: + case Extension_Code::KeyShare: + break; // considered as 'unknown extension' #endif } @@ -107,13 +135,30 @@ } // namespace +Extensions::~Extensions() = default; + +bool Extensions::has(Extension_Code type) const { + return m_extensions.contains(type); +} + +Extension* Extensions::get(Extension_Code type) const { + const auto i = m_extensions.find(type); + + if(i == m_extensions.end()) { + return nullptr; + } else { + return i->second.get(); + } +} + void Extensions::add(std::unique_ptr extn) { - if(has(extn->type())) { - throw Invalid_Argument("cannot add the same extension twice: " + - std::to_string(static_cast(extn->type()))); + const auto type = extn->type(); + if(has(type)) { + throw Invalid_Argument("cannot add the same extension twice: " + std::to_string(static_cast(type))); } - m_extensions.emplace_back(extn.release()); + m_extension_codes.push_back(type); + m_extensions.emplace(type, std::move(extn)); } void Extensions::deserialize(TLS_Data_Reader& reader, const Connection_Side from, const Handshake_Type message_type) { @@ -137,6 +182,7 @@ // TODO offer a function on reader that returns a byte range as a reference // to avoid this copy of the extension data const std::vector extn_data = reader.get_fixed(extension_size); + m_raw_extension_data[type] = extn_data; TLS_Data_Reader extn_reader("Extension", extn_data); this->add(make_extension(extn_reader, type, from, message_type)); extn_reader.assert_done(); @@ -167,31 +213,37 @@ return !diff.empty(); } -std::unique_ptr Extensions::take(Extension_Code type) { - const auto i = - std::find_if(m_extensions.begin(), m_extensions.end(), [type](const auto& ext) { return ext->type() == type; }); - - std::unique_ptr result; - if(i != m_extensions.end()) { - std::swap(result, *i); +bool Extensions::remove_extension(Extension_Code type) { + auto i = m_extensions.find(type); + + if(i == m_extensions.end()) { + return false; + } else { m_extensions.erase(i); + std::erase(m_extension_codes, type); + m_raw_extension_data.erase(type); + return true; } - - return result; } std::vector Extensions::serialize(Connection_Side whoami) const { std::vector buf(2); // 2 bytes for length field - for(const auto& extn : m_extensions) { + // Serialize in the order extensions were added, which matters for TLS 1.3 + for(const auto extn_type : m_extension_codes) { + const auto& extn = m_extensions.at(extn_type); + if(extn->empty()) { continue; } - const uint16_t extn_code = static_cast(extn->type()); + const uint16_t extn_code = static_cast(extn_type); const std::vector extn_val = extn->serialize(whoami); + // Each extension carries a uint16 length prefix. + BOTAN_ASSERT_NOMSG(extn_val.size() <= 0xFFFF); + buf.push_back(get_byte<0>(extn_code)); buf.push_back(get_byte<1>(extn_code)); @@ -201,6 +253,8 @@ buf += extn_val; } + // The outer extensions block is itself uint16-length-prefixed. + BOTAN_ASSERT_NOMSG(buf.size() - 2 <= 0xFFFF); const uint16_t extn_size = static_cast(buf.size() - 2); buf[0] = get_byte<0>(extn_size); @@ -216,13 +270,43 @@ std::set Extensions::extension_types() const { std::set offers; - std::transform( - m_extensions.cbegin(), m_extensions.cend(), std::inserter(offers, offers.begin()), [](const auto& ext) { - return ext->type(); - }); + for(const auto& [extn_type, extn] : m_extensions) { + // Consistent with serialize(): empty extensions are not placed on + // the wire so they must not appear in the "offered" set either. + if(!extn->empty()) { + offers.insert(extn_type); + } + } return offers; } +void Extensions::reorder(const std::vector& order) { + const std::set in_order(order.begin(), order.end()); + + std::vector new_codes; + new_codes.reserve(m_extension_codes.size()); + + // First: extensions not mentioned in the order (preserving their relative order) + for(auto code : m_extension_codes) { + if(!in_order.contains(code)) { + new_codes.push_back(code); + } + } + + // Then: extensions in the specified order. Deduplicate so a caller that + // accidentally lists the same code twice doesn't cause it to be + // serialized twice (which would also break peers that reject duplicate + // extension codes per RFC 8446 4.2 / RFC 5246 7.4.1.4). + std::unordered_set already_pushed; + for(auto code : order) { + if(m_extensions.contains(code) && already_pushed.insert(code).second) { + new_codes.push_back(code); + } + } + + m_extension_codes = std::move(new_codes); +} + Unknown_Extension::Unknown_Extension(Extension_Code type, TLS_Data_Reader& reader, uint16_t extension_size) : m_type(type), m_value(reader.get_fixed(extension_size)) {} @@ -230,32 +314,62 @@ return m_value; } -Server_Name_Indicator::Server_Name_Indicator(TLS_Data_Reader& reader, uint16_t extension_size) { +Server_Name_Indicator::Server_Name_Indicator(TLS_Data_Reader& reader, uint16_t extension_size, Connection_Side from) { /* - * This is used by the server to confirm that it knew the name + RFC 6066 Section 3 + + A server that receives a client hello containing the "server_name" + extension MAY use the information contained in the extension to guide + its selection of an appropriate certificate to return to the client, + and/or other aspects of security policy. In this event, the server + SHALL include an extension of type "server_name" in the (extended) + server hello. The "extension_data" field of this extension SHALL be + empty. */ - if(extension_size == 0) { - return; - } + if(from == Connection_Side::Server) { + if(extension_size != 0) { + throw TLS_Exception(Alert::IllegalParameter, "Server sent non-empty SNI extension"); + } + } else { + // Clients are required to send at least one name in the SNI + if(extension_size == 0) { + throw TLS_Exception(Alert::IllegalParameter, "Client sent empty SNI extension"); + } - uint16_t name_bytes = reader.get_uint16_t(); + const uint16_t name_bytes = reader.get_uint16_t(); - if(name_bytes + 2 != extension_size) { - throw Decoding_Error("Bad encoding of SNI extension"); - } + // RFC 6066 3: a ServerName carrying a host_name (the only NameType + // currently defined and the only one this implementation acts on) + // requires at least 1 byte name_type + 2 byte length + 1 byte HostName. + if(name_bytes + 2 != extension_size || name_bytes < 4) { + throw Decoding_Error("Bad encoding of SNI extension"); + } - while(name_bytes) { - uint8_t name_type = reader.get_byte(); - name_bytes--; + BOTAN_ASSERT_NOMSG(reader.remaining_bytes() == name_bytes); - if(name_type == 0) { - // DNS - m_sni_host_name = reader.get_string(2, 1, 65535); - name_bytes -= static_cast(2 + m_sni_host_name.size()); - } else { - // some other unknown name type, which we will ignore - reader.discard_next(name_bytes); - name_bytes = 0; + while(reader.has_remaining()) { + const uint8_t name_type = reader.get_byte(); + + if(name_type == 0) { + /* + RFC 6066 Section 3 + The ServerNameList MUST NOT contain more than one name of the same name_type. + */ + if(!m_sni_host_name.empty()) { + throw Decoding_Error("TLS ServerNameIndicator contains more than one host_name"); + } + m_sni_host_name = reader.get_string(2, 1, 65535); + } else { + /* + Unknown name type - skip its length-prefixed value and continue + + RFC 6066 Section 3 + For backward compatibility, all future data structures associated + with new NameTypes MUST begin with a 16-bit length field. + */ + const uint16_t unknown_name_len = reader.get_uint16_t(); + reader.discard_next(unknown_name_len); + } } } } @@ -271,7 +385,12 @@ std::vector buf; - size_t name_len = m_sni_host_name.size(); + const size_t name_len = m_sni_host_name.size(); + + // RFC 6066 3: HostName<1..2^16-1>; the outer ServerNameList wraps a + // 1-byte name_type and a 2-byte length so the whole entry must fit in + // a uint16_t too. + BOTAN_ASSERT_NOMSG(name_len + 3 <= 0xFFFF); buf.push_back(get_byte<0>(static_cast(name_len + 3))); buf.push_back(get_byte<1>(static_cast(name_len + 3))); @@ -280,29 +399,50 @@ buf.push_back(get_byte<0>(static_cast(name_len))); buf.push_back(get_byte<1>(static_cast(name_len))); - buf += std::make_pair(cast_char_ptr_to_uint8(m_sni_host_n