azalea_crypto/
signing.rs

1//! Chat signing, used in Minecraft to allow for messages to be reported.
2
3use std::time::{SystemTime, UNIX_EPOCH};
4
5use azalea_buf::{AzBuf, AzaleaWrite};
6use rsa::{
7    RsaPrivateKey,
8    signature::{RandomizedSigner, SignatureEncoding},
9};
10use sha2::Sha256;
11use uuid::Uuid;
12
13#[derive(AzBuf, Clone, Debug)]
14pub struct SaltSignaturePair {
15    pub salt: u64,
16    pub signature: Vec<u8>,
17}
18
19#[derive(AzBuf, Clone, Debug, PartialEq)]
20pub struct MessageSignature {
21    pub bytes: [u8; 256],
22}
23
24#[derive(AzBuf, Clone, Debug, PartialEq)]
25pub struct SignedMessageHeader {
26    pub previous_signature: Option<MessageSignature>,
27    pub sender: Uuid,
28}
29
30/// Generates a random u64 to use as a salt
31pub fn make_salt() -> u64 {
32    rand::random()
33}
34
35pub struct SignChatMessageOptions {
36    pub account_uuid: Uuid,
37    pub chat_session_uuid: Uuid,
38
39    pub message_index: u32,
40
41    /// Can be acquired with [`make_salt`].
42    pub salt: u64,
43
44    /// The current time that we're sending the message at.
45    pub timestamp: SystemTime,
46
47    /// The message that we're sending in chat.
48    pub message: String,
49
50    pub private_key: RsaPrivateKey,
51}
52
53pub fn sign_chat_message(opts: &SignChatMessageOptions) -> MessageSignature {
54    let mut data_to_sign = Vec::new();
55    // always 1 for some reason
56    1i32.azalea_write(&mut data_to_sign).unwrap();
57    // player uuid
58    opts.account_uuid.azalea_write(&mut data_to_sign).unwrap();
59    // chat session uuid
60    opts.chat_session_uuid
61        .azalea_write(&mut data_to_sign)
62        .unwrap();
63    // message index
64    opts.message_index.azalea_write(&mut data_to_sign).unwrap();
65    // salt
66    opts.salt.azalea_write(&mut data_to_sign).unwrap();
67
68    // timestamp as seconds
69    let seconds_since_epoch = opts
70        .timestamp
71        .duration_since(UNIX_EPOCH)
72        .expect("timestamp must be after epoch")
73        .as_secs();
74    seconds_since_epoch.azalea_write(&mut data_to_sign).unwrap();
75
76    // message length as u32
77    let message_len: u32 = opts.message.len().try_into().unwrap();
78    message_len.azalea_write(&mut data_to_sign).unwrap();
79    // message bytes
80    data_to_sign.extend_from_slice(opts.message.as_bytes());
81
82    // last seen messages length
83    0i32.azalea_write(&mut data_to_sign).unwrap();
84    // signatures of last seen messages
85    // ... not implemented yet
86
87    let signing_key = rsa::pkcs1v15::SigningKey::<Sha256>::new(opts.private_key.clone());
88    let mut rng = rand::rng();
89    let signature = signing_key
90        .sign_with_rng(&mut rng, &data_to_sign)
91        .to_bytes();
92
93    MessageSignature {
94        bytes: signature
95            .as_ref()
96            .try_into()
97            .expect("signature must be 256 bytes"),
98    }
99}