Browse code

Parse static challenge response in auth-pam plugin

If static challenge is in use, the password passed to the plugin by openvpn
is of the form "SCRV1:base64-pass:base64-response". Parse this string to
separate it into password and response and use them to respond to queries
in the pam conversation function.

On the plugin parameters line the substitution keyword for the static
challenge response is "OTP". For example, for pam config named "test" that
prompts for "user", "password" and "pin", use

plugin openvpn-auth-pam.so "test user USERNAME password PASSWORD pin OTP"

Signed-off-by: Selva Nair <selva.nair@gmail.com>

Acked-by: Gert Doering <gert@greenie.muc.de>
Message-Id: <1532486093-24793-1-git-send-email-selva.nair@gmail.com>
URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg17307.html
Signed-off-by: Gert Doering <gert@greenie.muc.de>

Selva Nair authored on 2018/07/25 11:34:53
Showing 2 changed files
... ...
@@ -36,19 +36,20 @@ pairs to answer PAM module queries.
36 36
 
37 37
 For example:
38 38
 
39
-  plugin openvpn-auth-pam.so "login login USERNAME password PASSWORD"
39
+  plugin openvpn-auth-pam.so "login login USERNAME password PASSWORD pin OTP"
40 40
 
41 41
 tells auth-pam to (a) use the "login" PAM module, (b) answer a
42
-"login" query with the username given by the OpenVPN client, and
43
-(c) answer a "password" query with the password given by the
44
-OpenVPN client.  This provides flexibility in dealing with the different
42
+"login" query with the username given by the OpenVPN client,
43
+(c) answer a "password" query with the password, and (d) answer a
44
+"pin" query with the OTP given by the OpenVPN client.
45
+This provides flexibility in dealing with different
45 46
 types of query strings which different PAM modules might generate.
46 47
 For example, suppose you were using a PAM module called
47 48
 "test" which queried for "name" rather than "login":
48 49
 
49 50
   plugin openvpn-auth-pam.so "test name USERNAME password PASSWORD"
50 51
 
51
-While "USERNAME" "COMMONNAME" and "PASSWORD" are special strings which substitute
52
+While "USERNAME" "COMMONNAME" "PASSWORD" and "OTP" are special strings which substitute
52 53
 to client-supplied values, it is also possible to name literal values
53 54
 to use as PAM module query responses.  For example, suppose that the
54 55
 login module queried for a third parameter, "domain" which
... ...
@@ -61,6 +62,10 @@ the operation of this plugin:
61 61
 
62 62
   client-cert-not-required
63 63
   username-as-common-name
64
+  static-challenge
65
+
66
+Use of --static challenege is required to pass a pin (represented by "OTP" in
67
+parameter substituion) or a second password.
64 68
 
65 69
 Run OpenVPN with --verb 7 or higher to get debugging output from
66 70
 this plugin, including the list of queries presented by the
... ...
@@ -6,6 +6,7 @@
6 6
  *             packet compression.
7 7
  *
8 8
  *  Copyright (C) 2002-2018 OpenVPN Inc <sales@openvpn.net>
9
+ *  Copyright (C) 2016-2018 Selva Nair <selva.nair@gmail.com>
9 10
  *
10 11
  *  This program is free software; you can redistribute it and/or modify
11 12
  *  it under the terms of the GNU General Public License version 2
... ...
@@ -64,6 +65,7 @@
64 64
 
65 65
 /* Pointers to functions exported from openvpn */
66 66
 static plugin_secure_memzero_t plugin_secure_memzero = NULL;
67
+static plugin_base64_decode_t plugin_base64_decode = NULL;
67 68
 
68 69
 /*
69 70
  * Plugin state, used by foreground
... ...
@@ -87,6 +89,7 @@ struct auth_pam_context
87 87
  *  "USERNAME" -- substitute client-supplied username
88 88
  *  "PASSWORD" -- substitute client-specified password
89 89
  *  "COMMONNAME" -- substitute client certificate common name
90
+ *  "OTP" -- substitute static challenge response if available
90 91
  */
91 92
 
92 93
 #define N_NAME_VALUE 16
... ...
@@ -111,6 +114,7 @@ struct user_pass {
111 111
     char username[128];
112 112
     char password[128];
113 113
     char common_name[128];
114
+    char response[128];
114 115
 
115 116
     const struct name_value_list *name_value_list;
116 117
 };
... ...
@@ -276,6 +280,66 @@ name_value_match(const char *query, const char *match)
276 276
     return strncasecmp(match, query, strlen(match)) == 0;
