diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:41:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:41:41 +0000 |
commit | 10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87 (patch) | |
tree | bdffd5d80c26cf4a7a518281a204be1ace85b4c1 /vendor/pasetors/src/footer.rs | |
parent | Releasing progress-linux version 1.70.0+dfsg1-9~progress7.99u1. (diff) | |
download | rustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.tar.xz rustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.zip |
Merging upstream version 1.70.0+dfsg2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/pasetors/src/footer.rs')
-rw-r--r-- | vendor/pasetors/src/footer.rs | 396 |
1 files changed, 396 insertions, 0 deletions
diff --git a/vendor/pasetors/src/footer.rs b/vendor/pasetors/src/footer.rs new file mode 100644 index 000000000..cff1998df --- /dev/null +++ b/vendor/pasetors/src/footer.rs @@ -0,0 +1,396 @@ +#![cfg_attr(docsrs, doc(cfg(feature = "std")))] + +use crate::errors::Error; +#[cfg(feature = "paserk")] +use crate::paserk::{FormatAsPaserk, Id}; +use regex::Regex; +use serde_json::Value; +use std::collections::HashMap; + +#[derive(Debug, PartialEq, Eq, Clone)] +/// A footer with optional claims that are JSON-encoded. +pub struct Footer { + list_of: HashMap<String, Value>, + max_keys: usize, + max_len: usize, +} + +impl Default for Footer { + fn default() -> Self { + Self::new() + } +} + +impl Footer { + /// Keys for registered claims in the footer, that are reserved for usage by PASETO in top-level. + pub const REGISTERED_CLAIMS: [&'static str; 2] = ["kid", "wpk"]; + + /// All PASERK types that are (implemented in this library) unsafe in the footer. + pub const DISALLOWED_FOOTER: [&'static str; 8] = [ + "k2.local.", + "k4.local.", + "k2.secret.", + "k3.secret.", + "k4.secret.", + "k2.public.", + "k3.public.", + "k4.public.", + ]; + + /// See [PASETO docs] for the reason behind this limit. + /// + /// Maximum number of named keys within an object. + /// + /// [PASETO docs]: https://github.com/paseto-standard/paseto-spec/blob/master/docs/02-Implementation-Guide/01-Payload-Processing.md#enforcing-maximum-depth-without-parsing-the-json-string + pub const DEFAULT_MAX_KEYS: usize = 512; + + /// See [PASETO docs] for the reason behind this limit. + /// + /// Maximum length of the JSON-encoded string. + /// + /// [PASETO docs]: https://github.com/paseto-standard/paseto-spec/blob/master/docs/02-Implementation-Guide/01-Payload-Processing.md#enforcing-maximum-depth-without-parsing-the-json-string + pub const DEFAULT_MAX_LEN: usize = 8192; + + /// See [PASETO docs] for the reason behind this limit. + /// + /// This value has been set by `serde_json` and cannot be changed. + /// + /// [PASETO docs]: https://github.com/paseto-standard/paseto-spec/blob/master/docs/02-Implementation-Guide/01-Payload-Processing.md#enforcing-maximum-depth-without-parsing-the-json-string + pub const MAX_RECURSION_DEPTH: usize = 128; + + /// Create a new `Footer` instance. + pub fn new() -> Self { + Self { + list_of: HashMap::new(), + max_keys: Self::DEFAULT_MAX_KEYS, + max_len: Self::DEFAULT_MAX_LEN, + } + } + + /// Change the default (512) amount of maximum number of named keys within an object. + /// + /// __NOTE__: There should be no need to change this if you don't know this is a specific problem for you. + pub fn max_keys(&mut self, max_keys: usize) { + self.max_keys = max_keys; + } + + /// Change the default (8192) amount of maximum number of named keys within an object. + /// + /// __NOTE__: There should be no need to change this if you don't know this is a specific problem for you. + pub fn max_len(&mut self, max_len: usize) { + self.max_len = max_len; + } + + /// Add additional claims. If `claim` already exists, it is replaced with the new. + /// + /// Errors: + /// - `claim` is a reserved claim (see [`Self::REGISTERED_CLAIMS`]) + /// - `value` is any of (starts with) the disallowed PASERK types (see [`Self::DISALLOWED_FOOTER`]). + pub fn add_additional(&mut self, claim: &str, value: &str) -> Result<(), Error> { + for unsafe_value in Self::DISALLOWED_FOOTER { + if value.starts_with(unsafe_value) { + return Err(Error::InvalidClaim); + } + } + + if !Self::REGISTERED_CLAIMS.contains(&claim) { + self.list_of.insert(claim.into(), value.into()); + Ok(()) + } else { + Err(Error::InvalidClaim) + } + } + + /// Checks whether a specific claim has been added to the list. + /// + /// E.g `contains_claim("kid") == true` if `kid` has been added before. + pub fn contains_claim(&self, claim: &str) -> bool { + self.list_of.contains_key(claim) + } + + /// Return Some(claim value) if claims list contains the `claim`. + /// None otherwise. + pub fn get_claim(&self, claim: &str) -> Option<&Value> { + self.list_of.get(claim) + } + + #[cfg(feature = "paserk")] + /// Set the `kid` claim. If it already exists, replace it with the new. + pub fn key_id(&mut self, id: &Id) { + let mut paserk_kid = String::new(); + id.fmt(&mut paserk_kid).unwrap(); + + self.list_of.insert("kid".into(), paserk_kid.into()); + } + + /// Attempt to create `Footer` from a sequence of bytes. + /// + /// Errors: + /// - `bytes` contains non-UTF-8 sequences + /// - `bytes` does not decode as valid JSON + /// - `bytes` top-most JSON object does not decode to a map + /// - if any registered claims exist and they are not a `String` + /// - Parsing JSON maps and arrays that are more than 128 layers deep + /// - Maximum number of named keys is exceeded + /// - Maximum JSON-encoded string length is exceeded + pub fn parse_bytes(&mut self, bytes: &[u8]) -> Result<(), Error> { + let input = bytes.to_vec(); + + self.parse_string(&String::from_utf8(input).map_err(|_| Error::FooterParsing)?) + } + + /// Attempt to parse a `Footer` from a string. + /// + /// Errors: + /// - `string` does not decode as valid JSON + /// - `string` top-most JSON object does not decode to a map + /// - if any registered claims exist and they are not a `String` + /// - Parsing JSON maps and arrays that are more than 128 layers deep + /// - Maximum number of named keys is exceeded + /// - Maximum JSON-encoded string length is exceeded + pub fn parse_string(&mut self, string: &str) -> Result<(), Error> { + if string.len() > self.max_len { + return Err(Error::FooterParsing); + } + if Regex::new(r#"[^\\]":"#).unwrap().find_iter(string).count() > self.max_keys { + return Err(Error::FooterParsing); + } + + self.list_of = serde_json::from_str(string).map_err(|_| Error::FooterParsing)?; + + Ok(()) + } + + /// Return the JSON serialized representation of `Self`. + /// + /// Errors: + /// - `self` cannot be serialized as JSON + pub fn to_string(&self) -> Result<String, Error> { + match serde_json::to_string(&self.list_of) { + Ok(ret) => Ok(ret), + Err(_) => Err(Error::FooterParsing), + } + } +} + +#[cfg(test)] +mod tests { + use crate::footer::Footer; + use regex::Regex; + + #[test] + fn test_count_keys() { + // https://www.rustescaper.com/ + let string = r#""name": "3-S-2", + "expect-fail": false, + "public-key": "02fbcb7c69ee1c60579be7a334134878d9c5c5bf35d552dab63c0140397ed14cef637d7720925c44699ea30e72874c72fb", + "secret-key": "20347609607477aca8fbfbc5e6218455f3199669792ef8b466faa87bdc67798144c848dd03661eed5ac62461340cea96", + "secret-key-pem": "-----BEGIN EC PRIVATE KEY-----nMIGkAgEBBDAgNHYJYHR3rKj7+8XmIYRV8xmWaXku+LRm+qh73Gd5gUTISN0DZh7tnWsYkYTQM6pagBwYFK4EEACKhZANiAAT7y3xp7hxgV5vnozQTSHjZxcW/NdVS2rY8nAUA5ftFM72N9dyCSXERpnqMOcodMcvt8kgcrB8KcKee0HU23E79/s4CvEs8hBfnjnSUd/gcAm08EjSIz06iWjrNy4NakxR3I=n-----END EC PRIVATE KEY-----", + "public-key-pem": "-----BEGIN PUBLIC KEY-----nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE+8t8ae4cYFeb56M0E0h42cXFvzXVUtq2nPAFAOX7RTO9jfXcgklxEaZ6jDnKHTHL7fJIHKwfCnCnntB1NtxO/f7OArxLPIQX5n40lHf4HAJtPBI0iM9Oolo6zcuDWpMUdyn-----END PUBLIC KEY-----", + "token": "v3.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAyMi0wMS0wMVQwMDowMDowMCswMDowMCJ9ZWrbGZ6L0MDK72skosUaS0Dz7wJ_2bMcM6tOxFuCasO9GhwHrvvchqgXQNLQQyWzGC2wkr-VKII71AvkLpC8tJOrzJV1cap9NRwoFzbcXjzMZyxQ0wkshxZxx8ImmNWP.eyJraWQiOiJkWWtJU3lseFFlZWNFY0hFTGZ6Rjg4VVpyd2JMb2xOaUNkcHpVSEd3OVVxbiJ9", + "payload": "{"data":"this is a signed message","exp":"2022-01-01T00:00:00+00:00"}", + "footer": "{"kid":"dYkISylxQeecEcHELfzF88UZrwbLolNiCdpzUHGw9Uqn"}", + "implicit-assertion": """#; + + assert_eq!( + Regex::new(r#"[^\\]":"#).unwrap().find_iter(string).count(), + 13 + ); + } + + #[test] + fn err_on_max_keys() { + let mut footer = Footer::default(); + for n in 1..=11 { + footer + .add_additional(format!("{}", n).as_str(), "test") + .unwrap(); + } + + let mut footer_parse = Footer::default(); + footer_parse.max_keys(10); + assert!(footer_parse + .parse_bytes(footer.to_string().unwrap().as_bytes()) + .is_err()); + } + + #[test] + fn err_on_max_len() { + let mut footer = Footer::new(); + for n in 1..=11 { + footer + .add_additional(format!("{}", n).as_str(), "test") + .unwrap(); + } + let ser_footer = footer.to_string().unwrap(); + + let mut footer_parse = Footer::new(); + footer_parse.max_len(ser_footer.len() - 1); + assert!(footer_parse.parse_bytes(ser_footer.as_bytes()).is_err()); + } + + #[test] + fn err_on_custom_with_registered() { + let mut footer = Footer::new(); + + assert!(footer.add_additional("wpk", "test").is_err()); + assert!(footer.add_additional("kid", "test").is_err()); + assert!(footer.add_additional("custom", "test").is_ok()); + } + + #[test] + #[cfg(all(feature = "paserk", feature = "v2", feature = "v3", feature = "v4"))] + fn err_on_disallowed_in_footer() { + use crate::keys::{AsymmetricKeyPair, Generate, SymmetricKey}; + use crate::paserk::FormatAsPaserk; + use crate::version2::V2; + use crate::version3::V3; + use crate::version4::V4; + + let mut footer = Footer::new(); + + let kpv2 = AsymmetricKeyPair::<V2>::generate().unwrap(); + let kpv3 = AsymmetricKeyPair::<V3>::generate().unwrap(); + let kpv4 = AsymmetricKeyPair::<V4>::generate().unwrap(); + let skv2 = SymmetricKey::<V2>::generate().unwrap(); + let skv4 = SymmetricKey::<V4>::generate().unwrap(); + + let mut buf = String::new(); + kpv2.secret.fmt(&mut buf).unwrap(); + assert!(footer.add_additional("wpk", &buf).is_err()); + assert!(footer.add_additional("kid", &buf).is_err()); + assert!(footer.add_additional("custom", &buf).is_err()); + + let mut buf = String::new(); + kpv2.public.fmt(&mut buf).unwrap(); + assert!(footer.add_additional("wpk", &buf).is_err()); + assert!(footer.add_additional("kid", &buf).is_err()); + assert!(footer.add_additional("custom", &buf).is_err()); + + let mut buf = String::new(); + kpv3.secret.fmt(&mut buf).unwrap(); + assert!(footer.add_additional("wpk", &buf).is_err()); + assert!(footer.add_additional("kid", &buf).is_err()); + assert!(footer.add_additional("custom", &buf).is_err()); + + let mut buf = String::new(); + kpv3.public.fmt(&mut buf).unwrap(); + assert!(footer.add_additional("wpk", &buf).is_err()); + assert!(footer.add_additional("kid", &buf).is_err()); + assert!(footer.add_additional("custom", &buf).is_err()); + + let mut buf = String::new(); + kpv4.secret.fmt(&mut buf).unwrap(); + assert!(footer.add_additional("wpk", &buf).is_err()); + assert!(footer.add_additional("kid", &buf).is_err()); + assert!(footer.add_additional("custom", &buf).is_err()); + + let mut buf = String::new(); + kpv4.public.fmt(&mut buf).unwrap(); + assert!(footer.add_additional("wpk", &buf).is_err()); + assert!(footer.add_additional("kid", &buf).is_err()); + assert!(footer.add_additional("custom", &buf).is_err()); + + let mut buf = String::new(); + skv2.fmt(&mut buf).unwrap(); + assert!(footer.add_additional("wpk", &buf).is_err()); + assert!(footer.add_additional("kid", &buf).is_err()); + assert!(footer.add_additional("custom", &buf).is_err()); + + let mut buf = String::new(); + skv4.fmt(&mut buf).unwrap(); + assert!(footer.add_additional("wpk", &buf).is_err()); + assert!(footer.add_additional("kid", &buf).is_err()); + assert!(footer.add_additional("custom", &buf).is_err()); + } + + #[test] + #[cfg(all(feature = "paserk", feature = "v2", feature = "v3", feature = "v4"))] + fn kid_in_footer() { + use crate::keys::{AsymmetricKeyPair, Generate, SymmetricKey}; + use crate::paserk::{FormatAsPaserk, Id}; + use crate::version2::V2; + use crate::version3::V3; + use crate::version4::V4; + + let mut footer = Footer::new(); + + let kpv2 = AsymmetricKeyPair::<V2>::generate().unwrap(); + let kpv3 = AsymmetricKeyPair::<V3>::generate().unwrap(); + let kpv4 = AsymmetricKeyPair::<V4>::generate().unwrap(); + let skv2 = SymmetricKey::<V2>::generate().unwrap(); + let skv4 = SymmetricKey::<V4>::generate().unwrap(); + + let mut buf = String::new(); + let paserk_id = Id::from(&kpv2.secret); + paserk_id.fmt(&mut buf).unwrap(); + assert!(footer.add_additional("kid", &buf).is_err()); + assert!(footer.add_additional("custom", &buf).is_ok()); + footer.key_id(&paserk_id); + assert!(footer.contains_claim("kid")); + assert_eq!(footer.get_claim("kid").unwrap().as_str().unwrap(), buf); + + let mut buf = String::new(); + let paserk_id = Id::from(&kpv2.public); + paserk_id.fmt(&mut buf).unwrap(); + assert!(footer.add_additional("kid", &buf).is_err()); + assert!(footer.add_additional("custom", &buf).is_ok()); + footer.key_id(&paserk_id); + assert!(footer.contains_claim("kid")); + assert_eq!(footer.get_claim("kid").unwrap().as_str().unwrap(), buf); + + let mut buf = String::new(); + let paserk_id = Id::from(&kpv3.secret); + paserk_id.fmt(&mut buf).unwrap(); + assert!(footer.add_additional("kid", &buf).is_err()); + assert!(footer.add_additional("custom", &buf).is_ok()); + footer.key_id(&paserk_id); + assert!(footer.contains_claim("kid")); + assert_eq!(footer.get_claim("kid").unwrap().as_str().unwrap(), buf); + + let mut buf = String::new(); + let paserk_id = Id::from(&kpv3.public); + paserk_id.fmt(&mut buf).unwrap(); + assert!(footer.add_additional("kid", &buf).is_err()); + assert!(footer.add_additional("custom", &buf).is_ok()); + footer.key_id(&paserk_id); + assert!(footer.contains_claim("kid")); + assert_eq!(footer.get_claim("kid").unwrap().as_str().unwrap(), buf); + + let mut buf = String::new(); + let paserk_id = Id::from(&kpv4.secret); + paserk_id.fmt(&mut buf).unwrap(); + assert!(footer.add_additional("kid", &buf).is_err()); + assert!(footer.add_additional("custom", &buf).is_ok()); + footer.key_id(&paserk_id); + assert!(footer.contains_claim("kid")); + assert_eq!(footer.get_claim("kid").unwrap().as_str().unwrap(), buf); + + let mut buf = String::new(); + let paserk_id = Id::from(&kpv4.public); + paserk_id.fmt(&mut buf).unwrap(); + assert!(footer.add_additional("kid", &buf).is_err()); + assert!(footer.add_additional("custom", &buf).is_ok()); + footer.key_id(&paserk_id); + assert!(footer.contains_claim("kid")); + assert_eq!(footer.get_claim("kid").unwrap().as_str().unwrap(), buf); + + let mut buf = String::new(); + let paserk_id = Id::from(&skv2); + paserk_id.fmt(&mut buf).unwrap(); + assert!(footer.add_additional("kid", &buf).is_err()); + assert!(footer.add_additional("custom", &buf).is_ok()); + footer.key_id(&paserk_id); + assert!(footer.contains_claim("kid")); + assert_eq!(footer.get_claim("kid").unwrap().as_str().unwrap(), buf); + + let mut buf = String::new(); + let paserk_id = Id::from(&skv4); + paserk_id.fmt(&mut buf).unwrap(); + assert!(footer.add_additional("kid", &buf).is_err()); + assert!(footer.add_additional("custom", &buf).is_ok()); + footer.key_id(&paserk_id); + assert!(footer.contains_claim("kid")); + assert_eq!(footer.get_claim("kid").unwrap().as_str().unwrap(), buf); + } +} |