Skip to content

Commit

Permalink
handle not encoded + in query params, fixes #15
Browse files Browse the repository at this point in the history
- add debug level, by givein parameter -vv
- if the base64 string is not urlencoded, then + will be replaced by a space,
  what cannot be decoded anymore
  --> replace spaces back to plus
- add test
  • Loading branch information
scito committed Sep 7, 2022
1 parent df8b99d commit cd2d325
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 5 deletions.
16 changes: 11 additions & 5 deletions extract_otp_secret_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def sys_main():
def main(sys_args):
global verbose, quiet
args = parse_args(sys_args)
verbose = args.verbose
verbose = args.verbose if args.verbose else 0
quiet = args.quiet

otps = extract_otps(args)
Expand All @@ -70,7 +70,7 @@ def main(sys_args):

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('--verbose', '-v', help='verbose output', action='count')
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')
Expand Down Expand Up @@ -125,15 +125,21 @@ def extract_otps(args):


def get_payload_from_line(line, i, args):
global verbose
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 verbose > 1: print('\nDEBUG: parsed_url={}'.format(parsed_url))
params = parse_qs(parsed_url.query, strict_parsing=True)
if verbose > 1: print('\nDEBUG: querystring params={}'.format(params))
if 'data' not 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, validate=True)
data_base64 = params['data'][0]
if verbose > 1: print('\nDEBUG: data_base64={}'.format(data_base64))
data_base64_fixed = data_base64.replace(' ', '+')
if verbose > 1: print('\nDEBUG: data_base64_fixed={}'.format(data_base64))
data = base64.b64decode(data_base64_fixed, validate=True)
payload = protobuf_generated_python.google_auth_pb2.MigrationPayload()
payload.ParseFromString(data)
if verbose:
Expand Down
1 change: 1 addition & 0 deletions test/test_plus_problem_export.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
otpauth-migration://offline?data=ClEKFAciUeGF4aS6IDCvMv99ySZ1ekKsEiVTZXJlbml0eUxhYnM6dGVzdDFAc2VyZW5pdHlsYWJzLmNvLnVrGgxTZXJlbml0eUxhYnMgASgBMAIKUQoUkIY8/fbrHZWTb4CBln18lvqt0HcSJVNlcmVuaXR5TGFiczp0ZXN0MkBzZXJlbml0eWxhYnMuY28udWsaDFNlcmVuaXR5TGFicyABKAEwAgpRChScf+1/Ua4d4gCY0W/7fj9VBkM9PBIlU2VyZW5pdHlMYWJzOnRlc3QzQHNlcmVuaXR5bGFicy5jby51axoMU2VyZW5pdHlMYWJzIAEoATACClEKFG6Qu0ryTSFA/l5rmvTIXtNeb5LtEiVTZXJlbml0eUxhYnM6dGVzdDRAc2VyZW5pdHlsYWJzLmNvLnVrGgxTZXJlbml0eUxhYnMgASgBMAIQARgBIAAogtTa1vz/////AQ==
33 changes: 33 additions & 0 deletions test_extract_otp_secret_keys_pytest.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,39 @@ def test_extract_stdout(capsys):
assert captured.err == ''


def test_extract_not_encoded_plus(capsys):
# Act
extract_otp_secret_keys.main(['test/test_plus_problem_export.txt'])

# Assert
captured = capsys.readouterr()

expected_stdout = '''Name: SerenityLabs:[email protected]
Secret: A4RFDYMF4GSLUIBQV4ZP67OJEZ2XUQVM
Issuer: SerenityLabs
Type: OTP_TOTP
Name: SerenityLabs:[email protected]
Secret: SCDDZ7PW5MOZLE3PQCAZM7L4S35K3UDX
Issuer: SerenityLabs
Type: OTP_TOTP
Name: SerenityLabs:[email protected]
Secret: TR76272RVYO6EAEY2FX7W7R7KUDEGPJ4
Issuer: SerenityLabs
Type: OTP_TOTP
Name: SerenityLabs:[email protected]
Secret: N2ILWSXSJUQUB7S6NONPJSC62NPG7EXN
Issuer: SerenityLabs
Type: OTP_TOTP
'''

assert captured.out == expected_stdout
assert captured.err == ''


def test_extract_printqr(capsys):
# Act
extract_otp_secret_keys.main(['-p', 'example_export.txt'])
Expand Down
29 changes: 29 additions & 0 deletions test_extract_otp_secret_keys_unittest.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,35 @@ def test_extract_stdout_2(self):
Issuer: raspberrypi
Type: OTP_TOTP
'''
self.assertEqual(actual_output, expected_output)

def test_extract_not_encoded_plus(self):
out = io.StringIO()
with redirect_stdout(out):
extract_otp_secret_keys.main(['test/test_plus_problem_export.txt'])
actual_output = out.getvalue()

expected_output = '''Name: SerenityLabs:[email protected]
Secret: A4RFDYMF4GSLUIBQV4ZP67OJEZ2XUQVM
Issuer: SerenityLabs
Type: OTP_TOTP
Name: SerenityLabs:[email protected]
Secret: SCDDZ7PW5MOZLE3PQCAZM7L4S35K3UDX
Issuer: SerenityLabs
Type: OTP_TOTP
Name: SerenityLabs:[email protected]
Secret: TR76272RVYO6EAEY2FX7W7R7KUDEGPJ4
Issuer: SerenityLabs
Type: OTP_TOTP
Name: SerenityLabs:[email protected]
Secret: N2ILWSXSJUQUB7S6NONPJSC62NPG7EXN
Issuer: SerenityLabs
Type: OTP_TOTP
'''
self.assertEqual(actual_output, expected_output)

Expand Down

0 comments on commit cd2d325

Please sign in to comment.