idempotent_proxy_types/
auth.rs1use 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; #[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
48pub 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
62pub 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 println!("{:?}", token);
141 }
143}