From dd82c26f879d98e7e64540f5f9a1b8e4472aa600 Mon Sep 17 00:00:00 2001 From: Steffen Eiden Date: Thu, 4 May 2023 14:28:01 +0200 Subject: [PATCH] rust: Add tool to manage UV-secrets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add `pvsecret` a tool to create, add, list, and delete Ultravisor secrets. `pvsecret` uses the functionality from the pv-crate to provide an command line tool to manage the secrets. Add a new target group PV_TARGETS in rust/Makefile that additionally requires openssl and libcurl as pv with the feature "request" uses openssl and libcurl fearures. Acked-by: Jan Höppner Acked-by: Marc Hartmayer Signed-off-by: Steffen Eiden [hoeppner@linux.ibm.com: Adapt man pages and help output] Signed-off-by: Jan Höppner --- CHANGELOG.md | 1 + rust/Makefile | 53 +- rust/pvsecret/.gitignore | 1 + rust/pvsecret/Cargo.lock | 728 ++++++++++++++++++ rust/pvsecret/Cargo.toml | 18 + rust/pvsecret/README.md | 284 +++++++ rust/pvsecret/man/pvsecret-add.1 | 33 + .../man/pvsecret-create-association.1 | 56 ++ rust/pvsecret/man/pvsecret-create-meta.1 | 24 + rust/pvsecret/man/pvsecret-create.1 | 154 ++++ rust/pvsecret/man/pvsecret-list.1 | 47 ++ rust/pvsecret/man/pvsecret-lock.1 | 26 + rust/pvsecret/man/pvsecret-version.1 | 25 + rust/pvsecret/man/pvsecret.1 | 103 +++ rust/pvsecret/src/cli.rs | 322 ++++++++ rust/pvsecret/src/cmd.rs | 22 + rust/pvsecret/src/cmd/add.rs | 19 + rust/pvsecret/src/cmd/create.rs | 240 ++++++ rust/pvsecret/src/cmd/list.rs | 44 ++ rust/pvsecret/src/cmd/lock.rs | 14 + rust/pvsecret/src/main.rs | 125 +++ 21 files changed, 2338 insertions(+), 1 deletion(-) create mode 100644 rust/pvsecret/.gitignore create mode 100644 rust/pvsecret/Cargo.lock create mode 100644 rust/pvsecret/Cargo.toml create mode 100644 rust/pvsecret/README.md create mode 100644 rust/pvsecret/man/pvsecret-add.1 create mode 100644 rust/pvsecret/man/pvsecret-create-association.1 create mode 100644 rust/pvsecret/man/pvsecret-create-meta.1 create mode 100644 rust/pvsecret/man/pvsecret-create.1 create mode 100644 rust/pvsecret/man/pvsecret-list.1 create mode 100644 rust/pvsecret/man/pvsecret-lock.1 create mode 100644 rust/pvsecret/man/pvsecret-version.1 create mode 100644 rust/pvsecret/man/pvsecret.1 create mode 100644 rust/pvsecret/src/cli.rs create mode 100644 rust/pvsecret/src/cmd.rs create mode 100644 rust/pvsecret/src/cmd/add.rs create mode 100644 rust/pvsecret/src/cmd/create.rs create mode 100644 rust/pvsecret/src/cmd/list.rs create mode 100644 rust/pvsecret/src/cmd/lock.rs create mode 100644 rust/pvsecret/src/main.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 98a90ac3..ef7cde3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Release history for s390-tools (MIT version) Add new tools / libraries: - rust/pv: Library for pv tools written in rust + - rust/pvsecret: Tool to manage UV-secrets Changes of existing tools: - genprotimg: add support for add-secret requests diff --git a/rust/Makefile b/rust/Makefile index 60e7e4d8..d4a1c5f9 100644 --- a/rust/Makefile +++ b/rust/Makefile @@ -1,10 +1,14 @@ include ../common.mak SHELL := /bin/bash HAVE_CARGO ?= 1 +HAVE_OPENSSL ?= 1 +HAVE_LIBCURL ?= 1 INSTALL_TARGETS := skip-build BUILD_TARGETS := skip-build +PV_BUILD_TARGETS := skip-pv-build CARGO_TARGETS := +PV_TARGETS := CARGO_TEST_TARGETS := ifneq (${HAVE_CARGO},0) @@ -14,7 +18,17 @@ ifneq (${HAVE_CARGO},0) INSTALL_TARGETS := install-rust-tools install-man CARGO_TEST_TARGETS = $(addsuffix, _test, $(CARGO_TARGETS)) -endif +ifneq (${HAVE_OPENSSL},0) +ifneq (${HAVE_LIBCURL},0) + PV_TARGETS := pvsecret + + PV_BUILD_TARGETS = $(PV_TARGETS) + CARGO_TEST_TARGETS += $(addsuffix, _test, $(PV_TARGETS)) pv +endif #LIBCURL +endif #OPENSSL +endif #CARGO + +BUILD_TARGETS += $(PV_BUILD_TARGETS) # build release targets by default ifeq ("${D}","0") @@ -35,9 +49,16 @@ $(CARGO_TEST_TARGETS): .check-cargo .no-cross-compile $(CARGO_TEST) --manifest-path=$@/Cargo.toml --all-features $(CARGOFLAGS) .PHONY: $(CARGO_TEST_TARGETS) +$(PV_TARGETS): .check-cargo .no-cross-compile .check-dep-pvtools + $(CARGO_BUILD) --manifest-path=$@/Cargo.toml $(ALL_CARGOFLAGS) +.PHONY: $(PV_TARGETS) + skip-build: echo " SKIP rust-tools due to unresolved dependencies" +skip-pv-build: + echo " SKIP rust-pv-tools due to unresolved dependencies" + all: $(BUILD_TARGETS) install: $(INSTALL_TARGETS) @@ -47,6 +68,8 @@ print-rust-targets: clean: $(foreach target,$(CARGO_TARGETS),\ $(CARGO_CLEAN) --manifest-path=$(target)/Cargo.toml ${ALL_CARGOFLAGS} ;) + $(foreach target,$(PV_TARGETS),\ + $(CARGO_CLEAN) --manifest-path=$(target)/Cargo.toml ${CARGOFLAGS} ;) $(RM) -- .check-dep-pvtools .detect-openssl.dep.c .check-cargo rust-test: .check-cargo .no-cross-compile @@ -57,10 +80,14 @@ install-rust-tools: $(BUILD_TARGETS) $(INSTALL) -d -m 755 $(DESTDIR)$(USRBINDIR) $(foreach target,$(CARGO_TARGETS),\ $(INSTALL) $(target)/target/release/$(target) $(DESTDIR)$(USRBINDIR);) + $(foreach target,$(PV_TARGETS),\ + $(INSTALL) $(target)/target/release/$(target) $(DESTDIR)$(USRBINDIR);) install-man: $(foreach target,$(CARGO_TARGETS),\ $(INSTALL) -m 644 $(target)/man/*.1 -t $(DESTDIR)$(MANDIR)/man1;) + $(foreach target,$(PV_TARGETS),\ + $(INSTALL) -m 644 $(target)/man/*.1 -t $(DESTDIR)$(MANDIR)/man1;) .PHONY: all install clean skip-build install-rust-tools print-rust-targets install-man rust-test @@ -79,3 +106,27 @@ ifneq ($(HOST_ARCH), $(BUILD_ARCH)) $(error Cross compiling is not supported for rust code. Specify HAVE_CARGO=0 to disable rust compilation) endif .PHONY: .no-cross-compile + +.detect-openssl.dep.c: + echo "#include " > $@ + echo "#if OPENSSL_VERSION_NUMBER < 0x10101000L" >> $@ + echo " #error openssl version 1.1.1 is required" >> $@ + echo "#endif" >> $@ + echo "static void __attribute__((unused)) test(void) {" >> $@ + echo " EVP_MD_CTX *ctx = EVP_MD_CTX_new();" >> $@ + echo " EVP_MD_CTX_free(ctx);" >> $@ + echo "}" >> $@ + +.check-dep-pvtools: .detect-openssl.dep.c + $(call check_dep, \ + "$(BIN_PROGRAM)", \ + $^, \ + "openssl-devel / libssl-dev version >= 1.1.1", \ + "HAVE_OPENSSL=0", \ + "-I.") + $(call check_dep, \ + "$(BIN_PROGRAM)", \ + "curl/curl.h", \ + "libcurl-devel", \ + "HAVE_LIBCURL=0") + touch $@ diff --git a/rust/pvsecret/.gitignore b/rust/pvsecret/.gitignore new file mode 100644 index 00000000..9da4a887 --- /dev/null +++ b/rust/pvsecret/.gitignore @@ -0,0 +1 @@ +!Cargo.lock diff --git a/rust/pvsecret/Cargo.lock b/rust/pvsecret/Cargo.lock new file mode 100644 index 00000000..1db32c29 --- /dev/null +++ b/rust/pvsecret/Cargo.lock @@ -0,0 +1,728 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anstream" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" + +[[package]] +name = "anstyle-parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", +] + +[[package]] +name = "anyhow" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80672091db20273a15cf9fdd4e47ed43b5091ec9841bf4c6145c9dfbbcae09ed" +dependencies = [ + "clap_builder", + "clap_derive", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1458a1df40e1e2afebb7ab60ce55c1fa8f431146205aa5f4887e0b111c27636" +dependencies = [ + "anstream", + "anstyle", + "bitflags", + "clap_lex", + "strsim", + "terminal_size", +] + +[[package]] +name = "clap_derive" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "clap_lex" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "curl" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22" +dependencies = [ + "curl-sys", + "libc", + "openssl-probe", + "openssl-sys", + "schannel", + "socket2", + "winapi", +] + +[[package]] +name = "curl-sys" +version = "0.4.63+curl-8.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aeb0fef7046022a1e2ad67a004978f0e3cacb9e3123dc62ce768f92197b771dc" +dependencies = [ + "cc", + "libc", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", + "winapi", +] + +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "is-terminal" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +dependencies = [ + "hermit-abi", + "io-lifetimes", + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "libc" +version = "0.2.146" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" + +[[package]] +name = "libz-sys" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ee889ecc9568871456d42f603d6a0ce59ff328d291063a45cbdf0036baf6db" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "log" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "openssl" +version = "0.10.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b3f656a17a6cbc115b5c7a40c616947d213ba182135b014d6051b73ab6f019" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2ce0f250f34a308dcfdbb351f511359857d4ed2134ba715a4eadd46e1ffd617" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "openssl_extensions" +version = "0.1.0" +dependencies = [ + "foreign-types", + "libc", + "log", + "openssl", + "openssl-sys", +] + +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + +[[package]] +name = "proc-macro2" +version = "1.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pv" +version = "0.9.0" +dependencies = [ + "byteorder", + "cfg-if", + "clap", + "curl", + "libc", + "log", + "openssl", + "openssl_extensions", + "serde", + "thiserror", + "zerocopy", +] + +[[package]] +name = "pvsecret" +version = "0.9.0" +dependencies = [ + "anyhow", + "clap", + "log", + "pv", + "serde_yaml", + "utils", +] + +[[package]] +name = "quote" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustix" +version = "0.37.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + +[[package]] +name = "schannel" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +dependencies = [ + "windows-sys 0.42.0", +] + +[[package]] +name = "serde" +version = "1.0.164" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.164" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "serde_yaml" +version = "0.9.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9d684e3ec7de3bf5466b32bd75303ac16f0736426e5a4e0d6e489559ce1249c" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "terminal_size" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" +dependencies = [ + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "thiserror" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "unicode-ident" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1865806a559042e51ab5414598446a5871b561d21b6764f2eabb0dd481d880a6" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "utils" +version = "0.1.0" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "zerocopy" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "332f188cc1bcf1fe1064b8c58d150f497e697f49774aa846f2dc949d9a25f236" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6505e6815af7de1746a08f69c69606bb45695a17149517680f3b2149713b19a3" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] diff --git a/rust/pvsecret/Cargo.toml b/rust/pvsecret/Cargo.toml new file mode 100644 index 00000000..da4deab3 --- /dev/null +++ b/rust/pvsecret/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "pvsecret" +version = "0.9.0" +edition = "2021" +license = "MIT" + +[dependencies] +anyhow = { version = "1", features = ["std"] } +clap = { version ="4", features = ["derive", "wrap_help"]} +log = { version = "0.4", features = ["std", "release_max_level_debug"] } +serde_yaml = "0.9" + +pv = { path = "../pv", features = ["uvsecret", "request"] } +utils = { path = "../utils" } + +[profile.release] +lto = true +panic = "abort" # release builds now do not clean up stack after panics. .1 Mb diff --git a/rust/pvsecret/README.md b/rust/pvsecret/README.md new file mode 100644 index 00000000..e1808444 --- /dev/null +++ b/rust/pvsecret/README.md @@ -0,0 +1,284 @@ + +# pvsecret +## Description +Use **pvsecret** to manage secrets for IBM Secure Execution guests. **pvsecret** +can **create** add-secret requests on any architecture. On s390x systems, use +**pvsecret** to **add** the secrets to the ultravisor secret store, **list** all +secrets in the secret store, or lock the secret store to prevent any +modifications in the future. + +The ultravisor secret store stores secrets for the IBM Secure Execution guest. +The secret store is cleared on guest reboot. + +Create requests only on trusted systems that are not the IBM Secure Execution +guest where you want to inject the secrets. This approach prevents the secrets +from being in cleartext on the guest. For extra safety, do an attestation with +**pvattest** of your guest beforehand, and include the configuration UID in the +secret request using **--cuid**. Refer to **pvsecret-add**(1) for more +information. For all certificates, revocation lists, and host-key documents, +both the PEM and DER input formats are supported. + + +## Synopsis +`pvsecret [OPTIONS] ` +## Commands Overview + - **create** + Create a new add-secret request + - **add** + Repeat an add-secret request (s390x only) + - **lock** + Lock the secret-store (s390x only) + - **list** + List all ultravisor secrets (s390x only) +## Options + +`-v`, `--verbose` +
    + Provide more detailed output +
