pub struct CertStore {
dir: PathBuf,
account_dir: PathBuf,
}Expand description
Where this crate’s own state lives inside a PDS data directory: a tls/
subdirectory, so it sits beside the write-ahead log and blob store rather
than mixed into either.
§Two directories, because two things have different scopes
The ACME account is one account per deployment however many zones it
serves — orders are per certificate, registration is not — so
acme-account.json stays at the tls/ root. A certificate covers
exactly one zone (<zone> plus *.<zone>, and a wildcard matches one
label, so a second zone needs a second certificate), so each zone’s
certificate and key live in tls/zones/<zone>/. CertStore::open_zone
is the constructor that splits them; CertStore::open points both at
the root and is what a caller with no zone at all — a test, or a tool
only touching the account — wants.
Fields§
§dir: PathBuf§account_dir: PathBufImplementations§
Source§impl CertStore
impl CertStore
Sourcepub fn open(data_dir: &Path) -> Result<Self, StorageError>
pub fn open(data_dir: &Path) -> Result<Self, StorageError>
Opens <data_dir>/tls, creating it 0700 if absent and tightening it
to 0700 if it already existed.
Sourcepub fn open_zone(data_dir: &Path, zone: &str) -> Result<Self, StorageError>
pub fn open_zone(data_dir: &Path, zone: &str) -> Result<Self, StorageError>
Opens <data_dir>/tls/zones/<zone> for zone’s certificate, with the
ACME account still read from and written to <data_dir>/tls.
One directory per zone is what makes a per-zone certificate pair
possible at all: two zones’ cert.pem files cannot share a path, and
the alternative — one file whose contents depend on which zone was
bootstrapped last — is the bug this layout exists to make
unrepresentable.
A zone hostname is a validated DNS name by the time it reaches here,
but this checks anyway rather than joining an attacker-controlled
string onto a path: anything with a separator, a .., or nothing in
it at all is refused.
Sourcepub fn account_credentials(&self) -> Result<Option<StoredAccount>, StorageError>
pub fn account_credentials(&self) -> Result<Option<StoredAccount>, StorageError>
The ACME account last saved here, if any: the credentials, and the directory they were registered against.
Sourcepub fn save_account_credentials(
&self,
credentials: &AccountCredentials,
) -> Result<(), StorageError>
pub fn save_account_credentials( &self, credentials: &AccountCredentials, ) -> Result<(), StorageError>
Persists ACME account credentials, 0600.
Sourcepub fn certificate(
&self,
) -> Result<Option<(String, String, CertMeta)>, StorageError>
pub fn certificate( &self, ) -> Result<Option<(String, String, CertMeta)>, StorageError>
The certificate chain, key and metadata last saved here, if any.
Sourcepub fn save_certificate(
&self,
cert_pem: &str,
key_pem: &str,
meta: &CertMeta,
) -> Result<(), StorageError>
pub fn save_certificate( &self, cert_pem: &str, key_pem: &str, meta: &CertMeta, ) -> Result<(), StorageError>
Persists a certificate chain, its key and metadata, each 0600.
All three are staged beside their final paths before any is renamed
into place (see Staged), so a save that fails leaves the previous
set untouched, and a crash leaves every file whole: its old contents
or its new ones, never empty or cut short. meta.json is renamed
last, so its presence means the key and certificate beside it were
both written in full. The renames are three steps, not one: a crash
between them can leave a new key beside the old certificate, or a
certificate with no meta.json. The next bootstrap finds that set
unusable and orders a new certificate.