diff --git a/ZB.MOM.WW.Secrets/src/ZB.MOM.WW.Secrets.Cli/Interactive/Flows/KekDoctorFlow.cs b/ZB.MOM.WW.Secrets/src/ZB.MOM.WW.Secrets.Cli/Interactive/Flows/KekDoctorFlow.cs index ea4d5b8..0fd8ed8 100644 --- a/ZB.MOM.WW.Secrets/src/ZB.MOM.WW.Secrets.Cli/Interactive/Flows/KekDoctorFlow.cs +++ b/ZB.MOM.WW.Secrets/src/ZB.MOM.WW.Secrets.Cli/Interactive/Flows/KekDoctorFlow.cs @@ -108,35 +108,41 @@ public sealed class KekDoctorFlow : IInteractiveFlow { IMasterKeyProvider oldKek = PromptForOldKek(console); + // Prompt and confirm OUTSIDE the guard: only the rewrap call itself is caught, so a stray + // ArgumentException from setup/prompt code is never mis-reported as a "rewrap failed" message. + string oldKekId = oldKek.KekId; + int moving = report.Rows.Count(r => string.Equals(r.RowKekId, oldKekId, StringComparison.Ordinal)); + + string confirmText = + $"Re-wrap {moving} row(s) from KEK '{Markup.Escape(oldKekId)}' → session KEK " + + $"'{Markup.Escape(report.SessionKekId)}'? (rewraps every row on the old KEK)"; + if (!console.Prompt(new ConfirmationPrompt(confirmText) { DefaultValue = false })) + { + console.MarkupLine("[grey]Rewrap cancelled — no rows changed.[/]"); + return; + } + + RewrapReport result; try { - string oldKekId = oldKek.KekId; - int moving = report.Rows.Count(r => string.Equals(r.RowKekId, oldKekId, StringComparison.Ordinal)); - - string confirmText = - $"Re-wrap {moving} row(s) from KEK '{Markup.Escape(oldKekId)}' → session KEK " + - $"'{Markup.Escape(report.SessionKekId)}'? (rewraps every row on the old KEK)"; - if (!console.Prompt(new ConfirmationPrompt(confirmText) { DefaultValue = false })) - { - console.MarkupLine("[grey]Rewrap cancelled — no rows changed.[/]"); - return; - } - - RewrapReport result = await doctor.RewrapAllAsync(session, oldKek, ct).ConfigureAwait(false); - console.MarkupLineInterpolated( - $"[green]Re-wrapped {result.Rewrapped} row(s)[/] onto the session KEK ({result.AlreadyCurrent} already current) of {result.Total} total."); + result = await doctor.RewrapAllAsync(session, oldKek, ct).ConfigureAwait(false); } catch (ArgumentException ex) { // Old and session KEK are identical — nothing to rotate. console.MarkupLineInterpolated($"[red]Rewrap failed:[/] {ex.Message}"); + return; } catch (SecretDecryptionException ex) { // Wrong old key, or a row wrapped by neither the old nor the session KEK (the pass aborted; // any rows re-wrapped before the anomaly stay persisted). console.MarkupLineInterpolated($"[red]Rewrap failed:[/] {ex.Message}"); + return; } + + console.MarkupLineInterpolated( + $"[green]Re-wrapped {result.Rewrapped} row(s)[/] onto the session KEK ({result.AlreadyCurrent} already current) of {result.Total} total."); } // Prompts for the old-KEK source and builds the matching provider. Paste feeds a masked base64 entry to