+ + +`--version` +
    + Print version information and exit +
+ + +## pvsecret create +### Description +Create add-secret requests for IBM Secure Execution guests. Only create these +requests in a trusted environment, such as your workstation. The **pvattest +create** command creates a randomly generated key to protect the request. The +generated requests can then be added on an IBM Secure Execution guest using +**pvsecret add**. The guest can then use the secrets with the use case +depending on the secret type. + +Such a request is bound to a specific IBM Secure Execution image specified with +**--hdr**. Optionally, the request can be bound to a specific instance when +bound to the Configuration Unique ID from **pvattest** using **--cuid** + + +### Synopsis +`pvsecret create [OPTIONS] --host-key-document --hdr --output <--no-verify|--cert > ` +### Commands Overview + - **meta** + Use a meta secret to carry flags to the ultravisor without having to provide + an actual secret value. Meta secrets do not appear in the list of secrets + - **association** + Use an association secret to connect a trusted I/O device to a guest. The + `pvapconfig` tool provides more information about association secrets + +### Options + +`-k`, `--host-key-document ` +
    + Use FILE as a host-key document. Can be specified multiple times and must be + used at least once. +
+ +`--no-verify` +
    + Disable the host-key document verification. Does not require the host-key + documents to be valid. Do not use for a production request unless you + verified the host-key document beforehand. +
+ +`-C`, `--cert ` +
    + Use FILE as a certificate to verify the host key or keys. The certificates + are used to establish a chain of trust for the verification of the host-key + documents. Specify this option twice to specify the IBM Z signing key and + the intermediate CA certificate (signed by the root CA). +
+ +`--crl ` +
    + Use FILE as a certificate revocation list. The list is used to check whether + a certificate of the chain of trust is revoked. Specify this option multiple + times to use multiple CRLs. +
+ +`--offline` +
    + Make no attempt to download CRLs +
+ +`--root-ca ` +
    + Use FILE as the root-CA certificate for the verification. If omitted, the + system-wide root CAs installed on the system are used. Use this only if you + trust the specified certificate. +
