Compare commits

...

10 Commits

18 changed files with 238 additions and 27 deletions

2
.gitignore vendored

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

24
LICENSE Normal file

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

29
dtrfs_api/Cargo.lock generated

@ -1,6 +1,8 @@
# SPDX-License-Identifier: Unlicense
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "actix-codec"
@ -409,6 +411,15 @@ dependencies = [
"alloc-stdlib",
]
[[package]]
name = "bs58"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4"
dependencies = [
"tinyvec",
]
[[package]]
name = "byteorder"
version = "1.5.0"
@ -670,6 +681,7 @@ dependencies = [
"anyhow",
"base64",
"bincode",
"bs58",
"ed25519-dalek",
"lazy_static",
"regex",
@ -2081,6 +2093,21 @@ dependencies = [
"zerovec",
]
[[package]]
name = "tinyvec"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tls_codec"
version = "0.4.1"

@ -1,18 +1,21 @@
# SPDX-License-Identifier: Unlicense
[package]
name = "dtrfs_api"
version = "0.1.0"
edition = "2021"
[dependencies]
bs58 = "0.5.1"
anyhow = "1.0.93"
base64 = "0.22.1"
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", features = ["pem", "pkcs8"] }
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,3 +1,5 @@
# SPDX-License-Identifier: Unlicense
reorder_impl_items = true
use_small_heuristics = "Max"
merge_imports = true

@ -1,9 +1,10 @@
// SPDX-License-Identifier: Unlicense
mod os;
mod snp;
use actix_web::{get, post, web, App, HttpRequest, HttpResponse, HttpServer};
use base64::prelude::{Engine, BASE64_URL_SAFE};
use ed25519_dalek::{pkcs8::DecodePublicKey, Signature, Verifier, VerifyingKey};
use ed25519_dalek::{Signature, Verifier, VerifyingKey};
use lazy_static::lazy_static;
use regex::Regex;
use rustls::{pki_types::PrivateKeyDer, ServerConfig};
@ -45,13 +46,16 @@ fn get_cert_hash() -> [u8; 64] {
}
fn verifying_key() -> Result<VerifyingKey, Box<dyn std::error::Error>> {
let re = Regex::new(r"detee_admin=([A-Za-z0-9+/=]+)").unwrap();
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_pem = format!(
"-----BEGIN PUBLIC KEY-----\n{}\n-----END PUBLIC KEY-----\n",
key_str.strip_prefix("detee_admin=").ok_or("Could not get admin key from cmdline")?
);
Ok(VerifyingKey::from_public_key_pem(&key_pem)?)
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>> {
@ -60,8 +64,8 @@ fn verify(req: &HttpRequest) -> Result<(), Box<dyn std::error::Error>> {
.get("ed25519-signature")
.ok_or_else(|| "Did not find ed25519-signature header")?;
let signature: &[u8] = &BASE64_URL_SAFE.decode(signature)?;
let signature = Signature::from_bytes(signature.try_into()?);
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)?)
}
@ -82,6 +86,7 @@ async fn get_report() -> HttpResponse {
#[derive(Deserialize)]
struct InstallForm {
hostname: String,
url: String,
sha: String,
keyfile: String,
@ -93,7 +98,7 @@ async fn post_install_form(req: HttpRequest, form: web::Form<InstallForm>) -> Ht
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) {
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:?}")),
}
@ -132,8 +137,17 @@ struct SSHKeyForm {
ssh_key: String,
}
#[get("/ssh_key")]
async fn get_ssh_keys(req: HttpRequest) -> HttpResponse {
#[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));
};
@ -143,13 +157,13 @@ async fn get_ssh_keys(req: HttpRequest) -> HttpResponse {
}
}
#[post("/ssh_key")]
async fn post_ssh_key(req: HttpRequest, form: web::Form<SSHKeyForm>) -> HttpResponse {
#[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_ssh_key(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:?}")),
}
@ -183,7 +197,7 @@ async fn main() -> std::io::Result<()> {
Ok(_) => {
println!("Hot decryption successful. Booting OS...");
return Ok(());
},
}
Err(e) => {
println!("Hot decryption failed: {e:?}");
}
@ -194,8 +208,9 @@ async fn main() -> std::io::Result<()> {
.service(post_install_form)
.service(post_decrypt_form)
.service(post_process_exit)
.service(post_ssh_key)
.service(get_ssh_keys)
.service(get_server_pubkeys)
.service(post_authorized_keys)
.service(get_authorized_keys)
.service(get_report)
.service(homepage)
})

