This commit is contained in:
Elias Bonnici 2024-03-19 17:16:07 +01:00
commit 80637042ba
No known key found for this signature in database
GPG Key ID: 5EAC28EA3F980CCF
12 changed files with 261 additions and 43 deletions

View File

@ -399,12 +399,21 @@ class SlotNode(RpcNode):
else None,
)
@action(condition=lambda self: self.certificate)
@action(condition=lambda self: self.certificate or self.metadata)
def delete(self, params, event, signal):
self.session.delete_certificate(self.slot)
self.session.put_object(OBJECT_ID.CHUID, generate_chuid())
delete_cert = params.pop("delete_cert", False)
delete_key = params.pop("delete_key", False)
if not delete_cert and not delete_key:
raise ValueError("Missing delete option")
if delete_cert:
self.session.delete_certificate(self.slot)
self.session.put_object(OBJECT_ID.CHUID, generate_chuid())
self.certificate = None
if delete_key:
self.session.delete_key(self.slot)
self._refresh()
self.certificate = None
return dict()
@action

View File

@ -317,8 +317,10 @@ class _DesktopPivSlotsNotifier extends PivSlotsNotifier {
}
@override
Future<void> delete(SlotId slot) async {
await _session.command('delete', target: ['slots', slot.hexId]);
Future<void> delete(SlotId slot, bool deleteCert, bool deleteKey) async {
await _session.command('delete',
target: ['slots', slot.hexId],
params: {'delete_cert': deleteCert, 'delete_key': deleteKey});
ref.invalidateSelf();
}

View File

@ -505,6 +505,10 @@
"l_unsupported_key_type": null,
"l_delete_certificate": null,
"l_delete_certificate_desc": null,
"l_delete_key": null,
"l_delete_key_desc": null,
"l_delete_certificate_or_key": null,
"l_delete_certificate_or_key_desc": null,
"s_issuer": null,
"s_serial": null,
"s_certificate_fingerprint": null,
@ -522,14 +526,31 @@
},
"l_generating_private_key": null,
"s_private_key_generated": null,
"p_select_what_to_delete": null,
"p_warning_delete_certificate": null,
"p_warning_delete_key": null,
"p_warning_delete_certificate_and_key": null,
"q_delete_certificate_confirm": null,
"@q_delete_certificate_confirm": {
"placeholders": {
"slot": {}
}
},
"q_delete_key_confirm": null,
"@q_delete_key_confirm": {
"placeholders": {
"slot": {}
}
},
"q_delete_certificate_and_key_confirm": null,
"@q_delete_certificate_and_key_confirm": {
"placeholders": {
"slot": {}
}
},
"l_certificate_deleted": null,
"l_key_deleted": null,
"l_certificate_and_key_deleted": null,
"p_password_protected_file": null,
"p_import_items_desc": null,
"@p_import_items_desc": {

View File

@ -505,6 +505,10 @@
"l_unsupported_key_type": "Unsupported key type",
"l_delete_certificate": "Delete certificate",
"l_delete_certificate_desc": "Remove the certificate from your YubiKey",
"l_delete_key": "Delete key",
"l_delete_key_desc": "Remove the key from your YubiKey",
"l_delete_certificate_or_key": "Delete certificate/key",
"l_delete_certificate_or_key_desc": "Remove the certificate or key from your YubiKey",
"s_issuer": "Issuer",
"s_serial": "Serial",
"s_certificate_fingerprint": "Fingerprint",
@ -522,14 +526,31 @@
},
"l_generating_private_key": "Generating private key\u2026",
"s_private_key_generated": "Private key generated",
"p_select_what_to_delete": "Select what to delete from the slot.",
"p_warning_delete_certificate": "Warning! This action will delete the certificate from your YubiKey.",
"p_warning_delete_key": "Warning! This action will delete the key from your YubiKey.",
"p_warning_delete_certificate_and_key": "Warning! This action will delete the certificate and key from your YubiKey.",
"q_delete_certificate_confirm": "Delete the certificate in PIV slot {slot}?",
"@q_delete_certificate_confirm": {
"placeholders": {
"slot": {}
}
},
"q_delete_key_confirm": "Delete the key in PIV slot {slot}?",
"@q_delete_key_confirm": {
"placeholders": {
"slot": {}
}
},
"q_delete_certificate_and_key_confirm": "Delete the certificate and key in PIV slot {slot}?",
"@q_delete_certificate_and_key_confirm": {
"placeholders": {
"slot": {}
}
},
"l_certificate_deleted": "Certificate deleted",
"l_key_deleted": "Key deleted",
"l_certificate_and_key_deleted": "Certificate and key deleted",
"p_password_protected_file": "The selected file is password protected. Enter the password to proceed.",
"p_import_items_desc": "The following item(s) will be imported into PIV slot {slot}.",
"@p_import_items_desc": {

View File

@ -505,6 +505,10 @@
"l_unsupported_key_type": null,
"l_delete_certificate": "Supprimer un certificat",
"l_delete_certificate_desc": "Supprimer un certificat de votre YubiKey",
"l_delete_key": null,
"l_delete_key_desc": null,
"l_delete_certificate_or_key": null,
"l_delete_certificate_or_key_desc": null,
"s_issuer": "Émetteur",
"s_serial": "Série",
"s_certificate_fingerprint": "Empreinte digitale",
@ -522,14 +526,31 @@
},
"l_generating_private_key": "Génération d'une clé privée\u2026",
"s_private_key_generated": "Clé privée générée",
"p_select_what_to_delete": null,
"p_warning_delete_certificate": "Attention! Cette action supprimera le certificat de votre YubiKey.",
"p_warning_delete_key": null,
"p_warning_delete_certificate_and_key": null,
"q_delete_certificate_confirm": "Supprimer le certficat du slot PIV {slot}?",
"@q_delete_certificate_confirm": {
"placeholders": {
"slot": {}
}
},
"q_delete_key_confirm": null,
"@q_delete_key_confirm": {
"placeholders": {
"slot": {}
}
},
"q_delete_certificate_and_key_confirm": null,
"@q_delete_certificate_and_key_confirm": {
"placeholders": {
"slot": {}
}
},
"l_certificate_deleted": "Certificat supprimé",
"l_key_deleted": null,
"l_certificate_and_key_deleted": null,
"p_password_protected_file": "Le fichier sélectionné est protégé par un mot de passe. Enterez le mot de passe pour continuer.",
"p_import_items_desc": "Les éléments suivants seront importés dans le slot PIV {slot}.",
"@p_import_items_desc": {

View File

@ -505,6 +505,10 @@
"l_unsupported_key_type": null,
"l_delete_certificate": "証明書を削除",
"l_delete_certificate_desc": "YubiKeyか証明書の削除",
"l_delete_key": null,
"l_delete_key_desc": null,
"l_delete_certificate_or_key": null,
"l_delete_certificate_or_key_desc": null,
"s_issuer": "発行者",
"s_serial": "シリアル番号",
"s_certificate_fingerprint": "指紋",
@ -522,14 +526,31 @@
},
"l_generating_private_key": "秘密鍵を生成しています\u2026",
"s_private_key_generated": "秘密鍵を生成しました",
"p_select_what_to_delete": null,
"p_warning_delete_certificate": "警告この操作によってYubiKeyから証明書が削除されます",
"p_warning_delete_key": null,
"p_warning_delete_certificate_and_key": null,
"q_delete_certificate_confirm": "PIVスロット{slot}の証明書を削除しますか?",
"@q_delete_certificate_confirm": {
"placeholders": {
"slot": {}
}
},
"q_delete_key_confirm": null,
"@q_delete_key_confirm": {
"placeholders": {
"slot": {}
}
},
"q_delete_certificate_and_key_confirm": null,
"@q_delete_certificate_and_key_confirm": {
"placeholders": {
"slot": {}
}
},
"l_certificate_deleted": "証明書が削除されました",
"l_key_deleted": null,
"l_certificate_and_key_deleted": null,
"p_password_protected_file": "選択したファイルはパスワードで保護されています。パスワードを入力して続行します",
"p_import_items_desc": "次のアイテムはPIVスロット{slot}にインポートされます",
"@p_import_items_desc": {

View File

@ -505,6 +505,10 @@
"l_unsupported_key_type": null,
"l_delete_certificate": "Usuń certyfikat",
"l_delete_certificate_desc": "Usuń certyfikat z klucza YubiKey",
"l_delete_key": null,
"l_delete_key_desc": null,
"l_delete_certificate_or_key": null,
"l_delete_certificate_or_key_desc": null,
"s_issuer": "Wydawca",
"s_serial": "Nr. seryjny",
"s_certificate_fingerprint": "Odcisk palca",
@ -522,14 +526,31 @@
},
"l_generating_private_key": "Generowanie prywatnego klucza\u2026",
"s_private_key_generated": "Wygenerowano klucz prywatny",
"p_select_what_to_delete": null,
"p_warning_delete_certificate": "Uwaga! Ta czynność spowoduje usunięcie certyfikatu z klucza YubiKey.",
"p_warning_delete_key": null,
"p_warning_delete_certificate_and_key": null,
"q_delete_certificate_confirm": "Usunąć certyfikat ze slotu PIV {slot}?",
"@q_delete_certificate_confirm": {
"placeholders": {
"slot": {}
}
},
"q_delete_key_confirm": null,
"@q_delete_key_confirm": {
"placeholders": {
"slot": {}
}
},
"q_delete_certificate_and_key_confirm": null,
"@q_delete_certificate_and_key_confirm": {
"placeholders": {
"slot": {}
}
},
"l_certificate_deleted": "Certyfikat został usunięty",
"l_key_deleted": null,
"l_certificate_and_key_deleted": null,
"p_password_protected_file": "Wybrany plik jest chroniony hasłem. Wprowadź je, aby kontynuować.",
"p_import_items_desc": "Następujące elementy zostaną zaimportowane do slotu PIV {slot}.",
"@p_import_items_desc": {

View File

@ -66,5 +66,5 @@ abstract class PivSlotsNotifier
PinPolicy pinPolicy = PinPolicy.dfault,
TouchPolicy touchPolicy = TouchPolicy.dfault,
});
Future<void> delete(SlotId slot);
Future<void> delete(SlotId slot, bool deleteCert, bool deleteKey);
}

