Skip to content

Commit

Permalink
Merge PR Yubico#1154.
Browse files Browse the repository at this point in the history
  • Loading branch information
dainnilsson committed Aug 25, 2023
2 parents b5b3773 + 1be66e1 commit ad645a4
Show file tree
Hide file tree
Showing 21 changed files with 619 additions and 251 deletions.
3 changes: 3 additions & 0 deletions helper/helper/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from yubikit.core import InvalidPinError
from functools import partial

import logging
Expand Down Expand Up @@ -123,6 +124,8 @@ def __call__(self, action, target, params, event, signal, traversed=None):
except ChildResetException as e:
self._close_child()
raise StateResetException(e.message, traversed)
except InvalidPinError:
raise # Prevent catching this as a ValueError below
except ValueError as e:
raise InvalidParametersException(e)
raise NoSuchActionException(action)
Expand Down
94 changes: 59 additions & 35 deletions helper/helper/piv.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,12 @@
TimeoutException,
AuthRequiredException,
)
from yubikit.core import NotSupportedError, BadResponseError
from yubikit.core import NotSupportedError, BadResponseError, InvalidPinError
from yubikit.core.smartcard import ApduError, SW
from yubikit.piv import (
PivSession,
OBJECT_ID,
MANAGEMENT_KEY_TYPE,
InvalidPinError,
SLOT,
require_version,
KEY_TYPE,
Expand All @@ -43,6 +42,7 @@
generate_self_signed_certificate,
generate_csr,
generate_chuid,
parse_rfc4514_string,
)
from ykman.util import (
parse_certificates,
Expand Down Expand Up @@ -234,6 +234,34 @@ def reset(self, params, event, signal):
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)
certificate = _choose_cert(certs)

return dict(
status=True,
password=password is not None,
key_type=KEY_TYPE.from_public_key(private_key.public_key())
if private_key
else None,
cert_info=_get_cert_info(certificate),
)
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))
Expand All @@ -255,6 +283,29 @@ def _parse_file(data, password=None):
return private_key, certs


def _choose_cert(certs):
if certs:
if len(certs) > 1:
leafs = get_leaf_certificates(certs)
return leafs[0]
else:
return certs[0]
return None


def _get_cert_info(cert):
if cert is None:
return None
return dict(
subject=cert.subject.rfc4514_string(),
issuer=cert.issuer.rfc4514_string(),
serial=hex(cert.serial_number)[2:],
not_valid_before=cert.not_valid_before.isoformat(),
not_valid_after=cert.not_valid_after.isoformat(),
fingerprint=cert.fingerprint(hashes.SHA256()),
)


class SlotsNode(RpcNode):
def __init__(self, session):
super().__init__()
Expand Down Expand Up @@ -290,16 +341,7 @@ def list_children(self):
slot=int(slot),
name=slot.name,
has_key=metadata is not None if self._has_metadata else None,
cert_info=dict(
subject=cert.subject.rfc4514_string(),
issuer=cert.issuer.rfc4514_string(),
serial=hex(cert.serial_number)[2:],
not_valid_before=cert.not_valid_before.isoformat(),
not_valid_after=cert.not_valid_after.isoformat(),
fingerprint=cert.fingerprint(hashes.SHA256()),
)
if cert
else None,
cert_info=_get_cert_info(cert),
)
for slot, (metadata, cert) in self._slots.items()
}
Expand All @@ -311,22 +353,6 @@ def create_child(self, name):
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):
Expand Down Expand Up @@ -382,12 +408,8 @@ def import_file(self, params, event, signal):
except (ApduError, BadResponseError):
pass

if certs:
if len(certs) > 1:
leafs = get_leaf_certificates(certs)
certificate = leafs[0]
else:
certificate = certs[0]
certificate = _choose_cert(certs)
if certificate:
self.session.put_certificate(self.slot, certificate)
self.session.put_object(OBJECT_ID.CHUID, generate_chuid())
self.certificate = certificate
Expand All @@ -414,7 +436,9 @@ def generate(self, params, event, signal):
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
)
Expand Down
12 changes: 9 additions & 3 deletions lib/desktop/piv/state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
Expand All @@ -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,
Expand Down
27 changes: 20 additions & 7 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@
"s_unlock": "Unlock",
"s_calculate": "Calculate",
"s_import": "Import",
"s_overwrite": "Overwrite",
"s_label": "Label",
"s_name": "Name",
"s_usb": "USB",
"s_nfc": "NFC",
"s_options": "Options",
"s_show_window": "Show window",
"s_hide_window": "Hide window",
"q_rename_target": "Rename {label}?",
Expand All @@ -48,14 +50,9 @@
"item": {}
}
},
"s_definition": "{item}:",
"@s_definition" : {
"placeholders": {
"item": {}
}
},

"s_about": "About",
"s_algorithm": "Algorithm",
"s_appearance": "Appearance",
"s_authenticator": "Authenticator",
"s_actions": "Actions",
Expand Down Expand Up @@ -281,6 +278,8 @@
"p_change_management_key_desc": "Change your management key. You can optionally choose to allow the PIN to be used instead of the management key.",
"l_management_key_changed": "Management key changed",
"l_default_key_used": "Default management key used",
"s_generate_random": "Generate random",
"s_use_default": "Use default",
"l_warning_default_key": "Warning: Default key used",
"s_protect_key": "Protect with PIN",
"l_pin_protected_key": "PIN can be used instead",
Expand Down Expand Up @@ -457,12 +456,26 @@
},
"l_certificate_deleted": "Certificate deleted",
"p_password_protected_file": "The selected file is password protected. Enter the password to proceed.",
"p_import_items_desc": "The following items will be imported into PIV slot {slot}.",
"p_import_items_desc": "The following item(s) will be imported into PIV slot {slot}.",
"@p_import_items_desc" : {
"placeholders": {
"slot": {}
}
},
"p_subject_desc": "A distinguished name (DN) formatted in accordance to the RFC 4514 specification.",
"l_rfc4514_invalid": "Invalid RFC 4514 format",
"rfc4514_examples": "Examples:\nCN=Example Name\nCN=jsmith,DC=example,DC=net",
"p_cert_options_desc": "Key algorithm to use, output format, and expiration date (certificate only).",
"s_overwrite_slot": "Overwrite slot",
"p_overwrite_slot_desc": "This will permanently overwrite existing content in slot {slot}.",
"@p_overwrite_slot_desc" : {
"placeholders": {
"slot": {}
}
},
"l_overwrite_cert": "The certificate will be overwritten",
"l_overwrite_key": "The private key will be overwritten",
"l_overwrite_key_maybe": "Any existing private key in the slot will be overwritten",

"@_piv_slots": {},
"s_slot_display_name": "{name} ({hexid})",
Expand Down
4 changes: 2 additions & 2 deletions lib/piv/models.dart
Original file line number Diff line number Diff line change
Expand Up @@ -266,8 +266,8 @@ class PivSlot with _$PivSlot {
class PivExamineResult with _$PivExamineResult {
factory PivExamineResult.result({
required bool password,
required bool privateKey,
required int certificates,
required KeyType? keyType,
required CertInfo? certInfo,
}) = _ExamineResult;
factory PivExamineResult.invalidPassword() = _InvalidPassword;

Expand Down
Loading

0 comments on commit ad645a4

Please sign in to comment.