diff --git a/.cargo/audit.toml b/.cargo/audit.toml index 4437918d9a..cbfc687011 100644 --- a/.cargo/audit.toml +++ b/.cargo/audit.toml @@ -3,4 +3,9 @@ [advisories] # List of advisory IDs to ignore (extracted from deny.toml) -ignore = [] +ignore = [ + # NOTE: Must stay on scratchstack-aws-signature 0.10 for now (Feb 2026), + # which pulls ring 0.16.20. + "RUSTSEC-2025-0009", + "RUSTSEC-2025-0010", +] diff --git a/.github/scripts/nix.sh b/.github/scripts/nix.sh index d06c40886a..24e28372cc 100755 --- a/.github/scripts/nix.sh +++ b/.github/scripts/nix.sh @@ -411,6 +411,9 @@ test_command() { all) SCRIPT="$REPO_ROOT/.github/scripts/test_all.sh" ;; + xks) + SCRIPT="$REPO_ROOT/.github/scripts/test_xks.sh" + ;; wasm) SCRIPT="$REPO_ROOT/.github/scripts/test_wasm.sh" ;; @@ -483,7 +486,7 @@ test_command() { ;; *) echo "Error: Unknown test type '$TEST_TYPE'" >&2 - echo "Valid types: sqlite, mysql, percona, mariadb, psql, redis, google_cse, pykmip, otel_export, hsm [softhsm2|utimaco|proteccio|all]" >&2 + echo "Valid types: xks, sqlite, mysql, percona, mariadb, psql, redis, google_cse, pykmip, otel_export, hsm [softhsm2|utimaco|proteccio|all]" >&2 usage ;; esac @@ -497,6 +500,12 @@ test_command() { export WITH_PYTHON=1 fi + # AWS XKS curl-based test client requires extra tooling inside nix-shell + if [ "$TEST_TYPE" = "xks" ]; then + export WITH_XKS=1 + export WITH_CURL=1 + fi + KEEP_VARS=" \ --keep REDIS_HOST --keep REDIS_PORT \ --keep MYSQL_HOST --keep MYSQL_PORT \ @@ -512,6 +521,7 @@ test_command() { --keep GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY \ --keep WITH_WGET \ --keep WITH_CURL \ + --keep WITH_XKS \ --keep WITH_DOCKER \ --keep WITH_HSM \ --keep WITH_PYTHON \ @@ -548,7 +558,7 @@ sbom_command() { args+=("$1" "$2") shift 2 ;; - -h|--help) + -h | --help) args+=("$1") shift ;; diff --git a/.github/scripts/test_xks.sh b/.github/scripts/test_xks.sh new file mode 100755 index 0000000000..efb9915831 --- /dev/null +++ b/.github/scripts/test_xks.sh @@ -0,0 +1,198 @@ +#!/usr/bin/env bash +set -euo pipefail +set -x + +SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) +REPO_ROOT=$(cd "${SCRIPT_DIR}/../.." && pwd) + +source "$SCRIPT_DIR/common.sh" + +init_build_env "$@" +setup_test_logging + +require_cmd cargo "Cargo is required to build and run tests. Install Rust (rustup) and retry." +require_cmd curl "curl is required for readiness checks and the XKS test client." +require_cmd jq "jq is required by the XKS test client." +require_cmd bash "bash 4.2+ is required by the XKS test client." + +KMS_HOST="127.0.0.1" +KMS_PORT="9998" +KMS_URL="https://${KMS_HOST}:${KMS_PORT}" +KMS_PID="" +LOG_PATH="${LOG_PATH:-/tmp/kms-xks.log}" + +wait_for_kms_listen() { + local url="${KMS_URL}/kmip/2_1" + echo "Waiting for KMS to accept HTTPS connections at ${url} ..." + + for _ in {1..240}; do + if [ -n "${KMS_PID}" ] && ! kill -0 "${KMS_PID}" 2>/dev/null; then + echo "KMS process exited early. Log tail:" >&2 + tail -n 200 "${LOG_PATH}" >&2 || true + return 1 + fi + + # Any HTTP response code means the server is up enough to accept requests. + if curl -k -sS --max-time 2 -o /dev/null -w "%{http_code}" \ + -X POST "${url}" -H "Content-Type: application/json" -d '{}' 2>/dev/null | + grep -Eq '^[0-9]{3}$'; then + return 0 + fi + + sleep 0.5 + done + + echo "Timed out waiting for KMS to accept HTTPS connections." >&2 + tail -n 200 "${LOG_PATH}" >&2 || true + return 1 +} + +cleanup() { + local status=$? + + if [ -n "${KMS_PID}" ]; then + if kill -0 "${KMS_PID}" 2>/dev/null; then + kill "${KMS_PID}" 2>/dev/null || true + wait "${KMS_PID}" 2>/dev/null || true + fi + fi + + return "$status" +} +trap cleanup EXIT + +echo "=========================================" +echo "Running AWS XKS tests" +echo "Variant: ${VARIANT_NAME} | Mode: ${BUILD_PROFILE}" +echo "=========================================" + +if [ "${VARIANT}" != "non-fips" ]; then + echo "Error: AWS XKS tests require --variant non-fips (they rely on curl SigV4 + non-FIPS build flags)." >&2 + exit 1 +fi + +# Build binaries once to avoid repeated compilation in the provisioning steps. +# shellcheck disable=SC2086 +cargo build -p cosmian_kms_server $RELEASE_FLAG ${FEATURES_FLAG[@]+"${FEATURES_FLAG[@]}"} --bin cosmian_kms + +KMS_BIN="${REPO_ROOT}/target/${BUILD_PROFILE}/cosmian_kms" + +rm -f "${LOG_PATH}" + +# Use a per-run temp sqlite directory so repeated runs are stable. +SQLITE_PATH="$(mktemp -d -t kms-xks-sqlite-XXXXXX)" + +# Compose a minimal config based on test_data/aws_xks/aws_xks.toml and add a DB section. +# This keeps the documented XKS config intact while making the test hermetic. +KMS_CONF_PATH="$(mktemp -t kms-xks-conf-XXXXXX.toml)" +cat "${REPO_ROOT}/test_data/aws_xks/aws_xks.toml" >"${KMS_CONF_PATH}" +cat >>"${KMS_CONF_PATH}" <"${LOG_PATH}" 2>&1 & +KMS_PID=$! + +wait_for_kms_listen + +echo "Provisioning XKS test keys and access grants..." +cd "${REPO_ROOT}/test_data/aws_xks/scripts" + +# Shell helpers from the vendored test client. +# - utils/config.sh provides URI prefix + SigV4 credentials and key IDs +# - utils/test_config.sh provides default REGION/SCHEME values +source ./utils/config.sh +source ./utils/test_config.sh + +# Keep the same principal ARN as the vendored curl-suite. +aws_principal_arn="arn:aws:iam::123456789012:user/Alice" + +xks_create_key() { + local key_id="$1" + local request_id + request_id="$(uuidgen 2>/dev/null | tr '[:upper:]' '[:lower:]' || date +%s)" + + local json_body + json_body="$( + cat <&2 + echo "${response}" >&2 + return 1 + fi +} + +revoke_op_for_alice() { + local key_id="$1" + local op="$2" + + # Access endpoints are admin-authenticated (default user) in this test config. + local response + response="$( + curl -k -sS \ + -H "Content-Type:application/json" \ + -X POST "${KMS_URL}/access/revoke" \ + --data-binary "$( + cat <&2 + echo "${response}" >&2 + return 1 + fi +} + +# Ensure the upstream curl-suite keys exist before running any DescribeKey/Encrypt calls. +xks_create_key "aws_xks_kek" +xks_create_key "encrypt_only_key" +xks_create_key "decrypt_only_key" + +# Enforce the expected usage restrictions. +revoke_op_for_alice "encrypt_only_key" "decrypt" +revoke_op_for_alice "decrypt_only_key" "encrypt" + +echo "Running vendored AWS XKS curl-based test client..." + +# Ensure the client runs with bash 4.2+ (macOS system bash is 3.2). +BASH="$(command -v bash)" +export BASH + +./test_all diff --git a/.github/workflows/test_all.yml b/.github/workflows/test_all.yml index 0fa5b26c76..a0eaa56dd7 100644 --- a/.github/workflows/test_all.yml +++ b/.github/workflows/test_all.yml @@ -30,6 +30,7 @@ jobs: - redis - pykmip - wasm + - xks features: [fips, non-fips] exclude: # redis is exclusively for non-fips @@ -38,6 +39,9 @@ jobs: # pykmip is exclusively for non-fips since P12 is used for TLS KMS server - type: pykmip features: fips + # xks relies on curl SigV4 + non-FIPS build flags + - type: xks + features: fips steps: - name: Nix installation diff --git a/CHANGELOG.md b/CHANGELOG.md index fe8ceffd3e..847ac9e623 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to this project will be documented in this file. +## [5.17.0] - 2026-XX-XX + +### 🚀 Features + +- Added support for AWS XKS (External Key Store) + ## [5.16.1] - 2026-02-15 ### 🐛 Bug Fixes diff --git a/Cargo.lock b/Cargo.lock index c0db53c5be..e84361e995 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -46,7 +46,7 @@ dependencies = [ "actix-web", "bitflags", "bytes", - "derive_more 2.1.1", + "derive_more 2.1.0", "futures-core", "http-range", "log", @@ -73,7 +73,7 @@ dependencies = [ "brotli", "bytes", "bytestring", - "derive_more 2.1.1", + "derive_more 2.1.0", "encoding_rs", "flate2", "foldhash 0.1.5", @@ -242,7 +242,7 @@ dependencies = [ "bytestring", "cfg-if", "cookie", - "derive_more 2.1.1", + "derive_more 2.1.0", "encoding_rs", "foldhash 0.1.5", "futures-core", @@ -260,7 +260,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "smallvec", - "socket2 0.6.2", + "socket2 0.6.1", "time", "tracing", "url", @@ -307,12 +307,12 @@ dependencies = [ [[package]] name = "aes" -version = "0.9.0-rc.4" +version = "0.9.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04097e08a47d9ad181c2e1f4a5fabc9ae06ce8839a333ba9a949bcb0d31fd2a3" +checksum = "fd9e1c818b25efb32214df89b0ec22f01aa397aaeb718d1022bf0635a3bfd1a8" dependencies = [ - "cipher 0.5.0", - "cpubits", + "cfg-if", + "cipher 0.5.0-rc.2", "cpufeatures", ] @@ -348,12 +348,12 @@ dependencies = [ [[package]] name = "aes-kw" -version = "0.3.0-rc.2" +version = "0.3.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d3f56c4f20065fe12a323918242aefbbd7d85f8ce81dabfdb4b61726d0fe642" +checksum = "02eaa2d54d0fad0116e4b1efb65803ea0bf059ce970a67cd49718d87e807cb51" dependencies = [ - "aes 0.9.0-rc.4", - "const-oid 0.10.2", + "aes 0.9.0-rc.2", + "const-oid 0.10.1", ] [[package]] @@ -435,18 +435,15 @@ checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anyhow" -version = "1.0.101" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "arc-swap" -version = "1.8.1" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ded5f9a03ac8f24d1b8a25101ee812cd32cdc8c50a4c50237de2c4915850e73" -dependencies = [ - "rustversion", -] +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" [[package]] name = "argon2" @@ -478,7 +475,7 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror 2.0.18", + "thiserror 2.0.17", "time", ] @@ -507,9 +504,9 @@ dependencies = [ [[package]] name = "assert_cmd" -version = "2.1.2" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c5bcfa8749ac45dd12cb11055aeeb6b27a3895560d60d71e3c23bf979e60514" +checksum = "bcbb6924530aa9e0432442af08bbcafdad182db80d2e560da42a6d442535bf85" dependencies = [ "anstyle", "bstr", @@ -598,7 +595,7 @@ dependencies = [ "rustversion", "serde", "sync_wrapper", - "tower 0.5.3", + "tower 0.5.2", "tower-layer", "tower-service", ] @@ -664,9 +661,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.3" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "bitflags" @@ -735,9 +732,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "byteorder" @@ -768,9 +765,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.56" +version = "1.2.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +checksum = "c481bdbf0ed3b892f6f806287d72acd515b352a4ec27a208489b8c1bc839633a" dependencies = [ "find-msvc-tools", "jobserver", @@ -810,9 +807,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.43" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ "iana-time-zone", "js-sys", @@ -862,19 +859,19 @@ dependencies = [ [[package]] name = "cipher" -version = "0.5.0" +version = "0.5.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64727038c8c5e2bb503a15b9f5b9df50a1da9a33e83e1f93067d914f2c6604a5" +checksum = "155e4a260750fa4f7754649f049748aacc31db238a358d85fd721002f230f92f" dependencies = [ - "crypto-common 0.2.0", - "inout 0.2.2", + "crypto-common 0.2.0-rc.5", + "inout 0.2.1", ] [[package]] name = "clap" -version = "4.5.58" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" dependencies = [ "clap_builder", "clap_derive", @@ -882,9 +879,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.58" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" dependencies = [ "anstyle", "clap_lex", @@ -892,9 +889,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.55" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ "heck", "proc-macro2", @@ -904,9 +901,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "1.0.0" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "combine" @@ -940,9 +937,9 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "const-oid" -version = "0.10.2" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" +checksum = "0dabb6555f92fb9ee4140454eb5dcd14c7960e1225c6d1a6cc361f032947713e" [[package]] name = "const-random" @@ -959,7 +956,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "getrandom 0.2.17", + "getrandom 0.2.16", "once_cell", "tiny-keccak", ] @@ -1022,8 +1019,8 @@ dependencies = [ "base64 0.21.7", "serde", "serde_json", - "thiserror 2.0.18", - "toml", + "thiserror 2.0.17", + "toml 0.8.23", "tracing", "url", ] @@ -1058,7 +1055,7 @@ dependencies = [ "curve25519-dalek", "ed25519-dalek", "gensym", - "getrandom 0.2.17", + "getrandom 0.2.16", "leb128", "rand_chacha 0.3.1", "rand_core 0.6.4", @@ -1087,12 +1084,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44b2705be438091a343f880385c80d46ecafda93f47c95801f7cf42a54a98588" dependencies = [ "actix-web", - "derive_more 2.1.1", + "derive_more 2.1.0", "oauth2", "reqwest", "serde", "serde_json", - "thiserror 2.0.18", + "thiserror 2.0.17", "tokio", "tracing", "url", @@ -1119,7 +1116,7 @@ dependencies = [ "serde_json", "strum", "strum_macros", - "thiserror 2.0.18", + "thiserror 2.0.17", "time", "tracing", "uuid", @@ -1146,7 +1143,7 @@ dependencies = [ "lru 0.16.3", "pkcs11-sys", "rand 0.9.2", - "thiserror 2.0.18", + "thiserror 2.0.17", "uuid", "zeroize", ] @@ -1179,7 +1176,7 @@ dependencies = [ "strum", "tempfile", "test_kms_server", - "thiserror 2.0.18", + "thiserror 2.0.17", "time", "tokio", "url", @@ -1201,7 +1198,7 @@ dependencies = [ "pem", "serde", "serde_json", - "thiserror 2.0.18", + "thiserror 2.0.17", "url", ] @@ -1219,7 +1216,7 @@ dependencies = [ "serde", "serde_json", "strum", - "thiserror 2.0.18", + "thiserror 2.0.17", "time", "zeroize", ] @@ -1231,7 +1228,7 @@ dependencies = [ "base64 0.22.1", "console_error_panic_hook", "cosmian_kms_client_utils", - "getrandom 0.2.17", + "getrandom 0.2.16", "js-sys", "pem", "serde", @@ -1266,7 +1263,7 @@ dependencies = [ "serde_json", "sha2", "tempfile", - "thiserror 2.0.18", + "thiserror 2.0.17", "tokio", "uuid", "x509-parser", @@ -1282,7 +1279,7 @@ dependencies = [ "cosmian_logger", "num-bigint-dig", "serde_json", - "thiserror 2.0.18", + "thiserror 2.0.17", "zeroize", ] @@ -1313,6 +1310,7 @@ dependencies = [ "dotenvy", "futures", "hex", + "http 1.4.0", "jsonwebtoken", "native-tls", "num-bigint-dig", @@ -1323,16 +1321,17 @@ dependencies = [ "pem", "proteccio_pkcs11_loader", "reqwest", + "scratchstack-aws-signature", "serde", "serde_json", "sha2", "smartcardhsm_pkcs11_loader", "softhsm2_pkcs11_loader", "strum", - "thiserror 2.0.18", + "thiserror 2.0.17", "time", "tokio", - "toml", + "toml 0.9.8", "tracing", "url", "utimaco_pkcs11_loader", @@ -1365,7 +1364,7 @@ dependencies = [ "serde_json", "strum", "tempfile", - "thiserror 2.0.18", + "thiserror 2.0.17", "tokio", "tokio-postgres", "tokio-rusqlite", @@ -1375,9 +1374,9 @@ dependencies = [ [[package]] name = "cosmian_logger" -version = "0.5.5" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff3434d2a64271c79bb0f0b6c05d4995dd88396165b8aba92291b91ee1deb7ca" +checksum = "92e3c9c7a09cb59a839d7df9cba9fe3f781f2c46fa5b56545092acbbfd3e7aa3" dependencies = [ "opentelemetry 0.29.1", "opentelemetry-otlp 0.29.0", @@ -1385,7 +1384,7 @@ dependencies = [ "opentelemetry-stdout", "opentelemetry_sdk 0.29.0", "syslog-tracing", - "thiserror 2.0.18", + "thiserror 2.0.17", "tracing", "tracing-appender", "tracing-opentelemetry", @@ -1424,12 +1423,6 @@ dependencies = [ "redis", ] -[[package]] -name = "cpubits" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef0c543070d296ea414df2dd7625d1b24866ce206709d8a4a424f28377f5861" - [[package]] name = "cpufeatures" version = "0.2.17" @@ -1546,11 +1539,11 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.2.0" +version = "0.2.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "211f05e03c7d03754740fd9e585de910a095d6b99f8bcfffdef8319fa02a8331" +checksum = "919bd05924682a5480aec713596b9e2aabed3a0a6022fab6847f85a99e5f190a" dependencies = [ - "hybrid-array 0.4.7", + "hybrid-array 0.4.5", ] [[package]] @@ -1667,9 +1660,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.10.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] name = "deadpool" @@ -1691,7 +1684,7 @@ checksum = "3d697d376cbfa018c23eb4caab1fd1883dd9c906a8c034e8d9a3cb06a7e0bef9" dependencies = [ "async-trait", "deadpool", - "getrandom 0.2.17", + "getrandom 0.2.16", "tokio", "tokio-postgres", "tracing", @@ -1746,9 +1739,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.6" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", "serde_core", @@ -1769,18 +1762,18 @@ dependencies = [ [[package]] name = "derive_more" -version = "2.1.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +checksum = "10b768e943bed7bf2cab53df09f4bc34bfd217cdb57d971e769874c9a6710618" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "2.1.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +checksum = "6d286bfdaf75e988b4a78e013ecd79c581e06399ab53fbacd2d916c2f904f30b" dependencies = [ "convert_case 0.10.0", "proc-macro2", @@ -1965,9 +1958,9 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "find-msvc-tools" -version = "0.1.9" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] name = "flagset" @@ -1977,9 +1970,9 @@ checksum = "b7ac824320a75a52197e8f2d787f6a38b6718bb6897a35142d749af3c0e8f4fe" [[package]] name = "flate2" -version = "1.1.9" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" dependencies = [ "crc32fast", "libz-sys", @@ -2142,14 +2135,14 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.17" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] @@ -2165,19 +2158,6 @@ dependencies = [ "wasip2", ] -[[package]] -name = "getrandom" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasip2", - "wasip3", -] - [[package]] name = "ghash" version = "0.5.1" @@ -2217,7 +2197,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.13.0", + "indexmap 2.12.1", "slab", "tokio", "tokio-util", @@ -2226,9 +2206,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.13" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" dependencies = [ "atomic-waker", "bytes", @@ -2236,7 +2216,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.4.0", - "indexmap 2.13.0", + "indexmap 2.12.1", "slab", "tokio", "tokio-util", @@ -2409,9 +2389,9 @@ dependencies = [ [[package]] name = "hybrid-array" -version = "0.4.7" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1b229d73f5803b562cc26e4da0396c8610a4ee209f4fac8fa4f8d709166dc45" +checksum = "f471e0a81b2f90ffc0cb2f951ae04da57de8baa46fa99112b062a5173a5088d0" dependencies = [ "typenum", ] @@ -2426,7 +2406,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2 0.4.13", + "h2 0.4.12", "http 1.4.0", "http-body", "httparse", @@ -2486,13 +2466,14 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.20" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" dependencies = [ "base64 0.22.1", "bytes", "futures-channel", + "futures-core", "futures-util", "http 1.4.0", "http-body", @@ -2501,7 +2482,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.2", + "socket2 0.6.1", "system-configuration", "tokio", "tower-service", @@ -2511,9 +2492,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.65" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -2581,9 +2562,9 @@ checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" dependencies = [ "icu_collections", "icu_locale_core", @@ -2595,9 +2576,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" [[package]] name = "icu_provider" @@ -2614,12 +2595,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - [[package]] name = "ident_case" version = "1.0.1" @@ -2665,14 +2640,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", "hashbrown 0.16.1", - "serde", - "serde_core", ] [[package]] @@ -2686,11 +2659,11 @@ dependencies = [ [[package]] name = "inout" -version = "0.2.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +checksum = "c7357b6e7aa75618c7864ebd0634b115a7218b0615f4cb1df33ac3eca23943d4" dependencies = [ - "hybrid-array 0.4.7", + "hybrid-array 0.4.5", ] [[package]] @@ -2701,9 +2674,9 @@ checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "iri-string" -version = "0.7.10" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" dependencies = [ "memchr", "serde", @@ -2740,9 +2713,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jobserver" @@ -2771,7 +2744,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1" dependencies = [ "base64 0.22.1", - "getrandom 0.2.17", + "getrandom 0.2.16", "js-sys", "pem", "serde", @@ -2819,7 +2792,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ee7893dab2e44ae5f9d0173f26ff4aa327c10b01b06a72b52dd9405b628640d" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.12.1", ] [[package]] @@ -2842,7 +2815,7 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ - "spin", + "spin 0.9.8", ] [[package]] @@ -2851,17 +2824,11 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - [[package]] name = "libc" -version = "0.2.182" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libloading" @@ -2875,18 +2842,19 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.16" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.12" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ "bitflags", "libc", + "redox_syscall", ] [[package]] @@ -3000,9 +2968,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.0" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "mime" @@ -3054,15 +3022,15 @@ checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", "log", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "windows-sys 0.61.2", ] [[package]] name = "ml-kem" -version = "0.2.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcaee19a45f916d98f24a551cc9a2cdae705a040e66f3cbc4f3a282ea6a2e982" +checksum = "97befee0c869cb56f3118f49d0f9bb68c9e3f380dec23c1100aedc4ec3ba239a" dependencies = [ "hybrid-array 0.2.3", "kem", @@ -3086,7 +3054,7 @@ dependencies = [ "quote", "syn", "termcolor", - "thiserror 2.0.18", + "thiserror 2.0.17", ] [[package]] @@ -3111,7 +3079,7 @@ dependencies = [ "serde", "serde_json", "socket2 0.5.10", - "thiserror 2.0.18", + "thiserror 2.0.17", "tokio", "tokio-native-tls", "tokio-util", @@ -3142,15 +3110,15 @@ dependencies = [ "serde_json", "sha1", "sha2", - "thiserror 2.0.18", + "thiserror 2.0.17", "uuid", ] [[package]] name = "native-tls" -version = "0.2.15" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cdede44f9a69cab2899a2049e2c3bd49bf911a157f6a3353d4a91c61abbce44" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" dependencies = [ "libc", "log", @@ -3282,7 +3250,7 @@ checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" dependencies = [ "base64 0.22.1", "chrono", - "getrandom 0.2.17", + "getrandom 0.2.16", "http 1.4.0", "rand 0.8.5", "reqwest", @@ -3294,24 +3262,6 @@ dependencies = [ "url", ] -[[package]] -name = "objc2-core-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" -dependencies = [ - "bitflags", -] - -[[package]] -name = "objc2-system-configuration" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7216bd11cbda54ccabcab84d523dc93b858ec75ecfb3a7d89513fa22464da396" -dependencies = [ - "objc2-core-foundation", -] - [[package]] name = "oid-registry" version = "0.8.1" @@ -3407,7 +3357,7 @@ dependencies = [ "futures-sink", "js-sys", "pin-project-lite", - "thiserror 2.0.18", + "thiserror 2.0.17", "tracing", ] @@ -3458,7 +3408,7 @@ dependencies = [ "opentelemetry_sdk 0.29.0", "prost", "reqwest", - "thiserror 2.0.18", + "thiserror 2.0.17", "tokio", "tonic", "tracing", @@ -3541,7 +3491,7 @@ dependencies = [ "percent-encoding", "rand 0.9.2", "serde_json", - "thiserror 2.0.18", + "thiserror 2.0.17", "tracing", ] @@ -3736,9 +3686,9 @@ dependencies = [ [[package]] name = "postgres-protocol" -version = "0.6.10" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee9dd5fe15055d2b6806f4736aa0c9637217074e224bbec46d4041b91bb9491" +checksum = "fbef655056b916eb868048276cfd5d6a7dea4f81560dfd047f97c8c6fe3fcfd4" dependencies = [ "base64 0.22.1", "byteorder", @@ -3754,9 +3704,9 @@ dependencies = [ [[package]] name = "postgres-types" -version = "0.2.12" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b858f82211e84682fecd373f68e1ceae642d8d751a1ebd13f33de6257b3e20" +checksum = "ef4605b7c057056dd35baeb6ac0c0338e4975b1f2bef0f65da953285eb007095" dependencies = [ "bytes", "fallible-iterator 0.2.0", @@ -3792,9 +3742,9 @@ dependencies = [ [[package]] name = "predicates" -version = "3.1.4" +version = "3.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe" +checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" dependencies = [ "anstyle", "difflib", @@ -3803,30 +3753,20 @@ dependencies = [ [[package]] name = "predicates-core" -version = "1.0.10" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144" +checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" [[package]] name = "predicates-tree" -version = "1.0.13" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2" +checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" dependencies = [ "predicates-core", "termtree", ] -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn", -] - [[package]] name = "primeorder" version = "0.13.6" @@ -3842,7 +3782,7 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "toml_edit 0.23.10+spec-1.0.0", + "toml_edit 0.23.9", ] [[package]] @@ -3869,9 +3809,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] @@ -3918,9 +3858,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.44" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] @@ -3949,7 +3889,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.5", + "rand_core 0.9.3", ] [[package]] @@ -3969,7 +3909,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.5", + "rand_core 0.9.3", ] [[package]] @@ -3978,14 +3918,14 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.17", + "getrandom 0.2.16", ] [[package]] name = "rand_core" -version = "0.9.5" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ "getrandom 0.3.4", ] @@ -4016,7 +3956,7 @@ dependencies = [ "pin-project-lite", "ryu", "sha1_smol", - "socket2 0.6.2", + "socket2 0.6.1", "tokio", "tokio-util", "url", @@ -4033,9 +3973,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.3" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -4045,9 +3985,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -4056,28 +3996,28 @@ dependencies = [ [[package]] name = "regex-lite" -version = "0.1.9" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" +checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da" [[package]] name = "regex-syntax" -version = "0.8.9" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "reqwest" -version = "0.12.28" +version = "0.12.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" dependencies = [ "base64 0.22.1", "bytes", "futures-channel", "futures-core", "futures-util", - "h2 0.4.13", + "h2 0.4.12", "http 1.4.0", "http-body", "http-body-util", @@ -4097,7 +4037,7 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-native-tls", - "tower 0.5.3", + "tower 0.5.2", "tower-http", "tower-service", "url", @@ -4116,6 +4056,21 @@ dependencies = [ "subtle", ] +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin 0.5.2", + "untrusted 0.7.1", + "web-sys", + "winapi", +] + [[package]] name = "ring" version = "0.17.14" @@ -4124,9 +4079,9 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.17", + "getrandom 0.2.16", "libc", - "untrusted", + "untrusted 0.9.0", "windows-sys 0.52.0", ] @@ -4175,9 +4130,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.3" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ "bitflags", "errno", @@ -4188,9 +4143,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.36" +version = "0.23.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" dependencies = [ "once_cell", "rustls-pki-types", @@ -4201,22 +4156,22 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c" dependencies = [ "zeroize", ] [[package]] name = "rustls-webpki" -version = "0.103.9" +version = "0.103.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" dependencies = [ - "ring", + "ring 0.17.14", "rustls-pki-types", - "untrusted", + "untrusted 0.9.0", ] [[package]] @@ -4227,9 +4182,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.23" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "salsa20" @@ -4279,6 +4234,34 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scratchstack-aws-principal" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9bc5a143f43c1be0f0ea1e3093ed0ba5981ca53594eab4dcfed3d83e59158b4" +dependencies = [ + "log", +] + +[[package]] +name = "scratchstack-aws-signature" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66ca7b4792cf02b7d4e2f4e68da7cdbdc8d841c21ec3780c527507fc947a5273" +dependencies = [ + "async-trait", + "chrono", + "hex", + "http 1.4.0", + "lazy_static", + "log", + "regex", + "ring 0.16.20", + "scratchstack-aws-principal", + "subtle", + "tower 0.4.13", +] + [[package]] name = "sdd" version = "3.0.10" @@ -4371,16 +4354,16 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.12.1", "itoa", "memchr", + "ryu", "serde", "serde_core", - "zmij", ] [[package]] @@ -4403,6 +4386,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +dependencies = [ + "serde_core", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -4417,12 +4409,11 @@ dependencies = [ [[package]] name = "serial_test" -version = "3.3.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d0b343e184fc3b7bb44dff0705fffcf4b3756ba6aff420dddd8b24ca145e555" +checksum = "1b258109f244e1d6891bf1053a55d63a5cd4f8f4c30cf9a1280989f80e7a1fa9" dependencies = [ - "futures-executor", - "futures-util", + "futures", "log", "once_cell", "parking_lot", @@ -4432,9 +4423,9 @@ dependencies = [ [[package]] name = "serial_test_derive" -version = "3.3.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f50427f258fb77356e4cd4aa0e87e2bd2c66dbcee41dc405282cae2bfc26c83" +checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" dependencies = [ "proc-macro2", "quote", @@ -4496,11 +4487,10 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.8" +version = "1.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" dependencies = [ - "errno", "libc", ] @@ -4516,33 +4506,33 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "simple_asn1" -version = "0.6.4" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" +checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" dependencies = [ "num-bigint", "num-traits", - "thiserror 2.0.18", + "thiserror 2.0.17", "time", ] [[package]] name = "siphasher" -version = "1.0.2" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" -version = "0.4.12" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" @@ -4571,9 +4561,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" dependencies = [ "libc", "windows-sys 0.60.2", @@ -4588,6 +4578,12 @@ dependencies = [ "pkcs11-sys", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "spin" version = "0.9.8" @@ -4656,9 +4652,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.115" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", @@ -4698,9 +4694,9 @@ dependencies = [ [[package]] name = "system-configuration" -version = "0.7.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags", "core-foundation", @@ -4719,12 +4715,12 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.25.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", - "getrandom 0.4.1", + "getrandom 0.3.4", "once_cell", "rustix", "windows-sys 0.61.2", @@ -4771,11 +4767,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.18" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.18", + "thiserror-impl 2.0.17", ] [[package]] @@ -4791,9 +4787,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.18" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", @@ -4909,9 +4905,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.49.0" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ "bytes", "libc", @@ -4919,7 +4915,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.2", + "socket2 0.6.1", "tokio-macros", "windows-sys 0.61.2", ] @@ -4958,9 +4954,9 @@ dependencies = [ [[package]] name = "tokio-postgres" -version = "0.7.16" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcea47c8f71744367793f16c2db1f11cb859d28f436bdb4ca9193eb1f787ee42" +checksum = "2b40d66d9b2cfe04b628173409368e58247e8eddbbd3b0e6c6ba1d09f20f6c9e" dependencies = [ "async-trait", "byteorder", @@ -4976,7 +4972,7 @@ dependencies = [ "postgres-protocol", "postgres-types", "rand 0.9.2", - "socket2 0.6.2", + "socket2 0.6.1", "tokio", "tokio-util", "whoami", @@ -5005,9 +5001,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.18" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" dependencies = [ "futures-core", "pin-project-lite", @@ -5016,9 +5012,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.18" +version = "0.7.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" dependencies = [ "bytes", "futures-core", @@ -5034,11 +5030,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", - "serde_spanned", + "serde_spanned 0.6.9", "toml_datetime 0.6.11", "toml_edit 0.22.27", ] +[[package]] +name = "toml" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +dependencies = [ + "indexmap 2.12.1", + "serde_core", + "serde_spanned 1.0.4", + "toml_datetime 0.7.3", + "toml_parser", + "toml_writer", + "winnow", +] + [[package]] name = "toml_datetime" version = "0.6.11" @@ -5050,9 +5061,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.5+spec-1.1.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" dependencies = [ "serde_core", ] @@ -5063,9 +5074,9 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.12.1", "serde", - "serde_spanned", + "serde_spanned 0.6.9", "toml_datetime 0.6.11", "toml_write", "winnow", @@ -5073,21 +5084,21 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.23.10+spec-1.0.0" +version = "0.23.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +checksum = "5d7cbc3b4b49633d57a0509303158ca50de80ae32c265093b24c414705807832" dependencies = [ - "indexmap 2.13.0", - "toml_datetime 0.7.5+spec-1.1.0", + "indexmap 2.12.1", + "toml_datetime 0.7.3", "toml_parser", "winnow", ] [[package]] name = "toml_parser" -version = "1.0.8+spec-1.1.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0742ff5ff03ea7e67c8ae6c93cac239e0d9784833362da3f9a9c1da8dfefcbdc" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" dependencies = [ "winnow", ] @@ -5098,6 +5109,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +[[package]] +name = "toml_writer" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" + [[package]] name = "tonic" version = "0.12.3" @@ -5109,7 +5126,7 @@ dependencies = [ "axum", "base64 0.22.1", "bytes", - "h2 0.4.13", + "h2 0.4.12", "http 1.4.0", "http-body", "http-body-util", @@ -5150,9 +5167,9 @@ dependencies = [ [[package]] name = "tower" -version = "0.5.3" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", @@ -5165,9 +5182,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.8" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +checksum = "9cf146f99d442e8e68e585f5d798ccd3cad9a7835b917e09728880a862706456" dependencies = [ "bitflags", "bytes", @@ -5176,7 +5193,7 @@ dependencies = [ "http-body", "iri-string", "pin-project-lite", - "tower 0.5.3", + "tower 0.5.2", "tower-layer", "tower-service", ] @@ -5195,9 +5212,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.44" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" dependencies = [ "log", "pin-project-lite", @@ -5212,7 +5229,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" dependencies = [ "crossbeam-channel", - "thiserror 2.0.18", + "thiserror 2.0.17", "time", "tracing-subscriber", ] @@ -5230,9 +5247,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.36" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" dependencies = [ "once_cell", "valuable", @@ -5303,9 +5320,9 @@ checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicase" -version = "2.9.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-bidi" @@ -5315,9 +5332,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.23" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-normalization" @@ -5356,6 +5373,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "untrusted" version = "0.9.0" @@ -5364,15 +5387,14 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.8" +version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", - "serde_derive", ] [[package]] @@ -5394,7 +5416,7 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b913a3b5fe84142e269d63cc62b64319ccaf89b748fc31fe025177f767a756c4" dependencies = [ - "getrandom 0.2.17", + "getrandom 0.2.16", ] [[package]] @@ -5455,41 +5477,20 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasi" -version = "0.14.7+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" -dependencies = [ - "wasip2", -] - [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ "wit-bindgen", ] [[package]] name = "wasite" -version = "1.0.2" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66fe902b4a6b8028a753d5424909b764ccf79b7a209eac9bf97e59cda9f71a42" -dependencies = [ - "wasi 0.14.7+wasi-0.2.4", -] +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" @@ -5589,40 +5590,6 @@ version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8145dd1593bf0fb137dbfa85b8be79ec560a447298955877804640e40c2d6ea" -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap 2.13.0", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags", - "hashbrown 0.15.5", - "indexmap 2.13.0", - "semver", -] - [[package]] name = "web-sys" version = "0.3.85" @@ -5645,17 +5612,31 @@ dependencies = [ [[package]] name = "whoami" -version = "2.1.1" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6a5b12f9df4f978d2cfdb1bd3bac52433f44393342d7ee9c25f5a1c14c0f45d" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" dependencies = [ - "libc", "libredox", - "objc2-system-configuration", "wasite", "web-sys", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.11" @@ -5665,6 +5646,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.62.2" @@ -5902,91 +5889,9 @@ dependencies = [ [[package]] name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap 2.13.0", - "prettyplease", - "syn", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags", - "indexmap 2.13.0", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap 2.13.0", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "writeable" @@ -6018,9 +5923,9 @@ dependencies = [ "lazy_static", "nom", "oid-registry", - "ring", + "ring 0.17.14", "rusticata-macros", - "thiserror 2.0.18", + "thiserror 2.0.17", "time", ] @@ -6059,18 +5964,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.39" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.39" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" dependencies = [ "proc-macro2", "quote", @@ -6110,9 +6015,9 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.4.3" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", @@ -6152,12 +6057,6 @@ dependencies = [ "syn", ] -[[package]] -name = "zmij" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" - [[package]] name = "zstd" version = "0.13.3" diff --git a/Cargo.toml b/Cargo.toml index d9085c2cfe..829d4f3f3b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,8 @@ [workspace] default-members = ["crate/server"] members = [ + # Common crates + "crate/kmip", # Client crates "crate/client_utils", "crate/kms_client", @@ -16,7 +18,6 @@ members = [ "crate/hsm/utimaco", "crate/hsm/base_hsm", "crate/interfaces", - "crate/kmip", "crate/server", "crate/server_database", # Test crates @@ -102,13 +103,13 @@ categories = ["security"] [profile.release] # Deterministic build configuration for reproducible binaries # These settings ensure the same source produces identical binaries across builds -lto = "fat" # Fat LTO: maximum cross-crate optimization for smallest binaries -strip = "symbols" # Strip symbol tables for smaller binaries -opt-level = "z" # Optimize for size while maintaining performance -codegen-units = 1 # Single codegen unit: best optimization and determinism -panic = "abort" # Smaller binaries, no unwinding tables -incremental = false # Disable incremental compilation for determinism -debug = 0 # No debug info (timestamps/paths) +lto = "fat" # Fat LTO: maximum cross-crate optimization for smallest binaries +strip = "symbols" # Strip symbol tables for smaller binaries +opt-level = "z" # Optimize for size while maintaining performance +codegen-units = 1 # Single codegen unit: best optimization and determinism +panic = "abort" # Smaller binaries, no unwinding tables +incremental = false # Disable incremental compilation for determinism +debug = 0 # No debug info (timestamps/paths) [profile.dev] strip = "debuginfo" @@ -124,9 +125,17 @@ incremental = false opt-level = 0 [workspace.dependencies] +actix-cors = "0.6" +actix-files = "0.6.10" +actix-http = "3.10" +actix-identity = "0.6" actix-rt = "2.10" +actix-session = { version = "0.8" } actix-server = { version = "2.5", default-features = false } -actix-web = { version = "4.10", default-features = false } +actix-tls = "3.4" +actix-web = { version = "4.12", default-features = false } +alcoholic_jwt = "4091" +async-recursion = "1.1" async-trait = "0.1" base64 = "0.22" bitflags = "2.9" @@ -138,8 +147,11 @@ cosmian_crypto_core = { version = "11.0", default-features = false, features = [ ] } cosmian_logger = "0.5" der = { version = "0.7", default-features = false } +dotenvy = "0.15" futures = "0.3" hex = { version = "0.4", default-features = false } +http = "1.4" +jsonwebtoken = "10.3" lazy_static = "1.5" leb128 = "0.2" libloading = "0.8" @@ -157,6 +169,7 @@ pem = "3.0" pkcs11-sys = "0.2" rand = "0.9" reqwest = { version = "0.12", default-features = false } +scratchstack-aws-signature = "=0.10" # Must stay 0.10 for now (Feb 2026) serde = "1.0" serde_json = "1.0" sha2 = { version = "0.10", default-features = false } @@ -168,6 +181,7 @@ time = "0.3" tiny-keccak = "2.0" tempfile = "3.19" tokio = { version = "1.44", default-features = false } +toml = "0.9" tracing = "0.1" url = "2.5" uuid = "=1.11.1" diff --git a/crate/cli/src/actions/kms/access.rs b/crate/cli/src/actions/kms/access.rs index 2574416378..2f48210b58 100644 --- a/crate/cli/src/actions/kms/access.rs +++ b/crate/cli/src/actions/kms/access.rs @@ -70,7 +70,7 @@ pub struct GrantAccess { #[clap(long, short = 'i')] pub object_uid: Option, - /// The operations to grant (`create`, `get`, `encrypt`, `decrypt`, `import`, `revoke`, `locate`, `rekey`, `destroy`) + /// The operations to grant (`create`, `get`, `encrypt`, `decrypt`, `import`, `revoke`, `locate`, `rekey`, `destroy`, `get_attributes`) #[clap(required = true)] pub operations: Vec, } diff --git a/crate/cli/src/tests/kms/aws_xks_tests.rs b/crate/cli/src/tests/kms/aws_xks_tests.rs new file mode 100644 index 0000000000..f86d300db1 --- /dev/null +++ b/crate/cli/src/tests/kms/aws_xks_tests.rs @@ -0,0 +1,106 @@ +use crate::{ + actions::kms::{access::GrantAccess, shared::ImportSecretDataOrKeyAction}, + error::result::KmsCliResult, +}; +use cosmian_aws_structs::health_status::{self, GetHealthStatusResponse}; +use cosmian_kmip::kmip_2_1::KmipOperation; +use cosmian_kms_client::reexport::cosmian_kms_client_utils::import_utils::ImportKeyFormat; +use cosmian_logger::{info, log_init}; +use std::{fs, path::PathBuf}; +use tempfile::TempDir; +use test_kms_server::{ + MainDBConfig, + reexport::cosmian_kms_server::{ + config::{ClapConfig, SocketServerConfig, TlsConfig}, + routes::aws_xks::AwsXksConfig, + }, + start_test_kms_server_with_config, +}; + +const KEK_USER: &str = "KEK_USER"; +const ACCESS_KEY_ID: &str = "AKIAIOSFODNN7EXAMPLE"; +const ACCESS_KEY: &str = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"; + +#[tokio::test] +pub(super) async fn test_aws_xks() -> KmsCliResult<()> { + log_init(Some( + "info,cosmian_kms_server=debug,cosmian_kms_server_database=info", + )); + + // plaintext no auth + info!("==> Testing AWS XKS"); + let ctx = start_test_kms_server_with_config(ClapConfig { + socket_server: SocketServerConfig { + socket_server_start: true, + ..Default::default() + }, + tls: TlsConfig { + tls_p12_file: Some(PathBuf::from( + "../../test_data/certificates/client_server/server/kmserver.acme.com.p12", + )), + tls_p12_password: Some("password".to_owned()), + clients_ca_cert_file: Some(PathBuf::from( + "../../test_data/certificates/client_server/ca/ca.crt", + )), + tls_cipher_suites: None, + }, + db: MainDBConfig { + database_type: Some("sqlite".to_owned()), + ..Default::default() + }, + kms_public_url: None, + aws_xks_config: AwsXksConfig { + aws_xks_enable: true, + aws_xks_region: Some("us-east-1".to_owned()), + aws_xks_service: Some("xks-kms".to_owned()), + aws_xks_sigv4_access_key_id: Some(ACCESS_KEY_ID.to_owned()), + aws_xks_sigv4_secret_access_key: Some(ACCESS_KEY.to_owned()), + aws_xks_kek_user: Some(KEK_USER.to_owned()), + }, + ..Default::default() + }) + .await; + + // Create a temporary file to hold the access key + let tmp_dir = TempDir::new()?; + let tmp_path = tmp_dir.path(); + let tmp_file = tmp_path.join("access_key.key"); + fs::write(&tmp_file, ACCESS_KEY.as_bytes())?; + + // Import the AWS Key + ImportSecretDataOrKeyAction { + key_file: tmp_file, + key_id: Some(ACCESS_KEY_ID.to_owned()), + key_format: ImportKeyFormat::Aes, + replace_existing: true, + ..Default::default() + } + .run(ctx.get_owner_client()) + .await?; + + GrantAccess { + object_uid: Some(ACCESS_KEY_ID.to_owned()), + user: KEK_USER.to_owned(), + operations: vec![KmipOperation::Get], + } + .run(ctx.get_owner_client()) + .await?; + + let health_status_req = health_status::GetHealthStatusRequest { + requestMetadata: health_status::RequestMetadata { + kmsRequestId: "123e4567-e89b-12d3-a456-426614174000".to_owned(), + kmsOperation: "KmsHealthCheck".to_owned(), + }, + }; + + let health_status_response: GetHealthStatusResponse = ctx + .get_owner_client() + .post_no_ttlv("/aws/kms/xks/v1/health", Some(&health_status_req)) + .await?; + info!( + "AWS XKS GetHealthStatus response: fleet size {} model {}", + health_status_response.xksProxyFleetSize, health_status_response.xksProxyModel + ); + + Ok(()) +} diff --git a/crate/server/Cargo.toml b/crate/server/Cargo.toml index 00530814af..a55d9a040b 100644 --- a/crate/server/Cargo.toml +++ b/crate/server/Cargo.toml @@ -50,15 +50,15 @@ non-fips = [ interop = ["cosmian_kms_server_database/interop"] [dependencies] -actix-cors = "0.6" -actix-files = "0.6.10" -actix-identity = "0.6" +actix-cors = { workspace = true } +actix-files = { workspace = true } +actix-identity = { workspace = true } actix-rt = { workspace = true } -actix-session = { version = "0.8", features = ["cookie-session"] } -actix-tls = "3.4" +actix-session = { workspace = true, features = ["cookie-session"] } +actix-tls = { workspace = true } actix-web = { workspace = true, features = ["macros", "openssl"] } -alcoholic_jwt = "4091" -async-recursion = "1.1" +alcoholic_jwt = { workspace = true } +async-recursion = { workspace = true } base64 = { workspace = true } chrono = { workspace = true } clap = { workspace = true, features = [ @@ -75,10 +75,11 @@ cosmian_kms_base_hsm = { path = "../hsm/base_hsm", version = "5.16.1" } cosmian_kms_server_database = { path = "../server_database", version = "5.16.1" } cosmian_logger = { workspace = true, features = ["full"] } crypt2pay_pkcs11_loader = { path = "../hsm/crypt2pay", version = "5.16.1" } -dotenvy = "0.15" +dotenvy = {workspace = true } futures = { workspace = true } hex = { workspace = true, features = ["serde"] } -jsonwebtoken = "10.3" +http = { workspace = true } +jsonwebtoken = { workspace = true } num-bigint-dig = { workspace = true, features = [ "std", "rand", @@ -97,6 +98,7 @@ reqwest = { workspace = true, features = [ "native-tls", "socks", ] } +scratchstack-aws-signature = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } softhsm2_pkcs11_loader = { path = "../hsm/softhsm2", version = "5.16.1" } @@ -105,7 +107,7 @@ strum = { workspace = true, features = ["std", "derive", "strum_macros"] } thiserror = { workspace = true } time = { workspace = true, features = ["local-offset", "formatting"] } tokio = { workspace = true, features = ["full"] } -toml = "0.8" +toml = { workspace = true } tracing = { workspace = true } url = { workspace = true } utimaco_pkcs11_loader = { path = "../hsm/utimaco", version = "5.16.1" } @@ -114,14 +116,14 @@ x509-parser = { workspace = true } zeroize = { workspace = true } [dev-dependencies] -actix-http = "3.10" +actix-http = { workspace = true } cosmian_kms_client_utils = { path = "../client_utils", version = "5.16.1" } cosmian_kms_interfaces = { path = "../interfaces", version = "5.16.1" } native-tls = { workspace = true } pem = { workspace = true } [build-dependencies] -actix-http = "3.10" +actix-http = { workspace = true } time = { workspace = true, features = ["local-offset", "formatting"] } sha2 = { workspace = true } diff --git a/crate/server/src/config/command_line/clap_config.rs b/crate/server/src/config/command_line/clap_config.rs index 5da8a37052..59d3744f72 100644 --- a/crate/server/src/config/command_line/clap_config.rs +++ b/crate/server/src/config/command_line/clap_config.rs @@ -14,6 +14,7 @@ use crate::{ config::{ProxyConfig, SocketServerConfig, TlsConfig}, error::KmsError, result::KResult, + routes::aws_xks::AwsXksConfig, }; #[cfg(not(target_os = "windows"))] @@ -59,6 +60,7 @@ impl Default for ClapConfig { default_unwrap_type: None, non_revocable_key_id: None, privileged_users: None, + aws_xks_config: AwsXksConfig::default(), kmip_policy: KmipPolicyConfig::default(), } } @@ -164,6 +166,9 @@ pub struct ClapConfig { #[clap(long, verbatim_doc_comment)] pub privileged_users: Option>, + #[clap(flatten)] + pub aws_xks_config: AwsXksConfig, + /// KMIP algorithm policy. /// /// This policy is configured via parameter-specific allowlists under `[kmip.allowlists]`. @@ -362,6 +367,23 @@ impl fmt::Debug for ClapConfig { let x = x.field("default unwrap type", &self.default_unwrap_type); let x = x.field("non_revocable_key_id", &self.non_revocable_key_id); let x = x.field("privileged_users", &self.privileged_users); + + let x = x.field("aws_xks_config", &self.aws_xks_config); + let x = if self.aws_xks_config.aws_xks_enable { + x.field("aws_xks_enable", &self.aws_xks_config.aws_xks_enable) + .field("aws_xks_region", &self.aws_xks_config.aws_xks_region) + .field("aws_xks_service", &self.aws_xks_config.aws_xks_service) + .field( + "aws_xks_sigv4_access_key_id", + &self.aws_xks_config.aws_xks_sigv4_access_key_id, + ) + .field( + "aws_xks_sigv4_secret_access_key", + &self.aws_xks_config.aws_xks_sigv4_secret_access_key, + ) + } else { + x.field("aws_xks_enable", &self.aws_xks_config.aws_xks_enable) + }; let x = x.field("kmip", &self.kmip_policy); x.finish() diff --git a/crate/server/src/config/command_line/tls_config.rs b/crate/server/src/config/command_line/tls_config.rs index 58a40567d0..4c5bfa8566 100644 --- a/crate/server/src/config/command_line/tls_config.rs +++ b/crate/server/src/config/command_line/tls_config.rs @@ -7,11 +7,16 @@ use serde::{Deserialize, Serialize}; #[serde(default)] #[derive(Default)] pub struct TlsConfig { - /// The KMS server optional PKCS#12 Certificates and Key file. - /// Mandatory when starting the socket server. + /// The KMS server optional PKCS#12 Certificates and Key file as an alternative + /// to providing the key, certificate and chain in PEM format. /// When provided, the Socket and HTTP server will start in TLS Mode. #[cfg(feature = "non-fips")] - #[clap(long, env = "KMS_TLS_P12_FILE", verbatim_doc_comment)] + #[clap( + long, + env = "KMS_TLS_P12_FILE", + requires = "tls_p12_password", + verbatim_doc_comment + )] pub tls_p12_file: Option, /// The password to open the PKCS#12 Certificates and Key file @@ -20,22 +25,22 @@ pub struct TlsConfig { pub tls_p12_password: Option, /// The server's X.509 certificate in PEM format. - /// Only used in FIPS mode (default build). Provide a PEM containing the server leaf certificate, + /// Provide a PEM containing the server leaf certificate, /// optionally followed by intermediate certificates (full chain). When provided along with /// `--tls-key-file`, the servers will start in TLS mode. - #[cfg(not(feature = "non-fips"))] + /// Do not use in combination with `--tls-p12-file`. #[clap(long, env = "KMS_TLS_CERT_FILE", verbatim_doc_comment)] pub tls_cert_file: Option, /// The server's private key in PEM format (PKCS#8 or traditional format). - /// Only used in FIPS mode (default build). Must correspond to the certificate in `--tls-cert-file`. - #[cfg(not(feature = "non-fips"))] + /// Must correspond to the certificate in `--tls-cert-file`. + /// Do not use in combination with `--tls-p12-file`. #[clap(long, env = "KMS_TLS_KEY_FILE", verbatim_doc_comment)] pub tls_key_file: Option, /// Optional certificate chain in PEM format (intermediate CAs). - /// Only used in FIPS mode. If not provided, the chain may be appended to `--tls-cert-file` instead. - #[cfg(not(feature = "non-fips"))] + /// If not provided, the chain may be appended to `--tls-cert-file` instead. + /// Do not use in combination with `--tls-p12-file`. #[clap(long, env = "KMS_TLS_CHAIN_FILE", verbatim_doc_comment)] pub tls_chain_file: Option, @@ -83,7 +88,6 @@ impl Display for TlsConfig { ); } } - #[cfg(not(feature = "non-fips"))] { if self.tls_cert_file.is_some() && self.tls_key_file.is_some() { return write!( diff --git a/crate/server/src/config/params/server_params.rs b/crate/server/src/config/params/server_params.rs index 06af533b95..ea5ad2ec48 100644 --- a/crate/server/src/config/params/server_params.rs +++ b/crate/server/src/config/params/server_params.rs @@ -16,6 +16,7 @@ use crate::{ }, error::KmsError, result::{KResult, KResultHelper}, + routes::aws_xks::AwsXksParams, }; /// This structure is the context used by the server @@ -125,6 +126,9 @@ pub struct ServerParams { /// If None, all users can create and grant create access rights. pub privileged_users: Option>, + /// AWS XKS parameters, if any + pub aws_xks_params: Option, + /// KMIP algorithm policy. pub kmip_policy: KmipPolicyParams, } @@ -321,6 +325,11 @@ impl ServerParams { ui_session_salt: conf.ui_config.ui_session_salt, proxy_params: ProxyParams::try_from(&conf.proxy) .context("failed to create ProxyParams")?, + aws_xks_params: if conf.aws_xks_config.aws_xks_enable { + Some(conf.aws_xks_config.try_into()?) + } else { + None + }, kmip_policy: KmipPolicyParams { policy_id: kmip_policy_id, allowlists: KmipAllowlistsParams { @@ -337,6 +346,7 @@ impl ServerParams { }, }, }; + debug!("{res:#?}"); Ok(res) @@ -444,6 +454,19 @@ impl fmt::Debug for ServerParams { debug_struct.field("google_cse_enable", &self.google_cse.google_cse_enable); } + if let Some(aws_xks_params) = &self.aws_xks_params { + debug_struct + .field("aws_xks_params", &"configured") + .field("aws_xks_region", &aws_xks_params.region) + .field("aws_xks_service", &aws_xks_params.service) + .field( + "aws_xks_sigv4_access_key_id", + &aws_xks_params.sigv4_access_key_id, + ); + } else { + debug_struct.field("aws_xks_params", &"not configured"); + } + if self.hsm_model.is_some() { debug_struct .field("hsm_admin", &self.hsm_admin) diff --git a/crate/server/src/config/params/tls_params.rs b/crate/server/src/config/params/tls_params.rs index 5f23ae5186..605e9297a5 100644 --- a/crate/server/src/config/params/tls_params.rs +++ b/crate/server/src/config/params/tls_params.rs @@ -15,18 +15,16 @@ use crate::{ }; /// The TLS parameters of the API server +#[derive(Default)] pub struct TlsParams { /// The TLS private key and certificate of the HTTP server and Socket server (PKCS#12) #[cfg(feature = "non-fips")] - pub p12: ParsedPkcs12_2, + pub p12: Option, /// The server certificate in PEM (may include chain) - FIPS mode - #[cfg(not(feature = "non-fips"))] pub server_cert_pem: Vec, /// The server private key in PEM - FIPS mode - #[cfg(not(feature = "non-fips"))] pub server_key_pem: Vec, /// Optional separate chain PEM (intermediate CAs) - FIPS mode - #[cfg(not(feature = "non-fips"))] pub server_chain_pem: Option>, /// The certificate used to verify the client TLS certificates /// used for authentication in PEM format @@ -52,15 +50,31 @@ impl TlsParams { /// This function can return an error if there is an issue reading the PKCS#12 file or parsing it. pub fn try_from(config: &TlsConfig) -> KResult> { debug!("tls_config: {config:#?}"); + let clients_ca_cert_pem = + if let Some(authority_cert_file) = config.clients_ca_cert_file.as_ref() { + Some(std::fs::read(authority_cert_file).context(&format!( + "TLS configuration. Failed opening authority cert file at {:?}", + authority_cert_file.display() + ))?) + } else { + None + }; + let cipher_suites = config.tls_cipher_suites.clone(); + #[cfg(feature = "non-fips")] - let p12 = if let (Some(p12_file), Some(p12_password)) = + if let (Some(p12_file), Some(p12_password)) = (&config.tls_p12_file, &config.tls_p12_password) { - open_p12(p12_file, p12_password)? - } else { - return Ok(None); - }; - #[cfg(not(feature = "non-fips"))] + let p12 = open_p12(p12_file, p12_password)?; + return Ok(Some(Self { + p12: Some(p12), + clients_ca_cert_pem, + cipher_suites, + ..Default::default() + })); + } + + // This can be used both in FIPS and non-FIPS mode let (server_cert_pem, server_key_pem, server_chain_pem) = if let (Some(cert), Some(key)) = (&config.tls_cert_file, &config.tls_key_file) { ( @@ -77,39 +91,16 @@ impl TlsParams { } else { return Ok(None); }; - debug!( - "Client Authority cert file: {:?}", - config.clients_ca_cert_file - ); - let clients_ca_cert_pem = - if let Some(authority_cert_file) = config.clients_ca_cert_file.as_ref() { - Some(std::fs::read(authority_cert_file).context(&format!( - "TLS configuration. Failed opening authority cert file at {:?}", - authority_cert_file.display() - ))?) - } else { - None - }; - let cipher_suites = config.tls_cipher_suites.clone(); - #[cfg(feature = "non-fips")] - { - Ok(Some(Self { - p12, - clients_ca_cert_pem, - cipher_suites, - })) - } - #[cfg(not(feature = "non-fips"))] - { - Ok(Some(Self { - server_cert_pem, - server_key_pem, - server_chain_pem, - clients_ca_cert_pem, - cipher_suites, - })) - } + Ok(Some(Self { + server_cert_pem, + server_key_pem, + server_chain_pem, + clients_ca_cert_pem, + cipher_suites, + #[cfg(feature = "non-fips")] + p12: None, + })) } } @@ -144,35 +135,38 @@ impl fmt::Debug for TlsParams { |cipher_string| format!("Custom cipher string: {cipher_string}"), ); - #[cfg(feature = "non-fips")] - { - f.debug_struct("TlsParams") - .field( - "p12", - &self.p12.cert.as_ref().map_or_else( - || "[N/A]".to_owned(), - |cert| format!("{:?}", cert.subject_name()), - ), - ) - .field("authority_cert_file: ", &ca_cert) - .field("cipher_suites: ", &cipher_suites) - .finish() - } #[cfg(not(feature = "non-fips"))] + let mut ds = f.debug_struct("TlsParams"); + + #[cfg(feature = "non-fips")] + let mut ds = &mut f.debug_struct("TlsParams"); + + #[cfg(feature = "non-fips")] { - f.debug_struct("TlsParams") - .field("server_cert_pem", &"[PEM provided]") - .field("server_key_pem", &"[PEM provided]") - .field( - "server_chain_pem", - &self - .server_chain_pem - .as_ref() - .map_or("[N/A]", |_| "[PEM provided]"), - ) - .field("authority_cert_file: ", &ca_cert) - .field("cipher_suites: ", &cipher_suites) - .finish() + ds = ds.field( + "p12", + &self.p12.as_ref().map_or_else( + || "[N/A]".to_owned(), + |p12| { + p12.cert.as_ref().map_or_else( + || "[N/A]".to_owned(), + |cert| format!("{:?}", cert.subject_name()), + ) + }, + ), + ); } + ds.field("server_cert_pem", &"[PEM provided]") + .field("server_key_pem", &"[PEM provided]") + .field( + "server_chain_pem", + &self + .server_chain_pem + .as_ref() + .map_or("[N/A]", |_| "[PEM provided]"), + ) + .field("authority_cert_file: ", &ca_cert) + .field("cipher_suites: ", &cipher_suites) + .finish() } } diff --git a/crate/server/src/core/operations/decrypt.rs b/crate/server/src/core/operations/decrypt.rs index 5a445493cc..911f9c1e90 100644 --- a/crate/server/src/core/operations/decrypt.rs +++ b/crate/server/src/core/operations/decrypt.rs @@ -84,6 +84,7 @@ pub(crate) async fn decrypt(kms: &KMS, request: Decrypt, user: &str) -> KResult< // for each uid. This is also based on the high probability that there is still a single object // in the candidates' list. let mut selected_owm = None; + let mut found_but_no_permission = false; for uid in uids { if let Some(prefix) = has_prefix(&uid) { if !kms.database.is_object_owned_by(&uid, user).await? { @@ -106,10 +107,7 @@ pub(crate) async fn decrypt(kms: &KMS, request: Decrypt, user: &str) -> KResult< // Default database let owm = kms.database.retrieve_object(&uid).await?.ok_or_else(|| { debug!("failed to retrieve the key: {uid}"); - KmsError::Kmip21Error( - ErrorReason::Item_Not_Found, - format!("Decrypt: failed to retrieve the key: {uid}"), - ) + KmsError::ItemNotFound(format!("Decrypt: failed to retrieve the key: {uid}")) })?; // Check effective state (PreActive with past activation_date counts as Active) if get_effective_state(&owm)? != State::Active { @@ -137,6 +135,7 @@ pub(crate) async fn decrypt(kms: &KMS, request: Decrypt, user: &str) -> KResult< .any(|p| [KmipOperation::Decrypt, KmipOperation::Get].contains(p)) { debug!("{user} is not authorized to decrypt using: {uid}"); + found_but_no_permission = true; continue; } } @@ -161,10 +160,13 @@ pub(crate) async fn decrypt(kms: &KMS, request: Decrypt, user: &str) -> KResult< } } let mut owm = selected_owm.ok_or_else(|| { - KmsError::Kmip21Error( - ErrorReason::Item_Not_Found, - format!("Decrypt: no valid key for id: {unique_identifier}"), - ) + if found_but_no_permission { + KmsError::Unauthorized(format!( + "Decrypt: the user {user} does not have the permission to decrypt using the key: {unique_identifier}" + )) + } else { + KmsError::ItemNotFound(format!("Decrypt: key id: {unique_identifier}, not found")) + } })?; // Enforce time window constraints for Decrypt mirroring Encrypt semantics: deny usage when diff --git a/crate/server/src/core/operations/encrypt.rs b/crate/server/src/core/operations/encrypt.rs index 308808ca98..ee08c3ee61 100644 --- a/crate/server/src/core/operations/encrypt.rs +++ b/crate/server/src/core/operations/encrypt.rs @@ -97,6 +97,7 @@ pub(crate) async fn encrypt(kms: &KMS, request: Encrypt, user: &str) -> KResult< // in the candidate list. let mut selected_owm = None; + let mut found_but_no_permission = false; for uid in uids { if let Some(prefix) = has_prefix(&uid) { if !kms.database.is_object_owned_by(&uid, user).await? { @@ -115,7 +116,7 @@ pub(crate) async fn encrypt(kms: &KMS, request: Encrypt, user: &str) -> KResult< return encrypt_using_encryption_oracle(kms, &request, data, &uid, prefix).await; } let owm = kms.database.retrieve_object(&uid).await?.ok_or_else(|| { - KmsError::InvalidRequest(format!("Encrypt: failed to retrieve key: {uid}")) + KmsError::ItemNotFound(format!("Encrypt: failed to retrieve key: {uid}")) })?; // Check effective state (PreActive with past activation_date counts as Active) if get_effective_state(&owm)? != State::Active { @@ -131,6 +132,7 @@ pub(crate) async fn encrypt(kms: &KMS, request: Encrypt, user: &str) -> KResult< .iter() .any(|p| [KmipOperation::Encrypt, KmipOperation::Get].contains(p)) { + found_but_no_permission = true; continue; } } @@ -156,10 +158,13 @@ pub(crate) async fn encrypt(kms: &KMS, request: Encrypt, user: &str) -> KResult< } } let mut owm = selected_owm.ok_or_else(|| { - KmsError::Kmip21Error( - ErrorReason::Item_Not_Found, - format!("Encrypt: no valid key for id: {unique_identifier}"), - ) + if found_but_no_permission { + KmsError::Unauthorized(format!( + "Encrypt: the user {user} does not have permission to encrypt using the key: {unique_identifier}" + )) + } else { + KmsError::ItemNotFound(format!("Encrypt: key id: {unique_identifier}, not found")) + } })?; // Enforce time window constraints: Active key is unusable for Encrypt if current time is diff --git a/crate/server/src/core/operations/message.rs b/crate/server/src/core/operations/message.rs index e128431afc..780439f7be 100644 --- a/crate/server/src/core/operations/message.rs +++ b/crate/server/src/core/operations/message.rs @@ -132,6 +132,24 @@ pub(crate) async fn message( Some(error_message), None, ), + Err(KmsError::ItemNotFound(error_message)) => ( + ResultStatusEnumeration::OperationFailed, + Some(ErrorReason::Item_Not_Found), + Some(error_message), + None, + ), + Err(KmsError::CryptographicError(error_message)) => ( + ResultStatusEnumeration::OperationFailed, + Some(ErrorReason::Cryptographic_Failure), + Some(error_message), + None, + ), + Err(KmsError::Unauthorized(error_message)) => ( + ResultStatusEnumeration::OperationFailed, + Some(ErrorReason::Permission_Denied), + Some(error_message), + None, + ), Err(err) => ( ResultStatusEnumeration::OperationFailed, Some(ErrorReason::Operation_Not_Supported), diff --git a/crate/server/src/core/retrieve_object_utils.rs b/crate/server/src/core/retrieve_object_utils.rs index 509e7faedf..e82151d485 100644 --- a/crate/server/src/core/retrieve_object_utils.rs +++ b/crate/server/src/core/retrieve_object_utils.rs @@ -147,6 +147,10 @@ pub(crate) async fn retrieve_object_for_operation( return Ok(owm); } + trace!( + "User {user} does not have permission for operation {operation_type:?} on object {}", + owm.id() + ); } Err(KmsError::Kmip21Error( diff --git a/crate/server/src/main.rs b/crate/server/src/main.rs index 8caddd145e..ca5f6ad173 100644 --- a/crate/server/src/main.rs +++ b/crate/server/src/main.rs @@ -146,10 +146,13 @@ async fn run() -> KResult<()> { mod tests { use std::path::PathBuf; - use cosmian_kms_server::config::{ - ClapConfig, GoogleCseConfig, HttpConfig, IdpAuthConfig, KmipPolicyConfig, LoggingConfig, - MainDBConfig, OidcConfig, ProxyConfig, SocketServerConfig, TlsConfig, UiConfig, - WorkspaceConfig, + use cosmian_kms_server::{ + config::{ + ClapConfig, GoogleCseConfig, HttpConfig, IdpAuthConfig, KmipPolicyConfig, + LoggingConfig, MainDBConfig, OidcConfig, ProxyConfig, SocketServerConfig, TlsConfig, + UiConfig, WorkspaceConfig, + }, + routes::aws_xks::AwsXksConfig, }; #[cfg(feature = "non-fips")] @@ -178,6 +181,7 @@ mod tests { tls_p12_password: Some("[tls p12 password]".to_owned()), clients_ca_cert_file: Some(PathBuf::from("[authority cert file]")), tls_cipher_suites: Some("TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256".to_owned()), + ..Default::default() }, http: HttpConfig { port: 443, @@ -244,6 +248,15 @@ mod tests { hsm_slot: vec![], hsm_password: vec![], }, + aws_xks_config: AwsXksConfig { + aws_xks_enable: true, + aws_xks_region: Some("us-east-1".to_owned()), + aws_xks_service: Some("xks-kms".to_owned()), + aws_xks_sigv4_access_key_id: Some("AKIAIOSFODNN7EXAMPLE".to_owned()), + aws_xks_sigv4_secret_access_key: Some( + "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY".to_owned(), + ), + }, key_encryption_key: Some("key wrapping key".to_owned()), default_unwrap_type: None, non_revocable_key_id: None, @@ -322,6 +335,14 @@ rolling_log_name = "kms_log" enable_metering = false environment = "development" ansi_colors = false + +[aws_xks_config] +aws_xks_enable = true +aws_xks_region = "us-east-1" +aws_xks_service = "xks-kms" +aws_xks_sigv4_access_key_id = "AKIAIOSFODNN7EXAMPLE" +aws_xks_sigv4_secret_access_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" +aws_xks_kek_user = "kek_user" "#; assert_eq!(toml_string.trim(), toml::to_string(&config).unwrap().trim()); diff --git a/crate/server/src/routes/aws_xks/README.md b/crate/server/src/routes/aws_xks/README.md new file mode 100644 index 0000000000..e40f76c5dd --- /dev/null +++ b/crate/server/src/routes/aws_xks/README.md @@ -0,0 +1,9 @@ +# AWS XKS + +Specs: + +Code loosely inspired from (License Apache 2.0) + +## Testing + +Follow the instructions in `test_data/aws_xks/README.md` to run the AWS XKS tests. \ No newline at end of file diff --git a/crate/server/src/routes/aws_xks/aws_xks_config.rs b/crate/server/src/routes/aws_xks/aws_xks_config.rs new file mode 100644 index 0000000000..c91872b38f --- /dev/null +++ b/crate/server/src/routes/aws_xks/aws_xks_config.rs @@ -0,0 +1,43 @@ +use clap::Args; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Args, Deserialize, Serialize, Clone)] +#[serde(default)] +#[derive(Default)] +#[allow(clippy::struct_field_names)] +pub struct AwsXksConfig { + /// This setting turns on endpoints handling the AWS XKS feature + #[clap(long, env = "KMS_AWS_XKS_ENABLE", default_value = "false")] + pub aws_xks_enable: bool, + + /// The AWS XKS region to use for signing requests (sigv4) + #[clap( + long, + env = "KMS_AWS_XKS_REGION", + required_if_eq("aws_xks_enable", "true") + )] + pub aws_xks_region: Option, + + /// The AWS XKS service name to use for signing requests (sigv4) + #[clap( + long, + env = "KMS_AWS_XKS_SERVICE", + required_if_eq("aws_xks_enable", "true") + )] + pub aws_xks_service: Option, + + #[clap( + long, + env = "KMS_AWS_XKS_SIGV4_ACCESS_KEY_ID", + required_if_eq("aws_xks_enable", "true") + )] + /// The AWS XKS `SigV4` access key ID used to sign requests + pub aws_xks_sigv4_access_key_id: Option, + + #[clap( + long, + env = "KMS_AWS_XKS_SIGV4_SECRET_ACCESS_KEY", + required_if_eq("aws_xks_enable", "true") + )] + pub aws_xks_sigv4_secret_access_key: Option, +} diff --git a/crate/server/src/routes/aws_xks/encrypt_decrypt/decrypt_.rs b/crate/server/src/routes/aws_xks/encrypt_decrypt/decrypt_.rs new file mode 100644 index 0000000000..dcb437e6c8 --- /dev/null +++ b/crate/server/src/routes/aws_xks/encrypt_decrypt/decrypt_.rs @@ -0,0 +1,229 @@ +//! Decrypt +//! --------------------- +//! This API is used by KMS to decrypt data using a key which resides within an external key manager +use std::sync::Arc; + +use actix_web::{ + HttpRequest, HttpResponse, post, + web::{Data, Json, Path}, +}; +use base64::{Engine, engine::general_purpose::STANDARD}; +use cosmian_kms_server_database::reexport::cosmian_kmip::{ + kmip_0::kmip_types::ErrorReason, + kmip_1_4::kmip_types::ResultReason, + kmip_2_1::{ + kmip_operations::Decrypt, + kmip_types::{CryptographicAlgorithm, CryptographicParameters, UniqueIdentifier}, + }, +}; +use serde::{Deserialize, Serialize}; +use tracing::{debug, info}; + +use crate::{ + core::KMS, + error::KmsError, + result::KResult, + routes::aws_xks::{ + encrypt_decrypt::{EncryptionAlgorithm, RequestMetadata}, + error::{XksErrorName, XksErrorReply}, + }, +}; + +/// KMS uses this API to encrypt data using a key in an external key manager. +/// +/// Example: +/// ```json +/// { +/// "requestMetadata": { +/// "awsPrincipalArn": "arn:aws:iam::123456789012:user/Alice", +/// "kmsKeyArn": "arn:aws:kms:us-east-2:123456789012:/key/1234abcd-12ab-34cd-56ef-1234567890ab", +/// "kmsOperation": "Decrypt", +/// "kmsRequestId": "5112f4d6-db54-4af4-ae30-c55a22a8dfae", +/// "kmsViaService": "ebs" +/// }, +/// "additionalAuthenticatedData": "cHJvamVjdD1uaWxlLGRlcGFydG1lbnQ9bWFya2V0aW5n", +/// "encryptionAlgorithm": "AES_GCM", +/// "ciphertext": "ghxkK1txeDNn3q8Y", +/// "ciphertextMetadata": "a2V5X3ZlcnNpb249MQ==", +/// "initializationVector": "HMrlRw85cAJUd5Ax", +/// "authenticationTag": "vBxN2ncH1oEkR8WVXpmyYQ==" +/// } +/// ``` +#[derive(Deserialize, Debug, Serialize)] +#[allow(non_snake_case)] +pub(crate) struct DecryptRequest { + /// The HTTP body of the request contains requestMetadata fields + /// that provide additional context on the request being made. + /// This information is helpful for auditing and for implementing + /// an optional secondary layer of authorization at the XKS Proxy + /// (see a later section on Authorization). + /// There is no expectation for the XKS Proxy to validate any information + /// included in the requestMetadata beyond validating the signature + /// that covers the entire request payload. + pub requestMetadata: RequestMetadata, + /// Base64 encoded ciphertext provided to an external key manager for decryption. + /// At a minimum, the proxy MUST support the ability to process 4300 bytes of ciphertext. + /// Note the Base64 encoded string corresponding to 4300 bytes of binary data + /// will be 5736 bytes long. + /// This field is REQUIRED. + pub ciphertext: String, + /// Base64 encoded ciphertextMetadata that was included with the ciphertext + /// in the output of the encrypt call that produced the ciphertext being decrypted. + /// This is an OPTIONAL, vendor-specific field. + /// When present, the size of the field MUST NOT exceed 20 bytes (before Base64 encoding). + /// The XKS Proxy MUST detect when the ciphertextMetadata passed to decrypt + /// has been modified relative to the ciphertextMetadata generated + /// during the corresponding encrypt. + /// Appending the ciphertextMetadata to the additionalAuthenticatedData + /// and using that as the AAD for the external key manager, as described in the Encrypt API, + /// will automatically accomplish this. + pub ciphertextMetadata: Option, + /// Specifies the algorithm that will be used for encryption. + /// For the v1 specification, this MUST be `AES_GCM`. + /// This field is REQUIRED. + pub encryptionAlgorithm: EncryptionAlgorithm, + /// AES-GCM is an example of an AEAD (Authenticated Decryption with Additional Data) cipher + /// for which the encrypt operation produces an authenticationTag in addition to the ciphertext. + /// The authenticationTag can be used to ensure the integrity of the ciphertext + /// and additional data passed as AAD. + /// For a decrypt call to succeed, the same AAD that was used to create the ciphertext + /// must be supplied to the decrypt operation. + /// This field is OPTIONAL. + /// When present, this field MUST be specified as a Base64 encoded string + /// and used as the Additional Authenticated Data (AAD) input to the AES-GCM operation + /// inside the external key manager. + /// The XKS Proxy MUST be able to handle AAD values up to 8192 bytes in length + /// (the Base64 encoding of 8192 bytes will be 10924 bytes). + pub additionalAuthenticatedData: Option, + /// Base64 encoded initialization vector generated by the external key manager + /// that was used during encrypt operation. + /// For a decrypt call to succeed, this must be the same IV that was generated + /// when the ciphertext was created. + /// This field is REQUIRED. + /// For `AES_GCM`, the length of the initializationVector MUST be 12 bytes or 16 bytes + /// (the Base64 encoding will have 16 bytes or 24 bytes). + pub initializationVector: String, + /// Base64 encoded message authentication code. + /// Authentication tag size MUST be 16 bytes (the Base64 encoding will have 24 bytes). + /// For a decrypt call to succeed, this must be the same tag + /// that was generated by the encrypt call when the ciphertext was created. + /// This field is REQUIRED. + pub authenticationTag: String, +} + +/// The HTTP response body contains the keySpec, keyUsage, and keyStatus fields. +/// ```json +/// { +// "plaintext": "SGVsbG8gV29ybGQh" +/// } +/// ``` +#[derive(Serialize, Default, Deserialize)] +#[allow(non_snake_case)] +pub(crate) struct DecryptResponse { + /// Base64 encoded plaintext generated by an external key manager + /// from decrypting the provided ciphertext. + /// The size of the plaintext MUST be the same as the size of the ciphertext. + /// Plaintext returned by the decrypt API MUST NOT be logged at XKS Proxy + /// or the external key manager. + pub plaintext: String, +} + +#[post("/kms/xks/v1/keys/{key_id}/decrypt")] +pub(crate) async fn decrypt( + req_http: HttpRequest, + key_id: Path, + request: Json, + kms: Data>, +) -> HttpResponse { + let request = request.into_inner(); + let key_id = key_id.into_inner(); + info!( + "POST /kms/xks/v1/keys/{key_id}/decrypt - operation: {} - id: {} - user: {}", + request.requestMetadata.kmsOperation, + request.requestMetadata.kmsRequestId, + request.requestMetadata.awsPrincipalArn + ); + debug!("decrypt request: {:?}", request.requestMetadata); + let kms = kms.into_inner(); + match decrypt_inner(req_http, request, key_id, &kms) + .await + .map(Json) + { + Ok(wrap_response) => HttpResponse::Ok().json(wrap_response), + Err(e) => match e { + KmsError::Unauthorized(msg) => XksErrorReply { + errorName: XksErrorName::InvalidKeyUsageException, + errorMessage: Some(msg), + } + .into(), + KmsError::ItemNotFound(msg) => XksErrorReply { + errorName: XksErrorName::KeyNotFoundException, + errorMessage: Some(msg), + } + .into(), + KmsError::CryptographicError(msg) + | KmsError::Kmip21Error(ErrorReason::Cryptographic_Failure, msg) + | KmsError::Kmip14Error(ResultReason::CryptographicFailure, msg) => XksErrorReply { + errorName: XksErrorName::InvalidCiphertextException, + errorMessage: Some(msg), + } + .into(), + _ => { + info!("Decrypt error: {:?}", e); + HttpResponse::from_error(e) + } + }, + } +} + +async fn decrypt_inner( + _req_http: HttpRequest, + request: DecryptRequest, + key_id_or_tags: String, + kms: &Arc, +) -> KResult { + let user = request.requestMetadata.awsPrincipalArn; + let cryptographic_parameters = match request.encryptionAlgorithm { + EncryptionAlgorithm::AES_GCM => CryptographicParameters { + cryptographic_algorithm: Some(CryptographicAlgorithm::AES), + ..Default::default() + }, + }; + let data = STANDARD.decode(&request.ciphertext)?; + // Supplied Nonce or new one. + let nonce = STANDARD.decode(&request.initializationVector)?; + let aead = match request.additionalAuthenticatedData { + None => None, + Some(b64) => Some(STANDARD.decode(&b64)?), + }; + let tag = STANDARD.decode(request.authenticationTag)?; + // override the key_id if one is supplied as part of the metadata + let key_id = if let Some(key_id) = request.ciphertextMetadata { + String::from_utf8(STANDARD.decode(key_id)?)? + } else { + key_id_or_tags + }; + + let response = kms + .decrypt( + Decrypt { + unique_identifier: Some(UniqueIdentifier::TextString(key_id)), + cryptographic_parameters: Some(cryptographic_parameters), + data: Some(data), + i_v_counter_nonce: Some(nonce.clone()), + correlation_value: None, + init_indicator: None, + final_indicator: None, + authenticated_encryption_additional_data: aead.clone(), + authenticated_encryption_tag: Some(tag), + }, + &user, + ) + .await?; + let plaintext = response + .data + .ok_or_else(|| KmsError::ServerError("Missing AES GCM ciphertext".to_owned()))?; + Ok(DecryptResponse { + plaintext: STANDARD.encode(&plaintext), + }) +} diff --git a/crate/server/src/routes/aws_xks/encrypt_decrypt/encrypt_.rs b/crate/server/src/routes/aws_xks/encrypt_decrypt/encrypt_.rs new file mode 100644 index 0000000000..8754bb67ff --- /dev/null +++ b/crate/server/src/routes/aws_xks/encrypt_decrypt/encrypt_.rs @@ -0,0 +1,300 @@ +//! Encrypt +//! ---------------- +//! KMS uses this API to encrypt data using a key in an external key manager. +use std::sync::Arc; + +use actix_web::{ + HttpRequest, HttpResponse, post, + web::{Data, Json, Path}, +}; +use base64::{Engine, engine::general_purpose::STANDARD}; +use cosmian_kms_server_database::reexport::{ + cosmian_kmip::kmip_2_1::{ + kmip_operations::Encrypt, + kmip_types::{CryptographicAlgorithm, CryptographicParameters, UniqueIdentifier}, + }, + cosmian_kms_crypto::crypto::symmetric::symmetric_ciphers::AES_256_GCM_IV_LENGTH, +}; +use openssl::{rand::rand_bytes, sha::Sha256}; +use serde::{Deserialize, Serialize}; +use tracing::{debug, info}; +use zeroize::Zeroizing; + +use crate::{ + core::KMS, + error::KmsError, + result::KResult, + routes::aws_xks::{ + encrypt_decrypt::{CdivAlgorithm, EncryptionAlgorithm, RequestMetadata}, + error::{XksErrorName, XksErrorReply}, + }, +}; + +/// KMS uses this API to encrypt data using a key in an external key manager. +/// +/// Example: +/// ```json +/// { +/// "requestMetadata": { +/// "awsPrincipalArn": "arn:aws:iam::123456789012:user/Alice", +/// "kmsKeyArn": "arn:aws:kms:us-east-2:123456789012:/key/1234abcd-12ab-34cd-56ef-1234567890ab", +/// "kmsOperation": "Encrypt", +/// "kmsRequestId": "4112f4d6-db54-4af4-ae30-c55a22a8dfae", +/// "kmsViaService": "ebs" +/// }, +/// "additionalAuthenticatedData": "cHJvamVjdD1uaWxlLGRlcGFydG1lbnQ9bWFya2V0aW5n", +/// "plaintext": "SGVsbG8gV29ybGQh", +/// "encryptionAlgorithm": "AES_GCM", +/// "ciphertextDataIntegrityValueAlgorithm": "SHA_256" +/// } +/// ``` +#[derive(Deserialize, Debug, Serialize)] +#[allow(non_snake_case)] +pub(crate) struct EncryptRequest { + /// The HTTP body of the request contains requestMetadata fields + /// that provide additional context on the request being made. + /// This information is helpful for auditing and for implementing + /// an optional secondary layer of authorization at the XKS Proxy + /// (see a later section on Authorization). + /// There is no expectation for the XKS Proxy to validate any information + /// included in the requestMetadata beyond validating the signature + /// that covers the entire request payload. + pub requestMetadata: RequestMetadata, + /// Base64-encoded plaintext provided to external key manager for encryption. + /// The proxy MUST support the ability to process up to 4300 bytes of plaintext data. + /// Note that Base64 encoding of 4300 bytes of binary data will result in a string + /// that is 5736 bytes. + /// Plaintext passed to the encrypt API MUST NOT be logged at XKS Proxy + /// or the external key manager. + /// This field is REQUIRED. + pub plaintext: String, + /// Specifies the algorithm that will be used for encryption. + /// For the v1 specification, this MUST be `AES_GCM`. + /// This field is REQUIRED. + pub encryptionAlgorithm: EncryptionAlgorithm, + /// AES-GCM is an example of an AEAD (Authenticated Encryption with Additional Data) cipher + /// for which the encrypt operation produces an authenticationTag in addition to the ciphertext. + /// The authenticationTag can be used to ensure the integrity of the ciphertext + /// and additional data passed as AAD. + /// For a decrypt call to succeed, the same AAD that was used to create the ciphertext + /// must be supplied to the decrypt operation. + /// This field is OPTIONAL. + /// When present, this field MUST be specified as a Base64 encoded string + /// and used as the Additional Authenticated Data (AAD) input to the AES-GCM operation + /// inside the external key manager. + /// The XKS Proxy MUST be able to handle AAD values up to 8192 bytes in length + /// (the Base64 encoding of 8192 bytes will be 10924 bytes). + pub additionalAuthenticatedData: Option, + /// Indicates the hashing algorithm to be used in the computation + /// of the Ciphertext Data Integrity Value (CDIV). + /// For the first version (v1) of this specification, this MUST be "`SHA_256`". + /// This field is OPTIONAL. + /// When present, the XKS Proxy MUST return a ciphertextDataIntegrityValue field + /// in its response as described below. + pub ciphertextDataIntegrityValueAlgorithm: Option, +} + +/// The HTTP response body contains the keySpec, keyUsage, and keyStatus fields. +/// ```json +/// { +/// "authenticationTag": "vBxN2ncH1oEkR8WVXpmyYQ==", +/// "ciphertext": "ghxkK1txeDNn3q8Y", +/// "ciphertextDataIntegrityValue": "qHA/ImC9h5HsLRXqCyPmWgYx7tzyoTplzILbP0fPXsc=", +/// "ciphertextMetadata": "a2V5X3ZlcnNpb249MQ==", +/// "initializationVector": "HMrlRw85cAJUd5Ax" +/// } +/// ``` +#[derive(Serialize, Default, Deserialize)] +#[allow(non_snake_case)] +pub(crate) struct EncryptResponse { + /// Base64 encoded ciphertext generated by the external key manager from provided plaintext. + /// Since `AES_GCM` is a stream cipher, the length of the ciphertext + /// MUST be the same as the length of the plaintext + pub ciphertext: String, + /// The XKS Proxy MAY return up to 20 bytes of ciphertext metadata for internal housekeeping, + /// e.g. an external key manager may implement automatic key rotation + /// and use the extra bytes to encode versioning of the key material. + /// This is an OPTIONAL, vendor-specific field. + /// When present, the size of the field MUST NOT exceed 20 bytes + /// and the value MUST be Base64-encoded (the encoded string will be more than 20 bytes). + /// The XKS Proxy MUST append the ciphertextMetadata to the additionalAuthenticatedData + /// before normal AES GCM processing to ensure that integrity protection + /// offered by the authenticationTag extends to the ciphertextMetadata. + /// + /// NOTE: It is important to explicitly include the length of additionalAuthenticatedData + /// and the length of the ciphertextMetadata to avoid unintended successful decrypts, + /// e.g. when a caller calls encrypt with no additionalAuthenticatedData, + /// receives a ciphertextMetadata in the response and then calls decrypt + /// passing the ciphertextMetadata as additionalAuthenticatedData and no ciphertextMetadata. + /// The AAD input for the external key manager should be computed as (2-byte length, + /// before Base64 encoding, of additionalAuthenticatedData in big-endian format + /// || additionalAuthenticatedData || 1-byte length, before Base64 encoding, + /// of ciphertextMetadata || ciphertextMetadata) where || represents concatenation of + /// the binary values before Base64 encoding. If the additionalAuthenticatedData + /// or ciphertextMetadata is not present, the corresponding length MUST be set to zero. + /// If the inclusion of the lengths represents a departure from previously implemented behavior, + /// the XKS proxy SHOULD encode the new behavior in the ciphertextMetadata + /// and use the encoding to follow the same behavior during decrypt as was used + /// for the corresponding encrypt. + /// Otherwise, previously generated ciphertext will no longer be decryptable. + /// + /// For example, let's say version A of an XKS proxy concatenated the ciphertextMetadata + /// directly to additionalAuthenticatedData (without including the lengths) + /// but Version B implements new guidance then there needs to be a mechanism to distinguish + /// whether a decrypt call should use the old way or the new way to create the AAD + /// for the external key manager. + /// If Version B always implements the new behavior then ciphertext created by Version A + /// will no longer be decryptable. + /// The ciphertextMetadata is the natural place to encode this difference in + /// how the authenticationTag was created. + pub ciphertextMetadata: Option, + /// Base64 encoded initialization vector generated by the external key manager + /// that was used during encrypt operation. + /// The initialization vector MUST be either 12 bytes (96 bits) or 16 bytes (128 bits). + /// The Base64 encoding will have 16 bytes or 24 bytes. + pub initializationVector: String, + /// Base64 encoded message authentication code generated by external key manager + /// performing AES-GCM encryption. + /// Authentication tag size MUST be 16 bytes (128 bits). + /// Some key managers append the authentication tag to the ciphertext. + /// In such cases, the XKS proxy MUST separate the two before composing the response. + pub authenticationTag: String, + /// This field is a Base64 encoded hash computed over the + /// additionalAuthenticatedData (if present in the request), ciphertextMetadata (if present), + /// initializationVector, ciphertext and authenticationTag fields in the response. + /// It MUST be included whenever the request includes the ciphertextDataIntegrityValueAlgorithm + /// field. + /// The hashing algorithm used to compute this value MUST be the one specified + /// as the ciphertextDataIntegrityValueAlgorithm in the request. + /// KMS will independently calculate the ciphertextDataIntegrityValue (CDIV) + /// and return an error to the caller if the computed value does not match the value + /// in the response. + /// KMS interprets a match as assurance from the XKS Proxy that a subsequent decrypt call + /// where the caller passes in the same additionalAuthenticatedData + /// (if present, in the encrypt request), initializationVector, ciphertext + /// and authenticationTag values will succeed and return the plaintext + /// that was passed as input to this encrypt API. + /// See Appendix C for a complete example and specific CDIV implementation guidelines. + pub ciphertextDataIntegrityValue: Option, +} + +#[post("/kms/xks/v1/keys/{key_id}/encrypt")] +pub(crate) async fn encrypt( + req_http: HttpRequest, + key_id: Path, + request: Json, + kms: Data>, +) -> HttpResponse { + let request = request.into_inner(); + let key_id = key_id.into_inner(); + info!( + "POST /kms/xks/v1/keys/{key_id}/encrypt - operation: {} - id: {} - user: {}", + request.requestMetadata.kmsOperation, + request.requestMetadata.kmsRequestId, + request.requestMetadata.awsPrincipalArn + ); + debug!("encrypt request: {:?}", request.requestMetadata); + let kms = kms.into_inner(); + match encrypt_inner(req_http, request, key_id, &kms) + .await + .map(Json) + { + Ok(wrap_response) => HttpResponse::Ok().json(wrap_response), + Err(e) => match e { + KmsError::Unauthorized(msg) => XksErrorReply { + errorName: XksErrorName::InvalidKeyUsageException, + errorMessage: Some(msg), + } + .into(), + KmsError::ItemNotFound(msg) => XksErrorReply { + errorName: XksErrorName::KeyNotFoundException, + errorMessage: Some(msg), + } + .into(), + KmsError::CryptographicError(msg) => XksErrorReply { + errorName: XksErrorName::ValidationException, + errorMessage: Some(msg), + } + .into(), + _ => HttpResponse::from_error(e), + }, + } +} + +async fn encrypt_inner( + _req_http: HttpRequest, + request: EncryptRequest, + key_id_or_tags: String, + kms: &Arc, +) -> KResult { + let user = request.requestMetadata.awsPrincipalArn; + + let cryptographic_parameters = match request.encryptionAlgorithm { + EncryptionAlgorithm::AES_GCM => CryptographicParameters { + cryptographic_algorithm: Some(CryptographicAlgorithm::AES), + ..Default::default() + }, + }; + let data = Zeroizing::new(STANDARD.decode(&request.plaintext)?); + // Supplied Nonce or new one. + let nonce: [u8; AES_256_GCM_IV_LENGTH] = { + let mut iv = [0; AES_256_GCM_IV_LENGTH]; + rand_bytes(&mut iv)?; + iv + }; + let aead = match request.additionalAuthenticatedData { + None => None, + Some(b64) => Some(STANDARD.decode(&b64)?), + }; + let response = kms + .encrypt( + Encrypt { + unique_identifier: Some(UniqueIdentifier::TextString(key_id_or_tags.clone())), + cryptographic_parameters: Some(cryptographic_parameters), + data: Some(data), + i_v_counter_nonce: Some(nonce.to_vec()), + correlation_value: None, + init_indicator: None, + final_indicator: None, + authenticated_encryption_additional_data: aead.clone(), + }, + &user, + ) + .await?; + let ciphertext = response + .data + .ok_or_else(|| KmsError::ServerError("Missing AES GCM ciphertext".to_owned()))?; + let returned_key_id = response.unique_identifier.to_string(); + let ciphertext_metadata = if returned_key_id == key_id_or_tags { + None + } else { + // The encryption is likely performed using tags; keep track of the actual key used + Some(returned_key_id.as_bytes().to_vec()) + }; + let tag = response.authenticated_encryption_tag.ok_or_else(|| { + KmsError::ServerError("Missing AES GCM authenticated encryption tag".to_owned()) + })?; + let integrity_hash = match request.ciphertextDataIntegrityValueAlgorithm { + None => None, + Some(CdivAlgorithm::SHA_256) => { + let mut hasher = Sha256::new(); + if let Some(aead) = &aead { + hasher.update(aead); + } + if let Some(ciphertext_metadata) = &ciphertext_metadata { + hasher.update(ciphertext_metadata); + } + hasher.update(&nonce); + hasher.update(&ciphertext); + hasher.update(&tag); + Some(hasher.finish()) + } + }; + Ok(EncryptResponse { + ciphertext: STANDARD.encode(ciphertext), + ciphertextMetadata: ciphertext_metadata.map(|b| STANDARD.encode(&b)), + initializationVector: STANDARD.encode(nonce), + authenticationTag: STANDARD.encode(tag), + ciphertextDataIntegrityValue: integrity_hash.map(|b| STANDARD.encode(b)), + }) +} diff --git a/crate/server/src/routes/aws_xks/encrypt_decrypt/mod.rs b/crate/server/src/routes/aws_xks/encrypt_decrypt/mod.rs new file mode 100644 index 0000000000..4b5369952a --- /dev/null +++ b/crate/server/src/routes/aws_xks/encrypt_decrypt/mod.rs @@ -0,0 +1,63 @@ +mod decrypt_; +mod encrypt_; +pub(crate) use decrypt_::decrypt; +pub(crate) use encrypt_::encrypt; +use serde::{Deserialize, Serialize}; + +/// Request Payload Parameters: The HTTP body of the request contains the requestMetadata. +#[derive(Serialize, Deserialize, Debug, Clone)] +#[allow(non_snake_case)] +#[allow(dead_code)] +pub(crate) struct RequestMetadata { + /// This is the ARN of the principal that invoked KMS Decrypt (see aws:PrincipalArn). + /// When the caller is another AWS service, this field will contain either + /// the service principal ending in amazonaws.com, such as ec2.amazonaws.com or + /// “AWS Internal”. This field is REQUIRED. + pub awsPrincipalArn: String, + /// This field is OPTIONAL. It is present if and only if the KMS API request was made using + /// a VPC endpoint. + /// When present, this field indicates the VPC where the request originated (see aws:SourceVpc). + pub awsSourceVpc: Option, + /// This field is OPTIONAL. It is present if and only if the KMS API request was made using + /// a VPC endpoint. + /// When present, this field indicates the VPC endpoint used for the request (see aws:SourceVpce) + pub awsSourceVpce: Option, + /// This is the ARN of the KMS Key on which the Decrypt, `ReDecrypt`, `GenerateDataKey` + /// or `GenerateDataKeyWithoutPlaintext` API was invoked. This field is REQUIRED. + pub kmsKeyArn: String, + /// This is the KMS API call that resulted in the XKS Proxy API request, + /// e.g. `CreateKey` can result in a `GetKeyMetadata` call. This field is REQUIRED. + /// The XKS Proxy MUST NOT reject a request as invalid if it sees a kmsOperation + /// other than those listed for this API call. + /// In the future, KMS may introduce a new API that can be satisfied + /// by calling one of the XKS APIs listed in this document. + /// For proxies that implement secondary authorization, + /// it is acceptable for XKS API requests made as part of the new KMS API to fail authorization. + /// It is easier for a customer to update their XKS Proxy authorization policy + /// than to update their XKS Proxy software. + pub kmsOperation: String, + /// This is the requestId of the call made to KMS which is visible in AWS `CloudTrail`. + /// The XKS proxy SHOULD log this field to allow a customer + /// to correlate AWS `CloudTrail` entries with log entries in the XKS Proxy. + /// This field typically follows the format for UUIDs + /// but the XKS Proxy MUST treat this as an opaque string + /// and MUST NOT perform any validation on its structure. + /// This field is REQUIRED. + pub kmsRequestId: String, + /// This field is OPTIONAL. If present, it indicates the AWS service that called the KMS API + /// on behalf of a customer (see kms:ViaService) + pub kmsViaService: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +#[allow(non_camel_case_types)] +pub(crate) enum EncryptionAlgorithm { + AES_GCM, +} + +/// Ciphertext Data Integrity Value Algorithm +#[derive(Debug, Serialize, Deserialize)] +#[allow(non_camel_case_types)] +pub(crate) enum CdivAlgorithm { + SHA_256, +} diff --git a/crate/server/src/routes/aws_xks/error.rs b/crate/server/src/routes/aws_xks/error.rs new file mode 100644 index 0000000000..929baf4081 --- /dev/null +++ b/crate/server/src/routes/aws_xks/error.rs @@ -0,0 +1,188 @@ +#![allow(dead_code)] +use std::fmt::{Display, Formatter}; + +use actix_web::{ + // Error, HttpRequest, + Error, + HttpRequest, + HttpResponse, + ResponseError, + // dev::ServiceResponse, + error::JsonPayloadError, + // middleware::ErrorHandlerResponse, + // dev::ServiceResponse, error::JsonPayloadError, http, middleware::ErrorHandlerResponse, +}; +use cosmian_logger::debug; +use serde::{Deserialize, Serialize}; + +/// Error Name for AWS XKS Error replies +#[derive(Debug, Serialize, Deserialize, Clone)] +#[allow(non_camel_case_types)] +#[allow(clippy::enum_variant_names)] +pub enum XksErrorName { + /// The request was rejected because one + /// or more input parameters is invalid. + /// 400: ALL except `GetHealthStatus` + ValidationException, + + /// The request was rejected because the + /// specified external key or key store is + /// disabled, deactivated or blocked. + /// 400: ALL + InvalidStateException, + + /// The request was rejected because the + /// specified ciphertext, initialization vector, + /// additional authenticated data or + /// authentication tag is corrupted, missing, + /// or otherwise invalid. + /// 400: Decrypt + InvalidCiphertextException, + + /// The request was rejected because the + /// specified key does not support the + /// requested operation. + /// 400: Decrypt, Encrypt + InvalidKeyUsageException, + + /// The request was rejected due to + /// invalid AWS `SigV4` signature. + /// 401: ALL + AuthenticationFailedException, + + /// The request was rejected because the + /// operation is not authorized based on + /// request metadata. + /// 403: ALL except `GetHealthStatus` + AccessDeniedException, + + /// The request was rejected because the + /// specified external key is not found. + /// 404: ALL except `GetHealthStatus` + KeyNotFoundException, + + /// The request was rejected because the + /// specified URI path is not valid. + /// 404: ALL + InvalidUriPathException, + + /// The request was rejected because the + /// request rate is too high. The + /// proxy may send this either because + /// it is unable to keep up or the caller + /// exceeded its request quota. + /// 429: ALL + ThrottlingException, + + /// The request was rejected because the + /// specified cryptographic operation is not + /// implemented, or if a parameter value + /// exceeded the maximum size that is + /// currently supported by a specific + /// implementation beyond the minimize size + /// required by this API specification. + /// 501: ALL + UnsupportedOperationException, + + /// The XKS proxy timed out while trying to + /// access a dependency layer to fulfill the + /// request. + /// 503: ALL + DependencyTimeoutException, + + /// This is a generic server error. For example, + /// this exception is thrown due to failure of + /// the backing key manager, or failure of a + /// dependency layer. + /// 500: ALL + InternalException, +} + +/// Error reply for AWS XKS +/// +/// see: +/// +/// Example +/// ```json +/// { +/// "errorName": "InvalidCiphertextException", // required +/// "errorMessage": "The request was rejected because the specified ciphertext, or additional authenticated data is corrupted, missing, or otherwise invalid." // optional +/// } +/// ``` +#[derive(Serialize, Debug, Clone)] +#[allow(non_snake_case)] +pub struct XksErrorReply { + pub errorName: XksErrorName, + pub errorMessage: Option, +} + +impl Display for XksErrorReply { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("{self:?}")) + } +} + +impl From for HttpResponse { + fn from(e: XksErrorReply) -> Self { + debug!("Xks Error: {:?}", e); + match e.errorName { + XksErrorName::ValidationException + | XksErrorName::InvalidStateException + | XksErrorName::InvalidCiphertextException + | XksErrorName::InvalidKeyUsageException + | XksErrorName::AuthenticationFailedException => Self::Unauthorized().json(e), + XksErrorName::AccessDeniedException => Self::Forbidden().json(e), + // We map to I am a teapot to avoid falling into the generic 404 error handler + // and use another handler to convert it to 404 + XksErrorName::KeyNotFoundException => Self::ImATeapot().json(e), + XksErrorName::InvalidUriPathException => Self::NotFound().json(e), + XksErrorName::ThrottlingException => Self::TooManyRequests().json(e), + XksErrorName::UnsupportedOperationException => Self::NotImplemented().json(e), + XksErrorName::DependencyTimeoutException => Self::ServiceUnavailable().json(e), + XksErrorName::InternalException => Self::InternalServerError().json(e), + } + } +} + +impl ResponseError for XksErrorReply { + fn error_response(&self) -> HttpResponse { + HttpResponse::from(self.clone()) + } +} + +// Custom error handler for JSON deserialization errors +#[allow(clippy::needless_pass_by_value)] +pub(crate) fn xks_json_error_handler(err: JsonPayloadError, _req: &HttpRequest) -> Error { + let error_message = match &err { + JsonPayloadError::Deserialize(e) => format!("JSON deserialize error: {e}"), + _ => "Unknown error".to_owned(), + }; + XksErrorReply { + errorName: XksErrorName::ValidationException, + errorMessage: Some(error_message), + } + .into() +} + +pub(crate) async fn xks_path_not_found_handler( + req: HttpRequest, +) -> Result { + debug!("XKS Proxy - Path not found: {}", req.path()); + Err(XksErrorReply { + errorName: XksErrorName::InvalidUriPathException, + errorMessage: Some(format!("Resource not found: {}", req.path())), + }) +} + +// /// Custom error handler for "I am a teapot" which are "key not found" errors +// /// and must be reconverted to 404 to meet the spec +// pub(crate) fn xks_key_not_found_handler( +// mut service_response: ServiceResponse, +// ) -> actix_web::Result> { +// *service_response.response_mut().status_mut() = http::StatusCode::NOT_FOUND; + +// // body is unchanged, map to "left" slot +// Ok(ErrorHandlerResponse::Response( +// service_response.map_into_left_body(), +// )) +// } diff --git a/crate/server/src/routes/aws_xks/health_status.rs b/crate/server/src/routes/aws_xks/health_status.rs new file mode 100644 index 0000000000..d7c2fc9cbb --- /dev/null +++ b/crate/server/src/routes/aws_xks/health_status.rs @@ -0,0 +1,137 @@ +//! `GetHealthStatus` +//! --------------- +//! This API serves multiple purposes +//! +//! It is used to ensure that the XKS Proxy base URL (https://!//kms/xks/v1) +//! and `SigV4` credentials required to communicate with the proxy are configured correctly in KMS. +//! +//! It is used to ensure that the XKS Proxy is ready to handle +//! other API requests (encrypt/decrypt/getKeyMetadata) +//! +//! It is used to gather information for proactively monitoring availability risks +//! and processing KMS customer requests to raise the Transactions Per Second (TPS) limit +//! on their external key manager. +//! +//! Before returning a successful response (HTTP 200 OK), +//! the XKS Proxy SHOULD verify not only that the external key manager is reachable +//! but is also able to perform cryptographic operations, i.e. the health-check SHOULD be deep +//! rather than shallow. +//! +//! The health check should be implemented such that a successful check provides strong assurance +//! that an encrypt, decrypt or getKeyMetadata request issued immediately +//! after will succeed (except due to authorization checks). +//! +//! The XKS Proxy SHOULD create test keys in the external key manager +//! and invoke cryptographic operations on them as part of the deep Healthcheck. +//! +//! This API MUST be excluded from secondary authorization if the XKS Proxy implements such authorization. +//! +//! HTTP Method: POST +//! +//! API specs: +use std::sync::Arc; + +use actix_web::{ + HttpRequest, HttpResponse, post, + web::{Data, Json}, +}; +use clap::crate_version; +use serde::{Deserialize, Serialize}; +use tracing::info; + +use crate::core::KMS; + +/// Request Payload Parameters: The HTTP body of the request contains the requestMetadata. +#[derive(Deserialize, Debug, Serialize)] +#[allow(non_snake_case)] +pub(crate) struct GetHealthStatusRequest { + pub requestMetadata: RequestMetadata, +} + +/// Request Payload Parameters: The HTTP body of the request only contains the requestMetadata. +#[derive(Serialize, Deserialize, Debug, Clone)] +#[allow(non_snake_case)] +#[allow(dead_code)] +pub(crate) struct RequestMetadata { + /// This is the requestId of the call made by AWS KMS as part of + /// a periodic health check which is visible in AWS `CloudTrail`. + /// The XKS proxy SHOULD log this field to allow a customer to correlate + /// AWS `CloudTrail` entries with log entries in the XKS Proxy. + /// This field typically follows the format for UUIDs + /// but the XKS Proxy MUST treat this as an opaque string and + /// MUST NOT perform any validation on its structure. This field is REQUIRED. + pub kmsRequestId: String, + + /// This is the KMS API call that resulted in the XKS Proxy API request. + /// This field is REQUIRED. + /// The kmsOperation is set to `CreateCustomKeyStore`, `ConnectCustomKeyStore`, + /// or `UpdateCustomKeyStore` when the `GetHealthStatus` API is called as part of those KMS APIs. + /// This field is set to `KmsHealthCheck` when `GetHealthStatus` is called periodically + /// to get health status for publishing to `CloudWatch` metrics. + /// The XKS Proxy MUST NOT reject a request as invalid if it sees a kmsOperation + /// other than those listed for this API call. + pub kmsOperation: String, +} + +// External Key Manager Details +#[derive(Serialize, Deserialize, Debug)] +#[allow(non_snake_case)] +pub(crate) struct EkmFleetDetails { + /// Unique identifier for the external key manager in the external key manager cluster. + pub id: String, + /// Model of the external key manager. This SHOULD include the product name, + /// version of the hardware and any other information that would be useful + /// in troubleshooting and estimating TPS capacity. + pub model: String, + /// Status of health check on the external key manager from XKS proxy. + /// The possible statuses are ACTIVE, DEGRADED and UNAVAILABLE. ACTIVE means that + /// external key manager is healthy, DEGRADED means that external key manager is unhealthy + /// but can still serve traffic and UNAVAILABLE means that + /// external key manager is unable to serve traffic. + pub healthStatus: String, +} + +/// Response Payload Parameters: The HTTP body of the response contains +/// the health status of the XKS Proxy and the external key manager. +#[derive(Serialize, Deserialize, Debug)] +#[allow(non_snake_case)] +pub(crate) struct GetHealthStatusResponse { + /// Size of XKS proxy fleet. This MUST be an integer greater than zero. + pub xksProxyFleetSize: u16, + /// Name of the XKS Proxy vendor, this could be different from the name + /// of the external key manager vendor. + /// Both MUST be included even if they are the same. + pub xksProxyVendor: String, + /// Model of the XKS Proxy. This SHOULD include the product name and version. + pub xksProxyModel: String, + /// Name of the external key manager vendor. + pub ekmVendor: String, + /// External Key Manager Details + pub ekmFleetDetails: Vec, +} + +#[post("/kms/xks/v1/health")] +pub(crate) async fn get_health_status( + _req_http: HttpRequest, + request: Json, + _kms: Data>, +) -> HttpResponse { + let request = request.into_inner(); + info!( + "POST /aws/kms/xks/v1/health - request id {} - operation {}", + request.requestMetadata.kmsRequestId, request.requestMetadata.kmsOperation + ); + + let model = format!("Cosmian KMS {}", crate_version!()); + HttpResponse::Ok().json(GetHealthStatusResponse { + xksProxyFleetSize: 1, + xksProxyVendor: "Cosmian".to_owned(), + xksProxyModel: model.clone(), + ekmVendor: "Cosmian".to_owned(), + ekmFleetDetails: vec![EkmFleetDetails { + id: "1".to_owned(), + model, + healthStatus: "ACTIVE".to_owned(), + }], + }) +} diff --git a/crate/server/src/routes/aws_xks/key_metadata.rs b/crate/server/src/routes/aws_xks/key_metadata.rs new file mode 100644 index 0000000000..06d1e1e814 --- /dev/null +++ b/crate/server/src/routes/aws_xks/key_metadata.rs @@ -0,0 +1,370 @@ +//! `GetKeyMetaData` +//! ---------------- +//! This API is called by KMS to get metadata about an external key or create a new external key. +use std::sync::Arc; + +use actix_web::{ + HttpRequest, HttpResponse, post, + web::{Data, Json, Path}, +}; +use cosmian_kms_access::access::Access; +use cosmian_kms_server_database::reexport::cosmian_kmip::{ + kmip_0::kmip_types::CryptographicUsageMask, + kmip_2_1::{ + KmipOperation, + kmip_attributes::Attributes, + kmip_objects::ObjectType, + kmip_operations::{Create, GetAttributes}, + kmip_types::{CryptographicAlgorithm, KeyFormatType, UniqueIdentifier}, + }, +}; +use cosmian_logger::warn; +use serde::{Deserialize, Serialize}; +use time::OffsetDateTime; +use tracing::{debug, info}; + +use crate::{ + core::KMS, + routes::aws_xks::error::{XksErrorName, XksErrorReply}, +}; + +/// Returns the current UTC time with milliseconds set to zero. +/// +/// This function is used to normalize timestamps across the KMIP implementation, +/// ensuring consistent time representations without millisecond precision. +/// +/// # Returns +/// +/// Returns the current `OffsetDateTime` with milliseconds set to 0. +/// +/// # Errors +/// +/// Returns a `KmipError::Default` if the millisecond replacement fails. +fn time_normalize() -> Result { + OffsetDateTime::now_utc() + .replace_millisecond(0) + .map_err(|e| XksErrorReply { + errorName: XksErrorName::InternalException, + errorMessage: Some(format!("Failed to normalize time: {e}")), + }) +} + +/// Request Payload Parameters: The HTTP body of the request contains the requestMetadata. +#[derive(Serialize, Deserialize, Debug, Clone)] +#[allow(non_snake_case)] +#[allow(dead_code)] +pub(crate) struct RequestMetadata { + /// This is the ARN of the principal that invoked KMS `CreateKey` (see aws:PrincipalArn). + /// When the caller is another AWS service, this field will contain either + /// the service principal ending in amazonaws.com, such as ec2.amazonaws.com or + /// "AWS Internal". This field is REQUIRED. + pub awsPrincipalArn: String, + /// This field is OPTIONAL. It is present if and only if the KMS API request was made using + /// a VPC endpoint. + /// When present, this field indicates the VPC where the request originated (see aws:SourceVpc). + pub awsSourceVpc: Option, + /// This field is OPTIONAL. It is present if and only if the KMS API request was made using + /// a VPC endpoint. + /// When present, this field indicates the VPC endpoint used for the request (see aws:SourceVpce) + pub awsSourceVpce: Option, + /// This is the KMS API call that resulted in the XKS Proxy API request, + /// e.g. `CreateKey` can result in a `GetKeyMetadata` call. This field is REQUIRED. + /// The XKS Proxy MUST NOT reject a request as invalid if it sees a kmsOperation + /// other than those listed for this API call. + /// In the future, KMS may introduce a new API that can be satisfied + /// by calling one of the XKS APIs listed in this document. + /// For proxies that implement secondary authorization, + /// it is acceptable for XKS API requests made as part of the new KMS API to fail authorization. + /// It is easier for a customer to update their XKS Proxy authorization policy + /// than to update their XKS Proxy software. + pub kmsOperation: String, + /// This is the requestId of the call made to KMS which is visible in AWS `CloudTrail`. + /// The XKS proxy SHOULD log this field to allow a customer + /// to correlate AWS `CloudTrail` entries with log entries in the XKS Proxy. + /// This field typically follows the format for UUIDs + /// but the XKS Proxy MUST treat this as an opaque string + /// and MUST NOT perform any validation on its structure. + /// This field is REQUIRED. + pub kmsRequestId: String, +} + +/// The HTTP body of the request contains requestMetadata fields +/// that provide additional context on the request being made. +/// This information is helpful for auditing and for implementing +/// an optional secondary layer of authorization at the XKS Proxy +/// (see a later section on Authorization). +/// There is no expectation for the XKS Proxy to validate any information +/// included in the requestMetadata beyond validating the signature +/// that covers the entire request payload. +/// +/// Example: +/// ```json +/// { +/// "requestMetadata": { +/// "awsPrincipalArn": "arn:aws:iam::123456789012:user/Alice", +/// "kmsOperation": "CreateKey", +/// "kmsRequestId": "4112f4d6-db54-4af4-ae30-c55a22a8dfae" +/// } +/// } +/// ``` +#[derive(Deserialize, Debug, Serialize)] +#[allow(non_snake_case)] +pub(crate) struct GetKeyMetadataRequest { + pub requestMetadata: RequestMetadata, +} + +// Defined per XKS Proxy API spec. +#[derive(Serialize, Debug, PartialEq, Deserialize)] +#[allow(clippy::upper_case_acronyms)] +pub(crate) enum KeyUsage { + ENCRYPT, + DECRYPT, + SIGN, + VERIFY, + WRAP, + UNWRAP, +} + +/// The HTTP response body contains the keySpec, keyUsage, and keyStatus fields. +/// ```json +/// { +/// "keySpec": "AES_256", +/// "keyUsage": ["ENCRYPT", "DECRYPT"], +/// "keyStatus": "ENABLED" +/// } +/// ``` +#[derive(Serialize, Default, Deserialize)] +#[allow(non_snake_case)] +pub(crate) struct GetKeyMetadataResponse { + /// Specifies the type of external key. + /// This field is REQUIRED. + /// The XKS Proxy must use the string `AES_256` to indicate a 256-bit AES key. + pub keySpec: String, + /// Specifies an array of cryptographic operations for which external key can be used. + /// This field is REQUIRED. + /// The XKS Proxy must use the strings ENCRYPT and DECRYPT (all uppercase) + /// to indicate when an external key supports encrypt and decrypt operations, respectively. + /// The XKS Proxy response MAY include additional values supported by that external key, + /// e.g. PKCS11-based HSMs additionally support DERIVE, SIGN, VERIFY, WRAP, UNWRAP. + /// The response MUST NOT contain more than ten keyUsage values. + pub keyUsage: Vec, + /// Specifies the state of the external key. + /// The supported values are ENABLED and DISABLED. This field is REQUIRED. + /// If neither the external key manager nor the XKS Proxy support disabling individual keys, + /// the XKS Proxy MUST return ENABLED for this field. + pub keyStatus: String, +} + +#[post("/kms/xks/v1/keys/{key_id}/metadata")] +pub(crate) async fn get_key_metadata( + req_http: HttpRequest, + key_id: Path, + request: Json, + kms: Data>, +) -> HttpResponse { + let request = request.into_inner(); + let key_id = key_id.into_inner(); + info!( + "POST /kms/xks/v1/keys/{key_id}/metadata - operation: {} - id: {} - user: {}", + request.requestMetadata.kmsOperation, + request.requestMetadata.kmsRequestId, + request.requestMetadata.awsPrincipalArn + ); + debug!("get metadata request: {:?}", request.requestMetadata); + let kms = kms.into_inner(); + let response = match request.requestMetadata.kmsOperation.as_str() { + "GetKeyMetadata" | "DescribeKey" => get_key_metadata_inner(req_http, request, key_id, &kms) + .await + .map(Json), + "CreateKey" => create_key(req_http, request, key_id, &kms).await.map(Json), + x => Err(XksErrorReply { + errorName: XksErrorName::UnsupportedOperationException, + errorMessage: Some(format!("Unsupported kmsOperation: {x}")), + }), + }; + match response { + Ok(wrap_response) => HttpResponse::Ok().json(wrap_response), + Err(e) => HttpResponse::from_error(e), + } +} + +async fn get_key_metadata_inner( + _req_http: HttpRequest, + request: GetKeyMetadataRequest, + key_id: String, + kms: &Arc, +) -> Result { + let user = request.requestMetadata.awsPrincipalArn; + + let response = kms + .get_attributes( + GetAttributes { + unique_identifier: Some(UniqueIdentifier::TextString(key_id)), + attribute_reference: None, + }, + &user, + ) + .await + .map_err(|e| XksErrorReply { + errorName: XksErrorName::KeyNotFoundException, + errorMessage: Some(e.to_string()), + })?; + let cryptographic_algorithm = + response + .attributes + .cryptographic_algorithm + .ok_or_else(|| XksErrorReply { + errorName: XksErrorName::InternalException, + errorMessage: Some("No cryptographic algorithm found".to_owned()), + })?; + let key_size = response + .attributes + .cryptographic_length + .ok_or_else(|| XksErrorReply { + errorName: XksErrorName::InternalException, + errorMessage: Some("No cryptographic length found".to_owned()), + })?; + let (key_spec, key_usage) = match cryptographic_algorithm { + CryptographicAlgorithm::AES => ( + format!("AES_{key_size}"), + vec![ + KeyUsage::ENCRYPT, + KeyUsage::DECRYPT, + KeyUsage::WRAP, + KeyUsage::UNWRAP, + ], + ), + CryptographicAlgorithm::RSA => { + let key_spec = format!("RSA_{key_size}"); + if response.attributes.get_tags().contains("_sk") { + // a private key + ( + key_spec, + vec![KeyUsage::DECRYPT, KeyUsage::SIGN, KeyUsage::UNWRAP], + ) + } else { + ( + key_spec, + vec![KeyUsage::ENCRYPT, KeyUsage::VERIFY, KeyUsage::WRAP], + ) + } + } + xc => { + return Err(XksErrorReply { + errorName: XksErrorName::UnsupportedOperationException, + errorMessage: Some(format!("Unsupported cryptographic algorithm: {xc:?}")), + }); + } + }; + + let key_status = "ENABLED".to_owned(); + Ok(GetKeyMetadataResponse { + keySpec: key_spec, + keyUsage: key_usage, + keyStatus: key_status, + }) +} + +async fn create_key( + _req_http: HttpRequest, + request: GetKeyMetadataRequest, + key_id: String, + kms: &Arc, +) -> Result { + let aws_user = request.requestMetadata.awsPrincipalArn; + let uid = UniqueIdentifier::TextString(key_id); + // Set the activation date in the past to have the key immediately active + let activation_date = time_normalize().map_err(|e| XksErrorReply { + errorName: XksErrorName::InternalException, + errorMessage: Some(format!("Failed to get current time: {e}")), + })? - time::Duration::minutes(1); + + let mut attributes = Attributes { + cryptographic_algorithm: Some(CryptographicAlgorithm::AES), + cryptographic_length: Some(256), + cryptographic_usage_mask: Some( + CryptographicUsageMask::Encrypt + | CryptographicUsageMask::Decrypt + | CryptographicUsageMask::WrapKey + | CryptographicUsageMask::UnwrapKey, + ), + key_format_type: Some(KeyFormatType::TransparentSymmetricKey), + object_type: Some(ObjectType::SymmetricKey), + unique_identifier: Some(uid.clone()), + activation_date: Some(activation_date), + ..Attributes::default() + }; + attributes + .set_tags(["aws-xks"]) + .map_err(|e| XksErrorReply { + errorName: XksErrorName::InternalException, + errorMessage: Some(format!("Failed to set tags: {e}")), + })?; + let create = Create { + object_type: ObjectType::SymmetricKey, + attributes, + protection_storage_masks: None, + }; + + if let Err(e) = kms.create(create, &kms.params.default_username, None).await { + // If the key already exists, ignore the creation error (idempotent CreateKey). + let get_att_response = kms + .get_attributes( + GetAttributes { + unique_identifier: Some(uid.clone()), + attribute_reference: None, + }, + &kms.params.default_username, + ) + .await + .map_err(|e| XksErrorReply { + errorName: XksErrorName::InternalException, + errorMessage: Some(format!("Failed to check prior existence of key {uid}: {e}")), + })?; + if get_att_response.attributes.object_type == Some(ObjectType::SymmetricKey) { + warn!("AWS XKS create: key {uid} already exists (ignoring creation)."); + } else { + return Err(XksErrorReply { + errorName: XksErrorName::InternalException, + errorMessage: Some(format!("Failed to create XKS key {uid}: {e}")), + }); + } + } else { + // Grant Encrypt and Decrypt usage for the created key to the AWS user + kms.grant_access( + &Access { + unique_identifier: Some(uid.clone()), + user_id: aws_user.clone(), + operation_types: vec![ + KmipOperation::Encrypt, + KmipOperation::Decrypt, + KmipOperation::GetAttributes, + ], + }, + &kms.params.default_username, + None, + ) + .await + .map_err(|e| XksErrorReply { + errorName: XksErrorName::InternalException, + errorMessage: Some(format!( + "Failed to grant access to key {uid}, to user {aws_user}: {e}" + )), + })?; + } + + // Return the key metadata + let key_spec = "AES_256".to_owned(); + let key_usage = vec![ + KeyUsage::ENCRYPT, + KeyUsage::DECRYPT, + KeyUsage::WRAP, + KeyUsage::UNWRAP, + ]; + let key_status = "ENABLED".to_owned(); + Ok(GetKeyMetadataResponse { + keySpec: key_spec, + keyUsage: key_usage, + keyStatus: key_status, + }) +} diff --git a/crate/server/src/routes/aws_xks/mod.rs b/crate/server/src/routes/aws_xks/mod.rs new file mode 100644 index 0000000000..3812ec7a1c --- /dev/null +++ b/crate/server/src/routes/aws_xks/mod.rs @@ -0,0 +1,44 @@ +mod aws_xks_config; +mod encrypt_decrypt; +mod error; +mod health_status; +mod key_metadata; +mod sigv4_middleware; + +pub use aws_xks_config::AwsXksConfig; +pub(crate) use encrypt_decrypt::{decrypt, encrypt}; +pub(crate) use error::{xks_json_error_handler, xks_path_not_found_handler}; +pub(crate) use health_status::get_health_status; +pub(crate) use key_metadata::get_key_metadata; +pub use sigv4_middleware::Sigv4MWare; + +use crate::{error::KmsError, result::KResultHelper}; + +#[derive(Debug, Clone)] +pub struct AwsXksParams { + pub region: String, + pub service: String, + pub sigv4_access_key_id: String, + pub sigv4_secret_access_key: String, +} + +impl TryFrom for AwsXksParams { + type Error = KmsError; + + fn try_from(config: AwsXksConfig) -> Result { + Ok(Self { + region: config + .aws_xks_region + .context("AWS XKS region is required")?, + service: config + .aws_xks_service + .context("AWS XKS service is required")?, + sigv4_access_key_id: config + .aws_xks_sigv4_access_key_id + .context("AWS XKS SigV4 access key ID is required")?, + sigv4_secret_access_key: config + .aws_xks_sigv4_secret_access_key + .context("AWS XKS SigV4 secret access key is required")?, + }) + } +} diff --git a/crate/server/src/routes/aws_xks/sigv4_middleware.rs b/crate/server/src/routes/aws_xks/sigv4_middleware.rs new file mode 100644 index 0000000000..e1f4d79662 --- /dev/null +++ b/crate/server/src/routes/aws_xks/sigv4_middleware.rs @@ -0,0 +1,318 @@ +//! API Token Authentication Middleware +//! +//! This module contains the middleware implementation for API token-based authentication. +//! It provides a separate authentication pipeline that can be used independently of +//! other authentication methods. +//! +//! Authentication: +//! Proxy Impl: +//! Testing client: + +use std::{ + pin::Pin, + rc::Rc, + sync::Arc, + task::{Context, Poll}, +}; + +use actix_web::{ + Error, + body::{BoxBody, EitherBody}, + dev::{Payload, Service, ServiceRequest, ServiceResponse, Transform}, + error::InternalError, + http::StatusCode, +}; +use chrono::Duration; +use cosmian_kms_server_database::reexport::cosmian_kmip::kmip_2_1::{ + kmip_operations::Get, + kmip_types::{KeyFormatType, UniqueIdentifier}, +}; +use cosmian_logger::debug; +use futures::{ + Future, StreamExt, + future::{Ready, err, ok}, +}; +use scratchstack_aws_signature::{ + Request as Sigv4Request, SigningKey, SigningKeyKind::KSecret, sigv4_verify, +}; +use zeroize::Zeroizing; + +use crate::{ + core::KMS, + routes::aws_xks::error::{XksErrorName, XksErrorReply}, +}; + +/// `Sigv4MWare` is an Actix web middleware that handles AWS Signature Version 4 (sigv4) protocol. +/// +/// In Actix web, middlewares consist of two parts: +/// 1. A transformer (this struct), which is used during service configuration +/// 2. A middleware service that processes each request +/// +/// This transformer is responsible for creating the middleware service with the necessary +/// configuration for API token authentication. +#[derive(Clone)] +pub struct Sigv4MWare { + /// Reference to the KMS server for API token authentication + kms_server: Arc, +} + +impl Sigv4MWare { + /// Creates a new `Sigv4MWare` with the given KMS server + /// + /// # Parameters + /// * `kms_server` - The KMS server instance used for API token validation + #[must_use] + pub const fn new(kms_server: Arc) -> Self { + Self { kms_server } + } +} + +/// Implementation of the Transform trait, which is how Actix registers middleware +/// +/// This trait defines how to create a new middleware service (`Sigv4Service`) from the +/// transformer. The middleware will be part of the Actix service pipeline. +impl Transform for Sigv4MWare +where + S: Service, Error = Error> + 'static, + S::Future: 'static, +{ + type Error = Error; + type Future = Ready>; + type InitError = (); + type Response = ServiceResponse>; + type Transform = Sigv4Service; + + /// Creates a new instance of the `Sigv4Service` service + /// + /// This is called once during application startup for each service + /// that this middleware wraps. It passes the necessary configuration + /// to the `Sigv4Service`. + fn new_transform(&self, service: S) -> Self::Future { + if self.kms_server.params.aws_xks_params.is_none() { + tracing::error!( + "AWS XKS Sigv4 middleware should not be enabled if the aws_xks_params are not set" + ); + return err(()); + } + ok(Sigv4Service { + service: Rc::new(service), + kms_server: self.kms_server.clone(), + }) + } +} + +/// `Sigv4Service` is the actual middleware service that processes each request +/// +/// This middleware validates API tokens for each incoming request. +pub struct Sigv4Service +where + S: Service, Error = Error> + 'static, + S::Future: 'static, +{ + /// The next service in the middleware chain + service: Rc, + /// Reference to the KMS server for API token authentication + kms_server: Arc, +} + +/// Implementation of the Service trait, which defines how requests are processed +/// +/// This is where the actual API token authentication logic happens for each incoming request. +impl Service for Sigv4Service +where + S: Service, Error = Error> + 'static, + S::Future: 'static, +{ + type Error = Error; + type Future = Pin>>>; + type Response = ServiceResponse>; + + /// Checks if the middleware is ready to process a request + /// + /// This forwards the readiness check to the wrapped service. + fn poll_ready(&self, ctx: &mut Context) -> Poll> { + self.service.poll_ready(ctx) + } + + /// Processes each request by checking the signature v4 + fn call(&self, req: ServiceRequest) -> Self::Future { + let service = self.service.clone(); + let kms_server = self.kms_server.clone(); + + Box::pin(async move { + let params = kms_server.params.aws_xks_params.clone().ok_or_else(|| + actix_web::error::ErrorInternalServerError( + "AWS XKS Sigv4 middleware should not be enabled if the aws_xks_params are not set", + ) + )?; + let access_key_id = params.sigv4_access_key_id; + let access_key = params.sigv4_secret_access_key; + + let (actix_web_http_request, body): (actix_web::HttpRequest, actix_web::dev::Payload) = + req.into_parts(); + + let body_as_bytes = body + .map(Result::unwrap_or_default) + .fold(Vec::new(), |mut acc, chunk| async move { + acc.extend_from_slice(&chunk); + acc + }) + .await; + + let http_request = to_http_request(&actix_web_http_request, &body_as_bytes)?; + let (parts, body) = http_request.into_parts(); + // let body_as_bytes: Option = hyper::body::to_bytes(body).await.ok(); + // let body_as_vec_u8: Option> = + // body_as_bytes.as_ref().map(|bytes| bytes.to_vec()); + let sigv4_req = Sigv4Request::from_http_request_parts(&parts, Some(body)); + let gsk_req = sigv4_req + .to_get_signing_key_request( + KSecret, + params.region.as_str(), + params.service.as_str(), + ) + .map_err(|signature_err| { + actix_web::error::ErrorUnauthorized(signature_err.to_string()) + })?; + + if access_key_id != gsk_req.access_key { + let err: Self::Error = XksErrorReply { + errorName: XksErrorName::AuthenticationFailedException, + errorMessage: Some(format!("Access key id {} not found", gsk_req.access_key)), + } + .into(); + return Err(err); + } + + let signing_key = SigningKey { + kind: KSecret, + key: access_key.as_bytes().to_vec(), + }; + let allowed_mismatch = Some(Duration::minutes(5)); + if let Err(signature_error) = sigv4_verify( + &sigv4_req, + &signing_key, + allowed_mismatch, + params.region.as_str(), + params.service.as_str(), + ) { + tracing::warn!("SigV4 failure: {signature_error}"); + let err: Self::Error = XksErrorReply { + errorName: XksErrorName::AuthenticationFailedException, + errorMessage: Some(format!( + "Signature v4 verification failed: {signature_error}", + )), + } + .into(); + return Err(err); + } + + // rebuild request with body_as_bytes and forward to next service + let req = + ServiceRequest::from_parts(actix_web_http_request, Payload::from(body_as_bytes)); + let res = service.call(req).await?; + Ok(res.map_into_left_body()) + }) + } +} + +fn to_http_request( + actix_req: &actix_web::HttpRequest, + body: &[u8], +) -> Result>, actix_web::error::Error> { + let method: http::Method = actix_req.method().as_str().parse().map_err(|e| { + actix_web::error::ErrorBadRequest(format!( + "Failed to parse HTTP method for Sigv4 validation: {e:?}" + )) + })?; + let uri: http::Uri = actix_req.uri().to_string().parse().map_err(|e| { + actix_web::error::ErrorBadRequest(format!( + "Failed to parse HTTP URI for Sigv4 validation: {e:?}" + )) + })?; + let version: http::Version = match actix_req.version() { + actix_web::http::Version::HTTP_09 => http::Version::HTTP_09, + actix_web::http::Version::HTTP_10 => http::Version::HTTP_10, + actix_web::http::Version::HTTP_2 => http::Version::HTTP_2, + actix_web::http::Version::HTTP_3 => http::Version::HTTP_3, + _ => http::Version::HTTP_11, + }; + + let mut http_request_builder = http::request::Builder::new() + .method(method) + .uri(uri) + .version(version); + + // If using the HTTP/2, the host header is missing in the request and must be added manually + // for the signature to match + let mut host_header_available = false; + for (header_name, header_value) in actix_req.headers() { + if header_name.as_str() == http::header::HOST.as_str() { + host_header_available = true; + } + http_request_builder = + http_request_builder.header(header_name.as_str(), header_value.as_bytes()); + } + if !host_header_available { + debug!( + "Sigv4 Middleware - Adding missing HOST header: {}", + actix_req.connection_info().host() + ); + http_request_builder = http_request_builder.header( + http::header::HOST, + actix_req.connection_info().host().as_bytes(), + ); + } + // http_request_builder = + // http_request_builder.header(http::header::HOST, "localhost:9998".as_bytes()); + + let http_request = http_request_builder.body(body.to_vec()).map_err(|e| { + actix_web::error::ErrorBadRequest(format!( + "Failed to rebuild request for Sigv4 validation: {e:?}" + )) + })?; + + Ok(http_request) +} + +/// Retrieves the AWS XKS sigv4 signing key from the KMS server +#[allow(dead_code)] +async fn get_aws_key( + kms_server: &Arc, + sigv4_access_key_id: &str, + sigv4_access_key_user: &str, +) -> Result>, actix_web::error::InternalError> { + kms_server + .get( + Get { + unique_identifier: Some(UniqueIdentifier::TextString( + sigv4_access_key_id.to_owned(), + )), + key_format_type: Some(KeyFormatType::Raw), + ..Default::default() + }, + sigv4_access_key_user, + ) + .await + .map_err(|e| { + InternalError::new( + format!("Failed to get AWS XKS sigv4 key from KMS: {e:?}"), + StatusCode::INTERNAL_SERVER_ERROR, + ) + })? + .object + .key_block() + .map_err(|e| { + InternalError::new( + format!("Failed to get AWS XKS sigv4 key block from KMS: {e:?}"), + StatusCode::INTERNAL_SERVER_ERROR, + ) + })? + .secret_data_bytes() + .map_err(|e| { + InternalError::new( + format!("Failed to get AWS XKS sigv4 key bytes from KMS: {e:?}"), + StatusCode::INTERNAL_SERVER_ERROR, + ) + }) +} diff --git a/crate/server/src/routes/mod.rs b/crate/server/src/routes/mod.rs index c21cfb77dd..a74252554e 100644 --- a/crate/server/src/routes/mod.rs +++ b/crate/server/src/routes/mod.rs @@ -18,6 +18,7 @@ const CLI_ARCHIVE_FOLDER: &str = "./resources"; const CLI_ARCHIVE_FILE_NAME: &str = "cli.zip"; pub mod access; +pub mod aws_xks; pub mod google_cse; pub mod health; pub mod kmip; diff --git a/crate/server/src/socket_server.rs b/crate/server/src/socket_server.rs index f0aea86c11..07183f00ee 100644 --- a/crate/server/src/socket_server.rs +++ b/crate/server/src/socket_server.rs @@ -28,13 +28,10 @@ pub struct SocketServerParams<'a> { pub port: u16, /// Server certificate and private key (PKCS#12 format) - non-fips #[cfg(feature = "non-fips")] - pub p12: &'a openssl::pkcs12::ParsedPkcs12_2, + pub p12: Option<&'a openssl::pkcs12::ParsedPkcs12_2>, /// Server certificate and private key (PEM) - FIPS mode - #[cfg(not(feature = "non-fips"))] pub server_cert_pem: &'a [u8], - #[cfg(not(feature = "non-fips"))] pub server_key_pem: &'a [u8], - #[cfg(not(feature = "non-fips"))] pub server_chain_pem: Option<&'a [u8]>, /// Client CA certificate (PEM format, X509) pub client_ca_cert_pem: &'a [u8], @@ -56,28 +53,17 @@ impl<'a> TryFrom<&'a ServerParams> for SocketServerParams<'a> { "The Socket server cannot be started: Client CA certificate is not set".to_owned(), )); }; - #[cfg(feature = "non-fips")] - { - Ok(Self { - host: params.socket_server_hostname.clone(), - port: params.socket_server_port, - p12: &tls_params.p12, - client_ca_cert_pem, - cipher_suites: tls_params.cipher_suites.as_ref(), - }) - } - #[cfg(not(feature = "non-fips"))] - { - Ok(Self { - host: params.socket_server_hostname.clone(), - port: params.socket_server_port, - server_cert_pem: &tls_params.server_cert_pem, - server_key_pem: &tls_params.server_key_pem, - server_chain_pem: tls_params.server_chain_pem.as_deref(), - client_ca_cert_pem, - cipher_suites: tls_params.cipher_suites.as_ref(), - }) - } + Ok(Self { + host: params.socket_server_hostname.clone(), + port: params.socket_server_port, + #[cfg(feature = "non-fips")] + p12: tls_params.p12.as_ref(), + client_ca_cert_pem, + cipher_suites: tls_params.cipher_suites.as_ref(), + server_cert_pem: &tls_params.server_cert_pem, + server_key_pem: &tls_params.server_key_pem, + server_chain_pem: tls_params.server_chain_pem.as_deref(), + }) } } @@ -426,25 +412,14 @@ pub(crate) fn create_openssl_acceptor(server_config: &SocketServerParams) -> KRe trace!("Creating OpenSSL SslAcceptor for socket server"); // Use the common TLS configuration - let tls_config = { + let tls_config = TlsConfig { #[cfg(feature = "non-fips")] - { - TlsConfig { - cipher_suites: server_config.cipher_suites.map(std::string::String::as_str), - p12: server_config.p12, - client_ca_cert_pem: Some(server_config.client_ca_cert_pem), - } - } - #[cfg(not(feature = "non-fips"))] - { - TlsConfig { - cipher_suites: server_config.cipher_suites.map(std::string::String::as_str), - server_cert_pem: server_config.server_cert_pem, - server_key_pem: server_config.server_key_pem, - server_chain_pem: server_config.server_chain_pem, - client_ca_cert_pem: Some(server_config.client_ca_cert_pem), - } - } + p12: server_config.p12, + cipher_suites: server_config.cipher_suites.map(std::string::String::as_str), + server_cert_pem: server_config.server_cert_pem, + server_key_pem: server_config.server_key_pem, + server_chain_pem: server_config.server_chain_pem, + client_ca_cert_pem: Some(server_config.client_ca_cert_pem), }; let mut builder = create_base_openssl_acceptor(&tls_config, "socket server")?; diff --git a/crate/server/src/start_kms_server.rs b/crate/server/src/start_kms_server.rs index d11f8d28de..7a05b36203 100644 --- a/crate/server/src/start_kms_server.rs +++ b/crate/server/src/start_kms_server.rs @@ -55,7 +55,9 @@ use crate::{ }, result::{KResult, KResultHelper}, routes::{ - access, cli_archive_download, cli_archive_exists, get_version, + access, + aws_xks::{self}, + cli_archive_download, cli_archive_exists, get_version, google_cse::{self, GoogleCseConfig}, health, kmip::{self, handle_ttlv_bytes}, @@ -679,6 +681,9 @@ pub async fn prepare_kms_server(kms_server: Arc) -> KResult> = kms_server.params.privileged_users.clone(); // Compute the public URL first so we can use it to derive the session key @@ -756,6 +761,21 @@ pub async fn prepare_kms_server(kms_server: Arc) -> KResult KResult) -> ClapConf "../../test_data/certificates/client_server/ca/ca.crt", )), tls_cipher_suites: None, + ..Default::default() }; ClapConfig { diff --git a/crate/server/src/tests/ttlv_tests/config.rs b/crate/server/src/tests/ttlv_tests/config.rs index efce2fc3a1..3c89abffbf 100644 --- a/crate/server/src/tests/ttlv_tests/config.rs +++ b/crate/server/src/tests/ttlv_tests/config.rs @@ -29,9 +29,12 @@ fn load_test_config() -> SocketServerParams<'static> { SocketServerParams { host: TEST_HOST.to_owned(), port: 11117, - p12: &TEST_P12, + p12: Some(&TEST_P12), client_ca_cert_pem: &TEST_CLIENT_CA_CERT_PEM, cipher_suites: None, + server_cert_pem: &[], + server_key_pem: &[], + server_chain_pem: None, } } diff --git a/crate/server/src/tls_config.rs b/crate/server/src/tls_config.rs index 370cf62426..4c7b4cf652 100644 --- a/crate/server/src/tls_config.rs +++ b/crate/server/src/tls_config.rs @@ -1,9 +1,8 @@ use cosmian_logger::trace; #[cfg(feature = "non-fips")] use openssl::pkcs12::ParsedPkcs12_2; -#[cfg(not(feature = "non-fips"))] -use openssl::pkey::PKey; use openssl::{ + pkey::PKey, ssl::{SslAcceptor, SslAcceptorBuilder, SslMethod, SslVerifyMode, SslVersion}, x509::{X509, store::X509StoreBuilder}, }; @@ -22,12 +21,9 @@ const TLS13_CIPHER_SUITES: &[&str] = &[ pub struct TlsConfig<'a> { pub cipher_suites: Option<&'a str>, #[cfg(feature = "non-fips")] - pub p12: &'a ParsedPkcs12_2, - #[cfg(not(feature = "non-fips"))] + pub p12: Option<&'a ParsedPkcs12_2>, pub server_cert_pem: &'a [u8], - #[cfg(not(feature = "non-fips"))] pub server_key_pem: &'a [u8], - #[cfg(not(feature = "non-fips"))] pub server_chain_pem: Option<&'a [u8]>, pub client_ca_cert_pem: Option<&'a [u8]>, } @@ -61,18 +57,18 @@ pub(crate) fn create_base_openssl_acceptor( // Configure the server certificate and private key #[cfg(feature = "non-fips")] { - configure_server_certificate_p12(&mut builder, config.p12, server_type)?; - } - #[cfg(not(feature = "non-fips"))] - { - configure_server_certificate_pem( - &mut builder, - config.server_cert_pem, - config.server_key_pem, - config.server_chain_pem, - server_type, - )?; + if let Some(p12) = config.p12 { + configure_server_certificate_p12(&mut builder, p12, server_type)?; + return Ok(builder); + } } + configure_server_certificate_pem( + &mut builder, + config.server_cert_pem, + config.server_key_pem, + config.server_chain_pem, + server_type, + )?; Ok(builder) } @@ -156,7 +152,6 @@ fn configure_server_certificate_p12( } /// Configure server certificate and private key from PEM files (FIPS mode) -#[cfg(not(feature = "non-fips"))] fn configure_server_certificate_pem( builder: &mut SslAcceptorBuilder, cert_pem: &[u8], diff --git a/crate/test_kms_server/src/lib.rs b/crate/test_kms_server/src/lib.rs index 8df73f452f..5de0b93e89 100644 --- a/crate/test_kms_server/src/lib.rs +++ b/crate/test_kms_server/src/lib.rs @@ -7,7 +7,8 @@ pub use test_server::{ start_default_test_kms_server_with_non_revocable_key_ids, start_default_test_kms_server_with_privileged_users, start_default_test_kms_server_with_utimaco_and_kek, - start_default_test_kms_server_with_utimaco_hsm, start_test_server_with_options, + start_default_test_kms_server_with_utimaco_hsm, start_test_kms_server_with_config, + start_test_server_with_options, }; mod test_server; diff --git a/crate/test_kms_server/src/test_server.rs b/crate/test_kms_server/src/test_server.rs index 4acd3b7f49..6089f94241 100644 --- a/crate/test_kms_server/src/test_server.rs +++ b/crate/test_kms_server/src/test_server.rs @@ -10,7 +10,7 @@ use std::{ use actix_server::ServerHandle; use cosmian_kms_client::{ GmailApiConf, KmsClient, KmsClientConfig, KmsClientError, - cosmian_kmip::time_normalize, + cosmian_kmip::{KmipResultHelper, time_normalize}, kmip_0::kmip_types::CryptographicUsageMask, kmip_2_1::{ kmip_attributes::Attributes, @@ -189,6 +189,25 @@ fn get_db_config(_port: u16, workspace_dir: Option<&PathBuf>) -> MainDBConfig { ) } +/// Start a test KMS server in a thread with the default options: +/// No TLS, no certificate authentication +/// # Panics +/// - if the server fails to start +pub async fn start_test_kms_server_with_config(config: ClapConfig) -> &'static TestsContext { + trace!("Starting test server with config : {:#?}", config); + ONCE.get_or_try_init(|| async move { + let server_params = ServerParams::try_from(config).context( + "Failed to create ServerParams from ClapConfig in start_default_test_kms_server", + )?; + start_from_server_params(server_params).await + }) + .await + .unwrap_or_else(|e| { + error!("failed to start default test server: {e}"); + std::process::abort(); + }) +} + /// Start a test KMS server in a thread with the default options: /// No TLS, no certificate authentication /// # Panics @@ -780,6 +799,7 @@ fn server_tls_config(mode: TlsMode, server_tls_cipher_suites: Option) -> tls_p12_password: Some("password".to_owned()), clients_ca_cert_file: clients_ca, tls_cipher_suites: server_tls_cipher_suites, + ..Default::default() } } #[cfg(not(feature = "non-fips"))] diff --git a/crate/wasm/Cargo.toml b/crate/wasm/Cargo.toml index 1b5b21fed1..bc27d520ab 100644 --- a/crate/wasm/Cargo.toml +++ b/crate/wasm/Cargo.toml @@ -33,7 +33,7 @@ pem = { workspace = true } serde = { workspace = true } serde-wasm-bindgen = "0.6.5" serde_json = { workspace = true } -wasm-bindgen = "0.2.100" +wasm-bindgen = "0.2.108" console_error_panic_hook = "0.1" x509-cert = { workspace = true, features = ["pem"] } zeroize = { workspace = true } diff --git a/deny.toml b/deny.toml index debb6c611d..97789d85a6 100644 --- a/deny.toml +++ b/deny.toml @@ -75,6 +75,8 @@ ignore = [ # { id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" }, # "a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish # { crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" }, + { id = "RUSTSEC-2025-0009", reason = "Pinned to scratchstack-aws-signature 0.10 (Feb 2026) which depends on ring 0.16.x" }, + { id = "RUSTSEC-2025-0010", reason = "Pinned to scratchstack-aws-signature 0.10 (Feb 2026) which depends on ring 0.16.x" }, ] # If this is true, then cargo deny will use the git executable to fetch advisory database. # If this is false, then it uses a built-in git library. @@ -94,6 +96,7 @@ allow = [ "Apache-2.0", "Apache-2.0 WITH LLVM-exception", "ISC", + "OpenSSL", "Zlib", "BSD-2-Clause", "BSD-3-Clause", diff --git a/documentation/docs/aws/1_configure_key.png b/documentation/docs/aws/1_configure_key.png new file mode 100644 index 0000000000..90ee93b73c Binary files /dev/null and b/documentation/docs/aws/1_configure_key.png differ diff --git a/documentation/docs/aws/2_choose_external_key.png b/documentation/docs/aws/2_choose_external_key.png new file mode 100644 index 0000000000..aa77e97baa Binary files /dev/null and b/documentation/docs/aws/2_choose_external_key.png differ diff --git a/documentation/docs/aws/7_review.png b/documentation/docs/aws/7_review.png new file mode 100644 index 0000000000..98deab567a Binary files /dev/null and b/documentation/docs/aws/7_review.png differ diff --git a/documentation/docs/aws/xks.md b/documentation/docs/aws/xks.md new file mode 100644 index 0000000000..626ef6d6af --- /dev/null +++ b/documentation/docs/aws/xks.md @@ -0,0 +1,64 @@ +# Integration to AWS External Key Service (XKS) + +## Background + +AWS XKS (External Key Store) is a feature of AWS Key Management Service (AWS KMS) that allows you to use cryptographic keys stored in an external key management system with AWS KMS. +It enables you to maintain control over your keys while leveraging AWS services that integrate with AWS KMS. + +**Source:** [AWS KMS XKS Proxy API Specification - Background](https://github.com/aws/aws-kms-xksproxy-api-spec/blob/main/xks_proxy_api_spec.md#background) + +## Architecture + +The Cosmian KMS integrates to AWS XKS and proposes a novel architecture (dubbed *xksv2*) that solves the traditional XKS performance issues without compromising on security. + +![xksv2 architecture diagram](./xksv2.drawio.svg) + +The Cosmian XKSv2 architecture is composed of the following components: + +### Cosmian Confidential KMS + +This is the Confidential Key Management System, deployed as IaaS, in the customer AWS tenant. +It is responsible for managing the Key Encryption Keys (KEKs) wrapping the XKS keys in AWS KMS and for answering encryption and decryption requests from the AWS KMS. + +To protect the KEKs, the Cosmian KMS runs inside a Cosmian VM on top of confidential computing machines. Cosmian VM provides strong security and verifiability guarantees. + +The Cosmian KMS is deployed in AWS infrastructure, solving the XKS scaling problem, as it benefits from a stable high bandwidth network and can easily scale to reliably support large amount of transactions from the AWS KMS. + +The Confidential KMS is available as a ready-to-deploy product from the [AWS Marketplace](https://aws.amazon.com/marketplace/search/results?searchTerms=COSMIAN+KMS). + +### HSM + +The HSM is responsible for storing the Master keys and securing the Cosmian KMS keys. It is deployed in the customer premises or offered as a managed service by Atos. See the [HSM integration documentation](../hsms/index.md) for more details. + + +## Deployment + +1. Deploy a Cosmian KMS in your AWS tenant. You can find the product on the [AWS Marketplace](https://aws.amazon.com/marketplace/search/results?searchTerms=COSMIAN+KMS) and follow the deployment instructions in the product documentation. + +2. Configure the KMS for use with AWS XKS by filling up the `aws_xks_config` section of the configuration file with the following values: + +```toml +[aws_xks_config] +# set this to true +aws_xks_enable = true +# this is the region you Cosmian KMS is deployed in +aws_xks_region = "us-east-1" +# keep this to this value +aws_xks_service = "xks-kms" +# used for sigv4. The values set here must match the values configured +# when setting up the KMS as an external keystore for AWS KMS (see next step) +aws_xks_sigv4_access_key_id = "AKIAIOSFODNN7EXAMPLE" +aws_xks_sigv4_secret_access_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" +``` + +3. Configure the KMS to act as an External Key Store for AWS KMS. Follow the instructions in the [AWS documentation](https://docs.aws.amazon.com/kms/latest/developerguide/create-xks-keystore.html) to create an External Key Store. + +4. Create an external key in AWS KMS and specify the key store created in the previous step as the key store for the key. + +![Configure the key](./1_configure_key.png) +![Choose the external key](./2_choose_external_key.png) +![Review the key and create it](./7_review.png) + +5. Enforce the correct permissions for the key on the Cosmian KMS. +Make sure the user used by AWS has the permissions for `Encrypt`, `Decrypt` and `GetAttributes`. For instance, when using DynamoDB, the user should be called something like `dynamodb.amazonaws.com`, for Salesforce, it is the user configured as part of the setup. +In doubt, or for testing, grant theses permissions to all users (`*`). \ No newline at end of file diff --git a/documentation/docs/aws/xksv2.drawio.svg b/documentation/docs/aws/xksv2.drawio.svg new file mode 100644 index 0000000000..930f8caf82 --- /dev/null +++ b/documentation/docs/aws/xksv2.drawio.svg @@ -0,0 +1,694 @@ + + + + + + + + + + + + + + + + + +
+
+
+ + TLS PKCS#11 + +
+
+
+
+ + TLS PKCS#11 + +
+
+
+ + + + + + + + + + + + + + + + + +
+
+
+ + + Confidential Computing +
+ AMD SEV-SNP +
+
+
+
+
+
+
+ + Confidential Computing... + +
+
+
+ + + + + + + +
+
+
+ + + + +
+
+
+
+ + + + +
+
+
+ + + + + + + + + + + + + + +
+
+
+ + + Proteccio +
+ HSM +
+
+
+
+
+
+ + Proteccio... + +
+
+
+ + + + + + + + + + + + + + + + + + + + +
+
+
+ + + AWS +
+ KMS +
+
+
+
+
+
+
+ + AWS... + +
+
+
+ + + + + + + + + + +
+
+
+ + + AWS Services +
+
+
+
+
+
+
+ + AWS Services + +
+
+
+ + + + + + + + + + +
+
+
+ HTTPS +
+ XKS +
+
+
+
+ + HTTPS... + +
+
+
+ + + + + + + + +
+
+
+
+ + + Key Wrapping / Unwrapping + + +
+
+
+
+
+ + Key Wrapping / Unwrapping + +
+
+
+ + + + + + + + +
+
+
+ [ 🔑 ] +
+
+
+
+ + [ 🔑 ] + +
+
+
+ + + + + + + + + +
+ + + + + Text is not SVG - cannot display + + + +
\ No newline at end of file diff --git a/documentation/docs/drawings/xks_v2.drawio.svg b/documentation/docs/drawings/xks_v2.drawio.svg deleted file mode 100644 index 90fb1a045b..0000000000 --- a/documentation/docs/drawings/xks_v2.drawio.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - -
TLS PKCS#11
TLS PKCS#11
Confidential Computing
AMD SEV-SNP

