1use std::fmt;
5use std::panic::{AssertUnwindSafe, catch_unwind};
6use std::path::Path;
7
8use zeroize::Zeroizing;
9
10use super::{Certificate, KeyError, PrivateKey, SigningIdentity};
11use crate::error::{Error, FileOperation};
12
13const JKS_MAGIC: [u8; 4] = [0xfe, 0xed, 0xfe, 0xed];
14
15enum Store {
16 Jks { store: jks::KeyStore, password: Zeroizing<String> },
17 Pkcs12 { store: p12_keystore::KeyStore, password: Zeroizing<String> },
18}
19
20pub struct Keystore(Store);
30
31impl fmt::Debug for Keystore {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 let format = match self.0 {
34 Store::Jks { .. } => "JKS",
35 Store::Pkcs12 { .. } => "PKCS#12",
36 };
37 f.debug_struct("Keystore").field("format", &format).field("aliases", &self.aliases()).finish()
38 }
39}
40
41impl Keystore {
42 pub fn open(path: impl AsRef<Path>, password: &str) -> Result<Self, Error> {
44 let path = path.as_ref();
45 let bytes = Zeroizing::new(std::fs::read(path).map_err(Error::file(FileOperation::Read, path))?);
46 Ok(Self::from_bytes(&bytes, password)?)
47 }
48
49 pub fn from_bytes(bytes: &[u8], password: &str) -> Result<Self, KeyError> {
51 let parsed = catch_unwind(AssertUnwindSafe(|| -> Result<Self, KeyError> {
53 if bytes.starts_with(&JKS_MAGIC) {
54 let mut store = jks::KeyStore::new();
55 store.load(bytes, password.as_bytes())?;
56 Ok(Self(Store::Jks { store, password: Zeroizing::new(password.to_owned()) }))
57 } else {
58 let policy = p12_keystore::Pkcs12ImportPolicy::Strict;
59 let store = p12_keystore::KeyStore::from_pkcs12(bytes, password, policy)?;
60 Ok(Self(Store::Pkcs12 { store, password: Zeroizing::new(password.to_owned()) }))
61 }
62 }));
63 parsed.map_err(|_| KeyError::MalformedKeystore)?
64 }
65
66 pub fn aliases(&self) -> Vec<String> {
68 match &self.0 {
69 Store::Jks { store, .. } => store.aliases(),
70 Store::Pkcs12 { store, .. } => store.entries().map(|(alias, _)| alias.clone()).collect(),
71 }
72 }
73
74 pub fn identity(&self, alias: &str, key_password: Option<&str>) -> Result<SigningIdentity, KeyError> {
78 let unknown = || KeyError::UnknownAlias { alias: alias.to_owned() };
79 let (key, chain) = match &self.0 {
80 Store::Jks { store, password } => {
81 if !store.is_private_key_entry(alias) {
82 return Err(if store.aliases().iter().any(|a| a == alias) {
83 KeyError::NotAPrivateKey { alias: alias.to_owned() }
84 } else {
85 unknown()
86 });
87 }
88 let password = key_password.unwrap_or(password);
89 let entry = store.get_private_key_entry(alias, password.as_bytes())?;
90 let chain: Vec<Vec<u8>> = entry.certificate_chain.into_iter().map(|c| c.content).collect();
91 (PrivateKey::from_pkcs8_der(&entry.private_key)?, chain)
92 }
93 Store::Pkcs12 { store, password } => {
94 if key_password.is_some_and(|p| p != password.as_str()) {
95 return Err(KeyError::WrongPassword);
96 }
97 match store.entry(alias).ok_or_else(unknown)? {
98 p12_keystore::KeyStoreEntry::PrivateKeyChain(chain) => (
99 PrivateKey::from_pkcs8_der(chain.key().as_der())?,
100 chain.certs().iter().map(|c| c.as_der().to_vec()).collect(),
101 ),
102 _ => return Err(KeyError::NotAPrivateKey { alias: alias.to_owned() }),
103 }
104 }
105 };
106 if chain.is_empty() {
107 return Err(KeyError::MissingCertificate);
108 }
109 let chain = chain.iter().map(|der| Certificate::from_der(der)).collect::<Result<_, _>>()?;
110 SigningIdentity::new(key, chain)
111 }
112
113 pub fn sole_identity(&self, key_password: Option<&str>) -> Result<SigningIdentity, KeyError> {
115 let aliases: Vec<String> = self.aliases().into_iter().filter(|alias| self.is_private_key(alias)).collect();
116 match aliases.as_slice() {
117 [alias] => self.identity(alias, key_password),
118 _ => Err(KeyError::AmbiguousAlias { count: aliases.len() }),
119 }
120 }
121
122 fn is_private_key(&self, alias: &str) -> bool {
123 match &self.0 {
124 Store::Jks { store, .. } => store.is_private_key_entry(alias),
125 Store::Pkcs12 { store, .. } => {
126 matches!(store.entry(alias), Some(p12_keystore::KeyStoreEntry::PrivateKeyChain(_)))
127 }
128 }
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 const P12: &[u8] = include_bytes!("../../tests/fixtures/keys/test-only.p12");
137
138 #[test]
139 fn unlocks_pkcs12_created_by_keytool() {
140 let keystore = Keystore::from_bytes(P12, "android-test").unwrap();
141 assert_eq!(keystore.aliases(), ["postcard"]);
142 assert!(keystore.identity("postcard", None).is_ok());
143 assert!(keystore.sole_identity(None).is_ok());
144 assert!(matches!(keystore.identity("missing", None), Err(KeyError::UnknownAlias { .. })));
145 assert!(matches!(keystore.identity("postcard", Some("other")), Err(KeyError::WrongPassword)));
146 assert!(Keystore::from_bytes(P12, "wrong").is_err());
147 }
148
149 #[test]
150 fn unlocks_jks_with_separate_key_password() {
151 let identity = SigningIdentity::from_pem(include_str!("../../tests/fixtures/keys/debug.pem")).unwrap();
152 let key = identity.key().to_pkcs8_der().unwrap();
153 let mut store = jks::KeyStore::new();
154 let entry = jks::PrivateKeyEntry {
155 creation_time: std::time::UNIX_EPOCH,
156 private_key: key.to_vec(),
157 certificate_chain: vec![jks::Certificate {
158 cert_type: "X509".into(),
159 content: identity.certificate().der().to_vec(),
160 }],
161 };
162 store.set_private_key_entry("release", entry, b"key-pass").unwrap();
163 let mut bytes = Vec::new();
164 store.store(&mut bytes, b"store-pass").unwrap();
165
166 let keystore = Keystore::from_bytes(&bytes, "store-pass").unwrap();
167 let loaded = keystore.identity("release", Some("key-pass")).unwrap();
168 assert_eq!(loaded.certificate(), identity.certificate());
169 assert!(keystore.identity("release", None).is_err());
170 }
171}