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:
Manuthor 2025-03-24 22:13:14 +01:00 committed by GitHub
parent 5a826b465d
commit c6faaf751f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
329 changed files with 711 additions and 31990 deletions

View file

@ -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"

View file

@ -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
View 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

View file

@ -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'

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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' }}

View file

@ -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
View file

@ -0,0 +1,3 @@
[submodule "cli"]
path = cli
url = https://github.com/Cosmian/cli.git

View file

@ -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]

View file

@ -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

View file

@ -6,7 +6,7 @@
"chacha",
"ciphertext",
"ciphertexts",
"ckms",
"cosmian",
"cleartext",
"cloudproof",
"cosmian",

465
Cargo.lock generated
View file

@ -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"

View file

@ -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 }

View file

@ -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
#

View file

@ -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
```

View file

@ -2,3 +2,4 @@
GOST = "GOST"
vas = "vas"
passin = "passin"
typ = "typ"

1
cli Submodule

@ -0,0 +1 @@
Subproject commit 00b6d82bc6c751c314e3a74d25280d8e0e7d3313

View file

@ -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};

View file

@ -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"] }

View file

@ -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
```

View file

@ -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(())
}
}

View file

@ -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(())
}
}

View file

@ -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(())
}
}

View file

@ -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,
}
}
}

View file

@ -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(())
}
}

View file

@ -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)
}

View file

@ -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);
}

View file

@ -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(())
}
}

View file

@ -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
}
}

View file

@ -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(())
}
}

View file

@ -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(())
}
}

View file

@ -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);
}
}

View file

@ -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,
}
}
}

View file

@ -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
}
}

View file

@ -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(())
}
}

View file

@ -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(())
}
}

View file

@ -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(())
}
}

View file

@ -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(())
}
}

View file

@ -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(())
}
}

View file

@ -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()
}
}

View file

@ -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()
}
}

View file

@ -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
}
}

View file

@ -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(())
}
}

View file

@ -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()
}
}

View file

@ -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
}
}

View file

@ -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(())
}
}

View file

@ -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(())
}
}

View file

@ -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(())
}
}

View file

@ -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(())
}
}

View file

@ -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
}
}

View file

@ -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(())
}
}

View file

@ -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
}
}

View file

@ -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(())
}
}

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -1,5 +0,0 @@
mod client_builder;
mod error;
mod token;
pub(crate) use client_builder::GmailClient;
pub(crate) use error::GoogleApiError;

View file

@ -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)
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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,
}
}
}

View file

@ -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
}
}

View file

@ -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(())
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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,
}
}
}

View file

@ -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
}
}

View file

@ -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(())
}
}

View file

@ -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(())
}
}

View file

@ -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";

View file

@ -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(())
}
}

View file

@ -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(())
}
}

View file

@ -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(())
}
}

View file

@ -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;

View file

@ -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(())
}
}

View file

@ -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(())
}
}

View file

@ -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(())
}
}

View file

@ -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(),
))
}
}

View file

@ -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
}
}

View file

@ -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(())
}
}

View file

@ -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
}
}

View file

@ -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,
}
}
}

View file

@ -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(())
}
}

View file

@ -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)
}

View file

@ -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,
},
})
}

View file

@ -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
})
}
}

View file

@ -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;

View file

@ -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(())
}
}

View file

@ -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.")
}
}

View file

@ -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)
}

View file

@ -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;

View file

@ -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