rs-matter/matter/src/secure_channel/spake2p.rs
2022-12-27 09:52:25 +05:30

276 lines
8.9 KiB
Rust

/*
*
* 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<Sha256>,
Ke: [u8; 16],
cA: [u8; 32],
crypto_spake2: Option<Box<dyn CryptoSpake2>>,
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<Box<dyn CryptoSpake2>, Error> {
Ok(Box::new(CryptoOpenSSL::new()?))
}
#[cfg(feature = "crypto_mbedtls")]
fn crypto_spake2_new() -> Result<Box<dyn CryptoSpake2>, Error> {
Ok(Box::new(CryptoMbedTLS::new()?))
}
#[cfg(feature = "crypto_esp_mbedtls")]
fn crypto_spake2_new() -> Result<Box<dyn CryptoSpake2>, 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);
}
}
}