//! RSA encryption for Steam session key exchange.
//!
//! This module implements the RSA encryption used during the TCP ChannelEncrypt
//! handshake. The client generates a 32-byte session key, encrypts it with
//! Steam's public RSA key, and sends it to the server.
use rand::RngCore;
use rsa::{Oaep, RsaPublicKey};
use sha1::Sha1;
use crate::{error::CryptoError, session_key::SessionKey};
/// Steam's public RSA key for session key encryption.
/// This is the same key used by all Steam clients for the encryption handshake.
const STEAM_PUBLIC_KEY_MODULUS: &[u8] = &[
0xDF, 0xEC, 0x1A, 0xD6, 0x2C, 0x10, 0x66, 0x2C, 0x17, 0x35, 0x3A, 0x14, 0xB0, 0x7C, 0x59, 0x11, 0x7F, 0x9D, 0xD3, 0xD8, 0x2B, 0x7A, 0xE3, 0xE0, 0x15, 0xCD, 0x19, 0x1E, 0x46, 0xE8, 0x7B, 0x87, 0x74, 0xA2, 0x18, 0x46, 0x31, 0xA9, 0x03, 0x14, 0x79, 0x82, 0x1F, 0x11, 0x13, 0xF4, 0xC0, 0xCE, 0x63, 0x1F, 0x73, 0x53, 0xD0, 0x5C, 0x82, 0xD5, 0x14, 0x9C, 0x1E, 0xB8, 0x67, 0xE9, 0x5B, 0xF7, 0x0F, 0xD5, 0x51, 0x40, 0x11, 0x4E, 0xF9, 0x75, 0x6D, 0x29, 0x00, 0x10, 0xB4, 0xF6, 0x0E, 0x7F, 0x79, 0xE5, 0x67, 0xE7, 0x62, 0x25, 0x9E, 0xC7, 0x3B, 0xAB, 0x19, 0x7C, 0xD2, 0xF9, 0x18, 0x51, 0xBF, 0x68, 0x6E,
0xA5, 0x30, 0x6B, 0x00, 0x63, 0x1A, 0x5A, 0x1E, 0x1C, 0x11, 0x75, 0xC4, 0x15, 0xD9, 0x3C, 0xE0, 0xF5, 0x97, 0xF6, 0xE6, 0x08, 0x27, 0xFE, 0xA6, 0xF4, 0x08, 0x8C, 0xD8, 0x59,
];
const STEAM_PUBLIC_KEY_EXPONENT: u32 = 0x11; // 17
/// Result of generating a session key for the encryption handshake.
pub struct SessionKeyPair {
/// The plain 32-byte session key for symmetric encryption.
pub plain: SessionKey,
/// The RSA-encrypted session key to send to the server.
pub encrypted: Vec<u8>,
}
/// Generate a new session key and encrypt it with Steam's public key.
///
/// This is used during the TCP ChannelEncrypt handshake:
/// 1. Server sends ChannelEncryptRequest with a 16-byte nonce
/// 2. Client generates 32-byte session key, XORs first 16 bytes with nonce
/// 3. Client encrypts session key with Steam's RSA public key
/// 4. Client sends encrypted key in ChannelEncryptResponse
///
/// # Arguments
/// * `nonce` - The 16-byte nonce received from the server
///
/// # Returns
/// A [`SessionKeyPair`] containing both the plain and encrypted keys.
pub fn generate_session_key(nonce: &[u8; 16]) -> Result<SessionKeyPair, CryptoError> {
// Generate a random 32-byte session key
let mut key_bytes = [0u8; 32];
rand::rng().fill_bytes(&mut key_bytes);
// XOR the first 16 bytes with the server's nonce
for i in 0..16 {
key_bytes[i] ^= nonce[i];
}
// Build the RSA public key
let n = rsa::BigUint::from_bytes_be(STEAM_PUBLIC_KEY_MODULUS);
let e = rsa::BigUint::from(STEAM_PUBLIC_KEY_EXPONENT);
let public_key = RsaPublicKey::new(n, e).map_err(|e| CryptoError::EncryptionFailed(format!("Invalid RSA key: {}", e)))?;
// Encrypt with RSA-OAEP using SHA1
let padding = Oaep::new::<Sha1>();
// Compatibility wrapper for rand_core 0.6 required by rsa 0.9
struct RandCore6Wrapper;
impl rsa::rand_core::RngCore for RandCore6Wrapper {
fn next_u32(&mut self) -> u32 {
rand::random()
}
fn next_u64(&mut self) -> u64 {
rand::random()
}
fn fill_bytes(&mut self, dest: &mut [u8]) {
rand::rng().fill_bytes(dest)
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rsa::rand_core::Error> {
rand::rng().fill_bytes(dest);
Ok(())
}
}
impl rsa::rand_core::CryptoRng for RandCore6Wrapper {}
let mut rng = RandCore6Wrapper;
let encrypted = public_key.encrypt(&mut rng, padding, &key_bytes).map_err(|e| CryptoError::EncryptionFailed(format!("RSA encryption failed: {}", e)))?;
// Create the plain session key (without nonce XOR for actual use)
// The server will XOR with nonce on its side
let plain = SessionKey::from_bytes(&key_bytes).ok_or_else(|| CryptoError::EncryptionFailed("Invalid session key length".into()))?;
Ok(SessionKeyPair { plain, encrypted })
}
/// Calculate CRC32 checksum of the encrypted session key.
///
/// This is sent as part of ChannelEncryptResponse for verification.
pub fn calculate_key_crc(encrypted_key: &[u8]) -> u32 {
crc32fast::hash(encrypted_key)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_session_key() {
let nonce = [0u8; 16];
let result = generate_session_key(&nonce).unwrap();
// Encrypted key should be 128 bytes (1024-bit RSA)
assert_eq!(result.encrypted.len(), 128);
// Plain key should be 32 bytes
assert_eq!(result.plain.as_bytes().len(), 32);
}
#[test]
fn test_key_crc() {
let data = b"test data";
let crc = calculate_key_crc(data);
assert!(crc != 0);
}
}