This commit is contained in:
Elias Bonnici 2024-03-27 16:52:09 +01:00
commit f674836397
No known key found for this signature in database
GPG Key ID: 5EAC28EA3F980CCF
29 changed files with 1145 additions and 185 deletions

View File

@ -49,8 +49,10 @@ data class Info(
val isNfc: Boolean,
@SerialName("usb_pid")
val usbPid: Int?,
@SerialName("pin_complexity")
val pinComplexity: Boolean,
@SerialName("supported_capabilities")
val supportedCapabilities: Capabilities
val supportedCapabilities: Capabilities,
) {
constructor(name: String, isNfc: Boolean, usbPid: Int?, deviceInfo: DeviceInfo) : this(
config = Config(deviceInfo.config),
@ -63,6 +65,7 @@ data class Info(
name = name,
isNfc = isNfc,
usbPid = usbPid,
pinComplexity = deviceInfo.pinComplexity,
supportedCapabilities = Capabilities(
nfc = deviceInfo.capabilitiesFor(Transport.NFC),
usb = deviceInfo.capabilitiesFor(Transport.USB),

View File

@ -18,5 +18,6 @@ val UnknownDevice = Info(
name = "Unrecognized device",
isNfc = false,
usbPid = null,
pinComplexity = false,
supportedCapabilities = Capabilities()
)

View File

@ -313,18 +313,28 @@ class FidoManager(
} catch (ctapException: CtapException) {
if (ctapException.ctapError == CtapException.ERR_PIN_INVALID ||
ctapException.ctapError == CtapException.ERR_PIN_BLOCKED ||
ctapException.ctapError == CtapException.ERR_PIN_AUTH_BLOCKED
ctapException.ctapError == CtapException.ERR_PIN_AUTH_BLOCKED ||
ctapException.ctapError == CtapException.ERR_PIN_POLICY_VIOLATION
) {
pinStore.setPin(null)
fidoViewModel.updateCredentials(emptyList())
val pinRetriesResult = clientPin.pinRetries
JSONObject(
mapOf(
"success" to false,
"pinRetries" to pinRetriesResult.count,
"authBlocked" to (ctapException.ctapError == CtapException.ERR_PIN_AUTH_BLOCKED)
)
).toString()
if (ctapException.ctapError == CtapException.ERR_PIN_POLICY_VIOLATION) {
JSONObject(
mapOf(
"success" to false,
"pinViolation" to true
)
).toString()
} else {
JSONObject(
mapOf(
"success" to false,
"pinRetries" to clientPin.pinRetries.count,
"authBlocked" to (ctapException.ctapError == CtapException.ERR_PIN_AUTH_BLOCKED),
)
).toString()
}
} else {
throw ctapException
}

View File

@ -74,6 +74,7 @@ class SkyHelper(private val compatUtil: CompatUtil) {
name = (device.usbDevice.productName ?: "Yubico Security Key"),
isNfc = false,
usbPid = pid.value,
pinComplexity = false,
supportedCapabilities = Capabilities(usb = 0)
)
}

View File

@ -75,6 +75,11 @@ class AuthRequiredException(RpcException):
super().__init__("auth-required", "Authentication is required")
class PinComplexityException(RpcException):
def __init__(self):
super().__init__("pin-complexity", "PIN does not meet complexity requirements")
class ChildResetException(Exception):
def __init__(self, message):
self.message = message

View File

@ -19,6 +19,7 @@ from .base import (
RpcException,
TimeoutException,
AuthRequiredException,
PinComplexityException,
)
from fido2.ctap import CtapError
from fido2.ctap2 import Ctap2, ClientPin
@ -76,6 +77,8 @@ def _handle_pin_error(e, client_pin):
raise PinValidationException(
pin_retries, e.code == CtapError.ERR.PIN_AUTH_BLOCKED
)
if e.code == CtapError.ERR.PIN_POLICY_VIOLATION:
raise PinComplexityException()
raise e

View File

@ -20,6 +20,7 @@ from .base import (
ChildResetException,
TimeoutException,
AuthRequiredException,
PinComplexityException,
)
from yubikit.core import NotSupportedError, BadResponseError, InvalidPinError
from yubikit.core.smartcard import ApduError, SW
@ -80,6 +81,15 @@ class GENERATE_TYPE(str, Enum):
CERTIFICATE = "certificate"
def _handle_pin_puk_error(e):
if isinstance(e, ApduError):
if e.sw == SW.CONDITIONS_NOT_SATISFIED:
raise PinComplexityException()
if isinstance(e, InvalidPinError):
raise InvalidPinException(cause=e)
raise e
class PivNode(RpcNode):
def __init__(self, connection):
super().__init__()
@ -208,21 +218,30 @@ class PivNode(RpcNode):
def change_pin(self, params, event, signal):
old_pin = params.pop("pin")
new_pin = params.pop("new_pin")
pivman_change_pin(self.session, old_pin, new_pin)
try:
pivman_change_pin(self.session, old_pin, new_pin)
except Exception as e:
_handle_pin_puk_error(e)
return dict()
@action
def change_puk(self, params, event, signal):
old_puk = params.pop("puk")
new_puk = params.pop("new_puk")
self.session.change_puk(old_puk, new_puk)
try:
self.session.change_puk(old_puk, new_puk)
except Exception as e:
_handle_pin_puk_error(e)
return dict()
@action
def unblock_pin(self, params, event, signal):
puk = params.pop("puk")
new_pin = params.pop("new_pin")
self.session.unblock_pin(puk, new_pin)
try:
self.session.unblock_pin(puk, new_pin)
except Exception as e:
_handle_pin_puk_error(e)
return dict()
@action

12
helper/poetry.lock generated
View File

@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
[[package]]
name = "altgraph"
@ -717,13 +717,13 @@ files = [
[[package]]
name = "yubikey-manager"
version = "5.3.0"
version = "5.4.0"
description = "Tool for managing your YubiKey configuration."
optional = false
python-versions = ">=3.8,<4.0"
python-versions = "<4.0,>=3.8"
files = [
{file = "yubikey_manager-5.3.0-py3-none-any.whl", hash = "sha256:9a809620f5c910c1047323570095e10b885002f6b0a2e4d8ced7f62d7c2ce628"},
{file = "yubikey_manager-5.3.0.tar.gz", hash = "sha256:5492c36a10ce6a5995b8ea1d32cf5bd60db7587201b2aa3e63e0c1da2334b8b6"},
{file = "yubikey_manager-5.4.0-py3-none-any.whl", hash = "sha256:d53acb06c4028a833be7a05ca4145833afef1affa67aaab4347bc50ecce37985"},
{file = "yubikey_manager-5.4.0.tar.gz", hash = "sha256:53726a186722cd2683b2f5fd781fc0a2861f47ce62ba9d3527960832c8fabec8"},
]
[package.dependencies]
@ -787,4 +787,4 @@ files = [
[metadata]
lock-version = "2.0"
python-versions = "^3.8"
content-hash = "6664f12e752d8b41c996d11e43a8572ed47f7fbfaaf84fa894be725fe2208d80"
content-hash = "7543cc0ac90ea4eb701a7f52321d831bfe05c65e0ae896a6107eb7a9540d8543"

View File

@ -10,7 +10,7 @@ packages = [
[tool.poetry.dependencies]
python = "^3.8"
yubikey-manager = "^5.2"
yubikey-manager = "^5.4"
mss = "^9.0.1"
Pillow = "^10.2.0"
zxing-cpp = "^2.2.0"

View File

@ -109,15 +109,18 @@ class _FidoStateNotifier extends FidoStateNotifier {
},
));
if (response['success'] == true) {
_log.debug('FIDO pin set/change successful');
_log.debug('FIDO PIN set/change successful');
return PinResult.success();
}
_log.debug('FIDO pin set/change failed');
return PinResult.failed(
response['pinRetries'],
response['authBlocked'],
);
if (response['pinViolation'] == true) {
_log.debug('FIDO PIN violation');
return PinResult.failed(const FidoPinFailureReason.weakPin());
}
_log.debug('FIDO PIN set/change failed');
return PinResult.failed(FidoPinFailureReason.invalidPin(
response['pinRetries'], response['authBlocked']));
} on PlatformException catch (pe) {
var decodedException = pe.decode();
if (decodedException is CancellationException) {
@ -141,10 +144,8 @@ class _FidoStateNotifier extends FidoStateNotifier {
}
_log.debug('FIDO applet unlock failed');
return PinResult.failed(
response['pinRetries'],
response['authBlocked'],
);
return PinResult.failed(FidoPinFailureReason.invalidPin(
response['pinRetries'], response['authBlocked']));
} on PlatformException catch (pe) {
var decodedException = pe.decode();
if (decodedException is! CancellationException) {

View File

@ -153,7 +153,11 @@ class _DesktopFidoStateNotifier extends FidoStateNotifier {
return unlock(newPin);
} on RpcError catch (e) {
if (e.status == 'pin-validation') {
return PinResult.failed(e.body['retries'], e.body['auth_blocked']);
return PinResult.failed(FidoPinFailureReason.invalidPin(
e.body['retries'], e.body['auth_blocked']));
}
if (e.status == 'pin-complexity') {
return PinResult.failed(const FidoPinFailureReason.weakPin());
}
rethrow;
}
@ -172,7 +176,8 @@ class _DesktopFidoStateNotifier extends FidoStateNotifier {
} on RpcError catch (e) {
if (e.status == 'pin-validation') {
_pinController.state = null;
return PinResult.failed(e.body['retries'], e.body['auth_blocked']);
return PinResult.failed(FidoPinFailureReason.invalidPin(
e.body['retries'], e.body['auth_blocked']));
}
rethrow;
}

View File

@ -233,7 +233,12 @@ class _DesktopPivStateNotifier extends PivStateNotifier {
return const PinVerificationStatus.success();
} on RpcError catch (e) {
if (e.status == 'invalid-pin') {
return PinVerificationStatus.failure(e.body['attempts_remaining']);
return PinVerificationStatus.failure(
PivPinFailureReason.invalidPin(e.body['attempts_remaining']));
}
if (e.status == 'pin-complexity') {
return PinVerificationStatus.failure(
const PivPinFailureReason.weakPin());
}
rethrow;
} finally {
@ -251,7 +256,12 @@ class _DesktopPivStateNotifier extends PivStateNotifier {
return const PinVerificationStatus.success();
} on RpcError catch (e) {
if (e.status == 'invalid-pin') {
return PinVerificationStatus.failure(e.body['attempts_remaining']);
return PinVerificationStatus.failure(
PivPinFailureReason.invalidPin(e.body['attempts_remaining']));
}
if (e.status == 'pin-complexity') {
return PinVerificationStatus.failure(
const PivPinFailureReason.weakPin());
}
rethrow;
} finally {
@ -286,7 +296,12 @@ class _DesktopPivStateNotifier extends PivStateNotifier {
return const PinVerificationStatus.success();
} on RpcError catch (e) {
if (e.status == 'invalid-pin') {
return PinVerificationStatus.failure(e.body['attempts_remaining']);
return PinVerificationStatus.failure(
PivPinFailureReason.invalidPin(e.body['attempts_remaining']));
}
if (e.status == 'pin-complexity') {
return PinVerificationStatus.failure(
const PivPinFailureReason.weakPin());
}
rethrow;
} finally {

View File

@ -52,7 +52,14 @@ class FidoState with _$FidoState {
@freezed
class PinResult with _$PinResult {
factory PinResult.success() = _PinSuccess;
factory PinResult.failed(int retries, bool authBlocked) = _PinFailure;
factory PinResult.failed(FidoPinFailureReason reason) = _PinFailure;
}
@freezed
class FidoPinFailureReason with _$FidoPinFailureReason {
factory FidoPinFailureReason.invalidPin(int retries, bool authBlocked) =
FidoInvalidPin;
const factory FidoPinFailureReason.weakPin() = FidoWeakPin;
}
@freezed

View File

@ -184,19 +184,19 @@ mixin _$PinResult {
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() success,
required TResult Function(int retries, bool authBlocked) failed,
required TResult Function(FidoPinFailureReason reason) failed,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? success,
TResult? Function(int retries, bool authBlocked)? failed,
TResult? Function(FidoPinFailureReason reason)? failed,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? success,
TResult Function(int retries, bool authBlocked)? failed,
TResult Function(FidoPinFailureReason reason)? failed,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@ -277,7 +277,7 @@ class _$PinSuccessImpl implements _PinSuccess {
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() success,
required TResult Function(int retries, bool authBlocked) failed,
required TResult Function(FidoPinFailureReason reason) failed,
}) {
return success();
}
@ -286,7 +286,7 @@ class _$PinSuccessImpl implements _PinSuccess {
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? success,
TResult? Function(int retries, bool authBlocked)? failed,
TResult? Function(FidoPinFailureReason reason)? failed,
}) {
return success?.call();
}
@ -295,7 +295,7 @@ class _$PinSuccessImpl implements _PinSuccess {
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? success,
TResult Function(int retries, bool authBlocked)? failed,
TResult Function(FidoPinFailureReason reason)? failed,
required TResult orElse(),
}) {
if (success != null) {
@ -346,7 +346,9 @@ abstract class _$$PinFailureImplCopyWith<$Res> {
_$PinFailureImpl value, $Res Function(_$PinFailureImpl) then) =
__$$PinFailureImplCopyWithImpl<$Res>;
@useResult
$Res call({int retries, bool authBlocked});
$Res call({FidoPinFailureReason reason});
$FidoPinFailureReasonCopyWith<$Res> get reason;
}
/// @nodoc
@ -360,35 +362,36 @@ class __$$PinFailureImplCopyWithImpl<$Res>
@pragma('vm:prefer-inline')
@override
$Res call({
Object? retries = null,
Object? authBlocked = null,
Object? reason = null,
}) {
return _then(_$PinFailureImpl(
null == retries
? _value.retries
: retries // ignore: cast_nullable_to_non_nullable
as int,
null == authBlocked
? _value.authBlocked
: authBlocked // ignore: cast_nullable_to_non_nullable
as bool,
null == reason
? _value.reason
: reason // ignore: cast_nullable_to_non_nullable
as FidoPinFailureReason,
));
}
@override
@pragma('vm:prefer-inline')
$FidoPinFailureReasonCopyWith<$Res> get reason {
return $FidoPinFailureReasonCopyWith<$Res>(_value.reason, (value) {
return _then(_value.copyWith(reason: value));
});
}
}
/// @nodoc
class _$PinFailureImpl implements _PinFailure {
_$PinFailureImpl(this.retries, this.authBlocked);
_$PinFailureImpl(this.reason);
@override
final int retries;
@override
final bool authBlocked;
final FidoPinFailureReason reason;
@override
String toString() {
return 'PinResult.failed(retries: $retries, authBlocked: $authBlocked)';
return 'PinResult.failed(reason: $reason)';
}
@override
@ -396,13 +399,11 @@ class _$PinFailureImpl implements _PinFailure {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$PinFailureImpl &&
(identical(other.retries, retries) || other.retries == retries) &&
(identical(other.authBlocked, authBlocked) ||
other.authBlocked == authBlocked));
(identical(other.reason, reason) || other.reason == reason));
}
@override
int get hashCode => Object.hash(runtimeType, retries, authBlocked);
int get hashCode => Object.hash(runtimeType, reason);
@JsonKey(ignore: true)
@override
@ -414,29 +415,29 @@ class _$PinFailureImpl implements _PinFailure {
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() success,
required TResult Function(int retries, bool authBlocked) failed,
required TResult Function(FidoPinFailureReason reason) failed,
}) {
return failed(retries, authBlocked);
return failed(reason);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? success,
TResult? Function(int retries, bool authBlocked)? failed,
TResult? Function(FidoPinFailureReason reason)? failed,
}) {
return failed?.call(retries, authBlocked);
return failed?.call(reason);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? success,
TResult Function(int retries, bool authBlocked)? failed,
TResult Function(FidoPinFailureReason reason)? failed,
required TResult orElse(),
}) {
if (failed != null) {
return failed(retries, authBlocked);
return failed(reason);
}
return orElse();
}
@ -474,16 +475,322 @@ class _$PinFailureImpl implements _PinFailure {
}
abstract class _PinFailure implements PinResult {
factory _PinFailure(final int retries, final bool authBlocked) =
_$PinFailureImpl;
factory _PinFailure(final FidoPinFailureReason reason) = _$PinFailureImpl;
FidoPinFailureReason get reason;
@JsonKey(ignore: true)
_$$PinFailureImplCopyWith<_$PinFailureImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$FidoPinFailureReason {
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(int retries, bool authBlocked) invalidPin,
required TResult Function() weakPin,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(int retries, bool authBlocked)? invalidPin,
TResult? Function()? weakPin,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(int retries, bool authBlocked)? invalidPin,
TResult Function()? weakPin,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(FidoInvalidPin value) invalidPin,
required TResult Function(FidoWeakPin value) weakPin,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(FidoInvalidPin value)? invalidPin,
TResult? Function(FidoWeakPin value)? weakPin,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(FidoInvalidPin value)? invalidPin,
TResult Function(FidoWeakPin value)? weakPin,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $FidoPinFailureReasonCopyWith<$Res> {
factory $FidoPinFailureReasonCopyWith(FidoPinFailureReason value,
$Res Function(FidoPinFailureReason) then) =
_$FidoPinFailureReasonCopyWithImpl<$Res, FidoPinFailureReason>;
}
/// @nodoc
class _$FidoPinFailureReasonCopyWithImpl<$Res,
$Val extends FidoPinFailureReason>
implements $FidoPinFailureReasonCopyWith<$Res> {
_$FidoPinFailureReasonCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
}
/// @nodoc
abstract class _$$FidoInvalidPinImplCopyWith<$Res> {
factory _$$FidoInvalidPinImplCopyWith(_$FidoInvalidPinImpl value,
$Res Function(_$FidoInvalidPinImpl) then) =
__$$FidoInvalidPinImplCopyWithImpl<$Res>;
@useResult
$Res call({int retries, bool authBlocked});
}
/// @nodoc
class __$$FidoInvalidPinImplCopyWithImpl<$Res>
extends _$FidoPinFailureReasonCopyWithImpl<$Res, _$FidoInvalidPinImpl>
implements _$$FidoInvalidPinImplCopyWith<$Res> {
__$$FidoInvalidPinImplCopyWithImpl(
_$FidoInvalidPinImpl _value, $Res Function(_$FidoInvalidPinImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? retries = null,
Object? authBlocked = null,
}) {
return _then(_$FidoInvalidPinImpl(
null == retries
? _value.retries
: retries // ignore: cast_nullable_to_non_nullable
as int,
null == authBlocked
? _value.authBlocked
: authBlocked // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// @nodoc
class _$FidoInvalidPinImpl implements FidoInvalidPin {
_$FidoInvalidPinImpl(this.retries, this.authBlocked);
@override
final int retries;
@override
final bool authBlocked;
@override
String toString() {
return 'FidoPinFailureReason.invalidPin(retries: $retries, authBlocked: $authBlocked)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$FidoInvalidPinImpl &&
(identical(other.retries, retries) || other.retries == retries) &&
(identical(other.authBlocked, authBlocked) ||
other.authBlocked == authBlocked));
}
@override
int get hashCode => Object.hash(runtimeType, retries, authBlocked);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$FidoInvalidPinImplCopyWith<_$FidoInvalidPinImpl> get copyWith =>
__$$FidoInvalidPinImplCopyWithImpl<_$FidoInvalidPinImpl>(
this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(int retries, bool authBlocked) invalidPin,
required TResult Function() weakPin,
}) {
return invalidPin(retries, authBlocked);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(int retries, bool authBlocked)? invalidPin,
TResult? Function()? weakPin,
}) {
return invalidPin?.call(retries, authBlocked);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(int retries, bool authBlocked)? invalidPin,
TResult Function()? weakPin,
required TResult orElse(),
}) {
if (invalidPin != null) {
return invalidPin(retries, authBlocked);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(FidoInvalidPin value) invalidPin,
required TResult Function(FidoWeakPin value) weakPin,
}) {
return invalidPin(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(FidoInvalidPin value)? invalidPin,
TResult? Function(FidoWeakPin value)? weakPin,
}) {
return invalidPin?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(FidoInvalidPin value)? invalidPin,
TResult Function(FidoWeakPin value)? weakPin,
required TResult orElse(),
}) {
if (invalidPin != null) {
return invalidPin(this);
}
return orElse();
}
}
abstract class FidoInvalidPin implements FidoPinFailureReason {
factory FidoInvalidPin(final int retries, final bool authBlocked) =
_$FidoInvalidPinImpl;
int get retries;
bool get authBlocked;
@JsonKey(ignore: true)
_$$PinFailureImplCopyWith<_$PinFailureImpl> get copyWith =>
_$$FidoInvalidPinImplCopyWith<_$FidoInvalidPinImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class _$$FidoWeakPinImplCopyWith<$Res> {
factory _$$FidoWeakPinImplCopyWith(
_$FidoWeakPinImpl value, $Res Function(_$FidoWeakPinImpl) then) =
__$$FidoWeakPinImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$FidoWeakPinImplCopyWithImpl<$Res>
extends _$FidoPinFailureReasonCopyWithImpl<$Res, _$FidoWeakPinImpl>
implements _$$FidoWeakPinImplCopyWith<$Res> {
__$$FidoWeakPinImplCopyWithImpl(
_$FidoWeakPinImpl _value, $Res Function(_$FidoWeakPinImpl) _then)
: super(_value, _then);
}
/// @nodoc
class _$FidoWeakPinImpl implements FidoWeakPin {
const _$FidoWeakPinImpl();
@override
String toString() {
return 'FidoPinFailureReason.weakPin()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$FidoWeakPinImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(int retries, bool authBlocked) invalidPin,
required TResult Function() weakPin,
}) {
return weakPin();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(int retries, bool authBlocked)? invalidPin,
TResult? Function()? weakPin,
}) {
return weakPin?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(int retries, bool authBlocked)? invalidPin,
TResult Function()? weakPin,
required TResult orElse(),
}) {
if (weakPin != null) {
return weakPin();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(FidoInvalidPin value) invalidPin,
required TResult Function(FidoWeakPin value) weakPin,
}) {
return weakPin(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(FidoInvalidPin value)? invalidPin,
TResult? Function(FidoWeakPin value)? weakPin,
}) {
return weakPin?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(FidoInvalidPin value)? invalidPin,
TResult Function(FidoWeakPin value)? weakPin,
required TResult orElse(),
}) {
if (weakPin != null) {
return weakPin(this);
}
return orElse();
}
}
abstract class FidoWeakPin implements FidoPinFailureReason {
const factory FidoWeakPin() = _$FidoWeakPinImpl;
}
Fingerprint _$FingerprintFromJson(Map<String, dynamic> json) {
return _Fingerprint.fromJson(json);
}

View File

@ -48,7 +48,8 @@ class FidoPinDialog extends ConsumerStatefulWidget {
class _FidoPinDialogState extends ConsumerState<FidoPinDialog> {
final _currentPinController = TextEditingController();
final _currentPinFocus = FocusNode();
String _newPin = '';
final _newPinController = TextEditingController();
final _newPinFocus = FocusNode();
String _confirmPin = '';
String? _currentPinError;
String? _newPinError;
@ -63,6 +64,8 @@ class _FidoPinDialogState extends ConsumerState<FidoPinDialog> {
void dispose() {
_currentPinController.dispose();
_currentPinFocus.dispose();
_newPinController.dispose();
_newPinFocus.dispose();
super.dispose();
}
@ -77,8 +80,13 @@ class _FidoPinDialogState extends ConsumerState<FidoPinDialog> {
: (widget.state.forcePinChange ? 4 : widget.state.minPinLength);
final currentPinLenOk =
_currentPinController.text.length >= currentMinPinLen;
final newPinLenOk = _newPin.length >= minPinLength;
final isValid = currentPinLenOk && newPinLenOk && _newPin == _confirmPin;
final newPinLenOk = _newPinController.text.length >= minPinLength;
final isValid =
currentPinLenOk && newPinLenOk && _newPinController.text == _confirmPin;
final hasPinComplexity =
ref.read(currentDeviceDataProvider).valueOrNull?.info.pinComplexity ??
false;
return ResponsiveDialog(
title: Text(hasPin ? l10n.s_change_pin : l10n.s_set_pin),
@ -130,11 +138,15 @@ class _FidoPinDialogState extends ConsumerState<FidoPinDialog> {
},
).init(),
],
Text(l10n.p_enter_new_fido2_pin(minPinLength)),
Text(hasPinComplexity
? l10n.p_enter_new_fido2_pin_complexity_active(
minPinLength, 2, '123456')
: l10n.p_enter_new_fido2_pin(minPinLength)),
// TODO: Set max characters based on UTF-8 bytes
AppTextFormField(
key: newPin,
initialValue: _newPin,
controller: _newPinController,
focusNode: _newPinFocus,
autofocus: !hasPin,
obscureText: _isObscureNew,
autofillHints: const [AutofillHints.password],
@ -160,7 +172,6 @@ class _FidoPinDialogState extends ConsumerState<FidoPinDialog> {
onChanged: (value) {
setState(() {
_newIsWrong = false;
_newPin = value;
});
},
).init(),
@ -186,10 +197,11 @@ class _FidoPinDialogState extends ConsumerState<FidoPinDialog> {
_isObscureConfirm ? l10n.s_show_pin : l10n.s_hide_pin,
),
enabled: !_isBlocked && currentPinLenOk && newPinLenOk,
errorText: _newPin.length == _confirmPin.length &&
_newPin != _confirmPin
? l10n.l_pin_mismatch
: null,
errorText:
_newPinController.text.length == _confirmPin.length &&
_newPinController.text != _confirmPin
? l10n.l_pin_mismatch
: null,
helperText: '', // Prevents resizing when errorText shown
),
onChanged: (value) {
@ -219,28 +231,47 @@ class _FidoPinDialogState extends ConsumerState<FidoPinDialog> {
final oldPin = _currentPinController.text.isNotEmpty
? _currentPinController.text
: null;
final newPin = _newPinController.text;
try {
final result = await ref
.read(fidoStateProvider(widget.devicePath).notifier)
.setPin(_newPin, oldPin: oldPin);
result.when(success: () {
Navigator.of(context).pop(true);
showMessage(context, l10n.s_pin_set);
}, failed: (retries, authBlocked) {
setState(() {
_currentPinController.selection = TextSelection(
baseOffset: 0, extentOffset: _currentPinController.text.length);
_currentPinFocus.requestFocus();
if (authBlocked) {
_currentPinError = l10n.l_pin_soft_locked;
_currentIsWrong = true;
_isBlocked = true;
} else {
_currentPinError = l10n.l_wrong_pin_attempts_remaining(retries);
_currentIsWrong = true;
}
});
});
.setPin(newPin, oldPin: oldPin);
result.whenOrNull(
success: () {
Navigator.of(context).pop(true);
showMessage(context, l10n.s_pin_set);
},
failed: (reason) {
reason.when(
invalidPin: (retries, authBlocked) {
_currentPinController.selection = TextSelection(
baseOffset: 0,
extentOffset: _currentPinController.text.length);
_currentPinFocus.requestFocus();
setState(() {
if (authBlocked) {
_currentPinError = l10n.l_pin_soft_locked;
_currentIsWrong = true;
_isBlocked = true;
} else {
_currentPinError =
l10n.l_wrong_pin_attempts_remaining(retries);
_currentIsWrong = true;
}
});
},
weakPin: () {
_newPinController.selection = TextSelection(
baseOffset: 0, extentOffset: _newPinController.text.length);
_newPinFocus.requestFocus();
setState(() {
_newPinError = l10n.p_pin_puk_complexity_failure(l10n.s_pin);
_newIsWrong = true;
});
},
);
},
);
} on CancellationException catch (_) {
// ignored
} catch (e) {

View File

@ -60,15 +60,20 @@ class _PinEntryFormState extends ConsumerState<PinEntryForm> {
final result = await ref
.read(fidoStateProvider(widget._deviceNode.path).notifier)
.unlock(_pinController.text);
result.whenOrNull(failed: (retries, authBlocked) {
_pinController.selection = TextSelection(
baseOffset: 0, extentOffset: _pinController.text.length);
_pinFocus.requestFocus();
setState(() {
_pinIsWrong = true;
_retries = retries;
_blocked = authBlocked;
});
result.whenOrNull(failed: (reason) {
reason.maybeWhen(
invalidPin: (retries, authBlocked) {
_pinController.selection = TextSelection(
baseOffset: 0, extentOffset: _pinController.text.length);
_pinFocus.requestFocus();
setState(() {
_pinIsWrong = true;
_retries = retries;
_blocked = authBlocked;
});
},
orElse: () {},
);
});
} on CancellationException catch (_) {
// ignored

View File

@ -252,6 +252,18 @@
"message": {}
}
},
"l_set_puk_failed": null,
"@l_set_puk_failed": {
"placeholders": {
"message": {}
}
},
"l_unblock_pin_failed": null,
"@l_unblock_pin_failed": {
"placeholders": {
"message": {}
}
},
"l_attempts_remaining": null,
"@l_attempts_remaining": {
"placeholders": {
@ -288,6 +300,14 @@
"length": {}
}
},
"p_enter_new_fido2_pin_complexity_active": null,
"@p_enter_new_fido2_pin_complexity_active": {
"placeholders": {
"length": {},
"unique_characters": {},
"common_pin": {}
}
},
"s_pin_required": null,
"p_pin_required_desc": null,
"l_piv_pin_blocked": null,
@ -298,6 +318,19 @@
"name": {}
}
},
"p_enter_new_piv_pin_puk_complexity_active": null,
"@p_enter_new_piv_pin_puk_complexity_active": {
"placeholders": {
"name": {},
"common": {}
}
},
"p_pin_puk_complexity_failure": null,
"@p_pin_puk_complexity_failure": {
"placeholders": {
"name": {}
}
},
"l_warning_default_pin": null,
"l_warning_default_puk": null,
"l_default_pin_used": null,

View File

@ -252,6 +252,18 @@
"message": {}
}
},
"l_set_puk_failed": "Failed to set PUK: {message}",
"@l_set_puk_failed": {
"placeholders": {
"message": {}
}
},
"l_unblock_pin_failed": "Failed to unblock PIN: {message}",
"@l_unblock_pin_failed": {
"placeholders": {
"message": {}
}
},
"l_attempts_remaining": "{retries} attempt(s) remaining",
"@l_attempts_remaining": {
"placeholders": {
@ -288,6 +300,14 @@
"length": {}
}
},
"p_enter_new_fido2_pin_complexity_active": "Enter your new PIN. A PIN must be at least {length} characters long, contain at least {unique_characters} unique characters, and not be a commonly used PIN, like \"{common_pin}\". It may contain letters, numbers, and special characters.",
"@p_enter_new_fido2_pin_complexity_active": {
"placeholders": {
"length": {},
"unique_characters": {},
"common_pin": {}
}
},
"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",
@ -298,6 +318,19 @@
"name": {}
}
},
"p_enter_new_piv_pin_puk_complexity_active": "Enter a new {name} to set. Must be 6-8 characters, contain at least 2 unique characters, and not be a commonly used {name}, like \"{common}\".",
"@p_enter_new_piv_pin_puk_complexity_active": {
"placeholders": {
"name": {},
"common": {}
}
},
"p_pin_puk_complexity_failure": "New {name} doesn't meet complexity requirements.",
"@p_pin_puk_complexity_failure": {
"placeholders": {
"name": {}
}
},
"l_warning_default_pin": "Warning: Default PIN used",
"l_warning_default_puk": "Warning: Default PUK used",
"l_default_pin_used": "Default PIN used",

View File

@ -252,6 +252,18 @@
"message": {}
}
},
"l_set_puk_failed": null,
"@l_set_puk_failed": {
"placeholders": {
"message": {}
}
},
"l_unblock_pin_failed": null,
"@l_unblock_pin_failed": {
"placeholders": {
"message": {}
}
},
"l_attempts_remaining": "Nombre de tentative(s) restante(s) : {retries}",
"@l_attempts_remaining": {
"placeholders": {
@ -288,6 +300,14 @@
"length": {}
}
},
"p_enter_new_fido2_pin_complexity_active": null,
"@p_enter_new_fido2_pin_complexity_active": {
"placeholders": {
"length": {},
"unique_characters": {},
"common_pin": {}
}
},
"s_pin_required": "PIN requis",
"p_pin_required_desc": "L'action que vous allez faire demande d'entrer le code PIN du PIV.",
"l_piv_pin_blocked": "Vous êtes bloqué, utilisez le code PUK pour réinitialiser",
@ -298,6 +318,19 @@
"name": {}
}
},
"p_enter_new_piv_pin_puk_complexity_active": null,
"@p_enter_new_piv_pin_puk_complexity_active": {
"placeholders": {
"name": {},
"common": {}
}
},
"p_pin_puk_complexity_failure": null,
"@p_pin_puk_complexity_failure": {
"placeholders": {
"name": {}
}
},
"l_warning_default_pin": null,
"l_warning_default_puk": null,
"l_default_pin_used": null,

View File

@ -252,6 +252,18 @@
"message": {}
}
},
"l_set_puk_failed": null,
"@l_set_puk_failed": {
"placeholders": {
"message": {}
}
},
"l_unblock_pin_failed": null,
"@l_unblock_pin_failed": {
"placeholders": {
"message": {}
}
},
"l_attempts_remaining": "あと{retries}回試行できます",
"@l_attempts_remaining": {
"placeholders": {
@ -288,6 +300,14 @@
"length": {}
}
},
"p_enter_new_fido2_pin_complexity_active": null,
"@p_enter_new_fido2_pin_complexity_active": {
"placeholders": {
"length": {},
"unique_characters": {},
"common_pin": {}
}
},
"s_pin_required": "PINが必要",
"p_pin_required_desc": "実行しようとしている操作には、PIV PINの入力が必要です",
"l_piv_pin_blocked": "ブロックされています。PUK を使用してリセットしてください",
@ -298,6 +318,19 @@
"name": {}
}
},
"p_enter_new_piv_pin_puk_complexity_active": null,
"@p_enter_new_piv_pin_puk_complexity_active": {
"placeholders": {
"name": {},
"common": {}
}
},
"p_pin_puk_complexity_failure": null,
"@p_pin_puk_complexity_failure": {
"placeholders": {
"name": {}
}
},
"l_warning_default_pin": null,
"l_warning_default_puk": null,
"l_default_pin_used": null,

