1 /* zxpasswd.c  -  Password creation and user management tool
2  * Copyright (c) 2012-2015 Synergetics SA (sampo@synergetics.be), All Rights Reserved.
3  * Copyright (c) 2009-2011 Sampo Kellomaki (sampo@iki.fi), All Rights Reserved.
4  * This is confidential unpublished proprietary source code of the author.
5  * NO WARRANTY, not even implied warranties. Contains trade secrets.
6  * Distribution prohibited unless authorized in writing.
7  * Licensed under Apache License 2.0, see file COPYING.
8  * $Id: zxpasswd.c,v 1.6 2010-01-08 02:10:09 sampo Exp $
9  *
10  * 18.10.2009, created --Sampo
11  * 14.11.2009, added yubikey support --Sampo
12  * 16.9.2010,  added support for traditional Unix crypt(3) hashed passwords --Sampo
13  * 1.2.2011,   tweaked -at option --Sampo
14  * 5.2.2012,   changed -c flag to -n to reserve -c for config (to be consistent with other utils) --Sampo
15  * 24.4.2012,  obsoleted PATH=/var/zxid/idp. From now on, just use /var/zxid/ or VPATH --Sampo
16  * 29.5.2015,  added generation of PINs --Sampo
17  *
18  * See also: http://www.users.zetnet.co.uk/hopwood/crypto/scan/ph.html
19  * http://www.usenix.org/events/usenix99/provos/provos_html/index.html
20  * http://www.koders.com/c/fid18C2933FE8729E3DBC6E9B1DEB65D282560D4B14.aspx?s=md5
21  * zxid_pw_authn() in zxiduser.c
22  * phd/sampo-idp-disco-encfs-kbdtok-2009.pd
23  */
24 
25 #include "platform.h"  /* for dirent.h */
26 
27 #include <string.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <errno.h>
31 #include <sys/types.h>
32 #include <signal.h>
33 #include <fcntl.h>
34 #ifndef MINGW
35 #include <sys/stat.h>
36 #endif
37 
38 #ifdef USE_OPENSSL
39 #include <openssl/des.h>
40 #endif
41 
42 #include "errmac.h"
43 #include "zx.h"
44 #include "zxid.h"
45 #include "zxidutil.h"
46 #include "zxidconf.h"
47 #include "c/zxidvers.h"
48 #include "c/zx-ns.h"
49 #include "yubikey.h"
50 
51 #define UDIR "/var/zxid/uid/"
52 
53 char* help =
54 "zxpasswd  -  Password creation and user management tool R" ZXID_REL "\n\
55 Copyright (c) 2012-2015 Synergetics SA (sampo@synergetics.be), All Rights Reserved.\n\
56 Copyright (c) 2009-2011 Sampo Kellomaki (sampo@iki.fi), All Rights Reserved.\n\
57 NO WARRANTY, not even implied warranties. Licensed under Apache License v2.0\n\
58 See http://www.apache.org/licenses/LICENSE-2.0\n\
59 Send well researched bug reports to the author. Home: zxid.org\n\
60 \n\
61 Usage: zxpasswd [options] user [udir] <passwd      # Set user's password\n\
62        zxpasswd [options] -new user [udir] <passwd # Create user and set password\n\
63        zxpasswd [options] -a   user [udir] <passwd # Authenticate as user using pw\n\
64        zxpasswd [options] -l   [user [udir]]       # List information about user\n\
65   [udir]           Specify zxididp user directory. Default " UDIR "\n\
66   -new             Create New user\n\
67   -at 'attr: val'  Append attribute(s) to .bs/.at\n\
68   -s exist_uid     Symlink user to an existing user (e.g. yubikey alias)\n\
69   -a               Authenticate as user. exit(2) value 0 means success\n\
70   -l               List user info. If no user is specified, lists all users.\n\
71   -t N             Choose password hash type: 0=plain, 1=MD5 (default), y=yubikey\n\
72   -p               The password is considered as PIN and written in .pin\n\
73   -basic uid:pw    Print HTTP Basic Authenication blob, i.e. URI encoded base64 over uid:pw\n\
74   -v               Verbose messages.\n\
75   -q               Be extra quiet.\n\
76   -d               Turn on debugging.\n\
77   -h               This help message\n\
78   --               End of options\n\
79 \n\
80 For Yubikey (yubico.com) authentication (-a), supply the yubikey ticket\n\
81 as user and omit the password. For creating account or changing password,\n\
82 use -t y to indicate that you pass yubikey AES128 shared key in hex as password.\n";
83 
84 int verbose = 1;
85 int create = 0;
86 int an = 0;
87 int list = 0;
88 int is_pin = 0;
89 char* hash_type = "1";
90 char* udir = UDIR;
91 char* user = 0;
92 char* symlink_user = 0;
93 char* at = 0;
94 unsigned char pw_hash[120];
95 char pw[1024];
96 char userdir[4096];
97 char buf[4096];
98 struct zx_ctx ctx;
99 
100 /* Called by:  main x8, zxbusd_main, zxbuslist_main, zxbustailf_main, zxcall_main, zxcot_main, zxdecode_main */
opt(int * argc,char *** argv,char *** env)101 static void opt(int* argc, char*** argv, char*** env)
102 {
103   if (*argc <= 1) {
104     fprintf(stderr, "Too few arguments (%d). Must specify at least user name.\n", *argc);
105     goto help;
106   }
107 
108   while (1) {
109     ++(*argv); --(*argc);
110 
111     if (!(*argc) || ((*argv)[0][0] != '-')) break;  /* normal exit from options loop */
112 
113     switch ((*argv)[0][1]) {
114     case '-': if ((*argv)[0][2]) break;
115       ++(*argv); --(*argc);
116       DD("End of options by --");
117       goto last;  /* -- ends the options */
118 
119     case 'a':
120       switch ((*argv)[0][2]) {
121       case '\0':
122 	++an;
123 	continue;
124       case 't':
125 	++(*argv); --(*argc);
126 	if ((*argc) < 1) break;
127 	at = (*argv)[0];
128 	continue;
129       }
130       break;
131 
132     case 'n':
133       switch ((*argv)[0][2]) {
134       case 'e':
135 	++create;
136 	continue;
137       }
138       break;
139 
140     case 'l':
141       switch ((*argv)[0][2]) {
142       case '\0':
143 	++list;
144 	continue;
145 #if 0
146       case 'i':
147 	if (!strcmp((*argv)[0],"-license")) {
148 	  extern char* license;
149 	  fprintf(stderr, license);
150 	  exit(0);
151 	}
152 	break;
153 #endif
154       }
155       break;
156 
157     case 't':
158       switch ((*argv)[0][2]) {
159       case '\0':
160 	++(*argv); --(*argc);
161 	if ((*argc) < 1) break;
162 	hash_type = (*argv)[0];
163 	continue;
164       }
165       break;
166 
167     case 's':
168       switch ((*argv)[0][2]) {
169       case '\0':
170 	++(*argv); --(*argc);
171 	if ((*argc) < 1) break;
172 	symlink_user = (*argv)[0];
173 	continue;
174       }
175       break;
176 
177     case 'b':
178       switch ((*argv)[0][2]) {
179       case 'a':
180 	++(*argv); --(*argc);
181 	if ((*argc) < 1) break;
182 	user = (*argv)[0];
183 	at = strchr(user, ':');
184 	*at = 0;
185 	++at;
186 	printf("%s", zx_mk_basic_auth_b64(&ctx, user, at));
187 	exit(0);
188       }
189       break;
190 
191     case 'd':
192       switch ((*argv)[0][2]) {
193       case '\0':
194 	++errmac_debug;
195 	continue;
196       }
197       break;
198 
199     case 'q':
200       switch ((*argv)[0][2]) {
201       case '\0':
202 	verbose = 0;
203 	continue;
204       }
205       break;
206 
207     case 'p':
208       switch ((*argv)[0][2]) {
209       case '\0':
210 	is_pin = 1;
211 	continue;
212       }
213       break;
214 
215     case 'v':
216       switch ((*argv)[0][2]) {
217       case '\0':
218 	++verbose;
219 	continue;
220       }
221       break;
222 
223     }
224     /* fall thru means unrecognized flag */
225     if (*argc)
226       fprintf(stderr, "Unrecognized flag `%s'\n", (*argv)[0]);
227   help:
228     if (verbose>1) {
229       printf("%s", help);
230       exit(0);
231     }
232     fprintf(stderr, "%s", help);
233     /*fprintf(stderr, "version=0x%06x rel(%s)\n", zxid_version(), zxid_version_str());*/
234     exit(3);
235   }
236  last:
237   if (!list && !*argc) {
238     fprintf(stderr, "Too few arguments (%d). Must specify at least user name.\n", *argc);
239     goto help;
240   }
241 }
242 
243 /* Called by:  main */
list_user(char * userdir,char * udir)244 static int list_user(char* userdir, char* udir)
245 {
246   /*int got;*/
247   char* at;
248   struct dirent* de;
249   DIR* dir;
250   dir = opendir(userdir);
251   if (!dir) {
252     perror("opendir for /var/zxid/uid/USER userdir (or other if configured)");
253     D("failed path(%s)", userdir);
254     return 4;
255   }
256   printf("User dir:              %s\n", userdir);
257   /*got =*/ read_all(sizeof(buf), buf, "pw", 0, "%s/%s/.pw", udir, user);
258   printf("Password hash:         %s\n", buf);
259   at = read_all_alloc(&ctx, "at", 0, 0, "%s/%s/.bs/.at", udir, user);
260   if (at) printf("User attributes:       %s\n", at);
261   at = read_all_alloc(&ctx, "all at", 0, 0, "%s/.all/.bs/.at", udir, 0);
262   if (at) printf("Common (.all) user attributes: %s\n", buf);
263 
264   printf("User's Federated SPs\n");
265 
266   while (de = readdir(dir))
267     if (de->d_name[0] != '.' && de->d_name[strlen(de->d_name)-1] != '~') {
268       /*got =*/ read_all(sizeof(buf), buf, "sp at", 0, "%s/%s/.mni", userdir, de->d_name);
269       printf("SP specific NameID:  %s (%s)\n", buf, de->d_name);
270       at = read_all_alloc(&ctx, "sp at", 0, 0, "%s/%s/.at", userdir, de->d_name);
271       if (at) printf("SP specific attrib:  %s (%s)\n", buf, de->d_name);
272     }
273 
274   /* *** TODO: .all SPs, bootstraps, discovery regs */
275 
276   DD("HERE %p", cf);
277   closedir(dir);
278 
279   return 0;
280 }
281 
282 /* Called by:  main */
list_users(char * udir)283 static int list_users(char* udir)
284 {
285   /*int got;*/
286   char* at;
287   struct dirent* de;
288   DIR* dir;
289 
290   dir = opendir(udir);
291   if (!dir) {
292     perror("opendir for " UDIR " (or other if configured)");
293     D("failed path(%s)", udir);
294     return 1;
295   }
296   while (de = readdir(dir))
297     if (de->d_name[0] != '.' && de->d_name[strlen(de->d_name)-1] != '~') {
298       /*got =*/ read_all(sizeof(buf), buf, "sp at", 0, "%s/%s/.mni", userdir, de->d_name);
299       printf("SP specific NameID:  %s (%s)\n", buf, de->d_name);
300       at = read_all_alloc(&ctx, "sp at", 0, 0, "%s/%s/.bs/.at", userdir, de->d_name);
301       if (at) printf("SP specific attrib:  %s (%s)\n", buf, de->d_name);
302     }
303 
304   closedir(dir);
305   return 0;
306 }
307 
308 extern char pw_basis_64[64];
309 
310 /*() Authenticate user with the password (or other credential)
311  * See also: zxid_pw_authn() in zxiduser.c */
312 
313 /* Called by:  main */
authn_user(int isyk,int pwgot)314 static int authn_user(int isyk, int pwgot)
315 {
316   int got;
317   yubikey_token_st yktok;
318 
319   if (isyk) {
320     snprintf(userdir, sizeof(userdir)-1, "%s/%s", udir, user);
321     userdir[sizeof(userdir)-1] = 0;
322     got = read_all(sizeof(buf), buf, "ykspent", 1, "%s/.ykspent/%s", userdir, pw);
323     if (got) {
324       ERR("The Yubikey One Time Password has already been spent. ticket(%s%s) buf(%.*s)", user, pw, got, buf);
325       return 5;
326     }
327     if (!write_all_path("ykspent", "%s/.ykspent/%s", userdir, pw, 1, "1"))
328       return 1;
329 
330     got = read_all(sizeof(buf), buf, "ykaes", 1, "%s/%s/.yk", udir, user);
331     D("buf    (%s) got=%d", buf, got);
332     if (got < 32) {
333       ERR("User's %s/.yk file must contain aes128 key as 32 hexadecimal characters. Too few characters %d ticket(%s)", user, got, pw);
334       return 6;
335     }
336     if (got > 32) {
337       INFO("User's %s/.yk file must contain aes128 key as 32 hexadecimal characters. Too many characters %d ticket(%s). Truncating.", user, got, pw);
338       got = 32;
339       buf[got] = 0;
340     }
341     zx_hexdec(buf, buf, got, hex_trans);
342     ZERO(&yktok, sizeof(yktok));
343     zx_hexdec((void *)&yktok, pw, pwgot, ykmodhex_trans);
344     yubikey_aes_decrypt((void *)&yktok, (unsigned char*)buf);
345     D("internal uid %02x %02x %02x %02x %02x %02x counter=%d 0x%x timestamp=%d (hi=%x lo=%x) use=%d 0x%x rnd=0x%x crc=0x%x", yktok.uid[0], yktok.uid[1], yktok.uid[2], yktok.uid[3], yktok.uid[4], yktok.uid[5], yktok.ctr, yktok.ctr, (yktok.tstph << 16) | yktok.tstpl, yktok.tstph, yktok.tstpl, yktok.use, yktok.use, yktok.rnd, yktok.crc);
346 
347     if (yubikey_crc_ok_p((unsigned char*)&yktok)) {
348       D("yubikey ticket validates ok %d", 0);
349       if (verbose) printf("yubikey ticket validates ok\n");
350       return 0;
351     }
352     D("yubikey ticket validation failure %d", 0);
353     if (verbose) printf("yubikey ticket validation failure\n");
354     return 7;
355   }
356   got = read_all(sizeof(buf), buf, "pw", 1, "%s/%s/.pw", udir, user);
357   if (got>0) {
358     if (buf[got-1] == '\012') --got;
359     if (buf[got-1] == '\015') --got;
360   }
361   buf[got] = 0;
362   D("buf    (%s) got=%d", buf, got);
363   if (!memcmp(buf, "$1$", sizeof("$1$")-1)) {
364     zx_md5_crypt(pw, buf, (char*)pw_hash);
365     D("pw_hash(%s)", pw_hash);
366     got = strcmp(buf, (char*)pw_hash)?7:0;
367     if (verbose) printf("md5_crypt hash ($1$) validate: %s\n", got?"fail":"ok");
368     return got;
369   }
370 #ifdef USE_OPENSSL
371   if (!memcmp(buf, "$c$", sizeof("$c$")-1)) {
372     DES_fcrypt(pw, buf+3, (char*)pw_hash);
373     D("pw_hash(%s)", pw_hash);
374     got = strcmp(buf+3, (char*)pw_hash)?7:0;
375     if (verbose) printf("Unix DES_crypt hash ($c$) validate: %s\n", got?"fail":"ok");
376     return got;
377   }
378 #endif
379   if (ONE_OF_2(buf[0], '$', '_')) {
380     fprintf(stderr, "Unsupported password hash algorithm (%s).\n", buf);
381     return 8;
382   }
383   D("Assume plain text password %d", 0);
384   got = strcmp(buf, pw)?7:0;
385   if (verbose) printf("plaintext password validate: %s\n", got?"fail":"ok");
386   return got;
387 }
388 
389 extern int zxid_suppress_vpath_warning;
390 
391 /* Called by: */
main(int argc,char ** argv,char ** env)392 int main(int argc, char** argv, char** env)
393 {
394   int isyk = 0;
395   int pwgot = 0;
396   int got;
397   char* p;
398   unsigned char salt[16];
399   unsigned char ch;
400 
401   strcpy(errmac_instance, "\tzxpw");
402   zxid_suppress_vpath_warning = 1;
403   zx_reset_ctx(&ctx);
404   opt(&argc, &argv, &env);
405   if (argc)
406     user = argv[0];
407   else if (!list) {
408     fprintf(stderr, "Too few arguments (%d). Specify at least username.\n%s", argc, help);
409     /*fprintf(stderr, "version=0x%06x rel(%s)\n", zxid_version(), zxid_version_str());*/
410     exit(3);
411   }
412 
413   if (user) {
414     udir = argc>1?argv[1]:UDIR;
415     snprintf(userdir, sizeof(userdir)-1, "%s/%s", udir, user);
416     userdir[sizeof(userdir)-1] = 0;
417   }
418   if (list) {
419     if (user && user[0])  /* passing empty user results full listing */
420       return list_user(userdir, udir);
421     else
422       return list_users(udir);
423   }
424 
425   got = strlen(user);
426   if (got > 32) {  /* Very long user is actually yubikey ticket */
427     strcpy(pw, user + got - 32);
428     user[got - 32] = 0;
429     pwgot = 32;
430     D("yubikey user(%s) ticket(%s)", user, pw);
431     isyk = 1;
432   } else if (!at || create) {
433     read_all_fd(fdstdin, pw, sizeof(pw)-1, &pwgot);  /* Password from stdin */
434   }
435   if (pwgot) {
436     if (pw[pwgot-1] == '\012') --pwgot;
437     if (pw[pwgot-1] == '\015') --pwgot;
438   }
439   pw[pwgot] = 0;
440   D("pw(%s) len=%d", pw, pwgot);
441 
442   if (an)
443     return authn_user(isyk, pwgot);
444 
445   /* Create and other user management functions */
446 
447   if (create) {
448     if (MKDIR(userdir, 0770) == -1) {
449       ERR("User already exists %s", userdir);
450       return 3;
451     }
452     snprintf(buf, sizeof(buf)-1, "%s/.bs", userdir);
453     buf[sizeof(buf)-1] = 0; /* must terminate manually as on win32 nul is not guaranteed */
454     MKDIR(buf, 0770);
455     snprintf(buf, sizeof(buf)-1, "%s/.ps", userdir);
456     buf[sizeof(buf)-1] = 0; /* must terminate manually as on win32 nul is not guaranteed */
457     MKDIR(buf, 0770);
458     snprintf(buf, sizeof(buf)-1, "%s/.ykspent", userdir);
459     buf[sizeof(buf)-1] = 0; /* must terminate manually as on win32 nul is not guaranteed */
460     MKDIR(buf, 0770);
461   }
462   if (symlink_user) {
463     snprintf(buf, sizeof(buf), "%s/%s", udir, symlink_user);
464     buf[sizeof(buf)-1] = 0; /* must terminate manually as on win32 nul is not guaranteed */
465 #ifdef MINGW
466     ERR("Symlink not implemented on Win32. from(%s) (-s %s) path(%s)", buf, symlink_user, userdir);
467 #else
468     D("Symlink from(%s) (-s %s) path(%s)", buf, symlink_user, userdir);
469     if (symlink(buf, userdir) == -1) {
470       perror("symlink user alias");
471       return 2;
472     }
473 #endif
474   }
475 
476   if (at) {
477     for (p = at; *p; ++p)
478       if (*p == '$') *p = '\n';
479     snprintf(buf, sizeof(buf)-1, "%s/.bs/.at", userdir);
480     D("Appending to(%s) attributes(%s)", buf, at);
481     buf[sizeof(buf)-1] = 0; /* must terminate manually as on win32 nul is not guaranteed */
482     write2_or_append_lock_c_path(buf, strlen(at), at, 0, 0, "append .bs/.at", SEEK_END, O_APPEND);
483     if (!create)
484       return 0;
485   }
486 
487   /* ----- Change password logic ----- */
488 
489   /* $1$$6C2jXXYmjnyAkfWXmnCSf0 */
490   /* $y$$6012cab434c66ab87d43d4babe463331 */
491 
492   if (!strcmp(hash_type, "0")) {
493     strcpy((char*)pw_hash, pw);
494     D("pw0(%s) len=%d", pw, (int)strlen(pw));
495 #ifdef USE_OPENSSL
496   } else if (!strcmp(hash_type, "c")) {  /* Unix crypt(3) hash */
497     zx_rand((char*)salt, 2);
498     salt[0] = pw_basis_64[salt[0] & 0x3f];
499     salt[1] = pw_basis_64[salt[1] & 0x3f];
500     strcpy((char*)pw_hash, "$c$");  /* Our custom magic to identify Unix crypt(3) hash */
501     DES_fcrypt(pw, (char*)salt, (char*)pw_hash+3);
502 #endif
503   } else if (!strcmp(hash_type, "1")) {  /* MD5 hash */
504     for (got = 0; got < 8; ++got) {
505       zx_rand((char*)&ch, 1);
506       salt[got] = pw_basis_64[ch & 0x3f];
507     }
508     salt[8] = 0;
509     D("salt(%s)", salt);
510     zx_md5_crypt(pw, (char*)salt, (char*)pw_hash);
511     D("pw_hash(%s)", pw_hash);
512   } else if (!strcmp(hash_type, "y")) {
513     D("Provisioning yubikey aes(%s) in %s/%s/.yk", pw, udir, user);
514     if (!write_all_path("set yk", "%s/%s/.yk", udir, user, -1, pw))
515       return 1;
516     snprintf(userdir, sizeof(userdir)-1, "%s/%s/.ykspent", udir, user);
517     userdir[sizeof(userdir)-1] = 0;
518     MKDIR(userdir, 0770);
519     return 0;
520   } else {
521     fprintf(stderr, "Unsupported password hash algorithm (%s).\n", hash_type);
522   }
523 
524   DD("pw_hash(%s) len=%d", pw_hash, strlen(pw_hash));
525   if (!write_all_path("set pw", "%s/%s/.pw", udir, user, -1, (char*)pw_hash))
526     return 1;
527   return 0;
528 }
529 
530 /* EOF  --  zxpasswd.c */
531 
532 #if 0
533 
534 We choose $c$sshhhhhhhhhh representation for plain Unix crypt(3) hashed passwords.
535 
536 man crypt
537 
538 If salt is a character string starting with the characters "$id$" followed
539 by a string terminated by "$":
540 
541        $id$salt$encrypted
542 
543 then instead of using the DES machine, id identifies the encryption
544 method used and this then determines how the rest of the password string
545 is interpreted.  The following values of id are supported:
546 
547        ID  | Method
548        ---------------------------------------------------------
549        1   | MD5
550        2a  | Blowfish (not in mainline glibc; added in some
551            | Linux distributions)
552        5   | SHA-256 (since glibc 2.7)
553        6   | SHA-512 (since glibc 2.7)
554 
555 So $5$salt$encrypted is an SHA-256 encoded password and $6$salt$encrypted
556 is an SHA-512 encoded one.
557 
558 "salt" stands for the up to 16 characters following "$id$" in the salt.
559 The encrypted part of the password string is  the  actual  computed
560 password.  The size of this string is fixed:
561 
562 MD5     | 22 characters
563 SHA-256 | 43 characters
564 SHA-512 | 86 characters
565 
566 The  characters  in  "salt"  and  "encrypted" are drawn from the
567 set [a-zA-Z0-9./].  In the SHA implementation the entire key is significant
568 (instead of only the first 8 bytes in MD5).
569 
570 
571 See also man DES_fcrypt
572 
573 Personalizing yubikeys 2009: get and compile libyubikey-1.5 and ykpers-1.0
574 
575 ykpersonalize -y -v -ofixed=refucenikj -a6012cad434c66ab87d43d4babe463231
576 ykdebug 6012cad434c66ab87d43d4babe463231 refucenikjdbrgulutnjhurchlkcckdkergfitcebf
577 
578 Here -ofixed specifies the "username" for purposes of /var/zxid/uid and
579 -a specifies the AES128 key that will be put in .pw file as follows:
580 
581 y5e0w.RTowQpk
582 ldapinlisboa
583 
584 /var/lib/trac/conf/trac.htpasswd
585 sampo:y5e0w.RTowQpk
586 
587 #endif
588