+ +`--hdr ` +
    + Specifies the header of the guest image. Can be an IBM Secure Execution + image created by genprotimg or an extracted IBM Secure Execution header. The + header must start at a page boundary. +
+ +`-f`, `--force` +
    + Force the generation of add-secret requests on IBM Secure Execution guests. + If the program detects that it is running on an IBM Secure Execution guest, + it denies the generation of add-secret requests. The force flag overwrites + this behavior. +
+ +`-o`, `--output ` +
    + Write the generated request to FILE +
+ +`--extension-secret ` +
    + Use the content of FILE as an extension secret. The file must be exactly 32 + bytes long. If this request is the first, all subsequent requests must have + the same extension secret. Only makes sense if bit 1 of the secret control + flags of the IBM Secure Execution header is 0. Otherwise the ultravisor + rejects the request. +
+ +`--cck ` +
    + Use the content of FILE as the customer-communication key (CCK) to derive + the extension secret. The file must contain exactly 32 bytes of data. If the + target guest was started with bit 1 of the secret control flag set, the + ultravisor also derives the secret from the CCK. Otherwise, the ultravisor + interprets the extension secret as a normal one. This still works if you + use the same CCK for all requests. +
+ + +`--cuid-hex ` +
    + Use HEXSTRING as the Configuration Unique ID. Must be a hex 128-bit unsigned + big endian number string. Leading zeros must be provided. If specified, the + value must match with the Config-UID from the attestation result of that + guest. If not specified, the CUID will be ignored by the ultravisor during + the verification of the request. +
+ +`--cuid ` +
    + Use the content of FILE as the Configuration Unique ID. The file must + contain exactly 128 bit of data, a hex string, or a yaml with a `cuid` + entry. If specified, the value must match the Config-UID from the + attestation result of that guest. If not specified, the CUID will be ignored + by the Ultravisor during the verification of the request. +
+ +`--flags ` +
    + Flags for the add-secret request. + + Possible values: + - disable-dump: Disables host-initiated dumping for the target guest + instance +
+ + +### pvsecret create meta +#### Description +Use a meta secret to carry flags to the ultravisor without having to provide an +actual secret value. Meta secrets do not appear in the list of secrets. + + +#### Synopsis +`pvsecret create meta` + +### pvsecret create association +#### Description +Use an association secret to connect a trusted I/O device to a guest. The +`pvapconfig` tool provides more information about association secrets. + +#### Synopsis +`pvsecret create association [OPTIONS] ` +#### Arguments + +`` +
    + String to identify the new secret. The actual secret is set with + --input-secret. The name is saved in `NAME.yaml` with white-spaces + mapped to `_`. +
+ + +#### Options + +`--stdout` +
    + Print the hashed name to stdout. The hashed name will not be written to + `NAME.yaml` +
+ + +`--input-secret ` +
    + Path from which to read the plaintext secret. Uses a random secret if not + specified. +
+ + +`--output-secret ` +
    + Save the generated secret as plaintext in FILE. The generated secret can be + used to generate add-secret requests for a different guest with the same + secret using --input-secret. Destroy the secret when it is not used + anymore. +
+ + +## pvsecret add +### Description +Perform an add-secret request using a previously generated add-secret request. +Only available on s390x. + + +### Synopsis +`pvsecret add ` +### Arguments + +`` +
    + Specify the request to be sent +
+ + +## pvsecret lock +### Description +Lock the secret store (s390x only). After this command executed successfully, +all add-secret requests will fail. Only available on s390x. + + +### Synopsis +`pvsecret lock` + +## pvsecret list +### Description +Lists the IDs of all non-null secrets currently stored in the ultravisor for the +currently running IBM Secure Execution guest. Only available on s390x. + +### Synopsis +`pvsecret list [OPTIONS] [FILE]` +### Arguments + +`` +
    + Store the result in FILE. Default value: '-' +
