def test_modhex_encode(self): self.assertEqual("", modhex_encode(b"")) self.assertEqual("dteffuje", modhex_encode(b"\x2d\x34\x4e\x83")) self.assertEqual( "hknhfjbrjnlnldnhcujvddbikngjrtgh", modhex_encode(b"\x69\xb6\x48\x1c\x8b\xab\xa2\xb6" b"\x0e\x8f\x22\x17\x9b\x58\xcd\x56"), )
def prepare_upload_key( key, public_id, private_id, serial=None, user_agent="python-yubikey-manager/" + __version__, ): modhex_public_id = modhex_encode(public_id) data = { "aes_key": key.hex(), "serial": serial or 0, "public_id": modhex_public_id, "private_id": private_id.hex(), } httpconn = HTTPSConnection(UPLOAD_HOST, timeout=1) # nosec try: httpconn.request( "POST", UPLOAD_PATH, body=json.dumps(data, indent=False, sort_keys=True).encode("utf-8"), headers={"Content-Type": "application/json", "User-Agent": user_agent}, ) except Exception as e: logger.error("Failed to connect to %s", UPLOAD_HOST, exc_info=e) raise PrepareUploadFailed(None, None, [PrepareUploadError.CONNECTION_FAILED]) resp = httpconn.getresponse() if resp.status == 200: url = json.loads(resp.read().decode("utf-8"))["finish_url"] return url else: resp_body = resp.read() logger.debug("Upload failed with status %d: %s", resp.status, resp_body) if resp.status == 404: raise PrepareUploadFailed( resp.status, resp_body, [PrepareUploadError.NOT_FOUND] ) elif resp.status == 503: raise PrepareUploadFailed( resp.status, resp_body, [PrepareUploadError.SERVICE_UNAVAILABLE] ) else: try: errors = json.loads(resp_body.decode("utf-8")).get("errors") except Exception: errors = [] raise PrepareUploadFailed(resp.status, resp_body, errors)
def serial_modhex(self): with self._open_device([OtpConnection]) as conn: session = YubiOtpSession(conn) return modhex_encode(b'\xff\x00' + struct.pack(b'>I', session.get_serial()))
def yubiotp( ctx, slot, public_id, private_id, key, no_enter, force, serial_public_id, generate_private_id, generate_key, upload, ): """ Program a Yubico OTP credential. """ info = ctx.obj["info"] session = ctx.obj["session"] if public_id and serial_public_id: ctx.fail( "Invalid options: --public-id conflicts with --serial-public-id.") if private_id and generate_private_id: ctx.fail( "Invalid options: --private-id conflicts with --generate-public-id." ) if upload and force: ctx.fail("Invalid options: --upload conflicts with --force.") if key and generate_key: ctx.fail("Invalid options: --key conflicts with --generate-key.") if not public_id: if serial_public_id: serial = session.get_serial() if serial is None: cli_fail("Serial number not set, public ID must be provided") public_id = modhex_encode(b"\xff\x00" + struct.pack(b">I", serial)) click.echo(f"Using YubiKey serial as public ID: {public_id}") elif force: ctx.fail("Public ID not given. Please remove the --force flag, or " "add the --serial-public-id flag or --public-id option.") else: public_id = click_prompt("Enter public ID") try: public_id = modhex_decode(public_id) except KeyError: ctx.fail("Invalid public ID, must be modhex.") if not private_id: if generate_private_id: private_id = os.urandom(6) click.echo( f"Using a randomly generated private ID: {private_id.hex()}") elif force: ctx.fail( "Private ID not given. Please remove the --force flag, or " "add the --generate-private-id flag or --private-id option.") else: private_id = click_prompt("Enter private ID") private_id = bytes.fromhex(private_id) if not key: if generate_key: key = os.urandom(16) click.echo(f"Using a randomly generated secret key: {key.hex()}") elif force: ctx.fail( "Secret key not given. Please remove the --force flag, or " "add the --generate-key flag or --key option.") else: key = click_prompt("Enter secret key") key = bytes.fromhex(key) if not upload and not force: upload = click.confirm("Upload credential to YubiCloud?", abort=False, err=True) if upload: try: upload_url = prepare_upload_key( key, public_id, private_id, serial=info.serial, user_agent="ykman/" + __version__, ) click.echo("Upload to YubiCloud initiated successfully.") except PrepareUploadFailed as e: error_msg = "\n".join(e.messages()) cli_fail("Upload to YubiCloud failed.\n" + error_msg) force or click.confirm( f"Program a YubiOTP credential in slot {slot}?", abort=True, err=True) try: session.put_configuration( slot, YubiOtpSlotConfiguration(public_id, private_id, key).append_cr(not no_enter), ctx.obj["access_code"], ctx.obj["access_code"], ) except CommandError as e: _failed_to_write_msg(ctx, e) if upload: click.echo("Opening upload form in browser: " + upload_url) webbrowser.open_new_tab(upload_url)