aboutsummaryrefslogtreecommitdiff
path: root/src/crypto/signed.rs
blob: 436f3d12118f80e72f4af86588135d9641cf5fd3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
use hex;
use hmac::{Hmac, Mac};
use sha2::Sha256;
use std::str;
use std::time::{SystemTime, UNIX_EPOCH};

const SEP: &str = "-";

pub fn sign(key: &str, raw: &str) -> Result<String, String> {
    let nonce = get_nonce()?;
    let joined = format!("{nonce}{SEP}{raw}");
    let signature = get_signature(key, &joined)?;
    Ok(format!("{signature}{SEP}{joined}"))
}

pub fn verify(key: &str, signed: &str) -> Result<String, String> {
    let mut iter = signed.split(SEP);
    match (iter.next(), iter.next()) {
        (Some(signature), Some(nonce)) => {
            let raw = iter.collect::<Vec<&str>>().join(SEP);
            if signature == get_signature(key, &format!("{nonce}{SEP}{raw}"))? {
                Ok(raw)
            } else {
                Err("Signature does not match".to_string())
            }
        }
        _ => Err("Malformed signed".to_string()),
    }
}

fn get_signature(key: &str, message: &str) -> Result<String, String> {
    let mut mac = Hmac::<Sha256>::new_from_slice(key.as_bytes())
        .map_err(|e| format!("Error initializing MAC: {e}"))?;
    mac.update(message.as_bytes());
    let result = mac.finalize();
    Ok(hex::encode(result.into_bytes()))
}

fn get_nonce() -> Result<String, String> {
    Ok(SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map_err(|e| format!("Failure getting unix expoch: {e}"))?
        .as_millis()
        .to_string())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn sign_and_validate() {
        let key = "xagrlBUobnTj32Rm8tvmsZ6mh8qLfip5".to_string();
        assert_eq!(verify(&key, &sign(&key, "").unwrap()), Ok("".to_string()));
        assert_eq!(
            verify(&key, &sign(&key, "hello").unwrap()),
            Ok("hello".to_string())
        );
        assert_eq!(
            verify(&key, &sign(&key, "with-sep").unwrap()),
            Ok("with-sep".to_string())
        );
    }

    #[test]
    fn fail_when_key_mismatch() {
        let key1 = "xagrlBUobnTj32Rm8tvmsZ6mh8qLfip5".to_string();
        let key2 = "8KJBK6axEr9wQ390GgdWA8Pjn8FwILDa".to_string();
        assert!(verify(&key1, &sign(&key2, "hello").unwrap()).is_err());
    }
}