Skip to content

Commit

Permalink
Add reuse_private_key option
Browse files Browse the repository at this point in the history
  • Loading branch information
krtab committed Dec 7, 2024
1 parent f002ab9 commit 3e169be
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 12 deletions.
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ private_key_path = "priv_key.pem"

### 3. Certificates

For each account, several certificates can be ordered. Each certificate can cover multiple domains. On disk, a certificate is represented by two files: the full certificate chain, and the private key of the certificate (generated by agnos and different from the account private key).
For each account, several certificates can be ordered. Each certificate can cover multiple domains. On disk, a certificate is represented by two files: the full certificate chain, and the private key of the certificate (different from the account private key). This certificate private key is regenerated on each certificate renewal by default but if one is already present on disk, it can be reused by setting the `reuse_private_key` option to true
In the configuration file, `accounts.certificates` is a TOML [array of tables](https://toml.io/en/v1.0.0#array-of-tables) meaning that several certificates can be attached to one account by writing them one after the other.

```toml
Expand All @@ -181,14 +181,22 @@ In the configuration file, `accounts.certificates` is a TOML [array of tables](h
domains = ["doma.in","*.doma.in"]
fullchain_output_file = "fullchain_A.pem"
key_output_file = "cert_key_A.pem"
renewal_days_advance = 30 # Renew certificate 30 days in advance of its expiration (this is the default value and can be omitted).
# Renew certificate 30 days in advance of its expiration
# (this is the default value and can be omitted).
renewal_days_advance = 30
# Regenerate a private key for the certificate on each renewal
# (this is the default value and can be omitted).
reuse_private_key = false

# A second certificate ordered for that account.
[[accounts.certificates]]
renewal_days_advance = 21 # Renew certificate 21 days in advance of its expiration.
domains = ["examp.le","another.examp.le","and.a.completely.different.one"]
fullchain_output_file = "fullchain_B.pem"
key_output_file = "cert_key_B.pem"
# Re-use the existing private key.
# If no key is present at `key_output_file`, a new one will be generated.
reuse_private_key = true
```

## Configuration of your DNS provider
Expand Down
3 changes: 3 additions & 0 deletions config_example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ fullchain_output_file = "fullchain_B.pem"
key_output_file = "cert_key_B.pem"
# Renew certificate 21 days in advance of its expiration (defaults to 30 if omitted).
renewal_days_advance = 21
# Re-use the existing private key.
# If no key is present at `key_output_file`, a new one will be generated.
reuse_private_key = true

# A second account
[[accounts]]
Expand Down
2 changes: 2 additions & 0 deletions integration-testing/agnos/config_test.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ private_key_path = "priv_key_1.pem"
domains = ["a.agnos.test"]
fullchain_output_file = "fullchain_A.pem"
key_output_file = "cert_key_A.pem"
reuse_private_key = false

# A second certificate ordered for that account.
[[accounts.certificates]]
domains = ["b.agnos.test","*.b.agnos.test"]
fullchain_output_file = "fullchain_B.pem"
key_output_file = "cert_key_B.pem"
reuse_private_key = true

# A second account
[[accounts]]
Expand Down
2 changes: 2 additions & 0 deletions integration-testing/shell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ let
$OLDWORKDIR/$CARGO_TARGET_DIR/release/agnos-generate-accounts-keys --key-size 2048 --no-confirm ${agnos_config}
bash ${wait_for_it} -t 0 127.0.0.1:14000
$OLDWORKDIR/$CARGO_TARGET_DIR/release/agnos --debug --acme-url https://127.0.0.1:14000/dir --acme-serv-ca ${pebble_cert} ${agnos_config}
# Purposefully duplicated to test renewal
$OLDWORKDIR/$CARGO_TARGET_DIR/release/agnos --debug --acme-url https://127.0.0.1:14000/dir --acme-serv-ca ${pebble_cert} ${agnos_config}
cd $OLDWORKDIR
rm -rf $WORKDIR
'';
Expand Down
2 changes: 2 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ pub struct Certificate {
pub domains: Vec<String>,
pub fullchain_output_file: PathBuf,
pub key_output_file: PathBuf,
#[serde(default)]
pub reuse_private_key: bool,
}

const fn default_days() -> u32 {
Expand Down
47 changes: 37 additions & 10 deletions src/main_logic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use base64::Engine;
use futures_util::future::join_all;

use hickory_proto::rr::Name;
use openssl::pkey::PKey;
use std::io;
use std::path::Path;
use std::{sync::Arc, time::Duration};
Expand Down Expand Up @@ -237,8 +238,32 @@ pub async fn process_config_certificate(
order.status
)
}
let pkey = acme2::gen_ec_p256_private_key()?;
let pkey_pem = pkey.private_key_to_pem_pkcs8()?;

let (pkey, pkey_pem, loaded_pkey) = {
let existing_pkey_pem = if config_cert.reuse_private_key {
let loaded = try_load(&config_cert.key_output_file).await?;
if loaded.is_none() {
tracing::info!(
"Couldn't load certificate private key at {}, generating one.",
config_cert.key_output_file.display()
)
}
loaded
} else {
None
};
match existing_pkey_pem {
Some(pkey_pem) => {
let pkey = PKey::private_key_from_pem(&pkey_pem)?;
(pkey, pkey_pem, true)
}
None => {
let pkey = acme2::gen_ec_p256_private_key()?;
let pem = pkey.private_key_to_pem_pkcs8()?;
(pkey, pem, false)
}
}
};
let order = order.finalize(acme2::Csr::Automatic(pkey)).await?;
tracing::info!("Waiting for certificate signature by the ACME server.");
let order = order.wait_done(Duration::from_secs(5), 3).await?;
Expand Down Expand Up @@ -267,14 +292,16 @@ pub async fn process_config_certificate(
certificate_file.write_all(b"\n").await?;
}
}
tracing::info!(
"Writting certificate key to file {}.",
config_cert.key_output_file.display()
);
{
let mut private_key_file: tokio::fs::File =
create_restricted_file(&config_cert.key_output_file)?;
private_key_file.write_all(&pkey_pem).await?;
if !loaded_pkey {
tracing::info!(
"Writting certificate key to file {}.",
config_cert.key_output_file.display()
);
{
let mut private_key_file: tokio::fs::File =
create_restricted_file(&config_cert.key_output_file)?;
private_key_file.write_all(&pkey_pem).await?;
}
}
Ok(())
}

0 comments on commit 3e169be

Please sign in to comment.