hsmtool/util/key/
ecdsa.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 anyhow::{Context, Result};
6use der::{Encode, Reader, SliceReader, asn1::OctetStringRef};
7use ecdsa::elliptic_curve::pkcs8::{
8    self, AssociatedOid, DecodePrivateKey, DecodePublicKey, EncodePrivateKey, EncodePublicKey,
9};
10use p256::NistP256;
11use p256::ecdsa::{SigningKey, VerifyingKey};
12
13use std::convert::{AsRef, TryFrom};
14use std::path::Path;
15
16use super::KeyEncoding;
17use crate::error::HsmError;
18use crate::util::attribute::{AttrData, AttributeMap, AttributeType, KeyType, ObjectClass};
19
20fn _load_private_key(path: &Path) -> Result<SigningKey> {
21    let data = std::fs::read(path)?;
22    let sdata = std::str::from_utf8(&data);
23    let format = match sdata {
24        Ok(s) if s.contains("-----BEGIN PRIVATE KEY-----") => KeyEncoding::Pkcs8Pem,
25        _ => KeyEncoding::Der,
26    };
27    match format {
28        KeyEncoding::Pkcs8Pem => {
29            SigningKey::from_pkcs8_pem(sdata.unwrap()).context("Loading private key")
30        }
31        _ => SigningKey::from_pkcs8_der(&data).context("Loading private key"),
32    }
33}
34
35/// Loads an RSA private key from a file.  The key may be in either PKCS#1 or
36/// PKCS#8 format encoded in either DER or PEM encodings.
37pub fn load_private_key<P: AsRef<Path>>(path: P) -> Result<SigningKey> {
38    _load_private_key(path.as_ref())
39}
40
41impl TryFrom<&SigningKey> for AttributeMap {
42    type Error = HsmError;
43    /// Converts an `SigningKey` into an `AttributeMap` that can be sent
44    /// to an HSM.
45    fn try_from(k: &SigningKey) -> std::result::Result<Self, Self::Error> {
46        let mut attr = AttributeMap::default();
47        attr.insert(
48            AttributeType::Class,
49            AttrData::ObjectClass(ObjectClass::PrivateKey),
50        );
51        attr.insert(AttributeType::KeyType, AttrData::KeyType(KeyType::Ec));
52        attr.insert(
53            AttributeType::EcParams,
54            AttrData::from(NistP256::OID.to_der().unwrap().as_slice()),
55        );
56        attr.insert(
57            AttributeType::Value,
58            AttrData::from(k.to_bytes().as_slice()),
59        );
60        Ok(attr)
61    }
62}
63
64impl TryFrom<&AttributeMap> for SigningKey {
65    type Error = HsmError;
66    /// Converts an `AttributeMap` into an `SigningKey`.
67    fn try_from(a: &AttributeMap) -> std::result::Result<Self, Self::Error> {
68        let class: ObjectClass = a
69            .get(&AttributeType::Class)
70            .ok_or_else(|| HsmError::KeyError("missing class".into()))?
71            .try_into()
72            .map_err(HsmError::AttributeError)?;
73        let key_type: KeyType = a
74            .get(&AttributeType::KeyType)
75            .ok_or_else(|| HsmError::KeyError("missing key_type".into()))?
76            .try_into()
77            .map_err(HsmError::AttributeError)?;
78        if class != ObjectClass::PrivateKey || key_type != KeyType::Ec {
79            return Err(HsmError::KeyError("bad key type".into()));
80        }
81        let param: Vec<u8> = a
82            .get(&AttributeType::EcParams)
83            .ok_or_else(|| HsmError::KeyError("missing EC param".into()))?
84            .try_into()
85            .map_err(HsmError::AttributeError)?;
86        let oid = NistP256::OID.to_der().unwrap();
87        if param.as_slice() != oid.as_slice() {
88            return Err(HsmError::KeyError("not a P256 key".into()));
89        }
90        let value: Vec<u8> = a
91            .get(&AttributeType::Value)
92            .ok_or_else(|| HsmError::KeyError("missing value".into()))?
93            .try_into()
94            .map_err(HsmError::AttributeError)?;
95
96        SigningKey::from_slice(&value).map_err(|e| HsmError::KeyError(format!("{e:?}")))
97    }
98}
99
100fn _load_public_key(path: &Path) -> Result<VerifyingKey> {
101    let data = std::fs::read(path)?;
102    let sdata = std::str::from_utf8(&data);
103    let format = match sdata {
104        Ok(s) if s.contains("-----BEGIN PUBLIC KEY-----") => KeyEncoding::Pkcs8Pem,
105        _ => KeyEncoding::Der,
106    };
107    match format {
108        KeyEncoding::Pkcs8Pem => {
109            VerifyingKey::from_public_key_pem(sdata.unwrap()).context("Loading public key")
110        }
111        _ => VerifyingKey::from_public_key_der(&data).context("Loading public key"),
112    }
113}
114
115/// Loads an RSA public key from a file.  The key may be in either PKCS#1 or
116/// PKCS#8 format encoded in either DER or PEM encodings.
117pub fn load_public_key<P: AsRef<Path>>(path: P) -> Result<VerifyingKey> {
118    _load_public_key(path.as_ref())
119}
120
121impl TryFrom<&VerifyingKey> for AttributeMap {
122    type Error = HsmError;
123    /// Converts an `VerifyingKey` into an `AttributeMap` that can be sent
124    /// to an HSM.
125    fn try_from(k: &VerifyingKey) -> std::result::Result<Self, Self::Error> {
126        let mut attr = AttributeMap::default();
127        attr.insert(
128            AttributeType::Class,
129            AttrData::ObjectClass(ObjectClass::PublicKey),
130        );
131        attr.insert(AttributeType::KeyType, AttrData::KeyType(KeyType::Ec));
132        let k_sec1 = k.to_sec1_bytes();
133        let k_os = OctetStringRef::new(&k_sec1)
134            .or(Err(HsmError::DerError("building OctetString".into())))?;
135        let k_der = k_os
136            .to_der()
137            .or(Err(HsmError::DerError("encoding".into())))?;
138        attr.insert(AttributeType::EcPoint, AttrData::from(k_der.as_slice()));
139        attr.insert(
140            AttributeType::EcParams,
141            AttrData::from(NistP256::OID.to_der().unwrap().as_slice()),
142        );
143        Ok(attr)
144    }
145}
146
147impl TryFrom<&AttributeMap> for VerifyingKey {
148    type Error = HsmError;
149    /// Converts an `AttributeMap` into an `VerifyingKey`.
150    fn try_from(a: &AttributeMap) -> std::result::Result<Self, Self::Error> {
151        let class: ObjectClass = a
152            .get(&AttributeType::Class)
153            .ok_or_else(|| HsmError::KeyError("missing class".into()))?
154            .try_into()
155            .map_err(HsmError::AttributeError)?;
156        let key_type: KeyType = a
157            .get(&AttributeType::KeyType)
158            .ok_or_else(|| HsmError::KeyError("missing key_type".into()))?
159            .try_into()
160            .map_err(HsmError::AttributeError)?;
161        if class != ObjectClass::PublicKey || key_type != KeyType::Ec {
162            return Err(HsmError::KeyError("bad key type".into()));
163        }
164        let param: Vec<u8> = a
165            .get(&AttributeType::EcParams)
166            .ok_or_else(|| HsmError::KeyError("missing EC param".into()))?
167            .try_into()
168            .map_err(HsmError::AttributeError)?;
169        let oid = NistP256::OID.to_der().unwrap();
170        if param.as_slice() != oid.as_slice() {
171            return Err(HsmError::KeyError("not a P256 key".into()));
172        }
173
174        let point_asn1: Vec<u8> = a
175            .get(&AttributeType::EcPoint)
176            .ok_or_else(|| HsmError::KeyError("missing EC point".into()))?
177            .try_into()
178            .map_err(HsmError::AttributeError)?;
179        let mut reader = SliceReader::new(point_asn1.as_slice())
180            .or(Err(HsmError::DerError("instanciating reader".into())))?;
181        let point_os: OctetStringRef = reader
182            .decode()
183            .or(Err(HsmError::DerError("decoding OctetString".into())))?;
184
185        VerifyingKey::from_sec1_bytes(point_os.as_bytes())
186            .map_err(|e| HsmError::KeyError(format!("sec1: {e:?}")))
187    }
188}
189
190pub fn _save_private_key<P: AsRef<Path>>(
191    path: P,
192    key: &SigningKey,
193    enc: KeyEncoding,
194) -> Result<()> {
195    match enc {
196        KeyEncoding::Pem | KeyEncoding::Pkcs8 | KeyEncoding::Pkcs8Pem => key
197            .write_pkcs8_pem_file(path, pkcs8::LineEnding::default())
198            .context("Saving private key"),
199        KeyEncoding::Der | KeyEncoding::Pkcs8Der => {
200            key.write_pkcs8_der_file(path).context("Saving private key")
201        }
202        KeyEncoding::Pkcs1Der | KeyEncoding::Pkcs1 | KeyEncoding::Pkcs1Pem => {
203            Err(HsmError::Unsupported(format!("{enc:?}")).into())
204        }
205    }
206}
207
208/// Saves an RSA private `key` to a file in the requested encoding.
209pub fn save_private_key<P: AsRef<Path>>(path: P, key: &SigningKey, enc: KeyEncoding) -> Result<()> {
210    _save_private_key(path, key, enc)
211}
212
213pub fn _save_public_key<P: AsRef<Path>>(
214    path: P,
215    key: &VerifyingKey,
216    enc: KeyEncoding,
217) -> Result<()> {
218    match enc {
219        KeyEncoding::Pem | KeyEncoding::Pkcs8 | KeyEncoding::Pkcs8Pem => key
220            .write_public_key_pem_file(path, pkcs8::LineEnding::default())
221            .context("Saving public key"),
222        KeyEncoding::Der | KeyEncoding::Pkcs8Der => key
223            .write_public_key_der_file(path)
224            .context("Saving public key"),
225        KeyEncoding::Pkcs1Der | KeyEncoding::Pkcs1 | KeyEncoding::Pkcs1Pem => {
226            Err(HsmError::Unsupported(format!("{enc:?}")).into())
227        }
228    }
229}
230
231/// Saves an RSA public `key` to a file in the requested encoding.
232pub fn save_public_key<P: AsRef<Path>>(
233    path: P,
234    key: &VerifyingKey,
235    enc: KeyEncoding,
236) -> Result<()> {
237    _save_public_key(path, key, enc)
238}
239
240#[cfg(test)]
241mod tests {
242    use super::*;
243    use crate::util::testdata;
244
245    const TEST2_PRIVATE_KEY: &str = r#"{
246  "CKA_CLASS": "CKO_PRIVATE_KEY",
247  "CKA_KEY_TYPE": "CKK_EC",
248  "CKA_EC_PARAMS": "06:08:2A:86:48:CE:3D:03:01:07",
249  "CKA_VALUE": "5C:6D:5F:11:6E:AE:33:00:EC:F9:12:89:47:4E:38:D5:2F:47:D4:1A:40:B2:C8:3B:2E:D1:12:74:50:3D:5E:49"
250}"#;
251
252    const TEST2_PUBLIC_KEY: &str = r#"{
253  "CKA_CLASS": "CKO_PUBLIC_KEY",
254  "CKA_KEY_TYPE": "CKK_EC",
255  "CKA_EC_POINT": "04:41:04:1A:D2:74:FC:D8:79:7C:52:02:BF:A5:E1:0A:0E:DA:22:4C:23:68:31:8E:89:83:F6:F2:4E:40:73:3C:1E:29:35:1C:99:7D:E3:4B:93:41:F1:47:F3:D2:79:52:89:CE:80:42:D0:D1:A4:27:A3:99:5B:E0:0A:4F:5F:91:00:B9:15",
256  "CKA_EC_PARAMS": "06:08:2A:86:48:CE:3D:03:01:07"
257}"#;
258
259    #[test]
260    fn test_load_public_keys() -> Result<()> {
261        assert!(load_public_key(testdata("key/test2_pkcs8.pub.der")).is_ok());
262        //let k = load_public_key(testdata("key/test2_pkcs8.pub.der"))?;
263        //save_public_key("/tmp/test2_pkcs8.pub.pem", &k, KeyEncoding::Pkcs8Pem)?;
264        assert!(load_public_key(testdata("key/test2_pkcs8.pub.pem")).is_ok());
265        Ok(())
266    }
267
268    #[test]
269    fn test_load_private_keys() -> Result<()> {
270        assert!(load_private_key(testdata("key/test2_pkcs8.der")).is_ok());
271        //let k = load_private_key(testdata("key/test2_pkcs8.der"))?;
272        //save_private_key("/tmp/test2_pkcs8.pem", &k, KeyEncoding::Pkcs8Pem)?;
273        assert!(load_private_key(testdata("key/test2_pkcs8.pem")).is_ok());
274        Ok(())
275    }
276
277    #[test]
278    fn test_convert_key_to_hsm() -> Result<()> {
279        let k = load_private_key(testdata("key/test2_pkcs8.pem"))?;
280        let hsm = AttributeMap::try_from(&k)?;
281        assert_eq!(serde_json::to_string_pretty(&hsm)?, TEST2_PRIVATE_KEY);
282
283        let k = load_public_key(testdata("key/test2_pkcs8.pub.pem"))?;
284        let hsm = AttributeMap::try_from(&k)?;
285        assert_eq!(serde_json::to_string_pretty(&hsm)?, TEST2_PUBLIC_KEY);
286        Ok(())
287    }
288
289    #[test]
290    fn test_convert_hsm_to_key() -> Result<()> {
291        let hsm = serde_json::from_str::<AttributeMap>(TEST2_PRIVATE_KEY)?;
292        let kpriv = SigningKey::try_from(&hsm)?;
293        let k = load_private_key(testdata("key/test2_pkcs8.pem"))?;
294        assert_eq!(kpriv, k);
295
296        let hsm = serde_json::from_str::<AttributeMap>(TEST2_PUBLIC_KEY)?;
297        let kpub = VerifyingKey::try_from(&hsm)?;
298        let k = load_public_key(testdata("key/test2_pkcs8.pub.pem"))?;
299        assert_eq!(kpub, k);
300        Ok(())
301    }
302}