/* * * 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 std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; use crate::acl::{AclEntry, AclMgr, AuthMode}; use crate::cert::Cert; use crate::crypto::{self, CryptoKeyPair, KeyPair}; use crate::data_model::objects::*; use crate::data_model::sdm::dev_att; use crate::fabric::{Fabric, FabricMgr}; use crate::interaction_model::command::CommandReq; use crate::interaction_model::core::IMStatusCode; use crate::interaction_model::messages::ib; use crate::tlv::{FromTLV, OctetStr, TLVElement, TLVWriter, TagType, ToTLV}; use crate::transport::session::SessionMode; use crate::utils::writebuf::WriteBuf; use crate::{cmd_enter, error::*}; use log::{error, info}; use num_derive::FromPrimitive; use super::dev_att::{DataType, DevAttDataFetcher}; use super::failsafe::FailSafe; // Node Operational Credentials Cluster #[derive(Clone, Copy)] #[allow(dead_code)] enum NocStatus { Ok = 0, InvalidPublicKey = 1, InvalidNodeOpId = 2, InvalidNOC = 3, MissingCsr = 4, TableFull = 5, MissingAcl = 6, MissingIpk = 7, InsufficientPrivlege = 8, FabricConflict = 9, LabelConflict = 10, InvalidFabricIndex = 11, } // Some placeholder value for now const MAX_CERT_DECLARATION_LEN: usize = 600; // Some placeholder value for now const MAX_CSR_LEN: usize = 300; // As defined in the Matter Spec const RESP_MAX: usize = 900; pub const ID: u32 = 0x003E; #[derive(FromPrimitive)] pub enum Commands { AttReq = 0x00, AttReqResp = 0x01, CertChainReq = 0x02, CertChainResp = 0x03, CSRReq = 0x04, CSRResp = 0x05, AddNOC = 0x06, NOCResp = 0x08, AddTrustedRootCert = 0x0b, } #[derive(FromPrimitive)] enum Attributes { NOCs = 0, Fabrics = 1, SupportedFabrics = 2, CommissionedFabrics = 3, TrustedRootCerts = 4, CurrentFabricIndex = 5, } pub struct NocCluster { base: Cluster, dev_att: Box, fabric_mgr: Arc, acl_mgr: Arc, failsafe: Arc, } struct NocData { pub key_pair: KeyPair, pub root_ca: Cert, } impl NocData { pub fn new(key_pair: KeyPair) -> Self { Self { key_pair, root_ca: Cert::default(), } } } impl NocCluster { pub fn new( dev_att: Box, fabric_mgr: Arc, acl_mgr: Arc, failsafe: Arc, ) -> Result, Error> { let mut c = Box::new(Self { dev_att, fabric_mgr, acl_mgr, failsafe, base: Cluster::new(ID)?, }); c.base.add_attribute(attr_currfabindex_new()?)?; c.base.add_attribute(attr_fabrics_new()?)?; Ok(c) } fn add_acl(&self, fab_idx: u8, admin_subject: u64) -> Result<(), Error> { let mut acl = AclEntry::new(fab_idx as u8, Privilege::ADMIN, AuthMode::Case); acl.add_subject(admin_subject)?; self.acl_mgr.add(acl) } fn _handle_command_addnoc(&mut self, cmd_req: &mut CommandReq) -> Result<(), NocStatus> { let noc_data = cmd_req .trans .session .take_data::() .ok_or(NocStatus::MissingCsr)?; if !self .failsafe .allow_noc_change() .map_err(|_| NocStatus::InsufficientPrivlege)? { error!("AddNOC not allowed by Fail Safe"); return Err(NocStatus::InsufficientPrivlege); } let r = AddNocReq::from_tlv(&cmd_req.data).map_err(|_| NocStatus::InvalidNOC)?; let noc_value = Cert::new(r.noc_value.0).map_err(|_| NocStatus::InvalidNOC)?; info!("Received NOC as: {}", noc_value); let icac_value = if r.icac_value.0.len() != 0 { let cert = Cert::new(r.icac_value.0).map_err(|_| NocStatus::InvalidNOC)?; info!("Received ICAC as: {}", cert); Some(cert) } else { None }; let fabric = Fabric::new( noc_data.key_pair, noc_data.root_ca, icac_value, noc_value, r.ipk_value.0, r.vendor_id, ) .map_err(|_| NocStatus::TableFull)?; let fab_idx = self .fabric_mgr .add(fabric) .map_err(|_| NocStatus::TableFull)?; if self.add_acl(fab_idx as u8, r.case_admin_subject).is_err() { error!("Failed to add ACL, what to do?"); } if self.failsafe.record_add_noc(fab_idx).is_err() { error!("Failed to record NoC in the FailSafe, what to do?"); } let cmd_data = NocResp { status_code: NocStatus::Ok as u8, fab_idx, debug_txt: "".to_owned(), }; let resp = ib::InvResp::cmd_new( 0, ID, Commands::NOCResp as u16, EncodeValue::Value(&cmd_data), ); let _ = resp.to_tlv(cmd_req.resp, TagType::Anonymous); cmd_req.trans.complete(); Ok(()) } fn handle_command_addnoc(&mut self, cmd_req: &mut CommandReq) -> Result<(), IMStatusCode> { cmd_enter!("AddNOC"); if let Err(e) = self._handle_command_addnoc(cmd_req) { let cmd_data = NocResp { status_code: e as u8, fab_idx: 0, debug_txt: "".to_owned(), }; let invoke_resp = ib::InvResp::cmd_new( 0, ID, Commands::NOCResp as u16, EncodeValue::Value(&cmd_data), ); let _ = invoke_resp.to_tlv(cmd_req.resp, TagType::Anonymous); cmd_req.trans.complete(); } Ok(()) } fn handle_command_attrequest(&mut self, cmd_req: &mut CommandReq) -> Result<(), IMStatusCode> { cmd_enter!("AttestationRequest"); let req = CommonReq::from_tlv(&cmd_req.data).map_err(|_| IMStatusCode::InvalidCommand)?; info!("Received Attestation Nonce:{:?}", req.str); let mut attest_challenge = [0u8; crypto::SYMM_KEY_LEN_BYTES]; attest_challenge.copy_from_slice(cmd_req.trans.session.get_att_challenge()); let cmd_data = |tag: TagType, t: &mut TLVWriter| { let mut buf: [u8; RESP_MAX] = [0; RESP_MAX]; let mut attest_element = WriteBuf::new(&mut buf, RESP_MAX); let _ = t.start_struct(tag); let _ = add_attestation_element(self.dev_att.as_ref(), req.str.0, &mut attest_element, t); let _ = add_attestation_signature( self.dev_att.as_ref(), &mut attest_element, &attest_challenge, t, ); let _ = t.end_container(); }; let resp = ib::InvResp::cmd_new( 0, ID, Commands::AttReqResp as u16, EncodeValue::Closure(&cmd_data), ); let _ = resp.to_tlv(cmd_req.resp, TagType::Anonymous); cmd_req.trans.complete(); Ok(()) } fn handle_command_certchainrequest( &mut self, cmd_req: &mut CommandReq, ) -> Result<(), IMStatusCode> { cmd_enter!("CertChainRequest"); info!("Received data: {}", cmd_req.data); let cert_type = get_certchainrequest_params(&cmd_req.data).map_err(|_| IMStatusCode::InvalidCommand)?; let mut buf: [u8; RESP_MAX] = [0; RESP_MAX]; let len = self .dev_att .get_devatt_data(cert_type, &mut buf) .map_err(|_| IMStatusCode::Failure)?; let buf = &buf[0..len]; let cmd_data = CertChainResp { cert: OctetStr::new(buf), }; let resp = ib::InvResp::cmd_new( 0, ID, Commands::CertChainResp as u16, EncodeValue::Value(&cmd_data), ); let _ = resp.to_tlv(cmd_req.resp, TagType::Anonymous); cmd_req.trans.complete(); Ok(()) } fn handle_command_csrrequest(&mut self, cmd_req: &mut CommandReq) -> Result<(), IMStatusCode> { cmd_enter!("CSRRequest"); let req = CommonReq::from_tlv(&cmd_req.data).map_err(|_| IMStatusCode::InvalidCommand)?; info!("Received CSR Nonce:{:?}", req.str); if !self.failsafe.is_armed() { return Err(IMStatusCode::UnsupportedAccess); } let noc_keypair = KeyPair::new().map_err(|_| IMStatusCode::Failure)?; let mut attest_challenge = [0u8; crypto::SYMM_KEY_LEN_BYTES]; attest_challenge.copy_from_slice(cmd_req.trans.session.get_att_challenge()); let cmd_data = |tag: TagType, t: &mut TLVWriter| { let mut buf: [u8; RESP_MAX] = [0; RESP_MAX]; let mut nocsr_element = WriteBuf::new(&mut buf, RESP_MAX); let _ = t.start_struct(tag); let _ = add_nocsrelement(&noc_keypair, req.str.0, &mut nocsr_element, t); let _ = add_attestation_signature( self.dev_att.as_ref(), &mut nocsr_element, &attest_challenge, t, ); let _ = t.end_container(); }; let resp = ib::InvResp::cmd_new( 0, ID, Commands::CSRResp as u16, EncodeValue::Closure(&cmd_data), ); let _ = resp.to_tlv(cmd_req.resp, TagType::Anonymous); let noc_data = Box::new(NocData::new(noc_keypair)); // Store this in the session data instead of cluster data, so it gets cleared // if the session goes away for some reason cmd_req.trans.session.set_data(noc_data); cmd_req.trans.complete(); Ok(()) } fn handle_command_addtrustedrootcert( &mut self, cmd_req: &mut CommandReq, ) -> Result<(), IMStatusCode> { cmd_enter!("AddTrustedRootCert"); if !self.failsafe.is_armed() { return Err(IMStatusCode::UnsupportedAccess); } // This may happen on CASE or PASE. For PASE, the existence of NOC Data is necessary match cmd_req.trans.session.get_session_mode() { SessionMode::Case(_) => error!("CASE: AddTrustedRootCert handling pending"), // For a CASE Session, we just return success for now, SessionMode::Pase => { let noc_data = cmd_req .trans .session .get_data::() .ok_or(IMStatusCode::Failure)?; let req = CommonReq::from_tlv(&cmd_req.data).map_err(|_| IMStatusCode::InvalidCommand)?; info!("Received Trusted Cert:{:x?}", req.str); noc_data.root_ca = Cert::new(req.str.0).map_err(|_| IMStatusCode::Failure)?; } _ => (), } cmd_req.trans.complete(); Err(IMStatusCode::Sucess) } } fn attr_currfabindex_new() -> Result { Attribute::new( Attributes::CurrentFabricIndex as u16, AttrValue::Custom, Access::RV, Quality::NONE, ) } fn attr_fabrics_new() -> Result { Attribute::new( Attributes::Fabrics as u16, AttrValue::Custom, Access::RV | Access::FAB_SCOPED, Quality::NONE, ) } impl ClusterType for NocCluster { fn base(&self) -> &Cluster { &self.base } fn base_mut(&mut self) -> &mut Cluster { &mut self.base } fn handle_command(&mut self, cmd_req: &mut CommandReq) -> Result<(), IMStatusCode> { let cmd = cmd_req .cmd .path .leaf .map(num::FromPrimitive::from_u32) .ok_or(IMStatusCode::UnsupportedCommand)? .ok_or(IMStatusCode::UnsupportedCommand)?; match cmd { Commands::AddNOC => self.handle_command_addnoc(cmd_req), Commands::CSRReq => self.handle_command_csrrequest(cmd_req), Commands::AddTrustedRootCert => self.handle_command_addtrustedrootcert(cmd_req), Commands::AttReq => self.handle_command_attrequest(cmd_req), Commands::CertChainReq => self.handle_command_certchainrequest(cmd_req), _ => Err(IMStatusCode::UnsupportedCommand), } } fn read_custom_attribute(&self, encoder: &mut dyn Encoder, attr: &AttrDetails) { match num::FromPrimitive::from_u16(attr.attr_id) { Some(Attributes::CurrentFabricIndex) => { encoder.encode(EncodeValue::Value(&attr.fab_idx)) } Some(Attributes::Fabrics) => encoder.encode(EncodeValue::Closure(&|tag, tw| { let _ = tw.start_array(tag); let _ = self.fabric_mgr.for_each(|entry, fab_idx| { if !attr.fab_filter || attr.fab_idx == fab_idx { let _ = entry .get_fabric_desc(fab_idx) .to_tlv(tw, TagType::Anonymous); } }); let _ = tw.end_container(); })), _ => { error!("Attribute not supported: this shouldn't happen"); } } } } fn add_attestation_element( dev_att: &dyn DevAttDataFetcher, att_nonce: &[u8], write_buf: &mut WriteBuf, t: &mut TLVWriter, ) -> Result<(), Error> { let mut cert_dec: [u8; MAX_CERT_DECLARATION_LEN] = [0; MAX_CERT_DECLARATION_LEN]; let len = dev_att.get_devatt_data(dev_att::DataType::CertDeclaration, &mut cert_dec)?; let cert_dec = &cert_dec[0..len]; let epoch = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() as u32; let mut writer = TLVWriter::new(write_buf); writer.start_struct(TagType::Anonymous)?; writer.str16(TagType::Context(1), cert_dec)?; writer.str8(TagType::Context(2), att_nonce)?; writer.u32(TagType::Context(3), epoch)?; writer.end_container()?; t.str16(TagType::Context(0), write_buf.as_borrow_slice())?; Ok(()) } fn add_attestation_signature( dev_att: &dyn DevAttDataFetcher, attest_element: &mut WriteBuf, attest_challenge: &[u8], resp: &mut TLVWriter, ) -> Result<(), Error> { let dac_key = { let mut pubkey = [0_u8; crypto::EC_POINT_LEN_BYTES]; let mut privkey = [0_u8; crypto::BIGNUM_LEN_BYTES]; dev_att.get_devatt_data(dev_att::DataType::DACPubKey, &mut pubkey)?; dev_att.get_devatt_data(dev_att::DataType::DACPrivKey, &mut privkey)?; KeyPair::new_from_components(&pubkey, &privkey) }?; attest_element.copy_from_slice(attest_challenge)?; let mut signature = [0u8; crypto::EC_SIGNATURE_LEN_BYTES]; dac_key.sign_msg(attest_element.as_borrow_slice(), &mut signature)?; resp.str8(TagType::Context(1), &signature) } fn add_nocsrelement( noc_keypair: &KeyPair, csr_nonce: &[u8], write_buf: &mut WriteBuf, resp: &mut TLVWriter, ) -> Result<(), Error> { let mut csr: [u8; MAX_CSR_LEN] = [0; MAX_CSR_LEN]; let csr = noc_keypair.get_csr(&mut csr)?; let mut writer = TLVWriter::new(write_buf); writer.start_struct(TagType::Anonymous)?; writer.str8(TagType::Context(1), csr)?; writer.str8(TagType::Context(2), csr_nonce)?; writer.end_container()?; resp.str8(TagType::Context(0), write_buf.as_borrow_slice())?; Ok(()) } #[derive(ToTLV)] struct CertChainResp<'a> { cert: OctetStr<'a>, } #[derive(ToTLV)] struct NocResp { status_code: u8, fab_idx: u8, debug_txt: String, } #[derive(FromTLV)] #[tlvargs(lifetime = "'a")] struct AddNocReq<'a> { noc_value: OctetStr<'a>, icac_value: OctetStr<'a>, ipk_value: OctetStr<'a>, case_admin_subject: u64, vendor_id: u16, } #[derive(FromTLV)] #[tlvargs(lifetime = "'a")] struct CommonReq<'a> { str: OctetStr<'a>, } #[derive(FromTLV)] struct CertChainReq { cert_type: u8, } fn get_certchainrequest_params(data: &TLVElement) -> Result { let cert_type = CertChainReq::from_tlv(data)?.cert_type; const CERT_TYPE_DAC: u8 = 1; const CERT_TYPE_PAI: u8 = 2; info!("Received Cert Type:{:?}", cert_type); match cert_type { CERT_TYPE_DAC => Ok(dev_att::DataType::DAC), CERT_TYPE_PAI => Ok(dev_att::DataType::PAI), _ => Err(Error::Invalid), } }