Skip to main content

sig_net/
crypto.rs

1use hmac::{Hmac, Mac};
2use sha2::Sha256;
3
4use crate::*;
5
6pub fn hmac_sha256(key: &[u8], message: &[u8], output: &mut [u8; HMAC_SHA256_LENGTH]) -> Result<()> {
7    let mut mac = Hmac::<Sha256>::new_from_slice(key).map_err(|_| SigNetError::Crypto)?;
8    mac.update(message);
9    output.copy_from_slice(&mac.finalize().into_bytes());
10    Ok(())
11}
12
13pub fn hkdf_expand(prk: &[u8], info: &[u8], output: &mut [u8; DERIVED_KEY_LENGTH]) -> Result<()> {
14    let hk = hkdf::Hkdf::<Sha256>::from_prk(prk).map_err(|_| SigNetError::Crypto)?;
15    hk.expand(info, output).map_err(|_| SigNetError::Crypto)
16}
17
18pub fn derive_k0_from_passphrase(passphrase: &[u8], k0_output: &mut [u8; K0_KEY_LENGTH]) -> Result<()> {
19    if passphrase.is_empty() {
20        return Err(SigNetError::InvalidArgument);
21    }
22    pbkdf2::pbkdf2::<Hmac<Sha256>>(passphrase, PBKDF2_SALT, PBKDF2_ITERATIONS, k0_output)
23        .map_err(|_| SigNetError::Crypto)
24}
25
26pub fn derive_sender_key(k0: &[u8; K0_KEY_LENGTH], sender_key: &mut [u8; DERIVED_KEY_LENGTH]) -> Result<()> {
27    hkdf_expand(k0, HKDF_INFO_SENDER, sender_key)
28}
29
30pub fn derive_citizen_key(k0: &[u8; K0_KEY_LENGTH], citizen_key: &mut [u8; DERIVED_KEY_LENGTH]) -> Result<()> {
31    hkdf_expand(k0, HKDF_INFO_CITIZEN, citizen_key)
32}
33
34pub fn derive_manager_global_key(k0: &[u8; K0_KEY_LENGTH], mgr_key: &mut [u8; DERIVED_KEY_LENGTH]) -> Result<()> {
35    hkdf_expand(k0, HKDF_INFO_MANAGER_GLOBAL, mgr_key)
36}
37
38pub fn derive_manager_local_key(
39    k0: &[u8; K0_KEY_LENGTH],
40    tuid: &[u8; TUID_LENGTH],
41    mgr_local_key: &mut [u8; DERIVED_KEY_LENGTH],
42) -> Result<()> {
43    let hex = TUID(*tuid).to_hex_upper();
44    let mut info = [0u8; HKDF_INFO_INPUT_MAX];
45    let prefix_len = HKDF_INFO_MANAGER_LOCAL_PREFIX.len();
46    info[..prefix_len].copy_from_slice(HKDF_INFO_MANAGER_LOCAL_PREFIX);
47    info[prefix_len..prefix_len + TUID_HEX_LENGTH].copy_from_slice(&hex);
48    hkdf_expand(k0, &info[..prefix_len + TUID_HEX_LENGTH], mgr_local_key)
49}
50
51pub fn tuid_from_hex_string(hex_string: &[u8]) -> Result<[u8; TUID_LENGTH]> {
52    TUID::from_hex(hex_string).map(|t| t.0)
53}
54
55pub fn generate_dynamic_tuid(mfg_code: u16) -> Result<[u8; TUID_LENGTH]> {
56    let mut random_bytes = [0u8; 4];
57    getrandom::getrandom(&mut random_bytes).map_err(|_| SigNetError::Crypto)?;
58    let device_id = (random_bytes[0] as u32) << 24
59        | (random_bytes[1] as u32) << 16
60        | (random_bytes[2] as u32) << 8
61        | (random_bytes[3] as u32);
62    let device_id = (device_id | 0x80000000).min(0xFFFFFFEF);
63    let mut tuid = [0u8; TUID_LENGTH];
64    tuid[0] = (mfg_code >> 8) as u8;
65    tuid[1] = (mfg_code & 0xFF) as u8;
66    tuid[2] = (device_id >> 24) as u8;
67    tuid[3] = (device_id >> 16) as u8;
68    tuid[4] = (device_id >> 8) as u8;
69    tuid[5] = device_id as u8;
70    Ok(tuid)
71}
72
73#[derive(Debug, Clone)]
74pub struct PassphraseChecks {
75    pub length: usize,
76    pub length_ok: bool,
77    pub class_count: i32,
78    pub has_upper: bool,
79    pub has_lower: bool,
80    pub has_digit: bool,
81    pub has_symbol: bool,
82    pub classes_ok: bool,
83    pub no_identical: bool,
84    pub no_sequential: bool,
85}
86
87impl Default for PassphraseChecks {
88    fn default() -> Self {
89        PassphraseChecks {
90            length: 0,
91            length_ok: false,
92            class_count: 0,
93            has_upper: false,
94            has_lower: false,
95            has_digit: false,
96            has_symbol: false,
97            classes_ok: false,
98            no_identical: true,
99            no_sequential: true,
100        }
101    }
102}
103
104pub fn validate_passphrase(passphrase: &[u8]) -> Result<()> {
105    analyse_passphrase(passphrase).map(|_| ())
106}
107
108pub fn analyse_passphrase(passphrase: &[u8]) -> Result<PassphraseChecks> {
109    let length = passphrase.len();
110    let mut checks = PassphraseChecks { length, ..Default::default() };
111
112    // Bug 3 fix: empty passphrase must return error, not Ok
113    if passphrase.is_empty() {
114        return Err(SigNetError::PassphraseTooShort);
115    }
116
117    checks.length_ok = checks.length >= PASSPHRASE_MIN_LENGTH && checks.length <= PASSPHRASE_MAX_LENGTH;
118    if !checks.length_ok {
119        return if checks.length < PASSPHRASE_MIN_LENGTH {
120            Err(SigNetError::PassphraseTooShort)
121        } else {
122            Err(SigNetError::PassphraseTooLong)
123        };
124    }
125
126    for &c in passphrase {
127        if c.is_ascii_uppercase() {
128            checks.has_upper = true;
129        } else if c.is_ascii_lowercase() {
130            checks.has_lower = true;
131        } else if c.is_ascii_digit() {
132            checks.has_digit = true;
133        } else if PASSPHRASE_SYMBOLS.contains(&c) {
134            checks.has_symbol = true;
135        }
136    }
137
138    checks.class_count = [checks.has_upper, checks.has_lower, checks.has_digit, checks.has_symbol]
139        .iter()
140        .filter(|&&b| b)
141        .count() as i32;
142    checks.classes_ok = checks.class_count >= 3;
143
144    for i in 2..passphrase.len() {
145        if passphrase[i] == passphrase[i - 1] && passphrase[i] == passphrase[i - 2] {
146            checks.no_identical = false;
147            break;
148        }
149    }
150
151    // Bug 2 fix: check both ascending and descending sequences, matching C++
152    for i in 3..passphrase.len() {
153        let asc = passphrase[i] == passphrase[i - 1].wrapping_add(1)
154            && passphrase[i - 1] == passphrase[i - 2].wrapping_add(1)
155            && passphrase[i - 2] == passphrase[i - 3].wrapping_add(1);
156        let desc = passphrase[i] == passphrase[i - 1].wrapping_sub(1)
157            && passphrase[i - 1] == passphrase[i - 2].wrapping_sub(1)
158            && passphrase[i - 2] == passphrase[i - 3].wrapping_sub(1);
159        if asc || desc {
160            checks.no_sequential = false;
161            break;
162        }
163    }
164
165    // Bug 4 fix: error priority matches C++: identical → sequential → classes
166    if !checks.no_identical {
167        return Err(SigNetError::PassphraseConsecutiveIdentical);
168    }
169    if !checks.no_sequential {
170        return Err(SigNetError::PassphraseConsecutiveSequential);
171    }
172    if !checks.classes_ok {
173        return Err(SigNetError::PassphraseInsufficientClasses);
174    }
175
176    Ok(checks)
177}
178
179pub fn generate_random_passphrase(buf: &mut [u8; 11]) -> Result<()> {
180    let sets: &[&[u8]] = &[
181        PASSPHRASE_GEN_UPPERCASE,
182        PASSPHRASE_GEN_LOWERCASE,
183        PASSPHRASE_GEN_DIGITS,
184        PASSPHRASE_GEN_SYMBOLS,
185    ];
186
187    for _ in 0..100 {
188        let mut phrase = [0u8; PASSPHRASE_GENERATED_LENGTH];
189        getrandom::getrandom(&mut phrase).map_err(|_| SigNetError::Crypto)?;
190
191        for b in &mut phrase {
192            let idx = *b as usize;
193            let set_idx = idx % 4;
194            let set = sets[set_idx];
195            *b = set[idx % set.len()];
196        }
197
198        if analyse_passphrase(&phrase).is_ok() {
199            buf[..PASSPHRASE_GENERATED_LENGTH].copy_from_slice(&phrase);
200            buf[PASSPHRASE_GENERATED_LENGTH] = 0;
201            return Ok(());
202        }
203    }
204
205    Err(SigNetError::Crypto)
206}
207
208pub fn generate_random_k0(k0_output: &mut [u8; K0_KEY_LENGTH]) -> Result<()> {
209    getrandom::getrandom(k0_output).map_err(|_| SigNetError::Crypto)
210}
211
212/// Export guest keys (Km_global, Ks, Kc) from K0.
213/// Guest Managers possess only these global keys, not K0 or Km_local,
214/// enforcing a cryptographically restricted Read-Only administrative state.
215pub struct GuestKeys {
216    pub km_global: [u8; DERIVED_KEY_LENGTH],
217    pub ks: [u8; DERIVED_KEY_LENGTH],
218    pub kc: [u8; DERIVED_KEY_LENGTH],
219}
220
221pub fn export_guest_keys(k0: &[u8; K0_KEY_LENGTH]) -> Result<GuestKeys> {
222    let mut keys = GuestKeys {
223        km_global: [0u8; DERIVED_KEY_LENGTH],
224        ks: [0u8; DERIVED_KEY_LENGTH],
225        kc: [0u8; DERIVED_KEY_LENGTH],
226    };
227    derive_manager_global_key(k0, &mut keys.km_global)?;
228    derive_sender_key(k0, &mut keys.ks)?;
229    derive_citizen_key(k0, &mut keys.kc)?;
230    Ok(keys)
231}
232
233pub fn build_hmac_input(
234    uri_string: &str,
235    options: &SigNetOptions,
236    payload: &[u8],
237    output: &mut [u8],
238) -> Result<usize> {
239    let mut pos = 0;
240    let uri = uri_string.as_bytes();
241    output[pos..pos + uri.len()].copy_from_slice(uri);
242    pos += uri.len();
243    output[pos] = options.security_mode;
244    pos += 1;
245    output[pos..pos + SENDER_ID_LENGTH].copy_from_slice(&options.sender_id);
246    pos += SENDER_ID_LENGTH;
247    output[pos..pos + 2].copy_from_slice(&options.mfg_code.to_be_bytes());
248    pos += 2;
249    output[pos..pos + 4].copy_from_slice(&options.session_id.to_be_bytes());
250    pos += 4;
251    output[pos..pos + 4].copy_from_slice(&options.seq_num.to_be_bytes());
252    pos += 4;
253    output[pos..pos + payload.len()].copy_from_slice(payload);
254    pos += payload.len();
255    Ok(pos)
256}
257
258pub fn verify_packet_hmac(
259    uri_string: &str,
260    options: &SigNetOptions,
261    payload: &[u8],
262    role_key: &[u8],
263) -> Result<()> {
264    use subtle::ConstantTimeEq;
265
266    let input_len = uri_string.len() + 1 + SENDER_ID_LENGTH + 2 + 4 + 4 + payload.len();
267    if input_len > HMAC_INPUT_MAX {
268        return Err(SigNetError::InvalidArgument);
269    }
270    let mut hmac_input = [0u8; HMAC_INPUT_MAX];
271    build_hmac_input(uri_string, options, payload, &mut hmac_input[..input_len])?;
272
273    let mut computed = [0u8; HMAC_SHA256_LENGTH];
274    hmac_sha256(role_key, &hmac_input[..input_len], &mut computed)?;
275
276    if computed.ct_eq(&options.hmac).into() {
277        Ok(())
278    } else {
279        Err(SigNetError::HmacFailed)
280    }
281}
282
283pub fn compute_packet_hmac(
284    uri_string: &str,
285    options: &SigNetOptions,
286    payload: &[u8],
287    signing_key: &[u8],
288) -> Result<[u8; HMAC_SHA256_LENGTH]> {
289    let input_len = uri_string.len() + 1 + SENDER_ID_LENGTH + 2 + 4 + 4 + payload.len();
290    if input_len > HMAC_INPUT_MAX {
291        return Err(SigNetError::InvalidArgument);
292    }
293    let mut hmac_input = [0u8; HMAC_INPUT_MAX];
294    build_hmac_input(uri_string, options, payload, &mut hmac_input[..input_len])?;
295    let mut hmac = [0u8; HMAC_SHA256_LENGTH];
296    hmac_sha256(signing_key, &hmac_input[..input_len], &mut hmac)?;
297    Ok(hmac)
298}