Skip to content

Commit daba014

Browse files
authored
Merge pull request #36 from veraison/draft-ffm-rats-cca-token-00
feat: decode and verify draft-ffm-rats-cca-token-00 formatted tokens
2 parents 14f08ef + 53dc640 commit daba014

File tree

8 files changed

+137
-23
lines changed

8 files changed

+137
-23
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ readme = "README.md"
1313

1414
[dependencies]
1515
base64 = "0.21.5"
16-
ciborium = "0.2.0"
16+
ciborium = "0.2.2"
1717
multimap = "0.9.0"
1818
serde = { version = "1.0", features = ["derive"] }
1919
serde_json = { version = "1.0", features = ["raw_value"] }

deny.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,13 @@ allow = [
108108
"Apache-2.0",
109109
"ISC",
110110
"OpenSSL",
111+
# Superseded by Unicode-3.0
112+
# See https://opensource.org/license/unicode-inc-license-agreement-data-files-and-software
111113
"Unicode-DFS-2016",
112114
#"Apache-2.0 WITH LLVM-exception",
113115
# Considered Copyleft, but permitted in this project
114116
"MPL-2.0",
117+
"Unicode-3.0",
115118
]
116119
# List of explicitly disallowed licenses
117120
# See https://spdx.org/licenses/ for list of possible licenses

