From 4099f19f4be43980a72992f1f11136db6fe65627 Mon Sep 17 00:00:00 2001 From: Bruce Leidl Date: Mon, 31 Dec 2018 18:27:17 -0500 Subject: [PATCH] Big refactor for citadel installer --- .gitignore | 1 + {citadel-mkimage => citadel-image}/Cargo.lock | 281 +---------- {citadel-mkimage => citadel-image}/Cargo.toml | 2 +- .../src/build.rs | 121 ++--- .../src/config.rs | 13 +- citadel-image/src/main.rs | 173 +++++++ citadel-install/Cargo.lock | 188 ++++++++ citadel-install/Cargo.toml | 10 + citadel-install/src/cli.rs | 150 ++++++ {libcitadel => citadel-install}/src/disks.rs | 63 +-- citadel-install/src/installer.rs | 445 ++++++++++++++++++ citadel-install/src/main.rs | 150 ++++++ citadel-install/src/util.rs | 157 ++++++ citadel-mkimage/src/main.rs | 69 --- citadel-mkimage/src/util.rs | 110 ----- citadel-mount/Cargo.lock | 294 ++---------- citadel-mount/Cargo.toml | 2 +- citadel-mount/src/boot_select.rs | 12 +- citadel-mount/src/main.rs | 26 +- citadel-mount/src/rootfs.rs | 74 ++- citadel-mount/src/uname.rs | 48 -- libcitadel/Cargo.lock | 294 ++---------- libcitadel/Cargo.toml | 5 +- libcitadel/src/cmdline.rs | 15 +- libcitadel/src/config.rs | 18 +- libcitadel/src/header.rs | 120 +++-- libcitadel/src/keys.rs | 166 +++---- libcitadel/src/lib.rs | 26 +- libcitadel/src/mount.rs | 59 +++ libcitadel/src/partition.rs | 71 ++- libcitadel/src/path_ext.rs | 354 -------------- libcitadel/src/resource.rs | 434 +++++++++-------- libcitadel/src/util.rs | 129 +++++ libcitadel/src/verity.rs | 124 +++++ 34 files changed, 2247 insertions(+), 1957 deletions(-) rename {citadel-mkimage => citadel-image}/Cargo.lock (56%) rename {citadel-mkimage => citadel-image}/Cargo.toml (91%) rename {citadel-mkimage => citadel-image}/src/build.rs (54%) rename {citadel-mkimage => citadel-image}/src/config.rs (90%) create mode 100644 citadel-image/src/main.rs create mode 100644 citadel-install/Cargo.lock create mode 100644 citadel-install/Cargo.toml create mode 100644 citadel-install/src/cli.rs rename {libcitadel => citadel-install}/src/disks.rs (55%) create mode 100644 citadel-install/src/installer.rs create mode 100644 citadel-install/src/main.rs create mode 100644 citadel-install/src/util.rs delete mode 100644 citadel-mkimage/src/main.rs delete mode 100644 citadel-mkimage/src/util.rs delete mode 100644 citadel-mount/src/uname.rs create mode 100644 libcitadel/src/mount.rs delete mode 100644 libcitadel/src/path_ext.rs create mode 100644 libcitadel/src/util.rs create mode 100644 libcitadel/src/verity.rs diff --git a/.gitignore b/.gitignore index c8f7745..0814734 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ **/target **/*.rs.bk + diff --git a/citadel-mkimage/Cargo.lock b/citadel-image/Cargo.lock similarity index 56% rename from citadel-mkimage/Cargo.lock rename to citadel-image/Cargo.lock index 26025b1..4c0fb57 100644 --- a/citadel-mkimage/Cargo.lock +++ b/citadel-image/Cargo.lock @@ -6,11 +6,6 @@ dependencies = [ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "arrayref" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "atty" version = "0.2.11" @@ -47,25 +42,6 @@ name = "bitflags" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "block-buffer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "byte-tools" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "byteorder" -version = "1.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "cc" version = "1.0.25" @@ -77,7 +53,7 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "citadel-mkimage" +name = "citadel-image" version = "0.1.0" dependencies = [ "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -102,56 +78,6 @@ dependencies = [ "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "clear_on_drop" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "cloudabi" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "curve25519-dalek" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)", - "clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", - "generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", - "subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "digest" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "ed25519-dalek" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "curve25519-dalek 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", - "failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "failure" version = "0.1.3" @@ -172,33 +98,6 @@ dependencies = [ "synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "fake-simd" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "fuchsia-zircon" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "generic-array" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "lazy_static" version = "1.2.0" @@ -213,17 +112,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" name = "libcitadel" version = "0.1.0" dependencies = [ - "ed25519-dalek 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", "nix 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ring 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)", - "sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", + "untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -254,91 +152,6 @@ dependencies = [ "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "rand" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_chacha 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_pcg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_xorshift 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_chacha" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_core" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_core" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "rand_hc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_isaac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_pcg" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_xorshift" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "redox_syscall" version = "0.1.43" @@ -352,6 +165,17 @@ dependencies = [ "redox_syscall 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ring" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", + "untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rustc-demangle" version = "0.1.9" @@ -362,27 +186,6 @@ name = "rustc-serialize" version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "serde" version = "1.0.82" @@ -398,27 +201,11 @@ dependencies = [ "syn 0.15.22 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "sha2" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", - "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "strsim" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "subtle" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "syn" version = "0.15.22" @@ -466,11 +253,6 @@ dependencies = [ "serde 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "typenum" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "unicode-width" version = "0.1.5" @@ -481,6 +263,11 @@ name = "unicode-xid" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "untrusted" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "vec_map" version = "0.8.1" @@ -512,62 +299,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -"checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee" "checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" "checksum backtrace 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "89a47830402e9981c5c41223151efcced65a0510c13097c769cede7efb34782a" "checksum backtrace-sys 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)" = "c66d56ac8dabd07f6aacdaf633f4b8262f5b3601a810a0dcddffd5c22c69daa0" "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" -"checksum block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a076c298b9ecdb530ed9d967e74a6027d6a7478924520acddcddc24c1c8ab3ab" -"checksum byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" -"checksum byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "94f88df23a25417badc922ab0f5716cc1330e87f71ddd9203b3a3ccd9cedf75d" "checksum cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "f159dfd43363c4d08055a07703eb7a3406b0dac4d0584d96965a3262db3c9d16" "checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4" "checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" -"checksum clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "97276801e127ffb46b66ce23f35cc96bd454fa311294bced4bbace7baa8b1d17" -"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -"checksum curve25519-dalek 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3eacf6ff1b911e3170a8c400b402e10c86dc3cb166bd69034ebbc2b785fea4c2" -"checksum digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "03b072242a8cbaf9c145665af9d250c59af3b958f83ed6824e13533cf76d5b90" -"checksum ed25519-dalek 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cd66d8a16ef71c23cf5eeb2140d8d3cd293457c6c7fd6804b593397a933fcf1e" "checksum failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6dd377bcc1b1b7ce911967e3ec24fa19c3224394ec05b54aa7b083d498341ac7" "checksum failure_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "64c2d913fe8ed3b6c6518eedf4538255b989945c14c2a7d5cbff62a5e2120596" -"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" -"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" -"checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d" "checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1" "checksum libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)" = "2d2857ec59fadc0773853c664d2d18e7198e83883e7060b63c924cb077bd5c74" "checksum nix 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "921f61dc817b379d0834e45d5ec45beaacfae97082090a49c2cf30dcbc30206f" "checksum proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)" = "77619697826f31a02ae974457af0b29b723e5619e113e9397b8b82c6bd253f09" "checksum quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "53fa22a1994bd0f9372d7a816207d8a2677ad0325b073f5c5332760f0fb62b5c" -"checksum rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e464cd887e869cddcae8792a4ee31d23c7edd516700695608f5b98c67ee0131c" -"checksum rand 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae9d223d52ae411a33cf7e54ec6034ec165df296ccd23533d671a28252b6f66a" -"checksum rand_chacha 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "771b009e3a508cb67e8823dda454aaa5368c7bc1c16829fb77d3e980440dd34a" -"checksum rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1961a422c4d189dfb50ffa9320bf1f2a9bd54ecb92792fb9477f99a1045f3372" -"checksum rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0905b6b7079ec73b314d4c748701f6931eb79fd97c668caa3f1899b22b32c6db" -"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" -"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" -"checksum rand_pcg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "086bd09a33c7044e56bb44d5bdde5a60e7f119a9e95b0775f545de759a32fe05" -"checksum rand_xorshift 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "effa3fcaa47e18db002bdde6060944b6d2f9cfd8db471c30e873448ad9187be3" "checksum redox_syscall 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "679da7508e9a6390aeaf7fbd02a800fdc64b73fe2204dd2c8ae66d22d9d5ad5d" "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" +"checksum ring 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe642b9dd1ba0038d78c4a3999d1ee56178b4d415c1e1fbaba83b06dce012f0" "checksum rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "bcfe5b13211b4d78e5c2cadfebd7769197d95c639c35a50057eb4c05de811395" "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" -"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" "checksum serde 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)" = "6fa52f19aee12441d5ad11c9a00459122bd8f98707cadf9778c540674f1935b6" "checksum serde_derive 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)" = "96a7f9496ac65a2db5929afa087b54f8fc5008dcfbe48a8874ed20049b0d6154" -"checksum sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9eb6be24e4c23a84d7184280d2722f7f2731fcdd4a9d886efbfe4413e4847ea0" "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" -"checksum subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" "checksum syn 0.15.22 (registry+https://github.com/rust-lang/crates.io-index)" = "ae8b29eb5210bc5cf63ed6149cbf9adfc82ac0be023d8735c176ee74a2db4da7" "checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015" "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" "checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6" "checksum toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" -"checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169" "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "55cd1f4b4e96b46aeb8d4855db4a7a9bd96eeeb5c6a1ab54593328761642ce2f" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" diff --git a/citadel-mkimage/Cargo.toml b/citadel-image/Cargo.toml similarity index 91% rename from citadel-mkimage/Cargo.toml rename to citadel-image/Cargo.toml index cb29aea..30789f9 100644 --- a/citadel-mkimage/Cargo.toml +++ b/citadel-image/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "citadel-mkimage" +name = "citadel-image" version = "0.1.0" authors = ["Bruce Leidl "] homepage = "https://github.com/subgraph/citadel" diff --git a/citadel-mkimage/src/build.rs b/citadel-image/src/build.rs similarity index 54% rename from citadel-mkimage/src/build.rs rename to citadel-image/src/build.rs index 7b5e453..006cd8b 100644 --- a/citadel-mkimage/src/build.rs +++ b/citadel-image/src/build.rs @@ -1,17 +1,16 @@ use std::path::PathBuf; use std::fs::OpenOptions; -use std::path::Path; use std::fs::{self,File}; use std::io::{self,Write}; use failure::ResultExt; -use libcitadel::Result; +use libcitadel::{Result,ImageHeader,util,verity}; use BuildConfig; -use util; pub struct UpdateBuilder { config: BuildConfig, + target: PathBuf, nblocks: Option, shasum: Option, @@ -28,18 +27,24 @@ fn align(sz: usize, n: usize) -> usize { impl UpdateBuilder { - pub fn new(config: BuildConfig) -> Result { - let builder = UpdateBuilder { - config, + pub fn new(config: BuildConfig) -> UpdateBuilder { + let filename = UpdateBuilder::target_filename(&config); + let target = config.workdir_path(&filename); + UpdateBuilder { + config, target, nblocks: None, shasum: None, verity_salt: None, verity_root: None, - }; + } + } - builder.copy_source_image()?; - Ok(builder) + fn target_filename(config: &BuildConfig) -> String { + format!("citadel-{}-{}-{:03}", config.img_name(), config.channel(), config.version()) } pub fn build(&mut self) -> Result<()> { + info!("Copying source file to {}", self.target.display()); + fs::copy(self.config.source(), &self.target)?; + self.pad_image() .context("failed writing padding to image")?; @@ -55,27 +60,12 @@ impl UpdateBuilder { Ok(()) } - fn target(&self, ext: Option<&str>) -> PathBuf { - match ext { - Some(s) => self.config.workdir_path(&format!("citadel-{}-{}-{:03}.{}", self.config.img_name(), self.config.channel(), self.config.version(), s)), - None => self.config.workdir_path(&format!("citadel-{}-{}-{:03}", self.config.img_name(), self.config.channel(), self.config.version())) - } - } - - fn copy_source_image(&self) -> Result<()> { - sanity_check_source(self.config.source())?; - let target = self.target(None); - info!("Copying source file to {}", target.display()); - let mut from = File::open(self.config.source())?; - let mut to = File::create(&target)?; - io::copy(&mut from, &mut to)?; - Ok(()) - } - fn pad_image(&mut self) -> Result<()> { - let target = self.target(None); - let meta = target.metadata()?; + let meta = self.target.metadata()?; let len = meta.len() as usize; + if len % 512 != 0 { + bail!("Image file size is not a multiple of sector size (512 bytes)"); + } let padlen = align(len, BLOCK_SIZE) - len; if padlen > 0 { @@ -83,7 +73,7 @@ impl UpdateBuilder { let zeros = vec![0u8; padlen]; let mut file = OpenOptions::new() .append(true) - .open(&target)?; + .open(&self.target)?; file.write_all(&zeros)?; } @@ -95,7 +85,7 @@ impl UpdateBuilder { } fn calculate_shasum(&mut self) -> Result<()> { - let shasum = util::sha256(self.target(None))?; + let shasum = util::sha256(&self.target)?; info!("Sha256 of image data is {}", shasum); self.shasum = Some(shasum); Ok(()) @@ -105,7 +95,7 @@ impl UpdateBuilder { let hashfile = self.config.workdir_path(&format!("verity-hash-{}-{:03}", self.config.image_type(), self.config.version())); let outfile = self.config.workdir_path("verity-format.out"); - let verity = util::verity_initial_hashtree(self.target(None), &hashfile)?; + let verity = verity::generate_initial_hashtree(&self.target, &hashfile)?; fs::write(outfile, verity.output()) @@ -132,59 +122,33 @@ impl UpdateBuilder { fn compress_image(&self) -> Result<()> { info!("Compressing image data"); - util::xz_compress(self.target(None)) + util::xz_compress(&self.target) } fn write_final_image(&self) -> Result<()> { let header = self.generate_header()?; - let mut path = self.target(None); - let fname = path.file_name().unwrap().to_str().unwrap().to_owned(); - path.set_file_name(fname + ".img"); + let filename = format!("{}.img", UpdateBuilder::target_filename(&self.config)); + let mut image_path = self.config.workdir_path(&filename); - let mut out = File::create(&path) - .context(format!("could not open output file {}", path.display()))?; + let mut out = File::create(&image_path) + .context(format!("could not open output file {}", image_path.display()))?; - out.write_all(&header)?; + header.write_header(&out)?; - path.set_extension("xz"); - let mut data = File::open(&path) - .context(format!("could not open compressed image data file {}", path.display()))?; + image_path.set_extension("xz"); + let mut data = File::open(&image_path) + .context(format!("could not open compressed image data file {}", image_path.display()))?; io::copy(&mut data, &mut out) .context("error copying image data to output file")?; Ok(()) } - - /// - /// The Image Header structure is stored in a 4096 byte block at the start of - /// every resource image file. When an image is installed to a partition it - /// is stored at the last 4096 byte block of the block device for the partition. - /// - /// The layout of this structure is the following: - /// - /// field size (bytes) offset - /// ----- ------------ ------ - /// - /// magic 4 0 - /// status 1 4 - /// flags 1 5 - /// length 2 6 - /// - /// metainfo 8 - /// - /// signature 64 8 + length - /// - fn generate_header(&self) -> Result> { - let mut hdr = vec![0u8; BLOCK_SIZE]; - hdr[0..4].copy_from_slice(b"SGOS"); - hdr[5] = 0x04; // FLAG_DATA_COMPRESSED + fn generate_header(&self) -> Result { + let hdr = ImageHeader::new(); + hdr.set_flag(ImageHeader::FLAG_DATA_COMPRESSED); let metainfo = self.generate_metainfo(); - let metalen = metainfo.len(); - hdr[6] = (metalen >> 8) as u8; - hdr[7] = metalen as u8; - hdr[8..8+metalen].copy_from_slice(&metainfo); - + hdr.set_metainfo_bytes(&metainfo); Ok(hdr) } @@ -208,20 +172,3 @@ impl UpdateBuilder { Ok(v) } } - -fn sanity_check_source>(src: P) -> Result<()> { - let src: &Path = src.as_ref(); - let meta = match src.metadata() { - Ok(md) => md, - Err(e) => bail!("Could not load image file {}: {}", src.display(), e), - }; - - if !meta.file_type().is_file() { - bail!("Image file {} exists but is not a regular file", src.display()); - } - - if meta.len() % 512 != 0 { - bail!("Image file size is not a multiple of sector size (512 bytes)"); - } - Ok(()) -} diff --git a/citadel-mkimage/src/config.rs b/citadel-image/src/config.rs similarity index 90% rename from citadel-mkimage/src/config.rs rename to citadel-image/src/config.rs index b1f9160..bc5fe47 100644 --- a/citadel-mkimage/src/config.rs +++ b/citadel-image/src/config.rs @@ -1,21 +1,19 @@ - -use std::path::{Path,PathBuf}; use std::fs::File; use std::io::Read; +use std::path::{Path, PathBuf}; use toml; use libcitadel::Result; - #[derive(Deserialize)] pub struct BuildConfig { - #[serde (rename="image-type")] + #[serde(rename = "image-type")] image_type: String, channel: String, version: usize, source: String, - #[serde (rename="kernel-version")] + #[serde(rename = "kernel-version")] kernel_version: Option, #[serde(skip)] @@ -64,7 +62,10 @@ impl BuildConfig { }; let src = Path::new(&self.source); if !src.is_file() { - bail!("Source path '{}' does not exist or is not a regular file", src.display()); + bail!( + "Source path '{}' does not exist or is not a regular file", + src.display() + ); } if self.image_type == "modules" && self.kernel_version.is_none() { bail!("Cannot build 'modules' image without kernel-version field"); diff --git a/citadel-image/src/main.rs b/citadel-image/src/main.rs new file mode 100644 index 0000000..639ba5b --- /dev/null +++ b/citadel-image/src/main.rs @@ -0,0 +1,173 @@ +#[macro_use] extern crate libcitadel; +#[macro_use] extern crate failure; +#[macro_use] extern crate serde_derive; + +extern crate clap; +extern crate toml; + +use std::process::exit; +use std::path::Path; + +use clap::{App,Arg,SubCommand,ArgMatches}; +use clap::AppSettings::*; + +use build::UpdateBuilder; +use config::BuildConfig; +use libcitadel::{Result,ResourceImage,set_verbose,format_error,Partition,KeyPair}; + +mod build; +mod config; + +fn main() { + let app = App::new("citadel-image") + .about("Citadel update image builder") + .settings(&[ArgRequiredElseHelp,ColoredHelp, DisableHelpSubcommand, DisableVersion, DeriveDisplayOrder]) + + .subcommand(SubCommand::with_name("build") + .about("Build an update image specified by a configuration file") + .arg(Arg::with_name("build-file") + .required(true) + .help("Path to image build config file"))) + .subcommand(SubCommand::with_name("metainfo") + .about("Display metainfo variables for an image file") + .arg(Arg::with_name("path") + .required(true) + .help("Path to image file"))) + .subcommand(SubCommand::with_name("generate-verity") + .about("Generate dm-verity hash tree for an image file") + .arg(Arg::with_name("path") + .required(true) + .help("Path to image file"))) + .subcommand(SubCommand::with_name("verify") + .about("Verify dm-verity hash tree for an image file") + .arg(Arg::with_name("path") + .required(true) + .help("Path to image file"))) + + .subcommand(SubCommand::with_name("install-rootfs") + .about("Install rootfs image file to a partition") + .arg(Arg::with_name("path") + .required(true) + .help("Path to image file"))) + + .subcommand(SubCommand::with_name("genkeys") + .about("Generate a pair of keys")) + + .subcommand(SubCommand::with_name("decompress") + .about("Decompress a compressed image file") + .arg(Arg::with_name("path") + .required(true) + .help("Path to image file"))); + + set_verbose(true); + let matches = app.get_matches(); + + let result = match matches.subcommand() { + ("build", Some(m)) => build_image(m), + ("metainfo", Some(m)) => metainfo(m), + ("generate-verity", Some(m)) => generate_verity(m), + ("verify", Some(m)) => verify(m), + ("sign-image", Some(m)) => sign_image(m), + ("genkeys", Some(_)) => genkeys(), + ("decompress", Some(m)) => decompress(m), + ("install-rootfs", Some(m)) => install_rootfs(m), + _ => Ok(()), + }; + + if let Err(ref e) = result { + println!("Error: {}", format_error(e)); + exit(1); + } +} + +fn build_image(arg_matches: &ArgMatches) -> Result<()> { + let build_file = arg_matches.value_of("build-file").unwrap(); + let config = BuildConfig::load(build_file)?; + let mut builder = UpdateBuilder::new(config); + builder.build()?; + Ok(()) +} + +fn metainfo(arg_matches: &ArgMatches) -> Result<()> { + let img = load_image(arg_matches)?; + print!("{}",String::from_utf8(img.header().metainfo_bytes())?); + Ok(()) +} + +fn generate_verity(arg_matches: &ArgMatches) -> Result<()> { + let img = load_image(arg_matches)?; + if img.has_verity_hashtree() { + info!("Image already has dm-verity hashtree appended, doing nothing."); + } else { + img.generate_verity_hashtree()?; + } + Ok(()) +} + +fn verify(arg_matches: &ArgMatches) -> Result<()> { + let img = load_image(arg_matches)?; + let ok = img.verify_verity()?; + if ok { + info!("Image verification succeeded"); + } else { + warn!("Image verification FAILED!"); + } + Ok(()) +} + +fn load_image(arg_matches: &ArgMatches) -> Result { + let path = arg_matches.value_of("path").expect("path argument missing"); + if !Path::new(path).exists() { + bail!("Cannot load image {}: File does not exist", path); + } + let img = ResourceImage::from_path(path)?; + if !img.is_valid_image() { + bail!("File {} is not a valid image file", path); + } + Ok(img) +} + +fn install_rootfs(arg_matches: &ArgMatches) -> Result<()> { + let img = load_image(arg_matches)?; + let partition =choose_install_partition()?; + img.write_to_partition(&partition)?; + Ok(()) +} + +fn sign_image(arg_matches: &ArgMatches) -> Result<()> { + let _img = load_image(arg_matches)?; + info!("Not implemented yet"); + Ok(()) +} + +fn genkeys() -> Result<()> { + let keypair = KeyPair::generate()?; + println!("public-key = \"{}\"", keypair.public_key_hex()); + println!("private-key = \"{}\"", keypair.private_key_hex()); + Ok(()) +} + +fn decompress(arg_matches: &ArgMatches) -> Result<()> { + let img = load_image(arg_matches)?; + if !img.is_compressed() { + info!("Image is not compressed, not decompressing."); + } else { + img.decompress()?; + } + Ok(()) +} + +fn choose_install_partition() -> Result { + let partitions = Partition::rootfs_partitions()?; + for p in &partitions { + if !p.is_mounted() && !p.is_initialized() { + return Ok(p.clone()) + } + } + for p in &partitions { + if !p.is_mounted() { + return Ok(p.clone()) + } + } + Err(format_err!("No suitable install partition found")) +} diff --git a/citadel-install/Cargo.lock b/citadel-install/Cargo.lock new file mode 100644 index 0000000..52d93a1 --- /dev/null +++ b/citadel-install/Cargo.lock @@ -0,0 +1,188 @@ +[[package]] +name = "autocfg" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "backtrace" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-demangle 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "backtrace-sys" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cc" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "citadel-install" +version = "0.1.0" +dependencies = [ + "failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", + "rpassword 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "failure" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)", + "failure_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "failure_derive" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)", + "synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libc" +version = "0.2.45" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "proc-macro2" +version = "0.4.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quote" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rpassword" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "syn" +version = "0.15.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "synstructure" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum autocfg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4e5f34df7a019573fb8bdc7e24a2bfebe51a2a1d6bfdbaeccedb3c41fc574727" +"checksum backtrace 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)" = "b5b493b66e03090ebc4343eb02f94ff944e0cbc9ac6571491d170ba026741eb5" +"checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6" +"checksum cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4a8b715cb4597106ea87c7c84b2f1d452c7492033765df7f32651e66fcf749" +"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4" +"checksum failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6dd377bcc1b1b7ce911967e3ec24fa19c3224394ec05b54aa7b083d498341ac7" +"checksum failure_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "64c2d913fe8ed3b6c6518eedf4538255b989945c14c2a7d5cbff62a5e2120596" +"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +"checksum libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)" = "2d2857ec59fadc0773853c664d2d18e7198e83883e7060b63c924cb077bd5c74" +"checksum proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)" = "77619697826f31a02ae974457af0b29b723e5619e113e9397b8b82c6bd253f09" +"checksum quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "53fa22a1994bd0f9372d7a816207d8a2677ad0325b073f5c5332760f0fb62b5c" +"checksum rpassword 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d37473170aedbe66ffa3ad3726939ba677d83c646ad4fd99e5b4bc38712f45ec" +"checksum rustc-demangle 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "01b90379b8664dd83460d59bdc5dd1fd3172b8913788db483ed1325171eab2f7" +"checksum syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)" = "9545a6a093a3f0bd59adb472700acc08cad3776f860f16a897dfce8c88721cbc" +"checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015" +"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" +"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/citadel-install/Cargo.toml b/citadel-install/Cargo.toml new file mode 100644 index 0000000..7428f58 --- /dev/null +++ b/citadel-install/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "citadel-install" +version = "0.1.0" +authors = ["Bruce Leidl "] +homepage = "http://github.com/subgraph/citadel" + +[dependencies] +failure = "0.1.3" +libc = "0.2" +rpassword = "2.1.0" diff --git a/citadel-install/src/cli.rs b/citadel-install/src/cli.rs new file mode 100644 index 0000000..c8c6859 --- /dev/null +++ b/citadel-install/src/cli.rs @@ -0,0 +1,150 @@ +use std::io::{self,Write}; +use std::path::Path; +use Result; +use util::Disk; +use rpassword; +use installer::Installer; + +pub fn run_cli_install() -> Result { + let disk = match choose_disk()? { + Some(disk) => disk, + None => return Ok(false), + }; + + display_disk(&disk); + + let passphrase = match read_passphrase()? { + Some(passphrase) => passphrase, + None => return Ok(false), + }; + + if !confirm_install(&disk)? { + return Ok(false); + } + run_install(disk, passphrase)?; + Ok(true) +} + +pub fn run_cli_install_with>(target: P) -> Result { + let disk = find_disk_by_path(target.as_ref())?; + display_disk(&disk); + + let passphrase = match read_passphrase()? { + Some(passphrase) => passphrase, + None => return Ok(false), + }; + + if !confirm_install(&disk)? { + return Ok(false); + } + + run_install(disk, passphrase)?; + Ok(true) +} + +fn run_install(disk: Disk, passphrase: String) -> Result<()> { + let mut install = Installer::new(); + install.set_target(disk.path().to_str().unwrap()); + install.set_passphrase(&passphrase); + install.set_install_syslinux(true); + install.verify()?; + install.run() +} + +fn display_disk(disk: &Disk) { + println!(); + println!(" Device: {}", disk.path().display()); + println!(" Size: {}", disk.size_str()); + println!(" Model: {}", disk.model()); + println!(); +} + +fn find_disk_by_path(path: &Path) -> Result { + if !path.exists() { + bail!("Target disk path {} does not exist", path.display()); + } + for disk in Disk::probe_all()? { + if disk.path() == path { + return Ok(disk.clone()); + } + } + Err(format_err!("Installation target {} is not a valid disk", path.display())) +} + +fn choose_disk() -> Result> { + let disks = Disk::probe_all()?; + if disks.is_empty() { + bail!("No disks found."); + } + + loop { + prompt_choose_disk(&disks)?; + let line = read_line()?; + if line == "q" || line == "Q" { + return Ok(None); + } + if let Ok(n) = line.parse::() { + if n > 0 && n <= disks.len() { + return Ok(Some(disks[n-1].clone())); + } + } + } +} + +fn prompt_choose_disk(disks: &[Disk]) -> Result<()> { + println!("Available disks:\n"); + for idx in 0..disks.len() { + println!(" [{}]: {} Size: {} Model: {}", idx + 1, disks[idx].path().display(), disks[idx].size_str(), disks[idx].model()); + } + print!("\nChoose a disk to install to (q to quit): "); + io::stdout().flush()?; + Ok(()) +} + +fn read_line() -> Result { + let mut input = String::new(); + io::stdin().read_line(&mut input)?; + if input.ends_with('\n') { + input.pop(); + } + Ok(input) +} + +fn read_passphrase() -> Result> { + loop { + println!("Enter a disk encryption passphrase (or 'q' to quit)"); + println!(); + let passphrase = rpassword::read_password_from_tty(Some(" Passphrase : "))?; + if passphrase.is_empty() { + println!("Passphrase cannot be empty"); + continue; + } + if passphrase == "q" || passphrase == "Q" { + return Ok(None); + } + let confirm = rpassword::read_password_from_tty(Some(" Confirm : "))?; + if confirm == "q" || confirm == "Q" { + return Ok(None); + } + println!(); + if passphrase == confirm { + return Ok(Some(passphrase)); + } + println!("Passphrases do not match"); + println!(); + } +} + +fn confirm_install(disk: &Disk) -> Result { + println!("Are you sure you want to completely erase this this device?"); + println!(); + println!(" Device: {}", disk.path().display()); + println!(" Size: {}", disk.size_str()); + println!(" Model: {}", disk.model()); + println!(); + print!("Type YES (uppercase) to continue with install: "); + io::stdout().flush()?; + let answer = read_line()?; + Ok(answer == "YES") +} + diff --git a/libcitadel/src/disks.rs b/citadel-install/src/disks.rs similarity index 55% rename from libcitadel/src/disks.rs rename to citadel-install/src/disks.rs index c38aab6..e707514 100644 --- a/libcitadel/src/disks.rs +++ b/citadel-install/src/disks.rs @@ -1,10 +1,8 @@ -use std::path::{Path,PathBuf}; +use std::path::{Path, PathBuf}; +use std::fs; - -use {Result,PathExt}; - -// Partition Type GUID of UEFI boot (ESP) partition -const ESP_GUID: &str = "c12a7328-f81f-11d2-ba4b-00a0c93ec93b"; +use Result; +use util; /// /// Represents a disk partition device on the system @@ -20,13 +18,15 @@ pub struct DiskPartition { } impl DiskPartition { - /// Return list of all UEFI ESP partitions on the system as a `Vec` + /// Return list of all vfat partitions on the system as a `Vec` pub fn boot_partitions() -> Result> { + let pp = fs::read_to_string("/proc/partitions")?; let mut v = Vec::new(); - for line in Path::new("/proc/partitions").read_as_lines()?.iter().skip(2) { + for line in pp.lines().skip(2) + { let part = DiskPartition::from_proc_line(&line) .map_err(|e| format_err!("Failed to parse line '{}': {}", line, e))?; - if part.is_esp() { + if part.is_vfat()? { v.push(part); } } @@ -48,52 +48,39 @@ impl DiskPartition { v[0].parse::()?, // Major v[1].parse::()?, // Minor v[2].parse::()?, // number of blocks - v[3])) // device name + v[3], + )) // device name } // create a new `DiskPartion` from parsed components of line from /proc/partitions fn from_line_components(major: u8, minor: u8, blocks: usize, name: &str) -> DiskPartition { DiskPartition { path: PathBuf::from("/dev").join(name), - major, minor, blocks, + major, + minor, + blocks, } } - // return `true` if partition has UEFI ESP partition type - fn is_esp(&self) -> bool { - match self.path.partition_type_guid() { - Ok(guid) => guid == ESP_GUID, - Err(err) => { - warn!("Error: {}", err); - false - }, - } + // return `true` if partition is VFAT type + fn is_vfat(&self) -> Result { + let ok = self.partition_fstype()? == "vfat"; + Ok(ok) } pub fn path(&self) -> &Path { &self.path } - pub fn mount>(&self, target: P) -> bool { - match self.path.mount(target) { - Err(e) => { - warn!("{}", e); - false - }, - Ok(()) => true, - - } + pub fn mount>(&self, target: P) -> Result<()> { + util::exec_cmdline("/usr/bin/mount", format!("{} {}", self.path.display(), target.as_ref().display())) } - pub fn umount(&self) -> bool { - match self.path.umount() { - Err(e) => { - warn!("{}", e); - false - }, - Ok(()) => true, + pub fn umount(&self) -> Result<()> { + util::exec_cmdline("/usr/bin/umount", self.path().to_str().unwrap()) + } - } + fn partition_fstype(&self) -> Result { + util::exec_cmdline_with_output("/usr/bin/lsblk", format!("-dno FSTYPE {}", self.path().display())) } } - diff --git a/citadel-install/src/installer.rs b/citadel-install/src/installer.rs new file mode 100644 index 0000000..5b6dc37 --- /dev/null +++ b/citadel-install/src/installer.rs @@ -0,0 +1,445 @@ +use std::cell::RefCell; +use std::fs::{self,File}; +use std::io::Write; +use std::os::unix::fs as unixfs; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::time::Instant; + +use super::util; +use super::Result; + +const BLKDEACTIVATE: &str = "/sbin/blkdeactivate"; +const CRYPTSETUP: &str = "/sbin/cryptsetup"; +const PARTED: &str = "/sbin/parted"; +const EXTLINUX: &str = "/sbin/extlinux"; +const PVCREATE: &str = "/sbin/pvcreate"; +const VGCREATE: &str = "/sbin/vgcreate"; +const LVCREATE: &str = "/sbin/lvcreate"; +const VGCHANGE: &str = "/sbin/vgchange"; +const MKFS_VFAT: &str = "/sbin/mkfs.vfat"; +const MKFS_BTRFS: &str = "/bin/mkfs.btrfs"; +const LSBLK: &str = "/bin/lsblk"; +const BTRFS: &str = "/bin/btrfs"; +const MOUNT: &str = "/bin/mount"; +const UMOUNT: &str = "/bin/umount"; +const CHOWN: &str = "/bin/chown"; +const TAR: &str = "/bin/tar"; +const XZ: &str = "/bin/xz"; +const DD: &str = "/bin/dd"; +const CITADEL_IMAGE: &str = "/usr/bin/citadel-image"; + +const LUKS_UUID: &str = "683a17fc-4457-42cc-a946-cde67195a101"; + +const EXTRA_IMAGE_NAME: &str = "citadel-extra.img"; + +const INSTALL_MOUNT: &str = "/run/installer/mnt"; +const LUKS_PASSPHRASE_FILE: &str = "/run/installer/luks-passphrase"; + +const DEFAULT_ARTIFACT_DIRECTORY: &str = "/run/images"; + +const KERNEL_CMDLINE: &str = "add_efi_memmap intel_iommu=off cryptomgr.notests rcupdate.rcu_expedited=1 rcu_nocbs=0-64 tsc=reliable no_timer_check noreplace-smp i915.fastboot=1 citadel.nosignatures quiet splash"; + +pub struct Installer { + install_syslinux: bool, + target_device: String, + passphrase: String, + artifact_directory: String, + logfile: Option>, +} + +impl Installer { + pub fn new() -> Installer { + Installer { + install_syslinux: true, + target_device: String::new(), + passphrase: String::new(), + artifact_directory: DEFAULT_ARTIFACT_DIRECTORY.to_string(), + logfile: None, + } + } + + pub fn set_target(&mut self, target: &str) { + self.target_device = target.to_owned() + } + + pub fn set_passphrase(&mut self, passphrase: &str) { + self.passphrase = passphrase.to_owned() + } + + pub fn set_install_syslinux(&mut self, val: bool) { + self.install_syslinux = val; + } + + pub fn verify(&self) -> Result<()> { + let tools = vec![ + BLKDEACTIVATE,CRYPTSETUP,PARTED,EXTLINUX,PVCREATE,VGCREATE,LVCREATE,VGCHANGE, + MKFS_VFAT,MKFS_BTRFS,LSBLK,BTRFS,MOUNT,UMOUNT,CHOWN,TAR,XZ,CITADEL_IMAGE, + ]; + + let modules_img = self.modules_imagename(); + let artifacts = vec![ + "bootx64.efi", "bzImage", + modules_img.as_str(), EXTRA_IMAGE_NAME, + ]; + + if self.target_device.is_empty() { + bail!("No target device set in install configuration"); + } + if self.passphrase.is_empty() { + bail!("No passphrase set in install configuration"); + } + if !Path::new(&self.target_device).exists() { + bail!("Target device {} does not exist", self.target_device); + } + + for tool in tools { + if !Path::new(tool).exists() { + bail!("Required installer utility program does not exist: {}", tool); + } + } + + for a in artifacts { + if !self.artifact_path(a).exists() { + bail!("Required install artifact {} does not exist in {}", a, self.artifact_directory); + } + } + + if !self.artifact_path("appimg-rootfs.tar").exists() && !self.artifact_path("appimg-rootfs.tar.xz").exists() { + bail!("Required component appimg-rootfs.tar(.xz) does not exist in {}",self.artifact_directory); + } + + Ok(()) + } + + pub fn run(&self) -> Result<()> { + let start = Instant::now(); + + fs::create_dir_all(INSTALL_MOUNT)?; + + self.partition_disk()?; + self.setup_luks()?; + self.setup_lvm()?; + + self.setup_boot()?; + + self.create_storage()?; + + self.output("\n")?; + self.header("Installing rootfs partitions\n")?; + let args = format!("install-rootfs {}", self.artifact_path("citadel-rootfs.img").display()); + self.cmd(CITADEL_IMAGE, &args)?; + self.cmd(CITADEL_IMAGE, &args)?; + + self.cmd(LSBLK, format!("-o NAME,SIZE,TYPE,FSTYPE {}", &self.target_device))?; + + self.cmd(VGCHANGE, "-an citadel")?; + self.cmd(CRYPTSETUP, "luksClose luks-install")?; + + self.header(format!("Install completed successfully in {} seconds", start.elapsed().as_secs()))?; + + Ok(()) + } + + pub fn live_setup(&self) -> Result<()> { + self.cmd(MOUNT, "-t tmpfs var-tmpfs /sysroot/var")?; + self.cmd(MOUNT, "-t tmpfs home-tmpfs /sysroot/home")?; + let _ = fs::read("/sys/class/zram-control/hot_add")?; + // Create an 8gb zram disk to use as /storage partition + fs::write("/sys/block/zram1/comp_algorithm", "lz4")?; + fs::write("/sys/block/zram1/disksize", "8G")?; + self.cmd(MKFS_BTRFS, "/dev/zram1")?; + self.cmd(MOUNT, "/dev/zram1 /sysroot/storage")?; + fs::create_dir_all("/sysroot/storage/realms")?; + self.cmd(MOUNT, "--bind /sysroot/storage/realms /sysroot/realms")?; + + let cmdline = fs::read_to_string("/proc/cmdline")?; + if cmdline.contains("citadel.live") { + self.setup_storage(Path::new("/sysroot/storage"), false)?; + } + + Ok(()) + } + + fn partition_disk(&self) -> Result<()> { + self.header("Partitioning target disk")?; + self.cmd(BLKDEACTIVATE, &self.target_device)?; + self.parted("mklabel gpt")?; + self.parted("mkpart boot fat32 1MiB 513MiB")?; + self.parted("set 1 boot on")?; + self.parted("mkpart data ext4 513MiB 100%")?; + self.parted("set 2 lvm on")?; + Ok(()) + } + + fn parted(&self, cmdline: &str) -> Result<()> { + let args = format!("-s {} {}", self.target_device, cmdline); + self.cmd(PARTED, args) + } + + fn setup_luks(&self) -> Result<()> { + self.header("Setting up LUKS disk encryption")?; + fs::write(LUKS_PASSPHRASE_FILE, self.passphrase.as_bytes())?; + let luks_partition = self.target_partition(2); + + let args = format!( + "-q --uuid={} luksFormat {} {}", + LUKS_UUID, luks_partition, LUKS_PASSPHRASE_FILE + ); + self.cmd(CRYPTSETUP, args)?; + + let args = format!( + "open --type luks --key-file {} {} luks-install", + LUKS_PASSPHRASE_FILE, luks_partition + ); + self.cmd(CRYPTSETUP, args)?; + fs::remove_file(LUKS_PASSPHRASE_FILE)?; + Ok(()) + } + + fn setup_lvm(&self) -> Result<()> { + self.header("Setting up LVM volumes")?; + self.cmd(PVCREATE, "-ff --yes /dev/mapper/luks-install")?; + self.cmd(VGCREATE, "--yes citadel /dev/mapper/luks-install")?; + + self.cmd(LVCREATE, "--yes --size 2g --name rootfsA citadel")?; + self.cmd(LVCREATE, "--yes --size 2g --name rootfsB citadel")?; + self.cmd(LVCREATE, "--yes --extents 100%VG --name storage citadel")?; + Ok(()) + } + + fn setup_boot(&self) -> Result<()> { + self.header("Setting up /boot partition")?; + let boot_partition = self.target_partition(1); + self.cmd(MKFS_VFAT, format!("-F 32 {}", boot_partition))?; + + self.cmd(MOUNT, format!("{} {}", boot_partition, INSTALL_MOUNT))?; + + fs::create_dir_all(format!("{}/loader/entries", INSTALL_MOUNT))?; + + self.info("Writing /boot/loader/loader.conf")?; + fs::write(format!("{}/loader/loader.conf", INSTALL_MOUNT), self.loader_conf())?; + + self.info("Writing /boot/entries/citadel.conf")?; + fs::write(format!("{}/loader/entries/citadel.conf", INSTALL_MOUNT), self.boot_conf())?; + + self.copy_artifact("bzImage", INSTALL_MOUNT)?; + self.copy_artifact("bootx64.efi", format!("{}/EFI/BOOT", INSTALL_MOUNT))?; + + if self.install_syslinux { + self.setup_syslinux()?; + } + + self.cmd(UMOUNT, INSTALL_MOUNT)?; + + if self.install_syslinux { + self.setup_syslinux_post_umount()?; + } + + Ok(()) + } + + fn loader_conf(&self) -> Vec { + let mut v = Vec::new(); + writeln!(&mut v, "default citadel").unwrap(); + writeln!(&mut v, "timeout 5").unwrap(); + v + } + + fn boot_conf(&self) -> Vec { + let mut v = Vec::new(); + writeln!(&mut v, "title Subgraph OS (Citadel)").unwrap(); + writeln!(&mut v, "linux /bzImage").unwrap(); + writeln!(&mut v, "options root=/dev/mapper/rootfs {}", KERNEL_CMDLINE).unwrap(); + v + } + + fn setup_syslinux(&self) -> Result<()> { + self.header("Installing syslinux")?; + let syslinux_src = self.artifact_path("syslinux"); + if !syslinux_src.exists() { + bail!("No syslinux directory found in artifact directory, cannot install syslinux"); + } + let dst = Path::new(INSTALL_MOUNT).join("syslinux"); + fs::create_dir_all(&dst)?; + self.info("Copying syslinux files to /boot/syslinux")?; + for entry in fs::read_dir(&syslinux_src)? { + let entry = entry?; + fs::copy(entry.path(), dst.join(entry.file_name()))?; + } + self.info("Writing syslinux.cfg")?; + fs::write(dst.join("syslinux.cfg"), self.syslinux_conf())?; + self.cmd(EXTLINUX, format!("--install {}", dst.display()))?; + Ok(()) + } + + fn setup_syslinux_post_umount(&self) -> Result<()> { + let mbrbin = self.artifact_path("syslinux/gptmbr.bin"); + if !mbrbin.exists() { + bail!("Could not find MBR image: {}", mbrbin.display()); + } + let args = format!("bs=440 count=1 conv=notrunc if={} of={}", mbrbin.display(), self.target_device); + self.cmd(DD, args)?; + self.parted("set 1 legacy_boot on")?; + Ok(()) + } + + fn syslinux_conf(&self) -> Vec { + let mut v = Vec::new(); + writeln!(&mut v, "UI menu.c32").unwrap(); + writeln!(&mut v, "PROMPT 0").unwrap(); + writeln!(&mut v, "").unwrap(); + writeln!(&mut v, "MENU TITLE Boot Subgraph OS (Citadel)").unwrap(); + writeln!(&mut v, "TIMEOUT 50").unwrap(); + writeln!(&mut v, "DEFAULT subgraph").unwrap(); + writeln!(&mut v, "").unwrap(); + writeln!(&mut v, "LABEL subgraph").unwrap(); + writeln!(&mut v, " MENU LABEL Subgraph OS").unwrap(); + writeln!(&mut v, " LINUX ../bzImage").unwrap(); + writeln!(&mut v, " APPEND root=/dev/mapper/rootfs {}", KERNEL_CMDLINE).unwrap(); + v + } + + fn create_storage(&self) -> Result<()> { + self.header("Setting up /storage partition")?; + self.cmd(MKFS_BTRFS, "/dev/mapper/citadel-storage")?; + self.cmd(MOUNT, format!("/dev/mapper/citadel-storage {}", INSTALL_MOUNT))?; + self.setup_storage(Path::new(INSTALL_MOUNT), true)?; + self.cmd(UMOUNT, INSTALL_MOUNT)?; + Ok(()) + } + + fn setup_storage(&self, base: &Path, copy_resources: bool) -> Result<()> { + if copy_resources { + self.setup_storage_resources(base)?; + } + + self.setup_base_appimg(base)?; + self.setup_main_realm(base)?; + + self.info("Creating /Shared realms directory")?; + fs::create_dir_all(base.join("realms/Shared"))?; + self.cmd(CHOWN, format!("1000:1000 {}/realms/Shared", base.display()))?; + + Ok(()) + } + + fn setup_base_appimg(&self, base: &Path) -> Result<()> { + self.header("Unpacking appimg rootfs")?; + let appimg_dir = base.join("appimg"); + fs::create_dir_all(&appimg_dir)?; + self.cmd(BTRFS, format!("subvolume create {}/base.appimg", appimg_dir.display()))?; + + let xz_rootfs = self.artifact_path("appimg-rootfs.tar.xz"); + if xz_rootfs.exists() { + self.cmd(XZ, format!("-d {}", xz_rootfs.display()))?; + } + let rootfs_bundle = self.artifact_path("appimg-rootfs.tar"); + self.cmd(TAR, format!("-C {}/base.appimg -xf {}", appimg_dir.display(), rootfs_bundle.display()))?; + + Ok(()) + } + + fn setup_main_realm(&self, base: &Path) -> Result<()> { + self.header("Creating main realm")?; + + let realm = base.join("realms/realm-main"); + let home = realm.join("home"); + + self.info("Creating home directory /realms/realm-main/home")?; + fs::create_dir_all(&home)?; + + self.cmd(BTRFS, format!("subvolume snapshot {}/appimg/base.appimg {}/rootfs", + base.display(), realm.display()))?; + + self.info("Copying .bashrc and .profile into home diectory")?; + fs::copy(realm.join("rootfs/home/user/.bashrc"), home.join(".bashrc"))?; + fs::copy(realm.join("rootfs/home/user/.profile"), home.join(".profile"))?; + + self.cmd(CHOWN, format!("-R 1000:1000 {}", home.display()))?; + + self.info("Creating default.realm symlink")?; + unixfs::symlink("realm-main", base.join("realms/default.realm"))?; + + Ok(()) + } + + fn setup_storage_resources(&self, base: &Path) -> Result<()> { + let channel = util::read_rootfs_channel()?; + let resources = base.join("resources").join(channel); + fs::create_dir_all(&resources)?; + + self.copy_artifact(EXTRA_IMAGE_NAME, &resources)?; + + let modules = self.modules_imagename(); + self.copy_artifact(&modules, &resources)?; + + Ok(()) + } + + fn modules_imagename(&self) -> String { + let utsname = util::uname(); + let v = utsname.release().split("-").collect::>(); + format!("citadel-modules-{}.img", v[0]) + } + + fn target_partition(&self, num: usize) -> String { + format!("{}{}", self.target_device, num) + } + + fn artifact_path(&self, filename: &str) -> PathBuf { + Path::new(&self.artifact_directory).join(filename) + } + + fn copy_artifact>(&self, filename: &str, target: P) -> Result<()> { + self.info(format!("Copying {} to {}", filename, target.as_ref().display()))?; + let src = self.artifact_path(filename); + let target = target.as_ref(); + if !target.exists() { + fs::create_dir_all(target)?; + } + let dst = target.join(filename); + fs::copy(src, dst)?; + Ok(()) + } + + fn header>(&self, s: S) -> Result<()> { + self.output(format!("\n[+] {}\n", s.as_ref())) + } + + fn info>(&self, s: S) -> Result<()> { + self.output(format!(" [>] {}", s.as_ref())) + } + + fn output>(&self, s: S) -> Result<()> { + println!("{}", s.as_ref()); + if let Some(ref file) = self.logfile { + writeln!(file.borrow_mut(), "{}", s.as_ref())?; + } + Ok(()) + } + + fn cmd>(&self, cmd_path: &str, args: S) -> Result<()> { + self.output(format!(" # {} {}", cmd_path, args.as_ref()))?; + let args: Vec<&str> = args.as_ref().split_whitespace().collect::>(); + let result = Command::new(cmd_path) + .args(args) + .output()?; + + if !result.status.success() { + match result.status.code() { + Some(code) => bail!("command {} failed with exit code: {}", cmd_path, code), + None => bail!("command {} failed with no exit code", cmd_path), + } + } + + for line in String::from_utf8_lossy(&result.stdout).lines() { + self.output(format!(" {}", line))?; + } + + for line in String::from_utf8_lossy(&result.stderr).lines() { + self.output(format!("! {}", line))?; + } + Ok(()) + } +} diff --git a/citadel-install/src/main.rs b/citadel-install/src/main.rs new file mode 100644 index 0000000..cc37ea3 --- /dev/null +++ b/citadel-install/src/main.rs @@ -0,0 +1,150 @@ +#[macro_use] extern crate failure; +extern crate libc; +extern crate rpassword; + +mod installer; +mod cli; +mod util; +mod disks; + +use std::result; +use std::path::Path; +use std::env; +use std::thread; +use std::time; +use std::fs; +use std::process::exit; +use failure::Error; + +pub type Result = result::Result; + +fn main() { + + let mut args = env::args(); + args.next(); + let result = match args.next() { + Some(ref s) if s == "live-setup" => live_setup(), + Some(ref s) if s == "copy-artifacts" => copy_artifacts(), + Some(ref s) => cli_install_to(s), + None => cli_install(), + }; + + if let Err(ref e) = result { + println!("Failed: {}", format_error(e)); + exit(1); + } +} + +pub fn format_error(err: &Error) -> String { + let mut output = err.to_string(); + let mut prev = err.as_fail(); + while let Some(next) = prev.cause() { + output.push_str(": "); + output.push_str(&next.to_string()); + prev = next; + } + output +} + +fn live_setup() -> Result<()> { + if !Path::new("/etc/initrd-release").exists() { + bail!("Not running in initramfs, cannot do live-setup"); + } + let installer = installer::Installer::new(); + installer.live_setup() +} + +fn copy_artifacts() -> Result<()> { + + for _ in 0..3 { + if try_copy_artifacts()? { + return Ok(()) + } + // Try again after waiting for more devices to be discovered + println!("Failed to find partition with images, trying again in 2 seconds"); + thread::sleep(time::Duration::from_secs(2)); + } + Err(format_err!("Could not find partition containing resource images")) +} + +fn try_copy_artifacts() -> Result { + let rootfs_image = Path::new("/boot/images/citadel-rootfs.img"); + // Already mounted? + if rootfs_image.exists() { + deploy_artifacts()?; + return Ok(true); + } + for part in disks::DiskPartition::boot_partitions()? { + part.mount("/boot")?; + + if rootfs_image.exists() { + deploy_artifacts()?; + part.umount()?; + return Ok(true); + } + part.umount()?; + } + Ok(false) +} + +fn deploy_artifacts() -> Result<()> { + let run_images = Path::new("/run/images"); + if !run_images.exists() { + fs::create_dir_all(run_images)?; + util::exec_cmdline("/bin/mount", "-t tmpfs -o size=4g images /run/images")?; + } + + for entry in fs::read_dir("/boot/images")? { + let entry = entry?; + println!("Copying {:?} from /boot/images to /run/images", entry.file_name()); + fs::copy(entry.path(), run_images.join(entry.file_name()))?; + } + println!("Copying bzImage to /run/images"); + fs::copy("/boot/bzImage", "/run/images/bzImage")?; + + println!("Copying bootx64.efi to /run/images"); + fs::copy("/boot/EFI/BOOT/bootx64.efi", "/run/images/bootx64.efi")?; + + deploy_syslinux_artifacts()?; + + Ok(()) +} + +fn deploy_syslinux_artifacts() -> Result<()> { + let boot_syslinux = Path::new("/boot/syslinux"); + + if !boot_syslinux.exists() { + println!("Not copying syslinux components because /boot/syslinux does not exist"); + return Ok(()); + } + + println!("Copying contents of /boot/syslinux to /run/images/syslinux"); + + let run_images_syslinux = Path::new("/run/images/syslinux"); + fs::create_dir_all(run_images_syslinux)?; + for entry in fs::read_dir(boot_syslinux)? { + let entry = entry?; + if let Some(ext) = entry.path().extension() { + if ext == "c32" || ext == "bin" { + fs::copy(entry.path(), run_images_syslinux.join(entry.file_name()))?; + } + } + } + Ok(()) +} + +fn cli_install() -> Result<()> { + let ok = cli::run_cli_install()?; + if !ok { + println!("Install cancelled..."); + } + Ok(()) +} + +fn cli_install_to(target: &str) -> Result<()> { + let ok = cli::run_cli_install_with(target)?; + if !ok { + println!("Install cancelled..."); + } + Ok(()) +} diff --git a/citadel-install/src/util.rs b/citadel-install/src/util.rs new file mode 100644 index 0000000..8a4e099 --- /dev/null +++ b/citadel-install/src/util.rs @@ -0,0 +1,157 @@ +use std::mem; +use std::ffi::CStr; +use std::str::from_utf8_unchecked; +use std::path::{Path,PathBuf}; +use std::process::{Command,ExitStatus,Stdio}; +use std::fs; + +use libc::{self, c_char}; +use failure::ResultExt; + +use super::Result; + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct UtsName(libc::utsname); + +#[allow(dead_code)] +impl UtsName { + pub fn sysname(&self) -> &str { + to_str(&(&self.0.sysname as *const c_char ) as *const *const c_char) + } + + pub fn nodename(&self) -> &str { + to_str(&(&self.0.nodename as *const c_char ) as *const *const c_char) + } + + pub fn release(&self) -> &str { + to_str(&(&self.0.release as *const c_char ) as *const *const c_char) + } + + pub fn version(&self) -> &str { + to_str(&(&self.0.version as *const c_char ) as *const *const c_char) + } + + pub fn machine(&self) -> &str { + to_str(&(&self.0.machine as *const c_char ) as *const *const c_char) + } +} + +pub fn uname() -> UtsName { + unsafe { + let mut ret: UtsName = mem::uninitialized(); + libc::uname(&mut ret.0); + ret + } +} + +#[inline] +fn to_str<'a>(s: *const *const c_char) -> &'a str { + unsafe { + let res = CStr::from_ptr(*s).to_bytes(); + from_utf8_unchecked(res) + } +} + +#[derive(Debug, Clone)] +pub struct Disk { + path: PathBuf, + size: usize, + size_str: String, + model: String, +} + +impl Disk { + pub fn probe_all() -> Result> { + let mut v = Vec::new(); + for entry in fs::read_dir("/sys/block")? { + let path = entry?.path(); + if Disk::is_disk_device(&path) { + let disk = Disk::read_device(&path)?; + v.push(disk); + } + } + Ok(v) + } + + fn is_disk_device(device: &Path) -> bool { + device.join("device/model").exists() + } + + fn read_device(device: &Path) -> Result { + let path = Path::new("/dev/").join(device.file_name().unwrap()); + + let size = fs::read_to_string(device.join("size"))? + .trim() + .parse::()?; + + let size_str = format!("{}G", size >> 21); + + let model = fs::read_to_string(device.join("device/model"))? + .trim() + .to_string(); + + Ok(Disk { path, size, size_str, model }) + + } + + pub fn path(&self) -> &Path { + &self.path + } + + pub fn size_str(&self) -> &str { + &self.size_str + } + + pub fn model(&self) -> &str { + &self.model + } + +} + +pub fn read_rootfs_channel() -> Result { + let s = fs::read_to_string("/etc/citadel-channel") + .context("Failed to open /etc/citadel-channel")?; + match s.split_whitespace().next() { + Some(s) => Ok(s.to_owned()), + None => Err(format_err!("Failed to parse /etc/citadel-channel contents")), + } +} + +pub fn exec_cmdline>(cmd_path: &str, args: S) -> Result<()> { + let args: Vec<&str> = args.as_ref().split_whitespace().collect::>(); + let status = Command::new(cmd_path) + .args(args) + .stderr(Stdio::inherit()) + .status()?; + + if !status.success() { + match status.code() { + Some(code) => bail!("command {} failed with exit code: {}", cmd_path, code), + None => bail!("command {} failed with no exit code", cmd_path), + } + } + Ok(()) +} + +pub fn exec_cmdline_with_output>(cmd_path: &str, args: S) -> Result { + let args: Vec<&str> = args.as_ref().split_whitespace().collect::>(); + let res = Command::new(cmd_path) + .args(args) + .stderr(Stdio::inherit()) + .output() + .context(format!("unable to execute {}", cmd_path))?; + + check_cmd_status(cmd_path, &res.status)?; + Ok(String::from_utf8(res.stdout).unwrap().trim().to_owned()) +} + +fn check_cmd_status(cmd_path: &str, status: &ExitStatus) -> Result<()> { + if !status.success() { + match status.code() { + Some(code) => bail!("command {} failed with exit code: {}", cmd_path, code), + None => bail!("command {} failed with no exit code", cmd_path), + } + } + Ok(()) +} diff --git a/citadel-mkimage/src/main.rs b/citadel-mkimage/src/main.rs deleted file mode 100644 index 472f27b..0000000 --- a/citadel-mkimage/src/main.rs +++ /dev/null @@ -1,69 +0,0 @@ -#[macro_use] extern crate libcitadel; -#[macro_use] extern crate failure; -#[macro_use] extern crate serde_derive; - -extern crate clap; -extern crate toml; - -use std::process::exit; - -use clap::{App,Arg,SubCommand,ArgMatches}; -use clap::AppSettings::*; -use failure::Error; - -use build::UpdateBuilder; -use config::BuildConfig; -use libcitadel::{Result,set_verbose}; - -mod build; -mod config; -mod util; - - - -fn main() { - let app = App::new("citadel-mkimage") - .about("Citadel update image builder") - .settings(&[ArgRequiredElseHelp,ColoredHelp, DisableHelpSubcommand, DisableVersion, DeriveDisplayOrder]) - - .subcommand(SubCommand::with_name("build") - .about("Build an update image specified by a configuration file") - .arg(Arg::with_name("build-file") - .required(true) - .help("Path to image build config file"))); - - set_verbose(true); - let matches = app.get_matches(); - - let result = match matches.subcommand() { - ("build", Some(m)) => build_image(m), - _ => Ok(()), - }; - - if let Err(ref e) = result { - println!("Error: {}", format_error(e)); - exit(1); - } -} - -fn format_error(err: &Error) -> String { - let mut output = err.to_string(); - let mut prev = err.as_fail(); - while let Some(next) = prev.cause() { - output.push_str(": "); - output.push_str(&next.to_string()); - prev = next; - } - output -} - - -fn build_image(arg_matches: &ArgMatches) -> Result<()> { - let build_file = arg_matches.value_of("build-file").unwrap(); - let config = BuildConfig::load(build_file)?; - let mut builder = UpdateBuilder::new(config)?; - builder.build()?; - Ok(()) - -} - diff --git a/citadel-mkimage/src/util.rs b/citadel-mkimage/src/util.rs deleted file mode 100644 index 9c5e260..0000000 --- a/citadel-mkimage/src/util.rs +++ /dev/null @@ -1,110 +0,0 @@ -use std::process::{Command,Stdio}; -use std::path::Path; -use std::collections::HashMap; - -use failure::ResultExt; -use libcitadel::Result; - - -pub fn sha256>(path: P) -> Result { - let output = exec_command_with_output("/usr/bin/sha256sum", &[pathstr(path.as_ref())]) - .context(format!("failed to calculate sha256 on {}", path.as_ref().display()))?; - - let v: Vec<&str> = output.split_whitespace().collect(); - Ok(v[0].trim().to_owned()) -} - -pub fn xz_compress>(path: P) -> Result<()> { - exec_command("/usr/bin/xz", &["-T0", pathstr(path.as_ref())]) - .context(format!("failed to compress {}", path.as_ref().display()))?; - Ok( ()) -} - -pub fn verity_initial_hashtree, Q: AsRef>(path: P, hashfile: Q) -> Result { - let output = exec_command_with_output("/usr/sbin/veritysetup", - &["format",pathstr(path.as_ref()), pathstr(hashfile.as_ref())]) - .context("veritysetup format command failed")?; - - Ok(VerityOutput::parse(&output)) -} - -fn pathstr(path: &Path) -> &str { - path.to_str().unwrap() -} - -fn exec_command(cmd_path: &str, args: &[&str]) -> Result<()> { - let status = Command::new(cmd_path) - .args(args) - .stderr(Stdio::inherit()) - .status() - .context(format!("unable to execute {}", cmd_path))?; - - if !status.success() { - match status.code() { - Some(code) => bail!("command {} failed with exit code: {}", cmd_path, code), - None => bail!("command {} failed with no exit code", cmd_path), - } - } - Ok(()) -} - -fn exec_command_with_output(cmd_path: &str, args: &[&str]) -> Result { - let res = Command::new(cmd_path) - .args(args) - .stderr(Stdio::inherit()) - .output() - .context(format!("unable to execute {}", cmd_path))?; - - if !res.status.success() { - match res.status.code() { - Some(code) => bail!("command {} failed with exit code: {}", cmd_path, code), - None => bail!("command {} failed with no exit code", cmd_path), - } - } - - Ok(String::from_utf8(res.stdout).unwrap().trim().to_owned()) -} - -/// The output from the `veritysetup format` command can be parsed as key/value -/// pairs. This class parses the output and stores it in a map for querying. -pub struct VerityOutput { - output: String, - map: HashMap, -} - -impl VerityOutput { - /// Parse the string `output` as standard output from the dm-verity - /// `veritysetup format` command. - fn parse(output: &str) -> VerityOutput { - let mut vo = VerityOutput { - output: output.to_owned(), - map: HashMap::new(), - }; - for line in output.lines() { - vo.parse_line(line); - } - vo - } - - fn parse_line(&mut self, line: &str) { - let v = line.split(':') - .map(|s| s.trim()) - .collect::>(); - - if v.len() == 2 { - self.map.insert(v[0].to_owned(), v[1].to_owned()); - } - } - - pub fn root_hash(&self) -> Option<&str> { - self.map.get("Root hash").map(|s| s.as_str()) - } - - pub fn salt(&self) -> Option<&str> { - self.map.get("Salt").map(|s| s.as_str()) - } - - pub fn output(&self) -> &str { - &self.output - } -} diff --git a/citadel-mount/Cargo.lock b/citadel-mount/Cargo.lock index 99f9e31..24094a5 100644 --- a/citadel-mount/Cargo.lock +++ b/citadel-mount/Cargo.lock @@ -1,23 +1,24 @@ [[package]] -name = "arrayref" -version = "0.3.5" +name = "autocfg" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "backtrace" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "backtrace-sys 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace-sys 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-demangle 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "backtrace-sys" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cc 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", @@ -29,25 +30,6 @@ name = "bitflags" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "block-buffer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "byte-tools" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "byteorder" -version = "1.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "cc" version = "1.0.26" @@ -67,62 +49,12 @@ dependencies = [ "libcitadel 0.1.0", ] -[[package]] -name = "clear_on_drop" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cc 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "cloudabi" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "curve25519-dalek" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)", - "clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", - "generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", - "subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "digest" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "ed25519-dalek" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "curve25519-dalek 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", - "failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "failure" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "backtrace 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)", "failure_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -133,37 +65,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.22 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)", "synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "fake-simd" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "fuchsia-zircon" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "generic-array" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "lazy_static" version = "1.2.0" @@ -178,17 +83,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" name = "libcitadel" version = "0.1.0" dependencies = [ - "ed25519-dalek 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", "nix 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ring 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)", - "sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", + "untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -220,93 +124,19 @@ dependencies = [ ] [[package]] -name = "rand" -version = "0.5.5" +name = "ring" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_chacha 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_pcg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_xorshift 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_chacha" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_core" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_core" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "rand_hc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_isaac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_pcg" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_xorshift" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "rustc-demangle" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -314,27 +144,6 @@ name = "rustc-serialize" version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "serde" version = "1.0.82" @@ -347,28 +156,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.22 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "sha2" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", - "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "subtle" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "syn" -version = "0.15.22" +version = "0.15.23" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", @@ -383,7 +176,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.22 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -396,13 +189,13 @@ dependencies = [ ] [[package]] -name = "typenum" -version = "1.10.0" +name = "unicode-xid" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "unicode-xid" -version = "0.1.0" +name = "untrusted" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -430,54 +223,29 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] -"checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee" -"checksum backtrace 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)" = "a2eff3830839471718ef8522b9025b399bfb713e25bc220da721364efb660d7d" -"checksum backtrace-sys 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)" = "c66d56ac8dabd07f6aacdaf633f4b8262f5b3601a810a0dcddffd5c22c69daa0" +"checksum autocfg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4e5f34df7a019573fb8bdc7e24a2bfebe51a2a1d6bfdbaeccedb3c41fc574727" +"checksum backtrace 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)" = "b5b493b66e03090ebc4343eb02f94ff944e0cbc9ac6571491d170ba026741eb5" +"checksum backtrace-sys 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)" = "3fcce89e5ad5c8949caa9434501f7b55415b3e7ad5270cb88c75a8d35e8f1279" "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" -"checksum block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a076c298b9ecdb530ed9d967e74a6027d6a7478924520acddcddc24c1c8ab3ab" -"checksum byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" -"checksum byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "94f88df23a25417badc922ab0f5716cc1330e87f71ddd9203b3a3ccd9cedf75d" "checksum cc 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)" = "389803e36973d242e7fecb092b2de44a3d35ac62524b3b9339e51d577d668e02" "checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4" -"checksum clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "97276801e127ffb46b66ce23f35cc96bd454fa311294bced4bbace7baa8b1d17" -"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -"checksum curve25519-dalek 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3eacf6ff1b911e3170a8c400b402e10c86dc3cb166bd69034ebbc2b785fea4c2" -"checksum digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "03b072242a8cbaf9c145665af9d250c59af3b958f83ed6824e13533cf76d5b90" -"checksum ed25519-dalek 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cd66d8a16ef71c23cf5eeb2140d8d3cd293457c6c7fd6804b593397a933fcf1e" "checksum failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6dd377bcc1b1b7ce911967e3ec24fa19c3224394ec05b54aa7b083d498341ac7" "checksum failure_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "64c2d913fe8ed3b6c6518eedf4538255b989945c14c2a7d5cbff62a5e2120596" -"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" -"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" -"checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d" "checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1" "checksum libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)" = "2d2857ec59fadc0773853c664d2d18e7198e83883e7060b63c924cb077bd5c74" "checksum nix 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "921f61dc817b379d0834e45d5ec45beaacfae97082090a49c2cf30dcbc30206f" "checksum proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)" = "77619697826f31a02ae974457af0b29b723e5619e113e9397b8b82c6bd253f09" "checksum quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "53fa22a1994bd0f9372d7a816207d8a2677ad0325b073f5c5332760f0fb62b5c" -"checksum rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e464cd887e869cddcae8792a4ee31d23c7edd516700695608f5b98c67ee0131c" -"checksum rand 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae9d223d52ae411a33cf7e54ec6034ec165df296ccd23533d671a28252b6f66a" -"checksum rand_chacha 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "771b009e3a508cb67e8823dda454aaa5368c7bc1c16829fb77d3e980440dd34a" -"checksum rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1961a422c4d189dfb50ffa9320bf1f2a9bd54ecb92792fb9477f99a1045f3372" -"checksum rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0905b6b7079ec73b314d4c748701f6931eb79fd97c668caa3f1899b22b32c6db" -"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" -"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" -"checksum rand_pcg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "086bd09a33c7044e56bb44d5bdde5a60e7f119a9e95b0775f545de759a32fe05" -"checksum rand_xorshift 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "effa3fcaa47e18db002bdde6060944b6d2f9cfd8db471c30e873448ad9187be3" -"checksum rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "bcfe5b13211b4d78e5c2cadfebd7769197d95c639c35a50057eb4c05de811395" +"checksum ring 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe642b9dd1ba0038d78c4a3999d1ee56178b4d415c1e1fbaba83b06dce012f0" +"checksum rustc-demangle 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "01b90379b8664dd83460d59bdc5dd1fd3172b8913788db483ed1325171eab2f7" "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" -"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" "checksum serde 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)" = "6fa52f19aee12441d5ad11c9a00459122bd8f98707cadf9778c540674f1935b6" "checksum serde_derive 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)" = "96a7f9496ac65a2db5929afa087b54f8fc5008dcfbe48a8874ed20049b0d6154" -"checksum sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9eb6be24e4c23a84d7184280d2722f7f2731fcdd4a9d886efbfe4413e4847ea0" -"checksum subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" -"checksum syn 0.15.22 (registry+https://github.com/rust-lang/crates.io-index)" = "ae8b29eb5210bc5cf63ed6149cbf9adfc82ac0be023d8735c176ee74a2db4da7" +"checksum syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)" = "9545a6a093a3f0bd59adb472700acc08cad3776f860f16a897dfce8c88721cbc" "checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015" "checksum toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" -"checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "55cd1f4b4e96b46aeb8d4855db4a7a9bd96eeeb5c6a1ab54593328761642ce2f" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" diff --git a/citadel-mount/Cargo.toml b/citadel-mount/Cargo.toml index b8e2cdf..d4eb9ed 100644 --- a/citadel-mount/Cargo.toml +++ b/citadel-mount/Cargo.toml @@ -6,5 +6,5 @@ homepage = "http://github.com/subgraph/citadel" [dependencies] libcitadel = { path = "../libcitadel" } -libc = "0.2" failure = "0.1.3" +libc = "0.2" diff --git a/citadel-mount/src/boot_select.rs b/citadel-mount/src/boot_select.rs index c7a3b3d..2eba546 100644 --- a/citadel-mount/src/boot_select.rs +++ b/citadel-mount/src/boot_select.rs @@ -6,24 +6,24 @@ pub struct BootSelection { } impl BootSelection { - pub fn choose_install_partition(config: &Config) -> Result { - let bs = BootSelection::load_partitions(config)?; + pub fn choose_install_partition() -> Result { + let bs = BootSelection::load_partitions()?; match bs._choose_install_partition() { Some(p) => Ok(p.clone()), None => bail!("no partition found for installation"), } } - pub fn choose_boot_partition(config: &Config) -> Result { - let bs = BootSelection::load_partitions(config)?; + pub fn choose_boot_partition() -> Result { + let bs = BootSelection::load_partitions()?; match bs._choose_boot_partition() { Some(p) => Ok(p.clone()), None => bail!("no partition found to boot from"), } } - fn load_partitions(config: &Config) -> Result { - let partitions = Partition::rootfs_partitions(config) + fn load_partitions() -> Result { + let partitions = Partition::rootfs_partitions() .map_err(|e| format_err!("Could not load rootfs partition info: {}", e))?; Ok(BootSelection { diff --git a/citadel-mount/src/main.rs b/citadel-mount/src/main.rs index 21d6873..94461b6 100644 --- a/citadel-mount/src/main.rs +++ b/citadel-mount/src/main.rs @@ -3,34 +3,37 @@ extern crate libc; -use std::env; use std::process::exit; +use std::env; + +use libcitadel::{Result,Config,CommandLine,set_verbose,format_error,ResourceImage}; -use libcitadel::{Result,Config,CommandLine,set_verbose,ResourceImage}; mod boot_select; mod rootfs; -mod uname; pub use boot_select::BootSelection; use rootfs::Rootfs; -/// mount command supports 3 subcommands +/// mount command supports 4 subcommands /// /// citadel-mount rootfs /// citadel-mount modules /// citadel-mount extra +/// citadel-mount copy-artifacts /// /// 'rootfs' creates the /dev/mapper/rootfs device which will be mounted as root filesystem /// /// 'modules' mounts a resource bundle containing kernel modules -/// 'extra' mounts a resource bundle +/// 'extra' mounts a resource bundle containing extra files /// +/// 'copy-artifacts' searches for a boot partition containing an /images +/// directory and copies all image files to /run/images. Also, it +/// copies bzImage and EFI/BOOT/bootx64.efi /// fn main() { - if CommandLine::verbose() { set_verbose(true); } @@ -52,8 +55,8 @@ fn main() { _ => Err(format_err!("Bad or missing argument")), }; - if let Err(e) = result { - warn!("Failed: {}", e); + if let Err(ref e) = result { + warn!("Failed: {}", format_error(e)); exit(1); } } @@ -66,17 +69,14 @@ fn mount_rootfs(config: Config) -> Result<()> { fn mount_modules(config: Config) -> Result<()> { info!("citadel-mount modules"); - let utsname = uname::uname(); - let v = utsname.release().split("-").collect::>(); - let name = format!("citadel-modules-{}", v[0]); - let mut image = ResourceImage::find(&name)?; + let mut image = ResourceImage::find("modules")?; image.mount(&config)?; Ok(()) } fn mount_extra(config: Config) -> Result<()> { info!("citadel-mount extra"); - let mut image = ResourceImage::find("citadel-extra")?; + let mut image = ResourceImage::find("extra")?; image.mount(&config)?; Ok(()) } diff --git a/citadel-mount/src/rootfs.rs b/citadel-mount/src/rootfs.rs index 677aba9..ce6e180 100644 --- a/citadel-mount/src/rootfs.rs +++ b/citadel-mount/src/rootfs.rs @@ -1,11 +1,10 @@ use std::process::Command; -use libcitadel::{BlockDev,Result,Partition,CommandLine,Config,ImageHeader,MetaInfo,PathExt}; -use BootSelection; -use ResourceImage; +use libcitadel::{BlockDev,CommandLine,Config,ImageHeader,Partition,Result,verity}; use std::path::Path; use std::process::Stdio; - +use BootSelection; +use ResourceImage; pub struct Rootfs { config: Config, @@ -17,20 +16,12 @@ impl Rootfs { } pub fn setup(&self) -> Result<()> { - if let Ok(partition) = BootSelection::choose_boot_partition(&self.config) { - match self.setup_partition(partition) { - Ok(()) => return Ok(()), - Err(err) => { - warn!("Failed to set up selected boot partition: {}", err); - // fall through - } - } + if CommandLine::install_mode() || CommandLine::live_mode() { + self.setup_rootfs_resource() + } else { + let partition = BootSelection::choose_boot_partition()?; + self.setup_partition(partition) } - self.setup_rootfs_resource() - } - - fn allow_resource(&self) -> bool { - CommandLine::install_mode() || CommandLine::recovery_mode() } fn setup_partition(&self, partition: Partition) -> Result<()> { @@ -42,36 +33,38 @@ impl Rootfs { } fn setup_rootfs_resource(&self) -> Result<()> { - if !self.allow_resource() { - info!("Will not search for rootfs resource image because command line flags do not permit it"); - return Ok(()) - } info!("Searching for rootfs resource image"); let img = ResourceImage::find_rootfs()?; - let hdr = ImageHeader::from_file(img.path())?; - let metainfo = hdr.verified_metainfo(&self.config)?; if CommandLine::noverity() { - self.setup_resource_unverified(&img, &metainfo) + self.setup_resource_unverified(&img) } else { - self.setup_resource_verified(&img, &hdr, &metainfo) + self.setup_resource_verified(&img) } } - fn setup_resource_unverified(&self, img: &ResourceImage, metainfo: &MetaInfo) -> Result<()> { - let loop_dev = img.path().setup_loop( - Some(ImageHeader::HEADER_SIZE), - Some(metainfo.nblocks() * 4096))?; - - self.setup_linear_mapping(&loop_dev) + fn setup_resource_unverified(&self, img: &ResourceImage) -> Result<()> { + if img.is_compressed() { + img.decompress()?; + } + let loopdev = img.create_loopdev()?; + info!("Loop device created: {}", loopdev.display()); + self.setup_linear_mapping(&loopdev) } - fn setup_resource_verified(&self, img: &ResourceImage, hdr: &ImageHeader, metainfo: &MetaInfo) -> Result<()> { - if !hdr.has_flag(ImageHeader::FLAG_HASH_TREE) { - img.generate_verity_hashtree(&hdr, &metainfo)?; + fn maybe_check_signature(&self, hdr: &ImageHeader) -> Result<()> { + if !CommandLine::nosignatures() { + let signature = hdr.signature(); + let metainfo = hdr.metainfo()?; + metainfo.verify(&self.config, &signature)?; } - img.path().verity_setup(ImageHeader::HEADER_SIZE, metainfo.nblocks(), metainfo.verity_root(), "rootfs") + Ok(()) + } + + fn setup_resource_verified(&self, img: &ResourceImage) -> Result<()> { + let _ = img.setup_verity_device(&self.config)?; + Ok(()) } fn setup_partition_unverified(&self, partition: &Partition) -> Result<()> { @@ -81,17 +74,14 @@ impl Rootfs { fn setup_partition_verified(&self, partition: &Partition) -> Result<()> { info!("Creating /dev/mapper/rootfs dm-verity device"); - let nblocks = partition.metainfo().nblocks(); - let roothash = partition.metainfo().verity_root(); - - partition.path().verity_setup(nblocks * 4096, nblocks, roothash, "rootfs") + self.maybe_check_signature(partition.header())?; + verity::setup_partition_device(partition)?; + Ok(()) } fn setup_linear_mapping(&self, blockdev: &Path) -> Result<()> { let dev = BlockDev::open_ro(blockdev)?; - let table = format!("0 {} linear {} 0", - dev.nsectors()?, - blockdev.pathstr()); + let table = format!("0 {} linear {} 0", dev.nsectors()?, blockdev.display()); info!("/usr/sbin/dmsetup create rootfs --table '{}'", table); diff --git a/citadel-mount/src/uname.rs b/citadel-mount/src/uname.rs deleted file mode 100644 index 1239a00..0000000 --- a/citadel-mount/src/uname.rs +++ /dev/null @@ -1,48 +0,0 @@ -use std::mem; -use libc::{self, c_char}; -use std::ffi::CStr; -use std::str::from_utf8_unchecked; - -#[repr(C)] -#[derive(Clone, Copy)] -pub struct UtsName(libc::utsname); - -#[allow(dead_code)] -impl UtsName { - pub fn sysname(&self) -> &str { - to_str(&(&self.0.sysname as *const c_char ) as *const *const c_char) - } - - pub fn nodename(&self) -> &str { - to_str(&(&self.0.nodename as *const c_char ) as *const *const c_char) - } - - pub fn release(&self) -> &str { - to_str(&(&self.0.release as *const c_char ) as *const *const c_char) - } - - pub fn version(&self) -> &str { - to_str(&(&self.0.version as *const c_char ) as *const *const c_char) - } - - pub fn machine(&self) -> &str { - to_str(&(&self.0.machine as *const c_char ) as *const *const c_char) - } -} - -pub fn uname() -> UtsName { - unsafe { - let mut ret: UtsName = mem::uninitialized(); - libc::uname(&mut ret.0); - ret - } -} - -#[inline] -fn to_str<'a>(s: *const *const c_char) -> &'a str { - unsafe { - let res = CStr::from_ptr(*s).to_bytes(); - from_utf8_unchecked(res) - } -} - diff --git a/libcitadel/Cargo.lock b/libcitadel/Cargo.lock index 887ea64..d8be89f 100644 --- a/libcitadel/Cargo.lock +++ b/libcitadel/Cargo.lock @@ -1,23 +1,24 @@ [[package]] -name = "arrayref" -version = "0.3.5" +name = "autocfg" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "backtrace" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "backtrace-sys 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace-sys 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-demangle 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "backtrace-sys" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cc 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", @@ -29,25 +30,6 @@ name = "bitflags" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "block-buffer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "byte-tools" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "byteorder" -version = "1.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "cc" version = "1.0.26" @@ -58,62 +40,12 @@ name = "cfg-if" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "clear_on_drop" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cc 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "cloudabi" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "curve25519-dalek" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)", - "clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", - "generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", - "subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "digest" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "ed25519-dalek" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "curve25519-dalek 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", - "failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "failure" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "backtrace 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)", "failure_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -124,37 +56,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.22 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)", "synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "fake-simd" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "fuchsia-zircon" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "generic-array" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "lazy_static" version = "1.2.0" @@ -169,17 +74,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" name = "libcitadel" version = "0.1.0" dependencies = [ - "ed25519-dalek 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", "nix 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ring 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)", - "sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", + "untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -211,93 +115,19 @@ dependencies = [ ] [[package]] -name = "rand" -version = "0.5.5" +name = "ring" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_chacha 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_pcg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_xorshift 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_chacha" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_core" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_core" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "rand_hc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_isaac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_pcg" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_xorshift" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "rustc-demangle" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -305,27 +135,6 @@ name = "rustc-serialize" version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "serde" version = "1.0.82" @@ -338,28 +147,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.22 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "sha2" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", - "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "subtle" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "syn" -version = "0.15.22" +version = "0.15.23" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", @@ -374,7 +167,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.22 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -387,13 +180,13 @@ dependencies = [ ] [[package]] -name = "typenum" -version = "1.10.0" +name = "unicode-xid" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "unicode-xid" -version = "0.1.0" +name = "untrusted" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -421,54 +214,29 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] -"checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee" -"checksum backtrace 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)" = "a2eff3830839471718ef8522b9025b399bfb713e25bc220da721364efb660d7d" -"checksum backtrace-sys 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)" = "c66d56ac8dabd07f6aacdaf633f4b8262f5b3601a810a0dcddffd5c22c69daa0" +"checksum autocfg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4e5f34df7a019573fb8bdc7e24a2bfebe51a2a1d6bfdbaeccedb3c41fc574727" +"checksum backtrace 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)" = "b5b493b66e03090ebc4343eb02f94ff944e0cbc9ac6571491d170ba026741eb5" +"checksum backtrace-sys 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)" = "3fcce89e5ad5c8949caa9434501f7b55415b3e7ad5270cb88c75a8d35e8f1279" "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" -"checksum block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a076c298b9ecdb530ed9d967e74a6027d6a7478924520acddcddc24c1c8ab3ab" -"checksum byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" -"checksum byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "94f88df23a25417badc922ab0f5716cc1330e87f71ddd9203b3a3ccd9cedf75d" "checksum cc 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)" = "389803e36973d242e7fecb092b2de44a3d35ac62524b3b9339e51d577d668e02" "checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4" -"checksum clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "97276801e127ffb46b66ce23f35cc96bd454fa311294bced4bbace7baa8b1d17" -"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -"checksum curve25519-dalek 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3eacf6ff1b911e3170a8c400b402e10c86dc3cb166bd69034ebbc2b785fea4c2" -"checksum digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "03b072242a8cbaf9c145665af9d250c59af3b958f83ed6824e13533cf76d5b90" -"checksum ed25519-dalek 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cd66d8a16ef71c23cf5eeb2140d8d3cd293457c6c7fd6804b593397a933fcf1e" "checksum failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6dd377bcc1b1b7ce911967e3ec24fa19c3224394ec05b54aa7b083d498341ac7" "checksum failure_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "64c2d913fe8ed3b6c6518eedf4538255b989945c14c2a7d5cbff62a5e2120596" -"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" -"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" -"checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d" "checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1" "checksum libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)" = "2d2857ec59fadc0773853c664d2d18e7198e83883e7060b63c924cb077bd5c74" "checksum nix 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "921f61dc817b379d0834e45d5ec45beaacfae97082090a49c2cf30dcbc30206f" "checksum proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)" = "77619697826f31a02ae974457af0b29b723e5619e113e9397b8b82c6bd253f09" "checksum quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "53fa22a1994bd0f9372d7a816207d8a2677ad0325b073f5c5332760f0fb62b5c" -"checksum rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e464cd887e869cddcae8792a4ee31d23c7edd516700695608f5b98c67ee0131c" -"checksum rand 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae9d223d52ae411a33cf7e54ec6034ec165df296ccd23533d671a28252b6f66a" -"checksum rand_chacha 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "771b009e3a508cb67e8823dda454aaa5368c7bc1c16829fb77d3e980440dd34a" -"checksum rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1961a422c4d189dfb50ffa9320bf1f2a9bd54ecb92792fb9477f99a1045f3372" -"checksum rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0905b6b7079ec73b314d4c748701f6931eb79fd97c668caa3f1899b22b32c6db" -"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" -"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" -"checksum rand_pcg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "086bd09a33c7044e56bb44d5bdde5a60e7f119a9e95b0775f545de759a32fe05" -"checksum rand_xorshift 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "effa3fcaa47e18db002bdde6060944b6d2f9cfd8db471c30e873448ad9187be3" -"checksum rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "bcfe5b13211b4d78e5c2cadfebd7769197d95c639c35a50057eb4c05de811395" +"checksum ring 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe642b9dd1ba0038d78c4a3999d1ee56178b4d415c1e1fbaba83b06dce012f0" +"checksum rustc-demangle 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "01b90379b8664dd83460d59bdc5dd1fd3172b8913788db483ed1325171eab2f7" "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" -"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" "checksum serde 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)" = "6fa52f19aee12441d5ad11c9a00459122bd8f98707cadf9778c540674f1935b6" "checksum serde_derive 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)" = "96a7f9496ac65a2db5929afa087b54f8fc5008dcfbe48a8874ed20049b0d6154" -"checksum sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9eb6be24e4c23a84d7184280d2722f7f2731fcdd4a9d886efbfe4413e4847ea0" -"checksum subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" -"checksum syn 0.15.22 (registry+https://github.com/rust-lang/crates.io-index)" = "ae8b29eb5210bc5cf63ed6149cbf9adfc82ac0be023d8735c176ee74a2db4da7" +"checksum syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)" = "9545a6a093a3f0bd59adb472700acc08cad3776f860f16a897dfce8c88721cbc" "checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015" "checksum toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" -"checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "55cd1f4b4e96b46aeb8d4855db4a7a9bd96eeeb5c6a1ab54593328761642ce2f" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" diff --git a/libcitadel/Cargo.toml b/libcitadel/Cargo.toml index 73ebab6..769f148 100644 --- a/libcitadel/Cargo.toml +++ b/libcitadel/Cargo.toml @@ -6,10 +6,9 @@ authors = ["Bruce Leidl "] [dependencies] libc = "0.2" nix = "0.12.0" +ring = "=0.13.2" +untrusted = "0.6.2" failure = "0.1.3" -ed25519-dalek = "0.8.1" -sha2 = "0.7.0" -rand = "0.6" toml = "0.4.10" serde = "1.0.82" serde_derive = "1.0.82" diff --git a/libcitadel/src/cmdline.rs b/libcitadel/src/cmdline.rs index 9af4209..de5aec8 100644 --- a/libcitadel/src/cmdline.rs +++ b/libcitadel/src/cmdline.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; -use std::path::Path; +use std::fs; -use {Result,PathExt}; +use Result; lazy_static! { static ref CMDLINE: CommandLine = match CommandLine::load() { @@ -41,11 +41,20 @@ impl CommandLine { CommandLine::var_exists("citadel.noverity") } + pub fn nosignatures() -> bool { + CommandLine::var_exists("citadel.nosignatures") + } + /// Return `true` if variable citadel.install is present on kernel command line. pub fn install_mode() -> bool { CommandLine::var_exists("citadel.install") } + /// Return `true` if variable citadel.live is present on kernel command line. + pub fn live_mode() -> bool { + CommandLine::var_exists("citadel.live") + } + /// Return `true` if variable citadel.recovery is present on kernel command line. pub fn recovery_mode() -> bool { CommandLine::var_exists("citadel.recovery") @@ -61,7 +70,7 @@ impl CommandLine { } fn load() -> Result { - let s = Path::new("/proc/cmdline").read_as_string()?; + let s = fs::read_to_string("/proc/cmdline")?; let varmap = CommandLineParser::new(s).parse(); Ok(CommandLine{varmap}) } diff --git a/libcitadel/src/config.rs b/libcitadel/src/config.rs index 677c71f..9e56e33 100644 --- a/libcitadel/src/config.rs +++ b/libcitadel/src/config.rs @@ -1,12 +1,12 @@ use std::path::{Path,PathBuf}; use std::collections::HashMap; +use std::fs; -use ed25519_dalek::{Signature,PublicKey,Keypair}; use rustc_serialize::hex::FromHex; -use sha2::Sha512; use toml; -use {Result,PathExt}; +use Result; +use keys::{KeyPair,PublicKey,Signature}; const DEFAULT_CONFIG_PATH: &str = "/usr/share/citadel/citadel-image.conf"; @@ -38,7 +38,7 @@ impl Config { } fn from_path(path: &Path) -> Result { - let s = path.read_as_string()?; + let s = fs::read_to_string(path)?; let mut config = toml::from_str::(&s)?; for (k,v) in config.channel.iter_mut() { v.name = k.to_string(); @@ -107,19 +107,19 @@ impl Channel { pub fn sign(&self, data: &[u8]) -> Result { let keybytes = match self.keypair { - Some(ref hex) => hex.from_hex()?, + Some(ref hex) => hex, None => bail!("No private signing key available for channel {}", self.name), }; - let privkey = Keypair::from_bytes(&keybytes)?; - let sig = privkey.sign::(data); + + let privkey = KeyPair::from_hex(keybytes)?; + let sig = privkey.sign(data)?; Ok(sig) } pub fn verify(&self, data: &[u8], sigbytes: &[u8]) -> Result<()> { let keybytes = self.pubkey.from_hex()?; let pubkey = PublicKey::from_bytes(&keybytes)?; - let sig = Signature::from_bytes(sigbytes)?; - pubkey.verify::(data, &sig)?; + pubkey.verify(data, sigbytes)?; Ok(()) } diff --git a/libcitadel/src/header.rs b/libcitadel/src/header.rs index 122a766..cc49635 100644 --- a/libcitadel/src/header.rs +++ b/libcitadel/src/header.rs @@ -1,14 +1,14 @@ use std::cell::RefCell; -use std::path::Path; use std::fs::File; -use std::io::{Write,Read}; +use std::io::{Read, Write}; +use std::path::Path; use failure::ResultExt; use toml; -use {Channel,Config,Result,BlockDev}; use blockdev::AlignedBuffer; +use {BlockDev, Channel, Config, Result}; /// Expected magic value in header const MAGIC: &[u8] = b"SGOS"; @@ -19,7 +19,7 @@ const METAINFO_OFFSET: usize = 8; /// Signature is 64 bytes long const SIGNATURE_LENGTH: usize = 64; -/// Maximum amount of space in block for metainfo document +/// Maximum amount of space in block for metainfo document const MAX_METAINFO_LEN: usize = (ImageHeader::HEADER_SIZE - (METAINFO_OFFSET + SIGNATURE_LENGTH)); fn is_valid_status_code(code: u8) -> bool { @@ -61,26 +61,32 @@ fn is_valid_status_code(code: u8) -> bool { #[derive(Clone)] pub struct ImageHeader(RefCell>); -const CODE_TO_LABEL: [&str; 7] = ["Invalid", "New", "Try Boot", "Good", "Failed Boot", "Bad Signature", "Bad Metainfo"]; +const CODE_TO_LABEL: [&str; 7] = [ + "Invalid", + "New", + "Try Boot", + "Good", + "Failed Boot", + "Bad Signature", + "Bad Metainfo", +]; impl ImageHeader { + pub const FLAG_PREFER_BOOT: u8 = 0x01; // Set to override usual strategy for choosing a partition to boot and force this one. + pub const FLAG_HASH_TREE: u8 = 0x02; // dm-verity hash tree data is appended to the image + pub const FLAG_DATA_COMPRESSED: u8 = 0x04; // The image data is compressed and needs to be uncompressed before use. - pub const FLAG_PREFER_BOOT : u8 = 0x01; // Set to override usual strategy for choosing a partition to boot and force this one. - pub const FLAG_HASH_TREE : u8 = 0x02; // dm-verity hash tree data is appended to the image - pub const FLAG_DATA_COMPRESSED : u8 = 0x04; // The image data is compressed and needs to be uncompressed before use. - - pub const STATUS_INVALID : u8 = 0; // Set on partition before writing a new rootfs disk image - pub const STATUS_NEW : u8 = 1; // Set on partition after write of new rootfs disk image completes successfully - pub const STATUS_TRY_BOOT : u8 = 2; // Set on boot selected partition if in `STATUS_NEW` state. - pub const STATUS_GOOD : u8 = 3; // Set on boot when a `STATUS_TRY_BOOT` partition successfully launches desktop - pub const STATUS_FAILED : u8 = 4; // Set on boot for any partition in state `STATUS_TRY_BOOT` - pub const STATUS_BAD_SIG : u8 = 5; // Set on boot selected partition when signature fails to verify - pub const STATUS_BAD_META : u8 = 6; // Set on partition when metainfo cannot be parsed + pub const STATUS_INVALID: u8 = 0; // Set on partition before writing a new rootfs disk image + pub const STATUS_NEW: u8 = 1; // Set on partition after write of new rootfs disk image completes successfully + pub const STATUS_TRY_BOOT: u8 = 2; // Set on boot selected partition if in `STATUS_NEW` state. + pub const STATUS_GOOD: u8 = 3; // Set on boot when a `STATUS_TRY_BOOT` partition successfully launches desktop + pub const STATUS_FAILED: u8 = 4; // Set on boot for any partition in state `STATUS_TRY_BOOT` + pub const STATUS_BAD_SIG: u8 = 5; // Set on boot selected partition when signature fails to verify + pub const STATUS_BAD_META: u8 = 6; // Set on partition when metainfo cannot be parsed /// Size of header block pub const HEADER_SIZE: usize = 4096; - pub fn new() -> ImageHeader { let v = vec![0u8; ImageHeader::HEADER_SIZE]; let header = ImageHeader(RefCell::new(v)); @@ -89,12 +95,9 @@ impl ImageHeader { } pub fn from_file>(path: P) -> Result { - //let mut v = vec![0u8; ImageHeader::HEADER_SIZE]; // XXX check file size is at least HEADER_SIZE let mut f = File::open(path.as_ref())?; ImageHeader::from_reader(&mut f) - //f.read_exact(&mut v)?; - //Ok(ImageHeader(RefCell::new(v))) } pub fn from_reader(r: &mut R) -> Result { @@ -106,7 +109,12 @@ impl ImageHeader { pub fn from_partition>(path: P) -> Result { let mut dev = BlockDev::open_ro(path.as_ref())?; let nsectors = dev.nsectors()?; - ensure!(nsectors >= 8, "{} is a block device bit it's too short ({} sectors)", path.as_ref().display(), nsectors); + ensure!( + nsectors >= 8, + "{} is a block device bit it's too short ({} sectors)", + path.as_ref().display(), + nsectors + ); let mut buffer = AlignedBuffer::new(ImageHeader::HEADER_SIZE); dev.read_sectors(nsectors - 8, buffer.as_mut())?; let header = ImageHeader(RefCell::new(buffer.as_ref().into())); @@ -116,13 +124,18 @@ impl ImageHeader { pub fn write_partition>(&self, path: P) -> Result<()> { let mut dev = BlockDev::open_rw(path.as_ref())?; let nsectors = dev.nsectors()?; - ensure!(nsectors >= 8, "{} is a block device bit it's too short ({} sectors)", path.as_ref().display(), nsectors); + ensure!( + nsectors >= 8, + "{} is a block device bit it's too short ({} sectors)", + path.as_ref().display(), + nsectors + ); let buffer = AlignedBuffer::from_slice(&self.0.borrow()); dev.write_sectors(nsectors - 8, buffer.as_ref())?; Ok(()) } - pub fn verified_metainfo(&self, config: &Config) -> Result { + pub fn metainfo(&self) -> Result { let mlen = self.metainfo_len(); if mlen == 0 || mlen > MAX_METAINFO_LEN { bail!("Invalid metainfo-len field: {}", mlen); @@ -130,7 +143,6 @@ impl ImageHeader { let mbytes = self.metainfo_bytes(); let mut metainfo = MetaInfo::new(mbytes); metainfo.parse_toml()?; - metainfo.verify(config, &self.signature())?; Ok(metainfo) } @@ -156,7 +168,9 @@ impl ImageHeader { } } - pub fn flags(&self) -> u8 { self.read_u8(5) } + pub fn flags(&self) -> u8 { + self.read_u8(5) + } pub fn has_flag(&self, flag: u8) -> bool { (self.flags() & flag) == flag @@ -206,8 +220,20 @@ impl ImageHeader { pub fn sign_metainfo(&self, channel: &Channel) -> Result<()> { let mlen = self.metainfo_len(); // XXX assert mlen is good - let sig = channel.sign(&self.0.borrow()[8..8+mlen])?; - self.write_bytes(8+mlen, &sig.to_bytes()); + let sig = channel.sign(&self.0.borrow()[8..8 + mlen])?; + self.write_bytes(8 + mlen, sig.to_bytes()); + Ok(()) + } + + pub fn verify_signature(&self, config: &Config) -> Result<()> { + let metainfo = self.metainfo()?; + let channel = match config.channel(metainfo.channel()) { + Some(channel) => channel, + None => bail!("Cannot verify signature for channel '{}' because it does not exist in configuration file", metainfo.channel()), + }; + channel + .verify(metainfo.bytes(), &self.signature()) + .context("failed to verify header signature")?; Ok(()) } @@ -245,59 +271,57 @@ impl ImageHeader { } fn write_bytes(&self, offset: usize, data: &[u8]) { - self.0.borrow_mut()[offset..offset+data.len()].copy_from_slice(data) + self.0.borrow_mut()[offset..offset + data.len()].copy_from_slice(data) } fn read_bytes(&self, offset: usize, len: usize) -> Vec { - Vec::from(&self.0.borrow()[offset..offset+len]) + Vec::from(&self.0.borrow()[offset..offset + len]) } } - #[derive(Clone)] pub struct MetaInfo { bytes: Vec, is_parsed: bool, - is_valid: bool, - toml: Option, + toml: Option, } - -#[derive(Deserialize,Serialize,Clone)] +#[derive(Deserialize, Serialize, Clone)] struct MetaInfoToml { + #[serde(rename = "image-type")] + image_type: String, channel: String, version: u32, - #[serde(rename="base-version")] + #[serde(rename = "base-version")] base_version: Option, date: Option, gitrev: Option, nblocks: u32, shasum: String, - #[serde(rename="verity-salt")] + #[serde(rename = "verity-salt")] verity_salt: String, - #[serde(rename="verity-root")] + #[serde(rename = "verity-root")] verity_root: String, } impl MetaInfo { fn new(bytes: Vec) -> MetaInfo { MetaInfo { - bytes, + bytes, is_parsed: false, - is_valid: false, toml: None, } } - pub fn is_valid(&self) -> bool { - self.is_valid + fn bytes(&self) -> &[u8] { + &self.bytes } pub fn parse_toml(&mut self) -> Result<()> { if !self.is_parsed { self.is_parsed = true; - let toml = toml::from_slice::(&self.bytes) - .context("parsing header metainfo")?; + let toml = + toml::from_slice::(&self.bytes).context("parsing header metainfo")?; self.toml = Some(toml); } Ok(()) @@ -308,17 +332,21 @@ impl MetaInfo { Some(channel) => channel, None => bail!("Channel '{}' not found in config file", self.channel()), }; - channel.verify(&self.bytes, signature) + channel + .verify(&self.bytes, signature) .context("Bad metainfo signature in header")?; - + Ok(()) } fn toml(&self) -> &MetaInfoToml { - assert!(self.is_valid); self.toml.as_ref().unwrap() } + pub fn image_type(&self) -> &str { + self.toml().image_type.as_str() + } + pub fn channel(&self) -> &str { self.toml().channel.as_str() } diff --git a/libcitadel/src/keys.rs b/libcitadel/src/keys.rs index 635d94f..7757803 100644 --- a/libcitadel/src/keys.rs +++ b/libcitadel/src/keys.rs @@ -1,111 +1,85 @@ use Result; -use rand::rngs::OsRng; -use sha2::Sha512; -use ed25519_dalek::{self,PublicKey,Keypair,Signature}; -use rustc_serialize::hex::{ToHex,FromHex}; +use ring::rand; +use ring::signature::{self,Ed25519KeyPair,ED25519_PUBLIC_KEY_LEN,ED25519_PKCS8_V2_LEN}; +use untrusted::Input; +use rustc_serialize::hex::{FromHex,ToHex}; -pub const SIGNATURE_LENGTH: usize = ed25519_dalek::SIGNATURE_LENGTH; /// /// Keys for signing or verifying signatures. Small convenience -/// wrapper around `ed25519_dalek`. +/// wrapper around `ring/ed25519`. +/// /// -pub enum SigningKeys { - KEYPAIR(Keypair), - PUBLIC(PublicKey), -} -use self::SigningKeys::*; +pub struct PublicKey([u8; ED25519_PUBLIC_KEY_LEN]); +pub struct KeyPair([u8; ED25519_PKCS8_V2_LEN]); +pub struct Signature(signature::Signature); -impl SigningKeys { - - /// Generate a new pair of signing/verifying keys using - /// the system random number generator. The resulting - /// `ed25519_dalek::KeyPair` can be extracted in an ascii - /// hex encoded format for storage in configuration files - /// with the `to_hex()` method. - pub fn generate() -> Result { - let mut rng = OsRng::new()?; - let pair = Keypair::generate::(&mut rng); - Ok(SigningKeys::KEYPAIR(pair)) +impl PublicKey { + pub fn from_bytes(bytes: &[u8]) -> Result { + let mut key = [0u8; ED25519_PUBLIC_KEY_LEN]; + key.copy_from_slice(bytes); + Ok(PublicKey(key)) } - - /// Load a `Keypair` from ascii hex representation. - /// - /// The `hex` string is read from a configuration file - /// and is used here to construct a `SigningKeys` instance - /// which can then be used for signing (or verifying). - pub fn from_keypair_hex(hex: String) -> Result { - let bytes = hex.from_hex()?; - let pair = Keypair::from_bytes(&bytes)?; - Ok(SigningKeys::KEYPAIR(pair)) - } - - /// Load only `PublicKey` from ascii hex representation - /// - /// The string `hex` is read from a configuration file - /// and is used here to construct a `SigningKeys` instance - /// which can only be used for verifying signatures (not - /// for signing). - pub fn from_public_hex(hex: String) -> Result { - let bytes = hex.from_hex()?; - let public = PublicKey::from_bytes(&bytes)?; - Ok(SigningKeys::PUBLIC(public)) - } - - /// Return ascii hex representation of internal `Keypair` - /// or `PublicKey` depending on which variant `self` is. - /// - /// Caller is expected to know which variant is being - /// converted. - pub fn to_hex(&self) -> String { - match *self { - KEYPAIR(ref pair) => pair.to_bytes().to_hex(), - PUBLIC(ref public) => public.to_bytes().to_hex(), - } - } - - /// Return ascii hex representation of the `PublicKey` associated - /// with this instance. - pub fn to_public_hex(&self) -> String { - match *self { - KEYPAIR(ref pair) => pair.public.to_bytes().to_hex(), - PUBLIC(ref public) => public.to_bytes().to_hex(), - } - } - - /// Sign `data` with the private key associated with this instance - /// using `Sha512` as the hashing algorithm. Caller must ensure - /// that this instance is a `KEYPAIR` variant. - /// - /// Returns signature of `data` encoded as a `SIGNATURE_LENGTH` - /// byte array (64 bytes). - /// - pub fn sign(&self, data: &[u8]) -> [u8; SIGNATURE_LENGTH] { - let signature = match *self { - KEYPAIR(ref pair) => pair.sign::(data), - _ => panic!("Not a keypair, no signing key"), - }; - signature.to_bytes() - } - - /// Verify that `signature` is a valid signature for `data` using the - /// `PublicKey` associated with this instance. `signature` must be - /// a slice of `SIGNATURE_LENGTH` bytes. - /// - /// Returns `Ok(())` if signature is valid. - /// pub fn verify(&self, data: &[u8], signature: &[u8]) -> Result<()> { - assert_eq!(signature.len(), SIGNATURE_LENGTH, "Signature bytes are not expected length"); - let signature = Signature::from_bytes(signature)?; - self.pubkey().verify::(data, &signature)?; + let signature = Input::from(signature); + let data = Input::from(data); + let pubkey = Input::from(&self.0); + signature::verify(&signature::ED25519, pubkey, data, signature)?; Ok(()) } +} - fn pubkey(&self) -> &PublicKey { - match *self { - KEYPAIR(ref keypair) => &keypair.public, - PUBLIC(ref public) => &public, - } +impl KeyPair { + /// Generate a new pair of signing/verifying keys using + /// the system random number generator. The resulting + /// `Ed25519KeyPair` can be extracted in an ascii + /// hex encoded pkcs#8 format for storage in configuration files + /// with the `to_hex()` method. + pub fn generate() -> Result { + let rng = rand::SystemRandom::new(); + let bytes = Ed25519KeyPair::generate_pkcs8(&rng)?; + KeyPair::from_bytes(&bytes) + + } + + pub fn from_hex(hex: &str) -> Result { + KeyPair::from_bytes(&hex.from_hex()?) + } + + fn from_bytes(bytes: &[u8]) -> Result { + let mut pair = [0u8; ED25519_PKCS8_V2_LEN]; + pair.copy_from_slice(bytes); + Ok(KeyPair(pair)) + } + + pub fn public_key_bytes(&self) -> Vec { + let pair = Ed25519KeyPair::from_pkcs8(Input::from(&self.0)).expect("failed to parse pkcs8 key"); + pair.public_key_bytes().to_vec() + } + + pub fn private_key_bytes(&self) -> Vec { + self.0.to_vec() + } + + pub fn private_key_hex(&self) -> String { + self.0.to_hex() + } + pub fn public_key_hex(&self) -> String { + let pair = Ed25519KeyPair::from_pkcs8(Input::from(&self.0)).expect("failed to parse pkcs8 key"); + pair.public_key_bytes().to_hex() + } + + pub fn sign(&self, data: &[u8]) -> Result { + let pair = Ed25519KeyPair::from_pkcs8(Input::from(&self.0))?; + let signature = pair.sign(data); + Ok(Signature(signature)) } } + +impl Signature { + pub fn to_bytes(&self) -> &[u8] { + self.0.as_ref() + } +} + diff --git a/libcitadel/src/lib.rs b/libcitadel/src/lib.rs index dde3b1c..ed8756a 100644 --- a/libcitadel/src/lib.rs +++ b/libcitadel/src/lib.rs @@ -23,9 +23,8 @@ macro_rules! notify { extern crate libc; extern crate serde; extern crate toml; -extern crate ed25519_dalek; -extern crate sha2; -extern crate rand; +extern crate ring; +extern crate untrusted; extern crate rustc_serialize; use std::cell::RefCell; @@ -45,25 +44,38 @@ pub fn set_verbose(val: bool) { VERBOSE.with(|f| { *f.borrow_mut() = val }); } +pub fn format_error(err: &Error) -> String { + let mut output = err.to_string(); + let mut prev = err.as_fail(); + while let Some(next) = prev.cause() { + output.push_str(": "); + output.push_str(&next.to_string()); + prev = next; + } + output +} + mod blockdev; mod config; mod keys; -mod disks; mod cmdline; mod header; mod partition; mod resource; -mod path_ext; +pub mod util; +pub mod verity; +mod mount; pub use config::Config; pub use config::Channel; pub use blockdev::BlockDev; -pub use keys::SigningKeys; pub use cmdline::CommandLine; pub use header::{ImageHeader,MetaInfo}; -pub use path_ext::{PathExt,FileTypeResult,VerityOutput}; pub use partition::Partition; pub use resource::ResourceImage; +pub use keys::KeyPair; +pub use mount::Mount; + pub type Result = result::Result; diff --git a/libcitadel/src/mount.rs b/libcitadel/src/mount.rs new file mode 100644 index 0000000..d9defc5 --- /dev/null +++ b/libcitadel/src/mount.rs @@ -0,0 +1,59 @@ + +use std::path::{PathBuf,Path}; +use std::fs; +use Result; + +pub struct Mount { + source: String, + target: PathBuf, + fstype: String, + options: String, +} + +impl Mount { + /// + /// Returns `true` if `path` matches the source field (first field) + /// of any of the mount lines listed in /proc/mounts + /// + pub fn is_path_mounted>(path: P) -> Result { + let path_str = path.as_ref().to_string_lossy(); + let mounts = Mount::all_mounts()?; + Ok(mounts.into_iter().any(|m| m.source == path_str)) + } + + pub fn all_mounts() -> Result> { + let s = fs::read_to_string("/proc/mounts")?; + Ok(s.lines().flat_map(Mount::parse_mount_line).collect()) + } + + fn parse_mount_line(line: &str) -> Option { + let parts = line.split_whitespace().collect::>(); + if parts.len() < 4 { + warn!("Failed to parse mount line: {}", line); + return None; + } + Some(Mount{ + source: parts[0].to_string(), + target: PathBuf::from(parts[1]), + fstype: parts[2].to_string(), + options: parts[3].to_string(), + }) + } + + pub fn source(&self) -> &str { + &self.source + } + + pub fn target(&self) -> &Path { + &self.target + } + + pub fn fstype(&self) -> &str { + &self.fstype + } + + pub fn options(&self) -> &str { + &self.options + } +} + diff --git a/libcitadel/src/partition.rs b/libcitadel/src/partition.rs index 282cf03..581d142 100644 --- a/libcitadel/src/partition.rs +++ b/libcitadel/src/partition.rs @@ -1,6 +1,6 @@ use std::path::{Path,PathBuf}; use std::fs; -use {Config,Result,ImageHeader,MetaInfo,PathExt}; +use {Config,CommandLine,Result,ImageHeader,MetaInfo,Mount}; #[derive(Clone)] pub struct Partition { @@ -16,27 +16,27 @@ struct HeaderInfo { } impl Partition { - pub fn rootfs_partitions(config: &Config) -> Result> { + pub fn rootfs_partitions() -> Result> { let mut v = Vec::new(); for path in rootfs_partition_paths()? { - let partition = Partition::load(&path, config)?; + let partition = Partition::load(&path)?; v.push(partition); } Ok(v) } - fn load(dev: &Path, config: &Config) -> Result { - let is_mounted = is_path_mounted(dev)?; - let header = Partition::load_header(dev, config)?; + fn load(dev: &Path) -> Result { + let is_mounted = is_in_use(dev)?; + let header = Partition::load_header(dev)?; Ok(Partition::new(dev, header, is_mounted)) } - fn load_header(dev: &Path, config: &Config) -> Result> { + fn load_header(dev: &Path) -> Result> { let header = ImageHeader::from_partition(dev)?; if !header.is_magic_valid() { return Ok(None); } - let metainfo = match header.verified_metainfo(config) { + let metainfo = match header.metainfo() { Ok(metainfo) => metainfo, Err(e) => { warn!("Reading partition {}: {}", dev.display(), e); @@ -116,11 +116,48 @@ impl Partition { if self.header().status() == ImageHeader::STATUS_TRY_BOOT { self.write_status(ImageHeader::STATUS_FAILED)?; } - // XXX verify signature + + if !CommandLine::nosignatures() { + if let Err(e) = self.header().verify_signature(config) { + warn!("Signature verification failed on partition: {}", e); + self.write_status(ImageHeader::STATUS_BAD_SIG)?; + } + } Ok(()) } } +fn is_in_use(path: &Path) -> Result { + if Mount::is_path_mounted(path)? { + return Ok(true); + } + let holders = count_block_holders(path)?; + Ok(holders > 0) +} + +// +// Resolve /dev/mapper/citadel-rootfsX symlink to actual device name +// and then inspect directory /sys/block/${DEV}/holders and return +// the number of entries this directory contains. If this directory +// is not empty then device belongs to another device mapping. +// +fn count_block_holders(path: &Path) -> Result { + if !path.exists() { + bail!("Path to rootfs device does not exist: {}", path.display()); + } + let resolved = fs::canonicalize(path)?; + let fname = match resolved.file_name() { + Some(s) => s, + None => bail!("path does not have filename?"), + }; + let holders_dir = + Path::new("/sys/block") + .join(fname) + .join("holders"); + let count = fs::read_dir(holders_dir)?.count(); + Ok(count) +} + fn rootfs_partition_paths() -> Result> { let mut rootfs_paths = Vec::new(); for dent in fs::read_dir("/dev/mapper")? { @@ -145,19 +182,3 @@ fn path_filename(path: &Path) -> &str { "" } -/// -/// Returns `true` if `path` matches the source field (first field) -/// of any of the mount lines listed in /proc/mounts -/// -pub fn is_path_mounted(path: &Path) -> Result { - let path_str = path.to_str().unwrap(); - - for line in Path::new("/proc/mounts").read_as_lines()? { - if let Some(s) = line.split_whitespace().next() { - if s == path_str { - return Ok(true); - } - } - } - Ok(false) -} diff --git a/libcitadel/src/path_ext.rs b/libcitadel/src/path_ext.rs deleted file mode 100644 index 8c17346..0000000 --- a/libcitadel/src/path_ext.rs +++ /dev/null @@ -1,354 +0,0 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::{BufReader,BufRead,Read}; -use std::path::{Path,PathBuf}; -use std::process::{Command,Stdio}; - -use failure::ResultExt; - -use Result; - -/// A collection of utility methods added to `Path` to perform various types of operations -/// on files and directories. -pub trait PathExt { - - /// Run sha256sum command on file `self` and return output as a hex `String` - fn sha256(&self) -> Result; - - /// Write file `self` to partition device with dd command. - fn copy_to_partition>(&self, partition: P) -> Result<()>; - - /// Read entire file `self` and return contents as a `String` - fn read_as_string(&self) -> Result; - - /// Read entire file `self` and return contents as a `Vec` of individual lines. - fn read_as_lines(&self) -> Result>; - - /// Return `true` if path `self` is mounted. - fn is_mounted(&self) -> bool; - - /// Compress file `self` with xz utility. - fn xz_compress(&self) -> Result<()>; - - /// Uncompress file `self` with xz utility. - fn xz_uncompress(&self) -> Result<()>; - - /// Run /usr/bin/file command on file `self` and return output as `FileTypeResult` - fn file_type(&self) -> Result; - - /// Mount path `self` to `target` - fn mount>(&self, target: P) -> Result<()>; - - /// Mount path `self` to `target` with additional argument `args` to mount command. - fn mount_with_args>(&self, target: P, args: &str) -> Result<()>; - - /// Bind mount path `self` to path `target`. - fn bind_mount>(&self, target: P) -> Result<()>; - - /// Set up loop device for file `self` with optional offset and size limit. - /// Returns `PathBuf` to associated loop device upon success. - fn setup_loop(&self, offset: Option, sizelimit: Option) -> Result; - - /// Unmount path `self` - fn umount(&self) -> Result<()>; - - /// Return Partition Type GUID for a block device by running lsblk command - fn partition_type_guid(&self) -> Result; - - /// Generate dm-verity hashtree for a disk image and store in an external file - /// Parse output from command into VerityOutput structure and return it. - fn verity_initial_hashtree>(&self, hashfile: P) -> Result; - - /// Generate dm-verity hashtree with a given salt value and append it to the same image. - /// - /// device - /// Parse output from command into VerityOutput structure and return it. - fn verity_regenerate_hashtree(&self, offset: usize, nblocks: usize, salt: &str) -> Result; - - /// - fn verity_setup(&self, offset: usize, nblocks: usize, roothash: &str, devname: &str) -> Result<()>; - - - - /// Return path as a string without error checking - fn pathstr(&self) -> &str; -} - -impl PathExt for Path { - fn sha256(&self) -> Result { - let output = exec_command_with_output("/usr/bin/sha256sum", &[self.pathstr()]) - .context(format!("failed to calculate sha256 on {}", self.display()))?; - - let v: Vec<&str> = output.split_whitespace().collect(); - Ok(v[0].trim().to_owned()) - } - - fn copy_to_partition>(&self, partition: P) -> Result<()> { - let if_arg = format!("if={}", self.pathstr()); - let of_arg = format!("of={}", partition.as_ref().pathstr()); - exec_command("/usr/bin/dd", &[ if_arg.as_str(), of_arg.as_str(), "bs=4M" ]) - .context(format!("failed to copy {} to {} with dd", self.display(), partition.as_ref().display()))?; - Ok(()) - } - - fn read_as_string(&self) -> Result { - let mut f = File::open(&self)?; - let mut buffer = String::new(); - f.read_to_string(&mut buffer)?; - Ok(buffer) - } - - fn read_as_lines(&self) -> Result> { - let mut v = Vec::new(); - let f = File::open(&self)?; - let reader = BufReader::new(f); - for line in reader.lines() { - let line = line?; - v.push(line); - } - Ok(v) - } - - fn is_mounted(&self) -> bool { - exec_command("/usr/bin/findmnt", &[self.pathstr()]).is_ok() - } - - fn xz_compress(&self) -> Result<()> { - exec_command("/usr/bin/xz", &["-T0", self.pathstr()]) - .context(format!("failed to compress {}", self.display()))?; - Ok(()) - } - - fn xz_uncompress(&self) -> Result<()> { - exec_command("/usr/bin/xz", &["-d", self.pathstr()]) - .context(format!("failed to uncompress {}", self.display()))?; - Ok(()) - } - - fn file_type(&self) -> Result { - let output = exec_command_with_output("/usr/bin/file", &["-b", self.pathstr()]) - .context(format!("failed to run /usr/bin/file on {}", self.display()))?; - - Ok(FileTypeResult(output)) - } - - fn mount>(&self, target: P) -> Result<()> { - let target = target.as_ref().to_str().unwrap(); - exec_command("/usr/bin/mount", &[self.pathstr(), target]) - .context(format!("failed to mount {}", self.display()))?; - Ok(()) - } - - fn mount_with_args>(&self, target: P, args: &str) -> Result<()> { - let target = target.as_ref().to_str().unwrap(); - exec_command("/usr/bin/mount", &[args, self.pathstr(), target]) - .context(format!("failed to mount {} with args [{}]", self.display(), args))?; - Ok(()) - } - - fn bind_mount>(&self, target: P) -> Result<()> { - let target = target.as_ref().to_str().unwrap(); - exec_command("/usr/bin/mount", &["--bind", self.pathstr(), target]) - .context(format!("failed to bind mount {} to {}", self.display(), target))?; - Ok(()) - } - - fn setup_loop(&self, offset: Option, sizelimit: Option) -> Result { - let offset_str: String; - let sizelimit_str: String; - - let mut v = Vec::new(); - - if let Some(val) = offset { - v.push("--offset"); - offset_str = val.to_string(); - v.push(&offset_str); - } - - if let Some(val) = sizelimit { - v.push("--sizelimit"); - sizelimit_str = val.to_string(); - v.push(&sizelimit_str); - } - - v.push("-f"); - v.push(self.pathstr()); - - let output = exec_command_with_output("/sbin/losetup", &v) - .context(format!("failed to run /sbin/losetup on {}", self.display()))?; - Ok(PathBuf::from(output)) - } - - fn umount(&self) -> Result<()> { - exec_command("/usr/bin/umount", &[self.pathstr()]) - .context(format!("failed to umount {}", self.display()))?; - Ok(()) - } - - fn partition_type_guid(&self) -> Result { - let output = exec_command_with_output("/usr/bin/lsblk", &["-dno", "PARTTYPE", self.pathstr()]) - .context(format!("failed to run lsblk on {}", self.display()))?; - Ok(output) - } - - fn verity_initial_hashtree>(&self, hashfile: P) -> Result { - let output = exec_command_with_output("/usr/sbin/veritysetup", - &["format", self.pathstr(), hashfile.as_ref().pathstr()]) - .context("veritysetup format command failed")?; - - Ok(VerityOutput::parse(&output)) - } - - fn verity_regenerate_hashtree(&self, offset: usize, nblocks: usize, salt: &str) -> Result { - let arg_offset = format!("--hash-offset={}", offset); - let arg_blocks = format!("--data-blocks={}", nblocks); - let arg_salt = format!("--salt={}", salt); - let arg_path = self.pathstr(); - - let output = exec_command_with_output("/usr/sbin/veritysetup", - &[arg_offset.as_str(), arg_blocks.as_str(), arg_salt.as_str(), - "format", arg_path, arg_path]) - .context("running veritysetup command failed")?; - - Ok(VerityOutput::parse(&output)) - } - - fn verity_setup(&self, offset: usize, nblocks: usize, roothash: &str, devname: &str) -> Result<()> { - let arg_offset = format!("--hash-offset={}", offset); - let arg_blocks = format!("--data-blocks={}", nblocks); - let arg_path = self.pathstr(); - - exec_command("/usr/sbin/veritysetup", - &[arg_offset.as_str(), arg_blocks.as_str(), "create", - devname, arg_path, arg_path, roothash]) - .context("running veritysetup failed")?; - - Ok(()) - } - - fn pathstr(&self) -> &str { - self.to_str().unwrap() - } -} - -/* -fn exec_command(cmd_path: &str, args: &[&str]) -> bool { - Command::new(cmd_path) - .args(args) - .stderr(Stdio::inherit()) - .status() - .expect(&format!("unable to execute {}", cmd_path)) - .success() -} - - -fn exec_command_with_output(cmd_path: &str, args: &[&str]) -> (bool, String) { - let res = Command::new(cmd_path) - .args(args) - .stderr(Stdio::inherit()) - .output() - .expect(&format!("unable to execute {}", cmd_path)); - - if res.status.success() { - (true, String::from_utf8(res.stdout).unwrap().trim().to_owned()) - } else { - (false, String::new()) - } -} -*/ - -fn exec_command(cmd_path: &str, args: &[&str]) -> Result<()> { - let status = Command::new(cmd_path) - .args(args) - .stderr(Stdio::inherit()) - .status() - .context(format!("unable to execute {}", cmd_path))?; - - if !status.success() { - match status.code() { - Some(code) => bail!("command {} failed with exit code: {}", cmd_path, code), - None => bail!("command {} failed with no exit code", cmd_path), - } - } - Ok(()) -} - -fn exec_command_with_output(cmd_path: &str, args: &[&str]) -> Result { - let res = Command::new(cmd_path) - .args(args) - .stderr(Stdio::inherit()) - .output() - .context(format!("unable to execute {}", cmd_path))?; - - if !res.status.success() { - match res.status.code() { - Some(code) => bail!("command {} failed with exit code: {}", cmd_path, code), - None => bail!("command {} failed with no exit code", cmd_path), - } - } - - Ok(String::from_utf8(res.stdout).unwrap().trim().to_owned()) -} - -pub struct FileTypeResult(String); - -impl FileTypeResult { - pub fn is_xz_compressed(&self) -> bool { - self.0.starts_with("XZ") - } - - pub fn is_ext2_image(&self) -> bool { - self.0.starts_with("Linux rev 1.0 ext2 filesystem data") - } - - pub fn output(&self) -> &str { - self.0.as_str() - } -} - -/// The output from the `veritysetup format` command can be parsed as key/value -/// pairs. This class parses the output and stores it in a map for querying. -pub struct VerityOutput { - output: String, - map: HashMap, -} - -impl VerityOutput { - /// Parse the string `output` as standard output from the dm-verity - /// `veritysetup format` command. - fn parse(output: &str) -> VerityOutput { - let mut vo = VerityOutput { - output: output.to_owned(), - map: HashMap::new(), - }; - for line in output.lines() { - vo.parse_line(line); - } - vo - } - - fn parse_line(&mut self, line: &str) { - let v = line.split(':') - .map(|s| s.trim()) - .collect::>(); - - if v.len() == 2 { - self.map.insert(v[0].to_owned(), v[1].to_owned()); - } - } - - pub fn root_hash(&self) -> Option<&str> { - self.map.get("Root hash").map(|s| s.as_str()) - } - - pub fn salt(&self) -> Option<&str> { - self.map.get("Salt").map(|s| s.as_str()) - } - - pub fn output(&self) -> &str { - &self.output - } -} - - - diff --git a/libcitadel/src/resource.rs b/libcitadel/src/resource.rs index d2486ef..f24877d 100644 --- a/libcitadel/src/resource.rs +++ b/libcitadel/src/resource.rs @@ -1,20 +1,14 @@ -use std::path::{Path,PathBuf}; -use std::fs::{self,File}; -use std::io::{self,Read}; +use std::fs::{self, File}; +use std::io::{self,Seek,SeekFrom}; +use std::path::{Path, PathBuf}; -use disks::DiskPartition; -use Result; -use CommandLine; -use PathExt; -use ImageHeader; -use Config; -use MetaInfo; +use {CommandLine,Config,ImageHeader,MetaInfo,Result,Partition,Mount,verity,util}; + +use failure::ResultExt; const STORAGE_BASEDIR: &str = "/sysroot/storage/resources"; -const BOOT_BASEDIR: &str = "/boot/images"; const RUN_DIRECTORY: &str = "/run/images"; - /// Locates and mounts a resource image file. /// /// Resource image files are files containing a disk image that can be @@ -23,69 +17,89 @@ const RUN_DIRECTORY: &str = "/run/images"; /// contains a list of bind mounts to perform from the mounted tree to /// the system rootfs. /// -/// dm-verity will be set up for the mounted image unless the `citadel.noverity` -/// variable is set on the kernel command line. +/// Various kernel command line options control how the resource file is +/// searched for and how it is mounted. /// -/// Resource image files will first be searched for in the `/storage/resources/` -/// directory (with `/sysroot` prepended since these mounts are performed in initramfs). -/// If the storage device does not exist or kernel command line variables are set -/// indicating either an install mode or recovery mode boot then search of storage -/// directory is not performed. +/// citadel.noverity: Mount image without dm-verity. Also do not verify header signature. +/// citadel.nosignatures: Do not verify header signature. /// -/// If not located in `/storage/resources` the image file will be searched for on all -/// UEFI ESP partitions on the system. If found on a boot partition, it will be -/// copied to `/run/images` and uncompressed if necessary. +/// A requested image file will be searched for first in /run/images and if not found there the +/// usual location of /storage/resources is searched. /// pub struct ResourceImage { - name: String, path: PathBuf, + header: ImageHeader, + metainfo: MetaInfo, } impl ResourceImage { - /// Locate and return a resource image with `name`. - /// First the /storage/resources directory is searched, and if not found there, - /// each EFI boot partition will also be searched. + /// First the /run/images directory is searched, and if not found there, + /// the image will be searched for in /storage/resources/$channel pub fn find(name: &str) -> Result { - let mut img = ResourceImage::new(name); - let search_storage = !(CommandLine::install_mode() || CommandLine::recovery_mode()); - if search_storage && ResourceImage::ensure_storage_mounted() { - let path = PathBuf::from(format!("{}/{}.img", STORAGE_BASEDIR, name)); - if path.exists() { - img.path.push(path); - info!("Image found at {}", img.path.display()); - return Ok(img) - } + let filename = ResourceImage::image_filename(name); + + let run_path = Path::new(RUN_DIRECTORY).join(&filename); + + let channel = ResourceImage::read_rootfs_channel()?; + let storage_path = Path::new(STORAGE_BASEDIR).join(channel).join(&filename); + + if run_path.exists() { + return ResourceImage::from_path(run_path); } - if img.search_boot_partitions() { - Ok(img) + if !ResourceImage::ensure_storage_mounted()? { + bail!("Unable to mount /storage"); + } + + if storage_path.exists() { + ResourceImage::from_path(storage_path) } else { Err(format_err!("Failed to find resource image: {}", name)) } } - /// Locate and return a rootfs resource image. - /// Only EFI boot partitions will be searched. + /// Locate a rootfs image in /run/images and return it pub fn find_rootfs() -> Result { - let mut img = ResourceImage::new("citadel-rootfs"); - if img.search_boot_partitions() { - info!("Found rootfs image at {}", img.path.display()); - Ok(img) + let rootfs_path = Path::new(RUN_DIRECTORY).join(ResourceImage::image_filename("rootfs")); + if rootfs_path.exists() { + info!("Found rootfs image at {}", rootfs_path.display()); + ResourceImage::from_path(rootfs_path) } else { Err(format_err!("Failed to find rootfs resource image")) } } + pub fn from_path>(path: P) -> Result { + let header = ImageHeader::from_file(path.as_ref())?; + if !header.is_magic_valid() { + bail!("Image file {} does not have a valid header", path.as_ref().display()); + } + let metainfo = header.metainfo()?; + Ok(ResourceImage::new(path.as_ref(), header, metainfo)) + } + + pub fn is_valid_image(&self) -> bool { + self.header.is_magic_valid() + } + /// Return path to the resource image file. pub fn path(&self) -> &Path { &self.path } - fn new(name: &str) -> ResourceImage { + pub fn header(&self) -> &ImageHeader { + &self.header + } + + pub fn metainfo(&self) -> &MetaInfo { + &self.metainfo + } + + fn new(path: &Path, header: ImageHeader, metainfo: MetaInfo) -> ResourceImage { ResourceImage { - name: name.to_owned(), - path: PathBuf::new(), + path: path.to_owned(), + header, metainfo, } } @@ -99,172 +113,157 @@ impl ResourceImage { self.process_manifest_file() } - fn mount_verity(&self, config: &Config) -> Result<()> { - let hdr = ImageHeader::from_file(&self.path)?; - let metainfo = hdr.verified_metainfo(config)?; + pub fn is_compressed(&self) -> bool { + self.header.has_flag(ImageHeader::FLAG_DATA_COMPRESSED) + } - info!("Setting up dm-verity device for image"); + pub fn has_verity_hashtree(&self) -> bool { + self.header.has_flag(ImageHeader::FLAG_HASH_TREE) + } - if !hdr.has_flag(ImageHeader::FLAG_HASH_TREE) { - self.generate_verity_hashtree(&hdr, &metainfo)?; + pub fn decompress(&self) -> Result<()> { + if !self.is_compressed() { + return Ok(()) + } + self.decompress_and_generate_hashtree(true) + } + + // Avoid copying the body twice in the common case that image is both + // compressed and needs hashtree generated. + fn decompress_and_generate_hashtree(&self, decompress_only: bool) -> Result<()> { + assert!(self.is_compressed()); + let mut tmpfile = self.extract_body_to_tmpfile(Some("xz"))?; + util::xz_decompress(&tmpfile)?; + tmpfile.set_extension(""); + self.header.clear_flag(ImageHeader::FLAG_DATA_COMPRESSED); + + if !decompress_only && !self.has_verity_hashtree() { + verity::generate_image_hashtree(&tmpfile, &self.metainfo)?; + self.header.set_flag(ImageHeader::FLAG_HASH_TREE); + } + self.write_image_from_tmpfile(&tmpfile) + } + + fn extract_body_to_tmpfile(&self, extension: Option<&str>) -> Result { + let mut reader = File::open(&self.path)?; + reader.seek(SeekFrom::Start(4096))?; + fs::create_dir_all("/tmp/citadel-image-tmp")?; + let mut path = Path::new("/tmp/citadel-image-tmp").join(format!("{}-tmp", &self.metainfo.image_type())); + if let Some(ext) = extension { + path.set_extension(ext); + } + let mut out = File::create(&path)?; + io::copy(&mut reader, &mut out)?; + Ok(path) + } + + pub fn write_to_partition(&self, partition: &Partition) -> Result<()> { + if self.metainfo.image_type() != "rootfs" { + bail!("Cannot write to partition, image type is not rootfs"); } - let devname = format!("verity-{}", self.name); + if !self.has_verity_hashtree() { + self.generate_verity_hashtree()?; + } - self.path.verity_setup(ImageHeader::HEADER_SIZE, metainfo.nblocks(), metainfo.verity_root(), &devname)?; + info!("writing rootfs image to {}", partition.path().display()); + let args = format!("if={} of={} bs=4096 skip=1", + self.path.display(), partition.path().display()); + util::exec_cmdline("/bin/dd", args)?; + + self.header.set_status(ImageHeader::STATUS_NEW); + self.header.write_partition(partition.path())?; + + Ok(()) + } + + fn write_image_from_tmpfile(&self, tmpfile: &Path) -> Result<()> { + let mut reader = File::open(&tmpfile)?; + let mut out = File::create(self.path())?; + self.header.write_header(&mut out)?; + io::copy(&mut reader, &mut out)?; + fs::remove_file(tmpfile)?; + Ok(()) + } + + fn mount_verity(&self, config: &Config) -> Result<()> { + let verity_dev = self.setup_verity_device(config)?; info!("Mounting dm-verity device to {}", self.mount_path().display()); fs::create_dir_all(self.mount_path())?; - Path::new(&format!("/dev/mapper/{}", devname)).mount(self.mount_path()) + + util::mount(&verity_dev.to_string_lossy(), self.mount_path(), None) + } - pub fn generate_verity_hashtree(&self, hdr: &ImageHeader, metainfo: &MetaInfo) -> Result<()> { - info!("Generating dm-verity hash tree for image"); - if !hdr.has_flag(ImageHeader::FLAG_HASH_TREE) { - let _ = self.path.verity_regenerate_hashtree(ImageHeader::HEADER_SIZE, metainfo.nblocks(), metainfo.verity_salt())?; - hdr.set_flag(ImageHeader::FLAG_HASH_TREE); - let w = fs::OpenOptions::new().write(true).open(&self.path)?; - hdr.write_header(w)?; + pub fn setup_verity_device(&self, config: &Config) -> Result { + if !CommandLine::nosignatures() { + self.header.verify_signature(config)?; } - Ok(()) + info!("Setting up dm-verity device for image"); + if !self.has_verity_hashtree() { + self.generate_verity_hashtree()?; + } + verity::setup_image_device(self.path()) + } + + pub fn generate_verity_hashtree(&self) -> Result<()> { + if self.has_verity_hashtree() { + return Ok(()) + } + info!("Generating dm-verity hash tree for image {}", self.path.display()); + if self.is_compressed() { + info!("Image is compressed, so need to decompress first"); + self.decompress_and_generate_hashtree(false) + } else { + let tmpfile = self.extract_body_to_tmpfile(None)?; + verity::generate_image_hashtree(&tmpfile, &self.metainfo)?; + self.header.set_flag(ImageHeader::FLAG_HASH_TREE); + self.write_image_from_tmpfile(&tmpfile) + } + } + + pub fn verify_verity(&self) -> Result { + if !self.has_verity_hashtree() { + self.generate_verity_hashtree()?; + } + verity::verify_image(self.path(), &self.metainfo) + } + + pub fn verify_shasum(&self) -> Result { + unimplemented!(); } // Mount the resource image but use a simple loop mount rather than setting up a dm-verity // device for the image. fn mount_noverity(&self) -> Result<()> { info!("loop mounting image to {} (noverity)", self.mount_path().display()); - fs::create_dir_all(self.mount_path())?; - Path::new(&self.path).mount_with_args(self.mount_path(), "-oloop,ro,offset=4096") + + if self.is_compressed() { + self.decompress()?; + } + + let mount_path = self.mount_path(); + let loopdev = self.create_loopdev()?; + + info!("Loop device created: {}", loopdev.display()); + info!("Mounting to: {}", mount_path.display()); + + fs::create_dir_all(&mount_path)?; + + util::mount(&loopdev.to_string_lossy(), mount_path, Some("-oro")) } - // Copy resource image from /boot partition to /run/images and uncompress - // with xz if it is a compressed file. Update `self.path` to refer to the - // copy rather than the source file. - fn copy_to_run(&mut self) -> bool { - if let Err(err) = fs::create_dir_all(RUN_DIRECTORY) { - warn!("Error creating {} directory: {}", RUN_DIRECTORY, err); - return false; - } - let mut new_path = PathBuf::from(RUN_DIRECTORY); - new_path.set_file_name(self.path.file_name().unwrap()); - if let Err(err) = fs::copy(&self.path, &new_path) { - warn!("Error copying {} to {}: {}", self.path.display(), new_path.display(), err); - return false; - } - - if new_path.extension().unwrap() == "xz" { - if let Err(err) = new_path.xz_uncompress() { - warn!("Error uncompressing {}: {}", new_path.display(), err); - return false; - } - let stem = new_path.file_stem().unwrap().to_owned(); - new_path.set_file_name(stem); - } - self.path.push(new_path); - - if let Err(err) = self.maybe_decompress_image() { - warn!("Error decompressing image: {}", err); - return false; - } - - true - } - - fn maybe_decompress_image(&self) -> Result<()> { - let mut image = File::open(&self.path)?; - let hdr = ImageHeader::from_reader(&mut image)?; - if !hdr.has_flag(ImageHeader::FLAG_DATA_COMPRESSED) { - return Ok(()) - } - - info!("Decompressing internal image data"); - - let mut tempfile = self.write_compressed_tempfile(&mut image)?; - - tempfile.xz_uncompress()?; - tempfile.set_extension(""); - - self.write_uncompressed_image(&hdr, &tempfile)?; - - Ok(()) - } - - fn write_compressed_tempfile(&self, reader: &mut R) -> Result { - let mut tmp_path = Path::new(RUN_DIRECTORY).join(format!("{}-tmp", self.name)); - tmp_path.set_extension("xz"); - let mut tmp_out = File::create(&tmp_path)?; - io::copy(reader, &mut tmp_out)?; - Ok(tmp_path) - } - - fn write_uncompressed_image(&self, hdr: &ImageHeader, tempfile: &Path) -> Result<()> { - let mut image_out = File::create(&self.path)?; - hdr.clear_flag(ImageHeader::FLAG_DATA_COMPRESSED); - hdr.write_header(&mut image_out)?; - - let mut tmp_in = File::open(&tempfile)?; - io::copy(&mut tmp_in, &mut image_out)?; - fs::remove_file(tempfile)?; - - Ok(()) - } - - // Search for resource image file on any currently mounted /boot - // as well as on every UEFI ESP partition on the system. - // - // Return `true` if found - fn search_boot_partitions(&mut self) -> bool { - // Is /boot already mounted? - if Path::new("/boot").is_mounted() { - if self.search_current_boot_partition() && self.copy_to_run() { - info!("Image found on currently mounted boot partition and copied to {}", self.path.display()); - return true; - } - let _ = Path::new("/boot").umount(); - } - - let partitions = match DiskPartition::boot_partitions() { - Ok(ps) => ps, - Err(e) => { - warn!("Error reading disk partition information: {}", e); - return false; - }, - }; - - for part in partitions { - if part.mount("/boot") { - if self.search_current_boot_partition() && self.copy_to_run() { - part.umount(); - info!("Image found on boot partition {} and copied to {}", part.path().display(), self.path.display()); - return true; - } - part.umount(); - } - } - - false - } - - // Search for resource file on currently mounted /boot partition - // with both .img and .img.xz extensions. - // Return `true` if found. - fn search_current_boot_partition(&mut self) -> bool { - let mut path = PathBuf::from(BOOT_BASEDIR); - - for ext in ["img", "img.xz"].iter() { - path.set_file_name(format!("{}.{}", self.name, ext)); - if path.exists() { - self.path.push(path); - return true; - } - } - false + pub fn create_loopdev(&self) -> Result { + let args = format!("--offset 4096 -f --show {}", self.path.display()); + let output = util::exec_cmdline_with_output("/sbin/losetup", args)?; + Ok(PathBuf::from(output)) } // Return the path at which to mount this resource image. fn mount_path(&self) -> PathBuf { - PathBuf::from(format!("{}/{}.mountpoint", RUN_DIRECTORY, self.name)) + PathBuf::from(format!("{}/{}.mountpoint", RUN_DIRECTORY, self.metainfo.image_type())) } // Read and process a manifest file in the root directory of a mounted resource image. @@ -273,11 +272,12 @@ impl ResourceImage { let manifest = self.mount_path().join("manifest"); if !manifest.exists() { warn!("No manifest file found for resource image: {}", self.path.display()); - } else { - for line in manifest.read_as_lines()? { - if let Err(e) = self.process_manifest_line(&line) { - warn!("Processing manifest file for resource image ({}): {}", self.path.display(), e); - } + return Ok(()) + } + let s = fs::read_to_string(manifest)?; + for line in s.lines() { + if let Err(e) = self.process_manifest_line(&line) { + warn!("Processing manifest file for resource image ({}): {}", self.path.display(), e); } } Ok(()) @@ -305,29 +305,49 @@ impl ResourceImage { let to = Path::new("/sysroot").join(path_to); info!("Bind mounting {} to {} from manifest", from.display(), to.display()); - - from.bind_mount(&to) + util::mount(&from.to_string_lossy(), to, Some("--bind")) } // If the /storage directory is not mounted, attempt to mount it. // Return true if already mounted or if the attempt to mount it succeeds. - fn ensure_storage_mounted() -> bool { - if Path::new("/sysroot/storage").is_mounted() { - return true + fn ensure_storage_mounted() -> Result { + if Mount::is_path_mounted("/dev/mapper/citadel-storage")? { + return Ok(true); } let path = Path::new("/dev/mapper/citadel-storage"); if !path.exists() { - return false + return Ok(false); } info!("Mounting /sysroot/storage directory"); - const MOUNT_ARGS: &str = "-odefaults,nossd,noatime,commit=120"; - match path.mount_with_args("/sysroot/storage", MOUNT_ARGS) { - Err(e) => { - warn!("failed to mount /sysroot/storage: {}", e); - false - }, - Ok(()) => true, + let res = util::mount( + "/dev/mapper/citadel-storage", + "/sysroot/storage", + Some("-odefaults,nossd,noatime,commit=120") + ); + if let Err(err) = res { + warn!("failed to mount /sysroot/storage: {}", err); + return Ok(false); + } + Ok(true) + } + + fn read_rootfs_channel() -> Result { + let s = fs::read_to_string("/sysroot/etc/citadel-channel") + .context("Failed to open /sysroot/etc/citadel-channel")?; + match s.split_whitespace().next() { + Some(s) => Ok(s.to_owned()), + None => Err(format_err!("Failed to parse /sysroot/etc/citadel-channel contents")), + } + } + + + fn image_filename(image_type: &str) -> String { + if image_type == "modules" { + let utsname = util::uname(); + let v = utsname.release().split("-").collect::>(); + format!("citadel-modules-{}.img", v[0]) + } else { + format!("citadel-{}.img", image_type) } } } - diff --git a/libcitadel/src/util.rs b/libcitadel/src/util.rs new file mode 100644 index 0000000..e5c53b1 --- /dev/null +++ b/libcitadel/src/util.rs @@ -0,0 +1,129 @@ +use std::path::Path; +use std::process::{Command,ExitStatus,Stdio}; +use std::mem; +use libc::{self, c_char}; +use std::ffi::CStr; +use std::str::from_utf8_unchecked; + +use failure::ResultExt; + +use Result; + +pub fn ensure_command_exists(cmd_path: &str) -> Result<()> { + if !Path::new(cmd_path).exists() { + bail!("Cannot execute '{}': command does not exist"); + } + Ok(()) +} + +pub fn exec_cmdline>(cmd_path: &str, args: S) -> Result<()> { + ensure_command_exists(cmd_path)?; + let args: Vec<&str> = args.as_ref().split_whitespace().collect::>(); + let status = Command::new(cmd_path) + .args(args) + .stderr(Stdio::inherit()) + .status()?; + + check_cmd_status(cmd_path, &status) +} + +pub fn exec_cmdline_with_output>(cmd_path: &str, args: S) -> Result { + ensure_command_exists(cmd_path)?; + let args: Vec<&str> = args.as_ref().split_whitespace().collect::>(); + let res = Command::new(cmd_path) + .args(args) + .stderr(Stdio::inherit()) + .output() + .context(format!("unable to execute {}", cmd_path))?; + + check_cmd_status(cmd_path, &res.status)?; + Ok(String::from_utf8(res.stdout).unwrap().trim().to_owned()) +} + +fn check_cmd_status(cmd_path: &str, status: &ExitStatus) -> Result<()> { + if !status.success() { + match status.code() { + Some(code) => bail!("command {} failed with exit code: {}", cmd_path, code), + None => bail!("command {} failed with no exit code", cmd_path), + } + } + Ok(()) +} + +pub fn sha256>(path: P) -> Result { + let output = exec_cmdline_with_output("/usr/bin/sha256sum", format!("{}", path.as_ref().display())) + .context(format!("failed to calculate sha256 on {}", path.as_ref().display()))?; + + let v: Vec<&str> = output.split_whitespace().collect(); + Ok(v[0].trim().to_owned()) +} + +pub fn xz_compress>(path: P) -> Result<()> { + exec_cmdline("/usr/bin/xz", format!("-T0 {}", path.as_ref().display())) + .context(format!("failed to compress {}", path.as_ref().display()))?; + Ok(()) +} + +pub fn xz_decompress>(path: P) -> Result<()> { + exec_cmdline("/usr/bin/xz", format!("-d {}", path.as_ref().display())) + .context(format!("failed to decompress {}", path.as_ref().display()))?; + Ok(()) +} + +pub fn mount>(source: &str, target: P, options: Option<&str>) -> Result<()> { + let paths = format!("{} {}", source, target.as_ref().display()); + let args = match options { + Some(s) => format!("{} {}", s, paths), + None => paths, + }; + exec_cmdline("/usr/bin/mount", args) +} + +pub fn umount>(path: P) -> Result<()> { + let args = format!("{}", path.as_ref().display()); + exec_cmdline("/usr/bin/umount", args) +} + + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct UtsName(libc::utsname); + +#[allow(dead_code)] +impl UtsName { + pub fn sysname(&self) -> &str { + to_str(&(&self.0.sysname as *const c_char ) as *const *const c_char) + } + + pub fn nodename(&self) -> &str { + to_str(&(&self.0.nodename as *const c_char ) as *const *const c_char) + } + + pub fn release(&self) -> &str { + to_str(&(&self.0.release as *const c_char ) as *const *const c_char) + } + + pub fn version(&self) -> &str { + to_str(&(&self.0.version as *const c_char ) as *const *const c_char) + } + + pub fn machine(&self) -> &str { + to_str(&(&self.0.machine as *const c_char ) as *const *const c_char) + } +} + +pub fn uname() -> UtsName { + unsafe { + let mut ret: UtsName = mem::uninitialized(); + libc::uname(&mut ret.0); + ret + } +} + +#[inline] +fn to_str<'a>(s: *const *const c_char) -> &'a str { + unsafe { + let res = CStr::from_ptr(*s).to_bytes(); + from_utf8_unchecked(res) + } +} diff --git a/libcitadel/src/verity.rs b/libcitadel/src/verity.rs new file mode 100644 index 0000000..f1417e6 --- /dev/null +++ b/libcitadel/src/verity.rs @@ -0,0 +1,124 @@ +use std::path::{Path,PathBuf}; +use std::collections::HashMap; +use std::process::{Command, Stdio}; + +use failure::ResultExt; +use {Result,ImageHeader,MetaInfo,Partition,util}; + +const VERITYSETUP: &str = "/sbin/veritysetup"; +const LOSETUP: &str = "/sbin/losetup"; + +/// Generate dm-verity hashtree for a disk image and store in external file. +/// Parse output from veritysetup command and return as `VerityOutput`. +pub fn generate_initial_hashtree, Q:AsRef>(source: P, hashtree: Q) -> Result { + let args = format!("format {} {}", source.as_ref().display(), hashtree.as_ref().display()); + let output = util::exec_cmdline_with_output(VERITYSETUP, args) + .context("creating initial hashtree with veritysetup format failed")?; + Ok(VerityOutput::parse(&output)) +} + +pub fn generate_image_hashtree>(image: P, metainfo: &MetaInfo) -> Result { + let args = format!("--hash-offset={} --data-blocks={} --salt={} format {} {}", + metainfo.nblocks() * 4096, metainfo.nblocks(), metainfo.verity_salt(), + image.as_ref().display(), image.as_ref().display()); + + let output = util::exec_cmdline_with_output(VERITYSETUP, args) + .context("Failed to generate hashtree with veritysetup")?; + + // XXX check that root hash matches + + Ok(VerityOutput::parse(&output)) +} + +pub fn verify_image>(image: P, metainfo: &MetaInfo) -> Result { + let arg_offset = format!("--hash-offset={}", metainfo.nblocks() * 4096); + let loopdev = create_image_loop_device(image.as_ref())?; + + let status = Command::new(VERITYSETUP) + .args(&[ arg_offset.as_str(), "verify", &loopdev, &loopdev, metainfo.verity_root()]) + .stderr(Stdio::inherit()) + .status()?; + + util::exec_cmdline(LOSETUP, format!("-d {}", loopdev)) + .context("Error removing loop device created for verification")?; + + Ok(status.success()) +} + +pub fn setup_image_device>(image: P) -> Result { + let header = ImageHeader::from_file(image.as_ref())?; + let metainfo = header.metainfo()?; + + let devname = if metainfo.image_type() == "rootfs" { + String::from("rootfs") + } else { + format!("verity-{}", metainfo.image_type()) + }; + + let loopdev = create_image_loop_device(image.as_ref())?; + + setup_device(&loopdev, &devname, metainfo.nblocks(), metainfo.verity_root()) +} + +pub fn setup_partition_device(partition: &Partition) -> Result { + let metainfo = partition.header().metainfo()?; + let srcdev = partition.path().to_str().unwrap(); + setup_device(srcdev, "rootfs", metainfo.nblocks(), metainfo.verity_root()) +} + +fn setup_device(srcdev: &str, devname: &str, nblocks: usize, roothash: &str) -> Result { + let args = format!("--hash-offset={} --data-blocks={} create {} {} {} {}", + nblocks * 4096, nblocks, devname, srcdev, srcdev, roothash); + util::exec_cmdline(VERITYSETUP, args) + .context("Failed to set up verity device")?; + + Ok(PathBuf::from(format!("/dev/mapper/{}", devname))) +} + +fn create_image_loop_device(file: &Path) -> Result { + let args = format!("--offset 4096 -f --show {}", file.display()); + let output = util::exec_cmdline_with_output(LOSETUP, args)?; + Ok(output) +} + +/// The output from the `veritysetup format` command can be parsed as key/value +/// pairs. This class parses the output and stores it in a map for querying. +pub struct VerityOutput { + output: String, + map: HashMap, +} + +impl VerityOutput { + /// Parse the string `output` as standard output from the dm-verity + /// `veritysetup format` command. + fn parse(output: &str) -> VerityOutput { + let mut vo = VerityOutput { + output: output.to_owned(), + map: HashMap::new(), + }; + for line in output.lines() { + vo.parse_line(line); + } + vo + } + + fn parse_line(&mut self, line: &str) { + let v = line.split(':').map(|s| s.trim()).collect::>(); + + if v.len() == 2 { + self.map.insert(v[0].to_owned(), v[1].to_owned()); + } + } + + pub fn root_hash(&self) -> Option<&str> { + self.map.get("Root hash").map(|s| s.as_str()) + } + + pub fn salt(&self) -> Option<&str> { + self.map.get("Salt").map(|s| s.as_str()) + } + + pub fn output(&self) -> &str { + &self.output + } +}