Confidential Computing...
+
+
Proteccio
Proteccio
AWS
KMS
AWS...
AWS Services
AWS Services
HTTPS
XKS
HTTPS...
         Key Wrapping / Unwrapping
         Key Wrapping / Unwrapping
[ 🔑 ]
[ 🔑 ]
Text is not SVG - cannot display
\ No newline at end of file diff --git a/documentation/mkdocs.yml b/documentation/mkdocs.yml index 03c33d3a98..188c2c0601 100644 --- a/documentation/mkdocs.yml +++ b/documentation/mkdocs.yml @@ -74,7 +74,9 @@ nav: - Other HSMs: hsms/other_hsms.md - Integrations: - API Endpoints: api.md - - AWS ECS Fargate: aws_fargate.md + - AWS: + - ECS Fargate: aws_fargate.md + - External Key Store (XKS): ./aws/xks.md - Azure: - BYOK (Bring Your Own Key): azure/byok.md - Google GCP: diff --git a/nix/expected-hashes/server.vendor.dynamic.darwin.sha256 b/nix/expected-hashes/server.vendor.dynamic.darwin.sha256 index 1237af0dab..223e71ada1 100644 --- a/nix/expected-hashes/server.vendor.dynamic.darwin.sha256 +++ b/nix/expected-hashes/server.vendor.dynamic.darwin.sha256 @@ -1 +1 @@ -sha256-howIOhqJnUoJ4uGCxdNq34ktEMnSPCQt9yBgMaXUCKc= +sha256-fjXhpzGfYjxyjvQ+1EWf6DLzZhEkrggGE9BEu51pHDs= diff --git a/nix/expected-hashes/server.vendor.dynamic.linux.sha256 b/nix/expected-hashes/server.vendor.dynamic.linux.sha256 index 1237af0dab..223e71ada1 100644 --- a/nix/expected-hashes/server.vendor.dynamic.linux.sha256 +++ b/nix/expected-hashes/server.vendor.dynamic.linux.sha256 @@ -1 +1 @@ -sha256-howIOhqJnUoJ4uGCxdNq34ktEMnSPCQt9yBgMaXUCKc= +sha256-fjXhpzGfYjxyjvQ+1EWf6DLzZhEkrggGE9BEu51pHDs= diff --git a/nix/expected-hashes/server.vendor.static.darwin.sha256 b/nix/expected-hashes/server.vendor.static.darwin.sha256 index 358e394ecb..b3d1649246 100644 --- a/nix/expected-hashes/server.vendor.static.darwin.sha256 +++ b/nix/expected-hashes/server.vendor.static.darwin.sha256 @@ -1 +1 @@ -sha256-lc3iUbmgqSb3ZiEBjaSpbnoWJUKJYNCKNb2LYsRgaLc= +sha256-JKtQ1oKp+TK0SJnJgptAEAHGQHcMH8QVdFLlPfFVujQ= diff --git a/nix/expected-hashes/server.vendor.static.linux.sha256 b/nix/expected-hashes/server.vendor.static.linux.sha256 index 358e394ecb..b3d1649246 100644 --- a/nix/expected-hashes/server.vendor.static.linux.sha256 +++ b/nix/expected-hashes/server.vendor.static.linux.sha256 @@ -1 +1 @@ -sha256-lc3iUbmgqSb3ZiEBjaSpbnoWJUKJYNCKNb2LYsRgaLc= +sha256-JKtQ1oKp+TK0SJnJgptAEAHGQHcMH8QVdFLlPfFVujQ= diff --git a/nix/expected-hashes/ui.vendor.fips.sha256 b/nix/expected-hashes/ui.vendor.fips.sha256 index 31b88f4753..556a0ef9c5 100644 --- a/nix/expected-hashes/ui.vendor.fips.sha256 +++ b/nix/expected-hashes/ui.vendor.fips.sha256 @@ -1 +1 @@ -sha256-BSmBMTtDNgJJJ/5s2HyxQdS0vz/c/+jJ2DttfDaghYE= +sha256-9DF75WkkxR2ideROF/5Rk1jTIwCEpt8lPmwPlT4p0II= diff --git a/nix/expected-hashes/ui.vendor.non-fips.sha256 b/nix/expected-hashes/ui.vendor.non-fips.sha256 index 408558e17d..f475b1f719 100644 --- a/nix/expected-hashes/ui.vendor.non-fips.sha256 +++ b/nix/expected-hashes/ui.vendor.non-fips.sha256 @@ -1 +1 @@ -sha256-D8mmERNuf/f6GaZsD+6WMSITlOJzYo7RBQiEWIsy/7A= +sha256-X2LEqHEZ7nBUJNCaK+JghK9mcIfTrPkfLzqJ3ZnFYm4= diff --git a/shell.nix b/shell.nix index bb60762b41..cc724c4bcd 100644 --- a/shell.nix +++ b/shell.nix @@ -35,6 +35,7 @@ let withHsm = (builtins.getEnv "WITH_HSM") == "1"; withPython = (builtins.getEnv "WITH_PYTHON") == "1"; withCurl = (builtins.getEnv "WITH_CURL") == "1"; + withXks = (builtins.getEnv "WITH_XKS") == "1"; # Import FIPS OpenSSL 3.1.2 - will be used for FIPS builds openssl312Fips = import ./nix/openssl.nix { inherit (pkgs) @@ -76,12 +77,30 @@ pkgs.mkShell { openssl312Fips openssl312FipsShared pkgs.openssl + pkgs.openssl.dev pkgs.pkg-config pkgs.gcc pkgs.rust-bin.stable.latest.default opensslFipsBootstrap ] ++ (if withCurl then [ pkgs.curl ] else [ ]) + ++ ( + if withXks then + [ + pkgs.bash + pkgs.curl + pkgs.jq + pkgs.coreutils + pkgs.gawk + pkgs.gnused + pkgs.vim + pkgs.xxd + # Provides `uuidgen` in the pure nix-shell environment. + pkgs.util-linux + ] + else + [ ] + ) ++ ( if withHsm then [ diff --git a/test_data b/test_data index 2d72a12b75..0f057b0b95 160000 --- a/test_data +++ b/test_data @@ -1 +1 @@ -Subproject commit 2d72a12b7548bf672fe2d6cfe8db6f36d407bfc4 +Subproject commit 0f057b0b95b1ba0d659a15dfeb781e2ebd82fa15