use super::SeaError;
use base64::prelude::*;
use p256::ecdsa::{Signature, VerifyingKey, signature::Verifier};
use serde_json::Value;
use std::convert::TryInto;
pub fn verify_sync(signed_data: &Value, pub_key: &str) -> Result<Value, SeaError> {
let message = signed_data
.get("m")
.and_then(|v| v.as_str())
.ok_or(SeaError::VerificationFailed)?;
let signature = signed_data
.get("s")
.and_then(|v| v.as_str())
.ok_or(SeaError::VerificationFailed)?;
let parts: Vec<&str> = pub_key.split('.').collect();
if parts.len() != 2 {
return Err(SeaError::InvalidKey);
}
let x = BASE64_URL_SAFE_NO_PAD
.decode(parts[0])
.map_err(|_| SeaError::InvalidKey)?;
let y = BASE64_URL_SAFE_NO_PAD
.decode(parts[1])
.map_err(|_| SeaError::InvalidKey)?;
let mut pub_bytes: Vec<u8> = Vec::with_capacity(65);
pub_bytes.push(0x04);
pub_bytes.extend_from_slice(&x);
pub_bytes.extend_from_slice(&y);
let verifying_key = VerifyingKey::from_sec1_bytes(&pub_bytes)
.map_err(|e| SeaError::Crypto(format!("invalid public key: {}", e)))?;
let sig_bytes = BASE64_URL_SAFE_NO_PAD
.decode(signature)
.map_err(|_| SeaError::VerificationFailed)?;
if sig_bytes.len() != 64 {
return Err(SeaError::VerificationFailed);
}
let sig_array: [u8; 64] = sig_bytes
.try_into()
.map_err(|_| SeaError::VerificationFailed)?;
let signature = Signature::from_slice(&sig_array).map_err(|_| SeaError::VerificationFailed)?;
verifying_key
.verify(message.as_bytes(), &signature)
.map_err(|_| SeaError::VerificationFailed)?;
serde_json::from_str(message).map_err(|e| SeaError::Crypto(format!("invalid JSON: {}", e)))
}
pub async fn verify_async(signed_data: &Value, pub_key: &str) -> Result<Value, SeaError> {
let data = signed_data.clone();
let key = pub_key.to_string();
tokio::task::spawn_blocking(move || verify_sync(&data, &key))
.await
.map_err(|e| SeaError::Crypto(format!("task join error: {}", e)))?
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sea::{generate_pair, sign};
use serde_json::json;
#[tokio::test]
async fn test_verify_sync_valid() {
let pair = generate_pair().await.unwrap();
let data = json!({"hello": "world"});
let signed = sign(&data, &pair).await.unwrap();
let verified = verify_sync(&signed, &pair.pub_key).unwrap();
assert_eq!(verified, data);
}
#[tokio::test]
async fn test_verify_sync_wrong_key() {
let pair_a = generate_pair().await.unwrap();
let pair_b = generate_pair().await.unwrap();
let data = json!({"secret": "data"});
let signed = sign(&data, &pair_a).await.unwrap();
assert!(verify_sync(&signed, &pair_b.pub_key).is_err());
}
#[tokio::test]
async fn test_verify_async_valid() {
let pair = generate_pair().await.unwrap();
let data = json!({"async": true});
let signed = sign(&data, &pair).await.unwrap();
let verified = verify_async(&signed, &pair.pub_key).await.unwrap();
assert_eq!(verified, data);
}
#[tokio::test]
async fn test_verify_tampered_message() {
let pair = generate_pair().await.unwrap();
let data = json!({"original": true});
let mut signed = sign(&data, &pair).await.unwrap();
let tampered = serde_json::to_string(&json!({"tampered": true})).unwrap();
signed["m"] = json!(tampered);
assert!(verify_sync(&signed, &pair.pub_key).is_err());
}
#[tokio::test]
async fn test_verify_missing_m_field() {
let pair = generate_pair().await.unwrap();
let bad = json!({"s": "some_sig"});
assert!(verify_sync(&bad, &pair.pub_key).is_err());
}
#[tokio::test]
async fn test_verify_missing_s_field() {
let pair = generate_pair().await.unwrap();
let bad = json!({"m": "some_msg"});
assert!(verify_sync(&bad, &pair.pub_key).is_err());
}
#[tokio::test]
async fn test_verify_malformed_pub_key() {
let pair = generate_pair().await.unwrap();
let data = json!({"test": 1});
let signed = sign(&data, &pair).await.unwrap();
assert!(verify_sync(&signed, "invalidkey").is_err());
}
#[tokio::test]
async fn test_verify_short_signature() {
let pair = generate_pair().await.unwrap();
let signed = json!({
"m": "{}",
"s": "short"
});
assert!(verify_sync(&signed, &pair.pub_key).is_err());
}
}