277 277
 }
278 278
 
279
+/*
280
+ * Split and decode up->password in the form SCRV1:base64_pass:base64_response
281
+ * into pass and response and save in up->password and up->response.
282
+ * If the password is not in the expected format, input is not changed.
283
+ */
284
+static void
285
+split_scrv1_password(struct user_pass *up)
286
+{
287
+    const int skip = strlen("SCRV1:");
288
+    if (strncmp(up->password, "SCRV1:", skip) != 0)
289
+    {
290
+        return;
291
+    }
292
+
293
+    char *tmp = strdup(up->password);
294
+    if (!tmp)
295
+    {
296
+        fprintf(stderr, "AUTH-PAM: out of memory parsing static challenge password\n");
297
+        goto out;
298
+    }
299
+
300
+    char *pass = tmp + skip;
301
+    char *resp = strchr(pass, ':');
302
+    if (!resp) /* string not in SCRV1:xx:yy format */
303
+    {
304
+        goto out;
305
+    }
306
+    *resp++ = '\0';
307
+
308
+    int n = plugin_base64_decode(pass, up->password, sizeof(up->password)-1);
309
+    if (n > 0)
310
+    {
311
+        up->password[n] = '\0';
312
+        n = plugin_base64_decode(resp, up->response, sizeof(up->response)-1);
313
+        if (n > 0)
314
+        {
315
+            up->response[n] = '\0';
316
+            if (DEBUG(up->verb))
317
+            {
318
+                fprintf(stderr, "AUTH-PAM: BACKGROUND: parsed static challenge password\n");
319
+            }
320
+            goto out;
321
+        }
322
+    }
323
+
324
+    /* decode error: reinstate original value of up->password and return */
325
+    plugin_secure_memzero(up->password, sizeof(up->password));
326
+    plugin_secure_memzero(up->response, sizeof(up->response));
327
+    strcpy(up->password, tmp); /* tmp is guaranteed to fit in up->password */
328
+
329
+    fprintf(stderr, "AUTH-PAM: base64 decode error while parsing static challenge password\n");
330
+
331
+out:
332
+    if (tmp)
333
+    {
334
+        plugin_secure_memzero(tmp, strlen(tmp));
335
+        free(tmp);
336
+    }
337
+}
338
+
279 339
 OPENVPN_EXPORT int
280 340
 openvpn_plugin_open_v3(const int v3structver,
281 341
                        struct openvpn_plugin_args_open_in const *args,
... ...
@@ -316,6 +380,7 @@ openvpn_plugin_open_v3(const int v3structver,
316 316
 
317 317
     /* Save global pointers to functions exported from openvpn */
318 318
     plugin_secure_memzero = args->callbacks->plugin_secure_memzero;
319
+    plugin_base64_decode = args->callbacks->plugin_base64_decode;
319 320
 
320 321
     /*
321 322
      * Make sure we have two string arguments: the first is the .so name,
... ...
@@ -599,6 +664,10 @@ my_conv(int n, const struct pam_message **msg_array,
599 599
                     {
600 600
                         aresp[i].resp = searchandreplace(match_value, "COMMONNAME", up->common_name);
601 601
                     }
602
+                    else if (strstr(match_value, "OTP"))
603
+                    {
604
+                        aresp[i].resp = searchandreplace(match_value, "OTP", up->response);
605
+                    }
602 606
                     else
603 607
                     {
604 608
                         aresp[i].resp = strdup(match_value);
... ...
@@ -787,6 +856,9 @@ pam_server(int fd, const char *service, int verb, const struct name_value_list *
787 787
 #endif
788 788
                 }
789 789
 
790
+                /* If password is of the form SCRV1:base64:base64 split it up */
791
+                split_scrv1_password(&up);
792
+
790 793
                 if (pam_auth(service, &up)) /* Succeeded */
791 794
                 {
792 795
                     if (send_control(fd, RESPONSE_VERIFY_SUCCEEDED) == -1)
... ...
@@ -818,10 +890,11 @@ pam_server(int fd, const char *service, int verb, const struct name_value_list *
818 818
                         command);
819 819
                 goto done;
820 820
         }
821
+        plugin_secure_memzero(up.response, sizeof(up.response));
821 822
     }
822 823
 done:
823
-
824 824
     plugin_secure_memzero(up.password, sizeof(up.password));
825
+    plugin_secure_memzero(up.response, sizeof(up.response));
825 826
 #ifdef USE_PAM_DLOPEN
826 827
     dlclose_pam();
827 828
 #endif