2 * $Id: uams_dhx2_passwd.c,v 1.1 2008-11-22 12:07:26 didg Exp $
4 * Copyright (c) 1990,1993 Regents of The University of Michigan.
5 * Copyright (c) 1999 Adrian Sun (asun@u.washington.edu)
6 * All Rights Reserved. See COPYRIGHT.
11 #endif /* HAVE_CONFIG_H */
22 #define _XOPEN_SOURCE 500 /* for crypt() */
25 #define _XOPEN_SOURCE /* for crypt() */
36 #ifdef HAVE_SYS_TIME_H
52 #include <atalk/afp.h>
53 #include <atalk/uam.h>
54 #include <atalk/logger.h>
56 /* Number of bits for p which we generate. Everybode out there uses 512, so we beet them */
57 #define PRIMEBITS 1024
59 /* hash a number to a 16-bit quantity */
60 #define dhxhash(a) ((((unsigned long) (a) >> 8) ^ \
61 (unsigned long) (a)) & 0xffff)
63 /* Some parameters need be maintained across calls */
64 static gcry_mpi_t p, Ra;
65 static gcry_mpi_t serverNonce;
66 static char *K_MD5hash = NULL;
67 static int K_hash_len;
70 /* The initialization vectors for CAST128 are fixed by Apple. */
71 static unsigned char dhx_c2siv[] = { 'L', 'W', 'a', 'l', 'l', 'a', 'c', 'e' };
72 static unsigned char dhx_s2civ[] = { 'C', 'J', 'a', 'l', 'b', 'e', 'r', 't' };
74 /* Static variables used to communicate between the conversation function
75 * and the server_login function */
76 static struct passwd *dhxpwd;
78 /*********************************************************
79 * Crypto helper func to generate p and g for use in DH.
80 * libgcrpyt doesn't provide one directly.
81 * Algorithm taken from GNUTLS:gnutls_dh_primes.c
82 *********************************************************/
85 * This function will generate a new pair of prime and generator for use in
86 * the Diffie-Hellman key exchange.
87 * The bits value should be one of 768, 1024, 2048, 3072 or 4096.
91 dh_params_generate (gcry_mpi_t *ret_p, gcry_mpi_t *ret_g, unsigned int bits) {
93 int result, times = 0, qbits;
95 gcry_mpi_t g = NULL, prime = NULL;
96 gcry_mpi_t *factors = NULL;
99 /* Version check should be the very first call because it
100 makes sure that important subsystems are intialized. */
101 if (!gcry_check_version (GCRYPT_VERSION)) {
102 LOG(log_info, logtype_uams, "PAM DHX2: libgcrypt versions mismatch. Need: %u", GCRYPT_VERSION);
103 result = AFPERR_MISC;
110 qbits = (bits / 40) + 105;
112 if (qbits & 1) /* better have an even number */
115 /* find a prime number of size bits. */
118 gcry_mpi_release (prime);
119 gcry_prime_release_factors (factors);
121 err = gcry_prime_generate (&prime, bits, qbits, &factors, NULL, NULL,
122 GCRY_STRONG_RANDOM, GCRY_PRIME_FLAG_SPECIAL_FACTOR);
124 result = AFPERR_MISC;
127 err = gcry_prime_check (prime, 0);
129 } while (err != 0 && times < 10);
132 result = AFPERR_MISC;
136 /* generate the group generator. */
137 err = gcry_prime_group_generator (&g, prime, factors, NULL);
139 result = AFPERR_MISC;
143 gcry_prime_release_factors (factors);
149 gcry_mpi_release (g);
153 gcry_mpi_release (prime);
158 gcry_prime_release_factors (factors);
159 gcry_mpi_release (g);
160 gcry_mpi_release (prime);
165 static int dhx2_setup(void *obj, char *ibuf, int ibuflen _U_,
166 char *rbuf, int *rbuflen)
172 char *Ra_binary = NULL;
173 gcry_cipher_hd_t ctx;
174 gcry_error_t ctxerror;
181 #endif /* SHADOWPW */
184 /* Initialize passwd/shadow */
186 if (( sp = getspnam( dhxpwd->pw_name )) == NULL ) {
187 LOG(log_info, logtype_uams, "no shadow passwd entry for this user");
188 return AFPERR_NOTAUTH;
190 dhxpwd->pw_passwd = sp->sp_pwdp;
191 #endif /* SHADOWPW */
193 if (!dhxpwd->pw_passwd)
194 return AFPERR_NOTAUTH;
196 /* Initialize DH params */
200 Ra = gcry_mpi_new(0);
201 Ma = gcry_mpi_new(0);
203 /* Generate p and g for DH */
204 ret = dh_params_generate( &p, &g, PRIMEBITS);
206 LOG(log_info, logtype_uams, "DHX2: Couldn't generate p and g");
211 /* Generate our random number Ra. */
212 Ra_binary = calloc(1, PRIMEBITS/8);
213 if (Ra_binary == NULL) {
217 gcry_randomize(Ra_binary, PRIMEBITS/8, GCRY_STRONG_RANDOM);
218 gcry_mpi_scan(&Ra, GCRYMPI_FMT_USG, Ra_binary, PRIMEBITS/8, NULL);
222 /* Ma = g^Ra mod p. This is our "public" key */
223 gcry_mpi_powm(Ma, g, Ra, p);
225 /* ------- DH Init done ------ */
226 /* Start building reply packet */
228 /* Session ID first */
230 *(u_int16_t *)rbuf = htons(ID);
235 gcry_mpi_print( GCRYMPI_FMT_USG, rbuf, 4, &nwritten, g);
237 memmove( rbuf+4-nwritten, rbuf, nwritten);
238 memset( rbuf, 0, 4-nwritten);
243 /* len = length of p = PRIMEBITS/8 */
244 *(u_int16_t *)rbuf = htons((u_int16_t) PRIMEBITS/8);
249 gcry_mpi_print( GCRYMPI_FMT_USG, rbuf, PRIMEBITS/8, NULL, p);
251 *rbuflen += PRIMEBITS/8;
254 gcry_mpi_print( GCRYMPI_FMT_USG, rbuf, PRIMEBITS/8, &len, Ma);
255 if (len < PRIMEBITS/8) {
256 memmove(rbuf + (PRIMEBITS/8) - len, rbuf, len);
257 memset(rbuf, 0, (PRIMEBITS/8) - len);
260 *rbuflen += PRIMEBITS/8;
262 ret = AFPERR_AUTHCONT;
264 error: /* We exit here anyway */
265 /* We will only need p and Ra later, but mustn't forget to release it ! */
267 gcry_mpi_release(Ma);
271 /* -------------------------------- */
272 static int login(void *obj, char *username, int ulen, struct passwd **uam_pwd _U_,
273 char *ibuf, int ibuflen,
274 char *rbuf, int *rbuflen)
276 if (( dhxpwd = uam_getname(obj, username, ulen)) == NULL ) {
277 LOG(log_info, logtype_uams, "DHX2: unknown username");
281 LOG(log_info, logtype_uams, "DHX2 login: %s", username);
282 return dhx2_setup(obj, ibuf, ibuflen, rbuf, rbuflen);
285 /* -------------------------------- */
286 /* dhx login: things are done in a slightly bizarre order to avoid
287 * having to clean things up if there's an error. */
288 static int passwd_login(void *obj, struct passwd **uam_pwd,
289 char *ibuf, int ibuflen,
290 char *rbuf, int *rbuflen)
297 /* grab some of the options */
298 if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &ulen) < 0) {
299 LOG(log_info, logtype_uams, "DHX2: uam_afpserver_option didn't meet uam_option_username -- %s",
304 len = (unsigned char) *ibuf++;
306 LOG(log_info, logtype_uams, "DHX2: Signature Retieval Failure -- %s",
311 memcpy(username, ibuf, len );
313 username[ len ] = '\0';
315 if ((unsigned long) ibuf & 1) /* pad to even boundary */
318 return (login(obj, username, ulen, uam_pwd, ibuf, ibuflen, rbuf, rbuflen));
321 /* ----------------------------- */
322 static int passwd_login_ext(void *obj, char *uname, struct passwd **uam_pwd,
323 char *ibuf, int ibuflen,
324 char *rbuf, int *rbuflen)
332 /* grab some of the options */
333 if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &ulen) < 0) {
334 LOG(log_info, logtype_uams, "DHX2: uam_afpserver_option didn't meet uam_option_username -- %s",
342 memcpy(&temp16, uname, sizeof(temp16));
345 if ( !len || len > ulen ) {
346 LOG(log_info, logtype_uams, "DHX2: Signature Retrieval Failure -- %s",
350 memcpy(username, uname +2, len );
351 username[ len ] = '\0';
353 return (login(obj, username, ulen, uam_pwd, ibuf, ibuflen, rbuf, rbuflen));
356 /* -------------------------------- */
358 static int logincont1(void *obj, struct passwd **uam_pwd,
359 char *ibuf, int ibuflen,
360 char *rbuf, int *rbuflen)
367 gcry_mpi_t Mb, K, clientNonce;
369 char serverNonce_bin[16];
370 gcry_cipher_hd_t ctx;
371 gcry_error_t ctxerror;
373 Mb = gcry_mpi_new(0);
375 clientNonce = gcry_mpi_new(0);
376 serverNonce = gcry_mpi_new(0);
378 /* Packet size should be: Session ID + Ma + Encrypted client nonce */
379 if (ibuflen != 2 + PRIMEBITS/8 + 16) {
380 LOG(log_error, logtype_uams, "DHX2: Paket length not correct");
385 /* Skip session id */
388 /* Extract Mb, client's "public" key */
389 gcry_mpi_scan(&Mb, GCRYMPI_FMT_USG, ibuf, PRIMEBITS/8, NULL);
392 /* Now finally generate the Key: K = Mb^Ra mod p */
393 gcry_mpi_powm(K, Mb, Ra, p);
395 /* We need K in binary form in order to ... */
396 K_bin = calloc(1, PRIMEBITS/8);
401 gcry_mpi_print(GCRYMPI_FMT_USG, K_bin, PRIMEBITS/8, &nwritten, K);
402 if (nwritten < PRIMEBITS/8) {
403 memmove(K_bin + PRIMEBITS/8 - nwritten, K_bin, nwritten);
404 memset(K_bin, 0, PRIMEBITS/8 - nwritten);
407 /* ... generate the MD5 hash of K. K_MD5hash is what we actually use ! */
408 K_MD5hash = calloc(1, K_hash_len = gcry_md_get_algo_dlen(GCRY_MD_MD5));
409 if (K_MD5hash == NULL) {
413 gcry_md_hash_buffer(GCRY_MD_MD5, K_MD5hash, K_bin, PRIMEBITS/8);
417 /* FIXME: To support the Reconnect UAM, we need to store this key somewhere */
419 /* Set up our encryption context. */
420 ctxerror = gcry_cipher_open( &ctx, GCRY_CIPHER_CAST5, GCRY_CIPHER_MODE_CBC, 0);
421 if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
426 ctxerror = gcry_cipher_setkey(ctx, K_MD5hash, K_hash_len);
427 if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
431 /* Set the initialization vector for client->server transfer. */
432 ctxerror = gcry_cipher_setiv(ctx, dhx_c2siv, sizeof(dhx_c2siv));
433 if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
437 /* Finally: decrypt client's md5_K(client nonce, C2SIV) inplace */
438 ctxerror = gcry_cipher_decrypt(ctx, ibuf, 16, NULL, 0);
439 if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
443 /* Pull out clients nonce */
444 gcry_mpi_scan(&clientNonce, GCRYMPI_FMT_USG, ibuf, 16, NULL);
445 /* Increment nonce */
446 gcry_mpi_add_ui(clientNonce, clientNonce, 1);
448 /* Generate our nonce and remember it for Logincont2 */
449 gcry_create_nonce(serverNonce_bin, 16); /* We'll use this here */
450 gcry_mpi_scan(&serverNonce, GCRYMPI_FMT_USG, serverNonce_bin, 16, NULL); /* For use in Logincont2 */
452 /* ---- Start building reply packet ---- */
454 /* Session ID + 1 first */
455 *(u_int16_t *)rbuf = htons(ID+1);
459 /* Client nonce + 1 */
460 gcry_mpi_print(GCRYMPI_FMT_USG, rbuf, PRIMEBITS/8, NULL, clientNonce);
462 memcpy(rbuf+16, serverNonce_bin, 16);
464 /* Set the initialization vector for server->client transfer. */
465 ctxerror = gcry_cipher_setiv(ctx, dhx_s2civ, sizeof(dhx_s2civ));
466 if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
470 /* Encrypt md5_K(clientNonce+1, serverNonce) inplace */
471 ctxerror = gcry_cipher_encrypt(ctx, rbuf, 32, NULL, 0);
472 if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
479 ret = AFPERR_AUTHCONT;
483 gcry_cipher_close(ctx);
485 gcry_mpi_release(serverNonce);
490 gcry_mpi_release(Mb);
491 gcry_mpi_release(Ra);
493 gcry_mpi_release(clientNonce);
497 static int logincont2(void *obj, struct passwd **uam_pwd,
498 char *ibuf, int ibuflen,
499 char *rbuf, int *rbuflen)
503 #endif /* SHADOWPW */
508 gcry_mpi_t retServerNonce;
509 gcry_cipher_hd_t ctx;
510 gcry_error_t ctxerror;
512 /* Packet size should be: Session ID + ServerNonce + Passwd buffer */
513 if (ibuflen != 2 + 16 + 256) {
514 LOG(log_error, logtype_uams, "DHX2: Paket length not correct");
519 retServerNonce = gcry_mpi_new(0);
521 /* Set up our encryption context. */
522 ctxerror = gcry_cipher_open( &ctx, GCRY_CIPHER_CAST5, GCRY_CIPHER_MODE_CBC, 0);
523 if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
528 ctxerror = gcry_cipher_setkey(ctx, K_MD5hash, K_hash_len);
529 if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
533 /* Set the initialization vector for client->server transfer. */
534 ctxerror = gcry_cipher_setiv(ctx, dhx_c2siv, sizeof(dhx_c2siv));
535 if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
540 /* Skip Session ID */
543 /* Finally: decrypt client's md5_K(serverNonce+1, passwor, C2SIV) inplace */
544 ctxerror = gcry_cipher_decrypt(ctx, ibuf, 16+256, NULL, 0);
545 if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
549 /* Pull out nonce. Should be serverNonce+1 */
550 gcry_mpi_scan(&retServerNonce, GCRYMPI_FMT_USG, ibuf, 16, NULL);
551 gcry_mpi_sub_ui(retServerNonce, retServerNonce, 1);
552 if ( gcry_mpi_cmp( serverNonce, retServerNonce) != 0) {
554 ret = AFPERR_NOTAUTH;
557 ibuf += 16; /* ibuf now point to passwd in cleartext */
559 /* ---- Start authentication --- */
560 ret = AFPERR_NOTAUTH;
562 p = crypt( ibuf, dhxpwd->pw_passwd );
563 memset(ibuf, 0, 255);
564 if ( strcmp( p, dhxpwd->pw_passwd ) == 0 ) {
570 if (( sp = getspnam( dhxpwd->pw_name )) == NULL ) {
571 LOG(log_info, logtype_uams, "no shadow passwd entry for %s", dhxpwd->pw_name);
572 ret = AFPERR_NOTAUTH;
576 /* check for expired password */
577 if (sp && sp->sp_max != -1 && sp->sp_lstchg) {
578 time_t now = time(NULL) / (60*60*24);
579 int32_t expire_days = sp->sp_lstchg - now + sp->sp_max;
580 if ( expire_days < 0 ) {
581 LOG(log_info, logtype_uams, "password for user %s expired", dhxpwd->pw_name);
582 ret = AFPERR_PWDEXPR;
586 #endif /* SHADOWPW */
589 gcry_cipher_close(ctx);
594 gcry_mpi_release(serverNonce);
595 gcry_mpi_release(retServerNonce);
599 static int passwd_logincont(void *obj, struct passwd **uam_pwd,
600 char *ibuf, int ibuflen,
601 char *rbuf, int *rbuflen)
606 /* check for session id */
607 retID = ntohs(*(u_int16_t *)ibuf);
609 ret = logincont1(obj, uam_pwd, ibuf, ibuflen, rbuf, rbuflen);
610 else if (retID == ID+1)
611 ret = logincont2(obj, uam_pwd, ibuf,ibuflen, rbuf, rbuflen);
613 LOG(log_info, logtype_uams, "DHX2: Session ID Mismatch");
619 static int uam_setup(const char *path)
621 if (uam_register(UAM_SERVER_LOGIN_EXT, path, "DHX2", passwd_login,
622 passwd_logincont, NULL, passwd_login_ext) < 0)
627 static void uam_cleanup(void)
629 uam_unregister(UAM_SERVER_LOGIN, "DHX2");
633 UAM_MODULE_EXPORT struct uam_export uams_dhx2 = {
636 uam_setup, uam_cleanup
640 UAM_MODULE_EXPORT struct uam_export uams_dhx2_pam = {
643 uam_setup, uam_cleanup
646 #endif /* UAM_DHX2 */