diff --git a/extract_otp_secret_keys.py b/extract_otp_secret_keys.py index ed7c21d..6d049a8 100644 --- a/extract_otp_secret_keys.py +++ b/extract_otp_secret_keys.py @@ -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) @@ -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') @@ -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: diff --git a/test/test_plus_problem_export.txt b/test/test_plus_problem_export.txt new file mode 100644 index 0000000..54aa981 --- /dev/null +++ b/test/test_plus_problem_export.txt @@ -0,0 +1 @@ +otpauth-migration://offline?data=ClEKFAciUeGF4aS6IDCvMv99ySZ1ekKsEiVTZXJlbml0eUxhYnM6dGVzdDFAc2VyZW5pdHlsYWJzLmNvLnVrGgxTZXJlbml0eUxhYnMgASgBMAIKUQoUkIY8/fbrHZWTb4CBln18lvqt0HcSJVNlcmVuaXR5TGFiczp0ZXN0MkBzZXJlbml0eWxhYnMuY28udWsaDFNlcmVuaXR5TGFicyABKAEwAgpRChScf+1/Ua4d4gCY0W/7fj9VBkM9PBIlU2VyZW5pdHlMYWJzOnRlc3QzQHNlcmVuaXR5bGFicy5jby51axoMU2VyZW5pdHlMYWJzIAEoATACClEKFG6Qu0ryTSFA/l5rmvTIXtNeb5LtEiVTZXJlbml0eUxhYnM6dGVzdDRAc2VyZW5pdHlsYWJzLmNvLnVrGgxTZXJlbml0eUxhYnMgASgBMAIQARgBIAAogtTa1vz/////AQ== \ No newline at end of file diff --git a/test_extract_otp_secret_keys_pytest.py b/test_extract_otp_secret_keys_pytest.py index 2213814..cf9d987 100644 --- a/test_extract_otp_secret_keys_pytest.py +++ b/test_extract_otp_secret_keys_pytest.py @@ -88,6 +88,39 @@ Type: OTP_TOTP 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:test1@serenitylabs.co.uk +Secret: A4RFDYMF4GSLUIBQV4ZP67OJEZ2XUQVM +Issuer: SerenityLabs +Type: OTP_TOTP + +Name: SerenityLabs:test2@serenitylabs.co.uk +Secret: SCDDZ7PW5MOZLE3PQCAZM7L4S35K3UDX +Issuer: SerenityLabs +Type: OTP_TOTP + +Name: SerenityLabs:test3@serenitylabs.co.uk +Secret: TR76272RVYO6EAEY2FX7W7R7KUDEGPJ4 +Issuer: SerenityLabs +Type: OTP_TOTP + +Name: SerenityLabs:test4@serenitylabs.co.uk +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']) diff --git a/test_extract_otp_secret_keys_unittest.py b/test_extract_otp_secret_keys_unittest.py index 31f62ad..869888e 100644 --- a/test_extract_otp_secret_keys_unittest.py +++ b/test_extract_otp_secret_keys_unittest.py @@ -95,6 +95,35 @@ Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY 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:test1@serenitylabs.co.uk +Secret: A4RFDYMF4GSLUIBQV4ZP67OJEZ2XUQVM +Issuer: SerenityLabs +Type: OTP_TOTP + +Name: SerenityLabs:test2@serenitylabs.co.uk +Secret: SCDDZ7PW5MOZLE3PQCAZM7L4S35K3UDX +Issuer: SerenityLabs +Type: OTP_TOTP + +Name: SerenityLabs:test3@serenitylabs.co.uk +Secret: TR76272RVYO6EAEY2FX7W7R7KUDEGPJ4 +Issuer: SerenityLabs +Type: OTP_TOTP + +Name: SerenityLabs:test4@serenitylabs.co.uk +Secret: N2ILWSXSJUQUB7S6NONPJSC62NPG7EXN +Issuer: SerenityLabs +Type: OTP_TOTP + ''' self.assertEqual(actual_output, expected_output)