// Copyright 2021 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////////// //! COSE_Key functionality. use crate::{ cbor::value::Value, common::{AsCborValue, CborOrdering}, iana, iana::EnumI64, util::{to_cbor_array, ValueTryAs}, Algorithm, CoseError, Label, Result, }; use alloc::{collections::BTreeSet, vec, vec::Vec}; #[cfg(test)] mod tests; /// Key type. pub type KeyType = crate::RegisteredLabel; impl Default for KeyType { fn default() -> Self { KeyType::Assigned(iana::KeyType::Reserved) } } /// Key operation. pub type KeyOperation = crate::RegisteredLabel; /// A collection of [`CoseKey`] objects. #[derive(Clone, Debug, Default, PartialEq)] pub struct CoseKeySet(pub Vec); impl crate::CborSerializable for CoseKeySet {} impl AsCborValue for CoseKeySet { fn from_cbor_value(value: Value) -> Result { Ok(Self( value.try_as_array_then_convert(CoseKey::from_cbor_value)?, )) } fn to_cbor_value(self) -> Result { to_cbor_array(self.0) } } /// Structure representing a cryptographic key. /// /// ```cddl /// COSE_Key = { /// 1 => tstr / int, ; kty /// ? 2 => bstr, ; kid /// ? 3 => tstr / int, ; alg /// ? 4 => [+ (tstr / int) ], ; key_ops /// ? 5 => bstr, ; Base IV /// * label => values /// } /// ``` #[derive(Clone, Debug, Default, PartialEq)] pub struct CoseKey { /// Key type identification. pub kty: KeyType, /// Key identification. pub key_id: Vec, /// Key use restriction to this algorithm. pub alg: Option, /// Restrict set of possible operations. pub key_ops: BTreeSet, /// Base IV to be xor-ed with partial IVs. pub base_iv: Vec, /// Any additional parameter (label,value) pairs. If duplicate labels are present, /// CBOR-encoding will fail. pub params: Vec<(Label, Value)>, } impl CoseKey { /// Re-order the contents of the key so that the contents will be emitted in one of the standard /// CBOR sorted orders. pub fn canonicalize(&mut self, ordering: CborOrdering) { // The keys that are represented as named fields CBOR-encode as single bytes 0x01 - 0x05, // which sort before any other CBOR values (other than 0x00) in either sorting scheme: // - In length-first sorting, a single byte sorts before anything multi-byte and 1-5 sorts // before any other value. // - In encoded-lexicographic sorting, there are no valid CBOR-encoded single values that // start with a byte in the range 0x01 - 0x05 other than the values 1-5. // So we only need to sort the `params`. match ordering { CborOrdering::Lexicographic => self.params.sort_by(|l, r| l.0.cmp(&r.0)), CborOrdering::LengthFirstLexicographic => { self.params.sort_by(|l, r| l.0.cmp_canonical(&r.0)) } } } } impl crate::CborSerializable for CoseKey {} const KTY: Label = Label::Int(iana::KeyParameter::Kty as i64); const KID: Label = Label::Int(iana::KeyParameter::Kid as i64); const ALG: Label = Label::Int(iana::KeyParameter::Alg as i64); const KEY_OPS: Label = Label::Int(iana::KeyParameter::KeyOps as i64); const BASE_IV: Label = Label::Int(iana::KeyParameter::BaseIv as i64); impl AsCborValue for CoseKey { fn from_cbor_value(value: Value) -> Result { let m = value.try_as_map()?; let mut key = Self::default(); let mut seen = BTreeSet::new(); for (l, value) in m.into_iter() { // The `ciborium` CBOR library does not police duplicate map keys. // RFC 8152 section 14 requires that COSE does police duplicates, so do it here. let label = Label::from_cbor_value(l)?; if seen.contains(&label) { return Err(CoseError::DuplicateMapKey); } seen.insert(label.clone()); match label { KTY => key.kty = KeyType::from_cbor_value(value)?, KID => { key.key_id = value.try_as_nonempty_bytes()?; } ALG => key.alg = Some(Algorithm::from_cbor_value(value)?), KEY_OPS => { let key_ops = value.try_as_array()?; for key_op in key_ops.into_iter() { if !key.key_ops.insert(KeyOperation::from_cbor_value(key_op)?) { return Err(CoseError::UnexpectedItem( "repeated array entry", "unique array label", )); } } if key.key_ops.is_empty() { return Err(CoseError::UnexpectedItem("empty array", "non-empty array")); } } BASE_IV => { key.base_iv = value.try_as_nonempty_bytes()?; } label => key.params.push((label, value)), } } // Check that key type has been set. if key.kty == KeyType::Assigned(iana::KeyType::Reserved) { return Err(CoseError::UnexpectedItem( "no kty label", "mandatory kty label", )); } Ok(key) } fn to_cbor_value(self) -> Result { let mut map: Vec<(Value, Value)> = vec![(KTY.to_cbor_value()?, self.kty.to_cbor_value()?)]; if !self.key_id.is_empty() { map.push((KID.to_cbor_value()?, Value::Bytes(self.key_id))); } if let Some(alg) = self.alg { map.push((ALG.to_cbor_value()?, alg.to_cbor_value()?)); } if !self.key_ops.is_empty() { map.push((KEY_OPS.to_cbor_value()?, to_cbor_array(self.key_ops)?)); } if !self.base_iv.is_empty() { map.push((BASE_IV.to_cbor_value()?, Value::Bytes(self.base_iv))); } let mut seen = BTreeSet::new(); for (label, value) in self.params { if seen.contains(&label) { return Err(CoseError::DuplicateMapKey); } seen.insert(label.clone()); map.push((label.to_cbor_value()?, value)); } Ok(Value::Map(map)) } } /// Builder for [`CoseKey`] objects. #[derive(Debug, Default)] pub struct CoseKeyBuilder(CoseKey); impl CoseKeyBuilder { builder! {CoseKey} builder_set! {kty: KeyType} builder_set! {key_id: Vec} builder_set! {base_iv: Vec} /// Constructor for an elliptic curve public key specified by `x` and `y` coordinates. pub fn new_ec2_pub_key(curve: iana::EllipticCurve, x: Vec, y: Vec) -> Self { Self(CoseKey { kty: KeyType::Assigned(iana::KeyType::EC2), params: vec![ ( Label::Int(iana::Ec2KeyParameter::Crv as i64), Value::from(curve as u64), ), (Label::Int(iana::Ec2KeyParameter::X as i64), Value::Bytes(x)), (Label::Int(iana::Ec2KeyParameter::Y as i64), Value::Bytes(y)), ], ..Default::default() }) } /// Constructor for an elliptic curve public key specified by `x` coordinate plus sign of `y` /// coordinate. pub fn new_ec2_pub_key_y_sign(curve: iana::EllipticCurve, x: Vec, y_sign: bool) -> Self { Self(CoseKey { kty: KeyType::Assigned(iana::KeyType::EC2), params: vec![ ( Label::Int(iana::Ec2KeyParameter::Crv as i64), Value::from(curve as u64), ), (Label::Int(iana::Ec2KeyParameter::X as i64), Value::Bytes(x)), ( Label::Int(iana::Ec2KeyParameter::Y as i64), Value::Bool(y_sign), ), ], ..Default::default() }) } /// Constructor for an elliptic curve private key specified by `d`, together with public `x` and /// `y` coordinates. pub fn new_ec2_priv_key( curve: iana::EllipticCurve, x: Vec, y: Vec, d: Vec, ) -> Self { let mut builder = Self::new_ec2_pub_key(curve, x, y); builder .0 .params .push((Label::Int(iana::Ec2KeyParameter::D as i64), Value::Bytes(d))); builder } /// Constructor for a symmetric key specified by `k`. pub fn new_symmetric_key(k: Vec) -> Self { Self(CoseKey { kty: KeyType::Assigned(iana::KeyType::Symmetric), params: vec![( Label::Int(iana::SymmetricKeyParameter::K as i64), Value::Bytes(k), )], ..Default::default() }) } /// Constructor for a octet keypair key. pub fn new_okp_key() -> Self { Self(CoseKey { kty: KeyType::Assigned(iana::KeyType::OKP), ..Default::default() }) } /// Set the key type. #[must_use] pub fn key_type(mut self, key_type: iana::KeyType) -> Self { self.0.kty = KeyType::Assigned(key_type); self } /// Set the algorithm. #[must_use] pub fn algorithm(mut self, alg: iana::Algorithm) -> Self { self.0.alg = Some(Algorithm::Assigned(alg)); self } /// Add a key operation. #[must_use] pub fn add_key_op(mut self, op: iana::KeyOperation) -> Self { self.0.key_ops.insert(KeyOperation::Assigned(op)); self } /// Set a parameter value. /// /// # Panics /// /// This function will panic if it used to set a parameter label from the [`iana::KeyParameter`] /// range. #[must_use] pub fn param(mut self, label: i64, value: Value) -> Self { if iana::KeyParameter::from_i64(label).is_some() { panic!("param() method used to set KeyParameter"); // safe: invalid input } self.0.params.push((Label::Int(label), value)); self } }