this took forever
This commit is contained in:
parent
7864c53236
commit
d2b6b83950
59
dtrfs_api/Cargo.lock
generated
59
dtrfs_api/Cargo.lock
generated
@ -1,6 +1,6 @@
|
|||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 4
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "actix-codec"
|
name = "actix-codec"
|
||||||
@ -36,7 +36,7 @@ dependencies = [
|
|||||||
"brotli",
|
"brotli",
|
||||||
"bytes",
|
"bytes",
|
||||||
"bytestring",
|
"bytestring",
|
||||||
"derive_more",
|
"derive_more 0.99.18",
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"flate2",
|
"flate2",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
@ -172,7 +172,7 @@ dependencies = [
|
|||||||
"bytestring",
|
"bytestring",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cookie",
|
"cookie",
|
||||||
"derive_more",
|
"derive_more 0.99.18",
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@ -258,12 +258,6 @@ dependencies = [
|
|||||||
"alloc-no-stdlib",
|
"alloc-no-stdlib",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anyhow"
|
|
||||||
version = "1.0.93"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
@ -494,6 +488,15 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "convert_case"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-segmentation",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cookie"
|
name = "cookie"
|
||||||
version = "0.16.2"
|
version = "0.16.2"
|
||||||
@ -611,13 +614,35 @@ version = "0.99.18"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce"
|
checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"convert_case",
|
"convert_case 0.4.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"rustc_version",
|
"rustc_version",
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_more"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05"
|
||||||
|
dependencies = [
|
||||||
|
"derive_more-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_more-impl"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22"
|
||||||
|
dependencies = [
|
||||||
|
"convert_case 0.6.0",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"unicode-xid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.10.7"
|
version = "0.10.7"
|
||||||
@ -667,9 +692,9 @@ name = "dtrfs_api"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"anyhow",
|
|
||||||
"base64",
|
"base64",
|
||||||
"bincode",
|
"bincode",
|
||||||
|
"derive_more 1.0.0",
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"regex",
|
"regex",
|
||||||
@ -2175,6 +2200,18 @@ version = "1.0.14"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-segmentation"
|
||||||
|
version = "1.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-xid"
|
||||||
|
version = "0.2.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "untrusted"
|
name = "untrusted"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
@ -4,9 +4,9 @@ version = "0.1.0"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.93"
|
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
bincode = "1.3.3"
|
bincode = "1.3.3"
|
||||||
|
derive_more = {version = "1.0.0", features = ["full"] }
|
||||||
regex = "1.11.1"
|
regex = "1.11.1"
|
||||||
sev = { version = "4.0", default-features = false, features = ['crypto_nossl','snp'] }
|
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", features = ["pem", "pkcs8"] }
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
mod os;
|
mod os;
|
||||||
mod snp;
|
mod snp;
|
||||||
|
|
||||||
use actix_web::{get, post, web, App, HttpRequest, HttpResponse, HttpServer};
|
use crate::os::OsError;
|
||||||
|
use actix_web::{get, post, web, App, Error, HttpRequest, HttpResponse, HttpServer, ResponseError};
|
||||||
use base64::prelude::{Engine, BASE64_URL_SAFE};
|
use base64::prelude::{Engine, BASE64_URL_SAFE};
|
||||||
|
use derive_more::derive::{Display, Error, From};
|
||||||
use ed25519_dalek::{pkcs8::DecodePublicKey, Signature, Verifier, VerifyingKey};
|
use ed25519_dalek::{pkcs8::DecodePublicKey, Signature, Verifier, VerifyingKey};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
@ -15,50 +17,96 @@ use std::{
|
|||||||
io::{BufReader, Read},
|
io::{BufReader, Read},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Display, From, Error)]
|
||||||
|
pub enum DtrfsError {
|
||||||
|
#[display("OS error: {_0}")]
|
||||||
|
OsError(#[from] OsError),
|
||||||
|
#[display("SNP error: {_0}")]
|
||||||
|
SnpError(#[from] snp::SNPError),
|
||||||
|
#[display("Could not find admin key in cmdline")]
|
||||||
|
AdminKeyNotFound,
|
||||||
|
#[display("Could not parse verifying key: {_0}")]
|
||||||
|
VerifyingKeyParsingError(ed25519_dalek::pkcs8::spki::Error),
|
||||||
|
#[display("Could not get signature from request")]
|
||||||
|
SignatureNotFound,
|
||||||
|
#[display("Base64 decoding error: {_0}")]
|
||||||
|
Base64Error(base64::DecodeError),
|
||||||
|
#[display("IO error: {_0}")]
|
||||||
|
IoError(#[from] std::io::Error),
|
||||||
|
#[display("Error slicing into bytes: {_0}")]
|
||||||
|
SliceError(std::array::TryFromSliceError),
|
||||||
|
#[display("Error verifying signature: {_0}")]
|
||||||
|
SignatureVerificationError(ed25519_dalek::SignatureError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResponseError for DtrfsError {
|
||||||
|
fn error_response(&self) -> HttpResponse {
|
||||||
|
match self {
|
||||||
|
error => HttpResponse::InternalServerError().body(format!("{}", error)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const CRT_FILE: &str = "/tmp/certs/dtrfs_api.crt";
|
const CRT_FILE: &str = "/tmp/certs/dtrfs_api.crt";
|
||||||
const KEY_FILE: &str = "/tmp/certs/dtrfs_api.key";
|
const KEY_FILE: &str = "/tmp/certs/dtrfs_api.key";
|
||||||
const CMDLINE_FILE: &str = "/proc/cmdline";
|
const CMDLINE_FILE: &str = "/proc/cmdline";
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref SNP_REPORT: String = snp::get_report_as_base64(get_cert_hash()).unwrap();
|
static ref SNP_REPORT: String = match get_cert_hash()
|
||||||
|
.and_then(|hash| snp::get_report_as_base64(hash).map_err(|err| err.into()))
|
||||||
|
{
|
||||||
|
Ok(report) => report,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to get SNP report: {}", e);
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
};
|
||||||
static ref CRT_CONTENTS: String = {
|
static ref CRT_CONTENTS: String = {
|
||||||
let mut msg = String::new();
|
let mut msg = String::new();
|
||||||
let _ = BufReader::new(File::open(CRT_FILE).unwrap()).read_to_string(&mut msg);
|
if let Err(e) =
|
||||||
|
File::open(CRT_FILE).and_then(|file| BufReader::new(file).read_to_string(&mut msg))
|
||||||
|
{
|
||||||
|
eprintln!("Failed to read certificate file: {}", e);
|
||||||
|
}
|
||||||
msg
|
msg
|
||||||
};
|
};
|
||||||
static ref CMDLINE: String = {
|
static ref CMDLINE: String = {
|
||||||
let mut cmdline = String::new();
|
let mut cmdline = String::new();
|
||||||
let _ = BufReader::new(File::open(CMDLINE_FILE).unwrap()).read_to_string(&mut cmdline);
|
if let Err(e) = File::open(CMDLINE_FILE)
|
||||||
|
.and_then(|file| BufReader::new(file).read_to_string(&mut cmdline))
|
||||||
|
{
|
||||||
|
eprintln!("Failed to read cmdline file: {}", e);
|
||||||
|
}
|
||||||
cmdline
|
cmdline
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_cert_hash() -> [u8; 64] {
|
fn get_cert_hash() -> Result<[u8; 64], DtrfsError> {
|
||||||
let mut hasher = Sha3_512::new();
|
let mut hasher = Sha3_512::new();
|
||||||
let crt = File::open(CRT_FILE).expect("Could not open crt file.");
|
let crt = File::open(CRT_FILE)?;
|
||||||
let mut buf_reader = BufReader::new(crt);
|
let mut buf_reader = BufReader::new(crt);
|
||||||
let mut buffer = Vec::new();
|
let mut buffer = Vec::new();
|
||||||
buf_reader.read_to_end(&mut buffer).expect("Could not read certificate.");
|
buf_reader.read_to_end(&mut buffer)?;
|
||||||
hasher.update(buffer);
|
hasher.update(buffer);
|
||||||
let crt_hash = hasher.finalize();
|
let crt_hash: [u8; 64] = hasher.finalize().into();
|
||||||
crt_hash.into()
|
Ok(crt_hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verifying_key() -> Result<VerifyingKey, Box<dyn std::error::Error>> {
|
fn verifying_key() -> Result<VerifyingKey, DtrfsError> {
|
||||||
let re = Regex::new(r"detee_admin=([A-Za-z0-9+/=]+)").unwrap();
|
let re = Regex::new(r"detee_admin=([A-Za-z0-9+/=]+)").unwrap(); //unwrap here is ok since the regex is hardcoded and can only fail if the regex is bigger than the configured size limit via RegexBuilder::size_limit
|
||||||
let key_str = re.find(&CMDLINE).map(|m| m.as_str()).unwrap_or("");
|
let key_str = re.find(&CMDLINE).map(|m| m.as_str()).unwrap_or("");
|
||||||
let key_pem = format!(
|
let key_pem = format!(
|
||||||
"-----BEGIN PUBLIC KEY-----\n{}\n-----END PUBLIC KEY-----\n",
|
"-----BEGIN PUBLIC KEY-----\n{}\n-----END PUBLIC KEY-----\n",
|
||||||
key_str.strip_prefix("detee_admin=").ok_or("Could not get admin key from cmdline")?
|
key_str.strip_prefix("detee_admin=").ok_or(DtrfsError::AdminKeyNotFound)?
|
||||||
);
|
);
|
||||||
Ok(VerifyingKey::from_public_key_pem(&key_pem)?)
|
Ok(VerifyingKey::from_public_key_pem(&key_pem)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify(req: &HttpRequest) -> Result<(), Box<dyn std::error::Error>> {
|
fn verify(req: &HttpRequest) -> Result<(), DtrfsError> {
|
||||||
let signature = req
|
let signature = req
|
||||||
.headers()
|
.headers()
|
||||||
.get("ed25519-signature")
|
.get("ed25519-signature")
|
||||||
.ok_or_else(|| "Did not find ed25519-signature header")?;
|
.ok_or_else(|| DtrfsError::SignatureNotFound)?;
|
||||||
|
|
||||||
let signature: &[u8] = &BASE64_URL_SAFE.decode(signature)?;
|
let signature: &[u8] = &BASE64_URL_SAFE.decode(signature)?;
|
||||||
let signature = Signature::from_bytes(signature.try_into()?);
|
let signature = Signature::from_bytes(signature.try_into()?);
|
||||||
@ -89,14 +137,14 @@ struct InstallForm {
|
|||||||
|
|
||||||
// TODO: QA this function to make sure we don't accidentally allow empty string keyfile
|
// TODO: QA this function to make sure we don't accidentally allow empty string keyfile
|
||||||
#[post("/install")]
|
#[post("/install")]
|
||||||
async fn post_install_form(req: HttpRequest, form: web::Form<InstallForm>) -> HttpResponse {
|
async fn post_install_form(
|
||||||
if let Err(e) = verify(&req) {
|
req: HttpRequest,
|
||||||
return HttpResponse::BadRequest().body(format!("Signature verification failed: {}", e));
|
form: web::Form<InstallForm>,
|
||||||
};
|
) -> Result<HttpResponse, Error> {
|
||||||
match os::encrypt_and_install_os(&form.url, &form.sha, &form.keyfile) {
|
verify(&req)?;// TODO: This is temporary, we need to merget from the other branch
|
||||||
Ok(s) => HttpResponse::Ok().body(s),
|
os::encrypt_and_install_os(&form.url, &form.sha, &form.keyfile)
|
||||||
Err(e) => HttpResponse::InternalServerError().body(format!("{e:?}")),
|
.map(|msg| HttpResponse::Ok().body(msg))
|
||||||
}
|
.map_err(|e| DtrfsError::OsError(e).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
@ -106,24 +154,18 @@ struct DecryptForm {
|
|||||||
|
|
||||||
// TODO: QA this function to make sure we don't accidentally allow empty string keyfile
|
// TODO: QA this function to make sure we don't accidentally allow empty string keyfile
|
||||||
#[post("/decrypt")]
|
#[post("/decrypt")]
|
||||||
async fn post_decrypt_form(req: HttpRequest, form: web::Form<DecryptForm>) -> HttpResponse {
|
async fn post_decrypt_form(req: HttpRequest, form: web::Form<DecryptForm>) -> Result<HttpResponse, Error> {
|
||||||
if let Err(e) = verify(&req) {
|
verify(&req)?;
|
||||||
return HttpResponse::BadRequest().body(format!("Signature verification failed: {}", e));
|
|
||||||
};
|
let decrypt_result = os::try_backup_keyfile(&form.keyfile).map_err(DtrfsError::from)?;
|
||||||
let decrypt_result = os::try_backup_keyfile(&form.keyfile);
|
let hot_key_result = os::replace_hot_keyfile().map_err(DtrfsError::from)?;
|
||||||
if let Err(decryption_error) = decrypt_result {
|
|
||||||
return HttpResponse::BadRequest()
|
Ok(HttpResponse::Ok().body(format!("{:?}\n{:?}", decrypt_result, hot_key_result)))
|
||||||
.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")]
|
#[post("/switch_root")]
|
||||||
async fn post_process_exit(req: HttpRequest) -> HttpResponse {
|
async fn post_process_exit(req: HttpRequest) -> Result<HttpResponse, Error> {
|
||||||
if let Err(e) = verify(&req) {
|
verify(&req)?;
|
||||||
return HttpResponse::BadRequest().body(format!("Signature verification failed: {}", e));
|
|
||||||
};
|
|
||||||
std::process::exit(0);
|
std::process::exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,26 +175,22 @@ struct SSHKeyForm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/ssh_key")]
|
#[get("/ssh_key")]
|
||||||
async fn get_ssh_keys(req: HttpRequest) -> HttpResponse {
|
async fn get_ssh_keys(req: HttpRequest) -> Result<HttpResponse, Error> {
|
||||||
if let Err(e) = verify(&req) {
|
verify(&req)?;
|
||||||
return HttpResponse::BadRequest().body(format!("Signature verification failed: {}", e));
|
os::list_ssh_keys()
|
||||||
};
|
.map(|keys| HttpResponse::Ok().body(keys))
|
||||||
match os::list_ssh_keys() {
|
.map_err(|e| DtrfsError::OsError(e).into())
|
||||||
Ok(keys) => HttpResponse::Ok().body(keys),
|
|
||||||
Err(e) => HttpResponse::BadRequest().body(format!("{e:?}")),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/ssh_key")]
|
#[post("/ssh_key")]
|
||||||
async fn post_ssh_key(req: HttpRequest, form: web::Form<SSHKeyForm>) -> HttpResponse {
|
async fn post_ssh_key(
|
||||||
if let Err(e) = verify(&req) {
|
req: HttpRequest,
|
||||||
return HttpResponse::BadRequest().body(format!("Signature verification failed: {}", e));
|
form: web::Form<SSHKeyForm>,
|
||||||
};
|
) -> Result<HttpResponse, Error> {
|
||||||
|
verify(&req)?;
|
||||||
let ssh_key = &form.ssh_key;
|
let ssh_key = &form.ssh_key;
|
||||||
match os::add_ssh_key(ssh_key) {
|
os::add_ssh_key(ssh_key).map_err(DtrfsError::from)?;
|
||||||
Ok(()) => HttpResponse::Ok().body("Key added to authorized_keys"),
|
Ok(HttpResponse::Ok().body("SSH key added successfully"))
|
||||||
Err(e) => HttpResponse::BadRequest().body(format!("{e:?}")),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_rustls_config() -> rustls::ServerConfig {
|
fn load_rustls_config() -> rustls::ServerConfig {
|
||||||
@ -183,7 +221,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
println!("Hot decryption successful. Booting OS...");
|
println!("Hot decryption successful. Booting OS...");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
},
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Hot decryption failed: {e:?}");
|
println!("Hot decryption failed: {e:?}");
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,45 @@
|
|||||||
use crate::snp::get_derived_key;
|
use crate::snp::{get_derived_key, SNPError};
|
||||||
use anyhow::{anyhow, Result};
|
use base64::{
|
||||||
use base64::prelude::{Engine, BASE64_URL_SAFE};
|
prelude::{Engine, BASE64_URL_SAFE},
|
||||||
|
DecodeError,
|
||||||
|
};
|
||||||
|
use derive_more::{Display, Error, From};
|
||||||
use std::{
|
use std::{
|
||||||
fs::File,
|
fs::File,
|
||||||
io::{BufRead, BufReader, Write},
|
io::{self, BufRead, BufReader, Write},
|
||||||
path::Path,
|
path::Path,
|
||||||
process::Command,
|
process::Command,
|
||||||
|
string::FromUtf8Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Display, Error, From)]
|
||||||
|
pub enum OsError {
|
||||||
|
#[display(
|
||||||
|
"OS installation script failed.\nScript stdout:\n{stdout}\nScript stderr:\n{stderr}"
|
||||||
|
)]
|
||||||
|
InstallationFailed { stdout: String, stderr: String },
|
||||||
|
#[display("Could not decrypt disk.")]
|
||||||
|
DecryptionFailed,
|
||||||
|
#[display("Could not mount /dev/mapper/root to /mnt")]
|
||||||
|
MountFailed,
|
||||||
|
#[display("Could not try hot keyfile: {_0}")]
|
||||||
|
TryHotKeyfileFailed(#[from] SNPError),
|
||||||
|
#[display("Could not replace hot keyfile using SNP KDF.")]
|
||||||
|
ReplaceHotKeyfileFailed,
|
||||||
|
#[display("Operating system not mounted. Please install OS or decrypt existing OS.")]
|
||||||
|
OsNotMounted,
|
||||||
|
#[display("Supplied key is expected to have at least two words.")]
|
||||||
|
InvalidSshKey,
|
||||||
|
#[display("authorized_keys already contains {err}")]
|
||||||
|
SshKeyAlreadyExists { err: String },
|
||||||
|
#[display("I/O error: {_0}")]
|
||||||
|
IoError(#[from] io::Error),
|
||||||
|
#[display("Base64 decoding error: {_0}")]
|
||||||
|
Base64Error(#[from] DecodeError),
|
||||||
|
#[display("UTF-8 conversion error: {_0}")]
|
||||||
|
Utf8Error(#[from] FromUtf8Error),
|
||||||
|
}
|
||||||
|
|
||||||
const SNP_KEYFILE_PATH: &str = "/tmp/detee_snp_keyfile";
|
const SNP_KEYFILE_PATH: &str = "/tmp/detee_snp_keyfile";
|
||||||
const BACKUP_KEYFILE_PATH: &str = "/tmp/detee_backup_keyfile";
|
const BACKUP_KEYFILE_PATH: &str = "/tmp/detee_backup_keyfile";
|
||||||
|
|
||||||
@ -15,7 +47,7 @@ pub fn encrypt_and_install_os(
|
|||||||
install_url: &str,
|
install_url: &str,
|
||||||
install_sha: &str,
|
install_sha: &str,
|
||||||
keyfile: &str,
|
keyfile: &str,
|
||||||
) -> Result<String> {
|
) -> Result<String, OsError> {
|
||||||
let binary_keyfile = BASE64_URL_SAFE.decode(keyfile)?;
|
let binary_keyfile = BASE64_URL_SAFE.decode(keyfile)?;
|
||||||
std::fs::write(BACKUP_KEYFILE_PATH, binary_keyfile)?;
|
std::fs::write(BACKUP_KEYFILE_PATH, binary_keyfile)?;
|
||||||
// this path is hardcoded also in the initrd creation script
|
// this path is hardcoded also in the initrd creation script
|
||||||
@ -27,14 +59,14 @@ pub fn encrypt_and_install_os(
|
|||||||
.output()?;
|
.output()?;
|
||||||
|
|
||||||
if !install_result.status.success() {
|
if !install_result.status.success() {
|
||||||
return Err(anyhow!(
|
return Err(OsError::InstallationFailed {
|
||||||
"OS installation script failed.\nScript stdout:\n{}\nScript stderr:\n{}",
|
stdout: String::from_utf8(install_result.stdout)
|
||||||
String::from_utf8(install_result.stdout)
|
|
||||||
.unwrap_or("Could not grab stdout from installation script.".to_string()),
|
.unwrap_or("Could not grab stdout from installation script.".to_string()),
|
||||||
String::from_utf8(install_result.stderr)
|
stderr: String::from_utf8(install_result.stderr)
|
||||||
.unwrap_or("Could not grab stderr from installation script.".to_string()),
|
.unwrap_or("Could not grab stderr from installation script.".to_string()),
|
||||||
));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(format!(
|
Ok(format!(
|
||||||
"Successfully installed OS. Script stdout:\n{}\nScript stderr:\n{}",
|
"Successfully installed OS. Script stdout:\n{}\nScript stderr:\n{}",
|
||||||
String::from_utf8(install_result.stdout)
|
String::from_utf8(install_result.stdout)
|
||||||
@ -44,21 +76,21 @@ pub fn encrypt_and_install_os(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_hot_keyfile() -> Result<()> {
|
pub fn try_hot_keyfile() -> Result<(), OsError> {
|
||||||
let hot_key = get_derived_key()?;
|
let hot_key = get_derived_key()?;
|
||||||
std::fs::write(SNP_KEYFILE_PATH, hot_key)?;
|
std::fs::write(SNP_KEYFILE_PATH, hot_key)?;
|
||||||
decrypt_and_mount(SNP_KEYFILE_PATH)?;
|
decrypt_and_mount(SNP_KEYFILE_PATH)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_backup_keyfile(keyfile: &str) -> Result<String> {
|
pub fn try_backup_keyfile(keyfile: &str) -> Result<String, OsError> {
|
||||||
let binary_keyfile = BASE64_URL_SAFE.decode(keyfile)?;
|
let binary_keyfile = BASE64_URL_SAFE.decode(keyfile)?;
|
||||||
std::fs::write(BACKUP_KEYFILE_PATH, binary_keyfile)?;
|
std::fs::write(BACKUP_KEYFILE_PATH, binary_keyfile)?;
|
||||||
decrypt_and_mount(BACKUP_KEYFILE_PATH)?;
|
decrypt_and_mount(BACKUP_KEYFILE_PATH)?;
|
||||||
Ok("Succesfully mounted /mnt using backup keyfile.".to_string())
|
Ok("Succesfully mounted /mnt using backup keyfile.".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decrypt_and_mount(keyfile_path: &str) -> Result<()> {
|
fn decrypt_and_mount(keyfile_path: &str) -> Result<(), OsError> {
|
||||||
let decryption_result = Command::new("cryptsetup")
|
let decryption_result = Command::new("cryptsetup")
|
||||||
.arg("open")
|
.arg("open")
|
||||||
.arg("--key-file")
|
.arg("--key-file")
|
||||||
@ -67,27 +99,27 @@ fn decrypt_and_mount(keyfile_path: &str) -> Result<()> {
|
|||||||
.arg("root")
|
.arg("root")
|
||||||
.output()?;
|
.output()?;
|
||||||
if !decryption_result.status.success() {
|
if !decryption_result.status.success() {
|
||||||
return Err(anyhow!("Could not decrypt disk."));
|
return Err(OsError::DecryptionFailed);
|
||||||
}
|
}
|
||||||
let mount_result = Command::new("mount").arg("/dev/mapper/root").arg("/mnt").output()?;
|
let mount_result = Command::new("mount").arg("/dev/mapper/root").arg("/mnt").output()?;
|
||||||
if !mount_result.status.success() {
|
if !mount_result.status.success() {
|
||||||
return Err(anyhow!("Could not mount /dev/mapper/root to /mnt"));
|
return Err(OsError::MountFailed);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn replace_hot_keyfile() -> Result<String> {
|
pub fn replace_hot_keyfile() -> Result<String, OsError> {
|
||||||
let _delete_old_keyfile = Command::new("cryptsetup")
|
let _delete_old_keyfile = Command::new("cryptsetup")
|
||||||
.arg("luksKillSlot")
|
.arg("luksKillSlot")
|
||||||
.arg("-d")
|
.arg("-d")
|
||||||
.arg(BACKUP_KEYFILE_PATH)
|
.arg(BACKUP_KEYFILE_PATH)
|
||||||
.arg("/dev/vda1")
|
.arg("/dev/vda1")
|
||||||
.arg("1")
|
.arg("1")
|
||||||
.output();
|
.output()?;
|
||||||
|
|
||||||
let meta = std::fs::metadata(SNP_KEYFILE_PATH)?;
|
let meta = std::fs::metadata(SNP_KEYFILE_PATH)?;
|
||||||
if meta.len() == 0 {
|
if meta.len() == 0 {
|
||||||
return Err(anyhow!("Could not replace hot keyfile using SNP KDF."));
|
return Err(OsError::ReplaceHotKeyfileFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
let _add_hot_keyfile = Command::new("cryptsetup")
|
let _add_hot_keyfile = Command::new("cryptsetup")
|
||||||
@ -97,28 +129,26 @@ pub fn replace_hot_keyfile() -> Result<String> {
|
|||||||
.arg("--new-keyfile")
|
.arg("--new-keyfile")
|
||||||
.arg(SNP_KEYFILE_PATH)
|
.arg(SNP_KEYFILE_PATH)
|
||||||
.arg("/dev/vda1")
|
.arg("/dev/vda1")
|
||||||
.output();
|
.output()?;
|
||||||
|
|
||||||
Ok("Succesfully replaced hot keyfile using SNP KDF.".to_string())
|
Ok("Succesfully replaced hot keyfile using SNP KDF.".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_ssh_key(key: &str) -> Result<()> {
|
pub fn add_ssh_key(key: &str) -> Result<(), OsError> {
|
||||||
use std::os::unix::fs::PermissionsExt;
|
use std::os::unix::fs::PermissionsExt;
|
||||||
if !Path::new("/mnt/etc/os-release").try_exists().is_ok_and(|found| found == true) {
|
if !Path::new("/mnt/etc/os-release").try_exists().is_ok_and(|found| found == true) {
|
||||||
return Err(anyhow!(
|
return Err(OsError::OsNotMounted);
|
||||||
"Operating system not mounted. Please install OS or decrypt existing OS."
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let encoded_key: Vec<&str> = key.split(" ").collect();
|
let encoded_key: Vec<&str> = key.split(" ").collect();
|
||||||
if encoded_key.len() < 2 {
|
if encoded_key.len() < 2 {
|
||||||
return Err(anyhow!("Supplied key is expected to have at least two words."));
|
return Err(OsError::InvalidSshKey);
|
||||||
}
|
}
|
||||||
if let Ok(keys) = File::open("/mnt/.ssh/authorized_keys") {
|
if let Ok(keys) = File::open("/mnt/root/.ssh/authorized_keys") {
|
||||||
let mut buffered = BufReader::new(keys).lines();
|
let mut buffered = BufReader::new(keys).lines();
|
||||||
while let Some(Ok(k)) = buffered.next() {
|
while let Some(Ok(k)) = buffered.next() {
|
||||||
if k.contains(encoded_key[1]) {
|
if k.contains(encoded_key[1]) {
|
||||||
return Err(anyhow!("authorized_keys already contains {key}"));
|
return Err(OsError::SshKeyAlreadyExists { err: key.to_string() });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -142,6 +172,6 @@ pub fn add_ssh_key(key: &str) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_ssh_keys() -> Result<String> {
|
pub fn list_ssh_keys() -> Result<String, OsError> {
|
||||||
Ok(std::fs::read_to_string("/mnt/root/.ssh/authorized_keys")?)
|
Ok(std::fs::read_to_string("/mnt/root/.ssh/authorized_keys")?)
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,31 @@
|
|||||||
use anyhow::{Context, Result};
|
|
||||||
use sev::firmware::guest::{AttestationReport, DerivedKey, Firmware, GuestFieldSelect};
|
|
||||||
use base64::prelude::{Engine, BASE64_URL_SAFE};
|
use base64::prelude::{Engine, BASE64_URL_SAFE};
|
||||||
|
use derive_more::{Display, Error, From};
|
||||||
|
use sev::error::UserApiError;
|
||||||
|
use sev::firmware::guest::{AttestationReport, DerivedKey, Firmware, GuestFieldSelect};
|
||||||
|
|
||||||
fn request_hardware_report(data: [u8; 64]) -> Result<AttestationReport> {
|
#[derive(Debug, Display, From, Error)]
|
||||||
let mut fw = Firmware::open().context("unable to open /dev/sev-guest")?;
|
pub enum SNPError {
|
||||||
fw.get_report(None, Some(data), Some(0)).context("unable to fetch attestation report")
|
#[display("Could not parse the derived key: {_0}")]
|
||||||
|
KeyParsingError(#[from] std::num::ParseIntError),
|
||||||
|
#[display("authorized_keys already contains: {_0}")]
|
||||||
|
UserApiError(#[from] UserApiError),
|
||||||
|
#[display("I/O error: {_0}")]
|
||||||
|
FirmwareIOError(#[from] std::io::Error),
|
||||||
|
#[display("bincode Base64 decoding error: {_0}")]
|
||||||
|
Base64Error(#[from] bincode::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_report_as_base64(data: [u8; 64]) -> Result<String> {
|
fn request_hardware_report(data: [u8; 64]) -> Result<AttestationReport, SNPError> {
|
||||||
|
let mut fw = Firmware::open()?;
|
||||||
|
Ok(fw.get_report(None, Some(data), Some(0))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_report_as_base64(data: [u8; 64]) -> Result<String, SNPError> {
|
||||||
let report = request_hardware_report(data)?;
|
let report = request_hardware_report(data)?;
|
||||||
Ok(BASE64_URL_SAFE.encode(bincode::serialize(&report)?))
|
Ok(BASE64_URL_SAFE.encode(bincode::serialize(&report)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_derived_key() -> Result<String> {
|
pub fn get_derived_key() -> Result<String, SNPError> {
|
||||||
let mut fw = Firmware::open()?;
|
let mut fw = Firmware::open()?;
|
||||||
let request =
|
let request =
|
||||||
DerivedKey::new(false, GuestFieldSelect(u64::from_str_radix("11111", 2)?), 1, 0, 0);
|
DerivedKey::new(false, GuestFieldSelect(u64::from_str_radix("11111", 2)?), 1, 0, 0);
|
||||||
|
Loading…
Reference in New Issue
Block a user