Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 33 additions & 11 deletions bin/ca-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,17 @@ enum Command {
#[command(subcommand)]
action: RaAction,
},
/// Révoque un certificat émis et republie la CRL.
/// Révoque un certificat émis et republie la CRL. Voie de secours : la
/// voie primaire est l'action signée (`revoke_certificate`, docs/WEBUI.md §20).
Revoke {
/// Numéro de série en hexadécimal (voir `ra list`, le journal d'audit).
serial_hex: String,
/// Code motif RFC 5280 §5.3.1 (1=keyCompromise, 4=superseded, 5=cessationOfOperation, ...).
reason: i32,
operator: String,
#[arg(trailing_var_arg = true)]
/// Motif de la décision, obligatoire : voie de secours, l'opérateur
/// n'est que déclaré (docs/WEBUI.md §20).
#[arg(trailing_var_arg = true, required = true)]
comment: Vec<String>,
},
/// Actes de la racine hors ligne (constat C-1) : révocation d'une autorité
Expand Down Expand Up @@ -94,7 +97,9 @@ enum AuthorityAction {
/// 4=superseded, 5=cessationOfOperation, ...).
reason: i32,
operator: String,
#[arg(trailing_var_arg = true)]
/// Motif de la décision, obligatoire : voie de secours, l'opérateur
/// n'est que déclaré (docs/WEBUI.md §20).
#[arg(trailing_var_arg = true, required = true)]
comment: Vec<String>,
},
/// Publie une nouvelle ARL, même vide : à relancer avant l'échéance de la
Expand Down Expand Up @@ -190,18 +195,24 @@ enum OperatorsAction {
enum RaAction {
/// Liste les demandes d'enrôlement.
List { state: Option<String> },
/// Approuve une demande, sous l'identité d'un opérateur.
/// Approuve une demande, sous l'identité déclarée d'un opérateur (voie de
/// secours, consignée `authenticated_via: cli` avec l'identité système).
Approve {
transaction_id: String,
operator: String,
#[arg(trailing_var_arg = true)]
/// Motif de la décision, obligatoire : voie de secours, l'opérateur
/// n'est que déclaré (docs/WEBUI.md §20).
#[arg(trailing_var_arg = true, required = true)]
comment: Vec<String>,
},
/// Rejette une demande, sous l'identité d'un opérateur.
/// Rejette une demande, sous l'identité déclarée d'un opérateur (voie de
/// secours, consignée `authenticated_via: cli` avec l'identité système).
Reject {
transaction_id: String,
operator: String,
#[arg(trailing_var_arg = true)]
/// Motif de la décision, obligatoire : voie de secours, l'opérateur
/// n'est que déclaré (docs/WEBUI.md §20).
#[arg(trailing_var_arg = true, required = true)]
comment: Vec<String>,
},
}
Expand Down Expand Up @@ -481,8 +492,10 @@ async fn run_authority(action: AuthorityAction) {
comment,
} => {
let comment = join_comment(&comment);
// Voie de secours (docs/WEBUI.md §20, constat R-1), comme `revoke`.
let via = oe_ca_core::Via::Cli(oe_ca_core::SystemIdentity::current());
let arl = root
.revoke_authority(&name, reason, &operator, &comment)
.revoke_authority(&name, reason, &operator, &comment, &via)
.await
.unwrap_or_else(|e| die("révocation de l'autorité", e));
tracing::info!(autorite = %name, motif = reason, operateur = %operator, arl = arl.number, "autorité révoquée et ARL republiée");
Expand Down Expand Up @@ -839,10 +852,17 @@ async fn run_decide(
});

let comment = join_comment(&comment);
// Voie de secours (docs/WEBUI.md §20, constat R-1) : l'opérateur n'est que
// déclaré ; le journal le marque comme tel, avec l'identité système réelle.
let via = oe_raflow::Via::Cli(oe_raflow::SystemIdentity::current());
let r = if action_name == "approve" {
decider.approve(&transaction_id, &operator, &comment).await
decider
.approve(&transaction_id, &operator, &comment, &via)
.await
} else {
decider.reject(&transaction_id, &operator, &comment).await
decider
.reject(&transaction_id, &operator, &comment, &via)
.await
};
let r = r.unwrap_or_else(|e| die("décision RA", e));
tracing::info!(action = action_name, transaction = %r.transaction_id, profil = %r.profile, sujet_cn = %r.subject_cn, operateur = %operator, "décision enregistrée");
Expand All @@ -859,8 +879,10 @@ async fn run_revoke(serial_hex: String, reason: i32, operator: String, comment:
let issuer = build_issuer(&cfg, store, recorder).await;

let comment = join_comment(&comment);
// Voie de secours (docs/WEBUI.md §20, constat R-1), comme `ra approve`.
let via = oe_ca_core::Via::Cli(oe_ca_core::SystemIdentity::current());
issuer
.revoke(&serial, reason, &operator, &comment)
.revoke(&serial, reason, &operator, &comment, &via)
.await
.unwrap_or_else(|e| die("révocation", e));
// La CRL est republiée immédiatement : une révocation qui n'est pas
Expand Down
12 changes: 10 additions & 2 deletions bin/ca-server/src/revoker.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! Branche `oe_ca_core::Issuer` sur l'action signée de révocation de
//! certificat (`oe_actions::Revoker`).
//! certificat (`oe_actions::Revoker`). Ce branchement ne sert qu'à elle :
//! toute révocation qui passe ici a été signée par WebAuthn, d'où
//! `Via::WebAuthn` (constat R-1).

use std::sync::Arc;

Expand All @@ -17,7 +19,13 @@ impl oe_actions::Revoker for IssuerRevoker {
comment: &str,
) -> Result<(), String> {
self.0
.revoke(serial, reason, operator, comment)
.revoke(
serial,
reason,
operator,
comment,
&oe_ca_core::Via::WebAuthn,
)
.await
.map_err(|e| e.to_string())
}
Expand Down
8 changes: 7 additions & 1 deletion bin/ca-server/tests/arl_publication.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,13 @@ async fn the_arl_is_served_where_the_issuing_cdp_points_and_reflects_revocation(
// relit en base, sans redémarrage.
let revoked = f
.root_authority
.revoke_authority(AUTHORITY_ISSUING, 2, "test-operator", "compromission")
.revoke_authority(
AUTHORITY_ISSUING,
2,
"test-operator",
"compromission",
&oe_ca_core::Via::WebAuthn,
)
.await
.unwrap();
let (status, body) = get(&f.server, &path).await;
Expand Down
8 changes: 7 additions & 1 deletion bin/ca-server/tests/internal_tls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,13 @@ async fn revoking_the_client_certificate_cuts_access_at_the_next_connection() {

let serial = oe_ca_core::canonical_serial(cert.tbs_certificate().serial_number());
pki.issuer
.revoke(&serial, 1, "operateur-test", "clé compromise")
.revoke(
&serial,
1,
"operateur-test",
"clé compromise",
&oe_ca_core::Via::WebAuthn,
)
.await
.unwrap();
assert!(!ok(&get_ping(&pki, port, Some((&cert, &key))).await));
Expand Down
8 changes: 7 additions & 1 deletion bin/ra-console/tests/ca_link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,13 @@ async fn revoking_the_client_certificate_cuts_the_link() {

let serial = oe_ca_core::canonical_serial(client.0.tbs_certificate().serial_number());
pki.issuer
.revoke(&serial, 1, "operateur-test", "clé compromise")
.revoke(
&serial,
1,
"operateur-test",
"clé compromise",
&oe_ca_core::Via::WebAuthn,
)
.await
.unwrap();
let err = link.ping().await.expect_err("certificat révoqué");
Expand Down
14 changes: 12 additions & 2 deletions crates/oe-actions/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -858,7 +858,12 @@ impl Service {
..
} => self
.decider
.approve(transaction_id, &operator.name, comment)
.approve(
transaction_id,
&operator.name,
comment,
&oe_raflow::Via::WebAuthn,
)
.await
.map(|_| None)
.map_err(|e| Error::Effect(e.to_string()))?,
Expand All @@ -867,7 +872,12 @@ impl Service {
comment,
} => self
.decider
.reject(transaction_id, &operator.name, comment)
.reject(
transaction_id,
&operator.name,
comment,
&oe_raflow::Via::WebAuthn,
)
.await
.map(|_| None)
.map_err(|e| Error::Effect(e.to_string()))?,
Expand Down
7 changes: 6 additions & 1 deletion crates/oe-actions/tests/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,12 @@ async fn a_request_decided_meanwhile_is_not_overwritten() {

// Un autre chemin (le CLI de secours) rejette la demande entre-temps.
env.decider
.reject(&r.transaction_id, "cli", "refusée par le secours")
.reject(
&r.transaction_id,
"cli",
"refusée par le secours",
&oe_raflow::Via::Cli(oe_raflow::SystemIdentity::current()),
)
.await
.unwrap();

Expand Down
29 changes: 16 additions & 13 deletions crates/oe-ca-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

mod extensions;
pub mod profile;
pub mod provenance;
pub mod root;
mod signing;

Expand All @@ -41,6 +42,7 @@ use oe_castore::{Store, StoreError};
use oe_hsm::SigningToken;

pub use profile::{profile_by_name, Profile};
pub use provenance::{SystemIdentity, Via};

/// Consigne les décisions de l'autorité au journal d'audit — reproduit
/// `ca.Recorder` (Go) ; comme `oe_tsa_core::Recorder`, découplé de
Expand Down Expand Up @@ -411,37 +413,38 @@ impl Issuer {
}

/// Révoque un certificat émis par cette autorité et consigne la
/// décision. La CRL n'est pas republiée ici : `publish_crl` la reprend.
/// décision, avec la voie par laquelle son opérateur a été identifié
/// (constat R-1). La CRL n'est pas republiée ici : `publish_crl` la reprend.
pub async fn revoke(
&self,
serial: &[u8],
reason: i32,
operator: &str,
comment: &str,
via: &Via,
) -> Result<(), CaError> {
if operator.is_empty() {
return Err(CaError::Other(
"la révocation exige l'identité de l'opérateur qui la décide".to_string(),
));
}
via.check_comment(comment).map_err(CaError::Other)?;
let cert = self.opts.store.certificate(&serial.to_vec()).await?;
let at = time::OffsetDateTime::now_utc();

// Le journal *avant* la révocation en base (§15 étape 2b) : si
// l'écriture échoue, le certificat reste actif — pas de révocation
// à moitié consignée.
self.record(
"ca.certificate_revoked",
serde_json::json!({
"serie": hex::encode(serial),
"sujet": cert.subject_dn,
"motif": reason,
"operateur": operator,
"commentaire": comment,
"date": at.format(&time::format_description::well_known::Rfc3339).unwrap_or_default(),
}),
)
.await?;
let mut data = serde_json::json!({
"serie": hex::encode(serial),
"sujet": cert.subject_dn,
"motif": reason,
"operateur": operator,
"commentaire": comment,
"date": at.format(&time::format_description::well_known::Rfc3339).unwrap_or_default(),
});
via.annotate(&mut data);
self.record("ca.certificate_revoked", data).await?;
self.opts.store.revoke(&serial.to_vec(), at, reason).await?;

Ok(())
Expand Down
Loading
Loading