View File

@ -252,6 +252,18 @@
"message": {}
}
},
"l_set_puk_failed": null,
"@l_set_puk_failed": {
"placeholders": {
"message": {}
}
},
"l_unblock_pin_failed": null,
"@l_unblock_pin_failed": {
"placeholders": {
"message": {}
}
},
"l_attempts_remaining": "Pozostało prób: {retries}",
"@l_attempts_remaining": {
"placeholders": {
@ -288,6 +300,14 @@
"length": {}
}
},
"p_enter_new_fido2_pin_complexity_active": null,
"@p_enter_new_fido2_pin_complexity_active": {
"placeholders": {
"length": {},
"unique_characters": {},
"common_pin": {}
}
},
"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ć",
@ -298,6 +318,19 @@
"name": {}
}
},
"p_enter_new_piv_pin_puk_complexity_active": null,
"@p_enter_new_piv_pin_puk_complexity_active": {
"placeholders": {
"name": {},
"common": {}
}
},
"p_pin_puk_complexity_failure": null,
"@p_pin_puk_complexity_failure": {
"placeholders": {
"name": {}
}
},
"l_warning_default_pin": null,
"l_warning_default_puk": null,
"l_default_pin_used": null,

View File

@ -86,7 +86,8 @@ class DeviceInfo with _$DeviceInfo {
Map<Transport, int> supportedCapabilities,
bool isLocked,
bool isFips,
bool isSky) = _DeviceInfo;
bool isSky,
bool pinComplexity) = _DeviceInfo;
factory DeviceInfo.fromJson(Map<String, dynamic> json) =>
_$DeviceInfoFromJson(json);

