/* * * Copyright (c) 2020-2022 Project CHIP Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ use crate::crypto::{self, HmacSha256}; use byteorder::{ByteOrder, LittleEndian}; use subtle::ConstantTimeEq; use crate::{ crypto::{pbkdf2_hmac, Sha256}, error::Error, }; #[cfg(feature = "crypto_openssl")] use super::crypto_openssl::CryptoOpenSSL; #[cfg(feature = "crypto_mbedtls")] use super::crypto_mbedtls::CryptoMbedTLS; #[cfg(feature = "crypto_esp_mbedtls")] use super::crypto_esp_mbedtls::CryptoEspMbedTls; use super::{common::SCStatusCodes, crypto::CryptoSpake2}; // This file handle Spake2+ specific instructions. In itself, this file is // independent from the BigNum and EC operations that are typically required // Spake2+. We use the CryptoSpake2 trait object that allows us to abstract // out the specific implementations. // // In the case of the verifier, we don't actually release the Ke until we // validate that the cA is confirmed. #[derive(PartialEq, Copy, Clone, Debug)] pub enum Spake2VerifierState { // Initialised - w0, L are set Init, // Pending Confirmation - Keys are derived but pending confirmation PendingConfirmation, // Confirmed Confirmed, } #[derive(PartialEq, Debug)] pub enum Spake2Mode { Unknown, Prover, Verifier(Spake2VerifierState), } #[allow(non_snake_case)] pub struct Spake2P { mode: Spake2Mode, context: Option, Ke: [u8; 16], cA: [u8; 32], crypto_spake2: Option>, app_data: u32, } const SPAKE2P_KEY_CONFIRM_INFO: [u8; 16] = *b"ConfirmationKeys"; const SPAKE2P_CONTEXT_PREFIX: [u8; 26] = *b"CHIP PAKE V1 Commissioning"; const CRYPTO_GROUP_SIZE_BYTES: usize = 32; const CRYPTO_W_SIZE_BYTES: usize = CRYPTO_GROUP_SIZE_BYTES + 8; #[cfg(feature = "crypto_openssl")] fn crypto_spake2_new() -> Result, Error> { Ok(Box::new(CryptoOpenSSL::new()?)) } #[cfg(feature = "crypto_mbedtls")] fn crypto_spake2_new() -> Result, Error> { Ok(Box::new(CryptoMbedTLS::new()?)) } #[cfg(feature = "crypto_esp_mbedtls")] fn crypto_spake2_new() -> Result, Error> { Ok(Box::new(CryptoEspMbedTls::new()?)) } impl Default for Spake2P { fn default() -> Self { Self::new() } } impl Spake2P { pub fn new() -> Self { Spake2P { mode: Spake2Mode::Unknown, context: None, crypto_spake2: None, Ke: [0; 16], cA: [0; 32], app_data: 0, } } pub fn set_app_data(&mut self, data: u32) { self.app_data = data; } pub fn get_app_data(&mut self) -> u32 { self.app_data } pub fn set_context(&mut self, buf1: &[u8], buf2: &[u8]) -> Result<(), Error> { let mut context = Sha256::new()?; context.update(&SPAKE2P_CONTEXT_PREFIX)?; context.update(buf1)?; context.update(buf2)?; self.context = Some(context); Ok(()) } #[inline(always)] fn get_w0w1s(pw: u32, iter: u32, salt: &[u8], w0w1s: &mut [u8]) { let mut pw_str: [u8; 4] = [0; 4]; LittleEndian::write_u32(&mut pw_str, pw); let _ = pbkdf2_hmac(&pw_str, iter as usize, salt, w0w1s); } pub fn start_verifier(&mut self, pw: u32, iter: u32, salt: &[u8]) -> Result<(), Error> { let mut w0w1s: [u8; (2 * CRYPTO_W_SIZE_BYTES)] = [0; (2 * CRYPTO_W_SIZE_BYTES)]; Spake2P::get_w0w1s(pw, iter, salt, &mut w0w1s); self.crypto_spake2 = Some(crypto_spake2_new()?); let w0s_len = w0w1s.len() / 2; if let Some(crypto_spake2) = &mut self.crypto_spake2 { crypto_spake2.set_w0_from_w0s(&w0w1s[0..w0s_len])?; crypto_spake2.set_L(&w0w1s[w0s_len..])?; } self.mode = Spake2Mode::Verifier(Spake2VerifierState::Init); Ok(()) } #[allow(non_snake_case)] pub fn handle_pA(&mut self, pA: &[u8], pB: &mut [u8], cB: &mut [u8]) -> Result<(), Error> { if self.mode != Spake2Mode::Verifier(Spake2VerifierState::Init) { return Err(Error::InvalidState); } if let Some(crypto_spake2) = &mut self.crypto_spake2 { crypto_spake2.get_pB(pB)?; if let Some(context) = self.context.take() { let mut hash = [0u8; crypto::SHA256_HASH_LEN_BYTES]; context.finish(&mut hash)?; let mut TT = [0u8; crypto::SHA256_HASH_LEN_BYTES]; crypto_spake2.get_TT_as_verifier(&hash, pA, pB, &mut TT)?; Spake2P::get_Ke_and_cAcB(&TT, pA, pB, &mut self.Ke, &mut self.cA, cB)?; } } // We are finished with using the crypto_spake2 now self.crypto_spake2 = None; self.mode = Spake2Mode::Verifier(Spake2VerifierState::PendingConfirmation); Ok(()) } #[allow(non_snake_case)] pub fn handle_cA(&mut self, cA: &[u8]) -> (SCStatusCodes, Option<&[u8]>) { if self.mode != Spake2Mode::Verifier(Spake2VerifierState::PendingConfirmation) { return (SCStatusCodes::SessionNotFound, None); } self.mode = Spake2Mode::Verifier(Spake2VerifierState::Confirmed); if cA.ct_eq(&self.cA).unwrap_u8() == 1 { (SCStatusCodes::SessionEstablishmentSuccess, Some(&self.Ke)) } else { (SCStatusCodes::InvalidParameter, None) } } #[inline(always)] #[allow(non_snake_case)] #[allow(dead_code)] fn get_Ke_and_cAcB( TT: &[u8], pA: &[u8], pB: &[u8], Ke: &mut [u8], cA: &mut [u8], cB: &mut [u8], ) -> Result<(), Error> { // Step 1: Ka || Ke = Hash(TT) let KaKe = TT; let KaKe_len = KaKe.len(); let Ka = &KaKe[0..KaKe_len / 2]; let ke_internal = &KaKe[(KaKe_len / 2)..]; if ke_internal.len() == Ke.len() { Ke.copy_from_slice(ke_internal); } else { return Err(Error::NoSpace); } // Step 2: KcA || KcB = KDF(nil, Ka, "ConfirmationKeys") let mut KcAKcB: [u8; 32] = [0; 32]; crypto::hkdf_sha256(&[], Ka, &SPAKE2P_KEY_CONFIRM_INFO, &mut KcAKcB) .map_err(|_x| Error::NoSpace)?; let KcA = &KcAKcB[0..(KcAKcB.len() / 2)]; let KcB = &KcAKcB[(KcAKcB.len() / 2)..]; // Step 3: cA = HMAC(KcA, pB), cB = HMAC(KcB, pA) let mut mac = HmacSha256::new(KcA)?; mac.update(pB)?; mac.finish(cA)?; let mut mac = HmacSha256::new(KcB)?; mac.update(pA)?; mac.finish(cB)?; Ok(()) } } #[cfg(test)] mod tests { use super::Spake2P; use crate::{ crypto, secure_channel::{spake2p::CRYPTO_W_SIZE_BYTES, spake2p_test_vectors::test_vectors::*}, }; #[test] fn test_pbkdf2() { // These are the vectors from one sample run of chip-tool along with our PBKDFParamResponse let salt = [ 0x4, 0xa1, 0xd2, 0xc6, 0x11, 0xf0, 0xbd, 0x36, 0x78, 0x67, 0x79, 0x7b, 0xfe, 0x82, 0x36, 0x0, ]; let mut w0w1s: [u8; (2 * CRYPTO_W_SIZE_BYTES)] = [0; (2 * CRYPTO_W_SIZE_BYTES)]; Spake2P::get_w0w1s(123456, 2000, &salt, &mut w0w1s); assert_eq!( w0w1s, [ 0xc7, 0x89, 0x33, 0x9c, 0xc5, 0xeb, 0xbc, 0xf6, 0xdf, 0x04, 0xa9, 0x11, 0x11, 0x06, 0x4c, 0x15, 0xac, 0x5a, 0xea, 0x67, 0x69, 0x9f, 0x32, 0x62, 0xcf, 0xc6, 0xe9, 0x19, 0xe8, 0xa4, 0x0b, 0xb3, 0x42, 0xe8, 0xc6, 0x8e, 0xa9, 0x9a, 0x73, 0xe2, 0x59, 0xd1, 0x17, 0xd8, 0xed, 0xcb, 0x72, 0x8c, 0xbf, 0x3b, 0xa9, 0x88, 0x02, 0xd8, 0x45, 0x4b, 0xd0, 0x2d, 0xe5, 0xe4, 0x1c, 0xc3, 0xd7, 0x00, 0x03, 0x3c, 0x86, 0x20, 0x9a, 0x42, 0x5f, 0x55, 0x96, 0x3b, 0x9f, 0x6f, 0x79, 0xef, 0xcb, 0x37 ] ) } #[test] #[allow(non_snake_case)] fn test_get_Ke_and_cAcB() { for t in RFC_T { let mut Ke: [u8; 16] = [0; 16]; let mut cA: [u8; 32] = [0; 32]; let mut cB: [u8; 32] = [0; 32]; let mut TT_hash = [0u8; crypto::SHA256_HASH_LEN_BYTES]; let mut h = crypto::Sha256::new().unwrap(); h.update(&t.TT[0..t.TT_len]).unwrap(); h.finish(&mut TT_hash).unwrap(); Spake2P::get_Ke_and_cAcB(&TT_hash, &t.X, &t.Y, &mut Ke, &mut cA, &mut cB).unwrap(); assert_eq!(Ke, t.Ke); assert_eq!(cA, t.cA); assert_eq!(cB, t.cB); } } }