diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 49f99eb3..8620114f 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -13,24 +13,24 @@ dependencies = [ [[package]] name = "anstream" -version = "0.3.2" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", - "is-terminal", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.0" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" @@ -52,12 +52,12 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "1.0.1" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -78,6 +78,21 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitflags" version = "1.3.2" @@ -90,6 +105,18 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "byteorder" version = "1.4.3" @@ -110,24 +137,22 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.3.4" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80672091db20273a15cf9fdd4e47ed43b5091ec9841bf4c6145c9dfbbcae09ed" +checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3" dependencies = [ "clap_builder", "clap_derive", - "once_cell", ] [[package]] name = "clap_builder" -version = "4.3.4" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1458a1df40e1e2afebb7ab60ce55c1fa8f431146205aa5f4887e0b111c27636" +checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b" dependencies = [ "anstream", "anstyle", - "bitflags 1.3.2", "clap_lex", "strsim", "terminal_size", @@ -135,18 +160,18 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.3.1" +version = "4.5.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6b5c519bab3ea61843a7923d074b04245624bb84a64a8c150f5deb014e388b" +checksum = "e16bb32eaa759f7fe76c59793c4e00dad6d11705f74ddfe4887e62f459536f16" dependencies = [ "clap", ] [[package]] name = "clap_derive" -version = "4.3.2" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck", "proc-macro2", @@ -156,9 +181,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.5.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" +checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" [[package]] name = "colorchoice" @@ -211,6 +236,66 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "deku" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9711031e209dc1306d66985363b4397d4c7b911597580340b93c9729b55f6eb" +dependencies = [ + "bitvec", + "deku_derive", + "no_std_io2", + "rustversion", +] + +[[package]] +name = "deku_derive" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58cb0719583cbe4e81fb40434ace2f0d22ccc3e39a74bb3796c22b451b4f139d" +dependencies = [ + "darling", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "enum_dispatch" version = "0.3.13" @@ -223,6 +308,12 @@ dependencies = [ "syn", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.1" @@ -244,6 +335,18 @@ dependencies = [ "libc", ] +[[package]] +name = "fastrand" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foreign-types" version = "0.3.2" @@ -259,6 +362,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "getrandom" version = "0.2.10" @@ -277,16 +386,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] -name = "heck" -version = "0.4.1" +name = "hashbrown" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] -name = "hermit-abi" -version = "0.3.1" +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "indexmap" @@ -295,31 +410,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", ] [[package]] -name = "io-lifetimes" -version = "1.0.11" +name = "indexmap" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.48.0", + "equivalent", + "hashbrown 0.15.2", ] [[package]] -name = "is-terminal" -version = "0.4.7" +name = "is_terminal_polyfill" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" -dependencies = [ - "hermit-abi", - "io-lifetimes", - "rustix", - "windows-sys 0.48.0", -] +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itoa" @@ -339,6 +447,12 @@ version = "0.2.146" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" +[[package]] +name = "libm" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + [[package]] name = "libz-sys" version = "1.1.9" @@ -353,9 +467,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.3.8" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "log" @@ -369,6 +483,25 @@ version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +[[package]] +name = "no_std_io2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a3564ce7035b1e4778d8cb6cacebb5d766b5e8fe5a75b9e441e33fb61a872c6" +dependencies = [ + "memchr", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + [[package]] name = "once_cell" version = "1.19.0" @@ -431,6 +564,15 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "proc-macro-crate" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.75" @@ -440,6 +582,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags 2.4.1", + "lazy_static", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + [[package]] name = "pvapconfig" version = "0.10.0" @@ -476,6 +638,26 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "pvimg" +version = "0.10.0" +dependencies = [ + "anyhow", + "clap", + "clap_complete", + "deku", + "deku_derive", + "enum_dispatch", + "log", + "openssl", + "proptest", + "s390_pv", + "serde", + "serde_json", + "thiserror", + "utils", +] + [[package]] name = "pvsecret" version = "0.10.0" @@ -489,6 +671,12 @@ dependencies = [ "utils", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.35" @@ -498,6 +686,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -528,6 +722,24 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "regex" version = "1.10.2" @@ -559,18 +771,35 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "rustix" -version = "0.37.27" +version = "0.38.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" +checksum = "ac5ffa1efe7548069688cd7028f32591853cd7b5b756d41bcffd2353e4fc75b4" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.1", "errno", - "io-lifetimes", "libc", "linux-raw-sys", "windows-sys 0.48.0", ] +[[package]] +name = "rustversion" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" + +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "ryu" version = "1.0.13" @@ -664,7 +893,7 @@ version = "0.9.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9d684e3ec7de3bf5466b32bd75303ac16f0736426e5a4e0d6e489559ce1249c" dependencies = [ - "indexmap", + "indexmap 1.9.3", "itoa", "ryu", "serde", @@ -683,9 +912,9 @@ dependencies = [ [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" @@ -699,10 +928,29 @@ dependencies = [ ] [[package]] -name = "terminal_size" -version = "0.2.6" +name = "tap" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "terminal_size" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ "rustix", "windows-sys 0.48.0", @@ -728,6 +976,29 @@ dependencies = [ "syn", ] +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap 2.6.0", + "toml_datetime", + "winnow", +] + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-ident" version = "1.0.9" @@ -763,6 +1034,15 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -980,6 +1260,24 @@ version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "zerocopy" version = "0.7.32" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index dff6c4ee..08194ca2 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -5,6 +5,7 @@ members = [ "pv_core", "pvapconfig", "pvattest", + "pvimg", "pvsecret", "utils", ] diff --git a/rust/pv/src/crypto.rs b/rust/pv/src/crypto.rs index a589e963..8f11d2b4 100644 --- a/rust/pv/src/crypto.rs +++ b/rust/pv/src/crypto.rs @@ -28,7 +28,6 @@ pub type Aes256GcmKey = Confidential<[u8; SymKeyType::AES_256_GCM_KEY_LEN]>; pub type Aes256XtsKey = Confidential<[u8; SymKeyType::AES_256_XTS_KEY_LEN]>; /// SHA-512 digest length (in bytes) -#[allow(unused)] pub const SHA_512_HASH_LEN: usize = 64; #[allow(dead_code)] diff --git a/rust/pv/src/lib.rs b/rust/pv/src/lib.rs index 1ec19a7f..9c98f551 100644 --- a/rust/pv/src/lib.rs +++ b/rust/pv/src/lib.rs @@ -90,7 +90,7 @@ pub mod request { crypto::{ decrypt_aead, derive_aes256_gcm_key, encrypt_aead, gen_ec_key, random_array, AeadDecryptionResult, AeadEncryptionResult, Aes256GcmKey, Aes256XtsKey, SymKey, - SymKeyType, + SymKeyType, SHA_512_HASH_LEN, }, req::{EcPubKeyCoord, Encrypt, Keyslot, ReqEncrCtx, Request}, verify::{CertVerifier, HkdVerifier, NoVerifyHkd}, diff --git a/rust/pvimg/Cargo.toml b/rust/pvimg/Cargo.toml new file mode 100644 index 00000000..c2684323 --- /dev/null +++ b/rust/pvimg/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "pvimg" +version = "0.10.0" +edition.workspace = true +license.workspace = true +rust-version.workspace = true + +[lints] +workspace = true + +[dependencies] +anyhow = { version = "1.0.70", features = ["std"] } +clap = { version ="4.1", features = ["derive", "wrap_help"]} +deku = "0.18" +deku_derive = "0.18" +enum_dispatch = "0.3.13" +log = { version = "0.4.6", features = ["std", "release_max_level_debug"] } +openssl = "0.10.66" +serde = { version = "1.0.139", features = ["derive"]} +serde_json = "1.0" +thiserror = "1.0.33" + +pv = { path = "../pv", package = "s390_pv" } +utils = { path = "../utils" } + +[build-dependencies] +anyhow = { version = "1.0.70", features = ["std"] } +clap = { version ="4.1", features = ["derive", "wrap_help"]} +clap_complete = "4.5" +log = { version = "0.4.6", features = ["std", "release_max_level_debug"] } + +pv = { path = "../pv", package = "s390_pv" } +utils = { path = "../utils" } + +[dev-dependencies] +proptest = "1.3" diff --git a/rust/pvimg/examples/create-sehdr/main.rs b/rust/pvimg/examples/create-sehdr/main.rs new file mode 100644 index 00000000..4a743809 --- /dev/null +++ b/rust/pvimg/examples/create-sehdr/main.rs @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2024 + +#![allow(missing_docs)] +use std::{ + fmt::Display, + fs::{File, OpenOptions}, + io::{BufReader, Read, Write}, + path::PathBuf, + str::FromStr, +}; + +use anyhow::{anyhow, Context, Error}; +use clap::{Parser, ValueHint}; +use log::{info, warn}; +use pv::{ + misc::{decode_hex, open_file, read_certs, read_file, try_parse_u64}, + request::SymKeyType, + Error as PvError, Result, +}; +use pvimg::{ + misc::PSW, + secured_comp::{ComponentTrait, Layout, SecuredComponentBuilder}, + uvdata::{BuilderTrait, SeHdrBuilder, SeHdrVersion}, +}; +use utils::{AtomicFile, AtomicFileOperation, HexSlice, PvLogger, VerbosityOptions}; + +/// Converts the hexstring into a byte vector. +/// +/// # Errors +/// +/// Raises an error if a non-hex character was found or the length was not a +/// multiple of two. +pub fn decode_hex_str>(s: S) -> Result> { + let hex_str = s.as_ref(); + let hex_value = if hex_str.starts_with("0x") { + hex_str.split_at(2).1 + } else { + hex_str + }; + + Ok(decode_hex(hex_value)?) +} + +fn decode_u64_hex_str(s: &str) -> Result { + Ok(try_parse_u64(s, "The")?) +} + +impl FromStr for ComponentArg { + type Err = Error; + + fn from_str(s: &str) -> Result { + let parts: Vec<_> = s.split(',').collect(); + if parts.len() != 3 { + return Err(anyhow!("Invalid component format.")); + } + let path = parts[0].into(); + let addr = try_parse_u64(parts[1], "Invalid address")?; + let mut tweak = + decode_hex_str(parts[2]).with_context(|| format!("Invalid tweak {}", parts[2]))?; + if tweak.len() > SymKeyType::AES_256_XTS_TWEAK_LEN { + return Err(anyhow!( + "Invalid tweak because the length of {} is greater than the expected {}.", + tweak.len(), + SymKeyType::AES_256_XTS_TWEAK_LEN + )); + } + tweak.resize(SymKeyType::AES_256_XTS_TWEAK_LEN, 0x0); + Ok(Self { path, addr, tweak }) + } +} + +#[derive(Debug, Clone)] +struct ComponentArg { + path: PathBuf, + addr: u64, + tweak: Vec, +} + +impl Display for ComponentArg { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "component\n\ + Path ......: {:}\n\ + Address ...: {:#0x}\n\ + Tweak .....: {:#}", + self.path.display(), + self.addr, + HexSlice::from(&self.tweak), + ) + } +} + +/// Create a Secure Execution header. +#[derive(Parser, Debug)] +pub struct Args { + /// Use FILE as the component, ADDR as the component address, and TWEAK as + /// the component tweak. + /// + /// ADDR and TWEAK must be a hex-string. TWEAK is right padded with zero + /// bytes if the given tweak is not large enough. Can be specified multiple + /// times and must be used at least once. + #[arg(short, long = "component", required = true, value_name = "FILE,ADDR,TWEAK", value_hint = ValueHint::FilePath)] + components: Vec, + + /// Use FILE as a host key document. + /// + /// Can be specified multiple times and must be used at least once. + #[arg(short = 'k', long = "host-key", required = true)] + pub host_key_documents: Vec, + + /// Plain control flags. Must be a hex value. + #[arg(long, default_value = "0x10000000")] + pub pcf: String, + + /// Secret control flags. Must be a hex value. + #[arg(long, default_value = "0x0")] + pub scf: String, + + /// PSW address. Must be a hex value. + #[arg(long, default_value = "0x10000", value_parser=decode_u64_hex_str)] + pub psw_addr: u64, + + /// PSW mask. Must be a hex value. + #[arg(long, default_value = "0x0000000180000000", value_parser=decode_u64_hex_str)] + pub psw_mask: u64, + + /// Customer communication key (CCK) file path. + #[arg(long)] + pub cck: Option, + + /// Secure Execution header output location. + #[arg(short, long)] + pub output: PathBuf, + + #[clap(flatten)] + pub verbosity: VerbosityOptions, +} + +#[derive(Debug)] +pub struct Comp { + pub reader: BufReader, +} + +impl Read for Comp { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + self.reader.read(buf) + } +} + +enum CompType { + Dummy = 1, +} + +impl ComponentTrait for Comp { + fn secure_mode(&self) -> bool { + true + } + + fn kind(&self) -> CompType { + CompType::Dummy + } +} + +static LOGGER: PvLogger = PvLogger; + +fn main() -> anyhow::Result<()> { + let mut args = Args::parse(); + let log_level = args.verbosity.to_level_filter(); + LOGGER + .start(log_level) + .with_context(|| "Failed to set-up logger")?; + + info!("# Preparing components"); + let mut layout = Layout::new(0x0, SecuredComponentBuilder::COMPONENT_ALIGNMENT_V1)?; + + // Don't store the prepared components anywhere as we're only interested in + // the hashes. + let mut writer = std::io::empty(); + let mut secure_comp_builer = SecuredComponentBuilder::new_v1(false)?; + + // Sort components by address in ascending order + args.components.sort_by(|a, b| a.addr.cmp(&b.addr)); + for component_arg in args.components { + info!("## Preparing {}", component_arg); + let mut comp = Comp { + reader: BufReader::new(open_file(&component_arg.path)?), + }; + + let comp_addr = component_arg.addr; + let _ = secure_comp_builer + .prepare_and_insert_as_secure_component( + &mut writer, + &mut layout, + &mut comp, + comp_addr, + component_arg.tweak, + ) + .with_context(|| { + format!( + "Failed to prepare component '{}'", + component_arg.path.display() + ) + })?; + } + + info!("\n# Creating Secure Execution Header"); + let addr = args.psw_addr; + let mask = args.psw_mask; + let mut builder = SeHdrBuilder::new( + SeHdrVersion::V1, + PSW { addr, mask }, + secure_comp_builer.finish()?, + )?; + let mut target_pub_keys = vec![]; + for hkd_path in args.host_key_documents { + info!( + "Use the file '{}' as a host key document", + hkd_path.display() + ); + let hkd_data = read_file(&hkd_path, "host key document")?; + let certs = read_certs(&hkd_data)?; + if certs.is_empty() { + return Err(PvError::NoHkdInFile(hkd_path.display().to_string()).into()); + } + + if certs.len() > 1 { + warn!("The host key document in '{}' contains more than one certificate! All keys will be used.", + hkd_path.display()); + } + + for cert in &certs { + target_pub_keys.push(cert.public_key()?); + } + } + builder.add_hostkeys(&target_pub_keys)?; + + let pcf = try_parse_u64(&args.pcf, "pcf")?.into(); + let scf = try_parse_u64(&args.scf, "scf")?.into(); + info!( + "PSW addr ............: {addr:#018x}\n\ + PSW mask ............: {mask:#018x}\n\ + PCF .................: {pcf}\n\ + SCF .................: {scf}" + ); + builder.with_pcf(&pcf)?; + builder.with_scf(&scf)?; + if let Some(cck) = args.cck { + info!("CCK ................: {}", cck.display()); + builder + .with_cck(read_file(&cck, "CCK")?.into()) + .with_context(|| format!("Invalid CCK in '{}'", &cck.display()))?; + } + + let mut output = AtomicFile::new(args.output, &mut OpenOptions::new())?; + output.write_all(&builder.build()?.as_bytes()?)?; + Ok(output.finish(AtomicFileOperation::Replace)?) +} diff --git a/rust/pvimg/src/lib.rs b/rust/pvimg/src/lib.rs new file mode 100644 index 00000000..5b3f5656 --- /dev/null +++ b/rust/pvimg/src/lib.rs @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2024 + +//! # Library for Secure Execution headers +//! +//! This crate provides functionalities for creating and inspecting Secure +//! Execution headers. It also provides support for preparing arbitrary +//! components as secured components and calculating the PLD, ALD, and TLD of +//! them. +//! +//! ## Secure Execution headers +//! ### Creation +//! +//! [`uvdata::SeHdrBuilder`] +//! +//! ### Serialization and Deserialization +//! +//! [`uvdata::SeHdr`] +//! +//! ## Secured components +//! +//! [`secured_comp::SecuredComponentBuilder`] and +//! [`secured_comp::SecuredComponent`]. + +#![allow(missing_docs)] + +mod pv_utils; + +pub mod misc { + pub const PAGESIZE: usize = 4096; + pub use crate::pv_utils::{ + bytesize, round_up, serialize_to_bytes, ShortPsw, PSW, PSW_MASK_BA, PSW_MASK_EA, + }; +} + +pub mod uvdata { + pub use crate::pv_utils::{ + BuilderTrait, ComponentMetadataV1, ControlFlagTrait, ControlFlagsTrait, FlagData, + KeyExchangeTrait, PcfV1, PlaintextControlFlagsV1, ScfV1, SeHdr, SeHdrBuilder, SeHdrDataV1, + SeHdrVersion, SecretControlFlagsV1, UvDataTrait, UvKeyHashesV1, + }; +} + +pub mod secured_comp { + pub use crate::pv_utils::{ + ComponentTrait, Interval, Layout, SecuredComponent, SecuredComponentBuilder, + }; +} + +pub mod error { + pub use crate::pv_utils::{Error, OwnExitCode, PvError, Result}; +} diff --git a/rust/pvimg/src/pv_utils.rs b/rust/pvimg/src/pv_utils.rs new file mode 100644 index 00000000..4bff3ac9 --- /dev/null +++ b/rust/pvimg/src/pv_utils.rs @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2024 + +mod error; +mod layout; +mod misc; +mod psw; +mod se_hdr; +mod secured_comp; +mod serializing; +mod uv_keys; +mod uvdata; +mod uvdata_builder; + +pub use error::{Error, OwnExitCode, PvError, Result}; +pub use layout::{Interval, Layout}; +pub use misc::{round_up, try_copy_slice_to_array}; +pub use psw::{ShortPsw, PSW, PSW_MASK_BA, PSW_MASK_EA}; +pub use se_hdr::{ + ComponentMetadataV1, ControlFlagTrait, ControlFlagsTrait, FlagData, PcfV1, + PlaintextControlFlagsV1, ScfV1, SeHdr, SeHdrBuilder, SeHdrDataV1, SeHdrVersion, + SecretControlFlagsV1, +}; +pub use secured_comp::{ComponentTrait, SecuredComponent, SecuredComponentBuilder}; +pub use serializing::{bytesize, serialize_to_bytes}; +pub use uv_keys::UvKeyHashesV1; +pub use uvdata::{KeyExchangeTrait, UvDataTrait}; +pub use uvdata_builder::BuilderTrait; diff --git a/rust/pvimg/src/pv_utils/error.rs b/rust/pvimg/src/pv_utils/error.rs new file mode 100644 index 00000000..2a176276 --- /dev/null +++ b/rust/pvimg/src/pv_utils/error.rs @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2024 + +pub use pv::Error as PvError; +pub use pv::PvCoreError; +use utils::{impl_exitcodetrait, ExitCodeTrait}; + +/// Result type for this crate +pub type Result = std::result::Result; + +/// Error cases for this crate +#[derive(thiserror::Error, Debug)] +#[non_exhaustive] +pub enum Error { + #[error("First image component was already prepared")] + FirstComponentAlreadyPrepared, + + #[error( + "Stage3b is already added so there is no possibility to add another secured component" + )] + ImgAlreadyFinalized, + + #[error("Invalid target key hash")] + InvalidTargetKeyHash, + + #[error("Invalid UV key hashes")] + InvalidUvKeyHashes, + + #[error("Invalid Secure Execution header")] + InvalidSeHdr, + + #[error("Invalid component metadata.")] + InvalidComponentMetadata, + + #[error("Invalid alignment {alignment} as it's larger than the chunk size {chunk_size}.")] + InvalidAlignment { alignment: u64, chunk_size: usize }, + + #[error("Invalid interval: Start {start} is larger than {stop}")] + InvalidInterval { start: u64, stop: u64 }, + + #[error( + "The given tweak size {given} is smaller than the expected tweak size, which is {expected}" + )] + InvalidTweakSize { given: usize, expected: usize }, + + #[error("Invalid customer communication key (CCK)")] + InvalidCCK { source: Box }, + + #[error("Invalid stage3a")] + InvalidStage3a, + + #[error("Invalid stage3b")] + InvalidStage3b, + + #[error("Interval overlaps: {0}")] + IntervalOverlap(String), + + #[error("Image already finalized")] + ImageAlreadyFinalized, + + #[error("Provided kernel cmdline is too large: {size} > {max_size}")] + KernelCmdlineTooLarge { size: usize, max_size: usize }, + + #[error("Cannot convert to short PSW")] + TryToShortPSWError, + + #[error("Address {addr:#0x} is not aligned to {alignment:#0x}")] + UnalignedAddress { addr: u64, alignment: u64 }, + + #[error("Support for query UV host key hashes is not available")] + UnavailableQueryUvKeyHashesSupport { source: PvCoreError }, + + #[error("ELF file found, but only raw binary kernels are supported.")] + UnexpectedElfFile, + + #[error("Unexpected arithmetic overflow")] + UnexpectedOverflow, + + #[error("Unexpected key type. Given {given}, expected {expected}")] + UnexpectedKeyType { given: String, expected: String }, + + #[error("Unsupported message digest")] + UnsupportMessageDigest, + + #[error("Unexpected arithmetic underflow")] + UnexpectedUnderflow, + + #[error( + "Address {addr:#0x} is smaller than the next possible address, which is {next_addr:#0x}" + )] + NonMonotonicallyIncreasing { addr: u64, next_addr: u64 }, + + #[error("No host key document provided")] + NoHostkey, + + #[error("No s390x Linux kernel provided")] + NoS390Kernel, + + #[error("Expert mode is not enabled")] + NonExpertMode, + + #[error("Tweaks can be specified in expert mode only")] + NonExpertModeTweakGiven, + + #[error("No plaintext control flag")] + NoPlainTextControlFlag, + + #[error("No secret control flag")] + NoSecretControlFlag, + + #[error("No Secure Execution header found.")] + NoSeHdrFound, + + #[error("Address {addr} is already used")] + NoUnusedAddr { addr: u64 }, + + #[error("Prepared component is too large for the given location: {output_size} > {max_output_size}")] + PreparedComponentTooLarge { + output_size: usize, + max_output_size: usize, + }, + + // Errors from other crates + #[error(transparent)] + Deku(#[from] deku::DekuError), + + #[error(transparent)] + Crypto(#[from] openssl::error::ErrorStack), + + #[error(transparent)] + Pv(#[from] PvError), + + #[error(transparent)] + PvCore(#[from] PvCoreError), + + #[error(transparent)] + Io(#[from] std::io::Error), + + #[error(transparent)] + TryFromIntError(#[from] std::num::TryFromIntError), +} + +impl_exitcodetrait!( + #[repr(u8)] + #[derive(Debug)] + pub enum OwnExitCode { + /// Program finished successfully + /// + /// The command was executed successfully. + Success = 0, + + /// Generic error + /// + /// Something went wrong during the operation. Refer to the error + /// message. + GenericError = 1, + + /// Usage error + /// + /// The command was used incorrectly, for example: unsupported command + /// line flag, or wrong number of arguments. + UsageError = 2, // same exit code as used by `Clap` crate + } +); + +impl From for std::process::ExitCode { + fn from(value: OwnExitCode) -> Self { + Self::from(value as u8) + } +} diff --git a/rust/pvimg/src/pv_utils/layout.rs b/rust/pvimg/src/pv_utils/layout.rs new file mode 100644 index 00000000..fddeaa91 --- /dev/null +++ b/rust/pvimg/src/pv_utils/layout.rs @@ -0,0 +1,400 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2024 +use std::{collections::BTreeSet, fmt::Display, rc::Rc}; + +use crate::{ + misc::round_up, + pv_utils::error::{Error, Result}, +}; + +/// Represents a range from [start, stop) (inclusive start, exclusive stop) +#[derive(Debug, Clone, Eq, Ord, PartialEq, PartialOrd)] +pub struct Interval { + pub start: u64, + pub stop: u64, +} + +impl Display for Interval { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + format!("start: {:#10x} stop: {:#10x}", self.start, self.stop - 1).fmt(f) + } +} + +impl Interval { + /// Create a new [`Interval`]. + /// + /// # Errors + /// + /// This function will return an error if `stop` is not larger than `start`. + const fn new(start: u64, stop: u64) -> Result { + if stop <= start { + return Err(Error::InvalidInterval { start, stop }); + } + Ok(Self { start, stop }) + } + + /// Creates a new [`Interval`] with the start address `start` and the size + /// of `size`. + /// + /// # Errors + /// + /// This function will return an error if `size == 0` or if there was an + /// unexpected overflow. + pub fn new_with_size(start: u64, size: u64) -> Result { + Self::new( + start, + start.checked_add(size).ok_or(Error::UnexpectedOverflow)?, + ) + } + + const fn contains(&self, addr: u64) -> bool { + addr >= self.start && addr < self.stop + } + + /// Returns the size of this [`Interval`]. + pub const fn size(&self) -> u64 { + self.stop - self.start + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Layout { + pub next_addr: u64, + pub alignment: u64, + chunks: BTreeSet>, +} + +impl Layout { + /// Creates a new [`Layout`]. + /// + /// # Errors + /// + /// This function will return an error if `start_addr` is not aligned. + pub fn new(start_addr: u64, alignment: u64) -> Result { + if start_addr != round_up(start_addr, alignment)? { + return Err(Error::UnalignedAddress { + addr: start_addr, + alignment, + }); + } + + Ok(Self { + next_addr: start_addr, + alignment, + chunks: BTreeSet::new(), + }) + } + + fn is_aligned(&self, addr: u64) -> Result { + Ok(addr == round_up(addr, self.alignment)?) + } + + fn overlaps(&self, b: &Interval) -> Option> { + for a in self.chunks.iter() { + if a.start < b.stop && b.start < a.stop { + return Some(a.clone()); + } + } + None + } + + /// Returns the maximum chunk size at the given address `addr`. If there is + /// no limit `None` is returned. + /// + /// # Errors + /// + /// This function will return an error if the address is in use already. + pub fn max_size_of_chunk_at_addr(&self, addr: u64) -> Result> { + if !self.is_aligned(addr)? { + return Err(Error::UnalignedAddress { + addr, + alignment: self.alignment, + }); + } + + if addr >= self.next_addr { + return Ok(None); + } + + for chunk in &self.chunks { + if chunk.contains(addr) { + return Err(Error::NoUnusedAddr { addr }); + } + + if chunk.start >= addr { + let max_size = usize::try_from(chunk.start - addr).unwrap(); + return Ok(Some(max_size)); + } + } + + Ok(None) + } + + /// Insert an interval in the layout. + /// + /// # Errors + /// + /// This function will return an error if the given address was unaligned or + /// the interval would overlap with an existing interval in the layout. + pub fn insert_interval(&mut self, addr: u64, size: u64) -> Result> { + let interval = Interval::new_with_size(addr, size)?; + + assert!(self.next_addr % self.alignment == 0); + + if interval.start != round_up(interval.start, self.alignment)? { + return Err(Error::UnalignedAddress { + addr: interval.start, + alignment: self.alignment, + }); + } + + if let Some(overlapped) = self.overlaps(&interval) { + let msg = format!("{} ... {}", overlapped, interval); + return Err(Error::IntervalOverlap(msg)); + } + + let interval = Rc::new(interval); + self.chunks.insert(interval.clone()); + let maybe_next_addr = interval + .start + .checked_add(round_up(size, self.alignment)?) + .ok_or(Error::UnexpectedOverflow)?; + if maybe_next_addr > self.next_addr { + self.next_addr = maybe_next_addr; + } + + assert!(self.next_addr % self.alignment == 0); + + Ok(interval) + } + + /// Creates and appends this newly created interval with size `size` to the + /// layout. Returns the created interval. + /// + /// # Errors + /// + /// This function will return an error if it was not possible to append the + /// newly created interval. + pub fn push(&mut self, size: u64) -> Result> { + let addr = self.next_addr; + self.insert_interval(addr, size) + } +} + +impl IntoIterator for Layout { + type IntoIter = > as IntoIterator>::IntoIter; + type Item = Rc; + + fn into_iter(self) -> Self::IntoIter { + self.chunks.into_iter() + } +} + +#[allow(clippy::shadow_unrelated)] +#[cfg(test)] +mod tests { + use std::{collections::BTreeSet, rc::Rc}; + + use proptest::{ + prelude::{Just, Strategy}, + prop_assert, prop_assert_eq, proptest, + }; + + use crate::pv_utils::{Interval, Layout}; + + proptest! { + #[test] + fn interval_new( + (a,b) in (0..u64::MAX).prop_flat_map(|a| (Just(a), 0..a)) + ) { + prop_assert!(b < a); + Interval::new(b, a).expect("should not fail"); + } + + #[test] + fn interval_new_with_size( + (start, size) in (0..u64::MAX).prop_flat_map(|a| (Just(a), 1..=u64::MAX - a)) + ) { + Interval::new_with_size(start, size).expect("should not fail"); + } + + #[test] + fn interval_contains( + (start, size, c) in (0_u16..4_u16).prop_flat_map(|a| (Just(a), 1_u16..4_u16)).prop_flat_map(|(a,b)| (Just(a), Just(b), a..(a + b))) + ) { + let interval = Interval::new_with_size(start.into(), size.into()).expect("should not fail"); + prop_assert!(interval.contains(c.into())); + } + + #[test] + fn interval_cmp( + (start, size, start2) in (1..16_u64).prop_flat_map(|a| (Just(a), 1..16_u16)).prop_flat_map(|(a,b)| (Just(a), Just(b), 0..a)) + ) { + let interval = Interval::new_with_size(start, size.into()).expect("should not fail"); + let interval2 = Interval::new_with_size(start2, size.into()).expect("should not fail"); + let interval3 = Interval::new_with_size(start, >::into(size) + 1).expect("should not fail"); + let interval4 = Interval::new_with_size(start, size.into()).expect("should not fail"); + prop_assert!(interval > interval2); + prop_assert!(interval != interval2); + prop_assert!(interval < interval3); + prop_assert!(interval == interval4); + } + + #[test] + fn interval_size((start, size) in (0..u64::MAX).prop_flat_map(|a| (Just(a), 1..=u64::MAX - a)) + ) + { + let interval = Interval::new_with_size(start, size).expect("should not fail"); + prop_assert_eq!(interval.size(), size); + } + } + + #[test] + fn interval_overflow() { + Interval::new_with_size(u64::MAX, 1).expect_err("should fail"); + } + + #[test] + fn memory_layout_test() { + // Unaligned start address + let layout = Layout::new(0x1_u64, 0x1000_u64); + assert!(layout.is_err()); + + let mut layout = Layout::new(0x1000_u64, 0x1000_u64).unwrap(); + assert_eq!( + layout, + Layout { + next_addr: 0x1000, + alignment: 0x1000, + chunks: BTreeSet::new(), + } + ); + layout.push(0x16).unwrap(); + layout.push(0x1000).unwrap(); + layout.push(0x1).unwrap(); + let mut bin = BTreeSet::from([ + Rc::new(Interval::new_with_size(0x1000, 0x16).expect("should not fail")), + Rc::new(Interval::new_with_size(0x2000, 0x1000).expect("should not fail")), + Rc::new(Interval::new_with_size(0x3000, 0x1).expect("should not fail")), + ]); + assert_eq!( + layout, + Layout { + next_addr: 0x4000, + alignment: 0x1000, + chunks: bin.clone() + } + ); + + // Invalid chunk size + assert!(layout.push(0x0).is_err()); + + // NonMonolithic address + assert!(layout.insert_interval(0x0, 0x1001).is_err()); + assert!(layout.insert_interval(0x0, 0x1000).is_ok()); + assert!(layout.insert_interval(0x10, 0x1000).is_err()); + + bin.insert(Rc::new( + Interval::new_with_size(0x0, 0x1000).expect("should not fail"), + )); + assert_eq!( + layout, + Layout { + next_addr: 0x4000, + alignment: 0x1000, + chunks: bin.clone() + } + ); + + assert!(layout.insert_interval(0x3000, 0x400).is_err()); + assert!(layout.insert_interval(0x4000, 0x400).is_ok()); + + bin.insert(Rc::new( + Interval::new_with_size(0x4000, 0x400).expect("should not fail"), + )); + assert_eq!( + layout, + Layout { + next_addr: 0x5000, + alignment: 0x1000, + chunks: bin + } + ); + } + + #[test] + fn test_max_interval_size_at_addr() { + let mut layout = Layout::new(0x0_u64, 0x1000_u64).expect("should not fail"); + assert_eq!( + layout, + Layout { + next_addr: 0x0, + alignment: 0x1000, + chunks: BTreeSet::new(), + } + ); + layout.push(0x16).unwrap(); + layout.push(0x1000).unwrap(); + layout.push(0x1).unwrap(); + layout.push(0x0).expect_err("should fail"); + let bin = BTreeSet::from([ + Rc::new(Interval::new_with_size(0x0, 0x16).expect("should not fail")), + Rc::new(Interval::new_with_size(0x1000, 0x1000).expect("should not fail")), + Rc::new(Interval::new_with_size(0x2000, 0x1).expect("should not fail")), + ]); + assert_eq!( + layout, + Layout { + next_addr: 0x3000, + alignment: 0x1000, + chunks: bin, + } + ); + + layout + .max_size_of_chunk_at_addr(0x5) + .expect_err("should fail"); + layout + .max_size_of_chunk_at_addr(0x2000) + .expect_err("should fail"); + layout + .max_size_of_chunk_at_addr(0x2fff) + .expect_err("should fail"); + assert_eq!( + layout + .max_size_of_chunk_at_addr(0x3000) + .expect("should not fail"), + None + ); + + layout.alignment = 1; + assert_eq!( + layout + .max_size_of_chunk_at_addr(0x16) + .expect("should not fail"), + Some(0x1000 - 0x16) + ); + assert_eq!( + layout + .max_size_of_chunk_at_addr(0xfff) + .expect("should not fail"), + Some(0x1) + ); + layout + .max_size_of_chunk_at_addr(0x1000) + .expect_err("should not fail"); + assert_eq!( + layout + .max_size_of_chunk_at_addr(0x2fff) + .expect("should not fail"), + None + ); + assert_eq!( + layout + .max_size_of_chunk_at_addr(0x3000) + .expect("should not fail"), + None + ); + } +} diff --git a/rust/pvimg/src/pv_utils/misc.rs b/rust/pvimg/src/pv_utils/misc.rs new file mode 100644 index 00000000..0328a312 --- /dev/null +++ b/rust/pvimg/src/pv_utils/misc.rs @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2024 + +use pv::PvCoreError; + +use crate::error::{Error, Result}; + +/// Rounds up the given `value` to a multiple of `multiple`. +/// +/// # Errors +/// +/// This function will return an error if there was an unexpected arithmetic +/// overflow. +pub fn round_up(value: u64, multiple: u64) -> Result { + assert!(multiple >= 1); + Ok((value + .checked_add(multiple) + .ok_or(Error::UnexpectedOverflow)? + - 1) + & !(multiple - 1)) +} + +/// Try to copy a slice to an array. +/// +/// # Errors +/// +/// This function will return an error if the length of the slice is not equal +/// to the length of the destination array. +pub fn try_copy_slice_to_array( + src: &[T], +) -> Result<[T; COUNT]> { + if COUNT != src.len() { + return Err(Error::PvCore(PvCoreError::LengthMismatch { + expected: COUNT, + actual: src.len(), + })); + } + + let mut result = [T::default(); COUNT]; + result.copy_from_slice(src); + Ok(result) +} + +#[allow(clippy::shadow_unrelated)] +#[cfg(test)] +mod tests { + use pv::PvCoreError; + + use crate::{ + error::{Error, Result}, + pv_utils::try_copy_slice_to_array, + }; + + #[test] + fn test_try_copy_slice_to_array() { + let data = vec![]; + let result: [u8; 0] = try_copy_slice_to_array(data.as_slice()).expect("should not fail"); + assert_eq!(data, result); + + let data = vec![0x1_u8, 0x2_u8, 0x3_u8]; + let result: [u8; 3] = try_copy_slice_to_array(data.as_slice()).expect("should not fail"); + assert_eq!(data, result); + + let result: Result<[u8; 4]> = try_copy_slice_to_array(data.as_slice()); + assert!(matches!( + result, + Err(Error::PvCore(PvCoreError::LengthMismatch { + expected: 4, + actual: 3 + })) + )); + } +} diff --git a/rust/pvimg/src/pv_utils/psw.rs b/rust/pvimg/src/pv_utils/psw.rs new file mode 100644 index 00000000..60d6c139 --- /dev/null +++ b/rust/pvimg/src/pv_utils/psw.rs @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2024 + +use deku::{ctx::Endian, DekuRead, DekuWrite}; +use pv::request::Zeroize; +use serde::Serialize; + +use super::serializing::ser_lower_hex; +use crate::pv_utils::error::Error; + +pub const PSW32_ADDR_MASK: u64 = 0x000000007fffffff; +pub const PSW_MASK_BA: u64 = 0x0000000080000000; +pub const PSW_MASK_EA: u64 = 0x0000000100000000; +pub const PSW_MASK_BIT_12: u64 = 0x08000000000000; + +#[derive(Default, Debug, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize)] +#[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")] +pub struct PSW { + #[serde(serialize_with = "ser_lower_hex")] + pub mask: u64, + #[serde(serialize_with = "ser_lower_hex")] + pub addr: u64, +} + +impl Zeroize for PSW { + fn zeroize(&mut self) { + self.mask.zeroize(); + self.addr.zeroize(); + } +} + +#[derive(Debug, Default, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize)] +#[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")] +pub struct ShortPsw(u64); + +impl From for PSW { + fn from(value: ShortPsw) -> Self { + let mask = value.0 & !PSW32_ADDR_MASK & !PSW_MASK_BIT_12; + let addr = value.0 & PSW32_ADDR_MASK; + Self { mask, addr } + } +} + +impl TryFrom for ShortPsw { + type Error = Error; + + fn try_from(value: PSW) -> Result { + // test if PSW mask can be converted + if value.mask & PSW32_ADDR_MASK != 0 { + return Err(Error::TryToShortPSWError); + } + + // test for bit 12 + if value.mask & PSW_MASK_BIT_12 != 0 { + return Err(Error::TryToShortPSWError); + } + + // test if PSW addr can be converted + if value.addr & !PSW32_ADDR_MASK != 0 { + return Err(Error::TryToShortPSWError); + } + + let mut short_psw = value.mask; + // Set bit 12 to 1 + short_psw |= PSW_MASK_BIT_12; + short_psw |= value.addr; + Ok(Self(short_psw)) + } +} + +#[cfg(test)] +mod tests { + use super::{ShortPsw, PSW}; + use crate::pv_utils::{error::Result, psw::PSW_MASK_BIT_12}; + + #[test] + fn test_from_psw_to_short_psw_ok() { + let psw = PSW { + mask: 0x180000000, + addr: 0x11000, + }; + + let short_psw_res: Result = psw.try_into(); + assert!(short_psw_res.is_ok()); + let short_psw = short_psw_res.unwrap(); + + assert_eq!(short_psw, ShortPsw(0x8000180011000)); + } + + #[test] + fn test_from_psw_to_short_psw_mask_bit12_is_set() { + let psw = PSW { + mask: PSW_MASK_BIT_12, + addr: 0x11000, + }; + + let short_psw_res: Result = psw.try_into(); + assert!(short_psw_res.is_err()); + } + + #[test] + fn test_from_psw_to_short_psw_mask_too_large() { + let psw = PSW { + mask: 0x8000180011000, + addr: 0x11000, + }; + + let short_psw_res: Result = psw.try_into(); + assert!(short_psw_res.is_err()); + } + + #[test] + fn test_from_psw_to_short_psw_addr_too_large() { + let psw = PSW { + mask: 0x180000000, + addr: 0x8000180011000, + }; + + let short_psw_res: Result = psw.try_into(); + assert!(short_psw_res.is_err()); + } + + #[test] + fn test_from_psw_to_short_psw_and_vice_versa() { + let psw = PSW { + mask: 0x180000000, + addr: 0x11000, + }; + + let short_psw_res: Result = psw.clone().try_into(); + assert!(short_psw_res.is_ok()); + let short_psw = short_psw_res.unwrap(); + + let new_psw: PSW = short_psw.into(); + assert_eq!(new_psw, psw); + } +} diff --git a/rust/pvimg/src/pv_utils/se_hdr.rs b/rust/pvimg/src/pv_utils/se_hdr.rs new file mode 100644 index 00000000..3b91210e --- /dev/null +++ b/rust/pvimg/src/pv_utils/se_hdr.rs @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2024 + +mod brb; +mod builder; +mod flags; +mod hdr_v1; +mod keys; + +pub use brb::{ + ComponentMetadata, ComponentMetadataV1, SeHdr, SeHdrDataV1, SeHdrPlain, SeHdrVersion, +}; +pub use brb::{SeHdrBinV1, SeHdrData, SeHdrVersioned}; +pub use builder::SeHdrBuilder; +pub use flags::{ + ControlFlagTrait, ControlFlagsTrait, FlagData, PcfV1, PlaintextControlFlagsV1, ScfV1, + SecretControlFlagsV1, +}; diff --git a/rust/pvimg/src/pv_utils/se_hdr/brb.rs b/rust/pvimg/src/pv_utils/se_hdr/brb.rs new file mode 100644 index 00000000..f7ae1bc9 --- /dev/null +++ b/rust/pvimg/src/pv_utils/se_hdr/brb.rs @@ -0,0 +1,368 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2024 +use std::{ + io::{Read, Seek, SeekFrom}, + mem::size_of, +}; + +use deku::{ctx::Endian, prelude::*}; +use enum_dispatch::enum_dispatch; +use pv::{ + request::{ + openssl::pkey::{PKey, PKeyRef, Private, Public}, + seek_se_hdr_start, Aes256XtsKey, Confidential, SymKey, SymKeyType, + }, + static_assert, +}; +use serde::Serialize; + +pub use super::hdr_v1::{SeHdrBinV1, SeHdrDataV1}; +use super::{PlaintextControlFlagsV1, SecretControlFlagsV1}; +use crate::{ + misc::PAGESIZE, + pv_utils::{ + error::{Error, Result}, + serializing::{ser_hex, serialize_to_bytes}, + uvdata::{ + AeadCipherTrait, AeadDataTrait, AeadPlainDataTrait, KeyExchangeTrait, UvDataPlainTrait, + UvDataTrait, + }, + uvdata_builder::{AeadCipherBuilderTrait, KeyExchangeBuilderTrait}, + PSW, + }, +}; + +#[repr(u32)] +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq, DekuRead, DekuWrite, Serialize)] +#[deku( + endian = "endian", + id_type = "u32", + ctx = "endian: Endian", + ctx_default = "Endian::Big" +)] +pub enum SeHdrVersion { + /// Secure Execution header v1 + V1 = 0x100, +} + +#[repr(C)] +#[derive(Debug, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize)] +#[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")] +pub struct SeHdrCommon { + #[serde(serialize_with = "ser_hex")] + pub magic: [u8; 8], + pub version: SeHdrVersion, +} +static_assert!(::std::mem::size_of::() == 12); + +#[repr(C)] +#[derive(Debug, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize)] +#[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")] +pub struct SeHdrCommonWithSize { + pub magic: [u8; 8], + pub version: SeHdrVersion, + pub sehs: u32, +} +static_assert!(::std::mem::size_of::() == 16); + +impl SeHdrCommon { + /// Magic value for a SE-header (FIXME as soon as `concat_bytes!(b"IBMSecEx`") is stable) + pub(crate) const MAGIC: &'static [u8; 8] = &[73, 66, 77, 83, 101, 99, 69, 120]; + + pub(crate) const fn new(version: SeHdrVersion) -> Self { + Self { + magic: *Self::MAGIC, + version, + } + } +} + +#[derive(Clone, PartialEq, Eq, Debug, DekuRead, DekuWrite, Serialize)] +#[deku(endian = "Endian::Big")] +/// Secure Execution header structure +pub struct SeHdr { + /// Common Secure Execution header part + #[serde(flatten)] + pub(crate) common: SeHdrCommon, + #[serde(flatten)] + #[deku(ctx = "common.version")] + pub(crate) data: SeHdrVersioned, +} + +#[derive(Clone, PartialEq, Eq, Debug, DekuRead, DekuWrite, Serialize)] +#[deku(endian = "Endian::Big")] +/// Plain data Secure Execution header structure +pub struct SeHdrPlain { + #[serde(flatten)] + pub(crate) common: SeHdrCommon, + #[serde(flatten)] + #[deku(ctx = "common.version")] + pub(crate) data: SeHdrData, +} + +#[enum_dispatch(AeadCipherTrait, AeadDataTrait, KeyExchangeTrait)] +#[derive(Clone, PartialEq, Eq, Debug, DekuRead, DekuWrite, Serialize)] +#[serde(untagged)] +#[deku(ctx = "_endian: Endian, version: SeHdrVersion", id = "version")] +pub enum SeHdrVersioned { + #[deku(id = "SeHdrVersion::V1")] + SeHdrBinV1(SeHdrBinV1), +} + +#[enum_dispatch( + AeadCipherTrait, + AeadPlainDataTrait, + KeyExchangeTrait, + KeyExchangeBuilderTrait +)] +#[derive(Clone, PartialEq, Eq, Debug, DekuRead, DekuWrite, Serialize)] +#[serde(untagged)] +#[deku(ctx = "_endian: Endian, version: SeHdrVersion", id = "version")] +pub enum SeHdrData { + #[deku(id = "SeHdrVersion::V1")] + SeHdrDataV1(SeHdrDataV1), +} + +impl AeadCipherBuilderTrait for SeHdrData { + fn set_iv(&mut self, iv: &[u8]) -> Result<()> { + match self { + Self::SeHdrDataV1(data) => data.set_iv(iv), + } + } +} + +#[enum_dispatch(SeHdrData)] +pub trait SeHdrPubBuilderTrait { + // Payload related methods + fn set_components(&mut self, meta: ComponentMetadata) -> Result<()>; + fn set_pcf(&mut self, pcf: &PlaintextControlFlagsV1) -> Result<()>; +} + +#[enum_dispatch(SeHdrData)] +pub trait SeHdrConfBuilderTrait { + fn generate_cck(&self) -> Result; + fn set_cck(&mut self, cck: Confidential>) -> Result<()>; + fn set_psw(&mut self, psw: &PSW); + fn set_scf(&mut self, scf: &SecretControlFlagsV1) -> Result<()>; +} + +#[enum_dispatch(SeHdr)] +#[allow(unused)] +pub trait SeHdrTrait: UvDataTrait {} + +#[enum_dispatch(SeHdr)] +#[allow(unused)] +pub trait SeHdrPlainTrait: UvDataPlainTrait {} + +impl AeadCipherTrait for SeHdr { + fn aead_key_type(&self) -> SymKeyType { + self.data.aead_key_type() + } + + fn iv(&self) -> &[u8] { + self.data.iv() + } + + fn aead_tag_size(&self) -> usize { + self.data.aead_tag_size() + } +} + +impl AeadDataTrait for SeHdr { + fn aad(&self) -> Vec { + [serialize_to_bytes(&self.common).unwrap(), self.data.aad()].concat() + } + + fn data(&self) -> Vec { + self.data.data() + } + + fn tag(&self) -> Vec { + self.data.tag() + } +} + +impl KeyExchangeTrait for SeHdr { + fn contains_hash>(&self, hash: H) -> bool { + self.data.contains_hash(hash) + } + + fn contains>>(&self, key: K) -> Result { + self.data.contains(key) + } + + fn cust_pub_key(&mut self) -> Result> { + self.data.cust_pub_key() + } + + fn key_type(&self) -> SymKeyType { + self.aead_key_type() + } +} + +impl UvDataTrait for SeHdr { + type P = SeHdrPlain; +} + +impl SeHdr { + /// Seek to the start of the next Secure Execution header. + /// + /// # Errors + /// + /// This function will return an error if no Secure Execution header was + /// found or the IO operation has failed. + pub fn seek_sehdr(reader: &mut R, addr: Option) -> Result<()> { + if let Some(addr) = addr { + reader.seek(SeekFrom::Start(addr))?; + } + if !seek_se_hdr_start(reader)? { + return Err(Error::NoSeHdrFound); + } + Ok(()) + } + + /// Serializes the [`SeHdr`] to a byte vector. + /// + /// # Errors + /// + /// This function will return an error if the Secure Execution header could + /// not be serialized. + pub fn as_bytes(&self) -> Result> { + serialize_to_bytes(self) + } + + /// Deserializes a Secure Execution header from an I/O stream. + /// + /// # Errors + /// + /// This function will return an error if no Secure Execution header could + /// be read, e.g. because no Secure Execution header was found. + pub fn try_from_io(mut reader: R) -> Result + where + R: Read, + { + let common_size = size_of::(); + let mut data = vec![0_u8; common_size]; + + reader.read_exact(&mut data)?; + + let (_, common) = SeHdrCommonWithSize::from_bytes((&data, 0))?; + if &common.magic != SeHdrCommon::MAGIC { + return Err(Error::NoSeHdrFound); + } + let sehs = common.sehs.try_into()?; + + // DoS attack prevention + if sehs > 1024 * PAGESIZE { + return Err(Error::InvalidSeHdr); + } + + data.resize(sehs, 0); + reader.read_exact(&mut data[common_size..])?; + Self::try_from_data(&data) + } +} + +impl AeadCipherBuilderTrait for SeHdrPlain { + fn set_iv(&mut self, iv: &[u8]) -> Result<()> { + self.data.set_iv(iv) + } +} + +impl KeyExchangeBuilderTrait for SeHdrPlain { + fn add_keyslot( + &mut self, + hostkey: &PKeyRef, + aead_key: &SymKey, + priv_key: &PKeyRef, + ) -> Result<()> { + self.data.add_keyslot(hostkey, aead_key, priv_key) + } + + fn clear_keyslots(&mut self) -> Result<()> { + self.data.clear_keyslots() + } + + fn generate_private_key(&self) -> Result> { + self.data.generate_private_key() + } + + fn set_cust_public_key(&mut self, key: &PKeyRef) -> Result<()> { + self.data.set_cust_public_key(key) + } +} + +#[derive(Debug, Clone)] +pub struct ComponentMetadataV1 { + pub ald: [u8; 64], + pub pld: [u8; 64], + pub tld: [u8; 64], + pub nep: u64, + pub key: Aes256XtsKey, +} + +/// The `enum_dispatch` macros needs at least one local trait to be implemented. +#[allow(unused)] +#[enum_dispatch] +trait ComponentMetadataTrait {} + +#[non_exhaustive] +#[enum_dispatch(ComponentMetadataTrait)] +#[derive(Debug)] +pub enum ComponentMetadata { + ComponentMetadataV1(ComponentMetadataV1), +} + +impl KeyExchangeTrait for SeHdrPlain { + fn contains>>(&self, key: K) -> Result { + self.data.contains(key) + } + + fn cust_pub_key(&mut self) -> Result> { + self.data.cust_pub_key() + } + + fn key_type(&self) -> SymKeyType { + self.data.key_type() + } + + fn contains_hash>(&self, hash: H) -> bool { + self.data.contains_hash(hash) + } +} + +impl UvDataPlainTrait for SeHdrPlain { + type C = SeHdr; +} + +impl AeadPlainDataTrait for SeHdrPlain { + fn aad(&self) -> Vec { + let data_aad = self.data.aad(); + + [serialize_to_bytes(&self.common).unwrap(), data_aad].concat() + } + + fn data(&self) -> Confidential> { + self.data.data() + } + + fn tag(&self) -> Vec { + self.data.tag() + } +} + +impl AeadCipherTrait for SeHdrPlain { + fn aead_key_type(&self) -> SymKeyType { + self.data.aead_key_type() + } + + fn iv(&self) -> &[u8] { + self.data.iv() + } + + fn aead_tag_size(&self) -> usize { + self.data.aead_tag_size() + } +} diff --git a/rust/pvimg/src/pv_utils/se_hdr/builder.rs b/rust/pvimg/src/pv_utils/se_hdr/builder.rs new file mode 100644 index 00000000..ba6de898 --- /dev/null +++ b/rust/pvimg/src/pv_utils/se_hdr/builder.rs @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2024 + +use pv::request::Confidential; + +use super::{hdr_v1::SeHdrDataV1, SeHdr}; +use crate::pv_utils::{ + error::{Error, Result}, + se_hdr::{ + brb::{SeHdrCommon, SeHdrConfBuilderTrait, SeHdrData, SeHdrPubBuilderTrait}, + ComponentMetadata, SeHdrPlain, SeHdrVersion, + }, + uvdata::UvDataPlainTrait, + uvdata_builder::{ + AeadCipherBuilderTrait, BuilderTrait, KeyExchangeBuilderTrait, UvDataBuilder, + }, + PlaintextControlFlagsV1, SecretControlFlagsV1, PSW, +}; + +/// `SeHdrBuilder` +pub type SeHdrBuilder<'a> = UvDataBuilder<'a, SeHdrPlain>; + +impl SeHdrBuilder<'_> { + pub fn new>( + version: SeHdrVersion, + psw: PSW, + components_meta: M, + ) -> Result { + let (data, aead_key, priv_key) = match version { + SeHdrVersion::V1 => { + let mut data = SeHdrDataV1::new( + psw, + components_meta + .into() + .try_into() + .map_err(|_| Error::InvalidComponentMetadata)?, + )?; + let aead_key = data.generate_aead_key()?; + let priv_key = data.generate_private_key()?; + data.set_cust_public_key(&priv_key)?; + (SeHdrData::SeHdrDataV1(data), aead_key, priv_key) + } + }; + let common = SeHdrCommon::new(version); + let hdr = SeHdrPlain { common, data }; + Ok(Self { + plain_data: hdr, + target_keys: Vec::new(), + prot_key: aead_key, + expert_mode: false, + priv_key, + }) + } + + pub fn with_cck(&mut self, cck: Confidential>) -> Result<&mut Self> { + self.plain_data + .data + .set_cck(cck) + .map_err(|err| Error::InvalidCCK { + source: Box::new(err), + })?; + Ok(self) + } + + pub fn with_components>(&mut self, meta: M) -> Result<&mut Self> { + self.plain_data.data.set_components(meta.into())?; + Ok(self) + } + + pub fn with_pcf(&mut self, flags: &PlaintextControlFlagsV1) -> Result<&mut Self> { + self.plain_data.data.set_pcf(flags)?; + Ok(self) + } + + pub fn with_scf(&mut self, flags: &SecretControlFlagsV1) -> Result<&mut Self> { + self.plain_data.data.set_scf(flags)?; + Ok(self) + } +} + +impl BuilderTrait for SeHdrBuilder<'_> { + type T = SeHdr; + + fn build(self) -> Result { + // At least one target key must be set + if self.target_keys.is_empty() { + return Err(Error::NoHostkey); + } + + self.plain_data.encrypt(&self.prot_key) + } +} + +#[cfg(test)] +mod tests { + use std::io::Cursor; + + use pv::{ + request::{Confidential, SymKeyType, SHA_512_HASH_LEN}, + test_utils::get_test_key_and_cert, + }; + + use super::*; + use crate::pv_utils::{ + se_hdr::ComponentMetadataV1, + uvdata::{AeadDataTrait, AeadPlainDataTrait}, + UvDataTrait, + }; + + #[test] + fn builder_test() { + use pv::test_utils::get_test_key_and_cert; + + let (cust_key, host_key) = get_test_key_and_cert(); + let host_keys = [host_key.public_key().unwrap()]; + let xts_key = Confidential::new([0x3; SymKeyType::AES_256_XTS_KEY_LEN]); + let xts_key2 = Confidential::new([0x3; SymKeyType::AES_256_XTS_KEY_LEN]); + let mut builder = SeHdrBuilder::new( + SeHdrVersion::V1, + PSW { + addr: 1234, + mask: 5678, + }, + ComponentMetadata::ComponentMetadataV1(ComponentMetadataV1 { + ald: [0x1; SHA_512_HASH_LEN], + pld: [0x2; SHA_512_HASH_LEN], + tld: [0x3; SHA_512_HASH_LEN], + nep: 1, + key: xts_key, + }), + ) + .expect("should not fail"); + + // builder.add_comp_data(addr, tweak, )?; + builder + .with_components(ComponentMetadataV1 { + ald: [0x1; SHA_512_HASH_LEN], + pld: [0x2; SHA_512_HASH_LEN], + tld: [0x3; SHA_512_HASH_LEN], + nep: 1, + key: xts_key2, + }) + .expect("should not fail"); + builder + .with_priv_key(&cust_key) + .expect_err("Error expected as expert mode is not enabled"); + + builder.expert_mode = true; + builder.with_priv_key(&cust_key).expect("should not fail"); + + // Set CCK + // Too large key + builder + .with_cck([49; SymKeyType::AES_256_GCM_KEY_LEN - 1].to_vec().into()) + .expect_err("should fail"); + // Too small key + builder + .with_cck([49; SymKeyType::AES_256_GCM_KEY_LEN + 1].to_vec().into()) + .expect_err("should fail"); + + builder + .with_cck([49; SymKeyType::AES_256_GCM_KEY_LEN].to_vec().into()) + .expect("should not fail"); + + // Set protection key + // Too large key + builder + .with_aead_key(Confidential::new([50; 33].into())) + .expect_err("should fail"); + // Too small key + builder + .with_aead_key(Confidential::new([50; 31].into())) + .expect_err("should fail"); + + builder + .with_aead_key(Confidential::new([50; 32].into())) + .expect("should not fail"); + + // Set IV + // Too large IV + builder.with_iv(&[51; 13]).expect_err("should fail"); + // Too small IV + builder.with_iv(&[51; 11]).expect_err("should fail"); + + builder.with_iv(&[51; 12]).expect("should not fail"); + + builder.add_hostkeys(&host_keys).expect("should not fail"); + + let prot_key = builder.prot_key().clone(); + let bin = builder.build().expect("wuhu"); + assert_eq!(bin.common.version, SeHdrVersion::V1); + assert_eq!(bin.as_bytes().expect("should not fail").len(), 640); + assert_eq!( + bin.as_bytes().expect("should not fail"), + [ + 73, 66, 77, 83, 101, 99, 69, 120, 0, 0, 1, 0, 0, 0, 2, 128, 51, 51, 51, 51, 51, 51, + 51, 51, 51, 51, 51, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 128, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 199, 93, 52, 249, 22, 82, 219, 69, 123, 11, 32, 156, 70, 164, 145, + 164, 78, 226, 177, 110, 35, 194, 216, 218, 241, 22, 103, 138, 98, 242, 76, 227, 50, + 197, 153, 95, 8, 69, 107, 102, 177, 109, 213, 90, 146, 197, 7, 241, 227, 26, 247, + 140, 100, 168, 46, 122, 84, 27, 21, 19, 80, 21, 242, 2, 134, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 64, 128, 88, 167, 241, 165, 195, 80, 151, 83, 58, 2, 169, + 56, 121, 231, 222, 103, 186, 40, 11, 206, 131, 101, 236, 148, 178, 185, 8, 245, + 137, 195, 169, 152, 216, 190, 30, 99, 7, 215, 74, 224, 26, 220, 70, 130, 95, 246, + 187, 111, 160, 92, 17, 71, 207, 226, 204, 244, 162, 79, 61, 131, 61, 218, 112, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 65, 92, 37, + 165, 209, 156, 56, 30, 97, 151, 51, 225, 193, 183, 251, 216, 139, 221, 28, 49, 216, + 130, 213, 173, 224, 75, 151, 4, 60, 80, 16, 240, 229, 82, 102, 228, 113, 137, 5, + 64, 29, 48, 138, 18, 148, 179, 136, 59, 221, 205, 98, 76, 41, 121, 59, 220, 160, + 12, 56, 212, 171, 77, 85, 253, 38, 196, 235, 112, 49, 183, 94, 171, 221, 120, 96, + 65, 149, 102, 55, 59, 180, 25, 143, 227, 222, 144, 3, 77, 240, 4, 217, 205, 199, + 175, 123, 1, 191, 76, 78, 99, 115, 131, 5, 160, 112, 142, 117, 125, 30, 239, 7, 51, + 239, 66, 173, 61, 243, 199, 20, 71, 115, 107, 113, 139, 68, 200, 219, 233, 84, 220, + 108, 242, 133, 71, 91, 154, 160, 171, 4, 32, 67, 90, 107, 216, 149, 141, 210, 20, + 125, 4, 39, 73, 163, 75, 1, 148, 78, 245, 135, 76, 68, 42, 164, 174, 185, 216, 29, + 60, 76, 28, 232, 191, 209, 218, 134, 184, 110, 154, 227, 144, 66, 213, 145, 93, + 157, 150, 61, 80, 69, 238, 6, 190, 191, 202, 172, 221, 159, 190, 62, 253, 67, 162, + 142, 245, 109, 23, 26, 102, 113, 101, 23, 64, 85, 249, 255, 250, 30, 28, 136, 135, + 187, 109, 118, 222 + ] + ); + + let decrypted = bin.decrypt(&prot_key).expect("BUG"); + assert_eq!(bin.common, decrypted.common); + assert_eq!(bin.aad(), decrypted.aad()); + assert_ne!(&bin.data(), decrypted.data().value()); + let _decrypted_hdrv1: SeHdrDataV1 = decrypted.data.try_into().expect("BUG"); + } + + #[test] + fn chain_test() { + let (_, host_key) = get_test_key_and_cert(); + let host_keys = [host_key.public_key().unwrap()]; + let xts_key = Confidential::new([0x3; SymKeyType::AES_256_XTS_KEY_LEN]); + let meta = ComponentMetadataV1 { + ald: [0x1; SHA_512_HASH_LEN], + pld: [0x2; SHA_512_HASH_LEN], + tld: [0x3; SHA_512_HASH_LEN], + nep: 3, + key: xts_key, + }; + let cck = Confidential::new([0x42; 32].to_vec()); + let mut builder = SeHdrBuilder::new( + SeHdrVersion::V1, + PSW { + addr: 1234, + mask: 5678, + }, + meta, + ) + .expect("should not fail"); + + let prot_key = builder.prot_key().to_owned(); + builder + .add_hostkeys(&host_keys) + .expect("should not fail") + .with_cck(cck) + .expect("should not fail"); + let bin = builder.build().expect("should not fail"); + + let reader = Cursor::new(bin.as_bytes().expect("should not fail")); + let hdr = SeHdr::try_from_io(reader).unwrap(); + let hdr_plain = hdr.decrypt(&prot_key).unwrap(); + assert_eq!(hdr_plain.common.version, SeHdrVersion::V1); + assert_eq!(hdr_plain.common.version, hdr.common.version); + let _hdr_data_v1: SeHdrDataV1 = hdr_plain.data.try_into().expect("should not fail"); + } +} diff --git a/rust/pvimg/src/pv_utils/se_hdr/components.rs b/rust/pvimg/src/pv_utils/se_hdr/components.rs new file mode 100644 index 00000000..e69de29b diff --git a/rust/pvimg/src/pv_utils/se_hdr/flags.rs b/rust/pvimg/src/pv_utils/se_hdr/flags.rs new file mode 100644 index 00000000..0b5607bf --- /dev/null +++ b/rust/pvimg/src/pv_utils/se_hdr/flags.rs @@ -0,0 +1,308 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2024 + +use std::{fmt::Display, marker::PhantomData, mem::size_of}; + +use pv::misc::{Flags, Msb0Flags64}; + +pub trait ControlFlagTrait: std::fmt::Debug + std::hash::Hash + Copy + Eq + Ord { + fn discriminant(&self) -> u8 { + assert!(size_of::() == size_of::()); + unsafe { *(self as *const Self as *const u8) } + } + + fn enabled(self) -> FlagData { + FlagData::new(self, FlagState::Enabled) + } + + fn disabled(self) -> FlagData { + FlagData::new(self, FlagState::Disabled) + } + + fn all_enabled>(flags: F) -> Vec> { + flags + .as_ref() + .iter() + .map(|flag| (*flag).enabled()) + .collect() + } + + fn all_disabled>(flags: F) -> Vec> { + flags + .as_ref() + .iter() + .map(|flag| (*flag).disabled()) + .collect() + } +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] +enum FlagState { + Enabled, + Disabled, +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] +pub struct FlagData { + value: T, + state: FlagState, +} + +impl FlagData { + const fn new(value: T, state: FlagState) -> Self { + Self { value, state } + } +} + +pub trait ControlFlagsTrait: Display { + type T: ControlFlagTrait; + + fn from_flags]>>(flags: F) -> Self; + fn parse_flags]>>(&mut self, flags: F); + fn is_set(&self, flag: Self::T) -> bool; + fn is_unset(&self, flag: Self::T) -> bool { + !self.is_set(flag) + } + + fn no_duplicates]>>(flags: F) -> bool { + let mut flags_sorted = flags.as_ref().to_vec(); + flags_sorted.sort_by_key(|data| data.value); + flags_sorted.dedup_by_key(|data| data.value); + + flags_sorted.len() == flags.as_ref().len() + } + + fn all_set>(&self, flags: F) -> bool { + flags.as_ref().iter().all(|flag| self.is_set(*flag)) + } + + fn all_unset>(&self, flags: F) -> bool { + flags.as_ref().iter().all(|flag| self.is_unset(*flag)) + } +} + +/// Bitflags as used by the Secure Execution in MSB0 ordering +/// +/// Wraps an u64 to set/get individual bits +#[derive(Clone, Copy, Debug)] +pub struct ControlFlags { + flags: Msb0Flags64, + t: PhantomData, +} + +impl ControlFlags { + fn new() -> Self { + Self { + flags: 0x0.into(), + t: PhantomData {}, + } + } +} + +impl From for ControlFlags { + fn from(value: u64) -> Self { + Self { + flags: value.into(), + t: PhantomData, + } + } +} + +impl From<&ControlFlags> for u64 { + fn from(value: &ControlFlags) -> Self { + value.flags.into() + } +} + +impl From> for u64 { + fn from(value: ControlFlags) -> Self { + value.flags.into() + } +} + +impl ControlFlagsTrait for ControlFlags { + type T = T; + + fn from_flags]>>(flags: F) -> Self { + let mut ret = Self::new(); + ret.parse_flags(flags); + ret + } + + fn parse_flags]>>(&mut self, flags: F) { + flags.as_ref().iter().for_each(|v| match v.state { + FlagState::Enabled => self.flags.set_bit(v.value.discriminant()), + FlagState::Disabled => self.flags.unset_bit(v.value.discriminant()), + }); + } + + fn is_set(&self, flag: T) -> bool { + self.flags.is_set(flag.discriminant()) + } +} + +impl Display for ControlFlags { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let value: u64 = self.flags.into(); + write!(f, "{:#018x}", value) + } +} + +#[repr(u8)] +#[non_exhaustive] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub enum PcfV1 { + /// PV guest dump support. + AllowDumping = 34, + /// The components are not decrypted during the image unpack. + NoComponentEncryption = 35, + /// DEA/TDEA PCKMO encryption function are allowed. + PckmoDeaTdea = 56, + /// AES PCKMO encryption function are allowed. + PckmoAes = 57, + /// ECC PCKMO encryption function are allowed. + PckmoEcc = 58, + /// HMAC PCKMO encryption function are allowed. + PckmoHmac = 59, + /// Backup target keys can be used. + BackupTargetKeys = 62, +} +pub type PlaintextControlFlagsV1 = ControlFlags; + +impl Default for PlaintextControlFlagsV1 { + fn default() -> Self { + Self::from_flags(PcfV1::all_enabled([ + PcfV1::PckmoAes, + PcfV1::PckmoDeaTdea, + PcfV1::PckmoEcc, + ])) + } +} + +impl Display for PcfV1 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::AllowDumping => "allow dumping", + Self::NoComponentEncryption => "no component encryption", + Self::PckmoDeaTdea => "DEA and TDEA PCMKO", + Self::PckmoAes => "AES", + Self::PckmoEcc => "ECC PCKMO", + Self::PckmoHmac => "HMAC PCKMO", + Self::BackupTargetKeys => "backup target keys", + } + ) + } +} + +impl ControlFlagTrait for PcfV1 {} + +#[repr(u8)] +#[non_exhaustive] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub enum ScfV1 { + /// All add-secret requests must provide an extension secret + CCKExtensionSecretEnforcment = 1, +} +pub type SecretControlFlagsV1 = ControlFlags; +impl ControlFlagTrait for ScfV1 {} + +impl Default for SecretControlFlagsV1 { + fn default() -> Self { + Self::from_flags(ScfV1::all_enabled([])) + } +} + +#[allow(clippy::shadow_unrelated)] +#[cfg(test)] +mod test { + + use super::{ControlFlagTrait, ControlFlagsTrait, PcfV1, PlaintextControlFlagsV1}; + + #[test] + fn test_from_flags() { + let flags = PlaintextControlFlagsV1::from_flags(&[]); + assert_eq!(u64::from(flags), 0_u64); + + let flags = PlaintextControlFlagsV1::from_flags([PcfV1::AllowDumping.enabled()]); + assert_eq!(u64::from(&flags), 536870912); + assert!(flags.is_set(PcfV1::AllowDumping)); + + let flags = PlaintextControlFlagsV1::from_flags([ + PcfV1::AllowDumping.enabled(), + PcfV1::AllowDumping.disabled(), + ]); + assert_eq!(u64::from(flags), 0); + + let flags = PlaintextControlFlagsV1::from_flags([ + PcfV1::AllowDumping.disabled(), + PcfV1::AllowDumping.enabled(), + ]); + assert_eq!(u64::from(&flags), 536870912); + + let flags = PlaintextControlFlagsV1::from_flags([ + PcfV1::AllowDumping.enabled(), + PcfV1::BackupTargetKeys.enabled(), + ]); + assert_eq!(u64::from(&flags), 536870914); + } + + #[test] + fn test_all_set_unset() { + let flags = PlaintextControlFlagsV1::from_flags([ + PcfV1::AllowDumping.enabled(), + PcfV1::BackupTargetKeys.enabled(), + ]); + assert!(flags.all_set([PcfV1::AllowDumping, PcfV1::BackupTargetKeys])); + assert!(!flags.all_set([PcfV1::NoComponentEncryption, PcfV1::BackupTargetKeys])); + assert!(!flags.all_unset([PcfV1::NoComponentEncryption, PcfV1::BackupTargetKeys])); + assert!(flags.all_unset([PcfV1::NoComponentEncryption, PcfV1::PckmoHmac])); + } + + #[test] + fn test_display() { + let flags = PlaintextControlFlagsV1::from_flags([PcfV1::NoComponentEncryption.enabled()]); + assert_eq!("0x0000000010000000", format!("{}", flags)); + + let flags = PlaintextControlFlagsV1::from_flags([ + PcfV1::AllowDumping.enabled(), + PcfV1::BackupTargetKeys.enabled(), + PcfV1::NoComponentEncryption.enabled(), + PcfV1::PckmoAes.enabled(), + PcfV1::PckmoDeaTdea.enabled(), + PcfV1::PckmoEcc.enabled(), + PcfV1::PckmoHmac.enabled(), + ]); + assert_eq!("0x00000000300000f2", format!("{}", flags)); + } + + #[test] + fn test_no_duplicates() { + let flags: Vec<_> = [ + PcfV1::all_disabled([PcfV1::PckmoAes, PcfV1::PckmoDeaTdea, PcfV1::PckmoEcc]), + PcfV1::all_enabled([PcfV1::PckmoAes, PcfV1::PckmoDeaTdea, PcfV1::PckmoEcc]), + ] + .into_iter() + .flatten() + .collect(); + assert!(!PlaintextControlFlagsV1::no_duplicates(flags)); + + let flags: Vec<_> = [ + PcfV1::all_disabled([PcfV1::PckmoAes]), + PcfV1::all_enabled([PcfV1::PckmoDeaTdea, PcfV1::PckmoEcc]), + ] + .into_iter() + .flatten() + .collect(); + assert!(PlaintextControlFlagsV1::no_duplicates(flags)); + + let flags: Vec<_> = + std::iter::once(PcfV1::all_disabled([PcfV1::PckmoAes, PcfV1::PckmoAes])) + .flatten() + .collect(); + assert!(!PlaintextControlFlagsV1::no_duplicates(flags)); + } +} diff --git a/rust/pvimg/src/pv_utils/se_hdr/hdr_v1.rs b/rust/pvimg/src/pv_utils/se_hdr/hdr_v1.rs new file mode 100644 index 00000000..a7f2f609 --- /dev/null +++ b/rust/pvimg/src/pv_utils/se_hdr/hdr_v1.rs @@ -0,0 +1,613 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2024 +use std::mem::{size_of, size_of_val}; + +use deku::{ctx::Endian, prelude::*}; +use openssl::{ + nid::Nid, + pkey::{PKeyRef, Public}, +}; +use pv::request::{ + gen_ec_key, + openssl::pkey::{PKey, Private}, + random_array, Aes256XtsKey, Confidential, EcPubKeyCoord, Encrypt, Keyslot, SymKey, SymKeyType, + Zeroize, SHA_512_HASH_LEN, +}; +use serde::{Serialize, Serializer}; + +use super::keys::phkh_v1; +use crate::{ + error::Error, + pv_utils::{ + error::Result, + se_hdr::{ + brb::{ + ComponentMetadata, ComponentMetadataV1, SeHdrCommon, SeHdrConfBuilderTrait, + SeHdrPlainTrait, SeHdrPubBuilderTrait, SeHdrTrait, + }, + keys::{BinaryKeySlotV1, EcPubKeyCoordV1}, + }, + serializing::{ + bytesize, bytesize_confidential, confidential_read_slice, confidential_write_slice, + ser_hex, ser_hex_confidential, ser_lower_hex, serialize_to_bytes, + }, + try_copy_slice_to_array, + uvdata::{ + AeadCipherTrait, AeadDataTrait, AeadPlainDataTrait, KeyExchangeTrait, UvDataPlainTrait, + UvDataTrait, + }, + uvdata_builder::{AeadCipherBuilderTrait, KeyExchangeBuilderTrait}, + PlaintextControlFlagsV1, SecretControlFlagsV1, PSW, + }, +}; + +#[derive(Debug)] +struct HdrSizesV1 { + pub phs: u64, + pub sea: u64, +} + +#[derive(Debug, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize)] +#[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")] +struct SeHdrAadV1 { + sehs: u32, + #[serde(serialize_with = "ser_hex")] + iv: [u8; SymKeyType::AES_256_GCM_IV_LEN], + res1: u32, + nks: u64, + sea: u64, + nep: u64, + #[serde(serialize_with = "ser_lower_hex")] + pcf: u64, + cust_pub_key: EcPubKeyCoordV1, + #[serde(serialize_with = "ser_hex")] + pld: [u8; SHA_512_HASH_LEN], + #[serde(serialize_with = "ser_hex")] + ald: [u8; SHA_512_HASH_LEN], + #[serde(serialize_with = "ser_hex")] + tld: [u8; SHA_512_HASH_LEN], + #[deku(count = "nks")] + keyslots: Vec, +} + +impl SeHdrAadV1 { + const KEY_TYPE: SymKeyType = SymKeyType::Aes256Gcm; +} + +impl KeyExchangeTrait for SeHdrAadV1 { + fn contains>>(&self, key: K) -> Result { + let phkh = phkh_v1(key)?; + Ok(self.contains_hash(phkh)) + } + + fn cust_pub_key(&mut self) -> Result> { + self.cust_pub_key.clone().try_into() + } + + fn key_type(&self) -> SymKeyType { + Self::KEY_TYPE + } + + fn contains_hash>(&self, hash: H) -> bool { + for slot in &self.keyslots { + if hash.as_ref() != slot.phkh { + continue; + } + return true; + } + false + } +} + +#[derive(PartialEq, Eq, Debug, Clone, DekuRead, DekuWrite, Serialize)] +#[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")] +pub struct SeHdrConfV1 { + #[serde(serialize_with = "ser_hex_confidential")] + #[deku( + reader = "confidential_read_slice(deku::reader, endian)", + writer = "confidential_write_slice(cck, deku::writer, endian)" + )] + cck: Confidential<[u8; 32]>, + #[serde(serialize_with = "ser_hex_confidential")] + #[deku( + reader = "confidential_read_slice(deku::reader, endian)", + writer = "confidential_write_slice(xts, deku::writer, endian)" + )] + xts: Aes256XtsKey, + psw: PSW, + #[serde(serialize_with = "ser_lower_hex")] + scf: u64, + noi: u32, + res2: u32, + #[deku(count = "noi")] + opt_items: Vec, +} + +impl Zeroize for SeHdrConfV1 { + fn zeroize(&mut self) { + self.cck.zeroize(); + self.xts.zeroize(); + self.psw.zeroize(); + self.scf.zeroize(); + self.noi.zeroize(); + self.res2.zeroize(); + self.opt_items.zeroize(); + } +} + +#[derive(Default, PartialEq, Eq, Debug, Clone, DekuRead, DekuWrite, Serialize)] +#[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")] +pub struct SeHdrTagV1 { + #[serde(serialize_with = "ser_hex")] + tag: [u8; SymKeyType::AES_256_GCM_TAG_LEN], +} + +fn ser_confidential_confv1( + encrypted: &Confidential, + ser: S, +) -> std::result::Result { + encrypted.value().serialize(ser) +} + +/// Secure Execution Header definition +#[derive(Debug, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize)] +#[deku(endian = "big")] +pub struct SeHdrDataV1 { + #[serde(flatten)] + aad: SeHdrAadV1, + #[serde(flatten, serialize_with = "ser_confidential_confv1")] + #[deku( + reader = "confidential_read_sehdrconf_v1(deku::reader)", + writer = "confidential_write_sehdrconf_v1(data, deku::writer)" + )] + data: Confidential, + #[serde(flatten)] + tag: SeHdrTagV1, +} + +/// Reads from a `reader` and creates a confidential `SeHdrConfV1`. +/// +/// # Errors +/// +/// This function will return an error if there was an I/O error or the +/// `SeHdrConfV1` could not be constructed. +fn confidential_read_sehdrconf_v1( + reader: &mut Reader, +) -> Result, DekuError> +where + R: std::io::Read + std::io::Seek, +{ + Ok(Confidential::new(SeHdrConfV1::from_reader_with_ctx( + reader, + (), + )?)) +} + +/// Writes a `Confidential` into this `writer`. +/// +/// # Errors +/// +/// This function will return an error if there was an I/O error. +fn confidential_write_sehdrconf_v1( + value: &Confidential, + writer: &mut Writer, +) -> Result<(), DekuError> +where + W: std::io::Write + std::io::Seek, +{ + value.value().to_writer(writer, ()) +} + +impl SeHdrDataV1 { + const PCF_DEFAULT: u64 = 0x0; + const SCF_DEFAULT: u64 = 0x0; + + /// Creates a new `SeHdrDataV1`. It initializes the CCK and IV with random + /// data. + /// + /// # Errors + /// + /// This function will return an error if there was not enough entropy to + /// create the random data or another error has occurred. + pub fn new(psw: PSW, components: ComponentMetadataV1) -> Result { + // Safety: The CCK is also 32 bytes large. + let cck = SymKey::random(SymKeyType::Aes256Gcm)?.try_into().unwrap(); + let mut ret = Self { + aad: SeHdrAadV1 { + sehs: 0, + pcf: Self::PCF_DEFAULT, + ald: components.ald, + pld: components.pld, + tld: components.tld, + nep: components.nep, + sea: 0, + iv: random_array()?, + res1: 0, + nks: 0, + cust_pub_key: EcPubKeyCoordV1 { coord: [0_u8; 160] }, + keyslots: vec![], + }, + data: SeHdrConfV1 { + cck, + scf: Self::SCF_DEFAULT, + psw, + xts: components.key, + noi: 0, + res2: 0, + opt_items: vec![], + } + .into(), + tag: SeHdrTagV1::default(), + }; + let hdr_size = ret.size()?; + ret.aad.sehs = hdr_size.phs.try_into()?; + ret.aad.sea = hdr_size.sea; + Ok(ret) + } + + fn size(&self) -> Result { + let sea = bytesize_confidential(&self.data)?; + let mut phs = bytesize(&self.aad)? + .checked_add(size_of::()) + .ok_or(Error::UnexpectedOverflow)?; + phs = phs + .checked_add(bytesize(&self.tag)?) + .ok_or(Error::UnexpectedOverflow)?; + phs = phs.checked_add(sea).ok_or(Error::UnexpectedOverflow)?; + + Ok(HdrSizesV1 { + sea: sea.try_into()?, + phs: phs.try_into()?, + }) + } + + /// Return the expected size of an constructed `SeHdrDataV1` with `n` key + /// slots. + /// + /// # Errors + /// + /// This function will return an error if there was an arithmetic overflow + /// or. + pub fn expected_size(nks: usize) -> Result { + let cck = [0x0; 32].into(); + let hdr = Self { + aad: SeHdrAadV1 { + sehs: 0, + pcf: Self::PCF_DEFAULT, + ald: [0x0; SHA_512_HASH_LEN], + pld: [0x0; SHA_512_HASH_LEN], + tld: [0x0; SHA_512_HASH_LEN], + nep: 0, + sea: 0, + iv: [0x0_u8; SymKeyType::AES_256_GCM_IV_LEN], + res1: 0, + nks: 0, + cust_pub_key: EcPubKeyCoordV1 { coord: [0_u8; 160] }, + keyslots: vec![], + }, + data: SeHdrConfV1 { + cck, + scf: Self::SCF_DEFAULT, + psw: PSW { mask: 0, addr: 0 }, + xts: [0x0; SymKeyType::AES_256_XTS_KEY_LEN].into(), + noi: 0, + res2: 0, + opt_items: vec![], + } + .into(), + tag: SeHdrTagV1::default(), + }; + let hdr_size: usize = hdr.size()?.phs.try_into().unwrap(); + + hdr_size + .checked_add( + size_of::() + .checked_mul(nks) + .ok_or(Error::UnexpectedOverflow)?, + ) + .ok_or(Error::UnexpectedOverflow) + } +} + +impl UvDataPlainTrait for SeHdrDataV1 { + type C = SeHdrBinV1; +} +impl SeHdrPlainTrait for SeHdrDataV1 {} + +impl KeyExchangeBuilderTrait for SeHdrDataV1 { + fn add_keyslot( + &mut self, + hostkey: &PKeyRef, + aead_key: &SymKey, + priv_key: &PKeyRef, + ) -> Result<()> { + let keyslot = Keyslot::new(hostkey.to_owned()); + let keyslot_bin = keyslot.encrypt(aead_key.value(), priv_key)?.try_into()?; + let keyslot_bin_size = u32::try_from(size_of_val(&keyslot_bin)).unwrap(); + self.aad.keyslots.push(keyslot_bin); + self.aad.nks = self + .aad + .nks + .checked_add(1) + .ok_or(Error::UnexpectedOverflow)?; + self.aad.sehs = self + .aad + .sehs + .checked_add(keyslot_bin_size) + .ok_or(Error::UnexpectedOverflow)?; + Ok(()) + } + + fn generate_private_key(&self) -> Result> { + Ok(gen_ec_key(Nid::SECP521R1)?) + } + + fn set_cust_public_key(&mut self, key: &PKeyRef) -> Result<()> { + self.aad.cust_pub_key = TryInto::::try_into(key)?.into(); + Ok(()) + } + + fn clear_keyslots(&mut self) -> Result<()> { + let old_nks: usize = self.aad.nks.try_into().unwrap(); + let keyslot_bin_size = size_of::(); + self.aad.keyslots.clear(); + self.aad.nks = 0; + self.aad.sehs -= u32::try_from( + old_nks + .checked_mul(keyslot_bin_size) + .ok_or(Error::UnexpectedOverflow)?, + ) + .unwrap(); + Ok(()) + } +} + +impl KeyExchangeTrait for SeHdrDataV1 { + fn contains>>(&self, key: K) -> Result { + self.aad.contains(key) + } + + fn cust_pub_key(&mut self) -> Result> { + self.aad.cust_pub_key() + } + + fn key_type(&self) -> SymKeyType { + self.aad.key_type() + } + + fn contains_hash>(&self, hash: H) -> bool { + self.aad.contains_hash(hash) + } +} + +impl SeHdrConfBuilderTrait for SeHdrDataV1 { + fn set_psw(&mut self, psw: &PSW) { + self.data.value_mut().psw = psw.clone(); + } + + fn set_scf(&mut self, scf: &SecretControlFlagsV1) -> Result<()> { + self.data.value_mut().scf = scf.into(); + Ok(()) + } + + fn set_cck(&mut self, cck: Confidential>) -> Result<()> { + self.data.value_mut().cck = cck.try_into()?; + Ok(()) + } + + fn generate_cck(&self) -> Result { + Ok(SymKey::random(SymKeyType::Aes256Gcm)?) + } +} + +impl SeHdrPubBuilderTrait for SeHdrDataV1 { + fn set_pcf(&mut self, pcf: &PlaintextControlFlagsV1) -> Result<()> { + self.aad.pcf = pcf.into(); + Ok(()) + } + + fn set_components(&mut self, meta: ComponentMetadata) -> Result<()> { + let ComponentMetadataV1 { + ald, + pld, + tld, + nep, + key, + }: ComponentMetadataV1 = meta + .try_into() + .map_err(|_| Error::InvalidComponentMetadata)?; + self.data.value_mut().xts = key; + self.aad.ald = ald; + self.aad.pld = pld; + self.aad.tld = tld; + self.aad.nep = nep; + Ok(()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize)] +#[deku(endian = "big")] +pub struct SeHdrBinV1 { + #[serde(flatten)] + aad: SeHdrAadV1, + #[serde(serialize_with = "ser_hex")] + #[deku(bytes_read = "aad.sea")] + pub data: Vec, + #[serde(flatten)] + pub tag: SeHdrTagV1, +} + +impl SeHdrBinV1 { + pub fn new(d: &[u8]) -> Result { + Self::try_from_data(d) + } + + pub(crate) fn try_from_data(data: &[u8]) -> Result { + let (_rest, val) = Self::from_bytes((data, 0))?; + Ok(val) + } +} + +impl UvDataTrait for SeHdrBinV1 { + type P = SeHdrDataV1; +} +impl SeHdrTrait for SeHdrBinV1 {} + +impl AeadCipherTrait for SeHdrBinV1 { + fn aead_key_type(&self) -> SymKeyType { + self.key_type() + } + + fn iv(&self) -> &[u8] { + &self.aad.iv + } + + fn aead_tag_size(&self) -> usize { + SymKeyType::AES_256_GCM_TAG_LEN + } +} + +impl AeadCipherBuilderTrait for SeHdrDataV1 { + fn set_iv(&mut self, iv: &[u8]) -> Result<()> { + self.aad.iv = try_copy_slice_to_array(iv)?; + Ok(()) + } +} + +impl KeyExchangeTrait for SeHdrBinV1 { + fn contains>>(&self, key: K) -> Result { + self.aad.contains(key) + } + + fn cust_pub_key(&mut self) -> Result> { + self.aad.cust_pub_key() + } + + fn key_type(&self) -> SymKeyType { + self.aad.key_type() + } + + fn contains_hash>(&self, hash: H) -> bool { + self.aad.contains_hash(hash) + } +} + +impl AeadDataTrait for SeHdrBinV1 { + fn aad(&self) -> Vec { + serialize_to_bytes(&self.aad).unwrap() + } + + fn data(&self) -> Vec { + self.data.to_owned() + } + + fn tag(&self) -> Vec { + serialize_to_bytes(&self.tag).unwrap() + } +} + +impl AeadPlainDataTrait for SeHdrDataV1 { + fn aad(&self) -> Vec { + serialize_to_bytes(&self.aad).unwrap() + } + + fn data(&self) -> Confidential> { + serialize_to_bytes(self.data.value()).unwrap().into() + } + + fn tag(&self) -> Vec { + serialize_to_bytes(&self.tag).unwrap() + } +} + +impl AeadCipherTrait for SeHdrDataV1 { + fn aead_key_type(&self) -> SymKeyType { + self.aad.key_type() + } + + fn iv(&self) -> &[u8] { + &self.aad.iv + } + + fn aead_tag_size(&self) -> usize { + SymKeyType::AES_256_GCM_TAG_LEN + } +} + +#[cfg(test)] +mod tests { + + use std::io::Cursor; + + use pv::test_utils::get_test_key_and_cert; + + use super::*; + use crate::pv_utils::{BuilderTrait, SeHdr, SeHdrBuilder, SeHdrVersion}; + + #[test] + fn iv_keys_auto_generation_test() { + let (_, host_key) = get_test_key_and_cert(); + let host_keys = [host_key.public_key().unwrap()]; + let mut builder = SeHdrBuilder::new( + SeHdrVersion::V1, + PSW { + addr: 1234, + mask: 5678, + }, + ComponentMetadataV1 { + ald: [0x1; SHA_512_HASH_LEN], + pld: [0x2; SHA_512_HASH_LEN], + tld: [0x3; SHA_512_HASH_LEN], + nep: 1, + key: Confidential::new([0x0_u8; SymKeyType::AES_256_XTS_KEY_LEN]), + }, + ) + .expect("should not fail"); + builder.add_hostkeys(&host_keys).expect("should not fail"); + } + + #[test] + fn chain_test() { + let (_, host_key) = get_test_key_and_cert(); + let host_keys = [host_key.public_key().unwrap()]; + let xts_key = Confidential::new([0x3; SymKeyType::AES_256_XTS_KEY_LEN]); + let meta = ComponentMetadataV1 { + ald: [0x1; SHA_512_HASH_LEN], + pld: [0x2; SHA_512_HASH_LEN], + tld: [0x3; SHA_512_HASH_LEN], + nep: 3, + key: xts_key, + }; + let cck: Confidential> = [0x42; 32].to_vec().into(); + let psw = PSW { + addr: 1234, + mask: 5678, + }; + + let mut builder = SeHdrBuilder::new(SeHdrVersion::V1, psw.clone(), meta.clone()) + .expect("should not fail"); + + builder + .add_hostkeys(&host_keys) + .expect("should not fail") + .with_components(meta.clone()) + .expect("should not fail") + .with_cck(cck.clone()) + .expect("should not fail"); + let prot_key = builder.prot_key().to_owned(); + let bin = builder.build().expect("should not fail"); + + let reader = Cursor::new(bin.as_bytes().expect("should not fail")); + let hdr = SeHdr::try_from_io(reader).unwrap(); + + let hdr_plain = hdr.decrypt(&prot_key).unwrap(); + assert_eq!(hdr_plain.common.version, SeHdrVersion::V1); + let hdr_data_v1: SeHdrDataV1 = hdr_plain.data.try_into().expect("should not fail"); + assert_eq!(meta.ald, hdr_data_v1.aad.ald); + assert_eq!(meta.pld, hdr_data_v1.aad.pld); + assert_eq!(meta.tld, hdr_data_v1.aad.tld); + assert_eq!(psw, hdr_data_v1.data.value().psw); + assert_eq!(cck.value(), hdr_data_v1.data.value().cck.value()); + } +} diff --git a/rust/pvimg/src/pv_utils/se_hdr/keys.rs b/rust/pvimg/src/pv_utils/se_hdr/keys.rs new file mode 100644 index 00000000..9a52c97a --- /dev/null +++ b/rust/pvimg/src/pv_utils/se_hdr/keys.rs @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2024 + +use std::mem::size_of; + +use deku::{ctx::Endian, DekuRead, DekuWrite}; +use openssl::{ + hash::{hash, MessageDigest}, + pkey::{PKey, PKeyRef, Public}, +}; +use pv::{request::EcPubKeyCoord, static_assert}; +use serde::Serialize; + +use crate::{ + error::{Error, Result}, + pv_utils::{serializing::ser_hex, try_copy_slice_to_array}, +}; + +/// Try to hash the public EC key. +/// +/// # Errors +/// +/// This function will return an error if OpenSSL could not hash the key. +pub fn phkh_v1>>(key: T) -> Result<[u8; 32]> { + let phk: EcPubKeyCoord = key.as_ref().try_into()?; + let binding = hash(MessageDigest::sha256(), phk.as_ref())?; + try_copy_slice_to_array(&binding) +} + +#[derive(Debug, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize)] +#[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")] +pub struct EcPubKeyCoordV1 { + #[serde(serialize_with = "ser_hex")] + pub coord: [u8; 160], +} + +#[allow(clippy::fallible_impl_from)] +impl From for EcPubKeyCoordV1 { + fn from(value: EcPubKeyCoord) -> Self { + // SAFETY: `EcPubKeyCoord` has the same struct definition as + // `EcPubKeyCooardV1`. + let coord = try_copy_slice_to_array(value.as_ref()).unwrap(); + Self { coord } + } +} + +#[allow(clippy::fallible_impl_from)] +impl From for EcPubKeyCoord { + fn from(value: EcPubKeyCoordV1) -> Self { + // SAFETY: `EcPubKeyCoord` has the same struct definition as + // `EcPubKeyCooardV1`. + let coord = try_copy_slice_to_array(&value.coord).unwrap(); + // SAFETY: This call is safe because we do not expect, that + // EcPubKeyCoord is always a valid EC pub key. + unsafe { Self::from_data(coord) } + } +} + +impl TryFrom for PKey { + type Error = Error; + + fn try_from(value: EcPubKeyCoordV1) -> Result { + >::into(value) + .try_into() + .map_err(Error::Crypto) + } +} + +#[repr(C)] +#[derive(Default, Debug, PartialEq, Eq, Clone, DekuRead, DekuWrite, Serialize)] +#[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")] +/// Binary key slot v1 +pub struct BinaryKeySlotV1 { + #[serde(serialize_with = "ser_hex")] + /// Public host key hash + pub(crate) phkh: [u8; 32], + /// Wrapper key + #[serde(serialize_with = "ser_hex")] + pub(crate) wrpk: [u8; 32], + /// Tag + #[serde(serialize_with = "ser_hex")] + pub(crate) kst: [u8; 16], +} +static_assert!(size_of::() == 80); + +impl TryFrom> for BinaryKeySlotV1 { + type Error = Error; + + fn try_from(value: Vec) -> Result { + let data: [u8; 80] = try_copy_slice_to_array(&value)?; + let bin = Self { + phkh: data[..32].try_into().unwrap(), + wrpk: data[32..64].try_into().unwrap(), + kst: data[64..].try_into().unwrap(), + }; + Ok(bin) + } +} diff --git a/rust/pvimg/src/pv_utils/secured_comp.rs b/rust/pvimg/src/pv_utils/secured_comp.rs new file mode 100644 index 00000000..ae85e9f4 --- /dev/null +++ b/rust/pvimg/src/pv_utils/secured_comp.rs @@ -0,0 +1,820 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2024 + +use std::{ + fmt::{Debug, Formatter}, + io::{Read, Write}, + rc::Rc, +}; + +use log::debug; +use openssl::{ + bn::BigNum, + cipher::{Cipher, CipherRef}, + cipher_ctx::{CipherCtx, CipherCtxRef}, + hash::{Hasher, MessageDigest}, + nid::Nid, +}; +use pv::request::{Confidential, SymKey, SymKeyType}; + +use super::{try_copy_slice_to_array, Layout}; +use crate::pv_utils::{ + error::{Error, PvError, Result}, + se_hdr::{ComponentMetadata, ComponentMetadataV1}, + Interval, +}; + +#[allow(unused)] +#[derive(Copy, Clone, Eq, PartialEq)] +pub enum Mode { + Encrypt, + Decrypt, + Padding, +} + +fn update_ald_digest(hasher: &mut Hasher, interval: &Interval, chunk_size: usize) -> Result { + let mut num_chunks = 0; + + for addr in (interval.start..interval.stop).step_by(chunk_size) { + let addr_be_data = addr.to_be_bytes(); + + hasher.update(&addr_be_data)?; + num_chunks += 1; + } + Ok(num_chunks) +} + +pub struct PrepareSecuredComponentArgs<'a> { + pub(crate) addr: u64, + pub(crate) cipher: &'a CipherRef, + pub(crate) mode: Mode, + pub(crate) key: &'a [u8], + pub(crate) iv: &'a [u8], + pub(crate) chunk_size: usize, +} + +pub struct MetadataArgs<'a> { + pub(crate) content_hasher: Option<&'a mut Hasher>, + pub(crate) tweak_hasher: Option<&'a mut Hasher>, + pub(crate) address_hasher: Option<&'a mut Hasher>, + pub(crate) num_chunks: Option<&'a mut usize>, + pub(crate) max_component_size: Option, + pub(crate) input_size: usize, + pub(crate) padded_input_size: usize, + pub(crate) output_size: usize, +} + +/// This functions tries to read the exact number of bytes required to fill +/// `buf`. +/// +/// # Errors +/// +/// If this function encounters an EOF before completely filling the buffer, it +/// returns an error of the kind [`std::io::ErrorKind::UnexpectedEof`]. The +/// contents of `buf` are unspecfied in this case. +fn own_read_exact(reader: &mut R, mut buf: &mut [u8]) -> std::io::Result { + let mut data_read = 0; + while !buf.is_empty() { + match reader.read(buf) { + Ok(0) => break, + Ok(n) => { + buf = &mut buf[n..]; + data_read += n; + } + Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => {} + Err(e) => return Err(e), + } + } + Ok(data_read) +} + +/// This function is used for prepare a "secured component" used in the Secure Execution +/// context. It adds padding if needed, encrypts the components and calculates +/// the PLD and TLD. +pub fn prepare_component( + crypto_args: &PrepareSecuredComponentArgs, + src: &mut R, + dst: &mut W, + mut opt_data: Option<&mut MetadataArgs>, +) -> Result<()> { + let PrepareSecuredComponentArgs { + addr, + cipher, + mode, + key, + iv, + chunk_size, + } = *crypto_args; + let mut chunk_data = vec![0_u8; chunk_size]; + let mut output_data = vec![0_u8; chunk_data.len()]; + let mut chunks_count: usize = 0; + let mut count; + + let mut tweak_num = BigNum::from_slice(iv)?; + let mut ctx = if matches!(mode, Mode::Decrypt) || matches!(mode, Mode::Encrypt) { + Some(CipherCtx::new()?) + } else { + None + }; + + let init_func = match mode { + // The value for Mode::Padding will never be actually used. + Mode::Encrypt | Mode::Padding => CipherCtxRef::encrypt_init, + Mode::Decrypt => CipherCtxRef::decrypt_init, + }; + + if let Some(ref mut ctx) = &mut ctx { + assert!(chunk_size % cipher.block_size() == 0, "Invalid chunk size"); + + init_func(ctx, Some(cipher), None, None)?; + + if key.len() != cipher.key_length() { + debug!("Setting new key length: {}", key.len()); + ctx.set_key_length(key.len())?; + } + if iv.len() != cipher.iv_length() { + debug!("Setting new IV length: {}", iv.len()); + ctx.set_iv_length(iv.len())?; + } + + // Set key + init_func(ctx, None, Some(key), None)?; + }; + + loop { + let new_tweak = tweak_num.to_vec_padded(iv.len().try_into()?)?; + + // Set a new tweak + if let Some(ref mut ctx) = &mut ctx { + init_func(ctx, None, None, Some(&new_tweak))?; + } + + // Read input data + let read_count = own_read_exact(src, &mut chunk_data)?; + // EOF has been reached + if read_count == 0 { + // A chunk was read before and EOF was reached => it was not an + // empty file and therefore break the loop. + if chunks_count != 0 { + break; + } + } + + let input_slice = &chunk_data[..]; + + // Encrypt + if let Some(ref mut ctx) = ctx { + count = ctx.cipher_update(input_slice, Some(&mut output_data))?; + } else { + output_data.copy_from_slice(input_slice); + count = input_slice.len(); + } + + // Write output data and check if it fits in the image layout + let output_slice = &output_data[..count]; + if let Some(ops) = opt_data.as_mut() { + let output_size = ops + .output_size + .checked_add(output_slice.len()) + .ok_or(Error::UnexpectedOverflow)?; + + if let Some(max_output_size) = ops.max_component_size { + if output_size > max_output_size { + return Err(Error::PreparedComponentTooLarge { + output_size, + max_output_size, + }); + } + } + ops.output_size = output_size; + + // Calculate input size + ops.input_size = ops + .input_size + .checked_add(read_count) + .ok_or(Error::UnexpectedOverflow)?; + + // Calculate padded input size + ops.padded_input_size = ops + .padded_input_size + .checked_add(input_slice.len()) + .ok_or(Error::UnexpectedOverflow)?; + + // Calculate PLD + if let Some(ref mut hasher) = ops.content_hasher { + hasher.update(output_slice)?; + } + + // Calculate TLD + if let Some(ref mut hasher) = ops.tweak_hasher { + hasher.update(&new_tweak)?; + } + } + + dst.write_all(output_slice)?; + chunks_count = chunks_count + .checked_add(1) + .ok_or(Error::UnexpectedOverflow)?; + + // Prepare for the next chunk: + // * Calculate new tweak + // * Reset chunk data to zeroes + tweak_num.add_word(chunk_size.try_into()?)?; + chunk_data.fill(0x0); + } + + if let Some(ref mut ctx) = &mut ctx { + count = ctx.cipher_final(&mut output_data)?; + } else { + count = 0; + } + let output_slice = &output_data[..count]; + dst.write_all(output_slice)?; + + if let Some(ops) = opt_data.as_mut() { + // Calculate output size + let output_size = ops + .output_size + .checked_add(output_slice.len()) + .ok_or(Error::UnexpectedOverflow)?; + + if let Some(max_output_size) = ops.max_component_size { + if output_size > max_output_size { + return Err(Error::PreparedComponentTooLarge { + output_size, + max_output_size, + }); + } + } + ops.output_size = output_size; + + // Calculate PLD + if let Some(ref mut hasher) = ops.content_hasher { + hasher.update(output_slice)?; + } + + // Calculate ALD + if let Some(ref mut hasher) = ops.address_hasher { + update_ald_digest( + hasher, + &Interval::new_with_size(addr, output_size.try_into()?)?, + chunk_size, + )?; + } + + // Update the total number of prepared chunks. + if let Some(ref mut num_chunks) = ops.num_chunks { + **num_chunks = num_chunks + .checked_add(chunks_count) + .ok_or(Error::UnexpectedOverflow)?; + } + } + + Ok(()) +} + +/// A trait for dealing with (secured) components. +pub trait ComponentTrait: Debug + Read { + /// Returns if the component is used in secure mode. + fn secure_mode(&self) -> bool; + + /// Returns the component type. + fn kind(&self) -> T; +} + +/// Struct for representing a secured component that is going to be unpacked by +/// the Ultravisor. +#[derive(Debug, PartialEq, Eq)] +pub struct SecuredComponent { + /// Source of the prepared (encrypted) component + pub src: Rc, + /// Size of the unprepared (unencrypted) component. + pub original_size: usize, + /// Tweak or IV used for the (de/en)cryption of the component. + tweak_or_iv: Vec, +} + +impl SecuredComponent { + pub fn tweak(&self) -> &[u8] { + self.tweak_or_iv.as_slice() + } +} + +/// A builder that is used to prepare a [`SecuredComponent`]. +pub struct SecuredComponentBuilder { + /// Expert mode, in example the secured components encryption key and be set + /// manually. By default disabled. + expert_mode: bool, + /// Chunk size, currently only 4096 bytes is supported by the Ultravisor. + chunk_size: usize, + + /// Determines whether a secured component needs to be encrypted. + encrypt: bool, + /// Determines which cipher will be used for the encryption. + cipher: &'static CipherRef, + /// Key used for the encryption of the components. + comp_key: SymKey, + + // Cached values + /// Number of chunks already prepared by this [`Self`]. + num_chunks: usize, + /// ALD hasher + ald_hasher: Hasher, + /// PLD hasher + pld_hasher: Hasher, + /// TLD hasher + tld_hasher: Hasher, + /// Finalized image? + finalized: bool, +} + +// Needs to be implemented manually as `CipherRef` and `Hasher` do not implement +// [`Debug`]. +impl Debug for SecuredComponentBuilder { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("SecuredComponentBuilder") + .field("chunk_size", &self.chunk_size) + .field("cipher", &self.cipher.nid().long_name()?) + .field("comp_key", &self.comp_key) + .field("encrypt", &self.encrypt) + .field("expert_mode", &self.expert_mode) + .field("num_chunks", &self.num_chunks) + .finish() + } +} + +impl SecuredComponentBuilder { + /// Values used for the first (and current) Ultravisor implementation. + const CHUNK_SIZE_V1: usize = 4096; + const CIPHER_V1: SymKeyType = SymKeyType::Aes256Xts; + pub const COMPONENT_ALIGNMENT_V1: u64 = 4096; + const DIGEST_V1: Nid = Nid::SHA512; + + fn new( + encrypt: bool, + key_type: SymKeyType, + digest_nid: Nid, + chunk_size: usize, + ) -> Result { + let digest = MessageDigest::from_nid(digest_nid).ok_or(Error::UnsupportMessageDigest)?; + let nid = key_type.into(); + let cipher = Cipher::from_nid(nid).ok_or_else(|| PvError::UnsupportedCipher(nid))?; + let key = SymKey::random(key_type)?; + + Ok(Self { + expert_mode: false, + comp_key: key, + cipher, + encrypt, + num_chunks: 0, + chunk_size, + ald_hasher: Hasher::new(digest)?, + pld_hasher: Hasher::new(digest)?, + tld_hasher: Hasher::new(digest)?, + finalized: false, + }) + } + + /// Creates a new [`Self`] that can be used for the preparation of V1 + /// secured components. AES256-XTS is used for the components encryption, + /// SHA-512 for the ALD, PLD and TLD. The components must be aligned and a + /// multiple of 4096 bytes. + /// + /// # Errors + /// + /// This function will return an error if the cipher or digest algorithm is + /// not supported or no random key could be generated. + pub fn new_v1(encryption: bool) -> Result { + Self::new( + encryption, + Self::CIPHER_V1, + Self::DIGEST_V1, + Self::CHUNK_SIZE_V1, + ) + } + + /// Activate the expert mode. For example, it's then allowed to change + /// security related settings like setting the encryption keys manually. + pub fn i_know_what_i_am_doing(&mut self) { + self.expert_mode = true; + } + + /// Sets the components key. This requires the expert mode to be active and + /// there is also the restriction, that it cannot be changed after the first + /// secured component was prepared. + /// + /// * `key_data` - Data that is used as the new components key. + /// + /// # Errors + /// + /// This function will return an error if: + /// * the expert mode is not active (see [`Self::i_know_what_i_am_doing`]) + /// * or a first component was already prepared or finalized + /// * the key could not created by using `key_data`. + pub fn set_components_key(&mut self, key_data: Confidential>) -> Result<()> { + if !self.expert_mode { + return Err(Error::NonExpertMode); + } + // We have already encrypted a component, therefore reject the new + // components key. + if self.num_chunks > 0 || self.finalized { + return Err(Error::FirstComponentAlreadyPrepared); + } + + self.comp_key = SymKey::try_from_data(self.comp_key.key_type(), key_data)?; + Ok(()) + } + + /// Prepare the given component and write it into the given writer and + /// assume the given memory address. + /// + /// * `writer` - Write the prepared component into this writer. + /// * `layout` - Memory layout where the prepared component is later used. + /// * `component` - Component to be prepared as . + /// * `addr` - Memory address where the prepared component later will later be located + /// (important for the Secure Execution header). + /// * `tweak` - Tweak used for the component encryption. + /// + /// # Errors + /// + /// This function will return an error if: + /// * address is smaller than the expected next possible address. + /// * the image was already finalized + /// * the given tweak is invalid + pub fn prepare_and_insert_as_secure_component>( + &mut self, + writer: &mut W, + layout: &mut Layout, + component: &mut T, + addr: u64, + tweak: Vec, + ) -> Result { + let next_possible_addr = layout.next_addr; + if addr < next_possible_addr { + return Err(Error::NonMonotonicallyIncreasing { + addr, + next_addr: next_possible_addr, + }); + } + + let alignment = layout.alignment; + if (addr % alignment) != 0 { + return Err(Error::UnalignedAddress { addr, alignment }); + } + + if alignment > self.chunk_size.try_into().unwrap() { + return Err(Error::InvalidAlignment { + alignment, + chunk_size: self.chunk_size, + }); + } + + let max_component_size = layout.max_size_of_chunk_at_addr(addr)?; + let secured_comp = self.prepare_and_insert_as_secure_component_unchecked( + writer, + component, + addr, + max_component_size, + tweak, + )?; + layout.insert_interval(secured_comp.src.start, secured_comp.src.size())?; + Ok(secured_comp) + } + + /// Prepare the given component and insert it at the given image address. + /// + /// * `writer` - Write the prepared component into this writer. + /// * `component` - Component to be prepared. + /// * `addr` - Address where the prepared component should be inserted. + /// * `max_component_size`- Maximum possible size that the prepared component may have + /// * `tweak` - Tweak used for the component encryption. + /// + /// # Errors + /// + /// This function will return an error if: + /// * address is smaller than the expected next possible address. + /// * the image was already finalized + /// * the given tweak is invalid + fn prepare_and_insert_as_secure_component_unchecked>( + &mut self, + writer: &mut W, + component: &mut T, + addr: u64, + max_component_size: Option, + tweak: Vec, + ) -> Result { + assert!(component.secure_mode()); + assert_ne!(self.chunk_size, 0); + + if self.finalized { + return Err(Error::ImageAlreadyFinalized); + } + + let expected_tweak_len = self.cipher.iv_length(); + if expected_tweak_len != tweak.len() { + return Err(Error::InvalidTweakSize { + given: tweak.len(), + expected: expected_tweak_len, + }); + } + + let mode = if self.encrypt && component.secure_mode() { + Mode::Encrypt + } else { + Mode::Padding + }; + + let prepare_args = PrepareSecuredComponentArgs { + addr, + cipher: self.cipher, + mode, + key: self.comp_key.value(), + iv: &tweak, + chunk_size: self.chunk_size, + }; + + let mut ops = MetadataArgs { + content_hasher: Some(&mut self.pld_hasher), + tweak_hasher: Some(&mut self.tld_hasher), + address_hasher: Some(&mut self.ald_hasher), + num_chunks: Some(&mut self.num_chunks), + max_component_size, + input_size: 0, + padded_input_size: 0, + output_size: 0, + }; + // Prepare the component and write the prepared data directly to the output + prepare_component(&prepare_args, component, writer, Some(&mut ops))?; + + let original_size = ops.input_size; + let prepared_size = ops.output_size.try_into()?; + let src = Interval::new_with_size(addr, prepared_size)?; + + Ok(SecuredComponent { + original_size, + src: Rc::new(src), + tweak_or_iv: tweak, + }) + } + + /// Prepare the given component and append the prepared component to the + /// back of the image layout. + /// + /// # Errors + /// + /// This function will return an error if the image was already finalized or + /// the given tweak is invalid. + pub fn prepare_and_append_as_secure_component>( + &mut self, + writer: &mut W, + layout: &mut Layout, + component: &mut T, + tweak: Vec, + ) -> Result { + let next_addr = layout.next_addr; + self.prepare_and_insert_as_secure_component(writer, layout, component, next_addr, tweak) + } + + /// Finalizes the image and returns the image metadata (the digests, number + /// of chunks) and the key that was used for the components encryption. + /// + /// # Errors + /// + /// This function will return an error if the builder is already finalized + /// or there was a problem in a cryptographic operation. + pub fn finish(&mut self) -> Result { + if self.finalized { + return Err(Error::ImageAlreadyFinalized); + } + + self.finalized = true; + + Ok(ComponentMetadata::ComponentMetadataV1( + ComponentMetadataV1 { + ald: try_copy_slice_to_array(self.ald_hasher.finish()?.as_ref())?, + pld: try_copy_slice_to_array(self.pld_hasher.finish()?.as_ref())?, + tld: try_copy_slice_to_array(self.tld_hasher.finish()?.as_ref())?, + nep: self.num_chunks.try_into()?, + key: try_copy_slice_to_array(self.comp_key.value())?.into(), + }, + )) + } + + /// Returns if encryption is used. + pub const fn encryption_enabled(&self) -> bool { + self.encrypt + } + + /// Returns the chunk size. + pub const fn chunk_size(&self) -> usize { + self.chunk_size + } +} + +#[allow(clippy::shadow_unrelated)] +#[cfg(test)] +mod tests { + use std::{fmt::Debug, io::Cursor}; + + use pv::request::Aes256XtsKey; + + use super::*; + + #[test] + fn prepare_aligned_component_test() { + #[derive(Debug)] + struct TestComp { + reader: T, + } + + impl ComponentTrait<()> for TestComp { + fn secure_mode(&self) -> bool { + true + } + + fn kind(&self) {} + } + + impl Read for TestComp { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + self.reader.read(buf) + } + } + + let start_addr = 0x10000; + let encryption = true; + let mut writer = Cursor::new(Vec::new()); + let mut ctx = SecuredComponentBuilder::new_v1(encryption).expect("should work"); + let mut key = vec![0x42; 32]; + key.extend([0x43; 32]); + ctx.i_know_what_i_am_doing(); + ctx.set_components_key( + Aes256XtsKey::new(<[u8; 64]>::try_from(key.as_slice()).unwrap()).into(), + ) + .unwrap(); + let input_data1 = vec![0x1; 0x3400]; + let input_data2 = vec![0x2; 0x3000]; + + let mut comp1 = TestComp { + reader: Cursor::new(input_data1), + }; + let tweak1 = vec![ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x42, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + ]; + + let img_comp_res = ctx.prepare_and_insert_as_secure_component_unchecked( + &mut writer, + &mut comp1, + start_addr, + None, + tweak1, + ); + assert!(img_comp_res.is_ok()); + assert_eq!(ctx.num_chunks, 4); + + let reader2 = Cursor::new(input_data2); + let mut comp2 = TestComp { reader: reader2 }; + let tweak2 = vec![ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x42, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + ]; + let img_comp_res = ctx.prepare_and_insert_as_secure_component_unchecked( + &mut writer, + &mut comp2, + 0x20000, + None, + tweak2, + ); + assert!(img_comp_res.is_ok()); + assert_eq!(ctx.num_chunks, 7); + + let metav1: ComponentMetadataV1 = ctx + .finish() + .expect("should not fail") + .try_into() + .expect("should not fail"); + + // Check ALD + assert_eq!( + metav1.ald, + [ + 195, 145, 222, 87, 39, 160, 130, 18, 234, 47, 234, 156, 55, 249, 207, 9, 11, 229, + 31, 147, 198, 213, 33, 184, 144, 99, 50, 206, 114, 12, 95, 56, 173, 160, 231, 62, + 105, 102, 62, 82, 17, 208, 21, 254, 244, 29, 198, 38, 6, 245, 19, 94, 97, 153, 4, + 212, 244, 80, 171, 136, 159, 73, 202, 173 + ], + ); + + // Check PLD + assert_eq!( + metav1.pld, + [ + 162, 79, 243, 10, 138, 241, 41, 88, 136, 222, 223, 233, 54, 158, 181, 9, 41, 3, 9, + 169, 1, 89, 235, 195, 44, 162, 106, 83, 249, 212, 54, 74, 120, 24, 87, 226, 89, 5, + 135, 83, 108, 62, 118, 115, 85, 199, 183, 96, 63, 43, 12, 106, 64, 127, 22, 51, 13, + 130, 18, 141, 9, 100, 250, 210 + ] + ); + + // Check TLD + let digest = MessageDigest::sha512(); + let mut hasher_new = Hasher::new(digest).expect("should work"); + // Tweaks for comp1 + hasher_new + .update(&[0, 0, 0, 0, 0, 0, 0, 66, 0, 0, 0, 0, 0, 0, 0, 0]) + .expect("should work"); + hasher_new + .update(&[0, 0, 0, 0, 0, 0, 0, 66, 0, 0, 0, 0, 0, 0, 16, 0]) + .expect("should work"); + hasher_new + .update(&[0, 0, 0, 0, 0, 0, 0, 66, 0, 0, 0, 0, 0, 0, 32, 0]) + .expect("should work"); + hasher_new + .update(&[0, 0, 0, 0, 0, 0, 0, 66, 0, 0, 0, 0, 0, 0, 48, 0]) + .expect("should work"); + + // Tweaks for comp2 + hasher_new + .update(&[0, 0, 0, 0, 0, 0, 0, 66, 0, 0, 0, 0, 0, 0, 0, 0]) + .expect("should work"); + hasher_new + .update(&[0, 0, 0, 0, 0, 0, 0, 66, 0, 0, 0, 0, 0, 0, 16, 0]) + .expect("should work"); + hasher_new + .update(&[0, 0, 0, 0, 0, 0, 0, 66, 0, 0, 0, 0, 0, 0, 32, 0]) + .expect("should work"); + let exp = hasher_new.finish().expect("should work"); + + assert_eq!(metav1.tld, *exp); + assert_eq!( + metav1.tld, + [ + 66, 79, 227, 207, 4, 166, 246, 74, 122, 239, 24, 92, 59, 78, 246, 7, 192, 228, 245, + 75, 183, 225, 70, 32, 181, 116, 163, 211, 30, 239, 49, 199, 212, 98, 235, 4, 13, + 69, 238, 105, 24, 230, 184, 9, 104, 186, 68, 84, 249, 226, 237, 194, 111, 105, 41, + 237, 98, 77, 0, 85, 242, 53, 86, 89 + ] + ); + } + + #[test] + fn test_update_ald_digest() { + let start = 0x10000; + let stop = 0x13400; + let digest = MessageDigest::sha512(); + let mut hasher = Hasher::new(digest).expect("should work"); + let mut hasher_new = Hasher::new(digest).expect("should work"); + + hasher_new + .update(&0x10000_u64.to_be_bytes()) + .expect("should work"); + hasher_new + .update(&0x11000_u64.to_be_bytes()) + .expect("should work"); + hasher_new + .update(&0x12000_u64.to_be_bytes()) + .expect("should work"); + hasher_new + .update(&0x13000_u64.to_be_bytes()) + .expect("should work"); + hasher_new + .update(&0x20000_u64.to_be_bytes()) + .expect("should work"); + hasher_new + .update(&0x21000_u64.to_be_bytes()) + .expect("should work"); + hasher_new + .update(&0x22000_u64.to_be_bytes()) + .expect("should work"); + let exp = hasher_new.finish().expect("should work"); + + let chunks_count_res = update_ald_digest(&mut hasher, &Interval { start, stop }, 4096); + assert!(chunks_count_res.is_ok()); + assert_eq!(chunks_count_res.unwrap(), 4); + + let chunks_count_res = update_ald_digest( + &mut hasher, + &Interval { + start: 0x20000, + stop: 0x23000, + }, + 4096, + ); + assert!(chunks_count_res.is_ok()); + assert_eq!(chunks_count_res.unwrap(), 3); + + let res_ret = hasher.finish(); + assert!(res_ret.is_ok()); + let res = res_ret.unwrap(); + assert_eq!(&*exp, &*res,); + assert_eq!( + &*res, + [ + 195, 145, 222, 87, 39, 160, 130, 18, 234, 47, 234, 156, 55, 249, 207, 9, 11, 229, + 31, 147, 198, 213, 33, 184, 144, 99, 50, 206, 114, 12, 95, 56, 173, 160, 231, 62, + 105, 102, 62, 82, 17, 208, 21, 254, 244, 29, 198, 38, 6, 245, 19, 94, 97, 153, 4, + 212, 244, 80, 171, 136, 159, 73, 202, 173 + ] + ); + } +} diff --git a/rust/pvimg/src/pv_utils/serializing.rs b/rust/pvimg/src/pv_utils/serializing.rs new file mode 100644 index 00000000..a06bbf0e --- /dev/null +++ b/rust/pvimg/src/pv_utils/serializing.rs @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2024 + +use std::fmt::LowerHex; + +use deku::{ + reader::Reader, writer::Writer, DekuContainerRead, DekuContainerWrite, DekuError, DekuReader, + DekuWriter, +}; +use pv::request::{Confidential, Zeroize}; +use serde::{Serialize, Serializer}; +use utils::HexSlice; + +use crate::pv_utils::error::Result; + +pub fn ser_hex, S: Serializer>( + data: A, + ser: S, +) -> std::result::Result { + HexSlice::from(data.as_ref()).serialize(ser) +} + +pub fn ser_lower_hex( + data: &B, + ser: S, +) -> std::result::Result { + format!("{:#018x}", data).serialize(ser) +} + +pub fn ser_hex_confidential( + data: &Confidential<[u8; COUNT]>, + ser: S, +) -> std::result::Result { + ser_hex(data.value(), ser) +} + +/// Read a slice into a confidential array of type [`T`] and length [`N`]. +/// +/// # Errors +/// +/// This function will return an error if the result could not be constructed or +/// if there was an I/O error. +pub fn confidential_read_slice<'a, Ctx, T, const COUNT: usize, R>( + reader: &mut Reader, + ctx: Ctx, +) -> Result, DekuError> +where + Ctx: Copy, + T: Default + DekuReader<'a, Ctx>, + R: std::io::Read + std::io::Seek, +{ + Ok(Confidential::new(<[T; COUNT]>::from_reader_with_ctx( + reader, ctx, + )?)) +} + +/// Writes a confidential array into this writer. +/// +/// # Errors +/// +/// This function will return an error if there was an I/O error. +pub fn confidential_write_slice( + value: &Confidential<[T; COUNT]>, + writer: &mut Writer, + ctx: Ctx, +) -> Result<(), DekuError> +where + Ctx: Copy, + T: Default + DekuWriter, + W: std::io::Write + std::io::Seek, +{ + value.value().to_writer(writer, ctx) +} + +/// Serializes `value` to bytes. +/// +/// # Errors +/// +/// This function will return an error if the value could not be serialized. +pub fn serialize_to_bytes(value: &T) -> Result> +where + T: DekuContainerWrite, +{ + Ok(value.to_bytes()?) +} + +/// Deserializes `value` to `T`. +/// +/// # Errors +/// +/// This function will return an error if the given value could not be +/// deserialized. +pub fn deserialize_from_bytes<'a, T>(value: &'a [u8]) -> Result +where + T: DekuContainerRead<'a>, +{ + let ((_, rest), obj) = T::from_bytes((value, 0))?; + assert_eq!(rest, 0); + Ok(obj) +} + +/// Returns the size (in bytes) of the serialized `value`. +/// +/// # Errors +/// +/// This function will return an error if the given value could not be +/// serialized. +pub fn bytesize(value: &T) -> Result +where + T: DekuContainerWrite, +{ + let data = serialize_to_bytes(value)?; + Ok(data.len()) +} + +pub fn bytesize_confidential(value: &Confidential) -> Result +where + T: DekuContainerWrite + Zeroize, +{ + let data = Confidential::new(serialize_to_bytes(value.value())?); + Ok(data.value().len()) +} + +#[cfg(test)] +mod tests { + use deku::{ctx::Endian, DekuContainerWrite, DekuRead, DekuWrite}; + use pv::request::Confidential; + + use crate::pv_utils::serializing::{confidential_read_slice, confidential_write_slice}; + + #[test] + fn read_and_write() { + #[derive(DekuRead, DekuWrite)] + #[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")] + struct Test { + #[deku( + reader = "confidential_read_slice(deku::reader, endian)", + writer = "confidential_write_slice(test, deku::writer, endian)" + )] + test: Confidential<[u32; 1]>, + } + + const DATA: [u8; 4] = [15_u8, 1_u8, 2_u8, 3_u8]; + let test = Test::try_from(DATA.as_ref()).unwrap(); + assert_eq!(test.test.value()[0], 0x0f010203); + assert_eq!(test.to_bytes().unwrap().as_slice(), &DATA); + } +} diff --git a/rust/pvimg/src/pv_utils/uv_keys.rs b/rust/pvimg/src/pv_utils/uv_keys.rs new file mode 100644 index 00000000..e83c8e1e --- /dev/null +++ b/rust/pvimg/src/pv_utils/uv_keys.rs @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2024 + +use std::io::{BufRead, BufReader, Read}; + +use enum_dispatch::enum_dispatch; +use pv::misc::decode_hex; + +use super::try_copy_slice_to_array; +use crate::error::{Error, Result}; + +/// The `enum_dispatch` macros needs at least one local trait to be implemented. +#[allow(unused)] +#[enum_dispatch(UvKeyHashes)] +trait UvKeyHashTrait: AsRef<[u8]> {} + +#[derive(Debug, PartialEq, Eq)] +pub struct UvKeyHashV1([u8; 32]); + +#[non_exhaustive] +#[enum_dispatch] +#[derive(PartialEq, Eq, Debug)] +pub enum UvKeyHash { + UvKeyHashV1(UvKeyHashV1), +} + +impl AsRef<[u8]> for UvKeyHash { + fn as_ref(&self) -> &[u8] { + match self { + Self::UvKeyHashV1(hash) => hash.as_ref(), + } + } +} + +impl UvKeyHashV1 { + pub fn new>(data: T) -> Result { + let array = try_copy_slice_to_array(data.as_ref())?; + Ok(Self(array)) + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct UvKeyHashesV1 { + pub pchkh: UvKeyHashV1, + pub pbhkh: UvKeyHashV1, + pub res: [UvKeyHashV1; 13], +} + +impl UvKeyHashV1 { + pub const UV_KEY_HASH_NULL: Self = Self([0x0_u8; 32]); +} + +impl AsRef<[u8]> for UvKeyHashV1 { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl TryFrom<&str> for UvKeyHashV1 { + type Error = Error; + + fn try_from(value: &str) -> Result { + let bytes = decode_hex(value)?; + if bytes.len() != 32 { + return Err(Error::InvalidTargetKeyHash); + } + + Ok(Self(bytes.try_into().unwrap())) + } +} + +impl TryFrom for UvKeyHashV1 { + type Error = Error; + + fn try_from(value: String) -> Result { + value.as_str().try_into() + } +} + +impl UvKeyHashesV1 { + pub const SYS_UV_KEYS_ALL: &'static str = "/sys/firmware/uv/keys/all"; + + /// Reads a `UvKeyHashesV1` from an [`std::io::Read`]. + /// + /// # Errors + /// + /// This function will return an error if this functions encounters an I/O + /// error, if a line could not be interpreted as `UvKeyHashV1` or if the + /// count of hashes is less than 15. + #[allow(clippy::similar_names)] + pub fn read_from_io(reader: R) -> Result + where + R: Read, + { + let buf_reader = BufReader::new(reader); + let lines: Vec = buf_reader + .lines() + .collect::, std::io::Error>>()?; + let hashes: Vec = lines + .into_iter() + .map(UvKeyHashV1::try_from) + .collect::, Error>>()?; + let hashes_count = hashes.len(); + if hashes_count < 15 { + return Err(Error::InvalidUvKeyHashes); + } + + let [pchkh, pbhkh, res @ ..]: [UvKeyHashV1; 15] = + hashes.try_into().map_err(|_| Error::InvalidUvKeyHashes)?; + Ok(Self { pchkh, pbhkh, res }) + } +} + +#[cfg(test)] +mod tests { + use std::io::Cursor; + + use pv::misc::decode_hex; + + use crate::{pv_utils::uv_keys::UvKeyHashV1, uvdata::UvKeyHashesV1}; + + #[test] + fn from_reader() { + let data = "0b729fd62241b339840d61b964a06bb6a1fd4976d9ebea2b4fb48d44de3a2461 +8ec6bc2f77d5d6474b1417cf0a8c914f576245a5b9bb0eefacc7b821483ece7d +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +"; + let result = UvKeyHashesV1::read_from_io(Cursor::new(data)).expect("should not fail"); + assert_eq!( + result, + UvKeyHashesV1 { + pchkh: UvKeyHashV1::new( + decode_hex("0b729fd62241b339840d61b964a06bb6a1fd4976d9ebea2b4fb48d44de3a2461") + .unwrap() + ) + .unwrap(), + pbhkh: UvKeyHashV1::new( + decode_hex("8ec6bc2f77d5d6474b1417cf0a8c914f576245a5b9bb0eefacc7b821483ece7d") + .unwrap() + ) + .unwrap(), + res: [UvKeyHashV1::UV_KEY_HASH_NULL; 13], + } + ); + } +} diff --git a/rust/pvimg/src/pv_utils/uvdata.rs b/rust/pvimg/src/pv_utils/uvdata.rs new file mode 100644 index 00000000..b0ec355a --- /dev/null +++ b/rust/pvimg/src/pv_utils/uvdata.rs @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2024 + +use enum_dispatch::enum_dispatch; +use pv::request::{ + decrypt_aead, derive_aes256_gcm_key, encrypt_aead, + openssl::pkey::{PKey, PKeyRef, Private, Public}, + Confidential, SymKey, SymKeyType, +}; + +use super::se_hdr::{SeHdrBinV1, SeHdrData, SeHdrVersioned}; +use crate::pv_utils::{ + error::{Error, Result}, + serializing::deserialize_from_bytes, +}; + +/// Trait to be used for Authenticated Encryption with Associated Data (AEAD) +/// data structures. +#[enum_dispatch] +pub trait AeadCipherTrait { + /// Returns the AEAD key type used by the data structure. + fn aead_key_type(&self) -> SymKeyType; + + /// Returns the AEAD tag size used by the data structure. + fn aead_tag_size(&self) -> usize; + + /// Returns the initialization vector (IV) used for AEAD + /// encryption/decryption. + fn iv(&self) -> &[u8]; +} + +/// Trait to be used for AEAD cipher data +#[enum_dispatch] +pub trait AeadDataTrait { + /// Returns the authenticated associated data. + fn aad(&self) -> Vec; + + /// Returns the encrypted data. + fn data(&self) -> Vec; + + /// Returns the tag data. + fn tag(&self) -> Vec; +} + +/// Trait to be used for AEAD plaintext data +#[enum_dispatch] +pub trait AeadPlainDataTrait { + /// Returns the authenticated associated data. + fn aad(&self) -> Vec; + + /// Returns the unencrypted data. + fn data(&self) -> Confidential>; + + /// Returns the tag data. + fn tag(&self) -> Vec; +} + +/// Key exchange related methods +#[enum_dispatch] +pub trait KeyExchangeTrait { + /// Checks if a public key was used. + /// + /// # Errors + /// + /// This function will return an error if the public key cannot be converted + /// into a hash. + fn contains>>(&self, key: K) -> Result; + + /// Checks if the hash of a public key was used. + fn contains_hash>(&self, hash: H) -> bool; + + /// Returns the public key from the party (e.g. guest owner) that is stored + /// in the data structed. + /// + /// # Errors + /// + /// This function will return an error if the public key cannot be + /// reconstructed. + fn cust_pub_key(&mut self) -> Result>; + + /// Returns the key type of the exchanged key. + fn key_type(&self) -> SymKeyType; + + /// Derive the key. + /// + /// # Errors + /// + /// This function will return an error if there is no customer public key is + /// available or the key derivations fails. + fn derive_key>>(&mut self, other_priv_key: K) -> Result { + match self.key_type() { + SymKeyType::Aes256Gcm => Ok(derive_aes256_gcm_key( + other_priv_key.as_ref(), + self.cust_pub_key()?.as_ref(), + )? + .into()), + _ => unreachable!("BUG"), + } + } +} + +/// Trait to be used for plain UV data. +#[enum_dispatch] +pub trait UvDataPlainTrait: + AeadPlainDataTrait + AeadCipherTrait + KeyExchangeTrait + Clone +{ + /// Returned type by [`Self::encrypt`]. + type C: UvDataTrait; + + /// Encrypt the plain data. + /// + /// # Errors + /// + /// This function will return an error if the passed `key` has the wrong + /// key type or the encryption fails. + fn encrypt(&self, key: &SymKey) -> Result + where + ::C: for<'a> deku::DekuContainerRead<'a>, + { + if key.key_type() != self.aead_key_type() { + return Err(Error::UnexpectedKeyType { + given: self.key_type().to_string(), + expected: self.aead_key_type().to_string(), + }); + } + let aad = self.aad(); + let unecrypted_data = self.data(); + let iv = self.iv(); + let result = encrypt_aead(key, iv, &aad, unecrypted_data.value())?; + Self::C::try_from_data(&result.into_buf()) + } + + /// Parses and converts the data into an instance of [`Self`] if possible. + /// + /// # Errors + /// + /// This function will return an error if the data could not parsed or + /// converted. + fn try_from_data<'a>(data: &'a [u8]) -> Result + where + Self: deku::DekuContainerRead<'a> + Sized, + { + deserialize_from_bytes(data) + } +} + +/// Trait to be used for (cipher) UV data. +#[enum_dispatch] +pub trait UvDataTrait: AeadDataTrait + AeadCipherTrait + KeyExchangeTrait + Clone { + /// Returned type by [`Self::decrypt`]. + type P: UvDataPlainTrait; + + /// Decrypt the UV data. + /// + /// # Errors + /// + /// This function will return an error if the passed `key` has the wrong key + /// type or the decryption fails. + fn decrypt(&self, key: &SymKey) -> Result + where + ::P: for<'a> deku::DekuContainerRead<'a>, + { + if key.key_type() != self.aead_key_type() { + return Err(Error::UnexpectedKeyType { + given: key.key_type().to_string(), + expected: self.aead_key_type().to_string(), + }); + } + + let tag_size = self.aead_tag_size(); + let aad = self.aad(); + let unecrypted_data = self.data(); + let iv = self.iv(); + let tag = self.tag(); + assert_eq!(tag.len(), tag_size); + let result = decrypt_aead(key, iv, &aad, &unecrypted_data, &tag)?; + Self::P::try_from_data(result.into_buf().value()) + } + + /// Parses and converts the data into an instance of [`Self`] if possible. + /// + /// # Errors + /// + /// This function will return an error if the data could not parsed or + /// converted. + fn try_from_data<'a>(data: &'a [u8]) -> Result + where + Self: deku::DekuContainerRead<'a> + Sized, + { + deserialize_from_bytes(data) + } +} diff --git a/rust/pvimg/src/pv_utils/uvdata_builder.rs b/rust/pvimg/src/pv_utils/uvdata_builder.rs new file mode 100644 index 00000000..58cbd740 --- /dev/null +++ b/rust/pvimg/src/pv_utils/uvdata_builder.rs @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2024 + +use enum_dispatch::enum_dispatch; +use openssl::pkey::{PKey, PKeyRef, Private, Public}; +use pv::request::{Confidential, SymKey}; + +use super::Error; +use crate::pv_utils::{ + error::Result, + se_hdr::SeHdrData, + uvdata::{AeadCipherTrait, UvDataPlainTrait}, +}; + +#[enum_dispatch] +pub trait AeadCipherBuilderTrait: AeadCipherTrait { + fn set_iv(&mut self, iv: &[u8]) -> Result<()>; + fn generate_aead_key(&self) -> Result { + Ok(SymKey::random(self.aead_key_type())?) + } +} + +/// Key exchange related methods +#[enum_dispatch] +pub trait KeyExchangeBuilderTrait { + fn add_keyslot( + &mut self, + hostkey: &PKeyRef, + aead_key: &SymKey, + priv_key: &PKeyRef, + ) -> Result<()>; + fn clear_keyslots(&mut self) -> Result<()>; + fn generate_private_key(&self) -> Result>; + fn set_cust_public_key(&mut self, key: &PKeyRef) -> Result<()>; +} + +pub struct UvDataBuilder< + 'a, + T: KeyExchangeBuilderTrait + AeadCipherBuilderTrait, + K = PKeyRef, + P = PKey, +> { + pub(crate) expert_mode: bool, + pub(crate) prot_key: SymKey, + pub(crate) priv_key: P, + pub(crate) target_keys: Vec<&'a K>, + pub(crate) plain_data: T, +} + +impl UvDataBuilder<'_, T, K, P> { + /// Enable expert mode - this is required for specifying PSW, etc. + pub fn i_know_what_i_am_doing(&mut self) { + self.expert_mode = true; + } +} + +impl + std::fmt::Debug for UvDataBuilder<'_, T> +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("UvDataBuilder") + .field("expert_mode", &self.expert_mode) + .field("prot_key", &self.prot_key) + .field("plain_data", &self.plain_data) + .finish() + } +} + +impl<'a, T: KeyExchangeBuilderTrait + AeadCipherBuilderTrait> UvDataBuilder<'a, T> { + pub fn add_hostkeys>>( + &mut self, + hostkeys: &'a [P], + ) -> Result<&mut Self> { + for hk in hostkeys { + self.plain_data + .add_keyslot(hk.as_ref(), &self.prot_key, &self.priv_key)?; + self.target_keys.push(hk.as_ref()); + } + + Ok(self) + } + + pub fn with_iv(&mut self, iv: &[u8]) -> Result<&mut Self> { + if !self.expert_mode { + return Err(Error::NonExpertMode); + } + self.plain_data.set_iv(iv)?; + Ok(self) + } + + fn update_target_key_slots(&mut self) -> Result<()> { + self.plain_data.clear_keyslots()?; + for hk in &self.target_keys { + self.plain_data + .add_keyslot(hk, &self.prot_key, &self.priv_key)?; + } + Ok(()) + } + + pub fn with_aead_key(&mut self, data: Confidential>) -> Result<&mut Self> { + if !self.expert_mode { + return Err(Error::NonExpertMode); + } + let key = SymKey::try_from_data(self.plain_data.aead_key_type(), data)?; + self.prot_key = key; + self.update_target_key_slots()?; + + Ok(self) + } + + pub fn with_priv_key(&mut self, priv_key: &PKeyRef) -> Result<&mut Self> { + if !self.expert_mode { + return Err(Error::NonExpertMode); + } + self.plain_data.set_cust_public_key(priv_key)?; + self.priv_key = priv_key.to_owned(); + self.update_target_key_slots()?; + + Ok(self) + } + + pub const fn prot_key(&self) -> &SymKey { + &self.prot_key + } + + pub fn priv_key(&self) -> &PKeyRef { + self.priv_key.as_ref() + } +} + +/// A trait for the builder pattern. +pub trait BuilderTrait { + /// Data structure to construct + type T; + + /// Builds the type [`Self::T`]. + /// + /// # Errors + /// + /// This function will return an error if the data structure could not be + /// build. + fn build(self) -> Result; +}