Skip to main content

idempotent_proxy_types/
auth.rs

1use ciborium::{from_reader, into_writer};
2use ed25519_dalek::Signer;
3use k256::{
4    ecdsa,
5    ecdsa::signature::hazmat::{PrehashSigner, PrehashVerifier},
6};
7use serde::{Deserialize, Serialize};
8use serde_bytes::ByteBuf;
9use sha3::{Digest, Sha3_256};
10
11use crate::unix_ms;
12
13const PERMITTED_DRIFT: u64 = 10; // seconds
14
15// Token format: [expire_at in seconds, agent, signature]
16#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)]
17pub struct Token(pub u64, pub String, pub ByteBuf);
18
19pub fn ed25519_sign(key: &ed25519_dalek::SigningKey, expire_at: u64, agent: String) -> Vec<u8> {
20    let mut buf: Vec<u8> = Vec::new();
21    into_writer(&(expire_at, &agent), &mut buf).expect("failed to encode data in CBOR format");
22
23    let sig = key.sign(&buf).to_bytes();
24    buf.clear();
25    into_writer(&(expire_at, agent, ByteBuf::from(sig)), &mut buf)
26        .expect("failed to encode in CBOR format");
27    buf
28}
29
30pub fn ed25519_verify(keys: &[ed25519_dalek::VerifyingKey], data: &[u8]) -> Result<Token, String> {
31    let token: Token = from_reader(data).map_err(|_err| "failed to decode CBOR data")?;
32    if token.0 + PERMITTED_DRIFT < unix_ms() / 1000 {
33        return Err("token expired".to_string());
34    }
35    let sig = ed25519_dalek::Signature::from_slice(token.2.as_slice())
36        .map_err(|_err| "failed to parse Ed25519 signature")?;
37    let mut buf: Vec<u8> = Vec::new();
38    into_writer(&(token.0, &token.1), &mut buf).expect("failed to encode data in CBOR format");
39    for key in keys.iter() {
40        if key.verify_strict(&buf, &sig).is_ok() {
41            return Ok(token);
42        }
43    }
44
45    Err("failed to verify Ed25519 signature".to_string())
46}
47
48// Secp256k1
49pub fn ecdsa_sign(key: &ecdsa::SigningKey, expire_at: u64, agent: String) -> Vec<u8> {
50    let mut buf: Vec<u8> = Vec::new();
51    into_writer(&(expire_at, &agent), &mut buf).expect("failed to encode data in CBOR format");
52    let digest = sha3_256(&buf);
53    let sig: ecdsa::Signature = key
54        .sign_prehash(&digest)
55        .expect("failed to sign Secp256k1 signature");
56    buf.clear();
57    into_writer(&(expire_at, agent, ByteBuf::from(sig.to_vec())), &mut buf)
58        .expect("failed to encode in CBOR format");
59    buf
60}
61
62// Secp256k1
63pub fn ecdsa_verify(keys: &[ecdsa::VerifyingKey], data: &[u8]) -> Result<Token, String> {
64    let token: Token = from_reader(data).map_err(|_err| "failed to decode CBOR data")?;
65    if token.0 + PERMITTED_DRIFT < unix_ms() / 1000 {
66        return Err("token expired".to_string());
67    }
68    let sig = ecdsa::Signature::try_from(token.2.as_slice())
69        .map_err(|_err| "failed to parse Secp256k1 signature")?;
70    let mut buf: Vec<u8> = Vec::new();
71    into_writer(&(token.0, &token.1), &mut buf).expect("failed to encode data in CBOR format");
72    let digest = sha3_256(&buf);
73
74    for key in keys.iter() {
75        if key.verify_prehash(digest.as_slice(), &sig).is_ok() {
76            return Ok(token);
77        }
78    }
79
80    Err("failed to verify ECDSA/Secp256k1 signature".to_string())
81}
82
83pub fn sha3_256(data: &[u8]) -> [u8; 32] {
84    let mut hasher = Sha3_256::new();
85    hasher.update(data);
86    hasher.finalize().into()
87}
88
89#[cfg(test)]
90mod test {
91    use super::*;
92    use base64::{engine::general_purpose, Engine};
93    use k256::PublicKey;
94    use rand_core::{OsRng, RngCore};
95
96    #[test]
97    fn test_ed25519_token() {
98        let mut secret_key = [0u8; 32];
99        OsRng.fill_bytes(&mut secret_key);
100        let signing_key: ed25519_dalek::SigningKey =
101            ed25519_dalek::SigningKey::from_bytes(&secret_key);
102        let agent = "alice".to_string();
103        let expire_at = unix_ms() / 1000 + 3600;
104        let signed = super::ed25519_sign(&signing_key, expire_at, agent.clone());
105        let token = super::ed25519_verify(&[signing_key.verifying_key()], &signed).unwrap();
106        assert_eq!(token.0, expire_at);
107        assert_eq!(token.1, agent);
108    }
109
110    #[test]
111    #[ignore]
112    fn test_secp256k1_token() {
113        let signing_key = ecdsa::SigningKey::random(&mut OsRng);
114        let agent = "alice".to_string();
115        let expire_at = unix_ms() / 1000 + 3600;
116        let signed = super::ecdsa_sign(&signing_key, expire_at, agent.clone());
117        let token =
118            super::ecdsa_verify(&[ecdsa::VerifyingKey::from(&signing_key)], &signed).unwrap();
119        assert_eq!(token.0, expire_at);
120        assert_eq!(token.1, agent);
121
122        println!(
123            "token: {:?}",
124            general_purpose::URL_SAFE_NO_PAD.encode(&signed)
125        );
126
127        let pk: PublicKey = signing_key.verifying_key().into();
128        let pk = pk.to_sec1_bytes();
129        let pk = general_purpose::URL_SAFE_NO_PAD.encode(&pk);
130        println!("{:?}", pk);
131
132        // let pk = general_purpose::URL_SAFE_NO_PAD
133        //     .decode("A44DZpzDwDvq9HwW3_dynOfDgkMJHKgOxUyCOrv5Pl3O")
134        //     .expect("invalid base64");
135        // let pk = ecdsa::VerifyingKey::from_sec1_bytes(&pk).expect("invalid ecdsa public key");
136        // let data = general_purpose::URL_SAFE_NO_PAD
137        //     .decode("gxpmZDmJaklDUGFuZGFEQU9YQMQr36UI8JV2jJEM_PMe96GsgymHzjfsbZyAsFSHF0FUNsuj6LKsqHg2dzYG9RoxQRtrcGsphYsNiQJwG3g9Ju4")
138        //     .expect("invalid base64");
139        // let token = super::ecdsa_verify(&[pk], &data).unwrap();
140        println!("{:?}", token);
141        // Token(1717844361, "ICPandaDAO", [196, 43, 223, ... 61, 38, 238])
142    }
143}