예제 #1
0
 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"),
     )
예제 #2
0
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)
예제 #3
0
 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()))
예제 #4
0
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)