This commit is contained in:
Elias Bonnici 2024-08-13 16:47:24 +02:00
commit 42f70bedfc
No known key found for this signature in database
GPG Key ID: 5EAC28EA3F980CCF
18 changed files with 209 additions and 5 deletions

View File

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

View File

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

View File

@ -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 {

View File

@ -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:

View File

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

View File

@ -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;
}

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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を使用してください",

View File

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