Compare commits

..

No commits in common. "master" and "v0.1.1" have entirely different histories.

19 changed files with 177 additions and 3334 deletions

3
.gitignore vendored

@ -1,6 +1,3 @@
# SPDX-License-Identifier: Unlicense
dtrfs.tar dtrfs.tar
build build
tmp tmp
target

24
LICENSE

@ -1,24 +0,0 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <https://unlicense.org/>

@ -1,7 +1,3 @@
<!--
SPDX-License-Identifier: Unlicense
-->
## OS template ## OS template
You will need a working OS template to work with this project. You will need a working OS template to work with this project.

@ -3,8 +3,6 @@ asn1_encoder
async_tx async_tx
async_xor async_xor
atkbd atkbd
bridge
br_netfilter
cbc cbc
cdrom cdrom
crc16 crc16
@ -14,7 +12,6 @@ crc32_pclmul
crct10dif_pclmul crct10dif_pclmul
cryptd cryptd
crypto_simd crypto_simd
curve25519_x86_64
dm_bufio dm_bufio
dm_crypt dm_crypt
dm-integrity dm-integrity
@ -30,53 +27,23 @@ i2c_i801
i2c_mux i2c_mux
i2c_smbus i2c_smbus
i8042 i8042
inet_diag
intel_agp intel_agp
intel_gtt intel_gtt
intel_pmc_bxt intel_pmc_bxt
intel_rapl_common intel_rapl_common
intel_rapl_msr intel_rapl_msr
ip6table_filter
ip6table_mangle
ip6table_nat
ip6_tables
ip6_udp_tunnel
ip_set
ip_set_hash_net
iptable_filter
iptable_mangle
iptable_nat
iptable_raw
ip_tables ip_tables
ipt_REJECT
iTCO_vendor_support iTCO_vendor_support
iTCO_wdt iTCO_wdt
jbd2 jbd2
libaesgcm
libchacha20poly1305
libcrc32c
libcurve25519_generic
libps2 libps2
llc
loop loop
lpc_ich lpc_ich
mac_hid mac_hid
mbcache mbcache
mousedev mousedev
net_failover net_failover
nf_conntrack
nf_conntrack_netlink
nf_defrag_ipv4
nf_defrag_ipv6
nf_nat
nfnetlink nfnetlink
nfnetlink_acct
nfnetlink_log
nf_reject_ipv4
nf_tables
nft_chain_nat
nft_compat
overlay
parport parport
parport_pc parport_pc
pcspkr pcspkr
@ -94,13 +61,9 @@ sha256
sha256_ssse3 sha256_ssse3
sha512_ssse3 sha512_ssse3
sr_mod sr_mod
stp
tcp_diag
tee tee
trusted trusted
tsm tsm
udp_tunnel
veth
virtio_blk virtio_blk
virtio_net virtio_net
vivaldi_fmap vivaldi_fmap
@ -109,23 +72,5 @@ vmw_vsock_virtio_transport_common
vmw_vsock_vmci_transport vmw_vsock_vmci_transport
vsock vsock
vsock_loopback vsock_loopback
vxlan
wireguard
xfrm_algo
xfrm_user
xor xor
x_tables x_tables
xt_addrtype
xt_comment
xt_conntrack
xt_limit
xt_mark
xt_MASQUERADE
xt_multiport
xt_nat
xt_nfacct
xt_NFLOG
xt_physdev
xt_REDIRECT
xt_set
xt_tcpudp

@ -1,7 +1,4 @@
#!/bin/bash #!/bin/bash
# SPDX-License-Identifier: Unlicense
cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" cd -- "$( dirname -- "${BASH_SOURCE[0]}" )"
source creator_exports.sh source creator_exports.sh
source creator_functions.sh source creator_functions.sh
@ -22,13 +19,11 @@ install_binary $(which openssl) && cp -r /etc/ssl "${ROOT}/etc/"
install_binary $(which cryptsetup) install_binary $(which cryptsetup)
install_binary $(which blkid) install_binary $(which blkid)
install_binary $(which fdisk) install_binary $(which fdisk)
install_binary $(which sysctl)
install_binary $(which mkfs.ext4) install_binary $(which mkfs.ext4)
install_binary $(which ssh-keygen)
install_binary $(which fsarchiver) install_binary $(which fsarchiver)
install_kmod install_kmod
install_busybox install_busybox
install_dtrfs_api install_guest_api
echo_cyan "Installing scripts..." echo_cyan "Installing scripts..."
install_init_script install_init_script

@ -1,7 +1,4 @@
#!/bin/bash #!/bin/bash
# SPDX-License-Identifier: Unlicense
script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
# root of the initrd, that will be used to create the cpio archive # root of the initrd, that will be used to create the cpio archive
@ -13,6 +10,9 @@ script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
# choose a kernel if you don't want to use the active one # choose a kernel if you don't want to use the active one
[[ -n "$KERNEL" ]] || [[ -n "$KERNEL" ]] ||
export KERNEL="$(uname -r)" export KERNEL="$(uname -r)"
# this is the DeTEE Guest API that allows you to control the initrd
[[ -n "$GUEST_API_URL" ]] ||
export GUEST_API_URL="https://gitea.detee.cloud/ghe0/stuff/releases/download/v0.0.0/guest_api.zst"
# this will allow you to grab modules from the machine where the installer is running # this will allow you to grab modules from the machine where the installer is running
[[ -n "$GRAB_LOCAL_MODS" ]] || [[ -n "$GRAB_LOCAL_MODS" ]] ||
export GRAB_LOCAL_MODS="no" export GRAB_LOCAL_MODS="no"

