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
#![cfg(feature = "v1_local")]
use std::str;
use hmac::{Hmac, Mac};
use subtle::ConstantTimeEq;
use crate::core::{Footer, Header, Key, Local, Paseto, PasetoError, PasetoNonce, PasetoSymmetricKey, V1};
use crate::core::common::{AuthenticationKey, AuthenticationKeySeparator, CipherText, EncryptionKey, EncryptionKeySeparator, PreAuthenticationEncoding, RawPayload, Tag};
use sha2::Sha384;
impl<'a> Paseto<'a, V1, Local> {
/// Attempts to decrypt a PASETO token
/// ```
/// # use serde_json::json;
/// # use rusty_paseto::core::*;
/// # let key = PasetoSymmetricKey::<V1, Local>::from(Key::<32>::try_from("707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f")?);
/// # let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?;
/// # let nonce = PasetoNonce::<V1, Local>::from(&nonce);
/// # let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string();
/// # let payload = payload.as_str();
/// # let payload = Payload::from(payload);
/// # let token = Paseto::<V1, Local>::builder().set_payload(payload).try_encrypt(&key, &nonce)?;
/// // decrypt a public v1 token
/// let json = Paseto::<V1, Local>::try_decrypt(&token, &key, None)?;
/// # assert_eq!(payload, json);
/// # Ok::<(),anyhow::Error>(())
/// ```
pub fn try_decrypt(
token: &'a str,
key: &PasetoSymmetricKey<V1, Local>,
footer: impl Into<Option<Footer<'a>>> + Copy,
) -> Result<String, PasetoError> {
// V1 local token structure: nonce (32 bytes) + ciphertext (variable) + tag (48 bytes)
const NONCE_SIZE: usize = 32;
const TAG_SIZE: usize = 48;
const MIN_PAYLOAD_SIZE: usize = NONCE_SIZE + TAG_SIZE;
let decoded_payload = Self::parse_raw_token(token, footer, &V1::default(), &Local::default())?;
// Validate minimum payload size before slicing
if decoded_payload.len() < MIN_PAYLOAD_SIZE {
return Err(PasetoError::IncorrectSize);
}
// Safe slicing with bounds-checked access
let nonce_bytes = decoded_payload.get(..NONCE_SIZE).ok_or(PasetoError::IncorrectSize)?;
let nonce = Key::<NONCE_SIZE>::try_from(nonce_bytes)?;
let nonce = PasetoNonce::<V1, Local>::from(&nonce);
let aks: &[u8] = &AuthenticationKeySeparator::default();
let aks_key = Key::<24>::try_from(aks)?;
let authentication_key =
AuthenticationKey::<V1, Local>::try_from(&aks_key, key, &nonce)?;
let eks: &[u8] = &EncryptionKeySeparator::default();
let eks_key = Key::<21>::try_from(eks)?;
let encryption_key = EncryptionKey::<V1, Local>::try_from(&eks_key, key, &nonce)?;
// Ciphertext is between nonce and tag
let ciphertext_end = decoded_payload.len().saturating_sub(TAG_SIZE);
let ciphertext = decoded_payload.get(NONCE_SIZE..ciphertext_end).ok_or(PasetoError::IncorrectSize)?;
//pack preauth
let pae = PreAuthenticationEncoding::parse(&[
&Header::<V1, Local>::default(),
nonce.as_ref(),
ciphertext,
&footer.into().unwrap_or_default(),
]);
//generate tags - tag is the last TAG_SIZE bytes
let tag_start = NONCE_SIZE
.checked_add(ciphertext.len())
.ok_or(PasetoError::IncorrectSize)?;
let tag = decoded_payload.get(tag_start..).ok_or(PasetoError::IncorrectSize)?;
let tag2 = Tag::<V1, Local>::try_from(authentication_key, &pae)?;
//compare tags
if !bool::from(tag.ct_eq(tag2.as_ref())) {
return Err(PasetoError::Cryption);
}
//decrypt payload
let ciphertext = CipherText::<V1, Local>::from(ciphertext, &encryption_key);
let decoded_str = str::from_utf8(&ciphertext)?;
//return decrypted payload
Ok(decoded_str.to_owned())
}
/// Attempts to encrypt a PASETO token
/// ```
/// # use serde_json::json;
/// # use rusty_paseto::core::*;
/// # let key = PasetoSymmetricKey::<V1, Local>::from(Key::<32>::try_from("707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f")?);
/// # let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?;
/// # let nonce = PasetoNonce::<V1, Local>::from(&nonce);
/// # let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string();
/// # let payload = payload.as_str();
/// # let payload = Payload::from(payload);
/// // encrypt a public v1 token
/// let token = Paseto::<V1, Local>::builder().set_payload(payload).try_encrypt(&key, &nonce)?;
/// # let json = Paseto::<V1, Local>::try_decrypt(&token, &key, None)?;
/// # assert_eq!(payload, json);
/// # Ok::<(),anyhow::Error>(())
/// ```
pub fn try_encrypt(
&mut self,
key: &PasetoSymmetricKey<V1, Local>,
nonce: &PasetoNonce<V1, Local>,
) -> Result<String, PasetoError> {
//setup
let footer = self.footer.unwrap_or_default();
//calculate nonce
type HmacSha384 = Hmac<Sha384>;
let mut mac = HmacSha384::new_from_slice(nonce.as_ref())
.map_err(|_| PasetoError::InvalidKey)?;
mac.update(&self.payload);
let out = mac.finalize();
let hmac_bytes = out.into_bytes();
let nonce_bytes = hmac_bytes.get(..32).ok_or(PasetoError::IncorrectSize)?;
let nonce = Key::<32>::try_from(nonce_bytes)?;
let nonce = PasetoNonce::<V1, Local>::from(&nonce);
//split key
let aks: &[u8] = &AuthenticationKeySeparator::default();
let aks_key = Key::<24>::try_from(aks)?;
let authentication_key =
AuthenticationKey::<V1, Local>::try_from(&aks_key, key, &nonce)?;
let eks: &[u8] = &EncryptionKeySeparator::default();
let eks_key = Key::<21>::try_from(eks)?;
let encryption_key = EncryptionKey::<V1, Local>::try_from(&eks_key, key, &nonce)?;
//encrypt payload
let ciphertext = CipherText::<V1, Local>::from(&self.payload, &encryption_key);
//pack preauth
let pae = PreAuthenticationEncoding::parse(&[&self.header, nonce.as_ref(), &ciphertext, &footer]);
//generate tag
let tag = Tag::<V1, Local>::try_from(authentication_key, &pae)?;
//generate appended and base64 encoded payload
let raw_payload = RawPayload::<V1, Local>::from(&nonce, &ciphertext, &tag)?;
//format as paseto with header and optional footer
Ok(self.format_token(&raw_payload))
}
}