@ -1,3 +1,5 @@
// SPDX-License-Identifier: Unlicense
use crate::snp::get_derived_key;
use anyhow::{anyhow, Result};
use base64::prelude::{Engine, BASE64_URL_SAFE};
@ -15,6 +17,7 @@ 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)?;
@ -24,6 +27,7 @@ pub fn encrypt_and_install_os(
.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() {
@ -102,7 +106,7 @@ pub fn replace_hot_keyfile() -> Result<String> {
Ok("Succesfully replaced hot keyfile using SNP KDF.".to_string())
}
pub fn add_ssh_key(key: &str) -> Result<()> {
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!(
@ -142,6 +146,19 @@ pub fn add_ssh_key(key: &str) -> Result<()> {
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,3 +1,5 @@
// SPDX-License-Identifier: Unlicense
use anyhow::{Context, Result};
use sev::firmware::guest::{AttestationReport, DerivedKey, Firmware, GuestFieldSelect};
use base64::prelude::{Engine, BASE64_URL_SAFE};

@ -3,6 +3,8 @@ asn1_encoder
async_tx
async_xor
atkbd
bridge
br_netfilter
cbc
cdrom
crc16
@ -12,6 +14,7 @@ crc32_pclmul
crct10dif_pclmul
cryptd
crypto_simd
curve25519_x86_64
dm_bufio
dm_crypt
dm-integrity
@ -27,23 +30,53 @@ i2c_i801
i2c_mux
i2c_smbus
i8042
inet_diag
intel_agp
intel_gtt
intel_pmc_bxt
intel_rapl_common
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
ipt_REJECT
iTCO_vendor_support
iTCO_wdt
jbd2
libaesgcm
libchacha20poly1305
libcrc32c
libcurve25519_generic
libps2
llc
loop
lpc_ich
mac_hid
mbcache
mousedev
net_failover
nf_conntrack
nf_conntrack_netlink
nf_defrag_ipv4
nf_defrag_ipv6
nf_nat
nfnetlink
nfnetlink_acct
nfnetlink_log
nf_reject_ipv4
nf_tables
nft_chain_nat
nft_compat
overlay
parport
parport_pc
pcspkr
@ -61,9 +94,13 @@ sha256
sha256_ssse3
sha512_ssse3
sr_mod
stp
tcp_diag
tee
trusted
tsm
udp_tunnel
veth
virtio_blk
virtio_net
vivaldi_fmap
@ -72,5 +109,23 @@ vmw_vsock_virtio_transport_common
vmw_vsock_vmci_transport
vsock
vsock_loopback
vxlan
wireguard
xfrm_algo
xfrm_user
xor
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,4 +1,7 @@
#!/bin/bash
# SPDX-License-Identifier: Unlicense
cd -- "$( dirname -- "${BASH_SOURCE[0]}" )"
source creator_exports.sh
source creator_functions.sh
@ -21,6 +24,7 @@ install_binary $(which blkid)
install_binary $(which fdisk)
install_binary $(which sysctl)
install_binary $(which mkfs.ext4)
install_binary $(which ssh-keygen)
install_binary $(which fsarchiver)
install_kmod
install_busybox

@ -1,4 +1,7 @@
#!/bin/bash
# SPDX-License-Identifier: Unlicense
script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
# root of the initrd, that will be used to create the cpio archive

@ -1,5 +1,7 @@
#!/bin/bash
# SPDX-License-Identifier: Unlicense
echo_cyan() {
echo -e "\033[0;36m$1\033[0m"
}

@ -1,4 +1,7 @@
#!/bin/bash
# SPDX-License-Identifier: Unlicense
source /usr/lib/dtrfs/init_functions.sh
install_url="/tmp/detee_install_url"

@ -1,5 +1,7 @@
#!/bin/bash
# SPDX-License-Identifier: Unlicense
load_modules() {
cat /load_modules.sh | bash
}

@ -1,5 +1,7 @@
#!/bin/bash
# SPDX-License-Identifier: Unlicense
# This script is called by dtrfs_api to install an OS.
[[ -z "$INSTALL_URL" ]] && {
@ -7,11 +9,16 @@
exit 1
}
[[ -z "$INSTALL_URL" ]] && {
[[ -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
@ -55,6 +62,15 @@ 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)
echo "=== Setting up guest hostname as $hostname"
[[ -n "$hostname" ]] && echo $hostname > /mnt/etc/hostname
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,4 +1,7 @@
#!/bin/bash
# SPDX-License-Identifier: Unlicense
cd -- "$( dirname -- "${BASH_SOURCE[0]}" )"
dir="/tmp/dtrfs"

27
scripts/upload_to_registry.sh Executable file

@ -0,0 +1,27 @@
#!/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()
"