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