hsmtool/util/attribute/
attr.rs

1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
5use std::collections::HashSet;
6use std::str::FromStr;
7use std::sync::LazyLock;
8
9use anyhow::Result;
10use cryptoki::object::{Attribute, AttributeInfo, ObjectHandle, ParameterSetType};
11use cryptoki::session::Session;
12use cryptoki::types::Ulong;
13use indexmap::IndexMap;
14use serde::{Deserialize, Serialize};
15use strum::IntoEnumIterator;
16
17use super::AttributeError;
18use super::AttributeType;
19use super::CertificateType;
20use super::Date;
21use super::KeyType;
22use super::MechanismType;
23use super::ObjectClass;
24use super::{AttrData, Redacted};
25
26/// Converts a cryptoki `Attribute` into a key-value pair of
27/// `(AttributeType, AttrData)`.  This allows converting HSM
28/// attribute lists into an easier to manipulat mapping.
29fn into_kv(attr: &Attribute) -> Result<(AttributeType, AttrData)> {
30    match attr {
31        // CK_BBOOL
32        Attribute::AlwaysAuthenticate(b)
33        | Attribute::AlwaysSensitive(b)
34        | Attribute::Copyable(b)
35        | Attribute::Decrypt(b)
36        | Attribute::Derive(b)
37        | Attribute::Destroyable(b)
38        | Attribute::Encrypt(b)
39        | Attribute::Extractable(b)
40        | Attribute::Local(b)
41        | Attribute::Modifiable(b)
42        | Attribute::NeverExtractable(b)
43        | Attribute::Private(b)
44        | Attribute::Sensitive(b)
45        | Attribute::Sign(b)
46        | Attribute::SignRecover(b)
47        | Attribute::Token(b)
48        | Attribute::Trusted(b)
49        | Attribute::Unwrap(b)
50        | Attribute::Verify(b)
51        | Attribute::VerifyRecover(b)
52        | Attribute::Wrap(b)
53        | Attribute::WrapWithTrusted(b) => Ok((
54            AttributeType::from(attr.attribute_type()),
55            AttrData::from(*b),
56        )),
57        // CK_ULONG
58        Attribute::ModulusBits(val) | Attribute::ValueLen(val) => Ok((
59            AttributeType::from(attr.attribute_type()),
60            AttrData::from(*val),
61        )),
62        // Vec<u8>, but ascii text
63        Attribute::Application(bytes) | Attribute::Label(bytes) | Attribute::Url(bytes) => Ok((
64            AttributeType::from(attr.attribute_type()),
65            AttrData::from_ascii_bytes(bytes.as_slice()),
66        )),
67        // Vec<u8>, binary data
68        Attribute::AcIssuer(bytes)
69        | Attribute::AttrTypes(bytes)
70        | Attribute::Base(bytes)
71        | Attribute::CheckValue(bytes)
72        | Attribute::Coefficient(bytes)
73        | Attribute::EcParams(bytes)
74        | Attribute::EcPoint(bytes)
75        | Attribute::Exponent1(bytes)
76        | Attribute::Exponent2(bytes)
77        | Attribute::HashOfIssuerPublicKey(bytes)
78        | Attribute::HashOfSubjectPublicKey(bytes)
79        | Attribute::Issuer(bytes)
80        | Attribute::ObjectId(bytes)
81        | Attribute::Prime(bytes)
82        | Attribute::Prime1(bytes)
83        | Attribute::Prime2(bytes)
84        | Attribute::PrivateExponent(bytes)
85        | Attribute::PublicExponent(bytes)
86        | Attribute::PublicKeyInfo(bytes)
87        | Attribute::Modulus(bytes)
88        | Attribute::Owner(bytes)
89        | Attribute::SerialNumber(bytes)
90        | Attribute::Subject(bytes)
91        | Attribute::Value(bytes)
92        | Attribute::Id(bytes) => Ok((
93            AttributeType::from(attr.attribute_type()),
94            AttrData::from(bytes.as_slice()),
95        )),
96        // Unique types
97        Attribute::CertificateType(certificate_type) => {
98            let val = CertificateType::from(*certificate_type);
99            Ok((
100                AttributeType::from(attr.attribute_type()),
101                AttrData::from(val),
102            ))
103        }
104        Attribute::ParameterSet(val) => Ok((
105            AttributeType::from(attr.attribute_type()),
106            AttrData::from(**val),
107        )),
108        Attribute::Class(object_class) => {
109            let val = ObjectClass::from(*object_class);
110            Ok((
111                AttributeType::from(attr.attribute_type()),
112                AttrData::from(val),
113            ))
114        }
115        Attribute::KeyGenMechanism(mech) => {
116            let val = MechanismType::from(*mech);
117            Ok((
118                AttributeType::from(attr.attribute_type()),
119                AttrData::from(val),
120            ))
121        }
122        Attribute::KeyType(key_type) => {
123            let val = KeyType::from(*key_type);
124            Ok((
125                AttributeType::from(attr.attribute_type()),
126                AttrData::from(val),
127            ))
128        }
129        Attribute::AllowedMechanisms(mechanisms) => {
130            let val = mechanisms
131                .iter()
132                .map(|m| AttrData::from(MechanismType::from(*m)))
133                .collect::<Vec<_>>();
134            Ok((
135                AttributeType::from(attr.attribute_type()),
136                AttrData::List(val),
137            ))
138        }
139        Attribute::EndDate(date) | Attribute::StartDate(date) => {
140            let val = Date::from(*date);
141            Ok((
142                AttributeType::from(attr.attribute_type()),
143                AttrData::Str(val.into()),
144            ))
145        }
146        _ => Err(AttributeError::UnknownAttribute(attr.clone()).into()),
147    }
148}
149
150/// Converts an hsmtool `(AttributeType, AttrData)` pair into a
151/// cryptoki `Attribute`.  This facilitates converting a mapping of attributes
152/// into an HSM-ready `Attribute`.
153fn from_kv(atype: AttributeType, data: &AttrData) -> Result<Attribute> {
154    match atype {
155        AttributeType::AcIssuer => Ok(Attribute::AcIssuer(data.try_into()?)),
156        AttributeType::AllowedMechanisms => match data {
157            AttrData::List(v) => {
158                let mechs = v
159                    .iter()
160                    .map(|a| Ok(MechanismType::try_from(a)?.try_into()?))
161                    .collect::<Result<Vec<cryptoki::mechanism::MechanismType>>>()?;
162                Ok(Attribute::AllowedMechanisms(mechs))
163            }
164            _ => Err(AttributeError::InvalidDataType.into()),
165        },
166        AttributeType::AlwaysAuthenticate => Ok(Attribute::AlwaysAuthenticate(data.try_into()?)),
167        AttributeType::AlwaysSensitive => Ok(Attribute::AlwaysSensitive(data.try_into()?)),
168        AttributeType::Application => Ok(Attribute::Application(data.try_into()?)),
169        AttributeType::AttrTypes => Ok(Attribute::AttrTypes(data.try_into()?)),
170        AttributeType::Base => Ok(Attribute::Base(data.try_into()?)),
171        AttributeType::CertificateType => Ok(Attribute::CertificateType(
172            CertificateType::try_from(data)?.try_into()?,
173        )),
174        AttributeType::CheckValue => Ok(Attribute::CheckValue(data.try_into()?)),
175        AttributeType::Class => Ok(Attribute::Class(ObjectClass::try_from(data)?.try_into()?)),
176        AttributeType::Coefficient => Ok(Attribute::Coefficient(data.try_into()?)),
177        AttributeType::Copyable => Ok(Attribute::Copyable(data.try_into()?)),
178        AttributeType::Decrypt => Ok(Attribute::Decrypt(data.try_into()?)),
179        AttributeType::Derive => Ok(Attribute::Derive(data.try_into()?)),
180        AttributeType::Destroyable => Ok(Attribute::Destroyable(data.try_into()?)),
181        AttributeType::EcParams => Ok(Attribute::EcParams(data.try_into()?)),
182        AttributeType::EcPoint => Ok(Attribute::EcPoint(data.try_into()?)),
183        AttributeType::Encrypt => Ok(Attribute::Encrypt(data.try_into()?)),
184        AttributeType::EndDate => Ok(Attribute::EndDate(Date::try_from(data)?.try_into()?)),
185        AttributeType::Exponent1 => Ok(Attribute::Exponent1(data.try_into()?)),
186        AttributeType::Exponent2 => Ok(Attribute::Exponent2(data.try_into()?)),
187        AttributeType::Extractable => Ok(Attribute::Extractable(data.try_into()?)),
188        AttributeType::HashOfIssuerPublicKey => {
189            Ok(Attribute::HashOfIssuerPublicKey(data.try_into()?))
190        }
191        AttributeType::HashOfSubjectPublicKey => {
192            Ok(Attribute::HashOfSubjectPublicKey(data.try_into()?))
193        }
194        AttributeType::Id => Ok(Attribute::Id(data.try_into()?)),
195        AttributeType::Issuer => Ok(Attribute::Issuer(data.try_into()?)),
196        AttributeType::KeyGenMechanism => Ok(Attribute::KeyGenMechanism(
197            MechanismType::try_from(data)?.try_into()?,
198        )),
199        AttributeType::KeyType => Ok(Attribute::KeyType(KeyType::try_from(data)?.try_into()?)),
200        AttributeType::Label => Ok(Attribute::Label(data.try_into()?)),
201        AttributeType::Local => Ok(Attribute::Local(data.try_into()?)),
202        AttributeType::Modifiable => Ok(Attribute::Modifiable(data.try_into()?)),
203        AttributeType::Modulus => Ok(Attribute::Modulus(data.try_into()?)),
204        AttributeType::ModulusBits => Ok(Attribute::ModulusBits(data.try_into()?)),
205        AttributeType::NeverExtractable => Ok(Attribute::NeverExtractable(data.try_into()?)),
206        AttributeType::ObjectId => Ok(Attribute::ObjectId(data.try_into()?)),
207        AttributeType::Owner => Ok(Attribute::Owner(data.try_into()?)),
208        AttributeType::ParameterSet => {
209            // TODO: Remove this once cryptoki supports the ML-DSA ParameterSetType.
210            let val = u64::try_from(data)?;
211            match ParameterSetType::try_from(Ulong::from(val)) {
212                Ok(p) => Ok(Attribute::ParameterSet(p)),
213                Err(e) => {
214                    log::error!("ParameterSetType conversion failed for {}: {:?}", val, e);
215                    Err(AttributeError::InvalidDataType.into())
216                }
217            }
218        }
219        AttributeType::Prime => Ok(Attribute::Prime(data.try_into()?)),
220        AttributeType::Prime1 => Ok(Attribute::Prime1(data.try_into()?)),
221        AttributeType::Prime2 => Ok(Attribute::Prime2(data.try_into()?)),
222        AttributeType::Private => Ok(Attribute::Private(data.try_into()?)),
223        AttributeType::PrivateExponent => Ok(Attribute::PrivateExponent(data.try_into()?)),
224        AttributeType::PublicExponent => Ok(Attribute::PublicExponent(data.try_into()?)),
225        AttributeType::PublicKeyInfo => Ok(Attribute::PublicKeyInfo(data.try_into()?)),
226        AttributeType::Sensitive => Ok(Attribute::Sensitive(data.try_into()?)),
227        AttributeType::SerialNumber => Ok(Attribute::SerialNumber(data.try_into()?)),
228        AttributeType::Sign => Ok(Attribute::Sign(data.try_into()?)),
229        AttributeType::SignRecover => Ok(Attribute::SignRecover(data.try_into()?)),
230        AttributeType::StartDate => Ok(Attribute::StartDate(Date::try_from(data)?.try_into()?)),
231        AttributeType::Subject => Ok(Attribute::Subject(data.try_into()?)),
232        AttributeType::Token => Ok(Attribute::Token(data.try_into()?)),
233        AttributeType::Trusted => Ok(Attribute::Trusted(data.try_into()?)),
234        AttributeType::Unwrap => Ok(Attribute::Unwrap(data.try_into()?)),
235        AttributeType::Url => Ok(Attribute::Url(data.try_into()?)),
236        AttributeType::Value => Ok(Attribute::Value(data.try_into()?)),
237        AttributeType::ValueLen => Ok(Attribute::ValueLen(data.try_into()?)),
238        AttributeType::Verify => Ok(Attribute::Verify(data.try_into()?)),
239        AttributeType::VerifyRecover => Ok(Attribute::VerifyRecover(data.try_into()?)),
240        AttributeType::Wrap => Ok(Attribute::Wrap(data.try_into()?)),
241        AttributeType::WrapWithTrusted => Ok(Attribute::WrapWithTrusted(data.try_into()?)),
242        _ => Err(AttributeError::UnknownAttributeType(atype).into()),
243    }
244}
245
246#[derive(Clone, Debug, Default, Serialize, Deserialize)]
247pub struct AttributeMap(IndexMap<AttributeType, AttrData>);
248
249impl From<&[Attribute]> for AttributeMap {
250    fn from(a: &[Attribute]) -> Self {
251        AttributeMap(
252            a.iter()
253                .map(|a| into_kv(a).expect("convert from attribute"))
254                .collect(),
255        )
256    }
257}
258
259impl AttributeMap {
260    /// Generates a list of all AttributeTypes known to the cryptoki library.
261    /// This list can be used to retrieve all attributes of an HSM-object to
262    /// faciltate showing, exporting or modifying objects.
263    ///
264    /// This function only generates the list of known attributes once.
265    pub fn all() -> &'static [cryptoki::object::AttributeType] {
266        static VALID_TYPES: LazyLock<Vec<cryptoki::object::AttributeType>> = LazyLock::new(|| {
267            AttributeType::iter()
268                // TODO: Skip KeyGenMechanism because querying it for MLDSA keys
269                // triggers a mechanism validation error in the cryptoki library.
270                // Remove this once cryptoki supports the MLDSA keygenmechanism.
271                .filter(|&a| a != AttributeType::KeyGenMechanism)
272                .map(|a| Ok(a.try_into()?))
273                .filter(|a| a.is_ok())
274                .collect::<Result<Vec<_>>>()
275                .unwrap()
276        });
277
278        VALID_TYPES.as_slice()
279    }
280
281    /// Generates a list of sensitive attributes that should be redacted when
282    /// exporting an object. This list is used to prevent sensitive data from
283    /// being accidentally read. Some HSMs will not mark theses attributes as
284    /// sensitive, so we have to do it manually.
285    pub fn sensitive_attrs() -> &'static [cryptoki::object::AttributeType] {
286        &[
287            // For symmetric and EC private keys.
288            cryptoki::object::AttributeType::Value,
289            // For RSA private keys.
290            cryptoki::object::AttributeType::PrivateExponent,
291            cryptoki::object::AttributeType::Prime1,
292            cryptoki::object::AttributeType::Prime2,
293            cryptoki::object::AttributeType::Exponent1,
294            cryptoki::object::AttributeType::Exponent2,
295            cryptoki::object::AttributeType::Coefficient,
296        ]
297    }
298
299    /// Inserts a `key`/`value` pair into the mapping, returing the
300    /// previous value (if any).
301    pub fn insert(&mut self, key: AttributeType, value: AttrData) -> Option<AttrData> {
302        self.0.insert(key, value)
303    }
304
305    /// Retrieves an `AttrData` value for the given `key`.
306    pub fn get(&self, key: &AttributeType) -> Option<&AttrData> {
307        self.0.get(key)
308    }
309
310    /// Returns true if the `AttributeMap` is empty.
311    pub fn is_empty(&self) -> bool {
312        self.0.is_empty()
313    }
314
315    /// Converts the `AttributeMap` to a cryptoki ready list of `Attributes`.
316    pub fn to_vec(&self) -> Result<Vec<Attribute>> {
317        self.0.iter().map(|(k, v)| from_kv(*k, v)).collect()
318    }
319
320    /// Merges an `other` mapping into `self`.
321    pub fn merge(&mut self, other: AttributeMap) {
322        for (k, v) in other.0 {
323            self.insert(k, v);
324        }
325    }
326
327    pub fn redact(&mut self, redactions: &HashSet<AttributeType>) {
328        for (k, v) in self.0.iter_mut() {
329            if redactions.contains(k) && !matches!(v, AttrData::Redacted(_)) {
330                *v = AttrData::Redacted(Redacted::RedactedByTool);
331            }
332        }
333    }
334
335    /// Retrieves an object from the PKCS#11 interface as an `AttributeMap`.
336    pub fn from_object(session: &Session, object: ObjectHandle) -> Result<Self> {
337        let all = Self::all();
338        let sensitive_attrs = Self::sensitive_attrs();
339        let info = session.get_attribute_info(object, all)?;
340        let mut atypes = Vec::new();
341        let mut sensitive_types = Vec::new();
342        for (&a, i) in all.iter().zip(info.iter()) {
343            // Skip the AllowedMechanism as cloud-kms returns a list of
344            // mechanisms that aren't understood by cryptoki's MechanismType.
345            if a == cryptoki::object::AttributeType::AllowedMechanisms {
346                continue;
347            }
348
349            // Skip sensitive attributes. We first have to check if the object
350            // is sensitive before being able to load it. Some Luna HSM
351            // versions will return an error if we attempt to read a sensitive
352            // attribute.
353            if sensitive_attrs.contains(&a) {
354                sensitive_types.push(a);
355                continue;
356            }
357
358            if matches!(i, AttributeInfo::Available(_)) {
359                atypes.push(a);
360            }
361        }
362
363        let attrs = session.get_attributes(object, &atypes)?;
364        let mut map = AttributeMap::from(attrs.as_slice());
365
366        let sensitive = map.get(&AttributeType::Sensitive);
367        let object_sensitive = matches!(sensitive, Some(AttrData::Bool(true)));
368
369        // Use a functional iteration to query the sensitive attributes. This
370        // is necessary because some HSMs will not allow reading sensitive
371        // attributes. In this case, we have to redact the attribute.
372        sensitive_types.iter().for_each(|&attr| {
373            if object_sensitive {
374                map.insert(
375                    AttributeType::Value,
376                    AttrData::Redacted(Redacted::RedactedByHsm),
377                );
378            } else if let Ok(attrs) = session.get_attributes(object, &[attr])
379                && let Some(attr_obj) = attrs.into_iter().next()
380                && let Ok((ty, val)) = into_kv(&attr_obj)
381            {
382                map.insert(ty, val);
383            }
384        });
385
386        // Finally, regardles of the above, if any attribute info explicitly marks
387        // an attribute as sensitive, redact it.
388        for (&a, i) in all.iter().zip(info.iter()) {
389            if matches!(i, AttributeInfo::Sensitive) {
390                map.insert(a.into(), AttrData::Redacted(Redacted::RedactedByHsm));
391            }
392        }
393        Ok(map)
394    }
395}
396
397impl FromStr for AttributeMap {
398    type Err = anyhow::Error;
399
400    /// Parses an `AttributeMap` from a string or file.
401    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
402        if let Some(path) = s.strip_prefix('@') {
403            let data = std::fs::read_to_string(path)?;
404            Ok(serde_annotate::from_str(&data)?)
405        } else {
406            Ok(serde_annotate::from_str(s)?)
407        }
408    }
409}
410
411#[cfg(test)]
412mod tests {
413    use super::*;
414
415    #[test]
416    fn test_attribute_to_kv() -> Result<()> {
417        let a = Attribute::Copyable(true);
418        let (ty, data) = into_kv(&a)?;
419        assert_eq!(ty, AttributeType::Copyable);
420        assert_eq!(data, AttrData::Bool(true));
421        Ok(())
422    }
423
424    #[test]
425    fn test_kv_to_attribute() -> Result<()> {
426        let a = from_kv(AttributeType::Copyable, &AttrData::Bool(true))?;
427        assert!(matches!(a, Attribute::Copyable(true)));
428        Ok(())
429    }
430
431    const ATTR_MAP: &str = r#"{
432  "CKA_COPYABLE": true,
433  "CKA_MODULUS_BITS": 3072,
434  "CKA_LABEL": "foo",
435  "CKA_OBJECT_ID": "12:34:56:78",
436  "CKA_CERTIFICATE_TYPE": "CKC_X_509",
437  "CKA_CLASS": "CKO_CERTIFICATE",
438  "CKA_KEY_TYPE": "CKK_RSA",
439  "CKA_KEY_GEN_MECHANISM": "CKM_RSA_PKCS",
440  "CKA_ALLOWED_MECHANISMS": [
441    "CKM_RSA_PKCS",
442    "CKM_RSA_PKCS_KEY_PAIR_GEN"
443  ],
444  "CKA_START_DATE": "2023-02-15"
445}"#;
446
447    #[test]
448    fn test_to_attribute_map() -> Result<()> {
449        let a = [
450            Attribute::Copyable(true),
451            Attribute::ModulusBits(3072u64.into()),
452            Attribute::Label(vec![b'f', b'o', b'o']),
453            Attribute::ObjectId(vec![0x12, 0x34, 0x56, 0x78]),
454            Attribute::CertificateType(CertificateType::X509.try_into()?),
455            Attribute::Class(ObjectClass::Certificate.try_into()?),
456            Attribute::KeyType(KeyType::Rsa.try_into()?),
457            Attribute::KeyGenMechanism(MechanismType::RsaPkcs.try_into()?),
458            Attribute::AllowedMechanisms(vec![
459                MechanismType::RsaPkcs.try_into()?,
460                MechanismType::RsaPkcsKeyPairGen.try_into()?,
461            ]),
462            Attribute::StartDate(Date::from("2023-02-15").try_into()?),
463        ];
464        let am = AttributeMap::from(a.as_slice());
465        let json = serde_json::to_string_pretty(&am)?;
466        assert_eq!(json, ATTR_MAP);
467        Ok(())
468    }
469
470    #[test]
471    fn test_from_attribute_map() -> Result<()> {
472        let map = serde_json::from_str::<AttributeMap>(ATTR_MAP)?;
473        let a = map.to_vec()?;
474        // Currently, the best way to check that the attributes have the
475        // expected values is to check the output of the Debug trait.
476        let result = format!("{:x?}", a);
477        assert_eq!(
478            result,
479            "[Copyable(true), ModulusBits(Ulong { val: c00 }), Label([66, 6f, 6f]), ObjectId([12, 34, 56, 78]), CertificateType(CertificateType { val: 0 }), Class(ObjectClass { val: 1 }), KeyType(KeyType { val: 0 }), KeyGenMechanism(MechanismType { val: 1 }), AllowedMechanisms([MechanismType { val: 1 }, MechanismType { val: 0 }]), StartDate(Date { date: CK_DATE { year: [32, 30, 32, 33], month: [30, 32], day: [31, 35] } })]"
480        );
481        Ok(())
482    }
483}