Skip to main content

p12_keystore/
keystore.rs

1use std::collections::{BTreeMap, btree_map::Iter};
2
3use cms::content_info::ContentInfo;
4use der::{Any, Decode, Encode, asn1::OctetString, oid::ObjectIdentifier};
5use hex::ToHex;
6use pkcs12::{
7    AuthenticatedSafe,
8    pfx::{Pfx, Version},
9};
10
11use crate::{
12    Result,
13    cert::Certificate,
14    codec::{self, ParsedAuthSafe, secret_to_safe_bag},
15    error::Error,
16    keychain::PrivateKeyChain,
17    oid,
18    secret::Secret,
19};
20
21/// KeyStoreEntry represents one entry in the keystore
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub enum KeyStoreEntry {
24    PrivateKeyChain(PrivateKeyChain),
25    Certificate(Certificate),
26    Secret(Secret),
27}
28
29/// Keystore entries iterator
30pub struct Entries<'a> {
31    iter: Iter<'a, String, KeyStoreEntry>,
32    len: usize,
33}
34
35impl Entries<'_> {
36    /// Returns a total number of entries
37    pub fn len(&self) -> usize {
38        self.len
39    }
40
41    /// Returns true if there are no entries
42    pub fn is_empty(&self) -> bool {
43        self.len == 0
44    }
45}
46
47impl<'a> Iterator for Entries<'a> {
48    type Item = (&'a String, &'a KeyStoreEntry);
49
50    fn next(&mut self) -> Option<Self::Item> {
51        self.iter.next()
52    }
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
56pub enum Pkcs12ImportPolicy {
57    /// Ignore unmatched private keys and untrusted certificates
58    #[default]
59    Strict,
60    /// Try to import everything, may produce keychains without certificates
61    Relaxed,
62    /// Bundle mode: no linking, all entries may be independent
63    Raw,
64}
65
66/// KeyStore holds a dictionary of [KeyStoreEntry] instances indexed by aliases (names)
67#[derive(Debug, Clone, Default)]
68pub struct KeyStore {
69    entries: BTreeMap<String, KeyStoreEntry>,
70}
71
72impl KeyStore {
73    /// Create new empty keystore
74    pub fn new() -> Self {
75        Self::default()
76    }
77
78    /// Parse keystore from PKCS#12 data
79    pub fn from_pkcs12(data: &[u8], password: &str, policy: Pkcs12ImportPolicy) -> Result<Self> {
80        let pfx = Pfx::from_der(data)?;
81
82        if pfx.version != Version::V3 {
83            return Err(Error::InvalidVersion);
84        }
85
86        if let Some(mac_data) = pfx.mac_data {
87            codec::verify_mac(&mac_data, password, pfx.auth_safe.content.value())?;
88        }
89
90        let safes: AuthenticatedSafe = if pfx.auth_safe.content_type == oid::CONTENT_TYPE_DATA_OID {
91            AuthenticatedSafe::from_der(&OctetString::from_der(&pfx.auth_safe.content.to_der()?)?.into_bytes())?
92        } else {
93            return Err(Error::UnsupportedContentType);
94        };
95
96        let mut keystore = Self::new();
97
98        let mut parsed_keys = Vec::new();
99        let mut parsed_certs = Vec::new();
100        let mut parsed_secrets = Vec::new();
101
102        for safe in safes.into_iter() {
103            let ParsedAuthSafe { keys, certs, secrets } = codec::parse_auth_safe(&safe, password)?;
104            parsed_keys.extend(keys);
105            parsed_certs.extend(certs);
106            parsed_secrets.extend(secrets);
107        }
108
109        for key in parsed_keys {
110            let should_link = policy != Pkcs12ImportPolicy::Raw;
111            let cert_entry = if should_link {
112                parsed_certs.iter().find(|c| {
113                    c.local_key_id
114                        .as_ref()
115                        .is_some_and(|k| k.as_slice() == key.key.local_key_id.as_ref())
116                })
117            } else {
118                None
119            };
120
121            if let Some(mut entry) = cert_entry {
122                let alias = key
123                    .friendly_name
124                    .as_deref()
125                    .unwrap_or_else(|| entry.cert.subject.as_ref());
126
127                let mut certs = vec![entry.cert.clone()];
128                let leaf_cert = &entry.cert;
129
130                while let Some(issuer) = parsed_certs
131                    .iter()
132                    .find(|c| c.cert.subject == entry.cert.issuer && !c.trusted)
133                {
134                    // Avoid duplication of self-signed certs.
135                    if issuer.cert.subject != leaf_cert.subject {
136                        certs.push(issuer.cert.clone());
137                    }
138                    if issuer.cert.issuer == issuer.cert.subject {
139                        break;
140                    }
141                    entry = issuer;
142                }
143
144                let key_chain = PrivateKeyChain {
145                    key: key.key.key,
146                    local_key_id: key.key.local_key_id,
147                    certs,
148                };
149                keystore.add_entry(alias, KeyStoreEntry::PrivateKeyChain(key_chain));
150            } else if policy != Pkcs12ImportPolicy::Strict {
151                let alias = key
152                    .friendly_name
153                    .clone()
154                    .unwrap_or_else(|| key.key.local_key_id.encode_hex());
155
156                let key_chain = PrivateKeyChain {
157                    key: key.key.key,
158                    local_key_id: key.key.local_key_id,
159                    certs: vec![],
160                };
161                keystore.add_entry(&alias, KeyStoreEntry::PrivateKeyChain(key_chain));
162            }
163        }
164
165        for cert in parsed_certs {
166            let should_import = policy == Pkcs12ImportPolicy::Raw || (cert.local_key_id.is_none() && cert.trusted);
167
168            if should_import {
169                let alias: String = cert.friendly_name.clone().unwrap_or_else(|| cert.cert.subject.clone());
170                keystore.add_entry(&alias, KeyStoreEntry::Certificate(cert.cert));
171            }
172        }
173
174        for secret in parsed_secrets {
175            let alias = secret
176                .friendly_name
177                .clone()
178                .unwrap_or_else(|| secret.key.local_key_id.encode_hex());
179            keystore.add_entry(&alias, KeyStoreEntry::Secret(secret.key));
180        }
181
182        Ok(keystore)
183    }
184
185    /// Create a keystore writer with a given password to use for data encryption
186    pub fn writer<'a, 'b>(&'a self, password: &'b str) -> Pkcs12Writer<'a, 'b> {
187        // default values are taken from the JVM java.security config file
188        Pkcs12Writer {
189            keystore: self,
190            password,
191            encryption_algorithm: EncryptionAlgorithm::PbeWithHmacSha256AndAes256,
192            encryption_iterations: 10000,
193            mac_algorithm: MacAlgorithm::HmacSha256,
194            mac_iterations: 10000,
195        }
196    }
197
198    /// Get entries iterator
199    pub fn entries(&self) -> Entries<'_> {
200        let iter = self.entries.iter();
201        Entries {
202            iter,
203            len: self.entries.len(),
204        }
205    }
206
207    /// Get an entry for a given alias
208    pub fn entry(&self, alias: &str) -> Option<&KeyStoreEntry> {
209        self.entries.get(alias)
210    }
211
212    /// Get entries count in the keystore
213    pub fn entries_len(&self) -> usize {
214        self.entries.len()
215    }
216
217    /// Add a new entry to the keystore
218    pub fn add_entry(&mut self, alias: &str, entry: KeyStoreEntry) {
219        self.entries.insert(alias.to_owned(), entry);
220    }
221
222    /// Delete entry from the keystore
223    pub fn delete_entry(&mut self, alias: &str) -> Option<KeyStoreEntry> {
224        self.entries.remove(alias)
225    }
226
227    /// Rename entry in the keystore. If an entry with new alias already exists it will be replaced
228    pub fn rename_entry(&mut self, old_alias: &str, new_alias: &str) -> Option<&KeyStoreEntry> {
229        if let Some(old) = self.entries.remove(old_alias) {
230            self.entries.insert(new_alias.to_owned(), old);
231            self.entry(new_alias)
232        } else {
233            None
234        }
235    }
236
237    /// Get the first private keychain
238    pub fn private_key_chain(&self) -> Option<(&str, &PrivateKeyChain)> {
239        self.entries().find_map(|(alias, entry)| match entry {
240            KeyStoreEntry::PrivateKeyChain(chain) => Some((alias.as_str(), chain)),
241            _ => None,
242        })
243    }
244}
245
246/// Encryption algorithm to use when creating the PKCS#12 file
247#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
248#[non_exhaustive]
249pub enum EncryptionAlgorithm {
250    PbeWithHmacSha256AndAes256,
251    PbeWithShaAnd40BitRc4Cbc,
252    PbeWithShaAnd3KeyTripleDesCbc,
253}
254
255impl EncryptionAlgorithm {
256    pub(crate) fn to_oid(self) -> ObjectIdentifier {
257        match self {
258            EncryptionAlgorithm::PbeWithHmacSha256AndAes256 => oid::PBES2_OID,
259            EncryptionAlgorithm::PbeWithShaAnd40BitRc4Cbc => oid::PBE_WITH_SHA_AND_40BIT_RC2_CBC_OID,
260            EncryptionAlgorithm::PbeWithShaAnd3KeyTripleDesCbc => oid::PBE_WITH_SHA_AND3_KEY_TRIPLE_DES_CBC_OID,
261        }
262    }
263}
264
265/// MAC algorithm to use when creating the PKCS#12 file
266#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
267#[non_exhaustive]
268pub enum MacAlgorithm {
269    HmacSha1,
270    HmacSha256,
271}
272
273/// PKCS#12 writer
274pub struct Pkcs12Writer<'a, 'b> {
275    keystore: &'a KeyStore,
276    password: &'b str,
277    encryption_algorithm: EncryptionAlgorithm,
278    encryption_iterations: u32,
279    mac_algorithm: MacAlgorithm,
280    mac_iterations: u32,
281}
282
283impl Pkcs12Writer<'_, '_> {
284    /// Set an encryption algorithm. Default is [EncryptionAlgorithm::PbeWithHmacSha256AndAes256]
285    pub fn encryption_algorithm(mut self, algorithm: EncryptionAlgorithm) -> Self {
286        self.encryption_algorithm = algorithm;
287        self
288    }
289
290    /// Set the number of iterations for encryption key derivation. Default is 10,000.
291    pub fn encryption_iterations(mut self, iterations: u32) -> Self {
292        self.encryption_iterations = iterations;
293        self
294    }
295
296    /// Set MAC algorithm. Default is [MacAlgorithm::HmacSha256]
297    pub fn mac_algorithm(mut self, algorithm: MacAlgorithm) -> Self {
298        self.mac_algorithm = algorithm;
299        self
300    }
301
302    /// Set the number of iterations for MAC key derivation. Default is 10,000.
303    pub fn mac_iterations(mut self, iterations: u32) -> Self {
304        self.mac_iterations = iterations;
305        self
306    }
307
308    /// Write keystore into PKCS#12 format
309    pub fn write(self) -> Result<Vec<u8>> {
310        let mut cert_bags = Vec::new();
311
312        let certs = self.keystore.entries.iter().filter_map(|(alias, entry)| match entry {
313            KeyStoreEntry::PrivateKeyChain(_) => None,
314            KeyStoreEntry::Certificate(cert) => Some((alias, cert)),
315            KeyStoreEntry::Secret(_) => None,
316        });
317
318        for (alias, cert) in certs {
319            cert_bags.push(codec::certificate_to_safe_bag(cert, alias, None, true)?);
320        }
321
322        let chain_certs = self
323            .keystore
324            .entries
325            .values()
326            .filter_map(|entry| match entry {
327                KeyStoreEntry::PrivateKeyChain(chain) => Some(chain.certs.iter().enumerate().map(|(i, c)| {
328                    (
329                        if i == 0 {
330                            Some(chain.local_key_id.as_ref())
331                        } else {
332                            None
333                        },
334                        c,
335                    )
336                })),
337                KeyStoreEntry::Certificate(_) => None,
338                KeyStoreEntry::Secret(_) => None,
339            })
340            .flatten();
341
342        for (local_key_id, cert) in chain_certs {
343            cert_bags.push(codec::certificate_to_safe_bag(
344                cert,
345                &cert.subject,
346                local_key_id,
347                false,
348            )?);
349        }
350
351        let certs_safe = codec::cert_bags_to_auth_safe(
352            cert_bags,
353            self.encryption_algorithm,
354            self.encryption_iterations as i32,
355            self.password,
356        )?;
357
358        let private_keys = self.keystore.entries.iter().filter_map(|(alias, entry)| match entry {
359            KeyStoreEntry::PrivateKeyChain(chain) => Some((alias, chain)),
360            KeyStoreEntry::Certificate(_) => None,
361            KeyStoreEntry::Secret(_) => None,
362        });
363
364        let mut key_bags = Vec::new();
365
366        for (alias, chain) in private_keys {
367            key_bags.push(codec::private_key_to_safe_bag(
368                chain,
369                alias,
370                self.encryption_algorithm,
371                self.encryption_iterations as i32,
372                self.password,
373            )?);
374        }
375
376        let keys_safe = codec::key_bags_to_auth_safe(key_bags)?;
377
378        let mut safes = vec![certs_safe, keys_safe];
379
380        let secrets = self
381            .keystore
382            .entries
383            .iter()
384            .filter_map(|(alias, entry)| match entry {
385                KeyStoreEntry::PrivateKeyChain(_) => None,
386                KeyStoreEntry::Certificate(_) => None,
387                KeyStoreEntry::Secret(secret) => {
388                    let bag = secret_to_safe_bag(
389                        secret,
390                        self.encryption_algorithm,
391                        alias,
392                        self.encryption_iterations as i32,
393                        self.password,
394                    );
395                    Some(bag)
396                }
397            })
398            .flatten();
399
400        for secret in secrets {
401            safes.push(codec::key_bags_to_auth_safe(vec![secret])?)
402        }
403
404        let safe_bags = OctetString::new(safes.to_der()?)?;
405        let auth_safe = ContentInfo {
406            content_type: oid::CONTENT_TYPE_DATA_OID,
407            content: Any::from_der(&safe_bags.to_der()?)?,
408        };
409
410        let mac_data = codec::compute_mac(
411            auth_safe.content.value(),
412            self.mac_algorithm,
413            self.mac_iterations as i32,
414            self.password,
415        )?;
416
417        let pfx = Pfx {
418            version: Version::V3,
419            auth_safe,
420            mac_data: Some(mac_data),
421        };
422
423        Ok(pfx.to_der()?)
424    }
425}