origin_trial_token/
lib.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Implements a simple processor for
6//! https://github.com/chromium/chromium/blob/d7da0240cae77824d1eda25745c4022757499131/third_party/blink/public/common/origin_trials/origin_trials_token_structure.md
7//!
8//! This crate intentionally leaves the cryptography to the caller. See the
9//! tools/ directory for example usages.
10
11
12/// Latest version as documented.
13pub const LATEST_VERSION: u8 = 3;
14
15#[repr(C)]
16pub struct RawToken {
17    version: u8,
18    signature: [u8; 64],
19    payload_length: [u8; 4],
20    /// Payload is an slice of payload_length bytes, and has to be verified
21    /// before returning it to the caller.
22    payload: [u8; 0],
23}
24
25#[derive(Debug)]
26pub enum TokenValidationError {
27    BufferTooSmall,
28    MismatchedPayloadSize { expected: usize, actual: usize },
29    InvalidSignature,
30    UnknownVersion,
31    UnsupportedThirdPartyToken,
32    UnexpectedUsageInNonThirdPartyToken,
33    MalformedPayload(serde_json::Error),
34}
35
36impl RawToken {
37    const HEADER_SIZE: usize = std::mem::size_of::<Self>();
38
39    #[inline]
40    pub fn version(&self) -> u8 {
41        self.version
42    }
43
44    #[inline]
45    pub fn signature(&self) -> &[u8; 64] {
46        &self.signature
47    }
48
49    #[inline]
50    pub fn payload_length(&self) -> usize {
51        u32::from_be_bytes(self.payload_length) as usize
52    }
53
54    #[inline]
55    pub fn as_buffer(&self) -> &[u8] {
56        let buffer_size = Self::HEADER_SIZE + self.payload_length();
57        unsafe { std::slice::from_raw_parts(self as *const _ as *const u8, buffer_size) }
58    }
59
60    #[inline]
61    pub fn payload(&self) -> &[u8] {
62        let len = self.payload_length();
63        unsafe { std::slice::from_raw_parts(self.payload.as_ptr(), len) }
64    }
65
66    /// Returns a RawToken from a raw buffer.
67    pub fn from_buffer<'a>(buffer: &'a [u8]) -> Result<&'a Self, TokenValidationError> {
68        if buffer.len() <= Self::HEADER_SIZE {
69            return Err(TokenValidationError::BufferTooSmall);
70        }
71        assert_eq!(
72            std::mem::align_of::<Self>(),
73            1,
74            "RawToken is a view over the buffer"
75        );
76        let raw_token = unsafe { &*(buffer.as_ptr() as *const Self) };
77        let payload = &buffer[Self::HEADER_SIZE..];
78        let expected = raw_token.payload_length();
79        let actual = payload.len();
80        if expected != actual {
81            return Err(TokenValidationError::MismatchedPayloadSize { expected, actual });
82        }
83        Ok(raw_token)
84    }
85
86    /// The data to verify the signature in this raw token.
87    fn signature_data(&self) -> Vec<u8> {
88        Self::raw_signature_data(self.version, self.payload())
89    }
90
91    /// The data to sign or verify given a payload and a version.
92    fn raw_signature_data(version: u8, payload: &[u8]) -> Vec<u8> {
93        let mut data = Vec::with_capacity(payload.len() + 5);
94        data.push(version);
95        data.extend((payload.len() as u32).to_be_bytes());
96        data.extend(payload);
97        data
98    }
99
100    /// Verify the signature of this raw token.
101    pub fn verify(&self, verify_signature: impl FnOnce(&[u8; 64], &[u8]) -> bool) -> bool {
102        let signature_data = self.signature_data();
103        verify_signature(&self.signature, &signature_data)
104    }
105}
106
107#[derive(serde::Deserialize, serde::Serialize, Debug, Eq, PartialEq)]
108#[serde(rename_all = "camelCase")]
109pub enum Usage {
110    #[serde(rename = "")]
111    None,
112    Subset,
113}
114
115impl Usage {
116    fn is_none(&self) -> bool {
117        *self == Self::None
118    }
119}
120
121impl Default for Usage {
122    fn default() -> Self {
123        Self::None
124    }
125}
126
127fn is_false(t: &bool) -> bool {
128    *t == false
129}
130
131/// An already decoded and maybe-verified token.
132#[derive(serde::Deserialize, serde::Serialize, Debug, Eq, PartialEq)]
133#[serde(rename_all = "camelCase")]
134pub struct Token {
135    pub origin: String,
136    pub feature: String,
137    pub expiry: u64, // Timestamp. Seconds since epoch.
138    #[serde(default, skip_serializing_if = "is_false")]
139    pub is_subdomain: bool,
140    #[serde(default, skip_serializing_if = "is_false")]
141    pub is_third_party: bool,
142    #[serde(default, skip_serializing_if = "Usage::is_none")]
143    pub usage: Usage,
144}
145
146impl Token {
147    #[inline]
148    pub fn origin(&self) -> &str {
149        &self.origin
150    }
151
152    #[inline]
153    pub fn feature(&self) -> &str {
154        &self.feature
155    }
156
157    #[inline]
158    pub fn expiry_since_unix_epoch(&self) -> std::time::Duration {
159        std::time::Duration::from_secs(self.expiry)
160    }
161
162    #[inline]
163    pub fn expiry_time(&self) -> Option<std::time::SystemTime> {
164        std::time::UNIX_EPOCH.checked_add(self.expiry_since_unix_epoch())
165    }
166
167    #[inline]
168    pub fn is_expired(&self) -> bool {
169        let now_duration = std::time::SystemTime::now()
170            .duration_since(std::time::UNIX_EPOCH)
171            .expect("System time before epoch?");
172        now_duration >= self.expiry_since_unix_epoch()
173    }
174
175    /// Most high-level function: For a given buffer, tries to parse it and
176    /// verify it as a token.
177    pub fn from_buffer(
178        buffer: &[u8],
179        verify_signature: impl FnOnce(&[u8; 64], &[u8]) -> bool,
180    ) -> Result<Self, TokenValidationError> {
181        Self::from_raw_token(RawToken::from_buffer(buffer)?, verify_signature)
182    }
183
184    /// Validates a RawToken's signature and converts the token if valid.
185    pub fn from_raw_token(
186        token: &RawToken,
187        verify_signature: impl FnOnce(&[u8; 64], &[u8]) -> bool,
188    ) -> Result<Self, TokenValidationError> {
189        if !token.verify(verify_signature) {
190            return Err(TokenValidationError::InvalidSignature);
191        }
192        Self::from_raw_token_unverified(token)
193    }
194
195    /// Converts the token from a raw token, without verifying first.
196    pub fn from_raw_token_unverified(token: &RawToken) -> Result<Self, TokenValidationError> {
197        Self::from_payload(token.version, token.payload())
198    }
199
200    /// Converts the token from a raw payload, version pair.
201    pub fn from_payload(version: u8, payload: &[u8]) -> Result<Self, TokenValidationError> {
202        if version != 2 && version != 3 {
203            assert_ne!(version, LATEST_VERSION);
204            return Err(TokenValidationError::UnknownVersion);
205        }
206
207        let token: Token = match serde_json::from_slice(payload) {
208            Ok(t) => t,
209            Err(e) => return Err(TokenValidationError::MalformedPayload(e)),
210        };
211
212        // Third-party tokens are not supported in version 2.
213        if token.is_third_party {
214            if version == 2 {
215                return Err(TokenValidationError::UnsupportedThirdPartyToken);
216            }
217        } else if !token.usage.is_none() {
218            return Err(TokenValidationError::UnexpectedUsageInNonThirdPartyToken);
219        }
220
221        Ok(token)
222    }
223
224    /// Converts the token to a raw payload.
225    pub fn to_payload(&self) -> Vec<u8> {
226        serde_json::to_string(self)
227            .expect("Should always be able to turn a token into a payload")
228            .into_bytes()
229    }
230
231    /// Converts the token to the data that should be signed.
232    pub fn to_signature_data(&self) -> Vec<u8> {
233        RawToken::raw_signature_data(LATEST_VERSION, &self.to_payload())
234    }
235
236    /// Turns the token into a fully signed token.
237    pub fn to_signed_token(&self, sign: impl FnOnce(&[u8]) -> [u8; 64]) -> Vec<u8> {
238        self.to_signed_token_with_payload(sign, &self.to_payload())
239    }
240
241    /// DO NOT EXPOSE: This is intended for testing only. We need to test with
242    /// the original payload so that the tokens match, but we assert
243    /// that self.to_payload() and payload are equivalent.
244    fn to_signed_token_with_payload(
245        &self,
246        sign: impl FnOnce(&[u8]) -> [u8; 64],
247        payload: &[u8],
248    ) -> Vec<u8> {
249        let signature_data_with_payload = RawToken::raw_signature_data(LATEST_VERSION, &payload);
250        let signature = sign(&signature_data_with_payload);
251
252        let mut buffer = Vec::with_capacity(1 + signature.len() + 4 + payload.len());
253        buffer.push(LATEST_VERSION);
254        buffer.extend(signature);
255        buffer.extend((payload.len() as u32).to_be_bytes());
256        buffer.extend(payload);
257
258        if cfg!(debug_assertions) {
259            let token = Self::from_buffer(&buffer, |_, _| true).expect("Creating malformed token?");
260            assert_eq!(self, &token, "Token differs after deserialization?");
261        }
262
263        buffer
264    }
265}
266
267#[cfg(test)]
268mod tests;