diff options
Diffstat (limited to 'server/mfad.py')
-rwxr-xr-x | server/mfad.py | 120 |
1 files changed, 69 insertions, 51 deletions
diff --git a/server/mfad.py b/server/mfad.py index 7a2fc40..d045e14 100755 --- a/server/mfad.py +++ b/server/mfad.py | |||
@@ -26,28 +26,54 @@ FORMAT = "utf-8" | |||
26 | AUTHED = 0 | 26 | AUTHED = 0 |
27 | DENIED = 1 | 27 | DENIED = 1 |
28 | 28 | ||
29 | # DB object index constants | ||
30 | DB_USERNAME_INDEX = 0 | ||
31 | DB_HOSTNAME_INDEX = 1 | ||
32 | DB_SERVICE_INDEX = 2 | ||
33 | DB_ALIAS_INDEX = 3 | ||
34 | DB_MFAMETHODS_INDEX = 4 | ||
35 | |||
36 | CLIENT_ALIAS_INDEX = 0 | ||
37 | CLIENT_KEY_INDEX = 1 | ||
38 | CLIENT_SECRET_INDEX = 2 | ||
39 | |||
29 | # Stores connected clients as a dictionary with the client key as the dictionary | 40 | # Stores connected clients as a dictionary with the client key as the dictionary |
30 | # key and a tuple of (socket,(addr,port)) as the value | 41 | # key and a tuple of (socket,(addr,port)) as the value |
31 | client_connections = dict() | 42 | client_connections = dict() |
32 | 43 | ||
33 | 44 | ||
34 | def eval_mfa(mfa_methods,client_response): | 45 | def eval_mfa(client_key, mfa_methods, client_response): |
46 | print("response: " + client_response) | ||
47 | print("length: " + str(len(client_response))) | ||
48 | print("methods: " + str(mfa_methods)) | ||
35 | # Evaluates MFA and decides if authenticated or denied | 49 | # Evaluates MFA and decides if authenticated or denied |
36 | # Returns 0 for authenticated on 1 for denied | 50 | # Returns 0 for authenticated on 1 for denied |
37 | if "push" in mfa_methods and client_response == "allow": | 51 | if "push" in mfa_methods and client_response == "allow": |
38 | return AUTHED | 52 | return AUTHED |
39 | elif "totp" in mfa_methods and len(client_response) == 6: | 53 | elif "totp" in mfa_methods and len(client_response) == 6: |
40 | # Only attempt to validate if response is a valid TOTP format | 54 | # Only attempt to validate if response is a valid TOTP format |
41 | totp_format = (r'(\d)(\d)(\d)(\d)(\d)(\d)') | 55 | totp_format = (r'(\d)(\d)(\d)(\d)(\d)(\d)') |
42 | totp_regex = re.compile(totp_format) | 56 | totp_regex = re.compile(totp_format) |
43 | matched = totp_regex.match(client_response) | 57 | matched = totp_regex.match(client_response) |
44 | if matched: | 58 | if matched: |
45 | return validate_totp(int(client_response)) | 59 | return validate_totp(client_key, client_response) |
46 | return DENIED | 60 | return DENIED |
47 | 61 | ||
48 | 62 | ||
49 | def validate_totp(client_response): | 63 | def validate_totp(client_key, client_response): |
50 | pass | 64 | secret = "" |
65 | with sqlite3.connect(DB_NAME) as conn: | ||
66 | c = conn.cursor() | ||
67 | c.execute("SELECT * FROM clients WHERE key=?",(client_key,)) | ||
68 | client = c.fetchone() | ||
69 | secret = client[CLIENT_SECRET_INDEX] | ||
70 | totp = pyotp.TOTP(secret) | ||
71 | print("Client Response: " + str(client_response)) | ||
72 | print("Valid TOTP: " + str(totp.now())) | ||
73 | if totp.verify(client_response): | ||
74 | return AUTHED | ||
75 | else: | ||
76 | return DENIED | ||
51 | 77 | ||
52 | 78 | ||
53 | ################################################################################ | 79 | ################################################################################ |
@@ -64,28 +90,22 @@ def get_client_key(username,hostname,service): | |||
64 | # This is done by checking the PAM request against a preconfigured | 90 | # This is done by checking the PAM request against a preconfigured |
65 | # database mapping request info (username,hostname,etc...) to clients | 91 | # database mapping request info (username,hostname,etc...) to clients |
66 | # Returns a tuple consisting of the key and approved MFA methods | 92 | # Returns a tuple consisting of the key and approved MFA methods |
67 | DB_USERNAME_INDEX = 0 | 93 | |
68 | DB_HOSTNAME_INDEX = 1 | 94 | application = None |
69 | DB_SERVICE_INDEX = 2 | 95 | client = None |
70 | DB_ALIAS_INDEX = 3 | 96 | with sqlite3.connect(DB_NAME) as conn: |
71 | DB_MFAMETHODS_INDEX = 4 | 97 | c = conn.cursor() |
72 | 98 | c.execute("""SELECT * FROM applications WHERE username=? AND hostname=? | |
73 | CLIENT_ALIAS_INDEX = 0 | 99 | AND service=?""",(username,hostname,service)) |
74 | CLIENT_KEY_INDEX = 1 | 100 | application = c.fetchone() |
75 | 101 | # Return None if no results found | |
76 | conn = sqlite3.connect(DB_NAME) | 102 | if application == None: |
77 | c = conn.cursor() | 103 | return application |
78 | c.execute("""SELECT * FROM applications WHERE username=? AND hostname=? | 104 | |
79 | AND service=?""",(username,hostname,service)) | 105 | alias = application[DB_ALIAS_INDEX] |
80 | application = c.fetchone() | 106 | c.execute("SELECT * FROM clients WHERE alias=?",(alias,)) |
81 | # Return None if no results found | 107 | client = c.fetchone() |
82 | if application == None: | 108 | |
83 | return application | ||
84 | |||
85 | alias = application[DB_ALIAS_INDEX] | ||
86 | c.execute("SELECT * FROM clients WHERE alias=?",(alias,)) | ||
87 | client = c.fetchone() | ||
88 | conn.close() | ||
89 | client_key = client[CLIENT_KEY_INDEX] | 109 | client_key = client[CLIENT_KEY_INDEX] |
90 | methods = application[DB_MFAMETHODS_INDEX] | 110 | methods = application[DB_MFAMETHODS_INDEX] |
91 | return (client_key,methods) | 111 | return (client_key,methods) |
@@ -129,11 +149,12 @@ def prompt_client(client_key, user, host, service, methods, timeout=10): | |||
129 | 149 | ||
130 | def validate_client(client_key): | 150 | def validate_client(client_key): |
131 | # Validates a client | 151 | # Validates a client |
132 | conn = sqlite3.connect(DB_NAME) | 152 | client = None |
133 | c = conn.cursor() | 153 | with sqlite3.connect(DB_NAME) as conn: |
134 | c.execute("SELECT * FROM clients WHERE key=?",(client_key,)) | 154 | c = conn.cursor() |
135 | client = c.fetchall() | 155 | c.execute("SELECT * FROM clients WHERE key=?",(client_key,)) |
136 | conn.close() | 156 | client = c.fetchall() |
157 | |||
137 | if len(client) == 0: | 158 | if len(client) == 0: |
138 | # No client matches provided key, invalid | 159 | # No client matches provided key, invalid |
139 | return False | 160 | return False |
@@ -172,7 +193,7 @@ def handle_pam(conn, addr): | |||
172 | 193 | ||
173 | # Correlate request to client | 194 | # Correlate request to client |
174 | client_key,mfa_methods = get_client_key(user,host,service) | 195 | client_key,mfa_methods = get_client_key(user,host,service) |
175 | mfa_methods = mfa_methods.split(',') | 196 | mfa_methods = mfa_methods.split(' ') |
176 | if client_key == None: | 197 | if client_key == None: |
177 | print("No applications found for user="+user+" host="+host+" service="+service) | 198 | print("No applications found for user="+user+" host="+host+" service="+service) |
178 | conn.send(str(DENIED).encode(FORMAT)) | 199 | conn.send(str(DENIED).encode(FORMAT)) |
@@ -182,8 +203,7 @@ def handle_pam(conn, addr): | |||
182 | response = prompt_client(client_key,user,host,service,mfa_methods) | 203 | response = prompt_client(client_key,user,host,service,mfa_methods) |
183 | 204 | ||
184 | # Evaluate Response | 205 | # Evaluate Response |
185 | auth_type = "push" | 206 | decision = eval_mfa(client_key, mfa_methods, response) |
186 | decision = eval_mfa(auth_type, response) | ||
187 | 207 | ||
188 | # Return response to PAM module | 208 | # Return response to PAM module |
189 | # Respone will either be 0 for authenticated and 1 for denied | 209 | # Respone will either be 0 for authenticated and 1 for denied |
@@ -209,22 +229,20 @@ def listen_pam(addr, port): | |||
209 | ################################################################################ | 229 | ################################################################################ |
210 | 230 | ||
211 | def create_db(): | 231 | def create_db(): |
212 | conn = sqlite3.connect(DB_NAME) | 232 | with sqlite3.connect(DB_NAME) as conn: |
213 | c = conn.cursor() | 233 | c = conn.cursor() |
214 | c.execute("""CREATE TABLE applications ( | 234 | c.execute("""CREATE TABLE applications ( |
215 | username text, | 235 | username text, |
216 | hostname text, | 236 | hostname text, |
217 | service text, | 237 | service text, |
218 | client_key text, | 238 | client_key text, |
219 | mfa_methods text | 239 | mfa_methods text |
220 | )""") | 240 | )""") |
221 | conn.commit() | 241 | c.execute("""CREATE TABLE clients ( |
222 | c.execute("""CREATE TABLE clients ( | 242 | alias text, |
223 | alias, | 243 | key text, |
224 | key | 244 | totp_secret text |
225 | )""") | 245 | )""") |
226 | conn.commit() | ||
227 | conn.close() | ||
228 | 246 | ||
229 | 247 | ||
230 | def main(): | 248 | def main(): |