View File

@ -245,6 +245,7 @@ mixin _$DeviceInfo {
bool get isLocked => throw _privateConstructorUsedError;
bool get isFips => throw _privateConstructorUsedError;
bool get isSky => throw _privateConstructorUsedError;
bool get pinComplexity => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
@ -266,7 +267,8 @@ abstract class $DeviceInfoCopyWith<$Res> {
Map<Transport, int> supportedCapabilities,
bool isLocked,
bool isFips,
bool isSky});
bool isSky,
bool pinComplexity});
$DeviceConfigCopyWith<$Res> get config;
$VersionCopyWith<$Res> get version;
@ -293,6 +295,7 @@ class _$DeviceInfoCopyWithImpl<$Res, $Val extends DeviceInfo>
Object? isLocked = null,
Object? isFips = null,
Object? isSky = null,
Object? pinComplexity = null,
}) {
return _then(_value.copyWith(
config: null == config
@ -327,6 +330,10 @@ class _$DeviceInfoCopyWithImpl<$Res, $Val extends DeviceInfo>
? _value.isSky
: isSky // ignore: cast_nullable_to_non_nullable
as bool,
pinComplexity: null == pinComplexity
? _value.pinComplexity
: pinComplexity // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val);
}
@ -363,7 +370,8 @@ abstract class _$$DeviceInfoImplCopyWith<$Res>
Map<Transport, int> supportedCapabilities,
bool isLocked,
bool isFips,
bool isSky});
bool isSky,
bool pinComplexity});
@override
$DeviceConfigCopyWith<$Res> get config;
@ -390,6 +398,7 @@ class __$$DeviceInfoImplCopyWithImpl<$Res>
Object? isLocked = null,
Object? isFips = null,
Object? isSky = null,
Object? pinComplexity = null,
}) {
return _then(_$DeviceInfoImpl(
null == config
@ -424,6 +433,10 @@ class __$$DeviceInfoImplCopyWithImpl<$Res>
? _value.isSky
: isSky // ignore: cast_nullable_to_non_nullable
as bool,
null == pinComplexity
? _value.pinComplexity
: pinComplexity // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
@ -439,7 +452,8 @@ class _$DeviceInfoImpl implements _DeviceInfo {
final Map<Transport, int> supportedCapabilities,
this.isLocked,
this.isFips,
this.isSky)
this.isSky,
this.pinComplexity)
: _supportedCapabilities = supportedCapabilities;
factory _$DeviceInfoImpl.fromJson(Map<String, dynamic> json) =>
@ -468,10 +482,12 @@ class _$DeviceInfoImpl implements _DeviceInfo {
final bool isFips;
@override
final bool isSky;
@override
final bool pinComplexity;
@override
String toString() {
return 'DeviceInfo(config: $config, serial: $serial, version: $version, formFactor: $formFactor, supportedCapabilities: $supportedCapabilities, isLocked: $isLocked, isFips: $isFips, isSky: $isSky)';
return 'DeviceInfo(config: $config, serial: $serial, version: $version, formFactor: $formFactor, supportedCapabilities: $supportedCapabilities, isLocked: $isLocked, isFips: $isFips, isSky: $isSky, pinComplexity: $pinComplexity)';
}
@override
@ -489,7 +505,9 @@ class _$DeviceInfoImpl implements _DeviceInfo {
(identical(other.isLocked, isLocked) ||
other.isLocked == isLocked) &&
(identical(other.isFips, isFips) || other.isFips == isFips) &&
(identical(other.isSky, isSky) || other.isSky == isSky));
(identical(other.isSky, isSky) || other.isSky == isSky) &&
(identical(other.pinComplexity, pinComplexity) ||
other.pinComplexity == pinComplexity));
}
@JsonKey(ignore: true)
@ -503,7 +521,8 @@ class _$DeviceInfoImpl implements _DeviceInfo {
const DeepCollectionEquality().hash(_supportedCapabilities),
isLocked,
isFips,
isSky);
isSky,
pinComplexity);
@JsonKey(ignore: true)
@override
@ -528,7 +547,8 @@ abstract class _DeviceInfo implements DeviceInfo {
final Map<Transport, int> supportedCapabilities,
final bool isLocked,
final bool isFips,
final bool isSky) = _$DeviceInfoImpl;
final bool isSky,
final bool pinComplexity) = _$DeviceInfoImpl;
factory _DeviceInfo.fromJson(Map<String, dynamic> json) =
_$DeviceInfoImpl.fromJson;
@ -550,6 +570,8 @@ abstract class _DeviceInfo implements DeviceInfo {
@override
bool get isSky;
@override
bool get pinComplexity;
@override
@JsonKey(ignore: true)
_$$DeviceInfoImplCopyWith<_$DeviceInfoImpl> get copyWith =>
throw _privateConstructorUsedError;

View File

@ -42,6 +42,7 @@ _$DeviceInfoImpl _$$DeviceInfoImplFromJson(Map<String, dynamic> json) =>
json['is_locked'] as bool,
json['is_fips'] as bool,
json['is_sky'] as bool,
json['pin_complexity'] as bool,
);
Map<String, dynamic> _$$DeviceInfoImplToJson(_$DeviceInfoImpl instance) =>
@ -55,6 +56,7 @@ Map<String, dynamic> _$$DeviceInfoImplToJson(_$DeviceInfoImpl instance) =>
'is_locked': instance.isLocked,
'is_fips': instance.isFips,
'is_sky': instance.isSky,
'pin_complexity': instance.pinComplexity,
};
const _$FormFactorEnumMap = {

View File

@ -209,7 +209,14 @@ class PinMetadata with _$PinMetadata {
@freezed
class PinVerificationStatus with _$PinVerificationStatus {
const factory PinVerificationStatus.success() = PinSuccess;
factory PinVerificationStatus.failure(int attemptsRemaining) = PinFailure;
factory PinVerificationStatus.failure(PivPinFailureReason reason) =
PinFailure;
}
@freezed
class PivPinFailureReason with _$PivPinFailureReason {
factory PivPinFailureReason.invalidPin(int attemptsRemaining) = PivInvalidPin;
const factory PivPinFailureReason.weakPin() = PivWeakPin;
}
@freezed

View File

@ -193,19 +193,19 @@ mixin _$PinVerificationStatus {
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() success,
required TResult Function(int attemptsRemaining) failure,
required TResult Function(PivPinFailureReason reason) failure,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? success,
TResult? Function(int attemptsRemaining)? failure,
TResult? Function(PivPinFailureReason reason)? failure,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? success,
TResult Function(int attemptsRemaining)? failure,
TResult Function(PivPinFailureReason reason)? failure,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@ -288,7 +288,7 @@ class _$PinSuccessImpl implements PinSuccess {
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() success,
required TResult Function(int attemptsRemaining) failure,
required TResult Function(PivPinFailureReason reason) failure,
}) {
return success();
}
@ -297,7 +297,7 @@ class _$PinSuccessImpl implements PinSuccess {
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? success,
TResult? Function(int attemptsRemaining)? failure,
TResult? Function(PivPinFailureReason reason)? failure,
}) {
return success?.call();
}
@ -306,7 +306,7 @@ class _$PinSuccessImpl implements PinSuccess {
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? success,
TResult Function(int attemptsRemaining)? failure,
TResult Function(PivPinFailureReason reason)? failure,
required TResult orElse(),
}) {
if (success != null) {
@ -357,7 +357,9 @@ abstract class _$$PinFailureImplCopyWith<$Res> {
_$PinFailureImpl value, $Res Function(_$PinFailureImpl) then) =
__$$PinFailureImplCopyWithImpl<$Res>;
@useResult
$Res call({int attemptsRemaining});
$Res call({PivPinFailureReason reason});
$PivPinFailureReasonCopyWith<$Res> get reason;
}
/// @nodoc
@ -371,28 +373,36 @@ class __$$PinFailureImplCopyWithImpl<$Res>
@pragma('vm:prefer-inline')
@override
$Res call({
Object? attemptsRemaining = null,
Object? reason = null,
}) {
return _then(_$PinFailureImpl(
null == attemptsRemaining
? _value.attemptsRemaining
: attemptsRemaining // ignore: cast_nullable_to_non_nullable
as int,
null == reason
? _value.reason
: reason // ignore: cast_nullable_to_non_nullable
as PivPinFailureReason,
));
}
@override
@pragma('vm:prefer-inline')
$PivPinFailureReasonCopyWith<$Res> get reason {
return $PivPinFailureReasonCopyWith<$Res>(_value.reason, (value) {
return _then(_value.copyWith(reason: value));
});
}
}
/// @nodoc
class _$PinFailureImpl implements PinFailure {
_$PinFailureImpl(this.attemptsRemaining);
_$PinFailureImpl(this.reason);
@override
final int attemptsRemaining;
final PivPinFailureReason reason;
@override
String toString() {
return 'PinVerificationStatus.failure(attemptsRemaining: $attemptsRemaining)';
return 'PinVerificationStatus.failure(reason: $reason)';
}
@override
@ -400,12 +410,11 @@ class _$PinFailureImpl implements PinFailure {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$PinFailureImpl &&
(identical(other.attemptsRemaining, attemptsRemaining) ||
other.attemptsRemaining == attemptsRemaining));
(identical(other.reason, reason) || other.reason == reason));
}
@override
int get hashCode => Object.hash(runtimeType, attemptsRemaining);
int get hashCode => Object.hash(runtimeType, reason);
@JsonKey(ignore: true)
@override
@ -417,29 +426,29 @@ class _$PinFailureImpl implements PinFailure {
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() success,
required TResult Function(int attemptsRemaining) failure,
required TResult Function(PivPinFailureReason reason) failure,
}) {
return failure(attemptsRemaining);
return failure(reason);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? success,
TResult? Function(int attemptsRemaining)? failure,
TResult? Function(PivPinFailureReason reason)? failure,
}) {
return failure?.call(attemptsRemaining);
return failure?.call(reason);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? success,
TResult Function(int attemptsRemaining)? failure,
TResult Function(PivPinFailureReason reason)? failure,
required TResult orElse(),
}) {
if (failure != null) {
return failure(attemptsRemaining);
return failure(reason);
}
return orElse();
}
@ -477,14 +486,310 @@ class _$PinFailureImpl implements PinFailure {
}
abstract class PinFailure implements PinVerificationStatus {
factory PinFailure(final int attemptsRemaining) = _$PinFailureImpl;
factory PinFailure(final PivPinFailureReason reason) = _$PinFailureImpl;
int get attemptsRemaining;
PivPinFailureReason get reason;
@JsonKey(ignore: true)
_$$PinFailureImplCopyWith<_$PinFailureImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$PivPinFailureReason {
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(int attemptsRemaining) invalidPin,
required TResult Function() weakPin,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(int attemptsRemaining)? invalidPin,
TResult? Function()? weakPin,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(int attemptsRemaining)? invalidPin,
TResult Function()? weakPin,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(PivInvalidPin value) invalidPin,
required TResult Function(PivWeakPin value) weakPin,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(PivInvalidPin value)? invalidPin,
TResult? Function(PivWeakPin value)? weakPin,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(PivInvalidPin value)? invalidPin,
TResult Function(PivWeakPin value)? weakPin,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $PivPinFailureReasonCopyWith<$Res> {
factory $PivPinFailureReasonCopyWith(
PivPinFailureReason value, $Res Function(PivPinFailureReason) then) =
_$PivPinFailureReasonCopyWithImpl<$Res, PivPinFailureReason>;
}
/// @nodoc
class _$PivPinFailureReasonCopyWithImpl<$Res, $Val extends PivPinFailureReason>
implements $PivPinFailureReasonCopyWith<$Res> {
_$PivPinFailureReasonCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
}
/// @nodoc
abstract class _$$PivInvalidPinImplCopyWith<$Res> {
factory _$$PivInvalidPinImplCopyWith(
_$PivInvalidPinImpl value, $Res Function(_$PivInvalidPinImpl) then) =
__$$PivInvalidPinImplCopyWithImpl<$Res>;
@useResult
$Res call({int attemptsRemaining});
}
/// @nodoc
class __$$PivInvalidPinImplCopyWithImpl<$Res>
extends _$PivPinFailureReasonCopyWithImpl<$Res, _$PivInvalidPinImpl>
implements _$$PivInvalidPinImplCopyWith<$Res> {
__$$PivInvalidPinImplCopyWithImpl(
_$PivInvalidPinImpl _value, $Res Function(_$PivInvalidPinImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? attemptsRemaining = null,
}) {
return _then(_$PivInvalidPinImpl(
null == attemptsRemaining
? _value.attemptsRemaining
: attemptsRemaining // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// @nodoc
class _$PivInvalidPinImpl implements PivInvalidPin {
_$PivInvalidPinImpl(this.attemptsRemaining);
@override
final int attemptsRemaining;
@override
String toString() {
return 'PivPinFailureReason.invalidPin(attemptsRemaining: $attemptsRemaining)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$PivInvalidPinImpl &&
(identical(other.attemptsRemaining, attemptsRemaining) ||
other.attemptsRemaining == attemptsRemaining));
}
@override
int get hashCode => Object.hash(runtimeType, attemptsRemaining);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$PivInvalidPinImplCopyWith<_$PivInvalidPinImpl> get copyWith =>
__$$PivInvalidPinImplCopyWithImpl<_$PivInvalidPinImpl>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(int attemptsRemaining) invalidPin,
required TResult Function() weakPin,
}) {
return invalidPin(attemptsRemaining);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(int attemptsRemaining)? invalidPin,
TResult? Function()? weakPin,
}) {
return invalidPin?.call(attemptsRemaining);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(int attemptsRemaining)? invalidPin,
TResult Function()? weakPin,
required TResult orElse(),
}) {
if (invalidPin != null) {
return invalidPin(attemptsRemaining);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(PivInvalidPin value) invalidPin,
required TResult Function(PivWeakPin value) weakPin,
}) {
return invalidPin(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(PivInvalidPin value)? invalidPin,
TResult? Function(PivWeakPin value)? weakPin,
}) {
return invalidPin?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(PivInvalidPin value)? invalidPin,
TResult Function(PivWeakPin value)? weakPin,
required TResult orElse(),
}) {
if (invalidPin != null) {
return invalidPin(this);
}
return orElse();
}
}
abstract class PivInvalidPin implements PivPinFailureReason {
factory PivInvalidPin(final int attemptsRemaining) = _$PivInvalidPinImpl;
int get attemptsRemaining;
@JsonKey(ignore: true)
_$$PivInvalidPinImplCopyWith<_$PivInvalidPinImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class _$$PivWeakPinImplCopyWith<$Res> {
factory _$$PivWeakPinImplCopyWith(
_$PivWeakPinImpl value, $Res Function(_$PivWeakPinImpl) then) =
__$$PivWeakPinImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$PivWeakPinImplCopyWithImpl<$Res>
extends _$PivPinFailureReasonCopyWithImpl<$Res, _$PivWeakPinImpl>
implements _$$PivWeakPinImplCopyWith<$Res> {
__$$PivWeakPinImplCopyWithImpl(
_$PivWeakPinImpl _value, $Res Function(_$PivWeakPinImpl) _then)
: super(_value, _then);
}
/// @nodoc
class _$PivWeakPinImpl implements PivWeakPin {
const _$PivWeakPinImpl();
@override
String toString() {
return 'PivPinFailureReason.weakPin()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$PivWeakPinImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(int attemptsRemaining) invalidPin,
required TResult Function() weakPin,
}) {
return weakPin();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(int attemptsRemaining)? invalidPin,
TResult? Function()? weakPin,
}) {
return weakPin?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(int attemptsRemaining)? invalidPin,
TResult Function()? weakPin,
required TResult orElse(),
}) {
if (weakPin != null) {
return weakPin();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(PivInvalidPin value) invalidPin,
required TResult Function(PivWeakPin value) weakPin,
}) {
return weakPin(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(PivInvalidPin value)? invalidPin,
TResult? Function(PivWeakPin value)? weakPin,
}) {
return weakPin?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(PivInvalidPin value)? invalidPin,
TResult Function(PivWeakPin value)? weakPin,
required TResult orElse(),
}) {
if (weakPin != null) {
return weakPin(this);
}
return orElse();
}
}
abstract class PivWeakPin implements PivPinFailureReason {
const factory PivWeakPin() = _$PivWeakPinImpl;
}
ManagementKeyMetadata _$ManagementKeyMetadataFromJson(
Map<String, dynamic> json) {
return _ManagementKeyMetadata.fromJson(json);

View File

@ -106,14 +106,19 @@ class _ManageKeyDialogState extends ConsumerState<ManageKeyDialog> {
if (_usesStoredKey) {
final status = (await notifier.verifyPin(_currentController.text)).when(
success: () => true,
failure: (attemptsRemaining) {
_currentController.selection = TextSelection(
baseOffset: 0, extentOffset: _currentController.text.length);
_currentFocus.requestFocus();
setState(() {
_attemptsRemaining = attemptsRemaining;
_currentIsWrong = true;
});
failure: (reason) {
reason.maybeWhen(
invalidPin: (attemptsRemaining) {
_currentController.selection = TextSelection(
baseOffset: 0, extentOffset: _currentController.text.length);
_currentFocus.requestFocus();
setState(() {
_attemptsRemaining = attemptsRemaining;
_currentIsWrong = true;
});
},
orElse: () {},
);
return false;
},
);

View File

@ -21,6 +21,7 @@ import 'package:material_symbols_icons/symbols.dart';
import '../../app/message.dart';
import '../../app/models.dart';
import '../../app/state.dart';
import '../../widgets/app_input_decoration.dart';
import '../../widgets/app_text_field.dart';
import '../../widgets/responsive_dialog.dart';
@ -46,10 +47,13 @@ class ManagePinPukDialog extends ConsumerStatefulWidget {
class _ManagePinPukDialogState extends ConsumerState<ManagePinPukDialog> {
final _currentPinController = TextEditingController();
final _currentPinFocus = FocusNode();
String _newPin = '';
final _newPinController = TextEditingController();
final _newPinFocus = FocusNode();
String _confirmPin = '';
bool _pinIsBlocked = false;
bool _currentIsWrong = false;
bool _newIsWrong = false;
String? _newPinError;
int _attemptsRemaining = -1;
bool _isObscureCurrent = true;
bool _isObscureNew = true;
@ -80,23 +84,26 @@ class _ManagePinPukDialogState extends ConsumerState<ManagePinPukDialog> {
void dispose() {
_currentPinController.dispose();
_currentPinFocus.dispose();
_newPinController.dispose();
_newPinFocus.dispose();
super.dispose();
}
_submit() async {
final notifier = ref.read(pivStateProvider(widget.path).notifier);
final l10n = AppLocalizations.of(context)!;
final result = await switch (widget.target) {
ManageTarget.pin =>
notifier.changePin(_currentPinController.text, _newPin),
notifier.changePin(_currentPinController.text, _newPinController.text),
ManageTarget.puk =>
notifier.changePuk(_currentPinController.text, _newPin),
notifier.changePuk(_currentPinController.text, _newPinController.text),
ManageTarget.unblock =>
notifier.unblockPin(_currentPinController.text, _newPin),
notifier.unblockPin(_currentPinController.text, _newPinController.text),
};
result.when(success: () {
if (!mounted) return;
final l10n = AppLocalizations.of(context)!;
Navigator.of(context).pop();
showMessage(
context,
@ -104,17 +111,31 @@ class _ManagePinPukDialogState extends ConsumerState<ManagePinPukDialog> {
ManageTarget.puk => l10n.s_puk_set,
_ => l10n.s_pin_set,
});
}, failure: (attemptsRemaining) {
_currentPinController.selection = TextSelection(
baseOffset: 0, extentOffset: _currentPinController.text.length);
_currentPinFocus.requestFocus();
setState(() {
_attemptsRemaining = attemptsRemaining;
_currentIsWrong = true;
if (_attemptsRemaining == 0) {
_pinIsBlocked = true;
}
});
}, failure: (reason) {
reason.when(
invalidPin: (attemptsRemaining) {
_currentPinController.selection = TextSelection(
baseOffset: 0, extentOffset: _currentPinController.text.length);
_currentPinFocus.requestFocus();
setState(() {
_attemptsRemaining = attemptsRemaining;
_currentIsWrong = true;
if (_attemptsRemaining == 0) {
_pinIsBlocked = true;
}
});
},
weakPin: () {
_newPinController.selection = TextSelection(
baseOffset: 0, extentOffset: _newPinController.text.length);
_newPinFocus.requestFocus();
setState(() {
_newPinError = l10n.p_pin_puk_complexity_failure(
widget.target == ManageTarget.puk ? l10n.s_puk : l10n.s_pin);
_newIsWrong = true;
});
},
);
});
}
@ -123,10 +144,11 @@ class _ManagePinPukDialogState extends ConsumerState<ManagePinPukDialog> {
final l10n = AppLocalizations.of(context)!;
final currentPin = _currentPinController.text;
final currentPinLen = byteLength(currentPin);
final newPinLen = byteLength(_newPin);
final newPin = _newPinController.text;
final newPinLen = byteLength(newPin);
final isValid = !_currentIsWrong &&
_newPin.isNotEmpty &&
_newPin == _confirmPin &&
newPin.isNotEmpty &&
newPin == _confirmPin &&
currentPin.isNotEmpty;
final titleText = switch (widget.target) {
@ -140,6 +162,10 @@ class _ManagePinPukDialogState extends ConsumerState<ManagePinPukDialog> {
final showDefaultPukUsed =
widget.target != ManageTarget.pin && _defaultPukUsed;
final hasPinComplexity =
ref.read(currentDeviceDataProvider).valueOrNull?.info.pinComplexity ??
false;
return ResponsiveDialog(
title: Text(titleText),
actions: [
@ -213,21 +239,29 @@ class _ManagePinPukDialogState extends ConsumerState<ManagePinPukDialog> {
});
},
).init(),
Text(l10n.p_enter_new_piv_pin_puk(
widget.target == ManageTarget.puk ? l10n.s_puk : l10n.s_pin)),
Text(hasPinComplexity
? l10n.p_enter_new_piv_pin_puk_complexity_active(
widget.target == ManageTarget.puk ? l10n.s_puk : l10n.s_pin,
'123456')
: l10n.p_enter_new_piv_pin_puk(widget.target == ManageTarget.puk
? l10n.s_puk
: l10n.s_pin)),
AppTextField(
key: keys.newPinPukField,
autofocus: showDefaultPinUsed || showDefaultPukUsed,
obscureText: _isObscureNew,
controller: _newPinController,
focusNode: _newPinFocus,
maxLength: 8,
inputFormatters: [limitBytesLength(8)],
buildCounter: buildByteCounterFor(_newPin),
buildCounter: buildByteCounterFor(newPin),
autofillHints: const [AutofillHints.newPassword],
decoration: AppInputDecoration(
border: const OutlineInputBorder(),
labelText: widget.target == ManageTarget.puk
? l10n.s_new_puk
: l10n.s_new_pin,
errorText: _newIsWrong ? _newPinError : null,
prefixIcon: const Icon(Symbols.password),
suffixIcon: IconButton(
icon: Icon(_isObscureNew
@ -247,7 +281,7 @@ class _ManagePinPukDialogState extends ConsumerState<ManagePinPukDialog> {
textInputAction: TextInputAction.next,
onChanged: (value) {
setState(() {
_newPin = value;
_newIsWrong = false;
});
},
onSubmitted: (_) {
@ -284,8 +318,9 @@ class _ManagePinPukDialogState extends ConsumerState<ManagePinPukDialog> {
),
enabled: currentPinLen >= _minPinLen && newPinLen >= 6,
errorText:
newPinLen == _confirmPin.length && _newPin != _confirmPin
? (widget.target == ManageTarget.pin
newPinLen == _confirmPin.length && newPin != _confirmPin
? (widget.target == ManageTarget.pin ||
widget.target == ManageTarget.unblock
? l10n.l_pin_mismatch
: l10n.l_puk_mismatch)
: null,

View File

@ -60,14 +60,19 @@ class _PinDialogState extends ConsumerState<PinDialog> {
success: () {
navigator.pop(true);
},
failure: (attemptsRemaining) {
_pinController.selection = TextSelection(
baseOffset: 0, extentOffset: _pinController.text.length);
_pinFocus.requestFocus();
setState(() {
_attemptsRemaining = attemptsRemaining;
_pinIsWrong = true;
});
failure: (reason) {
reason.maybeWhen(
invalidPin: (attemptsRemaining) {
_pinController.selection = TextSelection(
baseOffset: 0, extentOffset: _pinController.text.length);
_pinFocus.requestFocus();
setState(() {
_attemptsRemaining = attemptsRemaining;
_pinIsWrong = true;
});
},
orElse: () {},
);
},
);
} on CancellationException catch (_) {