Skip to content

Commit

Permalink
Disable PIV actions on PIN blocked
Browse files Browse the repository at this point in the history
  • Loading branch information
elibon99 committed Feb 7, 2025
1 parent afcaf56 commit 135a97f
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 38 deletions.
53 changes: 31 additions & 22 deletions lib/app/views/action_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -54,31 +54,40 @@ class ActionListItem extends StatelessWidget {
// };
final theme = Theme.of(context);

return ListTile(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(48)),
title: TooltipIfTruncated(
text: title,
style: TextStyle(fontSize: theme.textTheme.bodyLarge!.fontSize),
),
subtitle: subtitle != null
? TooltipIfTruncated(
text: subtitle!,
style: TextStyle(fontSize: theme.textTheme.bodyMedium!.fontSize),
maxLines: 2,
overflow: TextOverflow.ellipsis,
)
return GestureDetector(
onTap: onTap == null
? () {
// Needed to avoid triggering escape intent when tapping
// on a disabled item
}
: null,
leading: Opacity(
opacity: onTap != null ? 1.0 : 0.4,
child: CircleAvatar(
foregroundColor: theme.colorScheme.onSurfaceVariant,
backgroundColor: Colors.transparent,
child: icon,
child: ListTile(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(48)),
title: TooltipIfTruncated(
text: title,
style: TextStyle(fontSize: theme.textTheme.bodyLarge!.fontSize),
),
subtitle: subtitle != null
? TooltipIfTruncated(
text: subtitle!,
style:
TextStyle(fontSize: theme.textTheme.bodyMedium!.fontSize),
maxLines: 2,
overflow: TextOverflow.ellipsis,
)
: null,
leading: Opacity(
opacity: onTap != null ? 1.0 : 0.4,
child: CircleAvatar(
foregroundColor: theme.colorScheme.onSurfaceVariant,
backgroundColor: Colors.transparent,
child: icon,
),
),
trailing: trailing,
onTap: onTap != null ? () => onTap?.call(context) : null,
enabled: onTap != null,
),
trailing: trailing,
onTap: onTap != null ? () => onTap?.call(context) : null,
enabled: onTap != null,
);
}
}
Expand Down
8 changes: 7 additions & 1 deletion lib/fido/views/pin_entry_form.dart
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,13 @@ class _PinEntryFormState extends ConsumerState<PinEntryForm> {
_pinIsWrong = false;
});
}, // Update state on change
onSubmitted: (_) => _submit(),
onSubmitted: (_) {
if (_pinController.text.length >= widget._state.minPinLength) {
_submit();
} else {
_pinFocus.requestFocus();
}
},
).init(),
),
ListTile(
Expand Down
20 changes: 14 additions & 6 deletions lib/piv/views/actions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ Future<bool> _authIfNeeded(BuildContext context, WidgetRef ref,
return await showBlurDialog(
context: context,
builder: (context) => pivState.protectedKey
? PinDialog(devicePath)
? PinDialog(devicePath, pivState)
: AuthenticationDialog(
devicePath,
pivState,
Expand Down Expand Up @@ -120,7 +120,8 @@ class PivActions extends ConsumerWidget {
verified = await withContext((context) async =>
await showBlurDialog(
context: context,
builder: (context) => PinDialog(devicePath))) ??
builder: (context) =>
PinDialog(devicePath, pivState))) ??
false;
}

Expand Down Expand Up @@ -333,6 +334,10 @@ List<ActionItem> buildSlotActions(
final hasCert = slot.certInfo != null;
final hasKey = slot.metadata != null;
final canDeleteOrMoveKey = hasKey && pivState.version.isAtLeast(5, 7);
final pinIsBlocked = pivState.pinAttempts == 0;
final defaultPin = pivState.metadata?.pinMetadata.defaultValue == true;
final requiresPinAuth =
pivState.needsAuth && pivState.protectedKey && !defaultPin;
return [
if (!slot.slot.isRetired) ...[
ActionItem(
Expand All @@ -342,15 +347,18 @@ List<ActionItem> buildSlotActions(
actionStyle: ActionStyle.primary,
title: l10n.s_generate_key,
subtitle: l10n.l_generate_desc,
intent: GenerateIntent(slot),
intent: (pinIsBlocked &&
(requiresPinAuth || (!pivState.protectedKey && !defaultPin)))
? null
: GenerateIntent(slot),
),
ActionItem(
key: keys.importAction,
feature: features.slotsImport,
icon: const Icon(Symbols.file_download),
title: l10n.l_import_file,
subtitle: l10n.l_import_desc,
intent: ImportIntent(slot),
intent: pinIsBlocked && requiresPinAuth ? null : ImportIntent(slot),
),
],
if (hasCert) ...[
Expand Down Expand Up @@ -380,7 +388,7 @@ List<ActionItem> buildSlotActions(
icon: const Icon(Symbols.move_item),
title: l10n.l_move_key,
subtitle: l10n.l_move_key_desc,
intent: MoveIntent(slot),
intent: pinIsBlocked && requiresPinAuth ? null : MoveIntent(slot),
),
if (hasCert || canDeleteOrMoveKey)
ActionItem(
Expand All @@ -398,7 +406,7 @@ List<ActionItem> buildSlotActions(
: hasCert
? l10n.l_delete_certificate_desc
: l10n.l_delete_key_desc,
intent: DeleteIntent(slot),
intent: pinIsBlocked && requiresPinAuth ? null : DeleteIntent(slot),
),
];
}
3 changes: 2 additions & 1 deletion lib/piv/views/manage_key_dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,8 @@ class _ManageKeyDialogState extends ConsumerState<ManageKeyDialog> {
final verified = await withContext((context) async =>
await showBlurDialog(
context: context,
builder: (context) => PinDialog(widget.path))) ??
builder: (context) =>
PinDialog(widget.path, widget.pivState))) ??
false;

if (!verified) {
Expand Down
39 changes: 31 additions & 8 deletions lib/piv/views/pin_dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@ import '../../widgets/app_text_field.dart';
import '../../widgets/responsive_dialog.dart';
import '../../widgets/utf8_utils.dart';
import '../keys.dart' as keys;
import '../models.dart';
import '../state.dart';

class PinDialog extends ConsumerStatefulWidget {
final DevicePath devicePath;
const PinDialog(this.devicePath, {super.key});
final PivState pivState;
const PinDialog(this.devicePath, this.pivState, {super.key});

@override
ConsumerState<ConsumerStatefulWidget> createState() => _PinDialogState();
Expand All @@ -41,8 +43,15 @@ class _PinDialogState extends ConsumerState<PinDialog> {
final _pinFocus = FocusNode();
bool _pinIsWrong = false;
int _attemptsRemaining = -1;
late bool _pinIsBlocked;
bool _isObscure = true;

@override
void initState() {
super.initState();
_pinIsBlocked = widget.pivState.pinAttempts == 0;
}

@override
void dispose() {
_pinController.dispose();
Expand All @@ -69,6 +78,9 @@ class _PinDialogState extends ConsumerState<PinDialog> {
setState(() {
_attemptsRemaining = attemptsRemaining;
_pinIsWrong = true;
if (_attemptsRemaining == 0) {
_pinIsBlocked = true;
}
});
},
orElse: () {},
Expand All @@ -83,15 +95,16 @@ class _PinDialogState extends ConsumerState<PinDialog> {
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final version = ref.watch(pivStateProvider(widget.devicePath)).valueOrNull;
final minPinLen = version?.version.isAtLeast(4, 3, 1) == true ? 6 : 4;
final minPinLen =
widget.pivState.version.isAtLeast(4, 3, 1) == true ? 6 : 4;
final currentPinLen = byteLength(_pinController.text);
return ResponsiveDialog(
title: Text(l10n.s_pin_required),
actions: [
TextButton(
key: keys.unlockButton,
onPressed: currentPinLen >= minPinLen ? _submit : null,
onPressed:
currentPinLen >= minPinLen && !_pinIsBlocked ? _submit : null,
child: Text(l10n.s_unlock),
),
],
Expand All @@ -111,12 +124,16 @@ class _PinDialogState extends ConsumerState<PinDialog> {
key: keys.managementKeyField,
controller: _pinController,
focusNode: _pinFocus,
enabled: !_pinIsBlocked,
decoration: AppInputDecoration(
border: const OutlineInputBorder(),
labelText: l10n.s_pin,
errorText: _pinIsWrong
? l10n.l_wrong_pin_attempts_remaining(_attemptsRemaining)
: null,
errorText: _pinIsBlocked
? l10n.l_piv_pin_blocked
: _pinIsWrong
? l10n
.l_wrong_pin_attempts_remaining(_attemptsRemaining)
: null,
errorMaxLines: 3,
icon: const Icon(Symbols.pin),
suffixIcon: IconButton(
Expand All @@ -136,7 +153,13 @@ class _PinDialogState extends ConsumerState<PinDialog> {
_pinIsWrong = false;
});
},
onSubmitted: (_) => _submit(),
onSubmitted: (_) {
if (currentPinLen >= minPinLen) {
_submit();
} else {
_pinFocus.requestFocus();
}
},
).init(),
]
.map((e) => Padding(
Expand Down

0 comments on commit 135a97f

Please sign in to comment.