Skip to content

Commit

Permalink
fix: Strip padding and double quotes from encryption and crypto-key h…
Browse files Browse the repository at this point in the history
…eaders (#200)

* Strip padding and double quotes from encryption and crypto-key headers

The regex is a little more complicated than the Python autopush version
because Rust's regex library does not support look-ahead, for
performance reasons.

* Improve strip_headers test

Closes #192
  • Loading branch information
AzureMarker committed Jul 27, 2020
1 parent 6a7fa49 commit e20fc6a
Showing 1 changed file with 41 additions and 3 deletions.
44 changes: 41 additions & 3 deletions autoendpoint/src/extractors/notification_headers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ use validator_derive::Validate;

lazy_static! {
static ref VALID_BASE64_URL: Regex = Regex::new(r"^[0-9A-Za-z\-_]+=*$").unwrap();
static ref STRIP_PADDING: Regex =
Regex::new(r"(?P<head>[0-9A-Za-z\-_]+)=+(?P<tail>[,;]|$)").unwrap();
}

const MAX_TTL: i64 = 60 * 60 * 24 * 60;
Expand Down Expand Up @@ -78,6 +80,10 @@ impl NotificationHeaders {
let encryption_key = get_owned_header(req, "encryption-key");
let crypto_key = get_owned_header(req, "crypto-key");

// Strip quotes and padding from some headers
let encryption = encryption.map(Self::strip_header);
let crypto_key = crypto_key.map(Self::strip_header);

let headers = NotificationHeaders {
ttl,
topic,
Expand All @@ -99,6 +105,12 @@ impl NotificationHeaders {
}
}

/// Remove Base64 padding and double-quotes
fn strip_header(header: String) -> String {
let header = header.replace('"', "");
STRIP_PADDING.replace_all(&header, "$head$tail").to_string()
}

/// Validate the encryption headers according to the various WebPush
/// standard versions
fn validate_encryption(&self) -> ApiResult<()> {
Expand Down Expand Up @@ -161,7 +173,7 @@ impl NotificationHeaders {
Ok(())
}

/// Assert that the given key exists in the header and is valid base64.
/// Assert that the given item exists in the header and is valid base64.
fn assert_base64_item_exists(
header_name: &str,
header: Option<&str>,
Expand All @@ -173,14 +185,14 @@ impl NotificationHeaders {
let header_data = CryptoKeyHeader::parse(header).ok_or_else(|| {
ApiErrorKind::InvalidEncryption(format!("Invalid {} header", header_name))
})?;
let salt = header_data.get_by_key(key).ok_or_else(|| {
let value = header_data.get_by_key(key).ok_or_else(|| {
ApiErrorKind::InvalidEncryption(format!(
"Missing {} value in {} header",
key, header_name
))
})?;

if !VALID_BASE64_URL.is_match(salt) {
if !VALID_BASE64_URL.is_match(value) {
return Err(ApiErrorKind::InvalidEncryption(format!(
"Invalid {} value in {} header",
key, header_name
Expand Down Expand Up @@ -411,5 +423,31 @@ mod tests {
);
}

/// The encryption and crypto-key headers are stripped of Base64 padding and
/// double-quotes.
#[test]
fn strip_headers() {
let req = TestRequest::post()
.header("TTL", "10")
.header("Content-Encoding", "aesgcm")
.header("Encryption", "salt=\"foo\"")
.header("Crypto-Key", "keyid=\"p256dh\";dh=\"deadbeef==\"")
.to_http_request();
let result = NotificationHeaders::from_request(&req, true);

assert!(result.is_ok());
assert_eq!(
result.unwrap(),
NotificationHeaders {
ttl: 10,
topic: None,
encoding: Some("aesgcm".to_string()),
encryption: Some("salt=foo".to_string()),
encryption_key: None,
crypto_key: Some("keyid=p256dh;dh=deadbeef".to_string())
}
);
}

// TODO: Add negative test cases for encryption validation?
}

0 comments on commit e20fc6a

Please sign in to comment.