refactor: move all CLI relative crates on https://github.com/Cosmian/cli/actions (#383)
* fix: merge ckms with cosmian CLI * fix: rebase with develop * chore: update submodule cli * ci: fix release step * chore: update submodule cli * chore: update submodule cli * chore: update covercrypt to v15 * chore: update covercrypt to v15 * chore: update pre-commit * ci: let cargo test capture stdout * chore: revert cargo fmt of new rust toolchain... * chore: revert cargo fmt of new rust toolchain... * fix: no formatting for the time being - wait for KMIP 1.x * ci: align rust toolchain version * chore: use CLI develop branch * chore: use CLI develop branch * docs: add git submodule pulling command
This commit is contained in:
parent
5a826b465d
commit
c6faaf751f
329 changed files with 711 additions and 31990 deletions
18
.github/scripts/cargo_build.ps1
vendored
18
.github/scripts/cargo_build.ps1
vendored
|
@ -16,8 +16,8 @@ function BuildProject
|
|||
$env:OPENSSL_DIR = "$env:VCPKG_INSTALLATION_ROOT\packages\openssl_x64-windows-static"
|
||||
Get-ChildItem -Recurse $env:OPENSSL_DIR
|
||||
|
||||
# Build pkcs11 provider
|
||||
Get-ChildItem crate\pkcs11\provider
|
||||
# Build `cosmian`
|
||||
Get-ChildItem cli\crate\cli
|
||||
if ($BuildType -eq "release")
|
||||
{
|
||||
cargo build --release --target x86_64-pc-windows-msvc
|
||||
|
@ -28,21 +28,9 @@ function BuildProject
|
|||
}
|
||||
Get-ChildItem ..\..\..
|
||||
|
||||
# Build `ckms`
|
||||
Get-ChildItem crate\cli
|
||||
if ($BuildType -eq "release")
|
||||
{
|
||||
cargo build --release --target x86_64-pc-windows-msvc
|
||||
}
|
||||
else
|
||||
{
|
||||
cargo build --target x86_64-pc-windows-msvc
|
||||
}
|
||||
Get-ChildItem ..\..
|
||||
|
||||
# Check dynamic links
|
||||
$ErrorActionPreference = "SilentlyContinue"
|
||||
$output = & "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.29.30133\bin\HostX64\x64\dumpbin.exe" /dependents target\x86_64-pc-windows-msvc\$BuildType\ckms.exe -ErrorAction SilentlyContinue | Select-String "libcrypto"
|
||||
$output = & "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.29.30133\bin\HostX64\x64\dumpbin.exe" /dependents target\x86_64-pc-windows-msvc\$BuildType\cosmian.exe -ErrorAction SilentlyContinue | Select-String "libcrypto"
|
||||
if ($output)
|
||||
{
|
||||
throw "OpenSSL (libcrypto) found in dynamic dependencies. Error: $output"
|
||||
|
|
40
.github/scripts/cargo_build.sh
vendored
40
.github/scripts/cargo_build.sh
vendored
|
@ -4,6 +4,8 @@ set -ex
|
|||
|
||||
# --- Declare the following variables for tests
|
||||
# export TARGET=x86_64-unknown-linux-gnu
|
||||
# export TARGET=x86_64-apple-darwin
|
||||
# export TARGET=aarch64-apple-darwin
|
||||
# export DEBUG_OR_RELEASE=debug
|
||||
# export OPENSSL_DIR=/usr/local/openssl
|
||||
# export SKIP_SERVICES_TESTS="--skip test_mysql --skip test_pgsql --skip test_redis --skip google_cse"
|
||||
|
@ -13,7 +15,7 @@ ROOT_FOLDER=$(pwd)
|
|||
|
||||
if [ "$DEBUG_OR_RELEASE" = "release" ]; then
|
||||
# First build the Debian and RPM packages. It must come at first since
|
||||
# after this step `ckms` and `cosmian_kms` are built with custom features flags (fips for example).
|
||||
# after this step `cosmian` and `cosmian_kms` are built with custom features flags (fips for example).
|
||||
rm -rf target/"$TARGET"/debian
|
||||
rm -rf target/"$TARGET"/generate-rpm
|
||||
if [ -f /etc/redhat-release ]; then
|
||||
|
@ -74,18 +76,12 @@ if [ "$DEBUG_OR_RELEASE" = "release" ]; then
|
|||
done
|
||||
fi
|
||||
|
||||
echo "Building crate/pkcs11/provider"
|
||||
cd crate/pkcs11/provider
|
||||
# shellcheck disable=SC2086
|
||||
cargo build --target $TARGET $RELEASE
|
||||
cd "$ROOT_FOLDER"
|
||||
|
||||
if [ -z "$OPENSSL_DIR" ]; then
|
||||
echo "Error: OPENSSL_DIR is not set."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
crates=("crate/server" "crate/cli")
|
||||
crates=("crate/server" "cli/crate/cli")
|
||||
for crate in "${crates[@]}"; do
|
||||
echo "Building $crate"
|
||||
cd "$crate"
|
||||
|
@ -97,34 +93,46 @@ done
|
|||
# Debug
|
||||
# find .
|
||||
|
||||
./target/"$TARGET/$DEBUG_OR_RELEASE"/ckms -h
|
||||
COSMIAN_EXE="cli/target/$TARGET/$DEBUG_OR_RELEASE/cosmian"
|
||||
COSMIAN_KMS_EXE="target/$TARGET/$DEBUG_OR_RELEASE/cosmian_kms"
|
||||
|
||||
./"$COSMIAN_EXE" -h
|
||||
|
||||
# Must use OpenSSL with this specific version 3.2.0
|
||||
OPENSSL_VERSION_REQUIRED="3.2.0"
|
||||
correct_openssl_version_found=$(./target/"$TARGET/$DEBUG_OR_RELEASE"/cosmian_kms --info | grep "$OPENSSL_VERSION_REQUIRED")
|
||||
correct_openssl_version_found=$(./"$COSMIAN_KMS_EXE" --info | grep "$OPENSSL_VERSION_REQUIRED")
|
||||
if [ -z "$correct_openssl_version_found" ]; then
|
||||
echo "Error: The correct OpenSSL version $OPENSSL_VERSION_REQUIRED is not found."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$(uname)" = "Linux" ]; then
|
||||
ldd target/"$TARGET/$DEBUG_OR_RELEASE"/ckms | grep ssl && exit 1
|
||||
ldd target/"$TARGET/$DEBUG_OR_RELEASE"/cosmian_kms | grep ssl && exit 1
|
||||
ldd "$COSMIAN_EXE" | grep ssl && exit 1
|
||||
ldd "$COSMIAN_KMS_EXE" | grep ssl && exit 1
|
||||
else
|
||||
otool -L target/"$TARGET/$DEBUG_OR_RELEASE"/ckms | grep openssl && exit 1
|
||||
otool -L target/"$TARGET/$DEBUG_OR_RELEASE"/cosmian_kms | grep openssl && exit 1
|
||||
otool -L "$COSMIAN_EXE" | grep openssl && exit 1
|
||||
otool -L "$COSMIAN_KMS_EXE" | grep openssl && exit 1
|
||||
fi
|
||||
|
||||
find . -type d -name cosmian-kms -exec rm -rf \{\} \; -print || true
|
||||
rm -f /tmp/*.toml
|
||||
|
||||
export RUST_LOG="cosmian_kms_cli=debug,cosmian_kms_server=debug"
|
||||
export RUST_LOG="cosmian_cli=debug,cosmian_kms_server=info,cosmian_kmip=info"
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
cargo build --target $TARGET $RELEASE $FEATURES
|
||||
|
||||
echo "Database KMS: $KMS_TEST_DB"
|
||||
# shellcheck disable=SC2086
|
||||
cargo test --lib --target $TARGET $RELEASE $FEATURES --workspace -- --nocapture $SKIP_SERVICES_TESTS
|
||||
cargo test -v --workspace --lib --target $TARGET $RELEASE $FEATURES -- $SKIP_SERVICES_TESTS
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
cargo test --workspace --bins --target $TARGET $RELEASE $FEATURES
|
||||
|
||||
if [ "$DEBUG_OR_RELEASE" = "release" ]; then
|
||||
# shellcheck disable=SC2086
|
||||
cargo bench --target $TARGET $FEATURES --no-run
|
||||
fi
|
||||
|
||||
# Uncomment this code to run tests indefinitely
|
||||
# counter=1
|
||||
|
|
13
.github/scripts/cargo_deny.sh
vendored
Normal file
13
.github/scripts/cargo_deny.sh
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
|
||||
# Install cargo deny if not already installed
|
||||
# cargo install --version 0.18.2 cargo-deny --locked
|
||||
|
||||
# Run cargo deny in each directory containing a Cargo.toml
|
||||
find . -name "Cargo.toml" -exec dirname {} \; | while read -r dir; do
|
||||
echo "Running cargo deny check in $dir"
|
||||
(cd "$dir" && cargo deny check advisories)
|
||||
done
|
27
.github/workflows/build_all.yml
vendored
27
.github/workflows/build_all.yml
vendored
|
@ -41,31 +41,31 @@ jobs:
|
|||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- distribution: ubuntu-20.04
|
||||
archive-name: fips_ubuntu_20_04
|
||||
- distribution: ubuntu-22.04
|
||||
archive-name: fips_ubuntu_22_04
|
||||
target: x86_64-unknown-linux-gnu
|
||||
artifacts: |
|
||||
/usr/local/openssl/lib64/ossl-modules/fips.so
|
||||
/usr/local/openssl/ssl/openssl.cnf
|
||||
/usr/local/openssl/ssl/fipsmodule.cnf
|
||||
features: fips
|
||||
skip_services_tests: --skip test_mysql --skip test_postgresql --skip test_redis --skip google_cse
|
||||
|
||||
docker_compose: true
|
||||
|
||||
- distribution: ubuntu-24.04
|
||||
archive-name: ubuntu_24_04
|
||||
target: x86_64-unknown-linux-gnu
|
||||
artifacts: |
|
||||
/usr/local/openssl/lib64/ossl-modules/legacy.so
|
||||
skip_services_tests: --skip test_mysql --skip test_postgresql --skip test_redis --skip google_cse
|
||||
|
||||
docker_compose: true
|
||||
|
||||
- distribution: macos-14
|
||||
archive-name: macos_arm
|
||||
target: aarch64-apple-darwin
|
||||
artifacts: |
|
||||
/usr/local/openssl/lib/ossl-modules/legacy.dylib
|
||||
skip_services_tests: --skip test_mysql --skip test_postgresql --skip test_redis --skip google_cse --skip hsm
|
||||
skip_services_tests: --skip test_mysql --skip test_postgresql --skip test_redis --skip google_cse --skip hsm --skip findex_server
|
||||
docker_compose: false
|
||||
|
||||
name: ${{ matrix.distribution }}
|
||||
uses: ./.github/workflows/build_generic.yml
|
||||
|
@ -79,34 +79,28 @@ jobs:
|
|||
skip_services_tests: ${{ matrix.skip_services_tests }}
|
||||
artifacts: ${{ matrix.artifacts }}
|
||||
features: ${{ matrix.features }}
|
||||
docker_compose: ${{ matrix.docker_compose }}
|
||||
|
||||
linux-mac-release:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- distribution: ubuntu-20.04
|
||||
archive-name: ubuntu_20_04
|
||||
target: x86_64-unknown-linux-gnu
|
||||
artifacts: |
|
||||
/usr/local/openssl/lib64/ossl-modules/legacy.so
|
||||
skip_services_tests: --skip test_mysql --skip test_postgresql --skip test_redis --skip google_cse
|
||||
|
||||
|
||||
- distribution: ubuntu-22.04
|
||||
archive-name: ubuntu_22_04
|
||||
target: x86_64-unknown-linux-gnu
|
||||
artifacts: |
|
||||
/usr/local/openssl/lib64/ossl-modules/legacy.so
|
||||
skip_services_tests: --skip test_mysql --skip test_postgresql --skip test_redis --skip google_cse
|
||||
docker_compose: true
|
||||
|
||||
|
||||
- distribution: macos-13
|
||||
archive-name: macos_intel
|
||||
target: x86_64-apple-darwin
|
||||
skip_services_tests: --skip test_mysql --skip test_postgresql --skip test_redis --skip google_cse --skip hsm --skip findex_server
|
||||
artifacts: |
|
||||
/usr/local/openssl/lib/ossl-modules/legacy.dylib
|
||||
skip_services_tests: --skip test_mysql --skip test_postgresql --skip test_redis --skip google_cse --skip hsm
|
||||
docker_compose: false
|
||||
|
||||
|
||||
name: ${{ matrix.distribution }}
|
||||
|
@ -121,6 +115,7 @@ jobs:
|
|||
debug_or_release: ${{ inputs.debug_or_release }}
|
||||
skip_services_tests: ${{ matrix.skip_services_tests }}
|
||||
artifacts: ${{ matrix.artifacts }}
|
||||
docker_compose: ${{ matrix.docker_compose }}
|
||||
|
||||
windows-2022:
|
||||
if: inputs.debug_or_release == 'release'
|
||||
|
|
5
.github/workflows/build_docker_image.yml
vendored
5
.github/workflows/build_docker_image.yml
vendored
|
@ -34,7 +34,10 @@ jobs:
|
|||
- name: Install tar
|
||||
run: apk add --no-cache tar
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
|
||||
- name: Login to GitHub Packages
|
||||
uses: docker/login-action@v2
|
||||
|
|
47
.github/workflows/build_generic.yml
vendored
47
.github/workflows/build_generic.yml
vendored
|
@ -28,6 +28,10 @@ on:
|
|||
skip_services_tests:
|
||||
required: false
|
||||
type: string
|
||||
docker_compose:
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
env:
|
||||
OPENSSL_DIR: /usr/local/openssl
|
||||
|
@ -41,7 +45,9 @@ jobs:
|
|||
if: contains(runner.os, 'Linux')
|
||||
run: cat /proc/cpuinfo
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- run: |
|
||||
sudo mkdir -p ${{ env.OPENSSL_DIR }}/ssl
|
||||
|
@ -56,12 +62,34 @@ jobs:
|
|||
toolchain: ${{ inputs.toolchain }}
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Build
|
||||
- name: Run docker containers
|
||||
if: ${{ inputs.docker_compose }}
|
||||
run: |
|
||||
docker compose -h
|
||||
docker compose up -d
|
||||
|
||||
- name: Build and tests
|
||||
env:
|
||||
OPENSSL_DIR: ${{ env.OPENSSL_DIR }}
|
||||
|
||||
POSTGRES_USER: kms
|
||||
PGUSER: kms
|
||||
POSTGRES_PASSWORD: kms
|
||||
POSTGRES_DB: kms
|
||||
KMS_POSTGRES_URL: postgresql://kms:kms@127.0.0.1:5432/kms
|
||||
|
||||
MYSQL_DATABASE: kms
|
||||
MYSQL_ROOT_PASSWORD: kms
|
||||
KMS_MYSQL_URL: mysql://kms:kms@localhost:3306/kms
|
||||
|
||||
KMS_SQLITE_PATH: data/shared
|
||||
|
||||
# Google variables
|
||||
TEST_GOOGLE_OAUTH_CLIENT_ID: ${{ secrets.TEST_GOOGLE_OAUTH_CLIENT_ID }}
|
||||
TEST_GOOGLE_OAUTH_CLIENT_SECRET: ${{ secrets.TEST_GOOGLE_OAUTH_CLIENT_SECRET }}
|
||||
TEST_GOOGLE_OAUTH_REFRESH_TOKEN: ${{ secrets.TEST_GOOGLE_OAUTH_REFRESH_TOKEN }}
|
||||
GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY }}
|
||||
|
||||
TARGET: ${{ inputs.target }}
|
||||
DEBUG_OR_RELEASE: ${{ inputs.debug_or_release }}
|
||||
FEATURES: ${{ inputs.features }}
|
||||
|
@ -78,9 +106,8 @@ jobs:
|
|||
with:
|
||||
name: ${{ inputs.archive-name }}-${{ inputs.debug_or_release }}
|
||||
path: |
|
||||
target/${{ inputs.target }}/${{ inputs.debug_or_release }}/ckms
|
||||
target/${{ inputs.target }}/${{ inputs.debug_or_release }}/cosmian
|
||||
target/${{ inputs.target }}/${{ inputs.debug_or_release }}/cosmian_kms
|
||||
target/${{ inputs.target }}/${{ inputs.debug_or_release }}/libckms_pkcs11.*
|
||||
target/${{ inputs.target }}/debian/*.deb
|
||||
${{ inputs.artifacts }}
|
||||
retention-days: 1
|
||||
|
@ -98,7 +125,7 @@ jobs:
|
|||
- run: find .
|
||||
if: contains(runner.os, 'linux')
|
||||
|
||||
- name: Linux launch ckms and cosmian_kms
|
||||
- name: Linux launch cosmian and cosmian_kms
|
||||
if: contains(runner.os, 'Linux')
|
||||
run: |
|
||||
set -ex
|
||||
|
@ -109,10 +136,10 @@ jobs:
|
|||
# usr/local/openssl/
|
||||
# home/runner/work/kms/kms/target/
|
||||
KMS_PATH="home/runner/work/kms/kms/target/${{ inputs.target }}/${{ inputs.debug_or_release }}"
|
||||
chmod u+x ./$KMS_PATH/ckms
|
||||
chmod u+x ./$KMS_PATH/cosmian
|
||||
chmod u+x ./$KMS_PATH/cosmian_kms
|
||||
|
||||
./$KMS_PATH/ckms -V
|
||||
./$KMS_PATH/cosmian -V
|
||||
|
||||
# Copy openssl build for Legacy mode
|
||||
sudo mkdir /usr/local/openssl
|
||||
|
@ -122,7 +149,7 @@ jobs:
|
|||
chmod u+x /usr/local/openssl/lib64/ossl-modules/*.so
|
||||
./$KMS_PATH/cosmian_kms --info
|
||||
|
||||
- name: MacOS launch ckms and cosmian_kms
|
||||
- name: MacOS launch cosmian and cosmian_kms
|
||||
if: contains(runner.os, 'macos')
|
||||
run: |
|
||||
set -ex
|
||||
|
@ -134,10 +161,10 @@ jobs:
|
|||
# usr/local/openssl/
|
||||
# Users/runner/work/kms/kms/target/
|
||||
KMS_PATH="Users/runner/work/kms/kms/target/${{ inputs.target }}/${{ inputs.debug_or_release }}"
|
||||
chmod u+x ./$KMS_PATH/ckms
|
||||
chmod u+x ./$KMS_PATH/cosmian
|
||||
chmod u+x ./$KMS_PATH/cosmian_kms
|
||||
|
||||
./$KMS_PATH/ckms -V
|
||||
./$KMS_PATH/cosmian -V
|
||||
|
||||
# Copy openssl build for Legacy mode
|
||||
sudo mkdir /usr/local/openssl
|
||||
|
|
17
.github/workflows/build_rhel9.yml
vendored
17
.github/workflows/build_rhel9.yml
vendored
|
@ -75,8 +75,6 @@ jobs:
|
|||
- name: Display cpuinfo
|
||||
run: cat /proc/cpuinfo
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: RHEL 9 prerequisites
|
||||
run: |
|
||||
set -x
|
||||
|
@ -84,6 +82,11 @@ jobs:
|
|||
yum -y install python-devel
|
||||
yum -y install wget
|
||||
yum -y install perl-IPC-Cmd perl-Digest-SHA1 perl-CPAN perl-devel
|
||||
yum -y install git
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
|
@ -118,6 +121,7 @@ jobs:
|
|||
KMS_SQLITE_PATH: data/shared
|
||||
|
||||
REDIS_HOST: redis
|
||||
REDIS_URL: redis://redis:6379
|
||||
|
||||
# Google variables
|
||||
TEST_GOOGLE_OAUTH_CLIENT_ID: ${{ secrets.TEST_GOOGLE_OAUTH_CLIENT_ID }}
|
||||
|
@ -143,9 +147,8 @@ jobs:
|
|||
with:
|
||||
name: ${{ inputs.archive-name }}-${{ inputs.debug_or_release }}
|
||||
path: |
|
||||
target/${{ inputs.target }}/${{ inputs.debug_or_release }}/ckms
|
||||
target/${{ inputs.target }}/${{ inputs.debug_or_release }}/cosmian
|
||||
target/${{ inputs.target }}/${{ inputs.debug_or_release }}/cosmian_kms
|
||||
target/${{ inputs.target }}/${{ inputs.debug_or_release }}/libckms_pkcs11.*
|
||||
target/${{ inputs.target }}/generate-rpm/cosmian_kms_server-*
|
||||
${{ inputs.artifacts }}
|
||||
retention-days: 1
|
||||
|
@ -164,7 +167,7 @@ jobs:
|
|||
- run: find .
|
||||
if: contains(runner.os, 'linux')
|
||||
|
||||
- name: Launch ckms and cosmian_kms
|
||||
- name: Launch cosmian and cosmian_kms
|
||||
if: contains(runner.os, 'Linux') || contains(runner.os, 'macos')
|
||||
run: |
|
||||
# The current path should be /home/runner/work/kms/kms
|
||||
|
@ -174,10 +177,10 @@ jobs:
|
|||
# usr/local/openssl/
|
||||
# __w/kms/kms/target/
|
||||
KMS_PATH="__w/kms/kms/target/${{ inputs.target }}/${{ inputs.debug_or_release }}"
|
||||
chmod u+x ./$KMS_PATH/ckms
|
||||
chmod u+x ./$KMS_PATH/cosmian
|
||||
chmod u+x ./$KMS_PATH/cosmian_kms
|
||||
|
||||
./$KMS_PATH/ckms -V
|
||||
./$KMS_PATH/cosmian -V
|
||||
|
||||
# Copy openssl build for FIPS mode
|
||||
sudo mkdir /usr/local/openssl
|
||||
|
|
11
.github/workflows/build_windows.yml
vendored
11
.github/workflows/build_windows.yml
vendored
|
@ -22,7 +22,9 @@ jobs:
|
|||
- name: Print ENV
|
||||
run: printenv
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
|
@ -81,9 +83,8 @@ jobs:
|
|||
with:
|
||||
name: ${{ inputs.archive-name }}-${{ inputs.debug_or_release }}
|
||||
path: |
|
||||
target/x86_64-pc-windows-msvc/${{ inputs.debug_or_release }}/ckms.exe
|
||||
target/x86_64-pc-windows-msvc/${{ inputs.debug_or_release }}/cosmian.exe
|
||||
target/x86_64-pc-windows-msvc/${{ inputs.debug_or_release }}/cosmian_kms.exe
|
||||
target/x86_64-pc-windows-msvc/${{ inputs.debug_or_release }}/ckms_pkcs11.dll
|
||||
fips.dll
|
||||
legacy.dll
|
||||
retention-days: 1
|
||||
|
@ -109,8 +110,8 @@ jobs:
|
|||
Copy-Item -Path legacy.dll -Destination C:/Windows/System32/OpenSSL/lib/ossl-modules/legacy.dll
|
||||
Get-ChildItem -Recurse C:/Windows/System32/OpenSSL
|
||||
|
||||
- name: Launch ckms and cosmian_kms
|
||||
- name: Launch cosmian and cosmian_kms
|
||||
run: |
|
||||
pwd
|
||||
./target/x86_64-pc-windows-msvc/${{ inputs.debug_or_release }}/ckms.exe -V
|
||||
./target/x86_64-pc-windows-msvc/${{ inputs.debug_or_release }}/cosmian.exe -V
|
||||
./target/x86_64-pc-windows-msvc/${{ inputs.debug_or_release }}/cosmian_kms.exe --info
|
||||
|
|
8
.github/workflows/clippy.yml
vendored
8
.github/workflows/clippy.yml
vendored
|
@ -16,7 +16,9 @@ jobs:
|
|||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Cache dependencies
|
||||
id: cargo_cache
|
||||
|
@ -37,8 +39,8 @@ jobs:
|
|||
components: rustfmt, clippy
|
||||
|
||||
# Ensure all code has been formatted with rustfmt
|
||||
- name: Check formatting
|
||||
run: cargo +${{ inputs.toolchain }} fmt --all -- --check --color always
|
||||
# - name: Check formatting
|
||||
# run: cargo +${{ inputs.toolchain }} fmt --all -- --check --color always
|
||||
|
||||
- name: Static analysis
|
||||
run: cargo clippy --workspace --all-targets -- -D warnings
|
||||
|
|
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
|
@ -9,6 +9,6 @@ jobs:
|
|||
secrets: inherit
|
||||
uses: ./.github/workflows/main_base.yml
|
||||
with:
|
||||
toolchain: nightly-2024-06-09
|
||||
toolchain: nightly-2025-01-01
|
||||
debug_or_release: debug
|
||||
platforms: linux/amd64
|
||||
|
|
17
.github/workflows/main_base.yml
vendored
17
.github/workflows/main_base.yml
vendored
|
@ -19,7 +19,9 @@ jobs:
|
|||
name: Security Audit
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: EmbarkStudios/cargo-deny-action@v1
|
||||
|
||||
cargo-lint:
|
||||
|
@ -74,7 +76,7 @@ jobs:
|
|||
- /home/cosmian/.ssh/id_rsa:/root/.ssh/id_rsa
|
||||
|
||||
env:
|
||||
ARCHIVE_NAMES: rhel9-${{ inputs.debug_or_release }} fips_ubuntu_20_04-${{ inputs.debug_or_release }} ubuntu_24_04-${{ inputs.debug_or_release }} macos_arm-${{
|
||||
ARCHIVE_NAMES: rhel9-${{ inputs.debug_or_release }} fips_ubuntu_22_04-${{ inputs.debug_or_release }} ubuntu_24_04-${{ inputs.debug_or_release }} macos_arm-${{
|
||||
inputs.debug_or_release }}
|
||||
|
||||
steps:
|
||||
|
@ -94,7 +96,6 @@ jobs:
|
|||
|
||||
# Warning, no all binaries in debug, only in release
|
||||
if [ "${{ inputs.debug_or_release }}" == "release" ]; then
|
||||
zip -r ubuntu_20_04-release.zip ubuntu_20_04-release
|
||||
zip -r ubuntu_22_04-release.zip ubuntu_22_04-release
|
||||
zip -r windows-release.zip windows-release
|
||||
zip -r macos_intel-release.zip macos_intel-release
|
||||
|
@ -116,7 +117,7 @@ jobs:
|
|||
cosmian@package.cosmian.com:$DESTINATION_DIR/
|
||||
|
||||
if [ "${{ inputs.debug_or_release }}" == "release" ]; then
|
||||
ssh -o 'StrictHostKeyChecking no' -i /root/.ssh/id_rsa cosmian@package.cosmian.com mkdir -p $DESTINATION_DIR/{rhel9,ubuntu-20.04,ubuntu-22.04,ubuntu-24.04}
|
||||
ssh -o 'StrictHostKeyChecking no' -i /root/.ssh/id_rsa cosmian@package.cosmian.com mkdir -p $DESTINATION_DIR/{rhel9,ubuntu-22.04,ubuntu-24.04}
|
||||
|
||||
# RedHat 9 package
|
||||
scp -o 'StrictHostKeyChecking no' \
|
||||
|
@ -124,9 +125,6 @@ jobs:
|
|||
cosmian@package.cosmian.com:$DESTINATION_DIR/rhel9
|
||||
|
||||
# Ubuntu packages
|
||||
scp -o 'StrictHostKeyChecking no' \
|
||||
-i /root/.ssh/id_rsa ubuntu_20_04-${{ inputs.debug_or_release }}/home/runner/work/kms/kms/target/x86_64-unknown-linux-gnu/debian/*.deb \
|
||||
cosmian@package.cosmian.com:$DESTINATION_DIR/ubuntu-20.04
|
||||
scp -o 'StrictHostKeyChecking no' \
|
||||
-i /root/.ssh/id_rsa ubuntu_22_04-${{ inputs.debug_or_release }}/home/runner/work/kms/kms/target/x86_64-unknown-linux-gnu/debian/*.deb \
|
||||
cosmian@package.cosmian.com:$DESTINATION_DIR/ubuntu-22.04
|
||||
|
@ -142,7 +140,6 @@ jobs:
|
|||
files: |
|
||||
*.zip
|
||||
rhel9-release/__w/kms/kms/target/x86_64-unknown-linux-gnu/generate-rpm/*.rpm \
|
||||
ubuntu_20_04-release/home/runner/work/kms/kms/target/x86_64-unknown-linux-gnu/debian/*.deb \
|
||||
ubuntu_22_04-release/home/runner/work/kms/kms/target/x86_64-unknown-linux-gnu/debian/*.deb \
|
||||
ubuntu_24_04-release/home/runner/work/kms/kms/target/x86_64-unknown-linux-gnu/debian/*.deb
|
||||
|
||||
|
@ -150,7 +147,9 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Deploy documentation in staging
|
||||
if: ${{ github.ref_name == 'develop' }}
|
||||
|
|
2
.github/workflows/main_release.yml
vendored
2
.github/workflows/main_release.yml
vendored
|
@ -16,6 +16,6 @@ jobs:
|
|||
secrets: inherit
|
||||
uses: ./.github/workflows/main_base.yml
|
||||
with:
|
||||
toolchain: nightly-2024-06-09
|
||||
toolchain: nightly-2025-01-01
|
||||
debug_or_release: release
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
[submodule "cli"]
|
||||
path = cli
|
||||
url = https://github.com/Cosmian/cli.git
|
|
@ -10,39 +10,33 @@
|
|||
exclude: documentation/pandoc|documentation/overrides|crate/server/src/tests/test_utils.rs|.pre-commit-config.yaml|crate/server/src/routes/google_cse/jwt.rs|crate/server/src/routes/google_cse/python/openssl|documentation/docs/google_cse|crate/pkcs11/sys|documentation/docs/drawings|test_data|documentation/docs/benchmarks.md|crate/hsm/proteccio/driver
|
||||
repos:
|
||||
- repo: https://github.com/compilerla/conventional-pre-commit
|
||||
rev: v3.4.0
|
||||
rev: v4.0.0
|
||||
hooks:
|
||||
- id: conventional-pre-commit
|
||||
stages: [ commit-msg ]
|
||||
args: [ ] # optional: list of Conventional Commits types to allow e.g. [feat, fix, ci, chore, test]
|
||||
stages: [commit-msg]
|
||||
args: [] # optional: list of Conventional Commits types to allow e.g. [feat, fix, ci, chore, test]
|
||||
|
||||
- repo: https://github.com/igorshubovych/markdownlint-cli
|
||||
rev: v0.42.0
|
||||
rev: v0.44.0
|
||||
hooks:
|
||||
- id: markdownlint-fix
|
||||
args:
|
||||
[
|
||||
--disable=MD013,
|
||||
--disable=MD041,
|
||||
--disable=MD046,
|
||||
--fix,
|
||||
]
|
||||
args: [--disable=MD013, --disable=MD041, --disable=MD046, --fix]
|
||||
|
||||
- repo: https://github.com/tcort/markdown-link-check
|
||||
rev: v3.12.2
|
||||
rev: v3.13.7
|
||||
hooks:
|
||||
- id: markdown-link-check
|
||||
args: [ -q, --config, .markdown-link-check.json ]
|
||||
args: [-q, --config, .markdown-link-check.json]
|
||||
|
||||
- repo: https://github.com/jumanjihouse/pre-commit-hook-yamlfmt
|
||||
rev: 0.2.3
|
||||
hooks:
|
||||
- id: yamlfmt
|
||||
args: [ --mapping, "2", --sequence, "4", --offset, "2" ]
|
||||
args: [--mapping, "2", --sequence, "4", --offset, "2"]
|
||||
exclude: ansible
|
||||
|
||||
- repo: https://github.com/crate-ci/typos
|
||||
rev: v1.25.0
|
||||
rev: v1
|
||||
hooks:
|
||||
- id: typos
|
||||
exclude: documentation/docs/images/google_cse.drawio.svg|crate/test_server/src/test_jwt.rs|crate/pkcs11/documentation/veracrypt_ckms.svg|crate/server/src/tests/google_cse/|documentation/docs/pkcs11/images|crate/server/resources|documentation/docs/algorithms.md|crate/server/src/tests/certificates/chain/root/ca/|documentation/docs/pki/smime.md|documentation/docs/hsms/proteccio.md
|
||||
|
@ -58,7 +52,7 @@ repos:
|
|||
exclude: ^.git/|crate/server/src/tests/certificates/chain/root/ca
|
||||
|
||||
- repo: https://github.com/sirosen/texthooks
|
||||
rev: 0.6.7
|
||||
rev: 0.6.8
|
||||
hooks:
|
||||
- id: fix-smartquotes
|
||||
- id: fix-ligatures
|
||||
|
@ -99,7 +93,7 @@ repos:
|
|||
- id: fix-byte-order-marker
|
||||
- id: fix-encoding-pragma
|
||||
- id: mixed-line-ending
|
||||
args: [ --fix=lf ]
|
||||
args: [--fix=lf]
|
||||
- id: name-tests-test
|
||||
- id: requirements-txt-fixer
|
||||
- id: sort-simple-yaml
|
||||
|
@ -107,24 +101,31 @@ repos:
|
|||
exclude: crate/crypto/src/openssl/x509_extensions.rs|documentation/docs/cli/main_commands.md
|
||||
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 24.8.0
|
||||
rev: 25.1.0
|
||||
hooks:
|
||||
- id: black
|
||||
# avoid clash with `double-quote-string-fixer`
|
||||
args: [ --skip-string-normalization ]
|
||||
args: [--skip-string-normalization]
|
||||
|
||||
- repo: https://github.com/Cosmian/git-hooks.git
|
||||
rev: v1.0.32
|
||||
rev: v1.0.36
|
||||
hooks:
|
||||
# - id: cargo-format
|
||||
# - id: dprint-toml-fix
|
||||
# - id: cargo-upgrade
|
||||
# - id: cargo-update
|
||||
- id: dprint-toml-fix
|
||||
stages: [manual]
|
||||
- id: cargo-upgrade
|
||||
stages: [manual]
|
||||
- id: cargo-update
|
||||
stages: [manual]
|
||||
- id: cargo-machete
|
||||
- id: cargo-build-kms
|
||||
- id: docker-compose-up
|
||||
- id: cargo-test
|
||||
args: [ --, --skip, test_wrap_auth, --skip, google_cse, --skip, hsm ]
|
||||
args: [--, --skip, test_wrap_auth, --skip, google_cse, --skip, hsm]
|
||||
- id: cargo-build-all
|
||||
- id: cargo-test
|
||||
alias: cargo-test-all
|
||||
args: [--all-features, --, --skip, test_wrap_auth, --skip, google_cse, --skip, hsm]
|
||||
- id: clippy-autofix-unreachable-pub
|
||||
- id: clippy-autofix-all-targets-all-features
|
||||
- id: clippy-autofix-all-targets
|
||||
|
@ -134,7 +135,7 @@ repos:
|
|||
- id: docker-compose-down
|
||||
|
||||
- repo: https://github.com/EmbarkStudios/cargo-deny
|
||||
rev: 0.16.1
|
||||
rev: 0.18.2
|
||||
hooks:
|
||||
- id: cargo-deny
|
||||
args: [ --all-features, check ]
|
||||
args: [--all-features, check]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Specifies which edition is used by the parser.
|
||||
# Default value: "2015"
|
||||
edition = "2021"
|
||||
style_edition = "2024"
|
||||
|
||||
# How imports should be grouped into use statements. Imports will be merged or split to the configured level of granularity.
|
||||
# Default value: Preserve
|
||||
|
@ -44,10 +44,6 @@ unstable_features = true
|
|||
# Default value: false
|
||||
use_field_init_shorthand = true
|
||||
|
||||
# Which version of the formatting rules to use. Version::One is backwards-compatible with Rustfmt 1.0. Other versions are only backwards compatible within a major version number.
|
||||
# Default value: "One"
|
||||
version = "Two"
|
||||
|
||||
# The following rust files listing have been made in october 2021.
|
||||
# This listing allows us first to ignore all rust files formmatting.
|
||||
# Then we can remove progressively from this list the files we want to format
|
||||
|
|
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
@ -6,7 +6,7 @@
|
|||
"chacha",
|
||||
"ciphertext",
|
||||
"ciphertexts",
|
||||
"ckms",
|
||||
"cosmian",
|
||||
"cleartext",
|
||||
"cloudproof",
|
||||
"cosmian",
|
||||
|
|
465
Cargo.lock
generated
465
Cargo.lock
generated
|
@ -416,9 +416,9 @@ checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
|
|||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.86"
|
||||
version = "1.0.96"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
||||
checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4"
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
|
@ -449,15 +449,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.8"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a"
|
||||
checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.4"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
|
||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||
|
||||
[[package]]
|
||||
name = "asn1-rs"
|
||||
|
@ -618,6 +618,15 @@ dependencies = [
|
|||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backon"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49fef586913a57ff189f25c9b3d034356a5bf6b3fa9a7f067588fe1698ba1f5d"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.74"
|
||||
|
@ -703,9 +712,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "binstring"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e0d60973d9320722cb1206f412740e162a33b8547ea8d6be75d7cff237c7a85"
|
||||
checksum = "ed79c2a8151273c70956b5e3cdfdc1ff6c1a8b9779ba59c6807d281b32ee2f86"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
|
@ -733,9 +742,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "blake2b_simd"
|
||||
version = "1.0.2"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780"
|
||||
checksum = "06e903a20b159e944f91ec8499fe1e55651480c541ea0a584f5d967c49ad9d99"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"arrayvec",
|
||||
|
@ -783,9 +792,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.10.0"
|
||||
version = "1.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c"
|
||||
checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"regex-automata 0.4.9",
|
||||
|
@ -937,31 +946,6 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ckms_pkcs11"
|
||||
version = "4.22.1"
|
||||
dependencies = [
|
||||
"cosmian_config_utils",
|
||||
"cosmian_kmip",
|
||||
"cosmian_kms_client",
|
||||
"cosmian_logger",
|
||||
"cosmian_pkcs11_module",
|
||||
"etcetera",
|
||||
"kms_test_server",
|
||||
"p256",
|
||||
"pkcs1",
|
||||
"serde_json",
|
||||
"sha3",
|
||||
"thiserror 2.0.11",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-error",
|
||||
"tracing-journald",
|
||||
"tracing-subscriber",
|
||||
"x509-cert",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.8.1"
|
||||
|
@ -975,9 +959,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.27"
|
||||
version = "4.5.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796"
|
||||
checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
|
@ -985,9 +969,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.27"
|
||||
version = "4.5.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7"
|
||||
checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
|
@ -995,9 +979,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.24"
|
||||
version = "4.5.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c"
|
||||
checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
|
@ -1019,8 +1003,8 @@ checksum = "a25dad7ff37557df4cb27a20d80727d75dfa2ddcbed14f02761e8bfb1f595741"
|
|||
dependencies = [
|
||||
"async-trait",
|
||||
"cosmian_crypto_core 9.5.0",
|
||||
"cosmian_findex",
|
||||
"redis",
|
||||
"cosmian_findex 5.0.3",
|
||||
"redis 0.23.3",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 1.0.69",
|
||||
|
@ -1030,9 +1014,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "coarsetime"
|
||||
version = "0.1.34"
|
||||
version = "0.1.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13b3839cf01bb7960114be3ccf2340f541b6d0c81f8690b007b2b39f750f7e5d"
|
||||
checksum = "91849686042de1b41cd81490edc83afbcb0abe5a9b6f2c4114f23ce8cca1bcf4"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasix",
|
||||
|
@ -1093,9 +1077,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2"
|
||||
checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
|
@ -1137,6 +1121,55 @@ version = "0.8.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "cosmian_cli"
|
||||
version = "4.22.1"
|
||||
dependencies = [
|
||||
"actix-rt",
|
||||
"actix-server",
|
||||
"assert_cmd",
|
||||
"base64 0.22.1",
|
||||
"clap",
|
||||
"const-oid",
|
||||
"cosmian_config_utils",
|
||||
"cosmian_cover_crypt",
|
||||
"cosmian_crypto_core 10.0.1",
|
||||
"cosmian_findex 7.0.0",
|
||||
"cosmian_findex_client",
|
||||
"cosmian_findex_structs",
|
||||
"cosmian_kms_client",
|
||||
"cosmian_kms_crypto",
|
||||
"cosmian_logger",
|
||||
"csv",
|
||||
"der",
|
||||
"hex",
|
||||
"jwt-simple",
|
||||
"lazy_static",
|
||||
"leb128",
|
||||
"num-format",
|
||||
"openssl",
|
||||
"pem",
|
||||
"predicates",
|
||||
"rand 0.9.0",
|
||||
"rand_chacha 0.9.0",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum",
|
||||
"tempfile",
|
||||
"test_findex_server",
|
||||
"test_kms_server",
|
||||
"thiserror 2.0.11",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid",
|
||||
"x509-cert",
|
||||
"x509-parser",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmian_config_utils"
|
||||
version = "0.1.0"
|
||||
|
@ -1154,7 +1187,8 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "cosmian_cover_crypt"
|
||||
version = "15.0.0"
|
||||
source = "git+https://github.com/Cosmian/cover_crypt.git?branch=release/v15.0.0#018daaf2c39a64473028200a8ee0085d1bfadfdb"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f12eb7e96555b6b8841966fa264e7a6dfe5e39ad0733317c088c52259e99993c"
|
||||
dependencies = [
|
||||
"cosmian_crypto_core 10.0.1",
|
||||
"ml-kem",
|
||||
|
@ -1239,6 +1273,89 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmian_findex"
|
||||
version = "7.0.0"
|
||||
source = "git+https://github.com/Cosmian/findex?rev=98fcb6e97054e1bb9dddd7a0d620e27438cb91b7#98fcb6e97054e1bb9dddd7a0d620e27438cb91b7"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"rand 0.9.0",
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_core 0.9.0",
|
||||
"redis 0.28.2",
|
||||
"tiny-keccak",
|
||||
"xts-mode",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmian_findex_client"
|
||||
version = "4.22.1"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"cosmian_crypto_core 10.0.1",
|
||||
"cosmian_findex 7.0.0",
|
||||
"cosmian_findex_structs",
|
||||
"cosmian_http_client",
|
||||
"cosmian_kms_client",
|
||||
"cosmian_logger",
|
||||
"rand 0.9.0",
|
||||
"rand_chacha 0.9.0",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"test_kms_server",
|
||||
"thiserror 2.0.11",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmian_findex_server"
|
||||
version = "0.2.0"
|
||||
source = "git+https://www.github.com/Cosmian/findex-server?branch=update_cli#ac08ef892d56ba65ed320a99cdf5094c7172c873"
|
||||
dependencies = [
|
||||
"actix-cors",
|
||||
"actix-identity",
|
||||
"actix-service",
|
||||
"actix-tls",
|
||||
"actix-web",
|
||||
"alcoholic_jwt",
|
||||
"async-trait",
|
||||
"chrono",
|
||||
"clap",
|
||||
"cosmian_crypto_core 10.0.1",
|
||||
"cosmian_findex 7.0.0",
|
||||
"cosmian_findex_structs",
|
||||
"cosmian_logger",
|
||||
"dotenvy",
|
||||
"futures",
|
||||
"openssl",
|
||||
"redis 0.28.2",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"thiserror 2.0.11",
|
||||
"tokio",
|
||||
"toml",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmian_findex_structs"
|
||||
version = "0.2.0"
|
||||
source = "git+https://www.github.com/Cosmian/findex-server?branch=update_cli#ac08ef892d56ba65ed320a99cdf5094c7172c873"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"cosmian_crypto_core 10.0.1",
|
||||
"cosmian_findex 7.0.0",
|
||||
"thiserror 2.0.11",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmian_http_client"
|
||||
version = "0.1.0"
|
||||
|
@ -1303,44 +1420,6 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmian_kms_cli"
|
||||
version = "4.22.1"
|
||||
dependencies = [
|
||||
"assert_cmd",
|
||||
"base64 0.22.1",
|
||||
"clap",
|
||||
"cosmian_config_utils",
|
||||
"cosmian_cover_crypt",
|
||||
"cosmian_crypto_core 10.0.1",
|
||||
"cosmian_kms_client",
|
||||
"cosmian_kms_crypto",
|
||||
"cosmian_logger",
|
||||
"der",
|
||||
"hex",
|
||||
"jwt-simple",
|
||||
"kms_test_server",
|
||||
"leb128",
|
||||
"num-format",
|
||||
"openssl",
|
||||
"pem",
|
||||
"predicates",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum",
|
||||
"tempfile",
|
||||
"thiserror 2.0.11",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid",
|
||||
"x509-cert",
|
||||
"x509-parser",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmian_kms_client"
|
||||
version = "4.22.1"
|
||||
|
@ -1350,7 +1429,6 @@ dependencies = [
|
|||
"cosmian_http_client",
|
||||
"cosmian_kmip",
|
||||
"cosmian_kms_access",
|
||||
"cosmian_logger",
|
||||
"der",
|
||||
"pem",
|
||||
"reqwest",
|
||||
|
@ -1472,7 +1550,7 @@ dependencies = [
|
|||
"lru",
|
||||
"num_cpus",
|
||||
"rawsql",
|
||||
"redis",
|
||||
"redis 0.23.3",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
|
@ -1496,6 +1574,30 @@ dependencies = [
|
|||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmian_pkcs11"
|
||||
version = "4.22.1"
|
||||
dependencies = [
|
||||
"cosmian_kmip",
|
||||
"cosmian_kms_client",
|
||||
"cosmian_logger",
|
||||
"cosmian_pkcs11_module",
|
||||
"etcetera",
|
||||
"p256",
|
||||
"pkcs1",
|
||||
"serde_json",
|
||||
"sha3",
|
||||
"test_kms_server",
|
||||
"thiserror 2.0.11",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-error",
|
||||
"tracing-journald",
|
||||
"tracing-subscriber",
|
||||
"x509-cert",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmian_pkcs11_module"
|
||||
version = "4.22.1"
|
||||
|
@ -1508,7 +1610,7 @@ dependencies = [
|
|||
"p256",
|
||||
"pkcs1",
|
||||
"pkcs11_sys",
|
||||
"rand 0.8.5",
|
||||
"rand 0.9.0",
|
||||
"rsa",
|
||||
"serial_test",
|
||||
"strum_macros 0.26.4",
|
||||
|
@ -1662,10 +1764,31 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "ct-codecs"
|
||||
version = "1.1.1"
|
||||
name = "csv"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3b7eb4404b8195a9abb6356f4ac07d8ba267045c8d6d220ac4dc992e6cc75df"
|
||||
checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf"
|
||||
dependencies = [
|
||||
"csv-core",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csv-core"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ct-codecs"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b916ba8ce9e4182696896f015e8a5ae6081b305f74690baa8465e35f5a142ea4"
|
||||
|
||||
[[package]]
|
||||
name = "ctr"
|
||||
|
@ -1997,9 +2120,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "float-cmp"
|
||||
version = "0.9.0"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
|
||||
checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
@ -2301,9 +2424,9 @@ checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
|
|||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.4.0"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
|
||||
checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
|
@ -2334,24 +2457,24 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "hmac-sha1-compact"
|
||||
version = "1.1.4"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dff9d405ec732fa3fcde87264e54a32a84956a377b3e3107de96e59b798c84a7"
|
||||
checksum = "18492c9f6f9a560e0d346369b665ad2bdbc89fa9bceca75796584e79042694c3"
|
||||
|
||||
[[package]]
|
||||
name = "hmac-sha256"
|
||||
version = "1.1.7"
|
||||
version = "1.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3688e69b38018fec1557254f64c8dc2cc8ec502890182f395dbb0aa997aa5735"
|
||||
checksum = "4a8575493d277c9092b988c780c94737fb9fd8651a1001e16bee3eccfc1baedb"
|
||||
dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hmac-sha512"
|
||||
version = "1.1.5"
|
||||
version = "1.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4ce1f4656bae589a3fab938f9f09bf58645b7ed01a2c5f8a3c238e01a4ef78a"
|
||||
checksum = "b0b3a0f572aa8389d325f5852b9e0a333a15b0f86ecccbb3fdb6e97cd86dc67c"
|
||||
dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
@ -2677,13 +2800,13 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
|
|||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.13"
|
||||
version = "0.4.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b"
|
||||
checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
|
||||
dependencies = [
|
||||
"hermit-abi 0.4.0",
|
||||
"hermit-abi 0.5.0",
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2721,9 +2844,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "jwt-simple"
|
||||
version = "0.12.9"
|
||||
version = "0.12.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "094661f5aad510abe2658bff20409e89046b753d9dc2d4007f5c100b6d982ba0"
|
||||
checksum = "b00e03c08ce71da10a3ad9267b963c03fc4234a56713d87648547b3fdda872a6"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"binstring",
|
||||
|
@ -2741,15 +2864,15 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"superboring",
|
||||
"thiserror 1.0.69",
|
||||
"thiserror 2.0.11",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "k256"
|
||||
version = "0.13.3"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b"
|
||||
checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"ecdsa",
|
||||
|
@ -2778,25 +2901,6 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kms_test_server"
|
||||
version = "4.22.1"
|
||||
dependencies = [
|
||||
"actix-server",
|
||||
"base64 0.22.1",
|
||||
"cosmian_kms_client",
|
||||
"cosmian_kms_crypto",
|
||||
"cosmian_kms_server",
|
||||
"cosmian_kms_server_database",
|
||||
"cosmian_logger",
|
||||
"criterion",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "language-tags"
|
||||
version = "0.3.2"
|
||||
|
@ -3176,9 +3280,9 @@ checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
|||
|
||||
[[package]]
|
||||
name = "oorandom"
|
||||
version = "11.1.4"
|
||||
version = "11.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9"
|
||||
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
|
@ -3573,9 +3677,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "predicates"
|
||||
version = "3.1.2"
|
||||
version = "3.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97"
|
||||
checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"difflib",
|
||||
|
@ -3587,15 +3691,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "predicates-core"
|
||||
version = "1.0.8"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931"
|
||||
checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa"
|
||||
|
||||
[[package]]
|
||||
name = "predicates-tree"
|
||||
version = "1.0.11"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13"
|
||||
checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c"
|
||||
dependencies = [
|
||||
"predicates-core",
|
||||
"termtree",
|
||||
|
@ -3765,6 +3869,30 @@ dependencies = [
|
|||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redis"
|
||||
version = "0.28.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e37ec3fd44bea2ec947ba6cc7634d7999a6590aca7c35827c250bc0de502bda6"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"backon",
|
||||
"bytes",
|
||||
"combine",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"itoa",
|
||||
"num-bigint",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"ryu",
|
||||
"sha1_smol",
|
||||
"socket2 0.5.8",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.7"
|
||||
|
@ -4589,9 +4717,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
|||
|
||||
[[package]]
|
||||
name = "superboring"
|
||||
version = "0.1.2"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbde97f499e51ef384f585dc8f8fb6a9c3a71b274b8d12469b516758e6540607"
|
||||
checksum = "515cce34a781d7250b8a65706e0f2a5b99236ea605cb235d4baed6685820478f"
|
||||
dependencies = [
|
||||
"getrandom 0.2.15",
|
||||
"hmac-sha256",
|
||||
|
@ -4665,9 +4793,43 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "termtree"
|
||||
version = "0.4.1"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76"
|
||||
checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683"
|
||||
|
||||
[[package]]
|
||||
name = "test_findex_server"
|
||||
version = "4.22.1"
|
||||
dependencies = [
|
||||
"actix-server",
|
||||
"cosmian_findex_client",
|
||||
"cosmian_findex_server",
|
||||
"cosmian_logger",
|
||||
"criterion",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "test_kms_server"
|
||||
version = "4.22.1"
|
||||
dependencies = [
|
||||
"actix-server",
|
||||
"base64 0.22.1",
|
||||
"cosmian_cli",
|
||||
"cosmian_kms_client",
|
||||
"cosmian_kms_crypto",
|
||||
"cosmian_kms_server",
|
||||
"cosmian_kms_server_database",
|
||||
"cosmian_logger",
|
||||
"criterion",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
|
@ -5222,6 +5384,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "b913a3b5fe84142e269d63cc62b64319ccaf89b748fc31fe025177f767a756c4"
|
||||
dependencies = [
|
||||
"getrandom 0.2.15",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -5250,9 +5413,9 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
|||
|
||||
[[package]]
|
||||
name = "wait-timeout"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
|
||||
checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
@ -5701,6 +5864,16 @@ dependencies = [
|
|||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xts-mode"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09cbddb7545ca0b9ffa7bdc653e8743303e1712687a6918ced25f2cdbed42520"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.7.5"
|
||||
|
|
59
Cargo.toml
59
Cargo.toml
|
@ -1,21 +1,20 @@
|
|||
[workspace]
|
||||
default-members = ["crate/cli", "crate/server", "crate/pkcs11/provider"]
|
||||
default-members = ["cli/crate/cli", "crate/server"]
|
||||
members = [
|
||||
"cli/crate/cli",
|
||||
"cli/crate/kms_client",
|
||||
"cli/crate/pkcs11/module",
|
||||
"cli/crate/pkcs11/provider",
|
||||
"crate/access",
|
||||
"crate/cli",
|
||||
"crate/client",
|
||||
"crate/crypto",
|
||||
"crate/hsm/proteccio",
|
||||
"crate/hsm/utimaco",
|
||||
"crate/hsm/base_hsm",
|
||||
"crate/interfaces",
|
||||
"crate/kmip",
|
||||
"crate/pkcs11/module",
|
||||
"crate/pkcs11/provider",
|
||||
"crate/pkcs11/sys",
|
||||
"crate/server",
|
||||
"crate/server_database",
|
||||
"crate/test_server",
|
||||
]
|
||||
# Do that if you don't want to enable `dev` feature by default due to the `dev-dependencies` of the cli.
|
||||
# For more details, read: https://doc.rust-lang.org/cargo/reference/resolver.html#feature-resolver-version-2
|
||||
|
@ -57,46 +56,58 @@ opt-level = 0
|
|||
actix-rt = "2.10"
|
||||
actix-server = { version = "2.5", default-features = false }
|
||||
actix-web = { version = "4.9", default-features = false }
|
||||
async-trait = "0.1.86"
|
||||
base64 = "0.22.1"
|
||||
bitflags = "2.8.0"
|
||||
chrono = "0.4.39"
|
||||
clap = { version = "4.5.27", default-features = false }
|
||||
cloudproof_findex = { version = "5.0", features = ["findex-redis"] }
|
||||
async-trait = "0.1"
|
||||
base64 = "0.22"
|
||||
bitflags = "2.8"
|
||||
chrono = "0.4"
|
||||
clap = { version = "4.5", default-features = false }
|
||||
cosmian_cli = { path = "cli/crate/cli" }
|
||||
cosmian_findex = { git = "https://github.com/Cosmian/findex", default-features = false, rev = "98fcb6e97054e1bb9dddd7a0d620e27438cb91b7" }
|
||||
cosmian_findex_client = { path = "cli/crate/findex_client" }
|
||||
cosmian_findex_server = { git = "https://www.github.com/Cosmian/findex-server", branch = "update_cli" }
|
||||
cosmian_findex_structs = { git = "https://www.github.com/Cosmian/findex-server", branch = "update_cli" }
|
||||
cosmian_config_utils = { git = "https://www.github.com/Cosmian/http_client_server", tag = "0.1.0" }
|
||||
cosmian_cover_crypt = { git = "https://github.com/Cosmian/cover_crypt.git", branch = "release/v15.0.0" }
|
||||
cosmian_cover_crypt = "15.0"
|
||||
cosmian_crypto_core = { version = "10.0.1", default-features = false, features = [
|
||||
"ser",
|
||||
] }
|
||||
cosmian_http_client = { git = "https://www.github.com/Cosmian/http_client_server", tag = "0.1.0" }
|
||||
cosmian_kmip = { path = "crate/kmip" }
|
||||
cosmian_kms_access = { path = "crate/access" }
|
||||
cosmian_kms_client = { path = "cli/crate/kms_client" }
|
||||
cosmian_kms_crypto = { path = "crate/crypto" }
|
||||
cosmian_kms_server = { path = "crate/server" }
|
||||
cosmian_kms_server_database = { path = "crate/server_database" }
|
||||
cosmian_logger = { git = "https://www.github.com/Cosmian/http_client_server", tag = "0.1.0" }
|
||||
der = { version = "0.7", default-features = false }
|
||||
hex = { version = "0.4", default-features = false }
|
||||
lazy_static = "1.5"
|
||||
leb128 = "0.2"
|
||||
libloading = "0.8.6"
|
||||
log = { version = "0.4.25", default-features = false }
|
||||
lru = "0.12.5"
|
||||
libloading = "0.8"
|
||||
log = { version = "0.4", default-features = false }
|
||||
lru = "0.12"
|
||||
num_cpus = "1.16"
|
||||
num-format = "0.4"
|
||||
num-bigint-dig = { version = "0.8", default-features = false }
|
||||
openssl = { version = "0.10.70", default-features = false }
|
||||
openssl = { version = "0.10", default-features = false }
|
||||
pem = "3.0"
|
||||
pkcs11_sys = { path = "crate/pkcs11/sys" }
|
||||
pyo3 = { version = "0.20", default-features = false }
|
||||
rand = "0.9"
|
||||
reqwest = { version = "0.11", default-features = false }
|
||||
serde = "1.0.217"
|
||||
serde_json = "1.0.138"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
sha3 = { version = "0.10", default-features = false }
|
||||
strum = { version = "0.25", default-features = false }
|
||||
thiserror = "2.0.11"
|
||||
time = "0.3.37"
|
||||
tiny-keccak = "2.0.2"
|
||||
tempfile = "3.16.0"
|
||||
thiserror = "2.0"
|
||||
time = "0.3"
|
||||
tiny-keccak = "2.0"
|
||||
tempfile = "3.16"
|
||||
tokio = { version = "1.43", default-features = false }
|
||||
tracing-subscriber = { version = "0.3", default-features = false }
|
||||
tracing = "0.1"
|
||||
url = "2.5"
|
||||
uuid = "=1.11.1"
|
||||
x509-cert = { version = "0.2", default-features = false }
|
||||
x509-parser = "0.17.0"
|
||||
x509-parser = "0.17"
|
||||
zeroize = { version = "1.8", default-features = false }
|
||||
|
|
|
@ -20,9 +20,9 @@ RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then export ARCHITECTURE=x86_64; e
|
|||
|
||||
# Conditional cargo build based on FIPS argument
|
||||
RUN if [ "$FIPS" = "true" ]; then \
|
||||
cargo build -p cosmian_kms_cli -p cosmian_kms_server --release --no-default-features --features="fips"; \
|
||||
cargo build -p cosmian_cli -p cosmian_kms_server --release --no-default-features --features="fips"; \
|
||||
else \
|
||||
cargo build -p cosmian_kms_cli -p cosmian_kms_server --release --no-default-features; \
|
||||
cargo build -p cosmian_cli -p cosmian_kms_server --release --no-default-features; \
|
||||
fi
|
||||
|
||||
#
|
||||
|
@ -31,7 +31,7 @@ RUN if [ "$FIPS" = "true" ]; then \
|
|||
FROM debian:bullseye-slim AS kms-server
|
||||
|
||||
COPY --from=builder /root/kms/target/release/cosmian_kms /usr/bin/cosmian_kms
|
||||
COPY --from=builder /root/kms/target/release/ckms /usr/bin/ckms
|
||||
COPY --from=builder /root/kms/target/release/cosmian /usr/bin/cosmian
|
||||
COPY --from=builder /usr/local/openssl /usr/local/openssl
|
||||
|
||||
#
|
||||
|
|
24
README.md
24
README.md
|
@ -74,11 +74,11 @@ docker run -p 9998:9998 --name kms ghcr.io/cosmian/kms:latest
|
|||
```
|
||||
|
||||
Then, use the CLI to issue commands to the KMS.
|
||||
The CLI, called `ckms`, can be either downloaded from [Cosmian packages](https://package.cosmian.com/kms/) or built and
|
||||
The CLI, called `cosmian`, can be either downloaded from [Cosmian packages](https://package.cosmian.com/kms/) or built and
|
||||
launched from this GitHub project by running
|
||||
|
||||
```sh
|
||||
cargo run --bin ckms -- --help
|
||||
cargo run --bin cosmian -- --help
|
||||
```
|
||||
|
||||
### Example
|
||||
|
@ -86,7 +86,7 @@ cargo run --bin ckms -- --help
|
|||
1. Create a 256-bit symmetric key
|
||||
|
||||
```sh
|
||||
➜ cargo run --bin ckms -- sym keys create --number-of-bits 256 --algorithm aes --tag my-key-file
|
||||
➜ cargo run --bin cosmian -- sym keys create --number-of-bits 256 --algorithm aes --tag my-key-file
|
||||
...
|
||||
The symmetric key was successfully generated.
|
||||
Unique identifier: 87e9e2a8-4538-4701-aa8c-e3af94e44a9e
|
||||
|
@ -98,7 +98,7 @@ cargo run --bin ckms -- --help
|
|||
2. Encrypt the `image.png` file with AES GCM using the key
|
||||
|
||||
```sh
|
||||
➜ cargo run --bin ckms -- sym encrypt --tag my-key-file --output-file image.enc image.png
|
||||
➜ cargo run --bin cosmian -- sym encrypt --tag my-key-file --output-file image.enc image.png
|
||||
...
|
||||
The encrypted file is available at "image.enc"
|
||||
```
|
||||
|
@ -106,7 +106,7 @@ cargo run --bin ckms -- --help
|
|||
3. Decrypt the `image.enc` file using the key
|
||||
|
||||
```sh
|
||||
➜ cargo run --bin ckms -- sym decrypt --tag my-key-file --output-file image2.png image.enc
|
||||
➜ cargo run --bin cosmian -- sym decrypt --tag my-key-file --output-file image2.png image.enc
|
||||
...
|
||||
The decrypted file is available at "image2.png"
|
||||
```
|
||||
|
@ -119,7 +119,7 @@ The server is written in [Rust](https://www.rust-lang.org/) and is broken down i
|
|||
binaries:
|
||||
|
||||
- A server (`cosmian_kms`) which is the KMS itself
|
||||
- A CLI (`ckms`) to interact with this server
|
||||
- A CLI (`cosmian`) to interact with this server
|
||||
|
||||
And also some crates:
|
||||
|
||||
|
@ -129,7 +129,7 @@ And also some crates:
|
|||
- `kmip` which is an implementation of the KMIP standard
|
||||
- `server_database` to handle the database
|
||||
- `pkcs11_*` to handle PKCS11 support
|
||||
- `kms_test_server` which is a library to instantiate programmatically the KMS server.
|
||||
- `test_kms_server` which is a library to instantiate programmatically the KMS server.
|
||||
|
||||
**Please refer to the README of the inner directories to have more information.**
|
||||
|
||||
|
@ -138,7 +138,13 @@ directory.
|
|||
|
||||
## Building the KMS
|
||||
|
||||
OpenSSL v3.2.0 is required to build the KMS.
|
||||
First, pull the git submodule for client requirements such as CLI and UI:
|
||||
|
||||
```sh
|
||||
git submodule update --recursive --init
|
||||
````
|
||||
|
||||
Then OpenSSL v3.2.0 is required to build the KMS.
|
||||
|
||||
### Linux or MacOS (CPU Intel or MacOs ARM)
|
||||
|
||||
|
@ -189,7 +195,7 @@ Build the server and CLI binaries:
|
|||
cd crate/server
|
||||
cargo build --release
|
||||
cd ../..
|
||||
cd crate/ckms
|
||||
cd cli/crate/cli
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
|
|
|
@ -2,3 +2,4 @@
|
|||
GOST = "GOST"
|
||||
vas = "vas"
|
||||
passin = "passin"
|
||||
typ = "typ"
|
||||
|
|
1
cli
Submodule
1
cli
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 00b6d82bc6c751c314e3a74d25280d8e0e7d3313
|
|
@ -4,8 +4,8 @@ use std::{
|
|||
};
|
||||
|
||||
use cosmian_kmip::kmip_2_1::{
|
||||
kmip_types::{Attributes, StateEnumeration, UniqueIdentifier},
|
||||
KmipOperation,
|
||||
kmip_types::{Attributes, StateEnumeration, UniqueIdentifier},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
|
|
@ -1,72 +0,0 @@
|
|||
[package]
|
||||
name = "cosmian_kms_cli"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "CLI used to manage the Cosmian KMS."
|
||||
|
||||
[[bin]]
|
||||
name = "ckms"
|
||||
path = "src/main.rs"
|
||||
test = false
|
||||
|
||||
[lib]
|
||||
# doc test linking as a separate binary is extremely slow
|
||||
# and is not needed for internal lib
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
fips = [
|
||||
"cosmian_kms_client/fips",
|
||||
"cosmian_kms_crypto/fips",
|
||||
"kms_test_server/fips",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
base64 = { workspace = true }
|
||||
clap = { workspace = true, features = [
|
||||
"help",
|
||||
"env",
|
||||
"std",
|
||||
"usage",
|
||||
"error-context",
|
||||
"derive",
|
||||
"cargo",
|
||||
] }
|
||||
cosmian_config_utils = { workspace = true }
|
||||
cosmian_cover_crypt = { workspace = true }
|
||||
cosmian_crypto_core = { workspace = true, features = ["ser"] }
|
||||
cosmian_kms_client = { path = "../client" }
|
||||
cosmian_kms_crypto = { path = "../crypto" }
|
||||
cosmian_logger = { workspace = true }
|
||||
der = { workspace = true, features = ["pem"] }
|
||||
hex = { workspace = true }
|
||||
jwt-simple = { version = "0.12", default-features = false, features = [
|
||||
"pure-rust",
|
||||
] }
|
||||
leb128 = { workspace = true }
|
||||
num-format = { workspace = true }
|
||||
pem = { workspace = true }
|
||||
reqwest = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
strum = { workspace = true, features = ["std", "derive", "strum_macros"] }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
tracing = { workspace = true }
|
||||
url = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
x509-cert = { workspace = true, features = ["pem"] }
|
||||
zeroize = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "2.0"
|
||||
kms_test_server = { path = "../test_server" }
|
||||
openssl = { workspace = true }
|
||||
predicates = "3.1"
|
||||
regex = { version = "1.11", default-features = false }
|
||||
tempfile = { workspace = true }
|
||||
x509-parser = { workspace = true, features = ["verify"] }
|
|
@ -1,21 +0,0 @@
|
|||
# Cosmian KMS CLI
|
||||
|
||||
Cosmian KMS can be managed using the `ckms` command line interface (CLI) or its graphical
|
||||
client `ckms_gui`.
|
||||
|
||||
## Build
|
||||
|
||||
```sh
|
||||
cargo build --bin ckms
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
[Usage](https://docs.cosmian.com/cosmian_cli/)
|
||||
|
||||
## Testing
|
||||
|
||||
```sh
|
||||
cargo build --bin ckms
|
||||
cargo test -p ckms
|
||||
```
|
|
@ -1,268 +0,0 @@
|
|||
use clap::Parser;
|
||||
use cosmian_kms_client::{
|
||||
cosmian_kmip::kmip_2_1::kmip_types::UniqueIdentifier, kmip_2_1::KmipOperation,
|
||||
reexport::cosmian_kms_access::access::Access, KmsClient,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
actions::console,
|
||||
error::result::{CliResult, CliResultHelper},
|
||||
};
|
||||
|
||||
/// Manage the users' access rights to the cryptographic objects
|
||||
#[derive(Parser, Debug)]
|
||||
pub enum AccessAction {
|
||||
Grant(GrantAccess),
|
||||
Revoke(RevokeAccess),
|
||||
List(ListAccessesGranted),
|
||||
Owned(ListOwnedObjects),
|
||||
Obtained(ListAccessRightsObtained),
|
||||
}
|
||||
|
||||
impl AccessAction {
|
||||
/// Processes the access action.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `kms_rest_client` - The KMS client used for the action.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if there was a problem running the action.
|
||||
pub async fn process(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
match self {
|
||||
Self::Grant(action) => action.run(kms_rest_client).await?,
|
||||
Self::Revoke(action) => action.run(kms_rest_client).await?,
|
||||
Self::List(action) => action.run(kms_rest_client).await?,
|
||||
Self::Owned(action) => action.run(kms_rest_client).await?,
|
||||
Self::Obtained(action) => action.run(kms_rest_client).await?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Grant another user one or multiple access rights to an object.
|
||||
///
|
||||
/// This command can only be called by the owner of the object.
|
||||
///
|
||||
/// The right is granted for one or multiple supported KMIP operations:
|
||||
/// `create`, `get`, `encrypt`, `decrypt`, `import`, `revoke`, `locate`, `rekey`, `destroy`.
|
||||
///
|
||||
/// Multiple operations must be supplied whitespace separated, such as: 'create get rekey'
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct GrantAccess {
|
||||
/// The user identifier to allow
|
||||
#[clap(required = true)]
|
||||
user: String,
|
||||
|
||||
/// The object unique identifier stored in the KMS
|
||||
#[clap(required = true)]
|
||||
object_uid: String,
|
||||
|
||||
/// The operations to grant (`create`, `get`, `encrypt`, `decrypt`, `import`, `revoke`, `locate`, `rekey`, `destroy`)
|
||||
#[clap(required = true)]
|
||||
operations: Vec<KmipOperation>,
|
||||
}
|
||||
|
||||
impl GrantAccess {
|
||||
/// Runs the `GrantAccess` action.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `kms_rest_client` - A reference to the KMS client used to communicate with the KMS server.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the query execution on the KMS server fails.
|
||||
///
|
||||
pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
let access = Access {
|
||||
unique_identifier: Some(UniqueIdentifier::TextString(self.object_uid.clone())),
|
||||
user_id: self.user.clone(),
|
||||
operation_types: self.operations.clone(),
|
||||
};
|
||||
|
||||
kms_rest_client
|
||||
.grant_access(access)
|
||||
.await
|
||||
.with_context(|| "Can't execute the query on the kms server")?;
|
||||
|
||||
let stdout = format!(
|
||||
"The following kmip operations: {:?}, were successfully granted to user `{}` on \
|
||||
object `{}`",
|
||||
self.operations, self.user, self.object_uid
|
||||
);
|
||||
console::Stdout::new(&stdout).write()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Revoke another user one or multiple access rights to an object.
|
||||
///
|
||||
/// This command can only be called by the owner of the object.
|
||||
///
|
||||
/// The right is revoked for one or multiple supported KMIP operations:
|
||||
/// `create`, `get`, `encrypt`, `decrypt`, `import`, `revoke`, `locate`, `rekey`, `destroy`
|
||||
///
|
||||
/// Multiple operations must be supplied whitespace separated, such as: 'create get rekey'
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct RevokeAccess {
|
||||
/// The user to revoke access to
|
||||
#[clap(required = true)]
|
||||
user: String,
|
||||
|
||||
/// The object unique identifier stored in the KMS
|
||||
#[clap(required = true)]
|
||||
object_uid: String,
|
||||
|
||||
/// The operations to revoke (`create`, `get`, `encrypt`, `decrypt`, `import`, `revoke`, `locate`, `rekey`, `destroy`)
|
||||
#[clap(required = true)]
|
||||
operations: Vec<KmipOperation>,
|
||||
}
|
||||
|
||||
impl RevokeAccess {
|
||||
/// Runs the `RevokeAccess` action.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `kms_rest_client` - A reference to the KMS client used to communicate with the KMS server.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the query execution on the KMS server fails.
|
||||
///
|
||||
pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
let access = Access {
|
||||
unique_identifier: Some(UniqueIdentifier::TextString(self.object_uid.clone())),
|
||||
user_id: self.user.clone(),
|
||||
operation_types: self.operations.clone(),
|
||||
};
|
||||
|
||||
kms_rest_client
|
||||
.revoke_access(access)
|
||||
.await
|
||||
.with_context(|| "Can't execute the query on the kms server")?;
|
||||
|
||||
let stdout = format!(
|
||||
"The following kmip operations: {:?}, have been removed for user `{}` on object `{}`",
|
||||
self.operations, self.user, self.object_uid
|
||||
);
|
||||
console::Stdout::new(&stdout).write()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// List the access rights granted on an object to other users.
|
||||
///
|
||||
/// This command can only be called by the owner of the object.
|
||||
/// Returns a list of users and the operations they have been granted access to.
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct ListAccessesGranted {
|
||||
/// The object unique identifier
|
||||
#[clap(required = true)]
|
||||
object_uid: String,
|
||||
}
|
||||
|
||||
impl ListAccessesGranted {
|
||||
/// Runs the `ListAccessesGranted` action.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `kms_rest_client` - A reference to the KMS client used to communicate with the KMS server.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the query execution on the KMS server fails.
|
||||
///
|
||||
pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
let accesses = kms_rest_client
|
||||
.list_access(&self.object_uid)
|
||||
.await
|
||||
.with_context(|| "Can't execute the query on the kms server")?;
|
||||
|
||||
let stdout = format!(
|
||||
"The access rights granted on object {} are:",
|
||||
&self.object_uid
|
||||
);
|
||||
let mut stdout = console::Stdout::new(&stdout);
|
||||
stdout.set_accesses(accesses);
|
||||
stdout.write()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// List the objects owned by the calling user.
|
||||
///
|
||||
/// Owners of objects can perform any operation on these objects
|
||||
/// and can grant access rights on any of these operations to any other user.
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct ListOwnedObjects;
|
||||
|
||||
impl ListOwnedObjects {
|
||||
/// Runs the `ListOwnedObjects` action.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `kms_rest_client` - A reference to the KMS client used to communicate with the KMS server.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the query execution on the KMS server fails.
|
||||
///
|
||||
pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
let objects = kms_rest_client
|
||||
.list_owned_objects()
|
||||
.await
|
||||
.with_context(|| "Can't execute the query on the kms server")?;
|
||||
|
||||
if objects.is_empty() {
|
||||
console::Stdout::new("No object owned by this user.").write()?;
|
||||
} else {
|
||||
let mut stdout = console::Stdout::new("The objects owned by this user are:");
|
||||
stdout.set_object_owned(objects);
|
||||
stdout.write()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// List the access rights obtained by the calling user
|
||||
///
|
||||
/// Returns a list of objects, their state, their owner
|
||||
/// and the accesses rights granted on the object
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct ListAccessRightsObtained;
|
||||
|
||||
impl ListAccessRightsObtained {
|
||||
/// Runs the `ListAccessRightsObtained` action.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `kms_rest_client` - A reference to the KMS client used to communicate with the KMS server.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the query execution on the KMS server fails.
|
||||
///
|
||||
pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
let objects = kms_rest_client
|
||||
.list_access_rights_obtained()
|
||||
.await
|
||||
.with_context(|| "Can't execute the query on the kms server")?;
|
||||
|
||||
if objects.is_empty() {
|
||||
console::Stdout::new("No access right obtained.").write()?;
|
||||
} else {
|
||||
let mut stdout = console::Stdout::new("The access rights obtained are: ");
|
||||
stdout.set_access_rights_obtained(objects);
|
||||
stdout.write()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,90 +0,0 @@
|
|||
use clap::Parser;
|
||||
use cosmian_kms_client::{
|
||||
cosmian_kmip::kmip_2_1::kmip_types::UniqueIdentifier,
|
||||
kmip_2_1::{
|
||||
kmip_operations::{DeleteAttribute, DeleteAttributeResponse},
|
||||
kmip_types::{Attribute, AttributeReference, Tag},
|
||||
},
|
||||
KmsClient,
|
||||
};
|
||||
use tracing::trace;
|
||||
|
||||
use super::set::SetOrDeleteAttributes;
|
||||
use crate::{
|
||||
actions::{console, labels::ATTRIBUTE_ID, shared::get_key_uid},
|
||||
error::result::CliResult,
|
||||
};
|
||||
|
||||
/// Delete the KMIP object attributes.
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct DeleteAttributesAction {
|
||||
#[clap(flatten)]
|
||||
requested_attributes: SetOrDeleteAttributes,
|
||||
|
||||
/// The attributes or tags to retrieve.
|
||||
/// To specify multiple attributes, use the option multiple times.
|
||||
#[clap(long = "attribute", value_name = "ATTRIBUTE", verbatim_doc_comment)]
|
||||
attribute_tags: Option<Vec<Tag>>,
|
||||
}
|
||||
|
||||
impl DeleteAttributesAction {
|
||||
async fn delete_attribute(
|
||||
&self,
|
||||
kms_rest_client: &KmsClient,
|
||||
id: &str,
|
||||
current_attribute: Option<Attribute>,
|
||||
attribute_references: Option<Vec<AttributeReference>>,
|
||||
) -> CliResult<()> {
|
||||
let DeleteAttributeResponse { unique_identifier } = kms_rest_client
|
||||
.delete_attribute(DeleteAttribute {
|
||||
unique_identifier: Some(UniqueIdentifier::TextString(id.to_owned())),
|
||||
current_attribute: current_attribute.clone(),
|
||||
attribute_references,
|
||||
})
|
||||
.await?;
|
||||
trace!("delete_attribute response for {unique_identifier}: {current_attribute:?}",);
|
||||
let mut stdout = console::Stdout::new("Attribute deleted successfully");
|
||||
stdout.set_tags(self.requested_attributes.tags.as_ref());
|
||||
stdout.set_unique_identifier(id);
|
||||
if let Some(current_attribute) = current_attribute {
|
||||
stdout.set_attribute(current_attribute);
|
||||
}
|
||||
stdout.write()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Processes the `DeleteAttributes` action.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function can return a `CliError` if one of the following conditions occur:
|
||||
///
|
||||
/// - Either `--id` or one or more `--tag` must be specified.
|
||||
///
|
||||
pub async fn process(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
trace!("DeleteAttributeAction: {:?}", self);
|
||||
let id = get_key_uid(
|
||||
self.requested_attributes.id.as_ref(),
|
||||
self.requested_attributes.tags.as_ref(),
|
||||
ATTRIBUTE_ID,
|
||||
)?;
|
||||
|
||||
for attribute in self.requested_attributes.get_attributes_from_args()? {
|
||||
self.delete_attribute(kms_rest_client, &id, Some(attribute), None)
|
||||
.await?;
|
||||
}
|
||||
|
||||
if let Some(tags) = &self.attribute_tags {
|
||||
let mut references: Vec<AttributeReference> = Vec::with_capacity(tags.len());
|
||||
for tag in tags {
|
||||
references.push(AttributeReference::Standard(tag.to_owned()));
|
||||
}
|
||||
self.delete_attribute(kms_rest_client, &id, None, Some(references))
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,460 +0,0 @@
|
|||
use std::{collections::HashMap, fmt::Display, path::PathBuf};
|
||||
|
||||
use clap::{Parser, ValueEnum};
|
||||
use cosmian_kms_client::{
|
||||
cosmian_kmip::kmip_2_1::{
|
||||
kmip_operations::{GetAttributes, GetAttributesResponse},
|
||||
kmip_types::{AttributeReference, LinkType, Tag, UniqueIdentifier},
|
||||
},
|
||||
write_bytes_to_file, KmsClient,
|
||||
};
|
||||
use serde_json::Value;
|
||||
use strum::{EnumIter, IntoEnumIterator};
|
||||
use tracing::{debug, trace};
|
||||
|
||||
use crate::{
|
||||
actions::{console, labels::ATTRIBUTE_ID, shared::get_key_uid},
|
||||
error::result::CliResult,
|
||||
};
|
||||
|
||||
#[derive(ValueEnum, Debug, Clone, PartialEq, Eq, EnumIter)]
|
||||
pub enum CLinkType {
|
||||
/// For Certificate objects: the parent certificate for a certificate in a
|
||||
/// certificate chain. For Public Key objects: the corresponding
|
||||
/// certificate(s), containing the same public key.
|
||||
Certificate,
|
||||
/// For a Private Key object: the public key corresponding to the private
|
||||
/// key. For a Certificate object: the public key contained in the
|
||||
/// certificate.
|
||||
PublicKey,
|
||||
/// For a Public Key object: the private key corresponding to the public
|
||||
/// key.
|
||||
PrivateKey,
|
||||
/// For a derived Symmetric Key or Secret Data object: the object(s) from
|
||||
/// which the current symmetric key was derived.
|
||||
DerivationBaseObject,
|
||||
/// The symmetric key(s) or Secret Data object(s) that were derived from
|
||||
/// the current object.
|
||||
DerivedKey,
|
||||
/// For a Symmetric Key, an Asymmetric Private Key, or an Asymmetric
|
||||
/// Public Key object: the key that resulted from the re-key of the current
|
||||
/// key. For a Certificate object: the certificate that resulted from the
|
||||
/// re- certify. Note that there SHALL be only one such replacement
|
||||
/// object per Managed Object.
|
||||
ReplacementObject,
|
||||
/// For a Symmetric Key, an Asymmetric Private Key, or an Asymmetric
|
||||
/// Public Key object: the key that was re-keyed to obtain the current key.
|
||||
/// For a Certificate object: the certificate that was re-certified to
|
||||
/// obtain the current certificate.
|
||||
ReplacedObject,
|
||||
/// For all object types: the container or other parent object corresponding
|
||||
/// to the object.
|
||||
Parent,
|
||||
/// For all object types: the subordinate, derived or other child object
|
||||
/// corresponding to the object.
|
||||
Child,
|
||||
/// For all object types: the previous object to this object.
|
||||
Previous,
|
||||
/// For all object types: the next object to this object.
|
||||
Next,
|
||||
PKCS12Certificate,
|
||||
PKCS12Password,
|
||||
/// For wrapped objects: the object that was used to wrap this object.
|
||||
WrappingKey,
|
||||
//Extensions 8XXXXXXX
|
||||
}
|
||||
|
||||
impl Display for CLinkType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Certificate => write!(f, "certificate"),
|
||||
Self::PublicKey => write!(f, "public-key"),
|
||||
Self::PrivateKey => write!(f, "private-key"),
|
||||
Self::DerivationBaseObject => write!(f, "derivation-base-object"),
|
||||
Self::DerivedKey => write!(f, "derived-key"),
|
||||
Self::ReplacementObject => write!(f, "replacement-object"),
|
||||
Self::ReplacedObject => write!(f, "replaced-object"),
|
||||
Self::Parent => write!(f, "parent"),
|
||||
Self::Child => write!(f, "child"),
|
||||
Self::Previous => write!(f, "previous"),
|
||||
Self::Next => write!(f, "next"),
|
||||
Self::PKCS12Certificate => write!(f, "pkcs12-certificate"),
|
||||
Self::PKCS12Password => write!(f, "pkcs12-password"),
|
||||
Self::WrappingKey => write!(f, "wrapping-key"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CLinkType> for LinkType {
|
||||
fn from(value: CLinkType) -> Self {
|
||||
match value {
|
||||
CLinkType::Certificate => Self::CertificateLink,
|
||||
CLinkType::PublicKey => Self::PublicKeyLink,
|
||||
CLinkType::PrivateKey => Self::PrivateKeyLink,
|
||||
CLinkType::DerivationBaseObject => Self::DerivationBaseObjectLink,
|
||||
CLinkType::DerivedKey => Self::DerivedKeyLink,
|
||||
CLinkType::ReplacementObject => Self::ReplacementObjectLink,
|
||||
CLinkType::ReplacedObject => Self::ReplacedObjectLink,
|
||||
CLinkType::Parent => Self::ParentLink,
|
||||
CLinkType::Child => Self::ChildLink,
|
||||
CLinkType::Previous => Self::PreviousLink,
|
||||
CLinkType::Next => Self::NextLink,
|
||||
CLinkType::PKCS12Certificate => Self::PKCS12CertificateLink,
|
||||
CLinkType::PKCS12Password => Self::PKCS12PasswordLink,
|
||||
CLinkType::WrappingKey => Self::WrappingKeyLink,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the KMIP object attributes and tags.
|
||||
///
|
||||
/// When using tags to retrieve the object, rather than the object id,
|
||||
/// an error is returned if multiple objects matching the tags are found.
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct GetAttributesAction {
|
||||
/// The unique identifier of the cryptographic object.
|
||||
/// If not specified, tags should be specified
|
||||
#[clap(long = ATTRIBUTE_ID, short = 'i', group = "id-tags")]
|
||||
id: Option<String>,
|
||||
|
||||
/// Tag to use to retrieve the key when no key id is specified.
|
||||
/// To specify multiple tags, use the option multiple times.
|
||||
#[clap(long = "tag", short = 't', value_name = "TAG", group = "id-tags")]
|
||||
tags: Option<Vec<String>>,
|
||||
|
||||
/// The attributes or `KMIP-tags` to retrieve.
|
||||
/// To specify multiple attributes, use the option multiple times.
|
||||
/// If not specified, all possible attributes are returned.
|
||||
#[clap(
|
||||
long = "attribute",
|
||||
short = 'a',
|
||||
value_name = "ATTRIBUTE",
|
||||
verbatim_doc_comment
|
||||
)]
|
||||
attribute_tags: Vec<Tag>,
|
||||
|
||||
/// Filter on retrieved links. Only if KMIP tag `LinkType` is used in `attribute` parameter.
|
||||
/// To specify multiple attributes, use the option multiple times.
|
||||
/// If not specified, all possible link types are returned.
|
||||
#[clap(
|
||||
long = "link-type",
|
||||
short = 'l',
|
||||
value_name = "LINK_TYPE",
|
||||
verbatim_doc_comment
|
||||
)]
|
||||
attribute_link_types: Vec<CLinkType>,
|
||||
|
||||
/// An optional file where to export the attributes.
|
||||
/// The attributes will be in JSON TTLV format.
|
||||
#[clap(long = "output-file", short = 'o', verbatim_doc_comment)]
|
||||
output_file: Option<PathBuf>,
|
||||
}
|
||||
|
||||
fn add_if_not_empty(tag: Tag, new_value: &str, results: &mut HashMap<String, Value>) {
|
||||
if !new_value.is_empty() {
|
||||
results.insert(
|
||||
tag.to_string(),
|
||||
serde_json::to_value(new_value).unwrap_or_default(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl GetAttributesAction {
|
||||
/// Get the KMIP object attributes and tags.
|
||||
///
|
||||
/// When using tags to retrieve the object, rather than the object id,
|
||||
/// an error is returned if multiple objects matching the tags are found.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function can return an error if:
|
||||
///
|
||||
/// - The `--id` or one or more `--tag` options is not specified.
|
||||
/// - There is an error serializing the tags to a string.
|
||||
/// - There is an error performing the Get Attributes request.
|
||||
/// - There is an error serializing the attributes to JSON.
|
||||
/// - There is an error writing the attributes to the output file.
|
||||
/// - There is an error writing to the console.
|
||||
pub async fn process(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
trace!("GetAttributesAction: {:?}", self);
|
||||
let id = get_key_uid(self.id.as_ref(), self.tags.as_ref(), ATTRIBUTE_ID)?;
|
||||
|
||||
let mut references: Vec<AttributeReference> = Vec::with_capacity(self.attribute_tags.len());
|
||||
for tag in &self.attribute_tags {
|
||||
references.push(AttributeReference::Standard(*tag));
|
||||
}
|
||||
|
||||
// perform the Get Attributes request
|
||||
let GetAttributesResponse {
|
||||
unique_identifier,
|
||||
attributes,
|
||||
} = kms_rest_client
|
||||
.get_attributes(GetAttributes {
|
||||
unique_identifier: Some(UniqueIdentifier::TextString(id)),
|
||||
attribute_references: Some(references),
|
||||
})
|
||||
.await?;
|
||||
|
||||
debug!("GetAttributes response for {unique_identifier}: {attributes:?}",);
|
||||
|
||||
// if no tag asked -> return values for all possible tags
|
||||
let tags = if self.attribute_tags.is_empty() {
|
||||
trace!("No attribute tag specified, returning all possible tags");
|
||||
let mut all_tags = Vec::new();
|
||||
for tag in Tag::iter() {
|
||||
all_tags.push(tag);
|
||||
}
|
||||
all_tags
|
||||
} else {
|
||||
self.attribute_tags.clone()
|
||||
};
|
||||
|
||||
let mut results: HashMap<String, Value> = HashMap::new();
|
||||
for tag in &tags {
|
||||
match tag {
|
||||
Tag::ActivationDate => {
|
||||
if let Some(v) = attributes.activation_date.as_ref() {
|
||||
results
|
||||
.insert(tag.to_string(), serde_json::to_value(v).unwrap_or_default());
|
||||
}
|
||||
}
|
||||
Tag::CertificateLength => {
|
||||
if let Some(v) = attributes.certificate_length.as_ref() {
|
||||
results
|
||||
.insert(tag.to_string(), serde_json::to_value(v).unwrap_or_default());
|
||||
}
|
||||
}
|
||||
Tag::CertificateType => {
|
||||
if let Some(v) = attributes.certificate_type.as_ref() {
|
||||
results
|
||||
.insert(tag.to_string(), serde_json::to_value(v).unwrap_or_default());
|
||||
}
|
||||
}
|
||||
Tag::CertificateSubjectC => {
|
||||
if let Some(v) = attributes.certificate_attributes.as_ref() {
|
||||
add_if_not_empty(*tag, &v.certificate_subject_c, &mut results);
|
||||
}
|
||||
}
|
||||
Tag::CertificateSubjectCN => {
|
||||
if let Some(v) = attributes.certificate_attributes.as_ref() {
|
||||
add_if_not_empty(*tag, &v.certificate_subject_cn, &mut results);
|
||||
}
|
||||
}
|
||||
Tag::CertificateSubjectDC => {
|
||||
if let Some(v) = attributes.certificate_attributes.as_ref() {
|
||||
add_if_not_empty(*tag, &v.certificate_subject_dc, &mut results);
|
||||
}
|
||||
}
|
||||
Tag::CertificateSubjectEmail => {
|
||||
if let Some(v) = attributes.certificate_attributes.as_ref() {
|
||||
add_if_not_empty(*tag, &v.certificate_subject_email, &mut results);
|
||||
}
|
||||
}
|
||||
Tag::CertificateSubjectL => {
|
||||
if let Some(v) = attributes.certificate_attributes.as_ref() {
|
||||
add_if_not_empty(*tag, &v.certificate_subject_l, &mut results);
|
||||
}
|
||||
}
|
||||
Tag::CertificateSubjectO => {
|
||||
if let Some(v) = attributes.certificate_attributes.as_ref() {
|
||||
add_if_not_empty(*tag, &v.certificate_subject_o, &mut results);
|
||||
}
|
||||
}
|
||||
Tag::CertificateSubjectOU => {
|
||||
if let Some(v) = attributes.certificate_attributes.as_ref() {
|
||||
add_if_not_empty(*tag, &v.certificate_subject_ou, &mut results);
|
||||
}
|
||||
}
|
||||
Tag::CertificateSubjectST => {
|
||||
if let Some(v) = attributes.certificate_attributes.as_ref() {
|
||||
add_if_not_empty(*tag, &v.certificate_subject_st, &mut results);
|
||||
}
|
||||
}
|
||||
Tag::CertificateSubjectDNQualifier => {
|
||||
if let Some(v) = attributes.certificate_attributes.as_ref() {
|
||||
add_if_not_empty(*tag, &v.certificate_subject_dn_qualifier, &mut results);
|
||||
}
|
||||
}
|
||||
Tag::CertificateSubjectSerialNumber => {
|
||||
if let Some(v) = attributes.certificate_attributes.as_ref() {
|
||||
add_if_not_empty(*tag, &v.certificate_subject_serial_number, &mut results);
|
||||
}
|
||||
}
|
||||
Tag::CertificateSubjectTitle => {
|
||||
if let Some(v) = attributes.certificate_attributes.as_ref() {
|
||||
add_if_not_empty(*tag, &v.certificate_subject_title, &mut results);
|
||||
}
|
||||
}
|
||||
Tag::CertificateSubjectUID => {
|
||||
if let Some(v) = attributes.certificate_attributes.as_ref() {
|
||||
add_if_not_empty(*tag, &v.certificate_subject_uid, &mut results);
|
||||
}
|
||||
}
|
||||
Tag::CertificateIssuerC => {
|
||||
if let Some(v) = attributes.certificate_attributes.as_ref() {
|
||||
add_if_not_empty(*tag, &v.certificate_issuer_c, &mut results);
|
||||
}
|
||||
}
|
||||
Tag::CertificateIssuerCN => {
|
||||
if let Some(v) = attributes.certificate_attributes.as_ref() {
|
||||
add_if_not_empty(*tag, &v.certificate_issuer_cn, &mut results);
|
||||
}
|
||||
}
|
||||
Tag::CertificateIssuerDC => {
|
||||
if let Some(v) = attributes.certificate_attributes.as_ref() {
|
||||
add_if_not_empty(*tag, &v.certificate_issuer_dc, &mut results);
|
||||
}
|
||||
}
|
||||
Tag::CertificateIssuerEmail => {
|
||||
if let Some(v) = attributes.certificate_attributes.as_ref() {
|
||||
add_if_not_empty(*tag, &v.certificate_issuer_email, &mut results);
|
||||
}
|
||||
}
|
||||
Tag::CertificateIssuerL => {
|
||||
if let Some(v) = attributes.certificate_attributes.as_ref() {
|
||||
add_if_not_empty(*tag, &v.certificate_issuer_l, &mut results);
|
||||
}
|
||||
}
|
||||
Tag::CertificateIssuerO => {
|
||||
if let Some(v) = attributes.certificate_attributes.as_ref() {
|
||||
add_if_not_empty(*tag, &v.certificate_issuer_o, &mut results);
|
||||
}
|
||||
}
|
||||
Tag::CertificateIssuerOU => {
|
||||
if let Some(v) = attributes.certificate_attributes.as_ref() {
|
||||
add_if_not_empty(*tag, &v.certificate_issuer_ou, &mut results);
|
||||
}
|
||||
}
|
||||
Tag::CertificateIssuerST => {
|
||||
if let Some(v) = attributes.certificate_attributes.as_ref() {
|
||||
add_if_not_empty(*tag, &v.certificate_issuer_st, &mut results);
|
||||
}
|
||||
}
|
||||
Tag::CertificateIssuerDNQualifier => {
|
||||
if let Some(v) = attributes.certificate_attributes.as_ref() {
|
||||
add_if_not_empty(*tag, &v.certificate_issuer_dn_qualifier, &mut results);
|
||||
}
|
||||
}
|
||||
Tag::CertificateIssuerUID => {
|
||||
if let Some(v) = attributes.certificate_attributes.as_ref() {
|
||||
add_if_not_empty(*tag, &v.certificate_issuer_uid, &mut results);
|
||||
}
|
||||
}
|
||||
Tag::CertificateIssuerSerialNumber => {
|
||||
if let Some(v) = attributes.certificate_attributes.as_ref() {
|
||||
add_if_not_empty(*tag, &v.certificate_issuer_serial_number, &mut results);
|
||||
}
|
||||
}
|
||||
Tag::CertificateIssuerTitle => {
|
||||
if let Some(v) = attributes.certificate_attributes.as_ref() {
|
||||
add_if_not_empty(*tag, &v.certificate_issuer_title, &mut results);
|
||||
}
|
||||
}
|
||||
Tag::CryptographicAlgorithm => {
|
||||
if let Some(v) = attributes.cryptographic_algorithm.as_ref() {
|
||||
results
|
||||
.insert(tag.to_string(), serde_json::to_value(v).unwrap_or_default());
|
||||
}
|
||||
}
|
||||
Tag::CryptographicLength => {
|
||||
if let Some(v) = attributes.cryptographic_length.as_ref() {
|
||||
results
|
||||
.insert(tag.to_string(), serde_json::to_value(v).unwrap_or_default());
|
||||
}
|
||||
}
|
||||
Tag::CryptographicParameters => {
|
||||
if let Some(v) = attributes.cryptographic_parameters.as_ref() {
|
||||
results
|
||||
.insert(tag.to_string(), serde_json::to_value(v).unwrap_or_default());
|
||||
}
|
||||
}
|
||||
Tag::CryptographicDomainParameters => {
|
||||
if let Some(v) = attributes.cryptographic_domain_parameters.as_ref() {
|
||||
results
|
||||
.insert(tag.to_string(), serde_json::to_value(v).unwrap_or_default());
|
||||
}
|
||||
}
|
||||
Tag::CryptographicUsageMask => {
|
||||
if let Some(v) = attributes.cryptographic_usage_mask.as_ref() {
|
||||
results
|
||||
.insert(tag.to_string(), serde_json::to_value(v).unwrap_or_default());
|
||||
}
|
||||
}
|
||||
Tag::KeyFormatType => {
|
||||
if let Some(v) = attributes.key_format_type.as_ref() {
|
||||
results
|
||||
.insert(tag.to_string(), serde_json::to_value(v).unwrap_or_default());
|
||||
}
|
||||
}
|
||||
Tag::ObjectType => {
|
||||
if let Some(v) = attributes.object_type.as_ref() {
|
||||
results
|
||||
.insert(tag.to_string(), serde_json::to_value(v).unwrap_or_default());
|
||||
}
|
||||
}
|
||||
Tag::Tag => {
|
||||
let tags = attributes.get_tags();
|
||||
results.insert(
|
||||
tag.to_string(),
|
||||
serde_json::to_value(tags).unwrap_or_default(),
|
||||
);
|
||||
}
|
||||
Tag::VendorExtension => {
|
||||
if let Some(vendor_attributes) = attributes.vendor_attributes.as_ref() {
|
||||
results.insert(
|
||||
tag.to_string(),
|
||||
serde_json::to_value(vendor_attributes).unwrap_or_default(),
|
||||
);
|
||||
}
|
||||
}
|
||||
_x => {}
|
||||
}
|
||||
}
|
||||
|
||||
// if no link asked -> return values for all possible links
|
||||
let link_types: Vec<LinkType> = if self.attribute_link_types.is_empty() {
|
||||
trace!("No link type specified, returning all possible link types");
|
||||
let mut all_links = Vec::new();
|
||||
for link in LinkType::iter() {
|
||||
all_links.push(link);
|
||||
}
|
||||
all_links
|
||||
} else {
|
||||
self.attribute_link_types
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(LinkType::from)
|
||||
.collect()
|
||||
};
|
||||
|
||||
trace!("Attributes at this point: {attributes:?}",);
|
||||
for link_type in &link_types {
|
||||
trace!("Processing link type: {link_type:?}",);
|
||||
if let Some(v) = attributes.get_link(*link_type).as_ref() {
|
||||
trace!("Link type {link_type} found: {v:?}",);
|
||||
results.insert(
|
||||
link_type.to_string(),
|
||||
serde_json::to_value(v).unwrap_or_default(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(output_file) = &self.output_file {
|
||||
let json = serde_json::to_string_pretty(&results)?;
|
||||
debug!("GetAttributes response for {unique_identifier}: {}", json);
|
||||
write_bytes_to_file(json.as_bytes(), output_file)?;
|
||||
let stdout = format!(
|
||||
"The attributes for {unique_identifier} were exported to {:?}",
|
||||
&output_file
|
||||
);
|
||||
console::Stdout::new(&stdout).write()?;
|
||||
} else {
|
||||
let mut stdout = console::Stdout::new(&format!("Attributes for {unique_identifier}"));
|
||||
stdout.set_unique_identifier(unique_identifier);
|
||||
stdout.set_attributes(results);
|
||||
stdout.write()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
mod delete;
|
||||
mod get;
|
||||
mod set;
|
||||
|
||||
use clap::Subcommand;
|
||||
use cosmian_kms_client::KmsClient;
|
||||
pub use delete::DeleteAttributesAction;
|
||||
pub use get::GetAttributesAction;
|
||||
pub use set::{SetAttributesAction, SetOrDeleteAttributes, VendorAttributeCli};
|
||||
|
||||
use crate::error::result::CliResult;
|
||||
|
||||
/// Get/Set/Delete the KMIP object attributes.
|
||||
#[derive(Subcommand)]
|
||||
pub enum AttributesCommands {
|
||||
Get(GetAttributesAction),
|
||||
Set(SetAttributesAction),
|
||||
Delete(DeleteAttributesAction),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub use get::CLinkType;
|
||||
#[cfg(test)]
|
||||
pub use set::CCryptographicAlgorithm;
|
||||
|
||||
impl AttributesCommands {
|
||||
/// Process the Attributes commands action.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `kms_rest_client` - The KMS client instance used to communicate with the KMS server.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the version query fails or if there is an issue writing to the console.
|
||||
pub async fn process(&self, client_connector: &KmsClient) -> CliResult<()> {
|
||||
match self {
|
||||
Self::Get(action) => action.process(client_connector).await,
|
||||
Self::Set(action) => action.process(client_connector).await,
|
||||
Self::Delete(action) => action.process(client_connector).await,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,372 +0,0 @@
|
|||
use std::{convert::TryFrom, fmt::Display};
|
||||
|
||||
use clap::{Parser, ValueEnum};
|
||||
use cosmian_kms_client::{
|
||||
cosmian_kmip::kmip_2_1::kmip_types::UniqueIdentifier,
|
||||
kmip_2_1::{
|
||||
kmip_operations::{SetAttribute, SetAttributeResponse},
|
||||
kmip_types::{
|
||||
self, Attribute, CryptographicAlgorithm, Link, LinkType, LinkedObjectIdentifier,
|
||||
VendorAttribute,
|
||||
},
|
||||
},
|
||||
KmsClient,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use strum::EnumIter;
|
||||
use tracing::{info, trace};
|
||||
|
||||
use crate::{
|
||||
actions::{
|
||||
console,
|
||||
labels::ATTRIBUTE_ID,
|
||||
shared::{
|
||||
get_key_uid,
|
||||
utils::{build_usage_mask_from_key_usage, KeyUsage},
|
||||
},
|
||||
},
|
||||
cli_bail,
|
||||
error::result::CliResult,
|
||||
};
|
||||
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
#[derive(ValueEnum, Clone, Copy, Debug, EnumIter)]
|
||||
pub enum CCryptographicAlgorithm {
|
||||
AES,
|
||||
/// This is `CKM_RSA_PKCS_OAEP` from PKCS#11
|
||||
/// see <https://docs.oasis-open.org/pkcs11/pkcs11-curr/v2.40/cos01/pkcs11-curr-v2.40-cos01.html>#_Toc408226895
|
||||
/// To use `CKM_RSA_AES_KEY_WRAP` from PKCS#11, use and RSA key with AES as the algorithm
|
||||
/// See <https://docs.oasis-open.org/pkcs11/pkcs11-curr/v2.40/cos01/pkcs11-curr-v2.40-cos01.html>#_Toc408226908
|
||||
RSA,
|
||||
ECDSA,
|
||||
ECDH,
|
||||
EC,
|
||||
Chacha20,
|
||||
Chacha20Poly1305,
|
||||
SHA3224,
|
||||
SHA3256,
|
||||
SHA3384,
|
||||
SHA3512,
|
||||
Ed25519,
|
||||
Ed448,
|
||||
Covercrypt,
|
||||
CovercryptBulk,
|
||||
}
|
||||
|
||||
impl Display for CCryptographicAlgorithm {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let value = match self {
|
||||
Self::AES => "aes",
|
||||
Self::RSA => "rsa",
|
||||
Self::ECDSA => "ecdsa",
|
||||
Self::ECDH => "ecdh",
|
||||
Self::EC => "ec",
|
||||
Self::Chacha20 => "chacha20",
|
||||
Self::Chacha20Poly1305 => "chacha20-poly1305",
|
||||
Self::SHA3224 => "sha3224",
|
||||
Self::SHA3256 => "sha3256",
|
||||
Self::SHA3384 => "sha3384",
|
||||
Self::SHA3512 => "sha3512",
|
||||
Self::Ed25519 => "ed25519",
|
||||
Self::Ed448 => "ed448",
|
||||
Self::Covercrypt => "covercrypt",
|
||||
Self::CovercryptBulk => "covercrypt-bulk",
|
||||
};
|
||||
write!(f, "{value}")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CCryptographicAlgorithm> for CryptographicAlgorithm {
|
||||
fn from(value: CCryptographicAlgorithm) -> Self {
|
||||
match value {
|
||||
CCryptographicAlgorithm::AES => Self::AES,
|
||||
CCryptographicAlgorithm::RSA => Self::RSA,
|
||||
CCryptographicAlgorithm::ECDSA => Self::ECDSA,
|
||||
CCryptographicAlgorithm::ECDH => Self::ECDH,
|
||||
CCryptographicAlgorithm::EC => Self::EC,
|
||||
CCryptographicAlgorithm::Chacha20 => Self::ChaCha20,
|
||||
CCryptographicAlgorithm::Chacha20Poly1305 => Self::ChaCha20Poly1305,
|
||||
CCryptographicAlgorithm::SHA3224 => Self::SHA3224,
|
||||
CCryptographicAlgorithm::SHA3256 => Self::SHA3256,
|
||||
CCryptographicAlgorithm::SHA3384 => Self::SHA3384,
|
||||
CCryptographicAlgorithm::SHA3512 => Self::SHA3512,
|
||||
CCryptographicAlgorithm::Ed25519 => Self::Ed25519,
|
||||
CCryptographicAlgorithm::Ed448 => Self::Ed448,
|
||||
CCryptographicAlgorithm::Covercrypt => Self::CoverCrypt,
|
||||
CCryptographicAlgorithm::CovercryptBulk => Self::CoverCryptBulk,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Parser, Deserialize, Default, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct VendorAttributeCli {
|
||||
/// The vendor identification.
|
||||
#[clap(long, short = 'v', requires = "attribute_name")]
|
||||
pub vendor_identification: Option<String>,
|
||||
/// The attribute name.
|
||||
#[clap(long, short = 'n', requires = "vendor_identification")]
|
||||
pub attribute_name: Option<String>,
|
||||
/// The attribute value (in hex format).
|
||||
#[clap(long, requires = "vendor_identification")]
|
||||
pub attribute_value: Option<String>,
|
||||
}
|
||||
|
||||
impl TryFrom<&VendorAttributeCli> for Attribute {
|
||||
type Error = crate::error::CliError;
|
||||
|
||||
fn try_from(vendor_attribute: &VendorAttributeCli) -> Result<Self, Self::Error> {
|
||||
let vendor_attribute = kmip_types::VendorAttribute {
|
||||
vendor_identification: vendor_attribute
|
||||
.vendor_identification
|
||||
.clone()
|
||||
.unwrap_or_default(),
|
||||
attribute_name: vendor_attribute.attribute_name.clone().unwrap_or_default(),
|
||||
attribute_value: hex::decode(
|
||||
vendor_attribute.attribute_value.clone().unwrap_or_default(),
|
||||
)?,
|
||||
};
|
||||
Ok(Self::VendorAttributes(vec![vendor_attribute]))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&VendorAttributeCli> for VendorAttribute {
|
||||
type Error = crate::error::CliError;
|
||||
|
||||
fn try_from(vendor_attribute: &VendorAttributeCli) -> Result<Self, Self::Error> {
|
||||
let vendor_attribute = Self {
|
||||
vendor_identification: vendor_attribute
|
||||
.vendor_identification
|
||||
.clone()
|
||||
.unwrap_or_default(),
|
||||
attribute_name: vendor_attribute.attribute_name.clone().unwrap_or_default(),
|
||||
attribute_value: hex::decode(
|
||||
vendor_attribute.attribute_value.clone().unwrap_or_default(),
|
||||
)?,
|
||||
};
|
||||
Ok(vendor_attribute)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Parser, Default, Debug)]
|
||||
pub struct SetOrDeleteAttributes {
|
||||
/// The unique identifier of the cryptographic object.
|
||||
/// If not specified, tags should be specified
|
||||
#[clap(long = ATTRIBUTE_ID, short = 'i', group = "id-tags")]
|
||||
pub(crate) id: Option<String>,
|
||||
|
||||
/// Tag to use to retrieve the key when no key id is specified.
|
||||
/// To specify multiple tags, use the option multiple times.
|
||||
#[clap(long = "tag", short = 't', value_name = "TAG", group = "id-tags")]
|
||||
pub(crate) tags: Option<Vec<String>>,
|
||||
|
||||
/// Set the activation date of the key. Epoch time (or Unix time) in milliseconds.
|
||||
#[clap(long, short = 'd')]
|
||||
pub(crate) activation_date: Option<u64>,
|
||||
|
||||
/// The cryptographic algorithm used by the key.
|
||||
#[clap(long, short = 'a')]
|
||||
pub(crate) cryptographic_algorithm: Option<CCryptographicAlgorithm>,
|
||||
|
||||
/// The length of the cryptographic key.
|
||||
#[clap(long)]
|
||||
pub(crate) cryptographic_length: Option<i32>,
|
||||
|
||||
/// The key usage. Add multiple times to specify multiple key usages.
|
||||
#[clap(long, short = 'u')]
|
||||
pub(crate) key_usage: Option<Vec<KeyUsage>>,
|
||||
|
||||
/// The link to the corresponding public key id if any.
|
||||
#[clap(long)]
|
||||
pub(crate) public_key_id: Option<String>,
|
||||
|
||||
/// The link to the corresponding private key id if any.
|
||||
#[clap(long)]
|
||||
pub(crate) private_key_id: Option<String>,
|
||||
|
||||
/// The link to the corresponding certificate id if any.
|
||||
#[clap(long)]
|
||||
pub(crate) certificate_id: Option<String>,
|
||||
|
||||
/// The link to the corresponding PKCS12 certificate id if any.
|
||||
#[clap(long = "p12-id")]
|
||||
pub(crate) pkcs12_certificate_id: Option<String>,
|
||||
|
||||
/// The link to the corresponding PKCS12 password certificate if any.
|
||||
#[clap(long = "p12-pwd")]
|
||||
pub(crate) pkcs12_password_certificate: Option<String>,
|
||||
|
||||
/// The link to the corresponding parent id if any.
|
||||
#[clap(long)]
|
||||
pub(crate) parent_id: Option<String>,
|
||||
|
||||
/// The link to the corresponding child id if any.
|
||||
#[clap(long)]
|
||||
pub(crate) child_id: Option<String>,
|
||||
|
||||
#[clap(flatten)]
|
||||
pub vendor_attributes: Option<VendorAttributeCli>,
|
||||
}
|
||||
|
||||
impl SetOrDeleteAttributes {
|
||||
pub(crate) fn get_attributes_from_args(&self) -> CliResult<Vec<Attribute>> {
|
||||
let mut result = Vec::new();
|
||||
|
||||
if let Some(activation_date) = &self.activation_date {
|
||||
let attribute = Attribute::ActivationDate(*activation_date);
|
||||
result.push(attribute);
|
||||
}
|
||||
|
||||
if let Some(cryptographic_algorithm) = &self.cryptographic_algorithm {
|
||||
let attribute = Attribute::CryptographicAlgorithm(CryptographicAlgorithm::from(
|
||||
cryptographic_algorithm.to_owned(),
|
||||
));
|
||||
result.push(attribute);
|
||||
}
|
||||
|
||||
if let Some(cryptographic_length) = &self.cryptographic_length {
|
||||
let attribute = Attribute::CryptographicLength(*cryptographic_length);
|
||||
result.push(attribute);
|
||||
}
|
||||
|
||||
if let Some(key_usage) = &self.key_usage {
|
||||
let cryptographic_usage_mask =
|
||||
build_usage_mask_from_key_usage(key_usage).ok_or_else(|| {
|
||||
crate::error::CliError::Conversion(format!(
|
||||
"Could not convert {key_usage:?} to cryptographic usage mask"
|
||||
))
|
||||
})?;
|
||||
let attribute = Attribute::CryptographicUsageMask(cryptographic_usage_mask);
|
||||
result.push(attribute);
|
||||
}
|
||||
|
||||
if let Some(public_key_id) = &self.public_key_id {
|
||||
let attribute = Attribute::Links(vec![Link {
|
||||
link_type: LinkType::PublicKeyLink,
|
||||
linked_object_identifier: LinkedObjectIdentifier::TextString(public_key_id.clone()),
|
||||
}]);
|
||||
result.push(attribute);
|
||||
}
|
||||
|
||||
if let Some(private_key_id) = &self.private_key_id {
|
||||
let attribute = Attribute::Links(vec![Link {
|
||||
link_type: LinkType::PrivateKeyLink,
|
||||
linked_object_identifier: LinkedObjectIdentifier::TextString(
|
||||
private_key_id.clone(),
|
||||
),
|
||||
}]);
|
||||
result.push(attribute);
|
||||
}
|
||||
|
||||
if let Some(certificate_id) = &self.certificate_id {
|
||||
let attribute = Attribute::Links(vec![Link {
|
||||
link_type: LinkType::CertificateLink,
|
||||
linked_object_identifier: LinkedObjectIdentifier::TextString(
|
||||
certificate_id.clone(),
|
||||
),
|
||||
}]);
|
||||
result.push(attribute);
|
||||
}
|
||||
|
||||
if let Some(pkcs12_certificate_id) = &self.pkcs12_certificate_id {
|
||||
let attribute = Attribute::Links(vec![Link {
|
||||
link_type: LinkType::PKCS12CertificateLink,
|
||||
linked_object_identifier: LinkedObjectIdentifier::TextString(
|
||||
pkcs12_certificate_id.clone(),
|
||||
),
|
||||
}]);
|
||||
result.push(attribute);
|
||||
}
|
||||
|
||||
if let Some(pkcs12_password_certificate) = &self.pkcs12_password_certificate {
|
||||
let attribute = Attribute::Links(vec![Link {
|
||||
link_type: LinkType::PKCS12PasswordLink,
|
||||
linked_object_identifier: LinkedObjectIdentifier::TextString(
|
||||
pkcs12_password_certificate.clone(),
|
||||
),
|
||||
}]);
|
||||
result.push(attribute);
|
||||
}
|
||||
|
||||
if let Some(parent_id) = &self.parent_id {
|
||||
let attribute = Attribute::Links(vec![Link {
|
||||
link_type: LinkType::ParentLink,
|
||||
linked_object_identifier: LinkedObjectIdentifier::TextString(parent_id.clone()),
|
||||
}]);
|
||||
result.push(attribute);
|
||||
}
|
||||
|
||||
if let Some(child_id) = &self.child_id {
|
||||
let attribute = Attribute::Links(vec![Link {
|
||||
link_type: LinkType::ChildLink,
|
||||
linked_object_identifier: LinkedObjectIdentifier::TextString(child_id.clone()),
|
||||
}]);
|
||||
result.push(attribute);
|
||||
}
|
||||
|
||||
if let Some(vendor_attributes) = &self.vendor_attributes {
|
||||
let attribute = Attribute::try_from(vendor_attributes)?;
|
||||
result.push(attribute);
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the KMIP object attributes.
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct SetAttributesAction {
|
||||
#[clap(flatten)]
|
||||
requested_attributes: SetOrDeleteAttributes,
|
||||
}
|
||||
|
||||
impl SetAttributesAction {
|
||||
async fn set_attribute(
|
||||
&self,
|
||||
kms_rest_client: &KmsClient,
|
||||
id: &str,
|
||||
attribute: Attribute,
|
||||
) -> CliResult<()> {
|
||||
let SetAttributeResponse { unique_identifier } = kms_rest_client
|
||||
.set_attribute(SetAttribute {
|
||||
unique_identifier: Some(UniqueIdentifier::TextString(id.to_owned())),
|
||||
new_attribute: attribute.clone(),
|
||||
})
|
||||
.await?;
|
||||
info!("SetAttributes response for {unique_identifier}: {attribute:?}");
|
||||
let mut stdout = console::Stdout::new("Attribute set successfully");
|
||||
stdout.set_tags(self.requested_attributes.tags.as_ref());
|
||||
stdout.set_unique_identifier(id);
|
||||
stdout.set_attribute(attribute);
|
||||
stdout.write()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Processes the `SetAttributes` action.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function can return a `CliError` if one of the following conditions occur:
|
||||
///
|
||||
/// - Either `--id` or one or more `--tag` must be specified.
|
||||
///
|
||||
pub async fn process(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
trace!("SetAttributeAction: {:?}", self);
|
||||
let id = get_key_uid(
|
||||
self.requested_attributes.id.as_ref(),
|
||||
self.requested_attributes.tags.as_ref(),
|
||||
ATTRIBUTE_ID,
|
||||
)?;
|
||||
|
||||
let attributes_to_set = self.requested_attributes.get_attributes_from_args()?;
|
||||
if attributes_to_set.is_empty() {
|
||||
cli_bail!("No attribute specified")
|
||||
}
|
||||
|
||||
for attribute in attributes_to_set {
|
||||
self.set_attribute(kms_rest_client, &id, attribute).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,417 +0,0 @@
|
|||
use std::{
|
||||
io,
|
||||
io::Write,
|
||||
sync::{atomic::AtomicUsize, Arc, Mutex},
|
||||
};
|
||||
|
||||
use clap::Parser;
|
||||
use cosmian_kms_client::{
|
||||
kmip_2_1::{
|
||||
extra::BulkData,
|
||||
kmip_operations::{Decrypt, Encrypt},
|
||||
kmip_types::{
|
||||
BlockCipherMode, CryptographicAlgorithm, CryptographicParameters, UniqueIdentifier,
|
||||
},
|
||||
},
|
||||
KmsClient,
|
||||
};
|
||||
use num_format::{CustomFormat, Grouping, ToFormattedString};
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
use crate::{
|
||||
actions::{
|
||||
rsa::keys::{create_key_pair::CreateKeyPairAction, revoke_key::RevokeKeyAction},
|
||||
symmetric::keys::create_key::CreateKeyAction,
|
||||
},
|
||||
error::{
|
||||
result::{CliResult, CliResultHelper},
|
||||
CliError,
|
||||
},
|
||||
};
|
||||
|
||||
struct EncryptionResult {
|
||||
batch_id: usize,
|
||||
ciphertext: Zeroizing<Vec<u8>>,
|
||||
encryption_time: u128,
|
||||
}
|
||||
|
||||
struct FinalResult {
|
||||
batch_id: usize,
|
||||
encryption_time: u128,
|
||||
decryption_time: u128,
|
||||
}
|
||||
|
||||
/// Run a set of benches to check the server performance.
|
||||
///
|
||||
/// This command will create one or more keys, encrypt and decrypt a set of data
|
||||
/// then revoke the keys.
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct BenchAction {
|
||||
/// The number of parallel threads to use
|
||||
#[clap(long = "number-of-threads", short = 't', default_value = "1")]
|
||||
num_threads: usize,
|
||||
|
||||
/// The size of an encryption/decryption batch.
|
||||
/// A size of 1 does not use the `BulkData` API
|
||||
#[clap(
|
||||
long = "batch-size",
|
||||
short = 'b',
|
||||
default_value = "1",
|
||||
verbatim_doc_comment
|
||||
)]
|
||||
batch_size: usize,
|
||||
|
||||
/// The number of batches to run
|
||||
#[clap(long = "num-batches", short = 'n', default_value = "1")]
|
||||
num_batches: usize,
|
||||
|
||||
/// Use a wrapped key (by a 4096 RSA key) to encrypt the symmetric key
|
||||
#[clap(long = "wrapped-key", short = 'w', default_value = "false")]
|
||||
wrapped_key: bool,
|
||||
|
||||
/// Display batch results details
|
||||
#[clap(long = "verbose", short = 'v', default_value = "false")]
|
||||
verbose: bool,
|
||||
}
|
||||
|
||||
impl BenchAction {
|
||||
/// Run the tests
|
||||
/// # Errors
|
||||
/// Returns an error if the server is not reachable or if the keys can't be created.
|
||||
#[allow(clippy::print_stdout)]
|
||||
pub async fn process(&self, kms_rest_client: Arc<KmsClient>) -> CliResult<()> {
|
||||
let version = kms_rest_client
|
||||
.version()
|
||||
.await
|
||||
.with_context(|| "Can't execute the version query on the kms server")?;
|
||||
println!("Server version: {version}");
|
||||
println!(
|
||||
"Running bench with {} threads, batch size {}, {} batches.",
|
||||
self.num_threads, self.batch_size, self.num_batches
|
||||
);
|
||||
if self.wrapped_key {
|
||||
println!("Algorithm: AES GCM using a 256 bit key wrapped by a 4096 bit RSA key");
|
||||
} else {
|
||||
println!("Algorithm: AES GCM using a 256 bit key");
|
||||
}
|
||||
let (key_id, wrapping_key) = self.create_keys(&kms_rest_client).await?;
|
||||
|
||||
// u128 formatter
|
||||
let format = CustomFormat::builder()
|
||||
.grouping(Grouping::Standard)
|
||||
.separator(" ")
|
||||
.build()
|
||||
.map_err(|e| CliError::Default(format!("Failed to create the formatter: {e}")))?;
|
||||
|
||||
// the data to encrypt
|
||||
let data = if self.batch_size == 1 {
|
||||
Zeroizing::new(vec![1u8; 64])
|
||||
} else {
|
||||
BulkData::new(vec![Zeroizing::new(vec![1u8; 64]); self.batch_size]).serialize()?
|
||||
};
|
||||
|
||||
// Encryption
|
||||
{
|
||||
let mut stdout = io::stdout().lock();
|
||||
write!(stdout, "Encrypting")?;
|
||||
stdout.flush()?;
|
||||
}
|
||||
let amortized_encryption_time = std::time::Instant::now();
|
||||
let counter = Arc::new(AtomicUsize::new(0));
|
||||
let mut handles = Vec::new();
|
||||
for _ in 0..self.num_threads {
|
||||
let kms_rest_client = kms_rest_client.clone();
|
||||
let key_id = key_id.clone();
|
||||
let data = data.clone();
|
||||
let counter = counter.clone();
|
||||
let num_batches = self.num_batches;
|
||||
let handle = tokio::spawn(async move {
|
||||
encrypt(kms_rest_client, key_id, data, counter, num_batches).await
|
||||
});
|
||||
handles.push(handle);
|
||||
}
|
||||
|
||||
let mut encryption_results = Vec::new();
|
||||
for handle in handles {
|
||||
match handle.await {
|
||||
Ok(Ok(results)) => {
|
||||
encryption_results.extend(results);
|
||||
}
|
||||
Ok(Err(e)) => return Err(e),
|
||||
Err(e) => return Err(CliError::Default(format!("Tokio Error: {e}"))),
|
||||
}
|
||||
}
|
||||
let total_encryption_time_amortized = amortized_encryption_time.elapsed().as_micros();
|
||||
{
|
||||
let mut stdout = io::stdout().lock();
|
||||
writeln!(
|
||||
stdout,
|
||||
": {}µs",
|
||||
total_encryption_time_amortized.to_formatted_string(&format)
|
||||
)?;
|
||||
}
|
||||
|
||||
// Decryption
|
||||
{
|
||||
let mut stdout = io::stdout().lock();
|
||||
write!(stdout, "Decrypting")?;
|
||||
stdout.flush()?;
|
||||
}
|
||||
let amortized_decryption_time = std::time::Instant::now();
|
||||
let ciphertexts_to_process = Arc::new(Mutex::new(encryption_results));
|
||||
let mut handles = Vec::new();
|
||||
for _ in 0..self.num_threads {
|
||||
let kms_rest_client = kms_rest_client.clone();
|
||||
let key_id = key_id.clone();
|
||||
let ciphertexts_to_process = ciphertexts_to_process.clone();
|
||||
let handle = tokio::spawn(async move {
|
||||
decrypt(kms_rest_client, key_id, ciphertexts_to_process).await
|
||||
});
|
||||
handles.push(handle);
|
||||
}
|
||||
|
||||
let mut final_results = Vec::new();
|
||||
for handle in handles {
|
||||
match handle.await {
|
||||
Ok(Ok(results)) => {
|
||||
final_results.extend(results);
|
||||
}
|
||||
Ok(Err(e)) => return Err(e),
|
||||
Err(e) => return Err(CliError::Default(format!("Tokio Error: {e}"))),
|
||||
}
|
||||
}
|
||||
let total_decryption_time_amortized = amortized_decryption_time.elapsed().as_micros();
|
||||
{
|
||||
let mut stdout = io::stdout().lock();
|
||||
writeln!(
|
||||
stdout,
|
||||
": {}µs",
|
||||
total_decryption_time_amortized.to_formatted_string(&format)
|
||||
)?;
|
||||
stdout.flush()?;
|
||||
}
|
||||
// revoke the keys
|
||||
self.revoke_keys(&kms_rest_client, key_id, wrapping_key)
|
||||
.await?;
|
||||
|
||||
// Parse results
|
||||
final_results.sort_by_key(|r| r.batch_id);
|
||||
let mut total_encryption_time = 0_u128;
|
||||
let mut total_decryption_time = 0_u128;
|
||||
if self.verbose {
|
||||
for result in final_results {
|
||||
total_encryption_time += result.encryption_time;
|
||||
total_decryption_time += result.decryption_time;
|
||||
if self.verbose {
|
||||
println!(
|
||||
"{}: encryption: {}µs ({}µs/v), decryption: {}µs ({}µs/v)",
|
||||
result.batch_id,
|
||||
result.encryption_time.to_formatted_string(&format),
|
||||
result.encryption_time / (self.batch_size as u128),
|
||||
result.decryption_time.to_formatted_string(&format),
|
||||
result.decryption_time / (self.batch_size as u128)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!(
|
||||
"Encryption time {}µs => {}µs/batch => {}µs/value",
|
||||
total_encryption_time.to_formatted_string(&format),
|
||||
(total_encryption_time / (self.num_batches as u128)).to_formatted_string(&format),
|
||||
total_encryption_time / (self.num_batches * self.batch_size) as u128
|
||||
);
|
||||
println!(
|
||||
"Decryption time {}µs => {}µs/batch => {}µs/value",
|
||||
total_decryption_time.to_formatted_string(&format),
|
||||
(total_decryption_time / self.num_batches as u128).to_formatted_string(&format),
|
||||
total_decryption_time / (self.num_batches * self.batch_size) as u128
|
||||
);
|
||||
println!(
|
||||
"Amortized encryption time ({} threads): {}µs => {}µs/batch => {}µs/value",
|
||||
self.num_threads,
|
||||
total_encryption_time_amortized.to_formatted_string(&format),
|
||||
(total_encryption_time_amortized / (self.num_batches as u128))
|
||||
.to_formatted_string(&format),
|
||||
total_encryption_time_amortized / (self.num_batches * self.batch_size) as u128
|
||||
);
|
||||
println!(
|
||||
"Amortized decryption time ({} threads): {}µs => {}µs/batch => {}µs/value",
|
||||
self.num_threads,
|
||||
total_decryption_time_amortized.to_formatted_string(&format),
|
||||
(total_decryption_time_amortized / (self.num_batches as u128))
|
||||
.to_formatted_string(&format),
|
||||
total_decryption_time_amortized / (self.num_batches * self.batch_size) as u128
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn create_keys(
|
||||
&self,
|
||||
kms_rest_client: &KmsClient,
|
||||
) -> CliResult<(
|
||||
UniqueIdentifier,
|
||||
Option<(UniqueIdentifier, UniqueIdentifier)>,
|
||||
)> {
|
||||
if self.wrapped_key {
|
||||
// create an RSA key pair
|
||||
let (sk, pk) = CreateKeyPairAction {
|
||||
tags: vec!["bench".to_owned()],
|
||||
..Default::default()
|
||||
}
|
||||
.run(kms_rest_client)
|
||||
.await?;
|
||||
let kk = CreateKeyAction {
|
||||
number_of_bits: Some(256),
|
||||
wrapping_key_id: Some(pk.to_string()),
|
||||
tags: vec!["bench".to_owned()],
|
||||
..Default::default()
|
||||
}
|
||||
.run(kms_rest_client)
|
||||
.await?;
|
||||
return Ok((kk, Some((sk, pk))));
|
||||
}
|
||||
let kk = CreateKeyAction {
|
||||
number_of_bits: Some(256),
|
||||
tags: vec!["bench".to_owned()],
|
||||
..Default::default()
|
||||
}
|
||||
.run(kms_rest_client)
|
||||
.await?;
|
||||
Ok((kk, None))
|
||||
}
|
||||
|
||||
async fn revoke_keys(
|
||||
&self,
|
||||
kms_rest_client: &KmsClient,
|
||||
symmetric_key: UniqueIdentifier,
|
||||
wrapping_key: Option<(UniqueIdentifier, UniqueIdentifier)>,
|
||||
) -> CliResult<()> {
|
||||
RevokeKeyAction {
|
||||
revocation_reason: "Bench".to_owned(),
|
||||
key_id: Some(symmetric_key.to_string()),
|
||||
tags: None,
|
||||
}
|
||||
.run(kms_rest_client)
|
||||
.await?;
|
||||
if let Some((sk, _pk)) = wrapping_key {
|
||||
// revoking the private key will revoke the public key
|
||||
RevokeKeyAction {
|
||||
revocation_reason: "Bench".to_owned(),
|
||||
key_id: Some(sk.to_string()),
|
||||
tags: None,
|
||||
}
|
||||
.run(kms_rest_client)
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn encrypt(
|
||||
kms_rest_client: Arc<KmsClient>,
|
||||
key_id: UniqueIdentifier,
|
||||
data: Zeroizing<Vec<u8>>,
|
||||
counter: Arc<AtomicUsize>,
|
||||
num_batches: usize,
|
||||
) -> CliResult<Vec<EncryptionResult>> {
|
||||
let mut results = Vec::new();
|
||||
loop {
|
||||
let next = counter.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
if next >= num_batches {
|
||||
break;
|
||||
}
|
||||
{
|
||||
let mut stdout = io::stdout().lock();
|
||||
write!(stdout, ".",)?;
|
||||
stdout.flush()?;
|
||||
}
|
||||
let encrypt = Encrypt {
|
||||
unique_identifier: Some(key_id.clone()),
|
||||
cryptographic_parameters: Some(CryptographicParameters {
|
||||
cryptographic_algorithm: Some(CryptographicAlgorithm::AES),
|
||||
block_cipher_mode: Some(BlockCipherMode::GCM),
|
||||
..Default::default()
|
||||
}),
|
||||
data: Some(data.clone()),
|
||||
..Default::default()
|
||||
};
|
||||
let start = std::time::Instant::now();
|
||||
let response = kms_rest_client
|
||||
.encrypt(encrypt)
|
||||
.await
|
||||
.with_context(|| "failed encrypting")?;
|
||||
let elapsed = start.elapsed().as_micros();
|
||||
let ciphertext = Zeroizing::new(
|
||||
[
|
||||
response.iv_counter_nonce.unwrap_or_default(),
|
||||
response.data.unwrap_or_default(),
|
||||
response.authenticated_encryption_tag.unwrap_or_default(),
|
||||
]
|
||||
.concat(),
|
||||
);
|
||||
results.push(EncryptionResult {
|
||||
batch_id: next,
|
||||
ciphertext,
|
||||
encryption_time: elapsed,
|
||||
});
|
||||
}
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
async fn decrypt(
|
||||
kms_rest_client: Arc<KmsClient>,
|
||||
key_id: UniqueIdentifier,
|
||||
encryptions: Arc<Mutex<Vec<EncryptionResult>>>,
|
||||
) -> CliResult<Vec<FinalResult>> {
|
||||
let mut results = Vec::new();
|
||||
loop {
|
||||
let next = encryptions
|
||||
.lock()
|
||||
.expect("could not lock encryption results")
|
||||
.pop();
|
||||
let Some(next) = next else { break };
|
||||
{
|
||||
let mut stdout = io::stdout().lock();
|
||||
write!(stdout, ".",)?;
|
||||
stdout.flush()?;
|
||||
}
|
||||
let (iv, data, tag) = match BulkData::deserialize(next.ciphertext.as_ref()) {
|
||||
Ok(_data) => (None, Some(next.ciphertext.as_slice()), None),
|
||||
Err(_e) => {
|
||||
// Single AES GCM query => split the data
|
||||
let iv_len = 12;
|
||||
let tag_len = 16;
|
||||
let iv = &next.ciphertext[..iv_len];
|
||||
let tag = &next.ciphertext[next.ciphertext.len() - tag_len..];
|
||||
let data = &next.ciphertext[iv_len..next.ciphertext.len() - tag_len];
|
||||
(Some(iv), Some(data), Some(tag))
|
||||
}
|
||||
};
|
||||
let decrypt = Decrypt {
|
||||
unique_identifier: Some(key_id.clone()),
|
||||
cryptographic_parameters: Some(CryptographicParameters {
|
||||
cryptographic_algorithm: Some(CryptographicAlgorithm::AES),
|
||||
block_cipher_mode: Some(BlockCipherMode::GCM),
|
||||
..Default::default()
|
||||
}),
|
||||
iv_counter_nonce: iv.map(Vec::from),
|
||||
data: data.map(Vec::from),
|
||||
authenticated_encryption_tag: tag.map(Vec::from),
|
||||
..Default::default()
|
||||
};
|
||||
let start = std::time::Instant::now();
|
||||
let _response = kms_rest_client
|
||||
.decrypt(decrypt)
|
||||
.await
|
||||
.with_context(|| "failed encrypting")?;
|
||||
let elapsed = start.elapsed().as_micros();
|
||||
results.push(FinalResult {
|
||||
batch_id: next.batch_id,
|
||||
encryption_time: next.encryption_time,
|
||||
decryption_time: elapsed,
|
||||
});
|
||||
}
|
||||
Ok(results)
|
||||
}
|
|
@ -1,448 +0,0 @@
|
|||
use std::{
|
||||
fmt::{Display, Formatter},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use clap::{Parser, ValueEnum};
|
||||
use cosmian_kms_client::{
|
||||
cosmian_kmip::kmip_2_1::{
|
||||
kmip_objects::ObjectType,
|
||||
kmip_operations::Certify,
|
||||
kmip_types::{
|
||||
Attributes, CertificateAttributes, CertificateRequestType, LinkType,
|
||||
LinkedObjectIdentifier, UniqueIdentifier,
|
||||
},
|
||||
},
|
||||
kmip_2_1::kmip_types::{
|
||||
CryptographicAlgorithm, CryptographicDomainParameters, KeyFormatType, RecommendedCurve,
|
||||
},
|
||||
read_bytes_from_file, KmsClient,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
actions::{
|
||||
console,
|
||||
labels::{CERTIFICATE_ID, CERTIFICATE_RECERTIFY},
|
||||
},
|
||||
error::{result::CliResult, CliError},
|
||||
};
|
||||
|
||||
/// The algorithm to use for the keypair generation
|
||||
#[derive(ValueEnum, Debug, Clone, Copy)]
|
||||
pub(crate) enum Algorithm {
|
||||
#[cfg(not(feature = "fips"))]
|
||||
NistP192,
|
||||
NistP224,
|
||||
NistP256,
|
||||
NistP384,
|
||||
NistP521,
|
||||
#[cfg(not(feature = "fips"))]
|
||||
X25519,
|
||||
#[cfg(not(feature = "fips"))]
|
||||
Ed25519,
|
||||
#[cfg(not(feature = "fips"))]
|
||||
X448,
|
||||
#[cfg(not(feature = "fips"))]
|
||||
Ed448,
|
||||
#[cfg(not(feature = "fips"))]
|
||||
RSA1024,
|
||||
RSA2048,
|
||||
RSA3072,
|
||||
RSA4096,
|
||||
}
|
||||
|
||||
impl Display for Algorithm {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
#[cfg(not(feature = "fips"))]
|
||||
Self::NistP192 => write!(f, "nist-p192"),
|
||||
Self::NistP224 => write!(f, "nist-p224"),
|
||||
Self::NistP256 => write!(f, "nist-p256"),
|
||||
Self::NistP384 => write!(f, "nist-p384"),
|
||||
Self::NistP521 => write!(f, "nist-p521"),
|
||||
#[cfg(not(feature = "fips"))]
|
||||
Self::X25519 => write!(f, "x25519"),
|
||||
#[cfg(not(feature = "fips"))]
|
||||
Self::Ed25519 => write!(f, "ed25519"),
|
||||
#[cfg(not(feature = "fips"))]
|
||||
Self::X448 => write!(f, "x448"),
|
||||
#[cfg(not(feature = "fips"))]
|
||||
Self::Ed448 => write!(f, "ed448"),
|
||||
#[cfg(not(feature = "fips"))]
|
||||
Self::RSA1024 => write!(f, "rsa1024"),
|
||||
Self::RSA2048 => write!(f, "rsa2048"),
|
||||
Self::RSA3072 => write!(f, "rsa3072"),
|
||||
Self::RSA4096 => write!(f, "rsa4096"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Issue or renew a X509 certificate
|
||||
///
|
||||
/// There are 4 possibilities to generate a certificate
|
||||
/// 1. Provide a Certificate Signing Request (CSR)
|
||||
/// using -certificate-signing-request
|
||||
/// 2. Provide a public key id to certify
|
||||
/// using -public-key-id-to-certify as well as a subject name
|
||||
/// 3. Provide the id of an existing certificate to re-certify
|
||||
/// using -certificate-id-to-re-certify
|
||||
/// 4. Generate a keypair then sign the public key to generate a certificate
|
||||
/// using -generate-key-pair as well as a subject name and an algorithm
|
||||
///
|
||||
/// The signer (issuer) is specified by providing
|
||||
/// - an issuer private key id using -issuer-private-key-id
|
||||
/// - and/or an issuer certificate id using -issuer-certificate-id.
|
||||
///
|
||||
/// If only one of this parameter is specified, the other one will be inferred
|
||||
/// from the links of the cryptographic object behind the provided parameter.
|
||||
///
|
||||
/// If no signer is provided, the certificate will be self-signed.
|
||||
/// It is not possible to self-sign a CSR.
|
||||
///
|
||||
/// When re-certifying a certificate, if no --certificate-id is provided,
|
||||
/// the original certificate id will be used and the original certificate will
|
||||
/// be replaced by the new one. In all other cases, a random certificate id
|
||||
/// will be generated.
|
||||
///
|
||||
/// Tags can be later used to retrieve the certificate. Tags are optional.
|
||||
///
|
||||
/// Examples:
|
||||
///
|
||||
/// 1. Generate a self-signed certificate with 10 years validity using curve (NIST) P-256
|
||||
///```sh
|
||||
///ckms certificates certify --certificate-id acme_root_ca \
|
||||
///--generate-key-pair --algorithm nist-p256 \
|
||||
///--subject-name "CN=ACME Root CA,OU=IT,O=ACME,L=New York,ST=New York,C=US" \
|
||||
///--days 3650
|
||||
///```
|
||||
///
|
||||
/// 2. Generate an intermediate CA certificate signed by the root CA and using
|
||||
/// some x509 extensions. The root CA certificate and private key are already in the KMS.
|
||||
/// The Root CA (issuer) private key id is 1bba3cfa-4ecb-47ad-a9cf-7a2c236e25a8
|
||||
/// and the x509 extensions are in the file intermediate.ext containing a `v3_ca` paragraph:
|
||||
///
|
||||
///```text
|
||||
/// [ v3_ca ]
|
||||
/// basicConstraints=CA:TRUE,pathlen:0
|
||||
/// keyUsage=keyCertSign,digitalSignature
|
||||
/// extendedKeyUsage=emailProtection
|
||||
/// crlDistributionPoints=URI:https://acme.com/crl.pem
|
||||
/// ```
|
||||
///
|
||||
/// ```sh
|
||||
/// ckms -- certificates certify --certificate-id acme_intermediate_ca \
|
||||
/// --issuer-private-key-id 1bba3cfa-4ecb-47ad-a9cf-7a2c236e25a8 \
|
||||
/// --generate-key-pair --algorithm nist-p256 \
|
||||
/// --subject-name "CN=ACME S/MIME intermediate,OU=IT,O=ACME,L=New York,ST=New York,C=US" \
|
||||
/// --days 1825 \
|
||||
/// --certificate-extensions intermediate.ext
|
||||
/// ```
|
||||
#[derive(Parser)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct CertifyAction {
|
||||
/// The unique identifier of the certificate to issue or renew.
|
||||
/// If not provided, a random one will be generated when issuing a certificate,
|
||||
/// or the original one will be used when renewing a certificate.
|
||||
#[clap(long = CERTIFICATE_ID, short = 'c')]
|
||||
certificate_id: Option<String>,
|
||||
|
||||
/// The path to a certificate signing request.
|
||||
#[clap(
|
||||
long = "certificate-signing-request",
|
||||
short = 'r',
|
||||
group = "csr_pk",
|
||||
required = false
|
||||
)]
|
||||
certificate_signing_request: Option<PathBuf>,
|
||||
|
||||
/// The format of the certificate signing request.
|
||||
#[clap(long ="certificate-signing-request-format", short = 'f', default_value="pem", value_parser(["pem", "der"]))]
|
||||
certificate_signing_request_format: String,
|
||||
|
||||
/// The id of a public key to certify
|
||||
#[clap(
|
||||
long = "public-key-id-to-certify",
|
||||
short = 'p',
|
||||
group = "csr_pk",
|
||||
requires = "subject_name",
|
||||
required = false
|
||||
)]
|
||||
public_key_id_to_certify: Option<String>,
|
||||
|
||||
/// The id of a certificate to re-certify
|
||||
#[clap(
|
||||
long = CERTIFICATE_RECERTIFY,
|
||||
short = 'n',
|
||||
group = "csr_pk",
|
||||
required = false
|
||||
)]
|
||||
certificate_id_to_re_certify: Option<String>,
|
||||
|
||||
/// Generate a keypair then sign the public key
|
||||
/// and generate a certificate
|
||||
#[clap(
|
||||
long = "generate-key-pair",
|
||||
short = 'g',
|
||||
group = "csr_pk",
|
||||
requires = "subject_name",
|
||||
requires = "algorithm",
|
||||
required = false
|
||||
)]
|
||||
generate_key_pair: bool,
|
||||
|
||||
/// When certifying a public key, or generating a keypair,
|
||||
/// the subject name to use.
|
||||
///
|
||||
/// For instance: "CN=John Doe,OU=Org Unit,O=Org Name,L=City,ST=State,C=US"
|
||||
#[clap(long = "subject-name", short = 's', verbatim_doc_comment)]
|
||||
subject_name: Option<String>,
|
||||
|
||||
/// The algorithm to use for the keypair generation
|
||||
#[clap(long = "algorithm", short = 'a', default_value = "rsa4096")]
|
||||
algorithm: Algorithm,
|
||||
|
||||
/// The unique identifier of the private key of the issuer.
|
||||
/// A certificate must be linked to that private key
|
||||
/// if no issuer certificate id is provided.
|
||||
#[clap(long = "issuer-private-key-id", short = 'k')]
|
||||
issuer_private_key_id: Option<String>,
|
||||
|
||||
/// The unique identifier of the certificate of the issuer.
|
||||
/// A private key must be linked to that certificate
|
||||
/// if no issuer private key id is provided.
|
||||
#[clap(long = "issuer-certificate-id", short = 'i')]
|
||||
issuer_certificate_id: Option<String>,
|
||||
|
||||
/// The requested number of validity days
|
||||
/// The server may grant a different value
|
||||
#[clap(long = "days", short = 'd', default_value = "365")]
|
||||
number_of_days: usize,
|
||||
|
||||
/// The path to a X509 extension's file, containing a `v3_ca` paragraph
|
||||
/// with the x509 extensions to use. For instance:
|
||||
///
|
||||
/// ```text
|
||||
/// [ v3_ca ]
|
||||
/// basicConstraints=CA:TRUE,pathlen:0
|
||||
/// keyUsage=keyCertSign,digitalSignature
|
||||
/// extendedKeyUsage=emailProtection
|
||||
/// crlDistributionPoints=URI:https://acme.com/crl.pem
|
||||
/// ```
|
||||
#[clap(long = "certificate-extensions", short = 'e', verbatim_doc_comment)]
|
||||
certificate_extensions: Option<PathBuf>,
|
||||
|
||||
/// The tag to associate to the certificate.
|
||||
/// To specify multiple tags, use the option multiple times.
|
||||
#[clap(long = "tag", short = 't', value_name = "TAG")]
|
||||
tags: Vec<String>,
|
||||
}
|
||||
|
||||
impl CertifyAction {
|
||||
pub async fn run(&self, client_connector: &KmsClient) -> CliResult<()> {
|
||||
let mut attributes = Attributes {
|
||||
object_type: Some(ObjectType::Certificate),
|
||||
..Attributes::default()
|
||||
};
|
||||
|
||||
// set the issuer certificate id
|
||||
if let Some(issuer_certificate_id) = &self.issuer_certificate_id {
|
||||
attributes.set_link(
|
||||
LinkType::CertificateLink,
|
||||
LinkedObjectIdentifier::TextString(issuer_certificate_id.clone()),
|
||||
);
|
||||
}
|
||||
|
||||
// set the issuer private key id
|
||||
if let Some(issuer_private_key_id) = &self.issuer_private_key_id {
|
||||
attributes.set_link(
|
||||
LinkType::PrivateKeyLink,
|
||||
LinkedObjectIdentifier::TextString(issuer_private_key_id.clone()),
|
||||
);
|
||||
}
|
||||
|
||||
// set the number of requested days
|
||||
attributes.set_requested_validity_days(self.number_of_days);
|
||||
|
||||
// A certificate id has been provided
|
||||
if let Some(certificate_id) = &self.certificate_id {
|
||||
attributes.unique_identifier =
|
||||
Some(UniqueIdentifier::TextString(certificate_id.clone()));
|
||||
}
|
||||
|
||||
attributes.set_tags(&self.tags)?;
|
||||
|
||||
let mut certificate_request_value = None;
|
||||
let mut certificate_request_type = None;
|
||||
let mut unique_identifier = None;
|
||||
|
||||
if let Some(certificate_signing_request) = &self.certificate_signing_request {
|
||||
certificate_request_value = Some(read_bytes_from_file(certificate_signing_request)?);
|
||||
certificate_request_type = match self.certificate_signing_request_format.as_str() {
|
||||
"der" => Some(CertificateRequestType::PKCS10),
|
||||
_ => Some(CertificateRequestType::PEM),
|
||||
};
|
||||
} else if let Some(public_key_to_certify) = &self.public_key_id_to_certify {
|
||||
attributes.certificate_attributes =
|
||||
Some(Box::new(CertificateAttributes::parse_subject_line(
|
||||
self.subject_name.as_ref().ok_or_else(|| {
|
||||
CliError::Default(
|
||||
"subject name is required when certifying a public key".to_owned(),
|
||||
)
|
||||
})?,
|
||||
)?));
|
||||
unique_identifier = Some(UniqueIdentifier::TextString(
|
||||
public_key_to_certify.to_string(),
|
||||
));
|
||||
} else if let Some(certificate_id_to_renew) = &self.certificate_id_to_re_certify {
|
||||
unique_identifier = Some(UniqueIdentifier::TextString(
|
||||
certificate_id_to_renew.clone(),
|
||||
));
|
||||
} else if self.generate_key_pair {
|
||||
attributes.certificate_attributes =
|
||||
Some(Box::new(CertificateAttributes::parse_subject_line(
|
||||
self.subject_name.as_ref().ok_or_else(|| {
|
||||
CliError::Default(
|
||||
"subject name is required when generating a keypair".to_owned(),
|
||||
)
|
||||
})?,
|
||||
)?));
|
||||
match self.algorithm {
|
||||
#[cfg(not(feature = "fips"))]
|
||||
Algorithm::RSA1024 => {
|
||||
rsa_algorithm(&mut attributes, 1024);
|
||||
}
|
||||
Algorithm::RSA2048 => {
|
||||
rsa_algorithm(&mut attributes, 2048);
|
||||
}
|
||||
Algorithm::RSA3072 => {
|
||||
rsa_algorithm(&mut attributes, 3072);
|
||||
}
|
||||
Algorithm::RSA4096 => {
|
||||
rsa_algorithm(&mut attributes, 4096);
|
||||
}
|
||||
#[cfg(not(feature = "fips"))]
|
||||
Algorithm::NistP192 => {
|
||||
ec_algorithm(
|
||||
&mut attributes,
|
||||
CryptographicAlgorithm::EC,
|
||||
RecommendedCurve::P192,
|
||||
);
|
||||
}
|
||||
Algorithm::NistP224 => {
|
||||
ec_algorithm(
|
||||
&mut attributes,
|
||||
CryptographicAlgorithm::EC,
|
||||
RecommendedCurve::P224,
|
||||
);
|
||||
}
|
||||
Algorithm::NistP256 => {
|
||||
ec_algorithm(
|
||||
&mut attributes,
|
||||
CryptographicAlgorithm::EC,
|
||||
RecommendedCurve::P256,
|
||||
);
|
||||
}
|
||||
Algorithm::NistP384 => {
|
||||
ec_algorithm(
|
||||
&mut attributes,
|
||||
CryptographicAlgorithm::EC,
|
||||
RecommendedCurve::P384,
|
||||
);
|
||||
}
|
||||
Algorithm::NistP521 => {
|
||||
ec_algorithm(
|
||||
&mut attributes,
|
||||
CryptographicAlgorithm::EC,
|
||||
RecommendedCurve::P521,
|
||||
);
|
||||
}
|
||||
#[cfg(not(feature = "fips"))]
|
||||
Algorithm::X25519 => {
|
||||
ec_algorithm(
|
||||
&mut attributes,
|
||||
CryptographicAlgorithm::EC,
|
||||
RecommendedCurve::CURVE25519,
|
||||
);
|
||||
}
|
||||
#[cfg(not(feature = "fips"))]
|
||||
Algorithm::Ed25519 => {
|
||||
ec_algorithm(
|
||||
&mut attributes,
|
||||
CryptographicAlgorithm::Ed25519,
|
||||
RecommendedCurve::CURVEED25519,
|
||||
);
|
||||
}
|
||||
#[cfg(not(feature = "fips"))]
|
||||
Algorithm::X448 => {
|
||||
ec_algorithm(
|
||||
&mut attributes,
|
||||
CryptographicAlgorithm::EC,
|
||||
RecommendedCurve::CURVE448,
|
||||
);
|
||||
}
|
||||
#[cfg(not(feature = "fips"))]
|
||||
Algorithm::Ed448 => {
|
||||
ec_algorithm(
|
||||
&mut attributes,
|
||||
CryptographicAlgorithm::Ed448,
|
||||
RecommendedCurve::CURVEED448,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(CliError::Default(
|
||||
"Supply a certificate signing request, a public key id or an existing certificate \
|
||||
id or request a keypair to be generated"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(extension_file) = &self.certificate_extensions {
|
||||
attributes.set_x509_extension_file(std::fs::read(extension_file)?);
|
||||
}
|
||||
|
||||
let certify_request = Certify {
|
||||
unique_identifier,
|
||||
attributes: Some(attributes),
|
||||
certificate_request_value,
|
||||
certificate_request_type,
|
||||
..Certify::default()
|
||||
};
|
||||
|
||||
let certificate_unique_identifier = client_connector
|
||||
.certify(certify_request)
|
||||
.await
|
||||
.map_err(|e| CliError::ServerError(format!("failed creating certificate: {e:?}")))?
|
||||
.unique_identifier;
|
||||
|
||||
let mut stdout = console::Stdout::new("The certificate was successfully generated.");
|
||||
stdout.set_tags(Some(&self.tags));
|
||||
stdout.set_unique_identifier(certificate_unique_identifier);
|
||||
stdout.write()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn ec_algorithm(
|
||||
attributes: &mut Attributes,
|
||||
cryptographic_algorithm: CryptographicAlgorithm,
|
||||
recommended_curve: RecommendedCurve,
|
||||
) {
|
||||
attributes.cryptographic_algorithm = Some(cryptographic_algorithm);
|
||||
attributes.cryptographic_domain_parameters = Some(CryptographicDomainParameters {
|
||||
recommended_curve: Some(recommended_curve),
|
||||
..CryptographicDomainParameters::default()
|
||||
});
|
||||
attributes.key_format_type = Some(KeyFormatType::ECPrivateKey);
|
||||
attributes.object_type = Some(ObjectType::PrivateKey);
|
||||
}
|
||||
|
||||
fn rsa_algorithm(attributes: &mut Attributes, cryptographic_length: i32) {
|
||||
attributes.cryptographic_algorithm = Some(CryptographicAlgorithm::RSA);
|
||||
attributes.cryptographic_length = Some(cryptographic_length);
|
||||
attributes.cryptographic_domain_parameters = None;
|
||||
attributes.cryptographic_parameters = None;
|
||||
attributes.key_format_type = Some(KeyFormatType::TransparentRSAPrivateKey);
|
||||
attributes.object_type = Some(ObjectType::PrivateKey);
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
use std::{fs::File, io::prelude::*, path::PathBuf};
|
||||
|
||||
use clap::Parser;
|
||||
use cosmian_kms_client::{
|
||||
cosmian_kmip::kmip_2_1::{kmip_operations::Decrypt, kmip_types::UniqueIdentifier},
|
||||
read_bytes_from_file, KmsClient,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
actions::{
|
||||
console,
|
||||
labels::KEY_ID,
|
||||
rsa::{HashFn, RsaEncryptionAlgorithm},
|
||||
shared::get_key_uid,
|
||||
},
|
||||
error::result::{CliResult, CliResultHelper},
|
||||
};
|
||||
|
||||
/// Decrypt a file using the private key of a certificate.
|
||||
///
|
||||
/// Note: this is not a streaming call: the file is entirely loaded in memory before being sent for decryption.
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct DecryptCertificateAction {
|
||||
/// The file to decrypt
|
||||
#[clap(required = true, name = "FILE")]
|
||||
input_file: PathBuf,
|
||||
|
||||
/// The private key unique identifier related to certificate
|
||||
/// If not specified, tags should be specified
|
||||
#[clap(long = KEY_ID, short = 'k', group = "key-tags")]
|
||||
private_key_id: Option<String>,
|
||||
|
||||
/// Tag to use to retrieve the key when no key id is specified.
|
||||
/// To specify multiple tags, use the option multiple times.
|
||||
#[clap(long = "tag", short = 't', value_name = "TAG", group = "key-tags")]
|
||||
tags: Option<Vec<String>>,
|
||||
|
||||
/// The encrypted output file path
|
||||
#[clap(required = false, long, short = 'o')]
|
||||
output_file: Option<PathBuf>,
|
||||
|
||||
/// Optional authentication data that was supplied during encryption.
|
||||
#[clap(required = false, long, short)]
|
||||
authentication_data: Option<String>,
|
||||
|
||||
/// Optional encryption algorithm.
|
||||
/// This is only available for RSA keys for now.
|
||||
/// The default for RSA is `PKCS_OAEP`.
|
||||
#[clap(long, short = 'e', verbatim_doc_comment)]
|
||||
encryption_algorithm: Option<RsaEncryptionAlgorithm>,
|
||||
}
|
||||
|
||||
impl DecryptCertificateAction {
|
||||
pub async fn run(&self, client_connector: &KmsClient) -> CliResult<()> {
|
||||
// Read the file to decrypt
|
||||
let ciphertext = read_bytes_from_file(&self.input_file)?;
|
||||
|
||||
// Recover the unique identifier or set of tags
|
||||
let id = get_key_uid(self.private_key_id.as_ref(), self.tags.as_ref(), KEY_ID)?;
|
||||
|
||||
// Create the kmip query
|
||||
let decrypt_request = Decrypt {
|
||||
unique_identifier: Some(UniqueIdentifier::TextString(id.clone())),
|
||||
data: Some(ciphertext),
|
||||
authenticated_encryption_additional_data: self
|
||||
.authentication_data
|
||||
.clone()
|
||||
.map(|s| s.as_bytes().to_vec()),
|
||||
cryptographic_parameters: self
|
||||
.encryption_algorithm
|
||||
.map(|alg| alg.to_cryptographic_parameters(HashFn::Sha256)),
|
||||
..Decrypt::default()
|
||||
};
|
||||
|
||||
// Query the KMS with your kmip data and retrieve the cleartext
|
||||
let decrypt_response = client_connector
|
||||
.decrypt(decrypt_request)
|
||||
.await
|
||||
.with_context(|| "Can't execute the query on the kms server")?;
|
||||
let plaintext = decrypt_response
|
||||
.data
|
||||
.context("Decrypt with certificate: the plaintext is empty")?;
|
||||
|
||||
// Write the decrypted file
|
||||
let output_file = self
|
||||
.output_file
|
||||
.clone()
|
||||
.unwrap_or_else(|| self.input_file.clone().with_extension("plain"));
|
||||
let mut buffer =
|
||||
File::create(&output_file).with_context(|| "Fail to write the plaintext file")?;
|
||||
buffer
|
||||
.write_all(&plaintext)
|
||||
.with_context(|| "Fail to write the plaintext file")?;
|
||||
|
||||
console::Stdout::new(&format!(
|
||||
"The decrypted file is available at {:?}",
|
||||
&output_file
|
||||
))
|
||||
.write()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
use clap::Parser;
|
||||
use cosmian_kms_client::KmsClient;
|
||||
|
||||
use crate::{
|
||||
actions::{
|
||||
labels::CERTIFICATE_ID,
|
||||
shared::{get_key_uid, utils::destroy},
|
||||
},
|
||||
error::result::CliResult,
|
||||
};
|
||||
|
||||
/// Destroy a certificate.
|
||||
///
|
||||
/// The certificate must have been revoked first.
|
||||
///
|
||||
/// When a certificate is destroyed but not removed,
|
||||
/// its metadata can only be exported
|
||||
/// by the owner of the certificate
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct DestroyCertificateAction {
|
||||
/// The certificate unique identifier.
|
||||
/// If not specified, tags should be specified
|
||||
#[clap(long = CERTIFICATE_ID, short = 'c', group = "certificate-tags")]
|
||||
certificate_id: Option<String>,
|
||||
|
||||
/// Tag to use to retrieve the certificate when no certificate id is specified.
|
||||
/// To specify multiple tags, use the option multiple times.
|
||||
#[clap(
|
||||
long = "tag",
|
||||
short = 't',
|
||||
value_name = "TAG",
|
||||
group = "certificate-tags"
|
||||
)]
|
||||
tags: Option<Vec<String>>,
|
||||
|
||||
/// If the certificate should be removed from the database
|
||||
/// If not specified, the certificate will be destroyed
|
||||
/// but its metadata will still be available in the database.
|
||||
/// Please note that the KMIP specification does not support the removal of objects.
|
||||
#[clap(long = "remove", default_value = "false", verbatim_doc_comment)]
|
||||
remove: bool,
|
||||
}
|
||||
|
||||
impl DestroyCertificateAction {
|
||||
pub async fn run(&self, client_connector: &KmsClient) -> CliResult<()> {
|
||||
let id = get_key_uid(
|
||||
self.certificate_id.as_ref(),
|
||||
self.tags.as_ref(),
|
||||
CERTIFICATE_ID,
|
||||
)?;
|
||||
destroy(client_connector, &id, self.remove).await
|
||||
}
|
||||
}
|
|
@ -1,117 +0,0 @@
|
|||
use std::{fs::File, io::prelude::*, path::PathBuf};
|
||||
|
||||
use clap::Parser;
|
||||
use cosmian_kms_client::{
|
||||
cosmian_kmip::kmip_2_1::{kmip_operations::Encrypt, kmip_types::UniqueIdentifier},
|
||||
read_bytes_from_file, KmsClient,
|
||||
};
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
use crate::{
|
||||
actions::{
|
||||
console,
|
||||
labels::CERTIFICATE_ID,
|
||||
rsa::{HashFn, RsaEncryptionAlgorithm},
|
||||
shared::get_key_uid,
|
||||
},
|
||||
error::result::{CliResult, CliResultHelper},
|
||||
};
|
||||
|
||||
/// Encrypt a file using the certificate public key.
|
||||
///
|
||||
/// Note: this is not a streaming call: the file is entirely loaded in memory before being sent for encryption.
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct EncryptCertificateAction {
|
||||
/// The file to encrypt
|
||||
#[clap(required = true, name = "FILE")]
|
||||
input_file: PathBuf,
|
||||
|
||||
/// The certificate unique identifier.
|
||||
/// If not specified, tags should be specified
|
||||
#[clap(long = CERTIFICATE_ID, short = 'c', group = "key-tags")]
|
||||
certificate_id: Option<String>,
|
||||
|
||||
/// Tag to use to retrieve the key when no key id is specified.
|
||||
/// To specify multiple tags, use the option multiple times.
|
||||
#[clap(long = "tag", short = 't', value_name = "TAG", group = "key-tags")]
|
||||
tags: Option<Vec<String>>,
|
||||
|
||||
/// The encrypted output file path
|
||||
#[clap(required = false, long, short = 'o')]
|
||||
output_file: Option<PathBuf>,
|
||||
|
||||
/// Optional authentication data.
|
||||
/// This data needs to be provided back for decryption.
|
||||
#[clap(required = false, long, short = 'a')]
|
||||
authentication_data: Option<String>,
|
||||
|
||||
/// Optional encryption algorithm.
|
||||
/// This is only available for RSA keys for now.
|
||||
/// The default for RSA is `PKCS_OAEP`.
|
||||
#[clap(long, short = 'e', verbatim_doc_comment)]
|
||||
encryption_algorithm: Option<RsaEncryptionAlgorithm>,
|
||||
}
|
||||
|
||||
impl EncryptCertificateAction {
|
||||
pub async fn run(&self, client_connector: &KmsClient) -> CliResult<()> {
|
||||
// Read the file to encrypt
|
||||
let data = Zeroizing::from(read_bytes_from_file(&self.input_file)?);
|
||||
|
||||
// Recover the unique identifier or set of tags
|
||||
let id = get_key_uid(
|
||||
self.certificate_id.as_ref(),
|
||||
self.tags.as_ref(),
|
||||
CERTIFICATE_ID,
|
||||
)?;
|
||||
|
||||
let authenticated_encryption_additional_data = self
|
||||
.authentication_data
|
||||
.as_ref()
|
||||
.map(|auth_data| auth_data.as_bytes().to_vec());
|
||||
|
||||
let cryptographic_parameters =
|
||||
self.encryption_algorithm
|
||||
.as_ref()
|
||||
.map(|encryption_algorithm| {
|
||||
encryption_algorithm.to_cryptographic_parameters(HashFn::Sha256)
|
||||
});
|
||||
|
||||
let encrypt_request = Encrypt {
|
||||
unique_identifier: Some(UniqueIdentifier::TextString(id.clone())),
|
||||
data: Some(data),
|
||||
authenticated_encryption_additional_data,
|
||||
cryptographic_parameters,
|
||||
..Encrypt::default()
|
||||
};
|
||||
|
||||
// Query the KMS for encryption
|
||||
let encrypt_response = client_connector
|
||||
.encrypt(encrypt_request)
|
||||
.await
|
||||
.with_context(|| "Can't execute the query on the kms server")?;
|
||||
|
||||
// Retrieve the ciphertext
|
||||
let ciphertext = encrypt_response
|
||||
.data
|
||||
.context("The encrypted data are empty")?;
|
||||
|
||||
// Write the encrypted file
|
||||
let output_file = self
|
||||
.output_file
|
||||
.clone()
|
||||
.unwrap_or_else(|| self.input_file.with_extension("enc"));
|
||||
let mut buffer =
|
||||
File::create(&output_file).with_context(|| "failed to write the encrypted file")?;
|
||||
buffer
|
||||
.write_all(&ciphertext)
|
||||
.with_context(|| "failed to write the encrypted file")?;
|
||||
|
||||
console::Stdout::new(&format!(
|
||||
"The encrypted file is available at {:?}",
|
||||
&output_file
|
||||
))
|
||||
.write()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,195 +0,0 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use clap::{Parser, ValueEnum};
|
||||
use cosmian_kms_client::{
|
||||
export_object,
|
||||
kmip_2_1::{kmip_objects::Object, kmip_types::KeyFormatType, ttlv::serializer::to_ttlv},
|
||||
write_bytes_to_file, write_json_object_to_file, write_kmip_object_to_file, ExportObjectParams,
|
||||
KmsClient,
|
||||
};
|
||||
use tracing::log::trace;
|
||||
|
||||
use crate::{
|
||||
actions::{console, labels::CERTIFICATE_ID, shared::get_key_uid},
|
||||
cli_bail,
|
||||
error::result::CliResult,
|
||||
};
|
||||
|
||||
#[derive(ValueEnum, Debug, Clone, PartialEq, Eq)]
|
||||
pub enum CertificateExportFormat {
|
||||
JsonTtlv,
|
||||
Pem,
|
||||
Pkcs12,
|
||||
#[cfg(not(feature = "fips"))]
|
||||
Pkcs12Legacy,
|
||||
Pkcs7,
|
||||
}
|
||||
|
||||
/// Export a certificate from the KMS
|
||||
///
|
||||
/// The certificate is exported either:
|
||||
/// - in TTLV JSON KMIP format (json-ttlv)
|
||||
/// - in X509 PEM format (pem)
|
||||
/// - in PKCS12 format including private key, certificate and chain (pkcs12)
|
||||
/// - in legacy PKCS12 format (pkcs12-legacy), compatible with openssl 1.x,
|
||||
/// for keystores that do not support the new format
|
||||
/// (e.g. Java keystores, `macOS` Keychains,...)
|
||||
/// This format is not available in FIPS mode.
|
||||
/// - in PKCS7 format including the entire certificates chain (pkcs7)
|
||||
///
|
||||
/// When using tags to retrieve rather than the unique id,
|
||||
/// an error is returned if multiple objects match the tags.
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct ExportCertificateAction {
|
||||
/// The file to export the certificate to
|
||||
#[clap(required = true)]
|
||||
certificate_file: PathBuf,
|
||||
|
||||
/// The certificate unique identifier stored in the KMS; for PKCS#12, provide the private key id
|
||||
/// If not specified, tags should be specified
|
||||
#[clap(
|
||||
long = CERTIFICATE_ID,
|
||||
short = 'c',
|
||||
group = "certificate-tags",
|
||||
verbatim_doc_comment
|
||||
)]
|
||||
certificate_id: Option<String>,
|
||||
|
||||
/// Tag to use to retrieve the certificate/private key when no unique id is specified.
|
||||
/// To specify multiple tags, use the option multiple times.
|
||||
#[clap(
|
||||
long = "tag",
|
||||
short = 't',
|
||||
value_name = "TAG",
|
||||
group = "certificate-tags",
|
||||
verbatim_doc_comment
|
||||
)]
|
||||
tags: Option<Vec<String>>,
|
||||
|
||||
/// Export the certificate in the selected format
|
||||
#[clap(long = "format", short = 'f', default_value = "json-ttlv")]
|
||||
output_format: CertificateExportFormat,
|
||||
|
||||
/// Password to use to protect the PKCS#12 file
|
||||
#[clap(long = "pkcs12-password", short = 'p')]
|
||||
pkcs12_password: Option<String>,
|
||||
|
||||
/// Allow exporting revoked and destroyed certificates or private key (for PKCS#12).
|
||||
/// The user must be the owner of the certificate.
|
||||
/// Destroyed objects have their key material removed.
|
||||
#[clap(
|
||||
long = "allow-revoked",
|
||||
short = 'r',
|
||||
default_value = "false",
|
||||
verbatim_doc_comment
|
||||
)]
|
||||
allow_revoked: bool,
|
||||
}
|
||||
|
||||
impl ExportCertificateAction {
|
||||
/// Export a certificate from the KMS
|
||||
pub async fn run(&self, client_connector: &KmsClient) -> CliResult<()> {
|
||||
trace!("Export certificate: {:?}", self);
|
||||
|
||||
let id = get_key_uid(
|
||||
self.certificate_id.as_ref(),
|
||||
self.tags.as_ref(),
|
||||
CERTIFICATE_ID,
|
||||
)?;
|
||||
|
||||
let (key_format_type, wrapping_key_id) = match self.output_format {
|
||||
CertificateExportFormat::JsonTtlv | CertificateExportFormat::Pem => {
|
||||
(KeyFormatType::X509, None)
|
||||
}
|
||||
CertificateExportFormat::Pkcs12 => {
|
||||
(KeyFormatType::PKCS12, self.pkcs12_password.as_deref())
|
||||
}
|
||||
#[cfg(not(feature = "fips"))]
|
||||
CertificateExportFormat::Pkcs12Legacy => {
|
||||
(KeyFormatType::Pkcs12Legacy, self.pkcs12_password.as_deref())
|
||||
}
|
||||
CertificateExportFormat::Pkcs7 => (KeyFormatType::PKCS7, None),
|
||||
};
|
||||
|
||||
// export the object
|
||||
let (id, object, export_attributes) = export_object(
|
||||
client_connector,
|
||||
&id,
|
||||
ExportObjectParams {
|
||||
wrapping_key_id,
|
||||
allow_revoked: self.allow_revoked,
|
||||
key_format_type: Some(key_format_type),
|
||||
..ExportObjectParams::default()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
match &object {
|
||||
Object::Certificate {
|
||||
certificate_value, ..
|
||||
} => {
|
||||
match self.output_format {
|
||||
CertificateExportFormat::JsonTtlv => {
|
||||
// save it to a file
|
||||
write_kmip_object_to_file(&object, &self.certificate_file)?;
|
||||
}
|
||||
CertificateExportFormat::Pem => {
|
||||
// save the pem to a file
|
||||
let pem = pem::Pem::new("CERTIFICATE", certificate_value.as_slice());
|
||||
write_bytes_to_file(pem.to_string().as_bytes(), &self.certificate_file)?;
|
||||
}
|
||||
CertificateExportFormat::Pkcs12 => {
|
||||
// PKCS12 is exported as a private key object
|
||||
cli_bail!("PKCS12: invalid object returned by the server.");
|
||||
}
|
||||
#[cfg(not(feature = "fips"))]
|
||||
CertificateExportFormat::Pkcs12Legacy => {
|
||||
// PKCS12 is exported as a private key object
|
||||
cli_bail!("PKCS12: invalid object returned by the server.");
|
||||
}
|
||||
CertificateExportFormat::Pkcs7 => {
|
||||
// save the pem to a file
|
||||
let pem =
|
||||
pem::Pem::new(String::from("PKCS7"), certificate_value.as_slice());
|
||||
write_bytes_to_file(pem.to_string().as_bytes(), &self.certificate_file)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
// PKCS12 is exported as a private key object
|
||||
Object::PrivateKey { key_block } => {
|
||||
let p12_bytes = key_block.key_bytes()?.to_vec();
|
||||
// save it to a file
|
||||
write_bytes_to_file(&p12_bytes, &self.certificate_file)?;
|
||||
}
|
||||
_ => {
|
||||
cli_bail!(
|
||||
"The object {} is not a certificate but a {}",
|
||||
&id,
|
||||
object.object_type()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let mut stdout = format!(
|
||||
"The certificate {} was exported to {:?}",
|
||||
&id, &self.certificate_file
|
||||
);
|
||||
|
||||
// write attributes to a file
|
||||
if let Some(export_attributes) = export_attributes {
|
||||
let attributes_file = self.certificate_file.with_extension("attributes.json");
|
||||
write_json_object_to_file(&to_ttlv(&export_attributes)?, &attributes_file)?;
|
||||
let stdout_attributes = format!(
|
||||
"The attributes of the certificate {} were exported to {:?}",
|
||||
&id, &attributes_file
|
||||
);
|
||||
stdout = format!("{stdout} - {stdout_attributes}");
|
||||
}
|
||||
let mut stdout = console::Stdout::new(&stdout);
|
||||
stdout.set_unique_identifier(id);
|
||||
stdout.write()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,396 +0,0 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use clap::{Parser, ValueEnum};
|
||||
use cosmian_kms_client::{
|
||||
cosmian_kmip::kmip_2_1::{
|
||||
kmip_objects::Object,
|
||||
kmip_types::{
|
||||
Attributes, CertificateType, KeyFormatType, LinkType, LinkedObjectIdentifier,
|
||||
},
|
||||
},
|
||||
import_object, read_bytes_from_file, read_object_from_json_ttlv_file, KmsClient,
|
||||
};
|
||||
use der::{Decode, DecodePem, Encode};
|
||||
use tracing::{debug, trace};
|
||||
use x509_cert::Certificate;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
use crate::{
|
||||
actions::{
|
||||
console,
|
||||
shared::{
|
||||
import_key::build_private_key_from_der_bytes,
|
||||
utils::{build_usage_mask_from_key_usage, KeyUsage},
|
||||
},
|
||||
},
|
||||
error::{result::CliResult, CliError},
|
||||
};
|
||||
|
||||
const MOZILLA_CCADB: &str =
|
||||
"https://ccadb.my.salesforce-sites.com/mozilla/IncludedRootsPEMTxt?TrustBitsInclude=Websites";
|
||||
|
||||
#[derive(ValueEnum, Debug, Clone)]
|
||||
pub enum CertificateInputFormat {
|
||||
JsonTtlv,
|
||||
Pem,
|
||||
Der,
|
||||
Chain,
|
||||
CCADB,
|
||||
Pkcs12,
|
||||
}
|
||||
|
||||
/// Import one of the following:
|
||||
/// - a certificate: formatted as a X509 PEM (pem), X509 DER (der) or JSON TTLV (json-ttlv)
|
||||
/// - a certificate chain as a PEM-stack (chain)
|
||||
/// - a PKCS12 file containing a certificate, a private key and possibly a chain (pkcs12)
|
||||
/// - the Mozilla Common CA Database (CCADB - fetched by the CLI before import) (ccadb)
|
||||
///
|
||||
/// When no unique id is specified, a unique id based on the key material is generated.
|
||||
///
|
||||
/// Tags can later be used to retrieve the certificate. Tags are optional.
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct ImportCertificateAction {
|
||||
/// The input file in PEM, KMIP-JSON-TTLV or PKCS#12 format.
|
||||
#[clap(
|
||||
required_if_eq_any([
|
||||
("input_format", "json-ttlv"),
|
||||
("input_format", "pem"),
|
||||
("input_format", "der"),
|
||||
("input_format", "chain"),
|
||||
("input_format", "pkcs12")
|
||||
])
|
||||
)]
|
||||
certificate_file: Option<PathBuf>,
|
||||
|
||||
/// The unique id of the leaf certificate; a unique id
|
||||
/// based on the key material is generated if not specified.
|
||||
/// When importing a PKCS12, the unique id will be that of the private key.
|
||||
#[clap(required = false, verbatim_doc_comment)]
|
||||
certificate_id: Option<String>,
|
||||
|
||||
/// Import the certificate in the selected format.
|
||||
#[clap(
|
||||
required = true,
|
||||
long = "format",
|
||||
short = 'f',
|
||||
default_value = "json-ttlv"
|
||||
)]
|
||||
input_format: CertificateInputFormat,
|
||||
|
||||
/// The corresponding private key id if any.
|
||||
/// Ignored for PKCS12 and CCADB formats.
|
||||
#[clap(long, short = 'k')]
|
||||
private_key_id: Option<String>,
|
||||
|
||||
/// The corresponding public key id if any.
|
||||
/// Ignored for PKCS12 and CCADB formats.
|
||||
#[clap(long, short = 'q')]
|
||||
public_key_id: Option<String>,
|
||||
|
||||
/// The issuer certificate id if any.
|
||||
/// Ignored for PKCS12 and CCADB formats.
|
||||
#[clap(long, short = 'i')]
|
||||
issuer_certificate_id: Option<String>,
|
||||
|
||||
/// PKCS12 password: only available for PKCS12 format.
|
||||
#[clap(long = "pkcs12-password", short = 'p')]
|
||||
pkcs12_password: Option<String>,
|
||||
|
||||
/// Replace an existing certificate under the same id.
|
||||
#[clap(
|
||||
required = false,
|
||||
long = "replace",
|
||||
short = 'r',
|
||||
default_value = "false"
|
||||
)]
|
||||
replace_existing: bool,
|
||||
|
||||
/// The tag to associate with the certificate.
|
||||
/// To specify multiple tags, use the option multiple times.
|
||||
#[clap(long = "tag", short = 't', value_name = "TAG")]
|
||||
tags: Vec<String>,
|
||||
|
||||
/// For what operations should the certificate be used.
|
||||
#[clap(long = "key-usage")]
|
||||
key_usage: Option<Vec<KeyUsage>>,
|
||||
}
|
||||
|
||||
impl ImportCertificateAction {
|
||||
pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
trace!("CLI: entering import certificate: {:?}", self);
|
||||
|
||||
//generate the leaf certificate attributes if links are specified
|
||||
let mut leaf_certificate_attributes = None;
|
||||
if let Some(issuer_certificate_id) = &self.issuer_certificate_id {
|
||||
let attributes = leaf_certificate_attributes.get_or_insert(Attributes::default());
|
||||
attributes.set_link(
|
||||
LinkType::CertificateLink,
|
||||
LinkedObjectIdentifier::TextString(issuer_certificate_id.clone()),
|
||||
);
|
||||
}
|
||||
if let Some(private_key_id) = &self.private_key_id {
|
||||
let attributes = leaf_certificate_attributes.get_or_insert(Attributes::default());
|
||||
attributes.set_link(
|
||||
LinkType::PrivateKeyLink,
|
||||
LinkedObjectIdentifier::TextString(private_key_id.clone()),
|
||||
);
|
||||
}
|
||||
if let Some(public_key_id) = &self.public_key_id {
|
||||
let attributes = leaf_certificate_attributes.get_or_insert(Attributes::default());
|
||||
attributes.set_link(
|
||||
LinkType::PublicKeyLink,
|
||||
LinkedObjectIdentifier::TextString(public_key_id.clone()),
|
||||
);
|
||||
}
|
||||
|
||||
trace!(
|
||||
"CLI: leaf_certificate_attributes: {:?}",
|
||||
leaf_certificate_attributes
|
||||
);
|
||||
let (stdout_message, returned_unique_identifier) = match self.input_format {
|
||||
CertificateInputFormat::JsonTtlv => {
|
||||
trace!("CLI: import certificate as TTLV JSON file");
|
||||
// read the certificate file
|
||||
let object = read_object_from_json_ttlv_file(self.get_certificate_file()?)?;
|
||||
let certificate_id = self
|
||||
.import_chain(
|
||||
kms_rest_client,
|
||||
vec![object],
|
||||
self.replace_existing,
|
||||
leaf_certificate_attributes,
|
||||
)
|
||||
.await?;
|
||||
(
|
||||
"The certificate in the JSON TTLV was successfully imported!".to_owned(),
|
||||
Some(certificate_id),
|
||||
)
|
||||
}
|
||||
CertificateInputFormat::Pem => {
|
||||
trace!("CLI: import certificate as PEM file");
|
||||
let pem_value = read_bytes_from_file(&self.get_certificate_file()?)?;
|
||||
// convert the PEM to X509 to make sure it is correct
|
||||
let certificate = Certificate::from_pem(&pem_value).map_err(|e| {
|
||||
CliError::Conversion(format!("Cannot read PEM content to X509. Error: {e:?}"))
|
||||
})?;
|
||||
let object = Object::Certificate {
|
||||
certificate_type: CertificateType::X509,
|
||||
certificate_value: certificate.to_der()?,
|
||||
};
|
||||
let certificate_id = self
|
||||
.import_chain(
|
||||
kms_rest_client,
|
||||
vec![object],
|
||||
self.replace_existing,
|
||||
leaf_certificate_attributes,
|
||||
)
|
||||
.await?;
|
||||
(
|
||||
"The certificate in the PEM file was successfully imported!".to_owned(),
|
||||
Some(certificate_id),
|
||||
)
|
||||
}
|
||||
CertificateInputFormat::Der => {
|
||||
debug!("CLI: import certificate as a DER file");
|
||||
let der_value = read_bytes_from_file(&self.get_certificate_file()?)?;
|
||||
// convert DER to X509 to make sure it is correct
|
||||
let certificate = Certificate::from_der(&der_value).map_err(|e| {
|
||||
CliError::Conversion(format!("Cannot read DER content to X509. Error: {e:?}"))
|
||||
})?;
|
||||
let object = Object::Certificate {
|
||||
certificate_type: CertificateType::X509,
|
||||
certificate_value: certificate.to_der()?,
|
||||
};
|
||||
let certificate_id = self
|
||||
.import_chain(
|
||||
kms_rest_client,
|
||||
vec![object],
|
||||
self.replace_existing,
|
||||
leaf_certificate_attributes,
|
||||
)
|
||||
.await?;
|
||||
(
|
||||
"The certificate in the DER file was successfully imported!".to_owned(),
|
||||
Some(certificate_id),
|
||||
)
|
||||
}
|
||||
CertificateInputFormat::Pkcs12 => {
|
||||
debug!("CLI: import certificate as PKCS12 file");
|
||||
let private_key_id = self.import_pkcs12(kms_rest_client).await?;
|
||||
(
|
||||
"The certificate(s) and private key were successfully imported! The private \
|
||||
key has id:"
|
||||
.to_string(),
|
||||
Some(private_key_id),
|
||||
)
|
||||
}
|
||||
CertificateInputFormat::Chain => {
|
||||
debug!("CLI: import certificate chain as PEM file");
|
||||
let pem_stack = read_bytes_from_file(&self.get_certificate_file()?)?;
|
||||
let objects = build_chain_from_stack(&pem_stack)?;
|
||||
// import the full chain
|
||||
let leaf_certificate_id = self
|
||||
.import_chain(
|
||||
kms_rest_client,
|
||||
objects,
|
||||
self.replace_existing,
|
||||
leaf_certificate_attributes,
|
||||
)
|
||||
.await?;
|
||||
(
|
||||
"The certificate chain in the PEM file was successfully imported!".to_owned(),
|
||||
Some(leaf_certificate_id),
|
||||
)
|
||||
}
|
||||
CertificateInputFormat::CCADB => {
|
||||
let ccadb_bytes = reqwest::get(MOZILLA_CCADB)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
CliError::ItemNotFound(format!(
|
||||
"Cannot fetch Mozilla CCADB ({MOZILLA_CCADB:?}. Error: {e:?})",
|
||||
))
|
||||
})?
|
||||
.bytes()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
CliError::Conversion(format!(
|
||||
"Cannot convert Mozilla CCADB content to bytes. Error: {e:?}"
|
||||
))
|
||||
})?;
|
||||
// import the certificates
|
||||
let objects = build_chain_from_stack(&ccadb_bytes)?;
|
||||
self.import_chain(kms_rest_client, objects, self.replace_existing, None)
|
||||
.await?;
|
||||
|
||||
("The list of Mozilla CCADB certificates".to_owned(), None)
|
||||
}
|
||||
};
|
||||
let mut stdout = console::Stdout::new(&stdout_message);
|
||||
stdout.set_tags(Some(&self.tags));
|
||||
if let Some(id) = returned_unique_identifier {
|
||||
stdout.set_unique_identifier(id);
|
||||
}
|
||||
stdout.write()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Import the certificate, the chain and the associated private key
|
||||
async fn import_pkcs12(&self, kms_rest_client: &KmsClient) -> CliResult<String> {
|
||||
let cryptographic_usage_mask = self
|
||||
.key_usage
|
||||
.as_deref()
|
||||
.and_then(build_usage_mask_from_key_usage);
|
||||
let pkcs12_bytes = Zeroizing::from(read_bytes_from_file(&self.get_certificate_file()?)?);
|
||||
|
||||
// Create a KMIP private key from the PKCS12 private key
|
||||
let private_key = build_private_key_from_der_bytes(KeyFormatType::PKCS12, pkcs12_bytes);
|
||||
|
||||
let mut attributes = private_key.attributes().cloned().unwrap_or_default();
|
||||
attributes.set_cryptographic_usage_mask(cryptographic_usage_mask);
|
||||
|
||||
if let Some(password) = &self.pkcs12_password {
|
||||
attributes.set_link(
|
||||
LinkType::PKCS12PasswordLink,
|
||||
LinkedObjectIdentifier::TextString(password.clone()),
|
||||
);
|
||||
}
|
||||
|
||||
let private_key_id = import_object(
|
||||
kms_rest_client,
|
||||
self.certificate_id.clone(),
|
||||
private_key,
|
||||
Some(attributes),
|
||||
false,
|
||||
self.replace_existing,
|
||||
&self.tags,
|
||||
)
|
||||
.await?;
|
||||
Ok(private_key_id)
|
||||
}
|
||||
|
||||
fn get_certificate_file(&self) -> CliResult<&PathBuf> {
|
||||
self.certificate_file.as_ref().ok_or_else(|| {
|
||||
CliError::InvalidRequest(format!(
|
||||
"Certificate file parameter is MANDATORY for {:?} format",
|
||||
self.input_format
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
/// Import the certificates in reverse order (from root to leaf)
|
||||
/// linking the child to the parent with `Link` of `LinkType::CertificateLink`
|
||||
async fn import_chain(
|
||||
&self,
|
||||
kms_rest_client: &KmsClient,
|
||||
mut objects: Vec<Object>,
|
||||
replace_existing: bool,
|
||||
leaf_certificate_attributes: Option<Attributes>,
|
||||
) -> CliResult<String> {
|
||||
let mut previous_identifier: Option<String> = None;
|
||||
while let Some(object) = objects.pop() {
|
||||
let mut import_attributes = if objects.is_empty() {
|
||||
// this is the leaf certificate
|
||||
leaf_certificate_attributes.clone()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// add link to issuer/parent certificate if any
|
||||
if let Some(id) = previous_identifier {
|
||||
let attributes = import_attributes.get_or_insert(Attributes::default());
|
||||
attributes.set_link(
|
||||
LinkType::CertificateLink,
|
||||
LinkedObjectIdentifier::TextString(id.clone()),
|
||||
);
|
||||
}
|
||||
// import the certificate
|
||||
let unique_identifier = import_object(
|
||||
kms_rest_client,
|
||||
self.certificate_id.clone(),
|
||||
object,
|
||||
import_attributes,
|
||||
false,
|
||||
replace_existing,
|
||||
&self.tags,
|
||||
)
|
||||
.await?;
|
||||
previous_identifier = Some(unique_identifier);
|
||||
}
|
||||
// return the identifier of the leaf certificate
|
||||
previous_identifier.ok_or_else(|| {
|
||||
CliError::Default("The certificate chain does not contain any certificate".to_owned())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a chain of certificates from a PEM stack
|
||||
fn build_chain_from_stack(pem_chain: &[u8]) -> CliResult<Vec<Object>> {
|
||||
let pem_s = pem::parse_many(pem_chain)
|
||||
.map_err(|e| CliError::Conversion(format!("Cannot parse PEM content. Error: {e:?}")))?; // check the PEM is valid (no error
|
||||
let mut objects = vec![];
|
||||
for pem_data in pem_s {
|
||||
// convert the PEM to X509 to make sure it is correct
|
||||
let certificate = Certificate::from_der(pem_data.contents()).map_err(|e| {
|
||||
CliError::Conversion(format!("Cannot read DER content to X509. Error: {e:?}"))
|
||||
})?;
|
||||
let object = Object::Certificate {
|
||||
certificate_type: CertificateType::X509,
|
||||
certificate_value: certificate.to_der()?,
|
||||
};
|
||||
objects.push(object);
|
||||
}
|
||||
Ok(objects)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::actions::certificates::import_certificate::build_chain_from_stack;
|
||||
|
||||
#[test]
|
||||
fn test_chain_parse() {
|
||||
let chain_str =
|
||||
include_bytes!("../../../../../test_data/certificates/mozilla_IncludedRootsPEM.txt");
|
||||
let objects = build_chain_from_stack(chain_str).unwrap();
|
||||
assert_eq!(objects.len(), 144);
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
#[cfg(test)]
|
||||
pub(crate) use certify::Algorithm;
|
||||
use clap::Subcommand;
|
||||
use cosmian_kms_client::KmsClient;
|
||||
pub use export_certificate::CertificateExportFormat;
|
||||
pub use import_certificate::CertificateInputFormat;
|
||||
|
||||
use self::{
|
||||
certify::CertifyAction, decrypt_certificate::DecryptCertificateAction,
|
||||
destroy_certificate::DestroyCertificateAction, encrypt_certificate::EncryptCertificateAction,
|
||||
export_certificate::ExportCertificateAction, import_certificate::ImportCertificateAction,
|
||||
revoke_certificate::RevokeCertificateAction, validate_certificate::ValidateCertificatesAction,
|
||||
};
|
||||
use crate::error::result::CliResult;
|
||||
|
||||
mod certify;
|
||||
mod decrypt_certificate;
|
||||
mod destroy_certificate;
|
||||
mod encrypt_certificate;
|
||||
mod export_certificate;
|
||||
mod import_certificate;
|
||||
mod revoke_certificate;
|
||||
mod validate_certificate;
|
||||
|
||||
/// Manage certificates. Create, import, destroy and revoke. Encrypt and decrypt data
|
||||
#[derive(Subcommand)]
|
||||
pub enum CertificatesCommands {
|
||||
Certify(CertifyAction),
|
||||
Decrypt(DecryptCertificateAction),
|
||||
Encrypt(EncryptCertificateAction),
|
||||
Export(ExportCertificateAction),
|
||||
Import(ImportCertificateAction),
|
||||
Revoke(RevokeCertificateAction),
|
||||
Destroy(DestroyCertificateAction),
|
||||
Validate(ValidateCertificatesAction),
|
||||
}
|
||||
|
||||
impl CertificatesCommands {
|
||||
/// Process the `Certificates` main commands.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `kms_rest_client` - A reference to the KMS client used to communicate with the KMS server.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the query execution on the KMS server fails.
|
||||
///
|
||||
pub async fn process(&self, client_connector: &KmsClient) -> CliResult<()> {
|
||||
match self {
|
||||
Self::Certify(action) => action.run(client_connector).await,
|
||||
Self::Decrypt(action) => action.run(client_connector).await,
|
||||
Self::Encrypt(action) => action.run(client_connector).await,
|
||||
Self::Export(action) => action.run(client_connector).await,
|
||||
Self::Import(action) => action.run(client_connector).await,
|
||||
Self::Revoke(action) => action.run(client_connector).await,
|
||||
Self::Destroy(action) => action.run(client_connector).await,
|
||||
Self::Validate(action) => action.run(client_connector).await,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
use clap::Parser;
|
||||
use cosmian_kms_client::KmsClient;
|
||||
|
||||
use crate::{
|
||||
actions::{
|
||||
labels::{CERTIFICATE_ID, TAG},
|
||||
shared::{get_key_uid, utils::revoke},
|
||||
},
|
||||
error::result::CliResult,
|
||||
};
|
||||
|
||||
/// Revoke a certificate.
|
||||
///
|
||||
/// When a certificate is revoked, it can only be exported by the owner of the certificate,
|
||||
/// using the --allow-revoked flag on the export function.
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct RevokeCertificateAction {
|
||||
/// The reason for the revocation as a string
|
||||
#[clap(required = true)]
|
||||
revocation_reason: String,
|
||||
|
||||
/// The certificate unique identifier of the certificate to revoke.
|
||||
/// If not specified, tags should be specified
|
||||
#[clap(long = CERTIFICATE_ID, short = 'c', group = "certificate-tags")]
|
||||
certificate_id: Option<String>,
|
||||
|
||||
/// Tag to use to retrieve the certificate when no certificate id is specified.
|
||||
/// To specify multiple tags, use the option multiple times.
|
||||
#[clap(
|
||||
long = TAG,
|
||||
short = 't',
|
||||
value_name = "TAG",
|
||||
group = "certificate-tags"
|
||||
)]
|
||||
tags: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl RevokeCertificateAction {
|
||||
pub async fn run(&self, client_connector: &KmsClient) -> CliResult<()> {
|
||||
let id = get_key_uid(
|
||||
self.certificate_id.as_ref(),
|
||||
self.tags.as_ref(),
|
||||
CERTIFICATE_ID,
|
||||
)?;
|
||||
revoke(client_connector, &id, &self.revocation_reason).await
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use clap::Parser;
|
||||
use cosmian_kms_client::{
|
||||
kmip_2_1::{kmip_types::ValidityIndicator, requests::build_validate_certificate_request},
|
||||
KmsClient,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
actions::{console, labels::CERTIFICATE_ID},
|
||||
error::result::CliResult,
|
||||
};
|
||||
|
||||
/// Validate a certificate.
|
||||
///
|
||||
/// A certificate or a chain of certificates is validated.
|
||||
/// It means that the certificate chain is valid in terms of time, well-signed,
|
||||
/// complete, and no components have been flagged as removed.
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct ValidateCertificatesAction {
|
||||
/// One or more Certificates filepath.
|
||||
#[clap(long = "certificate", short = 'v')]
|
||||
certificate: Vec<PathBuf>,
|
||||
/// One or more Unique Identifiers of Certificate Objects.
|
||||
#[clap(long = CERTIFICATE_ID, short = 'k')]
|
||||
certificate_id: Vec<String>,
|
||||
/// A Date-Time object indicating when the certificate chain needs to be
|
||||
/// valid. If omitted, the current date and time SHALL be assumed.
|
||||
#[clap(long = "validity-time", short = 't')]
|
||||
validity_time: Option<String>,
|
||||
}
|
||||
|
||||
impl ValidateCertificatesAction {
|
||||
pub async fn run(&self, client_connector: &KmsClient) -> CliResult<()> {
|
||||
let request = build_validate_certificate_request(
|
||||
&self.certificate,
|
||||
&self.certificate_id,
|
||||
self.validity_time.clone(),
|
||||
)?;
|
||||
let validity_indicator = client_connector.validate(request).await?.validity_indicator;
|
||||
console::Stdout::new(match validity_indicator {
|
||||
ValidityIndicator::Valid => "Valid",
|
||||
ValidityIndicator::Invalid => "Invalid",
|
||||
ValidityIndicator::Unknown => "Unknown",
|
||||
})
|
||||
.write()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,222 +0,0 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use cosmian_kms_client::{
|
||||
kmip_2_1::kmip_types::{Attribute, UniqueIdentifier},
|
||||
reexport::cosmian_kms_access::access::{
|
||||
AccessRightsObtainedResponse, ObjectOwnedResponse, UserAccessResponse,
|
||||
},
|
||||
};
|
||||
use serde::Serialize;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{
|
||||
cli_bail,
|
||||
error::{result::CliResult, CliError},
|
||||
};
|
||||
|
||||
pub const KMS_CLI_FORMAT: &str = "KMS_CLI_FORMAT";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum OutputFormat {
|
||||
Text,
|
||||
Json,
|
||||
Quiet,
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for OutputFormat {
|
||||
type Error = CliError;
|
||||
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
match value.to_lowercase().as_str() {
|
||||
"json" => Ok(Self::Json),
|
||||
"text" => Ok(Self::Text),
|
||||
"quiet" => Ok(Self::Quiet),
|
||||
_ => {
|
||||
cli_bail!(
|
||||
"Invalid output format: {value}. Supported values are: json, text, quiet"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Default)]
|
||||
pub struct Stdout {
|
||||
stdout: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
unique_identifier: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
unique_identifiers: Option<Vec<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
private_key_unique_identifier: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
public_key_unique_identifier: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
tags: Option<Vec<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
attribute: Option<Attribute>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
attributes: Option<HashMap<String, Value>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
accesses: Option<Vec<UserAccessResponse>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
access_rights_obtained: Option<Vec<AccessRightsObtainedResponse>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
object_owned: Option<Vec<ObjectOwnedResponse>>,
|
||||
}
|
||||
|
||||
impl Stdout {
|
||||
#[must_use]
|
||||
pub fn new(stdout: &str) -> Self {
|
||||
Self {
|
||||
stdout: stdout.to_string(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_tags(&mut self, tags: Option<&Vec<String>>) {
|
||||
self.tags = tags.cloned();
|
||||
}
|
||||
|
||||
pub fn set_unique_identifier<T: Into<String>>(&mut self, unique_identifier: T) {
|
||||
self.unique_identifier = Some(unique_identifier.into());
|
||||
}
|
||||
|
||||
pub fn set_unique_identifiers(&mut self, unique_identifiers: &[UniqueIdentifier]) {
|
||||
self.unique_identifiers = Some(
|
||||
unique_identifiers
|
||||
.iter()
|
||||
.map(std::string::ToString::to_string)
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn set_key_pair_unique_identifier<T: Into<String>>(
|
||||
&mut self,
|
||||
private_key_unique_identifier: T,
|
||||
public_key_unique_identifier: T,
|
||||
) {
|
||||
self.private_key_unique_identifier = Some(private_key_unique_identifier.into());
|
||||
self.public_key_unique_identifier = Some(public_key_unique_identifier.into());
|
||||
}
|
||||
|
||||
pub fn set_attribute(&mut self, attribute: Attribute) {
|
||||
self.attribute = Some(attribute);
|
||||
}
|
||||
|
||||
pub fn set_attributes(&mut self, attributes: HashMap<String, Value>) {
|
||||
self.attributes = Some(attributes);
|
||||
}
|
||||
|
||||
pub fn set_accesses(&mut self, accesses: Vec<UserAccessResponse>) {
|
||||
self.accesses = Some(accesses);
|
||||
}
|
||||
|
||||
pub fn set_access_rights_obtained(
|
||||
&mut self,
|
||||
access_rights_obtained: Vec<AccessRightsObtainedResponse>,
|
||||
) {
|
||||
self.access_rights_obtained = Some(access_rights_obtained);
|
||||
}
|
||||
|
||||
pub fn set_object_owned(&mut self, object_owned: Vec<ObjectOwnedResponse>) {
|
||||
self.object_owned = Some(object_owned);
|
||||
}
|
||||
|
||||
/// Writes the output to the console.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if there is an issue with writing to the console.
|
||||
#[allow(clippy::print_stdout)]
|
||||
pub fn write(&self) -> CliResult<()> {
|
||||
// Check if the output format should be JSON
|
||||
let output_format = match std::env::var(KMS_CLI_FORMAT) {
|
||||
Ok(output_format_var_env) => OutputFormat::try_from(output_format_var_env.as_ref())?,
|
||||
Err(_) => OutputFormat::Text,
|
||||
};
|
||||
|
||||
match output_format {
|
||||
OutputFormat::Text => {
|
||||
// Print the output in text format
|
||||
if !self.stdout.is_empty() {
|
||||
println!("{}", self.stdout);
|
||||
}
|
||||
|
||||
// Print the unique identifier if present
|
||||
if let Some(id) = &self.unique_identifier {
|
||||
println!("\t Unique identifier: {id}");
|
||||
}
|
||||
|
||||
// Print the list of unique identifiers if present
|
||||
if let Some(ids) = &self.unique_identifiers {
|
||||
for id in ids {
|
||||
println!("{id}");
|
||||
}
|
||||
}
|
||||
|
||||
// Print the public key unique identifier if present
|
||||
if let Some(id) = &self.public_key_unique_identifier {
|
||||
println!("\t Public key unique identifier: {id}");
|
||||
}
|
||||
|
||||
// Print the private key unique identifier if present
|
||||
if let Some(id) = &self.private_key_unique_identifier {
|
||||
println!("\t Private key unique identifier: {id}");
|
||||
}
|
||||
|
||||
// Print the attribute if present: attribute is a single element
|
||||
if let Some(attribute) = &self.attribute {
|
||||
let json = serde_json::to_string_pretty(attribute)?;
|
||||
println!("{json}");
|
||||
}
|
||||
|
||||
// Print the attributes if present: attributes are a hashmap of key-value pairs
|
||||
if let Some(attributes) = &self.attributes {
|
||||
let json = serde_json::to_string_pretty(attributes)?;
|
||||
println!("{json}");
|
||||
}
|
||||
|
||||
// Print the list of accesses if present
|
||||
if let Some(accesses) = &self.accesses {
|
||||
for access in accesses {
|
||||
println!(" - {}: {:?}", access.user_id, access.operations);
|
||||
}
|
||||
}
|
||||
|
||||
// Print the list of access rights obtained if present
|
||||
if let Some(access_rights_obtained) = &self.access_rights_obtained {
|
||||
for access in access_rights_obtained {
|
||||
println!("{access}");
|
||||
}
|
||||
}
|
||||
|
||||
// Print the list of objects owned if present
|
||||
if let Some(object_owned) = &self.object_owned {
|
||||
for obj in object_owned {
|
||||
println!("{obj}");
|
||||
}
|
||||
}
|
||||
|
||||
// Print the list of tags if present
|
||||
if let Some(t) = &self.tags {
|
||||
if !t.is_empty() {
|
||||
println!("\n Tags:");
|
||||
for tag in t {
|
||||
println!(" - {tag}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
OutputFormat::Json => {
|
||||
// Serialize the output as JSON and print it
|
||||
let console_stdout = serde_json::to_string_pretty(&self)?;
|
||||
println!("{console_stdout}");
|
||||
}
|
||||
OutputFormat::Quiet => {
|
||||
// Print nothing
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,314 +0,0 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use cosmian_cover_crypt::{EncryptionHint, MasterPublicKey, QualifiedAttribute};
|
||||
use cosmian_crypto_core::bytes_ser_de::Serializable;
|
||||
use cosmian_kms_client::{
|
||||
cosmian_kmip::KmipResultHelper,
|
||||
export_object,
|
||||
kmip_2_1::{
|
||||
kmip_objects::Object,
|
||||
ttlv::{deserializer::from_ttlv, TTLV},
|
||||
},
|
||||
read_from_json_file, ExportObjectParams, KmsClient,
|
||||
};
|
||||
use cosmian_kms_crypto::{
|
||||
crypto::cover_crypt::{
|
||||
attributes::RekeyEditAction, kmip_requests::build_rekey_keypair_request,
|
||||
},
|
||||
CryptoError,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
actions::{console, labels::KEY_ID, shared::get_key_uid},
|
||||
cli_bail,
|
||||
error::result::CliResult,
|
||||
};
|
||||
|
||||
/// Extract, view, or edit policies of existing keys
|
||||
#[derive(Subcommand)]
|
||||
pub enum AccessStructureCommands {
|
||||
View(ViewAction),
|
||||
AddAttribute(AddQualifiedAttributeAction),
|
||||
RemoveAttribute(RemoveAttributeAction),
|
||||
DisableAttribute(DisableAttributeAction),
|
||||
RenameAttribute(RenameAttributeAction),
|
||||
}
|
||||
|
||||
impl AccessStructureCommands {
|
||||
pub async fn process(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
match self {
|
||||
Self::View(action) => action.run(kms_rest_client).await,
|
||||
Self::AddAttribute(action) => action.run(kms_rest_client).await,
|
||||
Self::RemoveAttribute(action) => action.run(kms_rest_client).await,
|
||||
Self::DisableAttribute(action) => action.run(kms_rest_client).await,
|
||||
Self::RenameAttribute(action) => action.run(kms_rest_client).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// View the access structure of an existing public or private master key.
|
||||
///
|
||||
/// - Use the `--key-id` switch to extract the access structure from a key stored in the KMS.
|
||||
/// - Use the `--key-file` switch to extract the access structure from a Key exported as TTLV.
|
||||
#[derive(Parser)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct ViewAction {
|
||||
/// The public or private master key ID if the key is stored in the KMS
|
||||
#[clap(long = KEY_ID, short = 'i', required_unless_present = "key_file")]
|
||||
key_id: Option<String>,
|
||||
|
||||
/// If `key-id` is not provided, use `--key-file` to provide the file containing the public or private master key in TTLV format.
|
||||
#[clap(long = "key-file", short = 'f')]
|
||||
key_file: Option<PathBuf>,
|
||||
}
|
||||
impl ViewAction {
|
||||
pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
let object: Object = if let Some(id) = &self.key_id {
|
||||
export_object(
|
||||
kms_rest_client,
|
||||
id,
|
||||
ExportObjectParams {
|
||||
unwrap: true,
|
||||
..ExportObjectParams::default()
|
||||
},
|
||||
)
|
||||
.await?
|
||||
.1
|
||||
} else if let Some(key_file) = &self.key_file {
|
||||
let ttlv: TTLV = read_from_json_file(key_file)?;
|
||||
from_ttlv(&ttlv)?
|
||||
} else {
|
||||
cli_bail!("either a key ID or a key TTLV file must be supplied");
|
||||
};
|
||||
let mpk = MasterPublicKey::deserialize(&object.key_block()?.key_bytes()?).map_err(|e| {
|
||||
CryptoError::Kmip(format!("Failed deserializing the CoverCrypt MPK: {e}"))
|
||||
})?;
|
||||
|
||||
let stdout: String = format!("{:?}", mpk.access_structure);
|
||||
console::Stdout::new(&stdout).write()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Add an attribute to the access structure of an existing private master key.
|
||||
#[derive(Parser)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct AddQualifiedAttributeAction {
|
||||
/// The name of the attribute to create.
|
||||
/// Example: `department::rnd`
|
||||
#[clap(required = true)]
|
||||
attribute: String,
|
||||
|
||||
/// Hybridize this qualified attribute.
|
||||
#[clap(required = false, long, default_value = "false")]
|
||||
hybridized: bool,
|
||||
|
||||
/// The master secret key unique identifier stored in the KMS.
|
||||
/// If not specified, tags should be specified
|
||||
#[clap(long = KEY_ID, short = 'k', group = "key-tags")]
|
||||
secret_key_id: Option<String>,
|
||||
|
||||
/// Tag to use to retrieve the key when no key id is specified.
|
||||
/// To specify multiple tags, use the option multiple times.
|
||||
#[clap(long = "tag", short = 't', value_name = "TAG", group = "key-tags")]
|
||||
tags: Option<Vec<String>>,
|
||||
}
|
||||
impl AddQualifiedAttributeAction {
|
||||
pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
let id = get_key_uid(self.secret_key_id.as_ref(), self.tags.as_ref(), KEY_ID)?;
|
||||
|
||||
let rekey_query = build_rekey_keypair_request(
|
||||
&id,
|
||||
&RekeyEditAction::AddAttribute(vec![(
|
||||
QualifiedAttribute::try_from(self.attribute.as_str())?,
|
||||
EncryptionHint::new(self.hybridized),
|
||||
None,
|
||||
)]),
|
||||
)?;
|
||||
|
||||
// Query the KMS with your kmip data
|
||||
let rekey_response = kms_rest_client
|
||||
.rekey_keypair(rekey_query)
|
||||
.await
|
||||
.with_context(|| "failed adding an attribute to the master keys")?;
|
||||
|
||||
let stdout = format!(
|
||||
"New attribute {} was successfully added to the master secret key {} and master \
|
||||
public key {}.",
|
||||
&self.attribute,
|
||||
&rekey_response.private_key_unique_identifier,
|
||||
&rekey_response.public_key_unique_identifier,
|
||||
);
|
||||
console::Stdout::new(&stdout).write()
|
||||
}
|
||||
}
|
||||
|
||||
/// Rename an attribute in the access structure of an existing private master key.
|
||||
#[derive(Parser)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct RenameAttributeAction {
|
||||
/// The name of the attribute to rename.
|
||||
/// Example: `department::mkg`
|
||||
#[clap(required = true)]
|
||||
attribute: String,
|
||||
|
||||
/// The new name for the attribute.
|
||||
/// Example: `marketing`
|
||||
#[clap(required = true)]
|
||||
new_name: String,
|
||||
|
||||
/// The master secret key unique identifier stored in the KMS.
|
||||
/// If not specified, tags should be specified
|
||||
#[clap(long = KEY_ID, short = 'k', group = "key-tags")]
|
||||
master_secret_key_id: Option<String>,
|
||||
|
||||
/// Tag to use to retrieve the key when no key id is specified.
|
||||
/// To specify multiple tags, use the option multiple times.
|
||||
#[clap(long = "tag", short = 't', value_name = "TAG", group = "key-tags")]
|
||||
tags: Option<Vec<String>>,
|
||||
}
|
||||
impl RenameAttributeAction {
|
||||
pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
let id = get_key_uid(
|
||||
self.master_secret_key_id.as_ref(),
|
||||
self.tags.as_ref(),
|
||||
KEY_ID,
|
||||
)?;
|
||||
|
||||
// Create the kmip query
|
||||
let rekey_query = build_rekey_keypair_request(
|
||||
&id,
|
||||
&RekeyEditAction::RenameAttribute(vec![(
|
||||
QualifiedAttribute::try_from(self.attribute.as_str())?,
|
||||
self.new_name.clone(),
|
||||
)]),
|
||||
)?;
|
||||
|
||||
// Query the KMS with your kmip data
|
||||
kms_rest_client
|
||||
.rekey_keypair(rekey_query)
|
||||
.await
|
||||
.with_context(|| "failed renaming an attribute in the master keys' access structure")?;
|
||||
|
||||
let stdout = format!(
|
||||
"Attribute {} was successfully renamed to {}.",
|
||||
&self.attribute, &self.new_name
|
||||
);
|
||||
console::Stdout::new(&stdout).write()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Disable an attribute from the access structure of an existing private master key.
|
||||
/// Prevents the creation of new ciphertexts for this attribute while keeping the ability to decrypt existing ones.
|
||||
#[derive(Parser)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct DisableAttributeAction {
|
||||
/// The name of the attribute to disable.
|
||||
/// Example: `department::marketing`
|
||||
#[clap(required = true)]
|
||||
attribute: String,
|
||||
|
||||
/// The master secret key unique identifier stored in the KMS.
|
||||
/// If not specified, tags should be specified
|
||||
#[clap(long = KEY_ID, short = 'k', group = "key-tags")]
|
||||
master_secret_key_id: Option<String>,
|
||||
|
||||
/// Tag to use to retrieve the key when no key id is specified.
|
||||
/// To specify multiple tags, use the option multiple times.
|
||||
#[clap(long = "tag", short = 't', value_name = "TAG", group = "key-tags")]
|
||||
tags: Option<Vec<String>>,
|
||||
}
|
||||
impl DisableAttributeAction {
|
||||
pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
let id = get_key_uid(
|
||||
self.master_secret_key_id.as_ref(),
|
||||
self.tags.as_ref(),
|
||||
KEY_ID,
|
||||
)?;
|
||||
|
||||
// Create the kmip query
|
||||
let rekey_query = build_rekey_keypair_request(
|
||||
&id,
|
||||
&RekeyEditAction::DisableAttribute(vec![QualifiedAttribute::try_from(
|
||||
self.attribute.as_str(),
|
||||
)?]),
|
||||
)?;
|
||||
|
||||
// Query the KMS with your kmip data
|
||||
let rekey_response = kms_rest_client
|
||||
.rekey_keypair(rekey_query)
|
||||
.await
|
||||
.with_context(|| "failed disabling an attribute from the master keys")?;
|
||||
|
||||
let stdout = format!(
|
||||
"Attribute {} was successfully disabled from the master public key {}.",
|
||||
&self.attribute, &rekey_response.public_key_unique_identifier,
|
||||
);
|
||||
console::Stdout::new(&stdout).write()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove an attribute from the access structure of an existing private master key.
|
||||
/// Permanently removes the ability to use this attribute in both encryptions and decryptions.
|
||||
///
|
||||
/// Note that messages whose encryption access structure does not contain any other attributes
|
||||
/// belonging to the dimension of the deleted attribute will be lost.
|
||||
#[derive(Parser)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct RemoveAttributeAction {
|
||||
/// The name of the attribute to remove.
|
||||
/// Example: `department::marketing`
|
||||
/// Note: prevents ciphertexts only targeting this qualified attribute to be decrypted.
|
||||
#[clap(required = true)]
|
||||
attribute: String,
|
||||
|
||||
/// The master secret key unique identifier stored in the KMS.
|
||||
/// If not specified, tags should be specified
|
||||
#[clap(long = KEY_ID, short = 'k', group = "key-tags")]
|
||||
master_secret_key_id: Option<String>,
|
||||
|
||||
/// Tag to use to retrieve the key when no key id is specified.
|
||||
/// To specify multiple tags, use the option multiple times.
|
||||
#[clap(long = "tag", short = 't', value_name = "TAG", group = "key-tags")]
|
||||
tags: Option<Vec<String>>,
|
||||
}
|
||||
impl RemoveAttributeAction {
|
||||
pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
let id = get_key_uid(
|
||||
self.master_secret_key_id.as_ref(),
|
||||
self.tags.as_ref(),
|
||||
KEY_ID,
|
||||
)?;
|
||||
|
||||
// Create the kmip query
|
||||
let rekey_query = build_rekey_keypair_request(
|
||||
&id,
|
||||
&RekeyEditAction::DeleteAttribute(vec![QualifiedAttribute::try_from(
|
||||
self.attribute.as_str(),
|
||||
)?]),
|
||||
)?;
|
||||
|
||||
// Query the KMS with your kmip data
|
||||
let rekey_response = kms_rest_client
|
||||
.rekey_keypair(rekey_query)
|
||||
.await
|
||||
.with_context(|| "failed removing an attribute from the master keys")?;
|
||||
|
||||
let stdout = format!(
|
||||
"Attribute {} was successfully removed from the master secret key {} and master \
|
||||
public key {}.",
|
||||
&self.attribute,
|
||||
&rekey_response.private_key_unique_identifier,
|
||||
&rekey_response.public_key_unique_identifier,
|
||||
);
|
||||
console::Stdout::new(&stdout).write()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use clap::Parser;
|
||||
use cosmian_kms_client::{
|
||||
cosmian_kmip::kmip_2_1::kmip_types::CryptographicAlgorithm,
|
||||
kmip_2_1::{kmip_types::CryptographicParameters, requests::decrypt_request},
|
||||
read_bytes_from_file, read_bytes_from_files_to_bulk, write_bulk_decrypted_data,
|
||||
write_single_decrypted_data, KmsClient,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
actions::{labels::KEY_ID, shared::get_key_uid},
|
||||
error::result::{CliResult, CliResultHelper},
|
||||
};
|
||||
|
||||
/// Decrypt a file using Covercrypt
|
||||
///
|
||||
/// Note: this is not a streaming call: the file is entirely loaded in memory before being sent for decryption.
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct DecryptAction {
|
||||
/// The files to decrypt
|
||||
#[clap(required = true, name = "FILE")]
|
||||
input_files: Vec<PathBuf>,
|
||||
|
||||
/// The user key unique identifier
|
||||
/// If not specified, tags should be specified
|
||||
#[clap(long = KEY_ID, short = 'k', group = "key-tags")]
|
||||
key_id: Option<String>,
|
||||
|
||||
/// Tag to use to retrieve the key when no key id is specified.
|
||||
/// To specify multiple tags, use the option multiple times.
|
||||
#[clap(long = "tag", short = 't', value_name = "TAG", group = "key-tags")]
|
||||
tags: Option<Vec<String>>,
|
||||
|
||||
/// The encrypted output file path
|
||||
#[clap(required = false, long, short = 'o')]
|
||||
output_file: Option<PathBuf>,
|
||||
|
||||
/// Optional authentication data that was supplied during encryption.
|
||||
#[clap(required = false, long, short)]
|
||||
authentication_data: Option<String>,
|
||||
}
|
||||
|
||||
impl DecryptAction {
|
||||
pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
// Read the file(s) to decrypt
|
||||
let (cryptographic_algorithm, data) = if self.input_files.len() > 1 {
|
||||
(
|
||||
CryptographicAlgorithm::CoverCryptBulk,
|
||||
read_bytes_from_files_to_bulk(&self.input_files).with_context(|| {
|
||||
"Cannot read bytes from encrypted files to LEB-serialize them"
|
||||
})?,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
CryptographicAlgorithm::CoverCrypt,
|
||||
read_bytes_from_file(&self.input_files[0]).with_context(|| {
|
||||
"Cannot read bytes from encrypted files to LEB-serialize them"
|
||||
})?,
|
||||
)
|
||||
};
|
||||
|
||||
// Recover the unique identifier or set of tags
|
||||
let id = get_key_uid(self.key_id.as_ref(), self.tags.as_ref(), KEY_ID)?;
|
||||
|
||||
// Create the kmip query
|
||||
let decrypt_request = decrypt_request(
|
||||
&id,
|
||||
None,
|
||||
data,
|
||||
None,
|
||||
self.authentication_data
|
||||
.as_deref()
|
||||
.map(|s| s.as_bytes().to_vec()),
|
||||
Some(CryptographicParameters {
|
||||
cryptographic_algorithm: Some(cryptographic_algorithm),
|
||||
..Default::default()
|
||||
}),
|
||||
);
|
||||
|
||||
tracing::debug!("{decrypt_request}");
|
||||
|
||||
// Query the KMS with your kmip data and get the key pair ids
|
||||
let decrypt_response = kms_rest_client
|
||||
.decrypt(decrypt_request)
|
||||
.await
|
||||
.with_context(|| "Can't execute the query on the kms server")?;
|
||||
|
||||
let cleartext = decrypt_response.data.context("The plain data are empty")?;
|
||||
|
||||
// Write the decrypted files
|
||||
if cryptographic_algorithm == CryptographicAlgorithm::CoverCryptBulk {
|
||||
write_bulk_decrypted_data(&cleartext, &self.input_files, self.output_file.as_ref())?;
|
||||
} else {
|
||||
write_single_decrypted_data(
|
||||
&cleartext,
|
||||
&self.input_files[0],
|
||||
self.output_file.as_ref(),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,105 +0,0 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use clap::Parser;
|
||||
use cosmian_kms_client::{
|
||||
cosmian_kmip::kmip_2_1::kmip_types::CryptographicAlgorithm,
|
||||
kmip_2_1::{kmip_types::CryptographicParameters, requests::encrypt_request},
|
||||
read_bytes_from_file, read_bytes_from_files_to_bulk, write_bulk_encrypted_data,
|
||||
write_single_encrypted_data, KmsClient,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
actions::{labels::KEY_ID, shared::get_key_uid},
|
||||
error::result::{CliResult, CliResultHelper},
|
||||
};
|
||||
|
||||
/// Encrypt a file using Covercrypt
|
||||
///
|
||||
/// Note: this is not a streaming call: the file is entirely loaded in memory before being sent for encryption.
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct EncryptAction {
|
||||
/// The files to encrypt
|
||||
#[clap(required = true, name = "FILE")]
|
||||
input_files: Vec<PathBuf>,
|
||||
|
||||
/// The encryption policy to encrypt the file with
|
||||
/// Example: "`department::marketing` && `level::confidential`"
|
||||
#[clap(required = true)]
|
||||
encryption_policy: String,
|
||||
|
||||
/// The public key unique identifier.
|
||||
/// If not specified, tags should be specified
|
||||
#[clap(long = KEY_ID, short = 'k', group = "key-tags")]
|
||||
key_id: Option<String>,
|
||||
|
||||
/// Tag to use to retrieve the key when no key id is specified.
|
||||
/// To specify multiple tags, use the option multiple times.
|
||||
#[clap(long = "tag", short = 't', value_name = "TAG", group = "key-tags")]
|
||||
tags: Option<Vec<String>>,
|
||||
|
||||
/// The encrypted output file path
|
||||
#[clap(required = false, long, short = 'o')]
|
||||
output_file: Option<PathBuf>,
|
||||
|
||||
/// Optional authentication data.
|
||||
/// This data needs to be provided back for decryption.
|
||||
#[clap(required = false, long, short = 'a')]
|
||||
authentication_data: Option<String>,
|
||||
}
|
||||
|
||||
impl EncryptAction {
|
||||
pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
// Read the file(s) to encrypt
|
||||
let (cryptographic_algorithm, mut data) = if self.input_files.len() > 1 {
|
||||
(
|
||||
CryptographicAlgorithm::CoverCryptBulk,
|
||||
read_bytes_from_files_to_bulk(&self.input_files)
|
||||
.with_context(|| "Cannot read bytes from files to LEB-serialize them")?,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
CryptographicAlgorithm::CoverCrypt,
|
||||
read_bytes_from_file(&self.input_files[0])
|
||||
.with_context(|| "Cannot read bytes from files to LEB-serialize them")?,
|
||||
)
|
||||
};
|
||||
|
||||
// Recover the unique identifier or set of tags
|
||||
let id = get_key_uid(self.key_id.as_ref(), self.tags.as_ref(), KEY_ID)?;
|
||||
|
||||
// Create the kmip query
|
||||
let encrypt_request = encrypt_request(
|
||||
&id,
|
||||
Some(self.encryption_policy.to_string()),
|
||||
data,
|
||||
None,
|
||||
self.authentication_data
|
||||
.as_deref()
|
||||
.map(|s| s.as_bytes().to_vec()),
|
||||
Some(CryptographicParameters {
|
||||
cryptographic_algorithm: Some(cryptographic_algorithm),
|
||||
..Default::default()
|
||||
}),
|
||||
)?;
|
||||
|
||||
tracing::debug!("{encrypt_request}");
|
||||
|
||||
// Query the KMS with your kmip data and get the key pair ids
|
||||
let encrypt_response = kms_rest_client
|
||||
.encrypt(encrypt_request)
|
||||
.await
|
||||
.with_context(|| "Can't execute the query on the kms server")?;
|
||||
|
||||
data = encrypt_response
|
||||
.data
|
||||
.context("The encrypted data are empty")?;
|
||||
|
||||
// Write the encrypted data
|
||||
if cryptographic_algorithm == CryptographicAlgorithm::CoverCryptBulk {
|
||||
write_bulk_encrypted_data(&data, &self.input_files, self.output_file.as_ref())?;
|
||||
} else {
|
||||
write_single_encrypted_data(&data, &self.input_files[0], self.output_file.as_ref())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use clap::Parser;
|
||||
use cosmian_kms_client::KmsClient;
|
||||
use cosmian_kms_crypto::crypto::cover_crypt::{
|
||||
access_structure::access_structure_from_json_file,
|
||||
kmip_requests::build_create_covercrypt_master_keypair_request,
|
||||
};
|
||||
use tracing::debug;
|
||||
|
||||
use crate::{
|
||||
actions::console,
|
||||
error::result::{CliResult, CliResultHelper},
|
||||
};
|
||||
|
||||
/// Create a new master keypair for a given access structure and return the key
|
||||
/// IDs.
|
||||
///
|
||||
///
|
||||
/// - The master public key is used to encrypt the files and can be safely shared.
|
||||
/// - The master secret key is used to generate user decryption keys and must be kept confidential.
|
||||
///
|
||||
/// The access structure specifications must be passed as a JSON in a file, for example:
|
||||
/// ```json
|
||||
/// {
|
||||
/// "Security Level::<": [
|
||||
/// "Protected",
|
||||
/// "Confidential",
|
||||
/// "Top Secret::+"
|
||||
/// ],
|
||||
/// "Department": [
|
||||
/// "RnD",
|
||||
/// "HR",
|
||||
/// "MKG",
|
||||
/// "FIN"
|
||||
/// ]
|
||||
/// }
|
||||
/// ```
|
||||
/// This specification creates an access structure with:
|
||||
/// - 2 dimensions: `Security Level` and `Department`
|
||||
/// - `Security Level` as hierarchical dimension, as indicated by the `::<` suffix,
|
||||
/// - `Security Level` has 3 possible values: `Protected`, `Confidential`, and `Top Secret`,
|
||||
/// - `Department` has 4 possible values: `RnD`, `HR`, `MKG`, and `FIN`,
|
||||
/// - all encapsulations targeting `Top Secret` will be hybridized, as indicated by the `::+` suffix on the value,
|
||||
///
|
||||
/// Tags can later be used to retrieve the keys. Tags are optional.
|
||||
#[derive(Parser)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct CreateMasterKeyPairAction {
|
||||
/// The JSON access structure specifications file to use to generate the keys.
|
||||
/// See the inline doc of the `create-master-key-pair` command for details.
|
||||
#[clap(long, short = 's')]
|
||||
specification: PathBuf,
|
||||
|
||||
/// The tag to associate with the master key pair.
|
||||
/// To specify multiple tags, use the option multiple times.
|
||||
#[clap(long = "tag", short = 't', value_name = "TAG")]
|
||||
tags: Vec<String>,
|
||||
|
||||
/// Sensitive: if set, the private key will not be exportable
|
||||
#[clap(long = "sensitive", default_value = "false")]
|
||||
sensitive: bool,
|
||||
}
|
||||
|
||||
impl CreateMasterKeyPairAction {
|
||||
pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
let access_structure = access_structure_from_json_file(&self.specification)?;
|
||||
|
||||
debug!("client: access_structure: {access_structure:?}");
|
||||
|
||||
let res = kms_rest_client
|
||||
.create_key_pair(build_create_covercrypt_master_keypair_request(
|
||||
&access_structure,
|
||||
&self.tags,
|
||||
self.sensitive,
|
||||
)?)
|
||||
.await
|
||||
.with_context(|| "failed creating a Covercrypt Master Key Pair")?;
|
||||
|
||||
let mut stdout = console::Stdout::new("The master keypair has been properly generated.");
|
||||
stdout.set_tags(Some(&self.tags));
|
||||
stdout.set_key_pair_unique_identifier(
|
||||
&res.private_key_unique_identifier,
|
||||
&res.public_key_unique_identifier,
|
||||
);
|
||||
stdout.write()
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
use clap::Parser;
|
||||
use cosmian_cover_crypt::AccessPolicy;
|
||||
use cosmian_kms_client::KmsClient;
|
||||
use cosmian_kms_crypto::crypto::cover_crypt::kmip_requests::build_create_covercrypt_usk_request;
|
||||
|
||||
use crate::{
|
||||
actions::console,
|
||||
error::result::{CliResult, CliResultHelper},
|
||||
};
|
||||
|
||||
/// Create a new user secret key for an access policy, and index it under some
|
||||
/// (optional) tags, that can later be used to retrieve the key.
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct CreateUserKeyAction {
|
||||
/// The master secret key unique identifier
|
||||
#[clap(required = true)]
|
||||
master_secret_key_id: String,
|
||||
|
||||
/// The access policy should be expressed as a boolean expression of
|
||||
/// attributes. For example (provided the corresponding attributes are
|
||||
/// defined in the MSK):
|
||||
///
|
||||
/// `"(Department::HR || Department::MKG) && Security Level::Confidential"`
|
||||
#[clap(required = true)]
|
||||
access_policy: String,
|
||||
|
||||
/// The tag to associate with the user decryption key.
|
||||
/// To specify multiple tags, use the option multiple times.
|
||||
#[clap(long = "tag", short = 't', value_name = "TAG")]
|
||||
tags: Vec<String>,
|
||||
|
||||
/// Sensitive: if set, the key will not be exportable
|
||||
#[clap(long = "sensitive", default_value = "false")]
|
||||
sensitive: bool,
|
||||
}
|
||||
|
||||
impl CreateUserKeyAction {
|
||||
pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
// Validate the access policy: side-effect only.
|
||||
AccessPolicy::parse(&self.access_policy).with_context(|| "bad access policy syntax")?;
|
||||
|
||||
let request = build_create_covercrypt_usk_request(
|
||||
&self.access_policy,
|
||||
&self.master_secret_key_id,
|
||||
&self.tags,
|
||||
self.sensitive,
|
||||
)?;
|
||||
|
||||
let response = kms_rest_client
|
||||
.create(request)
|
||||
.await
|
||||
.with_context(|| "user decryption key creation failed")?;
|
||||
|
||||
let usk_uid = &response.unique_identifier;
|
||||
|
||||
let mut stdout =
|
||||
console::Stdout::new("The user decryption key pair has been properly generated.");
|
||||
stdout.set_tags(Some(&self.tags));
|
||||
stdout.set_unique_identifier(usk_uid.to_owned());
|
||||
stdout.write()
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
use clap::Parser;
|
||||
use cosmian_kms_client::KmsClient;
|
||||
|
||||
use crate::{
|
||||
actions::{
|
||||
labels::KEY_ID,
|
||||
shared::{get_key_uid, utils::destroy},
|
||||
},
|
||||
error::result::CliResult,
|
||||
};
|
||||
|
||||
/// Destroy a Covercrypt master or user decryption key.
|
||||
///
|
||||
/// The key must have been revoked first.
|
||||
///
|
||||
/// When a key is destroyed, it can only be exported by the owner of the key,
|
||||
/// and without its key material
|
||||
///
|
||||
/// Destroying a master public or private key will destroy the whole key pair
|
||||
/// and all the associated decryption keys present in the KMS.
|
||||
///
|
||||
/// When using tags to revoke the key, rather than the key id,
|
||||
/// an error is returned if multiple keys matching the tags are found.
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct DestroyKeyAction {
|
||||
/// The key unique identifier.
|
||||
/// If not specified, tags should be specified
|
||||
#[clap(long = KEY_ID, short = 'k', group = "key-tags")]
|
||||
key_id: Option<String>,
|
||||
|
||||
/// Tag to use to retrieve the key when no key id is specified.
|
||||
/// To specify multiple tags, use the option multiple times.
|
||||
#[clap(long = "tag", short = 't', value_name = "TAG", group = "key-tags")]
|
||||
tags: Option<Vec<String>>,
|
||||
|
||||
/// If the key should be removed from the database
|
||||
/// If not specified, the key will be destroyed
|
||||
/// but its metadata will still be available in the database.
|
||||
/// Please note that the KMIP specification does not support the removal of objects.
|
||||
#[clap(long = "remove", default_value = "false", verbatim_doc_comment)]
|
||||
remove: bool,
|
||||
}
|
||||
|
||||
impl DestroyKeyAction {
|
||||
pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
let id = get_key_uid(self.key_id.as_ref(), self.tags.as_ref(), KEY_ID)?;
|
||||
destroy(kms_rest_client, &id, self.remove).await
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
use clap::Subcommand;
|
||||
use cosmian_kms_client::KmsClient;
|
||||
|
||||
use self::{
|
||||
create_key_pair::CreateMasterKeyPairAction,
|
||||
create_user_key::CreateUserKeyAction,
|
||||
destroy_key::DestroyKeyAction,
|
||||
rekey::{PruneAction, RekeyAction},
|
||||
revoke_key::RevokeKeyAction,
|
||||
};
|
||||
use crate::{
|
||||
actions::shared::{ExportKeyAction, ImportKeyAction, UnwrapKeyAction, WrapKeyAction},
|
||||
error::result::CliResult,
|
||||
};
|
||||
|
||||
mod create_key_pair;
|
||||
mod create_user_key;
|
||||
mod destroy_key;
|
||||
mod rekey;
|
||||
mod revoke_key;
|
||||
|
||||
/// Create, destroy, import, export, and rekey `Covercrypt` master and user keys
|
||||
#[derive(Subcommand)]
|
||||
pub enum KeysCommands {
|
||||
CreateMasterKeyPair(CreateMasterKeyPairAction),
|
||||
CreateUserKey(CreateUserKeyAction),
|
||||
Export(ExportKeyAction),
|
||||
Import(ImportKeyAction),
|
||||
Wrap(WrapKeyAction),
|
||||
Unwrap(UnwrapKeyAction),
|
||||
Revoke(RevokeKeyAction),
|
||||
Destroy(DestroyKeyAction),
|
||||
Rekey(RekeyAction),
|
||||
Prune(PruneAction),
|
||||
}
|
||||
|
||||
impl KeysCommands {
|
||||
pub async fn process(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
match self {
|
||||
Self::CreateMasterKeyPair(action) => action.run(kms_rest_client).await?,
|
||||
Self::CreateUserKey(action) => action.run(kms_rest_client).await?,
|
||||
Self::Export(action) => action.run(kms_rest_client).await?,
|
||||
Self::Import(action) => action.run(kms_rest_client).await?,
|
||||
Self::Wrap(action) => action.run(kms_rest_client).await?,
|
||||
Self::Unwrap(action) => action.run(kms_rest_client).await?,
|
||||
Self::Revoke(action) => action.run(kms_rest_client).await?,
|
||||
Self::Destroy(action) => action.run(kms_rest_client).await?,
|
||||
Self::Rekey(action) => action.run(kms_rest_client).await?,
|
||||
Self::Prune(action) => action.run(kms_rest_client).await?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,127 +0,0 @@
|
|||
use clap::Parser;
|
||||
use cosmian_kms_client::KmsClient;
|
||||
use cosmian_kms_crypto::crypto::cover_crypt::{
|
||||
attributes::RekeyEditAction, kmip_requests::build_rekey_keypair_request,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
actions::{console, labels::KEY_ID, shared::get_key_uid},
|
||||
error::result::{CliResult, CliResultHelper},
|
||||
};
|
||||
|
||||
/// Rekey the given access policy.
|
||||
///
|
||||
/// Active USKs are automatically re-keyed. Revoked or destroyed USKs are not
|
||||
/// re-keyed.
|
||||
///
|
||||
/// USKs that have not been rekeyed will only be able to decrypt data encrypted
|
||||
/// before this operation.
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct RekeyAction {
|
||||
/// The access policy should be expressed as a boolean expression of
|
||||
/// attributes. For example (provided the corresponding attributes are
|
||||
/// defined in the MSK):
|
||||
///
|
||||
/// `"(Department::HR || Department::MKG) && Security Level::Confidential"`
|
||||
#[clap(required = true)]
|
||||
access_policy: String,
|
||||
|
||||
/// The MSK UID stored in the KMS. If not specified, tags should be
|
||||
/// specified.
|
||||
#[clap(long = KEY_ID, short = 'k', group = "key-tags")]
|
||||
msk_uid: Option<String>,
|
||||
|
||||
/// Tag to use to retrieve the MSK when no key id is specified.
|
||||
/// To specify multiple tags, use the option multiple times.
|
||||
#[clap(long = "tag", short = 't', value_name = "TAG", group = "key-tags")]
|
||||
tags: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl RekeyAction {
|
||||
pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
let uid = get_key_uid(self.msk_uid.as_ref(), self.tags.as_ref(), KEY_ID)?;
|
||||
|
||||
let res = kms_rest_client
|
||||
.rekey_keypair(build_rekey_keypair_request(
|
||||
&uid,
|
||||
&RekeyEditAction::RekeyAccessPolicy(self.access_policy.clone()),
|
||||
)?)
|
||||
.await
|
||||
.with_context(|| "failed rekeying the master keys")?;
|
||||
|
||||
let stdout = format!(
|
||||
"The MSK {} and MPK {} were rekeyed for the access policy {:?}",
|
||||
&res.private_key_unique_identifier,
|
||||
&res.public_key_unique_identifier,
|
||||
&self.access_policy
|
||||
);
|
||||
|
||||
let mut stdout = console::Stdout::new(&stdout);
|
||||
stdout.set_key_pair_unique_identifier(
|
||||
res.private_key_unique_identifier,
|
||||
res.public_key_unique_identifier,
|
||||
);
|
||||
stdout.write()
|
||||
}
|
||||
}
|
||||
|
||||
/// Prune all keys linked to an MSK w.r.t an given access policy.
|
||||
///
|
||||
/// Active USKs are automatically pruned. Revoked or destroyed user decryption
|
||||
/// keys are not.
|
||||
///
|
||||
/// Pruned user keys can only open encapsulations generated for this access
|
||||
/// policy since the last rekeying.
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct PruneAction {
|
||||
/// The access policy should be expressed as a boolean expression of
|
||||
/// attributes. For example (provided the corresponding attributes are
|
||||
/// defined in the MSK):
|
||||
///
|
||||
/// `"(Department::HR || Department::MKG) && Security Level::Confidential"`
|
||||
#[clap(required = true)]
|
||||
access_policy: String,
|
||||
|
||||
/// The private master key unique identifier stored in the KMS.
|
||||
/// If not specified, tags should be specified
|
||||
#[clap(long = KEY_ID, short = 'k', group = "key-tags")]
|
||||
msk_uid: Option<String>,
|
||||
|
||||
/// Tag to use to retrieve the key when no key id is specified.
|
||||
/// To specify multiple tags, use the option multiple times.
|
||||
#[clap(long = "tag", short = 't', value_name = "TAG", group = "key-tags")]
|
||||
tags: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl PruneAction {
|
||||
pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
let uid = get_key_uid(self.msk_uid.as_ref(), self.tags.as_ref(), KEY_ID)?;
|
||||
|
||||
let request = build_rekey_keypair_request(
|
||||
&uid,
|
||||
&RekeyEditAction::PruneAccessPolicy(self.access_policy.clone()),
|
||||
)?;
|
||||
|
||||
let res = kms_rest_client
|
||||
.rekey_keypair(request)
|
||||
.await
|
||||
.with_context(|| "failed pruning the master keys")?;
|
||||
|
||||
let stdout = format!(
|
||||
"The MSK {} and MPK {} were pruned for the access policy {:?}",
|
||||
&res.private_key_unique_identifier,
|
||||
&res.public_key_unique_identifier,
|
||||
&self.access_policy
|
||||
);
|
||||
|
||||
let mut stdout = console::Stdout::new(&stdout);
|
||||
stdout.set_tags(self.tags.as_ref());
|
||||
stdout.set_key_pair_unique_identifier(
|
||||
res.private_key_unique_identifier,
|
||||
res.public_key_unique_identifier,
|
||||
);
|
||||
stdout.write()
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
use clap::Parser;
|
||||
use cosmian_kms_client::KmsClient;
|
||||
|
||||
use crate::{
|
||||
actions::{
|
||||
labels::KEY_ID,
|
||||
shared::{get_key_uid, utils::revoke},
|
||||
},
|
||||
error::result::CliResult,
|
||||
};
|
||||
|
||||
/// Revoke a Covercrypt master or user decryption key.
|
||||
///
|
||||
/// Once a key is revoked, it can only be exported by the owner of the key,
|
||||
/// using the --allow-revoked flag on the export function.
|
||||
///
|
||||
/// Revoking a master public or private key will revoke the whole key pair
|
||||
/// and all the associated user decryption keys present in the KMS.
|
||||
///
|
||||
/// Once a user decryption key is revoked, it will no longer be rekeyed
|
||||
/// when attributes are rotated on the master key.
|
||||
///
|
||||
/// When using tags to revoke the key, rather than the key id,
|
||||
/// an error is returned if multiple keys matching the tags are found.
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct RevokeKeyAction {
|
||||
/// The reason for the revocation as a string
|
||||
#[clap(required = true)]
|
||||
revocation_reason: String,
|
||||
|
||||
/// The key unique identifier of the key to revoke.
|
||||
/// If not specified, tags should be specified
|
||||
#[clap(long = KEY_ID, short = 'k', group = "key-tags")]
|
||||
key_id: Option<String>,
|
||||
|
||||
/// Tag to use to retrieve the key when no key id is specified.
|
||||
/// To specify multiple tags, use the option multiple times.
|
||||
#[clap(long = "tag", short = 't', value_name = "TAG", group = "key-tags")]
|
||||
tags: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl RevokeKeyAction {
|
||||
pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
let id = get_key_uid(self.key_id.as_ref(), self.tags.as_ref(), KEY_ID)?;
|
||||
revoke(kms_rest_client, &id, &self.revocation_reason).await
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
use clap::Parser;
|
||||
use cosmian_kms_client::KmsClient;
|
||||
|
||||
use crate::{
|
||||
actions::cover_crypt::{
|
||||
access_structure::AccessStructureCommands, decrypt::DecryptAction, encrypt::EncryptAction,
|
||||
keys::KeysCommands,
|
||||
},
|
||||
error::result::CliResult,
|
||||
};
|
||||
|
||||
pub(crate) mod access_structure;
|
||||
pub(crate) mod decrypt;
|
||||
pub(crate) mod encrypt;
|
||||
pub(crate) mod keys;
|
||||
|
||||
/// Manage Covercrypt keys and policies. Rotate attributes. Encrypt and decrypt data.
|
||||
#[derive(Parser)]
|
||||
pub enum CovercryptCommands {
|
||||
#[command(subcommand)]
|
||||
Keys(KeysCommands),
|
||||
#[command(subcommand)]
|
||||
AccessStructure(AccessStructureCommands),
|
||||
Encrypt(EncryptAction),
|
||||
Decrypt(DecryptAction),
|
||||
}
|
||||
|
||||
impl CovercryptCommands {
|
||||
/// Process the Covercrypt command and execute the corresponding action.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `kms_rest_client` - The KMS client used for communication with the KMS service.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function can return an error if any of the underlying actions encounter an error.
|
||||
///
|
||||
pub async fn process(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
match self {
|
||||
Self::AccessStructure(command) => command.process(kms_rest_client).await?,
|
||||
Self::Keys(command) => command.process(kms_rest_client).await?,
|
||||
Self::Encrypt(action) => action.run(kms_rest_client).await?,
|
||||
Self::Decrypt(action) => action.run(kms_rest_client).await?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
use std::{fs::File, io::Write, path::PathBuf};
|
||||
|
||||
use clap::Parser;
|
||||
use cosmian_kms_client::{kmip_2_1::requests::decrypt_request, read_bytes_from_file, KmsClient};
|
||||
|
||||
use crate::{
|
||||
actions::{console, labels::KEY_ID, shared::get_key_uid},
|
||||
error::result::{CliResult, CliResultHelper},
|
||||
};
|
||||
|
||||
/// Decrypts a file with the given private key using ECIES
|
||||
///
|
||||
/// Note: this is not a streaming call: the file is entirely loaded in memory before being sent for decryption.
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct DecryptAction {
|
||||
/// The file to decrypt
|
||||
#[clap(required = true, name = "FILE")]
|
||||
input_file: PathBuf,
|
||||
|
||||
/// The private key unique identifier
|
||||
/// If not specified, tags should be specified
|
||||
#[clap(long = KEY_ID, short = 'k', group = "key-tags")]
|
||||
key_id: Option<String>,
|
||||
|
||||
/// Tag to use to retrieve the key when no key id is specified.
|
||||
/// To specify multiple tags, use the option multiple times.
|
||||
#[clap(long = "tag", short = 't', value_name = "TAG", group = "key-tags")]
|
||||
tags: Option<Vec<String>>,
|
||||
|
||||
/// The encrypted output file path
|
||||
#[clap(required = false, long, short = 'o')]
|
||||
output_file: Option<PathBuf>,
|
||||
|
||||
/// Optional authentication data that was supplied during encryption.
|
||||
#[clap(required = false, long, short)]
|
||||
authentication_data: Option<String>,
|
||||
}
|
||||
|
||||
impl DecryptAction {
|
||||
pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
// Read the file to decrypt
|
||||
let data = read_bytes_from_file(&self.input_file)
|
||||
.with_context(|| "Cannot read bytes from the file to decrypt")?;
|
||||
|
||||
// Recover the unique identifier or set of tags
|
||||
let id = get_key_uid(self.key_id.as_ref(), self.tags.as_ref(), KEY_ID)?;
|
||||
|
||||
// Create the kmip query
|
||||
let decrypt_request = decrypt_request(
|
||||
&id,
|
||||
None,
|
||||
data,
|
||||
None,
|
||||
self.authentication_data
|
||||
.as_deref()
|
||||
.map(|s| s.as_bytes().to_vec()),
|
||||
None,
|
||||
);
|
||||
|
||||
// Query the KMS with your kmip data and get the key pair ids
|
||||
let decrypt_response = kms_rest_client
|
||||
.decrypt(decrypt_request)
|
||||
.await
|
||||
.with_context(|| "Can't execute the query on the kms server")?;
|
||||
let plaintext = decrypt_response
|
||||
.data
|
||||
.context("Decrypt with elliptic curve: the plaintext is empty")?;
|
||||
|
||||
// Write the decrypted file
|
||||
let output_file = self
|
||||
.output_file
|
||||
.clone()
|
||||
.unwrap_or_else(|| self.input_file.clone().with_extension("plain"));
|
||||
let mut buffer =
|
||||
File::create(&output_file).with_context(|| "Fail to write the plain file")?;
|
||||
buffer
|
||||
.write_all(&plaintext)
|
||||
.with_context(|| "Fail to write the plain file")?;
|
||||
|
||||
let stdout = format!("The decrypted file is available at {output_file:?}");
|
||||
let mut stdout = console::Stdout::new(&stdout);
|
||||
stdout.set_tags(self.tags.as_ref());
|
||||
stdout.write()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
use std::{fs::File, io::Write, path::PathBuf};
|
||||
|
||||
use clap::Parser;
|
||||
use cosmian_kms_client::{kmip_2_1::requests::encrypt_request, read_bytes_from_file, KmsClient};
|
||||
|
||||
use crate::{
|
||||
actions::{console, labels::KEY_ID, shared::get_key_uid},
|
||||
error::result::{CliResult, CliResultHelper},
|
||||
};
|
||||
|
||||
/// Encrypt a file with the given public key using ECIES
|
||||
///
|
||||
/// Note: this is not a streaming call: the file is entirely loaded in memory before being sent for encryption.
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct EncryptAction {
|
||||
/// The file to encrypt
|
||||
#[clap(required = true, name = "FILE")]
|
||||
input_file: PathBuf,
|
||||
|
||||
/// The public key unique identifier.
|
||||
/// If not specified, tags should be specified
|
||||
#[clap(long = KEY_ID, short = 'k', group = "key-tags")]
|
||||
key_id: Option<String>,
|
||||
|
||||
/// Tag to use to retrieve the key when no key id is specified.
|
||||
/// To specify multiple tags, use the option multiple times.
|
||||
#[clap(long = "tag", short = 't', value_name = "TAG", group = "key-tags")]
|
||||
tags: Option<Vec<String>>,
|
||||
|
||||
/// The encrypted output file path
|
||||
#[clap(required = false, long, short = 'o')]
|
||||
output_file: Option<PathBuf>,
|
||||
|
||||
/// Optional authentication data.
|
||||
/// This data needs to be provided back for decryption.
|
||||
#[clap(required = false, long, short = 'a')]
|
||||
authentication_data: Option<String>,
|
||||
}
|
||||
|
||||
impl EncryptAction {
|
||||
pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
// Read the file to encrypt
|
||||
let mut data = read_bytes_from_file(&self.input_file)
|
||||
.with_context(|| "Cannot read bytes from the file to encrypt")?;
|
||||
|
||||
// Recover the unique identifier or set of tags
|
||||
let id = get_key_uid(self.key_id.as_ref(), self.tags.as_ref(), KEY_ID)?;
|
||||
|
||||
// Create the kmip query
|
||||
let encrypt_request = encrypt_request(
|
||||
&id,
|
||||
None,
|
||||
data,
|
||||
None,
|
||||
self.authentication_data
|
||||
.as_deref()
|
||||
.map(|s| s.as_bytes().to_vec()),
|
||||
None,
|
||||
)?;
|
||||
|
||||
// Query the KMS with your kmip data and get the key pair ids
|
||||
let encrypt_response = kms_rest_client
|
||||
.encrypt(encrypt_request)
|
||||
.await
|
||||
.with_context(|| "Can't execute the query on the kms server")?;
|
||||
|
||||
data = encrypt_response
|
||||
.data
|
||||
.context("The encrypted data is empty")?;
|
||||
|
||||
// Write the encrypted file
|
||||
let output_file = self
|
||||
.output_file
|
||||
.clone()
|
||||
.unwrap_or_else(|| self.input_file.with_extension("enc"));
|
||||
let mut buffer =
|
||||
File::create(&output_file).with_context(|| "failed to write the encrypted file")?;
|
||||
buffer
|
||||
.write_all(&data)
|
||||
.with_context(|| "failed to write the encrypted file")?;
|
||||
|
||||
let stdout = format!("The encrypted file is available at {output_file:?}");
|
||||
console::Stdout::new(&stdout).write()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,116 +0,0 @@
|
|||
use clap::{Parser, ValueEnum};
|
||||
use cosmian_kms_client::{
|
||||
cosmian_kmip::kmip_2_1::kmip_types::RecommendedCurve,
|
||||
kmip_2_1::{kmip_types::UniqueIdentifier, requests::create_ec_key_pair_request},
|
||||
KmsClient,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
actions::console,
|
||||
error::result::{CliResult, CliResultHelper},
|
||||
};
|
||||
|
||||
#[derive(ValueEnum, Debug, Clone, Copy)]
|
||||
pub enum Curve {
|
||||
#[cfg(not(feature = "fips"))]
|
||||
NistP192,
|
||||
NistP224,
|
||||
NistP256,
|
||||
NistP384,
|
||||
NistP521,
|
||||
#[cfg(not(feature = "fips"))]
|
||||
X25519,
|
||||
#[cfg(not(feature = "fips"))]
|
||||
Ed25519,
|
||||
#[cfg(not(feature = "fips"))]
|
||||
X448,
|
||||
#[cfg(not(feature = "fips"))]
|
||||
Ed448,
|
||||
}
|
||||
|
||||
impl From<Curve> for RecommendedCurve {
|
||||
fn from(curve: Curve) -> Self {
|
||||
match curve {
|
||||
#[cfg(not(feature = "fips"))]
|
||||
Curve::NistP192 => Self::P192,
|
||||
Curve::NistP224 => Self::P224,
|
||||
Curve::NistP256 => Self::P256,
|
||||
Curve::NistP384 => Self::P384,
|
||||
Curve::NistP521 => Self::P521,
|
||||
#[cfg(not(feature = "fips"))]
|
||||
Curve::X25519 => Self::CURVE25519,
|
||||
#[cfg(not(feature = "fips"))]
|
||||
Curve::Ed25519 => Self::CURVEED25519,
|
||||
#[cfg(not(feature = "fips"))]
|
||||
Curve::X448 => Self::CURVE448,
|
||||
#[cfg(not(feature = "fips"))]
|
||||
Curve::Ed448 => Self::CURVEED448,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an elliptic curve key pair
|
||||
///
|
||||
/// - The public is used to encrypt
|
||||
/// and can be safely shared.
|
||||
/// - The private key is used to decrypt
|
||||
/// and must be kept secret.
|
||||
///
|
||||
/// Run this subcommand with --help to see the list of supported curves.
|
||||
/// Default to NIST P256
|
||||
///
|
||||
/// Tags can later be used to retrieve the keys. Tags are optional.
|
||||
#[derive(Parser)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct CreateKeyPairAction {
|
||||
/// The elliptic curve
|
||||
#[clap(long = "curve", short = 'c', default_value = "nist-p256")]
|
||||
curve: Curve,
|
||||
|
||||
/// The tag to associate with the master key pair.
|
||||
/// To specify multiple tags, use the option multiple times.
|
||||
#[clap(long = "tag", short = 't', value_name = "TAG")]
|
||||
tags: Vec<String>,
|
||||
|
||||
/// The unique id of the private key; a random uuid
|
||||
/// is generated if not specified.
|
||||
#[clap(required = false)]
|
||||
private_key_id: Option<String>,
|
||||
|
||||
/// Sensitive: if set, the key will not be exportable
|
||||
#[clap(long = "sensitive", default_value = "false")]
|
||||
sensitive: bool,
|
||||
}
|
||||
|
||||
impl CreateKeyPairAction {
|
||||
pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
let private_key_id = self
|
||||
.private_key_id
|
||||
.as_ref()
|
||||
.map(|id| UniqueIdentifier::TextString(id.clone()));
|
||||
let create_key_pair_request = create_ec_key_pair_request(
|
||||
private_key_id,
|
||||
&self.tags,
|
||||
self.curve.into(),
|
||||
self.sensitive,
|
||||
)?;
|
||||
// Query the KMS with your kmip data and get the key pair ids
|
||||
let create_key_pair_response = kms_rest_client
|
||||
.create_key_pair(create_key_pair_request)
|
||||
.await
|
||||
.with_context(|| "failed creating a Elliptic Curve key pair")?;
|
||||
|
||||
let private_key_unique_identifier = &create_key_pair_response.private_key_unique_identifier;
|
||||
let public_key_unique_identifier = &create_key_pair_response.public_key_unique_identifier;
|
||||
|
||||
let mut stdout = console::Stdout::new("The EC key pair has been created.");
|
||||
stdout.set_tags(Some(&self.tags));
|
||||
stdout.set_key_pair_unique_identifier(
|
||||
private_key_unique_identifier,
|
||||
public_key_unique_identifier,
|
||||
);
|
||||
stdout.write()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
use clap::Parser;
|
||||
use cosmian_kms_client::KmsClient;
|
||||
|
||||
use crate::{
|
||||
actions::{
|
||||
labels::KEY_ID,
|
||||
shared::{get_key_uid, utils::destroy},
|
||||
},
|
||||
error::result::CliResult,
|
||||
};
|
||||
|
||||
/// Destroy a public or private key.
|
||||
///
|
||||
/// The key must have been revoked first.
|
||||
///
|
||||
/// Keys belonging to external stores, such as HSMs,
|
||||
/// are automatically removed.
|
||||
///
|
||||
/// When a key is destroyed but not removed,
|
||||
/// it can only be exported by the owner of the key,
|
||||
/// and without its key material
|
||||
///
|
||||
/// Destroying a public or private key will destroy the whole key pair
|
||||
/// when the two keys are stored in the KMS.
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct DestroyKeyAction {
|
||||
/// The key unique identifier of the key to destroy
|
||||
/// If not specified, tags should be specified
|
||||
#[clap(long = KEY_ID, short = 'k', group = "key-tags")]
|
||||
key_id: Option<String>,
|
||||
|
||||
/// Tag to use to retrieve the key when no key id is specified.
|
||||
/// To specify multiple tags, use the option multiple times.
|
||||
#[clap(long = "tag", short = 't', value_name = "TAG", group = "key-tags")]
|
||||
tags: Option<Vec<String>>,
|
||||
|
||||
/// If the key should be removed from the database
|
||||
/// If not specified, the key will be destroyed
|
||||
/// but its metadata will still be available in the database.
|
||||
/// Please note that the KMIP specification does not support the removal of objects.
|
||||
#[clap(long = "remove", default_value = "false", verbatim_doc_comment)]
|
||||
remove: bool,
|
||||
}
|
||||
|
||||
impl DestroyKeyAction {
|
||||
pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
let id = get_key_uid(self.key_id.as_ref(), self.tags.as_ref(), KEY_ID)?;
|
||||
|
||||
destroy(kms_rest_client, &id, self.remove).await
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
use clap::Subcommand;
|
||||
use cosmian_kms_client::KmsClient;
|
||||
|
||||
use self::{
|
||||
create_key_pair::CreateKeyPairAction, destroy_key::DestroyKeyAction,
|
||||
revoke_key::RevokeKeyAction,
|
||||
};
|
||||
use crate::{
|
||||
actions::shared::{ExportKeyAction, ImportKeyAction, UnwrapKeyAction, WrapKeyAction},
|
||||
error::result::CliResult,
|
||||
};
|
||||
|
||||
mod create_key_pair;
|
||||
mod destroy_key;
|
||||
mod revoke_key;
|
||||
|
||||
/// Create, destroy, import, and export elliptic curve key pairs
|
||||
#[derive(Subcommand)]
|
||||
pub enum KeysCommands {
|
||||
Create(CreateKeyPairAction),
|
||||
Export(ExportKeyAction),
|
||||
Import(ImportKeyAction),
|
||||
Wrap(WrapKeyAction),
|
||||
Unwrap(UnwrapKeyAction),
|
||||
Revoke(RevokeKeyAction),
|
||||
Destroy(DestroyKeyAction),
|
||||
}
|
||||
|
||||
impl KeysCommands {
|
||||
pub async fn process(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
match self {
|
||||
Self::Create(action) => action.run(kms_rest_client).await?,
|
||||
Self::Export(action) => action.run(kms_rest_client).await?,
|
||||
Self::Import(action) => action.run(kms_rest_client).await?,
|
||||
Self::Wrap(action) => action.run(kms_rest_client).await?,
|
||||
Self::Unwrap(action) => action.run(kms_rest_client).await?,
|
||||
Self::Revoke(action) => action.run(kms_rest_client).await?,
|
||||
Self::Destroy(action) => action.run(kms_rest_client).await?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
use clap::Parser;
|
||||
use cosmian_kms_client::KmsClient;
|
||||
|
||||
use crate::{
|
||||
actions::{
|
||||
labels::KEY_ID,
|
||||
shared::{get_key_uid, utils::revoke},
|
||||
},
|
||||
error::result::CliResult,
|
||||
};
|
||||
|
||||
/// Revoke a public or private key.
|
||||
///
|
||||
/// Once a key is revoked, it can only be exported by the owner of the key,
|
||||
/// using the --allow-revoked flag on the export function.
|
||||
///
|
||||
/// Revoking a public or private key will revoke the whole key pair
|
||||
/// (the two keys need to be stored in the KMS).
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct RevokeKeyAction {
|
||||
/// The reason for the revocation as a string
|
||||
#[clap(required = true)]
|
||||
revocation_reason: String,
|
||||
|
||||
/// The key unique identifier of the key to revoke.
|
||||
/// If not specified, tags should be specified
|
||||
#[clap(long = KEY_ID, short = 'k', group = "key-tags")]
|
||||
key_id: Option<String>,
|
||||
|
||||
/// Tag to use to retrieve the key when no key id is specified.
|
||||
/// To specify multiple tags, use the option multiple times.
|
||||
#[clap(long = "tag", short = 't', value_name = "TAG", group = "key-tags")]
|
||||
tags: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl RevokeKeyAction {
|
||||
pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
let id = get_key_uid(self.key_id.as_ref(), self.tags.as_ref(), KEY_ID)?;
|
||||
revoke(kms_rest_client, &id, &self.revocation_reason).await
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
use clap::Parser;
|
||||
use cosmian_kms_client::KmsClient;
|
||||
|
||||
use self::keys::KeysCommands;
|
||||
#[cfg(not(feature = "fips"))]
|
||||
use self::{decrypt::DecryptAction, encrypt::EncryptAction};
|
||||
use crate::error::result::CliResult;
|
||||
|
||||
#[cfg(not(feature = "fips"))]
|
||||
mod decrypt;
|
||||
#[cfg(not(feature = "fips"))]
|
||||
mod encrypt;
|
||||
mod keys;
|
||||
|
||||
/// Manage elliptic curve keys. Encrypt and decrypt data using ECIES.
|
||||
#[derive(Parser)]
|
||||
pub enum EllipticCurveCommands {
|
||||
#[command(subcommand)]
|
||||
Keys(KeysCommands),
|
||||
#[cfg(not(feature = "fips"))]
|
||||
Encrypt(EncryptAction),
|
||||
#[cfg(not(feature = "fips"))]
|
||||
Decrypt(DecryptAction),
|
||||
}
|
||||
|
||||
impl EllipticCurveCommands {
|
||||
/// Runs the `EllipticCurveCommands` main commands.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `kms_rest_client` - A reference to the KMS client used to communicate with the KMS server.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the query execution on the KMS server fails.
|
||||
///
|
||||
pub async fn process(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
match self {
|
||||
Self::Keys(command) => command.process(kms_rest_client).await?,
|
||||
#[cfg(not(feature = "fips"))]
|
||||
Self::Encrypt(action) => action.run(kms_rest_client).await?,
|
||||
#[cfg(not(feature = "fips"))]
|
||||
Self::Decrypt(action) => action.run(kms_rest_client).await?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,133 +0,0 @@
|
|||
use cosmian_kms_client::{GmailApiConf, KmsClientConfig};
|
||||
use reqwest::{Client, Response};
|
||||
use serde::Deserialize;
|
||||
|
||||
use super::{token::retrieve_token, GoogleApiError};
|
||||
use crate::{
|
||||
actions::console,
|
||||
error::{result::CliResult, CliError},
|
||||
};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub(crate) struct RequestError {
|
||||
pub error: ErrorContent,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub(crate) struct ErrorContent {
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct GmailClientBuilder {
|
||||
service_account: GmailApiConf,
|
||||
user_id: String,
|
||||
}
|
||||
|
||||
impl GmailClientBuilder {
|
||||
pub(crate) const fn new(service_account: GmailApiConf, user_id: String) -> Self {
|
||||
Self {
|
||||
service_account,
|
||||
user_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn build(self) -> CliResult<GmailClient> {
|
||||
let token = retrieve_token(&self.service_account, &self.user_id).await?;
|
||||
|
||||
Ok(GmailClient {
|
||||
client: Client::new(),
|
||||
token,
|
||||
base_url: [
|
||||
"https://gmail.googleapis.com/gmail/v1/users/".to_owned(),
|
||||
self.user_id,
|
||||
]
|
||||
.concat(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct GmailClient {
|
||||
client: Client,
|
||||
token: String,
|
||||
base_url: String,
|
||||
}
|
||||
|
||||
impl GmailClient {
|
||||
pub(crate) async fn new(config: &KmsClientConfig, user_id: &str) -> CliResult<Self> {
|
||||
let gmail_api_conf = config
|
||||
.gmail_api_conf
|
||||
.clone()
|
||||
.ok_or_else(|| CliError::Default(format!("No gmail_api_conf object in {config:?}",)))?;
|
||||
|
||||
GmailClientBuilder::new(gmail_api_conf, user_id.to_string())
|
||||
.build()
|
||||
.await
|
||||
}
|
||||
|
||||
#[allow(clippy::print_stdout)]
|
||||
pub(crate) async fn handle_response(response: Response) -> CliResult<()> {
|
||||
if response.status().is_success() {
|
||||
let stdout = response.text().await.map_err(GoogleApiError::Reqwest)?;
|
||||
console::Stdout::new(&stdout).write()?;
|
||||
Ok(())
|
||||
} else {
|
||||
let json_body = response
|
||||
.json::<RequestError>()
|
||||
.await
|
||||
.map_err(GoogleApiError::Reqwest)?;
|
||||
Err(CliError::GmailApiError(json_body.error.message))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn get(&self, endpoint: &str) -> Result<Response, GoogleApiError> {
|
||||
self.client
|
||||
.get([&self.base_url, endpoint].concat())
|
||||
.bearer_auth(&self.token)
|
||||
.send()
|
||||
.await
|
||||
.map_err(GoogleApiError::from)
|
||||
}
|
||||
|
||||
pub(crate) async fn post(
|
||||
&self,
|
||||
endpoint: &str,
|
||||
content: String,
|
||||
) -> Result<Response, GoogleApiError> {
|
||||
self.client
|
||||
.post([&self.base_url, endpoint].concat())
|
||||
.header(reqwest::header::CONTENT_TYPE, "application/json")
|
||||
.header(reqwest::header::CONTENT_LENGTH, content.len())
|
||||
.body(content)
|
||||
.bearer_auth(&self.token)
|
||||
.send()
|
||||
.await
|
||||
.map_err(GoogleApiError::from)
|
||||
}
|
||||
|
||||
pub(crate) async fn patch(
|
||||
&self,
|
||||
endpoint: &str,
|
||||
content: String,
|
||||
) -> Result<Response, GoogleApiError> {
|
||||
self.client
|
||||
.patch([&self.base_url, endpoint].concat())
|
||||
.header(reqwest::header::CONTENT_TYPE, "application/json")
|
||||
.header(reqwest::header::CONTENT_LENGTH, content.len())
|
||||
.body(content)
|
||||
.bearer_auth(&self.token)
|
||||
.send()
|
||||
.await
|
||||
.map_err(GoogleApiError::from)
|
||||
}
|
||||
|
||||
pub(crate) async fn delete(&self, endpoint: &str) -> Result<Response, GoogleApiError> {
|
||||
self.client
|
||||
.delete([&self.base_url, endpoint].concat())
|
||||
.bearer_auth(&self.token)
|
||||
.send()
|
||||
.await
|
||||
.map_err(GoogleApiError::from)
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
use thiserror::Error;
|
||||
|
||||
/// Errors that may occur during any of the operations in this lib.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum GoogleApiError {
|
||||
/// A jwt error occurred.
|
||||
#[error("JWT error `{0}`")]
|
||||
Jwt(jwt_simple::Error),
|
||||
/// Got an error whilst processing a request.
|
||||
#[error("Reqwest error `{0}`")]
|
||||
Reqwest(reqwest::Error),
|
||||
// A serialization error occurred
|
||||
#[error("Serialization error `{0}`")]
|
||||
Serde(serde_json::Error),
|
||||
}
|
||||
|
||||
impl From<jwt_simple::Error> for GoogleApiError {
|
||||
fn from(e: jwt_simple::Error) -> Self {
|
||||
Self::Jwt(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<reqwest::Error> for GoogleApiError {
|
||||
fn from(e: reqwest::Error) -> Self {
|
||||
Self::Reqwest(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for GoogleApiError {
|
||||
fn from(e: serde_json::Error) -> Self {
|
||||
Self::Serde(e)
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
mod client_builder;
|
||||
mod error;
|
||||
mod token;
|
||||
pub(crate) use client_builder::GmailClient;
|
||||
pub(crate) use error::GoogleApiError;
|
|
@ -1,79 +0,0 @@
|
|||
use cosmian_kms_client::GmailApiConf;
|
||||
use jwt_simple::{
|
||||
algorithms::RSAKeyPairLike,
|
||||
prelude::{Claims, Duration, RS256KeyPair},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::GoogleApiError;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub(crate) struct GoogleAuthResponse {
|
||||
pub access_token: String,
|
||||
pub expires_in: u32,
|
||||
pub token_type: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub(crate) struct GoogleAuthRequest {
|
||||
grant_type: String,
|
||||
assertion: String,
|
||||
}
|
||||
|
||||
const GRANT_TYPE_SERVICE_ACCOUNT: &str = "urn:ietf:params:oauth:grant-type:jwt-bearer";
|
||||
impl GoogleAuthRequest {
|
||||
pub(crate) fn new(assertion: String) -> Self {
|
||||
Self {
|
||||
grant_type: GRANT_TYPE_SERVICE_ACCOUNT.to_string(),
|
||||
assertion,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const GMAIL_SCOPE: &str = "https://www.googleapis.com/auth/gmail.settings.basic";
|
||||
pub(crate) const GOOGLE_AUD_VALUE: &str = "https://oauth2.googleapis.com/token";
|
||||
// Token expiration time in hours
|
||||
const TOKEN_EXPIRATION_TIME: u64 = 1;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct JwtAuth {
|
||||
aud: String,
|
||||
iss: String,
|
||||
scope: String,
|
||||
sub: String,
|
||||
}
|
||||
|
||||
pub(crate) fn create_jwt(
|
||||
service_account: &GmailApiConf,
|
||||
user_email: &str,
|
||||
) -> Result<String, GoogleApiError> {
|
||||
let key_pair = RS256KeyPair::from_pem(&service_account.private_key)?;
|
||||
let jwt_data = JwtAuth {
|
||||
aud: GOOGLE_AUD_VALUE.to_string(),
|
||||
iss: service_account.client_email.clone(),
|
||||
scope: GMAIL_SCOPE.to_string(),
|
||||
sub: user_email.to_string(),
|
||||
};
|
||||
|
||||
let claims = Claims::with_custom_claims(jwt_data, Duration::from_hours(TOKEN_EXPIRATION_TIME));
|
||||
|
||||
Ok(key_pair.sign(claims)?)
|
||||
}
|
||||
|
||||
pub(crate) async fn retrieve_token(
|
||||
service_account: &GmailApiConf,
|
||||
user_email: &str,
|
||||
) -> Result<String, GoogleApiError> {
|
||||
let jwt = create_jwt(service_account, user_email)?;
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let response: GoogleAuthResponse = client
|
||||
.post(&service_account.token_uri)
|
||||
.form(&GoogleAuthRequest::new(jwt))
|
||||
.send()
|
||||
.await?
|
||||
.json()
|
||||
.await?;
|
||||
Ok(response.access_token)
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
use clap::Parser;
|
||||
use cosmian_kms_client::KmsClientConfig;
|
||||
|
||||
use super::IDENTITIES_ENDPOINT;
|
||||
use crate::{actions::google::gmail_client::GmailClient, error::result::CliResult};
|
||||
|
||||
/// Deletes a client-side encryption identity. The authenticated user can no longer use the identity
|
||||
/// to send encrypted messages. You cannot restore the identity after you delete it. Instead, use
|
||||
/// the identities.create method to create another identity with the same configuration.
|
||||
#[derive(Parser)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct DeleteIdentitiesAction {
|
||||
/// The primary email address associated with the client-side encryption identity configuration
|
||||
/// that's retrieved.
|
||||
#[clap(required = true)]
|
||||
user_id: String,
|
||||
}
|
||||
|
||||
impl DeleteIdentitiesAction {
|
||||
pub async fn run(&self, config: &KmsClientConfig) -> CliResult<()> {
|
||||
let gmail_client = GmailClient::new(config, &self.user_id);
|
||||
let endpoint = [IDENTITIES_ENDPOINT, &self.user_id].concat();
|
||||
let response = gmail_client.await?.delete(&endpoint).await?;
|
||||
GmailClient::handle_response(response).await
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
use clap::Parser;
|
||||
use cosmian_kms_client::KmsClientConfig;
|
||||
|
||||
use super::IDENTITIES_ENDPOINT;
|
||||
use crate::{actions::google::gmail_client::GmailClient, error::result::CliResult};
|
||||
|
||||
/// Retrieves a client-side encryption identity configuration.
|
||||
#[derive(Parser)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct GetIdentitiesAction {
|
||||
/// The primary email address associated with the client-side encryption identity configuration
|
||||
/// that's retrieved.
|
||||
#[clap(required = true)]
|
||||
user_id: String,
|
||||
}
|
||||
|
||||
impl GetIdentitiesAction {
|
||||
pub async fn run(&self, config: &KmsClientConfig) -> CliResult<()> {
|
||||
let gmail_client = GmailClient::new(config, &self.user_id);
|
||||
let endpoint = [IDENTITIES_ENDPOINT, &self.user_id].concat();
|
||||
let response = gmail_client.await?.get(&endpoint).await?;
|
||||
GmailClient::handle_response(response).await
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
use clap::Parser;
|
||||
use cosmian_kms_client::KmsClientConfig;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::IDENTITIES_ENDPOINT;
|
||||
use crate::{actions::google::gmail_client::GmailClient, error::result::CliResult};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[allow(non_snake_case)]
|
||||
struct IdentityInfo {
|
||||
primaryKeyPairId: String,
|
||||
emailAddress: String,
|
||||
}
|
||||
|
||||
/// Creates and configures a client-side encryption identity that's authorized to send mail from the
|
||||
/// user account. Google publishes the S/MIME certificate to a shared domain-wide directory so that
|
||||
/// people within a Google Workspace organization can encrypt and send mail to the identity.
|
||||
#[derive(Parser)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct InsertIdentitiesAction {
|
||||
/// The keypair id, associated with a given cert/key. You can get the by listing the keypairs
|
||||
/// associated with the user-id
|
||||
#[clap(required = true)]
|
||||
key_pairs_id: String,
|
||||
|
||||
/// The primary email address associated with the client-side encryption identity configuration
|
||||
/// that's retrieved.
|
||||
#[clap(long = "user-id", short = 'u', required = true)]
|
||||
user_id: String,
|
||||
}
|
||||
|
||||
impl InsertIdentitiesAction {
|
||||
pub async fn run(&self, config: &KmsClientConfig) -> CliResult<()> {
|
||||
let gmail_client = GmailClient::new(config, &self.user_id);
|
||||
|
||||
// Construct identity_info
|
||||
let identity_info = IdentityInfo {
|
||||
primaryKeyPairId: self.key_pairs_id.clone(),
|
||||
emailAddress: self.user_id.clone(),
|
||||
};
|
||||
let response = gmail_client
|
||||
.await?
|
||||
.post(IDENTITIES_ENDPOINT, serde_json::to_string(&identity_info)?)
|
||||
.await?;
|
||||
GmailClient::handle_response(response).await
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
use clap::Parser;
|
||||
use cosmian_kms_client::KmsClientConfig;
|
||||
|
||||
use super::IDENTITIES_ENDPOINT;
|
||||
use crate::{actions::google::gmail_client::GmailClient, error::result::CliResult};
|
||||
|
||||
/// Lists the client-side encrypted identities for an authenticated user.
|
||||
#[derive(Parser)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct ListIdentitiesAction {
|
||||
/// The requester's primary email address.
|
||||
#[clap(required = true)]
|
||||
user_id: String,
|
||||
}
|
||||
|
||||
impl ListIdentitiesAction {
|
||||
pub async fn run(&self, config: &KmsClientConfig) -> CliResult<()> {
|
||||
let gmail_client = GmailClient::new(config, &self.user_id);
|
||||
let response = gmail_client.await?.get(IDENTITIES_ENDPOINT).await?;
|
||||
GmailClient::handle_response(response).await
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
use clap::Subcommand;
|
||||
use cosmian_kms_client::KmsClientConfig;
|
||||
|
||||
use self::{
|
||||
delete_identities::DeleteIdentitiesAction, get_identities::GetIdentitiesAction,
|
||||
insert_identities::InsertIdentitiesAction, list_identities::ListIdentitiesAction,
|
||||
patch_identities::PatchIdentitiesAction,
|
||||
};
|
||||
use crate::error::result::CliResult;
|
||||
|
||||
mod delete_identities;
|
||||
mod get_identities;
|
||||
mod insert_identities;
|
||||
mod list_identities;
|
||||
mod patch_identities;
|
||||
|
||||
pub(crate) const IDENTITIES_ENDPOINT: &str = "/settings/cse/identities/";
|
||||
|
||||
/// Insert, get, list, patch and delete identities from Gmail API.
|
||||
#[derive(Subcommand)]
|
||||
pub enum IdentitiesCommands {
|
||||
Get(GetIdentitiesAction),
|
||||
List(ListIdentitiesAction),
|
||||
Insert(InsertIdentitiesAction),
|
||||
Delete(DeleteIdentitiesAction),
|
||||
Patch(PatchIdentitiesAction),
|
||||
}
|
||||
|
||||
impl IdentitiesCommands {
|
||||
pub async fn process(&self, config: &KmsClientConfig) -> CliResult<()> {
|
||||
match self {
|
||||
Self::Get(action) => action.run(config).await,
|
||||
Self::List(action) => action.run(config).await,
|
||||
Self::Insert(action) => action.run(config).await,
|
||||
Self::Delete(action) => action.run(config).await,
|
||||
Self::Patch(action) => action.run(config).await,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
use clap::Parser;
|
||||
use cosmian_kms_client::KmsClientConfig;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::IDENTITIES_ENDPOINT;
|
||||
use crate::{actions::google::gmail_client::GmailClient, error::result::CliResult};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[allow(non_snake_case)]
|
||||
struct IdentityInfo {
|
||||
primaryKeyPairId: String,
|
||||
emailAddress: String,
|
||||
}
|
||||
|
||||
/// Associates a different key pair with an existing client-side encryption identity. The updated
|
||||
/// key pair must validate against Google's S/MIME certificate profiles.
|
||||
#[derive(Parser)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct PatchIdentitiesAction {
|
||||
/// The key pair id, associated with a given cert/key. You can get the by listing the key pairs
|
||||
/// associated with the user-id
|
||||
#[clap(required = true)]
|
||||
key_pairs_id: String,
|
||||
|
||||
/// The primary email address associated with the client-side encryption identity configuration
|
||||
/// that's retrieved.
|
||||
#[clap(long = "user-id", short = 'u', required = true)]
|
||||
user_id: String,
|
||||
}
|
||||
|
||||
impl PatchIdentitiesAction {
|
||||
pub async fn run(&self, config: &KmsClientConfig) -> CliResult<()> {
|
||||
let gmail_client = GmailClient::new(config, &self.user_id);
|
||||
let endpoint = [IDENTITIES_ENDPOINT, &self.user_id].concat();
|
||||
|
||||
// Construct identity_info
|
||||
let identity_info = IdentityInfo {
|
||||
primaryKeyPairId: self.key_pairs_id.clone(),
|
||||
emailAddress: self.user_id.clone(),
|
||||
};
|
||||
let response = gmail_client
|
||||
.await?
|
||||
.patch(&endpoint, serde_json::to_string(&identity_info)?)
|
||||
.await?;
|
||||
GmailClient::handle_response(response).await
|
||||
}
|
||||
}
|
|
@ -1,259 +0,0 @@
|
|||
use base64::{engine::general_purpose, Engine};
|
||||
use clap::Parser;
|
||||
use cosmian_kms_client::{
|
||||
export_object,
|
||||
kmip_2_1::{
|
||||
extra::{VENDOR_ATTR_X509_EXTENSION, VENDOR_ID_COSMIAN},
|
||||
kmip_objects::{Object, ObjectType},
|
||||
kmip_operations::{Certify, GetAttributes},
|
||||
kmip_types::{
|
||||
Attributes, BlockCipherMode, CertificateAttributes, CryptographicAlgorithm,
|
||||
CryptographicParameters, KeyFormatType, Link, LinkType, LinkedObjectIdentifier,
|
||||
UniqueIdentifier, VendorAttribute,
|
||||
},
|
||||
requests::create_rsa_key_pair_request,
|
||||
},
|
||||
ExportObjectParams, KmsClient,
|
||||
};
|
||||
use cosmian_kms_crypto::crypto::certificates::EXTENSION_CONFIG;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::trace;
|
||||
|
||||
use super::KEY_PAIRS_ENDPOINT;
|
||||
use crate::{actions::google::gmail_client::GmailClient, error::CliError};
|
||||
|
||||
const RSA_4096: usize = 4096;
|
||||
|
||||
/// Creates and uploads a client-side encryption S/MIME public key certificate chain and private key
|
||||
/// metadata for a user.
|
||||
#[derive(Parser)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct CreateKeyPairsAction {
|
||||
/// The requester's primary email address
|
||||
#[clap(required = true)]
|
||||
user_id: String,
|
||||
|
||||
/// CSE key ID to wrap exported user private key
|
||||
#[clap(long, short = 'w', required = true)]
|
||||
cse_key_id: String,
|
||||
|
||||
/// The issuer private key id
|
||||
#[clap(long, short = 'i', required = true)]
|
||||
issuer_private_key_id: String,
|
||||
|
||||
/// When certifying a public key, or generating a keypair,
|
||||
/// the subject name to use.
|
||||
///
|
||||
/// For instance: "CN=John Doe,OU=Org Unit,O=Org Name,L=City,ST=State,C=US"
|
||||
#[clap(long, short = 's', verbatim_doc_comment, required = true)]
|
||||
subject_name: String,
|
||||
|
||||
/// The existing private key id of an existing RSA keypair to use (optional - if no ID is provided, a RSA keypair will be created)
|
||||
#[clap(long, short = 'k')]
|
||||
rsa_private_key_id: Option<String>,
|
||||
|
||||
/// Sensitive: if set, the key will not be exportable
|
||||
#[clap(long = "sensitive", default_value = "false")]
|
||||
sensitive: bool,
|
||||
|
||||
/// Dry run mode. If set, the action will not be executed.
|
||||
#[clap(long, default_value = "false")]
|
||||
dry_run: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub(crate) struct KeyFile {
|
||||
kacls_url: String,
|
||||
wrapped_private_key: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[allow(non_snake_case)]
|
||||
struct KeyPairInfo {
|
||||
pkcs7: String,
|
||||
privateKeyMetadata: Vec<PrivateKeyMetadata>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[allow(non_snake_case)]
|
||||
struct PrivateKeyMetadata {
|
||||
kaclsKeyMetadata: KaclsKeyMetadata,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[allow(non_snake_case)]
|
||||
struct KaclsKeyMetadata {
|
||||
kaclsUri: String,
|
||||
kaclsData: String,
|
||||
}
|
||||
|
||||
impl CreateKeyPairsAction {
|
||||
async fn post_keypair(
|
||||
gmail_client: &GmailClient,
|
||||
certificate_value: Vec<u8>,
|
||||
wrapped_private_key: String,
|
||||
kacls_url: String,
|
||||
) -> Result<(), CliError> {
|
||||
let key_pair_info = KeyPairInfo {
|
||||
pkcs7: pem::encode(&pem::Pem::new(String::from("PKCS7"), certificate_value)),
|
||||
privateKeyMetadata: vec![PrivateKeyMetadata {
|
||||
kaclsKeyMetadata: KaclsKeyMetadata {
|
||||
kaclsUri: kacls_url,
|
||||
kaclsData: wrapped_private_key,
|
||||
},
|
||||
}],
|
||||
};
|
||||
|
||||
let response = gmail_client
|
||||
.post(KEY_PAIRS_ENDPOINT, serde_json::to_string(&key_pair_info)?)
|
||||
.await?;
|
||||
GmailClient::handle_response(response).await
|
||||
}
|
||||
|
||||
#[allow(clippy::print_stdout)]
|
||||
pub async fn run(&self, kms_rest_client: &KmsClient) -> Result<(), CliError> {
|
||||
let gmail_client = GmailClient::new(&kms_rest_client.config, &self.user_id);
|
||||
|
||||
let kacls_url = kms_rest_client.google_cse_status();
|
||||
|
||||
let (private_key_id, public_key_id) = if let Some(id) = &self.rsa_private_key_id {
|
||||
let attributes_response = kms_rest_client
|
||||
.get_attributes(GetAttributes {
|
||||
unique_identifier: Some(UniqueIdentifier::TextString(id.to_string())),
|
||||
attribute_references: None,
|
||||
})
|
||||
.await?;
|
||||
if attributes_response.attributes.object_type == Some(ObjectType::PrivateKey) {
|
||||
// Do we need to add encryption Algorithm to RSA too ?
|
||||
if let Some(linked_public_key_id) = attributes_response
|
||||
.attributes
|
||||
.get_link(LinkType::PublicKeyLink)
|
||||
{
|
||||
(id.to_string(), linked_public_key_id.to_string())
|
||||
} else {
|
||||
return Err(CliError::ServerError(
|
||||
"Invalid private-key-id - no linked public key found".to_string(),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(CliError::ServerError(
|
||||
"Invalid private-key-id - must be of PrivateKey type".to_string(),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
let created_key_pair = kms_rest_client
|
||||
.create_key_pair(create_rsa_key_pair_request(
|
||||
None,
|
||||
Vec::<String>::new(),
|
||||
RSA_4096,
|
||||
self.sensitive,
|
||||
)?)
|
||||
.await?;
|
||||
(
|
||||
created_key_pair.private_key_unique_identifier.to_string(),
|
||||
created_key_pair.public_key_unique_identifier.to_string(),
|
||||
)
|
||||
};
|
||||
|
||||
// Export wrapped private key with google CSE key
|
||||
let (_, wrapped_private_key, _attributes) = export_object(
|
||||
kms_rest_client,
|
||||
&private_key_id,
|
||||
ExportObjectParams {
|
||||
wrapping_key_id: Some(&self.cse_key_id),
|
||||
wrapping_cryptographic_parameters: Some(CryptographicParameters {
|
||||
cryptographic_algorithm: Some(CryptographicAlgorithm::AES),
|
||||
block_cipher_mode: Some(BlockCipherMode::GCM),
|
||||
..CryptographicParameters::default()
|
||||
}),
|
||||
..ExportObjectParams::default()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
let wrapped_key_bytes = wrapped_private_key.key_block()?.key_bytes()?;
|
||||
|
||||
// Sign created public key with the issuer private key
|
||||
let attributes = Attributes {
|
||||
object_type: Some(ObjectType::Certificate),
|
||||
certificate_attributes: Some(Box::new(CertificateAttributes::parse_subject_line(
|
||||
&self.subject_name,
|
||||
)?)),
|
||||
link: Some(vec![Link {
|
||||
link_type: LinkType::PrivateKeyLink,
|
||||
linked_object_identifier: LinkedObjectIdentifier::TextString(
|
||||
self.issuer_private_key_id.clone(),
|
||||
),
|
||||
}]),
|
||||
vendor_attributes: Some(vec![VendorAttribute {
|
||||
vendor_identification: VENDOR_ID_COSMIAN.to_string(),
|
||||
attribute_name: VENDOR_ATTR_X509_EXTENSION.to_string(),
|
||||
attribute_value: EXTENSION_CONFIG.to_vec(),
|
||||
}]),
|
||||
..Attributes::default()
|
||||
};
|
||||
|
||||
let certify_request = Certify {
|
||||
unique_identifier: Some(UniqueIdentifier::TextString(public_key_id)),
|
||||
attributes: Some(attributes),
|
||||
..Certify::default()
|
||||
};
|
||||
|
||||
let certificate_unique_identifier = kms_rest_client
|
||||
.certify(certify_request)
|
||||
.await
|
||||
.map_err(|e| CliError::ServerError(format!("failed creating certificate: {e:?}")))?
|
||||
.unique_identifier;
|
||||
|
||||
// From the created leaf certificate, export the associated PKCS7 containing the whole cert chain
|
||||
let (_, pkcs7_object, _pkcs7_object_export_attributes) = export_object(
|
||||
kms_rest_client,
|
||||
&certificate_unique_identifier.to_string(),
|
||||
ExportObjectParams {
|
||||
key_format_type: Some(KeyFormatType::PKCS7),
|
||||
..ExportObjectParams::default()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Object::Certificate {
|
||||
certificate_value, ..
|
||||
} = &pkcs7_object
|
||||
{
|
||||
trace!(
|
||||
"pkcs7_object: {:?}",
|
||||
general_purpose::STANDARD.encode(certificate_value)
|
||||
);
|
||||
trace!(
|
||||
"wrapped_key_bytes: {:?}",
|
||||
general_purpose::STANDARD.encode(wrapped_key_bytes.clone())
|
||||
);
|
||||
}
|
||||
|
||||
if self.dry_run {
|
||||
println!("Dry run mode - key pair not pushed to Gmail API");
|
||||
} else {
|
||||
let email = &self.user_id;
|
||||
println!("[{email}] - Pushing new keypair to Gmail API");
|
||||
if let Object::Certificate {
|
||||
certificate_value, ..
|
||||
} = pkcs7_object
|
||||
{
|
||||
tracing::info!("Processing {email:?}.");
|
||||
Self::post_keypair(
|
||||
&gmail_client.await?,
|
||||
certificate_value,
|
||||
general_purpose::STANDARD.encode(wrapped_key_bytes.clone()),
|
||||
kacls_url.await?.kacls_url,
|
||||
)
|
||||
.await?;
|
||||
tracing::info!("Key pair inserted for {email:?}.");
|
||||
} else {
|
||||
Err(CliError::ServerError(format!(
|
||||
"Error inserting key pair for {email:?} - exported object is not a Certificate"
|
||||
)))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
use clap::Parser;
|
||||
use cosmian_kms_client::KmsClientConfig;
|
||||
|
||||
use super::KEY_PAIRS_ENDPOINT;
|
||||
use crate::{actions::google::gmail_client::GmailClient, error::result::CliResult};
|
||||
|
||||
/// Turns off a client-side encryption key pair. The authenticated user can no longer use the key
|
||||
/// pair to decrypt incoming CSE message texts or sign outgoing CSE mail. To regain access, use the
|
||||
/// key pairs.enable to turn on the key pair. After 30 days, you can permanently delete the key pair
|
||||
/// by using the key pairs.obliterate method.
|
||||
#[derive(Parser)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct DisableKeyPairsAction {
|
||||
/// The identifier of the key pair to disable
|
||||
#[clap(required = true)]
|
||||
key_pairs_id: String,
|
||||
|
||||
/// The requester's primary email address
|
||||
#[clap(long = "user-id", short = 'u', required = true)]
|
||||
user_id: String,
|
||||
}
|
||||
|
||||
impl DisableKeyPairsAction {
|
||||
pub async fn run(&self, config: &KmsClientConfig) -> CliResult<()> {
|
||||
let endpoint = [KEY_PAIRS_ENDPOINT, &self.key_pairs_id, ":disable"].concat();
|
||||
let gmail_client = GmailClient::new(config, &self.user_id);
|
||||
let response = gmail_client.await?.post(&endpoint, String::new()).await?;
|
||||
GmailClient::handle_response(response).await
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
use clap::Parser;
|
||||
use cosmian_kms_client::KmsClientConfig;
|
||||
|
||||
use super::KEY_PAIRS_ENDPOINT;
|
||||
use crate::{actions::google::gmail_client::GmailClient, error::result::CliResult};
|
||||
|
||||
/// Turns on a client-side encryption key pair that was turned off. The key pair becomes active
|
||||
/// again for any associated client-side encryption identities.
|
||||
#[derive(Parser)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct EnableKeyPairsAction {
|
||||
/// The identifier of the key pair to enable
|
||||
#[clap(required = true)]
|
||||
key_pairs_id: String,
|
||||
|
||||
/// The requester's primary email address
|
||||
#[clap(long, short = 'u', required = true)]
|
||||
user_id: String,
|
||||
}
|
||||
|
||||
impl EnableKeyPairsAction {
|
||||
pub async fn run(&self, config: &KmsClientConfig) -> CliResult<()> {
|
||||
let endpoint = [KEY_PAIRS_ENDPOINT, &self.key_pairs_id, ":enable"].concat();
|
||||
let gmail_client = GmailClient::new(config, &self.user_id);
|
||||
let response = gmail_client.await?.post(&endpoint, String::new()).await?;
|
||||
GmailClient::handle_response(response).await
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
use clap::Parser;
|
||||
use cosmian_kms_client::KmsClientConfig;
|
||||
|
||||
use super::KEY_PAIRS_ENDPOINT;
|
||||
use crate::{actions::google::gmail_client::GmailClient, error::result::CliResult};
|
||||
|
||||
/// Retrieves an existing client-side encryption key pair.
|
||||
#[derive(Parser)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct GetKeyPairsAction {
|
||||
/// The identifier of the key pair to retrieve
|
||||
#[clap(required = true)]
|
||||
key_pairs_id: String,
|
||||
|
||||
/// The requester's primary email address
|
||||
#[clap(long, short = 'u', required = true)]
|
||||
user_id: String,
|
||||
}
|
||||
|
||||
impl GetKeyPairsAction {
|
||||
pub async fn run(&self, config: &KmsClientConfig) -> CliResult<()> {
|
||||
let endpoint = [KEY_PAIRS_ENDPOINT, &self.key_pairs_id].concat();
|
||||
let gmail_client = GmailClient::new(config, &self.user_id);
|
||||
let response = gmail_client.await?.get(&endpoint).await?;
|
||||
GmailClient::handle_response(response).await
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
use clap::Parser;
|
||||
use cosmian_kms_client::KmsClientConfig;
|
||||
|
||||
use super::KEY_PAIRS_ENDPOINT;
|
||||
use crate::{actions::google::gmail_client::GmailClient, error::result::CliResult};
|
||||
|
||||
/// Lists client-side encryption key pairs for a user.
|
||||
#[derive(Parser)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct ListKeyPairsAction {
|
||||
/// The requester's primary email address
|
||||
#[clap(required = true)]
|
||||
user_id: String,
|
||||
}
|
||||
|
||||
impl ListKeyPairsAction {
|
||||
pub async fn run(&self, config: &KmsClientConfig) -> CliResult<()> {
|
||||
let gmail_client = GmailClient::new(config, &self.user_id);
|
||||
let response = gmail_client.await?.get(KEY_PAIRS_ENDPOINT).await?;
|
||||
GmailClient::handle_response(response).await
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
use clap::Subcommand;
|
||||
use cosmian_kms_client::KmsClient;
|
||||
use create::CreateKeyPairsAction;
|
||||
use disable::DisableKeyPairsAction;
|
||||
use enable::EnableKeyPairsAction;
|
||||
use get::GetKeyPairsAction;
|
||||
use list::ListKeyPairsAction;
|
||||
use obliterate::ObliterateKeyPairsAction;
|
||||
|
||||
use crate::error::result::CliResult;
|
||||
|
||||
mod create;
|
||||
mod disable;
|
||||
mod enable;
|
||||
mod get;
|
||||
mod list;
|
||||
mod obliterate;
|
||||
|
||||
pub(crate) const KEY_PAIRS_ENDPOINT: &str = "/settings/cse/keypairs/";
|
||||
|
||||
/// Insert, get, list, enable, disabled and obliterate key pairs to Gmail API
|
||||
#[derive(Subcommand)]
|
||||
pub enum KeyPairsCommands {
|
||||
Get(GetKeyPairsAction),
|
||||
List(ListKeyPairsAction),
|
||||
Enable(EnableKeyPairsAction),
|
||||
Disable(DisableKeyPairsAction),
|
||||
Obliterate(ObliterateKeyPairsAction),
|
||||
Create(CreateKeyPairsAction),
|
||||
}
|
||||
|
||||
impl KeyPairsCommands {
|
||||
pub async fn process(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
match self {
|
||||
Self::Get(action) => action.run(&kms_rest_client.config).await,
|
||||
Self::List(action) => action.run(&kms_rest_client.config).await,
|
||||
Self::Enable(action) => action.run(&kms_rest_client.config).await,
|
||||
Self::Disable(action) => action.run(&kms_rest_client.config).await,
|
||||
Self::Obliterate(action) => action.run(&kms_rest_client.config).await,
|
||||
Self::Create(action) => action.run(kms_rest_client).await,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
use clap::Parser;
|
||||
use cosmian_kms_client::KmsClientConfig;
|
||||
|
||||
use super::KEY_PAIRS_ENDPOINT;
|
||||
use crate::{actions::google::gmail_client::GmailClient, error::result::CliResult};
|
||||
|
||||
/// Deletes a client-side encryption key pair permanently and immediately. You can only permanently
|
||||
/// delete key pairs that have been turned off for more than 30 days. To turn off a key pair, use
|
||||
/// the key pairs disable method. Gmail can't restore or decrypt any messages that were encrypted by
|
||||
/// an obliterated key. Authenticated users and Google Workspace administrators lose access to
|
||||
/// reading the encrypted messages.
|
||||
#[derive(Parser)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct ObliterateKeyPairsAction {
|
||||
/// The identifier of the key pair to obliterate
|
||||
#[clap(required = true)]
|
||||
key_pairs_id: String,
|
||||
|
||||
/// The requester's primary email address
|
||||
#[clap(long = "user-id", short = 'u', required = true)]
|
||||
user_id: String,
|
||||
}
|
||||
|
||||
impl ObliterateKeyPairsAction {
|
||||
pub async fn run(&self, config: &KmsClientConfig) -> CliResult<()> {
|
||||
let endpoint: String = [KEY_PAIRS_ENDPOINT, &self.key_pairs_id, ":obliterate"].concat();
|
||||
let gmail_client = GmailClient::new(config, &self.user_id);
|
||||
let response = gmail_client.await?.post(&endpoint, String::new()).await?;
|
||||
GmailClient::handle_response(response).await
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
use clap::Parser;
|
||||
use cosmian_kms_client::KmsClient;
|
||||
|
||||
use self::{identities::IdentitiesCommands, keypairs::KeyPairsCommands};
|
||||
use crate::error::result::CliResult;
|
||||
|
||||
mod gmail_client;
|
||||
mod identities;
|
||||
mod keypairs;
|
||||
pub(crate) use gmail_client::GoogleApiError;
|
||||
|
||||
/// Manage google elements. Handle key pairs and identities from Gmail API.
|
||||
#[derive(Parser)]
|
||||
pub enum GoogleCommands {
|
||||
#[command(subcommand)]
|
||||
KeyPairs(KeyPairsCommands),
|
||||
#[command(subcommand)]
|
||||
Identities(IdentitiesCommands),
|
||||
}
|
||||
|
||||
impl GoogleCommands {
|
||||
/// Process the Google command by delegating the execution to the appropriate subcommand.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `conf_path` - The path to the configuration file.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns a `CliResult` indicating the success or failure of the command.
|
||||
///
|
||||
pub async fn process(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
match self {
|
||||
Self::KeyPairs(command) => command.process(kms_rest_client).await?,
|
||||
Self::Identities(command) => command.process(&kms_rest_client.config).await?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
use clap::Parser;
|
||||
use cosmian_kms_client::{
|
||||
kmip_2_1::{kmip_operations::Hash, kmip_types::CryptographicParameters},
|
||||
KmsClient,
|
||||
};
|
||||
|
||||
use super::mac::CHashingAlgorithm;
|
||||
use crate::{actions::console, error::result::CliResult};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct HashAction {
|
||||
/// Hashing algorithm (case insensitive)
|
||||
///
|
||||
/// Running the locate sub-command with a wrong value will list all the possible values.
|
||||
/// e.g. `ckms hash --algorithm WRONG`
|
||||
#[clap(
|
||||
long = "algorithm",
|
||||
short = 'a',
|
||||
value_name = "ALGORITHM",
|
||||
verbatim_doc_comment
|
||||
)]
|
||||
pub hashing_algorithm: CHashingAlgorithm,
|
||||
|
||||
/// The data to be hashed in hexadecimal format.
|
||||
#[clap(long,
|
||||
short = 'd',
|
||||
value_parser = |s: &str| hex::decode(s).map(|_| s.to_string()).map_err(|e| format!("Invalid hex format: {}", e)))]
|
||||
pub data: Option<String>,
|
||||
|
||||
/// Specifies the existing stream or by-parts cryptographic operation (as returned from a previous call to this operation).
|
||||
#[clap(long,
|
||||
short = 'c',
|
||||
value_parser = |s: &str| hex::decode(s).map(|_| s.to_string()).map_err(|e| format!("Invalid hex format: {}", e)))]
|
||||
pub correlation_value: Option<String>,
|
||||
|
||||
/// Initial operation as Boolean
|
||||
#[clap(long, short = 'i')]
|
||||
pub init_indicator: bool,
|
||||
|
||||
/// Final operation as Boolean
|
||||
#[clap(long, short = 'f')]
|
||||
pub final_indicator: bool,
|
||||
}
|
||||
|
||||
impl HashAction {
|
||||
/// Processes the access action.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if there was a problem running the action.
|
||||
pub async fn process(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
let cryptographic_parameters = CryptographicParameters {
|
||||
hashing_algorithm: Some(self.hashing_algorithm.clone().into()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let data = match self.data.clone() {
|
||||
Some(data) => Some(hex::decode(data)?),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let correlation_value = match self.correlation_value.clone() {
|
||||
Some(correlation_value) => Some(hex::decode(correlation_value)?),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let init_indicator = Some(self.init_indicator);
|
||||
let final_indicator = Some(self.final_indicator);
|
||||
|
||||
let response = kms_rest_client
|
||||
.hash(Hash {
|
||||
cryptographic_parameters,
|
||||
data,
|
||||
correlation_value,
|
||||
init_indicator,
|
||||
final_indicator,
|
||||
})
|
||||
.await?;
|
||||
|
||||
let hex_output = response.data.map_or_else(String::new, hex::encode);
|
||||
|
||||
console::Stdout::new(&format!("Hash output: {hex_output}")).write()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
pub(crate) const KEY_ID: &str = "key-id";
|
||||
pub(crate) const CERTIFICATE_ID: &str = "certificate-id";
|
||||
pub(crate) const CERTIFICATE_RECERTIFY: &str = "certificate-id-to-re-certify";
|
||||
pub(crate) const ATTRIBUTE_ID: &str = "id";
|
||||
pub(crate) const TAG: &str = "tag";
|
|
@ -1,46 +0,0 @@
|
|||
use clap::Parser;
|
||||
use cosmian_kms_client::{reexport::cosmian_http_client::LoginState, KmsClientConfig};
|
||||
|
||||
use crate::error::{result::CliResult, CliError};
|
||||
|
||||
/// Login to the Identity Provider of the KMS server using the `OAuth2` authorization code flow.
|
||||
///
|
||||
/// This command will open a browser window and ask you to login to the Identity Provider.
|
||||
/// Once you have logged in, the access token will be saved in the ckms configuration file.
|
||||
///
|
||||
/// The configuration file must contain an `oauth2_conf` object with the following fields:
|
||||
/// - `client_id`: The client ID of your application. This is provided by the Identity Provider.
|
||||
/// - `client_secret`: The client secret of your application. This is provided by the Identity Provider.
|
||||
/// - `authorize_url`: The authorization URL of the provider. For example, for Google it is `https://accounts.google.com/o/oauth2/v2/auth`.
|
||||
/// - `token_url`: The token URL of the provider. For example, for Google it is `https://oauth2.googleapis.com/token`.
|
||||
/// - `scopes`: The scopes to request. For example, for Google it is `["openid", "email"]`.
|
||||
///
|
||||
/// The callback url must be authorized on the Identity Provider with value `http://localhost:17899/token`.
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct LoginAction;
|
||||
|
||||
impl LoginAction {
|
||||
/// Process the login action
|
||||
///
|
||||
/// # Errors
|
||||
/// - If the `OAuth2` configuration invalid
|
||||
#[allow(clippy::print_stdout)]
|
||||
pub async fn process(&self, config: &mut KmsClientConfig) -> CliResult<()> {
|
||||
let login_config = config.http_config.oauth2_conf.as_ref().ok_or_else(|| {
|
||||
CliError::Default(format!(
|
||||
"The `login` command (only used for JWT authentication) requires an Identity \
|
||||
Provider (IdP) that MUST be configured in the oauth2_conf object in {config:?}",
|
||||
))
|
||||
})?;
|
||||
|
||||
let state = LoginState::try_from(login_config.clone())?;
|
||||
println!("Browse to: {}", state.auth_url);
|
||||
let access_token = state.finalize().await?;
|
||||
|
||||
config.http_config.access_token = Some(access_token);
|
||||
println!("\nSuccess! The access token was saved in the KMS configuration");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
use clap::Parser;
|
||||
use cosmian_kms_client::KmsClientConfig;
|
||||
|
||||
use crate::error::result::CliResult;
|
||||
|
||||
/// Logout from the Identity Provider.
|
||||
///
|
||||
/// The access token will be removed from the ckms configuration file.
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct LogoutAction;
|
||||
|
||||
impl LogoutAction {
|
||||
/// Process the logout action.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `conf_path` - The path to the ckms configuration file.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if there is an issue loading or saving the configuration file.
|
||||
///
|
||||
#[allow(clippy::print_stdout)]
|
||||
pub fn process(&self, config: &mut KmsClientConfig) -> CliResult<()> {
|
||||
config.http_config.access_token = None;
|
||||
println!("\nThe access token was removed from the KMS configuration file",);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,143 +0,0 @@
|
|||
use std::fmt::Display;
|
||||
|
||||
use clap::{Parser, ValueEnum};
|
||||
use cosmian_kms_client::{
|
||||
cosmian_kmip::kmip_2_1::kmip_types::UniqueIdentifier,
|
||||
kmip_2_1::{
|
||||
kmip_operations::Mac,
|
||||
kmip_types::{CryptographicParameters, HashingAlgorithm},
|
||||
},
|
||||
KmsClient,
|
||||
};
|
||||
|
||||
use crate::{actions::console, error::result::CliResult};
|
||||
|
||||
#[derive(ValueEnum, Debug, Clone, PartialEq, Eq)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum CHashingAlgorithm {
|
||||
SHA256,
|
||||
SHA384,
|
||||
SHA512,
|
||||
SHA3_224,
|
||||
SHA3_256,
|
||||
SHA3_384,
|
||||
SHA3_512,
|
||||
}
|
||||
|
||||
impl Display for CHashingAlgorithm {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::SHA256 => write!(f, "sha256"),
|
||||
Self::SHA384 => write!(f, "sha384"),
|
||||
Self::SHA512 => write!(f, "sha512"),
|
||||
Self::SHA3_224 => write!(f, "sha3-224"),
|
||||
Self::SHA3_256 => write!(f, "sha3-256"),
|
||||
Self::SHA3_384 => write!(f, "sha3-384"),
|
||||
Self::SHA3_512 => write!(f, "sha3-512"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CHashingAlgorithm> for HashingAlgorithm {
|
||||
fn from(algo: CHashingAlgorithm) -> Self {
|
||||
match algo {
|
||||
CHashingAlgorithm::SHA256 => Self::SHA256,
|
||||
CHashingAlgorithm::SHA384 => Self::SHA384,
|
||||
CHashingAlgorithm::SHA512 => Self::SHA512,
|
||||
CHashingAlgorithm::SHA3_224 => Self::SHA3224,
|
||||
CHashingAlgorithm::SHA3_256 => Self::SHA3256,
|
||||
CHashingAlgorithm::SHA3_384 => Self::SHA3384,
|
||||
CHashingAlgorithm::SHA3_512 => Self::SHA3512,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct MacAction {
|
||||
/// Locate an object which has a link to this MAC key id.
|
||||
#[clap(long, short = 'k')]
|
||||
pub mac_key_id: String,
|
||||
|
||||
/// Hashing algorithm (case insensitive)
|
||||
///
|
||||
/// Running the locate sub-command with a wrong value will list all the possible values.
|
||||
/// e.g. `ckms mac --algorithm WRONG`
|
||||
#[clap(
|
||||
long = "algorithm",
|
||||
short = 'a',
|
||||
value_name = "ALGORITHM",
|
||||
verbatim_doc_comment
|
||||
)]
|
||||
pub hashing_algorithm: CHashingAlgorithm,
|
||||
|
||||
/// The data to be hashed in hexadecimal format.
|
||||
/// The data to be hashed in hexadecimal format.
|
||||
#[clap(
|
||||
long,
|
||||
short = 'd',
|
||||
value_parser = |s: &str| hex::decode(s).map(|_| s.to_string()).map_err(|e| format!("Invalid hex format: {}", e))
|
||||
)]
|
||||
pub data: Option<String>,
|
||||
|
||||
/// Specifies the existing stream or by-parts cryptographic operation (as returned from a previous call to this operation).
|
||||
/// The correlation value is represented as a hexadecimal string.
|
||||
#[clap(long,
|
||||
short = 'c',
|
||||
value_parser = |s: &str| hex::decode(s).map(|_| s.to_string()).map_err(|e| format!("Invalid hex format: {}", e))
|
||||
)]
|
||||
pub correlation_value: Option<String>,
|
||||
|
||||
/// Initial operation as Boolean
|
||||
#[clap(long, short = 'i')]
|
||||
pub init_indicator: bool,
|
||||
|
||||
/// Final operation as Boolean
|
||||
#[clap(long, short = 'f')]
|
||||
pub final_indicator: bool,
|
||||
}
|
||||
|
||||
impl MacAction {
|
||||
/// Processes the access action.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if there was a problem running the action.
|
||||
pub async fn process(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
let unique_identifier = Some(UniqueIdentifier::TextString(self.mac_key_id.clone()));
|
||||
|
||||
let cryptographic_parameters = CryptographicParameters {
|
||||
hashing_algorithm: Some(self.hashing_algorithm.clone().into()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let data = match self.data.clone() {
|
||||
Some(data) => Some(hex::decode(data)?),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let correlation_value = match self.correlation_value.clone() {
|
||||
Some(correlation_value) => Some(hex::decode(correlation_value)?),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let init_indicator = Some(self.init_indicator);
|
||||
let final_indicator = Some(self.final_indicator);
|
||||
|
||||
let response = kms_rest_client
|
||||
.mac(Mac {
|
||||
unique_identifier: unique_identifier.clone(),
|
||||
cryptographic_parameters,
|
||||
data,
|
||||
correlation_value,
|
||||
init_indicator,
|
||||
final_indicator,
|
||||
})
|
||||
.await?;
|
||||
|
||||
let hex_output = response.data.map_or_else(String::new, hex::encode);
|
||||
|
||||
console::Stdout::new(&format!("Mac output: {hex_output}")).write()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
pub mod access;
|
||||
pub mod attributes;
|
||||
pub mod bench;
|
||||
pub mod certificates;
|
||||
pub mod console;
|
||||
#[cfg(not(feature = "fips"))]
|
||||
pub mod cover_crypt;
|
||||
pub mod elliptic_curves;
|
||||
pub mod google;
|
||||
pub mod hash;
|
||||
pub(crate) mod labels;
|
||||
pub mod login;
|
||||
pub mod logout;
|
||||
pub mod mac;
|
||||
pub mod new_database;
|
||||
pub mod rsa;
|
||||
pub mod shared;
|
||||
pub mod symmetric;
|
||||
pub mod version;
|
|
@ -1,52 +0,0 @@
|
|||
use clap::Parser;
|
||||
use cosmian_kms_client::KmsClient;
|
||||
|
||||
use crate::error::result::{CliResult, CliResultHelper};
|
||||
|
||||
/// Initialize a new user encrypted database and return the secret (`SQLCipher` only).
|
||||
///
|
||||
/// This secret is only displayed once and is not stored anywhere on the server.
|
||||
/// The secret must be set in the `database_secret` property
|
||||
/// of the CLI `kms.toml` configuration file to use the encrypted database.
|
||||
///
|
||||
/// Passing the correct secret "auto-selects" the correct encrypted database:
|
||||
/// multiple encrypted databases can be used concurrently on the same KMS server.
|
||||
///
|
||||
/// Note: this action creates a new database: it will not return the secret
|
||||
/// of the last created database and will not overwrite it.
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct NewDatabaseAction;
|
||||
|
||||
impl NewDatabaseAction {
|
||||
/// Process the `NewDatabaseAction` by querying the KMS to get a new database.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `kms_rest_client` - The KMS client used to communicate with the KMS server.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the query execution on the KMS server fails.
|
||||
///
|
||||
#[allow(clippy::print_stdout)]
|
||||
pub async fn process(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
// Query the KMS to get a new database
|
||||
let token = kms_rest_client
|
||||
.new_database()
|
||||
.await
|
||||
.with_context(|| "Can't execute the query on the kms server")?;
|
||||
|
||||
println!(
|
||||
"A new user encrypted database is configured. Use the following token (by adding it \
|
||||
to the 'database_secret' entry of your KMS_CLI_CONF):\n\n{token}\n\n"
|
||||
);
|
||||
|
||||
println!(
|
||||
"Do not loose it: there is not other copy!\nIt is impossible to recover the database \
|
||||
without the token."
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,150 +0,0 @@
|
|||
use std::{fs::File, io::Write, path::PathBuf};
|
||||
|
||||
use clap::Parser;
|
||||
use cosmian_kms_client::{kmip_2_1::requests::decrypt_request, read_bytes_from_file, KmsClient};
|
||||
|
||||
use crate::{
|
||||
actions::{
|
||||
console,
|
||||
labels::KEY_ID,
|
||||
rsa::{HashFn, RsaEncryptionAlgorithm},
|
||||
shared::get_key_uid,
|
||||
},
|
||||
error::result::{CliResult, CliResultHelper},
|
||||
};
|
||||
|
||||
/// Decrypt a file with the given public key using either
|
||||
/// - `CKM_RSA_PKCS` a.k.a PKCS #1 RSA V1.5 as specified in PKCS#11 v2.40
|
||||
/// - `CKM_RSA_PKCS_OAEP` a.k.a PKCS #1 RSA OAEP as specified in PKCS#11 v2.40
|
||||
/// - `CKM_RSA_AES_KEY_WRAP` as specified in PKCS#11 v2.40
|
||||
///
|
||||
/// `CKM_RSA_PKCS` is deprecated in FIPS 140-3 and is therefore not available in FIPS mode.
|
||||
/// `CKM_RSA_AES_KEY_WRAP` is meant be used to wrap/unwrap keys with RSA keys although,
|
||||
/// since it is using `AES_KEY_WRAP_PAD` (a.k.a RFC 5649), encrypt/decrypt operations of text
|
||||
/// with arbitrary length should be possible as specified in PKCS#11 v2.40 2.14.
|
||||
///
|
||||
/// By default, the hashing function used with `CKM_RSA_PKCS_OAEP` and `CKM_RSA_AES_KEY_WRAP`
|
||||
/// is set to SHA-256 and is ignored with RSA PKCS.
|
||||
/// When using `CKM_RSA_PKCS`:
|
||||
/// - the maximum plaintext length is k-11 where k is the length in octets of the RSA modulus
|
||||
/// - the ciphertext input length is the same as the RSA modulus length.
|
||||
///
|
||||
/// When using `CKM_RSA_PKCS_OAEP`:
|
||||
/// - the maximum plaintext length is k-2-2*hLen where
|
||||
/// - k is the length in octets of the RSA modulus
|
||||
/// - hLen is the length in octets of the hash function output
|
||||
/// - the ciphertext input length is the same as the RSA modulus length.
|
||||
///
|
||||
/// When using `CKM_RSA_AES_KEY_WRAP`:
|
||||
/// - the plaintext length is unlimited
|
||||
///
|
||||
/// Note: this is not a streaming call: the file is entirely loaded in memory before being sent for decryption.
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct DecryptAction {
|
||||
/// The file to decrypt
|
||||
#[clap(required = true, name = "FILE")]
|
||||
input_file: PathBuf,
|
||||
|
||||
/// The private key unique identifier
|
||||
/// If not specified, tags should be specified
|
||||
#[clap(long = KEY_ID, short = 'k', group = "key-tags")]
|
||||
key_id: Option<String>,
|
||||
|
||||
/// Tag to use to retrieve the key when no key id is specified.
|
||||
/// To specify multiple tags, use the option multiple times.
|
||||
#[clap(long = "tag", short = 't', value_name = "TAG", group = "key-tags")]
|
||||
tags: Option<Vec<String>>,
|
||||
|
||||
/// The encryption algorithm
|
||||
#[clap(
|
||||
long = "encryption-algorithm",
|
||||
short = 'e',
|
||||
default_value = "ckm-rsa-pkcs-oaep"
|
||||
)]
|
||||
encryption_algorithm: RsaEncryptionAlgorithm,
|
||||
|
||||
/// The hashing algorithm (for OAEP and AES key wrap)
|
||||
#[clap(long = "hashing-algorithm", short = 's', default_value = "sha256")]
|
||||
hash_fn: HashFn,
|
||||
|
||||
/// The encrypted output file path
|
||||
#[clap(required = false, long, short = 'o')]
|
||||
output_file: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl DecryptAction {
|
||||
/// Runs the decryption process.
|
||||
///
|
||||
/// This function performs the following steps:
|
||||
/// 1. Reads the file to decrypt.
|
||||
/// 2. Recovers the unique identifier or set of tags for the key.
|
||||
/// 3. Creates the KMIP decryption request.
|
||||
/// 4. Queries the KMS with the KMIP data and retrieves the plaintext.
|
||||
/// 5. Writes the decrypted file to the specified output path.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `kms_rest_client` - A reference to the KMS client.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `CliResult<()>` - The result of the decryption process.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if:
|
||||
/// * The file to decrypt cannot be read.
|
||||
/// * Neither `--key-id` nor `--tag` is specified.
|
||||
/// * The KMIP decryption request cannot be created.
|
||||
/// * The KMS query fails.
|
||||
/// * The decrypted file cannot be written.
|
||||
pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
// Read the file to decrypt
|
||||
let data = read_bytes_from_file(&self.input_file)
|
||||
.with_context(|| "Cannot read bytes from the file to decrypt")?;
|
||||
|
||||
// Recover the unique identifier or set of tags
|
||||
let id = get_key_uid(self.key_id.as_ref(), self.tags.as_ref(), KEY_ID)?;
|
||||
|
||||
// Create the kmip query
|
||||
let decrypt_request = decrypt_request(
|
||||
&id,
|
||||
None,
|
||||
data,
|
||||
None,
|
||||
None,
|
||||
Some(
|
||||
self.encryption_algorithm
|
||||
.to_cryptographic_parameters(self.hash_fn),
|
||||
),
|
||||
);
|
||||
|
||||
// Query the KMS with your kmip data and get the key pair ids
|
||||
let decrypt_response = kms_rest_client
|
||||
.decrypt(decrypt_request)
|
||||
.await
|
||||
.with_context(|| "Can't execute the query on the kms server")?;
|
||||
let plaintext = decrypt_response
|
||||
.data
|
||||
.context("Decrypt with RSA: the plaintext is empty")?;
|
||||
|
||||
// Write the decrypted file
|
||||
let output_file = self
|
||||
.output_file
|
||||
.clone()
|
||||
.unwrap_or_else(|| self.input_file.clone().with_extension("plain"));
|
||||
let mut buffer =
|
||||
File::create(&output_file).with_context(|| "Fail to write the plain file")?;
|
||||
buffer
|
||||
.write_all(&plaintext)
|
||||
.with_context(|| "Fail to write the plain file")?;
|
||||
|
||||
let stdout = format!("The decrypted file is available at {output_file:?}");
|
||||
let mut stdout = console::Stdout::new(&stdout);
|
||||
stdout.set_tags(self.tags.as_ref());
|
||||
stdout.write()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,142 +0,0 @@
|
|||
use std::{fs::File, io::Write, path::PathBuf};
|
||||
|
||||
use clap::Parser;
|
||||
use cosmian_kms_client::{kmip_2_1::requests::encrypt_request, read_bytes_from_file, KmsClient};
|
||||
|
||||
use crate::{
|
||||
actions::{
|
||||
console,
|
||||
labels::KEY_ID,
|
||||
rsa::{HashFn, RsaEncryptionAlgorithm},
|
||||
shared::get_key_uid,
|
||||
},
|
||||
error::result::{CliResult, CliResultHelper},
|
||||
};
|
||||
|
||||
/// Encrypt a file with the given public key using either
|
||||
/// - `CKM_RSA_PKCS` a.k.a PKCS #1 RSA V1.5 as specified in PKCS#11 v2.40
|
||||
/// - `CKM_RSA_PKCS_OAEP` a.k.a PKCS #1 RSA OAEP as specified in PKCS#11 v2.40
|
||||
/// - `CKM_RSA_AES_KEY_WRAP` as specified in PKCS#11 v2.40
|
||||
///
|
||||
/// `CKM_RSA_PKCS` is deprecated in FIPS 140-3 and is therefore not available in FIPS mode.
|
||||
/// `CKM_RSA_AES_KEY_WRAP` is meant be used to wrap/unwrap keys with RSA keys although,
|
||||
/// since it is using `AES_KEY_WRAP_PAD` (a.k.a RFC 5649), encrypt/decrypt operations of text
|
||||
/// with arbitrary length should be possible as specified in PKCS#11 v2.40 2.14.
|
||||
///
|
||||
/// When using `CKM_RSA_PKCS`:
|
||||
/// - the maximum plaintext length is k-11 where k is the length in octets of the RSA modulus
|
||||
/// - the output length is the same as the RSA modulus length.
|
||||
///
|
||||
/// When using `CKM_RSA_PKCS_OAEP`:
|
||||
/// - the authentication data is ignored
|
||||
/// - the maximum plaintext length is k-2-2*hLen where
|
||||
/// - k is the length in octets of the RSA modulus
|
||||
/// - hLen is the length in octets of the hash function output
|
||||
/// - the output length is the same as the RSA modulus length.
|
||||
///
|
||||
/// When using `CKM_RSA_AES_KEY_WRAP`:
|
||||
/// - the plaintext length is unlimited
|
||||
///
|
||||
/// Note: this is not a streaming call: the file is entirely loaded in memory before being sent for encryption.
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct EncryptAction {
|
||||
/// The file to encrypt
|
||||
#[clap(required = true, name = "FILE")]
|
||||
input_file: PathBuf,
|
||||
|
||||
/// The public key unique identifier.
|
||||
/// If not specified, tags should be specified
|
||||
#[clap(long = KEY_ID, short = 'k', group = "key-tags")]
|
||||
key_id: Option<String>,
|
||||
|
||||
/// Tag to use to retrieve the key when no key id is specified.
|
||||
/// To specify multiple tags, use the option multiple times.
|
||||
#[clap(long = "tag", short = 't', value_name = "TAG", group = "key-tags")]
|
||||
tags: Option<Vec<String>>,
|
||||
|
||||
/// The encryption algorithm
|
||||
#[clap(
|
||||
long = "encryption-algorithm",
|
||||
short = 'e',
|
||||
default_value = "ckm-rsa-pkcs-oaep"
|
||||
)]
|
||||
encryption_algorithm: RsaEncryptionAlgorithm,
|
||||
|
||||
/// The hashing algorithm
|
||||
#[clap(long = "hashing-algorithm", short = 's', default_value = "sha256")]
|
||||
hash_fn: HashFn,
|
||||
|
||||
/// The encrypted output file path
|
||||
#[clap(required = false, long, short = 'o')]
|
||||
output_file: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl EncryptAction {
|
||||
/// Run the encryption action
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `kms_rest_client` - A reference to the KMS client used to perform encryption.
|
||||
///
|
||||
/// # Results
|
||||
///
|
||||
/// This function returns a `CliResult<()>` indicating the success or failure of the encryption action.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if:
|
||||
/// * The input file cannot be read.
|
||||
/// * The key ID or tags are not specified.
|
||||
/// * The encryption request cannot be built.
|
||||
/// * The KMS server query fails.
|
||||
/// * The encrypted data is empty.
|
||||
/// * The encrypted file cannot be written.
|
||||
pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
// Read the file to encrypt
|
||||
let mut data = read_bytes_from_file(&self.input_file)
|
||||
.with_context(|| "Cannot read bytes from the file to encrypt")?;
|
||||
|
||||
// Recover the unique identifier or set of tags
|
||||
let id = get_key_uid(self.key_id.as_ref(), self.tags.as_ref(), KEY_ID)?;
|
||||
|
||||
// Create the kmip query
|
||||
let encrypt_request = encrypt_request(
|
||||
&id,
|
||||
None,
|
||||
data,
|
||||
None,
|
||||
None,
|
||||
Some(
|
||||
self.encryption_algorithm
|
||||
.to_cryptographic_parameters(self.hash_fn),
|
||||
),
|
||||
)?;
|
||||
|
||||
// Query the KMS with your kmip data and get the key pair ids
|
||||
let encrypt_response = kms_rest_client
|
||||
.encrypt(encrypt_request)
|
||||
.await
|
||||
.with_context(|| "Can't execute the query on the kms server")?;
|
||||
|
||||
data = encrypt_response
|
||||
.data
|
||||
.context("The encrypted data is empty")?;
|
||||
|
||||
// Write the encrypted file
|
||||
let output_file = self
|
||||
.output_file
|
||||
.clone()
|
||||
.unwrap_or_else(|| self.input_file.with_extension("enc"));
|
||||
let mut buffer =
|
||||
File::create(&output_file).with_context(|| "failed to write the encrypted file")?;
|
||||
buffer
|
||||
.write_all(&data)
|
||||
.with_context(|| "failed to write the encrypted file")?;
|
||||
|
||||
let stdout = format!("The encrypted file is available at {output_file:?}");
|
||||
console::Stdout::new(&stdout).write()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
use clap::Parser;
|
||||
use cosmian_kms_client::{
|
||||
kmip_2_1::{kmip_types::UniqueIdentifier, requests::create_rsa_key_pair_request},
|
||||
KmsClient,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
actions::console,
|
||||
error::result::{CliResult, CliResultHelper},
|
||||
};
|
||||
|
||||
/// Create a new RSA key pair
|
||||
///
|
||||
/// - The public is used to encrypt
|
||||
/// and can be safely shared.
|
||||
/// - The private key is used to decrypt
|
||||
/// and must be kept secret.
|
||||
///
|
||||
/// Tags can later be used to retrieve the keys. Tags are optional.
|
||||
#[derive(Parser)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct CreateKeyPairAction {
|
||||
/// The expected size in bits
|
||||
#[clap(
|
||||
long = "size_in_bits",
|
||||
short = 's',
|
||||
value_name = "SIZE_IN_BITS",
|
||||
default_value = "4096"
|
||||
)]
|
||||
pub key_size: usize,
|
||||
|
||||
/// The tag to associate with the master key pair.
|
||||
/// To specify multiple tags, use the option multiple times.
|
||||
#[clap(long = "tag", short = 't', value_name = "TAG")]
|
||||
pub tags: Vec<String>,
|
||||
|
||||
/// The unique id of the private key; a random uuid
|
||||
/// is generated if not specified.
|
||||
#[clap(required = false)]
|
||||
pub private_key_id: Option<String>,
|
||||
|
||||
/// Sensitive: if set, the private key will not be exportable
|
||||
#[clap(long = "sensitive", default_value = "false")]
|
||||
pub sensitive: bool,
|
||||
}
|
||||
|
||||
impl Default for CreateKeyPairAction {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
key_size: 4096,
|
||||
tags: Vec::new(),
|
||||
private_key_id: None,
|
||||
sensitive: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CreateKeyPairAction {
|
||||
/// Run the create key pair action
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `kms_rest_client` - A reference to the KMS client used to perform the key pair creation.
|
||||
///
|
||||
/// # Results
|
||||
///
|
||||
/// This function returns a `CliResult<(UniqueIdentifier, UniqueIdentifier)>` indicating the success or failure of the key pair creation action.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if:
|
||||
/// * The key pair request cannot be built.
|
||||
/// * The KMS server query fails.
|
||||
/// * The key pair unique identifiers are empty.
|
||||
pub async fn run(
|
||||
&self,
|
||||
kms_rest_client: &KmsClient,
|
||||
) -> CliResult<(UniqueIdentifier, UniqueIdentifier)> {
|
||||
let private_key_id = self
|
||||
.private_key_id
|
||||
.as_ref()
|
||||
.map(|id| UniqueIdentifier::TextString(id.clone()));
|
||||
let create_key_pair_request =
|
||||
create_rsa_key_pair_request(private_key_id, &self.tags, self.key_size, self.sensitive)?;
|
||||
|
||||
// Query the KMS with your kmip data and get the key pair ids
|
||||
let create_key_pair_response = kms_rest_client
|
||||
.create_key_pair(create_key_pair_request)
|
||||
.await
|
||||
.with_context(|| "failed creating a RSA key pair")?;
|
||||
|
||||
let private_key_unique_identifier = &create_key_pair_response.private_key_unique_identifier;
|
||||
let public_key_unique_identifier = &create_key_pair_response.public_key_unique_identifier;
|
||||
|
||||
let mut stdout = console::Stdout::new("The RSA key pair has been created.");
|
||||
stdout.set_tags(Some(&self.tags));
|
||||
stdout.set_key_pair_unique_identifier(
|
||||
private_key_unique_identifier,
|
||||
public_key_unique_identifier,
|
||||
);
|
||||
stdout.write()?;
|
||||
|
||||
Ok((
|
||||
private_key_unique_identifier.to_owned(),
|
||||
public_key_unique_identifier.to_owned(),
|
||||
))
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
use clap::Parser;
|
||||
use cosmian_kms_client::KmsClient;
|
||||
|
||||
use crate::{
|
||||
actions::{
|
||||
labels::KEY_ID,
|
||||
shared::{get_key_uid, utils::destroy},
|
||||
},
|
||||
error::result::CliResult,
|
||||
};
|
||||
|
||||
/// Destroy a public or private key.
|
||||
///
|
||||
/// The key must have been revoked first.
|
||||
///
|
||||
/// Keys belonging to external stores, such as HSMs,
|
||||
/// are automatically removed.
|
||||
///
|
||||
/// When a key is destroyed but not removed,
|
||||
/// it can only be exported by the owner of the key,
|
||||
/// and without its key material
|
||||
///
|
||||
/// Destroying a public or private key will destroy the whole key pair
|
||||
/// when the two keys are stored in the KMS.
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct DestroyKeyAction {
|
||||
/// The key unique identifier of the key to destroy
|
||||
/// If not specified, tags should be specified
|
||||
#[clap(long = KEY_ID, short = 'k', group = "key-tags")]
|
||||
key_id: Option<String>,
|
||||
|
||||
/// Tag to use to retrieve the key when no key id is specified.
|
||||
/// To specify multiple tags, use the option multiple times.
|
||||
#[clap(long = "tag", short = 't', value_name = "TAG", group = "key-tags")]
|
||||
tags: Option<Vec<String>>,
|
||||
|
||||
/// If the key should be removed from the database
|
||||
/// If not specified, the key will be destroyed
|
||||
/// but its metadata will still be available in the database.
|
||||
/// Please note that the KMIP specification does not support the removal of objects.
|
||||
#[clap(long = "remove", default_value = "false", verbatim_doc_comment)]
|
||||
remove: bool,
|
||||
}
|
||||
|
||||
impl DestroyKeyAction {
|
||||
/// Run the destroy key action
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `kms_rest_client` - A reference to the KMS client used to perform the key destruction.
|
||||
///
|
||||
/// # Results
|
||||
///
|
||||
/// This function returns a `CliResult<()>` indicating the success or failure of the key destruction action.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if:
|
||||
/// * Neither `key_id` nor `tags` are specified.
|
||||
/// * The key destruction request fails.
|
||||
/// * The KMS server query fails.
|
||||
pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
let id = get_key_uid(self.key_id.as_ref(), self.tags.as_ref(), KEY_ID)?;
|
||||
destroy(kms_rest_client, &id, self.remove).await
|
||||
}
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
use clap::Subcommand;
|
||||
use cosmian_kms_client::KmsClient;
|
||||
|
||||
use self::{
|
||||
create_key_pair::CreateKeyPairAction, destroy_key::DestroyKeyAction,
|
||||
revoke_key::RevokeKeyAction,
|
||||
};
|
||||
use crate::{
|
||||
actions::shared::{ExportKeyAction, ImportKeyAction, UnwrapKeyAction, WrapKeyAction},
|
||||
error::result::CliResult,
|
||||
};
|
||||
|
||||
pub mod create_key_pair;
|
||||
pub mod destroy_key;
|
||||
pub mod revoke_key;
|
||||
|
||||
/// Create, destroy, import, and export RSA key pairs
|
||||
#[derive(Subcommand)]
|
||||
pub enum KeysCommands {
|
||||
Create(CreateKeyPairAction),
|
||||
Export(ExportKeyAction),
|
||||
Import(ImportKeyAction),
|
||||
Wrap(WrapKeyAction),
|
||||
Unwrap(UnwrapKeyAction),
|
||||
Revoke(RevokeKeyAction),
|
||||
Destroy(DestroyKeyAction),
|
||||
}
|
||||
|
||||
impl KeysCommands {
|
||||
/// Process the key command
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `kms_rest_client` - A reference to the KMS client used to perform the key operations.
|
||||
///
|
||||
/// # Results
|
||||
///
|
||||
/// This function returns a `CliResult<()>` indicating the success or failure of the key command processing.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if:
|
||||
/// * The specific key action fails.
|
||||
/// * The KMS server query fails.
|
||||
pub async fn process(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
match self {
|
||||
Self::Create(action) => {
|
||||
action.run(kms_rest_client).await?;
|
||||
}
|
||||
Self::Export(action) => {
|
||||
action.run(kms_rest_client).await?;
|
||||
}
|
||||
Self::Import(action) => {
|
||||
action.run(kms_rest_client).await?;
|
||||
}
|
||||
Self::Wrap(action) => {
|
||||
action.run(kms_rest_client).await?;
|
||||
}
|
||||
Self::Unwrap(action) => {
|
||||
action.run(kms_rest_client).await?;
|
||||
}
|
||||
Self::Revoke(action) => {
|
||||
action.run(kms_rest_client).await?;
|
||||
}
|
||||
Self::Destroy(action) => {
|
||||
action.run(kms_rest_client).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
use clap::Parser;
|
||||
use cosmian_kms_client::KmsClient;
|
||||
|
||||
use crate::{
|
||||
actions::{
|
||||
labels::KEY_ID,
|
||||
shared::{get_key_uid, utils::revoke},
|
||||
},
|
||||
error::result::CliResult,
|
||||
};
|
||||
|
||||
/// Revoke a public or private key.
|
||||
///
|
||||
/// Once a key is revoked, it can only be exported by the owner of the key,
|
||||
/// using the --allow-revoked flag on the export function.
|
||||
///
|
||||
/// Revoking a public or private key will revoke the whole key pair
|
||||
/// (the two keys need to be stored in the KMS).
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct RevokeKeyAction {
|
||||
/// The reason for the revocation as a string
|
||||
#[clap(required = true)]
|
||||
pub(crate) revocation_reason: String,
|
||||
|
||||
/// The key unique identifier of the key to revoke.
|
||||
/// If not specified, tags should be specified
|
||||
#[clap(long = KEY_ID, short = 'k', group = "key-tags")]
|
||||
pub(crate) key_id: Option<String>,
|
||||
|
||||
/// Tag to use to retrieve the key when no key id is specified.
|
||||
/// To specify multiple tags, use the option multiple times.
|
||||
#[clap(long = "tag", short = 't', value_name = "TAG", group = "key-tags")]
|
||||
pub(crate) tags: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl RevokeKeyAction {
|
||||
/// Runs the key revocation process.
|
||||
///
|
||||
/// This function performs the following steps:
|
||||
/// 1. Recovers the unique identifier or set of tags for the key.
|
||||
/// 2. Calls the `revoke` utility function to revoke the key.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `kms_rest_client` - A reference to the KMS client.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `CliResult<()>` - The result of the revocation process.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if:
|
||||
/// * Neither `--key-id` nor `--tag` is specified.
|
||||
/// * The revocation request fails.
|
||||
pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
let id = get_key_uid(self.key_id.as_ref(), self.tags.as_ref(), KEY_ID)?;
|
||||
revoke(kms_rest_client, &id, &self.revocation_reason).await
|
||||
}
|
||||
}
|
|
@ -1,143 +0,0 @@
|
|||
use std::fmt::Display;
|
||||
|
||||
use clap::{Parser, ValueEnum};
|
||||
use cosmian_kms_client::{
|
||||
cosmian_kmip::kmip_2_1::kmip_types::{CryptographicAlgorithm, HashingAlgorithm},
|
||||
kmip_2_1::kmip_types::{CryptographicParameters, PaddingMethod},
|
||||
KmsClient,
|
||||
};
|
||||
|
||||
use self::{decrypt::DecryptAction, encrypt::EncryptAction, keys::KeysCommands};
|
||||
use crate::error::result::CliResult;
|
||||
|
||||
pub mod decrypt;
|
||||
pub mod encrypt;
|
||||
pub mod keys;
|
||||
|
||||
/// Manage RSA keys. Encrypt and decrypt data using RSA keys.
|
||||
#[derive(Parser)]
|
||||
pub enum RsaCommands {
|
||||
#[command(subcommand)]
|
||||
Keys(KeysCommands),
|
||||
Encrypt(EncryptAction),
|
||||
Decrypt(DecryptAction),
|
||||
}
|
||||
|
||||
impl RsaCommands {
|
||||
/// Process the RSA command by executing the corresponding action.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `kms_rest_client` - A reference to the KMS client used for communication with the KMS service.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if there is an issue executing the command.
|
||||
pub async fn process(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
match self {
|
||||
Self::Keys(command) => command.process(kms_rest_client).await?,
|
||||
Self::Encrypt(action) => action.run(kms_rest_client).await?,
|
||||
Self::Decrypt(action) => action.run(kms_rest_client).await?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(ValueEnum, Debug, Clone, Copy)]
|
||||
pub enum RsaEncryptionAlgorithm {
|
||||
#[cfg(not(feature = "fips"))]
|
||||
// a.k.a PKCS#1 v1.5 RSA
|
||||
CkmRsaPkcs,
|
||||
// a.k.a PKCS#1 RSA OAEP
|
||||
CkmRsaPkcsOaep,
|
||||
// CKM_RSA_AES_KEY_WRAP
|
||||
CkmRsaAesKeyWrap,
|
||||
}
|
||||
|
||||
impl Display for RsaEncryptionAlgorithm {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl RsaEncryptionAlgorithm {
|
||||
#[must_use]
|
||||
pub fn to_cryptographic_parameters(self, hash_fn: HashFn) -> CryptographicParameters {
|
||||
match self {
|
||||
#[cfg(not(feature = "fips"))]
|
||||
Self::CkmRsaPkcs => CryptographicParameters {
|
||||
cryptographic_algorithm: Some(CryptographicAlgorithm::RSA),
|
||||
padding_method: Some(PaddingMethod::PKCS1v15),
|
||||
hashing_algorithm: None,
|
||||
..Default::default()
|
||||
},
|
||||
Self::CkmRsaPkcsOaep => CryptographicParameters {
|
||||
cryptographic_algorithm: Some(CryptographicAlgorithm::RSA),
|
||||
padding_method: Some(PaddingMethod::OAEP),
|
||||
hashing_algorithm: Some(hash_fn.into()),
|
||||
..Default::default()
|
||||
},
|
||||
Self::CkmRsaAesKeyWrap => CryptographicParameters {
|
||||
cryptographic_algorithm: Some(CryptographicAlgorithm::AES),
|
||||
padding_method: Some(PaddingMethod::OAEP),
|
||||
hashing_algorithm: Some(hash_fn.into()),
|
||||
..Default::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::CkmRsaPkcsOaep => "ckm-rsa-pkcs-oaep",
|
||||
Self::CkmRsaAesKeyWrap => "ckm-rsa-aes-key-wrap",
|
||||
#[cfg(not(feature = "fips"))]
|
||||
Self::CkmRsaPkcs => "ckm-rsa-pkcs",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(ValueEnum, Debug, Clone, Copy)]
|
||||
pub enum HashFn {
|
||||
Sha1,
|
||||
Sha224,
|
||||
Sha256,
|
||||
Sha384,
|
||||
Sha512,
|
||||
Sha3_224,
|
||||
Sha3_256,
|
||||
Sha3_384,
|
||||
Sha3_512,
|
||||
}
|
||||
|
||||
impl Display for HashFn {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Sha1 => write!(f, "sha1"),
|
||||
Self::Sha224 => write!(f, "sha224"),
|
||||
Self::Sha256 => write!(f, "sha256"),
|
||||
Self::Sha384 => write!(f, "sha384"),
|
||||
Self::Sha512 => write!(f, "sha512"),
|
||||
Self::Sha3_224 => write!(f, "sha3-224"),
|
||||
Self::Sha3_256 => write!(f, "sha3-256"),
|
||||
Self::Sha3_384 => write!(f, "sha3-384"),
|
||||
Self::Sha3_512 => write!(f, "sha3-512"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HashFn> for HashingAlgorithm {
|
||||
fn from(value: HashFn) -> Self {
|
||||
match value {
|
||||
HashFn::Sha1 => Self::SHA1,
|
||||
HashFn::Sha224 => Self::SHA224,
|
||||
HashFn::Sha256 => Self::SHA256,
|
||||
HashFn::Sha384 => Self::SHA384,
|
||||
HashFn::Sha512 => Self::SHA512,
|
||||
HashFn::Sha3_224 => Self::SHA3224,
|
||||
HashFn::Sha3_256 => Self::SHA3256,
|
||||
HashFn::Sha3_384 => Self::SHA3384,
|
||||
HashFn::Sha3_512 => Self::SHA3512,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,289 +0,0 @@
|
|||
use std::{fmt::Display, path::PathBuf};
|
||||
|
||||
use base64::Engine;
|
||||
use clap::{Parser, ValueEnum};
|
||||
use cosmian_kms_client::{
|
||||
cosmian_kmip::kmip_2_1::kmip_types::{BlockCipherMode, KeyFormatType},
|
||||
der_to_pem, export_object,
|
||||
kmip_2_1::kmip_types::{
|
||||
CryptographicAlgorithm, CryptographicParameters, HashingAlgorithm, PaddingMethod,
|
||||
},
|
||||
write_bytes_to_file, write_kmip_object_to_file, ExportObjectParams, KmsClient,
|
||||
};
|
||||
|
||||
use super::get_key_uid;
|
||||
use crate::{
|
||||
actions::{console, labels::KEY_ID},
|
||||
error::result::{CliResult, CliResultHelper},
|
||||
};
|
||||
|
||||
#[derive(ValueEnum, Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ExportKeyFormat {
|
||||
JsonTtlv,
|
||||
Sec1Pem,
|
||||
Sec1Der,
|
||||
Pkcs1Pem,
|
||||
Pkcs1Der,
|
||||
Pkcs8Pem,
|
||||
Pkcs8Der,
|
||||
SpkiPem,
|
||||
SpkiDer,
|
||||
Base64,
|
||||
Raw,
|
||||
}
|
||||
|
||||
#[derive(ValueEnum, Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) enum WrappingAlgorithm {
|
||||
NistKeyWrap,
|
||||
AesGCM,
|
||||
RsaPkcsV15,
|
||||
RsaOaep,
|
||||
RsaAesKeyWrap,
|
||||
}
|
||||
|
||||
impl WrappingAlgorithm {
|
||||
pub(crate) const fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::NistKeyWrap => "nist-key-wrap",
|
||||
Self::AesGCM => "aes-gcm",
|
||||
Self::RsaPkcsV15 => "rsa-pkcs-v15",
|
||||
Self::RsaOaep => "rsa-oaep",
|
||||
Self::RsaAesKeyWrap => "rsa-aes-key-wrap",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for WrappingAlgorithm {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
/// Export a key from the KMS
|
||||
///
|
||||
/// If not format is specified, the key is exported as a json-ttlv with a
|
||||
/// `KeyFormatType` that follows the section 4.26 of the KMIP specification.
|
||||
/// <https://docs.oasis-open.org/kmip/kmip-spec/v2.1/os/kmip-spec-v2.1-os.html#_Toc57115585>
|
||||
///
|
||||
/// The key can optionally be unwrapped and/or wrapped when exported.
|
||||
///
|
||||
/// If wrapping is specified, the key is wrapped using the specified wrapping key.
|
||||
/// The chosen Key Format must be either `json-ttlv` or `raw`. When `raw` is selected,
|
||||
/// only the wrapped bytes are returned.
|
||||
///
|
||||
/// Wrapping a key that is already wrapped is an error.
|
||||
/// Unwrapping a key that is not wrapped is ignored and returns the unwrapped key.
|
||||
///
|
||||
/// When using tags to retrieve the key, rather than the key id,
|
||||
/// an error is returned if multiple keys matching the tags are found.
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct ExportKeyAction {
|
||||
/// The file to export the key to
|
||||
#[clap(required = true)]
|
||||
key_file: PathBuf,
|
||||
|
||||
/// The key unique identifier stored in the KMS.
|
||||
/// If not specified, tags should be specified
|
||||
#[clap(long = KEY_ID, short = 'k', group = "key-tags")]
|
||||
key_id: Option<String>,
|
||||
|
||||
/// Tag to use to retrieve the key when no key id is specified.
|
||||
/// To specify multiple tags, use the option multiple times.
|
||||
#[clap(long = "tag", short = 't', value_name = "TAG", group = "key-tags")]
|
||||
tags: Option<Vec<String>>,
|
||||
|
||||
/// The format of the key
|
||||
/// - `json-ttlv` [default]. It should be the format to use to later re-import the key
|
||||
/// - `sec1-pem` and `sec1-der`only apply to NIST EC private keys (Not Curve25519 or X448)
|
||||
/// - `pkcs1-pem` and `pkcs1-der` only apply to RSA private and public keys
|
||||
/// - `pkcs8-pem` and `pkcs8-der` only apply to RSA and EC private keys
|
||||
/// - `spki-pem` and `spki-der` only apply to RSA and EC public keys
|
||||
/// - `raw` returns the raw bytes of
|
||||
/// - symmetric keys
|
||||
/// - Covercrypt keys
|
||||
/// - wrapped keys
|
||||
#[clap(
|
||||
long = "key-format",
|
||||
short = 'f',
|
||||
default_value = "json-ttlv",
|
||||
verbatim_doc_comment
|
||||
)]
|
||||
key_format: ExportKeyFormat,
|
||||
|
||||
/// Unwrap the key if it is wrapped before export
|
||||
#[clap(
|
||||
long = "unwrap",
|
||||
short = 'u',
|
||||
default_value = "false",
|
||||
group = "wrapping"
|
||||
)]
|
||||
unwrap: bool,
|
||||
|
||||
/// The id of the key/certificate to use to wrap this key before export
|
||||
#[clap(
|
||||
long = "wrap-key-id",
|
||||
short = 'w',
|
||||
required = false,
|
||||
group = "wrapping"
|
||||
)]
|
||||
wrap_key_id: Option<String>,
|
||||
|
||||
/// Allow exporting revoked and destroyed keys.
|
||||
/// The user must be the owner of the key.
|
||||
/// Destroyed keys have their key material removed.
|
||||
#[clap(
|
||||
long = "allow-revoked",
|
||||
short = 'i',
|
||||
default_value = "false",
|
||||
verbatim_doc_comment
|
||||
)]
|
||||
allow_revoked: bool,
|
||||
|
||||
/// Wrapping algorithm to use when exporting the key
|
||||
/// By default, the algorithm used is
|
||||
/// - `NISTKeyWrap` for symmetric keys (a.k.a. RFC 5649)
|
||||
/// - `RsaPkcsOaep` for RSA keys
|
||||
#[clap(
|
||||
long = "wrapping-algorithm",
|
||||
short = 'm',
|
||||
default_value = None,
|
||||
verbatim_doc_comment
|
||||
)]
|
||||
wrapping_algorithm: Option<WrappingAlgorithm>,
|
||||
|
||||
/// Authenticated encryption additional data
|
||||
/// Only available for AES GCM wrapping
|
||||
#[clap(
|
||||
long = "authenticated-additional-data",
|
||||
short = 'd',
|
||||
default_value = None,
|
||||
)]
|
||||
authenticated_additional_data: Option<String>,
|
||||
}
|
||||
|
||||
impl ExportKeyAction {
|
||||
/// Export a key from the KMS
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function can return an error if:
|
||||
///
|
||||
/// - Either `--key-id` or one or more `--tag` is not specified.
|
||||
/// - There is a server error while exporting the object.
|
||||
///
|
||||
pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
let id = get_key_uid(self.key_id.as_ref(), self.tags.as_ref(), KEY_ID)?;
|
||||
|
||||
let (key_format_type, encode_to_pem) = match self.key_format {
|
||||
// For Raw: use the default format then do the local extraction of the bytes
|
||||
ExportKeyFormat::JsonTtlv | ExportKeyFormat::Raw | ExportKeyFormat::Base64 => {
|
||||
(None, false)
|
||||
}
|
||||
ExportKeyFormat::Sec1Pem => (Some(KeyFormatType::ECPrivateKey), true),
|
||||
ExportKeyFormat::Sec1Der => (Some(KeyFormatType::ECPrivateKey), false),
|
||||
ExportKeyFormat::Pkcs1Pem => (Some(KeyFormatType::PKCS1), true),
|
||||
ExportKeyFormat::Pkcs1Der => (Some(KeyFormatType::PKCS1), false),
|
||||
ExportKeyFormat::Pkcs8Pem | ExportKeyFormat::SpkiPem => {
|
||||
(Some(KeyFormatType::PKCS8), true)
|
||||
}
|
||||
ExportKeyFormat::Pkcs8Der | ExportKeyFormat::SpkiDer => {
|
||||
(Some(KeyFormatType::PKCS8), false)
|
||||
}
|
||||
};
|
||||
|
||||
let encode_to_ttlv = self.key_format == ExportKeyFormat::JsonTtlv;
|
||||
|
||||
let wrapping_cryptographic_parameters =
|
||||
self.wrapping_algorithm
|
||||
.as_ref()
|
||||
.map(|wrapping_algorithm| match wrapping_algorithm {
|
||||
WrappingAlgorithm::NistKeyWrap => CryptographicParameters {
|
||||
cryptographic_algorithm: Some(CryptographicAlgorithm::AES),
|
||||
block_cipher_mode: Some(BlockCipherMode::NISTKeyWrap),
|
||||
..CryptographicParameters::default()
|
||||
},
|
||||
WrappingAlgorithm::AesGCM => CryptographicParameters {
|
||||
cryptographic_algorithm: Some(CryptographicAlgorithm::AES),
|
||||
block_cipher_mode: Some(BlockCipherMode::GCM),
|
||||
..CryptographicParameters::default()
|
||||
},
|
||||
WrappingAlgorithm::RsaPkcsV15 => CryptographicParameters {
|
||||
cryptographic_algorithm: Some(CryptographicAlgorithm::RSA),
|
||||
padding_method: Some(PaddingMethod::PKCS1v15),
|
||||
hashing_algorithm: Some(HashingAlgorithm::SHA256),
|
||||
..CryptographicParameters::default()
|
||||
},
|
||||
WrappingAlgorithm::RsaOaep => CryptographicParameters {
|
||||
cryptographic_algorithm: Some(CryptographicAlgorithm::RSA),
|
||||
padding_method: Some(PaddingMethod::OAEP),
|
||||
hashing_algorithm: Some(HashingAlgorithm::SHA256),
|
||||
..CryptographicParameters::default()
|
||||
},
|
||||
WrappingAlgorithm::RsaAesKeyWrap => CryptographicParameters {
|
||||
cryptographic_algorithm: Some(CryptographicAlgorithm::AES),
|
||||
padding_method: Some(PaddingMethod::OAEP),
|
||||
hashing_algorithm: Some(HashingAlgorithm::SHA256),
|
||||
..CryptographicParameters::default()
|
||||
},
|
||||
});
|
||||
|
||||
// export the object
|
||||
let (id, object, _) = export_object(
|
||||
kms_rest_client,
|
||||
&id,
|
||||
ExportObjectParams {
|
||||
unwrap: self.unwrap,
|
||||
wrapping_key_id: self.wrap_key_id.as_deref(),
|
||||
allow_revoked: self.allow_revoked,
|
||||
key_format_type,
|
||||
encode_to_ttlv,
|
||||
wrapping_cryptographic_parameters,
|
||||
authenticated_encryption_additional_data: self
|
||||
.authenticated_additional_data
|
||||
.clone(),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
// write the object to a file
|
||||
if self.key_format == ExportKeyFormat::JsonTtlv {
|
||||
// save it to a file
|
||||
write_kmip_object_to_file(&object, &self.key_file)?;
|
||||
} else if self.key_format == ExportKeyFormat::Base64 {
|
||||
// export the key bytes in base64
|
||||
let base64_key = base64::engine::general_purpose::STANDARD
|
||||
.encode(object.key_block()?.key_bytes()?)
|
||||
.to_lowercase();
|
||||
write_bytes_to_file(base64_key.as_bytes(), &self.key_file)?;
|
||||
} else {
|
||||
// export the bytes only
|
||||
let bytes = {
|
||||
let mut bytes = object.key_block()?.key_bytes()?;
|
||||
if encode_to_pem {
|
||||
bytes = der_to_pem(
|
||||
bytes.as_slice(),
|
||||
key_format_type.context(
|
||||
"Server Error: the Key Format Type should be known at this stage",
|
||||
)?,
|
||||
object.object_type(),
|
||||
)?;
|
||||
}
|
||||
bytes
|
||||
};
|
||||
write_bytes_to_file(&bytes, &self.key_file)?;
|
||||
}
|
||||
|
||||
let stdout = format!(
|
||||
"The key {} of type {} was exported to {:?}",
|
||||
&id,
|
||||
object.object_type(),
|
||||
&self.key_file
|
||||
);
|
||||
let mut stdout = console::Stdout::new(&stdout);
|
||||
stdout.set_unique_identifier(id);
|
||||
stdout.write()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
use tracing::trace;
|
||||
|
||||
use crate::{cli_bail, error::result::CliResult};
|
||||
|
||||
pub(crate) fn get_key_uid(
|
||||
key_id: Option<&String>,
|
||||
tags: Option<&Vec<String>>,
|
||||
argument_name: &str,
|
||||
) -> CliResult<String> {
|
||||
let id = if let Some(kid) = key_id {
|
||||
kid.clone()
|
||||
} else if let Some(tags) = tags {
|
||||
serde_json::to_string(tags)?
|
||||
} else {
|
||||
cli_bail!("Either --{argument_name} or one or more --tag must be specified")
|
||||
};
|
||||
trace!("Key UID: {id}");
|
||||
Ok(id)
|
||||
}
|
|
@ -1,320 +0,0 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use clap::{Parser, ValueEnum};
|
||||
use cosmian_kms_client::{
|
||||
cosmian_kmip::kmip_2_1::{
|
||||
kmip_data_structures::{KeyBlock, KeyMaterial, KeyValue},
|
||||
kmip_objects::{Object, ObjectType},
|
||||
kmip_types::{
|
||||
Attributes, CryptographicAlgorithm, KeyFormatType, LinkType, LinkedObjectIdentifier,
|
||||
},
|
||||
},
|
||||
import_object, objects_from_pem, read_bytes_from_file, read_object_from_json_ttlv_bytes,
|
||||
KmsClient,
|
||||
};
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
use super::utils::{build_usage_mask_from_key_usage, KeyUsage};
|
||||
use crate::{
|
||||
actions::console,
|
||||
error::{result::CliResult, CliError},
|
||||
};
|
||||
|
||||
#[derive(ValueEnum, Debug, Clone)]
|
||||
pub(crate) enum ImportKeyFormat {
|
||||
JsonTtlv,
|
||||
Pem,
|
||||
Sec1,
|
||||
Pkcs1Priv,
|
||||
Pkcs1Pub,
|
||||
Pkcs8,
|
||||
Spki,
|
||||
Aes,
|
||||
Chacha20,
|
||||
}
|
||||
|
||||
/// Import a private or public key in the KMS.
|
||||
///
|
||||
/// When no unique id is specified, a unique id is generated.
|
||||
///
|
||||
/// By default, the format is expected to be JSON TTLV but
|
||||
/// other formats can be specified with the option `-f`.
|
||||
/// * json-ttlv (the default)
|
||||
/// * pem (PKCS#1, PKCS#8, SEC1, SPKI): the function will attempt to detect the type of key and key format
|
||||
/// * sec1: an elliptic curve private key in SEC1 DER format (NIST curves only - SECG SEC1-v2 #C.4)
|
||||
/// * pkcs1-priv: an RSA private key in PKCS#1 DER format (RFC 8017)
|
||||
/// * pkcs1-pub: an RSA public key in PKCS#1 DER format (RFC 8017)
|
||||
/// * pkcs8: an RSA or Elliptic Curve private key in PKCS#8 DER format (RFC 5208 and 5958)
|
||||
/// * spki: an RSA or Elliptic Curve public key in Subject Public Key Info DER format (RFC 5480)
|
||||
/// * aes: the bytes of an AES symmetric key
|
||||
/// * chacha20: the bytes of a `ChaCha20` symmetric key
|
||||
///
|
||||
/// Tags can later be used to retrieve the key. Tags are optional.
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct ImportKeyAction {
|
||||
/// The KMIP JSON TTLV key file.
|
||||
#[clap(required = true)]
|
||||
key_file: PathBuf,
|
||||
|
||||
/// The unique id of the key; a random uuid
|
||||
/// is generated if not specified.
|
||||
#[clap(required = false)]
|
||||
key_id: Option<String>,
|
||||
|
||||
/// The format of the key.
|
||||
#[clap(long, short = 'f', default_value = "json-ttlv")]
|
||||
key_format: ImportKeyFormat,
|
||||
|
||||
/// For a private key: the corresponding KMS public key id if any.
|
||||
#[clap(long, short = 'p')]
|
||||
public_key_id: Option<String>,
|
||||
|
||||
/// For a public key: the corresponding KMS private key id if any.
|
||||
#[clap(long, short = 'k')]
|
||||
private_key_id: Option<String>,
|
||||
|
||||
/// For a public or private key: the corresponding certificate id if any.
|
||||
#[clap(long, short = 'c')]
|
||||
certificate_id: Option<String>,
|
||||
|
||||
/// In the case of a JSON TTLV key,
|
||||
/// unwrap the key if it is wrapped before storing it.
|
||||
#[clap(long, short = 'u', required = false, default_value = "false")]
|
||||
unwrap: bool,
|
||||
|
||||
/// Replace an existing key under the same id.
|
||||
#[clap(
|
||||
required = false,
|
||||
long = "replace",
|
||||
short = 'r',
|
||||
default_value = "false"
|
||||
)]
|
||||
replace_existing: bool,
|
||||
|
||||
/// The tag to associate with the key.
|
||||
/// To specify multiple tags, use the option multiple times.
|
||||
#[clap(long = "tag", short = 't', value_name = "TAG")]
|
||||
tags: Vec<String>,
|
||||
|
||||
/// For what operations should the key be used.
|
||||
#[clap(long)]
|
||||
key_usage: Option<Vec<KeyUsage>>,
|
||||
|
||||
/// Optional authenticated encryption additional data to use for AES256GCM authenticated encryption unwrapping
|
||||
#[clap(
|
||||
long,
|
||||
short = 'd',
|
||||
default_value = None,
|
||||
)]
|
||||
authenticated_additional_data: Option<String>,
|
||||
}
|
||||
|
||||
impl ImportKeyAction {
|
||||
/// Run the import key action.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function can return a [`CliError`] if an error occurs during the import process.
|
||||
///
|
||||
/// Possible error cases include:
|
||||
///
|
||||
/// - Failed to read the key file.
|
||||
/// - Failed to parse the key file in the specified format.
|
||||
/// - Invalid key format specified.
|
||||
/// - Failed to assign cryptographic usage mask.
|
||||
/// - Failed to generate import attributes.
|
||||
/// - Failed to import the key.
|
||||
/// - Failed to write the response to stdout.
|
||||
///
|
||||
/// [`CliError`]: ../error/result/enum.CliError.html
|
||||
pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
let cryptographic_usage_mask = self
|
||||
.key_usage
|
||||
.as_deref()
|
||||
.and_then(build_usage_mask_from_key_usage);
|
||||
// read the key file
|
||||
let bytes = Zeroizing::from(read_bytes_from_file(&self.key_file)?);
|
||||
let mut object = match &self.key_format {
|
||||
ImportKeyFormat::JsonTtlv => read_object_from_json_ttlv_bytes(&bytes)?,
|
||||
ImportKeyFormat::Pem => read_key_from_pem(&bytes)?,
|
||||
ImportKeyFormat::Sec1 => {
|
||||
build_private_key_from_der_bytes(KeyFormatType::ECPrivateKey, bytes)
|
||||
}
|
||||
ImportKeyFormat::Pkcs1Priv => {
|
||||
build_private_key_from_der_bytes(KeyFormatType::PKCS1, bytes)
|
||||
}
|
||||
ImportKeyFormat::Pkcs1Pub => {
|
||||
build_public_key_from_der_bytes(KeyFormatType::PKCS1, bytes)
|
||||
}
|
||||
ImportKeyFormat::Pkcs8 => build_private_key_from_der_bytes(KeyFormatType::PKCS8, bytes),
|
||||
ImportKeyFormat::Spki => build_public_key_from_der_bytes(KeyFormatType::PKCS8, bytes),
|
||||
ImportKeyFormat::Aes => {
|
||||
build_symmetric_key_from_bytes(CryptographicAlgorithm::AES, bytes)?
|
||||
}
|
||||
ImportKeyFormat::Chacha20 => {
|
||||
build_symmetric_key_from_bytes(CryptographicAlgorithm::ChaCha20, bytes)?
|
||||
}
|
||||
};
|
||||
// Assign CryptographicUsageMask from command line arguments.
|
||||
object
|
||||
.attributes_mut()?
|
||||
.set_cryptographic_usage_mask(cryptographic_usage_mask);
|
||||
|
||||
let object_type = object.object_type();
|
||||
|
||||
// Generate the import attributes if links are specified.
|
||||
let mut import_attributes = object.attributes().cloned().unwrap_or_else(|_| Attributes {
|
||||
cryptographic_usage_mask,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
if let Some(issuer_certificate_id) = &self.certificate_id {
|
||||
//let attributes = import_attributes.get_or_insert(Attributes::default());
|
||||
import_attributes.set_link(
|
||||
LinkType::CertificateLink,
|
||||
LinkedObjectIdentifier::TextString(issuer_certificate_id.clone()),
|
||||
);
|
||||
}
|
||||
if let Some(private_key_id) = &self.private_key_id {
|
||||
//let attributes = import_attributes.get_or_insert(Attributes::default());
|
||||
import_attributes.set_link(
|
||||
LinkType::PrivateKeyLink,
|
||||
LinkedObjectIdentifier::TextString(private_key_id.clone()),
|
||||
);
|
||||
}
|
||||
if let Some(public_key_id) = &self.public_key_id {
|
||||
import_attributes.set_link(
|
||||
LinkType::PublicKeyLink,
|
||||
LinkedObjectIdentifier::TextString(public_key_id.clone()),
|
||||
);
|
||||
}
|
||||
|
||||
if self.unwrap {
|
||||
if let Some(data) = &self.authenticated_additional_data {
|
||||
// If authenticated_additional_data are provided, must be added on key attributes for unwrapping
|
||||
let aad = data.as_bytes();
|
||||
object.attributes_mut()?.add_aad(aad);
|
||||
}
|
||||
}
|
||||
|
||||
// import the key
|
||||
let unique_identifier = import_object(
|
||||
kms_rest_client,
|
||||
self.key_id.clone(),
|
||||
object,
|
||||
Some(import_attributes),
|
||||
self.unwrap,
|
||||
self.replace_existing,
|
||||
&self.tags,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// print the response
|
||||
let stdout = format!(
|
||||
"The {:?} in file {:?} was imported with id: {}",
|
||||
object_type, &self.key_file, unique_identifier,
|
||||
);
|
||||
let mut stdout = console::Stdout::new(&stdout);
|
||||
stdout.set_tags(Some(&self.tags));
|
||||
stdout.set_unique_identifier(unique_identifier);
|
||||
stdout.write()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Read a key from a PEM file
|
||||
#[allow(clippy::print_stdout)]
|
||||
fn read_key_from_pem(bytes: &[u8]) -> CliResult<Object> {
|
||||
let mut objects = objects_from_pem(bytes)?;
|
||||
let object = objects
|
||||
.pop()
|
||||
.ok_or_else(|| CliError::Default("The PEM file does not contain any object".to_owned()))?;
|
||||
match object.object_type() {
|
||||
ObjectType::PrivateKey | ObjectType::PublicKey => {
|
||||
if !objects.is_empty() {
|
||||
println!(
|
||||
"WARNING: the PEM file contains multiple objects. Only the private key will \
|
||||
be imported. A corresponding public key will be generated automatically."
|
||||
);
|
||||
}
|
||||
Ok(object)
|
||||
}
|
||||
ObjectType::Certificate => Err(CliError::Default(
|
||||
"For certificates, use the `ckms certificate` sub-command".to_owned(),
|
||||
)),
|
||||
_ => Err(CliError::Default(format!(
|
||||
"The PEM file contains an object of type {:?} which is not supported",
|
||||
object.object_type()
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn build_private_key_from_der_bytes(
|
||||
key_format_type: KeyFormatType,
|
||||
bytes: Zeroizing<Vec<u8>>,
|
||||
) -> Object {
|
||||
Object::PrivateKey {
|
||||
key_block: KeyBlock {
|
||||
key_format_type,
|
||||
key_compression_type: None,
|
||||
key_value: KeyValue {
|
||||
key_material: KeyMaterial::ByteString(bytes),
|
||||
attributes: Some(Attributes::default()),
|
||||
},
|
||||
// According to the KMIP spec, the cryptographic algorithm is not required
|
||||
// as long as it can be recovered from the Key Format Type or the Key Value.
|
||||
// Also it should not be specified if the cryptographic length is not specified.
|
||||
cryptographic_algorithm: None,
|
||||
// See comment above
|
||||
cryptographic_length: None,
|
||||
key_wrapping_data: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Here the zeroizing type on public key bytes is overkill, but it aligns with
|
||||
// other methods dealing with private components.
|
||||
fn build_public_key_from_der_bytes(
|
||||
key_format_type: KeyFormatType,
|
||||
bytes: Zeroizing<Vec<u8>>,
|
||||
) -> Object {
|
||||
Object::PublicKey {
|
||||
key_block: KeyBlock {
|
||||
key_format_type,
|
||||
key_compression_type: None,
|
||||
key_value: KeyValue {
|
||||
key_material: KeyMaterial::ByteString(bytes),
|
||||
attributes: Some(Attributes::default()),
|
||||
},
|
||||
// According to the KMIP spec, the cryptographic algorithm is not required
|
||||
// as long as it can be recovered from the Key Format Type or the Key Value.
|
||||
// Also it should not be specified if the cryptographic length is not specified.
|
||||
cryptographic_algorithm: None,
|
||||
// See comment above
|
||||
cryptographic_length: None,
|
||||
key_wrapping_data: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn build_symmetric_key_from_bytes(
|
||||
cryptographic_algorithm: CryptographicAlgorithm,
|
||||
bytes: Zeroizing<Vec<u8>>,
|
||||
) -> CliResult<Object> {
|
||||
let len = i32::try_from(bytes.len())? * 8;
|
||||
Ok(Object::SymmetricKey {
|
||||
key_block: KeyBlock {
|
||||
key_format_type: KeyFormatType::TransparentSymmetricKey,
|
||||
key_compression_type: None,
|
||||
key_value: KeyValue {
|
||||
key_material: KeyMaterial::TransparentSymmetricKey { key: bytes },
|
||||
attributes: Some(Attributes::default()),
|
||||
},
|
||||
cryptographic_algorithm: Some(cryptographic_algorithm),
|
||||
cryptographic_length: Some(len),
|
||||
key_wrapping_data: None,
|
||||
},
|
||||
})
|
||||
}
|
|
@ -1,294 +0,0 @@
|
|||
use std::ffi::OsString;
|
||||
|
||||
use clap::{
|
||||
error::{ContextKind, ContextValue, ErrorKind},
|
||||
Parser,
|
||||
};
|
||||
use cosmian_kms_client::{
|
||||
cosmian_kmip::kmip_2_1::{
|
||||
kmip_operations::Locate,
|
||||
kmip_types::{
|
||||
Attributes, CryptographicAlgorithm, KeyFormatType, LinkType, LinkedObjectIdentifier,
|
||||
},
|
||||
},
|
||||
kmip_2_1::kmip_objects::ObjectType,
|
||||
KmsClient,
|
||||
};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::{actions::console, error::result::CliResult};
|
||||
|
||||
/// Locate cryptographic objects inside the KMS
|
||||
///
|
||||
/// This command will return one id per line.
|
||||
/// There will be no output if no object is found.
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct LocateObjectsAction {
|
||||
/// User tags or system tags to locate the object.
|
||||
/// To specify multiple tags, use the option multiple times.
|
||||
#[clap(long = "tag", short = 't', value_name = "TAG", verbatim_doc_comment)]
|
||||
tags: Option<Vec<String>>,
|
||||
|
||||
/// Cryptographic algorithm (case insensitive)
|
||||
///
|
||||
/// The list of algorithms is the one specified by KMIP 2.1 in addition to "Covercrypt".
|
||||
/// Possible values include "Covercrypt", "ECDH", "`ChaCha20Poly1305`", "AES", "Ed25519"
|
||||
///
|
||||
/// Running the locate sub-command with a wrong value will list all the possible values.
|
||||
/// e.g. `ckms locate --algorithm WRONG`
|
||||
#[clap(
|
||||
long = "algorithm",
|
||||
short = 'a',
|
||||
value_parser = CryptographicAlgorithmParser,
|
||||
verbatim_doc_comment
|
||||
)]
|
||||
cryptographic_algorithm: Option<CryptographicAlgorithm>,
|
||||
|
||||
/// Cryptographic length (e.g. key size) in bits
|
||||
#[clap(long = "cryptographic-length", short = 'l')]
|
||||
cryptographic_length: Option<i32>,
|
||||
|
||||
/// Key format type (case insensitive)
|
||||
///
|
||||
/// The list is the one specified by KMIP 2.1
|
||||
/// in addition to the two Covercrypt formats: "`CoverCryptSecretKey`" and "`CoverCryptPublicKey`"
|
||||
/// Possible values also include: "RAW" and "PKCS8"
|
||||
/// Note: asymmetric keys are always stored in the "PKCS8" format; symmetric keys are always stored in the "Raw" format.
|
||||
///
|
||||
/// Running the locate sub-command with a wrong value will list all the possible values.
|
||||
/// e.g. `ckms locate --key-format-type WRONG`
|
||||
#[clap(long = "key-format-type", short = 'f',
|
||||
value_parser = KeyFormatTypeParser,verbatim_doc_comment)]
|
||||
key_format_type: Option<KeyFormatType>,
|
||||
|
||||
/// Object type (case insensitive)
|
||||
///
|
||||
/// The list is the one specified by KMIP 2.1
|
||||
/// Possible values are:
|
||||
/// * Certificate
|
||||
/// * `SymmetricKey`
|
||||
/// * `PublicKey`
|
||||
/// * `PrivateKey`
|
||||
/// * `SplitKey`
|
||||
/// * `SecretData`
|
||||
/// * `OpaqueObject`
|
||||
/// * `PGPKey`
|
||||
/// * `CertificateRequest`
|
||||
#[clap(long, short = 'o',
|
||||
value_parser = ObjectTypeParser,verbatim_doc_comment)]
|
||||
object_type: Option<ObjectType>,
|
||||
|
||||
/// Locate an object which has a link to this public key id.
|
||||
#[clap(long, short = 'p')]
|
||||
public_key_id: Option<String>,
|
||||
|
||||
/// Locate an object which has a link to this private key id.
|
||||
#[clap(long, short = 'k')]
|
||||
private_key_id: Option<String>,
|
||||
|
||||
/// Locate an object which has a link to this certificate key id.
|
||||
#[clap(long = "certificate-id", short = 'c')]
|
||||
certificate_id: Option<String>,
|
||||
}
|
||||
|
||||
impl LocateObjectsAction {
|
||||
/// Export a key from the KMS
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if there is a problem communicating with the KMS or if the requested key cannot be located.
|
||||
pub async fn process(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
let mut attributes = Attributes::default();
|
||||
|
||||
if let Some(crypto_algo) = self.cryptographic_algorithm {
|
||||
attributes.cryptographic_algorithm = Some(crypto_algo);
|
||||
}
|
||||
|
||||
if let Some(cryptographic_length) = self.cryptographic_length {
|
||||
attributes.cryptographic_length = Some(cryptographic_length);
|
||||
}
|
||||
|
||||
if let Some(key_format_type) = self.key_format_type {
|
||||
attributes.key_format_type = Some(key_format_type);
|
||||
}
|
||||
|
||||
if let Some(object_type) = self.object_type {
|
||||
attributes.object_type = Some(object_type);
|
||||
}
|
||||
|
||||
if let Some(public_key_id) = &self.public_key_id {
|
||||
attributes.set_link(
|
||||
LinkType::PublicKeyLink,
|
||||
LinkedObjectIdentifier::TextString(public_key_id.to_string()),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(private_key_id) = &self.private_key_id {
|
||||
attributes.set_link(
|
||||
LinkType::PrivateKeyLink,
|
||||
LinkedObjectIdentifier::TextString(private_key_id.to_string()),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(certificate_id) = &self.certificate_id {
|
||||
attributes.set_link(
|
||||
LinkType::CertificateLink,
|
||||
LinkedObjectIdentifier::TextString(certificate_id.to_string()),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(tags) = &self.tags {
|
||||
attributes.set_tags(tags.clone())?;
|
||||
}
|
||||
|
||||
let locate = Locate {
|
||||
maximum_items: None,
|
||||
offset_items: None,
|
||||
storage_status_mask: None,
|
||||
object_group_member: None,
|
||||
attributes,
|
||||
};
|
||||
|
||||
let response = kms_rest_client.locate(locate).await?;
|
||||
if let Some(ids) = response.unique_identifiers {
|
||||
if ids.is_empty() {
|
||||
console::Stdout::new("No object found.").write()?;
|
||||
} else {
|
||||
let mut stdout = console::Stdout::new("List of unique identifiers:");
|
||||
stdout.set_unique_identifiers(&ids);
|
||||
stdout.write()?;
|
||||
}
|
||||
} else {
|
||||
console::Stdout::new("No object found.").write()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a string entered by the user into a `CryptographicAlgorithm`
|
||||
#[derive(Clone)]
|
||||
struct CryptographicAlgorithmParser;
|
||||
|
||||
impl clap::builder::TypedValueParser for CryptographicAlgorithmParser {
|
||||
type Value = CryptographicAlgorithm;
|
||||
|
||||
fn parse_ref(
|
||||
&self,
|
||||
cmd: &clap::Command,
|
||||
arg: Option<&clap::Arg>,
|
||||
value: &std::ffi::OsStr,
|
||||
) -> Result<Self::Value, clap::Error> {
|
||||
CryptographicAlgorithm::iter()
|
||||
.find(|algo| {
|
||||
OsString::from(algo.to_string().to_lowercase()) == value.to_ascii_lowercase()
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
let mut err = clap::Error::new(ErrorKind::ValueValidation).with_cmd(cmd);
|
||||
if let Some(arg) = arg {
|
||||
err.insert(
|
||||
ContextKind::InvalidArg,
|
||||
ContextValue::String(arg.to_string()),
|
||||
);
|
||||
}
|
||||
err.insert(
|
||||
ContextKind::InvalidValue,
|
||||
ContextValue::String(value.to_string_lossy().to_string()),
|
||||
);
|
||||
err.insert(
|
||||
ContextKind::SuggestedValue,
|
||||
ContextValue::Strings(
|
||||
CryptographicAlgorithm::iter()
|
||||
.map(|algo| algo.to_string())
|
||||
.collect::<Vec<String>>(),
|
||||
),
|
||||
);
|
||||
err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a string entered by the user into a `KeyFormatType`
|
||||
#[derive(Clone)]
|
||||
struct KeyFormatTypeParser;
|
||||
|
||||
impl clap::builder::TypedValueParser for KeyFormatTypeParser {
|
||||
type Value = KeyFormatType;
|
||||
|
||||
fn parse_ref(
|
||||
&self,
|
||||
cmd: &clap::Command,
|
||||
arg: Option<&clap::Arg>,
|
||||
value: &std::ffi::OsStr,
|
||||
) -> Result<Self::Value, clap::Error> {
|
||||
KeyFormatType::iter()
|
||||
.find(|algo| {
|
||||
OsString::from(algo.to_string().to_lowercase()) == value.to_ascii_lowercase()
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
let mut err = clap::Error::new(ErrorKind::ValueValidation).with_cmd(cmd);
|
||||
if let Some(arg) = arg {
|
||||
err.insert(
|
||||
ContextKind::InvalidArg,
|
||||
ContextValue::String(arg.to_string()),
|
||||
);
|
||||
}
|
||||
err.insert(
|
||||
ContextKind::InvalidValue,
|
||||
ContextValue::String(value.to_string_lossy().to_string()),
|
||||
);
|
||||
err.insert(
|
||||
ContextKind::SuggestedValue,
|
||||
ContextValue::Strings(
|
||||
KeyFormatType::iter()
|
||||
.map(|algo| algo.to_string())
|
||||
.collect::<Vec<String>>(),
|
||||
),
|
||||
);
|
||||
err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a string entered by the user into a `ObjectType`
|
||||
#[derive(Clone)]
|
||||
struct ObjectTypeParser;
|
||||
|
||||
impl clap::builder::TypedValueParser for ObjectTypeParser {
|
||||
type Value = ObjectType;
|
||||
|
||||
fn parse_ref(
|
||||
&self,
|
||||
cmd: &clap::Command,
|
||||
arg: Option<&clap::Arg>,
|
||||
value: &std::ffi::OsStr,
|
||||
) -> Result<Self::Value, clap::Error> {
|
||||
ObjectType::iter()
|
||||
.find(|object_type| {
|
||||
OsString::from(object_type.to_string().to_lowercase()) == value.to_ascii_lowercase()
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
let mut err = clap::Error::new(ErrorKind::ValueValidation).with_cmd(cmd);
|
||||
if let Some(arg) = arg {
|
||||
err.insert(
|
||||
ContextKind::InvalidArg,
|
||||
ContextValue::String(arg.to_string()),
|
||||
);
|
||||
}
|
||||
err.insert(
|
||||
ContextKind::InvalidValue,
|
||||
ContextValue::String(value.to_string_lossy().to_string()),
|
||||
);
|
||||
err.insert(
|
||||
ContextKind::SuggestedValue,
|
||||
ContextValue::Strings(
|
||||
ObjectType::iter()
|
||||
.map(|object_type| object_type.to_string())
|
||||
.collect::<Vec<String>>(),
|
||||
),
|
||||
);
|
||||
err
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
pub(crate) mod export_key;
|
||||
mod get_key_uid;
|
||||
pub(crate) mod import_key;
|
||||
mod locate;
|
||||
pub mod utils;
|
||||
|
||||
mod wrap_key;
|
||||
|
||||
mod unwrap_key;
|
||||
|
||||
pub use export_key::{ExportKeyAction, ExportKeyFormat};
|
||||
pub(crate) use get_key_uid::get_key_uid;
|
||||
pub use import_key::ImportKeyAction;
|
||||
pub use locate::LocateObjectsAction;
|
||||
pub use unwrap_key::UnwrapKeyAction;
|
||||
pub use wrap_key::WrapKeyAction;
|
||||
|
||||
/// The size of a symmetric wrapping key in bytes derived from a password
|
||||
pub const SYMMETRIC_WRAPPING_KEY_SIZE: usize = 32;
|
|
@ -1,128 +0,0 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
use clap::Parser;
|
||||
use cosmian_kms_client::{
|
||||
cosmian_kmip::kmip_2_1::kmip_types::CryptographicAlgorithm, export_object,
|
||||
kmip_2_1::requests::create_symmetric_key_kmip_object, read_object_from_json_ttlv_file,
|
||||
write_kmip_object_to_file, ExportObjectParams, KmsClient,
|
||||
};
|
||||
use cosmian_kms_crypto::crypto::wrap::unwrap_key_block;
|
||||
use tracing::trace;
|
||||
|
||||
use crate::{
|
||||
actions::console,
|
||||
cli_bail,
|
||||
error::result::{CliResult, CliResultHelper},
|
||||
};
|
||||
|
||||
/// Locally unwrap a key in KMIP JSON TTLV format.
|
||||
///
|
||||
/// The key can be unwrapped using either:
|
||||
/// - a password derived into a symmetric key using Argon2
|
||||
/// - symmetric key bytes in base64
|
||||
/// - a key in the KMS (which will be exported first)
|
||||
/// - a key in a KMIP JSON TTLV file
|
||||
///
|
||||
/// For the latter 2 cases, the key may be a symmetric key,
|
||||
/// and RFC 5649 will be used, or a curve 25519 private key
|
||||
/// and ECIES will be used.
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(verbatim_doc_comment)]
|
||||
pub struct UnwrapKeyAction {
|
||||
/// The KMIP JSON TTLV input key file to unwrap
|
||||
#[clap(required = true)]
|
||||
key_file_in: PathBuf,
|
||||
|
||||
/// The KMIP JSON output file. When not specified the input file is overwritten.
|
||||
#[clap(required = false)]
|
||||
key_file_out: Option<PathBuf>,
|
||||
|
||||
/// A symmetric key as a base 64 string to unwrap the imported key.
|
||||
#[clap(
|
||||
long = "unwrap-key-b64",
|
||||
short = 'k',
|
||||
required = false,
|
||||
group = "unwrap"
|
||||
)]
|
||||
unwrap_key_b64: Option<String>,
|
||||
|
||||
/// The id of a unwrapping key in the KMS that will be exported and used to unwrap the key.
|
||||
#[clap(
|
||||
long = "unwrap-key-id",
|
||||
short = 'i',
|
||||
required = false,
|
||||
group = "unwrap"
|
||||
)]
|
||||
unwrap_key_id: Option<String>,
|
||||
|
||||
/// A unwrapping key in a KMIP JSON TTLV file used to unwrap the key.
|
||||
#[clap(
|
||||
long = "unwrap-key-file",
|
||||
short = 'f',
|
||||
required = false,
|
||||
group = "unwrap"
|
||||
)]
|
||||
unwrap_key_file: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl UnwrapKeyAction {
|
||||
/// Export a key from the KMS
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function can return an error if:
|
||||
///
|
||||
/// - The key file cannot be read.
|
||||
/// - The unwrap key fails to decode from base64.
|
||||
/// - The unwrapping key fails to be created.
|
||||
/// - The unwrapping key fails to unwrap the key.
|
||||
/// - The output file fails to be written.
|
||||
/// - The console output fails to be written.
|
||||
///
|
||||
pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
|
||||
// read the key file
|
||||
let mut object = read_object_from_json_ttlv_file(&self.key_file_in)?;
|
||||
|
||||
// cache the object type
|
||||
let object_type = object.object_type();
|
||||
|
||||
// if the key must be unwrapped, prepare the unwrapping key
|
||||
let unwrapping_key = if let Some(b64) = &self.unwrap_key_b64 {
|
||||
trace!("unwrap using a base64 encoded key: {b64}");
|
||||
let key_bytes = general_purpose::STANDARD
|
||||
.decode(b64)
|
||||
.with_context(|| "failed decoding the unwrap key")?;
|
||||
create_symmetric_key_kmip_object(&key_bytes, CryptographicAlgorithm::AES, false)?
|
||||
} else if let Some(key_id) = &self.unwrap_key_id {
|
||||
trace!("unwrap using the KMS server with the unique identifier of the unwrapping key");
|
||||
export_object(kms_rest_client, key_id, ExportObjectParams::default())
|
||||
.await?
|
||||
.1
|
||||
} else if let Some(key_file) = &self.unwrap_key_file {
|
||||
trace!("unwrap using a key file path");
|
||||
read_object_from_json_ttlv_file(key_file)?
|
||||
} else {
|
||||
cli_bail!("one of the unwrapping options must be specified");
|
||||
};
|
||||
|
||||
unwrap_key_block(object.key_block_mut()?, &unwrapping_key)?;
|
||||
|
||||
// set the output file path to the input file path if not specified
|
||||
let output_file = self
|
||||
.key_file_out
|
||||
.as_ref()
|
||||
.unwrap_or(&self.key_file_in)
|
||||
.clone();
|
||||
|
||||
write_kmip_object_to_file(&object, &output_file)?;
|
||||
|
||||
let stdout = format!(
|
||||
"The key of type {:?} in file {:?} was unwrapped in file: {:?}",
|
||||
object_type, self.key_file_in, &output_file
|
||||
);
|
||||
console::Stdout::new(&stdout).write()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
use cosmian_kms_client::{
|
||||
cosmian_kmip::kmip_2_1::{kmip_operations::Destroy, kmip_types::UniqueIdentifier},
|
||||
KmsClient,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
actions::console,
|
||||
cli_bail,
|
||||
error::result::{CliResult, CliResultHelper},
|
||||
};
|
||||
|
||||
/// Destroy a cryptographic object on the KMS
|
||||
/// # Arguments
|
||||
/// * `kms_rest_client` - The KMS client
|
||||
/// * `uid` - The object id
|
||||
/// * `remove` - If the object should be removed from the database
|
||||
/// # Returns
|
||||
/// * `CliResult<()>` - The result of the operation
|
||||
pub(crate) async fn destroy(kms_rest_client: &KmsClient, uid: &str, remove: bool) -> CliResult<()> {
|
||||
// Create the kmip query
|
||||
let destroy_query = Destroy {
|
||||
unique_identifier: Some(UniqueIdentifier::TextString(uid.to_string())),
|
||||
remove,
|
||||
};
|
||||
|
||||
// Query the KMS with your kmip data
|
||||
let destroy_response = kms_rest_client
|
||||
.destroy(destroy_query)
|
||||
.await
|
||||
.with_context(|| format!("destroying the object {} failed", &uid))?;
|
||||
|
||||
if uid
|
||||
== destroy_response
|
||||
.unique_identifier
|
||||
.as_str()
|
||||
.context("The server did not return the object uid as a string")?
|
||||
{
|
||||
let verb = if remove { "removed" } else { "destroyed" };
|
||||
let mut stdout = console::Stdout::new(format!("Successfully {verb} the object.").as_str());
|
||||
stdout.set_unique_identifier(uid);
|
||||
stdout.write()?;
|
||||
Ok(())
|
||||
} else {
|
||||
cli_bail!("Something went wrong when destroying the object.")
|
||||
}
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
use cosmian_kms_client::kmip_2_1::kmip_types::CryptographicUsageMask;
|
||||
use serde::Deserialize;
|
||||
use strum::EnumIter;
|
||||
|
||||
#[derive(clap::ValueEnum, Deserialize, Debug, Clone, EnumIter, PartialEq, Eq)]
|
||||
pub enum KeyUsage {
|
||||
Sign,
|
||||
Verify,
|
||||
Encrypt,
|
||||
Decrypt,
|
||||
WrapKey,
|
||||
UnwrapKey,
|
||||
MACGenerate,
|
||||
MACVerify,
|
||||
DeriveKey,
|
||||
KeyAgreement,
|
||||
CertificateSign,
|
||||
CRLSign,
|
||||
Authenticate,
|
||||
Unrestricted,
|
||||
}
|
||||
|
||||
impl From<KeyUsage> for String {
|
||||
fn from(key_usage: KeyUsage) -> Self {
|
||||
match key_usage {
|
||||
KeyUsage::Sign => "sign",
|
||||
KeyUsage::Verify => "verify",
|
||||
KeyUsage::Encrypt => "encrypt",
|
||||
KeyUsage::Decrypt => "decrypt",
|
||||
KeyUsage::WrapKey => "wrap-key",
|
||||
KeyUsage::UnwrapKey => "unwrap-key",
|
||||
KeyUsage::MACGenerate => "mac-generate",
|
||||
KeyUsage::MACVerify => "mac-verify",
|
||||
KeyUsage::DeriveKey => "derive-key",
|
||||
KeyUsage::KeyAgreement => "key-agreement",
|
||||
KeyUsage::CertificateSign => "certificate-sign",
|
||||
KeyUsage::CRLSign => "crl-sign",
|
||||
KeyUsage::Authenticate => "authenticate",
|
||||
KeyUsage::Unrestricted => "unrestricted",
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn build_usage_mask_from_key_usage(
|
||||
key_usage_vec: &[KeyUsage],
|
||||
) -> Option<CryptographicUsageMask> {
|
||||
let mut flags = 0;
|
||||
for key_usage in key_usage_vec {
|
||||
flags |= match key_usage {
|
||||
KeyUsage::Sign => CryptographicUsageMask::Sign,
|
||||
KeyUsage::Verify => CryptographicUsageMask::Verify,
|
||||
KeyUsage::Encrypt => CryptographicUsageMask::Encrypt,
|
||||
KeyUsage::Decrypt => CryptographicUsageMask::Decrypt,
|
||||
KeyUsage::WrapKey => CryptographicUsageMask::WrapKey,
|
||||
KeyUsage::UnwrapKey => CryptographicUsageMask::UnwrapKey,
|
||||
KeyUsage::MACGenerate => CryptographicUsageMask::MACGenerate,
|
||||
KeyUsage::MACVerify => CryptographicUsageMask::MACVerify,
|
||||
KeyUsage::DeriveKey => CryptographicUsageMask::DeriveKey,
|
||||
KeyUsage::KeyAgreement => CryptographicUsageMask::KeyAgreement,
|
||||
KeyUsage::CertificateSign => CryptographicUsageMask::CertificateSign,
|
||||
KeyUsage::CRLSign => CryptographicUsageMask::CRLSign,
|
||||
KeyUsage::Authenticate => CryptographicUsageMask::Authenticate,
|
||||
KeyUsage::Unrestricted => CryptographicUsageMask::Unrestricted,
|
||||
}
|
||||
.bits();
|
||||
}
|
||||
CryptographicUsageMask::from_bits(flags)
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
pub(crate) use destroy_utils::destroy;
|
||||
pub(crate) use key_usage::{build_usage_mask_from_key_usage, KeyUsage};
|
||||
pub(crate) use revoke_utils::revoke;
|
||||
|
||||
mod destroy_utils;
|
||||
mod key_usage;
|
||||
mod revoke_utils;
|
|
@ -1,43 +0,0 @@
|
|||
use cosmian_kms_client::{
|
||||
cosmian_kmip::kmip_2_1::kmip_types::RevocationReason,
|
||||
kmip_2_1::requests::build_revoke_key_request, KmsClient,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
actions::console,
|
||||
cli_bail,
|
||||
error::result::{CliResult, CliResultHelper},
|
||||
};
|
||||
|
||||
pub(crate) async fn revoke(
|
||||
kms_rest_client: &KmsClient,
|
||||
key_id: &str,
|
||||
revocation_reason: &str,
|
||||
) -> CliResult<()> {
|
||||
// Create the kmip query
|
||||
let revoke_query = build_revoke_key_request(
|
||||
key_id,
|
||||
RevocationReason::TextString(revocation_reason.to_string()),
|
||||
)?;
|
||||
|
||||
// Query the KMS with your kmip data
|
||||
let revoke_response = kms_rest_client
|
||||
.revoke(revoke_query)
|
||||
.await
|
||||
.with_context(|| format!("revocation of key {} failed", &key_id))?;
|
||||
|
||||
if key_id
|
||||
== revoke_response
|
||||
.unique_identifier
|
||||
.as_str()
|
||||
.context("the server did not return a key id as a string")?
|
||||
{
|
||||
let mut stdout = console::Stdout::new("Successfully revoked the object.");
|
||||
stdout.set_unique_identifier(key_id);
|
||||
stdout.write()?;
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
cli_bail!("Something went wrong when revoking the key.")
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue