steel_core/player/chat/
profile_key.rs1use std::time::{Duration, SystemTime, UNIX_EPOCH};
6
7use rsa::RsaPublicKey;
8use steel_crypto::{
9 CryptError, SignatureValidator, public_key_from_bytes, public_key_to_bytes,
10 signature::RsaPublicKeyValidator,
11};
12use steel_protocol::packets::game::ProtocolRemoteChatSessionData;
13use thiserror::Error;
14use uuid::Uuid;
15
16pub const EXPIRY_GRACE_PERIOD: Duration = Duration::from_hours(8);
18
19pub const MAX_KEY_SIGNATURE_SIZE: usize = 4096;
21
22#[derive(Debug, Error)]
24pub enum ValidationError {
25 #[error("Invalid public key signature")]
27 InvalidSignature,
28
29 #[error("Key has expired")]
31 KeyExpired,
32
33 #[error("Cryptographic error: {0}")]
35 CryptoError(#[from] CryptError),
36}
37
38#[derive(Clone, Debug)]
42pub struct ProfilePublicKeyData {
43 pub expires_at: SystemTime,
45
46 pub key: RsaPublicKey,
48
49 pub key_signature: Vec<u8>,
51}
52
53impl ProfilePublicKeyData {
54 #[must_use]
56 pub const fn new(expires_at: SystemTime, key: RsaPublicKey, key_signature: Vec<u8>) -> Self {
57 Self {
58 expires_at,
59 key,
60 key_signature,
61 }
62 }
63
64 #[must_use]
66 pub fn has_expired(&self) -> bool {
67 self.expires_at.elapsed().is_ok()
69 }
70
71 #[must_use]
73 pub fn has_expired_with_grace(&self, grace_period: Duration) -> bool {
74 let expiry_with_grace = self.expires_at + grace_period;
75 expiry_with_grace.elapsed().is_ok()
76 }
77
78 pub fn validate_signature(
85 &self,
86 profile_id: Uuid,
87 validator: &dyn SignatureValidator,
88 ) -> Result<(), ValidationError> {
89 let payload = self.signed_payload(profile_id)?;
90 let updater = ByteSliceUpdater(&payload);
91
92 let is_valid = validator
93 .validate(&updater, &self.key_signature)
94 .map_err(ValidationError::from)?;
95
96 if is_valid {
97 Ok(())
98 } else {
99 Err(ValidationError::InvalidSignature)
100 }
101 }
102
103 fn signed_payload(&self, profile_id: Uuid) -> Result<Vec<u8>, ValidationError> {
107 let key_bytes = public_key_to_bytes(&self.key)?;
108
109 let mut payload = Vec::with_capacity(24 + key_bytes.len());
110
111 payload.extend_from_slice(&profile_id.as_u128().to_be_bytes());
113
114 let expiry_millis = self
116 .expires_at
117 .duration_since(UNIX_EPOCH)
118 .unwrap_or_default()
119 .as_millis() as i64;
120 payload.extend_from_slice(&expiry_millis.to_be_bytes());
121
122 payload.extend_from_slice(&key_bytes);
124
125 Ok(payload)
126 }
127
128 pub fn to_bytes(&self) -> Result<Vec<u8>, ValidationError> {
133 let mut bytes = Vec::new();
134
135 let expiry_millis = self
137 .expires_at
138 .duration_since(UNIX_EPOCH)
139 .unwrap_or_default()
140 .as_millis() as i64;
141 bytes.extend_from_slice(&expiry_millis.to_be_bytes());
142
143 let key_bytes = public_key_to_bytes(&self.key)?;
145 bytes.extend_from_slice(&(key_bytes.len() as i32).to_be_bytes());
146 bytes.extend_from_slice(&key_bytes);
147
148 bytes.extend_from_slice(&(self.key_signature.len() as i32).to_be_bytes());
150 bytes.extend_from_slice(&self.key_signature);
151
152 Ok(bytes)
153 }
154
155 pub fn from_bytes(bytes: &[u8]) -> Result<Self, ValidationError> {
163 if bytes.len() < 16 {
164 return Err(ValidationError::CryptoError(CryptError::InvalidKeyFormat));
165 }
166
167 let mut offset = 0;
168
169 let expiry_millis = i64::from_be_bytes(
171 bytes[offset..offset + 8]
172 .try_into()
173 .expect("slice is exactly 8 bytes"),
174 );
175 offset += 8;
176 let expires_at = UNIX_EPOCH + Duration::from_millis(expiry_millis as u64);
177
178 if bytes.len() < offset + 4 {
180 return Err(ValidationError::CryptoError(CryptError::InvalidKeyFormat));
181 }
182 let key_len = i32::from_be_bytes(
183 bytes[offset..offset + 4]
184 .try_into()
185 .expect("slice is exactly 4 bytes"),
186 ) as usize;
187 offset += 4;
188
189 if bytes.len() < offset + key_len {
191 return Err(ValidationError::CryptoError(CryptError::InvalidKeyFormat));
192 }
193 let key = public_key_from_bytes(&bytes[offset..offset + key_len])?;
194 offset += key_len;
195
196 if bytes.len() < offset + 4 {
198 return Err(ValidationError::CryptoError(CryptError::InvalidKeyFormat));
199 }
200 let sig_len = i32::from_be_bytes(
201 bytes[offset..offset + 4]
202 .try_into()
203 .expect("slice is exactly 4 bytes"),
204 ) as usize;
205 offset += 4;
206
207 if bytes.len() < offset + sig_len {
209 return Err(ValidationError::CryptoError(CryptError::InvalidKeyFormat));
210 }
211 let key_signature = bytes[offset..offset + sig_len].to_vec();
212
213 Ok(Self {
214 expires_at,
215 key,
216 key_signature,
217 })
218 }
219}
220
221#[derive(Clone, Debug)]
225pub struct ProfilePublicKey {
226 data: ProfilePublicKeyData,
227}
228
229impl ProfilePublicKey {
230 #[must_use]
234 pub const fn new(data: ProfilePublicKeyData) -> Self {
235 Self { data }
236 }
237
238 pub fn create_validated(
245 profile_id: Uuid,
246 data: ProfilePublicKeyData,
247 validator: &dyn SignatureValidator,
248 ) -> Result<Self, ValidationError> {
249 data.validate_signature(profile_id, validator)?;
250 Ok(Self::new(data))
251 }
252
253 #[must_use]
255 pub const fn data(&self) -> &ProfilePublicKeyData {
256 &self.data
257 }
258
259 #[must_use]
261 pub fn create_signature_validator(&self) -> RsaPublicKeyValidator {
262 RsaPublicKeyValidator::new(self.data.key.clone())
263 }
264}
265
266#[derive(Clone, Debug)]
270pub struct RemoteChatSession {
271 pub session_id: Uuid,
273 pub profile_public_key: ProfilePublicKey,
275}
276
277impl RemoteChatSession {
278 #[must_use]
280 pub const fn new(session_id: Uuid, profile_public_key: ProfilePublicKey) -> Self {
281 Self {
282 session_id,
283 profile_public_key,
284 }
285 }
286
287 #[must_use]
289 pub fn has_expired(&self) -> bool {
290 self.profile_public_key.data().has_expired()
291 }
292
293 #[must_use]
295 pub fn as_data(&self) -> RemoteChatSessionData {
296 RemoteChatSessionData {
297 session_id: self.session_id,
298 profile_public_key: self.profile_public_key.data().clone(),
299 }
300 }
301}
302
303#[derive(Clone, Debug)]
307pub struct RemoteChatSessionData {
308 pub session_id: Uuid,
310 pub profile_public_key: ProfilePublicKeyData,
312}
313
314impl RemoteChatSessionData {
315 pub fn validate(
320 self,
321 profile_id: Uuid,
322 validator: &dyn SignatureValidator,
323 ) -> Result<RemoteChatSession, ValidationError> {
324 let public_key =
325 ProfilePublicKey::create_validated(profile_id, self.profile_public_key, validator)?;
326 Ok(RemoteChatSession::new(self.session_id, public_key))
327 }
328
329 pub fn to_protocol_data(&self) -> Result<ProtocolRemoteChatSessionData, ValidationError> {
334 let key_bytes = public_key_to_bytes(&self.profile_public_key.key)?;
335
336 Ok(ProtocolRemoteChatSessionData::new(
337 self.session_id,
338 self.profile_public_key.expires_at,
339 key_bytes,
340 self.profile_public_key.key_signature.clone(),
341 ))
342 }
343}
344
345mod signature_helpers {
347 use steel_crypto::{
348 CryptError,
349 signature::{SignatureOutput, SignatureUpdater},
350 };
351
352 pub struct ByteSliceUpdater<'a>(pub &'a [u8]);
353
354 impl SignatureUpdater for ByteSliceUpdater<'_> {
355 fn update(&self, output: &mut dyn SignatureOutput) -> Result<(), CryptError> {
356 output.update(self.0)
357 }
358 }
359}
360
361use signature_helpers::ByteSliceUpdater;