Skip to main content

m_bus_core/
decryption.rs

1use crate::{DeviceType, ManufacturerCode, SecurityMode};
2
3#[cfg(feature = "decryption")]
4use aes::Aes128;
5#[cfg(feature = "decryption")]
6use cbc::{
7    Decryptor,
8    cipher::{BlockDecryptMut, KeyIvInit},
9};
10
11#[derive(Debug, Clone, PartialEq)]
12pub struct KeyContext {
13    pub manufacturer: ManufacturerCode,
14    pub identification_number: u32,
15    pub version: u8,
16    pub device_type: DeviceType,
17    pub security_mode: SecurityMode,
18    pub access_number: u8,
19}
20
21#[derive(Debug, Clone, Copy, PartialEq)]
22pub enum DecryptionError {
23    UnsupportedMode(SecurityMode),
24    KeyNotFound,
25    DecryptionFailed,
26    InvalidKeyLength,
27    InvalidDataLength,
28    NotEncrypted,
29    UnknownEncryptionState,
30}
31
32impl core::fmt::Display for DecryptionError {
33    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34        match self {
35            Self::UnsupportedMode(mode) => write!(f, "Unsupported security mode: {:?}", mode),
36            Self::KeyNotFound => write!(f, "Decryption key not found"),
37            Self::DecryptionFailed => write!(f, "Decryption operation failed"),
38            Self::InvalidKeyLength => write!(f, "Invalid key length"),
39            Self::InvalidDataLength => write!(f, "Invalid data length"),
40            Self::NotEncrypted => write!(f, "Data is not encrypted"),
41            Self::UnknownEncryptionState => {
42                write!(f, "Unknown encryption state for this data block type")
43            }
44        }
45    }
46}
47
48impl core::error::Error for DecryptionError {}
49
50pub trait KeyProvider {
51    fn get_key(&self, context: &KeyContext) -> Result<&[u8], DecryptionError>;
52}
53
54#[derive(Debug)]
55pub struct EncryptedPayload<'a> {
56    pub data: &'a [u8],
57    pub context: KeyContext,
58}
59
60impl<'a> EncryptedPayload<'a> {
61    pub fn new(data: &'a [u8], context: KeyContext) -> Self {
62        Self { data, context }
63    }
64
65    #[cfg(feature = "decryption")]
66    pub fn decrypt_into<K: KeyProvider>(
67        &self,
68        provider: &K,
69        output: &mut [u8],
70    ) -> Result<usize, DecryptionError> {
71        let key = provider.get_key(&self.context)?;
72
73        match self.context.security_mode {
74            SecurityMode::NoEncryption => {
75                let len = self.data.len();
76                let dest = output
77                    .get_mut(..len)
78                    .ok_or(DecryptionError::InvalidDataLength)?;
79                dest.copy_from_slice(self.data);
80                Ok(len)
81            }
82            SecurityMode::AesCbc128IvZero => {
83                decrypt_aes_cbc_into(self.data, key, &[0u8; 16], output)
84            }
85            SecurityMode::AesCbc128IvNonZero => {
86                let iv = self._derive_iv();
87                decrypt_aes_cbc_into(self.data, key, &iv, output)
88            }
89            mode => Err(DecryptionError::UnsupportedMode(mode)),
90        }
91    }
92
93    #[cfg(feature = "decryption")]
94    fn _derive_iv(&self) -> [u8; 16] {
95        let mut iv = [0u8; 16];
96        // Bytes 0-1: Manufacturer ID (numeric, little-endian)
97        let mfr_id = self.context.manufacturer.to_id();
98        iv[0..2].copy_from_slice(&mfr_id.to_le_bytes());
99        // Bytes 2-5: Identification number as BCD bytes (how they appear in frame)
100        let bcd_bytes = decimal_to_bcd(self.context.identification_number);
101        iv[2..6].copy_from_slice(&bcd_bytes);
102        // Byte 6: Version
103        iv[6] = self.context.version;
104        // Byte 7: Device type
105        iv[7] = self.context.device_type.into();
106        // Bytes 8-15: Access number repeated 8 times
107        iv[8..16].fill(self.context.access_number);
108        iv
109    }
110}
111
112/// Convert a decimal number to BCD bytes (little-endian, 4 bytes)
113/// e.g., 14639203 -> [0x03, 0x92, 0x63, 0x14]
114#[cfg(feature = "decryption")]
115fn decimal_to_bcd(mut value: u32) -> [u8; 4] {
116    let mut bcd = [0u8; 4];
117    for byte in &mut bcd {
118        let low = (value % 10) as u8;
119        value /= 10;
120        let high = (value % 10) as u8;
121        value /= 10;
122        *byte = (high << 4) | low;
123    }
124    bcd
125}
126
127pub struct StaticKeyProvider<const N: usize> {
128    entries: [(u64, [u8; 16]); N],
129    count: usize,
130}
131
132impl<const N: usize> Default for StaticKeyProvider<N> {
133    fn default() -> Self {
134        Self {
135            entries: [(0, [0u8; 16]); N],
136            count: 0,
137        }
138    }
139}
140
141impl<const N: usize> StaticKeyProvider<N> {
142    pub fn new() -> Self {
143        Self::default()
144    }
145
146    pub fn add_key(
147        &mut self,
148        manufacturer_id: u16,
149        identification_number: u32,
150        key: [u8; 16],
151    ) -> Result<(), DecryptionError> {
152        let key_id = Self::compute_key_id(manufacturer_id, identification_number);
153        let entry = self
154            .entries
155            .get_mut(self.count)
156            .ok_or(DecryptionError::InvalidDataLength)?;
157        *entry = (key_id, key);
158        self.count += 1;
159        Ok(())
160    }
161
162    const fn compute_key_id(manufacturer_id: u16, identification_number: u32) -> u64 {
163        ((manufacturer_id as u64) << 32) | (identification_number as u64)
164    }
165}
166
167impl<const N: usize> KeyProvider for StaticKeyProvider<N> {
168    fn get_key(&self, context: &KeyContext) -> Result<&[u8], DecryptionError> {
169        let manufacturer_id = context.manufacturer.to_id();
170
171        let key_id = Self::compute_key_id(manufacturer_id, context.identification_number);
172
173        self.entries[..self.count]
174            .iter()
175            .find(|(id, _)| *id == key_id)
176            .map(|(_, key)| key.as_slice())
177            .ok_or(DecryptionError::KeyNotFound)
178    }
179}
180
181#[cfg(feature = "decryption")]
182fn decrypt_aes_cbc_into(
183    data: &[u8],
184    key: &[u8],
185    iv: &[u8],
186    output: &mut [u8],
187) -> Result<usize, DecryptionError> {
188    if key.len() != 16 {
189        return Err(DecryptionError::InvalidKeyLength);
190    }
191
192    if iv.len() != 16 {
193        return Err(DecryptionError::InvalidDataLength);
194    }
195
196    if data.is_empty() {
197        return Err(DecryptionError::InvalidDataLength);
198    }
199
200    let len = data.len();
201    // Round down to nearest multiple of 16 for encryption
202    let encrypted_len = len - (len % 16);
203
204    if encrypted_len == 0 {
205        // No full blocks to decrypt, just copy data as-is
206        let dest = output
207            .get_mut(..len)
208            .ok_or(DecryptionError::InvalidDataLength)?;
209        dest.copy_from_slice(data);
210        return Ok(len);
211    }
212
213    // Copy all data to output buffer (encrypted + any trailing plaintext)
214    let dest = output
215        .get_mut(..len)
216        .ok_or(DecryptionError::InvalidDataLength)?;
217    dest.copy_from_slice(data);
218
219    // Create decryptor
220    type Aes128CbcDec = Decryptor<Aes128>;
221
222    let key_array: [u8; 16] = key
223        .try_into()
224        .map_err(|_| DecryptionError::InvalidKeyLength)?;
225    let iv_array: [u8; 16] = iv
226        .try_into()
227        .map_err(|_| DecryptionError::InvalidDataLength)?;
228
229    let decryptor = Aes128CbcDec::new(&key_array.into(), &iv_array.into());
230
231    // Decrypt only the aligned portion in place
232    let decrypt_buf = output
233        .get_mut(..encrypted_len)
234        .ok_or(DecryptionError::InvalidDataLength)?;
235    decryptor
236        .decrypt_padded_mut::<cipher::block_padding::NoPadding>(decrypt_buf)
237        .map_err(|_| DecryptionError::DecryptionFailed)?;
238
239    // Return total length (decrypted blocks + trailing plaintext)
240    Ok(len)
241}
242
243#[cfg(all(test, feature = "decryption"))]
244mod tests {
245    use super::*;
246
247    #[test]
248    fn test_decrypt_aes_cbc_basic() {
249        // Test vector: simple AES-128-CBC decryption
250        let key = [0u8; 16];
251        let iv = [0u8; 16];
252        let encrypted = [
253            0x66, 0xe9, 0x4b, 0xd4, 0xef, 0x8a, 0x2c, 0x3b, 0x88, 0x4c, 0xfa, 0x59, 0xca, 0x34,
254            0x2b, 0x2e,
255        ];
256        let mut output = [0u8; 16];
257
258        let result = decrypt_aes_cbc_into(&encrypted, &key, &iv, &mut output);
259        assert!(result.is_ok());
260        let len = result.unwrap();
261        assert_eq!(len, 16);
262    }
263
264    #[test]
265    fn test_key_provider_basic() {
266        let mut provider = StaticKeyProvider::<10>::new();
267
268        // Add a test key
269        let key = [
270            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
271            0x0F, 0x10,
272        ];
273
274        // Manufacturer code "ABC" -> ID calculation
275        let manufacturer_code = ManufacturerCode::from_id(0x0421).unwrap(); // ABC
276        let identification_number = 12345678u32;
277
278        // Add the key
279        provider
280            .add_key(0x0421, identification_number, key)
281            .unwrap();
282
283        // Create context
284        let context = KeyContext {
285            manufacturer: manufacturer_code,
286            identification_number,
287            version: 0x01,
288            device_type: DeviceType::WaterMeter,
289            security_mode: crate::SecurityMode::AesCbc128IvZero,
290            access_number: 0x00,
291        };
292
293        // Retrieve the key
294        let retrieved_key = provider.get_key(&context).unwrap();
295        assert_eq!(retrieved_key, &key);
296    }
297
298    #[test]
299    fn test_derive_iv() {
300        let manufacturer_code = ManufacturerCode::from_id(0x1ee6).unwrap(); // GWF
301        let context = KeyContext {
302            manufacturer: manufacturer_code,
303            identification_number: 12345678,
304            version: 0x42,
305            device_type: DeviceType::WaterMeter,
306            security_mode: crate::SecurityMode::AesCbc128IvNonZero,
307            access_number: 0x50,
308        };
309
310        let payload = EncryptedPayload { data: &[], context };
311
312        let iv = payload._derive_iv();
313
314        // Check manufacturer ID (first 2 bytes should be 0x1ee6 in little-endian)
315        assert_eq!(iv[0], 0xe6);
316        assert_eq!(iv[1], 0x1e);
317
318        // Check identification number (bytes 2-5, as BCD)
319        // 12345678 decimal -> BCD [0x78, 0x56, 0x34, 0x12]
320        assert_eq!(&iv[2..6], &[0x78, 0x56, 0x34, 0x12]);
321
322        // Check version (byte 6)
323        assert_eq!(iv[6], 0x42);
324
325        // Check device type (byte 7) - WaterMeter should be 0x07
326        assert_eq!(iv[7], 0x07);
327
328        // Check access number repeated (bytes 8-15)
329        for &byte in iv.iter().skip(8) {
330            assert_eq!(byte, 0x50);
331        }
332    }
333
334    #[test]
335    fn test_mode5_decryption_real_frame() {
336        // Test vector from real wMBus Mode 5 encrypted frame
337        // Frame: 6644496A3100015514377203926314496A00075000500598A78E0D...
338        // Key: F8B24F12F9D113F680BEE765FDE67EC0
339        // Expected IV: 496A0392631400075050505050505050
340
341        let key = [
342            0xF8, 0xB2, 0x4F, 0x12, 0xF9, 0xD1, 0x13, 0xF6, 0x80, 0xBE, 0xE7, 0x65, 0xFD, 0xE6,
343            0x7E, 0xC0,
344        ];
345
346        // Encrypted payload (80 bytes after TPL header)
347        let encrypted = [
348            0x98, 0xA7, 0x8E, 0x0D, 0x71, 0xAA, 0x63, 0x58, 0xEE, 0xBD, 0x0B, 0x20, 0xBF, 0xDF,
349            0x99, 0xED, 0xA2, 0xD2, 0x2F, 0xA2, 0x53, 0x14, 0xF3, 0xF1, 0xB8, 0x44, 0x70, 0x89,
350            0x8E, 0x49, 0x53, 0x03, 0x92, 0x37, 0x70, 0xBA, 0x8D, 0xDA, 0x97, 0xC9, 0x64, 0xF0,
351            0xEA, 0x6C, 0xE2, 0x4F, 0x56, 0x50, 0xC0, 0xA6, 0xCD, 0xF3, 0xDE, 0x37, 0xDE, 0x33,
352            0xFB, 0xFB, 0xEB, 0xAC, 0xE4, 0x00, 0x9B, 0xB0, 0xD8, 0xEB, 0xA2, 0xCB, 0xE8, 0x04,
353            0x33, 0xFF, 0x13, 0x13, 0x28, 0x20, 0x60, 0x20, 0xB1, 0xBF,
354        ];
355
356        // Expected decrypted data
357        let expected = [
358            0x2F, 0x2F, 0x0C, 0x13, 0x29, 0x73, 0x06, 0x00, 0x02, 0x6C, 0x94, 0x21, 0x82, 0x04,
359            0x6C, 0x81, 0x21, 0x8C, 0x04, 0x13, 0x75, 0x44, 0x06, 0x00, 0x8D, 0x04, 0x93, 0x13,
360            0x2C, 0xFB, 0xFE, 0x12, 0x44, 0x00, 0x51, 0x41, 0x00, 0x70, 0x35, 0x00, 0x77, 0x33,
361            0x00, 0x75, 0x49, 0x00, 0x16, 0x36, 0x00, 0x73, 0x56, 0x00, 0x91, 0x55, 0x00, 0x95,
362            0x57, 0x00, 0x31, 0x57, 0x00, 0x28, 0x42, 0x00, 0x18, 0x47, 0x00, 0x61, 0x39, 0x00,
363            0x56, 0x42, 0x00, 0x02, 0xFD, 0x17, 0x00, 0x00, 0x2F, 0x2F,
364        ];
365
366        // Build context from TPL header:
367        // Frame bytes [0x49, 0x6A] = manufacturer ID 0x6A49 when parsed as little-endian
368        // ID bytes [0x03, 0x92, 0x63, 0x14] in BCD = 14639203 decimal
369        let manufacturer = ManufacturerCode::from_id(0x6A49).unwrap();
370        let context = KeyContext {
371            manufacturer,
372            identification_number: 14639203, // BCD 03926314 parsed as decimal
373            version: 0x00,
374            device_type: DeviceType::WaterMeter,
375            security_mode: crate::SecurityMode::AesCbc128IvNonZero,
376            access_number: 0x50,
377        };
378
379        // Verify IV derivation
380        let payload = EncryptedPayload {
381            data: &encrypted,
382            context: context.clone(),
383        };
384        let iv = payload._derive_iv();
385        // Expected IV: 496A0392631400075050505050505050
386        let expected_iv = [
387            0x49, 0x6A, 0x03, 0x92, 0x63, 0x14, 0x00, 0x07, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50,
388            0x50, 0x50,
389        ];
390        assert_eq!(iv, expected_iv);
391
392        // Test decryption
393        let mut provider = StaticKeyProvider::<1>::new();
394        provider.add_key(0x6A49, 14639203, key).unwrap();
395
396        let mut output = [0u8; 80];
397        let len = payload.decrypt_into(&provider, &mut output).unwrap();
398
399        assert_eq!(len, 80);
400        assert_eq!(&output[..80], &expected[..]);
401    }
402}