mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-11-22 00:12:09 +03:00
Merge PR #1617
This commit is contained in:
commit
42f70bedfc
@ -26,7 +26,8 @@ enum class FidoActionDescription(private val value: Int) {
|
||||
DeleteFingerprint(4),
|
||||
RenameFingerprint(5),
|
||||
RegisterFingerprint(6),
|
||||
ActionFailure(7);
|
||||
EnableEnterpriseAttestation(7),
|
||||
ActionFailure(8);
|
||||
|
||||
val id: Int
|
||||
get() = value + dialogDescriptionFidoIndex
|
||||
|
@ -42,6 +42,7 @@ import com.yubico.yubikit.core.smartcard.SmartCardConnection
|
||||
import com.yubico.yubikit.core.util.Result
|
||||
import com.yubico.yubikit.fido.ctap.BioEnrollment
|
||||
import com.yubico.yubikit.fido.ctap.ClientPin
|
||||
import com.yubico.yubikit.fido.ctap.Config
|
||||
import com.yubico.yubikit.fido.ctap.CredentialManagement
|
||||
import com.yubico.yubikit.fido.ctap.Ctap2Session.InfoData
|
||||
import com.yubico.yubikit.fido.ctap.FingerprintBioEnrollment
|
||||
@ -159,6 +160,8 @@ class FidoManager(
|
||||
|
||||
"cancelRegisterFingerprint" -> cancelRegisterFingerprint()
|
||||
|
||||
"enableEnterpriseAttestation" -> enableEnterpriseAttestation()
|
||||
|
||||
else -> throw NotImplementedError()
|
||||
}
|
||||
}
|
||||
@ -603,6 +606,42 @@ class FidoManager(
|
||||
).toString()
|
||||
}
|
||||
|
||||
private suspend fun enableEnterpriseAttestation(): String =
|
||||
connectionHelper.useSession(FidoActionDescription.EnableEnterpriseAttestation) { fidoSession ->
|
||||
try {
|
||||
val uvAuthProtocol = getPreferredPinUvAuthProtocol(fidoSession.cachedInfo)
|
||||
val clientPin = ClientPin(fidoSession, uvAuthProtocol)
|
||||
val token = if (pinStore.hasPin()) {
|
||||
clientPin.getPinToken(
|
||||
pinStore.getPin(),
|
||||
ClientPin.PIN_PERMISSION_ACFG,
|
||||
null
|
||||
)
|
||||
} else null
|
||||
|
||||
val config = Config(fidoSession, uvAuthProtocol, token)
|
||||
config.enableEnterpriseAttestation()
|
||||
fidoViewModel.setSessionState(
|
||||
Session(
|
||||
fidoSession.info,
|
||||
pinStore.hasPin(),
|
||||
pinRetries
|
||||
)
|
||||
)
|
||||
return@useSession JSONObject(
|
||||
mapOf(
|
||||
"success" to true,
|
||||
)
|
||||
).toString()
|
||||
} catch (e: Exception) {
|
||||
logger.error("Failed to enable enterprise attestation. ", e)
|
||||
return@useSession JSONObject(
|
||||
mapOf(
|
||||
"success" to false,
|
||||
)
|
||||
).toString()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDisconnected() {
|
||||
if (!resetHelper.inProgress) {
|
||||
|
@ -29,7 +29,8 @@ data class Options(
|
||||
val credMgmt: Boolean,
|
||||
val credentialMgmtPreview: Boolean,
|
||||
val bioEnroll: Boolean?,
|
||||
val alwaysUv: Boolean
|
||||
val alwaysUv: Boolean,
|
||||
val ep: Boolean?,
|
||||
) {
|
||||
constructor(infoData: InfoData) : this(
|
||||
infoData.getOptionsBoolean("clientPin") == true,
|
||||
@ -37,6 +38,7 @@ data class Options(
|
||||
infoData.getOptionsBoolean("credentialMgmtPreview") == true,
|
||||
infoData.getOptionsBoolean("bioEnroll"),
|
||||
infoData.getOptionsBoolean("alwaysUv") == true,
|
||||
infoData.getOptionsBoolean("ep"),
|
||||
)
|
||||
|
||||
companion object {
|
||||
|
@ -23,7 +23,7 @@ from .base import (
|
||||
PinComplexityException,
|
||||
)
|
||||
from fido2.ctap import CtapError
|
||||
from fido2.ctap2 import Ctap2, ClientPin
|
||||
from fido2.ctap2 import Ctap2, ClientPin, Config
|
||||
from fido2.ctap2.credman import CredentialManagement
|
||||
from fido2.ctap2.bio import BioEnrollment, FPBioEnrollment, CaptureError
|
||||
from fido2.pcsc import CtapPcscDevice
|
||||
@ -200,6 +200,8 @@ class Ctap2Node(RpcNode):
|
||||
permissions |= ClientPin.PERMISSION.CREDENTIAL_MGMT
|
||||
if BioEnrollment.is_supported(self._info):
|
||||
permissions |= ClientPin.PERMISSION.BIO_ENROLL
|
||||
if Config.is_supported(self._info):
|
||||
permissions |= ClientPin.PERMISSION.AUTHENTICATOR_CFG
|
||||
try:
|
||||
if permissions:
|
||||
self._token = self.client_pin.get_pin_token(pin, permissions)
|
||||
@ -229,6 +231,14 @@ class Ctap2Node(RpcNode):
|
||||
except CtapError as e:
|
||||
return _handle_pin_error(e, self.client_pin)
|
||||
|
||||
@action(condition=lambda self: Config.is_supported(self._info))
|
||||
def enable_ep_attestation(self, params, event, signal):
|
||||
if self._info.options["clientPin"] and not self._token:
|
||||
raise AuthRequiredException()
|
||||
config = Config(self.ctap, self.client_pin.protocol, self._token)
|
||||
config._call(Config.CMD.ENABLE_ENTERPRISE_ATT)
|
||||
return dict()
|
||||
|
||||
@child(condition=lambda self: BioEnrollment.is_supported(self._info))
|
||||
def fingerprints(self):
|
||||
if not self._token:
|
||||
|
@ -161,6 +161,29 @@ class _FidoStateNotifier extends FidoStateNotifier {
|
||||
throw decodedException;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> enableEnterpriseAttestation() async {
|
||||
try {
|
||||
final response = jsonDecode(await _methods.invokeMethod(
|
||||
'enableEnterpriseAttestation',
|
||||
));
|
||||
|
||||
if (response['success'] == true) {
|
||||
_log.debug('Enterprise attestation enabled');
|
||||
}
|
||||
} on PlatformException catch (pe) {
|
||||
var decodedException = pe.decode();
|
||||
if (decodedException is CancellationException) {
|
||||
_log.debug('User cancelled unlock FIDO operation');
|
||||
throw decodedException;
|
||||
}
|
||||
|
||||
_log.debug(
|
||||
'Platform exception during enable enterprise attestation: $pe');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final androidFingerprintProvider = AsyncNotifierProvider.autoDispose
|
||||
|
@ -80,6 +80,8 @@ enum _DDesc {
|
||||
fidoDeleteCredential,
|
||||
fidoDeleteFingerprint,
|
||||
fidoRenameFingerprint,
|
||||
fidoRegisterFingerprint,
|
||||
fidoEnableEnterpriseAttestation,
|
||||
fidoActionFailure,
|
||||
// Others
|
||||
invalid;
|
||||
@ -105,7 +107,9 @@ enum _DDesc {
|
||||
dialogDescriptionFidoIndex + 3: fidoDeleteCredential,
|
||||
dialogDescriptionFidoIndex + 4: fidoDeleteFingerprint,
|
||||
dialogDescriptionFidoIndex + 5: fidoRenameFingerprint,
|
||||
dialogDescriptionFidoIndex + 6: fidoActionFailure,
|
||||
dialogDescriptionFidoIndex + 6: fidoRegisterFingerprint,
|
||||
dialogDescriptionFidoIndex + 7: fidoEnableEnterpriseAttestation,
|
||||
dialogDescriptionFidoIndex + 8: fidoActionFailure,
|
||||
}[id] ??
|
||||
_DDesc.invalid;
|
||||
}
|
||||
|
@ -184,6 +184,12 @@ class _DesktopFidoStateNotifier extends FidoStateNotifier {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> enableEnterpriseAttestation() async {
|
||||
await _session.command('enable_ep_attestation');
|
||||
ref.invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
||||
final desktopFingerprintProvider = AsyncNotifierProvider.autoDispose
|
||||
|
@ -21,6 +21,8 @@ final actions = fido.feature('actions');
|
||||
final actionsPin = actions.feature('pin');
|
||||
final actionsAddFingerprint = actions.feature('addFingerprint');
|
||||
final actionsReset = actions.feature('reset');
|
||||
final enableEnterpriseAttestation =
|
||||
actions.feature('enableEnterpriseAttestation');
|
||||
|
||||
final credentials = fido.feature('credentials');
|
||||
|
||||
|
@ -25,6 +25,8 @@ const _credentialInfo = '$_prefix.credential.info';
|
||||
// Key actions
|
||||
const managePinAction = Key('$_keyAction.manage_pin');
|
||||
const addFingerprintAction = Key('$_keyAction.add_fingerprint');
|
||||
const enableEnterpriseAttestation =
|
||||
Key('$_keyAction.enable_enterprise_attestation');
|
||||
const newPin = Key('$_keyAction.new_pin');
|
||||
const confirmPin = Key('$_keyAction.confirm_pin');
|
||||
const currentPin = Key('$_keyAction.current_pin');
|
||||
|
@ -50,6 +50,8 @@ class FidoState with _$FidoState {
|
||||
bool get forcePinChange => info['force_pin_change'] == true;
|
||||
|
||||
bool get pinBlocked => pinRetries == 0;
|
||||
|
||||
bool? get enterpriseAttestation => info['options']['ep'];
|
||||
}
|
||||
|
||||
@freezed
|
||||
|
@ -47,6 +47,7 @@ abstract class FidoStateNotifier extends ApplicationStateNotifier<FidoState> {
|
||||
Stream<InteractionEvent> reset();
|
||||
Future<PinResult> setPin(String newPin, {String? oldPin});
|
||||
Future<PinResult> unlock(String pin);
|
||||
Future<void> enableEnterpriseAttestation();
|
||||
}
|
||||
|
||||
final fingerprintProvider = AsyncNotifierProvider.autoDispose
|
||||
|
50
lib/fido/views/enterprise_attestation_dialog.dart
Normal file
50
lib/fido/views/enterprise_attestation_dialog.dart
Normal file
@ -0,0 +1,50 @@
|
||||
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 '../../widgets/responsive_dialog.dart';
|
||||
import '../state.dart';
|
||||
|
||||
class EnableEnterpriseAttestationDialog extends ConsumerWidget {
|
||||
final DevicePath devicePath;
|
||||
const EnableEnterpriseAttestationDialog(this.devicePath, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
return ResponsiveDialog(
|
||||
title: Text(l10n.s_enable_ep_attestation),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await ref
|
||||
.read(fidoStateProvider(devicePath).notifier)
|
||||
.enableEnterpriseAttestation();
|
||||
await ref.read(withContextProvider)((context) async {
|
||||
Navigator.of(context).pop();
|
||||
showMessage(context, l10n.s_ep_attestation_enabled);
|
||||
});
|
||||
},
|
||||
child: Text(l10n.s_enable),
|
||||
),
|
||||
],
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(l10n.p_enable_ep_attestation_desc),
|
||||
]
|
||||
.map((e) => Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: e,
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -25,6 +25,7 @@ import '../features.dart' as features;
|
||||
import '../keys.dart' as keys;
|
||||
import '../models.dart';
|
||||
import 'add_fingerprint_dialog.dart';
|
||||
import 'enterprise_attestation_dialog.dart';
|
||||
import 'pin_dialog.dart';
|
||||
|
||||
bool passkeysShowActionsNotifier(FidoState state) {
|
||||
@ -50,6 +51,13 @@ Widget _fidoBuildActions(BuildContext context, DeviceNode node, FidoState state,
|
||||
Theme.of(context).colorScheme;
|
||||
final authBlocked = state.pinBlocked;
|
||||
|
||||
final enterpriseAttestation = state.enterpriseAttestation;
|
||||
final showEnterpriseAttestation = enterpriseAttestation != null &&
|
||||
!(state.alwaysUv && !state.hasPin) &&
|
||||
!(!state.unlocked && state.hasPin);
|
||||
final canEnableEnterpriseAttestation =
|
||||
enterpriseAttestation == false && showEnterpriseAttestation;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
if (fingerprints != null)
|
||||
@ -115,8 +123,27 @@ Widget _fidoBuildActions(BuildContext context, DeviceNode node, FidoState state,
|
||||
}
|
||||
: null,
|
||||
),
|
||||
if (showEnterpriseAttestation)
|
||||
ActionListItem(
|
||||
key: keys.enableEnterpriseAttestation,
|
||||
feature: features.enableEnterpriseAttestation,
|
||||
icon: const Icon(Symbols.local_police),
|
||||
title: l10n.s_ep_attestation,
|
||||
subtitle:
|
||||
enterpriseAttestation ? l10n.s_enabled : l10n.s_disabled,
|
||||
onTap: canEnableEnterpriseAttestation
|
||||
? (context) {
|
||||
Navigator.of(context).popUntil((route) => route.isFirst);
|
||||
showBlurDialog(
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
EnableEnterpriseAttestationDialog(node.path),
|
||||
);
|
||||
}
|
||||
: null,
|
||||
)
|
||||
],
|
||||
)
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -30,6 +30,9 @@
|
||||
"s_delete": "Löschen",
|
||||
"s_move": null,
|
||||
"s_quit": "Beenden",
|
||||
"s_enable": null,
|
||||
"s_enabled": null,
|
||||
"s_disabled": null,
|
||||
"s_status": null,
|
||||
"s_unlock": "Entsperren",
|
||||
"s_calculate": "Berechnen",
|
||||
@ -309,6 +312,10 @@
|
||||
"common_pin": {}
|
||||
}
|
||||
},
|
||||
"s_ep_attestation": null,
|
||||
"s_ep_attestation_enabled": null,
|
||||
"s_enable_ep_attestation": null,
|
||||
"p_enable_ep_attestation_desc": null,
|
||||
"s_pin_required": null,
|
||||
"p_pin_required_desc": null,
|
||||
"l_piv_pin_blocked": null,
|
||||
|
@ -30,6 +30,9 @@
|
||||
"s_delete": "Delete",
|
||||
"s_move": "Move",
|
||||
"s_quit": "Quit",
|
||||
"s_enable": "Enable",
|
||||
"s_enabled": "Enabled",
|
||||
"s_disabled": "Disabled",
|
||||
"s_status": "Status",
|
||||
"s_unlock": "Unlock",
|
||||
"s_calculate": "Calculate",
|
||||
@ -309,6 +312,10 @@
|
||||
"common_pin": {}
|
||||
}
|
||||
},
|
||||
"s_ep_attestation": "Enterprise Attestation",
|
||||
"s_ep_attestation_enabled": "Enterprise Attestation enabled",
|
||||
"s_enable_ep_attestation": "Enable Enterprise Attestation",
|
||||
"p_enable_ep_attestation_desc": "This will enable Enterprise Attestation, allowing authorized domains to uniquely identify your YubiKey.",
|
||||
"s_pin_required": "PIN required",
|
||||
"p_pin_required_desc": "The action you are about to perform requires the PIV PIN to be entered.",
|
||||
"l_piv_pin_blocked": "Blocked, use PUK to reset",
|
||||
|
@ -30,6 +30,9 @@
|
||||
"s_delete": "Supprimer",
|
||||
"s_move": "Déplacer",
|
||||
"s_quit": "Quitter",
|
||||
"s_enable": null,
|
||||
"s_enabled": null,
|
||||
"s_disabled": null,
|
||||
"s_status": "État",
|
||||
"s_unlock": "Déverrouiller",
|
||||
"s_calculate": "Calculer",
|
||||
@ -309,6 +312,10 @@
|
||||
"common_pin": {}
|
||||
}
|
||||
},
|
||||
"s_ep_attestation": null,
|
||||
"s_ep_attestation_enabled": null,
|
||||
"s_enable_ep_attestation": null,
|
||||
"p_enable_ep_attestation_desc": null,
|
||||
"s_pin_required": "PIN requis",
|
||||
"p_pin_required_desc": "L'action que vous allez effectuer nécessite la saisie du PIN PIV.",
|
||||
"l_piv_pin_blocked": "Bloqué, utilisez PUK pour réinitialiser",
|
||||
|
@ -30,6 +30,9 @@
|
||||
"s_delete": "削除",
|
||||
"s_move": "移動",
|
||||
"s_quit": "終了",
|
||||
"s_enable": null,
|
||||
"s_enabled": null,
|
||||
"s_disabled": null,
|
||||
"s_status": "ステータス",
|
||||
"s_unlock": "ロック解除",
|
||||
"s_calculate": "計算",
|
||||
@ -309,6 +312,10 @@
|
||||
"common_pin": {}
|
||||
}
|
||||
},
|
||||
"s_ep_attestation": null,
|
||||
"s_ep_attestation_enabled": null,
|
||||
"s_enable_ep_attestation": null,
|
||||
"p_enable_ep_attestation_desc": null,
|
||||
"s_pin_required": "PINが必要",
|
||||
"p_pin_required_desc": "実行しようとしているアクションでは、PIV PINを入力する必要があります。",
|
||||
"l_piv_pin_blocked": "ブロックされています。リセットするにはPUKを使用してください",
|
||||
|
@ -30,6 +30,9 @@
|
||||
"s_delete": "Usuń",
|
||||
"s_move": null,
|
||||
"s_quit": "Wyjdź",
|
||||
"s_enable": null,
|
||||
"s_enabled": null,
|
||||
"s_disabled": null,
|
||||
"s_status": "Status",
|
||||
"s_unlock": "Odblokuj",
|
||||
"s_calculate": "Oblicz",
|
||||
@ -309,6 +312,10 @@
|
||||
"common_pin": {}
|
||||
}
|
||||
},
|
||||
"s_ep_attestation": null,
|
||||
"s_ep_attestation_enabled": null,
|
||||
"s_enable_ep_attestation": null,
|
||||
"p_enable_ep_attestation_desc": null,
|
||||
"s_pin_required": "Wymagany PIN",
|
||||
"p_pin_required_desc": "Czynność, którą zamierzasz wykonać, wymaga wprowadzenia kodu PIN PIV.",
|
||||
"l_piv_pin_blocked": "Zablokowano, użyj PUK, aby zresetować",
|
||||
|
Loading…
Reference in New Issue
Block a user