From eba45f3d383419cc1736a81790daaebff69e6eb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C5=82az=CC=87ej=20Pankowski?= <86720177+pblazej@users.noreply.github.com> Date: Tue, 16 Jun 2026 10:14:58 +0200 Subject: [PATCH 1/3] perf(livekit-api): HMAC-only JWT provider to shrink binary size Replace jsonwebtoken's rust_crypto backend with an in-crate HMAC-only CryptoProvider (HS256/384/512). LiveKit access tokens are HS256 and the access_token API is HS256-hardcoded (jsonwebtoken isn't re-exported), so the dropped RSA/EC/EdDSA algorithms were never reachable. Installed as the process-global provider on first sign/verify. Shrinks the shipped binaries ~25-29% (also benefits livekit-ffi/livekit): - RustLiveKitUniFFI ios-arm64 framework: ~900 -> 672 KiB - livekit-uniffi android arm64-v8a .so: ~1157 -> 816 KiB Tighten CI size guards to 5% over the measured real artifacts: - SPM_SIZE_LIMIT_BYTES: 1 MiB -> 723467 - ANDROID_SIZE_LIMIT_BYTES: 1.5 MiB -> 877918 Co-Authored-By: Claude Opus 4.8 (1M context) --- .changeset/hmac-only-jwt-shrink-binaries.md | 13 + Cargo.lock | 322 +------------------- livekit-api/Cargo.toml | 9 +- livekit-api/src/access_token.rs | 3 + livekit-api/src/jwt_provider.rs | 119 ++++++++ livekit-api/src/lib.rs | 3 + livekit-uniffi/Makefile.toml | 4 +- 7 files changed, 148 insertions(+), 325 deletions(-) create mode 100644 .changeset/hmac-only-jwt-shrink-binaries.md create mode 100644 livekit-api/src/jwt_provider.rs diff --git a/.changeset/hmac-only-jwt-shrink-binaries.md b/.changeset/hmac-only-jwt-shrink-binaries.md new file mode 100644 index 000000000..4f32ad236 --- /dev/null +++ b/.changeset/hmac-only-jwt-shrink-binaries.md @@ -0,0 +1,13 @@ +--- +livekit-api: patch +--- + +# Shrink binaries with an HMAC-only JWT crypto provider + +`livekit-api` now installs a minimal in-crate HMAC `CryptoProvider` +(HS256/384/512) instead of jsonwebtoken's `rust_crypto` backend, dropping the +unused RSA/EC/EdDSA algorithms. LiveKit access tokens are HS256, so there is no +public API or behavior change. This trims ~200 KiB of unreachable crypto code, +shrinking the shipped binaries ~25–29% — the `RustLiveKitUniFFI` iOS framework +drops from ~900 KiB to ~672 KiB (arm64) and the Android arm64-v8a `.so` from +~1.13 MiB to ~816 KiB. `livekit-ffi` and `livekit` benefit too. diff --git a/Cargo.lock b/Cargo.lock index 84fe0528c..2aed71a33 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -644,12 +644,6 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "base16ct" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" - [[package]] name = "base64" version = "0.21.7" @@ -662,12 +656,6 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "base64ct" -version = "1.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" - [[package]] name = "basic-toml" version = "0.1.10" @@ -1278,12 +1266,6 @@ dependencies = [ "tracing-subscriber", ] -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - [[package]] name = "convert_case" version = "0.11.0" @@ -1517,18 +1499,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" -[[package]] -name = "crypto-bigint" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" -dependencies = [ - "generic-array", - "rand_core 0.6.4", - "subtle", - "zeroize", -] - [[package]] name = "crypto-common" version = "0.1.6" @@ -1612,33 +1582,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" -[[package]] -name = "curve25519-dalek" -version = "4.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" -dependencies = [ - "cfg-if 1.0.4", - "cpufeatures", - "curve25519-dalek-derive", - "digest", - "fiat-crypto", - "rustc_version", - "subtle", - "zeroize", -] - -[[package]] -name = "curve25519-dalek-derive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "cxx" version = "1.0.194" @@ -1813,17 +1756,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "der" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" -dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", -] - [[package]] name = "deranged" version = "0.5.8" @@ -1870,7 +1802,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", - "const-oid", "crypto-common", "subtle", ] @@ -1971,20 +1902,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15401da73a9ed8c80e3b2d4dc05fe10e7b72d7243b9f614e516a44fa99986e88" -[[package]] -name = "ecdsa" -version = "0.16.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" -dependencies = [ - "der", - "digest", - "elliptic-curve", - "rfc6979", - "signature", - "spki", -] - [[package]] name = "ecolor" version = "0.33.3" @@ -1996,30 +1913,6 @@ dependencies = [ "serde", ] -[[package]] -name = "ed25519" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" -dependencies = [ - "pkcs8", - "signature", -] - -[[package]] -name = "ed25519-dalek" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" -dependencies = [ - "curve25519-dalek", - "ed25519", - "serde", - "sha2", - "subtle", - "zeroize", -] - [[package]] name = "eframe" version = "0.33.3" @@ -2124,27 +2017,6 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" -[[package]] -name = "elliptic-curve" -version = "0.13.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" -dependencies = [ - "base16ct", - "crypto-bigint", - "digest", - "ff", - "generic-array", - "group", - "hkdf", - "pem-rfc7468", - "pkcs8", - "rand_core 0.6.4", - "sec1", - "subtle", - "zeroize", -] - [[package]] name = "emath" version = "0.33.3" @@ -2385,22 +2257,6 @@ dependencies = [ "simd-adler32", ] -[[package]] -name = "ff" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" -dependencies = [ - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "fiat-crypto" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" - [[package]] name = "filetime" version = "0.2.27" @@ -2685,7 +2541,6 @@ checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ "typenum", "version_check", - "zeroize", ] [[package]] @@ -2948,17 +2803,6 @@ dependencies = [ "bitflags 2.11.0", ] -[[package]] -name = "group" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" -dependencies = [ - "ff", - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "h2" version = "0.3.27" @@ -3062,15 +2906,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac", -] - [[package]] name = "hmac" version = "0.12.1" @@ -3736,17 +3571,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1" dependencies = [ "base64 0.22.1", - "ed25519-dalek", "getrandom 0.2.17", - "hmac", "js-sys", - "p256", - "p384", - "rand 0.8.5", - "rsa", "serde", "serde_json", - "sha2", "signature", ] @@ -3791,9 +3619,6 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin", -] [[package]] name = "lazycell" @@ -4031,6 +3856,7 @@ dependencies = [ "device-info", "flate2", "futures-util", + "hmac", "http 1.4.0", "isahc", "jsonwebtoken", @@ -4048,6 +3874,7 @@ dependencies = [ "serde", "serde_json", "sha2", + "signature", "thiserror 2.0.18", "tokio", "tokio-rustls", @@ -4770,22 +4597,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-bigint-dig" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" -dependencies = [ - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand 0.8.5", - "smallvec", - "zeroize", -] - [[package]] name = "num-complex" version = "0.4.6" @@ -4821,17 +4632,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-rational" version = "0.4.2" @@ -5438,30 +5238,6 @@ dependencies = [ "ttf-parser", ] -[[package]] -name = "p256" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2", -] - -[[package]] -name = "p384" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2", -] - [[package]] name = "parking" version = "2.2.1" @@ -5548,15 +5324,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - [[package]] name = "percent-encoding" version = "2.3.2" @@ -5670,27 +5437,6 @@ dependencies = [ "futures-io", ] -[[package]] -name = "pkcs1" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" -dependencies = [ - "der", - "pkcs8", - "spki", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - [[package]] name = "pkg-config" version = "0.3.32" @@ -5827,15 +5573,6 @@ dependencies = [ "num-integer", ] -[[package]] -name = "primeorder" -version = "0.13.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" -dependencies = [ - "elliptic-curve", -] - [[package]] name = "proc-macro-crate" version = "3.5.0" @@ -6384,16 +6121,6 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80f5ab61459422b1a350caae1aadef48ebae234b12e80248e9ba9557018ef060" -[[package]] -name = "rfc6979" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" -dependencies = [ - "hmac", - "subtle", -] - [[package]] name = "rgb" version = "0.8.53" @@ -6440,26 +6167,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "rsa" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" -dependencies = [ - "const-oid", - "digest", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8", - "rand_core 0.6.4", - "signature", - "spki", - "subtle", - "zeroize", -] - [[package]] name = "rtc-node-ffi-bindings" version = "0.0.1" @@ -6732,20 +6439,6 @@ version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" -[[package]] -name = "sec1" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" -dependencies = [ - "base16ct", - "der", - "generic-array", - "pkcs8", - "subtle", - "zeroize", -] - [[package]] name = "security-framework" version = "3.7.0" @@ -6935,7 +6628,6 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest", "rand_core 0.6.4", ] @@ -7132,16 +6824,6 @@ dependencies = [ "bitflags 2.11.0", ] -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - [[package]] name = "stable_deref_trait" version = "1.2.1" diff --git a/livekit-api/Cargo.toml b/livekit-api/Cargo.toml index d1e53ad80..d9926f3b4 100644 --- a/livekit-api/Cargo.toml +++ b/livekit-api/Cargo.toml @@ -47,7 +47,7 @@ __signal-client-async-compatible = [ services-tokio = ["dep:reqwest"] services-async = ["dep:isahc"] -access-token = ["dep:jsonwebtoken"] +access-token = ["dep:jsonwebtoken", "dep:hmac", "dep:signature"] webhooks = ["access-token", "dep:serde_json", "dep:base64"] # TLS Configuration @@ -114,8 +114,11 @@ pbjson-types = "0.6" serde_json = { workspace = true, optional = true } base64 = { version = "0.21", optional = true, features = ["std"] } -# access_token & services -jsonwebtoken = { version = "10", default-features = false, features = ["rust_crypto"], optional = true } +# access_token: HS256 only, via the in-crate HMAC CryptoProvider (jwt_provider.rs). +# Dropping jsonwebtoken's rust_crypto bundle avoids linking RSA/EC/EdDSA. +jsonwebtoken = { version = "10", default-features = false, optional = true } +hmac = { version = "0.12", optional = true } +signature = { version = "2", optional = true } # signal_client livekit-runtime = { workspace = true, optional = true} diff --git a/livekit-api/src/access_token.rs b/livekit-api/src/access_token.rs index 39d6e6a52..e5963d98f 100644 --- a/livekit-api/src/access_token.rs +++ b/livekit-api/src/access_token.rs @@ -153,6 +153,7 @@ pub struct Claims { impl Claims { pub fn from_unverified(token: &str) -> Result { + crate::jwt_provider::ensure_installed(); let token = jsonwebtoken::dangerous::insecure_decode::(token)?; Ok(token.claims) } @@ -261,6 +262,7 @@ impl AccessToken { } pub fn to_jwt(self) -> Result { + crate::jwt_provider::ensure_installed(); if self.api_key.is_empty() || self.api_secret.is_empty() { return Err(AccessTokenError::InvalidKeys); } @@ -304,6 +306,7 @@ impl TokenVerifier { } pub fn verify(&self, token: &str) -> Result { + crate::jwt_provider::ensure_installed(); let mut validation = jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::HS256); validation.validate_exp = true; validation.validate_nbf = true; diff --git a/livekit-api/src/jwt_provider.rs b/livekit-api/src/jwt_provider.rs new file mode 100644 index 000000000..76328a270 --- /dev/null +++ b/livekit-api/src/jwt_provider.rs @@ -0,0 +1,119 @@ +//! HMAC-only `jsonwebtoken` crypto provider (HS256/384/512). +//! +//! LiveKit access tokens are HS256, so livekit-api links only the HMAC backend +//! rather than jsonwebtoken's `rust_crypto` bundle (which also pulls in RSA, EC +//! and EdDSA — ~275 KiB of code that's never reachable through this crate's API). +//! jsonwebtoken 10 resolves its backend through a process-global `CryptoProvider`; +//! [`ensure_installed`] registers this one on first use. + +use std::sync::Once; + +use hmac::{Hmac, Mac}; +use jsonwebtoken::crypto::{CryptoProvider, JwkUtils, JwtSigner, JwtVerifier}; +use jsonwebtoken::errors::{Error, ErrorKind, Result}; +use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey}; +use sha2::{Sha256, Sha384, Sha512}; +use signature::{Error as SignatureError, Signer, Verifier}; + +type HmacSha256 = Hmac; +type HmacSha384 = Hmac; +type HmacSha512 = Hmac; + +macro_rules! hmac_signer { + ($name:ident, $alg:expr, $hmac:ty) => { + struct $name($hmac); + + impl $name { + fn new(key: &EncodingKey) -> Result { + <$hmac>::new_from_slice(key.try_get_hmac_secret()?) + .map(Self) + .map_err(|_| Error::from(ErrorKind::InvalidKeyFormat)) + } + } + + impl Signer> for $name { + fn try_sign(&self, msg: &[u8]) -> std::result::Result, SignatureError> { + let mut mac = self.0.clone(); + mac.update(msg); + Ok(mac.finalize().into_bytes().to_vec()) + } + } + + impl JwtSigner for $name { + fn algorithm(&self) -> Algorithm { + $alg + } + } + }; +} + +macro_rules! hmac_verifier { + ($name:ident, $alg:expr, $hmac:ty) => { + struct $name($hmac); + + impl $name { + fn new(key: &DecodingKey) -> Result { + <$hmac>::new_from_slice(key.try_get_hmac_secret()?) + .map(Self) + .map_err(|_| Error::from(ErrorKind::InvalidKeyFormat)) + } + } + + impl Verifier> for $name { + fn verify(&self, msg: &[u8], signature: &Vec) -> std::result::Result<(), SignatureError> { + let mut mac = self.0.clone(); + mac.update(msg); + // verify_slice is constant-time. + mac.verify_slice(signature).map_err(SignatureError::from_source) + } + } + + impl JwtVerifier for $name { + fn algorithm(&self) -> Algorithm { + $alg + } + } + }; +} + +hmac_signer!(Hs256Signer, Algorithm::HS256, HmacSha256); +hmac_signer!(Hs384Signer, Algorithm::HS384, HmacSha384); +hmac_signer!(Hs512Signer, Algorithm::HS512, HmacSha512); +hmac_verifier!(Hs256Verifier, Algorithm::HS256, HmacSha256); +hmac_verifier!(Hs384Verifier, Algorithm::HS384, HmacSha384); +hmac_verifier!(Hs512Verifier, Algorithm::HS512, HmacSha512); + +fn signer_factory(alg: &Algorithm, key: &EncodingKey) -> Result> { + match alg { + Algorithm::HS256 => Ok(Box::new(Hs256Signer::new(key)?) as Box), + Algorithm::HS384 => Ok(Box::new(Hs384Signer::new(key)?) as Box), + Algorithm::HS512 => Ok(Box::new(Hs512Signer::new(key)?) as Box), + _ => Err(Error::from(ErrorKind::InvalidAlgorithm)), + } +} + +fn verifier_factory(alg: &Algorithm, key: &DecodingKey) -> Result> { + match alg { + Algorithm::HS256 => Ok(Box::new(Hs256Verifier::new(key)?) as Box), + Algorithm::HS384 => Ok(Box::new(Hs384Verifier::new(key)?) as Box), + Algorithm::HS512 => Ok(Box::new(Hs512Verifier::new(key)?) as Box), + _ => Err(Error::from(ErrorKind::InvalidAlgorithm)), + } +} + +static PROVIDER: CryptoProvider = CryptoProvider { + signer_factory, + verifier_factory, + jwk_utils: JwkUtils::new_unimplemented(), +}; + +/// Register the HMAC-only provider as jsonwebtoken's process default. +/// +/// Idempotent and cheap; call before any `encode`/`decode`. If another provider +/// was already installed in this process, this is a no-op and that one is used. +pub(crate) fn ensure_installed() { + static INIT: Once = Once::new(); + INIT.call_once(|| { + let _ = PROVIDER.install_default(); + }); +} diff --git a/livekit-api/src/lib.rs b/livekit-api/src/lib.rs index e789635f6..850772f4a 100644 --- a/livekit-api/src/lib.rs +++ b/livekit-api/src/lib.rs @@ -17,6 +17,9 @@ #[cfg(feature = "access-token")] pub mod access_token; +#[cfg(feature = "access-token")] +mod jwt_provider; + #[cfg(any(feature = "services-tokio", feature = "services-async"))] pub mod services; diff --git a/livekit-uniffi/Makefile.toml b/livekit-uniffi/Makefile.toml index 8cba1535f..18021dfac 100644 --- a/livekit-uniffi/Makefile.toml +++ b/livekit-uniffi/Makefile.toml @@ -353,7 +353,7 @@ echo # are ~2× larger by construction (two archs in one Mach-O) and shouldn't be # the metric. Override the limit per release with SPM_SIZE_LIMIT_BYTES. [tasks.swift-check-size.env] -SPM_SIZE_LIMIT_BYTES = { value = "1048576", condition = { env_not_set = ["SPM_SIZE_LIMIT_BYTES"] } } +SPM_SIZE_LIMIT_BYTES = { value = "723467", condition = { env_not_set = ["SPM_SIZE_LIMIT_BYTES"] } } [tasks.swift-check-size] private = true @@ -486,7 +486,7 @@ run_task = "swift-package-flow" # during assemble; the raw target/ artifact is larger and not what ships). # Override the limit per release with ANDROID_SIZE_LIMIT_BYTES. [tasks.android-check-size.env] -ANDROID_SIZE_LIMIT_BYTES = { value = "1572864", condition = { env_not_set = ["ANDROID_SIZE_LIMIT_BYTES"] } } +ANDROID_SIZE_LIMIT_BYTES = { value = "877918", condition = { env_not_set = ["ANDROID_SIZE_LIMIT_BYTES"] } } [tasks.android-check-size] private = true From 8f0582ee401361b3e55c4c1bc3c6d2efb421a749 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C5=82az=CC=87ej=20Pankowski?= <86720177+pblazej@users.noreply.github.com> Date: Tue, 16 Jun 2026 10:50:49 +0200 Subject: [PATCH 2/3] ci(uniffi): drop obsolete nightly/build-std for tvOS/visionOS aarch64-apple-tvos and aarch64-apple-visionos (+ -sim) are now Tier 2 with prebuilt std (tvOS since 1.84; visionOS after), so cargo-swift builds them on stable. Add them to the regular target list and remove the nightly + rust-src install. Verified by a full xcframework build on the pinned 1.96.0: all slices use prebuilt std (no -Zbuild-std, no recompiled core/std). Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/uniffi-swift.yml | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/.github/workflows/uniffi-swift.yml b/.github/workflows/uniffi-swift.yml index ec0a11676..93b93835d 100644 --- a/.github/workflows/uniffi-swift.yml +++ b/.github/workflows/uniffi-swift.yml @@ -56,7 +56,7 @@ jobs: - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 - - name: Install Apple Rust targets (tier 1/2) + - name: Install Apple Rust targets run: | rustup target add \ aarch64-apple-ios \ @@ -65,14 +65,11 @@ jobs: aarch64-apple-ios-macabi \ x86_64-apple-ios-macabi \ aarch64-apple-darwin \ - x86_64-apple-darwin - - # tvOS + visionOS are still tier 3 (no prebuilt std), so cargo-swift - # builds them via `cargo +nightly -Zbuild-std`. Toolchain + rust-src - # need to exist ahead of time. - - name: Install nightly + rust-src (tvOS/visionOS tier-3 targets) - run: | - rustup toolchain install nightly --profile minimal --component rust-src + x86_64-apple-darwin \ + aarch64-apple-tvos \ + aarch64-apple-tvos-sim \ + aarch64-apple-visionos \ + aarch64-apple-visionos-sim - uses: cargo-bins/cargo-binstall@aaa84a43aec4955a42c5ffc65d258961e39f276e # v1.19.1 - run: cargo binstall cargo-make --no-confirm From 6d0fccfbed087f775817f012819d9733bf09ad36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C5=82az=CC=87ej=20Pankowski?= <86720177+pblazej@users.noreply.github.com> Date: Tue, 16 Jun 2026 10:56:21 +0200 Subject: [PATCH 3/3] style(livekit-api): add license header and rustfmt jwt_provider.rs Fixes the License Check (missing Apache header on the new file) and Rust Formatting CI jobs. Co-Authored-By: Claude Opus 4.8 (1M context) --- livekit-api/src/jwt_provider.rs | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/livekit-api/src/jwt_provider.rs b/livekit-api/src/jwt_provider.rs index 76328a270..6437f4242 100644 --- a/livekit-api/src/jwt_provider.rs +++ b/livekit-api/src/jwt_provider.rs @@ -1,3 +1,17 @@ +// Copyright 2025 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + //! HMAC-only `jsonwebtoken` crypto provider (HS256/384/512). //! //! LiveKit access tokens are HS256, so livekit-api links only the HMAC backend @@ -60,7 +74,11 @@ macro_rules! hmac_verifier { } impl Verifier> for $name { - fn verify(&self, msg: &[u8], signature: &Vec) -> std::result::Result<(), SignatureError> { + fn verify( + &self, + msg: &[u8], + signature: &Vec, + ) -> std::result::Result<(), SignatureError> { let mut mac = self.0.clone(); mac.update(msg); // verify_slice is constant-time. @@ -101,11 +119,8 @@ fn verifier_factory(alg: &Algorithm, key: &DecodingKey) -> Result