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": {}
|
"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.",
|
"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": "Delete the certficate in PIV slot {slot}?",
|
||||||
"@q_delete_certificate_confirm" : {
|
"@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_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_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": "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": {},
|
"@_copy_to_clipboard": {},
|
||||||
"l_copy_to_clipboard": "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_gen/gen_l10n/app_localizations.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
import '../../app/message.dart';
|
||||||
import '../../app/models.dart';
|
import '../../app/models.dart';
|
||||||
|
import '../../app/state.dart';
|
||||||
import '../../core/models.dart';
|
import '../../core/models.dart';
|
||||||
import '../../widgets/choice_filter_chip.dart';
|
import '../../widgets/choice_filter_chip.dart';
|
||||||
import '../../widgets/responsive_dialog.dart';
|
import '../../widgets/responsive_dialog.dart';
|
||||||
@ -46,6 +48,7 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
|
|||||||
late DateTime _validTo;
|
late DateTime _validTo;
|
||||||
late DateTime _validToDefault;
|
late DateTime _validToDefault;
|
||||||
late DateTime _validToMax;
|
late DateTime _validToMax;
|
||||||
|
bool _generating = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -61,31 +64,57 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final l10n = AppLocalizations.of(context)!;
|
final l10n = AppLocalizations.of(context)!;
|
||||||
final navigator = Navigator.of(context);
|
|
||||||
return ResponsiveDialog(
|
return ResponsiveDialog(
|
||||||
|
allowCancel: !_generating,
|
||||||
title: Text(l10n.s_generate_key),
|
title: Text(l10n.s_generate_key),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
key: keys.saveButton,
|
key: keys.saveButton,
|
||||||
onPressed: () async {
|
onPressed: _generating || _subject.isEmpty
|
||||||
final result = await ref
|
? null
|
||||||
.read(pivSlotsProvider(widget.devicePath).notifier)
|
: () async {
|
||||||
.generate(
|
setState(() {
|
||||||
widget.pivSlot.slot,
|
_generating = true;
|
||||||
_keyType,
|
});
|
||||||
parameters: switch (_generateType) {
|
|
||||||
GenerateType.certificate =>
|
|
||||||
PivGenerateParameters.certificate(
|
|
||||||
subject: _subject,
|
|
||||||
validFrom: _validFrom,
|
|
||||||
validTo: _validTo),
|
|
||||||
GenerateType.csr =>
|
|
||||||
PivGenerateParameters.csr(subject: _subject),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
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),
|
child: Text(l10n.s_save),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -102,9 +131,14 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
|
|||||||
labelText: l10n.s_subject,
|
labelText: l10n.s_subject,
|
||||||
),
|
),
|
||||||
textInputAction: TextInputAction.next,
|
textInputAction: TextInputAction.next,
|
||||||
|
enabled: !_generating,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
setState(() {
|
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,
|
value: _generateType,
|
||||||
selected: _generateType != defaultGenerateType,
|
selected: _generateType != defaultGenerateType,
|
||||||
itemBuilder: (value) => Text(value.getDisplayName(l10n)),
|
itemBuilder: (value) => Text(value.getDisplayName(l10n)),
|
||||||
onChanged: (value) {
|
onChanged: _generating
|
||||||
setState(() {
|
? null
|
||||||
_generateType = value;
|
: (value) {
|
||||||
});
|
setState(() {
|
||||||
},
|
_generateType = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
),
|
),
|
||||||
ChoiceFilterChip<KeyType>(
|
ChoiceFilterChip<KeyType>(
|
||||||
items: KeyType.values,
|
items: KeyType.values,
|
||||||
value: _keyType,
|
value: _keyType,
|
||||||
selected: _keyType != defaultKeyType,
|
selected: _keyType != defaultKeyType,
|
||||||
itemBuilder: (value) => Text(value.getDisplayName(l10n)),
|
itemBuilder: (value) => Text(value.getDisplayName(l10n)),
|
||||||
onChanged: (value) {
|
onChanged: _generating
|
||||||
setState(() {
|
? null
|
||||||
_keyType = value;
|
: (value) {
|
||||||
});
|
setState(() {
|
||||||
},
|
_keyType = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
),
|
),
|
||||||
if (_generateType == GenerateType.certificate)
|
if (_generateType == GenerateType.certificate)
|
||||||
FilterChip(
|
FilterChip(
|
||||||
label: Text(dateFormatter.format(_validTo)),
|
label: Text(dateFormatter.format(_validTo)),
|
||||||
onSelected: (value) async {
|
onSelected: _generating
|
||||||
final selected = await showDatePicker(
|
? null
|
||||||
context: context,
|
: (value) async {
|
||||||
initialDate: _validTo,
|
final selected = await showDatePicker(
|
||||||
firstDate: _validFrom,
|
context: context,
|
||||||
lastDate: _validToMax,
|
initialDate: _validTo,
|
||||||
);
|
firstDate: _validFrom,
|
||||||
if (selected != null) {
|
lastDate: _validToMax,
|
||||||
setState(() {
|
);
|
||||||
_validTo = selected;
|
if (selected != null) {
|
||||||
});
|
setState(() {
|
||||||
}
|
_validTo = selected;
|
||||||
},
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
]
|
]
|
||||||
|
@ -30,6 +30,8 @@ import 'reset_dialog.dart';
|
|||||||
|
|
||||||
Widget pivBuildActions(BuildContext context, DevicePath devicePath,
|
Widget pivBuildActions(BuildContext context, DevicePath devicePath,
|
||||||
PivState pivState, WidgetRef ref) {
|
PivState pivState, WidgetRef ref) {
|
||||||
|
final colors = Theme.of(context).buttonTheme.colorScheme ??
|
||||||
|
Theme.of(context).colorScheme;
|
||||||
final l10n = AppLocalizations.of(context)!;
|
final l10n = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
final usingDefaultMgmtKey =
|
final usingDefaultMgmtKey =
|
||||||
@ -87,7 +89,7 @@ Widget pivBuildActions(BuildContext context, DevicePath devicePath,
|
|||||||
: l10n.l_change_management_key),
|
: l10n.l_change_management_key),
|
||||||
icon: const Icon(Icons.key_outlined),
|
icon: const Icon(Icons.key_outlined),
|
||||||
trailing: usingDefaultMgmtKey
|
trailing: usingDefaultMgmtKey
|
||||||
? const Icon(Icons.warning_amber)
|
? Icon(Icons.warning_amber, color: colors.tertiary)
|
||||||
: null,
|
: null,
|
||||||
onTap: (context) {
|
onTap: (context) {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
|
@ -129,12 +129,13 @@ class _ManageKeyDialogState extends ConsumerState<ManageKeyDialog> {
|
|||||||
final currentLenOk = protected
|
final currentLenOk = protected
|
||||||
? _currentKeyOrPin.length >= 4
|
? _currentKeyOrPin.length >= 4
|
||||||
: _currentKeyOrPin.length == currentType.keyLength * 2;
|
: _currentKeyOrPin.length == currentType.keyLength * 2;
|
||||||
|
final newLenOk = _keyController.text.length == hexLength;
|
||||||
|
|
||||||
return ResponsiveDialog(
|
return ResponsiveDialog(
|
||||||
title: Text(l10n.l_change_management_key),
|
title: Text(l10n.l_change_management_key),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: _submit,
|
onPressed: currentLenOk && newLenOk ? _submit : null,
|
||||||
key: keys.saveButton,
|
key: keys.saveButton,
|
||||||
child: Text(l10n.s_save),
|
child: Text(l10n.s_save),
|
||||||
)
|
)
|
||||||
@ -232,7 +233,7 @@ class _ManageKeyDialogState extends ConsumerState<ManageKeyDialog> {
|
|||||||
),
|
),
|
||||||
textInputAction: TextInputAction.next,
|
textInputAction: TextInputAction.next,
|
||||||
onSubmitted: (_) {
|
onSubmitted: (_) {
|
||||||
if (_keyController.text.length == hexLength) {
|
if (currentLenOk && newLenOk) {
|
||||||
_submit();
|
_submit();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -160,7 +160,9 @@ class _ManagePinPukDialogState extends ConsumerState<ManagePinPukDialog> {
|
|||||||
autofillHints: const [AutofillHints.newPassword],
|
autofillHints: const [AutofillHints.newPassword],
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
border: const OutlineInputBorder(),
|
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),
|
prefixIcon: const Icon(Icons.password_outlined),
|
||||||
enabled: _currentPin.length >= 4 && _newPin.length >= 6,
|
enabled: _currentPin.length >= 4 && _newPin.length >= 6,
|
||||||
),
|
),
|
||||||
|
@ -21,6 +21,7 @@ const accentGreen = Color(0xff9aca3c);
|
|||||||
const primaryBlue = Color(0xff325f74);
|
const primaryBlue = Color(0xff325f74);
|
||||||
const primaryRed = Color(0xffea4335);
|
const primaryRed = Color(0xffea4335);
|
||||||
const darkRed = Color(0xffda4d41);
|
const darkRed = Color(0xffda4d41);
|
||||||
|
const amber = Color(0xffffca28);
|
||||||
|
|
||||||
class AppTheme {
|
class AppTheme {
|
||||||
static ThemeData get lightTheme => ThemeData(
|
static ThemeData get lightTheme => ThemeData(
|
||||||
@ -32,6 +33,7 @@ class AppTheme {
|
|||||||
).copyWith(
|
).copyWith(
|
||||||
primary: primaryBlue,
|
primary: primaryBlue,
|
||||||
//secondary: accentGreen,
|
//secondary: accentGreen,
|
||||||
|
tertiary: amber.withOpacity(0.7),
|
||||||
),
|
),
|
||||||
textTheme: TextTheme(
|
textTheme: TextTheme(
|
||||||
bodySmall: TextStyle(color: Colors.grey.shade900),
|
bodySmall: TextStyle(color: Colors.grey.shade900),
|
||||||
@ -57,6 +59,7 @@ class AppTheme {
|
|||||||
//onPrimaryContainer: Colors.grey.shade100,
|
//onPrimaryContainer: Colors.grey.shade100,
|
||||||
error: darkRed,
|
error: darkRed,
|
||||||
onError: Colors.white.withOpacity(0.9),
|
onError: Colors.white.withOpacity(0.9),
|
||||||
|
tertiary: amber.withOpacity(0.7),
|
||||||
),
|
),
|
||||||
textTheme: TextTheme(
|
textTheme: TextTheme(
|
||||||
bodySmall: TextStyle(color: Colors.grey.shade500),
|
bodySmall: TextStyle(color: Colors.grey.shade500),
|
||||||
|
@ -22,13 +22,16 @@ class ResponsiveDialog extends StatefulWidget {
|
|||||||
final Widget child;
|
final Widget child;
|
||||||
final List<Widget> actions;
|
final List<Widget> actions;
|
||||||
final Function()? onCancel;
|
final Function()? onCancel;
|
||||||
|
final bool allowCancel;
|
||||||
|
|
||||||
const ResponsiveDialog(
|
const ResponsiveDialog({
|
||||||
{super.key,
|
super.key,
|
||||||
required this.child,
|
required this.child,
|
||||||
this.title,
|
this.title,
|
||||||
this.actions = const [],
|
this.actions = const [],
|
||||||
this.onCancel});
|
this.onCancel,
|
||||||
|
this.allowCancel = true,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ResponsiveDialog> createState() => _ResponsiveDialogState();
|
State<ResponsiveDialog> createState() => _ResponsiveDialogState();
|
||||||
@ -47,12 +50,14 @@ class _ResponsiveDialogState extends State<ResponsiveDialog> {
|
|||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: widget.title,
|
title: widget.title,
|
||||||
actions: widget.actions,
|
actions: widget.actions,
|
||||||
leading: CloseButton(
|
leading: IconButton(
|
||||||
onPressed: () {
|
icon: const Icon(Icons.close),
|
||||||
widget.onCancel?.call();
|
onPressed: widget.allowCancel
|
||||||
Navigator.of(context).pop();
|
? () {
|
||||||
},
|
widget.onCancel?.call();
|
||||||
),
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
: null),
|
||||||
),
|
),
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
|
Loading…
Reference in New Issue
Block a user