PIV fixes.

This commit is contained in:
Dain Nilsson 2023-06-16 17:27:10 +02:00
parent 52bff18471
commit 7f1963d310
No known key found for this signature in database
GPG Key ID: F04367096FBA95E8
7 changed files with 115 additions and 60 deletions

View File

@ -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",

View File

@ -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,14 +64,29 @@ 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
onPressed: _generating || _subject.isEmpty
? null
: () async {
setState(() {
_generating = true;
});
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,
@ -83,8 +101,19 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
PivGenerateParameters.csr(subject: _subject),
},
);
} finally {
close?.call();
}
navigator.pop(result);
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(() {
if (value.isEmpty) {
_subject = '';
} else {
_subject = value.contains('=') ? value : 'CN=$value';
}
});
},
),
@ -118,7 +152,9 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
value: _generateType,
selected: _generateType != defaultGenerateType,
itemBuilder: (value) => Text(value.getDisplayName(l10n)),
onChanged: (value) {
onChanged: _generating
? null
: (value) {
setState(() {
_generateType = value;
});
@ -129,7 +165,9 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
value: _keyType,
selected: _keyType != defaultKeyType,
itemBuilder: (value) => Text(value.getDisplayName(l10n)),
onChanged: (value) {
onChanged: _generating
? null
: (value) {
setState(() {
_keyType = value;
});
@ -138,7 +176,9 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
if (_generateType == GenerateType.certificate)
FilterChip(
label: Text(dateFormatter.format(_validTo)),
onSelected: (value) async {
onSelected: _generating
? null
: (value) async {
final selected = await showDatePicker(
context: context,
initialDate: _validTo,

View File

@ -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();

View File

@ -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();
}
},

View File

@ -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,
),

View File

@ -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),

View File

@ -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,
const ResponsiveDialog({
super.key,
required this.child,
this.title,
this.actions = const [],
this.onCancel});
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: () {
leading: IconButton(
icon: const Icon(Icons.close),
onPressed: widget.allowCancel
? () {
widget.onCancel?.call();
Navigator.of(context).pop();
},
),
}
: null),
),
body: SingleChildScrollView(
child: SafeArea(