Skip to main content

matc/
certmanager.rs

1//! Certificate manager trait and default file based implementation
2
3use std::sync::Arc;
4
5use anyhow::{Context, Result};
6use serde::{Deserialize, Serialize};
7
8use crate::{cert_x509, util::cryptoutil};
9
10pub trait CertManager: Send + Sync {
11    fn get_ca_cert(&self) -> Result<Vec<u8>>;
12    fn get_ca_key(&self) -> Result<p256::SecretKey>;
13    fn get_ca_public_key(&self) -> Result<Vec<u8>>;
14    fn get_user_cert(&self, id: u64) -> Result<Vec<u8>>;
15    fn get_user_key(&self, id: u64) -> Result<p256::SecretKey>;
16    fn get_fabric_id(&self) -> u64;
17    fn get_ipk_epoch_key(&self) -> Vec<u8>;
18}
19
20/// Example implementation of [CertManager] trait.
21/// It stores keys and certificates in PEM files in specified directory.
22pub struct FileCertManager {
23    fabric_id: u64,
24    ipk_epoch_key: Vec<u8>,
25    path: String,
26}
27
28#[derive(Serialize, Deserialize)]
29struct Metadata {
30    fabric_id: String,
31    ipk_epoch_key: String,
32}
33
34impl FileCertManager {
35    pub fn new(fabric_id: u64, path: &str) -> Arc<Self> {
36        let ipk_epoch_key: [u8; 16] = rand::random();
37        Arc::new(Self {
38            fabric_id,
39            ipk_epoch_key: ipk_epoch_key.to_vec(),
40            path: path.to_owned(),
41        })
42    }
43
44    /// Load an existing CA identity from `path`.
45    ///
46    /// Reads `metadata.json` (fabric_id + ipk_epoch_key) if present.
47    /// Falls back to legacy `metadata.pem` (fabric_id only, hardcoded IPK)
48    /// so identities bootstrapped before this change keep working.
49    pub fn load(path: &str) -> Result<Arc<Self>> {
50        let json_path = format!("{}/metadata.json", path);
51        let pem_path = format!("{}/metadata.pem", path);
52
53        let (fabric_id, ipk_epoch_key) = if std::path::Path::new(&json_path).exists() {
54            let s = std::fs::read_to_string(&json_path)
55                .context(format!("can't read from {}", json_path))?;
56            let m: Metadata = serde_json::from_str(&s)
57                .context(format!("invalid JSON in {}", json_path))?;
58            let fid = m.fabric_id.parse::<u64>()
59                .context("invalid fabric_id in metadata.json")?;
60            let ipk = hex::decode(&m.ipk_epoch_key)
61                .context("invalid ipk_epoch_key hex in metadata.json")?;
62            (fid, ipk)
63        } else {
64            let s = std::fs::read_to_string(&pem_path)
65                .context(format!("can't read from {}", pem_path))?;
66            let fid = s.trim().parse::<u64>()?;
67            (fid, vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf])
68        };
69
70        Ok(Arc::new(Self {
71            fabric_id,
72            ipk_epoch_key,
73            path: path.to_owned(),
74        }))
75    }
76
77    fn user_key_fname(&self, id: u64) -> String {
78        format!("{}/{}-private.pem", self.path, id)
79    }
80    fn ca_key_fname(&self) -> String {
81        format!("{}/ca-private.pem", self.path)
82    }
83    fn user_cert_fname(&self, id: u64) -> String {
84        format!("{}/{}-cert.pem", self.path, id)
85    }
86    fn ca_cert_fname(&self) -> String {
87        format!("{}/ca-cert.pem", self.path)
88    }
89    fn metadata_json_fname(&self) -> String {
90        format!("{}/metadata.json", self.path)
91    }
92}
93
94const CA_NODE_ID: u64 = 1;
95
96/*fn extract_fabric_id(fname: &str) -> Result<u64> {
97    let x509_raw = cryptoutil::read_data_from_pem(fname)?;
98    let x509 = x509_cert::Certificate::from_der(&x509_raw)?;
99    let subject = x509.tbs_certificate.subject;
100    for rdn in subject.0 {
101        for av in rdn.0.as_slice() {
102            if av.oid == const_oid::ObjectIdentifier::new_unwrap("1.3.6.1.4.1.37244.1.5") {
103                let valstr = av.value.decode_as::<String>()?;
104                return Ok(u64::from_str_radix(&valstr, 16)?)
105            }
106        }
107    };
108    Err(anyhow::anyhow!("can't extract fabric id"))
109}*/
110
111impl FileCertManager {
112    /// Initialize CA. Create directory, generate CA key and certificate and store them in specified directory.
113    /// Also writes `metadata.json` with the fabric_id and a randomly generated IPK epoch key.
114    /// Directory must not exist before calling this function. If it exists function will fail.
115    pub fn bootstrap(&self) -> Result<()> {
116        std::fs::create_dir(&self.path)?;
117
118        let secret_key = p256::SecretKey::random(&mut rand::thread_rng());
119        let data = cryptoutil::secret_key_to_rfc5915(&secret_key)?;
120        let pem = pem::Pem::new("EC PRIVATE KEY", data);
121        std::fs::write(self.ca_key_fname(), pem::encode(&pem).as_bytes())?;
122        let node_public_key = secret_key.public_key().to_sec1_bytes();
123
124        let x509 = cert_x509::encode_x509(
125            &node_public_key,
126            CA_NODE_ID,
127            self.fabric_id,
128            CA_NODE_ID,
129            &secret_key,
130            true,
131        )?;
132        cryptoutil::write_pem("CERTIFICATE", &x509, &self.ca_cert_fname())?;
133        let metadata = Metadata {
134            fabric_id: format!("{}", self.fabric_id),
135            ipk_epoch_key: hex::encode(&self.ipk_epoch_key),
136        };
137        std::fs::write(
138            self.metadata_json_fname(),
139            serde_json::to_string_pretty(&metadata)?,
140        )?;
141        Ok(())
142    }
143
144    /// Create key and certificate for specified node identifier.
145    /// This can be used as credentials for admin(and any additional) user controlling devices.
146    pub fn create_user(&self, id: u64) -> Result<()> {
147        let ca_private = self.get_ca_key()?;
148        let secret_key = p256::SecretKey::random(&mut rand::thread_rng());
149        let data = cryptoutil::secret_key_to_rfc5915(&secret_key)?;
150        let pem = pem::Pem::new("EC PRIVATE KEY", data);
151        std::fs::write(self.user_key_fname(id), pem::encode(&pem).as_bytes())?;
152        let node_public_key = secret_key.public_key().to_sec1_bytes();
153
154        let x509 = cert_x509::encode_x509(
155            &node_public_key,
156            id,
157            self.fabric_id,
158            CA_NODE_ID,
159            &ca_private,
160            false,
161        )?;
162        cryptoutil::write_pem("CERTIFICATE", &x509, &self.user_cert_fname(id))?;
163        Ok(())
164    }
165}
166
167impl CertManager for FileCertManager {
168    fn get_ca_cert(&self) -> Result<Vec<u8>> {
169        cryptoutil::read_data_from_pem(&self.ca_cert_fname())
170    }
171
172    fn get_ca_key(&self) -> Result<p256::SecretKey> {
173        cryptoutil::read_private_key_from_pem(&self.ca_key_fname())
174    }
175
176    fn get_user_cert(&self, id: u64) -> Result<Vec<u8>> {
177        cryptoutil::read_data_from_pem(&self.user_cert_fname(id))
178    }
179
180    fn get_user_key(&self, id: u64) -> Result<p256::SecretKey> {
181        cryptoutil::read_private_key_from_pem(&self.user_key_fname(id))
182    }
183
184    fn get_ca_public_key(&self) -> Result<Vec<u8>> {
185        Ok(self.get_ca_key()?.public_key().to_sec1_bytes().to_vec())
186    }
187
188    fn get_fabric_id(&self) -> u64 {
189        self.fabric_id
190    }
191
192    fn get_ipk_epoch_key(&self) -> Vec<u8> {
193        self.ipk_epoch_key.clone()
194    }
195}