mirror of
https://github.com/scito/extract_otp_secrets.git
synced 2025-12-16 23:30:48 +01:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b54974e584 | ||
|
|
a2845556a4 | ||
|
|
7ce765ddb1 | ||
|
|
fc9b36d281 | ||
|
|
556d1350b8 | ||
|
|
a84d52fb38 |
40
Pipfile.lock
generated
40
Pipfile.lock
generated
@@ -197,21 +197,21 @@
|
||||
},
|
||||
"protobuf": {
|
||||
"hashes": [
|
||||
"sha256:0c4eec6f987338617072592b97943fdbe30d019c56126493111cf24344c1cc24",
|
||||
"sha256:135658402f71bbd49500322c0f736145731b16fc79dc8f367ab544a17eab4535",
|
||||
"sha256:27b246b3723692bf1068d5734ddaf2fccc2cdd6e0c9b47fe099244d80200593b",
|
||||
"sha256:3e6101d095dfd119513cde7259aa703d16c6bbdfae2554dfe5cfdbe94e32d548",
|
||||
"sha256:3fa2de6b8b29d12c61911505d893afe7320ce7ccba4df913e2971461fa36d584",
|
||||
"sha256:64badbc49180a5e401f373f9ce7ab1d18b63f7dd4a9cdc43c92b9f0b481cef7b",
|
||||
"sha256:70585a70fc2dd4818c51287ceef5bdba6387f88a578c86d47bb34669b5552c36",
|
||||
"sha256:712319fbdddb46f21abb66cd33cb9e491a5763b2febd8f228251add221981135",
|
||||
"sha256:91fba8f445723fcf400fdbe9ca796b19d3b1242cd873907979b9ed71e4afe868",
|
||||
"sha256:a3f6857551e53ce35e60b403b8a27b0295f7d6eb63d10484f12bc6879c715687",
|
||||
"sha256:cee1757663fa32a1ee673434fcf3bf24dd54763c79690201208bafec62f19eed"
|
||||
"sha256:0cd67a1e5c2d88930aa767f702773b2d054e29957432d7c6a18f8be02a07719a",
|
||||
"sha256:0d10091d6d03537c3f902279fcf11e95372bdd36a79556311da0487455791b20",
|
||||
"sha256:17d128eebbd5d8aee80300aed7a43a48a25170af3337f6f1333d1fac2c6839ac",
|
||||
"sha256:34a90cf30c908f47f40ebea7811f743d360e202b6f10d40c02529ebd84afc069",
|
||||
"sha256:445a0c02483869ed8513a585d80020d012c6dc60075f96fa0563a724987b1001",
|
||||
"sha256:6c3009e22717c6cc9e6594bb11ef9f15f669b19957ad4087214d69e08a213368",
|
||||
"sha256:85286a47caf63b34fa92fdc1fd98b649a8895db595cfa746c5286eeae890a0b1",
|
||||
"sha256:88c4af76a73183e21061881360240c0cdd3c39d263b4e8fb570aaf83348d608f",
|
||||
"sha256:c931c61d0cc143a2e756b1e7f8197a508de5365efd40f83c907a9febf36e6b43",
|
||||
"sha256:e467f81fdd12ded9655cea3e9b83dc319d93b394ce810b556fb0f421d8613e86",
|
||||
"sha256:ea7fb379b257911c8c020688d455e8f74efd2f734b72dc1ea4b4d7e9fd1326f2"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "python_version >= '3.8'",
|
||||
"version": "==5.28.3"
|
||||
"version": "==5.29.0"
|
||||
},
|
||||
"pyzbar": {
|
||||
"hashes": [
|
||||
@@ -240,12 +240,12 @@
|
||||
},
|
||||
"setuptools": {
|
||||
"hashes": [
|
||||
"sha256:5c4ccb41111392671f02bb5f8436dfc5a9a7185e80500531b133f5775c4163ef",
|
||||
"sha256:87cb777c3b96d638ca02031192d40390e0ad97737e27b6b4fa831bea86f2f829"
|
||||
"sha256:8199222558df7c86216af4f84c30e9b34a61d8ba19366cc914424cdbd28252f6",
|
||||
"sha256:ce74b49e8f7110f9bf04883b730f4765b774ef3ef28f722cce7c273d253aaf7d"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "python_version >= '3.9'",
|
||||
"version": "==75.5.0"
|
||||
"version": "==75.6.0"
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
@@ -444,10 +444,10 @@
|
||||
},
|
||||
"nuitka": {
|
||||
"hashes": [
|
||||
"sha256:f2499361b09727cd2e96307106ed88d67cef09d527ff1857bfec8c2d1c9ff38a"
|
||||
"sha256:52f3ec8df460d567362d3adf9e722534e07f4cc7bf51c2da874c86ba4103c6c1"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.4.11"
|
||||
"version": "==2.5.4"
|
||||
},
|
||||
"ordered-set": {
|
||||
"hashes": [
|
||||
@@ -603,12 +603,12 @@
|
||||
},
|
||||
"wheel": {
|
||||
"hashes": [
|
||||
"sha256:52f0baa5e6522155090a09c6bd95718cc46956d1b51d537ea5454249edb671c7",
|
||||
"sha256:a57353941a3183b3d5365346b567a260a0602a0f8a635926a7dede41b94c674a"
|
||||
"sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729",
|
||||
"sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "python_version >= '3.8'",
|
||||
"version": "==0.45.0"
|
||||
"version": "==0.45.1"
|
||||
},
|
||||
"zstandard": {
|
||||
"hashes": [
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
[](https://stand-with-ukraine.pp.ua)
|
||||
<!-- 
|
||||
[](https://github.com/scito/extract_otp_secrets/blob/master/Pipfile.lock)
|
||||
-->
|
||||
-->
|
||||
|
||||
<!-- [](https://GitHub.com/scito/extract_otp_secrets/releases/) -->
|
||||
|
||||
@@ -728,7 +728,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
|
||||
|
||||
The generated protobuf Python code was generated by protoc 28.3 (https://github.com/protocolbuffers/protobuf/releases/tag/v28.3).
|
||||
The generated protobuf Python code was generated by protoc 29.0 (https://github.com/protocolbuffers/protobuf/releases/tag/v29.0).
|
||||
|
||||
For Python type hint generation the [mypy-protobuf](https://github.com/nipunn1313/mypy-protobuf) package is used.
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ import argparse
|
||||
import base64
|
||||
import csv
|
||||
import fileinput
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import platform
|
||||
@@ -527,14 +528,20 @@ def extract_otps_from_files(args: Args) -> Otps:
|
||||
|
||||
files_count = urls_count = otps_count = 0
|
||||
if verbose: print(f"Input files: {args.infile}")
|
||||
for infile in args.infile:
|
||||
if verbose >= LogLevel.MORE_VERBOSE: log_verbose(f"Processing infile {infile}")
|
||||
files_count += 1
|
||||
for line in get_otp_urls_from_file(infile, args):
|
||||
if verbose >= LogLevel.MORE_VERBOSE: log_verbose(line)
|
||||
if line.startswith('#') or line == '': continue
|
||||
urls_count += 1
|
||||
otps_count += extract_otp_from_otp_url(line, otps, urls_count, infile, args)
|
||||
for infile_raw in args.infile:
|
||||
expanded_infiles = glob.glob(infile_raw)
|
||||
if not expanded_infiles:
|
||||
expanded_infiles = [infile_raw]
|
||||
if verbose >= LogLevel.DEBUG: log_debug(f"Could not expand input files, fallback to infile")
|
||||
if verbose >= LogLevel.DEBUG: log_debug(f"Expanded input files: {expanded_infiles}")
|
||||
for infile in expanded_infiles:
|
||||
if verbose >= LogLevel.MORE_VERBOSE: log_verbose(f"Processing infile {infile}")
|
||||
files_count += 1
|
||||
for line in get_otp_urls_from_file(infile, args):
|
||||
if verbose >= LogLevel.MORE_VERBOSE: log_verbose(line)
|
||||
if line.startswith('#') or line == '': continue
|
||||
urls_count += 1
|
||||
otps_count += extract_otp_from_otp_url(line, otps, urls_count, infile, args)
|
||||
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
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# NO CHECKED-IN PROTOBUF GENCODE
|
||||
# source: google_auth.proto
|
||||
# Protobuf Python Version: 5.28.3
|
||||
# Protobuf Python Version: 5.29.0
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
@@ -12,8 +12,8 @@ from google.protobuf.internal import builder as _builder
|
||||
_runtime_version.ValidateProtobufRuntimeVersion(
|
||||
_runtime_version.Domain.PUBLIC,
|
||||
5,
|
||||
28,
|
||||
3,
|
||||
29,
|
||||
0,
|
||||
'',
|
||||
'google_auth.proto'
|
||||
)
|
||||
|
||||
@@ -2,9 +2,11 @@ QReader installed: True
|
||||
CV2 version: 4.10.0
|
||||
QR reading mode: ZBAR
|
||||
|
||||
Version: extract_otp_secrets 2.8.1.post17+git.3dc7d1c2.dirty Linux x86_64 Python 3.11.9 (CPython/called as script)
|
||||
Version: extract_otp_secrets 2.8.4.post4+git.7ce765dd.dirty Linux x86_64 Python 3.11.10 (CPython/called as script)
|
||||
|
||||
Input files: ['example_export.txt']
|
||||
|
||||
DEBUG: Expanded 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/
|
||||
|
||||
@@ -2,9 +2,11 @@ QReader installed: True
|
||||
CV2 version: 4.10.0
|
||||
QR reading mode: ZBAR
|
||||
|
||||
Version: extract_otp_secrets 2.8.1.post17+git.3dc7d1c2.dirty Linux x86_64 Python 3.11.9 (CPython/called as script)
|
||||
Version: extract_otp_secrets 2.8.4.post4+git.7ce765dd.dirty Linux x86_64 Python 3.11.10 (CPython/called as script)
|
||||
|
||||
Input files: ['example_export.txt']
|
||||
[36m
|
||||
DEBUG: Expanded input files: ['example_export.txt'] [39m
|
||||
[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
|
||||
|
||||
@@ -869,19 +869,8 @@ def test_wrong_content(capsys: pytest.CaptureFixture[str]) -> None:
|
||||
# Assert
|
||||
captured = capsys.readouterr()
|
||||
|
||||
expected_stderr = '''
|
||||
WARN: input is not a otpauth-migration:// url
|
||||
source: tests/data/test_export_wrong_content.txt
|
||||
input: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
|
||||
Maybe a wrong file was given
|
||||
|
||||
ERROR: could not parse query parameter in input url
|
||||
source: tests/data/test_export_wrong_content.txt
|
||||
url: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
|
||||
'''
|
||||
|
||||
assert captured.out == ''
|
||||
assert captured.err == expected_stderr
|
||||
assert captured.err == EXPECTED_STDERR_OTP_URL_WRONG
|
||||
|
||||
|
||||
def test_one_wrong_file(capsys: pytest.CaptureFixture[str]) -> None:
|
||||
@@ -891,19 +880,8 @@ def test_one_wrong_file(capsys: pytest.CaptureFixture[str]) -> None:
|
||||
# Assert
|
||||
captured = capsys.readouterr()
|
||||
|
||||
expected_stderr = '''
|
||||
WARN: input is not a otpauth-migration:// url
|
||||
source: tests/data/test_export_wrong_content.txt
|
||||
input: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
|
||||
Maybe a wrong file was given
|
||||
|
||||
ERROR: could not parse query parameter in input url
|
||||
source: tests/data/test_export_wrong_content.txt
|
||||
url: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
|
||||
'''
|
||||
|
||||
assert captured.out == EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT
|
||||
assert captured.err == expected_stderr
|
||||
assert captured.err == EXPECTED_STDERR_OTP_URL_WRONG
|
||||
|
||||
|
||||
def test_one_wrong_file_colored(capsys: pytest.CaptureFixture[str]) -> None:
|
||||
@@ -913,19 +891,8 @@ def test_one_wrong_file_colored(capsys: pytest.CaptureFixture[str]) -> None:
|
||||
# Assert
|
||||
captured = capsys.readouterr()
|
||||
|
||||
expected_stderr = f'''{colorama.Fore.RED}
|
||||
WARN: input is not a otpauth-migration:// url
|
||||
source: tests/data/test_export_wrong_content.txt
|
||||
input: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
|
||||
Maybe a wrong file was given{colorama.Fore.RESET}
|
||||
{colorama.Fore.RED}
|
||||
ERROR: could not parse query parameter in input url
|
||||
source: tests/data/test_export_wrong_content.txt
|
||||
url: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.{colorama.Fore.RESET}
|
||||
'''
|
||||
|
||||
assert captured.out == EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT
|
||||
assert captured.err == expected_stderr
|
||||
assert captured.err == EXPECTED_STDERR_COLORED_OTP_URL_WRONG
|
||||
|
||||
|
||||
def test_one_wrong_line(capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -997,6 +964,46 @@ def test_img_qr_reader_from_file_happy_path(capsys: pytest.CaptureFixture[str])
|
||||
assert captured.err == ''
|
||||
|
||||
|
||||
@pytest.mark.qreader
|
||||
def test_img_qr_reader_but_no_otp_from_file(capsys: pytest.CaptureFixture[str]) -> None:
|
||||
# Act
|
||||
extract_otp_secrets.main(['-n', 'tests/data/qr_but_without_otp.png'])
|
||||
|
||||
# Assert
|
||||
captured = capsys.readouterr()
|
||||
|
||||
assert captured.out == ''
|
||||
assert captured.err == EXPECTED_STDERR_NO_OTP_URL
|
||||
|
||||
|
||||
@pytest.mark.qreader
|
||||
def test_img_qr_reader_from_wildcard(capsys: pytest.CaptureFixture[str]) -> None:
|
||||
# Act
|
||||
extract_otp_secrets.main(['-n', 'tests/data/*.png'])
|
||||
|
||||
# Assert
|
||||
captured = capsys.readouterr()
|
||||
|
||||
assert captured.out == EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT_PNG
|
||||
assert normalize_testfile_path(captured.err) == EXPECTED_STDERR_NO_OTP_URL
|
||||
|
||||
|
||||
def normalize_testfile_path(text: str):
|
||||
return text.replace('tests/data\\', 'tests/data/') if sys.platform.startswith("win") else text
|
||||
|
||||
|
||||
@pytest.mark.qreader
|
||||
def test_img_qr_reader_from_multiple_files(capsys: pytest.CaptureFixture[str]) -> None:
|
||||
# Act
|
||||
extract_otp_secrets.main(['-n', 'tests/data/test_googleauth_export.png', 'tests/data/text_masquerading_as_image.jpeg'])
|
||||
|
||||
# Assert
|
||||
captured = capsys.readouterr()
|
||||
|
||||
assert captured.out == EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT_PNG
|
||||
assert captured.err == EXPECTED_STDERR_BAD_IMAGE
|
||||
|
||||
|
||||
@pytest.mark.qreader
|
||||
def test_img_qr_reader_by_parameter(capsys: pytest.CaptureFixture[str], qr_mode: str) -> None:
|
||||
# Act
|
||||
@@ -1041,24 +1048,7 @@ def test_img_qr_reader_from_stdin(capsys: pytest.CaptureFixture[str], monkeypatc
|
||||
# Assert
|
||||
captured = capsys.readouterr()
|
||||
|
||||
expected_stdout = '''Name: Test1:test1@example1.com
|
||||
Secret: JBSWY3DPEHPK3PXP
|
||||
Issuer: Test1
|
||||
Type: totp
|
||||
|
||||
Name: Test2:test2@example2.com
|
||||
Secret: JBSWY3DPEHPK3PXQ
|
||||
Issuer: Test2
|
||||
Type: totp
|
||||
|
||||
Name: Test3:test3@example3.com
|
||||
Secret: JBSWY3DPEHPK3PXR
|
||||
Issuer: Test3
|
||||
Type: totp
|
||||
|
||||
'''
|
||||
|
||||
assert captured.out == expected_stdout
|
||||
assert captured.out == EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT_PNG
|
||||
assert captured.err == ''
|
||||
|
||||
|
||||
@@ -1143,19 +1133,9 @@ def test_non_image_file(capsys: pytest.CaptureFixture[str]) -> None:
|
||||
|
||||
# Assert
|
||||
captured = capsys.readouterr()
|
||||
expected_stderr = '''
|
||||
WARN: input is not a otpauth-migration:// url
|
||||
source: tests/data/text_masquerading_as_image.jpeg
|
||||
input: This is just a text file masquerading as an image file.
|
||||
Maybe a wrong file was given
|
||||
|
||||
ERROR: could not parse query parameter in input url
|
||||
source: tests/data/text_masquerading_as_image.jpeg
|
||||
url: This is just a text file masquerading as an image file.
|
||||
'''
|
||||
|
||||
assert captured.err == expected_stderr
|
||||
assert captured.out == ''
|
||||
assert captured.err == EXPECTED_STDERR_BAD_IMAGE
|
||||
|
||||
|
||||
def test_next_valid_qr_mode() -> None:
|
||||
@@ -1209,3 +1189,47 @@ Issuer: Test3
|
||||
Type: totp
|
||||
|
||||
'''
|
||||
|
||||
EXPECTED_STDERR_OTP_URL_WRONG = '''
|
||||
WARN: input is not a otpauth-migration:// url
|
||||
source: tests/data/test_export_wrong_content.txt
|
||||
input: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
|
||||
Maybe a wrong file was given
|
||||
|
||||
ERROR: could not parse query parameter in input url
|
||||
source: tests/data/test_export_wrong_content.txt
|
||||
url: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
|
||||
'''
|
||||
|
||||
EXPECTED_STDERR_COLORED_OTP_URL_WRONG = f'''{colorama.Fore.RED}
|
||||
WARN: input is not a otpauth-migration:// url
|
||||
source: tests/data/test_export_wrong_content.txt
|
||||
input: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
|
||||
Maybe a wrong file was given{colorama.Fore.RESET}
|
||||
{colorama.Fore.RED}
|
||||
ERROR: could not parse query parameter in input url
|
||||
source: tests/data/test_export_wrong_content.txt
|
||||
url: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.{colorama.Fore.RESET}
|
||||
'''
|
||||
|
||||
EXPECTED_STDERR_NO_OTP_URL = '''
|
||||
WARN: input is not a otpauth-migration:// url
|
||||
source: tests/data/qr_but_without_otp.png
|
||||
input: NOT A otpauth-migration:// URL
|
||||
Maybe a wrong file was given
|
||||
|
||||
ERROR: could not parse query parameter in input url
|
||||
source: tests/data/qr_but_without_otp.png
|
||||
url: NOT A otpauth-migration:// URL
|
||||
'''
|
||||
|
||||
EXPECTED_STDERR_BAD_IMAGE = '''
|
||||
WARN: input is not a otpauth-migration:// url
|
||||
source: tests/data/text_masquerading_as_image.jpeg
|
||||
input: This is just a text file masquerading as an image file.
|
||||
Maybe a wrong file was given
|
||||
|
||||
ERROR: could not parse query parameter in input url
|
||||
source: tests/data/text_masquerading_as_image.jpeg
|
||||
url: This is just a text file masquerading as an image file.
|
||||
'''
|
||||
|
||||
Reference in New Issue
Block a user