mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-12-23 10:11:52 +03:00
PIV fixes.
This commit is contained in:
parent
52bff18471
commit
7f1963d310
@ -449,6 +449,8 @@
|
||||
"slot": {}
|
||||
}
|
||||
},
|
||||
"l_generating_private_key": "Generating private key\u2026",
|
||||
"s_private_key_generated": "Private key generated",
|
||||
"p_warning_delete_certificate": "Warning! This action will delete the certificate from your YubiKey.",
|
||||
"q_delete_certificate_confirm": "Delete the certficate in PIV slot {slot}?",
|
||||
"@q_delete_certificate_confirm" : {
|
||||
@ -525,7 +527,7 @@
|
||||
"p_warning_deletes_accounts": "Warning! This will irrevocably delete all U2F and FIDO2 accounts from your YubiKey.",
|
||||
"p_warning_disable_accounts": "Your credentials, as well as any PIN set, will be removed from this YubiKey. Make sure to first disable these from their respective web sites to avoid being locked out of your accounts.",
|
||||
"p_warning_piv_reset": "Warning! All data stored for PIV will be irrevocably deleted from your YubiKey.",
|
||||
"p_warning_piv_reset_desc": "This includes private keys and certificates. Your PIN, PUK, and management key will be reset to their factory detault values.",
|
||||
"p_warning_piv_reset_desc": "This includes private keys and certificates. Your PIN, PUK, and management key will be reset to their factory default values.",
|
||||
|
||||
"@_copy_to_clipboard": {},
|
||||
"l_copy_to_clipboard": "Copy to clipboard",
|
||||
|
@ -18,7 +18,9 @@ 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 '../../core/models.dart';
|
||||
import '../../widgets/choice_filter_chip.dart';
|
||||
import '../../widgets/responsive_dialog.dart';
|
||||
@ -46,6 +48,7 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
|
||||
late DateTime _validTo;
|
||||
late DateTime _validToDefault;
|
||||
late DateTime _validToMax;
|
||||
bool _generating = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -61,31 +64,57 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final navigator = Navigator.of(context);
|
||||
|
||||
return ResponsiveDialog(
|
||||
allowCancel: !_generating,
|
||||
title: Text(l10n.s_generate_key),
|
||||
actions: [
|
||||
TextButton(
|
||||
key: keys.saveButton,
|
||||
onPressed: () async {
|
||||
final result = await ref
|
||||
.read(pivSlotsProvider(widget.devicePath).notifier)
|
||||
.generate(
|
||||
widget.pivSlot.slot,
|
||||
_keyType,
|
||||
parameters: switch (_generateType) {
|
||||
GenerateType.certificate =>
|
||||
PivGenerateParameters.certificate(
|
||||
subject: _subject,
|
||||
validFrom: _validFrom,
|
||||
validTo: _validTo),
|
||||
GenerateType.csr =>
|
||||
PivGenerateParameters.csr(subject: _subject),
|
||||
},
|
||||
);
|
||||
onPressed: _generating || _subject.isEmpty
|
||||
? null
|
||||
: () async {
|
||||
setState(() {
|
||||
_generating = true;
|
||||
});
|
||||
|
||||
navigator.pop(result);
|
||||
},
|
||||
Function()? close;
|
||||
final PivGenerateResult result;
|
||||
try {
|
||||
close = showMessage(
|
||||
context,
|
||||
l10n.l_generating_private_key,
|
||||
duration: const Duration(seconds: 30),
|
||||
);
|
||||
result = await ref
|
||||
.read(pivSlotsProvider(widget.devicePath).notifier)
|
||||
.generate(
|
||||
widget.pivSlot.slot,
|
||||
_keyType,
|
||||
parameters: switch (_generateType) {
|
||||
GenerateType.certificate =>
|
||||
PivGenerateParameters.certificate(
|
||||
subject: _subject,
|
||||
validFrom: _validFrom,
|
||||
validTo: _validTo),
|
||||
GenerateType.csr =>
|
||||
PivGenerateParameters.csr(subject: _subject),
|
||||
},
|
||||
);
|
||||
} finally {
|
||||
close?.call();
|
||||
}
|
||||
|
||||
await ref.read(withContextProvider)(
|
||||
(context) async {
|
||||
Navigator.of(context).pop(result);
|
||||
showMessage(
|
||||
context,
|
||||
l10n.s_private_key_generated,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Text(l10n.s_save),
|
||||
),
|
||||
],
|
||||
@ -102,9 +131,14 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
|
||||
labelText: l10n.s_subject,
|
||||
),
|
||||
textInputAction: TextInputAction.next,
|
||||
enabled: !_generating,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_subject = value.contains('=') ? value : 'CN=$value';
|
||||
if (value.isEmpty) {
|
||||
_subject = '';
|
||||
} else {
|
||||
_subject = value.contains('=') ? value : 'CN=$value';
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
@ -118,39 +152,45 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
|
||||
value: _generateType,
|
||||
selected: _generateType != defaultGenerateType,
|
||||
itemBuilder: (value) => Text(value.getDisplayName(l10n)),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_generateType = value;
|
||||
});
|
||||
},
|
||||
onChanged: _generating
|
||||
? null
|
||||
: (value) {
|
||||
setState(() {
|
||||
_generateType = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
ChoiceFilterChip<KeyType>(
|
||||
items: KeyType.values,
|
||||
value: _keyType,
|
||||
selected: _keyType != defaultKeyType,
|
||||
itemBuilder: (value) => Text(value.getDisplayName(l10n)),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_keyType = value;
|
||||
});
|
||||
},
|
||||
onChanged: _generating
|
||||
? null
|
||||
: (value) {
|
||||
setState(() {
|
||||
_keyType = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
if (_generateType == GenerateType.certificate)
|
||||
FilterChip(
|
||||
label: Text(dateFormatter.format(_validTo)),
|
||||
onSelected: (value) async {
|
||||
final selected = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: _validTo,
|
||||
firstDate: _validFrom,
|
||||
lastDate: _validToMax,
|
||||
);
|
||||
if (selected != null) {
|
||||
setState(() {
|
||||
_validTo = selected;
|
||||
});
|
||||
}
|
||||
},
|
||||
onSelected: _generating
|
||||
? null
|
||||
: (value) async {
|
||||
final selected = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: _validTo,
|
||||
firstDate: _validFrom,
|
||||
lastDate: _validToMax,
|
||||
);
|
||||
if (selected != null) {
|
||||
setState(() {
|
||||
_validTo = selected;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
]),
|
||||
]
|
||||
|
@ -30,6 +30,8 @@ import 'reset_dialog.dart';
|
||||
|
||||
Widget pivBuildActions(BuildContext context, DevicePath devicePath,
|
||||
PivState pivState, WidgetRef ref) {
|
||||
final colors = Theme.of(context).buttonTheme.colorScheme ??
|
||||
Theme.of(context).colorScheme;
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
final usingDefaultMgmtKey =
|
||||
@ -87,7 +89,7 @@ Widget pivBuildActions(BuildContext context, DevicePath devicePath,
|
||||
: l10n.l_change_management_key),
|
||||
icon: const Icon(Icons.key_outlined),
|
||||
trailing: usingDefaultMgmtKey
|
||||
? const Icon(Icons.warning_amber)
|
||||
? Icon(Icons.warning_amber, color: colors.tertiary)
|
||||
: null,
|
||||
onTap: (context) {
|
||||
Navigator.of(context).pop();
|
||||
|
@ -129,12 +129,13 @@ class _ManageKeyDialogState extends ConsumerState<ManageKeyDialog> {
|
||||
final currentLenOk = protected
|
||||
? _currentKeyOrPin.length >= 4
|
||||
: _currentKeyOrPin.length == currentType.keyLength * 2;
|
||||
final newLenOk = _keyController.text.length == hexLength;
|
||||
|
||||
return ResponsiveDialog(
|
||||
title: Text(l10n.l_change_management_key),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: _submit,
|
||||
onPressed: currentLenOk && newLenOk ? _submit : null,
|
||||
key: keys.saveButton,
|
||||
child: Text(l10n.s_save),
|
||||
)
|
||||
@ -232,7 +233,7 @@ class _ManageKeyDialogState extends ConsumerState<ManageKeyDialog> {
|
||||
),
|
||||
textInputAction: TextInputAction.next,
|
||||
onSubmitted: (_) {
|
||||
if (_keyController.text.length == hexLength) {
|
||||
if (currentLenOk && newLenOk) {
|
||||
_submit();
|
||||
}
|
||||
},
|
||||
|
@ -160,7 +160,9 @@ class _ManagePinPukDialogState extends ConsumerState<ManagePinPukDialog> {
|
||||
autofillHints: const [AutofillHints.newPassword],
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: l10n.s_confirm_pin,
|
||||
labelText: widget.target == ManageTarget.puk
|
||||
? l10n.s_confirm_puk
|
||||
: l10n.s_confirm_pin,
|
||||
prefixIcon: const Icon(Icons.password_outlined),
|
||||
enabled: _currentPin.length >= 4 && _newPin.length >= 6,
|
||||
),
|
||||
|
@ -21,6 +21,7 @@ const accentGreen = Color(0xff9aca3c);
|
||||
const primaryBlue = Color(0xff325f74);
|
||||
const primaryRed = Color(0xffea4335);
|
||||
const darkRed = Color(0xffda4d41);
|
||||
const amber = Color(0xffffca28);
|
||||
|
||||
class AppTheme {
|
||||
static ThemeData get lightTheme => ThemeData(
|
||||
@ -32,6 +33,7 @@ class AppTheme {
|
||||
).copyWith(
|
||||
primary: primaryBlue,
|
||||
//secondary: accentGreen,
|
||||
tertiary: amber.withOpacity(0.7),
|
||||
),
|
||||
textTheme: TextTheme(
|
||||
bodySmall: TextStyle(color: Colors.grey.shade900),
|
||||
@ -57,6 +59,7 @@ class AppTheme {
|
||||
//onPrimaryContainer: Colors.grey.shade100,
|
||||
error: darkRed,
|
||||
onError: Colors.white.withOpacity(0.9),
|
||||
tertiary: amber.withOpacity(0.7),
|
||||
),
|
||||
textTheme: TextTheme(
|
||||
bodySmall: TextStyle(color: Colors.grey.shade500),
|
||||
|
@ -22,13 +22,16 @@ class ResponsiveDialog extends StatefulWidget {
|
||||
final Widget child;
|
||||
final List<Widget> actions;
|
||||
final Function()? onCancel;
|
||||
final bool allowCancel;
|
||||
|
||||
const ResponsiveDialog(
|
||||
{super.key,
|
||||
required this.child,
|
||||
this.title,
|
||||
this.actions = const [],
|
||||
this.onCancel});
|
||||
const ResponsiveDialog({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.title,
|
||||
this.actions = const [],
|
||||
this.onCancel,
|
||||
this.allowCancel = true,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ResponsiveDialog> createState() => _ResponsiveDialogState();
|
||||
@ -47,12 +50,14 @@ class _ResponsiveDialogState extends State<ResponsiveDialog> {
|
||||
appBar: AppBar(
|
||||
title: widget.title,
|
||||
actions: widget.actions,
|
||||
leading: CloseButton(
|
||||
onPressed: () {
|
||||
widget.onCancel?.call();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: widget.allowCancel
|
||||
? () {
|
||||
widget.onCancel?.call();
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
: null),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: SafeArea(
|
||||
|
Loading…
Reference in New Issue
Block a user