mirror of
https://github.com/scito/extract_otp_secret_keys.git
synced 2025-12-12 09:36:35 +01:00
refactor cv2 window and logging
- increase font and cut if too long - refactor logging - extract key handling - refactor big methods
This commit is contained in:
parent
fe3e371897
commit
77e23b4ae4
11 changed files with 911 additions and 266 deletions
|
|
@ -54,7 +54,7 @@ import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import urllib.parse as urlparse
|
import urllib.parse as urlparse
|
||||||
from enum import Enum
|
from enum import Enum, IntEnum
|
||||||
from typing import Any, List, Optional, TextIO, Tuple, Union
|
from typing import Any, List, Optional, TextIO, Tuple, Union
|
||||||
|
|
||||||
# workaround for PYTHON <= 3.7: compatibility
|
# workaround for PYTHON <= 3.7: compatibility
|
||||||
|
|
@ -90,16 +90,26 @@ Exception: {e}""")
|
||||||
|
|
||||||
# CV2 camera capture constants
|
# CV2 camera capture constants
|
||||||
FONT: Final[int] = cv2.FONT_HERSHEY_PLAIN
|
FONT: Final[int] = cv2.FONT_HERSHEY_PLAIN
|
||||||
FONT_SCALE: Final[int] = 1
|
FONT_SCALE: Final[float] = 1.3
|
||||||
FONT_THICKNESS: Final[int] = 1
|
FONT_THICKNESS: Final[int] = 1
|
||||||
FONT_LINE_STYLE: Final[int] = cv2.LINE_AA
|
FONT_LINE_STYLE: Final[int] = cv2.LINE_AA
|
||||||
|
FONT_COLOR: Final[ColorBGR] = (255, 0, 0)
|
||||||
BOX_THICKNESS: Final[int] = 5
|
BOX_THICKNESS: Final[int] = 5
|
||||||
# workaround for PYTHON <= 3.7: must use () for assignments
|
# workaround for PYTHON <= 3.7: must use () for assignments
|
||||||
START_POS_TEXT: Final[Point] = (5, 20)
|
WINDOW_X: Final[int] = 0
|
||||||
|
WINDOW_Y: Final[int] = 1
|
||||||
|
WINDOW_WIDTH: Final[int] = 2
|
||||||
|
WINDOW_HEIGHT: Final[int] = 3
|
||||||
|
TEXT_WIDTH: Final[int] = 0
|
||||||
|
TEXT_HEIGHT: Final[int] = 1
|
||||||
|
BORDER: Final[int] = 5
|
||||||
|
START_Y: Final[int] = 20
|
||||||
|
START_POS_TEXT: Final[Point] = (BORDER, START_Y)
|
||||||
NORMAL_COLOR: Final[ColorBGR] = (255, 0, 255)
|
NORMAL_COLOR: Final[ColorBGR] = (255, 0, 255)
|
||||||
SUCCESS_COLOR: Final[ColorBGR] = (0, 255, 0)
|
SUCCESS_COLOR: Final[ColorBGR] = (0, 255, 0)
|
||||||
FAILURE_COLOR: Final[ColorBGR] = (0, 0, 255)
|
FAILURE_COLOR: Final[ColorBGR] = (0, 0, 255)
|
||||||
FONT_DY: Final[int] = cv2.getTextSize("M", FONT, FONT_SCALE, FONT_THICKNESS)[0][1] + 5
|
CHAR_DX: Final[int] = (lambda text: cv2.getTextSize(text, FONT, FONT_SCALE, FONT_THICKNESS)[0][TEXT_WIDTH] // len(text))("28 QR codes capturedMMM")
|
||||||
|
FONT_DY: Final[int] = cv2.getTextSize("M", FONT, FONT_SCALE, FONT_THICKNESS)[0][TEXT_HEIGHT] + 5
|
||||||
WINDOW_NAME: Final[str] = "Extract OTP Secrets: Capture QR Codes from Camera"
|
WINDOW_NAME: Final[str] = "Extract OTP Secrets: Capture QR Codes from Camera"
|
||||||
|
|
||||||
TextPosition = Enum('TextPosition', ['LEFT', 'RIGHT'])
|
TextPosition = Enum('TextPosition', ['LEFT', 'RIGHT'])
|
||||||
|
|
@ -121,13 +131,14 @@ Otps = List[Otp]
|
||||||
OtpUrls = List[OtpUrl]
|
OtpUrls = List[OtpUrl]
|
||||||
|
|
||||||
QRMode = Enum('QRMode', ['ZBAR', 'QREADER', 'QREADER_DEEP', 'CV2', 'CV2_WECHAT'], start=0)
|
QRMode = Enum('QRMode', ['ZBAR', 'QREADER', 'QREADER_DEEP', 'CV2', 'CV2_WECHAT'], start=0)
|
||||||
|
LogLevel = IntEnum('LogLevel', ['QUIET', 'NORMAL', 'VERBOSE', 'MORE_VERBOSE', 'DEBUG'], start=-1)
|
||||||
|
|
||||||
|
|
||||||
# Constants
|
# Constants
|
||||||
CAMERA: Final[str] = 'camera'
|
CAMERA: Final[str] = 'camera'
|
||||||
|
|
||||||
# Global variable declaration
|
# Global variable declaration
|
||||||
verbose: int = 0
|
verbose: IntEnum = LogLevel.NORMAL
|
||||||
quiet: bool = False
|
quiet: bool = False
|
||||||
colored: bool = True
|
colored: bool = True
|
||||||
|
|
||||||
|
|
@ -160,9 +171,9 @@ def main(sys_args: list[str]) -> None:
|
||||||
|
|
||||||
def parse_args(sys_args: list[str]) -> Args:
|
def parse_args(sys_args: list[str]) -> Args:
|
||||||
global verbose, quiet, colored
|
global verbose, quiet, colored
|
||||||
description_text = "Extracts one time password (OTP) / two-factor authentication (2FA) secrets from export QR codes, e.g. from Google Authenticator app."
|
description_text = "Extracts one time password (OTP) secrets from export QR codes from two-factor authentication (2FA) apps"
|
||||||
if qreader_available:
|
if qreader_available:
|
||||||
description_text += "\nIf no infiles are provided, the QR codes a GUI window starts and QR codes can interactively be captured from the system camera."
|
description_text += "\nIf no infiles are provided, a GUI window starts and QR codes are captured from the camera."
|
||||||
example_text = """examples:
|
example_text = """examples:
|
||||||
python extract_otp_secrets.py
|
python extract_otp_secrets.py
|
||||||
python extract_otp_secrets.py example_*.txt
|
python extract_otp_secrets.py example_*.txt
|
||||||
|
|
@ -191,12 +202,12 @@ b) image file containing a QR code or = for stdin for an image containing a QR c
|
||||||
if args.csv == '-' or args.json == '-' or args.keepass == '-':
|
if args.csv == '-' or args.json == '-' or args.keepass == '-':
|
||||||
args.quiet = args.q = True
|
args.quiet = args.q = True
|
||||||
|
|
||||||
verbose = args.verbose if args.verbose else 0
|
verbose = args.verbose if args.verbose else LogLevel.NORMAL
|
||||||
quiet = True if args.quiet else False
|
quiet = True if args.quiet else False
|
||||||
colored = not args.no_color
|
colored = not args.no_color
|
||||||
if verbose: print(f"QReader installed: {qreader_available}")
|
if verbose: print(f"QReader installed: {qreader_available}")
|
||||||
if qreader_available:
|
if qreader_available:
|
||||||
if verbose > 1: print(f"CV2 version: {cv2.__version__}")
|
if verbose >= LogLevel.VERBOSE: print(f"CV2 version: {cv2.__version__}")
|
||||||
if verbose: print(f"QR reading mode: {args.qr}\n")
|
if verbose: print(f"QR reading mode: {args.qr}\n")
|
||||||
|
|
||||||
return args
|
return args
|
||||||
|
|
@ -228,14 +239,17 @@ def cv2_draw_box(img: Any, raw_pts: Any, color: ColorBGR) -> Any:
|
||||||
|
|
||||||
|
|
||||||
# TODO use cv2 types if available
|
# TODO use cv2 types if available
|
||||||
def cv2_print_text(img: Any, text: str, line_number: int, position: TextPosition, color: ColorBGR) -> None:
|
def cv2_print_text(img: Any, text: str, line_number: int, position: TextPosition, color: ColorBGR, opposite_len: Optional[int] = None) -> None:
|
||||||
|
text_dim, _ = cv2.getTextSize(text, FONT, FONT_SCALE, FONT_THICKNESS)
|
||||||
|
window_dim = cv2.getWindowImageRect(WINDOW_NAME)
|
||||||
|
out_text = text if not opposite_len or (actual_width := text_dim[TEXT_WIDTH] + opposite_len * CHAR_DX + 4 * BORDER) <= window_dim[WINDOW_WIDTH] else text[:(window_dim[WINDOW_WIDTH] - actual_width) // CHAR_DX] + '.'
|
||||||
|
text_dim, _ = cv2.getTextSize(out_text, FONT, FONT_SCALE, FONT_THICKNESS)
|
||||||
if position == TextPosition.LEFT:
|
if position == TextPosition.LEFT:
|
||||||
pos = START_POS_TEXT[0], START_POS_TEXT[1] + line_number * FONT_DY
|
pos = BORDER, START_Y + line_number * FONT_DY
|
||||||
else:
|
else:
|
||||||
window_dim = cv2.getWindowImageRect(WINDOW_NAME)
|
pos = window_dim[WINDOW_WIDTH] - text_dim[TEXT_WIDTH] - BORDER, START_Y + line_number * FONT_DY
|
||||||
pos = window_dim[2] - cv2.getTextSize(text, FONT, FONT_SCALE, FONT_THICKNESS)[0][0] - 5, START_POS_TEXT[1] + line_number * FONT_DY
|
|
||||||
|
|
||||||
cv2.putText(img, text, pos, FONT, FONT_SCALE, color, FONT_THICKNESS, FONT_LINE_STYLE)
|
cv2.putText(img, out_text, pos, FONT, FONT_SCALE, color, FONT_THICKNESS, FONT_LINE_STYLE)
|
||||||
|
|
||||||
|
|
||||||
def extract_otps_from_camera(args: Args) -> Otps:
|
def extract_otps_from_camera(args: Args) -> Otps:
|
||||||
|
|
@ -289,23 +303,16 @@ def extract_otps_from_camera(args: Args) -> Otps:
|
||||||
qr_mode = next_qr_mode(qr_mode)
|
qr_mode = next_qr_mode(qr_mode)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
cv2_print_text(img, f"Mode: {qr_mode.name} (Hit space to change)", 0, TextPosition.LEFT, NORMAL_COLOR)
|
cv2_print_text(img, f"Mode: {qr_mode.name} (Hit space to change)", 0, TextPosition.LEFT, FONT_COLOR, 20)
|
||||||
cv2_print_text(img, "Hit ESC to quit", 1, TextPosition.LEFT, NORMAL_COLOR)
|
cv2_print_text(img, "Hit ESC to quit", 1, TextPosition.LEFT, FONT_COLOR, 17)
|
||||||
|
|
||||||
cv2_print_text(img, f"{len(otp_urls)} QR code{'s'[:len(otp_urls) != 1]} captured", 0, TextPosition.RIGHT, NORMAL_COLOR)
|
cv2_print_text(img, f"{len(otp_urls)} QR code{'s'[:len(otp_urls) != 1]} captured", 0, TextPosition.RIGHT, FONT_COLOR)
|
||||||
cv2_print_text(img, f"{len(otps)} otp{'s'[:len(otps) != 1]} extracted", 1, TextPosition.RIGHT, NORMAL_COLOR)
|
cv2_print_text(img, f"{len(otps)} otp{'s'[:len(otps) != 1]} extracted", 1, TextPosition.RIGHT, FONT_COLOR)
|
||||||
|
|
||||||
cv2.imshow(WINDOW_NAME, img)
|
cv2.imshow(WINDOW_NAME, img)
|
||||||
|
|
||||||
key = cv2.waitKey(1) & 0xFF
|
quit, qr_mode = cv2_handle_pressed_keys(qr_mode)
|
||||||
if key == 27 or key == ord('q') or key == 13:
|
if quit:
|
||||||
# ESC or Enter or q pressed
|
|
||||||
break
|
|
||||||
elif key == 32:
|
|
||||||
qr_mode = next_qr_mode(qr_mode)
|
|
||||||
if verbose: print(f"QR reading mode: {qr_mode}")
|
|
||||||
if cv2.getWindowProperty(WINDOW_NAME, cv2.WND_PROP_VISIBLE) < 1:
|
|
||||||
# Window close clicked
|
|
||||||
break
|
break
|
||||||
|
|
||||||
cam.release()
|
cam.release()
|
||||||
|
|
@ -314,9 +321,24 @@ def extract_otps_from_camera(args: Args) -> Otps:
|
||||||
return otps
|
return otps
|
||||||
|
|
||||||
|
|
||||||
|
def cv2_handle_pressed_keys(qr_mode: QRMode) -> Tuple[bool, QRMode]:
|
||||||
|
key = cv2.waitKey(1) & 0xFF
|
||||||
|
quit = False
|
||||||
|
if key == 27 or key == ord('q') or key == 13:
|
||||||
|
# ESC or Enter or q pressed
|
||||||
|
quit = True
|
||||||
|
elif key == 32:
|
||||||
|
qr_mode = next_qr_mode(qr_mode)
|
||||||
|
if verbose >= LogLevel.MORE_VERBOSE: print(f"QR reading mode: {qr_mode}")
|
||||||
|
if cv2.getWindowProperty(WINDOW_NAME, cv2.WND_PROP_VISIBLE) < 1:
|
||||||
|
# Window close clicked
|
||||||
|
quit = True
|
||||||
|
return quit, qr_mode
|
||||||
|
|
||||||
|
|
||||||
def extract_otps_from_otp_url(otp_url: str, otp_urls: OtpUrls, otps: Otps, args: Args) -> int:
|
def extract_otps_from_otp_url(otp_url: str, otp_urls: OtpUrls, otps: Otps, args: Args) -> int:
|
||||||
'''Returns -1 if opt_url was already added.'''
|
'''Returns -1 if opt_url was already added.'''
|
||||||
if otp_url and verbose: print(otp_url)
|
if otp_url and verbose >= LogLevel.VERBOSE: print(otp_url)
|
||||||
if not otp_url:
|
if not otp_url:
|
||||||
return 0
|
return 0
|
||||||
if otp_url not in otp_urls:
|
if otp_url not in otp_urls:
|
||||||
|
|
@ -334,14 +356,14 @@ def extract_otps_from_files(args: Args) -> Otps:
|
||||||
files_count = urls_count = otps_count = 0
|
files_count = urls_count = otps_count = 0
|
||||||
if verbose: print(f"Input files: {args.infile}")
|
if verbose: print(f"Input files: {args.infile}")
|
||||||
for infile in args.infile:
|
for infile in args.infile:
|
||||||
if verbose: print(f"Processing infile {infile}")
|
if verbose >= LogLevel.MORE_VERBOSE: log_verbose(f"Processing infile {infile}")
|
||||||
files_count += 1
|
files_count += 1
|
||||||
for line in get_otp_urls_from_file(infile, args):
|
for line in get_otp_urls_from_file(infile, args):
|
||||||
if verbose: print(line)
|
if verbose >= LogLevel.MORE_VERBOSE: log_verbose(line)
|
||||||
if line.startswith('#') or line == '': continue
|
if line.startswith('#') or line == '': continue
|
||||||
urls_count += 1
|
urls_count += 1
|
||||||
otps_count += extract_otp_from_otp_url(line, otps, urls_count, infile, args)
|
otps_count += extract_otp_from_otp_url(line, otps, urls_count, infile, args)
|
||||||
if verbose: print(f"{files_count} infile{'s'[:files_count != 1]} processed")
|
if verbose: print(f"Extracted {otps_count} otp{'s'[:otps_count != 1]} from {urls_count} otp url{'s'[:urls_count != 1]} by reading {files_count} infile{'s'[:files_count != 1]}")
|
||||||
return otps
|
return otps
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -355,13 +377,13 @@ def get_otp_urls_from_file(filename: str, args: Args) -> OtpUrls:
|
||||||
|
|
||||||
# could not process text file, try reading as image
|
# could not process text file, try reading as image
|
||||||
if filename != '-' and qreader_available:
|
if filename != '-' and qreader_available:
|
||||||
return convert_img_to_otp_url(filename, args)
|
return convert_img_to_otp_urls(filename, args)
|
||||||
|
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def read_lines_from_text_file(filename: str) -> list[str]:
|
def read_lines_from_text_file(filename: str) -> list[str]:
|
||||||
if verbose: print(f"Reading lines of {filename}")
|
if verbose >= LogLevel.DEBUG: print(f"Reading lines of {filename}")
|
||||||
# workaround for PYTHON <= 3.9 support encoding
|
# workaround for PYTHON <= 3.9 support encoding
|
||||||
if sys.version_info >= (3, 10):
|
if sys.version_info >= (3, 10):
|
||||||
finput = fileinput.input(filename, encoding='utf-8')
|
finput = fileinput.input(filename, encoding='utf-8')
|
||||||
|
|
@ -370,7 +392,7 @@ def read_lines_from_text_file(filename: str) -> list[str]:
|
||||||
try:
|
try:
|
||||||
lines = []
|
lines = []
|
||||||
for line in (line.strip() for line in finput):
|
for line in (line.strip() for line in finput):
|
||||||
if verbose: print(line)
|
if verbose >= LogLevel.DEBUG: log_verbose(line)
|
||||||
if is_binary(line):
|
if is_binary(line):
|
||||||
abort("Binary input was given in stdin, please use = instead of - as infile argument for images.")
|
abort("Binary input was given in stdin, please use = instead of - as infile argument for images.")
|
||||||
# unfortunately yield line leads to random test fails
|
# unfortunately yield line leads to random test fails
|
||||||
|
|
@ -400,7 +422,7 @@ def extract_otp_from_otp_url(otpauth_migration_url: str, otps: Otps, urls_count:
|
||||||
new_otps_count += 1
|
new_otps_count += 1
|
||||||
if verbose: print(f"\n{len(otps) + 1}. Secret")
|
if verbose: print(f"\n{len(otps) + 1}. Secret")
|
||||||
secret = convert_secret_from_bytes_to_base32_str(raw_otp.secret)
|
secret = convert_secret_from_bytes_to_base32_str(raw_otp.secret)
|
||||||
if verbose: print('OTP enum type:', get_enum_name_by_number(raw_otp, 'type'))
|
if verbose >= LogLevel.DEBUG: log_debug('OTP enum type:', get_enum_name_by_number(raw_otp, 'type'))
|
||||||
otp_type = get_otp_type_str_from_code(raw_otp.type)
|
otp_type = get_otp_type_str_from_code(raw_otp.type)
|
||||||
otp_url = build_otp_url(secret, raw_otp)
|
otp_url = build_otp_url(secret, raw_otp)
|
||||||
otp: Otp = {
|
otp: Otp = {
|
||||||
|
|
@ -424,7 +446,7 @@ def extract_otp_from_otp_url(otpauth_migration_url: str, otps: Otps, urls_count:
|
||||||
return new_otps_count
|
return new_otps_count
|
||||||
|
|
||||||
|
|
||||||
def convert_img_to_otp_url(filename: str, args: Args) -> OtpUrls:
|
def convert_img_to_otp_urls(filename: str, args: Args) -> OtpUrls:
|
||||||
if verbose: print(f"Reading image {filename}")
|
if verbose: print(f"Reading image {filename}")
|
||||||
try:
|
try:
|
||||||
if filename != '=':
|
if filename != '=':
|
||||||
|
|
@ -449,22 +471,7 @@ def convert_img_to_otp_url(filename: str, args: Args) -> OtpUrls:
|
||||||
abort(f"Unable to open file for reading.\ninput file: {filename}")
|
abort(f"Unable to open file for reading.\ninput file: {filename}")
|
||||||
|
|
||||||
qr_mode = QRMode[args.qr]
|
qr_mode = QRMode[args.qr]
|
||||||
otp_urls: OtpUrls = []
|
otp_urls = decode_qr_img_otp_urls(img, qr_mode)
|
||||||
if qr_mode in [QRMode.QREADER, QRMode.QREADER_DEEP]:
|
|
||||||
otp_url = QReader().detect_and_decode(img, qr_mode == QRMode.QREADER_DEEP)
|
|
||||||
otp_urls.append(otp_url)
|
|
||||||
elif qr_mode == QRMode.CV2:
|
|
||||||
otp_url, _, _ = cv2.QRCodeDetector().detectAndDecode(img)
|
|
||||||
otp_urls.append(otp_url)
|
|
||||||
elif qr_mode == QRMode.CV2_WECHAT:
|
|
||||||
otp_url, _ = cv2.wechat_qrcode.WeChatQRCode().detectAndDecode(img)
|
|
||||||
otp_urls += list(otp_url)
|
|
||||||
elif qr_mode == QRMode.ZBAR:
|
|
||||||
qrcodes = zbar.decode(img)
|
|
||||||
otp_urls += [qrcode.data.decode('utf-8') for qrcode in qrcodes]
|
|
||||||
else:
|
|
||||||
assert False, f"Wrong QReader mode {qr_mode.name}"
|
|
||||||
|
|
||||||
if len(otp_urls) == 0:
|
if len(otp_urls) == 0:
|
||||||
abort(f"Unable to read QR Code from file.\ninput file: {filename}")
|
abort(f"Unable to read QR Code from file.\ninput file: {filename}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -472,29 +479,45 @@ def convert_img_to_otp_url(filename: str, args: Args) -> OtpUrls:
|
||||||
return otp_urls
|
return otp_urls
|
||||||
|
|
||||||
|
|
||||||
|
def decode_qr_img_otp_urls(img: Any, qr_mode: QRMode) -> OtpUrls:
|
||||||
|
otp_urls: OtpUrls = []
|
||||||
|
if qr_mode in [QRMode.QREADER, QRMode.QREADER_DEEP]:
|
||||||
|
otp_url = QReader().detect_and_decode(img, qr_mode == QRMode.QREADER_DEEP)
|
||||||
|
otp_urls.append(otp_url)
|
||||||
|
elif qr_mode == QRMode.CV2:
|
||||||
|
otp_url, _, _ = cv2.QRCodeDetector().detectAndDecode(img)
|
||||||
|
otp_urls.append(otp_url)
|
||||||
|
elif qr_mode == QRMode.CV2_WECHAT:
|
||||||
|
otp_url, _ = cv2.wechat_qrcode.WeChatQRCode().detectAndDecode(img)
|
||||||
|
otp_urls += list(otp_url)
|
||||||
|
elif qr_mode == QRMode.ZBAR:
|
||||||
|
qrcodes = zbar.decode(img)
|
||||||
|
otp_urls += [qrcode.data.decode('utf-8') for qrcode in qrcodes]
|
||||||
|
else:
|
||||||
|
assert False, f"Wrong QReader mode {qr_mode.name}"
|
||||||
|
|
||||||
|
return otp_urls
|
||||||
|
|
||||||
|
|
||||||
# workaround for PYTHON <= 3.9 use: pb.MigrationPayload | None
|
# workaround for PYTHON <= 3.9 use: pb.MigrationPayload | None
|
||||||
def get_payload_from_otp_url(otp_url: str, i: int, source: str) -> Optional[pb.MigrationPayload]:
|
def get_payload_from_otp_url(otp_url: str, i: int, source: str) -> Optional[pb.MigrationPayload]:
|
||||||
if not otp_url.startswith('otpauth-migration://'):
|
'''Extracts the otp migration payload from an otp url. This function is the core of the this appliation.'''
|
||||||
msg = f"input is not a otpauth-migration:// url\nsource: {source}\ninput: {otp_url}"
|
if not is_opt_url(otp_url, source):
|
||||||
if source == CAMERA:
|
return None
|
||||||
log_error(f"{msg}")
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
log_warn(f"{msg}\nMaybe a wrong file was given")
|
|
||||||
parsed_url = urlparse.urlparse(otp_url)
|
parsed_url = urlparse.urlparse(otp_url)
|
||||||
if verbose > 2: print(f"\nDEBUG: parsed_url={parsed_url}")
|
if verbose >= LogLevel.DEBUG: log_debug(f"parsed_url={parsed_url}")
|
||||||
try:
|
try:
|
||||||
params = urlparse.parse_qs(parsed_url.query, strict_parsing=True)
|
params = urlparse.parse_qs(parsed_url.query, strict_parsing=True)
|
||||||
except Exception: # workaround for PYTHON <= 3.10
|
except Exception: # workaround for PYTHON <= 3.10
|
||||||
params = {}
|
params = {}
|
||||||
if verbose > 2: print(f"\nDEBUG: querystring params={params}")
|
if verbose >= LogLevel.DEBUG: log_debug(f"querystring params={params}")
|
||||||
if 'data' not in params:
|
if 'data' not in params:
|
||||||
log_error(f"could not parse query parameter in input url\nsource: {source}\nurl: {otp_url}")
|
log_error(f"could not parse query parameter in input url\nsource: {source}\nurl: {otp_url}")
|
||||||
return None
|
return None
|
||||||
data_base64 = params['data'][0]
|
data_base64 = params['data'][0]
|
||||||
if verbose > 2: print(f"\nDEBUG: data_base64={data_base64}")
|
if verbose >= LogLevel.DEBUG: log_debug(f"data_base64={data_base64}")
|
||||||
data_base64_fixed = data_base64.replace(' ', '+')
|
data_base64_fixed = data_base64.replace(' ', '+')
|
||||||
if verbose > 2: print(f"\nDEBUG: data_base64_fixed={data_base64_fixed}")
|
if verbose >= LogLevel.DEBUG: log_debug(f"data_base64_fixed={data_base64_fixed}")
|
||||||
data = base64.b64decode(data_base64_fixed, validate=True)
|
data = base64.b64decode(data_base64_fixed, validate=True)
|
||||||
payload = pb.MigrationPayload()
|
payload = pb.MigrationPayload()
|
||||||
try:
|
try:
|
||||||
|
|
@ -502,12 +525,22 @@ def get_payload_from_otp_url(otp_url: str, i: int, source: str) -> Optional[pb.M
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
abort(f"Cannot decode otpauth-migration migration payload.\n"
|
abort(f"Cannot decode otpauth-migration migration payload.\n"
|
||||||
f"data={data_base64}", e)
|
f"data={data_base64}", e)
|
||||||
if verbose:
|
if verbose >= LogLevel.DEBUG: log_debug(f"\n{i}. Payload Line", payload, sep='\n')
|
||||||
print(f"\n{i}. Payload Line", payload, sep='\n')
|
|
||||||
|
|
||||||
return payload
|
return payload
|
||||||
|
|
||||||
|
|
||||||
|
def is_opt_url(otp_url: str, source: str) -> bool:
|
||||||
|
if not otp_url.startswith('otpauth-migration://'):
|
||||||
|
msg = f"input is not a otpauth-migration:// url\nsource: {source}\ninput: {otp_url}"
|
||||||
|
if source == CAMERA:
|
||||||
|
log_warn(f"{msg}")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
log_warn(f"{msg}\nMaybe a wrong file was given")
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
# https://stackoverflow.com/questions/40226049/find-enums-listed-in-python-descriptor-for-protobuf
|
# https://stackoverflow.com/questions/40226049/find-enums-listed-in-python-descriptor-for-protobuf
|
||||||
def get_enum_name_by_number(parent: Any, field_name: str) -> str:
|
def get_enum_name_by_number(parent: Any, field_name: str) -> str:
|
||||||
field_value = getattr(parent, field_name)
|
field_value = getattr(parent, field_name)
|
||||||
|
|
@ -580,38 +613,48 @@ def write_keepass_csv(args: Args, otps: Otps) -> None:
|
||||||
has_hotp = has_otp_type(otps, 'hotp')
|
has_hotp = has_otp_type(otps, 'hotp')
|
||||||
otp_filename_totp = args.keepass if has_totp != has_hotp else add_pre_suffix(args.keepass, "totp")
|
otp_filename_totp = args.keepass if has_totp != has_hotp else add_pre_suffix(args.keepass, "totp")
|
||||||
otp_filename_hotp = args.keepass if has_totp != has_hotp else add_pre_suffix(args.keepass, "hotp")
|
otp_filename_hotp = args.keepass if has_totp != has_hotp else add_pre_suffix(args.keepass, "hotp")
|
||||||
count_totp_entries = 0
|
|
||||||
count_hotp_entries = 0
|
|
||||||
if has_totp:
|
if has_totp:
|
||||||
with open_file_or_stdout_for_csv(otp_filename_totp) as outfile:
|
count_totp_entries = write_keepass_totp_csv(otp_filename_totp, otps)
|
||||||
writer = csv.DictWriter(outfile, ["Title", "User Name", "TimeOtp-Secret-Base32", "Group"])
|
|
||||||
writer.writeheader()
|
|
||||||
for otp in otps:
|
|
||||||
if otp['type'] == 'totp':
|
|
||||||
writer.writerow({
|
|
||||||
'Title': otp['issuer'],
|
|
||||||
'User Name': otp['name'],
|
|
||||||
'TimeOtp-Secret-Base32': otp['secret'] if otp['type'] == 'totp' else None,
|
|
||||||
'Group': f"OTP/{otp['type'].upper()}"
|
|
||||||
})
|
|
||||||
count_totp_entries += 1
|
|
||||||
if has_hotp:
|
if has_hotp:
|
||||||
with open_file_or_stdout_for_csv(otp_filename_hotp) as outfile:
|
count_hotp_entries = write_keepass_htop_csv(otp_filename_hotp, otps)
|
||||||
writer = csv.DictWriter(outfile, ["Title", "User Name", "HmacOtp-Secret-Base32", "HmacOtp-Counter", "Group"])
|
|
||||||
writer.writeheader()
|
|
||||||
for otp in otps:
|
|
||||||
if otp['type'] == 'hotp':
|
|
||||||
writer.writerow({
|
|
||||||
'Title': otp['issuer'],
|
|
||||||
'User Name': otp['name'],
|
|
||||||
'HmacOtp-Secret-Base32': otp['secret'] if otp['type'] == 'hotp' else None,
|
|
||||||
'HmacOtp-Counter': otp['counter'] if otp['type'] == 'hotp' else None,
|
|
||||||
'Group': f"OTP/{otp['type'].upper()}"
|
|
||||||
})
|
|
||||||
count_hotp_entries += 1
|
|
||||||
if not quiet:
|
if not quiet:
|
||||||
if count_totp_entries > 0: print(f"Exported {count_totp_entries} totp entrie{'s'[:count_totp_entries != 1]} to keepass csv file {otp_filename_totp}")
|
if count_totp_entries: print(f"Exported {count_totp_entries} totp entrie{'s'[:count_totp_entries != 1]} to keepass csv file {otp_filename_totp}")
|
||||||
if count_hotp_entries > 0: print(f"Exported {count_hotp_entries} hotp entrie{'s'[:count_hotp_entries != 1]} to keepass csv file {otp_filename_hotp}")
|
if count_hotp_entries: print(f"Exported {count_hotp_entries} hotp entrie{'s'[:count_hotp_entries != 1]} to keepass csv file {otp_filename_hotp}")
|
||||||
|
|
||||||
|
|
||||||
|
def write_keepass_totp_csv(otp_filename: str, otps: Otps) -> int:
|
||||||
|
count_entries = 0
|
||||||
|
with open_file_or_stdout_for_csv(otp_filename) as outfile:
|
||||||
|
writer = csv.DictWriter(outfile, ["Title", "User Name", "TimeOtp-Secret-Base32", "Group"])
|
||||||
|
writer.writeheader()
|
||||||
|
for otp in otps:
|
||||||
|
if otp['type'] == 'totp':
|
||||||
|
writer.writerow({
|
||||||
|
'Title': otp['issuer'],
|
||||||
|
'User Name': otp['name'],
|
||||||
|
'TimeOtp-Secret-Base32': otp['secret'] if otp['type'] == 'totp' else None,
|
||||||
|
'Group': f"OTP/{otp['type'].upper()}"
|
||||||
|
})
|
||||||
|
count_entries += 1
|
||||||
|
return count_entries
|
||||||
|
|
||||||
|
|
||||||
|
def write_keepass_htop_csv(otp_filename: str, otps: Otps) -> int:
|
||||||
|
count_entries = 0
|
||||||
|
with open_file_or_stdout_for_csv(otp_filename) as outfile:
|
||||||
|
writer = csv.DictWriter(outfile, ["Title", "User Name", "HmacOtp-Secret-Base32", "HmacOtp-Counter", "Group"])
|
||||||
|
writer.writeheader()
|
||||||
|
for otp in otps:
|
||||||
|
if otp['type'] == 'hotp':
|
||||||
|
writer.writerow({
|
||||||
|
'Title': otp['issuer'],
|
||||||
|
'User Name': otp['name'],
|
||||||
|
'HmacOtp-Secret-Base32': otp['secret'] if otp['type'] == 'hotp' else None,
|
||||||
|
'HmacOtp-Counter': otp['counter'] if otp['type'] == 'hotp' else None,
|
||||||
|
'Group': f"OTP/{otp['type'].upper()}"
|
||||||
|
})
|
||||||
|
count_entries += 1
|
||||||
|
return count_entries
|
||||||
|
|
||||||
|
|
||||||
def write_json(args: Args, otps: Otps) -> None:
|
def write_json(args: Args, otps: Otps) -> None:
|
||||||
|
|
@ -667,21 +710,38 @@ def next_qr_mode(qr_mode: QRMode) -> QRMode:
|
||||||
return QRMode((qr_mode.value + 1) % len(QRMode))
|
return QRMode((qr_mode.value + 1) % len(QRMode))
|
||||||
|
|
||||||
|
|
||||||
|
# workaround for PYTHON <= 3.9 use: BaseException | None
|
||||||
|
def log_debug(*values: object, sep: Optional[str] = ' ') -> None:
|
||||||
|
if colored:
|
||||||
|
print(f"{colorama.Fore.CYAN}\nDEBUG: {str(values[0])}", *values[1:], colorama.Fore.RESET, sep)
|
||||||
|
else:
|
||||||
|
print(f"\nDEBUG: {str(values[0])}", *values[1:], sep)
|
||||||
|
|
||||||
|
|
||||||
|
# workaround for PYTHON <= 3.9 use: BaseException | None
|
||||||
|
def log_verbose(msg: str) -> None:
|
||||||
|
print(color(msg, colorama.Fore.CYAN))
|
||||||
|
|
||||||
|
|
||||||
# workaround for PYTHON <= 3.9 use: BaseException | None
|
# workaround for PYTHON <= 3.9 use: BaseException | None
|
||||||
def log_warn(msg: str, exception: Optional[BaseException] = None) -> None:
|
def log_warn(msg: str, exception: Optional[BaseException] = None) -> None:
|
||||||
exception_text = "\nException: "
|
exception_text = "\nException: "
|
||||||
eprint(f"{colorama.Fore.RED if colored else ''}\nWARN: {msg}{(exception_text + str(exception)) if exception else ''}{colorama.Fore.RESET if colored else ''}")
|
eprint(color(f"\nWARN: {msg}{(exception_text + str(exception)) if exception else ''}", colorama.Fore.RED))
|
||||||
|
|
||||||
|
|
||||||
# workaround for PYTHON <= 3.9 use: BaseException | None
|
# workaround for PYTHON <= 3.9 use: BaseException | None
|
||||||
def log_error(msg: str, exception: Optional[BaseException] = None) -> None:
|
def log_error(msg: str, exception: Optional[BaseException] = None) -> None:
|
||||||
exception_text = "\nException: "
|
exception_text = "\nException: "
|
||||||
eprint(f"{colorama.Fore.RED if colored else ''}\nERROR: {msg}{(exception_text + str(exception)) if exception else ''}{colorama.Fore.RESET if colored else ''}")
|
eprint(color(f"\nERROR: {msg}{(exception_text + str(exception)) if exception else ''}", colorama.Fore.RED))
|
||||||
|
|
||||||
|
|
||||||
def eprint(*args: Any, **kwargs: Any) -> None:
|
def color(msg: str, color: Optional[str] = None) -> str:
|
||||||
|
return f"{color if colored and color else ''}{msg}{colorama.Fore.RESET if colored and color else ''}"
|
||||||
|
|
||||||
|
|
||||||
|
def eprint(*values: object, **kwargs: Any) -> None:
|
||||||
'''Print to stderr.'''
|
'''Print to stderr.'''
|
||||||
print(*args, file=sys.stderr, **kwargs)
|
print(*values, file=sys.stderr, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
# workaround for PYTHON <= 3.9 use: BaseException | None
|
# workaround for PYTHON <= 3.9 use: BaseException | None
|
||||||
|
|
|
||||||
51
tests/data/print_verbose_output-n-v.txt
Normal file
51
tests/data/print_verbose_output-n-v.txt
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
QReader installed: True
|
||||||
|
CV2 version: 4.7.0
|
||||||
|
QR reading mode: ZBAR
|
||||||
|
|
||||||
|
Input files: ['example_export.txt']
|
||||||
|
|
||||||
|
1. Secret
|
||||||
|
Name: pi@raspberrypi
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Issuer: raspberrypi
|
||||||
|
Type: totp
|
||||||
|
otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
|
||||||
|
|
||||||
|
|
||||||
|
2. Secret
|
||||||
|
Name: pi@raspberrypi
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Type: totp
|
||||||
|
otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
|
||||||
|
|
||||||
|
3. Secret
|
||||||
|
Name: pi@raspberrypi
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Type: totp
|
||||||
|
otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
|
||||||
|
|
||||||
|
4. Secret
|
||||||
|
Name: pi@raspberrypi
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Issuer: raspberrypi
|
||||||
|
Type: totp
|
||||||
|
otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
|
||||||
|
|
||||||
|
|
||||||
|
5. Secret
|
||||||
|
Name: hotp demo
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Type: hotp
|
||||||
|
Counter: 4
|
||||||
|
otpauth://hotp/hotp%20demo?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&counter=4
|
||||||
|
|
||||||
|
|
||||||
|
6. Secret
|
||||||
|
Name: encoding: ¿äÄéÉ? (demo)
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Type: totp
|
||||||
|
otpauth://totp/encoding%3A%20%C2%BF%C3%A4%C3%84%C3%A9%C3%89%3F%20%28demo%29?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
|
||||||
|
Extracted 6 otps from 5 otp urls by reading 1 infile
|
||||||
71
tests/data/print_verbose_output-n-vv.txt
Normal file
71
tests/data/print_verbose_output-n-vv.txt
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
QReader installed: True
|
||||||
|
CV2 version: 4.7.0
|
||||||
|
QR reading mode: ZBAR
|
||||||
|
|
||||||
|
Input files: ['example_export.txt']
|
||||||
|
Processing infile example_export.txt
|
||||||
|
# 2FA example from https://www.raspberrypi.org/blog/setting-up-two-factor-authentication-on-your-raspberry-pi/
|
||||||
|
|
||||||
|
# Secret key: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
|
||||||
|
otpauth-migration://offline?data=CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK%2B%2F%2F%2F%2F%2F8B
|
||||||
|
|
||||||
|
1. Secret
|
||||||
|
Name: pi@raspberrypi
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Issuer: raspberrypi
|
||||||
|
Type: totp
|
||||||
|
otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
|
||||||
|
|
||||||
|
|
||||||
|
# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
otpauth-migration://offline?data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4%2F%2F%2F%2F%2FwE%3D
|
||||||
|
|
||||||
|
2. Secret
|
||||||
|
Name: pi@raspberrypi
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Type: totp
|
||||||
|
otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
|
||||||
|
|
||||||
|
# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
|
||||||
|
# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
otpauth-migration://offline?data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa%2Bf%2F%2F%2F%2F8B
|
||||||
|
|
||||||
|
3. Secret
|
||||||
|
Name: pi@raspberrypi
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Type: totp
|
||||||
|
otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
|
||||||
|
|
||||||
|
4. Secret
|
||||||
|
Name: pi@raspberrypi
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Issuer: raspberrypi
|
||||||
|
Type: totp
|
||||||
|
otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
|
||||||
|
|
||||||
|
|
||||||
|
# otpauth://hotp/hotp%20demo?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&counter=4
|
||||||
|
otpauth-migration://offline?data=CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6%2F%2F%2F%2F%2FwE%3D
|
||||||
|
|
||||||
|
5. Secret
|
||||||
|
Name: hotp demo
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Type: hotp
|
||||||
|
Counter: 4
|
||||||
|
otpauth://hotp/hotp%20demo?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&counter=4
|
||||||
|
|
||||||
|
|
||||||
|
# otpauth://totp/encoding%3A%20%C2%BF%C3%A4%C3%84%C3%A9%C3%89%3F%20%28demo%29?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
# Name: "encoding: ¿äÄéÉ? (demo)"
|
||||||
|
otpauth-migration://offline?data=CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv%2F%2F%2F%2F%2F%2FAQ%3D%3D
|
||||||
|
|
||||||
|
6. Secret
|
||||||
|
Name: encoding: ¿äÄéÉ? (demo)
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Type: totp
|
||||||
|
otpauth://totp/encoding%3A%20%C2%BF%C3%A4%C3%84%C3%A9%C3%89%3F%20%28demo%29?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
|
||||||
|
Extracted 6 otps from 5 otp urls by reading 1 infile
|
||||||
223
tests/data/print_verbose_output-n-vvv.txt
Normal file
223
tests/data/print_verbose_output-n-vvv.txt
Normal file
|
|
@ -0,0 +1,223 @@
|
||||||
|
QReader installed: True
|
||||||
|
CV2 version: 4.7.0
|
||||||
|
QR reading mode: ZBAR
|
||||||
|
|
||||||
|
Input files: ['example_export.txt']
|
||||||
|
Processing infile example_export.txt
|
||||||
|
Reading lines of example_export.txt
|
||||||
|
# 2FA example from https://www.raspberrypi.org/blog/setting-up-two-factor-authentication-on-your-raspberry-pi/
|
||||||
|
|
||||||
|
# Secret key: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
|
||||||
|
otpauth-migration://offline?data=CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK%2B%2F%2F%2F%2F%2F8B
|
||||||
|
|
||||||
|
# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
otpauth-migration://offline?data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4%2F%2F%2F%2F%2FwE%3D
|
||||||
|
|
||||||
|
# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
|
||||||
|
# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
otpauth-migration://offline?data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa%2Bf%2F%2F%2F%2F8B
|
||||||
|
|
||||||
|
# otpauth://hotp/hotp%20demo?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&counter=4
|
||||||
|
otpauth-migration://offline?data=CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6%2F%2F%2F%2F%2FwE%3D
|
||||||
|
|
||||||
|
# otpauth://totp/encoding%3A%20%C2%BF%C3%A4%C3%84%C3%A9%C3%89%3F%20%28demo%29?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
# Name: "encoding: ¿äÄéÉ? (demo)"
|
||||||
|
otpauth-migration://offline?data=CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv%2F%2F%2F%2F%2F%2FAQ%3D%3D
|
||||||
|
# 2FA example from https://www.raspberrypi.org/blog/setting-up-two-factor-authentication-on-your-raspberry-pi/
|
||||||
|
|
||||||
|
# Secret key: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
|
||||||
|
otpauth-migration://offline?data=CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK%2B%2F%2F%2F%2F%2F8B
|
||||||
|
|
||||||
|
DEBUG: parsed_url=ParseResult(scheme='otpauth-migration', netloc='offline', path='', params='', query='data=CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK%2B%2F%2F%2F%2F%2F8B', fragment='')
|
||||||
|
|
||||||
|
DEBUG: querystring params={'data': ['CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK+/////8B']}
|
||||||
|
|
||||||
|
DEBUG: data_base64=CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK+/////8B
|
||||||
|
|
||||||
|
DEBUG: data_base64_fixed=CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK+/////8B
|
||||||
|
|
||||||
|
DEBUG:
|
||||||
|
1. Payload Line otp_parameters {
|
||||||
|
secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106"
|
||||||
|
name: "pi@raspberrypi"
|
||||||
|
issuer: "raspberrypi"
|
||||||
|
algorithm: ALGO_SHA1
|
||||||
|
digits: 1
|
||||||
|
type: OTP_TOTP
|
||||||
|
}
|
||||||
|
version: 1
|
||||||
|
batch_size: 1
|
||||||
|
batch_id: -1320898453
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
1. Secret
|
||||||
|
|
||||||
|
DEBUG: OTP enum type: OTP_TOTP
|
||||||
|
Name: pi@raspberrypi
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Issuer: raspberrypi
|
||||||
|
Type: totp
|
||||||
|
otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
|
||||||
|
|
||||||
|
|
||||||
|
# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
otpauth-migration://offline?data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4%2F%2F%2F%2F%2FwE%3D
|
||||||
|
|
||||||
|
DEBUG: parsed_url=ParseResult(scheme='otpauth-migration', netloc='offline', path='', params='', query='data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4%2F%2F%2F%2F%2FwE%3D', fragment='')
|
||||||
|
|
||||||
|
DEBUG: querystring params={'data': ['CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4/////wE=']}
|
||||||
|
|
||||||
|
DEBUG: data_base64=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4/////wE=
|
||||||
|
|
||||||
|
DEBUG: data_base64_fixed=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4/////wE=
|
||||||
|
|
||||||
|
DEBUG:
|
||||||
|
2. Payload Line otp_parameters {
|
||||||
|
secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106"
|
||||||
|
name: "pi@raspberrypi"
|
||||||
|
algorithm: ALGO_SHA1
|
||||||
|
digits: 1
|
||||||
|
type: OTP_TOTP
|
||||||
|
}
|
||||||
|
version: 1
|
||||||
|
batch_size: 1
|
||||||
|
batch_id: -2094403140
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
2. Secret
|
||||||
|
|
||||||
|
DEBUG: OTP enum type: OTP_TOTP
|
||||||
|
Name: pi@raspberrypi
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Type: totp
|
||||||
|
otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
|
||||||
|
|
||||||
|
# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
|
||||||
|
# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
otpauth-migration://offline?data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa%2Bf%2F%2F%2F%2F8B
|
||||||
|
|
||||||
|
DEBUG: parsed_url=ParseResult(scheme='otpauth-migration', netloc='offline', path='', params='', query='data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa%2Bf%2F%2F%2F%2F8B', fragment='')
|
||||||
|
|
||||||
|
DEBUG: querystring params={'data': ['CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa+f////8B']}
|
||||||
|
|
||||||
|
DEBUG: data_base64=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa+f////8B
|
||||||
|
|
||||||
|
DEBUG: data_base64_fixed=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa+f////8B
|
||||||
|
|
||||||
|
DEBUG:
|
||||||
|
3. Payload Line otp_parameters {
|
||||||
|
secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106"
|
||||||
|
name: "pi@raspberrypi"
|
||||||
|
algorithm: ALGO_SHA1
|
||||||
|
digits: 1
|
||||||
|
type: OTP_TOTP
|
||||||
|
}
|
||||||
|
otp_parameters {
|
||||||
|
secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106"
|
||||||
|
name: "pi@raspberrypi"
|
||||||
|
issuer: "raspberrypi"
|
||||||
|
algorithm: ALGO_SHA1
|
||||||
|
digits: 1
|
||||||
|
type: OTP_TOTP
|
||||||
|
}
|
||||||
|
version: 1
|
||||||
|
batch_size: 1
|
||||||
|
batch_id: -1822886384
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
3. Secret
|
||||||
|
|
||||||
|
DEBUG: OTP enum type: OTP_TOTP
|
||||||
|
Name: pi@raspberrypi
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Type: totp
|
||||||
|
otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
|
||||||
|
|
||||||
|
4. Secret
|
||||||
|
|
||||||
|
DEBUG: OTP enum type: OTP_TOTP
|
||||||
|
Name: pi@raspberrypi
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Issuer: raspberrypi
|
||||||
|
Type: totp
|
||||||
|
otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
|
||||||
|
|
||||||
|
|
||||||
|
# otpauth://hotp/hotp%20demo?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&counter=4
|
||||||
|
otpauth-migration://offline?data=CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6%2F%2F%2F%2F%2FwE%3D
|
||||||
|
|
||||||
|
DEBUG: parsed_url=ParseResult(scheme='otpauth-migration', netloc='offline', path='', params='', query='data=CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6%2F%2F%2F%2F%2FwE%3D', fragment='')
|
||||||
|
|
||||||
|
DEBUG: querystring params={'data': ['CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6/////wE=']}
|
||||||
|
|
||||||
|
DEBUG: data_base64=CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6/////wE=
|
||||||
|
|
||||||
|
DEBUG: data_base64_fixed=CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6/////wE=
|
||||||
|
|
||||||
|
DEBUG:
|
||||||
|
4. Payload Line otp_parameters {
|
||||||
|
secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106"
|
||||||
|
name: "hotp demo"
|
||||||
|
algorithm: ALGO_SHA1
|
||||||
|
digits: 1
|
||||||
|
type: OTP_HOTP
|
||||||
|
counter: 4
|
||||||
|
}
|
||||||
|
version: 1
|
||||||
|
batch_size: 1
|
||||||
|
batch_id: -1558849573
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
5. Secret
|
||||||
|
|
||||||
|
DEBUG: OTP enum type: OTP_HOTP
|
||||||
|
Name: hotp demo
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Type: hotp
|
||||||
|
Counter: 4
|
||||||
|
otpauth://hotp/hotp%20demo?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&counter=4
|
||||||
|
|
||||||
|
|
||||||
|
# otpauth://totp/encoding%3A%20%C2%BF%C3%A4%C3%84%C3%A9%C3%89%3F%20%28demo%29?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
# Name: "encoding: ¿äÄéÉ? (demo)"
|
||||||
|
otpauth-migration://offline?data=CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv%2F%2F%2F%2F%2F%2FAQ%3D%3D
|
||||||
|
|
||||||
|
DEBUG: parsed_url=ParseResult(scheme='otpauth-migration', netloc='offline', path='', params='', query='data=CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv%2F%2F%2F%2F%2F%2FAQ%3D%3D', fragment='')
|
||||||
|
|
||||||
|
DEBUG: querystring params={'data': ['CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv//////AQ==']}
|
||||||
|
|
||||||
|
DEBUG: data_base64=CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv//////AQ==
|
||||||
|
|
||||||
|
DEBUG: data_base64_fixed=CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv//////AQ==
|
||||||
|
|
||||||
|
DEBUG:
|
||||||
|
5. Payload Line otp_parameters {
|
||||||
|
secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106"
|
||||||
|
name: "encoding: ¿äÄéÉ? (demo)"
|
||||||
|
algorithm: ALGO_SHA1
|
||||||
|
digits: 1
|
||||||
|
type: OTP_TOTP
|
||||||
|
}
|
||||||
|
version: 1
|
||||||
|
batch_size: 1
|
||||||
|
batch_id: -171198419
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
6. Secret
|
||||||
|
|
||||||
|
DEBUG: OTP enum type: OTP_TOTP
|
||||||
|
Name: encoding: ¿äÄéÉ? (demo)
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Type: totp
|
||||||
|
otpauth://totp/encoding%3A%20%C2%BF%C3%A4%C3%84%C3%A9%C3%89%3F%20%28demo%29?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
|
||||||
|
Extracted 6 otps from 5 otp urls by reading 1 infile
|
||||||
27
tests/data/print_verbose_output-n.txt
Normal file
27
tests/data/print_verbose_output-n.txt
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
Name: pi@raspberrypi
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Issuer: raspberrypi
|
||||||
|
Type: totp
|
||||||
|
|
||||||
|
Name: pi@raspberrypi
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Type: totp
|
||||||
|
|
||||||
|
Name: pi@raspberrypi
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Type: totp
|
||||||
|
|
||||||
|
Name: pi@raspberrypi
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Issuer: raspberrypi
|
||||||
|
Type: totp
|
||||||
|
|
||||||
|
Name: hotp demo
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Type: hotp
|
||||||
|
Counter: 4
|
||||||
|
|
||||||
|
Name: encoding: ¿äÄéÉ? (demo)
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Type: totp
|
||||||
|
|
||||||
51
tests/data/print_verbose_output-v.txt
Normal file
51
tests/data/print_verbose_output-v.txt
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
QReader installed: True
|
||||||
|
CV2 version: 4.7.0
|
||||||
|
QR reading mode: ZBAR
|
||||||
|
|
||||||
|
Input files: ['example_export.txt']
|
||||||
|
|
||||||
|
1. Secret
|
||||||
|
Name: pi@raspberrypi
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Issuer: raspberrypi
|
||||||
|
Type: totp
|
||||||
|
otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
|
||||||
|
|
||||||
|
|
||||||
|
2. Secret
|
||||||
|
Name: pi@raspberrypi
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Type: totp
|
||||||
|
otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
|
||||||
|
|
||||||
|
3. Secret
|
||||||
|
Name: pi@raspberrypi
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Type: totp
|
||||||
|
otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
|
||||||
|
|
||||||
|
4. Secret
|
||||||
|
Name: pi@raspberrypi
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Issuer: raspberrypi
|
||||||
|
Type: totp
|
||||||
|
otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
|
||||||
|
|
||||||
|
|
||||||
|
5. Secret
|
||||||
|
Name: hotp demo
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Type: hotp
|
||||||
|
Counter: 4
|
||||||
|
otpauth://hotp/hotp%20demo?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&counter=4
|
||||||
|
|
||||||
|
|
||||||
|
6. Secret
|
||||||
|
Name: encoding: ¿äÄéÉ? (demo)
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Type: totp
|
||||||
|
otpauth://totp/encoding%3A%20%C2%BF%C3%A4%C3%84%C3%A9%C3%89%3F%20%28demo%29?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
|
||||||
|
Extracted 6 otps from 5 otp urls by reading 1 infile
|
||||||
71
tests/data/print_verbose_output-vv.txt
Normal file
71
tests/data/print_verbose_output-vv.txt
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
QReader installed: True
|
||||||
|
CV2 version: 4.7.0
|
||||||
|
QR reading mode: ZBAR
|
||||||
|
|
||||||
|
Input files: ['example_export.txt']
|
||||||
|
[36mProcessing infile example_export.txt[39m
|
||||||
|
[36m# 2FA example from https://www.raspberrypi.org/blog/setting-up-two-factor-authentication-on-your-raspberry-pi/[39m
|
||||||
|
[36m[39m
|
||||||
|
[36m# Secret key: 7KSQL2JTUDIS5EF65KLMRQIIGY[39m
|
||||||
|
[36m# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi[39m
|
||||||
|
[36motpauth-migration://offline?data=CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK%2B%2F%2F%2F%2F%2F8B[39m
|
||||||
|
|
||||||
|
1. Secret
|
||||||
|
Name: pi@raspberrypi
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Issuer: raspberrypi
|
||||||
|
Type: totp
|
||||||
|
otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
|
||||||
|
|
||||||
|
[36m[39m
|
||||||
|
[36m# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY[39m
|
||||||
|
[36motpauth-migration://offline?data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4%2F%2F%2F%2F%2FwE%3D[39m
|
||||||
|
|
||||||
|
2. Secret
|
||||||
|
Name: pi@raspberrypi
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Type: totp
|
||||||
|
otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
|
||||||
|
[36m[39m
|
||||||
|
[36m# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi[39m
|
||||||
|
[36m# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY[39m
|
||||||
|
[36motpauth-migration://offline?data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa%2Bf%2F%2F%2F%2F8B[39m
|
||||||
|
|
||||||
|
3. Secret
|
||||||
|
Name: pi@raspberrypi
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Type: totp
|
||||||
|
otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
|
||||||
|
|
||||||
|
4. Secret
|
||||||
|
Name: pi@raspberrypi
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Issuer: raspberrypi
|
||||||
|
Type: totp
|
||||||
|
otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
|
||||||
|
|
||||||
|
[36m[39m
|
||||||
|
[36m# otpauth://hotp/hotp%20demo?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&counter=4[39m
|
||||||
|
[36motpauth-migration://offline?data=CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6%2F%2F%2F%2F%2FwE%3D[39m
|
||||||
|
|
||||||
|
5. Secret
|
||||||
|
Name: hotp demo
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Type: hotp
|
||||||
|
Counter: 4
|
||||||
|
otpauth://hotp/hotp%20demo?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&counter=4
|
||||||
|
|
||||||
|
[36m[39m
|
||||||
|
[36m# otpauth://totp/encoding%3A%20%C2%BF%C3%A4%C3%84%C3%A9%C3%89%3F%20%28demo%29?secret=7KSQL2JTUDIS5EF65KLMRQIIGY[39m
|
||||||
|
[36m# Name: "encoding: ¿äÄéÉ? (demo)"[39m
|
||||||
|
[36motpauth-migration://offline?data=CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv%2F%2F%2F%2F%2F%2FAQ%3D%3D[39m
|
||||||
|
|
||||||
|
6. Secret
|
||||||
|
Name: encoding: ¿äÄéÉ? (demo)
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Type: totp
|
||||||
|
otpauth://totp/encoding%3A%20%C2%BF%C3%A4%C3%84%C3%A9%C3%89%3F%20%28demo%29?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
|
||||||
|
Extracted 6 otps from 5 otp urls by reading 1 infile
|
||||||
223
tests/data/print_verbose_output-vvv.txt
Normal file
223
tests/data/print_verbose_output-vvv.txt
Normal file
|
|
@ -0,0 +1,223 @@
|
||||||
|
QReader installed: True
|
||||||
|
CV2 version: 4.7.0
|
||||||
|
QR reading mode: ZBAR
|
||||||
|
|
||||||
|
Input files: ['example_export.txt']
|
||||||
|
[36mProcessing infile example_export.txt[39m
|
||||||
|
Reading lines of example_export.txt
|
||||||
|
[36m# 2FA example from https://www.raspberrypi.org/blog/setting-up-two-factor-authentication-on-your-raspberry-pi/[39m
|
||||||
|
[36m[39m
|
||||||
|
[36m# Secret key: 7KSQL2JTUDIS5EF65KLMRQIIGY[39m
|
||||||
|
[36m# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi[39m
|
||||||
|
[36motpauth-migration://offline?data=CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK%2B%2F%2F%2F%2F%2F8B[39m
|
||||||
|
[36m[39m
|
||||||
|
[36m# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY[39m
|
||||||
|
[36motpauth-migration://offline?data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4%2F%2F%2F%2F%2FwE%3D[39m
|
||||||
|
[36m[39m
|
||||||
|
[36m# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi[39m
|
||||||
|
[36m# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY[39m
|
||||||
|
[36motpauth-migration://offline?data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa%2Bf%2F%2F%2F%2F8B[39m
|
||||||
|
[36m[39m
|
||||||
|
[36m# otpauth://hotp/hotp%20demo?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&counter=4[39m
|
||||||
|
[36motpauth-migration://offline?data=CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6%2F%2F%2F%2F%2FwE%3D[39m
|
||||||
|
[36m[39m
|
||||||
|
[36m# otpauth://totp/encoding%3A%20%C2%BF%C3%A4%C3%84%C3%A9%C3%89%3F%20%28demo%29?secret=7KSQL2JTUDIS5EF65KLMRQIIGY[39m
|
||||||
|
[36m# Name: "encoding: ¿äÄéÉ? (demo)"[39m
|
||||||
|
[36motpauth-migration://offline?data=CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv%2F%2F%2F%2F%2F%2FAQ%3D%3D[39m
|
||||||
|
[36m# 2FA example from https://www.raspberrypi.org/blog/setting-up-two-factor-authentication-on-your-raspberry-pi/[39m
|
||||||
|
[36m[39m
|
||||||
|
[36m# Secret key: 7KSQL2JTUDIS5EF65KLMRQIIGY[39m
|
||||||
|
[36m# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi[39m
|
||||||
|
[36motpauth-migration://offline?data=CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK%2B%2F%2F%2F%2F%2F8B[39m
|
||||||
|
[36m
|
||||||
|
DEBUG: parsed_url=ParseResult(scheme='otpauth-migration', netloc='offline', path='', params='', query='data=CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK%2B%2F%2F%2F%2F%2F8B', fragment='') [39m
|
||||||
|
[36m
|
||||||
|
DEBUG: querystring params={'data': ['CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK+/////8B']} [39m
|
||||||
|
[36m
|
||||||
|
DEBUG: data_base64=CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK+/////8B [39m
|
||||||
|
[36m
|
||||||
|
DEBUG: data_base64_fixed=CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK+/////8B [39m
|
||||||
|
[36m
|
||||||
|
DEBUG:
|
||||||
|
1. Payload Line otp_parameters {
|
||||||
|
secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106"
|
||||||
|
name: "pi@raspberrypi"
|
||||||
|
issuer: "raspberrypi"
|
||||||
|
algorithm: ALGO_SHA1
|
||||||
|
digits: 1
|
||||||
|
type: OTP_TOTP
|
||||||
|
}
|
||||||
|
version: 1
|
||||||
|
batch_size: 1
|
||||||
|
batch_id: -1320898453
|
||||||
|
[39m
|
||||||
|
|
||||||
|
|
||||||
|
1. Secret
|
||||||
|
[36m
|
||||||
|
DEBUG: OTP enum type: OTP_TOTP [39m
|
||||||
|
Name: pi@raspberrypi
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Issuer: raspberrypi
|
||||||
|
Type: totp
|
||||||
|
otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
|
||||||
|
|
||||||
|
[36m[39m
|
||||||
|
[36m# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY[39m
|
||||||
|
[36motpauth-migration://offline?data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4%2F%2F%2F%2F%2FwE%3D[39m
|
||||||
|
[36m
|
||||||
|
DEBUG: parsed_url=ParseResult(scheme='otpauth-migration', netloc='offline', path='', params='', query='data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4%2F%2F%2F%2F%2FwE%3D', fragment='') [39m
|
||||||
|
[36m
|
||||||
|
DEBUG: querystring params={'data': ['CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4/////wE=']} [39m
|
||||||
|
[36m
|
||||||
|
DEBUG: data_base64=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4/////wE= [39m
|
||||||
|
[36m
|
||||||
|
DEBUG: data_base64_fixed=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4/////wE= [39m
|
||||||
|
[36m
|
||||||
|
DEBUG:
|
||||||
|
2. Payload Line otp_parameters {
|
||||||
|
secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106"
|
||||||
|
name: "pi@raspberrypi"
|
||||||
|
algorithm: ALGO_SHA1
|
||||||
|
digits: 1
|
||||||
|
type: OTP_TOTP
|
||||||
|
}
|
||||||
|
version: 1
|
||||||
|
batch_size: 1
|
||||||
|
batch_id: -2094403140
|
||||||
|
[39m
|
||||||
|
|
||||||
|
|
||||||
|
2. Secret
|
||||||
|
[36m
|
||||||
|
DEBUG: OTP enum type: OTP_TOTP [39m
|
||||||
|
Name: pi@raspberrypi
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Type: totp
|
||||||
|
otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
|
||||||
|
[36m[39m
|
||||||
|
[36m# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi[39m
|
||||||
|
[36m# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY[39m
|
||||||
|
[36motpauth-migration://offline?data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa%2Bf%2F%2F%2F%2F8B[39m
|
||||||
|
[36m
|
||||||
|
DEBUG: parsed_url=ParseResult(scheme='otpauth-migration', netloc='offline', path='', params='', query='data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa%2Bf%2F%2F%2F%2F8B', fragment='') [39m
|
||||||
|
[36m
|
||||||
|
DEBUG: querystring params={'data': ['CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa+f////8B']} [39m
|
||||||
|
[36m
|
||||||
|
DEBUG: data_base64=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa+f////8B [39m
|
||||||
|
[36m
|
||||||
|
DEBUG: data_base64_fixed=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa+f////8B [39m
|
||||||
|
[36m
|
||||||
|
DEBUG:
|
||||||
|
3. Payload Line otp_parameters {
|
||||||
|
secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106"
|
||||||
|
name: "pi@raspberrypi"
|
||||||
|
algorithm: ALGO_SHA1
|
||||||
|
digits: 1
|
||||||
|
type: OTP_TOTP
|
||||||
|
}
|
||||||
|
otp_parameters {
|
||||||
|
secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106"
|
||||||
|
name: "pi@raspberrypi"
|
||||||
|
issuer: "raspberrypi"
|
||||||
|
algorithm: ALGO_SHA1
|
||||||
|
digits: 1
|
||||||
|
type: OTP_TOTP
|
||||||
|
}
|
||||||
|
version: 1
|
||||||
|
batch_size: 1
|
||||||
|
batch_id: -1822886384
|
||||||
|
[39m
|
||||||
|
|
||||||
|
|
||||||
|
3. Secret
|
||||||
|
[36m
|
||||||
|
DEBUG: OTP enum type: OTP_TOTP [39m
|
||||||
|
Name: pi@raspberrypi
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Type: totp
|
||||||
|
otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
|
||||||
|
|
||||||
|
4. Secret
|
||||||
|
[36m
|
||||||
|
DEBUG: OTP enum type: OTP_TOTP [39m
|
||||||
|
Name: pi@raspberrypi
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Issuer: raspberrypi
|
||||||
|
Type: totp
|
||||||
|
otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
|
||||||
|
|
||||||
|
[36m[39m
|
||||||
|
[36m# otpauth://hotp/hotp%20demo?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&counter=4[39m
|
||||||
|
[36motpauth-migration://offline?data=CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6%2F%2F%2F%2F%2FwE%3D[39m
|
||||||
|
[36m
|
||||||
|
DEBUG: parsed_url=ParseResult(scheme='otpauth-migration', netloc='offline', path='', params='', query='data=CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6%2F%2F%2F%2F%2FwE%3D', fragment='') [39m
|
||||||
|
[36m
|
||||||
|
DEBUG: querystring params={'data': ['CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6/////wE=']} [39m
|
||||||
|
[36m
|
||||||
|
DEBUG: data_base64=CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6/////wE= [39m
|
||||||
|
[36m
|
||||||
|
DEBUG: data_base64_fixed=CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6/////wE= [39m
|
||||||
|
[36m
|
||||||
|
DEBUG:
|
||||||
|
4. Payload Line otp_parameters {
|
||||||
|
secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106"
|
||||||
|
name: "hotp demo"
|
||||||
|
algorithm: ALGO_SHA1
|
||||||
|
digits: 1
|
||||||
|
type: OTP_HOTP
|
||||||
|
counter: 4
|
||||||
|
}
|
||||||
|
version: 1
|
||||||
|
batch_size: 1
|
||||||
|
batch_id: -1558849573
|
||||||
|
[39m
|
||||||
|
|
||||||
|
|
||||||
|
5. Secret
|
||||||
|
[36m
|
||||||
|
DEBUG: OTP enum type: OTP_HOTP [39m
|
||||||
|
Name: hotp demo
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Type: hotp
|
||||||
|
Counter: 4
|
||||||
|
otpauth://hotp/hotp%20demo?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&counter=4
|
||||||
|
|
||||||
|
[36m[39m
|
||||||
|
[36m# otpauth://totp/encoding%3A%20%C2%BF%C3%A4%C3%84%C3%A9%C3%89%3F%20%28demo%29?secret=7KSQL2JTUDIS5EF65KLMRQIIGY[39m
|
||||||
|
[36m# Name: "encoding: ¿äÄéÉ? (demo)"[39m
|
||||||
|
[36motpauth-migration://offline?data=CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv%2F%2F%2F%2F%2F%2FAQ%3D%3D[39m
|
||||||
|
[36m
|
||||||
|
DEBUG: parsed_url=ParseResult(scheme='otpauth-migration', netloc='offline', path='', params='', query='data=CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv%2F%2F%2F%2F%2F%2FAQ%3D%3D', fragment='') [39m
|
||||||
|
[36m
|
||||||
|
DEBUG: querystring params={'data': ['CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv//////AQ==']} [39m
|
||||||
|
[36m
|
||||||
|
DEBUG: data_base64=CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv//////AQ== [39m
|
||||||
|
[36m
|
||||||
|
DEBUG: data_base64_fixed=CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv//////AQ== [39m
|
||||||
|
[36m
|
||||||
|
DEBUG:
|
||||||
|
5. Payload Line otp_parameters {
|
||||||
|
secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106"
|
||||||
|
name: "encoding: ¿äÄéÉ? (demo)"
|
||||||
|
algorithm: ALGO_SHA1
|
||||||
|
digits: 1
|
||||||
|
type: OTP_TOTP
|
||||||
|
}
|
||||||
|
version: 1
|
||||||
|
batch_size: 1
|
||||||
|
batch_id: -171198419
|
||||||
|
[39m
|
||||||
|
|
||||||
|
|
||||||
|
6. Secret
|
||||||
|
[36m
|
||||||
|
DEBUG: OTP enum type: OTP_TOTP [39m
|
||||||
|
Name: encoding: ¿äÄéÉ? (demo)
|
||||||
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
Type: totp
|
||||||
|
otpauth://totp/encoding%3A%20%C2%BF%C3%A4%C3%84%C3%A9%C3%89%3F%20%28demo%29?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
|
||||||
|
Extracted 6 otps from 5 otp urls by reading 1 infile
|
||||||
|
|
@ -1,171 +1,27 @@
|
||||||
QReader installed: True
|
|
||||||
QR reading mode: ZBAR
|
|
||||||
|
|
||||||
Input files: ['example_export.txt']
|
|
||||||
Processing infile example_export.txt
|
|
||||||
Reading lines of example_export.txt
|
|
||||||
# 2FA example from https://www.raspberrypi.org/blog/setting-up-two-factor-authentication-on-your-raspberry-pi/
|
|
||||||
|
|
||||||
# Secret key: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
|
||||||
# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
|
|
||||||
otpauth-migration://offline?data=CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK%2B%2F%2F%2F%2F%2F8B
|
|
||||||
|
|
||||||
# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
|
||||||
otpauth-migration://offline?data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4%2F%2F%2F%2F%2FwE%3D
|
|
||||||
|
|
||||||
# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
|
|
||||||
# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
|
||||||
otpauth-migration://offline?data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa%2Bf%2F%2F%2F%2F8B
|
|
||||||
|
|
||||||
# otpauth://hotp/hotp%20demo?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&counter=4
|
|
||||||
otpauth-migration://offline?data=CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6%2F%2F%2F%2F%2FwE%3D
|
|
||||||
|
|
||||||
# otpauth://totp/encoding%3A%20%C2%BF%C3%A4%C3%84%C3%A9%C3%89%3F%20%28demo%29?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
|
||||||
# Name: "encoding: ¿äÄéÉ? (demo)"
|
|
||||||
otpauth-migration://offline?data=CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv%2F%2F%2F%2F%2F%2FAQ%3D%3D
|
|
||||||
# 2FA example from https://www.raspberrypi.org/blog/setting-up-two-factor-authentication-on-your-raspberry-pi/
|
|
||||||
|
|
||||||
# Secret key: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
|
||||||
# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
|
|
||||||
otpauth-migration://offline?data=CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK%2B%2F%2F%2F%2F%2F8B
|
|
||||||
|
|
||||||
1. Payload Line
|
|
||||||
otp_parameters {
|
|
||||||
secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106"
|
|
||||||
name: "pi@raspberrypi"
|
|
||||||
issuer: "raspberrypi"
|
|
||||||
algorithm: ALGO_SHA1
|
|
||||||
digits: 1
|
|
||||||
type: OTP_TOTP
|
|
||||||
}
|
|
||||||
version: 1
|
|
||||||
batch_size: 1
|
|
||||||
batch_id: -1320898453
|
|
||||||
|
|
||||||
|
|
||||||
1. Secret
|
|
||||||
OTP enum type: OTP_TOTP
|
|
||||||
Name: pi@raspberrypi
|
Name: pi@raspberrypi
|
||||||
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
Issuer: raspberrypi
|
Issuer: raspberrypi
|
||||||
Type: totp
|
Type: totp
|
||||||
otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
|
|
||||||
|
|
||||||
|
|
||||||
# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
|
||||||
otpauth-migration://offline?data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4%2F%2F%2F%2F%2FwE%3D
|
|
||||||
|
|
||||||
2. Payload Line
|
|
||||||
otp_parameters {
|
|
||||||
secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106"
|
|
||||||
name: "pi@raspberrypi"
|
|
||||||
algorithm: ALGO_SHA1
|
|
||||||
digits: 1
|
|
||||||
type: OTP_TOTP
|
|
||||||
}
|
|
||||||
version: 1
|
|
||||||
batch_size: 1
|
|
||||||
batch_id: -2094403140
|
|
||||||
|
|
||||||
|
|
||||||
2. Secret
|
|
||||||
OTP enum type: OTP_TOTP
|
|
||||||
Name: pi@raspberrypi
|
Name: pi@raspberrypi
|
||||||
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
Type: totp
|
Type: totp
|
||||||
otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
|
||||||
|
|
||||||
|
|
||||||
# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
|
|
||||||
# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
|
||||||
otpauth-migration://offline?data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa%2Bf%2F%2F%2F%2F8B
|
|
||||||
|
|
||||||
3. Payload Line
|
|
||||||
otp_parameters {
|
|
||||||
secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106"
|
|
||||||
name: "pi@raspberrypi"
|
|
||||||
algorithm: ALGO_SHA1
|
|
||||||
digits: 1
|
|
||||||
type: OTP_TOTP
|
|
||||||
}
|
|
||||||
otp_parameters {
|
|
||||||
secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106"
|
|
||||||
name: "pi@raspberrypi"
|
|
||||||
issuer: "raspberrypi"
|
|
||||||
algorithm: ALGO_SHA1
|
|
||||||
digits: 1
|
|
||||||
type: OTP_TOTP
|
|
||||||
}
|
|
||||||
version: 1
|
|
||||||
batch_size: 1
|
|
||||||
batch_id: -1822886384
|
|
||||||
|
|
||||||
|
|
||||||
3. Secret
|
|
||||||
OTP enum type: OTP_TOTP
|
|
||||||
Name: pi@raspberrypi
|
Name: pi@raspberrypi
|
||||||
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
Type: totp
|
Type: totp
|
||||||
otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
|
||||||
|
|
||||||
|
|
||||||
4. Secret
|
|
||||||
OTP enum type: OTP_TOTP
|
|
||||||
Name: pi@raspberrypi
|
Name: pi@raspberrypi
|
||||||
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
Issuer: raspberrypi
|
Issuer: raspberrypi
|
||||||
Type: totp
|
Type: totp
|
||||||
otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
|
|
||||||
|
|
||||||
|
|
||||||
# otpauth://hotp/hotp%20demo?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&counter=4
|
|
||||||
otpauth-migration://offline?data=CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6%2F%2F%2F%2F%2FwE%3D
|
|
||||||
|
|
||||||
4. Payload Line
|
|
||||||
otp_parameters {
|
|
||||||
secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106"
|
|
||||||
name: "hotp demo"
|
|
||||||
algorithm: ALGO_SHA1
|
|
||||||
digits: 1
|
|
||||||
type: OTP_HOTP
|
|
||||||
counter: 4
|
|
||||||
}
|
|
||||||
version: 1
|
|
||||||
batch_size: 1
|
|
||||||
batch_id: -1558849573
|
|
||||||
|
|
||||||
|
|
||||||
5. Secret
|
|
||||||
OTP enum type: OTP_HOTP
|
|
||||||
Name: hotp demo
|
Name: hotp demo
|
||||||
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
Type: hotp
|
Type: hotp
|
||||||
Counter: 4
|
Counter: 4
|
||||||
otpauth://hotp/hotp%20demo?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&counter=4
|
|
||||||
|
|
||||||
|
|
||||||
# otpauth://totp/encoding%3A%20%C2%BF%C3%A4%C3%84%C3%A9%C3%89%3F%20%28demo%29?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
|
||||||
# Name: "encoding: ¿äÄéÉ? (demo)"
|
|
||||||
otpauth-migration://offline?data=CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv%2F%2F%2F%2F%2F%2FAQ%3D%3D
|
|
||||||
|
|
||||||
5. Payload Line
|
|
||||||
otp_parameters {
|
|
||||||
secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106"
|
|
||||||
name: "encoding: ¿äÄéÉ? (demo)"
|
|
||||||
algorithm: ALGO_SHA1
|
|
||||||
digits: 1
|
|
||||||
type: OTP_TOTP
|
|
||||||
}
|
|
||||||
version: 1
|
|
||||||
batch_size: 1
|
|
||||||
batch_id: -171198419
|
|
||||||
|
|
||||||
|
|
||||||
6. Secret
|
|
||||||
OTP enum type: OTP_TOTP
|
|
||||||
Name: encoding: ¿äÄéÉ? (demo)
|
Name: encoding: ¿äÄéÉ? (demo)
|
||||||
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
Type: totp
|
Type: totp
|
||||||
otpauth://totp/encoding%3A%20%C2%BF%C3%A4%C3%84%C3%A9%C3%89%3F%20%28demo%29?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
|
||||||
|
|
||||||
1 infile processed
|
|
||||||
|
|
|
||||||
|
|
@ -19,20 +19,24 @@
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from __future__ import annotations # workaround for PYTHON <= 3.10
|
from __future__ import annotations # workaround for PYTHON <= 3.10
|
||||||
|
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
import colorama
|
import time
|
||||||
|
|
||||||
|
import colorama
|
||||||
import pytest
|
import pytest
|
||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
|
from utils import (count_files_in_dir, file_exits,
|
||||||
import extract_otp_secrets
|
quick_and_dirty_workaround_encoding_problem,
|
||||||
from utils import (file_exits, quick_and_dirty_workaround_encoding_problem,
|
|
||||||
read_binary_file_as_stream, read_csv, read_csv_str,
|
read_binary_file_as_stream, read_csv, read_csv_str,
|
||||||
read_file_to_str, read_json, read_json_str,
|
read_file_to_str, read_json, read_json_str,
|
||||||
replace_escaped_octal_utf8_bytes_with_str, count_files_in_dir)
|
replace_escaped_octal_utf8_bytes_with_str)
|
||||||
|
|
||||||
|
import extract_otp_secrets
|
||||||
|
|
||||||
qreader_available: bool = extract_otp_secrets.qreader_available
|
qreader_available: bool = extract_otp_secrets.qreader_available
|
||||||
|
|
||||||
|
|
@ -355,17 +359,26 @@ def test_normalize_bytes() -> None:
|
||||||
'Before\\\\302\\\\277\\\\303\nname: enc: \\302\\277\\303\\244\\303\\204\\303\\251\\303\\211?\nAfter') == 'Before\\\\302\\\\277\\\\303\nname: enc: ¿äÄéÉ?\nAfter'
|
'Before\\\\302\\\\277\\\\303\nname: enc: \\302\\277\\303\\244\\303\\204\\303\\251\\303\\211?\nAfter') == 'Before\\\\302\\\\277\\\\303\nname: enc: ¿äÄéÉ?\nAfter'
|
||||||
|
|
||||||
|
|
||||||
# Generate verbose output: python3.11 src/extract_otp_secrets.py example_export.txt -v -n > tests/data/print_verbose_output.txt
|
# Generate verbose output:
|
||||||
|
# for color in '' '-n'; do for level in '' '-v' '-vv' '-vvv'; do python3.11 src/extract_otp_secrets.py example_export.txt $color $level > tests/data/print_verbose_output$color$level.txt; done; done
|
||||||
# workaround for PYTHON <= 3.10
|
# workaround for PYTHON <= 3.10
|
||||||
@pytest.mark.skipif(sys.version_info < (3, 10), reason="fileinput.input encoding exists since PYTHON 3.10")
|
@pytest.mark.skipif(sys.version_info < (3, 10), reason="fileinput.input encoding exists since PYTHON 3.10")
|
||||||
def test_extract_verbose(capsys: pytest.CaptureFixture[str], relaxed: bool) -> None:
|
@pytest.mark.parametrize("verbose_level", ['', '-v', '-vv', '-vvv'])
|
||||||
|
@pytest.mark.parametrize("color", ['', '-n'])
|
||||||
|
def test_extract_verbose(verbose_level: str, color: str, capsys: pytest.CaptureFixture[str], relaxed: bool) -> None:
|
||||||
|
args = ['example_export.txt']
|
||||||
|
if verbose_level:
|
||||||
|
args.append(verbose_level)
|
||||||
|
if color:
|
||||||
|
args.append(color)
|
||||||
# Act
|
# Act
|
||||||
extract_otp_secrets.main(['-n', '-v', 'example_export.txt'])
|
extract_otp_secrets.main(args)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
captured = capsys.readouterr()
|
captured = capsys.readouterr()
|
||||||
|
|
||||||
expected_stdout = read_file_to_str('tests/data/print_verbose_output.txt')
|
expected_stdout = normalize_verbose_text(read_file_to_str(f'tests/data/print_verbose_output{color}{verbose_level}.txt'))
|
||||||
|
actual_stdout = normalize_verbose_text(captured.out)
|
||||||
|
|
||||||
if not qreader_available:
|
if not qreader_available:
|
||||||
expected_stdout = expected_stdout.replace('QReader installed: True', 'QReader installed: False')
|
expected_stdout = expected_stdout.replace('QReader installed: True', 'QReader installed: False')
|
||||||
|
|
@ -374,12 +387,15 @@ def test_extract_verbose(capsys: pytest.CaptureFixture[str], relaxed: bool) -> N
|
||||||
if relaxed or sys.implementation.name == 'pypy':
|
if relaxed or sys.implementation.name == 'pypy':
|
||||||
print('\nRelaxed mode\n')
|
print('\nRelaxed mode\n')
|
||||||
|
|
||||||
assert replace_escaped_octal_utf8_bytes_with_str(captured.out) == replace_escaped_octal_utf8_bytes_with_str(expected_stdout)
|
assert replace_escaped_octal_utf8_bytes_with_str(actual_stdout) == replace_escaped_octal_utf8_bytes_with_str(expected_stdout)
|
||||||
assert quick_and_dirty_workaround_encoding_problem(captured.out) == quick_and_dirty_workaround_encoding_problem(expected_stdout)
|
assert quick_and_dirty_workaround_encoding_problem(actual_stdout) == quick_and_dirty_workaround_encoding_problem(expected_stdout)
|
||||||
else:
|
else:
|
||||||
assert captured.out == expected_stdout
|
assert actual_stdout == expected_stdout
|
||||||
assert captured.err == ''
|
assert captured.err == ''
|
||||||
|
|
||||||
|
def normalize_verbose_text(text: str) -> str:
|
||||||
|
return re.sub('^.+ version: .+$', '', text, flags=re.MULTILINE | re.IGNORECASE)
|
||||||
|
|
||||||
|
|
||||||
def test_extract_debug(capsys: pytest.CaptureFixture[str]) -> None:
|
def test_extract_debug(capsys: pytest.CaptureFixture[str]) -> None:
|
||||||
# Act
|
# Act
|
||||||
|
|
@ -623,7 +639,9 @@ def test_img_qr_reader_from_file_happy_path(capsys: pytest.CaptureFixture[str])
|
||||||
@pytest.mark.qreader
|
@pytest.mark.qreader
|
||||||
def test_img_qr_reader_by_parameter(capsys: pytest.CaptureFixture[str], qr_mode: str) -> None:
|
def test_img_qr_reader_by_parameter(capsys: pytest.CaptureFixture[str], qr_mode: str) -> None:
|
||||||
# Act
|
# Act
|
||||||
|
start_s = time.process_time()
|
||||||
extract_otp_secrets.main(['--qr', qr_mode, 'tests/data/test_googleauth_export.png'])
|
extract_otp_secrets.main(['--qr', qr_mode, 'tests/data/test_googleauth_export.png'])
|
||||||
|
elapsed_s = time.process_time() - start_s
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
captured = capsys.readouterr()
|
captured = capsys.readouterr()
|
||||||
|
|
@ -631,6 +649,8 @@ def test_img_qr_reader_by_parameter(capsys: pytest.CaptureFixture[str], qr_mode:
|
||||||
assert captured.out == EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT_PNG
|
assert captured.out == EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT_PNG
|
||||||
assert captured.err == ''
|
assert captured.err == ''
|
||||||
|
|
||||||
|
print(f"Elapsed time for {qr_mode}: {elapsed_s:.2f}s")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.qreader
|
@pytest.mark.qreader
|
||||||
def test_extract_multiple_files_and_mixed(capsys: pytest.CaptureFixture[str]) -> None:
|
def test_extract_multiple_files_and_mixed(capsys: pytest.CaptureFixture[str]) -> None:
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,10 @@ from utils import (Capturing, read_csv, read_file_to_str, read_json,
|
||||||
remove_dir_with_files, remove_file, count_files_in_dir)
|
remove_dir_with_files, remove_file, count_files_in_dir)
|
||||||
|
|
||||||
|
|
||||||
|
# Conditional skip example
|
||||||
|
# if sys.implementation.name == 'pypy' or sys.platform.startswith("win") or sys.version_info < (3, 10):
|
||||||
|
# self.skipTest("Avoid encoding problems")
|
||||||
|
|
||||||
class TestExtract(unittest.TestCase):
|
class TestExtract(unittest.TestCase):
|
||||||
|
|
||||||
def test_extract_csv(self) -> None:
|
def test_extract_csv(self) -> None:
|
||||||
|
|
@ -170,18 +174,6 @@ Type: totp
|
||||||
self.assertTrue(os.path.isfile('testout/qr/6-encodingäÄéÉdemo.png'))
|
self.assertTrue(os.path.isfile('testout/qr/6-encodingäÄéÉdemo.png'))
|
||||||
self.assertEqual(count_files_in_dir('testout/qr'), 6)
|
self.assertEqual(count_files_in_dir('testout/qr'), 6)
|
||||||
|
|
||||||
def test_extract_verbose(self) -> None:
|
|
||||||
if sys.implementation.name == 'pypy' or sys.platform.startswith("win") or sys.version_info < (3, 10):
|
|
||||||
self.skipTest("Avoid encoding problems")
|
|
||||||
out = io.StringIO()
|
|
||||||
with redirect_stdout(out):
|
|
||||||
extract_otp_secrets.main(['-n', '-v', 'example_export.txt'])
|
|
||||||
actual_output = out.getvalue()
|
|
||||||
|
|
||||||
expected_output = read_file_to_str('tests/data/print_verbose_output.txt')
|
|
||||||
|
|
||||||
self.assertEqual(actual_output, expected_output)
|
|
||||||
|
|
||||||
def test_extract_debug(self) -> None:
|
def test_extract_debug(self) -> None:
|
||||||
out = io.StringIO()
|
out = io.StringIO()
|
||||||
with redirect_stdout(out):
|
with redirect_stdout(out):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue