Skip to content

Commit 26c14c1

Browse files
committed
fix: address Copilot review comments on PR #991
- dispatch: make check_role_permission async; remove dispatch-level blocking for KmipOperation-mapped ops (handlers enforce crypto_officer_users + explicit grants); LIFECYCLE_OPERATION_TAGS now allow through if user has explicit Create grant in DB -- fixes regression in test_crypto_officer_users - access: role_for() only returns Administrator when require_ceremony=false; ceremony candidates are not elevated at dispatch before ceremony completes - retrieve_object_utils: Administrator bypass limited to non-HSM objects - join_split_key: add split_key_method validation; add crypto_officer check before database.create(); fix doc comment wording - database_objects: find_all() propagates errors instead of swallowing - locate_query: rewrite query_all_from_attributes with full attribute filters - permissions_store: update revoke_administrator_activation doc contract - routes/access: fix config key reference in error msg (administrator_users) - documentation: replace Super-admin section with correct Administrator role docs - remove dead super_admin_config.rs file - CHANGELOG: fix log names and claims about rename completion
1 parent 58fb0b1 commit 26c14c1

11 files changed

Lines changed: 354 additions & 154 deletions

File tree

CHANGELOG/feat_split_key.md

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77
(Shamir 1979, Blakley 1979), normative references (NIST SP 800-57 §4.3.2–§4.3.3, FIPS 140-3 §7.4,
88
ANSI/INCITS 359-2004 §4.3.1), role operation tables, 4-phase Mermaid sequence diagrams, and
99
permission evaluation flowchart. Registered in `documentation/mkdocs.yml`.
10-
- `documentation/docs/configuration/authorization.md`: added **Super-admin role** section
11-
covering configuration, activation modes, ceremony flow, disable procedures, REST endpoints,
10+
- `documentation/docs/configuration/authorization.md`: replaced stale **Super-admin role** section
11+
with correct **Administrator role** section covering `administrator_*` config keys, activation
12+
modes, ceremony flow, disable procedures, actual REST endpoints (`/access/administrator/*`),
1213
and updated permission evaluation order table.
1314

1415
## Refactor
@@ -19,21 +20,25 @@
1920
ownership bypass (access any object without grant) in one coherent concept.
2021
References: NIST SP 800-57 Part 2 Rev 1 §4.3.2 (Dual Control), ANSI/INCITS 359-2004 §4.2.
2122
- **Renamed `super_admin``administrator` across the entire stack**: DB trait methods,
22-
route paths, ceremony attributes, config fields, test infra, SQL table (`super_admin_activations`
23-
`administrator_activations`), SQL query names, test vector directories, and config files.
24-
No remaining `super_admin`/`SuperAdmin`/`super-admin` references in `crate/` or `test_data/`.
23+
route paths, ceremony attributes, config fields, test infra, SQL table
24+
(`administrator_activations`), SQL query names, test vector directories, and config files.
25+
Removed residual `SuperAdminConfig` dead code (`super_admin_config.rs`).
2526

2627
## Security
2728

28-
- Super-admin access is audit-logged at `WARN` level (`SUPER_ADMIN_ACCESS`, `SUPER_ADMIN_DISABLED`).
29+
- Administrator access is audit-logged at `WARN` level (`ADMINISTRATOR_ACCESS`, `ADMINISTRATOR_DISABLED`).
2930
- Ceremony activation record stores a SHA-256 fingerprint of the reconstructed secret.
3031
- Config-only mode provides always-on access; ceremony mode provides defence-in-depth.
3132
- **Fail-secure unenrolled users**: when role enforcement is active, users not listed in any
3233
role now default to Operator (minimum privilege) instead of being unrestricted.
3334
Per NIST SP 800-57 Part 2 §4.4.2 (need-to-know / minimum necessary).
34-
- **Ceremony initiator check**: `JoinSplitKey` now verifies that all share owners are in
35-
`administrator.users` before activating the Administrator ceremony. Prevents non-Admins
36-
from bootstrapping a ceremony.
35+
- **Ceremony initiator check**: `JoinSplitKey` verifies the **assembling user** is in
36+
`administrator_users` before activating the Administrator ceremony.
37+
- **Administrator bypass excludes HSM-backed keys**: HSM objects continue to use their own
38+
admin rules; the Administrator bypass applies only to non-HSM Managed Objects.
39+
- **`role_for()` ceremony guard**: when `administrator_require_ceremony = true`, ceremony
40+
candidates are not granted Administrator privileges at dispatch time until the DB-backed
41+
`is_administrator()` check confirms the ceremony has been completed.
3742
- **Role separation validation**: `RoleConfig::validate()` rejects configs where a user
3843
appears in multiple role lists (FIPS 140-3 §7.4 separation of duty).
3944

