summaryrefslogtreecommitdiff
path: root/server/mfad.py
diff options
context:
space:
mode:
Diffstat (limited to 'server/mfad.py')
-rwxr-xr-xserver/mfad.py120
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"
26AUTHED = 0 26AUTHED = 0
27DENIED = 1 27DENIED = 1
28 28
29# DB object index constants
30DB_USERNAME_INDEX = 0
31DB_HOSTNAME_INDEX = 1
32DB_SERVICE_INDEX = 2
33DB_ALIAS_INDEX = 3
34DB_MFAMETHODS_INDEX = 4
35
36CLIENT_ALIAS_INDEX = 0
37CLIENT_KEY_INDEX = 1
38CLIENT_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
31client_connections = dict() 42client_connections = dict()
32 43
33 44
34def eval_mfa(mfa_methods,client_response): 45def 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
49def validate_totp(client_response): 63def 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
130def validate_client(client_key): 150def 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
211def create_db(): 231def 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
230def main(): 248def main():