PIV: Validate subject.

This commit is contained in:
Dain Nilsson 2023-08-22 11:06:37 +02:00
parent 33eccbb53c
commit cd006085a6
No known key found for this signature in database
GPG Key ID: F04367096FBA95E8
5 changed files with 79 additions and 44 deletions

View File

@ -42,6 +42,7 @@ from ykman.piv import (
generate_self_signed_certificate,
generate_csr,
generate_chuid,
parse_rfc4514_string,
)
from ykman.util import (
parse_certificates,
@ -233,6 +234,30 @@ class PivNode(RpcNode):
def slots(self):
return SlotsNode(self.session)
@action(closes_child=False)
def examine_file(self, params, event, signal):
data = bytes.fromhex(params.pop("data"))
password = params.pop("password", None)
try:
private_key, certs = _parse_file(data, password)
return dict(
status=True,
password=password is not None,
private_key=bool(private_key),
certificates=len(certs),
)
except InvalidPasswordError:
logger.debug("Invalid or missing password", exc_info=True)
return dict(status=False)
@action(closes_child=False)
def validate_rfc4514(self, params, event, signal):
try:
parse_rfc4514_string(params.pop("data"))
return dict(status=True)
except ValueError:
return dict(status=False)
def _slot_for(name):
return SLOT(int(name, base=16))
@ -310,22 +335,6 @@ class SlotsNode(RpcNode):
return SlotNode(self.session, slot, metadata, certificate, self.refresh)
return super().create_child(name)
@action
def examine_file(self, params, event, signal):
data = bytes.fromhex(params.pop("data"))
password = params.pop("password", None)
try:
private_key, certs = _parse_file(data, password)
return dict(
status=True,
password=password is not None,
private_key=bool(private_key),
certificates=len(certs),
)
except InvalidPasswordError:
logger.debug("Invalid or missing password", exc_info=True)
return dict(status=False)
class SlotNode(RpcNode):
def __init__(self, session, slot, metadata, certificate, refresh):
@ -413,7 +422,9 @@ class SlotNode(RpcNode):
pin_policy = PIN_POLICY(params.pop("pin_policy", PIN_POLICY.DEFAULT))
touch_policy = TOUCH_POLICY(params.pop("touch_policy", TOUCH_POLICY.DEFAULT))
subject = params.pop("subject")
generate_type = GENERATE_TYPE(params.pop("generate_type", GENERATE_TYPE.CERTIFICATE))
generate_type = GENERATE_TYPE(
params.pop("generate_type", GENERATE_TYPE.CERTIFICATE)
)
public_key = self.session.generate_key(
self.slot, key_type, pin_policy, touch_policy
)

View File

@ -380,9 +380,7 @@ class _DesktopPivSlotsNotifier extends PivSlotsNotifier {
@override
Future<PivExamineResult> examine(String data, {String? password}) async {
final result = await _session.command('examine_file', target: [
'slots',
], params: {
final result = await _session.command('examine_file', params: {
'data': data,
'password': password,
});
@ -394,6 +392,14 @@ class _DesktopPivSlotsNotifier extends PivSlotsNotifier {
}
}
@override
Future<bool> validateRfc4514(String value) async {
final result = await _session.command('validate_rfc4514', params: {
'data': value,
});
return result['status'];
}
@override
Future<PivImportResult> import(SlotId slot, String data,
{String? password,

View File

@ -463,6 +463,7 @@
"slot": {}
}
},
"l_invalid_rfc4514": "Invalid RFC4514 string",
"@_piv_slots": {},
"s_slot_display_name": "{name} ({hexid})",

View File

@ -50,6 +50,7 @@ final pivSlotsProvider = AsyncNotifierProvider.autoDispose
abstract class PivSlotsNotifier
extends AutoDisposeFamilyAsyncNotifier<List<PivSlot>, DevicePath> {
Future<PivExamineResult> examine(String data, {String? password});
Future<bool> validateRfc4514(String value);
Future<(SlotMetadata?, String?)> read(SlotId slot);
Future<PivGenerateResult> generate(
SlotId slot,

View File

@ -42,6 +42,7 @@ class GenerateKeyDialog extends ConsumerStatefulWidget {
class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
String _subject = '';
bool _invalidSubject = true;
GenerateType _generateType = defaultGenerateType;
KeyType _keyType = defaultKeyType;
late DateTime _validFrom;
@ -71,36 +72,47 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
actions: [
TextButton(
key: keys.saveButton,
onPressed: _generating || _subject.isEmpty
onPressed: _generating || _invalidSubject
? null
: () async {
setState(() {
_generating = true;
});
Function()? close;
final pivNotifier =
ref.read(pivSlotsProvider(widget.devicePath).notifier);
final withContext = ref.read(withContextProvider);
if (!await pivNotifier.validateRfc4514(_subject)) {
setState(() {
_generating = false;
});
_invalidSubject = true;
return;
}
void Function()? close;
final PivGenerateResult result;
try {
close = showMessage(
context,
l10n.l_generating_private_key,
duration: const Duration(seconds: 30),
close = await withContext<void Function()>(
(context) async => showMessage(
context,
l10n.l_generating_private_key,
duration: const Duration(seconds: 30),
));
result = await pivNotifier.generate(
widget.pivSlot.slot,
_keyType,
parameters: switch (_generateType) {
GenerateType.certificate =>
PivGenerateParameters.certificate(
subject: _subject,
validFrom: _validFrom,
validTo: _validTo),
GenerateType.csr =>
PivGenerateParameters.csr(subject: _subject),
},
);
result = await ref
.read(pivSlotsProvider(widget.devicePath).notifier)
.generate(
widget.pivSlot.slot,
_keyType,
parameters: switch (_generateType) {
GenerateType.certificate =>
PivGenerateParameters.certificate(
subject: _subject,
validFrom: _validFrom,
validTo: _validTo),
GenerateType.csr =>
PivGenerateParameters.csr(subject: _subject),
},
);
} finally {
close?.call();
}
@ -127,17 +139,21 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
autofocus: true,
key: keys.subjectField,
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: l10n.s_subject,
),
border: const OutlineInputBorder(),
labelText: l10n.s_subject,
errorText: _subject.isNotEmpty && _invalidSubject
? l10n.l_invalid_rfc4514
: null),
textInputAction: TextInputAction.next,
enabled: !_generating,
onChanged: (value) {
setState(() {
if (value.isEmpty) {
_subject = '';
_invalidSubject = true;
} else {
_subject = value.contains('=') ? value : 'CN=$value';
_invalidSubject = false;
}
});
},