crate/access/src/access.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,9 +231,19 @@ impl RoleConfig {
231231
/// Returns `None` when the user is not listed in any role **and** role enforcement
232232
/// is not active. When role enforcement is active, callers must treat `None` as
233233
/// [`Role::Operator`] (fail-secure default).
234+
///
235+
/// **Note**: when `administrator.require_ceremony = true`, users in `administrator.users`
236+
/// are candidates but are NOT returned as [`Role::Administrator`] here — they default to
237+
/// [`Role::Operator`] until the ceremony activates. The async [`KMS::is_administrator()`]
238+
/// method performs the DB-backed activation check for ownership bypass.
234239
#[must_use]
235240
pub fn role_for(&self, user: &str) -> Option<Role> {
236-
if self.administrator.users.iter().any(|x| x == user) {
241+
// Only grant Administrator at the dispatch level when ceremony is NOT required
242+
// (config-only mode). When ceremony is required, the role is dormant until the
243+
// DB-backed is_administrator() check confirms activation.
244+
if !self.administrator.require_ceremony
245+
&& self.administrator.users.iter().any(|x| x == user)
246+
{
237247
return Some(Role::Administrator);
238248
}
239249
if self

crate/interfaces/src/stores/permissions_store.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ pub trait PermissionsStore {
7373
async fn is_administrator_activated(&self) -> InterfaceResult<bool>;
7474

7575
/// Revoke the active ceremony record (set `revoked_at` to now).
76-
/// Returns an error if no active record exists.
76+
/// No-op if no active record exists (the SQL implementations do not check
77+
/// affected row count — this is intentional for idempotent disable calls).
7778
async fn revoke_administrator_activation(&self, revoked_by: &str) -> InterfaceResult<()>;
7879
}

crate/server/src/config/command_line/super_admin_config.rs

Lines changed: 0 additions & 60 deletions
This file was deleted.

crate/server/src/core/operations/dispatch.rs

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use crate::{
2121
algorithm_policy::enforce_kmip_algorithm_policy_for_operation,
2222
attributes::get_attribute_list, check, mac::mac_verify, query::query as query_op,
2323
},
24+
retrieve_object_utils::user_has_permission,
2425
},
2526
error::KmsError,
2627
kms_bail,
@@ -83,7 +84,8 @@ macro_rules! op {
8384
/// Map a TTLV operation tag string to a [`KmipOperation`] variant for role-based access control.
8485
///
8586
/// Operations not present in the [`KmipOperation`] enum (e.g. `CreateKeyPair`, `CreateSplitKey`,
86-
/// `JoinSplitKey`, `Register`, `ReKeyKeyPair`) return `None` and are **not** role-gated.
87+
/// `JoinSplitKey`, `Register`, `ReKeyKeyPair`) return `None` here but may still be
88+
/// gated via [`LIFECYCLE_OPERATION_TAGS`].
8789
fn operation_tag_to_kmip_operation(tag: &str) -> Option<KmipOperation> {
8890
match tag {
8991
"Activate" => Some(KmipOperation::Activate),
@@ -113,17 +115,49 @@ fn operation_tag_to_kmip_operation(tag: &str) -> Option<KmipOperation> {
113115
}
114116
}
115117

118+
/// Lifecycle operation tags that have no [`KmipOperation`] variant but must be restricted
119+
/// to `CryptoOfficer` (or Administrator) when role enforcement is active.
120+
///
121+
/// `CreateKeyPair`, `Register`, and `ReKeyKeyPair` create or replace Managed Objects and
122+
/// are therefore lifecycle operations equivalent to `Create`/`Import`/`Rekey`.
123+
/// `CreateSplitKey` produces new `SplitKey` share objects and is likewise lifecycle-scoped.
124+
///
125+
/// `JoinSplitKey` is intentionally omitted: it is needed by Administrator candidates (users
126+
/// in `administrator.users` with `require_ceremony = true`) to complete the split-key
127+
/// ceremony before they hold an active Administrator role.
128+
const LIFECYCLE_OPERATION_TAGS: &[&str] = &[
129+
"CreateKeyPair",
130+
"Register",
131+
"ReKeyKeyPair",
132+
"CreateSplitKey",
133+
];
134+
116135
/// Enforce role-based access control before dispatching a KMIP operation.
117136
///
118-
/// When role lists are configured on the server, a user assigned to `Operator` or `CryptoOfficer`
119-
/// can only call the operations permitted by their role's [`Role::allowed_operations`] set.
120-
/// `Administrator` users are always allowed through. Users not listed in any role default to
121-
/// `Operator` when role enforcement is active (fail-secure per NIST SP 800-57 Part 2 §4.4.2).
137+
/// ## Design
138+
///
139+
/// Operations with a [`KmipOperation`] variant (Create, Import, Certify, Rekey, Destroy, etc.)
140+
/// are **not** blocked at dispatch level — their individual handlers already enforce
141+
/// `crypto_officer_users` restrictions while respecting explicit per-user grants. Blocking them
142+
/// here would prevent a `CryptoOfficer` from delegating `Create` permission to an `Operator`.
143+
///
144+
/// The dispatch check only gates lifecycle operations that have **no** [`KmipOperation`] mapping
145+
/// (i.e. entries in [`LIFECYCLE_OPERATION_TAGS`]). For those, if the user is `Operator` and has
146+
/// no explicit `Create` permission in the database, the request is denied.
147+
///
148+
/// `Administrator` users are always allowed through.
149+
/// Users not listed in any role default to `Operator` when role enforcement is active
150+
/// (fail-secure per NIST SP 800-57 Part 2 §4.4.2).
122151
///
123152
/// # Errors
124153
/// Returns [`KmsError::Unauthorized`] when the user's role does not permit the requested
125154
/// operation.
126-
fn check_role_permission(user: &str, operation_tag: &str, role_config: &RoleConfig) -> KResult<()> {
155+
async fn check_role_permission(
156+
kms: &KMS,
157+
user: &str,
158+
operation_tag: &str,
159+
role_config: &RoleConfig,
160+
) -> KResult<()> {
127161
// If no role lists are configured, skip role enforcement entirely.
128162
if !role_config.is_configured() {
129163
return Ok(());
@@ -137,12 +171,24 @@ fn check_role_permission(user: &str, operation_tag: &str, role_config: &RoleConf
137171
match effective_role {
138172
Role::Administrator => Ok(()),
139173
role => {
140-
// Only gate operations that map to a KmipOperation enum variant.
141-
if let Some(op) = operation_tag_to_kmip_operation(operation_tag) {
142-
if !role.allowed_operations().contains(&op) {
174+
// For operations that have a KmipOperation mapping, defer to the individual handler.
175+
// Each handler already checks crypto_officer_users + explicit grants, which correctly
176+
// allows delegated privileges (e.g. an Operator who was explicitly granted Create).
177+
if operation_tag_to_kmip_operation(operation_tag).is_some() {
178+
return Ok(());
179+
}
180+
181+
// For lifecycle operations without a KmipOperation mapping (CreateKeyPair, Register,
182+
// ReKeyKeyPair, CreateSplitKey), block Operators unless they have an explicit Create
183+
// permission grant in the database.
184+
if LIFECYCLE_OPERATION_TAGS.contains(&operation_tag) && matches!(role, Role::Operator) {
185+
let has_create =
186+
user_has_permission(user, None, &KmipOperation::Create, kms).await?;
187+
if !has_create {
143188
kms_bail!(KmsError::Unauthorized(format!(
144-
"User `{user}` (role: {role:?}) is not authorized to perform \
145-
operation `{operation_tag}`"
189+
"User `{user}` (role: Operator) is not authorized to perform \
190+
operation `{operation_tag}` (lifecycle operation requires CryptoOfficer \
191+
role or explicit Create grant)"
146192
)))
147193
}
148194
}
@@ -180,7 +226,7 @@ async fn dispatch_inner(
180226
operation_tag: &str,
181227
) -> KResult<Operation> {
182228
// Enforce role-based access control before any other check.
183-
check_role_permission(user, operation_tag, &kms.params.role_config)?;
229+
check_role_permission(kms, user, operation_tag, &kms.params.role_config).await?;
184230

185231
// For operations where the request carries algorithm choices, validate them
186232
// before executing any cryptographic action.

crate/server/src/core/operations/join_split_key.rs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ use crate::{
3636
///
3737
/// If all provided shares carry the `x-cosmian-administrator-ceremony` vendor attribute,
3838
/// **and** the number of shares satisfies the threshold recorded in those shares, **and**
39-
/// all share owners are listed in `administrator.users`, then
39+
/// the **assembling user** (caller) is listed in `administrator.users`, then
4040
/// `activate_administrator_ceremony()` is called on the database to persist the activation record
4141
/// that enables the Administrator role for ceremony-protected deployments.
4242
pub(crate) async fn join_split_key(
@@ -81,6 +81,16 @@ pub(crate) async fn join_split_key(
8181
let cryptographic_algorithm = first_sk.key_block.cryptographic_algorithm;
8282
let cryptographic_length = first_sk.key_block.cryptographic_length;
8383

84+
// Validate that the declared split method in the request matches the shares.
85+
// Per KMIP 2.1 semantics, rejecting mismatches prevents confusing/misleading requests.
86+
if request.split_key_method != method {
87+
kms_bail!(KmsError::InvalidRequest(format!(
88+
"JoinSplitKey: request declares split key method {:?} \
89+
but shares use {method:?}",
90+
request.split_key_method
91+
)));
92+
}
93+
8494
// Validate threshold
8595
let shares_count = i32::try_from(owms.len()).unwrap_or(i32::MAX);
8696
if shares_count < threshold {
@@ -176,6 +186,35 @@ pub(crate) async fn join_split_key(
176186
}
177187
}
178188

189+
// Enforce the same Create/Import restriction as create.rs / import.rs.
190+
// Administrator ceremony candidates (users in administrator.users) are exempt because
191+
// they need JoinSplitKey to activate their role before they hold Administrator privileges.
192+
let is_ceremony_candidate = kms
193+
.params
194+
.role_config
195+
.administrator
196+
.users
197+
.iter()
198+
.any(|u| u == user);
199+
if !is_ceremony_candidate {
200+
if let Some(ref co_users) = kms.params.role_config.crypto_officer_users {
201+
let has_create_permission = crate::core::retrieve_object_utils::user_has_permission(
202+
user,
203+
None,
204+
&KmipOperation::Create,
205+
kms,
206+
)
207+
.await?;
208+
if !has_create_permission && !co_users.iter().any(|u| u == user) {
209+
kms_bail!(KmsError::Unauthorized(
210+
"JoinSplitKey: user does not have permission to create objects \
211+
(CryptoOfficer role or explicit Create grant required)"
212+
.to_owned()
213+
));
214+
}
215+
}
216+
}
217+
179218
// Store the reconstructed key
180219
let mut tags: HashSet<String> = HashSet::new();
181220
tags.insert("reconstructed-split-key".to_owned());

crate/server/src/core/retrieve_object_utils.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,9 @@ pub(crate) async fn user_has_permission(
221221
};
222222

223223
// Administrator bypass: if the user is an active Administrator, grant access to
224-
// everything (except HSM-backed keys, which are governed by HSM admin rules).
225-
if kms.is_administrator(user).await? {
224+
// all non-HSM objects. HSM-backed keys are governed by the HSM admin rules below
225+
// and are therefore excluded from this bypass.
226+
if has_prefix(id).is_none() && kms.is_administrator(user).await? {
226227
warn!(
227228
"ADMINISTRATOR_ACCESS: administrator {user} bypassed normal permission check on {id} for {operation_type:?}"
228229
);

crate/server/src/routes/access.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ pub(crate) async fn disable_administrator(
285285
if !cfg.require_ceremony {
286286
return Err(crate::error::KmsError::InvalidRequest(
287287
"Config-only Administrator cannot be disabled at runtime. Remove the user from \
288-
`[administrator].users` in kms.toml and restart the server."
288+
`administrator_users` in kms.toml and restart the server."
289289
.to_owned(),
290290
));
291291
}

crate/server_database/src/core/database_objects.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -337,11 +337,8 @@ impl Database {
337337
let map = self.objects.read().await;
338338
let mut results: Vec<(String, State, Attributes)> = Vec::new();
339339
for db in map.values() {
340-
results.extend(
341-
db.find_all(researched_attributes, state, vendor_id)
342-
.await
343-
.unwrap_or(vec![]),
344-
);
340+
let partial = db.find_all(researched_attributes, state, vendor_id).await?;
341+
results.extend(partial);
345342
}
346343
Ok(results)
347344
}

0 commit comments

Comments
 (0)