src/token/base64.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ impl<'de> Deserialize<'de> for Bytes {
7575

7676
struct BytesVisitor;
7777

78+
#[allow(clippy::needless_lifetimes)]
7879
impl<'de> Visitor<'de> for BytesVisitor {
7980
type Value = Bytes;
8081

src/token/errors.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ pub enum Error {
1616
#[error("Claim type mismatch: {0}")]
1717
TypeMismatch(String),
1818
#[error("Missing Platform Token: {0}")]
19+
UnknownProfile(String),
20+
#[error("Unknown profile: {0}")]
1921
MissingPlatformToken(String),
2022
#[error("Missing Realm Token: {0}")]
2123
MissingRealmToken(String),
@@ -50,6 +52,7 @@ impl std::fmt::Debug for Error {
5052
| Error::MissingClaim(e)
5153
| Error::DuplicatedClaim(e)
5254
| Error::TypeMismatch(e)
55+
| Error::UnknownProfile(e)
5356
| Error::MissingPlatformToken(e)
5457
| Error::MissingRealmToken(e)
5558
| Error::NotFoundTA(e)

src/token/evidence.rs

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use super::common::*;
66
use super::errors::Error;
77
use super::platform::Platform;
88
use super::realm::Realm;
9+
use super::realm::REALM_PROFILE;
910
use crate::store::PlatformRefValue;
1011
use crate::store::RealmRefValue;
1112
use crate::store::{Cpak, IRefValueStore, ITrustAnchorStore};
@@ -361,15 +362,29 @@ impl Evidence {
361362
pub fn verify_realm_token(&mut self) -> Result<(), Error> {
362363
let realm_pub_key = self.realm_claims.get_realm_key()?;
363364

364-
// re-format RAK into a COSE_Key
365-
let mut cose_key = self
366-
.ecdsa_public_key_from_raw(&realm_pub_key)
367-
.map_err(|e| {
368-
// a failure to reformat should happen only if the rak claim is malformed
365+
let mut cose_key: CoseKey;
366+
367+
if self.realm_claims.profile == REALM_PROFILE {
368+
// it is already a COSE_Key, it just need decoding
369+
cose_key = CoseKey::new();
370+
cose_key.bytes = realm_pub_key;
371+
cose_key.decode().map_err(|e| {
372+
// a failure to decode should happen only if the rak claim is malformed
369373
self.realm_tvec.set_all(UNEXPECTED_EVIDENCE);
370374

371-
Error::Syntax(format!("formatting the rak claim into ECDSA failed: {e:?}"))
375+
Error::Syntax(format!("decoding the rak claim as COSE_Key failed: {e:?}"))
372376
})?;
377+
} else {
378+
// re-format RAK into a COSE_Key
379+
cose_key = self
380+
.ecdsa_public_key_from_raw(&realm_pub_key)
381+
.map_err(|e| {
382+
// a failure to reformat should happen only if the rak claim is malformed
383+
self.realm_tvec.set_all(UNEXPECTED_EVIDENCE);
384+
385+
Error::Syntax(format!("formatting the rak claim into ECDSA failed: {e:?}"))
386+
})?;
387+
}
373388

374389
// explicitly set key-ops to verify
375390
cose_key.key_ops(vec![cose::keys::KEY_OPS_VERIFY]);
@@ -607,6 +622,8 @@ mod tests {
607622
const TEST_CCA_TOKEN_1_OK: &[u8; 1222] = include_bytes!("../../testdata/cca-token-01.cbor");
608623
const TEST_CCA_TOKEN_2_OK: &[u8; 1125] = include_bytes!("../../testdata/cca-token-02.cbor");
609624
const TEST_CCA_TOKEN_BUG_33: &[u8; 2507] = include_bytes!("../../testdata/bug-33-repro.cbor");
625+
const TEST_CCA_TOKEN_DRAFT_FFM_00: &[u8; 2124] =
626+
include_bytes!("../../testdata/cca-token-draft-ffm-00.cbor");
610627
const TEST_CCA_RVS_OK: &str = include_str!("../../testdata/rv.json");
611628
const TEST_TA_2_OK: &str = include_str!("../../testdata/ta-02-ok.json");
612629
const TEST_TA_2_BAD: &str = include_str!("../../testdata/ta-02-bad.json");
@@ -652,7 +669,7 @@ mod tests {
652669
// - non-matching personalisation value
653670

654671
#[test]
655-
fn verify_token_ok() {
672+
fn verify_legacy_token_ok() {
656673
let mut evidence =
657674
Evidence::decode(&TEST_CCA_TOKEN_2_OK.to_vec()).expect("decoding TEST_CCA_TOKEN_2_OK");
658675

@@ -674,7 +691,7 @@ mod tests {
674691
}
675692

676693
#[test]
677-
fn verify_token_error() {
694+
fn verify_legacy_token_error() {
678695
let mut evidence =
679696
Evidence::decode(&TEST_CCA_TOKEN_2_OK.to_vec()).expect("decoding TEST_CCA_TOKEN_2_OK");
680697

@@ -717,4 +734,29 @@ mod tests {
717734
assert!(evidence.realm_tvec.instance_identity.get() == CRYPTO_VALIDATION_FAILED);
718735
assert!(evidence.platform_tvec.instance_identity.get() == TRUSTWORTHY_INSTANCE);
719736
}
737+
738+
#[test]
739+
fn verify_draft_ffm_00_token_ok() {
740+
let mut evidence = Evidence::decode(&TEST_CCA_TOKEN_DRAFT_FFM_00.to_vec())
741+
.expect("decoding TEST_CCA_TOKEN_DRAFT_FFM_00");
742+
743+
let mut tas = MemoTrustAnchorStore::new();
744+
tas.load_json(TEST_TA_TFA).expect("loading trust anchors");
745+
746+
let r = evidence.verify(&tas);
747+
748+
assert!(r.is_ok());
749+
750+
assert!(evidence.realm_tvec.instance_identity.get() == TRUSTWORTHY_INSTANCE);
751+
assert!(evidence.platform_tvec.instance_identity.get() == TRUSTWORTHY_INSTANCE);
752+
753+
println!(
754+
"platform trust vector: {}",
755+
serde_json::to_string_pretty(&evidence.platform_tvec).unwrap()
756+
);
757+
println!(
758+
"realm trust vector: {}",
759+
serde_json::to_string_pretty(&evidence.realm_tvec).unwrap()
760+
);
761+
}
720762
}

src/token/platform.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,8 @@ impl SwComponent {
166166
}
167167
}
168168

169-
const PLATFORM_PROFILE: &str = "http://arm.com/CCA-SSD/1.0.0";
169+
const PLATFORM_PROFILE_LEGACY: &str = "http://arm.com/CCA-SSD/1.0.0";
170+
const PLATFORM_PROFILE: &str = "tag:arm.com,2023:cca_platform#1.0.0";
170171

171172
const PLATFORM_PROFILE_LABEL: i128 = 265;
172173
const PLATFORM_CHALLENGE_LABEL: i128 = 10;
@@ -303,8 +304,12 @@ impl Platform {
303304

304305
let p = to_tstr(v, "profile")?;
305306

306-
if p != PLATFORM_PROFILE {
307-
return Err(Error::Sema(format!("unknown profile {p}")));
307+
// There is not material difference between the two profiles regarding
308+
// the platform claims-set arrangement.
309+
// However, if p == PLATFORM_PROFILE, then the realm token shall also
310+
// have a profile claim.
311+
if p != PLATFORM_PROFILE && p != PLATFORM_PROFILE_LEGACY {
312+
return Err(Error::UnknownProfile(p.to_string()));
308313
}
309314

310315
self.profile = p;

src/token/realm.rs

Lines changed: 71 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ use bitmask::*;
77
use ciborium::de::from_reader;
88
use ciborium::Value;
99

10+
pub const REALM_PROFILE: &str = "tag:arm.com,2023:realm#1.0.0";
11+
1012
const REALM_CHALLENGE_LABEL: i128 = 10;
13+
const REALM_PROFILE_LABEL: i128 = 265;
1114
const REALM_PERSO_LABEL: i128 = 44235;
1215
const REALM_RIM_LABEL: i128 = 44238;
1316
const REALM_REM_LABEL: i128 = 44239;
@@ -25,6 +28,7 @@ bitmask! {
2528
HashAlg = 0x10,
2629
Rak = 0x20,
2730
RakHashAlg = 0x40,
31+
Profile = 0x80,
2832
}
2933
}
3034

@@ -33,11 +37,13 @@ bitmask! {
3337
#[derive(Debug)]
3438
pub struct Realm {
3539
pub challenge: [u8; 64], // 10 => bytes .size 64
40+
pub profile: String, // 265 => text
3641
pub perso: [u8; 64], // 44235 => bytes .size 64
3742
pub rim: Vec<u8>, // 44238 => bytes .size {32,48,64}
3843
pub rem: [Vec<u8>; 4], // 44239 => [ 4*4 bytes .size {32,48,64} ]
3944
pub hash_alg: String, // 44236 => text
40-
pub rak: [u8; 97], // 44237 => bytes .size 97
45+
pub raw_rak: [u8; 97], // 44237 => bytes .size 97 (profile=="")
46+
pub cose_rak: Vec<u8>, // 44237 => bytes .cbor COSE_Key (profile==REALM_PROFILE)
4147
pub rak_hash_alg: String, // 44240 => text
4248

4349
claims_set: ClaimsSet,
@@ -53,11 +59,13 @@ impl Realm {
5359
pub fn new() -> Self {
5460
Self {
5561
challenge: [0; 64],
62+
profile: String::from(""),
5663
perso: [0; 64],
5764
rim: vec![0, 64],
5865
rem: Default::default(),
5966
hash_alg: String::from(""),
60-
rak: [0; 97],
67+
raw_rak: [0; 97],
68+
cose_rak: Default::default(),
6169
rak_hash_alg: String::from(""),
6270
claims_set: ClaimsSet::none(),
6371
}
@@ -81,6 +89,17 @@ impl Realm {
8189
}
8290

8391
fn parse(&mut self, contents: Vec<(Value, Value)>) -> Result<(), Error> {
92+
// Extract the profile claim first. This is because the RAK claim
93+
// (44237) has different encodings depending on the profile value.
94+
for (k, v) in contents.iter() {
95+
if let Value::Integer(i) = k {
96+
match (*i).into() {
97+
REALM_PROFILE_LABEL => self.set_profile(v)?,
98+
_ => continue,
99+
}
100+
}
101+
}
102+
84103
for (k, v) in contents.iter() {
85104
if let Value::Integer(i) = k {
86105
match (*i).into() {
@@ -98,6 +117,7 @@ impl Realm {
98117
continue;
99118
}
100119
}
120+
101121
Ok(())
102122
}
103123

@@ -119,11 +139,38 @@ impl Realm {
119139
}
120140
}
121141

142+
// RAK format depends on the token profile
143+
if self.profile == REALM_PROFILE {
144+
if self.cose_rak.is_empty() {
145+
return Err(Error::Sema("RAK COSE_Key not set".to_string()));
146+
}
147+
} else if self.raw_rak == [0; 97] {
148+
return Err(Error::Sema("RAK raw public key not set".to_string()));
149+
}
150+
122151
// TODO: hash-type'd measurements are compatible with hash-alg
123152

124153
Ok(())
125154
}
126155

156+
fn set_profile(&mut self, v: &Value) -> Result<(), Error> {
157+
if self.claims_set.contains(Claims::Profile) {
158+
return Err(Error::DuplicatedClaim("profile".to_string()));
159+
}
160+
161+
let p = to_tstr(v, "profile")?;
162+
163+
if p != REALM_PROFILE {
164+
return Err(Error::UnknownProfile(p.to_string()));
165+
}
166+
167+
self.profile = p;
168+
169+
self.claims_set.set(Claims::Profile);
170+
171+
Ok(())
172+
}
173+
127174
fn set_challenge(&mut self, v: &Value) -> Result<(), Error> {
128175
if self.claims_set.contains(Claims::Challenge) {
129176
return Err(Error::DuplicatedClaim("challenge".to_string()));
@@ -203,15 +250,19 @@ impl Realm {
203250
let x = v.as_bytes().unwrap().clone();
204251
let x_len = x.len();
205252

206-
if x_len != 97 {
207-
return Err(Error::Sema(format!(
208-
"public-key: expecting 97 bytes, got {}",
209-
x_len
210-
)));
253+
// Backwards compatibility with the previous "raw key" format.
254+
if self.profile.is_empty() {
255+
if x_len != 97 {
256+
return Err(Error::Sema(format!(
257+
"public-key: expecting 97 bytes, got {}",
258+
x_len
259+
)));
260+
}
261+
self.raw_rak[..].clone_from_slice(&x);
262+
} else {
263+
self.cose_rak = x
211264
}
212265

213-
self.rak[..].clone_from_slice(&x);
214-
215266
self.claims_set.set(Claims::Rak);
216267

217268
Ok(())
@@ -279,19 +330,28 @@ impl Realm {
279330

280331
Ok(())
281332
}
282-
pub fn get_realm_key(&self) -> Result<[u8; 97], Error> {
283-
let rak = self.rak;
333+
334+
pub fn get_realm_key(&self) -> Result<Vec<u8>, Error> {
335+
let rak = if self.profile == REALM_PROFILE {
336+
self.cose_rak.clone()
337+
} else {
338+
self.raw_rak.clone().to_vec()
339+
};
340+
284341
if rak.is_empty() {
285342
return Err(Error::MissingClaim("No realm Key".to_string()));
286343
}
344+
287345
Ok(rak)
288346
}
289347

290348
pub fn get_rak_hash_alg(&self) -> Result<String, Error> {
291349
let rak_hash_alg = self.rak_hash_alg.clone();
350+
292351
if rak_hash_alg.is_empty() {
293352
return Err(Error::MissingClaim("No realm hash alg".to_string()));
294353
}
354+
295355
Ok(rak_hash_alg)
296356
}
297357
}
2.07 KB
Binary file not shown.

0 commit comments

Comments
 (0)