/* * linux/net/sunrpc/svcauth_des.c * * Server-side AUTH_DES handling. * * Copyright (C) 1996, 1997 Olaf Kirch */ #include #include #include #include #include #include #define RPCDBG_FACILITY RPCDBG_AUTH /* * DES cedential cache. * The cache is indexed by fullname/key to allow for multiple sessions * by the same user from different hosts. * It would be tempting to use the client's IP address rather than the * conversation key as an index, but that could become problematic for * multi-homed hosts that distribute traffic across their interfaces. */ struct des_cred { struct des_cred * dc_next; char * dc_fullname; u32 dc_nickname; des_cblock dc_key; /* conversation key */ des_cblock dc_xkey; /* encrypted conv. key */ des_key_schedule dc_keysched; }; #define ADN_FULLNAME 0 #define ADN_NICKNAME 1 /* * The default slack allowed when checking for replayed credentials * (in milliseconds). */ #define DES_REPLAY_SLACK 2000 /* * Make sure we don't place more than one call to the key server at * a time. */ static int in_keycall = 0; #define FAIL(err) \ { if (data) put_cred(data); \ *authp = rpc_autherr_##err; \ return; \ } void svcauth_des(struct svc_rqst *rqstp, u32 *statp, u32 *authp) { struct svc_buf *argp = &rqstp->rq_argbuf; struct svc_buf *resp = &rqstp->rq_resbuf; struct svc_cred *cred = &rqstp->rq_cred; struct des_cred *data = NULL; u32 cryptkey[2]; u32 cryptbuf[4]; u32 *p = argp->buf; int len = argp->len, slen, i; *authp = rpc_auth_ok; if ((argp->len -= 3) < 0) { *statp = rpc_garbage_args; return; } p++; /* skip length field */ namekind = ntohl(*p++); /* fullname/nickname */ /* Get the credentials */ if (namekind == ADN_NICKNAME) { /* If we can't find the cached session key, initiate a * new session. */ if (!(data = get_cred_bynick(*p++))) FAIL(rejectedcred); } else if (namekind == ADN_FULLNAME) { p = xdr_decode_string(p, &fullname, &len, RPC_MAXNETNAMELEN); if (p == NULL) FAIL(badcred); cryptkey[0] = *p++; /* get the encrypted key */ cryptkey[1] = *p++; cryptbuf[2] = *p++; /* get the encrypted window */ } else { FAIL(badcred); } /* If we're just updating the key, silently discard the request. */ if (data && data->dc_locked) { *authp = rpc_autherr_dropit; _put_cred(data); /* release but don't unlock */ return; } /* Get the verifier flavor and length */ if (ntohl(*p++) != RPC_AUTH_DES && ntohl(*p++) != 12) FAIL(badverf); cryptbuf[0] = *p++; /* encrypted time stamp */ cryptbuf[1] = *p++; cryptbuf[3] = *p++; /* 0 or window - 1 */ if (namekind == ADN_NICKNAME) { status = des_ecb_encrypt((des_block *) cryptbuf, (des_block *) cryptbuf, data->dc_keysched, DES_DECRYPT); } else { /* We first have to decrypt the new session key and * fill in the UNIX creds. */ if (!(data = get_cred_byname(rqstp, authp, fullname, cryptkey))) return; status = des_cbc_encrypt((des_cblock *) cryptbuf, (des_cblock *) cryptbuf, 16, data->dc_keysched, (des_cblock *) &ivec, DES_DECRYPT); } if (status) { printk("svcauth_des: DES decryption failed (status %d)\n", status); FAIL(badverf); } /* Now check the whole lot */ if (namekind == ADN_FULLNAME) { unsigned long winverf; data->dc_window = ntohl(cryptbuf[2]); winverf = ntohl(cryptbuf[2]); if (window != winverf - 1) { printk("svcauth_des: bad window verifier!\n"); FAIL(badverf); } } /* XDR the decrypted timestamp */ cryptbuf[0] = ntohl(cryptbuf[0]); cryptbuf[1] = ntohl(cryptbuf[1]); if (cryptbuf[1] > 1000000) { dprintk("svcauth_des: bad usec value %u\n", cryptbuf[1]); if (namekind == ADN_NICKNAME) FAIL(rejectedverf); FAIL(badverf); } /* * Check for replayed credentials. We must allow for reordering * of requests by the network, and the OS scheduler, hence we * cannot expect timestamps to be increasing monotonically. * This opens a small security hole, therefore the replay_slack * value shouldn't be too large. */ if ((delta = cryptbuf[0] - data->dc_timestamp[0]) <= 0) { switch (delta) { case -1: delta = -1000000; case 0: delta += cryptbuf[1] - data->dc_timestamp[1]; break; default: delta = -1000000; } if (delta < DES_REPLAY_SLACK) FAIL(rejectedverf); #ifdef STRICT_REPLAY_CHECKS /* TODO: compare time stamp to last five timestamps cached * and reject (drop?) request if a match is found. */ #endif } now = xtime; now.tv_secs -= data->dc_window; if (now.tv_secs < cryptbuf[0] || (now.tv_secs == cryptbuf[0] && now.tv_usec < cryptbuf[1])) FAIL(rejectedverf); /* Okay, we're done. Update the lot */ if (namekind == ADN_FULLNAME) data->dc_valid = 1; data->dc_timestamp[0] = cryptbuf[0]; data->dc_timestamp[1] = cryptbuf[1]; put_cred(data); return; garbage: *statp = rpc_garbage_args; return; } /* * Call the keyserver to obtain the decrypted conversation key and * UNIX creds. We use a Linux-specific keycall extension that does * both things in one go. */ static struct des_cred * get_cred_byname(struct svc_rqst *rqstp, u32 *authp, char *fullname, u32 *cryptkey) { static int in_keycall = 0; struct des_cred *cred; if (in_keycall) { *authp = rpc_autherr_dropit; return NULL; } in_keycall = 1; in_keycall = 0; return cred; }