diff --git a/Cargo.lock b/Cargo.lock index 32ff889..6699e32 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,15 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - [[package]] name = "android_system_properties" version = "0.1.5" @@ -134,15 +125,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "block2" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" -dependencies = [ - "objc2", -] - [[package]] name = "bumpalo" version = "3.19.0" @@ -220,17 +202,6 @@ dependencies = [ "clap_derive", ] -[[package]] -name = "clap-verbosity" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7bf75a8e0407a558bd7e8e7919baa352e21fb0c1c7702a63c853f2277c4c63" -dependencies = [ - "clap", - "log", - "serde", -] - [[package]] name = "clap_builder" version = "4.5.53" @@ -317,17 +288,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "ctrlc" -version = "3.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73736a89c4aff73035ba2ed2e565061954da00d4970fc9ac25dcc85a2a20d790" -dependencies = [ - "dispatch2", - "nix", - "windows-sys 0.61.2", -] - [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -457,18 +417,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "dispatch2" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" -dependencies = [ - "bitflags", - "block2", - "libc", - "objc2", -] - [[package]] name = "dyn-clone" version = "1.0.20" @@ -541,7 +489,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" dependencies = [ "log", - "regex", ] [[package]] @@ -550,10 +497,7 @@ version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" dependencies = [ - "anstream", - "anstyle", "env_filter", - "jiff", "log", ] @@ -797,30 +741,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" -[[package]] -name = "jiff" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" -dependencies = [ - "jiff-static", - "log", - "portable-atomic", - "portable-atomic-util", - "serde", -] - -[[package]] -name = "jiff-static" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.110", -] - [[package]] name = "js-sys" version = "0.3.82" @@ -911,21 +831,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "objc2" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" -dependencies = [ - "objc2-encode", -] - -[[package]] -name = "objc2-encode" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" - [[package]] name = "once_cell" version = "1.21.3" @@ -969,21 +874,6 @@ dependencies = [ "spki", ] -[[package]] -name = "portable-atomic" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" - -[[package]] -name = "portable-atomic-util" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" -dependencies = [ - "portable-atomic", -] - [[package]] name = "powerfmt" version = "0.2.0" @@ -1067,35 +957,6 @@ dependencies = [ "syn 2.0.110", ] -[[package]] -name = "regex" -version = "1.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" - [[package]] name = "rfc6979" version = "0.4.0" @@ -1532,14 +1393,9 @@ version = "0.1.0" dependencies = [ "anyhow", "attest-data", - "cfg-if", - "clap", - "clap-verbosity", "const-oid", - "ctrlc", "dice-verifier", "ed25519-dalek", - "env_logger", "getrandom 0.3.4", "hex", "hubpack", diff --git a/Cargo.toml b/Cargo.toml index 70427cf..e7895e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,16 +11,10 @@ test-data = [] vsock = ["dep:vsock"] [dependencies] -anyhow = "1.0.100" attest-data.git = "https://github.com/oxidecomputer/dice-util" -cfg-if = "1.0.4" -clap = { version = "4.5.53", features = ["derive"] } -clap-verbosity = "2.1.0" const-oid = { version = "0.9.5", features = ["db"] } -ctrlc = "3.5.1" dice-verifier = { git = "https://github.com/oxidecomputer/dice-util", features = ["ipcc", "mock"] } ed25519-dalek = { version = "2.1", default-features = false } -env_logger = "0.11.8" getrandom = { version = "0.3.4", features = ["std"] } hex = "0.4.3" hubpack = "0.1.2" @@ -41,18 +35,3 @@ anyhow.version = "1.0.100" [dev-dependencies] vm-attest = { path = ".", features = ["test-data"] } - -[[bin]] -name = "appraiser" -path = "src/bin/appraiser.rs" -required-features = ["vsock"] - -[[bin]] -name = "vm-instance" -path = "src/bin/vm-instance.rs" -required-features = ["vsock"] - -[[bin]] -name = "vm-instance-rot" -path = "src/bin/vm-instance-rot.rs" -required-features = ["vsock"] diff --git a/debian-vm.sh b/debian-vm.sh deleted file mode 100644 index b0e4679..0000000 --- a/debian-vm.sh +++ /dev/null @@ -1,229 +0,0 @@ -#!/usr/bin/env bash - -# This script uses `debootstrap` to create a qcow2 to host the in-VM parts of -# this system. This VM configuration makes the boot disk read-only through -# `fstab`. Further, it assumes that the block device hosting the boot disk will -# not be writable. The later is accomplished by using `overlayroot` to set up -# the `initramfs` to create a tmpfs / RAM disk as a writable `overlayfs` on top -# of the boot disk. -# -# Configuration changes made by tools like `cloud-init` (network config, -# hostname etc) will end up written to the overlayfs and will be lost when the -# instance is restarted. `cloud-init` works well in a configuration like this -# as all changes are reapplied on each boot. -# -# The disk produced by this script can be run under qemu with a -# command like: -# -# ```shell -# $ qemu-system-x86_64 -enable-kvm \ -# -m 2G \ -# -nographic \ -# -serial mon:stdio \ -# -drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \ -# -drive file=vm-instance.qcow2,if=virtio,readonly=on \ -# -net nic,model=virtio-net-pci \ -# -net bridge,br=br0 \ -# -device vhost-vsock-pci,guest-cid=3 -# ``` -# -# NOTE: Your environment will likely require fixups for file paths. - -set -euo pipefail - -if [ ! $# -ge 1 ]; then - >&2 echo "disk image name is required" - exit 1 -fi - -NAME="$1" -QCOW_FILE="$NAME".qcow2 - -# assumes pwd is `vm-attest-proto` src dir -cargo build - -BINS="target/debug/vm-instance target/debug/appraiser" -for BIN in $BINS; do - if [ ! -e "$BIN" ]; then - >&2 echo "missing required file: $BIN" - exit 1 - fi -done - -qemu-img create -f qcow2 "$QCOW_FILE" 2G - -if lsmod | awk '{print $2}' | grep '^nbd'; then - sudo modprobe nbd -fi - -# TODO: increment the integer part of the device name till we find an available -# one? -NBD_DEV=/dev/nbd0 -if /usr/sbin/nbd-client --check "$NBD_DEV"; then - >&2 echo "nbd device '$NBD_DEV' is in use" - exit 1 -fi -sudo qemu-nbd -c "$NBD_DEV" "$QCOW_FILE" - -sudo parted -s -a optimal -- "$NBD_DEV" \ - mklabel gpt \ - mkpart primary fat32 1MiB 128MiB \ - mkpart primary ext4 128MiB -0 \ - name 1 uefi \ - name 2 root \ - set 1 esp on - -sudo mkfs -t fat -F 32 -n EFI /dev/nbd0p1 -sudo mkfs -t ext4 -L root /dev/nbd0p2 - -ROOT_UUID=$(sudo blkid | grep "^$NBD_DEV" | grep ' LABEL="root" ' | grep -o ' UUID="[^"]\+"' | sed -e 's/^ //') -EFI_UUID=$(sudo blkid | grep "^$NBD_DEV" | grep ' LABEL="EFI" ' | grep -o ' UUID="[^"]\+"' | sed -e 's/^ //') - -BOOTSTRAP_ROOT="bootstrap-root" -mkdir "$BOOTSTRAP_ROOT" -sudo mount "$ROOT_UUID" "$BOOTSTRAP_ROOT" - -# do the `debootstrap` thing -sudo debootstrap --arch amd64 stable "$BOOTSTRAP_ROOT" http://ftp.us.debian.org/debian - -sudo mount -o bind,ro /dev "$BOOTSTRAP_ROOT"/dev -sudo mount -t proc /proc "$BOOTSTRAP_ROOT"/proc -sudo mount -t sysfs none "$BOOTSTRAP_ROOT"/sys - -sudo LANG=C.UTF-8 chroot "$BOOTSTRAP_ROOT" /bin/bash -x < /etc/fstab -$ROOT_UUID / ext4 errors=remount-ro 0 1 -$EFI_UUID /boot/efi vfat defaults 0 1 -EOF - -[[ -d /boot/efi ]] || mkdir /boot/efi -mount -a - -# cloud-init will find the network interface from metadata but we must source -# the files from 'interfaces.d' to get debian to put them into effect -cat < /etc/network/interfaces -source /etc/network/interfaces.d/* - -auto lo -iface lo inet loopback -EOF - -echo "vm-instance" > /etc/hostname - -cat < /etc/hosts -127.0.0.1 localhost -127.0.1.1 vm-instance - -# The following lines are desirable for IPv6 capable hosts -::1 localhost ip6-localhost ip6-loopback -ff02::1 ip6-allnodes -ff02::2 ip6-allrouters -EOF - -debconf-set-selections < /etc/overlayroot.conf -overlayroot=tmpfs -EOF - -# 'overlayroot' requires this workaround to function properly on Debian 13 -# https://github.com/systemd/systemd/issues/39558#issuecomment-3556323130 -mkdir -p /etc/systemd/system.conf.d/ -cat < /etc/systemd/system.conf.d/overlayfs.conf -[Manager] -DefaultEnvironment="LIBMOUNT_FORCE_MOUNT2=always" -EOF - -# Tell GRUB to use the serial console -cat - >>/etc/default/grub < /etc/systemd/system/vm-instance.service -[Unit] -Description=A simple daemon service -After=network-online.target - -[Service] -ExecStart=/usr/local/bin/vm-instance --verbose --retry --address "0.0.0.0:6666" vsock 3000 -Restart=always -Type=simple - -[Install] -WantedBy=multi-user.target -EOF - -systemctl enable vm-instance.service - -# done modifying the image / filesystems, configure fstab / mount to treat them -# as 'readonly' -cat < /etc/fstab -$ROOT_UUID / ext4 defaults,ro 0 1 -$EFI_UUID /boot/efi vfat defaults,ro 0 1 -EOF - -EOS - -for BIN in $BINS; do - sudo cp "$BIN" "$BOOTSTRAP_ROOT"/usr/local/bin -done - -MNTS="dev proc sys" -for MNT in $MNTS; do - sudo umount "$BOOTSTRAP_ROOT"/"$MNT" -done - -EFI_ROOT="$BOOTSTRAP_ROOT"/boot/efi - -sudo fstrim -v "$EFI_ROOT" -sudo fstrim -v "$BOOTSTRAP_ROOT" - -sudo umount "$EFI_ROOT" "$BOOTSTRAP_ROOT" -rmdir "$BOOTSTRAP_ROOT" - -sudo qemu-nbd -d "$NBD_DEV" - -virt-sparsify --in-place "$QCOW_FILE" -qemu-img convert -f qcow2 -O raw "$QCOW_FILE" "$NAME".raw -gzip --stdout "$NAME".raw > "$NAME".raw.gz diff --git a/src/bin/README.md b/src/bin/README.md deleted file mode 100644 index 589458e..0000000 --- a/src/bin/README.md +++ /dev/null @@ -1,43 +0,0 @@ -## Tools - -This directory has several tools that are useful for setting up two -demonstration / mock environments. - -### vm-instance-rot - -This is a mock implementation of the root of trust exposed to a virtual machine -(VM). It runs locally in a process accepting requests on either a unix domain -socket or on the host side of a vsock. The Oxide Platform RoT that backs this -server is a mock impl and is compiled into the tool. - -### vm-instance - -This tool acts as a client to the `vm-instance-rot`. It listens for qualifying -data (typically a nonce) sent from a challenger. These nonces are then combined -with a blob of data generated within the `vm-instance` to create the qualifying -data that's sent down to the `vm-instance-rot` where they're included in an -attestation from the Oxide Platform RoT. - -### appraiser - -The appraiser sends challenges in the form of qualifying data / nonces to the -`vm-instance`. It receives a `PlatformAttestation` in response. This attestation -is then appraised. - -### configurations - -These tools can be composed to demonstrate how the API works and how we intend -for it to be used. - -#### host only - -In this configuration all components run on a single host OS. The -`vm-instance-rot` and the `vm-instance` communicate over a unix domain socket. -The `appraiser` sends challenges to the `vm-instnace` over TCP. - -#### virtal - -To more accurately reflect the configuration we expect to deploy, the -`vm-instance` tool can be run in a VM with the help of the `debian-vm.sh` -script in the root of this repo. This requires that the `vm-instance-rot` be -configured to listen on a vsock. diff --git a/src/bin/appraiser.rs b/src/bin/appraiser.rs deleted file mode 100644 index e7498c7..0000000 --- a/src/bin/appraiser.rs +++ /dev/null @@ -1,351 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -use anyhow::{Context, Result, anyhow}; -use clap::{Parser, Subcommand}; -use clap_verbosity::{InfoLevel, Verbosity}; -use dice_verifier::{ - Attestation, Corim, Log, MeasurementSet, Nonce, ReferenceMeasurements, -}; -use log::{debug, info}; -use sha2::{Digest, Sha256}; -use std::{fs, net::TcpStream, os::unix::net::UnixStream, path::PathBuf}; -use vm_attest::{ - QualifyingData, RotType, VmInstanceAttestation, VmInstanceAttester, - VmInstanceConf, - socket::{VmInstanceRotSocketClient, VmInstanceTcp}, - vsock::VmInstanceRotVsockClient, -}; -use vsock::{VMADDR_CID_HOST, VsockAddr, VsockStream}; -use x509_cert::{ - Certificate, - der::{Decode, asn1::Utf8StringRef}, -}; - -// utility function to get common name from cert subject -fn get_cert_cn(cert: &Certificate) -> Option> { - use const_oid::db::rfc4519::COMMON_NAME; - - for elm in cert.tbs_certificate.subject.0.iter() { - for atav in elm.0.iter() { - if atav.oid == COMMON_NAME { - return Some( - Utf8StringRef::try_from(&atav.value) - .expect("Decode name attribute value to UTF8 string"), - ); - } - } - } - - None -} - -#[derive(Debug, Subcommand)] -pub enum SocketType { - /// Connect to `vm-instance-rot` as a client on a unix domain socket - Unix { - // path to unix socket file - sock: PathBuf, - }, - /// Connect to `vm-instance-rot` as a client on a vsock - Vsock { - // port to listen on - #[clap(default_value_t = 1024)] - port: u32, - }, -} - -#[derive(Debug, Subcommand)] -pub enum Backend { - /// Connect to the `vm-instance-rot` as a client - VmInstanceRot { - #[command(subcommand)] - socket_type: SocketType, - }, - /// Connect to the `vm-instnace` over TCP - VmInstance { address: String }, -} - -/// the appraiser challenges the VM instance by sending it a Nonce. It gets -/// back an attestation from the platform. It then uses the artifacts provided -/// to appraise the attestation. -#[derive(Debug, Parser)] -#[clap(author, version, about, long_about = None)] -struct Args { - /// Dump debug output - #[command(flatten)] - verbose: Verbosity, - - /// The root certificate(s) used for verifying cert chains from the RoT - #[clap(long)] - root_cert: Option, - - #[clap(long, default_value_t = false)] - self_signed: bool, - - /// Reference integrity measurements in CoRIM documents that identify the - /// various software components that we trust - #[clap(long)] - reference_measurements: Vec, - - /// Reference integrity measurements in a JSON structure that identify the - /// expected UUID & boot disk digest - #[clap(long)] - vm_instance_cfg: PathBuf, - - #[command(subcommand)] - backend: Backend, -} - -fn appraise_platform_attestation( - attestation: &VmInstanceAttestation, - qualifying_data: &QualifyingData, - root_certs: Option<&[Certificate]>, - rims: &ReferenceMeasurements, - instance_rim: &VmInstanceConf, -) -> Result<()> { - let mut cert_chain_pem = Vec::new(); - for cert in &attestation.cert_chain { - cert_chain_pem - .push(Certificate::from_der(cert).context("certificate from DER")?); - } - let cert_chain_pem = cert_chain_pem; - let verified_root = - dice_verifier::verify_cert_chain(&cert_chain_pem, root_certs) - .context("verify cert chain")?; - let cn = get_cert_cn(verified_root); - let cn = cn.ok_or(anyhow!("No CN in cert chain root"))?; - info!("cert chain verified against root with CN: {cn}"); - - // The qualifying data provided to this function must be the qualifying - // data passed from the vm instance down to the vm instance RoT. This means - // the nonce generated by the challenger / appraiser has already been - // combined with the data produced by the vm instance. - // - // So we must calculate the qualifying data produced byn the vm instance - // RoT by combining the provided qualifying data w/ the serialized log - // for the vm instance: - let mut qdata = Sha256::new(); - for log in &attestation.measurement_logs { - match log.rot { - RotType::OxideInstance => qdata.update(&log.data), - _ => continue, - } - } - qdata.update(qualifying_data); - - // smuggle this data into the `verify_attestation` function in the - // `attest_data::Nonce` type - let qualifying_data = - Nonce::N32(attest_data::Array(qdata.finalize().into())); - - // get the log from the Oxide platform RoT - let oxlog = attestation - .measurement_logs - .iter() - .find(|&log| log.rot == RotType::OxidePlatform); - - // put log in the form expected by the `verify_attestation` function - let (log, _): (Log, _) = if let Some(oxlog) = oxlog { - hubpack::deserialize(&oxlog.data) - .context("hubpack deserialize platform RoT log")? - } else { - return Err(anyhow!("no measurement log for Oxide Platform RoT")); - }; - - let (ox_attest, _): (Attestation, _) = - hubpack::deserialize(&attestation.attestation)?; - - dice_verifier::verify_attestation( - &cert_chain_pem[0], - &ox_attest, - &log, - &qualifying_data, - ) - .context("verify attestation")?; - - info!("attestation verified"); - - // appraise logs - for log in &attestation.measurement_logs { - match log.rot { - RotType::OxidePlatform => { - // use dice-verifier crate to use the RIMs to appraise the - // log from the OxidePlatform RoT - let (log, _): (Log, _) = hubpack::deserialize(&log.data)?; - let measurements = - MeasurementSet::from_artifacts(&cert_chain_pem, &log) - .context("measurement set from artifacts")?; - - dice_verifier::verify_measurements(&measurements, rims)?; - info!("measurement log from Oxide Platform RoT appraised"); - } - RotType::OxideInstance => { - // compare log / config description from the OxideInstance - // RoT to the reference from the config reference - let instance_cfg = - str::from_utf8(&log.data).context("string from UTF8")?; - let instance_cfg: VmInstanceConf = - serde_json::from_str(instance_cfg) - .context("VmInstanceConf from JSON")?; - - info!("comparing reference vm instance cfg: {instance_rim:?}"); - info!( - "to vm instance cfg from VmInstanceRot: {instance_cfg:?}" - ); - if *instance_rim != instance_cfg { - return Err(anyhow!( - "Vm Instance Conf verification failed" - )); - } - info!("metadata from Oxide VM Instance RoT appraised"); - } - } - } - - Ok(()) -} - -fn main() -> Result<()> { - let args = Args::parse(); - - env_logger::Builder::new() - .filter_level(args.verbose.log_level_filter()) - .init(); - - // load reference integrity measurements from CORIMs - let mut corims = Vec::new(); - for corim in &args.reference_measurements { - let corim = Corim::from_file(corim).with_context(|| { - format!("loading CORIM from: {}", corim.display()) - })?; - corims.push(corim); - } - let platform_rim = ReferenceMeasurements::try_from(corims.as_slice()) - .context("Reference measurements from CORIMs")?; - debug!("loaded reference integrity measurements"); - - // load the provided root certs - let root_certs = match args.root_cert { - Some(path) => { - let root_cert = fs::read(&path) - .with_context(|| format!("read file: {}", path.display()))?; - Some( - Certificate::load_pem_chain(&root_cert) - .context("failed to load certs from the provided file")?, - ) - } - None => { - if !args.self_signed { - return Err(anyhow!( - "No root cert, `--self-signed` must be explicit" - )); - } else { - None - } - } - }; - debug!("loaded root certs: {:?}", root_certs); - - // construct a `VmInstanceConf` from test data - // this is our reference for appraising the log produced by the - // `RotType::OxideInstance` - let instance_rim = fs::read_to_string(&args.vm_instance_cfg) - .context("read ATTEST_INSTANCE_LOG to string")?; - let instance_rim: VmInstanceConf = serde_json::from_str(&instance_rim) - .context("parse JSON from rim for instance RoT log")?; - - match args.backend { - // Using these backends we're talking directly to the `VmInstanceRot` - // so there's no `VmInstance` to complicate the qualifying data. In this - // case the qualifying data is just a random `Nonce`. Similarly, when - // verifying attestations from this backend there's no log from the - // `VmInstance` to appraise. - Backend::VmInstanceRot { socket_type } => { - let qualifying_data = QualifyingData::from_platform_rng() - .context("qualifying data from platform RNG")?; - match socket_type { - SocketType::Unix { sock } => { - let stream = UnixStream::connect(&sock) - .context("connect to domain socket")?; - debug!("connected to VmInstanceRotServer socket"); - let vm_instance_rot = - VmInstanceRotSocketClient::new(stream); - let attestation = - vm_instance_rot.attest(&qualifying_data)?; - appraise_platform_attestation( - &attestation, - &qualifying_data, - root_certs.as_deref(), - &platform_rim, - &instance_rim, - ) - .context("appraise platform attestation")?; - info!( - "appraised attestation from VmInstanceRot over socket" - ); - } - SocketType::Vsock { port } => { - let addr = VsockAddr::new(VMADDR_CID_HOST, port); - let stream = VsockStream::connect(&addr) - .context("vsock stream connect")?; - let vm_instance_rot = VmInstanceRotVsockClient::new(stream); - let attestation = - vm_instance_rot.attest(&qualifying_data)?; - appraise_platform_attestation( - &attestation, - &qualifying_data, - root_certs.as_deref(), - &platform_rim, - &instance_rim, - ) - .context("appraise platform attestation")?; - info!( - "appraised attestation from VmInstanceRot over vsock" - ); - } - }; - } - // Using this backend will cause us to talk to the `VmInstance` over - // Tcp. The `VmInstance` will include the public_key returned in the - // `AttestedKey` structure in the qualifying data that it passes down - // to the `VmInstanceRot`. We must recreate this qualifying data and - // pass it to the appraisal function. - Backend::VmInstance { address } => { - let stream = TcpStream::connect(&address) - .with_context(|| format!("tcp stream connect: {}", &address))?; - debug!("connected to server: {}", &address); - - let mut vm_instance = VmInstanceTcp::new(stream); - // send Nonce to server - let nonce = QualifyingData::from_platform_rng()?; - let attested_data = vm_instance - .attest_data(&nonce) - .context("get attested data")?; - - // reconstruct the qualifying data created by the vm instance - let mut qualifying_data = Sha256::new(); - qualifying_data.update(nonce); - qualifying_data.update(&attested_data.data); - let vm_qualifying_data = QualifyingData::from( - Into::<[u8; 32]>::into(qualifying_data.finalize()), - ); - - // appraise the platform attestation & qualifying data - appraise_platform_attestation( - &attested_data.attestation, - &vm_qualifying_data, - root_certs.as_deref(), - &platform_rim, - &instance_rim, - ) - .context("appraise platform attestation")?; - info!("attested data: {:?}", attested_data.data); - - // do something with the data - } - } - - Ok(()) -} diff --git a/src/bin/vm-instance-rot.rs b/src/bin/vm-instance-rot.rs deleted file mode 100644 index 980ed07..0000000 --- a/src/bin/vm-instance-rot.rs +++ /dev/null @@ -1,110 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -use anyhow::{Context, Result, anyhow}; -use clap::{Parser, Subcommand}; -use clap_verbosity::{InfoLevel, Verbosity}; -use dice_verifier::AttestMock as OxAttestMock; -use log::debug; -use std::{fs, os::unix::net::UnixListener, path::PathBuf}; -use vsock::{VsockAddr, VsockListener}; - -use vm_attest::{ - VmInstanceConf, VmInstanceRot, socket::VmInstanceRotSocketServer, - vsock::VmInstanceRotVsockServer, -}; - -#[derive(Debug, Subcommand)] -enum SocketType { - /// Listen for messages on a unix domain socket - Unix { sock: PathBuf }, - /// Listen for messages on the host side of a vsock - Vsock { - /// Accept connections from this specific context ID - #[clap(long, default_value_t = libc::VMADDR_CID_ANY)] - cid: u32, - - /// Port to listen on - #[clap(default_value_t = 1024)] - port: u32, - }, -} - -/// This is a mock implementation of the root of trust exposed to a virtual -/// machine (VM). It runs locally in a process accepting requests on either -/// a unix domain socket or on the host side of a vsock. -#[derive(Debug, Parser)] -#[clap(author, version, about, long_about = None)] -struct Args { - /// Dump debug output - #[command(flatten)] - verbose: Verbosity, - - /// The root certificate(s) used for verifying cert chains from the RoT - #[clap(long)] - cert_chain: PathBuf, - - /// The log returned by the mock platform RoT - #[clap(long)] - log: PathBuf, - - /// Key used by the mock platform RoT to sign attestations - #[clap(long)] - signing_key: PathBuf, - - /// Measurement log for VmInstanceRot, - #[clap(long)] - vm_instance_cfg: PathBuf, - - /// The type of the socket to listen on - #[command(subcommand)] - socket_type: SocketType, -} - -fn main() -> Result<()> { - let args = Args::parse(); - - env_logger::Builder::new() - .filter_level(args.verbose.log_level_filter()) - .init(); - - let oxide_platform_rot = Box::new( - OxAttestMock::load(&args.cert_chain, &args.log, &args.signing_key) - .context("create OxAttestMock from artifacts")?, - ); - - debug!("reading VmInstanceRotMock config from file"); - let instance_cfg = fs::read_to_string(&args.vm_instance_cfg) - .context("read ATTEST_INSTANCE_CFG to string")?; - let instance_cfg: VmInstanceConf = serde_json::from_str(&instance_cfg) - .context("parse JSON from mock cfg for instance RoT")?; - - debug!("creating instance of VmInstanceAttestMock"); - // instantiate an `AttestMock` w/ the Oxide platform RoT instance requested - // by the caller & the config - let rot = VmInstanceRot::new(oxide_platform_rot, instance_cfg); - - match args.socket_type { - SocketType::Unix { sock } => { - if sock.exists() { - return Err(anyhow!("socket file exists")); - } - debug!("binding to sock file: {}", sock.display()); - - let listener = UnixListener::bind(&sock) - .context("failed to bind to socket")?; - debug!("listening on socket file: {}", sock.display()); - - Ok(VmInstanceRotSocketServer::new(rot, listener).run()?) - } - SocketType::Vsock { cid, port } => { - debug!("binding to vsock cid:port: ({cid}, {port})"); - let listener = VsockListener::bind(&VsockAddr::new(cid, port)) - .with_context(|| format!("bind to cid,pid: ({cid},{port})"))?; - debug!("listening on cid,port: ({cid},{port})"); - - Ok(VmInstanceRotVsockServer::new(rot, listener).run()?) - } - } -} diff --git a/src/bin/vm-instance.rs b/src/bin/vm-instance.rs deleted file mode 100644 index 943f511..0000000 --- a/src/bin/vm-instance.rs +++ /dev/null @@ -1,121 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -use anyhow::{Context, Result, anyhow}; -use clap::{Parser, Subcommand}; -use clap_verbosity::{InfoLevel, Verbosity}; - -use log::{debug, info}; -use std::{ - net::TcpListener, os::unix::net::UnixStream, path::PathBuf, thread, time, -}; - -use vsock::{VMADDR_CID_HOST, VsockAddr, VsockStream}; - -use vm_attest::{ - socket::{VmInstanceRotSocketClient, VmInstanceTcpServer}, - vsock::VmInstanceRotVsockClient, -}; - -#[derive(Debug, Subcommand)] -enum SocketType { - /// Connect to `vm-instance-rot` as a client on a unix domain socket - Unix { - // path to unix socket file - sock: PathBuf, - }, - /// Connect to `vm-instance-rot` as a client on a vsock - Vsock { - // port to listen on - #[clap(default_value_t = 1024)] - port: u32, - }, -} - -/// This is a tool implements the minimal behavior we expect of the software -/// running in a virtual machine. It connects to the `vm-instance-rot` as a -/// client while accepting challenges from the `appraiser` over TCP. -#[derive(Debug, Parser)] -#[clap(author, version, about, long_about = None)] -struct Args { - // Dump debug output - #[command(flatten)] - verbose: Verbosity, - - /// Address used for server that listens for challenges - #[clap(long, default_value_t = String::from("localhost:6666"))] - address: String, - - #[clap(long, default_value_t = false)] - retry: bool, - - #[command(subcommand)] - socket_type: SocketType, -} - -fn main() -> Result<()> { - let args = Args::parse(); - - env_logger::Builder::new() - .filter_level(args.verbose.log_level_filter()) - .init(); - - match args.socket_type { - SocketType::Unix { sock } => { - // fail early if the socket file doesn't exist - if !sock.exists() { - return Err(anyhow!("socket file missing")); - } - - let stream = UnixStream::connect(&sock) - .context("connect to domain socket")?; - debug!("connected to VmInstanceRotServer socket"); - let vm_instance_rot = VmInstanceRotSocketClient::new(stream); - - let challenge_listener = TcpListener::bind(&args.address) - .context("bind to TCP socket")?; - debug!("Listening on TCP address{:?}", &args.address); - - let server = - VmInstanceTcpServer::new(challenge_listener, vm_instance_rot); - Ok(server.run()?) - } - SocketType::Vsock { port } => { - debug!("connecting to host vsock on port: {port}"); - let addr = VsockAddr::new(VMADDR_CID_HOST, port); - - // if `--retry` we repeatedly try to connect to the host vsock - let stream = loop { - let stream = - VsockStream::connect(&addr).context("vsock stream connect"); - match stream { - Ok(stream) => break stream, - // make this more specific by detecting whatever this is: - // Connection reset by peer (os error 104) - Err(e) => { - if args.retry { - info!("failed to connect to vsock stream: {e:?}"); - thread::sleep(time::Duration::from_secs(2)); - continue; - } else { - return Err(e); - } - } - } - }; - - debug!("creating VmInstanceRotVsockClient from VsockStream"); - let vm_instance_rot = VmInstanceRotVsockClient::new(stream); - - debug!("binding to address: {}", &args.address); - let challenge_listener = TcpListener::bind(&args.address) - .context("bind to TCP socket")?; - debug!("Listening on TCP address{:?}", &args.address); - - let server = - VmInstanceTcpServer::new(challenge_listener, vm_instance_rot); - Ok(server.run()?) - } - } -}