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