Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a467955b13 | ||
|
|
eea0179b5e | ||
|
|
d20d709d6e | ||
|
|
a0c21273e8 | ||
|
|
c7ab0b8ef4 | ||
|
|
8e70ae9da9 | ||
|
|
511bb4a91c | ||
|
|
15cbd77b07 | ||
|
|
2610afe5d8 | ||
|
|
8a4e2e3641 | ||
|
|
a0cf4246c1 | ||
|
|
b3c0912e1f | ||
|
|
1d8b49efcc | ||
|
|
f846ee40af | ||
|
|
f8b0283bdd | ||
|
|
bcbf189289 | ||
|
|
7c295e0963 | ||
|
|
6b72fad3c7 | ||
|
|
c937bede32 | ||
|
|
7402263dfe | ||
|
|
b721571101 | ||
|
|
72bac4d951 | ||
|
|
6c16d28194 | ||
|
|
87d2d4a05d | ||
|
|
ad3f184a56 | ||
|
|
994f96f749 | ||
|
|
8177a97d39 | ||
|
|
0c0ac817b1 | ||
|
|
5133711179 | ||
|
|
5c02e7d478 | ||
|
|
6932793236 |
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.x", "3.11", "3.9", "3.8", "3.7"]
|
python-version: ["3.x", "3.11", "3.10", "3.9", "3.8"]
|
||||||
platform: [ubuntu-latest, macos-latest, windows-latest]
|
platform: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
# exclude:
|
# exclude:
|
||||||
|
|
||||||
@@ -60,8 +60,7 @@ jobs:
|
|||||||
if: matrix.python-version == '3.x' && matrix.platform == 'ubuntu-latest'
|
if: matrix.python-version == '3.x' && matrix.platform == 'ubuntu-latest'
|
||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: pytest
|
run: pytest
|
||||||
if: (matrix.python-version != '3.x' || matrix.platform != 'ubuntu-latest')
|
if: (matrix.python-version != '3.x' || matrix.platform != 'ubuntu-latest') && (matrix.python-version != '3.10' && matrix.platform != 'macos-latest')
|
||||||
# && matrix.platform != 'macos-latest'
|
|
||||||
- name: Test with pytest (with code coverage)
|
- name: Test with pytest (with code coverage)
|
||||||
run: pytest --cov=extract_otp_secrets_test --junitxml=pytest.xml --cov-report=term-missing | tee pytest-coverage.txt
|
run: pytest --cov=extract_otp_secrets_test --junitxml=pytest.xml --cov-report=term-missing | tee pytest-coverage.txt
|
||||||
if: matrix.python-version == '3.x' && matrix.platform == 'ubuntu-latest'
|
if: matrix.python-version == '3.x' && matrix.platform == 'ubuntu-latest'
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -27,6 +27,7 @@ file_version_info.txt
|
|||||||
assets/*
|
assets/*
|
||||||
extract_otp_secrets_linux_arm64.spec
|
extract_otp_secrets_linux_arm64.spec
|
||||||
extract_otp_secrets_linux_x86_64_bullseye.spec
|
extract_otp_secrets_linux_x86_64_bullseye.spec
|
||||||
|
extract_otp_secrets_linux_x86_64_bookworm.spec
|
||||||
extract_otp_secrets_linux_x86_64.spec
|
extract_otp_secrets_linux_x86_64.spec
|
||||||
extract_otp_secrets.spec
|
extract_otp_secrets.spec
|
||||||
test.txt
|
test.txt
|
||||||
|
|||||||
1
Pipfile
1
Pipfile
@@ -7,7 +7,6 @@ name = "pypi"
|
|||||||
colorama = ">=0.4.6"
|
colorama = ">=0.4.6"
|
||||||
opencv-contrib-python = "*"
|
opencv-contrib-python = "*"
|
||||||
# for macOS: opencv-contrib-python = "<=4.7.0"
|
# for macOS: opencv-contrib-python = "<=4.7.0"
|
||||||
# for PYTHON <= 3.7: typing_extensions = "*"
|
|
||||||
pillow = "*"
|
pillow = "*"
|
||||||
pyzbar = "*"
|
pyzbar = "*"
|
||||||
protobuf = "*"
|
protobuf = "*"
|
||||||
|
|||||||
815
Pipfile.lock
generated
815
Pipfile.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@
|
|||||||

|

|
||||||
[](https://github.com/scito/extract_otp_secrets/blob/master/LICENSE)
|
[](https://github.com/scito/extract_otp_secrets/blob/master/LICENSE)
|
||||||
[](https://github.com/scito/extract_otp_secrets/releases/latest)
|
[](https://github.com/scito/extract_otp_secrets/releases/latest)
|
||||||

|

|
||||||
[](https://hub.docker.com/repository/docker/scit0/extract_otp_secrets/general)
|
[](https://hub.docker.com/repository/docker/scit0/extract_otp_secrets/general)
|
||||||
[](https://github.com/scito/extract_otp_secrets/releases/latest)
|
[](https://github.com/scito/extract_otp_secrets/releases/latest)
|
||||||
[](https://github.com/scito/extract_otp_secrets/releases/latest)
|
[](https://github.com/scito/extract_otp_secrets/releases/latest)
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
[](https://stand-with-ukraine.pp.ua)
|
[](https://stand-with-ukraine.pp.ua)
|
||||||
<!-- 
|
<!-- 
|
||||||
[](https://github.com/scito/extract_otp_secrets/blob/master/Pipfile.lock)
|
[](https://github.com/scito/extract_otp_secrets/blob/master/Pipfile.lock)
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<!-- [](https://GitHub.com/scito/extract_otp_secrets/releases/) -->
|
<!-- [](https://GitHub.com/scito/extract_otp_secrets/releases/) -->
|
||||||
|
|
||||||
@@ -367,7 +367,7 @@ python extract_otp_secrets.py = < example_export.png</pre>
|
|||||||
* macOS
|
* macOS
|
||||||
* Windows
|
* Windows
|
||||||
* Uses UTF-8 on all platforms
|
* Uses UTF-8 on all platforms
|
||||||
* Supports Python >= 3.7
|
* Supports Python >= 3.8
|
||||||
* Installation of shared system libraries is optional (🆕 since v2.3)
|
* Installation of shared system libraries is optional (🆕 since v2.3)
|
||||||
* Provides a debug mode (-d) for analyzing import problems
|
* Provides a debug mode (-d) for analyzing import problems
|
||||||
* Written in modern Python using type hints and following best practices
|
* Written in modern Python using type hints and following best practices
|
||||||
@@ -708,7 +708,7 @@ Command for regeneration of Python code from proto3 message definition file (onl
|
|||||||
|
|
||||||
protoc --plugin=protoc-gen-mypy=path/to/protoc-gen-mypy --python_out=src/protobuf_generated_python --mypy_out=src/protobuf_generated_python src/google_auth.proto
|
protoc --plugin=protoc-gen-mypy=path/to/protoc-gen-mypy --python_out=src/protobuf_generated_python --mypy_out=src/protobuf_generated_python src/google_auth.proto
|
||||||
|
|
||||||
The generated protobuf Python code was generated by protoc 22.3 (https://github.com/protocolbuffers/protobuf/releases/tag/v22.3).
|
The generated protobuf Python code was generated by protoc 24.4 (https://github.com/protocolbuffers/protobuf/releases/tag/v24.4).
|
||||||
|
|
||||||
For Python type hint generation the [mypy-protobuf](https://github.com/nipunn1313/mypy-protobuf) package is used.
|
For Python type hint generation the [mypy-protobuf](https://github.com/nipunn1313/mypy-protobuf) package is used.
|
||||||
|
|
||||||
|
|||||||
22
build.sh
22
build.sh
@@ -477,8 +477,8 @@ if $build_docker; then
|
|||||||
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
|
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
|
||||||
eval "$cmd"
|
eval "$cmd"
|
||||||
|
|
||||||
# Build extract_otp_secrets (Debian Bullseye)
|
# Build extract_otp_secrets (Debian Bookworm)
|
||||||
cmd="docker build . -t extract_otp_secrets -t extract_otp_secrets:bullseye --pull -f docker/Dockerfile --build-arg RUN_TESTS=false"
|
cmd="docker build . -t extract_otp_secrets -t extract_otp_secrets:bookworm --pull -f docker/Dockerfile --build-arg RUN_TESTS=false"
|
||||||
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
|
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
|
||||||
eval "$cmd"
|
eval "$cmd"
|
||||||
|
|
||||||
@@ -521,8 +521,8 @@ if $build_docker; then
|
|||||||
|
|
||||||
# Build executable from Docker latest
|
# Build executable from Docker latest
|
||||||
# sed "1!d" is workaround for head -n 1 since it head procduces exit code != 0
|
# sed "1!d" is workaround for head -n 1 since it head procduces exit code != 0
|
||||||
BULLSEYE_GLIBC_VERSION=$(docker run --entrypoint /bin/bash --rm extract_otp_secrets -c 'ldd --version | sed "1!d" | sed -E "s/.* ([[:digit:]]+\.[[:digit:]]+)$/\1/"')
|
BOOKWORM_GLIBC_VERSION=$(docker run --entrypoint /bin/bash --rm extract_otp_secrets -c 'ldd --version | sed "1!d" | sed -E "s/.* ([[:digit:]]+\.[[:digit:]]+)$/\1/"')
|
||||||
echo "Bullseye glibc: $BULLSEYE_GLIBC_VERSION"
|
echo "Bookworm glibc: $BOOKWORM_GLIBC_VERSION"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if $build_arm; then
|
if $build_arm; then
|
||||||
@@ -539,17 +539,17 @@ if $build_docker; then
|
|||||||
|
|
||||||
if $build_exe; then
|
if $build_exe; then
|
||||||
if $build_x86_64; then
|
if $build_x86_64; then
|
||||||
cmd="docker run --platform linux/amd64 --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets -c 'apt-get update && apt-get -y install binutils && pip install -U pip && pip install -U -r /files/requirements.txt && pip install pyinstaller && PYTHONHASHSEED=31 && pyinstaller -y --specpath installer --add-data /usr/local/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile --name extract_otp_secrets_linux_x86_64_bullseye --distpath /files/dist/ /files/src/extract_otp_secrets.py'"
|
cmd="docker run --platform linux/amd64 --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets -c 'apt-get update && apt-get -y install binutils && pip install -U pip && pip install -U -r /files/requirements.txt && pip install pyinstaller && PYTHONHASHSEED=31 && pyinstaller -y --specpath installer --add-data /usr/local/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile --name extract_otp_secrets_linux_x86_64_bookworm --distpath /files/dist/ /files/src/extract_otp_secrets.py'"
|
||||||
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
|
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
|
||||||
eval "$cmd"
|
eval "$cmd"
|
||||||
|
|
||||||
cmd="dist/extract_otp_secrets_linux_x86_64_bullseye -h"
|
cmd="dist/extract_otp_secrets_linux_x86_64_bookworm -h || echo 'Could not run exe; see error message above'"
|
||||||
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
|
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
|
||||||
eval "$cmd"
|
eval "$cmd"
|
||||||
|
|
||||||
# Build executable from Docker buster
|
# Build executable from Docker buster
|
||||||
BUSTER_GLIBC_VERSION=$(docker run --entrypoint /bin/bash --rm extract_otp_secrets:buster -c 'ldd --version | sed "1!d" | sed -E "s/.* ([[:digit:]]+\.[[:digit:]]+)$/\1/"')
|
BUSTER_GLIBC_VERSION=$(docker run --entrypoint /bin/bash --rm extract_otp_secrets:buster -c 'ldd --version | sed "1!d" | sed -E "s/.* ([[:digit:]]+\.[[:digit:]]+)$/\1/"')
|
||||||
echo "Bullseye glibc: $BUSTER_GLIBC_VERSION"
|
echo "Bookworm glibc: $BUSTER_GLIBC_VERSION"
|
||||||
|
|
||||||
cmd="docker run --platform linux/amd64 --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets:buster -c 'apt-get update && apt-get -y install binutils && pip install -U pip && pip install -U -r /files/requirements.txt && pip install pyinstaller && PYTHONHASHSEED=31 && pyinstaller -y --specpath installer --add-data /usr/local/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile --name extract_otp_secrets_linux_x86_64 --distpath /files/dist/ /files/src/extract_otp_secrets.py'"
|
cmd="docker run --platform linux/amd64 --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets:buster -c 'apt-get update && apt-get -y install binutils && pip install -U pip && pip install -U -r /files/requirements.txt && pip install pyinstaller && PYTHONHASHSEED=31 && pyinstaller -y --specpath installer --add-data /usr/local/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile --name extract_otp_secrets_linux_x86_64 --distpath /files/dist/ /files/src/extract_otp_secrets.py'"
|
||||||
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
|
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
|
||||||
@@ -574,21 +574,21 @@ if $build_docker; then
|
|||||||
|
|
||||||
if $build_nuitka_exe; then
|
if $build_nuitka_exe; then
|
||||||
if $build_x86_64; then
|
if $build_x86_64; then
|
||||||
cmd="docker run --platform linux/amd64 --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets -c 'apt-get update && apt-get -y install binutils build-essential patchelf && pip install -U pip && pip install -U -r /files/requirements.txt && pip install nuitka pyqt5 && PYTHONHASHSEED=31 && python -m nuitka --enable-plugin=tk-inter --enable-plugin=pyqt5 --include-data-dir=/usr/local/__yolo_v3_qr_detector/=__yolo_v3_qr_detector/ --onefile --output-dir=/files/build/docker/nuitka --output-filename=extract_otp_secrets_linux_x86_64_bullseye_compiled /files/src/extract_otp_secrets.py'"
|
cmd="docker run --platform linux/amd64 --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets -c 'apt-get update && apt-get -y install binutils build-essential patchelf && pip install -U pip && pip install -U -r /files/requirements.txt && pip install nuitka pyqt5 && PYTHONHASHSEED=31 && python -m nuitka --enable-plugin=tk-inter --enable-plugin=pyqt5 --include-data-dir=/usr/local/__yolo_v3_qr_detector/=__yolo_v3_qr_detector/ --onefile --output-dir=/files/build/docker/nuitka --output-filename=extract_otp_secrets_linux_x86_64_bookworm_compiled /files/src/extract_otp_secrets.py'"
|
||||||
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
|
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
|
||||||
eval "$cmd"
|
eval "$cmd"
|
||||||
|
|
||||||
cmd="build/docker/nuitka/extract_otp_secrets_linux_x86_64_bullseye_compiled -h"
|
cmd="build/docker/nuitka/extract_otp_secrets_linux_x86_64_bookworm_compiled -h"
|
||||||
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
|
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
|
||||||
eval "$cmd"
|
eval "$cmd"
|
||||||
|
|
||||||
cmd="cp build/docker/nuitka/extract_otp_secrets_linux_x86_64_bullseye_compiled dist/"
|
cmd="cp build/docker/nuitka/extract_otp_secrets_linux_x86_64_bookworm_compiled dist/"
|
||||||
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
|
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
|
||||||
eval "$cmd"
|
eval "$cmd"
|
||||||
|
|
||||||
# Build executable from Docker buster
|
# Build executable from Docker buster
|
||||||
BUSTER_GLIBC_VERSION=$(docker run --entrypoint /bin/bash --rm extract_otp_secrets:buster -c 'ldd --version | sed "1!d" | sed -E "s/.* ([[:digit:]]+\.[[:digit:]]+)$/\1/"')
|
BUSTER_GLIBC_VERSION=$(docker run --entrypoint /bin/bash --rm extract_otp_secrets:buster -c 'ldd --version | sed "1!d" | sed -E "s/.* ([[:digit:]]+\.[[:digit:]]+)$/\1/"')
|
||||||
echo "Bullseye glibc: $BUSTER_GLIBC_VERSION"
|
echo "Bookworm glibc: $BUSTER_GLIBC_VERSION"
|
||||||
|
|
||||||
cmd="docker run --platform linux/amd64 --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets:buster -c 'apt-get update && apt-get -y install binutils build-essential patchelf && pip install -U pip && pip install -U -r /files/requirements.txt && pip install nuitka pyqt5 && PYTHONHASHSEED=31 && python -m nuitka --enable-plugin=tk-inter --enable-plugin=pyqt5 --include-data-dir=/usr/local/__yolo_v3_qr_detector/=__yolo_v3_qr_detector/ --onefile --output-dir=/files/build/docker/nuitka --output-filename=extract_otp_secrets_linux_x86_64_buster_compiled /files/src/extract_otp_secrets.py'"
|
cmd="docker run --platform linux/amd64 --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets:buster -c 'apt-get update && apt-get -y install binutils build-essential patchelf && pip install -U pip && pip install -U -r /files/requirements.txt && pip install nuitka pyqt5 && PYTHONHASHSEED=31 && python -m nuitka --enable-plugin=tk-inter --enable-plugin=pyqt5 --include-data-dir=/usr/local/__yolo_v3_qr_detector/=__yolo_v3_qr_detector/ --onefile --output-dir=/files/build/docker/nuitka --output-filename=extract_otp_secrets_linux_x86_64_buster_compiled /files/src/extract_otp_secrets.py'"
|
||||||
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
|
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# --build-arg BASE_IMAGE=python:3.11-slim-buster
|
# --build-arg BASE_IMAGE=python:3.11-slim-buster
|
||||||
ARG BASE_IMAGE=python:3.11-slim-bullseye
|
ARG BASE_IMAGE=python:3.11-slim-bookworm
|
||||||
FROM $BASE_IMAGE
|
FROM $BASE_IMAGE
|
||||||
|
|
||||||
# https://docs.docker.com/engine/reference/builder/
|
# https://docs.docker.com/engine/reference/builder/
|
||||||
|
|||||||
3
mypy.ini
3
mypy.ini
@@ -1,4 +1 @@
|
|||||||
[mypy]
|
[mypy]
|
||||||
|
|
||||||
[mypy-protobuf_generated_python.*]
|
|
||||||
ignore_errors = True
|
|
||||||
|
|||||||
@@ -47,9 +47,6 @@ dependencies = [
|
|||||||
"pyzbar",
|
"pyzbar",
|
||||||
"qrcode",
|
"qrcode",
|
||||||
"qreader<2.0.0",
|
"qreader<2.0.0",
|
||||||
# workaround for PYTHON <= 3.7: compatibility
|
|
||||||
"typing_extensions; python_version<='3.7'",
|
|
||||||
"importlib_metadata; python_version<='3.7'",
|
|
||||||
]
|
]
|
||||||
description = "Extracts one time password (OTP) secrets from QR codes exported by two-factor authentication (2FA) apps such as 'Google Authenticator'"
|
description = "Extracts one time password (OTP) secrets from QR codes exported by two-factor authentication (2FA) apps such as 'Google Authenticator'"
|
||||||
dynamic = ["version"]
|
dynamic = ["version"]
|
||||||
|
|||||||
@@ -43,35 +43,23 @@ import re
|
|||||||
import sys
|
import sys
|
||||||
import urllib.parse as urlparse
|
import urllib.parse as urlparse
|
||||||
from enum import Enum, IntEnum
|
from enum import Enum, IntEnum
|
||||||
from typing import Any, List, Optional, Sequence, TextIO, Tuple, Union, TYPE_CHECKING
|
from importlib.metadata import PackageNotFoundError, version
|
||||||
|
from typing import (Any, Final, List, Optional, Sequence, TextIO, Tuple,
|
||||||
|
TypedDict, Union)
|
||||||
|
|
||||||
import colorama
|
import colorama
|
||||||
from pkg_resources import DistributionNotFound, get_distribution
|
|
||||||
from qrcode import QRCode # type: ignore
|
from qrcode import QRCode # type: ignore
|
||||||
|
|
||||||
import protobuf_generated_python.google_auth_pb2 as pb
|
import protobuf_generated_python.google_auth_pb2 as pb
|
||||||
|
|
||||||
# workaround for PYTHON <= 3.7: compatibility
|
|
||||||
if sys.version_info >= (3, 8):
|
|
||||||
from typing import Final, TypedDict
|
|
||||||
else:
|
|
||||||
from typing_extensions import Final, TypedDict
|
|
||||||
|
|
||||||
# workaround for PYTHON <= 3.7: compatibility
|
|
||||||
if sys.version_info >= (3, 8):
|
|
||||||
from importlib.metadata import PackageNotFoundError, version
|
|
||||||
else:
|
|
||||||
from importlib_metadata import PackageNotFoundError, version
|
|
||||||
|
|
||||||
|
|
||||||
debug_mode = '-d' in sys.argv[1:] or '--debug' in sys.argv[1:]
|
debug_mode = '-d' in sys.argv[1:] or '--debug' in sys.argv[1:]
|
||||||
quiet = '-q' in sys.argv[1:] or '--quiet' in sys.argv[1:]
|
quiet = '-q' in sys.argv[1:] or '--quiet' in sys.argv[1:]
|
||||||
headless: bool = False
|
headless: bool = False
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import cv2 # type: ignore # TODO use cv2 types if available
|
import cv2
|
||||||
import numpy as np # TODO use numpy types if available
|
import numpy as np
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import tkinter
|
import tkinter
|
||||||
@@ -104,9 +92,8 @@ Exception: {e}\n""", file=sys.stderr)
|
|||||||
FONT_SCALE: Final[float] = 1.3
|
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)
|
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
|
|
||||||
WINDOW_X: Final[int] = 0
|
WINDOW_X: Final[int] = 0
|
||||||
WINDOW_Y: Final[int] = 1
|
WINDOW_Y: Final[int] = 1
|
||||||
WINDOW_WIDTH: Final[int] = 2
|
WINDOW_WIDTH: Final[int] = 2
|
||||||
@@ -115,10 +102,10 @@ Exception: {e}\n""", file=sys.stderr)
|
|||||||
TEXT_HEIGHT: Final[int] = 1
|
TEXT_HEIGHT: Final[int] = 1
|
||||||
BORDER: Final[int] = 5
|
BORDER: Final[int] = 5
|
||||||
START_Y: Final[int] = 20
|
START_Y: Final[int] = 20
|
||||||
START_POS_TEXT: Final[Point] = (BORDER, START_Y)
|
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
|
||||||
CHAR_DX: Final[int] = (lambda text: cv2.getTextSize(text, FONT, FONT_SCALE, FONT_THICKNESS)[0][TEXT_WIDTH] // len(text))("28 QR codes capturedMMM")
|
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
|
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"
|
||||||
@@ -131,12 +118,12 @@ except ImportError as e:
|
|||||||
if debug_mode:
|
if debug_mode:
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
# Workaround for PYTHON <= 3.9: Union[int, None] used instead of int | None
|
# Workaround for PYTHON <= 3.9: Generally Union[int, None] used instead of int | None
|
||||||
|
|
||||||
# Types
|
# Types
|
||||||
Args = argparse.Namespace
|
Args = argparse.Namespace
|
||||||
OtpUrl = str
|
OtpUrl = str
|
||||||
# workaround for PYTHON <= 3.7: Otp = TypedDict('Otp', {'name': str, 'secret': str, 'issuer': str, 'type': str, 'counter': int | None, 'url': OtpUrl})
|
# Workaround for PYTHON <= 3.9: Otp = TypedDict('Otp', {'name': str, 'secret': str, 'issuer': str, 'type': str, 'counter': int | None, 'url': OtpUrl})
|
||||||
Otp = TypedDict('Otp', {'name': str, 'secret': str, 'issuer': str, 'type': str, 'counter': Union[int, None], 'url': OtpUrl})
|
Otp = TypedDict('Otp', {'name': str, 'secret': str, 'issuer': str, 'type': str, 'counter': Union[int, None], 'url': OtpUrl})
|
||||||
# workaround for PYTHON <= 3.9: Otps = list[Otp]
|
# workaround for PYTHON <= 3.9: Otps = list[Otp]
|
||||||
Otps = List[Otp]
|
Otps = List[Otp]
|
||||||
@@ -276,9 +263,7 @@ def extract_otp_from_otp_url(otpauth_migration_url: str, otps: Otps, urls_count:
|
|||||||
def parse_args(sys_args: list[str]) -> Args:
|
def parse_args(sys_args: list[str]) -> Args:
|
||||||
global verbose, quiet, colored
|
global verbose, quiet, colored
|
||||||
|
|
||||||
# For PYTHON <= 3.7: Use :=
|
cmd = f"python {name}" if (name := os.path.basename(sys.argv[0])).endswith('.py') else f"{name}"
|
||||||
name = os.path.basename(sys.argv[0])
|
|
||||||
cmd = f"python {name}" if name.endswith('.py') else f"{name}"
|
|
||||||
description_text = "Extracts one time password (OTP) secrets from QR codes exported by two-factor authentication (2FA) apps"
|
description_text = "Extracts one time password (OTP) secrets from QR codes exported by two-factor authentication (2FA) apps"
|
||||||
if cv2_available:
|
if cv2_available:
|
||||||
description_text += "\nIf no infiles are provided, a GUI window starts and QR codes are captured from the camera."
|
description_text += "\nIf no infiles are provided, a GUI window starts and QR codes are captured from the camera."
|
||||||
@@ -325,7 +310,7 @@ b) image file containing a QR code or = for stdin for an image containing a QR c
|
|||||||
quiet = True if args.quiet else False
|
quiet = True if args.quiet else False
|
||||||
if verbose: print(f"QReader installed: {cv2_available}")
|
if verbose: print(f"QReader installed: {cv2_available}")
|
||||||
if cv2_available:
|
if cv2_available:
|
||||||
if verbose >= LogLevel.VERBOSE: print(f"CV2 version: {cv2.__version__}")
|
if verbose >= LogLevel.VERBOSE: print(f"CV2 version: {cv2.__version__}") # type: ignore # cv2.__version__ is not available
|
||||||
if verbose: print(f"QR reading mode: {args.qr}\n")
|
if verbose: print(f"QR reading mode: {args.qr}\n")
|
||||||
|
|
||||||
return args
|
return args
|
||||||
@@ -420,7 +405,7 @@ def get_color(new_otps_count: int, otp_url: str) -> ColorBGR:
|
|||||||
|
|
||||||
|
|
||||||
# TODO use cv2 types if available
|
# TODO use cv2 types if available
|
||||||
def cv2_draw_box(img: Any, raw_pts: Any, color: ColorBGR) -> Any:
|
def cv2_draw_box(img: list[tuple[Any, Any]], raw_pts: list[tuple[Any, Any]], color: ColorBGR) -> np.ndarray[Any, np.dtype[np.int32]]:
|
||||||
pts = np.array([raw_pts], np.int32)
|
pts = np.array([raw_pts], np.int32)
|
||||||
pts = pts.reshape((-1, 1, 2))
|
pts = pts.reshape((-1, 1, 2))
|
||||||
cv2.polylines(img, [pts], True, color, BOX_THICKNESS)
|
cv2.polylines(img, [pts], True, color, BOX_THICKNESS)
|
||||||
@@ -428,7 +413,7 @@ 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, opposite_len: Optional[int] = None) -> None:
|
def cv2_print_text(img: cv2.UMat, text: str, line_number: int, position: TextPosition, color: ColorBGR, opposite_len: Optional[int] = None) -> None:
|
||||||
window_dim = cv2.getWindowImageRect(WINDOW_NAME)
|
window_dim = cv2.getWindowImageRect(WINDOW_NAME)
|
||||||
out_text = text
|
out_text = text
|
||||||
if opposite_len:
|
if opposite_len:
|
||||||
@@ -735,8 +720,8 @@ def write_keepass_csv(file: str, otps: Otps) -> None:
|
|||||||
if has_hotp:
|
if has_hotp:
|
||||||
count_hotp_entries = write_keepass_htop_csv(otp_filename_hotp, otps)
|
count_hotp_entries = write_keepass_htop_csv(otp_filename_hotp, otps)
|
||||||
if not quiet:
|
if not quiet:
|
||||||
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 has_totp and 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: print(f"Exported {count_hotp_entries} hotp entrie{'s'[:count_hotp_entries != 1]} to keepass csv file {otp_filename_hotp}")
|
if has_hotp and 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(file: str, otps: Otps) -> int:
|
def write_keepass_totp_csv(file: str, otps: Otps) -> int:
|
||||||
@@ -898,19 +883,7 @@ def get_raw_version() -> str:
|
|||||||
return __version__
|
return __version__
|
||||||
except PackageNotFoundError:
|
except PackageNotFoundError:
|
||||||
# package is not installed
|
# package is not installed
|
||||||
pass
|
return ''
|
||||||
|
|
||||||
# In some cases importlib cannot properly detect package version, for example it was compiled into executable file, so it uses some custom import mechanism.
|
|
||||||
# Instead, use pkg_resources which is included in setuptools (but has a significant runtime cost)
|
|
||||||
|
|
||||||
try:
|
|
||||||
__version__ = get_distribution("package-name").version
|
|
||||||
return __version__
|
|
||||||
except DistributionNotFound:
|
|
||||||
# package is not installed
|
|
||||||
pass
|
|
||||||
|
|
||||||
return ''
|
|
||||||
|
|
||||||
|
|
||||||
# workaround for PYTHON <= 3.9 use: BaseException | None
|
# workaround for PYTHON <= 3.9 use: BaseException | None
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||||
# source: google_auth.proto
|
# source: google_auth.proto
|
||||||
"""Generated protocol buffer code."""
|
"""Generated protocol buffer code."""
|
||||||
from google.protobuf.internal import builder as _builder
|
|
||||||
from google.protobuf import descriptor as _descriptor
|
from google.protobuf import descriptor as _descriptor
|
||||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||||
from google.protobuf import symbol_database as _symbol_database
|
from google.protobuf import symbol_database as _symbol_database
|
||||||
|
from google.protobuf.internal import builder as _builder
|
||||||
# @@protoc_insertion_point(imports)
|
# @@protoc_insertion_point(imports)
|
||||||
|
|
||||||
_sym_db = _symbol_database.Default()
|
_sym_db = _symbol_database.Default()
|
||||||
@@ -19,7 +19,6 @@ _globals = globals()
|
|||||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google_auth_pb2', _globals)
|
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google_auth_pb2', _globals)
|
||||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||||
|
|
||||||
DESCRIPTOR._options = None
|
DESCRIPTOR._options = None
|
||||||
_globals['_MIGRATIONPAYLOAD']._serialized_start=22
|
_globals['_MIGRATIONPAYLOAD']._serialized_start=22
|
||||||
_globals['_MIGRATIONPAYLOAD']._serialized_end=461
|
_globals['_MIGRATIONPAYLOAD']._serialized_end=461
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class MigrationPayload(google.protobuf.message.Message):
|
|||||||
ValueType = typing.NewType("ValueType", builtins.int)
|
ValueType = typing.NewType("ValueType", builtins.int)
|
||||||
V: typing_extensions.TypeAlias = ValueType
|
V: typing_extensions.TypeAlias = ValueType
|
||||||
|
|
||||||
class _AlgorithmEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[MigrationPayload._Algorithm.ValueType], builtins.type): # noqa: F821
|
class _AlgorithmEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[MigrationPayload._Algorithm.ValueType], builtins.type):
|
||||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||||
ALGO_INVALID: MigrationPayload._Algorithm.ValueType # 0
|
ALGO_INVALID: MigrationPayload._Algorithm.ValueType # 0
|
||||||
ALGO_SHA1: MigrationPayload._Algorithm.ValueType # 1
|
ALGO_SHA1: MigrationPayload._Algorithm.ValueType # 1
|
||||||
@@ -41,7 +41,7 @@ class MigrationPayload(google.protobuf.message.Message):
|
|||||||
ValueType = typing.NewType("ValueType", builtins.int)
|
ValueType = typing.NewType("ValueType", builtins.int)
|
||||||
V: typing_extensions.TypeAlias = ValueType
|
V: typing_extensions.TypeAlias = ValueType
|
||||||
|
|
||||||
class _OtpTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[MigrationPayload._OtpType.ValueType], builtins.type): # noqa: F821
|
class _OtpTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[MigrationPayload._OtpType.ValueType], builtins.type):
|
||||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||||
OTP_INVALID: MigrationPayload._OtpType.ValueType # 0
|
OTP_INVALID: MigrationPayload._OtpType.ValueType # 0
|
||||||
OTP_HOTP: MigrationPayload._OtpType.ValueType # 1
|
OTP_HOTP: MigrationPayload._OtpType.ValueType # 1
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ from utils import (count_files_in_dir, file_exits, read_binary_file_as_stream,
|
|||||||
import extract_otp_secrets
|
import extract_otp_secrets
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import cv2 # type: ignore
|
import cv2
|
||||||
from extract_otp_secrets import SUCCESS_COLOR, FAILURE_COLOR, FONT, FONT_SCALE, FONT_COLOR, FONT_THICKNESS, FONT_LINE_STYLE
|
from extract_otp_secrets import SUCCESS_COLOR, FAILURE_COLOR, FONT, FONT_SCALE, FONT_COLOR, FONT_THICKNESS, FONT_LINE_STYLE
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# ignore
|
# ignore
|
||||||
|
|||||||
Reference in New Issue
Block a user