def refresh_piv(self): with self._open_device([SmartCardConnection]) as conn: session = PivSession(conn) pivman = get_pivman_data(session) try: key_type = session.get_management_key_metadata().key_type except NotSupportedError: key_type = MANAGEMENT_KEY_TYPE.TDES return success({ 'piv_data': { 'certs': self._piv_list_certificates(session), 'has_derived_key': pivman.has_derived_key, 'has_protected_key': pivman.has_protected_key, 'has_stored_key': pivman.has_stored_key, 'pin_tries': session.get_pin_attempts(), 'puk_blocked': pivman.puk_blocked, 'supported_algorithms': _supported_algorithms( self._dev_info['version'].split('.')), 'key_type': key_type, }, })
def piv_change_mgm_key(self, pin, current_key_hex, new_key_hex, key_type, store_on_device=False): with self._open_device([SmartCardConnection]) as conn: session = PivSession(conn) pivman = get_pivman_data(session) if pivman.has_protected_key or store_on_device: pin_failed = self._piv_verify_pin( session, pin=pin) if pin_failed: return pin_failed with PromptTimeout(): auth_failed = self._piv_ensure_authenticated( session, pin=pin, mgm_key_hex=current_key_hex) if auth_failed: return auth_failed try: new_key = a2b_hex(new_key_hex) if new_key_hex else None except Exception as e: logger.debug('Failed to parse new management key', exc_info=e) return failure('new_mgm_key_bad_hex') if new_key is not None and len(new_key) != MANAGEMENT_KEY_TYPE(key_type).key_len: logger.debug('Wrong length for new management key: %d', len(new_key)) return failure('new_mgm_key_bad_length') pivman_set_mgm_key( session, new_key, MANAGEMENT_KEY_TYPE(key_type), touch=False, store_on_device=store_on_device ) return success()
def sign(self, message): if 'mplock' in globals(): mplock.acquire() try: conn = self._connect() with conn: session = PivSession(conn) if self.pin: try: session.verify_pin(self.pin) except InvalidPinError as err: controlflow.system_error_exit(7, f'YubiKey - {err}') try: signed = session.sign(slot=self.slot, key_type=self.key_type, message=message, hash_algorithm=hashes.SHA256(), padding=padding.PKCS1v15()) except ApduError as err: controlflow.system_error_exit(8, f'YubiKey - {err}') except ValueError as err: controlflow.system_error_exit(9, f'YubiKey - {err}') if 'mplock' in globals(): mplock.release() return signed
def piv_change_pin(self, old_pin, new_pin): with self._open_device([SmartCardConnection]) as conn: session = PivSession(conn) try: session.change_pin(old_pin, new_pin) logger.debug('PIN change successful!') return success() except InvalidPinError as e: attempts = e.attempts_remaining if attempts: logger.debug("Failed to change PIN, %d tries left", attempts, exc_info=e) return failure('wrong_pin', {'tries_left': attempts}) else: logger.debug("PIN is blocked.", exc_info=e) return failure('pin_blocked') except ApduError as e: if e.sw == SW.INCORRECT_PARAMETERS: return failure('incorrect_parameters') tries_left = e.attempts_remaining logger.debug('PIN change failed. %s tries left.', tries_left, exc_info=e) return { 'success': False, 'tries_left': tries_left, }
def piv(ctx): """ Manage PIV Application. Examples: \b Generate an ECC P-256 private key and a self-signed certificate in slot 9a: $ ykman piv generate-key --algorithm ECCP256 9a pubkey.pem $ ykman piv generate-certificate --subject "yubico" 9a pubkey.pem \b Change the PIN from 123456 to 654321: $ ykman piv change-pin --pin 123456 --new-pin 654321 \b Reset all PIV data and restore default settings: $ ykman piv reset """ try: app = PivSession(ctx.obj["conn"]) ctx.obj["controller"] = PivController(app) except ApduError as e: if e.sw == SW.FILE_NOT_FOUND: ctx.fail("The PIV application can't be found on this YubiKey.") raise
def piv_info(conn): try: piv = PivSession(conn) return ["\tPIV"] + [ f"\t\t{ln}" for ln in get_piv_info(piv).splitlines() if ln ] except Exception as e: return [f"\tPIV not accessible {e}"]
def piv_export_certificate(self, slot, file_url): file_path = self._get_file_path(file_url) with self._open_device([SmartCardConnection]) as conn: session = PivSession(conn) cert = session.get_certificate(SLOT[slot]) with open(file_path, 'wb') as file: file.write( cert.public_bytes(encoding=serialization.Encoding.PEM)) return success()
def piv_unblock_pin(self, puk, new_pin): with self._open_device([SmartCardConnection]) as conn: session = PivSession(conn) try: session.unblock_pin(puk, new_pin) return success() except InvalidPinError as e: attempts = e.attempts_remaining if attempts: logger.debug("Failed to unblock PIN, %d tries left", attempts, exc_info=e) return failure('wrong_puk', {'tries_left': attempts}) else: logger.debug("PUK is blocked.", exc_info=e) return failure('puk_blocked')
def piv_import_file(self, slot, file_url, password=None, pin=None, mgm_key=None): is_cert = False is_private_key = False file_path = self._get_file_path(file_url) if password: password = password.encode() with open(file_path, 'r+b') as file: data = file.read() try: certs = parse_certificates(data, password) is_cert = True except (ValueError, TypeError): pass try: private_key = parse_private_key(data, password) is_private_key = True except (ValueError, TypeError, InvalidPasswordError): pass if not (is_cert or is_private_key): return failure('failed_parsing') with self._open_device([SmartCardConnection]) as conn: session = PivSession(conn) with PromptTimeout(): auth_failed = self._piv_ensure_authenticated( session, pin, mgm_key) if auth_failed: return auth_failed if is_private_key: session.put_key(SLOT[slot], private_key) if is_cert: if len(certs) > 1: leafs = get_leaf_certificates(certs) cert_to_import = leafs[0] else: cert_to_import = certs[0] session.put_certificate(SLOT[slot], cert_to_import) session.put_object(OBJECT_ID.CHUID, generate_chuid()) return success({ 'imported_cert': is_cert, 'imported_key': is_private_key })
def piv_delete_certificate(self, slot_name, pin=None, mgm_key_hex=None): logger.debug('piv_delete_certificate %s', slot_name) with self._open_device([SmartCardConnection]) as conn: session = PivSession(conn) with PromptTimeout(): auth_failed = self._piv_ensure_authenticated( session, pin=pin, mgm_key_hex=mgm_key_hex) if auth_failed: return auth_failed try: session.delete_certificate(SLOT[slot_name]) session.put_object(OBJECT_ID.CHUID, generate_chuid()) return success() except ApduError as e: if e.sw == SW.SECURITY_CONDITION_NOT_SATISFIED: logger.debug("Wrong management key", exc_info=e) return failure('wrong_mgm_key')
def get_certificate(self): try: conn = self._connect() with conn: session = PivSession(conn) if self.pin: try: session.verify_pin(self.pin) except InvalidPinError as err: controlflow.system_error_exit(7, f'YubiKey - {err}') try: cert = session.get_certificate(self.slot) except ApduError as err: controlflow.system_error_exit(9, f'YubiKey - {err}') cert_pem = cert.public_bytes(serialization.Encoding.PEM).decode() publicKeyData = b64encode(cert_pem.encode()) if isinstance(publicKeyData, bytes): publicKeyData = publicKeyData.decode() return publicKeyData except ValueError as err: controlflow.system_error_exit(9, f'YubiKey - {err}')
def reset_piv(self): '''Resets YubiKey PIV app and generates new key for GAM to use.''' reply = str( input( 'This will wipe all PIV keys and configuration from your YubiKey. Are you sure? (y/N) ' ).lower().strip()) if reply != 'y': sys.exit(1) try: conn = self._connect() with conn: piv = PivSession(conn) piv.reset() rnd = SystemRandom() pin_puk_chars = string.ascii_letters + string.digits + string.punctuation new_puk = ''.join(rnd.choice(pin_puk_chars) for _ in range(8)) new_pin = ''.join(rnd.choice(pin_puk_chars) for _ in range(8)) piv.change_puk('12345678', new_puk) piv.change_pin('123456', new_pin) print(f'PIN set to: {new_pin}') piv.authenticate(MANAGEMENT_KEY_TYPE.TDES, DEFAULT_MANAGEMENT_KEY) piv.verify_pin(new_pin) print('YubiKey is generating a non-exportable private key...') pubkey = piv.generate_key(SLOT.AUTHENTICATION, KEY_TYPE.RSA2048, PIN_POLICY.ALWAYS, TOUCH_POLICY.NEVER) now = datetime.datetime.utcnow() valid_to = now + datetime.timedelta(days=36500) subject = 'CN=GAM Created Key' piv.authenticate(MANAGEMENT_KEY_TYPE.TDES, DEFAULT_MANAGEMENT_KEY) piv.verify_pin(new_pin) cert = generate_self_signed_certificate( piv, SLOT.AUTHENTICATION, pubkey, subject, now, valid_to) piv.put_certificate(SLOT.AUTHENTICATION, cert) piv.put_object(OBJECT_ID.CHUID, generate_chuid()) except ValueError as err: controlflow.system_error_exit(8, f'YubiKey - {err}')
def piv(ctx): """ Manage the PIV application. Examples: \b Generate an ECC P-256 private key and a self-signed certificate in slot 9a: $ ykman piv keys generate --algorithm ECCP256 9a pubkey.pem $ ykman piv certificates generate --subject "yubico" 9a pubkey.pem \b Change the PIN from 123456 to 654321: $ ykman piv access change-pin --pin 123456 --new-pin 654321 \b Reset all PIV data and restore default settings: $ ykman piv reset """ session = PivSession(ctx.obj["conn"]) ctx.obj["session"] = session ctx.obj["pivman_data"] = get_pivman_data(session)
def setUp(self): self.conn = open_device()[0] self.controller = PivController(PivSession(self.conn))
def session(ccid_connection): piv = PivSession(ccid_connection) piv.reset() yield piv reset_state(piv)
def setUpClass(cls): with open_device()[0] as conn: controller = PivController(PivSession(conn)) controller.reset()
def piv_reset(self): with self._open_device([SmartCardConnection]) as conn: session = PivSession(conn) session.reset() return success()
def reconnect(self): self.conn.close() self.conn = open_device()[0] self.controller = PivController(PivSession(self.conn))
def piv_generate_certificate( self, slot_name, algorithm, subject, expiration_date, self_sign=True, csr_file_url=None, pin=None, mgm_key_hex=None): logger.debug('slot_name=%s algorithm=%s common_name=%s ' 'expiration_date=%s self_sign=%s csr_file_url=%s', slot_name, algorithm, subject, expiration_date, self_sign, csr_file_url) if csr_file_url: file_path = self._get_file_path(csr_file_url) with self._open_device([SmartCardConnection]) as conn: session = PivSession(conn) with PromptTimeout(): auth_failed = self._piv_ensure_authenticated( session, pin=pin, mgm_key_hex=mgm_key_hex) if auth_failed: return auth_failed pin_failed = self._piv_verify_pin(session, pin) if pin_failed: return pin_failed if self_sign: now = datetime.datetime.utcnow() try: year = int(expiration_date[0:4]) month = int(expiration_date[(4+1):(4+1+2)]) day = int(expiration_date[(4+1+2+1):(4+1+2+1+2)]) valid_to = datetime.datetime(year, month, day) except ValueError as e: logger.debug( 'Failed to parse date: ' + expiration_date, exc_info=e) return failure( 'invalid_iso8601_date', {'date': expiration_date}) try: public_key = session.generate_key( SLOT[slot_name], KEY_TYPE[algorithm]) except ApduError as e: if e.sw == SW.SECURITY_CONDITION_NOT_SATISFIED: logger.debug("Wrong management key", exc_info=e) return failure('wrong_mgm_key') pin_failed = self._piv_verify_pin(session, pin) if pin_failed: return pin_failed if "=" not in subject: # Old style, common name only. subject = "CN=" + subject try: if self_sign: cert = generate_self_signed_certificate(session, SLOT[slot_name], public_key, subject, now, valid_to) session.put_certificate(SLOT[slot_name], cert) session.put_object(OBJECT_ID.CHUID, generate_chuid()) else: csr = generate_csr(session, SLOT[slot_name], public_key, subject) with open(file_path, 'w+b') as csr_file: csr_file.write(csr.public_bytes( encoding=serialization.Encoding.PEM)) except ApduError as e: if e.sw == SW.SECURITY_CONDITION_NOT_SATISFIED: return failure('pin_required') raise return success()