mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-11-23 00:57:26 +03:00
Merge PR #1455
This commit is contained in:
commit
b04920b018
@ -416,6 +416,26 @@ class SlotNode(RpcNode):
|
||||
self._refresh()
|
||||
return dict()
|
||||
|
||||
@action(condition=lambda self: self.metadata)
|
||||
def move_key(self, params, event, signal):
|
||||
destination = params.pop("destination")
|
||||
overwrite_key = params.pop("overwrite_key")
|
||||
include_certificate = params.pop("include_certificate")
|
||||
|
||||
if include_certificate:
|
||||
source_object = self.session.get_object(OBJECT_ID.from_slot(self.slot))
|
||||
destination = SLOT(int(destination, base=16))
|
||||
if overwrite_key:
|
||||
self.session.delete_key(destination)
|
||||
self.session.move_key(self.slot, destination)
|
||||
if include_certificate:
|
||||
self.session.put_object(OBJECT_ID.from_slot(destination), source_object)
|
||||
self.session.delete_certificate(self.slot)
|
||||
self.session.put_object(OBJECT_ID.CHUID, generate_chuid())
|
||||
self.certificate = None
|
||||
self._refresh()
|
||||
return dict()
|
||||
|
||||
@action
|
||||
def import_file(self, params, event, signal):
|
||||
data = bytes.fromhex(params.pop("data"))
|
||||
|
@ -324,6 +324,20 @@ class _DesktopPivSlotsNotifier extends PivSlotsNotifier {
|
||||
ref.invalidateSelf();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> moveKey(SlotId source, SlotId destination, bool overwriteKey,
|
||||
bool includeCertificate) async {
|
||||
await _session.command('move_key', target: [
|
||||
'slots',
|
||||
source.hexId
|
||||
], params: {
|
||||
'destination': destination.hexId,
|
||||
'overwrite_key': overwriteKey,
|
||||
'include_certificate': includeCertificate
|
||||
});
|
||||
ref.invalidateSelf();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PivGenerateResult> generate(
|
||||
SlotId slot,
|
||||
|
@ -28,6 +28,7 @@
|
||||
"s_cancel": "Abbrechen",
|
||||
"s_close": "Schließen",
|
||||
"s_delete": "Löschen",
|
||||
"s_move": null,
|
||||
"s_quit": "Beenden",
|
||||
"s_status": null,
|
||||
"s_unlock": "Entsperren",
|
||||
@ -523,6 +524,8 @@
|
||||
"l_delete_key_desc": null,
|
||||
"l_delete_certificate_or_key": null,
|
||||
"l_delete_certificate_or_key_desc": null,
|
||||
"l_move_key": null,
|
||||
"l_move_key_desc": null,
|
||||
"s_issuer": null,
|
||||
"s_serial": null,
|
||||
"s_certificate_fingerprint": null,
|
||||
@ -565,6 +568,28 @@
|
||||
"l_certificate_deleted": null,
|
||||
"l_key_deleted": null,
|
||||
"l_certificate_and_key_deleted": null,
|
||||
"l_include_certificate": null,
|
||||
"l_select_destination_slot": null,
|
||||
"q_move_key_confirm": null,
|
||||
"@q_move_key_confirm": {
|
||||
"placeholders": {
|
||||
"from_slot": {}
|
||||
}
|
||||
},
|
||||
"q_move_key_to_slot_confirm": null,
|
||||
"@q_move_key_to_slot_confirm": {
|
||||
"placeholders": {
|
||||
"from_slot": {},
|
||||
"to_slot": {}
|
||||
}
|
||||
},
|
||||
"q_move_key_and_certificate_to_slot_confirm": null,
|
||||
"@q_move_key_and_certificate_to_slot_confirm": {
|
||||
"placeholders": {
|
||||
"from_slot": {},
|
||||
"to_slot": {}
|
||||
}
|
||||
},
|
||||
"p_password_protected_file": null,
|
||||
"p_import_items_desc": null,
|
||||
"@p_import_items_desc": {
|
||||
@ -572,6 +597,8 @@
|
||||
"slot": {}
|
||||
}
|
||||
},
|
||||
"l_key_moved": null,
|
||||
"l_key_and_certificate_moved": null,
|
||||
"p_subject_desc": null,
|
||||
"l_rfc4514_invalid": null,
|
||||
"rfc4514_examples": null,
|
||||
@ -595,6 +622,12 @@
|
||||
"hexid": {}
|
||||
}
|
||||
},
|
||||
"s_retired_slot_display_name": null,
|
||||
"@s_retired_slot_display_name": {
|
||||
"placeholders": {
|
||||
"hexid": {}
|
||||
}
|
||||
},
|
||||
"s_slot_9a": null,
|
||||
"s_slot_9c": null,
|
||||
"s_slot_9d": null,
|
||||
|
@ -28,6 +28,7 @@
|
||||
"s_cancel": "Cancel",
|
||||
"s_close": "Close",
|
||||
"s_delete": "Delete",
|
||||
"s_move": "Move",
|
||||
"s_quit": "Quit",
|
||||
"s_status": "Status",
|
||||
"s_unlock": "Unlock",
|
||||
@ -523,6 +524,8 @@
|
||||
"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",
|
||||
"l_move_key": "Move key",
|
||||
"l_move_key_desc": "Move a key from one PIV slot into another",
|
||||
"s_issuer": "Issuer",
|
||||
"s_serial": "Serial",
|
||||
"s_certificate_fingerprint": "Fingerprint",
|
||||
@ -542,21 +545,21 @@
|
||||
"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.",
|
||||
"p_warning_delete_key": "Warning! This action will delete the private key from your YubiKey.",
|
||||
"p_warning_delete_certificate_and_key": "Warning! This action will delete the certificate and private 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": "Delete the private 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": "Delete the certificate and private key in PIV slot {slot}?",
|
||||
"@q_delete_certificate_and_key_confirm": {
|
||||
"placeholders": {
|
||||
"slot": {}
|
||||
@ -565,6 +568,28 @@
|
||||
"l_certificate_deleted": "Certificate deleted",
|
||||
"l_key_deleted": "Key deleted",
|
||||
"l_certificate_and_key_deleted": "Certificate and key deleted",
|
||||
"l_include_certificate": "Include certificate",
|
||||
"l_select_destination_slot": "Select destination slot",
|
||||
"q_move_key_confirm": "Move the private key in PIV slot {from_slot}?",
|
||||
"@q_move_key_confirm": {
|
||||
"placeholders": {
|
||||
"from_slot": {}
|
||||
}
|
||||
},
|
||||
"q_move_key_to_slot_confirm": "Move the private key in PIV slot {from_slot} to slot {to_slot}?",
|
||||
"@q_move_key_to_slot_confirm": {
|
||||
"placeholders": {
|
||||
"from_slot": {},
|
||||
"to_slot": {}
|
||||
}
|
||||
},
|
||||
"q_move_key_and_certificate_to_slot_confirm": "Move the private key and certificate in PIV slot {from_slot} to slot {to_slot}?",
|
||||
"@q_move_key_and_certificate_to_slot_confirm": {
|
||||
"placeholders": {
|
||||
"from_slot": {},
|
||||
"to_slot": {}
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
@ -572,6 +597,8 @@
|
||||
"slot": {}
|
||||
}
|
||||
},
|
||||
"l_key_moved": "Key moved",
|
||||
"l_key_and_certificate_moved": "Key and certificate moved",
|
||||
"p_subject_desc": "A distinguished name (DN) formatted in accordance to the RFC 4514 specification.",
|
||||
"l_rfc4514_invalid": "Invalid RFC 4514 format",
|
||||
"rfc4514_examples": "Examples:\nCN=Example Name\nCN=jsmith,DC=example,DC=net",
|
||||
@ -595,6 +622,12 @@
|
||||
"hexid": {}
|
||||
}
|
||||
},
|
||||
"s_retired_slot_display_name": "Retired Key Management ({hexid})",
|
||||
"@s_retired_slot_display_name": {
|
||||
"placeholders": {
|
||||
"hexid": {}
|
||||
}
|
||||
},
|
||||
"s_slot_9a": "Authentication",
|
||||
"s_slot_9c": "Digital Signature",
|
||||
"s_slot_9d": "Key Management",
|
||||
|
@ -28,6 +28,7 @@
|
||||
"s_cancel": "Annuler",
|
||||
"s_close": "Fermer",
|
||||
"s_delete": "Supprimer",
|
||||
"s_move": null,
|
||||
"s_quit": "Quitter",
|
||||
"s_status": null,
|
||||
"s_unlock": "Déverrouiller",
|
||||
@ -523,6 +524,8 @@
|
||||
"l_delete_key_desc": null,
|
||||
"l_delete_certificate_or_key": null,
|
||||
"l_delete_certificate_or_key_desc": null,
|
||||
"l_move_key": null,
|
||||
"l_move_key_desc": null,
|
||||
"s_issuer": "Émetteur",
|
||||
"s_serial": "Série",
|
||||
"s_certificate_fingerprint": "Empreinte digitale",
|
||||
@ -565,6 +568,28 @@
|
||||
"l_certificate_deleted": "Certificat supprimé",
|
||||
"l_key_deleted": null,
|
||||
"l_certificate_and_key_deleted": null,
|
||||
"l_include_certificate": null,
|
||||
"l_select_destination_slot": null,
|
||||
"q_move_key_confirm": null,
|
||||
"@q_move_key_confirm": {
|
||||
"placeholders": {
|
||||
"from_slot": {}
|
||||
}
|
||||
},
|
||||
"q_move_key_to_slot_confirm": null,
|
||||
"@q_move_key_to_slot_confirm": {
|
||||
"placeholders": {
|
||||
"from_slot": {},
|
||||
"to_slot": {}
|
||||
}
|
||||
},
|
||||
"q_move_key_and_certificate_to_slot_confirm": null,
|
||||
"@q_move_key_and_certificate_to_slot_confirm": {
|
||||
"placeholders": {
|
||||
"from_slot": {},
|
||||
"to_slot": {}
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
@ -572,6 +597,8 @@
|
||||
"slot": {}
|
||||
}
|
||||
},
|
||||
"l_key_moved": null,
|
||||
"l_key_and_certificate_moved": null,
|
||||
"p_subject_desc": "Un nom distinctif (DN) formaté conformément à la spécification RFC 4514.",
|
||||
"l_rfc4514_invalid": "Format RFC 4514 invalide",
|
||||
"rfc4514_examples": "Exemples:\nCN=Example Name\nCN=jsmith,DC=example,DC=net",
|
||||
@ -595,6 +622,12 @@
|
||||
"hexid": {}
|
||||
}
|
||||
},
|
||||
"s_retired_slot_display_name": null,
|
||||
"@s_retired_slot_display_name": {
|
||||
"placeholders": {
|
||||
"hexid": {}
|
||||
}
|
||||
},
|
||||
"s_slot_9a": "Authentification",
|
||||
"s_slot_9c": "Signature digitale",
|
||||
"s_slot_9d": "Gestion des clés",
|
||||
|
@ -28,6 +28,7 @@
|
||||
"s_cancel": "キャンセル",
|
||||
"s_close": "閉じる",
|
||||
"s_delete": "消去",
|
||||
"s_move": null,
|
||||
"s_quit": "終了",
|
||||
"s_status": null,
|
||||
"s_unlock": "ロック解除",
|
||||
@ -523,6 +524,8 @@
|
||||
"l_delete_key_desc": null,
|
||||
"l_delete_certificate_or_key": null,
|
||||
"l_delete_certificate_or_key_desc": null,
|
||||
"l_move_key": null,
|
||||
"l_move_key_desc": null,
|
||||
"s_issuer": "発行者",
|
||||
"s_serial": "シリアル番号",
|
||||
"s_certificate_fingerprint": "指紋",
|
||||
@ -565,6 +568,28 @@
|
||||
"l_certificate_deleted": "証明書が削除されました",
|
||||
"l_key_deleted": null,
|
||||
"l_certificate_and_key_deleted": null,
|
||||
"l_include_certificate": null,
|
||||
"l_select_destination_slot": null,
|
||||
"q_move_key_confirm": null,
|
||||
"@q_move_key_confirm": {
|
||||
"placeholders": {
|
||||
"from_slot": {}
|
||||
}
|
||||
},
|
||||
"q_move_key_to_slot_confirm": null,
|
||||
"@q_move_key_to_slot_confirm": {
|
||||
"placeholders": {
|
||||
"from_slot": {},
|
||||
"to_slot": {}
|
||||
}
|
||||
},
|
||||
"q_move_key_and_certificate_to_slot_confirm": null,
|
||||
"@q_move_key_and_certificate_to_slot_confirm": {
|
||||
"placeholders": {
|
||||
"from_slot": {},
|
||||
"to_slot": {}
|
||||
}
|
||||
},
|
||||
"p_password_protected_file": "選択したファイルはパスワードで保護されています。パスワードを入力して続行します",
|
||||
"p_import_items_desc": "次のアイテムはPIVスロット{slot}にインポートされます",
|
||||
"@p_import_items_desc": {
|
||||
@ -572,6 +597,8 @@
|
||||
"slot": {}
|
||||
}
|
||||
},
|
||||
"l_key_moved": null,
|
||||
"l_key_and_certificate_moved": null,
|
||||
"p_subject_desc": "RFC 4514フォーマットの識別名識別名 (DN)",
|
||||
"l_rfc4514_invalid": "無効な RFC 4514 形式です",
|
||||
"rfc4514_examples": "例:\nCN=Example Name\nCN=jsmith,DC=example,DC=net",
|
||||
@ -595,6 +622,12 @@
|
||||
"hexid": {}
|
||||
}
|
||||
},
|
||||
"s_retired_slot_display_name": null,
|
||||
"@s_retired_slot_display_name": {
|
||||
"placeholders": {
|
||||
"hexid": {}
|
||||
}
|
||||
},
|
||||
"s_slot_9a": "認証",
|
||||
"s_slot_9c": "デジタル署名",
|
||||
"s_slot_9d": "鍵の管理",
|
||||
|
@ -28,6 +28,7 @@
|
||||
"s_cancel": "Anuluj",
|
||||
"s_close": "Zamknij",
|
||||
"s_delete": "Usuń",
|
||||
"s_move": null,
|
||||
"s_quit": "Wyjdź",
|
||||
"s_status": "Status",
|
||||
"s_unlock": "Odblokuj",
|
||||
@ -523,6 +524,8 @@
|
||||
"l_delete_key_desc": null,
|
||||
"l_delete_certificate_or_key": null,
|
||||
"l_delete_certificate_or_key_desc": null,
|
||||
"l_move_key": null,
|
||||
"l_move_key_desc": null,
|
||||
"s_issuer": "Wydawca",
|
||||
"s_serial": "Nr. seryjny",
|
||||
"s_certificate_fingerprint": "Odcisk palca",
|
||||
@ -565,6 +568,28 @@
|
||||
"l_certificate_deleted": "Certyfikat został usunięty",
|
||||
"l_key_deleted": null,
|
||||
"l_certificate_and_key_deleted": null,
|
||||
"l_include_certificate": null,
|
||||
"l_select_destination_slot": null,
|
||||
"q_move_key_confirm": null,
|
||||
"@q_move_key_confirm": {
|
||||
"placeholders": {
|
||||
"from_slot": {}
|
||||
}
|
||||
},
|
||||
"q_move_key_to_slot_confirm": null,
|
||||
"@q_move_key_to_slot_confirm": {
|
||||
"placeholders": {
|
||||
"from_slot": {},
|
||||
"to_slot": {}
|
||||
}
|
||||
},
|
||||
"q_move_key_and_certificate_to_slot_confirm": null,
|
||||
"@q_move_key_and_certificate_to_slot_confirm": {
|
||||
"placeholders": {
|
||||
"from_slot": {},
|
||||
"to_slot": {}
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
@ -572,6 +597,8 @@
|
||||
"slot": {}
|
||||
}
|
||||
},
|
||||
"l_key_moved": null,
|
||||
"l_key_and_certificate_moved": null,
|
||||
"p_subject_desc": "Nazwa wyróżniająca (DN) sformatowana zgodnie ze specyfikacją RFC 4514.",
|
||||
"l_rfc4514_invalid": "Nieprawidłowy format RFC 4514",
|
||||
"rfc4514_examples": "Przykłady:\nCN=Przykładowa Nazwa\nCN=jkowalski,DC=przyklad,DC=pl",
|
||||
@ -595,6 +622,12 @@
|
||||
"hexid": {}
|
||||
}
|
||||
},
|
||||
"s_retired_slot_display_name": null,
|
||||
"@s_retired_slot_display_name": {
|
||||
"placeholders": {
|
||||
"hexid": {}
|
||||
}
|
||||
},
|
||||
"s_slot_9a": "Uwierzytelnienie",
|
||||
"s_slot_9c": "Cyfrowy podpis",
|
||||
"s_slot_9d": "Menedżer kluczy",
|
||||
|
@ -29,3 +29,4 @@ final slotsGenerate = slots.feature('generate');
|
||||
final slotsImport = slots.feature('import');
|
||||
final slotsExport = slots.feature('export');
|
||||
final slotsDelete = slots.feature('delete');
|
||||
final slotsMove = slots.feature('move');
|
||||
|
@ -32,6 +32,7 @@ const generateAction = Key('$_slotAction.generate');
|
||||
const importAction = Key('$_slotAction.import');
|
||||
const exportAction = Key('$_slotAction.export');
|
||||
const deleteAction = Key('$_slotAction.delete');
|
||||
const moveAction = Key('$_slotAction.move');
|
||||
|
||||
const saveButton = Key('$_prefix.save');
|
||||
const deleteButton = Key('$_prefix.delete');
|
||||
@ -50,11 +51,51 @@ const meatballButton9a = Key('$_prefix.9a.meatball.button');
|
||||
const meatballButton9c = Key('$_prefix.9c.meatball.button');
|
||||
const meatballButton9d = Key('$_prefix.9d.meatball.button');
|
||||
const meatballButton9e = Key('$_prefix.9e.meatball.button');
|
||||
const meatballButton82 = Key('$_prefix.82.meatball.button');
|
||||
const meatballButton83 = Key('$_prefix.83.meatball.button');
|
||||
const meatballButton84 = Key('$_prefix.84.meatball.button');
|
||||
const meatballButton85 = Key('$_prefix.85.meatball.button');
|
||||
const meatballButton86 = Key('$_prefix.86.meatball.button');
|
||||
const meatballButton87 = Key('$_prefix.87.meatball.button');
|
||||
const meatballButton88 = Key('$_prefix.88.meatball.button');
|
||||
const meatballButton89 = Key('$_prefix.89.meatball.button');
|
||||
const meatballButton8a = Key('$_prefix.8a.meatball.button');
|
||||
const meatballButton8b = Key('$_prefix.8b.meatball.button');
|
||||
const meatballButton8c = Key('$_prefix.8c.meatball.button');
|
||||
const meatballButton8d = Key('$_prefix.8d.meatball.button');
|
||||
const meatballButton8e = Key('$_prefix.8e.meatball.button');
|
||||
const meatballButton8f = Key('$_prefix.8f.meatball.button');
|
||||
const meatballButton90 = Key('$_prefix.90.meatball.button');
|
||||
const meatballButton91 = Key('$_prefix.91.meatball.button');
|
||||
const meatballButton92 = Key('$_prefix.92.meatball.button');
|
||||
const meatballButton93 = Key('$_prefix.93.meatball.button');
|
||||
const meatballButton94 = Key('$_prefix.94.meatball.button');
|
||||
const meatballButton95 = Key('$_prefix.95.meatball.button');
|
||||
|
||||
const appListItem9a = Key('$_prefix.9a.applistitem');
|
||||
const appListItem9c = Key('$_prefix.9c.applistitem');
|
||||
const appListItem9d = Key('$_prefix.9d.applistitem');
|
||||
const appListItem9e = Key('$_prefix.9e.applistitem');
|
||||
const appListItem82 = Key('$_prefix.82.applistitem');
|
||||
const appListItem83 = Key('$_prefix.83.applistitem');
|
||||
const appListItem84 = Key('$_prefix.84.applistitem');
|
||||
const appListItem85 = Key('$_prefix.85.applistitem');
|
||||
const appListItem86 = Key('$_prefix.86.applistitem');
|
||||
const appListItem87 = Key('$_prefix.87.applistitem');
|
||||
const appListItem88 = Key('$_prefix.88.applistitem');
|
||||
const appListItem89 = Key('$_prefix.89.applistitem');
|
||||
const appListItem8a = Key('$_prefix.8a.applistitem');
|
||||
const appListItem8b = Key('$_prefix.8b.applistitem');
|
||||
const appListItem8c = Key('$_prefix.8c.applistitem');
|
||||
const appListItem8d = Key('$_prefix.8d.applistitem');
|
||||
const appListItem8e = Key('$_prefix.8e.applistitem');
|
||||
const appListItem8f = Key('$_prefix.8f.applistitem');
|
||||
const appListItem90 = Key('$_prefix.90.applistitem');
|
||||
const appListItem91 = Key('$_prefix.91.applistitem');
|
||||
const appListItem92 = Key('$_prefix.92.applistitem');
|
||||
const appListItem93 = Key('$_prefix.93.applistitem');
|
||||
const appListItem94 = Key('$_prefix.94.applistitem');
|
||||
const appListItem95 = Key('$_prefix.95.applistitem');
|
||||
|
||||
// SlotMetadata body keys
|
||||
const slotMetadataKeyType = Key('$_prefix.slotMetadata.keyType');
|
||||
|
@ -47,10 +47,31 @@ enum SlotId {
|
||||
authentication(0x9a),
|
||||
signature(0x9c),
|
||||
keyManagement(0x9d),
|
||||
cardAuth(0x9e);
|
||||
cardAuth(0x9e),
|
||||
retired1(0x82, true),
|
||||
retired2(0x83, true),
|
||||
retired3(0x84, true),
|
||||
retired4(0x85, true),
|
||||
retired5(0x86, true),
|
||||
retired6(0x87, true),
|
||||
retired7(0x88, true),
|
||||
retired8(0x89, true),
|
||||
retired9(0x8a, true),
|
||||
retired10(0x8b, true),
|
||||
retired11(0x8c, true),
|
||||
retired12(0x8d, true),
|
||||
retired13(0x8e, true),
|
||||
retired14(0x8f, true),
|
||||
retired15(0x90, true),
|
||||
retired16(0x91, true),
|
||||
retired17(0x92, true),
|
||||
retired18(0x93, true),
|
||||
retired19(0x94, true),
|
||||
retired20(0x95, true);
|
||||
|
||||
final int id;
|
||||
const SlotId(this.id);
|
||||
final bool isRetired;
|
||||
const SlotId(this.id, [this.isRetired = false]);
|
||||
|
||||
String get hexId => id.toRadixString(16).padLeft(2, '0');
|
||||
|
||||
@ -61,6 +82,7 @@ enum SlotId {
|
||||
SlotId.signature => nameFor(l10n.s_slot_9c),
|
||||
SlotId.keyManagement => nameFor(l10n.s_slot_9d),
|
||||
SlotId.cardAuth => nameFor(l10n.s_slot_9e),
|
||||
_ => l10n.s_retired_slot_display_name(hexId)
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -79,4 +79,6 @@ abstract class PivSlotsNotifier
|
||||
TouchPolicy touchPolicy = TouchPolicy.dfault,
|
||||
});
|
||||
Future<void> delete(SlotId slot, bool deleteCert, bool deleteKey);
|
||||
Future<void> moveKey(SlotId source, SlotId destination, bool overwriteKey,
|
||||
bool includeCertificate);
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ import 'authentication_dialog.dart';
|
||||
import 'delete_certificate_dialog.dart';
|
||||
import 'generate_key_dialog.dart';
|
||||
import 'import_file_dialog.dart';
|
||||
import 'move_key_dialog.dart';
|
||||
import 'pin_dialog.dart';
|
||||
|
||||
class GenerateIntent extends Intent {
|
||||
@ -52,6 +53,11 @@ class ExportIntent extends Intent {
|
||||
const ExportIntent(this.slot);
|
||||
}
|
||||
|
||||
class MoveIntent extends Intent {
|
||||
final PivSlot slot;
|
||||
const MoveIntent(this.slot);
|
||||
}
|
||||
|
||||
Future<bool> _authIfNeeded(BuildContext context, WidgetRef ref,
|
||||
DevicePath devicePath, PivState pivState) async {
|
||||
if (pivState.needsAuth) {
|
||||
@ -270,6 +276,25 @@ class PivActions extends ConsumerWidget {
|
||||
false);
|
||||
return deleted;
|
||||
}),
|
||||
if (hasFeature(features.slotsMove))
|
||||
MoveIntent: CallbackAction<MoveIntent>(onInvoke: (intent) async {
|
||||
if (!await withContext((context) =>
|
||||
_authIfNeeded(context, ref, devicePath, pivState))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final bool? moved = await withContext((context) async =>
|
||||
await showBlurDialog(
|
||||
context: context,
|
||||
builder: (context) => MoveKeyDialog(
|
||||
devicePath,
|
||||
pivState,
|
||||
intent.slot,
|
||||
),
|
||||
) ??
|
||||
false);
|
||||
return moved;
|
||||
}),
|
||||
},
|
||||
child: Builder(
|
||||
// Builder to ensure new scope for actions, they can invoke parent actions
|
||||
@ -288,8 +313,9 @@ 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);
|
||||
final canDeleteOrMoveKey = hasKey && pivState.version.isAtLeast(5, 7);
|
||||
return [
|
||||
if (!slot.slot.isRetired) ...[
|
||||
ActionItem(
|
||||
key: keys.generateAction,
|
||||
feature: features.slotsGenerate,
|
||||
@ -307,6 +333,7 @@ List<ActionItem> buildSlotActions(
|
||||
subtitle: l10n.l_import_desc,
|
||||
intent: ImportIntent(slot),
|
||||
),
|
||||
],
|
||||
if (hasCert) ...[
|
||||
ActionItem(
|
||||
key: keys.exportAction,
|
||||
@ -326,18 +353,28 @@ List<ActionItem> buildSlotActions(
|
||||
intent: ExportIntent(slot),
|
||||
),
|
||||
],
|
||||
if (hasCert || canDeleteKey)
|
||||
if (canDeleteOrMoveKey)
|
||||
ActionItem(
|
||||
key: keys.moveAction,
|
||||
feature: features.slotsMove,
|
||||
actionStyle: ActionStyle.error,
|
||||
icon: const Icon(Symbols.move_item),
|
||||
title: l10n.l_move_key,
|
||||
subtitle: l10n.l_move_key_desc,
|
||||
intent: MoveIntent(slot),
|
||||
),
|
||||
if (hasCert || canDeleteOrMoveKey)
|
||||
ActionItem(
|
||||
key: keys.deleteAction,
|
||||
feature: features.slotsDelete,
|
||||
actionStyle: ActionStyle.error,
|
||||
icon: const Icon(Symbols.delete),
|
||||
title: hasCert && canDeleteKey
|
||||
title: hasCert && canDeleteOrMoveKey
|
||||
? l10n.l_delete_certificate_or_key
|
||||
: hasCert
|
||||
? l10n.l_delete_certificate
|
||||
: l10n.l_delete_key,
|
||||
subtitle: hasCert && canDeleteKey
|
||||
subtitle: hasCert && canDeleteOrMoveKey
|
||||
? l10n.l_delete_certificate_or_key_desc
|
||||
: hasCert
|
||||
? l10n.l_delete_certificate_desc
|
||||
|
162
lib/piv/views/move_key_dialog.dart
Normal file
162
lib/piv/views/move_key_dialog.dart
Normal file
@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Yubico.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../app/message.dart';
|
||||
import '../../app/models.dart';
|
||||
import '../../app/state.dart';
|
||||
import '../../exception/cancellation_exception.dart';
|
||||
import '../../widgets/choice_filter_chip.dart';
|
||||
import '../../widgets/responsive_dialog.dart';
|
||||
import '../keys.dart' as keys;
|
||||
import '../models.dart';
|
||||
import '../state.dart';
|
||||
import 'overwrite_confirm_dialog.dart';
|
||||
|
||||
class MoveKeyDialog extends ConsumerStatefulWidget {
|
||||
final DevicePath devicePath;
|
||||
final PivState pivState;
|
||||
final PivSlot pivSlot;
|
||||
const MoveKeyDialog(this.devicePath, this.pivState, this.pivSlot,
|
||||
{super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<ConsumerStatefulWidget> createState() => _MoveKeyDialogState();
|
||||
}
|
||||
|
||||
class _MoveKeyDialogState extends ConsumerState<MoveKeyDialog> {
|
||||
SlotId? _destination;
|
||||
late bool _includeCertificate;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_includeCertificate = widget.pivSlot.certInfo != null;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
return ResponsiveDialog(
|
||||
title: Text(l10n.l_move_key),
|
||||
actions: [
|
||||
TextButton(
|
||||
key: keys.deleteButton,
|
||||
onPressed: _destination != null
|
||||
? () async {
|
||||
try {
|
||||
final pivSlots =
|
||||
ref.read(pivSlotsProvider(widget.devicePath)).asData;
|
||||
if (pivSlots != null) {
|
||||
final destination = pivSlots.value.firstWhere(
|
||||
(element) => element.slot == _destination);
|
||||
|
||||
if (!await confirmOverwrite(context, destination,
|
||||
writeKey: true, writeCert: _includeCertificate)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await ref
|
||||
.read(pivSlotsProvider(widget.devicePath).notifier)
|
||||
.moveKey(
|
||||
widget.pivSlot.slot,
|
||||
destination.slot,
|
||||
destination.metadata != null,
|
||||
_includeCertificate);
|
||||
|
||||
await ref.read(withContextProvider)(
|
||||
(context) async {
|
||||
String message;
|
||||
if (_includeCertificate) {
|
||||
message = l10n.l_key_and_certificate_moved;
|
||||
} else {
|
||||
message = l10n.l_key_moved;
|
||||
}
|
||||
|
||||
Navigator.of(context).pop(true);
|
||||
showMessage(context, message);
|
||||
},
|
||||
);
|
||||
}
|
||||
} on CancellationException catch (_) {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
: null,
|
||||
child: Text(l10n.s_move),
|
||||
),
|
||||
],
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(_destination == null
|
||||
? l10n.q_move_key_confirm(
|
||||
widget.pivSlot.slot.getDisplayName(l10n))
|
||||
: widget.pivSlot.certInfo != null && _includeCertificate
|
||||
? l10n.q_move_key_and_certificate_to_slot_confirm(
|
||||
widget.pivSlot.slot.getDisplayName(l10n),
|
||||
_destination!.getDisplayName(l10n))
|
||||
: l10n.q_move_key_to_slot_confirm(
|
||||
widget.pivSlot.slot.getDisplayName(l10n),
|
||||
_destination!.getDisplayName(l10n))),
|
||||
Wrap(
|
||||
spacing: 4.0,
|
||||
runSpacing: 8.0,
|
||||
children: [
|
||||
ChoiceFilterChip<SlotId?>(
|
||||
menuConstraints: const BoxConstraints(maxHeight: 200),
|
||||
value: _destination,
|
||||
items: SlotId.values
|
||||
.where((element) => element != widget.pivSlot.slot)
|
||||
.toList(),
|
||||
labelBuilder: (value) => Text(_destination == null
|
||||
? l10n.l_select_destination_slot
|
||||
: _destination!.getDisplayName(l10n)),
|
||||
itemBuilder: (value) => Text(value!.getDisplayName(l10n)),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_destination = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
if (widget.pivSlot.certInfo != null)
|
||||
FilterChip(
|
||||
label: Text(l10n.l_include_certificate),
|
||||
selected: _includeCertificate,
|
||||
onSelected: (value) {
|
||||
setState(() {
|
||||
_includeCertificate = value;
|
||||
});
|
||||
})
|
||||
],
|
||||
),
|
||||
]
|
||||
.map((e) => Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: e,
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -72,6 +72,16 @@ class _PivScreenState extends ConsumerState<PivScreen> {
|
||||
final selected = _selected != null
|
||||
? pivSlots?.value.firstWhere((e) => e.slot == _selected)
|
||||
: null;
|
||||
final normalSlots = pivSlots?.value
|
||||
.where((element) => !element.slot.isRetired)
|
||||
.toList() ??
|
||||
[];
|
||||
final shownRetiredSlots = pivSlots?.value
|
||||
.where((element) =>
|
||||
element.slot.isRetired &&
|
||||
(element.certInfo != null && element.metadata != null))
|
||||
.toList() ??
|
||||
[];
|
||||
final theme = Theme.of(context);
|
||||
final textTheme = theme.textTheme;
|
||||
// This is what ListTile uses for subtitle
|
||||
@ -184,8 +194,7 @@ class _PivScreenState extends ConsumerState<PivScreen> {
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
if (pivSlots?.hasValue == true)
|
||||
...pivSlots!.value.map(
|
||||
...normalSlots.map(
|
||||
(e) => _CertificateListItem(
|
||||
pivState,
|
||||
e,
|
||||
@ -193,6 +202,14 @@ class _PivScreenState extends ConsumerState<PivScreen> {
|
||||
selected: e == selected,
|
||||
),
|
||||
),
|
||||
...shownRetiredSlots.map(
|
||||
(e) => _CertificateListItem(
|
||||
pivState,
|
||||
e,
|
||||
expanded: expanded,
|
||||
selected: e == selected,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
@ -229,7 +246,7 @@ class _CertificateListItem extends ConsumerWidget {
|
||||
leading: CircleAvatar(
|
||||
foregroundColor: colorScheme.onSecondary,
|
||||
backgroundColor: colorScheme.secondary,
|
||||
child: const Icon(Symbols.badge),
|
||||
child: Icon(slot.isRetired ? Symbols.manage_history : Symbols.badge),
|
||||
),
|
||||
title: slot.getDisplayName(l10n),
|
||||
subtitle: certInfo != null
|
||||
@ -258,12 +275,52 @@ class _CertificateListItem extends ConsumerWidget {
|
||||
SlotId.signature => meatballButton9c,
|
||||
SlotId.keyManagement => meatballButton9d,
|
||||
SlotId.cardAuth => meatballButton9e,
|
||||
SlotId.retired1 => meatballButton82,
|
||||
SlotId.retired2 => meatballButton83,
|
||||
SlotId.retired3 => meatballButton84,
|
||||
SlotId.retired4 => meatballButton85,
|
||||
SlotId.retired5 => meatballButton86,
|
||||
SlotId.retired6 => meatballButton87,
|
||||
SlotId.retired7 => meatballButton88,
|
||||
SlotId.retired8 => meatballButton89,
|
||||
SlotId.retired9 => meatballButton8a,
|
||||
SlotId.retired10 => meatballButton8b,
|
||||
SlotId.retired11 => meatballButton8c,
|
||||
SlotId.retired12 => meatballButton8d,
|
||||
SlotId.retired13 => meatballButton8e,
|
||||
SlotId.retired14 => meatballButton8f,
|
||||
SlotId.retired15 => meatballButton90,
|
||||
SlotId.retired16 => meatballButton91,
|
||||
SlotId.retired17 => meatballButton92,
|
||||
SlotId.retired18 => meatballButton93,
|
||||
SlotId.retired19 => meatballButton94,
|
||||
SlotId.retired20 => meatballButton95
|
||||
};
|
||||
|
||||
Key _getAppListItemKey(SlotId slotId) => switch (slotId) {
|
||||
SlotId.authentication => appListItem9a,
|
||||
SlotId.signature => appListItem9c,
|
||||
SlotId.keyManagement => appListItem9d,
|
||||
SlotId.cardAuth => appListItem9e
|
||||
SlotId.cardAuth => appListItem9e,
|
||||
SlotId.retired1 => appListItem82,
|
||||
SlotId.retired2 => appListItem83,
|
||||
SlotId.retired3 => appListItem84,
|
||||
SlotId.retired4 => appListItem85,
|
||||
SlotId.retired5 => appListItem86,
|
||||
SlotId.retired6 => appListItem87,
|
||||
SlotId.retired7 => appListItem88,
|
||||
SlotId.retired8 => appListItem89,
|
||||
SlotId.retired9 => appListItem8a,
|
||||
SlotId.retired10 => appListItem8b,
|
||||
SlotId.retired11 => appListItem8c,
|
||||
SlotId.retired12 => appListItem8d,
|
||||
SlotId.retired13 => appListItem8e,
|
||||
SlotId.retired14 => appListItem8f,
|
||||
SlotId.retired15 => appListItem90,
|
||||
SlotId.retired16 => appListItem91,
|
||||
SlotId.retired17 => appListItem92,
|
||||
SlotId.retired18 => appListItem93,
|
||||
SlotId.retired19 => appListItem94,
|
||||
SlotId.retired20 => appListItem95
|
||||
};
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ class ChoiceFilterChip<T> extends StatefulWidget {
|
||||
final Widget? avatar;
|
||||
final bool selected;
|
||||
final bool? disableHover;
|
||||
final BoxConstraints? menuConstraints;
|
||||
const ChoiceFilterChip({
|
||||
super.key,
|
||||
required this.value,
|
||||
@ -40,6 +41,7 @@ class ChoiceFilterChip<T> extends StatefulWidget {
|
||||
this.selected = false,
|
||||
this.disableHover,
|
||||
this.labelBuilder,
|
||||
this.menuConstraints,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -63,6 +65,7 @@ class _ChoiceFilterChipState<T> extends State<ChoiceFilterChip<T>> {
|
||||
Offset.zero & overlay.size,
|
||||
);
|
||||
return await showMenu(
|
||||
constraints: widget.menuConstraints,
|
||||
context: context,
|
||||
position: position,
|
||||
shape: const RoundedRectangleBorder(
|
||||
|
Loading…
Reference in New Issue
Block a user