View File

@ -263,6 +263,7 @@ class PivActions extends ConsumerWidget {
context: context,
builder: (context) => DeleteCertificateDialog(
devicePath,
pivState,
intent.target,
),
) ??
@ -283,9 +284,11 @@ class PivActions extends ConsumerWidget {
}
}
List<ActionItem> buildSlotActions(PivSlot slot, AppLocalizations l10n) {
List<ActionItem> buildSlotActions(
PivState pivState, PivSlot slot, AppLocalizations l10n) {
final hasCert = slot.certInfo != null;
final hasKey = slot.metadata != null;
final canDeleteKey = hasKey && pivState.version.isAtLeast(5, 7);
return [
ActionItem(
key: keys.generateAction,
@ -313,15 +316,6 @@ List<ActionItem> buildSlotActions(PivSlot slot, AppLocalizations l10n) {
subtitle: l10n.l_export_certificate_desc,
intent: ExportIntent(slot),
),
ActionItem(
key: keys.deleteAction,
feature: features.slotsDelete,
actionStyle: ActionStyle.error,
icon: const Icon(Symbols.delete),
title: l10n.l_delete_certificate,
subtitle: l10n.l_delete_certificate_desc,
intent: DeleteIntent(slot),
),
] else if (hasKey) ...[
ActionItem(
key: keys.exportAction,
@ -332,5 +326,23 @@ List<ActionItem> buildSlotActions(PivSlot slot, AppLocalizations l10n) {
intent: ExportIntent(slot),
),
],
if (hasCert || canDeleteKey)
ActionItem(
key: keys.deleteAction,
feature: features.slotsDelete,
actionStyle: ActionStyle.error,
icon: const Icon(Symbols.delete),
title: hasCert && canDeleteKey
? l10n.l_delete_certificate_or_key
: hasCert
? l10n.l_delete_certificate
: l10n.l_delete_key,
subtitle: hasCert && canDeleteKey
? l10n.l_delete_certificate_or_key_desc
: hasCert
? l10n.l_delete_certificate_desc
: l10n.l_delete_key_desc,
intent: DeleteIntent(slot),
),
];
}

View File

@ -27,34 +27,75 @@ import '../keys.dart' as keys;
import '../models.dart';
import '../state.dart';
class DeleteCertificateDialog extends ConsumerWidget {
class DeleteCertificateDialog extends ConsumerStatefulWidget {
final DevicePath devicePath;
final PivState pivState;
final PivSlot pivSlot;
const DeleteCertificateDialog(this.devicePath, this.pivSlot, {super.key});
const DeleteCertificateDialog(this.devicePath, this.pivState, this.pivSlot,
{super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
ConsumerState<ConsumerStatefulWidget> createState() =>
_DeleteCertificateDialogState();
}
class _DeleteCertificateDialogState
extends ConsumerState<DeleteCertificateDialog> {
late bool _deleteCertificate;
late bool _deleteKey;
@override
void initState() {
super.initState();
_deleteCertificate = widget.pivSlot.certInfo != null;
_deleteKey = widget.pivSlot.metadata != null &&
widget.pivState.version.isAtLeast(5, 7);
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final canDeleteCertificate = widget.pivSlot.certInfo != null;
final canDeleteKey = widget.pivSlot.metadata != null &&
widget.pivState.version.isAtLeast(5, 7);
return ResponsiveDialog(
title: Text(l10n.l_delete_certificate),
title: Text(canDeleteKey && canDeleteCertificate
? l10n.l_delete_certificate_or_key
: canDeleteCertificate
? l10n.l_delete_certificate
: l10n.l_delete_key),
actions: [
TextButton(
key: keys.deleteButton,
onPressed: () async {
try {
await ref
.read(pivSlotsProvider(devicePath).notifier)
.delete(pivSlot.slot);
await ref.read(withContextProvider)(
(context) async {
Navigator.of(context).pop(true);
showMessage(context, l10n.l_certificate_deleted);
},
);
} on CancellationException catch (_) {
// ignored
}
},
onPressed: _deleteKey || _deleteCertificate
? () async {
try {
await ref
.read(pivSlotsProvider(widget.devicePath).notifier)
.delete(widget.pivSlot.slot, _deleteCertificate,
_deleteKey);
await ref.read(withContextProvider)(
(context) async {
String message;
if (_deleteCertificate && _deleteKey) {
message = l10n.l_certificate_and_key_deleted;
} else if (_deleteCertificate) {
message = l10n.l_certificate_deleted;
} else {
message = l10n.l_key_deleted;
}
Navigator.of(context).pop(true);
showMessage(context, message);
},
);
} on CancellationException catch (_) {
// ignored
}
}
: null,
child: Text(l10n.s_delete),
),
],
@ -63,9 +104,55 @@ class DeleteCertificateDialog extends ConsumerWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(l10n.p_warning_delete_certificate),
Text(l10n.q_delete_certificate_confirm(
pivSlot.slot.getDisplayName(l10n))),
if (_deleteCertificate || _deleteKey) ...[
Text(
_deleteCertificate && _deleteKey
? l10n.p_warning_delete_certificate_and_key
: _deleteCertificate
? l10n.p_warning_delete_certificate
: l10n.p_warning_delete_key,
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(fontWeight: FontWeight.w700),
),
Text(_deleteCertificate && _deleteKey
? l10n.q_delete_certificate_and_key_confirm(
widget.pivSlot.slot.getDisplayName(l10n))
: _deleteCertificate
? l10n.q_delete_certificate_confirm(
widget.pivSlot.slot.getDisplayName(l10n))
: l10n.q_delete_key_confirm(
widget.pivSlot.slot.getDisplayName(l10n)))
],
if (!_deleteCertificate && !_deleteKey)
Text(l10n.p_select_what_to_delete),
if (canDeleteKey && canDeleteCertificate)
Wrap(
spacing: 4.0,
runSpacing: 8.0,
children: [
if (canDeleteCertificate)
FilterChip(
label: Text(l10n.s_certificate),
selected: _deleteCertificate,
onSelected: (value) {
setState(() {
_deleteCertificate = value;
});
},
),
if (canDeleteKey)
FilterChip(
label: Text(l10n.s_private_key),
selected: _deleteKey,
onSelected: (value) {
setState(() {
_deleteKey = value;
});
})
],
),
]
.map((e) => Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),

View File

@ -150,7 +150,8 @@ class _PivScreenState extends ConsumerState<PivScreen> {
ActionListSection.fromMenuActions(
context,
l10n.s_actions,
actions: buildSlotActions(selected, l10n),
actions:
buildSlotActions(pivState, selected, l10n),
),
],
)
@ -186,6 +187,7 @@ class _PivScreenState extends ConsumerState<PivScreen> {
if (pivSlots?.hasValue == true)
...pivSlots!.value.map(
(e) => _CertificateListItem(
pivState,
e,
expanded: expanded,
selected: e == selected,
@ -204,11 +206,12 @@ class _PivScreenState extends ConsumerState<PivScreen> {
}
class _CertificateListItem extends ConsumerWidget {
final PivState pivState;
final PivSlot pivSlot;
final bool expanded;
final bool selected;
const _CertificateListItem(this.pivSlot,
const _CertificateListItem(this.pivState, this.pivSlot,
{required this.expanded, required this.selected});
@override
@ -245,7 +248,7 @@ class _CertificateListItem extends ConsumerWidget {
tapIntent: isDesktop && !expanded ? null : OpenIntent(pivSlot),
doubleTapIntent: isDesktop && !expanded ? OpenIntent(pivSlot) : null,
buildPopupActions: hasFeature(features.slots)
? (context) => buildSlotActions(pivSlot, l10n)
? (context) => buildSlotActions(pivState, pivSlot, l10n)
: null,
);
}

View File

@ -111,7 +111,7 @@ class SlotDialog extends ConsumerWidget {
ActionListSection.fromMenuActions(
context,
l10n.s_actions,
actions: buildSlotActions(slotData, l10n),
actions: buildSlotActions(pivState, slotData, l10n),
),
],
),