2 * $Id: uams_dhx2_passwd.c,v 1.8 2010-03-30 10:25:49 franklahm 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 */
15 #include <atalk/standards.h>
22 #include <arpa/inet.h>
40 #include <atalk/afp.h>
41 #include <atalk/uam.h>
42 #include <atalk/logger.h>
44 /* Number of bits for p which we generate. Everybode out there uses 512, so we beet them */
45 #define PRIMEBITS 1024
47 /* hash a number to a 16-bit quantity */
48 #define dhxhash(a) ((((unsigned long) (a) >> 8) ^ \
49 (unsigned long) (a)) & 0xffff)
51 /* Some parameters need be maintained across calls */
52 static gcry_mpi_t p, Ra;
53 static gcry_mpi_t serverNonce;
54 static char *K_MD5hash = NULL;
55 static int K_hash_len;
58 /* The initialization vectors for CAST128 are fixed by Apple. */
59 static unsigned char dhx_c2siv[] = { 'L', 'W', 'a', 'l', 'l', 'a', 'c', 'e' };
60 static unsigned char dhx_s2civ[] = { 'C', 'J', 'a', 'l', 'b', 'e', 'r', 't' };
62 /* Static variables used to communicate between the conversation function
63 * and the server_login function */
64 static struct passwd *dhxpwd;
66 /*********************************************************
67 * Crypto helper func to generate p and g for use in DH.
68 * libgcrypt doesn't provide one directly.
69 * Algorithm taken from GNUTLS:gnutls_dh_primes.c
70 *********************************************************/
73 * This function will generate a new pair of prime and generator for use in
74 * the Diffie-Hellman key exchange.
75 * The bits value should be one of 768, 1024, 2048, 3072 or 4096.
79 dh_params_generate (gcry_mpi_t *ret_p, gcry_mpi_t *ret_g, unsigned int bits) {
81 int result, times = 0, qbits;
83 gcry_mpi_t g = NULL, prime = NULL;
84 gcry_mpi_t *factors = NULL;
87 /* Version check should be the very first call because it
88 makes sure that important subsystems are intialized. */
89 if (!gcry_check_version (GCRYPT_VERSION)) {
90 LOG(log_info, logtype_uams, "PAM DHX2: libgcrypt versions mismatch. Need: %s", GCRYPT_VERSION);
98 qbits = (bits / 40) + 105;
100 if (qbits & 1) /* better have an even number */
103 /* find a prime number of size bits. */
106 gcry_mpi_release (prime);
107 gcry_prime_release_factors (factors);
109 err = gcry_prime_generate (&prime, bits, qbits, &factors, NULL, NULL,
110 GCRY_STRONG_RANDOM, GCRY_PRIME_FLAG_SPECIAL_FACTOR);
112 result = AFPERR_MISC;
115 err = gcry_prime_check (prime, 0);
117 } while (err != 0 && times < 10);
120 result = AFPERR_MISC;
124 /* generate the group generator. */
125 err = gcry_prime_group_generator (&g, prime, factors, NULL);
127 result = AFPERR_MISC;
131 gcry_prime_release_factors (factors);
137 gcry_mpi_release (g);
141 gcry_mpi_release (prime);
146 gcry_prime_release_factors (factors);
147 gcry_mpi_release (g);
148 gcry_mpi_release (prime);
153 static int dhx2_setup(void *obj, char *ibuf _U_, size_t ibuflen _U_,
154 char *rbuf, size_t *rbuflen)
159 char *Ra_binary = NULL;
162 #endif /* SHADOWPW */
167 /* Initialize passwd/shadow */
169 if (( sp = getspnam( dhxpwd->pw_name )) == NULL ) {
170 LOG(log_info, logtype_uams, "DHX2: no shadow passwd entry for this user");
171 return AFPERR_NOTAUTH;
173 dhxpwd->pw_passwd = sp->sp_pwdp;
174 #endif /* SHADOWPW */
176 if (!dhxpwd->pw_passwd)
177 return AFPERR_NOTAUTH;
179 /* Initialize DH params */
183 Ra = gcry_mpi_new(0);
184 Ma = gcry_mpi_new(0);
186 /* Generate p and g for DH */
187 ret = dh_params_generate( &p, &g, PRIMEBITS);
189 LOG(log_info, logtype_uams, "DHX2: Couldn't generate p and g");
194 /* Generate our random number Ra. */
195 Ra_binary = calloc(1, PRIMEBITS/8);
196 if (Ra_binary == NULL) {
200 gcry_randomize(Ra_binary, PRIMEBITS/8, GCRY_STRONG_RANDOM);
201 gcry_mpi_scan(&Ra, GCRYMPI_FMT_USG, Ra_binary, PRIMEBITS/8, NULL);
205 /* Ma = g^Ra mod p. This is our "public" key */
206 gcry_mpi_powm(Ma, g, Ra, p);
208 /* ------- DH Init done ------ */
209 /* Start building reply packet */
211 /* Session ID first */
214 memcpy(rbuf, &uint16, sizeof(uint16_t));
219 gcry_mpi_print( GCRYMPI_FMT_USG, (unsigned char *)rbuf, 4, &nwritten, g);
221 memmove( rbuf+4-nwritten, rbuf, nwritten);
222 memset( rbuf, 0, 4-nwritten);
227 /* len = length of p = PRIMEBITS/8 */
228 uint16 = htons((uint16_t) PRIMEBITS/8);
229 memcpy(rbuf, &uint16, sizeof(uint16_t));
234 gcry_mpi_print( GCRYMPI_FMT_USG, (unsigned char *)rbuf, PRIMEBITS/8, NULL, p);
236 *rbuflen += PRIMEBITS/8;
239 gcry_mpi_print( GCRYMPI_FMT_USG, (unsigned char *)rbuf, PRIMEBITS/8, &nwritten, Ma);
240 if (nwritten < PRIMEBITS/8) {
241 memmove(rbuf + (PRIMEBITS/8) - nwritten, rbuf, nwritten);
242 memset(rbuf, 0, (PRIMEBITS/8) - nwritten);
245 *rbuflen += PRIMEBITS/8;
247 ret = AFPERR_AUTHCONT;
249 error: /* We exit here anyway */
250 /* We will only need p and Ra later, but mustn't forget to release it ! */
252 gcry_mpi_release(Ma);
256 /* -------------------------------- */
257 static int login(void *obj, char *username, int ulen, struct passwd **uam_pwd _U_,
258 char *ibuf, size_t ibuflen,
259 char *rbuf, size_t *rbuflen)
261 if (( dhxpwd = uam_getname(obj, username, ulen)) == NULL ) {
262 LOG(log_info, logtype_uams, "DHX2: unknown username");
263 return AFPERR_NOTAUTH;
266 LOG(log_info, logtype_uams, "DHX2 login: %s", username);
267 return dhx2_setup(obj, ibuf, ibuflen, rbuf, rbuflen);
270 /* -------------------------------- */
271 /* dhx login: things are done in a slightly bizarre order to avoid
272 * having to clean things up if there's an error. */
273 static int passwd_login(void *obj, struct passwd **uam_pwd,
274 char *ibuf, size_t ibuflen,
275 char *rbuf, size_t *rbuflen)
282 /* grab some of the options */
283 if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &ulen) < 0) {
284 LOG(log_info, logtype_uams, "DHX2: uam_afpserver_option didn't meet uam_option_username -- %s",
289 len = (unsigned char) *ibuf++;
291 LOG(log_info, logtype_uams, "DHX2: Signature Retieval Failure -- %s",
296 memcpy(username, ibuf, len );
298 username[ len ] = '\0';
300 if ((unsigned long) ibuf & 1) /* pad to even boundary */
303 return (login(obj, username, ulen, uam_pwd, ibuf, ibuflen, rbuf, rbuflen));
306 /* ----------------------------- */
307 static int passwd_login_ext(void *obj, char *uname, struct passwd **uam_pwd,
308 char *ibuf, size_t ibuflen,
309 char *rbuf, size_t *rbuflen)
317 /* grab some of the options */
318 if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &ulen) < 0) {
319 LOG(log_info, logtype_uams, "DHX2: uam_afpserver_option didn't meet uam_option_username -- %s",
327 memcpy(&temp16, uname, sizeof(temp16));
330 if ( !len || len > ulen ) {
331 LOG(log_info, logtype_uams, "DHX2: Signature Retrieval Failure -- %s",
335 memcpy(username, uname +2, len );
336 username[ len ] = '\0';
338 return (login(obj, username, ulen, uam_pwd, ibuf, ibuflen, rbuf, rbuflen));
341 /* -------------------------------- */
343 static int logincont1(void *obj _U_, struct passwd **uam_pwd _U_,
344 char *ibuf, size_t ibuflen,
345 char *rbuf, size_t *rbuflen)
349 gcry_mpi_t Mb, K, clientNonce;
350 unsigned char *K_bin = NULL;
351 char serverNonce_bin[16];
352 gcry_cipher_hd_t ctx;
353 gcry_error_t ctxerror;
358 Mb = gcry_mpi_new(0);
360 clientNonce = gcry_mpi_new(0);
361 serverNonce = gcry_mpi_new(0);
363 /* Packet size should be: Session ID + Ma + Encrypted client nonce */
364 if (ibuflen != 2 + PRIMEBITS/8 + 16) {
365 LOG(log_error, logtype_uams, "DHX2: Paket length not correct");
370 /* Skip session id */
373 /* Extract Mb, client's "public" key */
374 gcry_mpi_scan(&Mb, GCRYMPI_FMT_USG, ibuf, PRIMEBITS/8, NULL);
377 /* Now finally generate the Key: K = Mb^Ra mod p */
378 gcry_mpi_powm(K, Mb, Ra, p);
380 /* We need K in binary form in order to ... */
381 K_bin = calloc(1, PRIMEBITS/8);
386 gcry_mpi_print(GCRYMPI_FMT_USG, K_bin, PRIMEBITS/8, &nwritten, K);
387 if (nwritten < PRIMEBITS/8) {
388 memmove(K_bin + PRIMEBITS/8 - nwritten, K_bin, nwritten);
389 memset(K_bin, 0, PRIMEBITS/8 - nwritten);
392 /* ... generate the MD5 hash of K. K_MD5hash is what we actually use ! */
393 K_MD5hash = calloc(1, K_hash_len = gcry_md_get_algo_dlen(GCRY_MD_MD5));
394 if (K_MD5hash == NULL) {
400 gcry_md_hash_buffer(GCRY_MD_MD5, K_MD5hash, K_bin, PRIMEBITS/8);
404 /* FIXME: To support the Reconnect UAM, we need to store this key somewhere */
406 /* Set up our encryption context. */
407 ctxerror = gcry_cipher_open( &ctx, GCRY_CIPHER_CAST5, GCRY_CIPHER_MODE_CBC, 0);
408 if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
413 ctxerror = gcry_cipher_setkey(ctx, K_MD5hash, K_hash_len);
414 if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
418 /* Set the initialization vector for client->server transfer. */
419 ctxerror = gcry_cipher_setiv(ctx, dhx_c2siv, sizeof(dhx_c2siv));
420 if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
424 /* Finally: decrypt client's md5_K(client nonce, C2SIV) inplace */
425 ctxerror = gcry_cipher_decrypt(ctx, ibuf, 16, NULL, 0);
426 if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
430 /* Pull out clients nonce */
431 gcry_mpi_scan(&clientNonce, GCRYMPI_FMT_USG, ibuf, 16, NULL);
432 /* Increment nonce */
433 gcry_mpi_add_ui(clientNonce, clientNonce, 1);
435 /* Generate our nonce and remember it for Logincont2 */
436 gcry_create_nonce(serverNonce_bin, 16); /* We'll use this here */
437 gcry_mpi_scan(&serverNonce, GCRYMPI_FMT_USG, serverNonce_bin, 16, NULL); /* For use in Logincont2 */
439 /* ---- Start building reply packet ---- */
441 /* Session ID + 1 first */
442 uint16 = htons(ID+1);
443 memcpy(rbuf, &uint16, sizeof(uint16_t));
447 /* Client nonce + 1 */
448 gcry_mpi_print(GCRYMPI_FMT_USG, (unsigned char *)rbuf, PRIMEBITS/8, NULL, clientNonce);
450 memcpy(rbuf+16, serverNonce_bin, 16);
452 /* Set the initialization vector for server->client transfer. */
453 ctxerror = gcry_cipher_setiv(ctx, dhx_s2civ, sizeof(dhx_s2civ));
454 if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
458 /* Encrypt md5_K(clientNonce+1, serverNonce) inplace */
459 ctxerror = gcry_cipher_encrypt(ctx, rbuf, 32, NULL, 0);
460 if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
467 ret = AFPERR_AUTHCONT;
471 gcry_cipher_close(ctx);
473 gcry_mpi_release(serverNonce);
478 gcry_mpi_release(Mb);
479 gcry_mpi_release(Ra);
481 gcry_mpi_release(clientNonce);
485 static int logincont2(void *obj _U_, struct passwd **uam_pwd,
486 char *ibuf, size_t ibuflen,
487 char *rbuf _U_, size_t *rbuflen)
491 #endif /* SHADOWPW */
494 gcry_mpi_t retServerNonce;
495 gcry_cipher_hd_t ctx;
496 gcry_error_t ctxerror;
499 retServerNonce = gcry_mpi_new(0);
501 /* Packet size should be: Session ID + ServerNonce + Passwd buffer (evantually +10 extra bytes, see Apples Docs)*/
502 if ((ibuflen != 2 + 16 + 256) && (ibuflen != 2 + 16 + 256 + 10)) {
503 LOG(log_error, logtype_uams, "DHX2: Paket length not correct: %d. Should be 274 or 284.", ibuflen);
508 /* Set up our encryption context. */
509 ctxerror = gcry_cipher_open( &ctx, GCRY_CIPHER_CAST5, GCRY_CIPHER_MODE_CBC, 0);
510 if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
515 ctxerror = gcry_cipher_setkey(ctx, K_MD5hash, K_hash_len);
516 if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
520 /* Set the initialization vector for client->server transfer. */
521 ctxerror = gcry_cipher_setiv(ctx, dhx_c2siv, sizeof(dhx_c2siv));
522 if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
527 /* Skip Session ID */
530 /* Finally: decrypt client's md5_K(serverNonce+1, passwor, C2SIV) inplace */
531 ctxerror = gcry_cipher_decrypt(ctx, ibuf, 16+256, NULL, 0);
532 if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
536 /* Pull out nonce. Should be serverNonce+1 */
537 gcry_mpi_scan(&retServerNonce, GCRYMPI_FMT_USG, ibuf, 16, NULL);
538 gcry_mpi_sub_ui(retServerNonce, retServerNonce, 1);
539 if ( gcry_mpi_cmp( serverNonce, retServerNonce) != 0) {
541 ret = AFPERR_NOTAUTH;
544 ibuf += 16; /* ibuf now point to passwd in cleartext */
546 /* ---- Start authentication --- */
547 ret = AFPERR_NOTAUTH;
549 p = crypt( ibuf, dhxpwd->pw_passwd );
550 memset(ibuf, 0, 255);
551 if ( strcmp( p, dhxpwd->pw_passwd ) == 0 ) {
557 if (( sp = getspnam( dhxpwd->pw_name )) == NULL ) {
558 LOG(log_info, logtype_uams, "no shadow passwd entry for %s", dhxpwd->pw_name);
559 ret = AFPERR_NOTAUTH;
563 /* check for expired password */
564 if (sp && sp->sp_max != -1 && sp->sp_lstchg) {
565 time_t now = time(NULL) / (60*60*24);
566 int32_t expire_days = sp->sp_lstchg - now + sp->sp_max;
567 if ( expire_days < 0 ) {
568 LOG(log_info, logtype_uams, "password for user %s expired", dhxpwd->pw_name);
569 ret = AFPERR_PWDEXPR;
573 #endif /* SHADOWPW */
576 gcry_cipher_close(ctx);
581 gcry_mpi_release(serverNonce);
582 gcry_mpi_release(retServerNonce);
586 static int passwd_logincont(void *obj, struct passwd **uam_pwd,
587 char *ibuf, size_t ibuflen,
588 char *rbuf, size_t *rbuflen)
593 /* check for session id */
594 memcpy(&retID, ibuf, sizeof(uint16_t));
595 retID = ntohs(retID);
597 ret = logincont1(obj, uam_pwd, ibuf, ibuflen, rbuf, rbuflen);
598 else if (retID == ID+1)
599 ret = logincont2(obj, uam_pwd, ibuf,ibuflen, rbuf, rbuflen);
601 LOG(log_info, logtype_uams, "DHX2: Session ID Mismatch");
607 static int uam_setup(const char *path)
609 if (uam_register(UAM_SERVER_LOGIN_EXT, path, "DHX2", passwd_login,
610 passwd_logincont, NULL, passwd_login_ext) < 0)
615 static void uam_cleanup(void)
617 uam_unregister(UAM_SERVER_LOGIN, "DHX2");
621 UAM_MODULE_EXPORT struct uam_export uams_dhx2 = {
624 uam_setup, uam_cleanup
628 UAM_MODULE_EXPORT struct uam_export uams_dhx2_passwd = {
631 uam_setup, uam_cleanup
634 #endif /* UAM_DHX2 */