Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ version = "0.7.9"
version = "0.9.6"
features = ["cookie-private", "cookie-key-expansion", "typed-header"]

# Constant-time base64
[workspace.dependencies.base64ct]
version = "1.6.0"

# Bytes
[workspace.dependencies.bytes]
version = "1.9.0"
Expand Down
1 change: 1 addition & 0 deletions crates/data-model/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ publish = false
workspace = true

[dependencies]
base64ct.workspace = true
chrono.workspace = true
thiserror.workspace = true
serde.workspace = true
Expand Down
36 changes: 35 additions & 1 deletion crates/data-model/src/tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.

use base64ct::{Base64Url, Encoding};
use chrono::{DateTime, Utc};
use crc::{Crc, CRC_32_ISO_HDLC};
use mas_iana::oauth::OAuthTokenTypeHint;
Expand Down Expand Up @@ -294,7 +295,7 @@ impl TokenType {
pub fn check(token: &str) -> Result<TokenType, TokenFormatError> {
// these are legacy tokens imported from Synapse
// we don't do any validation on them and continue as is
if token.starts_with("syt_") {
if token.starts_with("syt_") || is_likely_synapse_macaroon(token) {
return Ok(TokenType::CompatAccessToken);
}
if token.starts_with("syr_") {
Expand Down Expand Up @@ -344,6 +345,20 @@ impl PartialEq<OAuthTokenTypeHint> for TokenType {
}
}

/// Returns true if and only if a token looks like it may be a macaroon.
///
/// Macaroons are a standard for tokens that support attenuation.
/// Synapse used them for old sessions and for guest sessions.
///
/// We won't bother to decode them fully, but we can check to see if the first
/// constraint is the `location` constraint.
fn is_likely_synapse_macaroon(token: &str) -> bool {
let Ok(decoded) = Base64Url::decode_vec(token) else {
return false;
};
decoded.get(4..13) == Some(b"location ")
}

const NUM: [u8; 62] = *b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

fn base62_encode(mut num: u32) -> String {
Expand Down Expand Up @@ -420,6 +435,25 @@ mod tests {
);
}

#[test]
fn test_is_likely_synapse_macaroon() {
// This is just the prefix of a Synapse macaroon, but it's enough to make the
// sniffing work
assert!(is_likely_synapse_macaroon(
"MDAxYmxvY2F0aW9uIGxpYnJlcHVzaC5uZXQKMDAx"
));

// Whilst this is a macaroon, it's not a Synapse macaroon
assert!(! is_likely_synapse_macaroon("MDAxY2xvY2F0aW9uIGh0dHA6Ly9teWJhbmsvCjAwMjZpZGVudGlmaWVyIHdlIHVzZWQgb3VyIHNlY3JldCBrZXkKMDAyZnNpZ25hdHVyZSDj2eApCFJsTAA5rhURQRXZf91ovyujebNCqvD2F9BVLwo"));

// None of these are macaroons
assert!(!is_likely_synapse_macaroon(
"eyJARTOhearotnaeisahtoarsnhiasra.arsohenaor.oarnsteao"
));
assert!(!is_likely_synapse_macaroon("...."));
assert!(!is_likely_synapse_macaroon("aaa"));
}

#[test]
fn test_generate_and_check() {
const COUNT: usize = 500; // Generate 500 of each token type
Expand Down
2 changes: 1 addition & 1 deletion crates/handlers/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ pbkdf2 = { version = "0.12.2", features = [
zeroize = "1.8.1"

# Various data types and utilities
base64ct = "1.6.0"
base64ct.workspace = true
camino.workspace = true
chrono.workspace = true
elliptic-curve.workspace = true
Expand Down
Loading