+ + +### Options + +`--format ` + Define the output format of the list. Default value: 'human' + + Possible values: + - human: Human-focused, non-parsable output format + - yaml: Use yaml format + - bin: Use the format the ultravisor uses to pass the list diff --git a/rust/pvsecret/man/pvsecret-add.1 b/rust/pvsecret/man/pvsecret-add.1 new file mode 100644 index 00000000..812ba08f --- /dev/null +++ b/rust/pvsecret/man/pvsecret-add.1 @@ -0,0 +1,33 @@ +.\" Copyright 2023 IBM Corp. +.\" s390-tools is free software; you can redistribute it and/or modify +.\" it under the terms of the MIT license. See LICENSE for details. +.\" + +.TH pvsecret-add 1 "2023-07-28" "s390-tools" "UV-Secret Manual" +.nh +.ad l +.SH NAME +\fBpvsecret add\fP - Repeat an add-secret request (s390x only) +\fB +.SH SYNOPSIS +.nf +.fam C +pvsecret add +.fam C +.fi +.SH DESCRIPTION +Perform an add-secret request using a previously generated add-secret request. +Only available on s390x. + +.SH OPTIONS +.PP + +.RS 4 +Specify the request to be sent. +.RE +.RE + + +.SH "SEE ALSO" +.sp +\fBpvsecret\fR(1) diff --git a/rust/pvsecret/man/pvsecret-create-association.1 b/rust/pvsecret/man/pvsecret-create-association.1 new file mode 100644 index 00000000..9657daf4 --- /dev/null +++ b/rust/pvsecret/man/pvsecret-create-association.1 @@ -0,0 +1,56 @@ +.\" Copyright 2023 IBM Corp. +.\" s390-tools is free software; you can redistribute it and/or modify +.\" it under the terms of the MIT license. See LICENSE for details. +.\" + +.TH pvsecret-create-association 1 "2023-07-28" "s390-tools" "UV-Secret Manual" +.nh +.ad l +.SH NAME +\fBpvsecret create association\fP - Use an association secret to connect an I/O device to a guest +\fB +.SH SYNOPSIS +.nf +.fam C +pvsecret create association [OPTIONS] +.fam C +.fi +.SH DESCRIPTION +Use an association secret to connect a trusted I/O device to a guest. The +`pvapconfig` tool provides more information about association secrets. + +.SH OPTIONS +.PP + +.RS 4 +String to identify the new secret. The actual secret is set with +\fB--input-secret\fR. The name is saved in `NAME.yaml` with white-spaces mapped +to `_`. +.RE +.RE + +.PP +\-\-stdout +.RS 4 +Print the hashed name to stdout. The hashed name is not written to `NAME.yaml` +.RE +.RE +.PP +\-\-input-secret +.RS 4 +Path from which to read the plaintext secret. Uses a random secret if not +specified. +.RE +.RE +.PP +\-\-output-secret +.RS 4 +Save the generated secret as plaintext in FILE. The generated secret can be used +to generate add-secret requests for a different guest with the same secret using +\fB--input-secret\fR. Destroy the secret when it is not used anymore. +.RE +.RE + +.SH "SEE ALSO" +.sp +\fBpvsecret\fR(1) \fBpvsecret-create\fR(1) diff --git a/rust/pvsecret/man/pvsecret-create-meta.1 b/rust/pvsecret/man/pvsecret-create-meta.1 new file mode 100644 index 00000000..07b12997 --- /dev/null +++ b/rust/pvsecret/man/pvsecret-create-meta.1 @@ -0,0 +1,24 @@ +.\" Copyright 2023 IBM Corp. +.\" s390-tools is free software; you can redistribute it and/or modify +.\" it under the terms of the MIT license. See LICENSE for details. +.\" + +.TH pvsecret-create-meta 1 "2023-07-28" "s390-tools" "UV-Secret Manual" +.nh +.ad l +.SH NAME +\fBpvsecret create meta\fP - Use a meta secret to carry flags to the ultravisor +\fB +.SH SYNOPSIS +.nf +.fam C +pvsecret create meta +.fam C +.fi +.SH DESCRIPTION +Use a meta secret to carry flags to the ultravisor without having to provide an +actual secret value. Meta secrets do not appear in the list of secrets. + +.SH "SEE ALSO" +.sp +\fBpvsecret\fR(1) \fBpvsecret-create\fR(1) diff --git a/rust/pvsecret/man/pvsecret-create.1 b/rust/pvsecret/man/pvsecret-create.1 new file mode 100644 index 00000000..9f91051a --- /dev/null +++ b/rust/pvsecret/man/pvsecret-create.1 @@ -0,0 +1,154 @@ +.\" Copyright 2023 IBM Corp. +.\" s390-tools is free software; you can redistribute it and/or modify +.\" it under the terms of the MIT license. See LICENSE for details. +.\" + +.TH pvsecret-create 1 "2023-07-28" "s390-tools" "UV-Secret Manual" +.nh +.ad l +.SH NAME +\fBpvsecret create\fP - Create a new add-secret request +\fB +.SH SYNOPSIS +.nf +.fam C +pvsecret create [OPTIONS] --host-key-document --hdr --output <--no-verify|--cert > +.fam C +.fi +.SH DESCRIPTION +Create add-secret requests for IBM Secure Execution guests. Only create these +requests in a trusted environment, such as your workstation. The \fBpvattest +create\fR command creates a randomly generated key to protect the request. The +generated requests can then be added on an IBM Secure Execution guest using +\fBpvsecret add\fR. The guest can then use the secrets with the use case +depending on the secret type. + +Such a request is bound to a specific IBM Secure Execution image specified with +\fB--hdr\fR. Optionally, the request can be bound to a specific instance when +bound to the Configuration Unique ID from \fBpvattest\fR using \fB--cuid\fR +.SH OPTIONS +.PP +\-k, \-\-host-key-document +.RS 4 +Use FILE as a host-key document. Can be specified multiple times and must be +used at least once. +.RE +.RE +.PP +\-\-no-verify +.RS 4 +Disable the host-key document verification. Does not require the host-key +documents to be valid. Do not use for a production request unless you verified +the host-key document beforehand. +.RE +.RE +.PP +\-C, \-\-cert +.RS 4 +Use FILE as a certificate to verify the host key or keys. The certificates are +used to establish a chain of trust for the verification of the host-key +documents. Specify this option twice to specify the IBM Z signing key and the +intermediate CA certificate (signed by the root CA). +.RE +.RE +.PP +\-\-crl +.RS 4 +Use FILE as a certificate revocation list. The list is used to check whether a +certificate of the chain of trust is revoked. Specify this option multiple times +to use multiple CRLs. +.RE +.RE +.PP +\-\-offline +.RS 4 +Make no attempt to download CRLs +.RE +.RE +.PP +\-\-root-ca +.RS 4 +Use FILE as the root-CA certificate for the verification. If omitted, the +system-wide root CAs installed on the system are used. Use this only if you +trust the specified certificate. +.RE +.RE +.PP +\-\-hdr +.RS 4 +Specifies the header of the guest image. Can be an IBM Secure Execution image +created by genprotimg or an extracted IBM Secure Execution header. The header +must start at a page boundary. +.RE +.RE +.PP +\-f, \-\-force +.RS 4 +Force the generation of add-secret requests on IBM Secure Execution guests. If +the program detects that it is running on an IBM Secure Execution guest, it +denies the generation of add-secret requests. The force flag overwrites this +behavior. +.RE +.RE +.PP +\-o, \-\-output +.RS 4 +Write the generated request to FILE +.RE +.RE +.PP +\-\-extension-secret +.RS 4 +Use the content of FILE as an extension secret. The file must be exactly 32 +bytes long. If this request is the first, all subsequent requests must have the +same extension secret. Only makes sense if bit 1 of the secret control flags of +the IBM Secure Execution header is 0. Otherwise the ultravisor rejects the +request. +.RE +.RE +.PP +\-\-cck +.RS 4 +Use the content of FILE as the customer-communication key (CCK) to derive the +extension secret. The file must contain exactly 32 bytes of data. If the target +guest was started with bit 1 of the secret control flag set, the ultravisor also +derives the secret from the CCK. Otherwise, the ultravisor interprets the +extension secret as a normal one. This still works if you use the same CCK for +all requests. +.RE +.RE +.PP +\-\-cuid-hex +.RS 4 +Use HEXSTRING as the Configuration Unique ID. Must be a hex 128-bit unsigned +big endian number string. Leading zeros must be provided. If specified, the +value must match with the Config-UID from the attestation result of that guest. +If not specified, the CUID will be ignored by the ultravisor during the +verification of the request. +.RE +.RE +.PP +\-\-cuid +.RS 4 +Use the content of FILE as the Configuration Unique ID. The file must contain +exactly 128 bit of data, a hex string, or a yaml with a `cuid` entry. If +specified, the value must match the Config-UID from the attestation result of +that guest. If not specified, the CUID will be ignored by the Ultravisor during +the verification of the request. +.RE +.RE +.PP +\-\-flags +.RS 4 +Flags for the add-secret request. + +Possible values: +.RS 4 +- \fBdisable-dump\fP: Disables host-initiated dumping for the target guest instance. + +.RE +.RE + +.SH "SEE ALSO" +.sp +\fBpvsecret\fR(1) \fBpvsecret-create-meta\fR(1) \fBpvsecret-create-association\fR(1) diff --git a/rust/pvsecret/man/pvsecret-list.1 b/rust/pvsecret/man/pvsecret-list.1 new file mode 100644 index 00000000..6f60a87d --- /dev/null +++ b/rust/pvsecret/man/pvsecret-list.1 @@ -0,0 +1,47 @@ +.\" Copyright 2023 IBM Corp. +.\" s390-tools is free software; you can redistribute it and/or modify +.\" it under the terms of the MIT license. See LICENSE for details. +.\" + +.TH pvsecret-list 1 "2023-07-28" "s390-tools" "UV-Secret Manual" +.nh +.ad l +.SH NAME +\fBpvsecret list\fP - List all ultravisor secrets (s390x only) +\fB +.SH SYNOPSIS +.nf +.fam C +pvsecret list [OPTIONS] [FILE] +.fam C +.fi +.SH DESCRIPTION +Lists the IDs of all non-null secrets currently stored in the ultravisor for the +currently running IBM Secure Execution guest. Only available on s390x. +.SH OPTIONS +.PP + +.RS 4 +Store the result in FILE. Default value: '-' +.RE +.RE + +.PP +\-\-format +.RS 4 +Define the output format of the list. Default value: 'human' + +Possible values: +.RS 4 +- \fBhuman\fP: Human-focused, non-parsable output format + +- \fByaml\fP: Use yaml format + +- \fBbin\fP: Use the format the ultravisor uses to pass the list + +.RE +.RE + +.SH "SEE ALSO" +.sp +\fBpvsecret\fR(1) diff --git a/rust/pvsecret/man/pvsecret-lock.1 b/rust/pvsecret/man/pvsecret-lock.1 new file mode 100644 index 00000000..125e7485 --- /dev/null +++ b/rust/pvsecret/man/pvsecret-lock.1 @@ -0,0 +1,26 @@ +.\" Copyright 2023 IBM Corp. +.\" s390-tools is free software; you can redistribute it and/or modify +.\" it under the terms of the MIT license. See LICENSE for details. +.\" + +.TH pvsecret-lock 1 "2023-07-28" "s390-tools" "UV-Secret Manual" +.nh +.ad l +.SH NAME +\fBpvsecret lock\fP - Lock the secret-store (s390x only) +\fB +.SH SYNOPSIS +.nf +.fam C +pvsecret lock +.fam C +.fi +.SH DESCRIPTION +Lock the secret store (s390x only). After this command executed successfully, +all add-secret requests will fail. Only available on s390x. + +.SH OPTIONS + +.SH "SEE ALSO" +.sp +\fBpvsecret\fR(1) diff --git a/rust/pvsecret/man/pvsecret-version.1 b/rust/pvsecret/man/pvsecret-version.1 new file mode 100644 index 00000000..e1e170f5 --- /dev/null +++ b/rust/pvsecret/man/pvsecret-version.1 @@ -0,0 +1,25 @@ +.\" Copyright 2023 IBM Corp. +.\" s390-tools is free software; you can redistribute it and/or modify +.\" it under the terms of the MIT license. See LICENSE for details. +.\" + +.TH pvsecret-version 1 "2023-07-28" "s390-tools" "UV-Secret Manual" +.nh +.ad l +.SH NAME +\fBpvsecret version\fP- Print version information and exit +\fB +.SH SYNOPSIS +.nf +.fam C +pvsecret version +.fam C +.fi +.SH DESCRIPTION +Print version information and exit + +.SH OPTIONS + +.SH "SEE ALSO" +.sp +\fBpvsecret\fR(1) diff --git a/rust/pvsecret/man/pvsecret.1 b/rust/pvsecret/man/pvsecret.1 new file mode 100644 index 00000000..5e1c0483 --- /dev/null +++ b/rust/pvsecret/man/pvsecret.1 @@ -0,0 +1,103 @@ +.\" Copyright 2023 IBM Corp. +.\" s390-tools is free software; you can redistribute it and/or modify +.\" it under the terms of the MIT license. See LICENSE for details. +.\" + +.TH pvsecret 1 "2023-07-28" "s390-tools" "UV-Secret Manual" +.nh +.ad l +.SH NAME +\fBpvsecret\fP - Manage secrets for IBM Secure Execution guests +\fB +.SH SYNOPSIS +.nf +.fam C +pvsecret [OPTIONS] +.fam C +.fi +.SH DESCRIPTION +Use \fBpvsecret\fR to manage secrets for IBM Secure Execution guests. +\fBpvsecret\fR can \fIcreate\fR add-secret requests on any architecture. On +s390x systems, use \fBpvsecret\fR to \fIadd\fR the secrets to the ultravisor +secret store, \fIlist\fR all secrets in the secret store, or \fIlock\fR the +secret store to prevent any modifications in the future. + +The ultravisor secret store stores secrets for the IBM Secure Execution guest. +The secret store is cleared on guest reboot. + +Create requests only on trusted systems that are not the IBM Secure Execution +guest where you want to inject the secrets. This approach prevents the secrets +from being in cleartext on the guest. For extra safety, do an attestation with +\fBpvattest\fR of your guest beforehand, and include the configuration UID in +the secret request using \fB--cuid\fR. Refer to \fBpvsecret-add\fR(1) for more +information. For all certificates, revocation lists, and host-key documents, +both the PEM and DER input formats are supported. + +.SH OPTIONS +.PP +\-v, \-\-verbose +.RS 4 +Provide more detailed output. +.RE +.RE +.PP +\-\-version +.RS 4 +Print version information and exit. +.RE +.RE + +.SH EXAMPLES +.PP +Create the add-secret request on a trusted system. The program generates three +files. \fFaddsecreq.bin\fP contains the add-secret request. \fFTEST.yaml\fP +contains the non-confidential information about the generated secret. It +contains the name and ID of the secret. \fFTEST\fP contains the plaintext secret +that is encrypted in the request. It can be used to generate add-secret requests +for a different guest with the same secret. Destroy the secret when it is not +used anymore. +.PP +.nf +.fam C + trusted:~$ pvsecret create -k hkd.crt --cert CA.crt --cert ibmsk.crt --hdr pvimage -o addsecreq.bin association EXAMPLE + Successfully generated the request + Successfully wrote association info to 'EXAMPLE.yaml' + Successfully wrote generated association secret to 'EXAMPLE' +.fam T +.fi +On the SE-guest, \fIadd\fP the secret from request to the secret store. +.PP +.nf +.fam C + seguest:~$ pvsecret add addsecreq.bin + Successfully added the secret + +.fam T +.fi +On the SE-guest, \fIlist\fP the secrets currently stored. +.PP +.nf +.fam C + seguest:~$ pvsecret list + Total number of secrets: 1 + + 0 Association: + 94ee059335e587e501cc4bf90613e0814f00a7b08bc7c648fd865a2af6a22cc2 +.fam T +.fi + +On the SE-guest, \fIlock\fP the secret store. +.PP +.nf +.fam C + seguest:~$ pvsecret lock + Successfully locked secret store + seguest:~$ pvsecret add addsecreq.bin + error: Ultravisor: 'secret store locked' (0x0102) + + +.fam T +.fi +.SH "SEE ALSO" +.sp +\fBpvsecret-create\fR(1) \fBpvsecret-add\fR(1) \fBpvsecret-lock\fR(1) \fBpvsecret-list\fR(1) \fBpvsecret-version\fR(1) diff --git a/rust/pvsecret/src/cli.rs b/rust/pvsecret/src/cli.rs new file mode 100644 index 00000000..10dbd1b2 --- /dev/null +++ b/rust/pvsecret/src/cli.rs @@ -0,0 +1,322 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +use clap::{ArgGroup, Args, CommandFactory, Parser, Subcommand, ValueEnum, ValueHint}; +use pv::misc::CertificateOptions; +#[cfg(target_arch = "s390x")] +use pv::misc::STDOUT; + +/// Manage secrets for IBM Secure Execution guests. +/// +/// Use to create and send add-secret requests, list the added secrets and lock the Secret Store. +#[derive(Parser, Debug)] +pub struct CliOptions { + /// Provide more detailed output. + #[arg(short='v', long, action = clap::ArgAction::Count, short_alias('V'))] + pub verbose: u8, + + /// Print version information and exit. + #[arg(long)] + pub version: bool, + + #[command(subcommand)] + pub cmd: Command, +} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)] +pub enum CreateSecretFlags { + /// Disables host-initiated dumping for the target guest instance. + DisableDump, +} + +#[derive(Args, Debug)] +#[command(group(ArgGroup::new("as-ext").args(["cck", "extension_secret"])),)] +pub struct CreateSecretOpt { + #[command(flatten)] + pub certificate_args: CertificateOptions, + + /// Specifies the header of the guest image. + /// + /// Can be an IBM Secure Execution image created by genprotimg or an extracted IBM Secure + /// Execution header. The header must start at a page boundary. + #[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath)] + pub hdr: String, + + /// Force the generation of add-secret requests on IBM Secure Execution guests. + /// + /// If the program detects that it is running on an IBM Secure Execution guest, it denies the + /// generation of add-secret requests. The force flag overwrites this behavior. + #[arg(short, long)] + pub force: bool, + + /// Write the generated request to FILE. + #[arg(short, long, value_name = "FILE", value_hint = ValueHint::FilePath,)] + pub output: String, + + /// Use the content of FILE as an extension secret. + /// + /// The file must be exactly 32 bytes long. If this request is the first, all subsequent + /// requests must have the same extension secret. Only makes sense if bit 1 of the secret + /// control flags of the IBM Secure Execution header is + /// 0. Otherwise the ultravisor rejects the request. + #[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath,)] + pub extension_secret: Option, + + /// Use the content of FILE as the customer-communication key (CCK) to derive the extension + /// secret. + /// + /// The file must contain exactly 32 bytes of data. If the target guest was started + /// with bit 1 of the secret control flag set, the ultravisor also derives the secret from the + /// CCK. Otherwise, the ultravisor interprets the extension secret as a normal one. This still + /// works if you use the same CCK for all requests. + #[arg(long, value_name = "FILE")] + pub cck: Option, + + /// Use HEXSTRING as the Configuration Unique ID. + /// + /// Must be a hex 128-bit unsigned big endian number string. Leading zeros must be provided. If + /// specified, the value must match with the Config-UID from the attestation result of that + /// guest. If not specified, the CUID will be ignored by the ultravisor during the + /// verification of the request. + #[arg(long, value_name = "HEXSTRING")] + pub cuid_hex: Option, + + /// Use the content of FILE as the Configuration Unique ID. + /// + /// The file must contain exactly 128 bit of data, a hex string, or a yaml with a `cuid` entry. + /// If specified, the value must match the Config-UID from the attestation result of that + /// guest. If not specified, the CUID will be ignored by the Ultravisor during the verification + /// of the request. + #[arg(long, value_name = "FILE", conflicts_with("cuid_hex"), value_hint = ValueHint::FilePath,)] + pub cuid: Option, + + #[command(subcommand)] + pub secret: AddSecretType, + + // FLAGS + // each flag must conflict with `flags` + // `flags` is hidden in the help menu + /// Manually set the Add Secret Request flags. + /// + /// No validity checks made. Hidden in user documentation. + #[arg(long, hide(true))] + pub pcf: Option, + + /// Flags for the add-secret request. + #[arg( + long, + conflicts_with("pcf"), + value_enum, + value_parser, + use_value_delimiter = true, + value_delimiter = ',' + )] + pub flags: Vec, +} + +#[derive(Subcommand, Debug)] +pub enum AddSecretType { + /// Use a meta secret to carry flags to the ultravisor without having to provide an actual + /// secret value. Meta secrets do not appear in the list of secrets. + Meta, + + /// Use an association secret to connect a trusted I/O device to a guest. The `pvapconfig` tool + /// provides more information about association secrets. + Association { + /// String to identify the new secret. + /// + /// The actual secret is set with --input-secret. The name is saved in `NAME.yaml` with + /// white-spaces mapped to `_`. + name: String, + + ///Print the hashed name to stdout. + /// + ///The hashed name will not be written to `NAME.yaml` + #[arg(long)] + stdout: bool, + + /// Path from which to read the plaintext secret. Uses a random secret if not specified. + #[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath, conflicts_with("output_secret"))] + input_secret: Option, + + /// Save the generated secret as plaintext in FILE. + /// + /// The generated secret can be used to generate add-secret requests for a different guest + /// with the same secret using --input-secret. Destroy the secret when it is not used + /// anymore. + #[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath,)] + output_secret: Option, + }, +} + +// all members s390x only +#[derive(Args, Debug)] +pub struct AddSecretOpt { + /// Specify the request to be sent. + #[arg(value_name = "FILE", value_hint = ValueHint::FilePath,)] + #[cfg(target_arch = "s390x")] + pub input: String, +} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug, Default)] +#[cfg(target_arch = "s390x")] +pub enum ListSecretOutputType { + /// Human-focused, non-parsable output format + #[default] + Human, + /// Use yaml format. + Yaml, + /// Use the format the ultravisor uses to pass the list. + Bin, +} + +// all members s390x only +#[derive(Args, Debug)] +pub struct ListSecretOpt { + /// Store the result in FILE + #[arg(value_name = "FILE", default_value = STDOUT, value_hint = ValueHint::FilePath,)] + #[cfg(target_arch = "s390x")] + pub output: String, + + /// Define the output format of the list. + #[arg(long, value_enum, default_value_t)] + #[cfg(target_arch = "s390x")] + pub format: ListSecretOutputType, +} + +#[derive(Subcommand, Debug)] +pub enum Command { + /// Create a new add-secret request. + /// + /// Create add-secret requests for IBM Secure Execution guests. Only create these requests in a + /// trusted environment, such as your workstation. The `pvattest create` command creates a + /// randomly generated key to protect the request. The generated requests can then be added on + /// an IBM Secure Execution guest using `pvsecret add`. The guest can then use the secrets with + /// the use case depending on the secret type. + Create(Box), + + /// Repeat an add-secret request (s390x only). + /// + /// Perform an add-secret request using a previously generated add-secret request. Only + /// available on s390x. + Add(AddSecretOpt), + + /// Lock the secret-store (s390x only). + /// + /// Lock the secret store (s390x only). After this command executed successfully, all + /// add-secret requests will fail. Only available on s390x. + Lock, + + /// List all ultravisor secrets (s390x only). + /// + /// Lists the IDs of all non-null secrets currently stored in the ultravisor for the currently + /// running IBM Secure Execution guest. Only available on s390x. + List(ListSecretOpt), + + /// Print version information and exit. + #[command(aliases(["--version"]), hide(true))] + Version, +} + +/// Additional checks to assure, option integrity +pub fn validate_cli(cli: &CliOptions) -> Result<(), clap::Error> { + if let Command::Create(opt) = &cli.cmd { + if let AddSecretType::Association { + name, + stdout, + input_secret: _, + output_secret: secret_out, + } = &opt.secret + { + if *stdout { + return Ok(()); + } + if secret_out == &Some(format!("{name}.yaml")) { + return Err(CliOptions::command().error( + clap::error::ErrorKind::ValueValidation, + format!("Secret output file and the secret name '{name}.yaml' are the same."), + )); + } + if format!("{name}.yaml") == opt.output { + return Err(CliOptions::command().error( + clap::error::ErrorKind::ValueValidation, + format!( + "output file and the secret name '{}' are the same.", + &opt.output + ), + )); + } + } + } + Ok(()) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + #[rustfmt::skip] + fn cli_args() { + //Verify only that some arguments are optional, we do not want to test clap, only the + //configuration + let valid_args = [ + vec!["pvsecret", "lock"], + vec!["pvsecret", "version"], + vec!["pvsecret", "list"], + #[cfg(target_arch = "s390x")] + vec!["pvsecret", "add", "abc"], + #[cfg(not(target_arch = "s390x"))] + vec!["pvsecret", "add"], + vec!["pvsecret", "create", "-k", "abc", "--hdr", "abc", "-o", "abc", "--no-verify", "meta"], + vec!["pvsecret", "create", "-k", "abc", "--hdr", "abc", "-o", "abc", "--no-verify", "association", "name" ], + // verify that arguments stay backwards compatible + vec!["pvsecret", "create", "-k", "abc,cdef", "--hdr", "abc", "-o", "abc", "-C", "uuu,ggg", "--crl", "yyy,hhh", "--root-ca", "tttt", + "--extension-secret", "fff", "--cuid", "cuid", "--flags", "disable-dump", "meta"], + vec!["pvsecret", "create", "--host-key-document", "abc", "-k", "y", "--hdr", "abc", "-o", "abc", "--cert", "uuu", "--crl", "yyy", + "--root-ca", "tttt", "--cck", "cck", "--cuid-hex", "0x11223344556677889900aabbccddeeff", "--pcf", "0x123", "association", "name", "--stdout", + "--output-secret", "secret"], + vec!["pvsecret", "create", "-k", "abc", "--hdr", "abc", "-o", "abc", "--no-verify", "association", "name", "--output-secret", "secret"], + #[cfg(target_arch = "s390x")] + vec!["pvsecret", "list", "--format", "human"], + #[cfg(target_arch = "s390x")] + vec!["pvsecret", "list", "--format", "yaml"], + #[cfg(target_arch = "s390x")] + vec!["pvsecret", "list", "--format", "bin"], + ]; + // Test for the minimal amount of flags to yield an invalid combination + let invalid_args = [ + vec!["pvsecret"], + vec!["pvsecret", "list", "--yaml", "--bin"], + vec!["pvsecret", "create", "--hdr", "abc", "-o", "abc", "--no-verify" ,"null"], + vec!["pvsecret", "create", "-k", "abc", "-o", "abc", "--no-verify", "null"], + vec!["pvsecret", "create", "-k", "abc", "--hdr", "abc", "--no-verify", "null"], + vec!["pvsecret", "create", "-k", "abc", "--hdr", "abc", "-o", "abc", "null"], + vec!["pvsecret", "create", "-k", "abc", "--hdr", "abc", "-o", "abc", "--cck", "abc", "--extension_secret", "abc", "--no-verify", "null"], + vec!["pvsecret", "create", "-k", "abc", "--hdr", "abc", "-o", "abc", "--no-verify", "--flags", "disable-dump", "--pcf", "0", "null"], + vec!["pvsecret", "create", "-k", "abc", "--hdr", "abc", "-o", "abc", "--no-verify", "--cuid", "abc", "--cuid_hex", "9", "null"], + vec!["pvsecret", "create", "-k", "abc", "--hdr", "abc", "-o", "abc", "--no-verify", "association"], + vec!["pvsecret", "create", "-k", "abc", "--hdr", "abc", "-o", "abc", "--no-verify", "association", "name", "--output-secret", "secret", "--input-secret", "secret"], + ]; + for arg in valid_args { + let res = CliOptions::try_parse_from(&arg); + if let Err(e) = &res { + println!("arg: {arg:?}"); + println!("{e}"); + } + assert!(res.is_ok()); + } + + for arg in invalid_args { + let res = CliOptions::try_parse_from(&arg); + assert!(res.is_err()); + } + } + + #[test] + fn verify_cli() { + use clap::CommandFactory; + CliOptions::command().debug_assert() + } +} diff --git a/rust/pvsecret/src/cmd.rs b/rust/pvsecret/src/cmd.rs new file mode 100644 index 00000000..5a655c4b --- /dev/null +++ b/rust/pvsecret/src/cmd.rs @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +mod create; +pub use create::create; + +// Commands (directly) related to UVCs are only available on s389x +#[cfg(target_arch = "s390x")] +mod add; +#[cfg(target_arch = "s390x")] +pub use add::add; + +#[cfg(target_arch = "s390x")] +mod list; +#[cfg(target_arch = "s390x")] +pub use list::list; + +#[cfg(target_arch = "s390x")] +mod lock; +#[cfg(target_arch = "s390x")] +pub use lock::lock; diff --git a/rust/pvsecret/src/cmd/add.rs b/rust/pvsecret/src/cmd/add.rs new file mode 100644 index 00000000..947604e4 --- /dev/null +++ b/rust/pvsecret/src/cmd/add.rs @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +use crate::cli::AddSecretOpt; +use anyhow::{Context, Result}; +use log::warn; +use pv::misc::get_reader_from_cli_file_arg; +use pv::uv::{AddCmd, UvDevice}; + +/// Do an Add Secret UVC +pub fn add(opt: &AddSecretOpt) -> Result<()> { + let mut rd_in = get_reader_from_cli_file_arg(&opt.input)?; + let mut cmd = + AddCmd::new(&mut rd_in).context(format!("Processing input file {}", opt.input))?; + UvDevice::open()?.send_cmd(&mut cmd)?; + warn!("Successfully added the secret"); + Ok(()) +} diff --git a/rust/pvsecret/src/cmd/create.rs b/rust/pvsecret/src/cmd/create.rs new file mode 100644 index 00000000..431ea0f9 --- /dev/null +++ b/rust/pvsecret/src/cmd/create.rs @@ -0,0 +1,240 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +use crate::cli::{AddSecretType, CreateSecretFlags, CreateSecretOpt}; +use anyhow::{anyhow, bail, Context, Result}; +use log::{debug, info, trace, warn}; +use pv::{ + misc::{ + get_writer_from_cli_file_arg, parse_hex, pv_guest_bit_set, read_certs, read_exact_file, + read_file, try_parse_u128, try_parse_u64, + }, + open_buffered_file, + request::{ + openssl::pkey::{PKey, Public}, + uvsecret::{AddSecretFlags, AddSecretRequest, AddSecretVersion, ExtSecret, GuestSecret}, + BootHdrTags, HkdVerifier, ReqEncrCtx, Request, SymKeyType, + }, + uv::ConfigUid, +}; +use serde_yaml::Value; + +fn write_out>(path: &str, data: D, ctx: &str) -> pv::Result<()> { + let mut wr = match get_writer_from_cli_file_arg(path) { + Ok(it) => it, + Err(err) => return Err(err), + }; + pv::misc::write(&mut wr, data, path, ctx)?; + Ok(()) +} + +/// Prepare an Add Secret Request +pub fn create(opt: &CreateSecretOpt) -> Result<()> { + if pv_guest_bit_set() { + warn!("The system seems to be a Secure Execution guest"); + if !opt.force { + bail!("Do NOT generate Add-secret requests on a machine where you want to use the secret! Overwrite with '-f'"); + } else { + warn!("WARNING: Enforcing of generating a request on a Secure Execution guest") + } + } + + let mut asrcb = build_asrcb(opt)?; + debug!("Generated Add-secret request"); + + // Add host-key documents + let verifier = opt.certificate_args.verifier()?; + read_and_verify_hkds(&opt.certificate_args.host_key_documents, verifier)? + .into_iter() + .for_each(|k| asrcb.add_hostkey(k)); + + debug!("Added all host-keys"); + + // build + encrypt the request + let rq = ReqEncrCtx::random(SymKeyType::Aes256).context("Failed to generate random input")?; + let ser_asrbc = asrcb.encrypt(&rq)?; + warn!("Successfully generated the request"); + write_out(&opt.output, ser_asrbc, "Add Secret Request")?; + info!("Successfully wrote the request to '{}'", &opt.output); + + write_secret(&opt.secret, &asrcb) +} + +/// Set-up the `Add Secret Request` from command-line arguments +fn build_asrcb(opt: &CreateSecretOpt) -> Result { + debug!("Build Add Secret Request"); + + let secret = match &opt.secret { + AddSecretType::Meta => GuestSecret::Null, + AddSecretType::Association { + name, + input_secret: Some(p), + .. + } => GuestSecret::association(name, read_exact_file(p, "Association secret")?)?, + AddSecretType::Association { + name, + input_secret: None, + .. + } => GuestSecret::association(name, None)?, + }; + trace!("AddSecret: {secret:x?}"); + + let mut flags = match &opt.pcf { + Some(v) => (&try_parse_u64(v, "pcf")?).into(), + None => AddSecretFlags::default(), + }; + opt.flags.iter().for_each(|v| match v { + CreateSecretFlags::DisableDump => flags.set_disable_dump(), + }); + debug!("FLAGS: {flags:x?}"); + + let mut se_hdr = open_buffered_file!(&opt.hdr); + let mut asrcb = AddSecretRequest::new( + AddSecretVersion::One, + secret, + BootHdrTags::from_se_image(&mut se_hdr) + .with_context(|| format!("Provided SE-header in '{}' is malformed", &opt.hdr))?, + flags, + ); + + // Set CUID + read_cuid(&mut asrcb, opt)?; + + // Set extension secret + if let Some(path) = &opt.extension_secret { + asrcb.set_ext_secret(ExtSecret::Simple( + read_exact_file(path, "extension secret")?.into(), + ))?; + } else if let Some(path) = &opt.cck { + asrcb.set_ext_secret(ExtSecret::Derived(read_exact_file(path, "CCK")?.into()))?; + } + + Ok(asrcb) +} + +// Try to extract a Config-UId from a yaml structure +// The cuid field can be embedded in an abritray amount of Mappings +// The function takes the first cuid it founds (width search). +fn try_from_val(val: Value) -> anyhow::Result { + fn get_cuid_from_mapping(val: &Value, depth: u8) -> Option { + if depth >= 8 { + return None; + } + match val { + Value::Mapping(m) if m.contains_key("cuid") => { + return m.get("cuid").and_then(|v| v.as_str()).map(|s| s.to_owned()) + } + Value::Mapping(m) => { + for (_, v) in m { + if let Some(v) = get_cuid_from_mapping(v, depth + 1) { + return Some(v); + } + } + } + _ => return None, + }; + None + } + let cuid = match &val { + Value::String(s) => Some(s.clone()), + Value::Mapping(_) => get_cuid_from_mapping(&val, 0), + _ => None, + } + .ok_or(anyhow!("No 'cuid' entry found"))?; + let cuid = cuid + .strip_prefix("0x") + .ok_or(anyhow!("Value starts not with 0x".to_string()))? + .to_owned(); + if cuid.len() != ::std::mem::size_of::() * 2 { + return Err(anyhow!(format!("len invalid ({})", cuid.len()))); + } + let cuid: ConfigUid = parse_hex(&cuid) + .try_into() + .map_err(|_| anyhow!("Cannot parse hex number".to_string()))?; + Ok(cuid) +} + +fn read_cuid(asrcb: &mut AddSecretRequest, opt: &CreateSecretOpt) -> Result<()> { + if let Some(path) = &opt.cuid { + let cuid = match read_exact_file(path, "The CUID-file") { + Ok(v) => v, + Err(_) => { + let buf = read_file(path, "The CUID-file")?; + let val: Value = serde_yaml::from_slice(&buf).context( + "The CUID-file does not contain a 128bit value or a yaml with a 'cuid' field", + )?; + try_from_val(val)? + } + }; + asrcb.set_cuid(cuid); + } else if let Some(v) = &opt.cuid_hex { + asrcb.set_cuid(try_parse_u128(v, "CUID")?); + } + Ok(()) +} + +/// reads HKDs into memory, verifies them with the provided HKD verifier. +/// returns list of public keys or Err +/// Aborts on first error +fn read_and_verify_hkds( + hkds: &Vec, + verifier: Box, +) -> Result>> { + let mut res = Vec::with_capacity(hkds.len()); + for hkd in hkds { + let hk = read_file(hkd, "host-key document")?; + let certs = read_certs(&hk).with_context(|| { + format!("The provided Host Key Document in '{hkd}' is not in PEM or DER format") + })?; + if certs.len() != 1 { + warn!("The host key document in '{hkd}' contains more than one certificate!") + } + + // len is 1 -> unwrap will succeed + let c = certs.first().unwrap(); + verifier.verify(c)?; + res.push(c.public_key()?); + info!("Use host-key document at '{hkd}'"); + } + Ok(res) +} +/// Write the generated secret (if any) to the specified output stream +fn write_secret(secret: &AddSecretType, asrcb: &AddSecretRequest) -> Result<()> { + if let AddSecretType::Association { + name, + stdout, + output_secret: secret_out, + .. + } = secret + { + let gen_path: String = name + .chars() + .map(|c| if c.is_whitespace() { '_' } else { c }) + .collect(); + + //write non confidential data (=name+id) to a yaml + let secret_info = serde_yaml::to_string(asrcb.guest_secret())?; + if stdout.to_owned() { + println!("{secret_info}"); + } else { + let secret_info_path = format!("{gen_path}.yaml"); + write_out(&secret_info_path, secret_info, "association secret info")?; + debug!( + "Non-confidential secret information: {:x?}", + asrcb.guest_secret() + ); + warn!("Successfully wrote association info to '{secret_info_path}'"); + } + + if let Some(path) = secret_out { + if let GuestSecret::Association { secret, .. } = asrcb.guest_secret() { + write_out(path, secret.value(), "Association secret")? + } else { + unreachable!("The secret type has to be `association` at this point (bug)!") + } + info!("Successfully wrote generated association secret to '{path}'"); + } + }; + Ok(()) +} diff --git a/rust/pvsecret/src/cmd/list.rs b/rust/pvsecret/src/cmd/list.rs new file mode 100644 index 00000000..e3d193ed --- /dev/null +++ b/rust/pvsecret/src/cmd/list.rs @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +use crate::cli::{ListSecretOpt, ListSecretOutputType}; +use anyhow::{Context, Result}; +use log::warn; +use pv::{ + misc::{get_writer_from_cli_file_arg, STDOUT}, + uv::{ListCmd, SecretList, UvDevice, UvcSuccess}, +}; + +/// Do a List Secrets UVC +pub fn list(opt: &ListSecretOpt) -> Result<()> { + let uv = UvDevice::open()?; + let mut cmd = ListCmd::default(); + match uv.send_cmd(&mut cmd)? { + UvcSuccess::RC_SUCCESS => (), + UvcSuccess::RC_MORE_DATA => warn!("There is more data available than expected"), + }; + + let secret_list: SecretList = cmd.try_into()?; + let mut wr_out = get_writer_from_cli_file_arg(&opt.output)?; + + match &opt.format { + ListSecretOutputType::Human => { + write!(wr_out, "{secret_list}").context("Cannot generate output")? + } + ListSecretOutputType::Yaml => write!(wr_out, "{}", serde_yaml::to_string(&secret_list)?) + .context("Cannot generate yaml output")?, + ListSecretOutputType::Bin => secret_list + .encode(&mut wr_out) + .context("Cannot encode secret list")?, + } + wr_out.flush()?; + + if opt.output != STDOUT { + warn!( + "Successfully wrote the list of secrets to '{}'", + &opt.output + ); + } + Ok(()) +} diff --git a/rust/pvsecret/src/cmd/lock.rs b/rust/pvsecret/src/cmd/lock.rs new file mode 100644 index 00000000..0619bc90 --- /dev/null +++ b/rust/pvsecret/src/cmd/lock.rs @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +use anyhow::Result; +use log::warn; +use pv::uv::{LockCmd, UvDevice}; + +/// Do a Lock Secret Store UVC +pub fn lock() -> Result<()> { + UvDevice::open()?.send_cmd(&mut LockCmd)?; + warn!("Successfully locked secret store"); + Ok(()) +} diff --git a/rust/pvsecret/src/main.rs b/rust/pvsecret/src/main.rs new file mode 100644 index 00000000..ebb2be27 --- /dev/null +++ b/rust/pvsecret/src/main.rs @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +mod cli; +mod cmd; + +use clap::CommandFactory; +use clap::Parser; +use cli::{CliOptions, Command}; +use log::trace; +use pv::misc::PvLogger; +use std::process::ExitCode; +use utils::release_string; + +use crate::cli::validate_cli; + +static LOGGER: PvLogger = PvLogger; +static EXIT_LOGGER: u8 = 3; +const FEATURES: &[&str] = &[ + "+create", + #[cfg(target_arch = "s390x")] + "+add", + #[cfg(target_arch = "s390x")] + "+lock", + #[cfg(target_arch = "s390x")] + "+list", +]; + +fn print_error(e: anyhow::Error, verbosity: u8) -> ExitCode { + if verbosity > 0 { + // Debug formatter also prints the whole error stack + // So only print it when on verbose + eprintln!("error: {e:?}") + } else { + eprintln!("error: {e}") + }; + ExitCode::FAILURE +} + +fn print_cli_error(e: clap::Error) -> ExitCode { + let ret = if e.use_stderr() { + ExitCode::FAILURE + } else { + ExitCode::SUCCESS + }; + //Ignore any errors during printing of the error + let _ = e.format(&mut CliOptions::command()).print(); + ret +} + +fn print_version(verbosity: u8) -> anyhow::Result<()> { + println!( + "{} version {}\nCopyright IBM Corp. 2023", + env!("CARGO_PKG_NAME"), + release_string!() + ); + if verbosity > 0 { + FEATURES.iter().for_each(|f| print!("{f} ")); + println!("(compiled)"); + println!( + "\n{}-crate {}", + env!("CARGO_PKG_NAME"), + env!("CARGO_PKG_VERSION") + ); + println!("{}", pv::crate_info()); + } + Ok(()) +} + +#[cfg(not(target_arch = "s390x"))] +fn not_supported() -> anyhow::Result<()> { + use anyhow::bail; + bail!("Command only available on s390x") +} + +fn main() -> ExitCode { + let cli: CliOptions = match CliOptions::try_parse() { + Ok(cli) => match validate_cli(&cli) { + Ok(_) => cli, + Err(e) => return print_cli_error(e), + }, + Err(e) => return print_cli_error(e), + }; + + // set up logger/std(out,err) + if let Err(e) = LOGGER.start(cli.verbose) { + //should(TM) never happen + eprintln!("Logger error: {e:?}"); + return EXIT_LOGGER.into(); + } + + // NOTE trace verbosity is disabled in release builds + trace!("Trace verbosity, may leak secrets to command-line"); + trace!("Options {cli:?}"); + + if cli.version { + let _ = print_version(cli.verbose); + return ExitCode::SUCCESS; + } + + // perform the command selected by the user + let res = match &cli.cmd { + #[cfg(target_arch = "s390x")] + Command::Add(opt) => cmd::add(opt), + #[cfg(target_arch = "s390x")] + Command::List(opt) => cmd::list(opt), + #[cfg(target_arch = "s390x")] + Command::Lock => cmd::lock(), + + #[cfg(not(target_arch = "s390x"))] + Command::Add(_) => not_supported(), + #[cfg(not(target_arch = "s390x"))] + Command::List(_) => not_supported(), + #[cfg(not(target_arch = "s390x"))] + Command::Lock => not_supported(), + Command::Create(opt) => cmd::create(opt), + Command::Version => print_version(cli.verbose), + }; + + match res { + Ok(_) => ExitCode::SUCCESS, + Err(e) => print_error(e, cli.verbose), + } +}