@ -1,7 +1,5 @@
#!/bin/bash #!/bin/bash
# SPDX-License-Identifier: Unlicense
echo_cyan() { echo_cyan() {
echo -e "\033[0;36m$1\033[0m" echo -e "\033[0;36m$1\033[0m"
} }
@ -34,8 +32,9 @@ install_build_deps() {
create_dirs() { create_dirs() {
rm -rf "$ROOT" 2>/dev/null rm -rf "$ROOT" 2>/dev/null
mkdir -p "${ROOT}/usr/bin/"
mkdir -p "${ROOT}/usr/bin" mkdir -p "${ROOT}/usr/bin"
mkdir -p "${ROOT}/usr/lib/dtrfs" mkdir -p "${ROOT}/usr/lib"
mkdir -p "${ROOT}/dev" mkdir -p "${ROOT}/dev"
mkdir -p "${ROOT}/etc" mkdir -p "${ROOT}/etc"
mkdir -p "${ROOT}/mnt" mkdir -p "${ROOT}/mnt"
@ -48,9 +47,9 @@ create_dirs() {
ln -s usr/bin "${ROOT}/sbin" ln -s usr/bin "${ROOT}/sbin"
ln -s usr/lib "${ROOT}/lib" ln -s usr/lib "${ROOT}/lib"
ln -s usr/lib "${ROOT}/lib64" ln -s usr/lib "${ROOT}/lib64"
ln -s lib "${ROOT}/usr/lib64" ln -s lib "${ROOT}/usr/lib64"
ln -s bin "${ROOT}/usr/sbin" ln -s bin "${ROOT}/usr/sbin"
ln -s ../run "${ROOT}/var/run" ln -s ../run "${ROOT}/var/run"
} }
# Installs a library. Expects absolute path. # Installs a library. Expects absolute path.
@ -117,8 +116,7 @@ install_busybox() {
install_init_script() { install_init_script() {
cp ../init.sh "${ROOT}/init" cp ../init.sh "${ROOT}/init"
cp ../init_functions.sh "${ROOT}/usr/lib/dtrfs/" cp ../init_functions.sh "${ROOT}/"
cp ../install_os.sh "${ROOT}/usr/lib/dtrfs/"
} }
install_module() { install_module() {
@ -184,25 +182,23 @@ scan_modules() {
done <<< "$( echo "$drivers" )" done <<< "$( echo "$drivers" )"
} }
install_dtrfs_api() { install_guest_api() {
local my_location="$(pwd)" echo_cyan "Installing the guest API from https://gitea.detee.cloud/SNP/remote_decryption/"
echo_cyan "Building dtrfs_api..." wget -O guest_api.zst "$GUEST_API_URL" 2> /dev/null
cd ../../dtrfs_api && cargo build --release || { zstd --decompress guest_api.zst
echo_yellow "Could not build dtrfs_api. Looking for binary at $(pwd)/dtrfs_api" chmod +x guest_api
} install_binary "$(pwd)/guest_api"
cd "$my_location" rm guest_api guest_api.zst
cp ../../dtrfs_api/target/release/dtrfs_api ./
install_binary "$(pwd)/dtrfs_api"
} }
create_archive() { create_archive() {
local archive="detee-$(hostnamectl hostname)-${KERNEL}.cpio.gz" local archive="detee-$(hostnamectl hostname)-${KERNEL}.cpio.gz"
echo_cyan "Creating archive $(pwd)/$archive" echo_cyan "Creating archive build/$archive"
echo $archive > .archive_name echo $archive > .archive_name
my_location="$(pwd)" my_location="$(pwd)"
cd ${ROOT} cd ${ROOT}
find . | cpio -o -H newc | gzip \ find . | cpio -o -H newc | gzip \
> "${my_location}/detee-$(hostnamectl hostname)-${KERNEL}.cpio.gz" > "${my_location}/detee-$(hostnamectl hostname)-${KERNEL}.cpio.gz"
cd "$my_location" cd $my_location
} }

2564
dtrfs_api/Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -1,21 +0,0 @@
# SPDX-License-Identifier: Unlicense
[package]
name = "dtrfs_api"
version = "0.1.0"
edition = "2021"
[dependencies]
bs58 = "0.5.1"
anyhow = "1.0.93"
bincode = "1.3.3"
regex = "1.11.1"
sev = { version = "4.0", default-features = false, features = ['crypto_nossl','snp'] }
ed25519-dalek = { version = "2.1.1" }
lazy_static = "1.5.0"
actix-web = { version = "4.9.0", features = ["rustls-0_23"] }
sha3 = "0.10.8"
rustls = "0.23.18"
rustls-pemfile = "2.2.0"
serde = { version = "1.0.215", features = ["derive"] }
base64 = "0.22.1"

@ -1,5 +0,0 @@
# SPDX-License-Identifier: Unlicense
reorder_impl_items = true
use_small_heuristics = "Max"
merge_imports = true

@ -1,220 +0,0 @@
// SPDX-License-Identifier: Unlicense
mod os;
mod snp;
use actix_web::{get, post, web, App, HttpRequest, HttpResponse, HttpServer};
use ed25519_dalek::{Signature, Verifier, VerifyingKey};
use lazy_static::lazy_static;
use regex::Regex;
use rustls::{pki_types::PrivateKeyDer, ServerConfig};
use rustls_pemfile::{certs, pkcs8_private_keys};
use serde::Deserialize;
use sha3::{Digest, Sha3_512};
use std::{
fs::File,
io::{BufReader, Read},
};
const CRT_FILE: &str = "/tmp/certs/dtrfs_api.crt";
const KEY_FILE: &str = "/tmp/certs/dtrfs_api.key";
const CMDLINE_FILE: &str = "/proc/cmdline";
lazy_static! {
static ref SNP_REPORT: String = snp::get_report_as_base64(get_cert_hash()).unwrap();
static ref CRT_CONTENTS: String = {
let mut msg = String::new();
let _ = BufReader::new(File::open(CRT_FILE).unwrap()).read_to_string(&mut msg);
msg
};
static ref CMDLINE: String = {
let mut cmdline = String::new();
let _ = BufReader::new(File::open(CMDLINE_FILE).unwrap()).read_to_string(&mut cmdline);
cmdline
};
}
fn get_cert_hash() -> [u8; 64] {
let mut hasher = Sha3_512::new();
let crt = File::open(CRT_FILE).expect("Could not open crt file.");
let mut buf_reader = BufReader::new(crt);
let mut buffer = Vec::new();
buf_reader.read_to_end(&mut buffer).expect("Could not read certificate.");
hasher.update(buffer);
let crt_hash = hasher.finalize();
crt_hash.into()
}
fn verifying_key() -> Result<VerifyingKey, Box<dyn std::error::Error>> {
let re = Regex::new(r"detee_admin=([A-Za-z0-9]+)").unwrap();
let key_str = re.find(&CMDLINE).map(|m| m.as_str()).unwrap_or("");
let key_str =
key_str.strip_prefix("detee_admin=").ok_or("Could not get admin key from cmdline")?;
Ok(VerifyingKey::from_bytes(
&bs58::decode(key_str)
.into_vec()?
.try_into()
.map_err(|_| bs58::decode::Error::BufferTooSmall)?,
)?)
}
fn verify(req: &HttpRequest) -> Result<(), Box<dyn std::error::Error>> {
let signature = req
.headers()
.get("ed25519-signature")
.ok_or_else(|| "Did not find ed25519-signature header")?;
let signature = bs58::decode(signature).into_vec()?;
let signature = Signature::from_bytes(signature.as_slice().try_into()?);
let verifying_key = verifying_key()?;
Ok(verifying_key.verify(CRT_CONTENTS.as_bytes(), &signature)?)
}
#[get("/")]
async fn homepage() -> HttpResponse {
let mut text: String = "Available commands:\n".to_string();
text += "GET: /report /ssh_key\n";
text += "POST: /install /decrypt /switch_root /ssh_key\n";
text += "\nAll requests require the ed25519-signature header";
HttpResponse::Ok().body(text)
}
#[get("/report")]
async fn get_report() -> HttpResponse {
HttpResponse::Ok().body(SNP_REPORT.clone())
}
#[derive(Deserialize)]
struct InstallForm {
hostname: String,
url: String,
sha: String,
keyfile: String,
}
// TODO: QA this function to make sure we don't accidentally allow empty string keyfile
#[post("/install")]
async fn post_install_form(req: HttpRequest, form: web::Form<InstallForm>) -> HttpResponse {
if let Err(e) = verify(&req) {
return HttpResponse::BadRequest().body(format!("Signature verification failed: {}", e));
};
match os::encrypt_and_install_os(&form.url, &form.sha, &form.keyfile, &form.hostname) {
Ok(s) => HttpResponse::Ok().body(s),
Err(e) => HttpResponse::InternalServerError().body(format!("{e:?}")),
}
}
#[derive(Deserialize)]
struct DecryptForm {
keyfile: String,
}
// TODO: QA this function to make sure we don't accidentally allow empty string keyfile
#[post("/decrypt")]
async fn post_decrypt_form(req: HttpRequest, form: web::Form<DecryptForm>) -> HttpResponse {
if let Err(e) = verify(&req) {
return HttpResponse::BadRequest().body(format!("Signature verification failed: {}", e));
};
let decrypt_result = os::try_backup_keyfile(&form.keyfile);
if let Err(decryption_error) = decrypt_result {
return HttpResponse::BadRequest()
.body(format!("Could not decrypt root: {decryption_error:?}"));
}
let hot_key_result = os::replace_hot_keyfile();
HttpResponse::Ok().body(format!("{:?}\n{:?}", decrypt_result, hot_key_result))
}
#[post("/switch_root")]
async fn post_process_exit(req: HttpRequest) -> HttpResponse {
if let Err(e) = verify(&req) {
return HttpResponse::BadRequest().body(format!("Signature verification failed: {}", e));
};
std::process::exit(0);
}
#[derive(Deserialize)]
struct SSHKeyForm {
ssh_key: String,
}
#[get("/server_ssh_pubkeys")]
async fn get_server_pubkeys() -> HttpResponse {
match os::get_server_ssh_pubkeys() {
Ok(keys) => HttpResponse::Ok().body(keys),
Err(e) => HttpResponse::InternalServerError()
.body(format!("Could not get pubkeys due to error: {e:?}\nDid you install the OS?")),
}
}
#[get("/authorized_keys")]
async fn get_authorized_keys(req: HttpRequest) -> HttpResponse {
if let Err(e) = verify(&req) {
return HttpResponse::BadRequest().body(format!("Signature verification failed: {}", e));
};
match os::list_ssh_keys() {
Ok(keys) => HttpResponse::Ok().body(keys),
Err(e) => HttpResponse::BadRequest().body(format!("{e:?}")),
}
}
#[post("/authorized_keys")]
async fn post_authorized_keys(req: HttpRequest, form: web::Form<SSHKeyForm>) -> HttpResponse {
if let Err(e) = verify(&req) {
return HttpResponse::BadRequest().body(format!("Signature verification failed: {}", e));
};
let ssh_key = &form.ssh_key;
match os::add_authorized_key(ssh_key) {
Ok(()) => HttpResponse::Ok().body("Key added to authorized_keys"),
Err(e) => HttpResponse::BadRequest().body(format!("{e:?}")),
}
}
fn load_rustls_config() -> rustls::ServerConfig {
rustls::crypto::aws_lc_rs::default_provider().install_default().unwrap();
let config = ServerConfig::builder().with_no_client_auth();
let cert_file = &mut BufReader::new(File::open(CRT_FILE).unwrap());
let key_file = &mut BufReader::new(File::open(KEY_FILE).unwrap());
let cert_chain = certs(cert_file).collect::<Result<Vec<_>, _>>().unwrap();
let mut keys = pkcs8_private_keys(key_file)
.map(|key| key.map(PrivateKeyDer::Pkcs8))
.collect::<Result<Vec<_>, _>>()
.unwrap();
if keys.is_empty() {
eprintln!("Could not locate PKCS 8 private keys.");
std::process::exit(1);
}
config.with_single_cert(cert_chain, keys.remove(0)).unwrap()
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
match os::try_hot_keyfile() {
Ok(_) => {
println!("Hot decryption successful. Booting OS...");
return Ok(());
}
Err(e) => {
println!("Hot decryption failed: {e:?}");
}
}
let config = load_rustls_config();
HttpServer::new(|| {
App::new()
.service(post_install_form)
.service(post_decrypt_form)
.service(post_process_exit)
.service(get_server_pubkeys)
.service(post_authorized_keys)
.service(get_authorized_keys)
.service(get_report)
.service(homepage)
})
.bind_rustls_0_23("[::]:22", config)?
.run()
.await
}

@ -1,164 +0,0 @@
// SPDX-License-Identifier: Unlicense
use crate::snp::get_derived_key;
use anyhow::{anyhow, Result};
use base64::prelude::{Engine, BASE64_URL_SAFE};
use std::{
fs::File,
io::{BufRead, BufReader, Write},
path::Path,
process::Command,
};
const SNP_KEYFILE_PATH: &str = "/tmp/detee_snp_keyfile";
const BACKUP_KEYFILE_PATH: &str = "/tmp/detee_backup_keyfile";
pub fn encrypt_and_install_os(
install_url: &str,
install_sha: &str,
keyfile: &str,
vm_hostname: &str,
) -> Result<String> {
let binary_keyfile = BASE64_URL_SAFE.decode(keyfile)?;
std::fs::write(BACKUP_KEYFILE_PATH, binary_keyfile)?;
// this path is hardcoded also in the initrd creation script
let install_result = Command::new("/usr/lib/dtrfs/install_os.sh")
.env("INSTALL_URL", install_url)
.env("INSTALL_SHA", install_sha)
.env("SNP_KEY_FILE", SNP_KEYFILE_PATH)
.env("ROOT_KEYFILE", BACKUP_KEYFILE_PATH)
.env("VM_HOSTNAME", vm_hostname)
.output()?;
if !install_result.status.success() {
return Err(anyhow!(
"OS installation script failed.\nScript stdout:\n{}\nScript stderr:\n{}",
String::from_utf8(install_result.stdout)
.unwrap_or("Could not grab stdout from installation script.".to_string()),
String::from_utf8(install_result.stderr)
.unwrap_or("Could not grab stderr from installation script.".to_string()),
));
}
Ok(format!(
"Successfully installed OS. Script stdout:\n{}\nScript stderr:\n{}",
String::from_utf8(install_result.stdout)
.unwrap_or("Could not grab stdout from installation script.".to_string()),
String::from_utf8(install_result.stderr)
.unwrap_or("Could not grab stderr from installation script.".to_string()),
))
}
pub fn try_hot_keyfile() -> Result<()> {
let hot_key = get_derived_key()?;
std::fs::write(SNP_KEYFILE_PATH, hot_key)?;
decrypt_and_mount(SNP_KEYFILE_PATH)?;
Ok(())
}
pub fn try_backup_keyfile(keyfile: &str) -> Result<String> {
let binary_keyfile = BASE64_URL_SAFE.decode(keyfile)?;
std::fs::write(BACKUP_KEYFILE_PATH, binary_keyfile)?;
decrypt_and_mount(BACKUP_KEYFILE_PATH)?;
Ok("Succesfully mounted /mnt using backup keyfile.".to_string())
}
fn decrypt_and_mount(keyfile_path: &str) -> Result<()> {
let decryption_result = Command::new("cryptsetup")
.arg("open")
.arg("--key-file")
.arg(keyfile_path)
.arg("/dev/vda1")
.arg("root")
.output()?;
if !decryption_result.status.success() {
return Err(anyhow!("Could not decrypt disk."));
}
let mount_result = Command::new("mount").arg("/dev/mapper/root").arg("/mnt").output()?;
if !mount_result.status.success() {
return Err(anyhow!("Could not mount /dev/mapper/root to /mnt"));
}
Ok(())
}
pub fn replace_hot_keyfile() -> Result<String> {
let _delete_old_keyfile = Command::new("cryptsetup")
.arg("luksKillSlot")
.arg("-d")
.arg(BACKUP_KEYFILE_PATH)
.arg("/dev/vda1")
.arg("1")
.output();
let meta = std::fs::metadata(SNP_KEYFILE_PATH)?;
if meta.len() == 0 {
return Err(anyhow!("Could not replace hot keyfile using SNP KDF."));
}
let _add_hot_keyfile = Command::new("cryptsetup")
.arg("luksAddKey")
.arg("--key-file")
.arg(BACKUP_KEYFILE_PATH)
.arg("--new-keyfile")
.arg(SNP_KEYFILE_PATH)
.arg("/dev/vda1")
.output();
Ok("Succesfully replaced hot keyfile using SNP KDF.".to_string())
}
pub fn add_authorized_key(key: &str) -> Result<()> {
use std::os::unix::fs::PermissionsExt;
if !Path::new("/mnt/etc/os-release").try_exists().is_ok_and(|found| found == true) {
return Err(anyhow!(
"Operating system not mounted. Please install OS or decrypt existing OS."
));
}
let encoded_key: Vec<&str> = key.split(" ").collect();
if encoded_key.len() < 2 {
return Err(anyhow!("Supplied key is expected to have at least two words."));
}
if let Ok(keys) = File::open("/mnt/.ssh/authorized_keys") {
let mut buffered = BufReader::new(keys).lines();
while let Some(Ok(k)) = buffered.next() {
if k.contains(encoded_key[1]) {
return Err(anyhow!("authorized_keys already contains {key}"));
}
}
} else {
std::fs::create_dir_all("/mnt/root/.ssh")?;
let permissions = std::fs::Permissions::from_mode(0o700);
std::fs::set_permissions("/mnt/root/.ssh", permissions)?;
std::fs::OpenOptions::new()
.create(true)
.write(true)
.open("/mnt/root/.ssh/authorized_keys")?;
let permissions = std::fs::Permissions::from_mode(0o600);
std::fs::set_permissions("/mnt/root/.ssh/authorized_keys", permissions)?;
}
let mut keys_file = std::fs::OpenOptions::new()
.append(true) // Open in append mode
.create(true) // Create the file if it doesn't exist
.open("/mnt/root/.ssh/authorized_keys")?;
writeln!(keys_file, "{key}")?;
Ok(())
}
pub fn get_server_ssh_pubkeys() -> Result<String> {
let files = vec![
"/mnt/etc/ssh/ssh_host_rsa_key.pub",
"/mnt/etc/ssh/ssh_host_ecdsa_key.pub",
"/mnt/etc/ssh/ssh_host_ed25519_key.pub",
];
Ok(files
.iter()
.map(|f| std::fs::read_to_string(f))
.collect::<Result<String, std::io::Error>>()?)
}
pub fn list_ssh_keys() -> Result<String> {
Ok(std::fs::read_to_string("/mnt/root/.ssh/authorized_keys")?)
}

@ -1,23 +0,0 @@
// SPDX-License-Identifier: Unlicense
use anyhow::{Context, Result};
use sev::firmware::guest::{AttestationReport, DerivedKey, Firmware, GuestFieldSelect};
use base64::prelude::{Engine, BASE64_URL_SAFE};
fn request_hardware_report(data: [u8; 64]) -> Result<AttestationReport> {
let mut fw = Firmware::open().context("unable to open /dev/sev-guest")?;
fw.get_report(None, Some(data), Some(0)).context("unable to fetch attestation report")
}
pub fn get_report_as_base64(data: [u8; 64]) -> Result<String> {
let report = request_hardware_report(data)?;
Ok(BASE64_URL_SAFE.encode(bincode::serialize(&report)?))
}
pub fn get_derived_key() -> Result<String> {
let mut fw = Firmware::open()?;
let request =
DerivedKey::new(false, GuestFieldSelect(u64::from_str_radix("11111", 2)?), 1, 0, 0);
let derived_key: [u8; 32] = fw.get_derived_key(None, request)?;
Ok(BASE64_URL_SAFE.encode(derived_key))
}

@ -1,8 +1,5 @@
#!/bin/bash #!/bin/bash
source /init_functions.sh
# SPDX-License-Identifier: Unlicense
source /usr/lib/dtrfs/init_functions.sh
install_url="/tmp/detee_install_url" install_url="/tmp/detee_install_url"
install_sha="/tmp/detee_install_sha" install_sha="/tmp/detee_install_sha"
@ -17,12 +14,28 @@ setup_network
# load this module again cause it fails the first time # load this module again cause it fails the first time
modprobe sev_guest modprobe sev_guest
create_certs snp_key="$(GET_DERIVATION_KEY=yes guest_api)"
dtrfs_api [[ -n $snp_key ]] && echo $snp_key > $snp_key_file
try_hot_decrypt || {
create_certs
guest_api
if [[ -f "$install_url" ]]; then
install_os
else
cryptsetup open -d $root_keyfile /dev/vda1 root
mount /dev/mapper/root /mnt
fi
cryptsetup luksKillSlot -d $root_keyfile /dev/vda1 1
[[ -f "$snp_key_file" ]] && cryptsetup luksAddKey \
--key-file $root_keyfile \
--new-keyfile $snp_key_file /dev/vda1
}
github_ssh_key github_ssh_key
cp /etc/resolv.conf /mnt/etc/resolv.conf detee_ssh_key
cp /etc/resolv.conf /mnt/etc/resolv.conf
# copy kernel modules in case the user deleted the old modules # copy kernel modules in case the user deleted the old modules
mkdir -p /mnt/lib/modules/ mkdir -p /mnt/lib/modules/
cp -rn /lib/modules/* /mnt/lib/modules/ cp -rn /lib/modules/* /mnt/lib/modules/

136
init_functions.sh Normal file

@ -0,0 +1,136 @@
#!/bin/bash
echo_blue() {
echo -e "\033[34m$1\033[0m"
}
echo_red() {
echo -e "\033[0;31m$1\033[0m"
}
load_modules() {
cat /load_modules.sh | bash
}
create_mounts() {
mount -t proc proc /proc -o nosuid,noexec,nodev
mount -t sysfs sys /sys -o nosuid,noexec,nodev
mount -t devtmpfs dev /dev -o mode=0755,nosuid
mount -t tmpfs run /run -o nosuid,nodev,mode=0755
mkdir -m755 /run/initramfs
if [ -e /sys/firmware/efi ]; then
mount -t efivarfs efivarfs /sys/firmware/efi/efivars -o nosuid,nodev,noexec
fi
# Setup /dev symlinks
if [ -e /proc/kcore ]; then
ln -sfT /proc/kcore /dev/core
fi
ln -sfT /proc/self/fd /dev/fd
ln -sfT /proc/self/fd/0 /dev/stdin
ln -sfT /proc/self/fd/1 /dev/stdout
ln -sfT /proc/self/fd/2 /dev/stderr
}
try_hot_decrypt() {
[[ -f "$snp_key_file" ]] && {
cryptsetup open --key-file $snp_key_file /dev/vda1 root || return 1
mount /dev/mapper/root /mnt || return 1
return 0
}
return 1
}
create_certs() {
cert_dir="/tmp/certs"
key="$cert_dir/guest_api.key"
cert="$cert_dir/guest_api.crt"
subject="/C=W3/O=DeTEE/OU=COCO/CN=guest-api"
mkdir -p "$cert_dir"
openssl genpkey -algorithm RSA -out "$key" \
-pkeyopt rsa_keygen_bits:4096 2>/dev/null
openssl req -x509 -new \
-key "$key" -out "$cert" \
-days 365 -subj "$subject" 2>/dev/null
}
# expects kernel param in this format: detee_net=192.168.122.140_24_192.168.122.1_1.1.1.1
setup_network() {
local settings='' ip_addr='' mask='' cidr='' gateway='' nameserver=''
settings=$(cat /proc/cmdline | grep -oE 'detee_net=[0-9a-z\_\:\.]+' | cut -d '=' -f2)
# TODO: replace with exit 0 when you are ready to force a kernel panic
[[ -z "$settings" ]] && return 0
settings="${settings#detee_net=}"
ip_addr="$( echo ${settings} | cut -d'_' -f1 )"
mask="$( echo ${settings} | cut -d'_' -f2 )"
cidr="${ip_addr}/${mask}"
gateway="$( echo ${settings} | cut -d'_' -f3 )"
nameserver="$( echo ${settings} | cut -d'_' -f4 )"
ip addr add $cidr dev eth0
ip link set eth0 up
ip route add default via $gateway
echo nameserver $nameserver > /etc/resolv.conf
sleep 2
ping -c 2 $gateway
}
install_os() {
local url="$(cat $install_url)" hostname=''
# mount root if it exists
blkid | grep vda1 | grep LUKS && {
cryptsetup open -d $root_keyfile /dev/vda1 root
mount /dev/mapper/root /mnt
return 0
}
# install OS if disk is empty
(
echo n
echo p
echo
echo
echo
echo w
) | fdisk /dev/vda
cryptsetup luksFormat --batch-mode -d $root_keyfile /dev/vda1
cryptsetup open -d $root_keyfile /dev/vda1 root
mkfs.ext4 /dev/mapper/root
mount /dev/mapper/root /mnt
wget -O /mnt/template.fsa "$url"
sha256sum /mnt/template.fsa | grep $(cat ${install_sha}) || exit 1
fsarchiver restdir /mnt/template.fsa /
rm /mnt/template.fsa
# TODO: decide for UX if maybe we should allow user to inject fstab
echo "" > /mnt/etc/fstab
hostname=$(cat /proc/cmdline | grep -oE 'detee_name=[0-9a-z\_\.\-]+' | cut -d '=' -f2)
[[ -n "$hostname" ]] && echo $hostname > /mnt/etc/hostname
}
# detee_ghu stands for GitHub user and expects format detee_ghu=ghe0
github_ssh_key() {
local key=''
github_user=$(cat /proc/cmdline | grep -oE 'detee_ghu=[0-9a-zA-Z\_\.\-]+' | cut -d '=' -f2)
[[ -z "$github_user" ]] && return 0
mkdir -p /mnt/root/.ssh
cd /mnt/root/.ssh
touch authorized_keys
key="$(wget -O - https://github.com/${github_user}.keys)"
grep -F "$( echo $key | awk '{ print $2 }' )" authorized_keys > /dev/null || {
echo "$key" >> authorized_keys
chmod 600 authorized_keys
}
}
# this can be injected through the guest_api
detee_ssh_key() {
local key=''
mkdir -p /mnt/root/.ssh
cd /mnt/root/.ssh
[[ -f "$ssh_key_file" ]] && while read -r key; do
grep -F "$( echo $key | awk '{ print $2 }' )" authorized_keys > /dev/null || {
echo "$key" >> authorized_keys
}
done < "$ssh_key_file"
chmod 600 authorized_keys
}

@ -1,7 +1,4 @@
#!/bin/bash #!/bin/bash
# SPDX-License-Identifier: Unlicense
cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" cd -- "$( dirname -- "${BASH_SOURCE[0]}" )"
dir="/tmp/dtrfs" dir="/tmp/dtrfs"
@ -33,16 +30,8 @@ fi
set -e set -e
# TODO: test if this works mkdir -p tmp
mkdir -p tmp build tar cf tmp/dtrfs.tar *.sh
my_location="$(pwd)"
cd ../dtrfs_api && cargo build --release || {
echo Could not build dtrfs_api
exit 1
}
cd "$my_location"
cp ../dtrfs_api/target/release/dtrfs_api ./build/
tar cf tmp/dtrfs.tar *.sh build/dtrfs_api arch_guest_mods
$ssh $server rm -rf ${dir} $ssh $server rm -rf ${dir}
$ssh $server mkdir -p ${dir} $ssh $server mkdir -p ${dir}
$scp tmp/dtrfs.tar ${scp_server}:${dir} $scp tmp/dtrfs.tar ${scp_server}:${dir}

@ -1,100 +0,0 @@
#!/bin/bash
# SPDX-License-Identifier: Unlicense
load_modules() {
cat /load_modules.sh | bash
}
create_mounts() {
mount -t proc proc /proc -o nosuid,noexec,nodev
mount -t sysfs sys /sys -o nosuid,noexec,nodev
mount -t devtmpfs dev /dev -o mode=0755,nosuid
mount -t tmpfs run /run -o nosuid,nodev,mode=0755
mkdir -m755 /run/initramfs
if [ -e /sys/firmware/efi ]; then
mount -t efivarfs efivarfs /sys/firmware/efi/efivars -o nosuid,nodev,noexec
fi
# Setup /dev symlinks
if [ -e /proc/kcore ]; then
ln -sfT /proc/kcore /dev/core
fi
ln -sfT /proc/self/fd /dev/fd
ln -sfT /proc/self/fd/0 /dev/stdin
ln -sfT /proc/self/fd/1 /dev/stdout
ln -sfT /proc/self/fd/2 /dev/stderr
}
create_certs() {
cert_dir="/tmp/certs"
key="$cert_dir/dtrfs_api.key"
cert="$cert_dir/dtrfs_api.crt"
subject="/C=W3/O=DeTEE/OU=COCO/CN=dtrfs-api"
mkdir -p "$cert_dir"
openssl genpkey -algorithm RSA -out "$key" \
-pkeyopt rsa_keygen_bits:4096 2>/dev/null
openssl req -x509 -new \
-key "$key" -out "$cert" \
-addext "subjectAltName=DNS:dtrfs-api" \
-days 365 -subj "$subject" 2>/dev/null
}
setup_network_device() {
local device="$1" settings="$2" ip_addr='' mask='' cidr='' gateway=''
[[ -z "$settings" ]] && return 0
ip_addr="$( echo ${settings} | cut -d '_' -f1 )"
mask="$( echo ${settings} | cut -d '_' -f2 )"
cidr="${ip_addr}/${mask}"
gateway="$( echo ${settings} | cut -d '_' -f3 )"
ip addr add $cidr dev $device
ip link set $device up
sysctl -w net.ipv6.conf.$device.accept_ra=0
ip route add default via $gateway
sleep 2
ping -c 2 $gateway
}
# Expects kernel param in this format: detee_net=192.168.122.140_24_192.168.122.1_1.1.1.1
# In case the interface name is not specified, it defaults to eth0
# Supports manual device, for example: detee_net_eth1
# TODO: test if it is required to specify mac instead of device name
setup_network() {
local device_eth0_cfg='' device_cfg='' device_configs=''
sysctl -w net.ipv6.conf.all.accept_ra=0
sysctl -w net.ipv6.conf.default.accept_ra=0
# handle the default: detee_net=...
device_eth0_cfg=$(cat /proc/cmdline | grep -oE "detee_net=[0-9a-f\_\:\.]+" | cut -d '=' -f2)
[[ -z "$device_eth0_cfg" ]] || setup_network_device eth0 $device_eth0_cfg
# handle extra devices: detee_net_eth1=...
device_configs=$(cat /proc/cmdline| grep -oE "detee_net_[a-z0-9]*=[0-9a-f\_\:\.]+")
while read -r device_cfg; do
setup_network_device \
$(echo $device_cfg | cut -d '=' -f1 | cut -d '_' -f3) \
$(echo $device_cfg | cut -d '=' -f2)
done <<< "$( echo "$device_configs" )"
echo nameserver 1.1.1.1 >> /etc/resolv.conf
echo nameserver 1.0.0.1 >> /etc/resolv.conf
echo nameserver 2606:4700:4700::1111 >> /etc/resolv.conf
echo nameserver 2606:4700:4700::1001 >> /etc/resolv.conf
}
# detee_ghu stands for GitHub user and expects format detee_ghu=ghe0
github_ssh_key() {
local key=''
github_user=$(cat /proc/cmdline | grep -oE 'detee_ghu=[0-9a-zA-Z\_\.\-]+' | cut -d '=' -f2)
[[ -z "$github_user" ]] && return 0
mkdir -p /mnt/root/.ssh
cd /mnt/root/.ssh
touch authorized_keys
key="$(wget -O - https://github.com/${github_user}.keys)"
grep -F "$( echo $key | awk '{ print $2 }' )" authorized_keys > /dev/null || {
echo "$key" >> authorized_keys
chmod 600 authorized_keys
}
}

@ -1,76 +0,0 @@
#!/bin/bash
# SPDX-License-Identifier: Unlicense
# This script is called by dtrfs_api to install an OS.
[[ -z "$INSTALL_URL" ]] && {
echo "Did not find INSTALL_URL env variable".
exit 1
}
[[ -z "$INSTALL_SHA" ]] && {
echo "Did not find INSTALL_SHA env variable".
exit 2
}
[[ -z "$VM_HOSTNAME" ]] && {
echo "Did not find VM_HOSTNAME env variable".
exit 2
}
[[ -f "$ROOT_KEYFILE" ]] || {
echo "Did not find keyfile at the following location: $ROOT_KEYFILE"
exit 3
}
# mount root if it exists
blkid | grep vda1 | grep LUKS && {
echo "/dev/vda1 already has a LUKS partition"
exit 4
}
echo === Creating partition /dev/vda1
(
echo n
echo p
echo
echo
echo
echo w
) | fdisk /dev/vda
echo "=== Formatting /dev/vda1 using cryptsetup luksFormat and opening as root"
cryptsetup luksFormat --batch-mode -d $ROOT_KEYFILE /dev/vda1 || exit 5
[[ -f "$SNP_KEY_FILE" ]] && {
echo "Adding LUKS slot via SNP KDF key found at $SNP_KEY_FILE"
cryptsetup luksAddKey \
--key-file $ROOT_KEYFILE \
--new-keyfile $SNP_KEY_FILE /dev/vda1
}
cryptsetup open -d $ROOT_KEYFILE /dev/vda1 root || exit 6
echo "=== Formatting /dev/mapper/root as ext4 and mounting at /mnt"
mkfs.ext4 /dev/mapper/root || exit 7
mount /dev/mapper/root /mnt || exit 8
echo "=== Downloading OS template from $INSTALL_URL and verifying hash"
wget -O /mnt/template.fsa "$INSTALL_URL" || {
echo "Failed to download $INSTALL_URL"
exit 9
}
sha256sum /mnt/template.fsa | grep "${INSTALL_SHA}" || exit 1
echo "=== Installing OS template"
fsarchiver restdir /mnt/template.fsa /
rm /mnt/template.fsa
# TODO: decide for UX if maybe we should allow user to inject fstab
echo "" > /mnt/etc/fstab
echo "=== Setting up guest hostname as $VM_HOSTNAME"
echo $VM_HOSTNAME > /mnt/etc/hostname
echo "=== Generating SSH public keys"
echo "root:x:0:0:root:/root:/bin/sh" > /etc/passwd
[[ -f "/mnt/etc/ssh/ssh_host_rsa_key" ]] ||
ssh-keygen -t rsa -f /mnt/etc/ssh/ssh_host_rsa_key -N '' > /dev/null
[[ -f "/mnt/etc/ssh/ssh_host_ecdsa_key" ]] ||
ssh-keygen -t ecdsa -f /mnt/etc/ssh/ssh_host_ecdsa_key -N '' > /dev/null
[[ -f "/mnt/etc/ssh/ssh_host_ed25519_key" ]] ||
ssh-keygen -t ed25519 -f /mnt/etc/ssh/ssh_host_ed25519_key -N '' > /dev/null
echo "=== Done! Download keys from /server_pubkeys"

@ -1,27 +0,0 @@
#!/bin/bash
# SPDX-License-Identifier: Unlicense
kernel_path="/boot/vmlinuz-linux"
dtrfs_path="$1"
dtrfs_name=$(basename $dtrfs_path)
dtrfs_sha=$(sha256sum $dtrfs_path | awk '{ print $1 }')
kernel_name="vmlinuz-linux-$(uname -r)"
kernel_sha=$(sha256sum $kernel_path | awk '{ print $1 }')
scp $dtrfs_path registry.detee.ltd:/var/www/html/${dtrfs_name}
ssh registry.detee.ltd ln -s $dtrfs_name /var/www/html/${dtrfs_sha}
scp $kernel_path registry.detee.ltd:/var/www/html/${kernel_name}
ssh registry.detee.ltd ln -s $kernel_name /var/www/html/${kernel_sha}
echo "Also add this to detee-cli/src/snp/mod.rs"
echo "
name: \"dtrfs-$(uname -r)\".to_string(),
vendor: \"ghe0\".to_string(),
dtrfs_url: \"http://registry.detee.ltd/${dtrfs_name}\".to_string(),
dtrfs_sha: \"${dtrfs_sha}\".to_string(),
kernel_url: \"http://registry.detee.ltd/${kernel_name}\".to_string(),
kernel_sha: \"${kernel_sha}\".to_string()
"