hsmtool/util/attribute/
data.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::convert::TryFrom;
6use std::sync::LazyLock;
7
8use cryptoki::types::Ulong;
9use regex::Regex;
10use serde::{Deserialize, Serialize};
11
12use crate::util::attribute::{
13    AttributeError, CertificateType, KeyType, MechanismType, ObjectClass,
14};
15use crate::util::escape::{as_hex, escape, unescape};
16
17#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
18pub enum Redacted {
19    RedactedByHsm,
20    RedactedByTool,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
24#[serde(untagged)]
25pub enum AttrData {
26    #[default]
27    None,
28    Bool(bool),
29    Ulong(u64),
30    CertificateType(CertificateType),
31    KeyType(KeyType),
32    MechanismType(MechanismType),
33    ObjectClass(ObjectClass),
34    Redacted(Redacted),
35    Str(String),
36    List(Vec<AttrData>),
37}
38
39impl From<bool> for AttrData {
40    fn from(v: bool) -> Self {
41        AttrData::Bool(v)
42    }
43}
44
45impl TryFrom<&AttrData> for bool {
46    type Error = AttributeError;
47    fn try_from(a: &AttrData) -> Result<Self, Self::Error> {
48        match a {
49            AttrData::Bool(v) => Ok(*v),
50            _ => Err(AttributeError::InvalidDataType),
51        }
52    }
53}
54
55impl From<u64> for AttrData {
56    fn from(v: u64) -> Self {
57        AttrData::Ulong(v)
58    }
59}
60
61impl From<Ulong> for AttrData {
62    fn from(v: Ulong) -> Self {
63        AttrData::Ulong(*v)
64    }
65}
66
67impl TryFrom<&AttrData> for u64 {
68    type Error = AttributeError;
69    fn try_from(a: &AttrData) -> Result<Self, Self::Error> {
70        match a {
71            AttrData::Ulong(v) => Ok(*v),
72            _ => Err(AttributeError::InvalidDataType),
73        }
74    }
75}
76
77impl TryFrom<&AttrData> for Ulong {
78    type Error = AttributeError;
79    fn try_from(a: &AttrData) -> Result<Self, Self::Error> {
80        match a {
81            AttrData::Ulong(v) => Ok(Ulong::from(*v)),
82            _ => Err(AttributeError::InvalidDataType),
83        }
84    }
85}
86
87impl From<&[u8]> for AttrData {
88    fn from(v: &[u8]) -> Self {
89        AttrData::Str(as_hex(v))
90    }
91}
92
93pub fn unhex(ch: u8) -> u8 {
94    match ch {
95        b'0'..=b'9' => ch - b'0',
96        b'A'..=b'F' => ch - b'A' + 10,
97        b'a'..=b'f' => ch - b'a' + 10,
98        _ => unreachable!(),
99    }
100}
101
102impl TryFrom<&AttrData> for Vec<u8> {
103    type Error = AttributeError;
104    fn try_from(a: &AttrData) -> Result<Self, Self::Error> {
105        static HEX: LazyLock<Regex> =
106            LazyLock::new(|| Regex::new("^[0-9A-Fa-f]{2}(:[0-9A-Fa-f]{2})*$").unwrap());
107        match a {
108            AttrData::Str(v) => {
109                if HEX.is_match(v) {
110                    // The format of a hex string is "01:23:45:67[:...]".
111                    // Take chunks of 3.  The regex guarantees that the
112                    // hex string is composed only of ASCII characters and we
113                    // can therefore view the str as bytes.
114                    Ok(v.as_bytes()
115                        .chunks(3)
116                        .map(|ch| (unhex(ch[0]) << 4) | unhex(ch[1]))
117                        .collect())
118                } else {
119                    Ok(unescape(v).map_err(|_| AttributeError::EncodingError)?)
120                }
121            }
122            _ => Err(AttributeError::InvalidDataType),
123        }
124    }
125}
126
127impl AttrData {
128    pub fn is_none(&self) -> bool {
129        self == &AttrData::None
130    }
131    pub fn from_ascii_bytes(v: &[u8]) -> Self {
132        AttrData::Str(escape(v))
133    }
134
135    pub fn try_str(&self) -> Result<&str, AttributeError> {
136        match self {
137            AttrData::Str(v) => Ok(v.as_str()),
138            _ => Err(AttributeError::InvalidDataType),
139        }
140    }
141
142    pub fn try_string(&self) -> Result<String, AttributeError> {
143        match self {
144            AttrData::Str(v) => Ok(v.clone()),
145            _ => Err(AttributeError::InvalidDataType),
146        }
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153    use anyhow::Result;
154
155    #[test]
156    fn test_bool() -> Result<()> {
157        let b = AttrData::from(true);
158        assert!(bool::try_from(&b)?);
159        assert!(u64::try_from(&b).is_err());
160        assert!(Ulong::try_from(&b).is_err());
161        assert!(Vec::<u8>::try_from(&b).is_err());
162        assert!(b.try_str().is_err());
163        Ok(())
164    }
165
166    #[test]
167    fn test_ulong() -> Result<()> {
168        let b = AttrData::from(12345u64);
169        assert!(bool::try_from(&b).is_err());
170        assert_eq!(u64::try_from(&b)?, 12345);
171        assert_eq!(Ulong::try_from(&b)?, Ulong::from(12345));
172        assert!(Vec::<u8>::try_from(&b).is_err());
173        assert!(b.try_str().is_err());
174        Ok(())
175    }
176
177    #[test]
178    fn test_str() -> Result<()> {
179        let data = vec![0x12u8, 0x34u8, 0x56u8, 0x78u8];
180        let b = AttrData::from(data.as_slice());
181        assert!(bool::try_from(&b).is_err());
182        assert!(u64::try_from(&b).is_err());
183        assert!(Ulong::try_from(&b).is_err());
184        assert_eq!(Vec::<u8>::try_from(&b)?, &[0x12, 0x34, 0x56, 0x78]);
185        assert_eq!(b.try_str()?, "12:34:56:78");
186        Ok(())
187    }
188}