1+ from abc import ABC , abstractmethod
12import os
23import re
34import shlex
@@ -37,25 +38,42 @@ class KeyStoreKeyNotFoundException(Exception):
3738 pass
3839
3940
40- def get_key (label : str ) -> bytearray :
41+ class AbstractSubprocessRunner (ABC ):
42+ """
43+ Wrapper class to enable easier unit testing
44+ """
45+
46+ @abstractmethod
47+ def run (self , command : list [str ]) -> str :
48+ ...
49+
50+
51+ class SubprocessRunner (AbstractSubprocessRunner ):
52+ """
53+ Runs generic `subprocess` implementation
54+ """
55+
56+ def run (self , command : list [str ]) -> str :
57+ return subprocess .run (command , capture_output = True , text = True ).stdout
58+
59+
60+ def get_key (label : str , runner : AbstractSubprocessRunner = SubprocessRunner ()) -> bytearray :
4161 """
4262 Tries to extract the key via the command `security find-generic-password -l <label> -w`.
4363
4464 :throws KeyStoreKeyNotFoundException: when not found or unable to extract
4565 """
4666 try :
47- result = subprocess .run (
48- [
49- "security" ,
50- "find-generic-password" ,
51- "-l" ,
52- label ,
53- "-w"
54- ],
55- capture_output = True ,
56- text = True
57- )
58- key_in_hex_format : str = result .stdout
67+ key_in_hex_format : str = runner .run ([
68+ "security" ,
69+ "find-generic-password" ,
70+ "-l" ,
71+ label ,
72+ "-w"
73+ ])
74+
75+ if len (key_in_hex_format .strip ()) == 0 :
76+ raise KeyStoreKeyNotFoundException ("security command output with flag -w returned empty" )
5977
6078 return extract_key (key_in_hex_format )
6179
@@ -72,7 +90,7 @@ def extract_key(key_in_hex_format: str) -> bytearray:
7290 return key
7391
7492
75- def get_key_from_full_output (label : str ):
93+ def get_key_from_full_output (label : str , runner : AbstractSubprocessRunner = SubprocessRunner () ):
7694 """
7795 Tries to extract the key via the command `security find-generic-password -l <label>`.
7896
@@ -81,17 +99,16 @@ def get_key_from_full_output(label: str):
8199 :throws KeyStoreKeyNotFoundException: when not found in output or unable to extract
82100 """
83101 try :
84- result = subprocess .run (
85- [
86- "security" ,
87- "find-generic-password" ,
88- "-l" ,
89- label
90- ],
91- capture_output = True ,
92- text = True
93- )
94- full_key_info : str = result .stdout
102+ full_key_info : str = runner .run ([
103+ "security" ,
104+ "find-generic-password" ,
105+ "-l" ,
106+ label
107+ ])
108+
109+ if len (full_key_info .strip ()) == 0 :
110+ raise KeyStoreKeyNotFoundException ("security command full output returned empty" )
111+
95112 return extract_gena_key (full_key_info )
96113 except Exception as e :
97114 raise KeyStoreKeyNotFoundException ("Failed to retrieve keystore value" ) from e
@@ -137,6 +154,14 @@ def extract_gena_key(output: str):
137154 return key
138155
139156
157+ def get_key_fallback (label : str , runner : AbstractSubprocessRunner = SubprocessRunner ()) -> bytearray :
158+ try :
159+ return get_key (label , runner )
160+ except KeyStoreKeyNotFoundException :
161+ # Fallback to alternate solution (see issue #13)
162+ return get_key_from_full_output (label , runner )
163+
164+
140165def decrypt_plist (in_file_path : str , key : bytearray ) -> dict :
141166 """
142167 Given an encrypted plist file at path `in_file_path`, decrypt it using `key` and AES-GMC and return
0 commit comments