Expose FIPS approved and ensure DeviceInfo is refreshed

This commit is contained in:
Dain Nilsson 2024-07-09 11:33:41 +02:00
parent 0b7d6736cb
commit 903f96acc8
No known key found for this signature in database
GPG Key ID: F04367096FBA95E8
10 changed files with 123 additions and 64 deletions

View File

@ -9,9 +9,9 @@ part of 'models.dart';
_$KeyCustomizationImpl _$$KeyCustomizationImplFromJson(
Map<String, dynamic> json) =>
_$KeyCustomizationImpl(
serial: json['serial'] as int,
serial: (json['serial'] as num).toInt(),
name: json['name'] as String?,
color: const _ColorConverter().fromJson(json['color'] as int?),
color: const _ColorConverter().fromJson((json['color'] as num?)?.toInt()),
);
Map<String, dynamic> _$$KeyCustomizationImplToJson(

View File

@ -224,12 +224,9 @@ final _desktopDeviceDataProvider =
ref.watch(rpcProvider).valueOrNull,
ref.watch(currentDeviceProvider),
);
if (notifier._deviceNode is NfcReaderNode) {
// If this is an NFC reader, listen on WindowState.
ref.listen<WindowState>(windowStateProvider, (_, windowState) {
notifier._notifyWindowState(windowState);
}, fireImmediately: true);
}
return notifier;
});
@ -259,7 +256,11 @@ class CurrentDeviceDataNotifier extends StateNotifier<AsyncValue<YubiKeyData>> {
void _notifyWindowState(WindowState windowState) {
if (windowState.active) {
if (_deviceNode is UsbYubiKeyNode?) {
_pollUsb();
} else {
_pollCard();
}
} else {
_pollTimer?.cancel();
// TODO: Should we clear the key here?
@ -275,11 +276,27 @@ class CurrentDeviceDataNotifier extends StateNotifier<AsyncValue<YubiKeyData>> {
super.dispose();
}
void _pollUsb() async {
_pollTimer?.cancel();
final node = _deviceNode!;
var result = await _rpc?.command('get', node.path.segments);
if (mounted && result != null) {
final newState = YubiKeyData(node, result['data']['name'],
DeviceInfo.fromJson(result['data']['info']));
if (state.valueOrNull != newState) {
_log.info('Configuration change in current USB device');
state = AsyncValue.data(newState);
}
}
if (mounted) {
_pollTimer = Timer(_usbPollDelay, _pollUsb);
}
}
void _pollCard() async {
_pollTimer?.cancel();
final node = _deviceNode!;
try {
_log.debug('Polling for NFC device changes...');
var result = await _rpc?.command('get', node.path.segments);
if (mounted && result != null) {
if (result['data']['present']) {
@ -289,9 +306,8 @@ class CurrentDeviceDataNotifier extends StateNotifier<AsyncValue<YubiKeyData>> {
if (oldState != null && oldState != newState) {
// Ensure state is cleared
state = const AsyncValue.loading();
} else {
state = AsyncValue.data(newState);
}
state = AsyncValue.data(newState);
} else {
final status = result['data']['status'];
// Only update if status is not changed

View File

@ -10,7 +10,7 @@ _$FidoStateImpl _$$FidoStateImplFromJson(Map<String, dynamic> json) =>
_$FidoStateImpl(
info: json['info'] as Map<String, dynamic>,
unlocked: json['unlocked'] as bool,
pinRetries: json['pin_retries'] as int?,
pinRetries: (json['pin_retries'] as num?)?.toInt(),
);
Map<String, dynamic> _$$FidoStateImplToJson(_$FidoStateImpl instance) =>

View File

@ -78,6 +78,8 @@ class DeviceConfig with _$DeviceConfig {
@freezed
class DeviceInfo with _$DeviceInfo {
const DeviceInfo._(); // Added constructor
factory DeviceInfo(
DeviceConfig config,
int? serial,
@ -88,8 +90,16 @@ class DeviceInfo with _$DeviceInfo {
bool isFips,
bool isSky,
bool pinComplexity,
int fipsCapable) = _DeviceInfo;
int fipsCapable,
int fipsApproved) = _DeviceInfo;
factory DeviceInfo.fromJson(Map<String, dynamic> json) =>
_$DeviceInfoFromJson(json);
/// Gets the tuple fipsCapable, fipsApproved for the given capability.
(bool fipsCapable, bool fipsApproved) getFipsStatus(Capability capability) {
final capable = fipsCapable & Capability.oath.value != 0;
final approved = capable && fipsApproved & Capability.oath.value != 0;
return (capable, approved);
}
}

View File

@ -247,6 +247,7 @@ mixin _$DeviceInfo {
bool get isSky => throw _privateConstructorUsedError;
bool get pinComplexity => throw _privateConstructorUsedError;
int get fipsCapable => throw _privateConstructorUsedError;
int get fipsApproved => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
@ -270,7 +271,8 @@ abstract class $DeviceInfoCopyWith<$Res> {
bool isFips,
bool isSky,
bool pinComplexity,
int fipsCapable});
int fipsCapable,
int fipsApproved});
$DeviceConfigCopyWith<$Res> get config;
$VersionCopyWith<$Res> get version;
@ -299,6 +301,7 @@ class _$DeviceInfoCopyWithImpl<$Res, $Val extends DeviceInfo>
Object? isSky = null,
Object? pinComplexity = null,
Object? fipsCapable = null,
Object? fipsApproved = null,
}) {
return _then(_value.copyWith(
config: null == config
@ -341,6 +344,10 @@ class _$DeviceInfoCopyWithImpl<$Res, $Val extends DeviceInfo>
? _value.fipsCapable
: fipsCapable // ignore: cast_nullable_to_non_nullable
as int,
fipsApproved: null == fipsApproved
? _value.fipsApproved
: fipsApproved // ignore: cast_nullable_to_non_nullable
as int,
) as $Val);
}
@ -379,7 +386,8 @@ abstract class _$$DeviceInfoImplCopyWith<$Res>
bool isFips,
bool isSky,
bool pinComplexity,
int fipsCapable});
int fipsCapable,
int fipsApproved});
@override
$DeviceConfigCopyWith<$Res> get config;
@ -408,6 +416,7 @@ class __$$DeviceInfoImplCopyWithImpl<$Res>
Object? isSky = null,
Object? pinComplexity = null,
Object? fipsCapable = null,
Object? fipsApproved = null,
}) {
return _then(_$DeviceInfoImpl(
null == config
@ -450,13 +459,17 @@ class __$$DeviceInfoImplCopyWithImpl<$Res>
? _value.fipsCapable
: fipsCapable // ignore: cast_nullable_to_non_nullable
as int,
null == fipsApproved
? _value.fipsApproved
: fipsApproved // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// @nodoc
@JsonSerializable()
class _$DeviceInfoImpl implements _DeviceInfo {
class _$DeviceInfoImpl extends _DeviceInfo {
_$DeviceInfoImpl(
this.config,
this.serial,
@ -467,8 +480,10 @@ class _$DeviceInfoImpl implements _DeviceInfo {
this.isFips,
this.isSky,
this.pinComplexity,
this.fipsCapable)
: _supportedCapabilities = supportedCapabilities;
this.fipsCapable,
this.fipsApproved)
: _supportedCapabilities = supportedCapabilities,
super._();
factory _$DeviceInfoImpl.fromJson(Map<String, dynamic> json) =>
_$$DeviceInfoImplFromJson(json);
@ -500,10 +515,12 @@ class _$DeviceInfoImpl implements _DeviceInfo {
final bool pinComplexity;
@override
final int fipsCapable;
@override
final int fipsApproved;
@override
String toString() {
return 'DeviceInfo(config: $config, serial: $serial, version: $version, formFactor: $formFactor, supportedCapabilities: $supportedCapabilities, isLocked: $isLocked, isFips: $isFips, isSky: $isSky, pinComplexity: $pinComplexity, fipsCapable: $fipsCapable)';
return 'DeviceInfo(config: $config, serial: $serial, version: $version, formFactor: $formFactor, supportedCapabilities: $supportedCapabilities, isLocked: $isLocked, isFips: $isFips, isSky: $isSky, pinComplexity: $pinComplexity, fipsCapable: $fipsCapable, fipsApproved: $fipsApproved)';
}
@override
@ -525,7 +542,9 @@ class _$DeviceInfoImpl implements _DeviceInfo {
(identical(other.pinComplexity, pinComplexity) ||
other.pinComplexity == pinComplexity) &&
(identical(other.fipsCapable, fipsCapable) ||
other.fipsCapable == fipsCapable));
other.fipsCapable == fipsCapable) &&
(identical(other.fipsApproved, fipsApproved) ||
other.fipsApproved == fipsApproved));
}
@JsonKey(ignore: true)
@ -541,7 +560,8 @@ class _$DeviceInfoImpl implements _DeviceInfo {
isFips,
isSky,
pinComplexity,
fipsCapable);
fipsCapable,
fipsApproved);
@JsonKey(ignore: true)
@override
@ -557,7 +577,7 @@ class _$DeviceInfoImpl implements _DeviceInfo {
}
}
abstract class _DeviceInfo implements DeviceInfo {
abstract class _DeviceInfo extends DeviceInfo {
factory _DeviceInfo(
final DeviceConfig config,
final int? serial,
@ -568,7 +588,9 @@ abstract class _DeviceInfo implements DeviceInfo {
final bool isFips,
final bool isSky,
final bool pinComplexity,
final int fipsCapable) = _$DeviceInfoImpl;
final int fipsCapable,
final int fipsApproved) = _$DeviceInfoImpl;
_DeviceInfo._() : super._();
factory _DeviceInfo.fromJson(Map<String, dynamic> json) =
_$DeviceInfoImpl.fromJson;
@ -594,6 +616,8 @@ abstract class _DeviceInfo implements DeviceInfo {
@override
int get fipsCapable;
@override
int get fipsApproved;
@override
@JsonKey(ignore: true)
_$$DeviceInfoImplCopyWith<_$DeviceInfoImpl> get copyWith =>
throw _privateConstructorUsedError;

View File

@ -46,6 +46,7 @@ _$DeviceInfoImpl _$$DeviceInfoImplFromJson(Map<String, dynamic> json) =>
json['is_sky'] as bool,
json['pin_complexity'] as bool,
(json['fips_capable'] as num).toInt(),
(json['fips_approved'] as num).toInt(),
);
Map<String, dynamic> _$$DeviceInfoImplToJson(_$DeviceInfoImpl instance) =>
@ -61,6 +62,7 @@ Map<String, dynamic> _$$DeviceInfoImplToJson(_$DeviceInfoImpl instance) =>
'is_sky': instance.isSky,
'pin_complexity': instance.pinComplexity,
'fips_capable': instance.fipsCapable,
'fips_approved': instance.fipsApproved,
};
const _$FormFactorEnumMap = {

View File

@ -13,7 +13,7 @@ _$OathCredentialImpl _$$OathCredentialImplFromJson(Map<String, dynamic> json) =>
const _IssuerConverter().fromJson(json['issuer'] as String?),
json['name'] as String,
$enumDecode(_$OathTypeEnumMap, json['oath_type']),
json['period'] as int,
(json['period'] as num).toInt(),
json['touch_required'] as bool,
);
@ -37,8 +37,8 @@ const _$OathTypeEnumMap = {
_$OathCodeImpl _$$OathCodeImplFromJson(Map<String, dynamic> json) =>
_$OathCodeImpl(
json['value'] as String,
json['valid_from'] as int,
json['valid_to'] as int,
(json['valid_from'] as num).toInt(),
(json['valid_to'] as num).toInt(),
);
Map<String, dynamic> _$$OathCodeImplToJson(_$OathCodeImpl instance) =>
@ -98,9 +98,9 @@ _$CredentialDataImpl _$$CredentialDataImplFromJson(Map<String, dynamic> json) =>
hashAlgorithm:
$enumDecodeNullable(_$HashAlgorithmEnumMap, json['hash_algorithm']) ??
defaultHashAlgorithm,
digits: json['digits'] as int? ?? defaultDigits,
period: json['period'] as int? ?? defaultPeriod,
counter: json['counter'] as int? ?? defaultCounter,
digits: (json['digits'] as num?)?.toInt() ?? defaultDigits,
period: (json['period'] as num?)?.toInt() ?? defaultPeriod,
counter: (json['counter'] as num?)?.toInt() ?? defaultCounter,
);
Map<String, dynamic> _$$CredentialDataImplToJson(

View File

@ -22,6 +22,7 @@ import 'package:material_symbols_icons/symbols.dart';
import '../../app/message.dart';
import '../../app/models.dart';
import '../../app/state.dart';
import '../../management/models.dart';
import '../../widgets/app_input_decoration.dart';
import '../../widgets/app_text_field.dart';
import '../../widgets/focus_utils.dart';
@ -82,6 +83,9 @@ class _ManagePasswordDialogState extends ConsumerState<ManagePasswordDialog> {
@override
Widget build(BuildContext context) {
final fipsCapable = ref.watch(currentDeviceDataProvider).maybeWhen(
data: (data) => data.info.getFipsStatus(Capability.oath).$1,
orElse: () => false);
final l10n = AppLocalizations.of(context)!;
final isValid = !_currentIsWrong &&
_newPassword.isNotEmpty &&
@ -142,6 +146,7 @@ class _ManagePasswordDialogState extends ConsumerState<ManagePasswordDialog> {
spacing: 4.0,
runSpacing: 8.0,
children: [
if (!fipsCapable)
OutlinedButton(
key: keys.removePasswordButton,
onPressed: _currentPasswordController.text.isNotEmpty &&
@ -149,13 +154,15 @@ class _ManagePasswordDialogState extends ConsumerState<ManagePasswordDialog> {
? () async {
final result = await ref
.read(oathStateProvider(widget.path).notifier)
.unsetPassword(_currentPasswordController.text);
.unsetPassword(
_currentPasswordController.text);
if (result) {
if (mounted) {
await ref.read(withContextProvider)(
(context) async {
Navigator.of(context).pop();
showMessage(context, l10n.s_password_removed);
showMessage(
context, l10n.s_password_removed);
});
}
} else {

View File

@ -9,8 +9,8 @@ part of 'models.dart';
_$PinMetadataImpl _$$PinMetadataImplFromJson(Map<String, dynamic> json) =>
_$PinMetadataImpl(
json['default_value'] as bool,
json['total_attempts'] as int,
json['attempts_remaining'] as int,
(json['total_attempts'] as num).toInt(),
(json['attempts_remaining'] as num).toInt(),
);
Map<String, dynamic> _$$PinMetadataImplToJson(_$PinMetadataImpl instance) =>
@ -113,7 +113,7 @@ _$PivStateImpl _$$PivStateImplFromJson(Map<String, dynamic> json) =>
authenticated: json['authenticated'] as bool,
derivedKey: json['derived_key'] as bool,
storedKey: json['stored_key'] as bool,
pinAttempts: json['pin_attempts'] as int,
pinAttempts: (json['pin_attempts'] as num).toInt(),
chuid: json['chuid'] as String?,
ccc: json['ccc'] as String?,
metadata: json['metadata'] == null
@ -157,7 +157,7 @@ Map<String, dynamic> _$$CertInfoImplToJson(_$CertInfoImpl instance) =>
_$PivSlotImpl _$$PivSlotImplFromJson(Map<String, dynamic> json) =>
_$PivSlotImpl(
slot: SlotId.fromJson(json['slot'] as int),
slot: SlotId.fromJson((json['slot'] as num).toInt()),
metadata: json['metadata'] == null
? null
: SlotMetadata.fromJson(json['metadata'] as Map<String, dynamic>),

View File

@ -165,8 +165,8 @@ class _ManagePinPukDialogState extends ConsumerState<ManagePinPukDialog> {
final isBio = [FormFactor.usbABio, FormFactor.usbCBio]
.contains(deviceData?.info.formFactor);
final fipsCapable = deviceData?.info.fipsCapable ?? 0;
final isFipsCapable = fipsCapable & Capability.piv.value != 0;
final isFipsCapable =
deviceData?.info.getFipsStatus(Capability.piv).$1 ?? false;
// Old YubiKeys allowed a 4 digit PIN
final currentMinPinLen = isFipsCapable