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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
use crate::errors::GenericError;
use crate::pae::pae;
use base64::{decode_config, encode_config, URL_SAFE_NO_PAD};
use failure::Error;
use ring::constant_time::verify_slices_are_equal as ConstantTimeEquals;
use ring::signature::{Ed25519KeyPair, UnparsedPublicKey, ED25519};
const HEADER: &str = "v2.public.";
pub fn public_paseto(msg: &str, footer: Option<&str>, key_pair: &Ed25519KeyPair) -> Result<String, Error> {
let footer_frd = footer.unwrap_or("");
let pre_auth = pae(&[HEADER.as_bytes(), msg.as_bytes(), footer_frd.as_bytes()]);
let sig = key_pair.sign(&pre_auth);
let mut m_and_sig = Vec::from(msg.as_bytes());
m_and_sig.extend_from_slice(sig.as_ref());
let token = if footer_frd.is_empty() {
format!("{}{}", HEADER, encode_config(&m_and_sig, URL_SAFE_NO_PAD))
} else {
format!(
"{}{}.{}",
HEADER,
encode_config(&m_and_sig, URL_SAFE_NO_PAD),
encode_config(footer_frd.as_bytes(), URL_SAFE_NO_PAD)
)
};
Ok(token)
}
pub fn verify_paseto(token: &str, footer: Option<&str>, public_key: &[u8]) -> Result<String, Error> {
let token_parts = token.split(".").collect::<Vec<_>>();
if token_parts.len() < 3 {
return Err(GenericError::InvalidToken {})?;
}
let has_provided_footer = footer.is_some();
let footer_as_str = footer.unwrap_or("");
if has_provided_footer {
if token_parts.len() < 4 {
return Err(GenericError::InvalidFooter {})?;
}
let footer_encoded = encode_config(footer_as_str.as_bytes(), URL_SAFE_NO_PAD);
if ConstantTimeEquals(footer_encoded.as_bytes(), token_parts[3].as_bytes()).is_err() {
return Err(GenericError::InvalidFooter {})?;
}
}
if token_parts[0] != "v2" || token_parts[1] != "public" {
return Err(GenericError::InvalidToken {})?;
}
let decoded = decode_config(token_parts[2].as_bytes(), URL_SAFE_NO_PAD)?;
let decoded_len = decoded.len();
let (msg, sig) = decoded.split_at(decoded_len - 64);
let pre_auth = pae(&[HEADER.as_bytes(), msg, footer_as_str.as_bytes()]);
let pk_unparsed = UnparsedPublicKey::new(&ED25519, public_key);
let verify_res = pk_unparsed.verify(&pre_auth, sig);
if verify_res.is_err() {
return Err(GenericError::InvalidToken {})?;
}
Ok(String::from_utf8(Vec::from(msg))?)
}
#[cfg(test)]
mod unit_tests {
use super::*;
use ring::rand::SystemRandom;
use ring::signature::KeyPair;
#[test]
fn paseto_public_verify() {
let sys_rand = SystemRandom::new();
let key_pkcs8 = Ed25519KeyPair::generate_pkcs8(&sys_rand).expect("Failed to generate pkcs8 key!");
let as_key = Ed25519KeyPair::from_pkcs8(key_pkcs8.as_ref()).expect("Failed to parse keypair");
let public_token_one = public_paseto("msg", None, &as_key).expect("Failed to public encode msg with no footer!");
let public_token_two = public_paseto(
"{\"data\": \"yo bro\", \"expires\": \"2018-01-01T00:00:00+00:00\"}",
None,
&as_key,
)
.expect("Failed to public encode json blob with no footer!");
assert!(public_token_one.starts_with("v2.public."));
assert!(public_token_two.starts_with("v2.public."));
let verified_one = verify_paseto(&public_token_one.clone(), None, as_key.public_key().as_ref());
let verified_two = verify_paseto(&public_token_two, None, as_key.public_key().as_ref());
assert!(verified_one.is_ok());
assert!(verified_two.is_ok());
assert_eq!(verified_one.unwrap(), "msg");
assert_eq!(
verified_two.unwrap(),
"{\"data\": \"yo bro\", \"expires\": \"2018-01-01T00:00:00+00:00\"}"
);
let should_not_verify_one = verify_paseto(&public_token_one, Some("data"), as_key.public_key().as_ref());
assert!(should_not_verify_one.is_err());
let public_token_three =
public_paseto("msg", Some("footer"), &as_key).expect("Failed to public encode msg with footer!");
let public_token_four = public_paseto(
"{\"data\": \"yo bro\", \"expires\": \"2018-01-01T00:00:00+00:00\"}",
Some("footer"),
&as_key,
)
.expect("Failed to public encode json blob with footer!");
assert!(public_token_three.starts_with("v2.public."));
assert!(public_token_four.starts_with("v2.public."));
let verified_three = verify_paseto(&public_token_three, Some("footer"), as_key.public_key().as_ref());
let verified_four = verify_paseto(&public_token_four, Some("footer"), as_key.public_key().as_ref());
assert!(verified_three.is_ok());
assert!(verified_four.is_ok());
assert_eq!(verified_three.unwrap(), "msg");
assert_eq!(
verified_four.unwrap(),
"{\"data\": \"yo bro\", \"expires\": \"2018-01-01T00:00:00+00:00\"}"
);
let should_not_verify_two = verify_paseto(&public_token_three, None, as_key.public_key().as_ref());
let should_not_verify_three = verify_paseto(&public_token_three, Some("bleh"), as_key.public_key().as_ref());
assert!(should_not_verify_two.is_err());
assert!(should_not_verify_three.is_err());
}
}