mirror of
https://github.com/scito/extract_otp_secret_keys.git
synced 2025-12-06 06:44:57 +01:00
upgrade to protobuf 4.21.5, add quiet mode, refactor code, add unit test
- update README.txt
- rename generated code directory
- refactor code:
- extract methods
- make code testable
This commit is contained in:
parent
c344c45e04
commit
c0d1cf6c51
8 changed files with 261 additions and 321 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -5,3 +5,5 @@ venv/
|
|||
*.csv
|
||||
*.json
|
||||
!devbox.json
|
||||
!example_output.json
|
||||
!example_output.csv
|
||||
|
|
|
|||
25
README.md
25
README.md
|
|
@ -13,18 +13,15 @@ Extract two-factor authentication (2FA, TFA) secret keys from export QR codes of
|
|||
|
||||
## Dependencies
|
||||
|
||||
The protobuf package of Google for proto3 is required for running this script. protobuf >= 3.14 is recommended.
|
||||
|
||||
pip install protobuf
|
||||
pip install -r requirements.txt
|
||||
|
||||
Known to work with
|
||||
|
||||
* Python 3.6.12 and protobuf 3.14.0
|
||||
* Python 3.8.5 and protobuf 3.14.0
|
||||
* Python 3.10.6, protobuf 4.21.5, qrcode 7.3.1, and pillow 9.2
|
||||
|
||||
### Optional
|
||||
|
||||
For printing QR codes, the qrcode module is required
|
||||
For printing QR codes, the qrcode module is required, otherwise it can be omitted.
|
||||
|
||||
pip install qrcode[pil]
|
||||
|
||||
|
|
@ -33,9 +30,11 @@ For printing QR codes, the qrcode module is required
|
|||
The export QR code of "Google Authenticator" contains the URL `otpauth-migration://offline?data=...`.
|
||||
The data parameter is a base64 encoded proto3 message (Google Protocol Buffers).
|
||||
|
||||
Command for regeneration of Python code from proto3 message definition file (only necessary in case of changes of the proto3 message definition):
|
||||
Command for regeneration of Python code from proto3 message definition file (only necessary in case of changes of the proto3 message definition or new protobuf versions):
|
||||
|
||||
protoc --python_out=generated_python google_auth.proto
|
||||
protoc --python_out=protobuf_generated_python google_auth.proto
|
||||
|
||||
The generated protobuf Python code was generated by protoc 21.5 (https://github.com/protocolbuffers/protobuf/releases/tag/v21.5).
|
||||
|
||||
## References
|
||||
|
||||
|
|
@ -63,3 +62,13 @@ Install [devbox](https://github.com/jetpack-io/devbox), which is a wrapper for n
|
|||
```
|
||||
devbox shell
|
||||
```
|
||||
|
||||
## Unit Tests
|
||||
|
||||
There are basic unit tests, see `unittest_extract_otp_secret_keys.py`.
|
||||
|
||||
Run unit tests:
|
||||
|
||||
```
|
||||
python -m unittest
|
||||
```
|
||||
|
|
|
|||
5
example_output.csv
Normal file
5
example_output.csv
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
name,secret,issuer,type,url
|
||||
pi@raspberrypi,7KSQL2JTUDIS5EF65KLMRQIIGY,raspberrypi,OTP_TOTP,otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
|
||||
pi@raspberrypi,7KSQL2JTUDIS5EF65KLMRQIIGY,,OTP_TOTP,otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||
pi@raspberrypi,7KSQL2JTUDIS5EF65KLMRQIIGY,,OTP_TOTP,otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||
pi@raspberrypi,7KSQL2JTUDIS5EF65KLMRQIIGY,raspberrypi,OTP_TOTP,otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
|
||||
|
30
example_output.json
Normal file
30
example_output.json
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
[
|
||||
{
|
||||
"name": "pi@raspberrypi",
|
||||
"secret": "7KSQL2JTUDIS5EF65KLMRQIIGY",
|
||||
"issuer": "raspberrypi",
|
||||
"type": "OTP_TOTP",
|
||||
"url": "otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi"
|
||||
},
|
||||
{
|
||||
"name": "pi@raspberrypi",
|
||||
"secret": "7KSQL2JTUDIS5EF65KLMRQIIGY",
|
||||
"issuer": "",
|
||||
"type": "OTP_TOTP",
|
||||
"url": "otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY"
|
||||
},
|
||||
{
|
||||
"name": "pi@raspberrypi",
|
||||
"secret": "7KSQL2JTUDIS5EF65KLMRQIIGY",
|
||||
"issuer": "",
|
||||
"type": "OTP_TOTP",
|
||||
"url": "otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY"
|
||||
},
|
||||
{
|
||||
"name": "pi@raspberrypi",
|
||||
"secret": "7KSQL2JTUDIS5EF65KLMRQIIGY",
|
||||
"issuer": "raspberrypi",
|
||||
"type": "OTP_TOTP",
|
||||
"url": "otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi"
|
||||
}
|
||||
]
|
||||
|
|
@ -50,19 +50,7 @@ import json
|
|||
from urllib.parse import parse_qs, urlencode, urlparse, quote
|
||||
from os import path, mkdir
|
||||
from re import sub, compile as rcompile
|
||||
import generated_python.google_auth_pb2
|
||||
|
||||
arg_parser = argparse.ArgumentParser()
|
||||
arg_parser.add_argument('--verbose', '-v', help='verbose output', action='store_true')
|
||||
arg_parser.add_argument('--saveqr', '-s', help='save QR code(s) as images to the "qr" subfolder', action='store_true')
|
||||
arg_parser.add_argument('--printqr', '-p', help='print QR code(s) as text to the terminal', action='store_true')
|
||||
arg_parser.add_argument('--json', '-j', help='export to json file')
|
||||
arg_parser.add_argument('--csv', '-c', help='export to csv file')
|
||||
arg_parser.add_argument('infile', help='file or - for stdin (default: -) with "otpauth-migration://..." URLs separated by newlines, lines starting with # are ignored')
|
||||
args = arg_parser.parse_args()
|
||||
|
||||
if args.saveqr or args.printqr: from qrcode import QRCode
|
||||
verbose = args.verbose
|
||||
import protobuf_generated_python.google_auth_pb2
|
||||
|
||||
# https://stackoverflow.com/questions/40226049/find-enums-listed-in-python-descriptor-for-protobuf
|
||||
def get_enum_name_by_number(parent, field_name):
|
||||
|
|
@ -73,6 +61,7 @@ def convert_secret_from_bytes_to_base32_str(bytes):
|
|||
return str(base64.b32encode(bytes), 'utf-8').replace('=', '')
|
||||
|
||||
def save_qr(data, name):
|
||||
global verbose
|
||||
qr = QRCode()
|
||||
qr.add_data(data)
|
||||
img = qr.make_image(fill_color='black', back_color='white')
|
||||
|
|
@ -84,66 +73,107 @@ def print_qr(data):
|
|||
qr.add_data(data)
|
||||
qr.print_ascii()
|
||||
|
||||
otps = []
|
||||
|
||||
i = j = 0
|
||||
for line in (line.strip() for line in fileinput.input(args.infile)):
|
||||
if verbose: print(line)
|
||||
if line.startswith('#') or line == '': continue
|
||||
if not line.startswith('otpauth-migration://'): print('\nWARN: line is not a otpauth-migration:// URL\ninput file: {}\nline "{}"\nProbably a wrong file was given'.format(args.infile, line))
|
||||
parsed_url = urlparse(line)
|
||||
params = parse_qs(parsed_url.query)
|
||||
if not 'data' in params:
|
||||
print('\nERROR: no data query parameter in input URL\ninput file: {}\nline "{}"\nProbably a wrong file was given'.format(args.infile, line))
|
||||
def parse_args(sys_args):
|
||||
arg_parser = argparse.ArgumentParser()
|
||||
arg_parser.add_argument('--verbose', '-v', help='verbose output', action='store_true')
|
||||
arg_parser.add_argument('--quiet', '-q', help='no stdout output', action='store_true')
|
||||
arg_parser.add_argument('--saveqr', '-s', help='save QR code(s) as images to the "qr" subfolder', action='store_true')
|
||||
arg_parser.add_argument('--printqr', '-p', help='print QR code(s) as text to the terminal', action='store_true')
|
||||
arg_parser.add_argument('--json', '-j', help='export to json file')
|
||||
arg_parser.add_argument('--csv', '-c', help='export to csv file')
|
||||
arg_parser.add_argument('infile', help='file or - for stdin (default: -) with "otpauth-migration://..." URLs separated by newlines, lines starting with # are ignored')
|
||||
args = arg_parser.parse_args(sys_args)
|
||||
if args.verbose and args.quiet:
|
||||
print("The arguments --verbose and --quite are mutual exclusive.")
|
||||
sys.exit(1)
|
||||
data_encoded = params['data'][0]
|
||||
data = base64.b64decode(data_encoded)
|
||||
payload = generated_python.google_auth_pb2.MigrationPayload()
|
||||
payload.ParseFromString(data)
|
||||
i += 1
|
||||
if verbose: print('\n{}. Payload Line'.format(i), payload, sep='\n')
|
||||
return args
|
||||
|
||||
# pylint: disable=no-member
|
||||
for otp in payload.otp_parameters:
|
||||
j += 1
|
||||
if verbose: print('\n{}. Secret Key'.format(j))
|
||||
else: print()
|
||||
print('Name: {}'.format(otp.name))
|
||||
secret = convert_secret_from_bytes_to_base32_str(otp.secret)
|
||||
print('Secret: {}'.format(secret))
|
||||
if otp.issuer: print('Issuer: {}'.format(otp.issuer))
|
||||
otp_type = get_enum_name_by_number(otp, 'type')
|
||||
print('Type: {}'.format(otp_type))
|
||||
url_params = { 'secret': secret }
|
||||
if otp.type == 1: url_params['counter'] = otp.counter
|
||||
if otp.issuer: url_params['issuer'] = otp.issuer
|
||||
otp_url = 'otpauth://{}/{}?'.format('totp' if otp.type == 2 else 'hotp', quote(otp.name)) + urlencode(url_params)
|
||||
if verbose: print(otp_url)
|
||||
if args.printqr:
|
||||
print_qr(otp_url)
|
||||
if args.saveqr:
|
||||
if not(path.exists('qr')): mkdir('qr')
|
||||
pattern = rcompile(r'[\W_]+')
|
||||
file_otp_name = pattern.sub('', otp.name)
|
||||
file_otp_issuer = pattern.sub('', otp.issuer)
|
||||
save_qr(otp_url, 'qr/{}-{}{}.png'.format(j, file_otp_name, '-' + file_otp_issuer if file_otp_issuer else ''))
|
||||
def sys_main():
|
||||
main(sys.argv[1:])
|
||||
|
||||
otps.append({
|
||||
"name": otp.name,
|
||||
"secret": secret,
|
||||
"issuer": otp.issuer,
|
||||
"type": otp_type,
|
||||
"url": otp_url
|
||||
})
|
||||
def main(sys_args):
|
||||
global verbose, quiet
|
||||
args = parse_args(sys_args)
|
||||
if args.saveqr or args.printqr: from qrcode import QRCode
|
||||
verbose = args.verbose
|
||||
quiet = args.quiet
|
||||
|
||||
if args.csv and len(otps) > 0:
|
||||
with open(args.csv, "w") as outfile:
|
||||
writer = csv.DictWriter(outfile, otps[0].keys())
|
||||
writer.writeheader()
|
||||
writer.writerows(otps)
|
||||
print("Exported {} otps to csv".format(len(otps)))
|
||||
otps = extract_otps(args)
|
||||
write_csv(args, otps)
|
||||
write_json(args, otps)
|
||||
|
||||
if args.json:
|
||||
with open(args.json, "w") as outfile:
|
||||
json.dump(otps, outfile, indent = 4)
|
||||
print("Exported {} otp entries to json".format(len(otps)))
|
||||
def extract_otps(args):
|
||||
global verbose, quiet
|
||||
quiet = args.quiet
|
||||
|
||||
otps = []
|
||||
|
||||
i = j = 0
|
||||
for line in (line.strip() for line in fileinput.input(args.infile)):
|
||||
if verbose: print(line)
|
||||
if line.startswith('#') or line == '': continue
|
||||
if not line.startswith('otpauth-migration://'): print('\nWARN: line is not a otpauth-migration:// URL\ninput file: {}\nline "{}"\nProbably a wrong file was given'.format(args.infile, line))
|
||||
parsed_url = urlparse(line)
|
||||
params = parse_qs(parsed_url.query)
|
||||
if not 'data' in params:
|
||||
print('\nERROR: no data query parameter in input URL\ninput file: {}\nline "{}"\nProbably a wrong file was given'.format(args.infile, line))
|
||||
sys.exit(1)
|
||||
data_encoded = params['data'][0]
|
||||
data = base64.b64decode(data_encoded)
|
||||
payload = protobuf_generated_python.google_auth_pb2.MigrationPayload()
|
||||
payload.ParseFromString(data)
|
||||
i += 1
|
||||
if verbose: print('\n{}. Payload Line'.format(i), payload, sep='\n')
|
||||
|
||||
# pylint: disable=no-member
|
||||
for otp in payload.otp_parameters:
|
||||
j += 1
|
||||
if verbose: print('\n{}. Secret Key'.format(j))
|
||||
if not quiet: print('Name: {}'.format(otp.name))
|
||||
secret = convert_secret_from_bytes_to_base32_str(otp.secret)
|
||||
if not quiet: print('Secret: {}'.format(secret))
|
||||
if otp.issuer and not quiet: print('Issuer: {}'.format(otp.issuer))
|
||||
otp_type = get_enum_name_by_number(otp, 'type')
|
||||
if not quiet: print('Type: {}'.format(otp_type))
|
||||
url_params = { 'secret': secret }
|
||||
if otp.type == 1: url_params['counter'] = otp.counter
|
||||
if otp.issuer: url_params['issuer'] = otp.issuer
|
||||
otp_url = 'otpauth://{}/{}?'.format('totp' if otp.type == 2 else 'hotp', quote(otp.name)) + urlencode(url_params)
|
||||
if verbose: print(otp_url)
|
||||
if args.printqr:
|
||||
print_qr(otp_url)
|
||||
if args.saveqr:
|
||||
if not(path.exists('qr')): mkdir('qr')
|
||||
pattern = rcompile(r'[\W_]+')
|
||||
file_otp_name = pattern.sub('', otp.name)
|
||||
file_otp_issuer = pattern.sub('', otp.issuer)
|
||||
save_qr(otp_url, 'qr/{}-{}{}.png'.format(j, file_otp_name, '-' + file_otp_issuer if file_otp_issuer else ''))
|
||||
if not quiet: print()
|
||||
|
||||
otps.append({
|
||||
"name": otp.name,
|
||||
"secret": secret,
|
||||
"issuer": otp.issuer,
|
||||
"type": otp_type,
|
||||
"url": otp_url
|
||||
})
|
||||
return otps
|
||||
|
||||
def write_csv(args, otps):
|
||||
global verbose, quiet
|
||||
if args.csv and len(otps) > 0:
|
||||
with open(args.csv, "w") as outfile:
|
||||
writer = csv.DictWriter(outfile, otps[0].keys())
|
||||
writer.writeheader()
|
||||
writer.writerows(otps)
|
||||
if not quiet: print("Exported {} otps to csv".format(len(otps)))
|
||||
|
||||
def write_json(args, otps):
|
||||
global verbose, quiet
|
||||
if args.json:
|
||||
with open(args.json, "w") as outfile:
|
||||
json.dump(otps, outfile, indent = 4)
|
||||
if not quiet: print("Exported {} otp entries to json".format(len(otps)))
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys_main()
|
||||
|
|
|
|||
|
|
@ -1,242 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: google_auth.proto
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import message as _message
|
||||
from google.protobuf import reflection as _reflection
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor.FileDescriptor(
|
||||
name='google_auth.proto',
|
||||
package='',
|
||||
syntax='proto3',
|
||||
serialized_options=None,
|
||||
create_key=_descriptor._internal_create_key,
|
||||
serialized_pb=b'\n\x11google_auth.proto\"\xb7\x03\n\x10MigrationPayload\x12\x37\n\x0eotp_parameters\x18\x01 \x03(\x0b\x32\x1f.MigrationPayload.OtpParameters\x12\x0f\n\x07version\x18\x02 \x01(\x05\x12\x12\n\nbatch_size\x18\x03 \x01(\x05\x12\x13\n\x0b\x62\x61tch_index\x18\x04 \x01(\x05\x12\x10\n\x08\x62\x61tch_id\x18\x05 \x01(\x05\x1a\xb7\x01\n\rOtpParameters\x12\x0e\n\x06secret\x18\x01 \x01(\x0c\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0e\n\x06issuer\x18\x03 \x01(\t\x12.\n\talgorithm\x18\x04 \x01(\x0e\x32\x1b.MigrationPayload.Algorithm\x12\x0e\n\x06\x64igits\x18\x05 \x01(\x05\x12\'\n\x04type\x18\x06 \x01(\x0e\x32\x19.MigrationPayload.OtpType\x12\x0f\n\x07\x63ounter\x18\x07 \x01(\x03\",\n\tAlgorithm\x12\x10\n\x0c\x41LGO_INVALID\x10\x00\x12\r\n\tALGO_SHA1\x10\x01\"6\n\x07OtpType\x12\x0f\n\x0bOTP_INVALID\x10\x00\x12\x0c\n\x08OTP_HOTP\x10\x01\x12\x0c\n\x08OTP_TOTP\x10\x02\x62\x06proto3'
|
||||
)
|
||||
|
||||
|
||||
|
||||
_MIGRATIONPAYLOAD_ALGORITHM = _descriptor.EnumDescriptor(
|
||||
name='Algorithm',
|
||||
full_name='MigrationPayload.Algorithm',
|
||||
filename=None,
|
||||
file=DESCRIPTOR,
|
||||
create_key=_descriptor._internal_create_key,
|
||||
values=[
|
||||
_descriptor.EnumValueDescriptor(
|
||||
name='ALGO_INVALID', index=0, number=0,
|
||||
serialized_options=None,
|
||||
type=None,
|
||||
create_key=_descriptor._internal_create_key),
|
||||
_descriptor.EnumValueDescriptor(
|
||||
name='ALGO_SHA1', index=1, number=1,
|
||||
serialized_options=None,
|
||||
type=None,
|
||||
create_key=_descriptor._internal_create_key),
|
||||
],
|
||||
containing_type=None,
|
||||
serialized_options=None,
|
||||
serialized_start=361,
|
||||
serialized_end=405,
|
||||
)
|
||||
_sym_db.RegisterEnumDescriptor(_MIGRATIONPAYLOAD_ALGORITHM)
|
||||
|
||||
_MIGRATIONPAYLOAD_OTPTYPE = _descriptor.EnumDescriptor(
|
||||
name='OtpType',
|
||||
full_name='MigrationPayload.OtpType',
|
||||
filename=None,
|
||||
file=DESCRIPTOR,
|
||||
create_key=_descriptor._internal_create_key,
|
||||
values=[
|
||||
_descriptor.EnumValueDescriptor(
|
||||
name='OTP_INVALID', index=0, number=0,
|
||||
serialized_options=None,
|
||||
type=None,
|
||||
create_key=_descriptor._internal_create_key),
|
||||
_descriptor.EnumValueDescriptor(
|
||||
name='OTP_HOTP', index=1, number=1,
|
||||
serialized_options=None,
|
||||
type=None,
|
||||
create_key=_descriptor._internal_create_key),
|
||||
_descriptor.EnumValueDescriptor(
|
||||
name='OTP_TOTP', index=2, number=2,
|
||||
serialized_options=None,
|
||||
type=None,
|
||||
create_key=_descriptor._internal_create_key),
|
||||
],
|
||||
containing_type=None,
|
||||
serialized_options=None,
|
||||
serialized_start=407,
|
||||
serialized_end=461,
|
||||
)
|
||||
_sym_db.RegisterEnumDescriptor(_MIGRATIONPAYLOAD_OTPTYPE)
|
||||
|
||||
|
||||
_MIGRATIONPAYLOAD_OTPPARAMETERS = _descriptor.Descriptor(
|
||||
name='OtpParameters',
|
||||
full_name='MigrationPayload.OtpParameters',
|
||||
filename=None,
|
||||
file=DESCRIPTOR,
|
||||
containing_type=None,
|
||||
create_key=_descriptor._internal_create_key,
|
||||
fields=[
|
||||
_descriptor.FieldDescriptor(
|
||||
name='secret', full_name='MigrationPayload.OtpParameters.secret', index=0,
|
||||
number=1, type=12, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=b"",
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='name', full_name='MigrationPayload.OtpParameters.name', index=1,
|
||||
number=2, type=9, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=b"".decode('utf-8'),
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='issuer', full_name='MigrationPayload.OtpParameters.issuer', index=2,
|
||||
number=3, type=9, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=b"".decode('utf-8'),
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='algorithm', full_name='MigrationPayload.OtpParameters.algorithm', index=3,
|
||||
number=4, type=14, cpp_type=8, label=1,
|
||||
has_default_value=False, default_value=0,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='digits', full_name='MigrationPayload.OtpParameters.digits', index=4,
|
||||
number=5, type=5, cpp_type=1, label=1,
|
||||
has_default_value=False, default_value=0,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='type', full_name='MigrationPayload.OtpParameters.type', index=5,
|
||||
number=6, type=14, cpp_type=8, label=1,
|
||||
has_default_value=False, default_value=0,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='counter', full_name='MigrationPayload.OtpParameters.counter', index=6,
|
||||
number=7, type=3, cpp_type=2, label=1,
|
||||
has_default_value=False, default_value=0,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
|
||||
],
|
||||
extensions=[
|
||||
],
|
||||
nested_types=[],
|
||||
enum_types=[
|
||||
],
|
||||
serialized_options=None,
|
||||
is_extendable=False,
|
||||
syntax='proto3',
|
||||
extension_ranges=[],
|
||||
oneofs=[
|
||||
],
|
||||
serialized_start=176,
|
||||
serialized_end=359,
|
||||
)
|
||||
|
||||
_MIGRATIONPAYLOAD = _descriptor.Descriptor(
|
||||
name='MigrationPayload',
|
||||
full_name='MigrationPayload',
|
||||
filename=None,
|
||||
file=DESCRIPTOR,
|
||||
containing_type=None,
|
||||
create_key=_descriptor._internal_create_key,
|
||||
fields=[
|
||||
_descriptor.FieldDescriptor(
|
||||
name='otp_parameters', full_name='MigrationPayload.otp_parameters', index=0,
|
||||
number=1, type=11, cpp_type=10, label=3,
|
||||
has_default_value=False, default_value=[],
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='version', full_name='MigrationPayload.version', index=1,
|
||||
number=2, type=5, cpp_type=1, label=1,
|
||||
has_default_value=False, default_value=0,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='batch_size', full_name='MigrationPayload.batch_size', index=2,
|
||||
number=3, type=5, cpp_type=1, label=1,
|
||||
has_default_value=False, default_value=0,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='batch_index', full_name='MigrationPayload.batch_index', index=3,
|
||||
number=4, type=5, cpp_type=1, label=1,
|
||||
has_default_value=False, default_value=0,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='batch_id', full_name='MigrationPayload.batch_id', index=4,
|
||||
number=5, type=5, cpp_type=1, label=1,
|
||||
has_default_value=False, default_value=0,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
|
||||
],
|
||||
extensions=[
|
||||
],
|
||||
nested_types=[_MIGRATIONPAYLOAD_OTPPARAMETERS, ],
|
||||
enum_types=[
|
||||
_MIGRATIONPAYLOAD_ALGORITHM,
|
||||
_MIGRATIONPAYLOAD_OTPTYPE,
|
||||
],
|
||||
serialized_options=None,
|
||||
is_extendable=False,
|
||||
syntax='proto3',
|
||||
extension_ranges=[],
|
||||
oneofs=[
|
||||
],
|
||||
serialized_start=22,
|
||||
serialized_end=461,
|
||||
)
|
||||
|
||||
_MIGRATIONPAYLOAD_OTPPARAMETERS.fields_by_name['algorithm'].enum_type = _MIGRATIONPAYLOAD_ALGORITHM
|
||||
_MIGRATIONPAYLOAD_OTPPARAMETERS.fields_by_name['type'].enum_type = _MIGRATIONPAYLOAD_OTPTYPE
|
||||
_MIGRATIONPAYLOAD_OTPPARAMETERS.containing_type = _MIGRATIONPAYLOAD
|
||||
_MIGRATIONPAYLOAD.fields_by_name['otp_parameters'].message_type = _MIGRATIONPAYLOAD_OTPPARAMETERS
|
||||
_MIGRATIONPAYLOAD_ALGORITHM.containing_type = _MIGRATIONPAYLOAD
|
||||
_MIGRATIONPAYLOAD_OTPTYPE.containing_type = _MIGRATIONPAYLOAD
|
||||
DESCRIPTOR.message_types_by_name['MigrationPayload'] = _MIGRATIONPAYLOAD
|
||||
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
|
||||
|
||||
MigrationPayload = _reflection.GeneratedProtocolMessageType('MigrationPayload', (_message.Message,), {
|
||||
|
||||
'OtpParameters' : _reflection.GeneratedProtocolMessageType('OtpParameters', (_message.Message,), {
|
||||
'DESCRIPTOR' : _MIGRATIONPAYLOAD_OTPPARAMETERS,
|
||||
'__module__' : 'google_auth_pb2'
|
||||
# @@protoc_insertion_point(class_scope:MigrationPayload.OtpParameters)
|
||||
})
|
||||
,
|
||||
'DESCRIPTOR' : _MIGRATIONPAYLOAD,
|
||||
'__module__' : 'google_auth_pb2'
|
||||
# @@protoc_insertion_point(class_scope:MigrationPayload)
|
||||
})
|
||||
_sym_db.RegisterMessage(MigrationPayload)
|
||||
_sym_db.RegisterMessage(MigrationPayload.OtpParameters)
|
||||
|
||||
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
31
protobuf_generated_python/google_auth_pb2.py
Normal file
31
protobuf_generated_python/google_auth_pb2.py
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: google_auth.proto
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf.internal import builder as _builder
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x11google_auth.proto\"\xb7\x03\n\x10MigrationPayload\x12\x37\n\x0eotp_parameters\x18\x01 \x03(\x0b\x32\x1f.MigrationPayload.OtpParameters\x12\x0f\n\x07version\x18\x02 \x01(\x05\x12\x12\n\nbatch_size\x18\x03 \x01(\x05\x12\x13\n\x0b\x62\x61tch_index\x18\x04 \x01(\x05\x12\x10\n\x08\x62\x61tch_id\x18\x05 \x01(\x05\x1a\xb7\x01\n\rOtpParameters\x12\x0e\n\x06secret\x18\x01 \x01(\x0c\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0e\n\x06issuer\x18\x03 \x01(\t\x12.\n\talgorithm\x18\x04 \x01(\x0e\x32\x1b.MigrationPayload.Algorithm\x12\x0e\n\x06\x64igits\x18\x05 \x01(\x05\x12\'\n\x04type\x18\x06 \x01(\x0e\x32\x19.MigrationPayload.OtpType\x12\x0f\n\x07\x63ounter\x18\x07 \x01(\x03\",\n\tAlgorithm\x12\x10\n\x0c\x41LGO_INVALID\x10\x00\x12\r\n\tALGO_SHA1\x10\x01\"6\n\x07OtpType\x12\x0f\n\x0bOTP_INVALID\x10\x00\x12\x0c\n\x08OTP_HOTP\x10\x01\x12\x0c\n\x08OTP_TOTP\x10\x02\x62\x06proto3')
|
||||
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google_auth_pb2', globals())
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
|
||||
DESCRIPTOR._options = None
|
||||
_MIGRATIONPAYLOAD._serialized_start=22
|
||||
_MIGRATIONPAYLOAD._serialized_end=461
|
||||
_MIGRATIONPAYLOAD_OTPPARAMETERS._serialized_start=176
|
||||
_MIGRATIONPAYLOAD_OTPPARAMETERS._serialized_end=359
|
||||
_MIGRATIONPAYLOAD_ALGORITHM._serialized_start=361
|
||||
_MIGRATIONPAYLOAD_ALGORITHM._serialized_end=405
|
||||
_MIGRATIONPAYLOAD_OTPTYPE._serialized_start=407
|
||||
_MIGRATIONPAYLOAD_OTPTYPE._serialized_end=461
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
75
unittest_extract_otp_secret_keys.py
Normal file
75
unittest_extract_otp_secret_keys.py
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
# Unit test for extract_otp_secret_keys.py
|
||||
|
||||
# Run tests:
|
||||
# python -m unittest
|
||||
|
||||
# Author: Scito (https://scito.ch)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import unittest
|
||||
import csv
|
||||
import json
|
||||
import os
|
||||
|
||||
import extract_otp_secret_keys
|
||||
|
||||
class TestExtract(unittest.TestCase):
|
||||
|
||||
def test_extract_csv(self):
|
||||
extract_otp_secret_keys.main(['-q', '-c', 'test_example_output.csv', 'example_export.txt'])
|
||||
|
||||
expected_csv = read_csv('example_output.csv')
|
||||
actual_csv = read_csv('test_example_output.csv')
|
||||
|
||||
self.assertEqual(actual_csv, expected_csv)
|
||||
|
||||
def test_extract_json(self):
|
||||
extract_otp_secret_keys.main(['-q', '-j', 'test_example_output.json', 'example_export.txt'])
|
||||
|
||||
expected_json = read_json('example_output.json')
|
||||
actual_json = read_json('test_example_output.json')
|
||||
|
||||
self.assertEqual(actual_json, expected_json)
|
||||
|
||||
def setUp(self):
|
||||
self.cleanup()
|
||||
|
||||
def tearDown(self):
|
||||
self.cleanup()
|
||||
|
||||
def cleanup(self):
|
||||
remove_file('test_example_output.csv')
|
||||
remove_file('test_example_output.json')
|
||||
|
||||
def remove_file(filename):
|
||||
if os.path.exists(filename): os.remove(filename)
|
||||
|
||||
def read_csv(filename):
|
||||
"""Returns a list of lines."""
|
||||
with open(filename, "r") as infile:
|
||||
lines = []
|
||||
reader = csv.reader(infile)
|
||||
for line in reader:
|
||||
lines.append(line)
|
||||
return lines
|
||||
|
||||
def read_json(filename):
|
||||
"""Returns a list or a dictionary."""
|
||||
with open(filename, "r") as infile:
|
||||
return json.load(infile)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue