use crate::{AppState, binary::analyze_binary};
use axum::{
extract::{Multipart, State},
http::StatusCode,
response::Json,
};
use base64::{Engine as _, engine::general_purpose};
use chrono::Utc;
use hex;
use serde_json::{Value, json};
use sha2::{Digest, Sha256};
use ed25519_dalek::Signer;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
pub struct ErrorResponse {
pub error: String,
pub message: String,
}
pub async fn attest_binary(
State(_state): State<AppState>,
mut multipart: Multipart,
) -> Result<Json<Value>, (StatusCode, Json<ErrorResponse>)> {
let mut file_bytes = None;
let mut file_name = None;
let mut signing_key_bytes = None;
while let Some(field) = multipart.next_field().await.unwrap_or(None) {
let name = field.name().unwrap_or("").to_string();
match name.as_str() {
"file" => {
file_name = field.file_name().map(str::to_string);
file_bytes = Some(field.bytes().await.unwrap_or_default());
}
"signing_key" => {
signing_key_bytes = Some(field.bytes().await.unwrap_or_default());
}
_ => {}
}
}
let file_bytes = match file_bytes {
Some(bytes) if !bytes.is_empty() => bytes,
_ => {
return Err((
StatusCode::BAD_REQUEST,
Json(ErrorResponse {
error: "missing_file".to_string(),
message: "Missing or empty binary".to_string(),
}),
));
}
};
if signing_key_bytes.is_none() {
return Err((StatusCode::BAD_REQUEST, Json(ErrorResponse {
error: "signing_key_required".to_string(),
message: "Signing key is required for binary attestation. Please provide a valid signing key.".to_string(),
})));
}
let mut hasher = Sha256::new();
hasher.update(&file_bytes);
let hash = hasher.finalize();
let _encoded_hash = general_purpose::STANDARD.encode(&hash);
let analysis_struct = match analyze_binary(
file_name.as_deref().unwrap_or("uploaded-binary"),
&file_bytes,
)
.await
{
Ok(data) => data,
Err(err) => {
tracing::error!("Analysis failed: {:?}", err);
return Err((
StatusCode::INTERNAL_SERVER_ERROR,
Json(ErrorResponse {
error: "analysis_failed".to_string(),
message: "Analysis failed".to_string(),
}),
));
}
};
let analysis = match serde_json::to_value(&analysis_struct) {
Ok(val) => val,
Err(err) => {
tracing::error!("Serialization failed: {:?}", err);
return Err((
StatusCode::INTERNAL_SERVER_ERROR,
Json(ErrorResponse {
error: "serialization_failed".to_string(),
message: "Serialization failed".to_string(),
}),
));
}
};
let encoded_hash = hex::encode(&hash);
let attestation = json!({
"_type": "https://in-toto.io/Statement/v0.1",
"subject": [{
"name": file_name.unwrap_or_else(|| "uploaded-binary".into()),
"digest": {
"sha256": encoded_hash
}
}],
"predicateType": "https://www.usenabla.com/attestation/v0.1",
"predicate": {
"timestamp": Utc::now().to_rfc3339(),
"analysis": analysis
}
});
let signing_key = signing_key_bytes.unwrap();
match create_signed_attestation(&attestation, &signing_key) {
Ok(signed_attestation) => Ok(Json(signed_attestation)),
Err(err) => {
tracing::error!("Signing failed: {:?}", err);
Err((
StatusCode::INTERNAL_SERVER_ERROR,
Json(ErrorResponse {
error: "signing_failed".to_string(),
message: "Signing failed".to_string(),
}),
))
}
}
}
fn create_signed_attestation(
attestation: &serde_json::Value,
signing_key: &[u8],
) -> anyhow::Result<serde_json::Value> {
let key_pair = match parse_signing_key(signing_key) {
Ok(key) => key,
Err(_) => {
return Err(anyhow::anyhow!("Invalid signing key format"));
}
};
let attestation_bytes = serde_json::to_vec(attestation)?;
let signature = key_pair
.try_sign(&attestation_bytes)
.map_err(|e| anyhow::anyhow!("Signing failed: {}", e))?;
let signature_b64 = general_purpose::STANDARD.encode(signature.to_bytes());
let mut signed_attestation = attestation.clone();
let signatures = json!([{
"keyid": "keyid123", "sig": signature_b64,
"cert": "certificate_here" }]);
if let Some(obj) = signed_attestation.as_object_mut() {
obj.insert("signatures".to_string(), signatures);
}
Ok(signed_attestation)
}
fn parse_signing_key(key_bytes: &[u8]) -> anyhow::Result<ed25519_dalek::SigningKey> {
let key_str = String::from_utf8_lossy(key_bytes);
let pem_content = if key_str.contains("-----BEGIN PRIVATE KEY-----") {
let lines: Vec<&str> = key_str.lines().collect();
let mut key_content = String::new();
let mut in_key = false;
for line in lines {
if line.contains("-----BEGIN PRIVATE KEY-----") {
in_key = true;
continue;
}
if line.contains("-----END PRIVATE KEY-----") {
break;
}
if in_key {
key_content.push_str(line);
}
}
general_purpose::STANDARD.decode(key_content.as_bytes())?
} else {
general_purpose::STANDARD.decode(key_bytes)?
};
if pem_content.len() != 32 {
return Err(anyhow::anyhow!(
"Invalid key length: expected 32 bytes, got {}",
pem_content.len()
));
}
let mut key_array = [0u8; 32];
key_array.copy_from_slice(&pem_content);
let signing_key = ed25519_dalek::SigningKey::from_bytes(&key_array);
Ok(signing_key)
}