From f0a263d770c5d7877fd1a06e9d55b030ff617fac Mon Sep 17 00:00:00 2001 From: Geovane Fedrecheski Date: Fri, 21 Jun 2024 15:58:58 +0200 Subject: [PATCH 01/16] creds: new more versatile credentials module will support more types of credentials, including different types of COSE_Key's the handling of id_cred's is inspired by chrysn's PR #274 --- lib/src/edhoc.rs | 5 ++ shared/src/buffer.rs | 18 +++++ shared/src/cred_new.rs | 161 +++++++++++++++++++++++++++++++++++++++++ shared/src/lib.rs | 4 + 4 files changed, 188 insertions(+) create mode 100644 shared/src/cred_new.rs diff --git a/lib/src/edhoc.rs b/lib/src/edhoc.rs index b20c1db8..aff32c23 100644 --- a/lib/src/edhoc.rs +++ b/lib/src/edhoc.rs @@ -125,6 +125,11 @@ pub fn r_prepare_message_2( CredentialTransfer::ByReference => IdCred::CompactKid(cred_r.kid), }; + // let id_cred_r = match cred_transfer { + // CredentialTransfer::ByValue => cred_r.by_value(), // KCSS or KCWT or X509 or C509 + // CredentialTransfer::ByReference => cred_r.by_reference(), // kid or x5t or c5t + // }; + // compute ciphertext_2 let plaintext_2 = encode_plaintext_2(c_r, &id_cred_r, &mac_2, &ead_2)?; diff --git a/shared/src/buffer.rs b/shared/src/buffer.rs index 1537ef54..e4f60770 100644 --- a/shared/src/buffer.rs +++ b/shared/src/buffer.rs @@ -123,6 +123,24 @@ impl Index for EdhocBuffer { } } +impl TryInto> for &[u8] { + type Error = (); + + fn try_into(self) -> Result, Self::Error> { + let mut buffer = [0u8; N]; + if self.len() <= buffer.len() { + buffer[..self.len()].copy_from_slice(self); + + Ok(EdhocBuffer { + content: buffer, + len: self.len(), + }) + } else { + Err(()) + } + } +} + mod test { #[test] diff --git a/shared/src/cred_new.rs b/shared/src/cred_new.rs new file mode 100644 index 00000000..2560884a --- /dev/null +++ b/shared/src/cred_new.rs @@ -0,0 +1,161 @@ +use core::panic; + +use super::*; + +pub type BufferCred = EdhocBuffer<128>; // arbitrary size +pub type BufferKid = EdhocBuffer<16>; // variable size, up to 16 bytes +pub type BufferIdCred = EdhocBuffer<128>; // variable size, can contain either the contents of a BufferCred or a BufferKid +pub type BytesKey128 = [u8; 16]; +pub type BytesKeyEC2 = [u8; 32]; +pub type BytesKeyOKP = [u8; 32]; +pub type BytesX5T = [u8; 8]; +pub type BytesC5T = [u8; 8]; + +pub trait EdhocCredentialAccessor { + /// Returns the key of the credential, e.g., a public EC2 key or a PSK + fn get_credential_key(&self) -> &[u8]; + /// Returns the CBOR-encoded ID_CRED containing the credential by value + fn as_id_cred_value(&self, cred_bytes: &[u8]) -> BufferCred; + /// Returns the CBOR-encoded ID_CRED containing the credential by reference + fn as_id_cred_ref(&self) -> BufferIdCred; +} + +#[derive(Clone, Copy, Debug, PartialEq)] +enum CredentialType { + CCS, + CCS_PSK, + // C509, +} + +pub struct Credential { + /// Original bytes of the credential, CBOR-encoded + pub bytes: BufferCred, + pub cred_type: CredentialType, + pub content: EdhocCred, +} + +impl Credential { + pub fn by_value(&self) -> BufferIdCred { + if self.cred_type == CredentialType::CCS_PSK { + panic!("The PSK must never be sent by value") + } else { + self.content.as_id_cred_value(self.bytes.as_slice()) + } + } + pub fn by_reference(&self) -> BufferIdCred { + self.content.as_id_cred_ref() + } +} + +#[derive(Clone, Copy, Debug)] +pub struct X509 { + pub x5t: Option, + pub c5t: Option, + pub public_key: BytesKeyEC2, +} + +#[derive(Clone, Copy, Debug)] +pub struct CoseKey { + pub kty: i8, + pub kid: BufferKid, + pub x: Option, + pub y: Option, + pub k: Option, +} + +impl CoseKey { + pub fn new(kty: i8, kid: BufferKid) -> Self { + Self { + kty, + kid, + x: None, + y: None, + k: None, + } + } + + pub fn with_x(self, x: BytesKeyEC2) -> Self { + Self { x: Some(x), ..self } + } + + pub fn set_y(self, y: BytesKeyEC2) -> Self { + Self { y: Some(y), ..self } + } + + pub fn set_k(self, k: BytesKey128) -> Self { + Self { k: Some(k), ..self } + } +} + +// NOTE: ideally, this should be implemented by a CredentialCCS struct, +// but we use CoseKey directly for now as we don't need the extra complexity +impl EdhocCredentialAccessor for CoseKey { + fn get_credential_key(&self) -> &[u8] { + match self.kty { + 4 => self.k.as_ref().unwrap(), + 2 => self.x.as_ref().unwrap(), + _ => panic!("No key found"), + } + } + + /// Returns a COSE_Header map with a single entry: + /// { /kccs/ 14: cred } + fn as_id_cred_value(&self, cred_bytes: &[u8]) -> BufferIdCred { + let mut cred = BufferIdCred::new(); + cred.extend_from_slice(&[CBOR_MAJOR_MAP + 1, KCSS_LABEL]) + .map_err(|_| EDHOCError::CredentialTooLongError) + .unwrap(); + cred.extend_from_slice(cred_bytes).unwrap(); + cred + } + + /// Returns a COSE_Header map with a single entry: + /// { /kid/ 4: kid } + fn as_id_cred_ref(&self) -> BufferIdCred { + let mut id_cred = BufferIdCred::new(); + id_cred + .extend_from_slice(&[CBOR_MAJOR_MAP + 1, KID_LABEL]) + .unwrap(); + id_cred + .push(CBOR_MAJOR_BYTE_STRING | self.kid.len() as u8) + .unwrap(); + // if self.kid.len() == 1 { + // let kid = self.kid[0]; + // // CBOR encoding + // } else { + // todo!("Larger kid not supported yet"); + // } + id_cred + } +} + +#[cfg(test)] +mod test { + use super::*; + use hexlit::hex; + + const CRED_TV: &[u8] = &hex!("a2026b6578616d706c652e65647508a101a501020241322001215820bbc34960526ea4d32e940cad2a234148ddc21791a12afbcbac93622046dd44f02258204519e257236b2a0ce2023f0931f1f386ca7afda64fcde0108c224c51eabf6072"); + const G_A_TV: &[u8] = &hex!("BBC34960526EA4D32E940CAD2A234148DDC21791A12AFBCBAC93622046DD44F0"); + const ID_CRED_TV: &[u8] = &hex!("a1044132"); + const KID_VALUE_TV: &[u8] = &hex!("32"); + + const CRED_PSK: &[u8] = + &hex!("A202686D79646F74626F7408A101A30104024132205050930FF462A77A3540CF546325DEA214"); + const K: &[u8] = &hex!("50930FF462A77A3540CF546325DEA214"); + + #[test] + fn test_new_cose_key() { + let key = CoseKey::new(2, BufferKid::new_from_slice(KID_VALUE_TV).unwrap()) + .with_x(G_A_TV.try_into().unwrap()); + + assert!(key.get_credential_key() == G_A_TV); + } + + #[test] + fn test_new_cose_key_psk() { + let key = CoseKey::new(4, BufferKid::new_from_slice(KID_VALUE_TV).unwrap()) + .set_k(K.try_into().unwrap()); + + assert!(key.get_credential_key() == K); + } +} diff --git a/shared/src/lib.rs b/shared/src/lib.rs index 3e99f03a..3fd124c1 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -27,6 +27,9 @@ pub use cred::*; mod buffer; pub use buffer::*; +mod cred_new; +pub use cred_new::*; + #[cfg(feature = "python-bindings")] use pyo3::prelude::*; #[cfg(feature = "python-bindings")] @@ -83,6 +86,7 @@ pub const MAX_INFO_LEN: usize = 2 + SHA256_DIGEST_LEN + // 32-byte digest as bst 1; // length as u8 pub const KCSS_LABEL: u8 = 14; +pub const KID_LABEL: u8 = 4; pub const ENC_STRUCTURE_LEN: usize = 8 + 5 + SHA256_DIGEST_LEN; // 8 for ENCRYPT0 From 59b50d52df3c25c609b24729ae7cf0a2edda324c Mon Sep 17 00:00:00 2001 From: Geovane Fedrecheski Date: Mon, 1 Jul 2024 09:44:02 +0200 Subject: [PATCH 02/16] creds: updates on new cred module --- lib/src/edhoc.rs | 5 --- shared/src/cred_new.rs | 93 +++++++++++++++++++++++++++--------------- 2 files changed, 61 insertions(+), 37 deletions(-) diff --git a/lib/src/edhoc.rs b/lib/src/edhoc.rs index aff32c23..b20c1db8 100644 --- a/lib/src/edhoc.rs +++ b/lib/src/edhoc.rs @@ -125,11 +125,6 @@ pub fn r_prepare_message_2( CredentialTransfer::ByReference => IdCred::CompactKid(cred_r.kid), }; - // let id_cred_r = match cred_transfer { - // CredentialTransfer::ByValue => cred_r.by_value(), // KCSS or KCWT or X509 or C509 - // CredentialTransfer::ByReference => cred_r.by_reference(), // kid or x5t or c5t - // }; - // compute ciphertext_2 let plaintext_2 = encode_plaintext_2(c_r, &id_cred_r, &mac_2, &ead_2)?; diff --git a/shared/src/cred_new.rs b/shared/src/cred_new.rs index 2560884a..8597766f 100644 --- a/shared/src/cred_new.rs +++ b/shared/src/cred_new.rs @@ -1,5 +1,3 @@ -use core::panic; - use super::*; pub type BufferCred = EdhocBuffer<128>; // arbitrary size @@ -11,30 +9,40 @@ pub type BytesKeyOKP = [u8; 32]; pub type BytesX5T = [u8; 8]; pub type BytesC5T = [u8; 8]; -pub trait EdhocCredentialAccessor { +pub enum CredentialKey { + Symmetric(BytesKey128), + EC2 { + x: BytesKeyEC2, + y: Option, + }, + // Add other key types as needed +} + +pub trait EdhocCredentialContent { /// Returns the key of the credential, e.g., a public EC2 key or a PSK - fn get_credential_key(&self) -> &[u8]; + fn get_credential_key(&self) -> CredentialKey; /// Returns the CBOR-encoded ID_CRED containing the credential by value - fn as_id_cred_value(&self, cred_bytes: &[u8]) -> BufferCred; + fn as_id_cred_value(&self, cred_bytes: &[u8]) -> BufferIdCred; /// Returns the CBOR-encoded ID_CRED containing the credential by reference fn as_id_cred_ref(&self) -> BufferIdCred; } #[derive(Clone, Copy, Debug, PartialEq)] -enum CredentialType { +pub enum CredentialType { CCS, CCS_PSK, // C509, } -pub struct Credential { +pub struct Credential { /// Original bytes of the credential, CBOR-encoded pub bytes: BufferCred, pub cred_type: CredentialType, pub content: EdhocCred, } -impl Credential { +impl Credential { + // FIXME: should handle errors instead of panicking pub fn by_value(&self) -> BufferIdCred { if self.cred_type == CredentialType::CCS_PSK { panic!("The PSK must never be sent by value") @@ -47,24 +55,35 @@ impl Credential { } } -#[derive(Clone, Copy, Debug)] -pub struct X509 { - pub x5t: Option, - pub c5t: Option, - pub public_key: BytesKeyEC2, -} - +// example of how some fields of a X509 certificate could be stored +// #[derive(Clone, Copy, Debug)] +// pub struct X509 { +// pub x5t: Option, +// pub c5t: Option, +// pub public_key: BytesKeyEC2, +// } + +/// A COSE_Key structure, as defined in RFC 9052 +/// +/// NOTE: ideally, this should be implemented by a CredentialCCS struct, +/// but we use CoseKey directly for now as we don't need the extra complexity #[derive(Clone, Copy, Debug)] pub struct CoseKey { - pub kty: i8, + pub kty: CoseKTY, pub kid: BufferKid, pub x: Option, pub y: Option, pub k: Option, } +#[derive(Clone, Copy, Debug)] +pub enum CoseKTY { + EC2 = 2, + Symmetric = 4, +} + impl CoseKey { - pub fn new(kty: i8, kid: BufferKid) -> Self { + pub fn new(kty: CoseKTY, kid: BufferKid) -> Self { Self { kty, kid, @@ -78,22 +97,28 @@ impl CoseKey { Self { x: Some(x), ..self } } - pub fn set_y(self, y: BytesKeyEC2) -> Self { + pub fn with_y(self, y: BytesKeyEC2) -> Self { Self { y: Some(y), ..self } } - pub fn set_k(self, k: BytesKey128) -> Self { + pub fn with_k(self, k: BytesKey128) -> Self { Self { k: Some(k), ..self } } } -// NOTE: ideally, this should be implemented by a CredentialCCS struct, -// but we use CoseKey directly for now as we don't need the extra complexity -impl EdhocCredentialAccessor for CoseKey { - fn get_credential_key(&self) -> &[u8] { +impl EdhocCredentialContent for CoseKey { + fn get_credential_key(&self) -> CredentialKey { match self.kty { - 4 => self.k.as_ref().unwrap(), - 2 => self.x.as_ref().unwrap(), + CoseKTY::Symmetric => { + let mut k: BytesKey128 = Default::default(); + k.copy_from_slice(self.k.as_ref().unwrap()); + CredentialKey::Symmetric(k) + } + CoseKTY::EC2 => { + let mut x: BytesKeyEC2 = Default::default(); + x.copy_from_slice(self.x.as_ref().unwrap()); + CredentialKey::EC2 { x, y: self.y } + } _ => panic!("No key found"), } } @@ -145,17 +170,21 @@ mod test { #[test] fn test_new_cose_key() { - let key = CoseKey::new(2, BufferKid::new_from_slice(KID_VALUE_TV).unwrap()) - .with_x(G_A_TV.try_into().unwrap()); + let g_a_tv = BytesKeyEC2::default(); - assert!(key.get_credential_key() == G_A_TV); + let key = CoseKey::new( + CoseKTY::EC2, + BufferKid::new_from_slice(KID_VALUE_TV).unwrap(), + ) + .with_x(G_A_TV.try_into().unwrap()); } #[test] fn test_new_cose_key_psk() { - let key = CoseKey::new(4, BufferKid::new_from_slice(KID_VALUE_TV).unwrap()) - .set_k(K.try_into().unwrap()); - - assert!(key.get_credential_key() == K); + let key = CoseKey::new( + CoseKTY::Symmetric, + BufferKid::new_from_slice(KID_VALUE_TV).unwrap(), + ) + .with_k(K.try_into().unwrap()); } } From ff45dd6c9953cc36bc47808d8bbdabf0e035245b Mon Sep 17 00:00:00 2001 From: Geovane Fedrecheski Date: Mon, 1 Jul 2024 10:29:38 +0200 Subject: [PATCH 03/16] creds: refactor into simpler structs --- shared/src/cred_new.rs | 181 ++++++++++++++--------------------------- 1 file changed, 59 insertions(+), 122 deletions(-) diff --git a/shared/src/cred_new.rs b/shared/src/cred_new.rs index 8597766f..134bbebd 100644 --- a/shared/src/cred_new.rs +++ b/shared/src/cred_new.rs @@ -3,30 +3,18 @@ use super::*; pub type BufferCred = EdhocBuffer<128>; // arbitrary size pub type BufferKid = EdhocBuffer<16>; // variable size, up to 16 bytes pub type BufferIdCred = EdhocBuffer<128>; // variable size, can contain either the contents of a BufferCred or a BufferKid -pub type BytesKey128 = [u8; 16]; +pub type BytesKeyAES128 = [u8; 16]; pub type BytesKeyEC2 = [u8; 32]; pub type BytesKeyOKP = [u8; 32]; pub type BytesX5T = [u8; 8]; pub type BytesC5T = [u8; 8]; pub enum CredentialKey { - Symmetric(BytesKey128), - EC2 { - x: BytesKeyEC2, - y: Option, - }, + Symmetric(BytesKeyAES128), + EC2Compact(BytesKeyEC2), // Add other key types as needed } -pub trait EdhocCredentialContent { - /// Returns the key of the credential, e.g., a public EC2 key or a PSK - fn get_credential_key(&self) -> CredentialKey; - /// Returns the CBOR-encoded ID_CRED containing the credential by value - fn as_id_cred_value(&self, cred_bytes: &[u8]) -> BufferIdCred; - /// Returns the CBOR-encoded ID_CRED containing the credential by reference - fn as_id_cred_ref(&self) -> BufferIdCred; -} - #[derive(Clone, Copy, Debug, PartialEq)] pub enum CredentialType { CCS, @@ -34,122 +22,71 @@ pub enum CredentialType { // C509, } -pub struct Credential { +pub struct Credential { /// Original bytes of the credential, CBOR-encoded pub bytes: BufferCred, + pub key: CredentialKey, + pub kid: Option, // other types of identifiers can be added, such as `pub x5t: Option` pub cred_type: CredentialType, - pub content: EdhocCred, } -impl Credential { - // FIXME: should handle errors instead of panicking - pub fn by_value(&self) -> BufferIdCred { - if self.cred_type == CredentialType::CCS_PSK { - panic!("The PSK must never be sent by value") - } else { - self.content.as_id_cred_value(self.bytes.as_slice()) +// FIXME: should handle errors instead of panicking +impl Credential { + /// Creates a new credential with the given bytes, key and type. + pub fn new(bytes: BufferCred, key: CredentialKey, cred_type: CredentialType) -> Self { + Self { + bytes, + key, + kid: None, + cred_type, } } - pub fn by_reference(&self) -> BufferIdCred { - self.content.as_id_cred_ref() - } -} -// example of how some fields of a X509 certificate could be stored -// #[derive(Clone, Copy, Debug)] -// pub struct X509 { -// pub x5t: Option, -// pub c5t: Option, -// pub public_key: BytesKeyEC2, -// } - -/// A COSE_Key structure, as defined in RFC 9052 -/// -/// NOTE: ideally, this should be implemented by a CredentialCCS struct, -/// but we use CoseKey directly for now as we don't need the extra complexity -#[derive(Clone, Copy, Debug)] -pub struct CoseKey { - pub kty: CoseKTY, - pub kid: BufferKid, - pub x: Option, - pub y: Option, - pub k: Option, -} - -#[derive(Clone, Copy, Debug)] -pub enum CoseKTY { - EC2 = 2, - Symmetric = 4, -} - -impl CoseKey { - pub fn new(kty: CoseKTY, kid: BufferKid) -> Self { + pub fn with_kid(self, kid: BufferKid) -> Self { Self { - kty, - kid, - x: None, - y: None, - k: None, + kid: Some(kid), + ..self } } - pub fn with_x(self, x: BytesKeyEC2) -> Self { - Self { x: Some(x), ..self } - } - - pub fn with_y(self, y: BytesKeyEC2) -> Self { - Self { y: Some(y), ..self } - } - - pub fn with_k(self, k: BytesKey128) -> Self { - Self { k: Some(k), ..self } - } -} - -impl EdhocCredentialContent for CoseKey { - fn get_credential_key(&self) -> CredentialKey { - match self.kty { - CoseKTY::Symmetric => { - let mut k: BytesKey128 = Default::default(); - k.copy_from_slice(self.k.as_ref().unwrap()); - CredentialKey::Symmetric(k) - } - CoseKTY::EC2 => { - let mut x: BytesKeyEC2 = Default::default(); - x.copy_from_slice(self.x.as_ref().unwrap()); - CredentialKey::EC2 { x, y: self.y } + /// Returns a COSE_Header map with a single entry representing a credential by value. + /// + /// For example, if the credential is a CCS: + /// { /kccs/ 14: bytes } + pub fn by_value(&self) -> BufferIdCred { + match self.cred_type { + CredentialType::CCS => { + let mut cred = BufferIdCred::new(); + cred.extend_from_slice(&[CBOR_MAJOR_MAP + 1, KCSS_LABEL]) + .map_err(|_| EDHOCError::CredentialTooLongError) + .unwrap(); + cred.extend_from_slice(self.bytes.as_slice()).unwrap(); + cred } - _ => panic!("No key found"), + CredentialType::CCS_PSK => panic!("Symmetric keys cannot be sent by value"), } } - /// Returns a COSE_Header map with a single entry: - /// { /kccs/ 14: cred } - fn as_id_cred_value(&self, cred_bytes: &[u8]) -> BufferIdCred { - let mut cred = BufferIdCred::new(); - cred.extend_from_slice(&[CBOR_MAJOR_MAP + 1, KCSS_LABEL]) - .map_err(|_| EDHOCError::CredentialTooLongError) - .unwrap(); - cred.extend_from_slice(cred_bytes).unwrap(); - cred - } - - /// Returns a COSE_Header map with a single entry: + /// Returns a COSE_Header map with a single entry representing a credential by reference. + /// + /// For example, if the reference is a kid: /// { /kid/ 4: kid } - fn as_id_cred_ref(&self) -> BufferIdCred { + pub fn by_reference(&self) -> BufferIdCred { + let Some(kid) = self.kid.as_ref() else { + panic!("Kid not set"); + }; let mut id_cred = BufferIdCred::new(); id_cred .extend_from_slice(&[CBOR_MAJOR_MAP + 1, KID_LABEL]) .unwrap(); id_cred - .push(CBOR_MAJOR_BYTE_STRING | self.kid.len() as u8) + .push(CBOR_MAJOR_BYTE_STRING | kid.len() as u8) .unwrap(); - // if self.kid.len() == 1 { - // let kid = self.kid[0]; - // // CBOR encoding - // } else { - // todo!("Larger kid not supported yet"); - // } + if kid.len() == 1 { + id_cred.extend_from_slice(kid.as_slice()).unwrap(); + } else { + todo!("Larger kid not supported yet"); + } id_cred } } @@ -169,22 +106,22 @@ mod test { const K: &[u8] = &hex!("50930FF462A77A3540CF546325DEA214"); #[test] - fn test_new_cose_key() { - let g_a_tv = BytesKeyEC2::default(); - - let key = CoseKey::new( - CoseKTY::EC2, - BufferKid::new_from_slice(KID_VALUE_TV).unwrap(), - ) - .with_x(G_A_TV.try_into().unwrap()); + fn test_new_cred_ccs() { + let cred = Credential::new( + CRED_TV.try_into().unwrap(), + CredentialKey::EC2Compact(G_A_TV.try_into().unwrap()), + CredentialType::CCS, + ); + assert_eq!(cred.bytes.as_slice(), CRED_TV); } #[test] - fn test_new_cose_key_psk() { - let key = CoseKey::new( - CoseKTY::Symmetric, - BufferKid::new_from_slice(KID_VALUE_TV).unwrap(), - ) - .with_k(K.try_into().unwrap()); + fn test_new_cred_ccs_psk() { + let cred = Credential::new( + CRED_PSK.try_into().unwrap(), + CredentialKey::Symmetric(K.try_into().unwrap()), + CredentialType::CCS_PSK, + ); + assert_eq!(cred.bytes.as_slice(), CRED_PSK); } } From 40a06ad855685f1534910bd75bff186530ba2c4f Mon Sep 17 00:00:00 2001 From: Geovane Fedrecheski Date: Mon, 1 Jul 2024 10:40:38 +0200 Subject: [PATCH 04/16] creds: add parse methods --- shared/src/cred.rs | 2 +- shared/src/cred_new.rs | 52 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/shared/src/cred.rs b/shared/src/cred.rs index b82d279e..23bf2063 100644 --- a/shared/src/cred.rs +++ b/shared/src/cred.rs @@ -27,7 +27,7 @@ impl CredentialRPK { [0xa1, 0x04, 0x41, self.kid] // cbor map = {4: kid} } - fn parse(cred: &[u8]) -> Result<(BytesP256ElemLen, u8), EDHOCError> { + pub fn parse(cred: &[u8]) -> Result<(BytesP256ElemLen, u8), EDHOCError> { // NOTE: this routine is only guaranteed to work with credentials from RFC9529 const CCS_PREFIX_LEN: usize = 3; const CNF_AND_COSE_KEY_PREFIX_LEN: usize = 8; diff --git a/shared/src/cred_new.rs b/shared/src/cred_new.rs index 134bbebd..5a5b919d 100644 --- a/shared/src/cred_new.rs +++ b/shared/src/cred_new.rs @@ -9,6 +9,7 @@ pub type BytesKeyOKP = [u8; 32]; pub type BytesX5T = [u8; 8]; pub type BytesC5T = [u8; 8]; +#[derive(Clone, Copy, Debug, PartialEq)] pub enum CredentialKey { Symmetric(BytesKeyAES128), EC2Compact(BytesKeyEC2), @@ -49,6 +50,38 @@ impl Credential { } } + /// Parse a CCS style credential + /// + /// If the given value matches the shape Lakers expects of a CCS, its public key and key ID are + /// extracted into a full credential. + pub fn parse_ccs(value: &[u8]) -> Result { + // Implementing in terms of the old structure, to be moved in here in later versions of + // this change set + let (public_key, kid) = CredentialRPK::parse(value)?; + Ok(Self { + bytes: BufferCred::new_from_slice(value).map_err(|_| EDHOCError::ParsingError)?, + key: CredentialKey::EC2Compact(public_key), + kid: Some(BufferKid::new_from_slice(&[kid]).unwrap()), + cred_type: CredentialType::CCS, + }) + } + + /// Parse a CCS style credential, but the key is a symmetric key + /// + /// If the given value matches the shape Lakers expects of a CCS, its public key and key ID are + /// extracted into a full credential. + pub fn parse_ccs_psk(value: &[u8]) -> Result { + // TODO: actually implement this + + // example return: + Ok(Self { + bytes: BufferCred::new_from_slice(value).map_err(|_| EDHOCError::ParsingError)?, + key: CredentialKey::Symmetric(/* FIXME */ Default::default()), + kid: Some(BufferKid::new_from_slice(/* FIXME */ Default::default()).unwrap()), + cred_type: CredentialType::CCS_PSK, + }) + } + /// Returns a COSE_Header map with a single entry representing a credential by value. /// /// For example, if the credential is a CCS: @@ -71,6 +104,8 @@ impl Credential { /// /// For example, if the reference is a kid: /// { /kid/ 4: kid } + /// + /// TODO: accept a parameter to specify the type of reference, e.g. kid, x5t, etc. pub fn by_reference(&self) -> BufferIdCred { let Some(kid) = self.kid.as_ref() else { panic!("Kid not set"); @@ -124,4 +159,21 @@ mod test { ); assert_eq!(cred.bytes.as_slice(), CRED_PSK); } + + #[test] + fn test_parse_ccs() { + let cred = Credential::parse_ccs(CRED_TV).unwrap(); + assert_eq!(cred.bytes.as_slice(), CRED_TV); + assert_eq!( + cred.key, + CredentialKey::EC2Compact(G_A_TV.try_into().unwrap()) + ); + assert_eq!(cred.kid.unwrap().as_slice(), KID_VALUE_TV); + assert_eq!(cred.cred_type, CredentialType::CCS); + } + + #[test] + fn test_parse_ccs_psk() { + // TODO + } } From b48d826d484b1a5f38a3e88d8aafa40e83b9a27d Mon Sep 17 00:00:00 2001 From: ElsaLopez Date: Mon, 1 Jul 2024 11:46:32 +0200 Subject: [PATCH 05/16] Implement parse_ccs_psk --- shared/src/cred_new.rs | 58 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/shared/src/cred_new.rs b/shared/src/cred_new.rs index 5a5b919d..505a605c 100644 --- a/shared/src/cred_new.rs +++ b/shared/src/cred_new.rs @@ -72,16 +72,48 @@ impl Credential { /// extracted into a full credential. pub fn parse_ccs_psk(value: &[u8]) -> Result { // TODO: actually implement this - - // example return: - Ok(Self { - bytes: BufferCred::new_from_slice(value).map_err(|_| EDHOCError::ParsingError)?, - key: CredentialKey::Symmetric(/* FIXME */ Default::default()), - kid: Some(BufferKid::new_from_slice(/* FIXME */ Default::default()).unwrap()), - cred_type: CredentialType::CCS_PSK, - }) + const CCS_PREFIX_LEN: usize = 3; + const CNF_AND_COSE_KEY_PREFIX_LEN: usize = 8; + const COSE_KEY_FIRST_ITEMS_LEN: usize = 4; //COSE for symmetric key + const SYMMETRIC_KEY_LEN: usize = 16; // Assuming a 128-bit symmetric key + + // Why do they add +3 and +1 in CredentialPRK::parse + if value.len() < CCS_PREFIX_LEN + CNF_AND_COSE_KEY_PREFIX_LEN + COSE_KEY_FIRST_ITEMS_LEN + SYMMETRIC_KEY_LEN { + return Err(EDHOCError::ParsingError); + } + + // Extracts len from 3rd byte (CBOR encoding) + let subject_len = CBORDecoder::info_of(value[2]) as usize; + + let id_cred_offset: usize = CCS_PREFIX_LEN + .checked_add(subject_len) + .and_then(|x| x.checked_add(CNF_AND_COSE_KEY_PREFIX_LEN)) + .ok_or(EDHOCError::ParsingError)?; + + let symmetric_key_offset: usize = id_cred_offset + .checked_add(COSE_KEY_FIRST_ITEMS_LEN) + .ok_or(EDHOCError::ParsingError)?; + + if symmetric_key_offset + .checked_add(SYMMETRIC_KEY_LEN) + .map_or(false, |end| end <= value.len()) + { + let symmetric_key: [u8; SYMMETRIC_KEY_LEN] = value[symmetric_key_offset..symmetric_key_offset + SYMMETRIC_KEY_LEN] + .try_into() + .map_err(|_| EDHOCError::ParsingError)?; + + let kid = value[id_cred_offset]; + + Ok(Self { + bytes: BufferCred::new_from_slice(value).map_err(|_| EDHOCError::ParsingError)?, + key: CredentialKey::Symmetric(symmetric_key), + kid: Some(BufferKid::new_from_slice(&[kid]).unwrap()), + cred_type: CredentialType::CCS_PSK, + }) + } else { + Err(EDHOCError::ParsingError) + } } - /// Returns a COSE_Header map with a single entry representing a credential by value. /// /// For example, if the credential is a CCS: @@ -174,6 +206,12 @@ mod test { #[test] fn test_parse_ccs_psk() { - // TODO + let cred = Credential::parse_ccs_psk(CRED_PSK).unwrap(); + assert_eq!(cred.bytes.as_slice(), CRED_PSK); + assert_eq!( + cred.key, + CredentialKey::Symmetric(K.try_into().unwrap()) + ); + assert_eq!(cred.cred_type, CredentialType::CCS_PSK); } } From 7f35e58fcbb4565d46114cb11501388d7c77a9aa Mon Sep 17 00:00:00 2001 From: ElsaLopez Date: Mon, 1 Jul 2024 11:46:32 +0200 Subject: [PATCH 06/16] creds(WIP): implement parse_ccs_psk --- shared/src/cred_new.rs | 62 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 9 deletions(-) diff --git a/shared/src/cred_new.rs b/shared/src/cred_new.rs index 5a5b919d..145f4ee7 100644 --- a/shared/src/cred_new.rs +++ b/shared/src/cred_new.rs @@ -72,16 +72,54 @@ impl Credential { /// extracted into a full credential. pub fn parse_ccs_psk(value: &[u8]) -> Result { // TODO: actually implement this + const CCS_PREFIX_LEN: usize = 3; + const CNF_AND_COSE_KEY_PREFIX_LEN: usize = 8; + const COSE_KEY_FIRST_ITEMS_LEN: usize = 4; //COSE for symmetric key + const SYMMETRIC_KEY_LEN: usize = 16; // Assuming a 128-bit symmetric key + + // Why do they add +3 and +1 in CredentialPRK::parse + if value.len() + < CCS_PREFIX_LEN + + CNF_AND_COSE_KEY_PREFIX_LEN + + COSE_KEY_FIRST_ITEMS_LEN + + SYMMETRIC_KEY_LEN + { + return Err(EDHOCError::ParsingError); + } - // example return: - Ok(Self { - bytes: BufferCred::new_from_slice(value).map_err(|_| EDHOCError::ParsingError)?, - key: CredentialKey::Symmetric(/* FIXME */ Default::default()), - kid: Some(BufferKid::new_from_slice(/* FIXME */ Default::default()).unwrap()), - cred_type: CredentialType::CCS_PSK, - }) + // Extracts len from 3rd byte (CBOR encoding) + let subject_len = CBORDecoder::info_of(value[2]) as usize; + + let id_cred_offset: usize = CCS_PREFIX_LEN + .checked_add(subject_len) + .and_then(|x| x.checked_add(CNF_AND_COSE_KEY_PREFIX_LEN)) + .ok_or(EDHOCError::ParsingError)?; + + let symmetric_key_offset: usize = id_cred_offset + .checked_add(COSE_KEY_FIRST_ITEMS_LEN) + .ok_or(EDHOCError::ParsingError)?; + + if symmetric_key_offset + .checked_add(SYMMETRIC_KEY_LEN) + .map_or(false, |end| end <= value.len()) + { + let symmetric_key: [u8; SYMMETRIC_KEY_LEN] = value + [symmetric_key_offset..symmetric_key_offset + SYMMETRIC_KEY_LEN] + .try_into() + .map_err(|_| EDHOCError::ParsingError)?; + + let kid = value[id_cred_offset]; + + Ok(Self { + bytes: BufferCred::new_from_slice(value).map_err(|_| EDHOCError::ParsingError)?, + key: CredentialKey::Symmetric(symmetric_key), + kid: Some(BufferKid::new_from_slice(&[kid]).unwrap()), + cred_type: CredentialType::CCS_PSK, + }) + } else { + Err(EDHOCError::ParsingError) + } } - /// Returns a COSE_Header map with a single entry representing a credential by value. /// /// For example, if the credential is a CCS: @@ -174,6 +212,12 @@ mod test { #[test] fn test_parse_ccs_psk() { - // TODO + // let cred = Credential::parse_ccs_psk(CRED_PSK).unwrap(); + // assert_eq!(cred.bytes.as_slice(), CRED_PSK); + // assert_eq!( + // cred.key, + // CredentialKey::Symmetric(K.try_into().unwrap()) + // ); + // assert_eq!(cred.cred_type, CredentialType::CCS_PSK); } } From f3781630cbc6e646061b5e23c8715cc7d84b2d94 Mon Sep 17 00:00:00 2001 From: Geovane Fedrecheski Date: Tue, 2 Jul 2024 18:07:48 +0200 Subject: [PATCH 07/16] creds: simplify api and add tests --- shared/src/cred_new.rs | 79 +++++++++++++++++++++++++++--------------- 1 file changed, 51 insertions(+), 28 deletions(-) diff --git a/shared/src/cred_new.rs b/shared/src/cred_new.rs index 145f4ee7..ac50edf7 100644 --- a/shared/src/cred_new.rs +++ b/shared/src/cred_new.rs @@ -19,6 +19,7 @@ pub enum CredentialKey { #[derive(Clone, Copy, Debug, PartialEq)] pub enum CredentialType { CCS, + #[allow(non_camel_case_types)] CCS_PSK, // C509, } @@ -33,13 +34,25 @@ pub struct Credential { // FIXME: should handle errors instead of panicking impl Credential { - /// Creates a new credential with the given bytes, key and type. - pub fn new(bytes: BufferCred, key: CredentialKey, cred_type: CredentialType) -> Self { + /// Creates a new CCS credential with the given bytes and public key + pub fn new_ccs(bytes: BufferCred, public_key: BytesKeyEC2) -> Self { Self { bytes, - key, + key: CredentialKey::EC2Compact(public_key), + kid: None, + cred_type: CredentialType::CCS, + } + } + + /// Creates a new CCS credential with the given bytes and a pre-shared key + /// + /// This type of credential is to be used with the under-development EDHOC method PSK. + pub fn new_ccs_psk(bytes: BufferCred, symmetric_key: BytesKeyAES128) -> Self { + Self { + bytes, + key: CredentialKey::Symmetric(symmetric_key), kid: None, - cred_type, + cred_type: CredentialType::CCS_PSK, } } @@ -124,17 +137,18 @@ impl Credential { /// /// For example, if the credential is a CCS: /// { /kccs/ 14: bytes } - pub fn by_value(&self) -> BufferIdCred { + pub fn by_value(&self) -> Result { match self.cred_type { CredentialType::CCS => { let mut cred = BufferIdCred::new(); cred.extend_from_slice(&[CBOR_MAJOR_MAP + 1, KCSS_LABEL]) - .map_err(|_| EDHOCError::CredentialTooLongError) - .unwrap(); + .map_err(|_| EDHOCError::CredentialTooLongError)?; cred.extend_from_slice(self.bytes.as_slice()).unwrap(); - cred + Ok(cred) } - CredentialType::CCS_PSK => panic!("Symmetric keys cannot be sent by value"), + // if we could encode a message along the error below, + // it would be this: "Symmetric keys cannot be sent by value" + CredentialType::CCS_PSK => Err(EDHOCError::UnexpectedCredential), } } @@ -144,23 +158,24 @@ impl Credential { /// { /kid/ 4: kid } /// /// TODO: accept a parameter to specify the type of reference, e.g. kid, x5t, etc. - pub fn by_reference(&self) -> BufferIdCred { + pub fn by_reference(&self) -> Result { let Some(kid) = self.kid.as_ref() else { - panic!("Kid not set"); + return Err(EDHOCError::MissingIdentity); }; let mut id_cred = BufferIdCred::new(); id_cred - .extend_from_slice(&[CBOR_MAJOR_MAP + 1, KID_LABEL]) - .unwrap(); - id_cred - .push(CBOR_MAJOR_BYTE_STRING | kid.len() as u8) - .unwrap(); + .extend_from_slice(&[ + CBOR_MAJOR_MAP + 1, + KID_LABEL, + CBOR_MAJOR_BYTE_STRING | kid.len() as u8, + ]) + .map_err(|_| EDHOCError::CredentialTooLongError)?; if kid.len() == 1 { id_cred.extend_from_slice(kid.as_slice()).unwrap(); } else { todo!("Larger kid not supported yet"); } - id_cred + Ok(id_cred) } } @@ -171,7 +186,8 @@ mod test { const CRED_TV: &[u8] = &hex!("a2026b6578616d706c652e65647508a101a501020241322001215820bbc34960526ea4d32e940cad2a234148ddc21791a12afbcbac93622046dd44f02258204519e257236b2a0ce2023f0931f1f386ca7afda64fcde0108c224c51eabf6072"); const G_A_TV: &[u8] = &hex!("BBC34960526EA4D32E940CAD2A234148DDC21791A12AFBCBAC93622046DD44F0"); - const ID_CRED_TV: &[u8] = &hex!("a1044132"); + const ID_CRED_BY_REF_TV: &[u8] = &hex!("a1044132"); + const ID_CRED_BY_VALUE_TV: &[u8] = &hex!("A10EA2026B6578616D706C652E65647508A101A501020241322001215820BBC34960526EA4D32E940CAD2A234148DDC21791A12AFBCBAC93622046DD44F02258204519E257236B2A0CE2023F0931F1F386CA7AFDA64FCDE0108C224C51EABF6072"); const KID_VALUE_TV: &[u8] = &hex!("32"); const CRED_PSK: &[u8] = @@ -180,21 +196,28 @@ mod test { #[test] fn test_new_cred_ccs() { - let cred = Credential::new( - CRED_TV.try_into().unwrap(), - CredentialKey::EC2Compact(G_A_TV.try_into().unwrap()), - CredentialType::CCS, - ); + let cred = Credential::new_ccs(CRED_TV.try_into().unwrap(), G_A_TV.try_into().unwrap()); assert_eq!(cred.bytes.as_slice(), CRED_TV); } + #[test] + fn test_cred_ccs_by_value_or_reference() { + let cred = Credential::new_ccs(CRED_TV.try_into().unwrap(), G_A_TV.try_into().unwrap()) + .with_kid(KID_VALUE_TV.try_into().unwrap()); + let id_cred = cred.by_value().unwrap(); + assert_eq!(id_cred.as_slice(), ID_CRED_BY_VALUE_TV); + let id_cred = cred.by_reference().unwrap(); + assert_eq!(id_cred.as_slice(), ID_CRED_BY_REF_TV); + } + + #[test] + fn test_cred_ccs_psk_by_value_or_reference() { + // TODO + } + #[test] fn test_new_cred_ccs_psk() { - let cred = Credential::new( - CRED_PSK.try_into().unwrap(), - CredentialKey::Symmetric(K.try_into().unwrap()), - CredentialType::CCS_PSK, - ); + let cred = Credential::new_ccs_psk(CRED_PSK.try_into().unwrap(), K.try_into().unwrap()); assert_eq!(cred.bytes.as_slice(), CRED_PSK); } From ea91f50a59c2fe7f7892f41fd62094f4d2be6fb0 Mon Sep 17 00:00:00 2001 From: ElsaLopez Date: Wed, 3 Jul 2024 14:43:53 +0200 Subject: [PATCH 08/16] creds: Correcting parse_css_psk --- shared/src/cred_new.rs | 85 ++++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 40 deletions(-) diff --git a/shared/src/cred_new.rs b/shared/src/cred_new.rs index 505a605c..b5bd72b5 100644 --- a/shared/src/cred_new.rs +++ b/shared/src/cred_new.rs @@ -74,44 +74,50 @@ impl Credential { // TODO: actually implement this const CCS_PREFIX_LEN: usize = 3; const CNF_AND_COSE_KEY_PREFIX_LEN: usize = 8; - const COSE_KEY_FIRST_ITEMS_LEN: usize = 4; //COSE for symmetric key - const SYMMETRIC_KEY_LEN: usize = 16; // Assuming a 128-bit symmetric key - - // Why do they add +3 and +1 in CredentialPRK::parse - if value.len() < CCS_PREFIX_LEN + CNF_AND_COSE_KEY_PREFIX_LEN + COSE_KEY_FIRST_ITEMS_LEN + SYMMETRIC_KEY_LEN { - return Err(EDHOCError::ParsingError); - } - - // Extracts len from 3rd byte (CBOR encoding) - let subject_len = CBORDecoder::info_of(value[2]) as usize; - - let id_cred_offset: usize = CCS_PREFIX_LEN - .checked_add(subject_len) - .and_then(|x| x.checked_add(CNF_AND_COSE_KEY_PREFIX_LEN)) - .ok_or(EDHOCError::ParsingError)?; - - let symmetric_key_offset: usize = id_cred_offset - .checked_add(COSE_KEY_FIRST_ITEMS_LEN) - .ok_or(EDHOCError::ParsingError)?; - - if symmetric_key_offset - .checked_add(SYMMETRIC_KEY_LEN) - .map_or(false, |end| end <= value.len()) + const COSE_KEY_FIRST_ITEMS_LEN: usize = 3; //COSE for symmetric key + const SYMMETRIC_KEY_LEN: usize = 16; // Assuming a 128-bit symmetric key + + if value.len() + < CCS_PREFIX_LEN + + 1 + + CNF_AND_COSE_KEY_PREFIX_LEN + + COSE_KEY_FIRST_ITEMS_LEN + + SYMMETRIC_KEY_LEN { - let symmetric_key: [u8; SYMMETRIC_KEY_LEN] = value[symmetric_key_offset..symmetric_key_offset + SYMMETRIC_KEY_LEN] - .try_into() - .map_err(|_| EDHOCError::ParsingError)?; - - let kid = value[id_cred_offset]; - - Ok(Self { - bytes: BufferCred::new_from_slice(value).map_err(|_| EDHOCError::ParsingError)?, - key: CredentialKey::Symmetric(symmetric_key), - kid: Some(BufferKid::new_from_slice(&[kid]).unwrap()), - cred_type: CredentialType::CCS_PSK, - }) - } else { Err(EDHOCError::ParsingError) + } else { + let subject_len = CBORDecoder::info_of(value[2]) as usize; + + let id_cred_offset: usize = CCS_PREFIX_LEN + .checked_add(subject_len) + .and_then(|x| x.checked_add(CNF_AND_COSE_KEY_PREFIX_LEN)) + .ok_or(EDHOCError::ParsingError)?; + + let symmetric_key_offset: usize = id_cred_offset + .checked_add(COSE_KEY_FIRST_ITEMS_LEN) + .ok_or(EDHOCError::ParsingError)?; + + if symmetric_key_offset + .checked_add(SYMMETRIC_KEY_LEN) + .map_or(false, |end| end <= value.len()) + { + let symmetric_key: [u8; SYMMETRIC_KEY_LEN] = value + [symmetric_key_offset..symmetric_key_offset + SYMMETRIC_KEY_LEN] + .try_into() + .map_err(|_| EDHOCError::ParsingError)?; + + let kid = value[id_cred_offset]; + + Ok(Self { + bytes: BufferCred::new_from_slice(value) + .map_err(|_| EDHOCError::ParsingError)?, + key: CredentialKey::Symmetric(symmetric_key), + kid: Some(BufferKid::new_from_slice(&[kid]).unwrap()), + cred_type: CredentialType::CCS_PSK, + }) + } else { + Err(EDHOCError::ParsingError) + } } } /// Returns a COSE_Header map with a single entry representing a credential by value. @@ -171,6 +177,7 @@ mod test { const CRED_PSK: &[u8] = &hex!("A202686D79646F74626F7408A101A30104024132205050930FF462A77A3540CF546325DEA214"); const K: &[u8] = &hex!("50930FF462A77A3540CF546325DEA214"); + const KID_VALUE_PSK: &[u8] = &hex!("32"); #[test] fn test_new_cred_ccs() { @@ -208,10 +215,8 @@ mod test { fn test_parse_ccs_psk() { let cred = Credential::parse_ccs_psk(CRED_PSK).unwrap(); assert_eq!(cred.bytes.as_slice(), CRED_PSK); - assert_eq!( - cred.key, - CredentialKey::Symmetric(K.try_into().unwrap()) - ); + assert_eq!(cred.key, CredentialKey::Symmetric(K.try_into().unwrap())); + assert_eq!(cred.kid.unwrap().as_slice(), KID_VALUE_PSK); assert_eq!(cred.cred_type, CredentialType::CCS_PSK); } } From 0ce1959e7849093e925c1853c49c6006bfc263b0 Mon Sep 17 00:00:00 2001 From: Geovane Fedrecheski Date: Wed, 3 Jul 2024 15:15:25 +0200 Subject: [PATCH 09/16] creds: use new struct for IdCred borrowing code from @chrysn's PR #274 --- shared/src/cred_new.rs | 67 +++++++++++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 11 deletions(-) diff --git a/shared/src/cred_new.rs b/shared/src/cred_new.rs index e264beae..0170ab63 100644 --- a/shared/src/cred_new.rs +++ b/shared/src/cred_new.rs @@ -24,8 +24,46 @@ pub enum CredentialType { // C509, } +/// A value of ID_CRED_x: a credential identifier +/// +/// Possible values include key IDs, credentials by value and others. +// TODO: rename to just IdCred +pub struct StructIdCredNew { + /// The value is always stored in the ID_CRED_x form as a serialized one-element dictionary; + /// while this technically wastes two bytes, it has the convenient property of having the full + /// value available as a slice. + pub bytes: BufferIdCred, // variable size, can contain either the contents of a BufferCred or a BufferKid +} + +impl StructIdCredNew { + pub fn new() -> Self { + Self { + bytes: BufferIdCred::new(), + } + } + + /// View the full value of the ID_CRED_x: the CBOR encoding of a 1-element CBOR map + pub fn as_full_value(&self) -> &[u8] { + self.bytes.as_slice() + } + + /// View the value as encoded in the ID_CRED_x position of plaintext_2 and plaintext_3, + /// applying the Compact Encoding of ID_CRED Fields described in RFC9528 Section 3.5.3.2 + pub fn as_compact_encoding(&self) -> &[u8] { + match self.bytes.as_slice() { + [0xa1, 0x04, 0x41, x] if (x >> 5) < 2 && (x & 0x1f) < 24 => &self.bytes.as_slice()[3..], + [0xa1, 0x04, ..] => &self.bytes.as_slice()[2..], + _ => self.bytes.as_slice(), + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] pub struct Credential { /// Original bytes of the credential, CBOR-encoded + /// + /// If the credential is a CCS, it contains an encoded CBOR map containnig + /// a COSE_Key in a cnf claim, see RFC 9528 Section 3.5.2. pub bytes: BufferCred, pub key: CredentialKey, pub kid: Option, // other types of identifiers can be added, such as `pub x5t: Option` @@ -137,14 +175,19 @@ impl Credential { /// /// For example, if the credential is a CCS: /// { /kccs/ 14: bytes } - pub fn by_value(&self) -> Result { + pub fn by_value(&self) -> Result { match self.cred_type { CredentialType::CCS => { - let mut cred = BufferIdCred::new(); - cred.extend_from_slice(&[CBOR_MAJOR_MAP + 1, KCSS_LABEL]) + let mut id_cred = StructIdCredNew::new(); + id_cred + .bytes + .extend_from_slice(&[CBOR_MAJOR_MAP + 1, KCSS_LABEL]) .map_err(|_| EDHOCError::CredentialTooLongError)?; - cred.extend_from_slice(self.bytes.as_slice()).unwrap(); - Ok(cred) + id_cred + .bytes + .extend_from_slice(self.bytes.as_slice()) + .unwrap(); + Ok(id_cred) } // if we could encode a message along the error below, // it would be this: "Symmetric keys cannot be sent by value" @@ -158,12 +201,13 @@ impl Credential { /// { /kid/ 4: kid } /// /// TODO: accept a parameter to specify the type of reference, e.g. kid, x5t, etc. - pub fn by_reference(&self) -> Result { + pub fn by_kid(&self) -> Result { let Some(kid) = self.kid.as_ref() else { return Err(EDHOCError::MissingIdentity); }; - let mut id_cred = BufferIdCred::new(); + let mut id_cred = StructIdCredNew::new(); id_cred + .bytes .extend_from_slice(&[ CBOR_MAJOR_MAP + 1, KID_LABEL, @@ -171,8 +215,9 @@ impl Credential { ]) .map_err(|_| EDHOCError::CredentialTooLongError)?; if kid.len() == 1 { - id_cred.extend_from_slice(kid.as_slice()).unwrap(); + id_cred.bytes.extend_from_slice(kid.as_slice()).unwrap(); } else { + // TODO: this should actually just work, but let's leave it as is for testing later todo!("Larger kid not supported yet"); } Ok(id_cred) @@ -206,9 +251,9 @@ mod test { let cred = Credential::new_ccs(CRED_TV.try_into().unwrap(), G_A_TV.try_into().unwrap()) .with_kid(KID_VALUE_TV.try_into().unwrap()); let id_cred = cred.by_value().unwrap(); - assert_eq!(id_cred.as_slice(), ID_CRED_BY_VALUE_TV); - let id_cred = cred.by_reference().unwrap(); - assert_eq!(id_cred.as_slice(), ID_CRED_BY_REF_TV); + assert_eq!(id_cred.bytes.as_slice(), ID_CRED_BY_VALUE_TV); + let id_cred = cred.by_kid().unwrap(); + assert_eq!(id_cred.bytes.as_slice(), ID_CRED_BY_REF_TV); } #[test] From a6792fccdcdf440cc1a26501f535da7c77be6c2b Mon Sep 17 00:00:00 2001 From: Geovane Fedrecheski Date: Thu, 4 Jul 2024 14:37:35 +0200 Subject: [PATCH 10/16] creds: new id_cred struct improvements --- shared/Cargo.toml | 1 + shared/src/cred_new.rs | 89 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 81 insertions(+), 9 deletions(-) diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 3bda9930..572854d8 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -18,6 +18,7 @@ log = "0.4" [dev-dependencies] hexlit = "0.5.3" +rstest = "0.21.0" [features] default = [ ] diff --git a/shared/src/cred_new.rs b/shared/src/cred_new.rs index 0170ab63..fe495c1b 100644 --- a/shared/src/cred_new.rs +++ b/shared/src/cred_new.rs @@ -24,24 +24,71 @@ pub enum CredentialType { // C509, } +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum IdCredType { + KID = 4, + KCCS = 14, +} + +impl From for IdCredType { + fn from(value: u8) -> Self { + match value { + 4 => IdCredType::KID, + 14 => IdCredType::KCCS, + _ => panic!("Invalid IdCredType"), + } + } +} + /// A value of ID_CRED_x: a credential identifier /// /// Possible values include key IDs, credentials by value and others. // TODO: rename to just IdCred -pub struct StructIdCredNew { +pub struct IdCredNew { /// The value is always stored in the ID_CRED_x form as a serialized one-element dictionary; /// while this technically wastes two bytes, it has the convenient property of having the full /// value available as a slice. pub bytes: BufferIdCred, // variable size, can contain either the contents of a BufferCred or a BufferKid } -impl StructIdCredNew { +impl IdCredNew { pub fn new() -> Self { Self { bytes: BufferIdCred::new(), } } + /// Instantiate an IdCredNew from an encoded value. + pub fn from_encoded_plaintext(value: &[u8]) -> Result { + let bytes = match value { + // kid that has been encoded as CBOR integer + &[x] if x < 24 => { + BufferIdCred::new_from_slice(&[0xa1, KID_LABEL, 0x41, x]) + .map_err(|_| EDHOCError::CredentialTooLongError)? // TODO: make this error handling less verbose? + } + // kid that has been encoded as CBOR byte string + &[0x41, ..] => { + let mut bytes = BufferIdCred::new_from_slice(&[0xa1, KID_LABEL]) + .map_err(|_| EDHOCError::CredentialTooLongError)?; + bytes + .extend_from_slice(value) + .map_err(|_| EDHOCError::CredentialTooLongError)?; + bytes + } + // credential by value + value => { + let mut bytes = BufferIdCred::new_from_slice(&[0xa1, KCSS_LABEL]) + .map_err(|_| EDHOCError::CredentialTooLongError)?; + bytes + .extend_from_slice(value) + .map_err(|_| EDHOCError::CredentialTooLongError)?; + bytes + } + }; + + Ok(Self { bytes }) + } + /// View the full value of the ID_CRED_x: the CBOR encoding of a 1-element CBOR map pub fn as_full_value(&self) -> &[u8] { self.bytes.as_slice() @@ -49,13 +96,20 @@ impl StructIdCredNew { /// View the value as encoded in the ID_CRED_x position of plaintext_2 and plaintext_3, /// applying the Compact Encoding of ID_CRED Fields described in RFC9528 Section 3.5.3.2 - pub fn as_compact_encoding(&self) -> &[u8] { + /// Note that this does NOT encode the value as CBOR, it rather just applies the EDHOC Compact Encoding when applicable. + pub fn encode_for_plaintext(&self) -> &[u8] { match self.bytes.as_slice() { - [0xa1, 0x04, 0x41, x] if (x >> 5) < 2 && (x & 0x1f) < 24 => &self.bytes.as_slice()[3..], - [0xa1, 0x04, ..] => &self.bytes.as_slice()[2..], + [0xa1, KID_LABEL, 0x41, x] if (x >> 5) < 2 && (x & 0x1f) < 24 => { + &self.bytes.as_slice()[3..] + } + [0xa1, KID_LABEL, ..] => &self.bytes.as_slice()[2..], _ => self.bytes.as_slice(), } } + + pub fn item_type(&self) -> IdCredType { + self.bytes.as_slice()[1].into() + } } #[derive(Clone, Copy, Debug, PartialEq)] @@ -171,14 +225,15 @@ impl Credential { } } } + /// Returns a COSE_Header map with a single entry representing a credential by value. /// /// For example, if the credential is a CCS: /// { /kccs/ 14: bytes } - pub fn by_value(&self) -> Result { + pub fn by_value(&self) -> Result { match self.cred_type { CredentialType::CCS => { - let mut id_cred = StructIdCredNew::new(); + let mut id_cred = IdCredNew::new(); id_cred .bytes .extend_from_slice(&[CBOR_MAJOR_MAP + 1, KCSS_LABEL]) @@ -201,11 +256,11 @@ impl Credential { /// { /kid/ 4: kid } /// /// TODO: accept a parameter to specify the type of reference, e.g. kid, x5t, etc. - pub fn by_kid(&self) -> Result { + pub fn by_kid(&self) -> Result { let Some(kid) = self.kid.as_ref() else { return Err(EDHOCError::MissingIdentity); }; - let mut id_cred = StructIdCredNew::new(); + let mut id_cred = IdCredNew::new(); id_cred .bytes .extend_from_slice(&[ @@ -228,6 +283,7 @@ impl Credential { mod test { use super::*; use hexlit::hex; + use rstest::rstest; const CRED_TV: &[u8] = &hex!("a2026b6578616d706c652e65647508a101a501020241322001215820bbc34960526ea4d32e940cad2a234148ddc21791a12afbcbac93622046dd44f02258204519e257236b2a0ce2023f0931f1f386ca7afda64fcde0108c224c51eabf6072"); const G_A_TV: &[u8] = &hex!("BBC34960526EA4D32E940CAD2A234148DDC21791A12AFBCBAC93622046DD44F0"); @@ -252,8 +308,10 @@ mod test { .with_kid(KID_VALUE_TV.try_into().unwrap()); let id_cred = cred.by_value().unwrap(); assert_eq!(id_cred.bytes.as_slice(), ID_CRED_BY_VALUE_TV); + assert_eq!(id_cred.item_type(), IdCredType::KCCS); let id_cred = cred.by_kid().unwrap(); assert_eq!(id_cred.bytes.as_slice(), ID_CRED_BY_REF_TV); + assert_eq!(id_cred.item_type(), IdCredType::KID); } #[test] @@ -287,4 +345,17 @@ mod test { assert_eq!(cred.kid.unwrap().as_slice(), KID_VALUE_PSK); assert_eq!(cred.cred_type, CredentialType::CCS_PSK); } + + #[rstest] + #[case(&[0x0D], &[0xa1, 0x04, 0x41, 0x0D])] // two optimizations: omit kid label and encode as CBOR integer + #[case(&[0x41, 0x18], &[0xa1, 0x04, 0x41, 0x18])] // one optimization: omit kid label + #[case(CRED_TV, ID_CRED_BY_VALUE_TV)] // regular credential by value + fn test_id_cred_from_encoded_plaintext(#[case] input: &[u8], #[case] expected: &[u8]) { + assert_eq!( + IdCredNew::from_encoded_plaintext(input) + .unwrap() + .as_full_value(), + expected + ); + } } From 6d449b3efd21bf25e2b4ceec01413318cf77030e Mon Sep 17 00:00:00 2001 From: Geovane Fedrecheski Date: Thu, 4 Jul 2024 15:10:04 +0200 Subject: [PATCH 11/16] creds: apply new id_cred struct across codebase --- lib/src/edhoc.rs | 128 ++++++++++++++++------------------------- lib/src/lib.rs | 29 +++++----- shared/src/cred_new.rs | 73 +++++++++++++++-------- shared/src/lib.rs | 49 ++-------------- 4 files changed, 119 insertions(+), 160 deletions(-) diff --git a/lib/src/edhoc.rs b/lib/src/edhoc.rs index b20c1db8..09554bb3 100644 --- a/lib/src/edhoc.rs +++ b/lib/src/edhoc.rs @@ -109,24 +109,28 @@ pub fn r_prepare_message_2( let salt_3e2m = compute_salt_3e2m(crypto, &prk_2e, &th_2); let prk_3e2m = compute_prk_3e2m(crypto, &salt_3e2m, r, &state.g_x); + let id_cred_r = match cred_transfer { + CredentialTransfer::ByValue => { + IdCred::tmp_from_ccs_or_kid(cred_r.value.as_slice(), IdCredType::KCCS as u8)? + } + CredentialTransfer::ByReference => { + IdCred::tmp_from_ccs_or_kid(&[cred_r.kid], IdCredType::KID as u8)? + } + }; + // compute MAC_2 let mac_2 = compute_mac_2( crypto, &prk_3e2m, c_r, - &cred_r.get_id_cred(), + id_cred_r.as_full_value(), cred_r.value.as_slice(), &th_2, ead_2, ); - let id_cred_r = match cred_transfer { - CredentialTransfer::ByValue => IdCred::FullCredential(cred_r.value.as_slice()), - CredentialTransfer::ByReference => IdCred::CompactKid(cred_r.kid), - }; - // compute ciphertext_2 - let plaintext_2 = encode_plaintext_2(c_r, &id_cred_r, &mac_2, &ead_2)?; + let plaintext_2 = encode_plaintext_2(c_r, id_cred_r.as_encoded_value(), &mac_2, &ead_2)?; // step is actually from processing of message_3 // but we do it here to avoid storing plaintext_2 in State @@ -156,37 +160,13 @@ pub fn r_parse_message_3( state: &mut WaitM3, crypto: &mut impl CryptoTrait, message_3: &BufferMessage3, -) -> Result<(ProcessingM3, CredentialRPK, Option), EDHOCError> { +) -> Result<(ProcessingM3, IdCred, Option), EDHOCError> { let plaintext_3 = decrypt_message_3(crypto, &state.prk_3e2m, &state.th_3, message_3); if let Ok(plaintext_3) = plaintext_3 { let decoded_p3_res = decode_plaintext_3(&plaintext_3); if let Ok((id_cred_i, mac_3, ead_3)) = decoded_p3_res { - let id_cred_i = match id_cred_i { - IdCred::CompactKid(kid) => CredentialRPK { - value: Default::default(), - public_key: Default::default(), - kid, - }, - IdCred::FullCredential(cred) => { - let Ok(buffer) = EdhocMessageBuffer::new_from_slice(cred) else { - return Err(EDHOCError::ParsingError); - }; - if let Ok(parsed_rpk) = CredentialRPK::new(buffer) { - parsed_rpk - } else { - // This is incomplete, and the application will need to fill in the gaps -- - // just as in the CompactKid case the CredentialRPK is also incomplete. - CredentialRPK { - value: buffer, - public_key: Default::default(), - kid: Default::default(), - } - } - } - }; - Ok(( ProcessingM3 { mac_3, @@ -306,7 +286,7 @@ pub fn i_parse_message_2<'a>( state: &WaitM2, crypto: &mut impl CryptoTrait, message_2: &BufferMessage2, -) -> Result<(ProcessingM2, ConnId, CredentialRPK, Option), EDHOCError> { +) -> Result<(ProcessingM2, ConnId, IdCred, Option), EDHOCError> { let res = parse_message_2(message_2); if let Ok((g_y, ciphertext_2)) = res { let th_2 = compute_th_2(crypto, &g_y, &state.h_message_1); @@ -331,20 +311,6 @@ pub fn i_parse_message_2<'a>( ead_2: ead_2.clone(), // needed for compute_mac_2 }; - let id_cred_r = match id_cred_r { - IdCred::CompactKid(kid) => CredentialRPK { - value: Default::default(), - public_key: Default::default(), - kid, - }, - IdCred::FullCredential(cred) => { - let Ok(buffer) = EdhocMessageBuffer::new_from_slice(cred) else { - return Err(EDHOCError::ParsingError); - }; - CredentialRPK::new(buffer)? - } - }; - Ok((state, c_r_2, id_cred_r, ead_2)) } else { Err(EDHOCError::ParsingError) @@ -409,20 +375,25 @@ pub fn i_prepare_message_3( cred_transfer: CredentialTransfer, ead_3: &Option, // FIXME: make it a list of EADItem ) -> Result<(Completed, BufferMessage3, BytesHashLen), EDHOCError> { + let id_cred_i = match cred_transfer { + CredentialTransfer::ByValue => { + IdCred::tmp_from_ccs_or_kid(cred_i.value.as_slice(), IdCredType::KCCS as u8)? + } + CredentialTransfer::ByReference => { + IdCred::tmp_from_ccs_or_kid(&[cred_i.kid], IdCredType::KID as u8)? + } + }; + let mac_3 = compute_mac_3( crypto, &state.prk_4e3m, &state.th_3, - &cred_i.get_id_cred(), + id_cred_i.as_full_value(), cred_i.value.as_slice(), ead_3, ); - let id_cred_i = match cred_transfer { - CredentialTransfer::ByValue => IdCred::FullCredential(cred_i.value.as_slice()), - CredentialTransfer::ByReference => IdCred::CompactKid(cred_i.kid), - }; - let plaintext_3 = encode_plaintext_3(&id_cred_i, &mac_3, &ead_3)?; + let plaintext_3 = encode_plaintext_3(id_cred_i.as_encoded_value(), &mac_3, &ead_3)?; let message_3 = encrypt_message_3(crypto, &state.prk_3e2m, &state.th_3, &plaintext_3); let th_4 = compute_th_4(crypto, &state.th_3, &plaintext_3, cred_i.value.as_slice()); @@ -643,14 +614,17 @@ fn edhoc_kdf( } fn encode_plaintext_3( - id_cred_i: &IdCred, + id_cred_i: &[u8], mac_3: &BytesMac3, ead_3: &Option, ) -> Result { let mut plaintext_3: BufferPlaintext3 = BufferPlaintext3::new(); // plaintext: P = ( ? PAD, ID_CRED_I / bstr / int, Signature_or_MAC_3, ? EAD_3 ) - id_cred_i.write_to_message(&mut plaintext_3)?; + // id_cred_i.write_to_message(&mut plaintext_3)?; + plaintext_3 + .extend_from_slice(id_cred_i) + .or(Err(EDHOCError::EncodingError))?; let offset_cred = plaintext_3.len; plaintext_3.content[offset_cred] = CBOR_MAJOR_BYTE_STRING | MAC_LENGTH_3 as u8; plaintext_3.content[offset_cred + 1..][..mac_3.len()].copy_from_slice(&mac_3[..]); @@ -793,7 +767,7 @@ fn decrypt_message_3( // output must hold id_cred.len() + cred.len() fn encode_kdf_context( c_r: Option, // only present for MAC_2 - id_cred: &BytesIdCred, + id_cred: &[u8], th: &BytesHashLen, cred: &[u8], ead: &Option, @@ -809,7 +783,7 @@ fn encode_kdf_context( } else { 0 // no u8 encoded }; - output[output_len..output_len + id_cred.len()].copy_from_slice(&id_cred[..]); + output[output_len..output_len + id_cred.len()].copy_from_slice(&id_cred); output[output_len + id_cred.len()] = CBOR_BYTE_STRING; output[output_len + id_cred.len() + 1] = SHA256_DIGEST_LEN as u8; output[output_len + id_cred.len() + 2..output_len + id_cred.len() + 2 + th.len()] @@ -835,7 +809,7 @@ fn compute_mac_3( crypto: &mut impl CryptoTrait, prk_4e3m: &BytesHashLen, th_3: &BytesHashLen, - id_cred_i: &BytesIdCred, + id_cred_i: &[u8], cred_i: &[u8], ead_3: &Option, ) -> BytesMac3 { @@ -861,7 +835,7 @@ fn compute_mac_2( crypto: &mut impl CryptoTrait, prk_3e2m: &BytesHashLen, c_r: ConnId, - id_cred_r: &BytesIdCred, + id_cred_r: &[u8], cred_r: &[u8], th_2: &BytesHashLen, ead_2: &Option, @@ -880,7 +854,7 @@ fn compute_mac_2( fn encode_plaintext_2( c_r: ConnId, - id_cred_r: &IdCred, + id_cred_r: &[u8], mac_2: &BytesMac2, ead_2: &Option, ) -> Result { @@ -890,7 +864,10 @@ fn encode_plaintext_2( plaintext_2 .extend_from_slice(c_r) .or(Err(EDHOCError::EncodingError))?; - id_cred_r.write_to_message(&mut plaintext_2)?; + // id_cred_r.write_to_message(&mut plaintext_2)?; + plaintext_2 + .extend_from_slice(id_cred_r) + .or(Err(EDHOCError::EncodingError))?; let offset_cred = plaintext_2.len; plaintext_2.content[offset_cred] = CBOR_MAJOR_BYTE_STRING | MAC_LENGTH_2 as u8; @@ -1395,7 +1372,9 @@ mod tests { let plaintext_2_tv = BufferPlaintext2::from_hex(PLAINTEXT_2_TV); let plaintext_2 = encode_plaintext_2( C_R_TV, - &IdCred::CompactKid(ID_CRED_R_TV[ID_CRED_R_TV.len() - 1]), + IdCred::from_full_value(&ID_CRED_R_TV[..]) + .unwrap() + .as_encoded_value(), &MAC_2_TV, &None::, ) @@ -1423,11 +1402,7 @@ mod tests { assert!(plaintext_2.is_ok()); let (c_r, id_cred_r, mac_2, ead_2) = plaintext_2.unwrap(); assert_eq!(c_r, C_R_TV); - let id_cred_r = match id_cred_r { - IdCred::CompactKid(id_cred_r) => id_cred_r, - _ => panic!("Invalid ID_CRED_R"), - }; - assert_eq!(id_cred_r, ID_CRED_R_TV[3]); + assert_eq!(id_cred_r.as_full_value(), ID_CRED_R_TV); assert_eq!(mac_2, MAC_2_TV); assert!(ead_2.is_none()); } @@ -1480,26 +1455,25 @@ mod tests { #[test] fn test_encode_plaintext_3() { let plaintext_3_tv = BufferPlaintext3::from_hex(PLAINTEXT_3_TV); - let kid_tv = ID_CRED_I_TV[ID_CRED_I_TV.len() - 1]; - let plaintext_3 = - encode_plaintext_3(&IdCred::CompactKid(kid_tv), &MAC_3_TV, &None::).unwrap(); + let plaintext_3 = encode_plaintext_3( + IdCred::from_full_value(&ID_CRED_I_TV[..]) + .unwrap() + .as_encoded_value(), + &MAC_3_TV, + &None::, + ) + .unwrap(); assert_eq!(plaintext_3, plaintext_3_tv); } #[test] fn test_decode_plaintext_3() { let plaintext_3_tv = BufferPlaintext3::from_hex(PLAINTEXT_3_TV); - let kid_tv = ID_CRED_I_TV[ID_CRED_I_TV.len() - 1]; let (id_cred_i, mac_3, ead_3) = decode_plaintext_3(&plaintext_3_tv).unwrap(); - let kid = match id_cred_i { - IdCred::CompactKid(id_cred_i) => id_cred_i, - _ => panic!("Invalid ID_CRED_I"), - }; - assert_eq!(mac_3, MAC_3_TV); - assert_eq!(kid, kid_tv); + assert_eq!(id_cred_i.as_full_value(), ID_CRED_I_TV); assert!(ead_3.is_none()); } diff --git a/lib/src/lib.rs b/lib/src/lib.rs index b4824bf3..6901915f 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -180,14 +180,7 @@ impl<'a, Crypto: CryptoTrait> EdhocResponderWaitM3 { pub fn parse_message_3( mut self, message_3: &'a BufferMessage3, - ) -> Result< - ( - EdhocResponderProcessingM3, - CredentialRPK, - Option, - ), - EDHOCError, - > { + ) -> Result<(EdhocResponderProcessingM3, IdCred, Option), EDHOCError> { trace!("Enter parse_message_3"); match r_parse_message_3(&mut self.state, &mut self.crypto, message_3) { Ok((state, id_cred_i, ead_3)) => Ok(( @@ -321,7 +314,7 @@ impl<'a, Crypto: CryptoTrait> EdhocInitiatorWaitM2 { ( EdhocInitiatorProcessingM2, ConnId, - CredentialRPK, + IdCred, Option, ), EDHOCError, @@ -471,7 +464,7 @@ pub fn generate_connection_identifier(crypto: &mut Crypto) // Implements auth credential checking according to draft-tiloca-lake-implem-cons pub fn credential_check_or_fetch( cred_expected: Option, - id_cred_received: CredentialRPK, + id_cred_received: IdCred, ) -> Result { trace!("Enter credential_check_or_fetch"); // Processing of auth credentials according to draft-tiloca-lake-implem-cons @@ -481,9 +474,10 @@ pub fn credential_check_or_fetch( // IMPL: compare cred_i_expected with id_cred // IMPL: assume cred_i_expected is well formed let credentials_match = if id_cred_received.reference_only() { - id_cred_received.kid == cred_expected.kid + // FIXME: will be fixed when we update CredentialRPK to Credential + id_cred_received.as_full_value()[3] == cred_expected.kid } else { - id_cred_received.value == cred_expected.value + &id_cred_received.as_full_value()[2..] == cred_expected.value.as_slice() }; // 2. Is this authentication credential still valid? @@ -512,7 +506,9 @@ pub fn credential_check_or_fetch( // Pair it with consistent credential identifiers, for each supported type of credential identifier. assert!(!id_cred_received.reference_only()); - Ok(id_cred_received) + // FIXME: will be fixed when we update CredentialRPK to Credential + CredentialRPK::new(id_cred_received.as_full_value()[2..].try_into().unwrap()) + .map_err(|_| EDHOCError::ParsingError) } // 8. Is this authentication credential good to use in the context of this EDHOC session? @@ -733,7 +729,12 @@ mod test_authz { EDHOCMethod::StatStat, EDHOCSuite::CipherSuite2, ); - let responder = EdhocResponder::new(default_crypto(), R, cred_r); + let responder = EdhocResponder::new( + default_crypto(), + EDHOCMethod::StatStat, + R.try_into().expect("Wrong length of responder private key"), + cred_r.clone(), + ); // ==== initialize ead-authz ==== let device = ZeroTouchDevice::new( diff --git a/shared/src/cred_new.rs b/shared/src/cred_new.rs index fe495c1b..44052309 100644 --- a/shared/src/cred_new.rs +++ b/shared/src/cred_new.rs @@ -44,30 +44,38 @@ impl From for IdCredType { /// /// Possible values include key IDs, credentials by value and others. // TODO: rename to just IdCred -pub struct IdCredNew { +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct IdCred { /// The value is always stored in the ID_CRED_x form as a serialized one-element dictionary; /// while this technically wastes two bytes, it has the convenient property of having the full /// value available as a slice. pub bytes: BufferIdCred, // variable size, can contain either the contents of a BufferCred or a BufferKid } -impl IdCredNew { +impl IdCred { pub fn new() -> Self { Self { bytes: BufferIdCred::new(), } } - /// Instantiate an IdCredNew from an encoded value. - pub fn from_encoded_plaintext(value: &[u8]) -> Result { + pub fn from_full_value(value: &[u8]) -> Result { + Ok(Self { + bytes: BufferIdCred::new_from_slice(value) + .map_err(|_| EDHOCError::CredentialTooLongError)?, + }) + } + + /// Instantiate an IdCred from an encoded value. + pub fn from_encoded_value(value: &[u8]) -> Result { let bytes = match value { // kid that has been encoded as CBOR integer - &[x] if x < 24 => { + &[x] if Self::bstr_representable_as_int(x) => { BufferIdCred::new_from_slice(&[0xa1, KID_LABEL, 0x41, x]) .map_err(|_| EDHOCError::CredentialTooLongError)? // TODO: make this error handling less verbose? } // kid that has been encoded as CBOR byte string - &[0x41, ..] => { + &[0x41, x, ..] if !Self::bstr_representable_as_int(x) => { let mut bytes = BufferIdCred::new_from_slice(&[0xa1, KID_LABEL]) .map_err(|_| EDHOCError::CredentialTooLongError)?; bytes @@ -75,15 +83,10 @@ impl IdCredNew { .map_err(|_| EDHOCError::CredentialTooLongError)?; bytes } - // credential by value - value => { - let mut bytes = BufferIdCred::new_from_slice(&[0xa1, KCSS_LABEL]) - .map_err(|_| EDHOCError::CredentialTooLongError)?; - bytes - .extend_from_slice(value) - .map_err(|_| EDHOCError::CredentialTooLongError)?; - bytes - } + // CCS by value + &[0xa1, KCSS_LABEL, ..] => BufferIdCred::new_from_slice(value) + .map_err(|_| EDHOCError::CredentialTooLongError)?, + _ => return Err(EDHOCError::ParsingError), }; Ok(Self { bytes }) @@ -97,7 +100,7 @@ impl IdCredNew { /// View the value as encoded in the ID_CRED_x position of plaintext_2 and plaintext_3, /// applying the Compact Encoding of ID_CRED Fields described in RFC9528 Section 3.5.3.2 /// Note that this does NOT encode the value as CBOR, it rather just applies the EDHOC Compact Encoding when applicable. - pub fn encode_for_plaintext(&self) -> &[u8] { + pub fn as_encoded_value(&self) -> &[u8] { match self.bytes.as_slice() { [0xa1, KID_LABEL, 0x41, x] if (x >> 5) < 2 && (x & 0x1f) < 24 => { &self.bytes.as_slice()[3..] @@ -107,9 +110,33 @@ impl IdCredNew { } } + pub fn reference_only(&self) -> bool { + [IdCredType::KID].contains(&self.item_type()) + } + pub fn item_type(&self) -> IdCredType { self.bytes.as_slice()[1].into() } + + fn bstr_representable_as_int(value: u8) -> bool { + (0x0..=0x17).contains(&value) || (0x20..=0x37).contains(&value) + } + + // FIXME: function only used while CredentialRPK is still around + pub fn tmp_from_ccs_or_kid(value: &[u8], label: u8) -> Result { + let mut bytes = BufferIdCred::new_from_slice(&[0xa1, label]) + .map_err(|_| EDHOCError::CredentialTooLongError)?; + if label == IdCredType::KID as u8 { + // the actual value of the kid is always a byte string + bytes + .extend_from_slice(&[0x40 | value.len() as u8]) + .map_err(|_| EDHOCError::CredentialTooLongError)?; + } + bytes + .extend_from_slice(value) + .map_err(|_| EDHOCError::CredentialTooLongError)?; + Ok(Self { bytes }) + } } #[derive(Clone, Copy, Debug, PartialEq)] @@ -230,10 +257,10 @@ impl Credential { /// /// For example, if the credential is a CCS: /// { /kccs/ 14: bytes } - pub fn by_value(&self) -> Result { + pub fn by_value(&self) -> Result { match self.cred_type { CredentialType::CCS => { - let mut id_cred = IdCredNew::new(); + let mut id_cred = IdCred::new(); id_cred .bytes .extend_from_slice(&[CBOR_MAJOR_MAP + 1, KCSS_LABEL]) @@ -256,11 +283,11 @@ impl Credential { /// { /kid/ 4: kid } /// /// TODO: accept a parameter to specify the type of reference, e.g. kid, x5t, etc. - pub fn by_kid(&self) -> Result { + pub fn by_kid(&self) -> Result { let Some(kid) = self.kid.as_ref() else { return Err(EDHOCError::MissingIdentity); }; - let mut id_cred = IdCredNew::new(); + let mut id_cred = IdCred::new(); id_cred .bytes .extend_from_slice(&[ @@ -349,12 +376,10 @@ mod test { #[rstest] #[case(&[0x0D], &[0xa1, 0x04, 0x41, 0x0D])] // two optimizations: omit kid label and encode as CBOR integer #[case(&[0x41, 0x18], &[0xa1, 0x04, 0x41, 0x18])] // one optimization: omit kid label - #[case(CRED_TV, ID_CRED_BY_VALUE_TV)] // regular credential by value + #[case(ID_CRED_BY_VALUE_TV, ID_CRED_BY_VALUE_TV)] // regular credential by value fn test_id_cred_from_encoded_plaintext(#[case] input: &[u8], #[case] expected: &[u8]) { assert_eq!( - IdCredNew::from_encoded_plaintext(input) - .unwrap() - .as_full_value(), + IdCred::from_encoded_value(input).unwrap().as_full_value(), expected ); } diff --git a/shared/src/lib.rs b/shared/src/lib.rs index 3fd124c1..f938de6e 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -545,31 +545,6 @@ impl EADItem { } } -// FIXME: homogenize the two structs below (likey keep only the owned version) -#[derive(Debug, Clone, Copy)] -pub enum IdCred<'a> { - CompactKid(u8), - /// Credential by value. It is required that the credential is a valid deterministic encoding - /// of a CCS. - FullCredential(&'a [u8]), -} - -impl<'a> IdCred<'a> { - pub fn write_to_message(&self, message: &mut EdhocMessageBuffer) -> Result<(), EDHOCError> { - match self { - IdCred::CompactKid(kid) => message.extend_from_slice(&[*kid]), - IdCred::FullCredential(cred) => { - let kccs_map_len = 1; - message - .extend_from_slice(&[CBOR_MAJOR_MAP + kccs_map_len, KCSS_LABEL]) - .map_err(|_| EDHOCError::CredentialTooLongError)?; - message.extend_from_slice(cred) - } - } - .map_err(|_| EDHOCError::CredentialTooLongError) - } -} - mod helpers { use super::*; @@ -768,16 +743,8 @@ mod edhoc_parser { let c_r = ConnId::from_int_raw(decoder.int_raw()?); - // NOTE: if len of bstr is 1, it is a compact kid and therefore should have been encoded as int - let id_cred_r = if CBOR_MAJOR_MAP == CBORDecoder::type_of(decoder.current()?) { - if decoder.map()? == 1 && decoder.u8()? == KCSS_LABEL { - IdCred::FullCredential(decoder.any_as_encoded()?) - } else { - return Err(EDHOCError::ParsingError); - } - } else { - IdCred::CompactKid(decoder.int_raw()?) - }; + // the id_cred may have been encoded as a single int, a byte string, or a map + let id_cred_r = IdCred::from_encoded_value(decoder.any_as_encoded()?)?; mac_2[..].copy_from_slice(decoder.bytes_sized(MAC_LENGTH_2)?); @@ -805,16 +772,8 @@ mod edhoc_parser { let mut decoder = CBORDecoder::new(plaintext_3.as_slice()); - // NOTE: if len of bstr is 1, it is a compact kid and therefore should have been encoded as int - let id_cred_i = if CBOR_MAJOR_MAP == CBORDecoder::type_of(decoder.current()?) { - if decoder.map()? == 1 && decoder.u8()? == KCSS_LABEL { - IdCred::FullCredential(decoder.any_as_encoded()?) - } else { - return Err(EDHOCError::ParsingError); - } - } else { - IdCred::CompactKid(decoder.int_raw()?) - }; + // the id_cred may have been encoded as a single int, a byte string, or a map + let id_cred_i = IdCred::from_encoded_value(decoder.any_as_encoded()?)?; mac_3[..].copy_from_slice(decoder.bytes_sized(MAC_LENGTH_3)?); From e4e1b6207496a711759cc19cadb5484c16853c0c Mon Sep 17 00:00:00 2001 From: Geovane Fedrecheski Date: Thu, 4 Jul 2024 17:35:01 +0200 Subject: [PATCH 12/16] creds: apply new credential struct across codebase --- lib/src/edhoc.rs | 66 ++++++++++++++++++++++-------------------- lib/src/lib.rs | 62 +++++++++++++++++++-------------------- shared/src/cred_new.rs | 21 ++++++++------ shared/src/lib.rs | 4 ++- 4 files changed, 81 insertions(+), 72 deletions(-) diff --git a/lib/src/edhoc.rs b/lib/src/edhoc.rs index 09554bb3..1437f5c8 100644 --- a/lib/src/edhoc.rs +++ b/lib/src/edhoc.rs @@ -95,7 +95,7 @@ pub fn r_process_message_1( pub fn r_prepare_message_2( state: &ProcessingM1, crypto: &mut impl CryptoTrait, - cred_r: CredentialRPK, + cred_r: Credential, r: &BytesP256ElemLen, // R's static private DH key c_r: ConnId, cred_transfer: CredentialTransfer, @@ -110,12 +110,8 @@ pub fn r_prepare_message_2( let prk_3e2m = compute_prk_3e2m(crypto, &salt_3e2m, r, &state.g_x); let id_cred_r = match cred_transfer { - CredentialTransfer::ByValue => { - IdCred::tmp_from_ccs_or_kid(cred_r.value.as_slice(), IdCredType::KCCS as u8)? - } - CredentialTransfer::ByReference => { - IdCred::tmp_from_ccs_or_kid(&[cred_r.kid], IdCredType::KID as u8)? - } + CredentialTransfer::ByValue => cred_r.by_value()?, + CredentialTransfer::ByReference => cred_r.by_kid()?, }; // compute MAC_2 @@ -124,7 +120,7 @@ pub fn r_prepare_message_2( &prk_3e2m, c_r, id_cred_r.as_full_value(), - cred_r.value.as_slice(), + cred_r.bytes.as_slice(), &th_2, ead_2, ); @@ -134,7 +130,7 @@ pub fn r_prepare_message_2( // step is actually from processing of message_3 // but we do it here to avoid storing plaintext_2 in State - let th_3 = compute_th_3(crypto, &th_2, &plaintext_2, cred_r.value.as_slice()); + let th_3 = compute_th_3(crypto, &th_2, &plaintext_2, cred_r.bytes.as_slice()); let mut ct: BufferCiphertext2 = BufferCiphertext2::new(); ct.fill_with_slice(plaintext_2.as_slice()).unwrap(); // TODO(hax): can we prove with hax that this won't panic since they use the same underlying buffer length? @@ -173,6 +169,7 @@ pub fn r_parse_message_3( y: state.y, prk_3e2m: state.prk_3e2m, th_3: state.th_3, + id_cred_i: id_cred_i.clone(), // needed for compute_mac_3 plaintext_3, // NOTE: this is needed for th_4, which needs valid_cred_i, which is only available at the 'verify' step ead_3: ead_3.clone(), // NOTE: this clone could be avoided by using a reference or an index to the ead_3 item in plaintext_3 }, @@ -191,20 +188,25 @@ pub fn r_parse_message_3( pub fn r_verify_message_3( state: &mut ProcessingM3, crypto: &mut impl CryptoTrait, - valid_cred_i: CredentialRPK, + valid_cred_i: Credential, ) -> Result<(Completed, BytesHashLen), EDHOCError> { // compute salt_4e3m let salt_4e3m = compute_salt_4e3m(crypto, &state.prk_3e2m, &state.th_3); - // TODO compute prk_4e3m - let prk_4e3m = compute_prk_4e3m(crypto, &salt_4e3m, &state.y, &valid_cred_i.public_key); + + let prk_4e3m = match valid_cred_i.key { + CredentialKey::EC2Compact(public_key) => { + compute_prk_4e3m(crypto, &salt_4e3m, &state.y, &public_key) + } + CredentialKey::Symmetric(_psk) => todo!("PSK not implemented"), + }; // compute mac_3 let expected_mac_3 = compute_mac_3( crypto, &prk_4e3m, &state.th_3, - &valid_cred_i.get_id_cred(), - valid_cred_i.value.as_slice(), + state.id_cred_i.as_full_value(), + valid_cred_i.bytes.as_slice(), &state.ead_3, ); @@ -214,7 +216,7 @@ pub fn r_verify_message_3( crypto, &state.th_3, &state.plaintext_3, - valid_cred_i.value.as_slice(), + valid_cred_i.bytes.as_slice(), ); let mut th_4_buf: BytesMaxContextBuffer = [0x00; MAX_KDF_CONTEXT_LEN]; @@ -308,7 +310,8 @@ pub fn i_parse_message_2<'a>( g_y, plaintext_2: plaintext_2, c_r: c_r_2, - ead_2: ead_2.clone(), // needed for compute_mac_2 + id_cred_r: id_cred_r.clone(), // needed for compute_mac_2 + ead_2: ead_2.clone(), // needed for compute_mac_2 }; Ok((state, c_r_2, id_cred_r, ead_2)) @@ -323,20 +326,25 @@ pub fn i_parse_message_2<'a>( pub fn i_verify_message_2( state: &ProcessingM2, crypto: &mut impl CryptoTrait, - valid_cred_r: CredentialRPK, // TODO: have a struct to hold credentials to avoid re-computing - i: &BytesP256ElemLen, // I's static private DH key + valid_cred_r: Credential, + i: &BytesP256ElemLen, // I's static private DH key ) -> Result { // verify mac_2 let salt_3e2m = compute_salt_3e2m(crypto, &state.prk_2e, &state.th_2); - let prk_3e2m = compute_prk_3e2m(crypto, &salt_3e2m, &state.x, &valid_cred_r.public_key); + let prk_3e2m = match valid_cred_r.key { + CredentialKey::EC2Compact(public_key) => { + compute_prk_3e2m(crypto, &salt_3e2m, &state.x, &public_key) + } + CredentialKey::Symmetric(_psk) => todo!("PSK not implemented"), + }; let expected_mac_2 = compute_mac_2( crypto, &prk_3e2m, state.c_r, - &valid_cred_r.get_id_cred(), - valid_cred_r.value.as_slice(), + state.id_cred_r.as_full_value(), + valid_cred_r.bytes.as_slice(), &state.th_2, &state.ead_2, ); @@ -348,7 +356,7 @@ pub fn i_verify_message_2( crypto, &state.th_2, &state.plaintext_2, - valid_cred_r.value.as_slice(), + valid_cred_r.bytes.as_slice(), ); // message 3 processing @@ -371,17 +379,13 @@ pub fn i_verify_message_2( pub fn i_prepare_message_3( state: &ProcessedM2, crypto: &mut impl CryptoTrait, - cred_i: CredentialRPK, + cred_i: Credential, cred_transfer: CredentialTransfer, ead_3: &Option, // FIXME: make it a list of EADItem ) -> Result<(Completed, BufferMessage3, BytesHashLen), EDHOCError> { let id_cred_i = match cred_transfer { - CredentialTransfer::ByValue => { - IdCred::tmp_from_ccs_or_kid(cred_i.value.as_slice(), IdCredType::KCCS as u8)? - } - CredentialTransfer::ByReference => { - IdCred::tmp_from_ccs_or_kid(&[cred_i.kid], IdCredType::KID as u8)? - } + CredentialTransfer::ByValue => cred_i.by_value()?, + CredentialTransfer::ByReference => cred_i.by_kid()?, }; let mac_3 = compute_mac_3( @@ -389,14 +393,14 @@ pub fn i_prepare_message_3( &state.prk_4e3m, &state.th_3, id_cred_i.as_full_value(), - cred_i.value.as_slice(), + cred_i.bytes.as_slice(), ead_3, ); let plaintext_3 = encode_plaintext_3(id_cred_i.as_encoded_value(), &mac_3, &ead_3)?; let message_3 = encrypt_message_3(crypto, &state.prk_3e2m, &state.th_3, &plaintext_3); - let th_4 = compute_th_4(crypto, &state.th_3, &plaintext_3, cred_i.value.as_slice()); + let th_4 = compute_th_4(crypto, &state.th_3, &plaintext_3, cred_i.bytes.as_slice()); let mut th_4_buf: BytesMaxContextBuffer = [0x00; MAX_KDF_CONTEXT_LEN]; th_4_buf[..th_4.len()].copy_from_slice(&th_4[..]); diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 6901915f..caed15c9 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -32,7 +32,7 @@ pub use edhoc::*; pub struct EdhocInitiator { state: InitiatorStart, // opaque state i: Option, // static public key of myself - cred_i: Option, + cred_i: Option, crypto: Crypto, } @@ -40,7 +40,7 @@ pub struct EdhocInitiator { pub struct EdhocInitiatorWaitM2 { state: WaitM2, // opaque state i: Option, - cred_i: Option, + cred_i: Option, crypto: Crypto, } @@ -48,14 +48,14 @@ pub struct EdhocInitiatorWaitM2 { pub struct EdhocInitiatorProcessingM2 { state: ProcessingM2, // opaque state i: Option, - cred_i: Option, + cred_i: Option, crypto: Crypto, } #[derive(Debug)] pub struct EdhocInitiatorProcessedM2 { state: ProcessedM2, // opaque state - cred_i: Option, + cred_i: Option, crypto: Crypto, } @@ -70,15 +70,15 @@ pub struct EdhocInitiatorDone { pub struct EdhocResponder { state: ResponderStart, // opaque state r: BytesP256ElemLen, // private authentication key of R - cred_r: CredentialRPK, // R's full credential + cred_r: Credential, // R's full credential crypto: Crypto, } #[derive(Debug)] pub struct EdhocResponderProcessedM1 { - state: ProcessingM1, // opaque state - r: BytesP256ElemLen, // private authentication key of R - cred_r: CredentialRPK, // R's full credential + state: ProcessingM1, // opaque state + r: BytesP256ElemLen, // private authentication key of R + cred_r: Credential, // R's full credential crypto: Crypto, } @@ -105,7 +105,7 @@ impl EdhocResponder { mut crypto: Crypto, method: EDHOCMethod, r: BytesP256ElemLen, - cred_r: CredentialRPK, + cred_r: Credential, ) -> Self { trace!("Initializing EdhocResponder"); let (y, g_y) = crypto.p256_generate_key_pair(); @@ -199,7 +199,7 @@ impl<'a, Crypto: CryptoTrait> EdhocResponderWaitM3 { impl<'a, Crypto: CryptoTrait> EdhocResponderProcessingM3 { pub fn verify_message_3( mut self, - cred_i: CredentialRPK, + cred_i: Credential, ) -> Result<(EdhocResponderDone, [u8; SHA256_DIGEST_LEN]), EDHOCError> { trace!("Enter verify_message_3"); match r_verify_message_3(&mut self.state, &mut self.crypto, cred_i) { @@ -267,7 +267,7 @@ impl<'a, Crypto: CryptoTrait> EdhocInitiator { } } - pub fn set_identity(&mut self, i: BytesP256ElemLen, cred_i: CredentialRPK) { + pub fn set_identity(&mut self, i: BytesP256ElemLen, cred_i: Credential) { self.i = Some(i); self.cred_i = Some(cred_i); } @@ -341,7 +341,7 @@ impl<'a, Crypto: CryptoTrait> EdhocInitiatorProcessingM2 { pub fn set_identity( &mut self, i: BytesP256ElemLen, - cred_i: CredentialRPK, + cred_i: Credential, ) -> Result<(), EDHOCError> { if self.i.is_some() || self.cred_i.is_some() { return Err(EDHOCError::IdentityAlreadySet); @@ -353,7 +353,7 @@ impl<'a, Crypto: CryptoTrait> EdhocInitiatorProcessingM2 { pub fn verify_message_2( mut self, - valid_cred_r: CredentialRPK, + valid_cred_r: Credential, ) -> Result, EDHOCError> { trace!("Enter verify_message_2"); let Some(i) = self.i else { @@ -463,9 +463,9 @@ pub fn generate_connection_identifier(crypto: &mut Crypto) // Implements auth credential checking according to draft-tiloca-lake-implem-cons pub fn credential_check_or_fetch( - cred_expected: Option, + cred_expected: Option, id_cred_received: IdCred, -) -> Result { +) -> Result { trace!("Enter credential_check_or_fetch"); // Processing of auth credentials according to draft-tiloca-lake-implem-cons // Comments tagged with a number refer to steps in Section 4.3.1. of draft-tiloca-lake-implem-cons @@ -474,10 +474,9 @@ pub fn credential_check_or_fetch( // IMPL: compare cred_i_expected with id_cred // IMPL: assume cred_i_expected is well formed let credentials_match = if id_cred_received.reference_only() { - // FIXME: will be fixed when we update CredentialRPK to Credential - id_cred_received.as_full_value()[3] == cred_expected.kid + id_cred_received.as_full_value() == cred_expected.by_kid()?.as_full_value() } else { - &id_cred_received.as_full_value()[2..] == cred_expected.value.as_slice() + id_cred_received.as_full_value() == cred_expected.by_value()?.as_full_value() }; // 2. Is this authentication credential still valid? @@ -506,8 +505,8 @@ pub fn credential_check_or_fetch( // Pair it with consistent credential identifiers, for each supported type of credential identifier. assert!(!id_cred_received.reference_only()); - // FIXME: will be fixed when we update CredentialRPK to Credential - CredentialRPK::new(id_cred_received.as_full_value()[2..].try_into().unwrap()) + // FIXME: this is not elegant, should be solved at IdCred level + Credential::parse_ccs(id_cred_received.as_full_value()[2..].try_into().unwrap()) .map_err(|_| EDHOCError::ParsingError) } @@ -553,7 +552,7 @@ mod test { default_crypto(), EDHOCMethod::StatStat, R.try_into().expect("Wrong length of responder private key"), - CredentialRPK::new(CRED_R.try_into().unwrap()).unwrap(), + Credential::parse_ccs(CRED_R.try_into().unwrap()).unwrap(), ); } @@ -578,7 +577,7 @@ mod test { default_crypto(), EDHOCMethod::StatStat, R.try_into().expect("Wrong length of responder private key"), - CredentialRPK::new(CRED_R.try_into().unwrap()).unwrap(), + Credential::parse_ccs(CRED_R.try_into().unwrap()).unwrap(), ); // process message_1 first time, when unsupported suite is selected @@ -592,7 +591,7 @@ mod test { default_crypto(), EDHOCMethod::StatStat, R.try_into().expect("Wrong length of responder private key"), - CredentialRPK::new(CRED_R.try_into().unwrap()).unwrap(), + Credential::parse_ccs(CRED_R.try_into().unwrap()).unwrap(), ); // process message_1 second time @@ -609,8 +608,8 @@ mod test { #[cfg(feature = "test-ead-none")] #[test] fn test_handshake() { - let cred_i = CredentialRPK::new(CRED_I.try_into().unwrap()).unwrap(); - let cred_r = CredentialRPK::new(CRED_R.try_into().unwrap()).unwrap(); + let cred_i = Credential::parse_ccs(CRED_I.try_into().unwrap()).unwrap(); + let cred_r = Credential::parse_ccs(CRED_R.try_into().unwrap()).unwrap(); let initiator = EdhocInitiator::new( default_crypto(), @@ -712,11 +711,11 @@ mod test_authz { // TODO: have a setup_test function that prepares the common objects for the ead tests #[test] fn test_handshake_authz() { - let cred_i = CredentialRPK::new(CRED_I.try_into().unwrap()).unwrap(); - let cred_r = CredentialRPK::new(CRED_R.try_into().unwrap()).unwrap(); + let cred_i = Credential::parse_ccs(CRED_I.try_into().unwrap()).unwrap(); + let cred_r = Credential::parse_ccs(CRED_R.try_into().unwrap()).unwrap(); - let mock_fetch_cred_i = |id_cred_i: CredentialRPK| -> Result { - if id_cred_i.kid == cred_i.kid { + let mock_fetch_cred_i = |id_cred_i: IdCred| -> Result { + if id_cred_i.as_full_value() == cred_i.by_kid()?.as_full_value() { Ok(cred_i.clone()) } else { Err(EDHOCError::UnexpectedCredential) @@ -744,7 +743,8 @@ mod test_authz { ); let authenticator = ZeroTouchAuthenticator::default(); - let acl = EdhocMessageBuffer::new_from_slice(&[cred_i.kid]).unwrap(); + let single_byte_kid = cred_i.kid.as_ref().unwrap()[0]; // FIXME: add longer kid support in ACL + let acl = EdhocMessageBuffer::new_from_slice(&[single_byte_kid]).unwrap(); let server = ZeroTouchServer::new( W_TV.try_into().unwrap(), CRED_R.try_into().unwrap(), @@ -804,7 +804,7 @@ mod test_authz { let valid_cred_i = if id_cred_i.reference_only() { mock_fetch_cred_i(id_cred_i).unwrap() } else { - id_cred_i + id_cred_i.get_ccs().unwrap() }; let (mut _responder, r_prk_out) = responder.verify_message_3(valid_cred_i).unwrap(); diff --git a/shared/src/cred_new.rs b/shared/src/cred_new.rs index 44052309..452cedc6 100644 --- a/shared/src/cred_new.rs +++ b/shared/src/cred_new.rs @@ -1,8 +1,8 @@ use super::*; -pub type BufferCred = EdhocBuffer<128>; // arbitrary size +pub type BufferCred = EdhocBuffer<192>; // arbitrary size pub type BufferKid = EdhocBuffer<16>; // variable size, up to 16 bytes -pub type BufferIdCred = EdhocBuffer<128>; // variable size, can contain either the contents of a BufferCred or a BufferKid +pub type BufferIdCred = EdhocBuffer<192>; // variable size, can contain either the contents of a BufferCred or a BufferKid pub type BytesKeyAES128 = [u8; 16]; pub type BytesKeyEC2 = [u8; 32]; pub type BytesKeyOKP = [u8; 32]; @@ -44,7 +44,7 @@ impl From for IdCredType { /// /// Possible values include key IDs, credentials by value and others. // TODO: rename to just IdCred -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, Default, PartialEq)] pub struct IdCred { /// The value is always stored in the ID_CRED_x form as a serialized one-element dictionary; /// while this technically wastes two bytes, it has the convenient property of having the full @@ -118,6 +118,14 @@ impl IdCred { self.bytes.as_slice()[1].into() } + pub fn get_ccs(&self) -> Option { + if self.item_type() == IdCredType::KCCS { + Credential::parse_ccs(&self.bytes.as_slice()[2..]).ok() + } else { + None + } + } + fn bstr_representable_as_int(value: u8) -> bool { (0x0..=0x17).contains(&value) || (0x20..=0x37).contains(&value) } @@ -296,12 +304,7 @@ impl Credential { CBOR_MAJOR_BYTE_STRING | kid.len() as u8, ]) .map_err(|_| EDHOCError::CredentialTooLongError)?; - if kid.len() == 1 { - id_cred.bytes.extend_from_slice(kid.as_slice()).unwrap(); - } else { - // TODO: this should actually just work, but let's leave it as is for testing later - todo!("Larger kid not supported yet"); - } + id_cred.bytes.extend_from_slice(kid.as_slice()).unwrap(); Ok(id_cred) } } diff --git a/shared/src/lib.rs b/shared/src/lib.rs index f938de6e..223a55f9 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -64,7 +64,7 @@ pub const MAC_LENGTH_3: usize = MAC_LENGTH_2; pub const ENCODED_VOUCHER_LEN: usize = 1 + MAC_LENGTH; // 1 byte for the length of the bstr-encoded voucher // maximum supported length of connection identifier for R -pub const MAX_KDF_CONTEXT_LEN: usize = SCALE_FACTOR * 150; +pub const MAX_KDF_CONTEXT_LEN: usize = SCALE_FACTOR * 256; pub const MAX_KDF_LABEL_LEN: usize = 15; // for "KEYSTREAM_2" pub const MAX_BUFFER_LEN: usize = SCALE_FACTOR * 256; pub const CBOR_BYTE_STRING: u8 = 0x58u8; @@ -367,6 +367,7 @@ pub struct ProcessingM2 { pub g_y: BytesP256ElemLen, pub plaintext_2: EdhocMessageBuffer, pub c_r: ConnId, + pub id_cred_r: IdCred, pub ead_2: Option, } @@ -384,6 +385,7 @@ pub struct ProcessingM3 { pub y: BytesP256ElemLen, // ephemeral private key of the responder pub prk_3e2m: BytesHashLen, pub th_3: BytesHashLen, + pub id_cred_i: IdCred, pub plaintext_3: EdhocMessageBuffer, pub ead_3: Option, } From d378208f77a516c4890c04a37cdfad4cde66f4c4 Mon Sep 17 00:00:00 2001 From: Geovane Fedrecheski Date: Thu, 4 Jul 2024 18:02:20 +0200 Subject: [PATCH 13/16] creds: cleaning up after refactor --- examples/lakers-no_std/src/main.rs | 24 ++++++--- lib/src/edhoc.rs | 5 -- lib/src/lib.rs | 12 ++--- shared/src/cred_new.rs | 78 ++++++++++++++++++------------ 4 files changed, 70 insertions(+), 49 deletions(-) diff --git a/examples/lakers-no_std/src/main.rs b/examples/lakers-no_std/src/main.rs index 18e8dbe7..61cf22db 100644 --- a/examples/lakers-no_std/src/main.rs +++ b/examples/lakers-no_std/src/main.rs @@ -101,15 +101,20 @@ fn main() -> ! { println!("Test test_prepare_message_1 passed."); fn test_handshake() { - let cred_i = CredentialRPK::new(CRED_I.try_into().unwrap()).unwrap(); - let cred_r = CredentialRPK::new(CRED_R.try_into().unwrap()).unwrap(); + let cred_i = Credential::parse_ccs(CRED_I.try_into().unwrap()).unwrap(); + let cred_r = Credential::parse_ccs(CRED_R.try_into().unwrap()).unwrap(); - let mut initiator = EdhocInitiator::new( + let initiator = EdhocInitiator::new( lakers_crypto::default_crypto(), EDHOCMethod::StatStat, EDHOCSuite::CipherSuite2, ); - let responder = EdhocResponder::new(lakers_crypto::default_crypto(), R, cred_r.clone()); + let responder = EdhocResponder::new( + lakers_crypto::default_crypto(), + EDHOCMethod::StatStat, + R.try_into().expect("Wrong length of responder private key"), + cred_r.clone(), + ); let (initiator, message_1) = initiator.prepare_message_1(None, &None).unwrap(); @@ -118,9 +123,16 @@ fn main() -> ! { .prepare_message_2(CredentialTransfer::ByReference, None, &None) .unwrap(); - let (initiator, c_r, id_cred_r, ead_2) = initiator.parse_message_2(&message_2).unwrap(); + let (mut initiator, _c_r, id_cred_r, _ead_2) = + initiator.parse_message_2(&message_2).unwrap(); let valid_cred_r = credential_check_or_fetch(Some(cred_r), id_cred_r).unwrap(); - let initiator = initiator.verify_message_2(I, cred_i, valid_cred_r).unwrap(); + initiator + .set_identity( + I.try_into().expect("Wrong length of initiator private key"), + cred_i.clone(), + ) + .unwrap(); // exposing own identity only after validating cred_r + let initiator = initiator.verify_message_2(valid_cred_r).unwrap(); let (mut initiator, message_3, i_prk_out) = initiator .prepare_message_3(CredentialTransfer::ByReference, &None) diff --git a/lib/src/edhoc.rs b/lib/src/edhoc.rs index 1437f5c8..446f264d 100644 --- a/lib/src/edhoc.rs +++ b/lib/src/edhoc.rs @@ -24,10 +24,6 @@ pub fn edhoc_key_update( context: &BytesMaxContextBuffer, context_len: usize, ) -> BytesHashLen { - // FIXME: Normally we would decompose `state` here, but hax disallows aliasing a `mut` item. - // The best fix for this is to change state from a tuple-struct to a regular struct. - // In the code below, `state.6` means `mut prk_out` and `state.7` means `mut prk_exporter` - // new PRK_out let prk_new_buf = edhoc_kdf( crypto, @@ -151,7 +147,6 @@ pub fn r_prepare_message_2( )) } -// FIXME fetch ID_CRED_I and CRED_I based on kid pub fn r_parse_message_3( state: &mut WaitM3, crypto: &mut impl CryptoTrait, diff --git a/lib/src/lib.rs b/lib/src/lib.rs index caed15c9..08b56e76 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -15,7 +15,6 @@ //! [EDHOC]: https://datatracker.ietf.org/doc/html/rfc9528 #![cfg_attr(not(test), no_std)] -// use defmt_or_log::*; // FIXME: still not working use log::trace; pub use {lakers_shared::Crypto as CryptoTrait, lakers_shared::*}; @@ -26,7 +25,6 @@ pub use lakers_ead_authz::*; mod edhoc; pub use edhoc::*; -// TODO: clean these structs and remove the cred_x whre they are not needed anymore /// Starting point for performing EDHOC in the role of the Initiator. #[derive(Debug)] pub struct EdhocInitiator { @@ -498,16 +496,16 @@ pub fn credential_check_or_fetch( // 4. Is the trust model Pre-knowledge + TOFU? YES (hardcoded to YES for now) // 6. Validate CRED_X. Generally a CCS has to be validated only syntactically and semantically, unlike a certificate or a CWT. // Is the validation successful? - // IMPL,NOTE: the credential has already been parsed with CredentialRPK::new in the *_parse_message_* function // 5. Is the authentication credential authorized for use in the context of this EDHOC session? // IMPL,TODO: we just skip this step for now // 7. Store CRED_X as valid and trusted. // Pair it with consistent credential identifiers, for each supported type of credential identifier. - assert!(!id_cred_received.reference_only()); - // FIXME: this is not elegant, should be solved at IdCred level - Credential::parse_ccs(id_cred_received.as_full_value()[2..].try_into().unwrap()) - .map_err(|_| EDHOCError::ParsingError) + if let Some(cred) = id_cred_received.get_ccs() { + Ok(cred) + } else { + Err(EDHOCError::ParsingError) + } } // 8. Is this authentication credential good to use in the context of this EDHOC session? diff --git a/shared/src/cred_new.rs b/shared/src/cred_new.rs index 452cedc6..b4f82956 100644 --- a/shared/src/cred_new.rs +++ b/shared/src/cred_new.rs @@ -43,7 +43,6 @@ impl From for IdCredType { /// A value of ID_CRED_x: a credential identifier /// /// Possible values include key IDs, credentials by value and others. -// TODO: rename to just IdCred #[derive(Clone, Copy, Debug, Default, PartialEq)] pub struct IdCred { /// The value is always stored in the ID_CRED_x form as a serialized one-element dictionary; @@ -72,7 +71,7 @@ impl IdCred { // kid that has been encoded as CBOR integer &[x] if Self::bstr_representable_as_int(x) => { BufferIdCred::new_from_slice(&[0xa1, KID_LABEL, 0x41, x]) - .map_err(|_| EDHOCError::CredentialTooLongError)? // TODO: make this error handling less verbose? + .map_err(|_| EDHOCError::CredentialTooLongError)? // TODO: how to avoid map_err overuse? } // kid that has been encoded as CBOR byte string &[0x41, x, ..] if !Self::bstr_representable_as_int(x) => { @@ -129,22 +128,6 @@ impl IdCred { fn bstr_representable_as_int(value: u8) -> bool { (0x0..=0x17).contains(&value) || (0x20..=0x37).contains(&value) } - - // FIXME: function only used while CredentialRPK is still around - pub fn tmp_from_ccs_or_kid(value: &[u8], label: u8) -> Result { - let mut bytes = BufferIdCred::new_from_slice(&[0xa1, label]) - .map_err(|_| EDHOCError::CredentialTooLongError)?; - if label == IdCredType::KID as u8 { - // the actual value of the kid is always a byte string - bytes - .extend_from_slice(&[0x40 | value.len() as u8]) - .map_err(|_| EDHOCError::CredentialTooLongError)?; - } - bytes - .extend_from_slice(value) - .map_err(|_| EDHOCError::CredentialTooLongError)?; - Ok(Self { bytes }) - } } #[derive(Clone, Copy, Debug, PartialEq)] @@ -159,7 +142,6 @@ pub struct Credential { pub cred_type: CredentialType, } -// FIXME: should handle errors instead of panicking impl Credential { /// Creates a new CCS credential with the given bytes and public key pub fn new_ccs(bytes: BufferCred, public_key: BytesKeyEC2) -> Self { @@ -192,18 +174,53 @@ impl Credential { /// Parse a CCS style credential /// - /// If the given value matches the shape Lakers expects of a CCS, its public key and key ID are - /// extracted into a full credential. + /// If the given value matches the shape lakers expects of a CCS, i.e. credentials from RFC9529, + /// its public key and key ID are extracted into a full credential. pub fn parse_ccs(value: &[u8]) -> Result { - // Implementing in terms of the old structure, to be moved in here in later versions of - // this change set - let (public_key, kid) = CredentialRPK::parse(value)?; - Ok(Self { - bytes: BufferCred::new_from_slice(value).map_err(|_| EDHOCError::ParsingError)?, - key: CredentialKey::EC2Compact(public_key), - kid: Some(BufferKid::new_from_slice(&[kid]).unwrap()), - cred_type: CredentialType::CCS, - }) + const CCS_PREFIX_LEN: usize = 3; + const CNF_AND_COSE_KEY_PREFIX_LEN: usize = 8; + const COSE_KEY_FIRST_ITEMS_LEN: usize = 6; + + if value.len() + < 3 + CCS_PREFIX_LEN + + 1 + + CNF_AND_COSE_KEY_PREFIX_LEN + + COSE_KEY_FIRST_ITEMS_LEN + + P256_ELEM_LEN + { + Err(EDHOCError::ParsingError) + } else { + let subject_len = CBORDecoder::info_of(value[2]) as usize; + + let id_cred_offset: usize = CCS_PREFIX_LEN + .checked_add(subject_len) + .and_then(|x| x.checked_add(CNF_AND_COSE_KEY_PREFIX_LEN)) + .ok_or(EDHOCError::ParsingError)?; + + let g_a_x_offset: usize = id_cred_offset + .checked_add(COSE_KEY_FIRST_ITEMS_LEN) + .ok_or(EDHOCError::ParsingError)?; + + if g_a_x_offset + .checked_add(P256_ELEM_LEN) + .map_or(false, |end| end <= value.len()) + { + let public_key: BytesKeyEC2 = value[g_a_x_offset..g_a_x_offset + P256_ELEM_LEN] + .try_into() + .expect("Wrong key length"); + let kid = value[id_cred_offset]; + + Ok(Self { + bytes: BufferCred::new_from_slice(value) + .map_err(|_| EDHOCError::ParsingError)?, + key: CredentialKey::EC2Compact(public_key), + kid: Some(BufferKid::new_from_slice(&[kid]).unwrap()), + cred_type: CredentialType::CCS, + }) + } else { + Err(EDHOCError::ParsingError) + } + } } /// Parse a CCS style credential, but the key is a symmetric key @@ -211,7 +228,6 @@ impl Credential { /// If the given value matches the shape Lakers expects of a CCS, its public key and key ID are /// extracted into a full credential. pub fn parse_ccs_psk(value: &[u8]) -> Result { - // TODO: actually implement this const CCS_PREFIX_LEN: usize = 3; const CNF_AND_COSE_KEY_PREFIX_LEN: usize = 8; const COSE_KEY_FIRST_ITEMS_LEN: usize = 3; //COSE for symmetric key From 23e276cf8738c00518a4250e47e39bb5bad0a16d Mon Sep 17 00:00:00 2001 From: Geovane Fedrecheski Date: Thu, 4 Jul 2024 18:09:48 +0200 Subject: [PATCH 14/16] creds: replace old cred module with newer --- shared/src/cred.rs | 387 ++++++++++++++++++++++++++++---- shared/src/cred_new.rs | 405 ---------------------------------- shared/src/lib.rs | 3 - shared/src/python_bindings.rs | 1 + 4 files changed, 350 insertions(+), 446 deletions(-) delete mode 100644 shared/src/cred_new.rs diff --git a/shared/src/cred.rs b/shared/src/cred.rs index 23bf2063..48f56255 100644 --- a/shared/src/cred.rs +++ b/shared/src/cred.rs @@ -1,39 +1,187 @@ use super::*; -#[derive(Clone, Copy, Debug)] -#[cfg_attr(feature = "python-bindings", pyclass)] -#[repr(C)] -pub struct CredentialRPK { - pub value: EdhocMessageBuffer, - pub public_key: BytesP256ElemLen, // could be a reference, but safe Rust doesn't allow self-referencing structs - pub kid: u8, +pub type BufferCred = EdhocBuffer<192>; // arbitrary size +pub type BufferKid = EdhocBuffer<16>; // variable size, up to 16 bytes +pub type BufferIdCred = EdhocBuffer<192>; // variable size, can contain either the contents of a BufferCred or a BufferKid +pub type BytesKeyAES128 = [u8; 16]; +pub type BytesKeyEC2 = [u8; 32]; + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum CredentialKey { + Symmetric(BytesKeyAES128), + EC2Compact(BytesKeyEC2), + // Add other key types as needed +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum CredentialType { + CCS, + #[allow(non_camel_case_types)] + CCS_PSK, + // C509, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum IdCredType { + KID = 4, + KCCS = 14, +} + +impl From for IdCredType { + fn from(value: u8) -> Self { + match value { + 4 => IdCredType::KID, + 14 => IdCredType::KCCS, + _ => panic!("Invalid IdCredType"), + } + } } -impl CredentialRPK { - pub fn new(value: EdhocMessageBuffer) -> Result { - let (public_key, kid) = Self::parse(value.as_slice())?; +/// A value of ID_CRED_x: a credential identifier +/// +/// Possible values include key IDs, credentials by value and others. +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct IdCred { + /// The value is always stored in the ID_CRED_x form as a serialized one-element dictionary; + /// while this technically wastes two bytes, it has the convenient property of having the full + /// value available as a slice. + pub bytes: BufferIdCred, // variable size, can contain either the contents of a BufferCred or a BufferKid +} + +impl IdCred { + pub fn new() -> Self { + Self { + bytes: BufferIdCred::new(), + } + } + + pub fn from_full_value(value: &[u8]) -> Result { Ok(Self { - value, - public_key, - kid, + bytes: BufferIdCred::new_from_slice(value) + .map_err(|_| EDHOCError::CredentialTooLongError)?, }) } + /// Instantiate an IdCred from an encoded value. + pub fn from_encoded_value(value: &[u8]) -> Result { + let bytes = match value { + // kid that has been encoded as CBOR integer + &[x] if Self::bstr_representable_as_int(x) => { + BufferIdCred::new_from_slice(&[0xa1, KID_LABEL, 0x41, x]) + .map_err(|_| EDHOCError::CredentialTooLongError)? // TODO: how to avoid map_err overuse? + } + // kid that has been encoded as CBOR byte string + &[0x41, x, ..] if !Self::bstr_representable_as_int(x) => { + let mut bytes = BufferIdCred::new_from_slice(&[0xa1, KID_LABEL]) + .map_err(|_| EDHOCError::CredentialTooLongError)?; + bytes + .extend_from_slice(value) + .map_err(|_| EDHOCError::CredentialTooLongError)?; + bytes + } + // CCS by value + &[0xa1, KCSS_LABEL, ..] => BufferIdCred::new_from_slice(value) + .map_err(|_| EDHOCError::CredentialTooLongError)?, + _ => return Err(EDHOCError::ParsingError), + }; + + Ok(Self { bytes }) + } + + /// View the full value of the ID_CRED_x: the CBOR encoding of a 1-element CBOR map + pub fn as_full_value(&self) -> &[u8] { + self.bytes.as_slice() + } + + /// View the value as encoded in the ID_CRED_x position of plaintext_2 and plaintext_3, + /// applying the Compact Encoding of ID_CRED Fields described in RFC9528 Section 3.5.3.2 + /// Note that this does NOT encode the value as CBOR, it rather just applies the EDHOC Compact Encoding when applicable. + pub fn as_encoded_value(&self) -> &[u8] { + match self.bytes.as_slice() { + [0xa1, KID_LABEL, 0x41, x] if (x >> 5) < 2 && (x & 0x1f) < 24 => { + &self.bytes.as_slice()[3..] + } + [0xa1, KID_LABEL, ..] => &self.bytes.as_slice()[2..], + _ => self.bytes.as_slice(), + } + } + pub fn reference_only(&self) -> bool { - self.value.len == 0 + [IdCredType::KID].contains(&self.item_type()) + } + + pub fn item_type(&self) -> IdCredType { + self.bytes.as_slice()[1].into() + } + + pub fn get_ccs(&self) -> Option { + if self.item_type() == IdCredType::KCCS { + Credential::parse_ccs(&self.bytes.as_slice()[2..]).ok() + } else { + None + } + } + + fn bstr_representable_as_int(value: u8) -> bool { + (0x0..=0x17).contains(&value) || (0x20..=0x37).contains(&value) + } +} + +// TODO: add back support for C and Python bindings +// #[cfg_attr(feature = "python-bindings", pyclass)] +// #[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Credential { + /// Original bytes of the credential, CBOR-encoded + /// + /// If the credential is a CCS, it contains an encoded CBOR map containnig + /// a COSE_Key in a cnf claim, see RFC 9528 Section 3.5.2. + pub bytes: BufferCred, + pub key: CredentialKey, + pub kid: Option, // other types of identifiers can be added, such as `pub x5t: Option` + pub cred_type: CredentialType, +} + +impl Credential { + /// Creates a new CCS credential with the given bytes and public key + pub fn new_ccs(bytes: BufferCred, public_key: BytesKeyEC2) -> Self { + Self { + bytes, + key: CredentialKey::EC2Compact(public_key), + kid: None, + cred_type: CredentialType::CCS, + } + } + + /// Creates a new CCS credential with the given bytes and a pre-shared key + /// + /// This type of credential is to be used with the under-development EDHOC method PSK. + pub fn new_ccs_psk(bytes: BufferCred, symmetric_key: BytesKeyAES128) -> Self { + Self { + bytes, + key: CredentialKey::Symmetric(symmetric_key), + kid: None, + cred_type: CredentialType::CCS_PSK, + } } - pub fn get_id_cred(&self) -> BytesIdCred { - [0xa1, 0x04, 0x41, self.kid] // cbor map = {4: kid} + pub fn with_kid(self, kid: BufferKid) -> Self { + Self { + kid: Some(kid), + ..self + } } - pub fn parse(cred: &[u8]) -> Result<(BytesP256ElemLen, u8), EDHOCError> { - // NOTE: this routine is only guaranteed to work with credentials from RFC9529 + /// Parse a CCS style credential + /// + /// If the given value matches the shape lakers expects of a CCS, i.e. credentials from RFC9529, + /// its public key and key ID are extracted into a full credential. + pub fn parse_ccs(value: &[u8]) -> Result { const CCS_PREFIX_LEN: usize = 3; const CNF_AND_COSE_KEY_PREFIX_LEN: usize = 8; const COSE_KEY_FIRST_ITEMS_LEN: usize = 6; - if cred.len() + if value.len() < 3 + CCS_PREFIX_LEN + 1 + CNF_AND_COSE_KEY_PREFIX_LEN @@ -42,7 +190,7 @@ impl CredentialRPK { { Err(EDHOCError::ParsingError) } else { - let subject_len = CBORDecoder::info_of(cred[2]) as usize; + let subject_len = CBORDecoder::info_of(value[2]) as usize; let id_cred_offset: usize = CCS_PREFIX_LEN .checked_add(subject_len) @@ -55,40 +203,203 @@ impl CredentialRPK { if g_a_x_offset .checked_add(P256_ELEM_LEN) - .map_or(false, |end| end <= cred.len()) + .map_or(false, |end| end <= value.len()) { - Ok(( - cred[g_a_x_offset..g_a_x_offset + P256_ELEM_LEN] - .try_into() - .expect("Wrong key length"), - cred[id_cred_offset], - )) + let public_key: BytesKeyEC2 = value[g_a_x_offset..g_a_x_offset + P256_ELEM_LEN] + .try_into() + .expect("Wrong key length"); + let kid = value[id_cred_offset]; + + Ok(Self { + bytes: BufferCred::new_from_slice(value) + .map_err(|_| EDHOCError::ParsingError)?, + key: CredentialKey::EC2Compact(public_key), + kid: Some(BufferKid::new_from_slice(&[kid]).unwrap()), + cred_type: CredentialType::CCS, + }) } else { Err(EDHOCError::ParsingError) } } } + + /// Parse a CCS style credential, but the key is a symmetric key + /// + /// If the given value matches the shape Lakers expects of a CCS, its public key and key ID are + /// extracted into a full credential. + pub fn parse_ccs_psk(value: &[u8]) -> Result { + const CCS_PREFIX_LEN: usize = 3; + const CNF_AND_COSE_KEY_PREFIX_LEN: usize = 8; + const COSE_KEY_FIRST_ITEMS_LEN: usize = 3; //COSE for symmetric key + const SYMMETRIC_KEY_LEN: usize = 16; // Assuming a 128-bit symmetric key + + if value.len() + < CCS_PREFIX_LEN + + 1 + + CNF_AND_COSE_KEY_PREFIX_LEN + + COSE_KEY_FIRST_ITEMS_LEN + + SYMMETRIC_KEY_LEN + { + Err(EDHOCError::ParsingError) + } else { + let subject_len = CBORDecoder::info_of(value[2]) as usize; + + let id_cred_offset: usize = CCS_PREFIX_LEN + .checked_add(subject_len) + .and_then(|x| x.checked_add(CNF_AND_COSE_KEY_PREFIX_LEN)) + .ok_or(EDHOCError::ParsingError)?; + + let symmetric_key_offset: usize = id_cred_offset + .checked_add(COSE_KEY_FIRST_ITEMS_LEN) + .ok_or(EDHOCError::ParsingError)?; + + if symmetric_key_offset + .checked_add(SYMMETRIC_KEY_LEN) + .map_or(false, |end| end <= value.len()) + { + let symmetric_key: [u8; SYMMETRIC_KEY_LEN] = value + [symmetric_key_offset..symmetric_key_offset + SYMMETRIC_KEY_LEN] + .try_into() + .map_err(|_| EDHOCError::ParsingError)?; + + let kid = value[id_cred_offset]; + + Ok(Self { + bytes: BufferCred::new_from_slice(value) + .map_err(|_| EDHOCError::ParsingError)?, + key: CredentialKey::Symmetric(symmetric_key), + kid: Some(BufferKid::new_from_slice(&[kid]).unwrap()), + cred_type: CredentialType::CCS_PSK, + }) + } else { + Err(EDHOCError::ParsingError) + } + } + } + + /// Returns a COSE_Header map with a single entry representing a credential by value. + /// + /// For example, if the credential is a CCS: + /// { /kccs/ 14: bytes } + pub fn by_value(&self) -> Result { + match self.cred_type { + CredentialType::CCS => { + let mut id_cred = IdCred::new(); + id_cred + .bytes + .extend_from_slice(&[CBOR_MAJOR_MAP + 1, KCSS_LABEL]) + .map_err(|_| EDHOCError::CredentialTooLongError)?; + id_cred + .bytes + .extend_from_slice(self.bytes.as_slice()) + .unwrap(); + Ok(id_cred) + } + // if we could encode a message along the error below, + // it would be this: "Symmetric keys cannot be sent by value" + CredentialType::CCS_PSK => Err(EDHOCError::UnexpectedCredential), + } + } + + /// Returns a COSE_Header map with a single entry representing a credential by reference. + /// + /// For example, if the reference is a kid: + /// { /kid/ 4: kid } + /// + /// TODO: accept a parameter to specify the type of reference, e.g. kid, x5t, etc. + pub fn by_kid(&self) -> Result { + let Some(kid) = self.kid.as_ref() else { + return Err(EDHOCError::MissingIdentity); + }; + let mut id_cred = IdCred::new(); + id_cred + .bytes + .extend_from_slice(&[ + CBOR_MAJOR_MAP + 1, + KID_LABEL, + CBOR_MAJOR_BYTE_STRING | kid.len() as u8, + ]) + .map_err(|_| EDHOCError::CredentialTooLongError)?; + id_cred.bytes.extend_from_slice(kid.as_slice()).unwrap(); + Ok(id_cred) + } } #[cfg(test)] mod test { use super::*; use hexlit::hex; + use rstest::rstest; const CRED_TV: &[u8] = &hex!("a2026b6578616d706c652e65647508a101a501020241322001215820bbc34960526ea4d32e940cad2a234148ddc21791a12afbcbac93622046dd44f02258204519e257236b2a0ce2023f0931f1f386ca7afda64fcde0108c224c51eabf6072"); const G_A_TV: &[u8] = &hex!("BBC34960526EA4D32E940CAD2A234148DDC21791A12AFBCBAC93622046DD44F0"); - const ID_CRED_TV: &[u8] = &hex!("a1044132"); + const ID_CRED_BY_REF_TV: &[u8] = &hex!("a1044132"); + const ID_CRED_BY_VALUE_TV: &[u8] = &hex!("A10EA2026B6578616D706C652E65647508A101A501020241322001215820BBC34960526EA4D32E940CAD2A234148DDC21791A12AFBCBAC93622046DD44F02258204519E257236B2A0CE2023F0931F1F386CA7AFDA64FCDE0108C224C51EABF6072"); + const KID_VALUE_TV: &[u8] = &hex!("32"); + + const CRED_PSK: &[u8] = + &hex!("A202686D79646F74626F7408A101A30104024132205050930FF462A77A3540CF546325DEA214"); + const K: &[u8] = &hex!("50930FF462A77A3540CF546325DEA214"); + const KID_VALUE_PSK: &[u8] = &hex!("32"); + + #[test] + fn test_new_cred_ccs() { + let cred = Credential::new_ccs(CRED_TV.try_into().unwrap(), G_A_TV.try_into().unwrap()); + assert_eq!(cred.bytes.as_slice(), CRED_TV); + } + + #[test] + fn test_cred_ccs_by_value_or_reference() { + let cred = Credential::new_ccs(CRED_TV.try_into().unwrap(), G_A_TV.try_into().unwrap()) + .with_kid(KID_VALUE_TV.try_into().unwrap()); + let id_cred = cred.by_value().unwrap(); + assert_eq!(id_cred.bytes.as_slice(), ID_CRED_BY_VALUE_TV); + assert_eq!(id_cred.item_type(), IdCredType::KCCS); + let id_cred = cred.by_kid().unwrap(); + assert_eq!(id_cred.bytes.as_slice(), ID_CRED_BY_REF_TV); + assert_eq!(id_cred.item_type(), IdCredType::KID); + } #[test] - fn test_new_cred() { - let cred_tv: EdhocMessageBuffer = CRED_TV.try_into().unwrap(); - - let res = CredentialRPK::new(CRED_TV.try_into().unwrap()); - assert!(res.is_ok()); - let cred = res.unwrap(); - assert_eq!(cred.value, cred_tv); - assert_eq!(cred.public_key, G_A_TV); - assert_eq!(cred.kid, ID_CRED_TV[3]); - assert_eq!(cred.get_id_cred(), ID_CRED_TV); + fn test_cred_ccs_psk_by_value_or_reference() { + // TODO + } + + #[test] + fn test_new_cred_ccs_psk() { + let cred = Credential::new_ccs_psk(CRED_PSK.try_into().unwrap(), K.try_into().unwrap()); + assert_eq!(cred.bytes.as_slice(), CRED_PSK); + } + + #[test] + fn test_parse_ccs() { + let cred = Credential::parse_ccs(CRED_TV).unwrap(); + assert_eq!(cred.bytes.as_slice(), CRED_TV); + assert_eq!( + cred.key, + CredentialKey::EC2Compact(G_A_TV.try_into().unwrap()) + ); + assert_eq!(cred.kid.unwrap().as_slice(), KID_VALUE_TV); + assert_eq!(cred.cred_type, CredentialType::CCS); + } + + #[test] + fn test_parse_ccs_psk() { + let cred = Credential::parse_ccs_psk(CRED_PSK).unwrap(); + assert_eq!(cred.bytes.as_slice(), CRED_PSK); + assert_eq!(cred.key, CredentialKey::Symmetric(K.try_into().unwrap())); + assert_eq!(cred.kid.unwrap().as_slice(), KID_VALUE_PSK); + assert_eq!(cred.cred_type, CredentialType::CCS_PSK); + } + + #[rstest] + #[case(&[0x0D], &[0xa1, 0x04, 0x41, 0x0D])] // two optimizations: omit kid label and encode as CBOR integer + #[case(&[0x41, 0x18], &[0xa1, 0x04, 0x41, 0x18])] // one optimization: omit kid label + #[case(ID_CRED_BY_VALUE_TV, ID_CRED_BY_VALUE_TV)] // regular credential by value + fn test_id_cred_from_encoded_plaintext(#[case] input: &[u8], #[case] expected: &[u8]) { + assert_eq!( + IdCred::from_encoded_value(input).unwrap().as_full_value(), + expected + ); } } diff --git a/shared/src/cred_new.rs b/shared/src/cred_new.rs deleted file mode 100644 index b4f82956..00000000 --- a/shared/src/cred_new.rs +++ /dev/null @@ -1,405 +0,0 @@ -use super::*; - -pub type BufferCred = EdhocBuffer<192>; // arbitrary size -pub type BufferKid = EdhocBuffer<16>; // variable size, up to 16 bytes -pub type BufferIdCred = EdhocBuffer<192>; // variable size, can contain either the contents of a BufferCred or a BufferKid -pub type BytesKeyAES128 = [u8; 16]; -pub type BytesKeyEC2 = [u8; 32]; -pub type BytesKeyOKP = [u8; 32]; -pub type BytesX5T = [u8; 8]; -pub type BytesC5T = [u8; 8]; - -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum CredentialKey { - Symmetric(BytesKeyAES128), - EC2Compact(BytesKeyEC2), - // Add other key types as needed -} - -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum CredentialType { - CCS, - #[allow(non_camel_case_types)] - CCS_PSK, - // C509, -} - -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum IdCredType { - KID = 4, - KCCS = 14, -} - -impl From for IdCredType { - fn from(value: u8) -> Self { - match value { - 4 => IdCredType::KID, - 14 => IdCredType::KCCS, - _ => panic!("Invalid IdCredType"), - } - } -} - -/// A value of ID_CRED_x: a credential identifier -/// -/// Possible values include key IDs, credentials by value and others. -#[derive(Clone, Copy, Debug, Default, PartialEq)] -pub struct IdCred { - /// The value is always stored in the ID_CRED_x form as a serialized one-element dictionary; - /// while this technically wastes two bytes, it has the convenient property of having the full - /// value available as a slice. - pub bytes: BufferIdCred, // variable size, can contain either the contents of a BufferCred or a BufferKid -} - -impl IdCred { - pub fn new() -> Self { - Self { - bytes: BufferIdCred::new(), - } - } - - pub fn from_full_value(value: &[u8]) -> Result { - Ok(Self { - bytes: BufferIdCred::new_from_slice(value) - .map_err(|_| EDHOCError::CredentialTooLongError)?, - }) - } - - /// Instantiate an IdCred from an encoded value. - pub fn from_encoded_value(value: &[u8]) -> Result { - let bytes = match value { - // kid that has been encoded as CBOR integer - &[x] if Self::bstr_representable_as_int(x) => { - BufferIdCred::new_from_slice(&[0xa1, KID_LABEL, 0x41, x]) - .map_err(|_| EDHOCError::CredentialTooLongError)? // TODO: how to avoid map_err overuse? - } - // kid that has been encoded as CBOR byte string - &[0x41, x, ..] if !Self::bstr_representable_as_int(x) => { - let mut bytes = BufferIdCred::new_from_slice(&[0xa1, KID_LABEL]) - .map_err(|_| EDHOCError::CredentialTooLongError)?; - bytes - .extend_from_slice(value) - .map_err(|_| EDHOCError::CredentialTooLongError)?; - bytes - } - // CCS by value - &[0xa1, KCSS_LABEL, ..] => BufferIdCred::new_from_slice(value) - .map_err(|_| EDHOCError::CredentialTooLongError)?, - _ => return Err(EDHOCError::ParsingError), - }; - - Ok(Self { bytes }) - } - - /// View the full value of the ID_CRED_x: the CBOR encoding of a 1-element CBOR map - pub fn as_full_value(&self) -> &[u8] { - self.bytes.as_slice() - } - - /// View the value as encoded in the ID_CRED_x position of plaintext_2 and plaintext_3, - /// applying the Compact Encoding of ID_CRED Fields described in RFC9528 Section 3.5.3.2 - /// Note that this does NOT encode the value as CBOR, it rather just applies the EDHOC Compact Encoding when applicable. - pub fn as_encoded_value(&self) -> &[u8] { - match self.bytes.as_slice() { - [0xa1, KID_LABEL, 0x41, x] if (x >> 5) < 2 && (x & 0x1f) < 24 => { - &self.bytes.as_slice()[3..] - } - [0xa1, KID_LABEL, ..] => &self.bytes.as_slice()[2..], - _ => self.bytes.as_slice(), - } - } - - pub fn reference_only(&self) -> bool { - [IdCredType::KID].contains(&self.item_type()) - } - - pub fn item_type(&self) -> IdCredType { - self.bytes.as_slice()[1].into() - } - - pub fn get_ccs(&self) -> Option { - if self.item_type() == IdCredType::KCCS { - Credential::parse_ccs(&self.bytes.as_slice()[2..]).ok() - } else { - None - } - } - - fn bstr_representable_as_int(value: u8) -> bool { - (0x0..=0x17).contains(&value) || (0x20..=0x37).contains(&value) - } -} - -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct Credential { - /// Original bytes of the credential, CBOR-encoded - /// - /// If the credential is a CCS, it contains an encoded CBOR map containnig - /// a COSE_Key in a cnf claim, see RFC 9528 Section 3.5.2. - pub bytes: BufferCred, - pub key: CredentialKey, - pub kid: Option, // other types of identifiers can be added, such as `pub x5t: Option` - pub cred_type: CredentialType, -} - -impl Credential { - /// Creates a new CCS credential with the given bytes and public key - pub fn new_ccs(bytes: BufferCred, public_key: BytesKeyEC2) -> Self { - Self { - bytes, - key: CredentialKey::EC2Compact(public_key), - kid: None, - cred_type: CredentialType::CCS, - } - } - - /// Creates a new CCS credential with the given bytes and a pre-shared key - /// - /// This type of credential is to be used with the under-development EDHOC method PSK. - pub fn new_ccs_psk(bytes: BufferCred, symmetric_key: BytesKeyAES128) -> Self { - Self { - bytes, - key: CredentialKey::Symmetric(symmetric_key), - kid: None, - cred_type: CredentialType::CCS_PSK, - } - } - - pub fn with_kid(self, kid: BufferKid) -> Self { - Self { - kid: Some(kid), - ..self - } - } - - /// Parse a CCS style credential - /// - /// If the given value matches the shape lakers expects of a CCS, i.e. credentials from RFC9529, - /// its public key and key ID are extracted into a full credential. - pub fn parse_ccs(value: &[u8]) -> Result { - const CCS_PREFIX_LEN: usize = 3; - const CNF_AND_COSE_KEY_PREFIX_LEN: usize = 8; - const COSE_KEY_FIRST_ITEMS_LEN: usize = 6; - - if value.len() - < 3 + CCS_PREFIX_LEN - + 1 - + CNF_AND_COSE_KEY_PREFIX_LEN - + COSE_KEY_FIRST_ITEMS_LEN - + P256_ELEM_LEN - { - Err(EDHOCError::ParsingError) - } else { - let subject_len = CBORDecoder::info_of(value[2]) as usize; - - let id_cred_offset: usize = CCS_PREFIX_LEN - .checked_add(subject_len) - .and_then(|x| x.checked_add(CNF_AND_COSE_KEY_PREFIX_LEN)) - .ok_or(EDHOCError::ParsingError)?; - - let g_a_x_offset: usize = id_cred_offset - .checked_add(COSE_KEY_FIRST_ITEMS_LEN) - .ok_or(EDHOCError::ParsingError)?; - - if g_a_x_offset - .checked_add(P256_ELEM_LEN) - .map_or(false, |end| end <= value.len()) - { - let public_key: BytesKeyEC2 = value[g_a_x_offset..g_a_x_offset + P256_ELEM_LEN] - .try_into() - .expect("Wrong key length"); - let kid = value[id_cred_offset]; - - Ok(Self { - bytes: BufferCred::new_from_slice(value) - .map_err(|_| EDHOCError::ParsingError)?, - key: CredentialKey::EC2Compact(public_key), - kid: Some(BufferKid::new_from_slice(&[kid]).unwrap()), - cred_type: CredentialType::CCS, - }) - } else { - Err(EDHOCError::ParsingError) - } - } - } - - /// Parse a CCS style credential, but the key is a symmetric key - /// - /// If the given value matches the shape Lakers expects of a CCS, its public key and key ID are - /// extracted into a full credential. - pub fn parse_ccs_psk(value: &[u8]) -> Result { - const CCS_PREFIX_LEN: usize = 3; - const CNF_AND_COSE_KEY_PREFIX_LEN: usize = 8; - const COSE_KEY_FIRST_ITEMS_LEN: usize = 3; //COSE for symmetric key - const SYMMETRIC_KEY_LEN: usize = 16; // Assuming a 128-bit symmetric key - - if value.len() - < CCS_PREFIX_LEN - + 1 - + CNF_AND_COSE_KEY_PREFIX_LEN - + COSE_KEY_FIRST_ITEMS_LEN - + SYMMETRIC_KEY_LEN - { - Err(EDHOCError::ParsingError) - } else { - let subject_len = CBORDecoder::info_of(value[2]) as usize; - - let id_cred_offset: usize = CCS_PREFIX_LEN - .checked_add(subject_len) - .and_then(|x| x.checked_add(CNF_AND_COSE_KEY_PREFIX_LEN)) - .ok_or(EDHOCError::ParsingError)?; - - let symmetric_key_offset: usize = id_cred_offset - .checked_add(COSE_KEY_FIRST_ITEMS_LEN) - .ok_or(EDHOCError::ParsingError)?; - - if symmetric_key_offset - .checked_add(SYMMETRIC_KEY_LEN) - .map_or(false, |end| end <= value.len()) - { - let symmetric_key: [u8; SYMMETRIC_KEY_LEN] = value - [symmetric_key_offset..symmetric_key_offset + SYMMETRIC_KEY_LEN] - .try_into() - .map_err(|_| EDHOCError::ParsingError)?; - - let kid = value[id_cred_offset]; - - Ok(Self { - bytes: BufferCred::new_from_slice(value) - .map_err(|_| EDHOCError::ParsingError)?, - key: CredentialKey::Symmetric(symmetric_key), - kid: Some(BufferKid::new_from_slice(&[kid]).unwrap()), - cred_type: CredentialType::CCS_PSK, - }) - } else { - Err(EDHOCError::ParsingError) - } - } - } - - /// Returns a COSE_Header map with a single entry representing a credential by value. - /// - /// For example, if the credential is a CCS: - /// { /kccs/ 14: bytes } - pub fn by_value(&self) -> Result { - match self.cred_type { - CredentialType::CCS => { - let mut id_cred = IdCred::new(); - id_cred - .bytes - .extend_from_slice(&[CBOR_MAJOR_MAP + 1, KCSS_LABEL]) - .map_err(|_| EDHOCError::CredentialTooLongError)?; - id_cred - .bytes - .extend_from_slice(self.bytes.as_slice()) - .unwrap(); - Ok(id_cred) - } - // if we could encode a message along the error below, - // it would be this: "Symmetric keys cannot be sent by value" - CredentialType::CCS_PSK => Err(EDHOCError::UnexpectedCredential), - } - } - - /// Returns a COSE_Header map with a single entry representing a credential by reference. - /// - /// For example, if the reference is a kid: - /// { /kid/ 4: kid } - /// - /// TODO: accept a parameter to specify the type of reference, e.g. kid, x5t, etc. - pub fn by_kid(&self) -> Result { - let Some(kid) = self.kid.as_ref() else { - return Err(EDHOCError::MissingIdentity); - }; - let mut id_cred = IdCred::new(); - id_cred - .bytes - .extend_from_slice(&[ - CBOR_MAJOR_MAP + 1, - KID_LABEL, - CBOR_MAJOR_BYTE_STRING | kid.len() as u8, - ]) - .map_err(|_| EDHOCError::CredentialTooLongError)?; - id_cred.bytes.extend_from_slice(kid.as_slice()).unwrap(); - Ok(id_cred) - } -} - -#[cfg(test)] -mod test { - use super::*; - use hexlit::hex; - use rstest::rstest; - - const CRED_TV: &[u8] = &hex!("a2026b6578616d706c652e65647508a101a501020241322001215820bbc34960526ea4d32e940cad2a234148ddc21791a12afbcbac93622046dd44f02258204519e257236b2a0ce2023f0931f1f386ca7afda64fcde0108c224c51eabf6072"); - const G_A_TV: &[u8] = &hex!("BBC34960526EA4D32E940CAD2A234148DDC21791A12AFBCBAC93622046DD44F0"); - const ID_CRED_BY_REF_TV: &[u8] = &hex!("a1044132"); - const ID_CRED_BY_VALUE_TV: &[u8] = &hex!("A10EA2026B6578616D706C652E65647508A101A501020241322001215820BBC34960526EA4D32E940CAD2A234148DDC21791A12AFBCBAC93622046DD44F02258204519E257236B2A0CE2023F0931F1F386CA7AFDA64FCDE0108C224C51EABF6072"); - const KID_VALUE_TV: &[u8] = &hex!("32"); - - const CRED_PSK: &[u8] = - &hex!("A202686D79646F74626F7408A101A30104024132205050930FF462A77A3540CF546325DEA214"); - const K: &[u8] = &hex!("50930FF462A77A3540CF546325DEA214"); - const KID_VALUE_PSK: &[u8] = &hex!("32"); - - #[test] - fn test_new_cred_ccs() { - let cred = Credential::new_ccs(CRED_TV.try_into().unwrap(), G_A_TV.try_into().unwrap()); - assert_eq!(cred.bytes.as_slice(), CRED_TV); - } - - #[test] - fn test_cred_ccs_by_value_or_reference() { - let cred = Credential::new_ccs(CRED_TV.try_into().unwrap(), G_A_TV.try_into().unwrap()) - .with_kid(KID_VALUE_TV.try_into().unwrap()); - let id_cred = cred.by_value().unwrap(); - assert_eq!(id_cred.bytes.as_slice(), ID_CRED_BY_VALUE_TV); - assert_eq!(id_cred.item_type(), IdCredType::KCCS); - let id_cred = cred.by_kid().unwrap(); - assert_eq!(id_cred.bytes.as_slice(), ID_CRED_BY_REF_TV); - assert_eq!(id_cred.item_type(), IdCredType::KID); - } - - #[test] - fn test_cred_ccs_psk_by_value_or_reference() { - // TODO - } - - #[test] - fn test_new_cred_ccs_psk() { - let cred = Credential::new_ccs_psk(CRED_PSK.try_into().unwrap(), K.try_into().unwrap()); - assert_eq!(cred.bytes.as_slice(), CRED_PSK); - } - - #[test] - fn test_parse_ccs() { - let cred = Credential::parse_ccs(CRED_TV).unwrap(); - assert_eq!(cred.bytes.as_slice(), CRED_TV); - assert_eq!( - cred.key, - CredentialKey::EC2Compact(G_A_TV.try_into().unwrap()) - ); - assert_eq!(cred.kid.unwrap().as_slice(), KID_VALUE_TV); - assert_eq!(cred.cred_type, CredentialType::CCS); - } - - #[test] - fn test_parse_ccs_psk() { - let cred = Credential::parse_ccs_psk(CRED_PSK).unwrap(); - assert_eq!(cred.bytes.as_slice(), CRED_PSK); - assert_eq!(cred.key, CredentialKey::Symmetric(K.try_into().unwrap())); - assert_eq!(cred.kid.unwrap().as_slice(), KID_VALUE_PSK); - assert_eq!(cred.cred_type, CredentialType::CCS_PSK); - } - - #[rstest] - #[case(&[0x0D], &[0xa1, 0x04, 0x41, 0x0D])] // two optimizations: omit kid label and encode as CBOR integer - #[case(&[0x41, 0x18], &[0xa1, 0x04, 0x41, 0x18])] // one optimization: omit kid label - #[case(ID_CRED_BY_VALUE_TV, ID_CRED_BY_VALUE_TV)] // regular credential by value - fn test_id_cred_from_encoded_plaintext(#[case] input: &[u8], #[case] expected: &[u8]) { - assert_eq!( - IdCred::from_encoded_value(input).unwrap().as_full_value(), - expected - ); - } -} diff --git a/shared/src/lib.rs b/shared/src/lib.rs index 223a55f9..3f0e76c5 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -27,9 +27,6 @@ pub use cred::*; mod buffer; pub use buffer::*; -mod cred_new; -pub use cred_new::*; - #[cfg(feature = "python-bindings")] use pyo3::prelude::*; #[cfg(feature = "python-bindings")] diff --git a/shared/src/python_bindings.rs b/shared/src/python_bindings.rs index f5a9dd8a..03774cb4 100644 --- a/shared/src/python_bindings.rs +++ b/shared/src/python_bindings.rs @@ -58,6 +58,7 @@ impl EADItem { } } +// FIXME: adjust for new Credential struct #[pymethods] impl CredentialRPK { /// Construct a new CredentialRPK From 03f7d435c9bec08f8a2564d2fdd5b1cd06f5e778 Mon Sep 17 00:00:00 2001 From: Geovane Fedrecheski Date: Fri, 5 Jul 2024 11:25:03 +0200 Subject: [PATCH 15/16] creds: improve id_cred encoding docs --- shared/src/cred.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/shared/src/cred.rs b/shared/src/cred.rs index 48f56255..8cf07a05 100644 --- a/shared/src/cred.rs +++ b/shared/src/cred.rs @@ -89,13 +89,17 @@ impl IdCred { } /// View the full value of the ID_CRED_x: the CBOR encoding of a 1-element CBOR map + /// + /// This is the value that is used when ID_CRED_x has no impact on message size, see RFC 9528 Section 3.5.3.2. pub fn as_full_value(&self) -> &[u8] { self.bytes.as_slice() } - /// View the value as encoded in the ID_CRED_x position of plaintext_2 and plaintext_3, - /// applying the Compact Encoding of ID_CRED Fields described in RFC9528 Section 3.5.3.2 - /// Note that this does NOT encode the value as CBOR, it rather just applies the EDHOC Compact Encoding when applicable. + /// View the value as encoded in the ID_CRED_x position of plaintext_2 and plaintext_3. + /// + /// Note that this is NOT doing CBOR encoding, it is rather performing (when applicable) + /// the compact encoding of ID_CRED fields. + /// This style of encoding is used when ID_CRED_x has an impact on message size. pub fn as_encoded_value(&self) -> &[u8] { match self.bytes.as_slice() { [0xa1, KID_LABEL, 0x41, x] if (x >> 5) < 2 && (x & 0x1f) < 24 => { From 71e9f6a25ae47880a385fcd6244fa81068bb23b8 Mon Sep 17 00:00:00 2001 From: Geovane Fedrecheski Date: Fri, 5 Jul 2024 15:10:29 +0200 Subject: [PATCH 16/16] creds: annotate that psk/symmetric code is experimental --- shared/src/cred.rs | 71 ++++++++++++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 28 deletions(-) diff --git a/shared/src/cred.rs b/shared/src/cred.rs index 8cf07a05..539481ea 100644 --- a/shared/src/cred.rs +++ b/shared/src/cred.rs @@ -18,7 +18,7 @@ pub enum CredentialType { CCS, #[allow(non_camel_case_types)] CCS_PSK, - // C509, + // Add other credential types as needed } #[derive(Clone, Copy, Debug, PartialEq)] @@ -131,6 +131,10 @@ impl IdCred { } } +/// A credential for use in EDHOC +/// +/// For now supports CCS credentials only. +/// Experimental support for CCS_PSK credentials is also available. // TODO: add back support for C and Python bindings // #[cfg_attr(feature = "python-bindings", pyclass)] // #[repr(C)] @@ -159,8 +163,8 @@ impl Credential { /// Creates a new CCS credential with the given bytes and a pre-shared key /// - /// This type of credential is to be used with the under-development EDHOC method PSK. - pub fn new_ccs_psk(bytes: BufferCred, symmetric_key: BytesKeyAES128) -> Self { + /// NOTE: For now this is only useful for the experimental PSK method. + pub fn new_ccs_symmetric(bytes: BufferCred, symmetric_key: BytesKeyAES128) -> Self { Self { bytes, key: CredentialKey::Symmetric(symmetric_key), @@ -176,7 +180,7 @@ impl Credential { } } - /// Parse a CCS style credential + /// Parse a CCS style credential. /// /// If the given value matches the shape lakers expects of a CCS, i.e. credentials from RFC9529, /// its public key and key ID are extracted into a full credential. @@ -227,11 +231,10 @@ impl Credential { } } - /// Parse a CCS style credential, but the key is a symmetric key + /// Parse a CCS style credential, but the key is a symmetric key. /// - /// If the given value matches the shape Lakers expects of a CCS, its public key and key ID are - /// extracted into a full credential. - pub fn parse_ccs_psk(value: &[u8]) -> Result { + /// NOTE: For now this is only useful for the experimental PSK method. + pub fn parse_ccs_symmetric(value: &[u8]) -> Result { const CCS_PREFIX_LEN: usize = 3; const CNF_AND_COSE_KEY_PREFIX_LEN: usize = 8; const COSE_KEY_FIRST_ITEMS_LEN: usize = 3; //COSE for symmetric key @@ -364,17 +367,6 @@ mod test { assert_eq!(id_cred.item_type(), IdCredType::KID); } - #[test] - fn test_cred_ccs_psk_by_value_or_reference() { - // TODO - } - - #[test] - fn test_new_cred_ccs_psk() { - let cred = Credential::new_ccs_psk(CRED_PSK.try_into().unwrap(), K.try_into().unwrap()); - assert_eq!(cred.bytes.as_slice(), CRED_PSK); - } - #[test] fn test_parse_ccs() { let cred = Credential::parse_ccs(CRED_TV).unwrap(); @@ -387,15 +379,6 @@ mod test { assert_eq!(cred.cred_type, CredentialType::CCS); } - #[test] - fn test_parse_ccs_psk() { - let cred = Credential::parse_ccs_psk(CRED_PSK).unwrap(); - assert_eq!(cred.bytes.as_slice(), CRED_PSK); - assert_eq!(cred.key, CredentialKey::Symmetric(K.try_into().unwrap())); - assert_eq!(cred.kid.unwrap().as_slice(), KID_VALUE_PSK); - assert_eq!(cred.cred_type, CredentialType::CCS_PSK); - } - #[rstest] #[case(&[0x0D], &[0xa1, 0x04, 0x41, 0x0D])] // two optimizations: omit kid label and encode as CBOR integer #[case(&[0x41, 0x18], &[0xa1, 0x04, 0x41, 0x18])] // one optimization: omit kid label @@ -407,3 +390,35 @@ mod test { ); } } + +#[cfg(test)] +mod test_experimental { + use super::*; + use hexlit::hex; + + const CRED_PSK: &[u8] = + &hex!("A202686D79646F74626F7408A101A30104024132205050930FF462A77A3540CF546325DEA214"); + const K: &[u8] = &hex!("50930FF462A77A3540CF546325DEA214"); + const KID_VALUE_PSK: &[u8] = &hex!("32"); + + #[test] + fn test_cred_ccs_symmetric_by_value_or_reference() { + // TODO + } + + #[test] + fn test_new_cred_ccs_symmetric() { + let cred = + Credential::new_ccs_symmetric(CRED_PSK.try_into().unwrap(), K.try_into().unwrap()); + assert_eq!(cred.bytes.as_slice(), CRED_PSK); + } + + #[test] + fn test_parse_ccs_symmetric() { + let cred = Credential::parse_ccs_symmetric(CRED_PSK).unwrap(); + assert_eq!(cred.bytes.as_slice(), CRED_PSK); + assert_eq!(cred.key, CredentialKey::Symmetric(K.try_into().unwrap())); + assert_eq!(cred.kid.unwrap().as_slice(), KID_VALUE_PSK); + assert_eq!(cred.cred_type, CredentialType::CCS_PSK); + } +}