90 lines
2.5 KiB
Rust
90 lines
2.5 KiB
Rust
use hmac::{Hmac, Mac};
|
|
use serde_json::Value;
|
|
use sha2::{Digest, Sha256};
|
|
use std::fmt::Write as _;
|
|
|
|
use crate::pipe::Action;
|
|
use crate::security::SecurityError;
|
|
|
|
type HmacSha256 = Hmac<Sha256>;
|
|
|
|
pub fn derive_session_key(hmac_seed: &str) -> Result<Vec<u8>, SecurityError> {
|
|
let seed = hex::decode(hmac_seed)?;
|
|
if seed.is_empty() {
|
|
return Err(SecurityError::InvalidSeed(
|
|
"hmac_seed must not be empty".to_string(),
|
|
));
|
|
}
|
|
|
|
let mut hasher = Sha256::new();
|
|
hasher.update(seed);
|
|
hasher.update(b"sgclaw-session-v1");
|
|
Ok(hasher.finalize().to_vec())
|
|
}
|
|
|
|
pub fn sign_command(
|
|
session_key: &[u8],
|
|
seq: u64,
|
|
action: &Action,
|
|
params: &Value,
|
|
expected_domain: &str,
|
|
) -> Result<String, SecurityError> {
|
|
if session_key.is_empty() {
|
|
return Err(SecurityError::InvalidSeed(
|
|
"session key must not be empty".to_string(),
|
|
));
|
|
}
|
|
|
|
let mut mac = HmacSha256::new_from_slice(session_key)
|
|
.map_err(|err| SecurityError::Hmac(err.to_string()))?;
|
|
let canonical = format!(
|
|
"{}\n{}\n{}\n{}",
|
|
seq,
|
|
action.as_str(),
|
|
stable_json_string(params)?,
|
|
expected_domain
|
|
);
|
|
mac.update(canonical.as_bytes());
|
|
|
|
Ok(hex::encode(mac.finalize().into_bytes()))
|
|
}
|
|
|
|
fn stable_json_string(value: &Value) -> Result<String, SecurityError> {
|
|
match value {
|
|
Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) => {
|
|
serde_json::to_string(value).map_err(SecurityError::from)
|
|
}
|
|
Value::Array(items) => {
|
|
let mut out = String::from("[");
|
|
for (index, item) in items.iter().enumerate() {
|
|
if index > 0 {
|
|
out.push(',');
|
|
}
|
|
out.push_str(&stable_json_string(item)?);
|
|
}
|
|
out.push(']');
|
|
Ok(out)
|
|
}
|
|
Value::Object(map) => {
|
|
let mut keys: Vec<&str> = map.keys().map(String::as_str).collect();
|
|
keys.sort_unstable();
|
|
|
|
let mut out = String::from("{");
|
|
for (index, key) in keys.iter().enumerate() {
|
|
if index > 0 {
|
|
out.push(',');
|
|
}
|
|
write!(
|
|
out,
|
|
"{}:{}",
|
|
serde_json::to_string(key)?,
|
|
stable_json_string(&map[*key])?
|
|
)
|
|
.map_err(|err| SecurityError::Hmac(err.to_string()))?;
|
|
}
|
|
out.push('}');
|
|
Ok(out)
|
|
}
|
|
}
|
|
}
|