afpfs-ng-mac/lib/uams.c

1033 lines
31 KiB
C

/*
* uams.c
*
* Copyright (C) 2006 Alex deVries
* Copyright (C) 2007 Derrik Pates
*
*/
#include <string.h>
#include <stdlib.h>
#include "afpfs-ng/dsi.h"
#include "afpfs-ng/afp.h"
#include "afpfs-ng/utils.h"
#include "afpfs-ng/uams_def.h"
#include "config.h"
#ifdef HAVE_LIBGCRYPT
#include <gcrypt.h>
#include <assert.h> /* for assert() */
#endif /* HAVE_LIBGCRYPT */
struct afp_uam {
unsigned int bitmap;
char name[AFP_UAM_LENGTH];
int (*do_server_login)(struct afp_server *server, char *username,
char *password);
int (*do_server_passwd)(struct afp_server * server, char *username,
char * oldpasswd, char * newpasswd);
struct afp_uam * next;
};
static struct afp_uam * uam_base = NULL;
static int noauth_login(struct afp_server *server, char *username,
char *passwd);
static int cleartxt_login(struct afp_server *server, char *username,
char *passwd);
static int cleartxt_passwd(struct afp_server *server, char *username,
char *passwd, char *newpasswd);
#ifdef HAVE_LIBGCRYPT
static int randnum_login(struct afp_server *server, char *username,
char *passwd);
static int randnum2_login(struct afp_server *server, char *username,
char *passwd);
static int dhx_login(struct afp_server *server, char *username, char *passwd);
static int dhx2_login(struct afp_server *server, char *username, char *passwd);
#endif /* HAVE_LIBGCRYPT */
static struct afp_uam uam_noauth =
{UAM_NOUSERAUTHENT,"No User Authent",&noauth_login,NULL,NULL};
static struct afp_uam uam_cleartxt =
{UAM_CLEARTXTPASSWRD,"Cleartxt Passwrd",&cleartxt_login,
&cleartxt_passwd,NULL};
#ifdef HAVE_LIBGCRYPT
static struct afp_uam uam_randnum =
{UAM_RANDNUMEXCHANGE, "Randnum Exchange", &randnum_login,NULL,NULL};
static struct afp_uam uam_randnum2 =
{UAM_2WAYRANDNUM, "2-Way Randnum Exchange", &randnum2_login,NULL,NULL};
static struct afp_uam uam_dhx =
{UAM_DHCAST128, "DHCAST128", &dhx_login, NULL,NULL};
static struct afp_uam uam_dhx2 =
{UAM_DHX2, "DHX2", &dhx2_login, NULL, NULL};
#endif /* HAVE_LIBGCRYPT */
#define UAMS_MAX_NAMES_LIST 255
char uam_names_list[UAMS_MAX_NAMES_LIST];
unsigned int default_uams_mask(void)
{
unsigned int uam_mask=UAM_CLEARTXTPASSWRD ;
#ifdef HAVE_LIBGCRYPT
uam_mask|=UAM_RANDNUMEXCHANGE|UAM_2WAYRANDNUM;
uam_mask|=UAM_DHCAST128 | UAM_DHX2;
#endif
return uam_mask;
}
char * get_uam_names_list(void)
{
return uam_names_list;
}
static int register_uam(struct afp_uam * uam)
{
struct afp_uam * u = uam_base;
if ((uam->bitmap=uam_string_to_bitmap(uam->name))==0) goto error;
if (!uam_base) {
uam_base=uam;
u=uam;
} else {
for (;u->next;u=u->next);
u->next=uam;
}
uam->next=NULL;
/* Add the name to the larger list */
if (strlen(uam_names_list)+20>UAMS_MAX_NAMES_LIST)
goto error;
if (strlen(uam_names_list))
sprintf(uam_names_list+strlen(uam_names_list),", %s",uam->name);
else
sprintf(uam_names_list+strlen(uam_names_list),"%s",uam->name);
return 0;
error:
log_for_client(NULL,AFPFSD,LOG_WARNING,
"Could not register all UAMs\n");
return -1;
}
static struct afp_uam * find_uam_by_bitmap(unsigned int i)
{
struct afp_uam * u=uam_base;
for (;u;u=u->next)
if (u->bitmap==i)
return u;
return NULL;
}
unsigned int find_uam_by_name(const char * name)
{
struct afp_uam * u=uam_base;
for (;u;u=u->next)
if (strcmp(u->name,name)==0)
return u->bitmap;
return 0;
}
int init_uams(void) {
memset(uam_names_list,0,UAMS_MAX_NAMES_LIST);
register_uam(&uam_cleartxt);
register_uam(&uam_noauth);
#ifdef HAVE_LIBGCRYPT
register_uam(&uam_randnum);
register_uam(&uam_randnum2);
register_uam(&uam_dhx);
register_uam(&uam_dhx2);
#endif /* HAVE_LIBGCRYPT */
return 0;
}
static int noauth_login(struct afp_server *server, char *username, char *passwd) {
return afp_login(server, "No User Authent", NULL, 0, NULL);
}
/*
* Request block when using the Cleartext Password UAM:
*
* +------------------+
* | kFPLogin |
* +------------------+
* | 0 |
* +------------------+
* |'Cleartxt Passwrd'|
* +------------------+
* / UserName /
* + - - - - - - - - +
* | 0 |
* + - - - - - - - - +
* | Password |
* | in cleartext |
* | (8 bytes) |
* +------------------+
*/
static int cleartxt_login(struct afp_server *server, char *username, char *passwd) {
char *p, *ai = NULL;
int len, ret;
/* Pack the username and password into the authinfo struct. */
p = ai = calloc(1, len = 1 + strlen(username) + 1 + 8);
if (ai == NULL)
goto cleartxt_fail;
p += copy_to_pascal(p, username) + 1;
if ((long)p & 0x1)
len--;
else
p++;
strncpy(p, passwd, 8);
/* Send the login request on to the server. */
ret = afp_login(server, "Cleartxt Passwrd", ai, len, NULL);
goto cleartxt_cleanup;
cleartxt_fail:
ret = -1;
cleartxt_cleanup:
free(ai);
return ret;
}
/*
* Request block when changing the pasword for cleartext
*
* +------------------+
* | kFPChangePassword|
* +------------------+
* | 0 |
* +------------------+
* |'Cleartxt Passwrd'|
* +------------------+
* / UserName /
* + - - - - - - - - +
* | 0 |
* + - - - - - - - - +
* | Password |
* | in cleartext |
* | (8 bytes) |
* +------------------+
*/
static int cleartxt_passwd(struct afp_server *server,
char *username, char *passwd, char *newpasswd) {
char *p, *ai = NULL;
int len, ret;
/* Pack the username and password into the authinfo struct. */
p = ai = calloc(1, len = 1 + strlen(username) + 1 + 8);
if (ai == NULL)
goto cleartxt_fail;
p += copy_to_pascal(p, username) + 1;
if ((long)p & 0x1)
len--;
else
p++;
strncpy(p, passwd, 8);
/* Send the login request on to the server. */
ret = afp_changepassword(server, "Cleartxt Passwrd", ai, len, NULL);
goto cleartxt_cleanup;
cleartxt_fail:
ret = -1;
cleartxt_cleanup:
free(ai);
return ret;
}
#ifdef HAVE_LIBGCRYPT
/*
* Transaction sequence for Random Number Exchange UAM:
*
* +------------------+ +---------------+ +---------------+
* | FPLogin | | ID | | FPLoginCont |
* +------------------+ +---------------+ +---------------+
* | 0 | | Random number | | 0 |
* +------------------+ | (8 bytes) | +---------------+
* |'Randnum Exchange'| +---------------+ | ID |
* +------------------+ +---------------+
* / UserName / | Random number |
* +------------------+ | encrypted |
* | with password |
* | (8 bytes) |
* +---------------+
*/
static int randnum_login(struct afp_server *server, char *username,
char *passwd) {
char *ai = NULL, *p;
char key_buffer[8];
int ai_len, ret;
const int randnum_len = 8;
gcry_cipher_hd_t ctx;
gcry_error_t ctxerror;
struct afp_rx_buffer rbuf;
unsigned short ID;
p = rbuf.data = calloc(1, rbuf.maxsize = sizeof(ID) + randnum_len);
if (rbuf.data == NULL)
goto randnum_noctx_fail;
rbuf.size = 0;
ai = calloc(1, ai_len = 1 + strlen(username));
if (ai == NULL)
goto randnum_noctx_fail;
copy_to_pascal(ai, username);
/* Send the initial FPLogin request to the server. */
ret = afp_login(server, "Randnum Exchange", ai, ai_len, &rbuf);
free(ai);
ai = NULL;
if (ret != kFPAuthContinue)
goto randnum_noctx_cleanup;
/* For now, if the response block from the server isn't *exactly*
* 10 bytes long (if we got kFPAuthContinue with this UAM, it
* should never be any other size), die a horrible death. */
if (rbuf.size != rbuf.maxsize)
assert("size of data returned during randnum auth process was wrong size, should be 10 bytes!");
/* Copy the relevant values out of the response block the server
* sent to us. */
ID = ntohs(*(unsigned short *)p);
p += sizeof(ID);
/* Establish encryption context for doing password encryption work. */
ctxerror = gcry_cipher_open(&ctx, GCRY_CIPHER_DES,
GCRY_CIPHER_MODE_ECB, 0);
if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
goto randnum_noctx_fail;
/* Copy (up to 8 characters of) the password into key_buffer. */
strncpy(key_buffer, passwd, sizeof(key_buffer));
/* Set the provided password (now in key_buffer) as the encryption
* key in our established context, for subsequent use to encrypt
* the random number that the server sends us. */
ctxerror = gcry_cipher_setkey(ctx, key_buffer, sizeof(key_buffer));
if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
goto randnum_fail;
/* Encrypt the random number data into the authinfo block for sending
* to the server. */
ctxerror = gcry_cipher_encrypt(ctx, p, randnum_len, NULL, 0);
if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
goto randnum_fail;
/* Send the FPLoginCont to the server, containing the server's
* random number encrypted with the password. */
ret = afp_logincont(server, ID, p, randnum_len, NULL);
goto randnum_cleanup;
randnum_noctx_fail:
ret = -1;
goto randnum_noctx_cleanup;
randnum_fail:
ret = -1;
randnum_cleanup:
/* Destroy the encryption context. */
gcry_cipher_close(ctx);
randnum_noctx_cleanup:
free(rbuf.data);
free(ai);
return ret;
}
/*
* First transaction of Two-Way Random Number Exchange UAM:
*
* +------------------------+ +---------------+
* | FPLogin | | ID |
* +------------------------+ +---------------+
* | 0 | | Random number |
* +------------------------+ | (8 bytes) |
* |'2-Way Randnum Exchange'| +---------------+
* +------------------------+
* / UserName /
* +------------------------+
*
* Second transaction of Two-Way Random Number Exchange UAM:
*
* +---------------+ +---------------+
* | FPLoginCont | | Client |
* +---------------+ | random number |
* | 0 | | encrypted |
* +---------------+ | with password |
* | ID | | (8 bytes) |
* +---------------+ +---------------+
* | Random number |
* | encrypted |
* | with password |
* | (8 bytes) |
* +---------------+
* | Client |
* | random number |
* | (8 bytes) |
* +---------------+
*/
static int randnum2_login(struct afp_server *server, char *username, char *passwd) {
char *ai = NULL, *p = NULL, key_buffer[8], crypted[8];
int ai_len, ret, i, carry;
const int randnum_len = 8, crypted_len = 8;
gcry_cipher_hd_t ctx;
gcry_error_t ctxerror;
struct afp_rx_buffer rbuf;
unsigned short ID;
p = rbuf.data = calloc(1, rbuf.maxsize = sizeof(ID) + 8);
if (rbuf.data == NULL)
return -1;
rbuf.size = 0;
ai = calloc(1, ai_len = 1 + strlen(username));
if (ai == NULL)
goto randnum2_noctx_fail;
copy_to_pascal(ai, username);
/* Send the initial FPLogin request to the server. */
ret = afp_login(server, "2-Way Randnum Exchange", ai, ai_len, &rbuf);
free(ai);
ai = NULL;
if (ret != kFPAuthContinue)
goto randnum2_noctx_cleanup;
/* For now, if the response block from the server isn't *exactly*
* 10 bytes long (if we got kFPAuthContinue with this UAM, it
* should never be any other size), die a horrible death. */
if (rbuf.size != rbuf.maxsize)
assert("size of data returned during randnum2 auth process was wrong size, should be 10 bytes!");
/* Copy the relevant values out of the response block the server
* sent to us. */
ID = ntohs(*(unsigned short *)p);
p += sizeof(ID);
/* Establish encryption context for doing password encryption work. */
ctxerror = gcry_cipher_open(&ctx, GCRY_CIPHER_DES,
GCRY_CIPHER_MODE_ECB, 0);
if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
goto randnum2_noctx_fail;
/* Copy (up to 8 characters of) the password into key_buffer, after
* zeroing it out first. */
strncpy(key_buffer, passwd, sizeof(key_buffer));
/* Rotate each byte left one bit, carrying the high bit to the next. */
carry = key_buffer[0] >> 7;
for (i = 0; i < sizeof(key_buffer) - 1; i++)
key_buffer[i] = key_buffer[i] << 1 | key_buffer[i + 1] >> 7;
/* Wrap the high bit we copied right away to the end of the array. */
key_buffer[i] = key_buffer[i] << 1 | carry;
/* Set the provided password (now in key_buffer) as the encryption
* key in our established context, for subsequent use to encrypt
* the random number that the server sends us. */
ctxerror = gcry_cipher_setkey(ctx, key_buffer, 8);
if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
goto randnum2_fail;
/* Setup a new authinfo block for the FPLoginCont invocation. It will
* contain the DES hashed password, followed by our chosen random
* number, which the server will use to hash the password and then
* send back to us for comparison. */
ai = calloc(1, ai_len = crypted_len + randnum_len);
if (ai == NULL)
goto randnum2_fail;
/* Encrypt the random number data into the new authinfo block. */
ctxerror = gcry_cipher_encrypt(ctx, ai, crypted_len, p, randnum_len);
free(rbuf.data);
rbuf.data = NULL;
if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
goto randnum2_fail;
p = ai + crypted_len;
/* Use an internal gcrypt function to create the random number, so
* we can do things (more) portably... */
gcry_create_nonce(p, randnum_len);
/* Make a place for the server's hashing of our password. */
rbuf.data = calloc(1, rbuf.maxsize = 8);
if (rbuf.data == NULL)
goto randnum2_fail;
rbuf.size = 0;
/* Send the FPLoginCont to the server, containing the server's
* random number encrypted with the password, and our random number.
*/
ret = afp_logincont(server, ID, ai, ai_len, &rbuf);
if (ret != kFPNoErr)
goto randnum2_cleanup;
if (rbuf.size != rbuf.maxsize)
assert("size of data returned during randnum2 auth process was wrong size, should be 8 bytes!");
/* Encrypt our random number data into crypted[]. */
ctxerror = gcry_cipher_encrypt(ctx, crypted, sizeof(crypted),
p, randnum_len);
if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
goto randnum2_fail;
/* If they didn't match, tell the caller that the user wasn't
* authenticated, so it'll junk the connection. */
if (memcmp(crypted, rbuf.data, sizeof(crypted)) != 0)
ret = kFPUserNotAuth;
goto randnum2_cleanup;
randnum2_noctx_fail:
ret = -1;
goto randnum2_noctx_cleanup;
randnum2_fail:
ret = -1;
randnum2_cleanup:
/* Destroy the encryption context. */
gcry_cipher_close(ctx);
randnum2_noctx_cleanup:
free(rbuf.data);
free(ai);
return ret;
}
/* The initialization vectors are universally fixed. These are the values
* documented by Apple.
*/
static const unsigned char dhx_c2siv[] = { 'L', 'W', 'a', 'l', 'l', 'a', 'c', 'e' };
static const unsigned char dhx_s2civ[] = { 'C', 'J', 'a', 'l', 'b', 'e', 'r', 't' };
/* The values of p and g are fixed for DHCAST128. */
static const unsigned char p_binary[] = { 0xba, 0x28, 0x73, 0xdf, 0xb0, 0x60,
0x57, 0xd4, 0x3f, 0x20, 0x24, 0x74, 0x4c, 0xee, 0xe7, 0x5b };
static const unsigned char g_binary[] = { 0x07 };
/*
* Transaction sequence for DHCAST128 UAM:
*
* +---------------+ +----------------+ +-----------------+
* | FPLogin | + ID + | FPLoginCont |
* +---------------+ +----------------+ +-----------------+
* | 0 | | Random number | | 0 |
* +---------------+ | (16 bytes) | +-----------------+
* / AFPVersion / +----------------+ + ID +
* +---------------+ | Nonce followed | +-----------------+
* | 'DHCAST128' | | by 16 bytes of | | Nonce + 1, |
* +---------------+ | zero encrypted | | followed by the |
* / Username / | by session key | | password, all |
* + - - - - - - - + | (32 bytes) | | encrypted by |
* | 0 | +----------------+ | session key |
* + - - - - - - - + +-----------------|
* | Random number |
* | (16 bytes) |
* +---------------+
*/
static int dhx_login(struct afp_server *server, char *username, char *passwd) {
char *ai = NULL, *d = NULL;
unsigned char Ra_binary[32], K_binary[16];
int ai_len, ret;
const int Ma_len = 16, Mb_len = 16, nonce_len = 16;
gcry_mpi_t p, g, Ra, Ma, Mb, K, nonce, new_nonce;
size_t len;
struct afp_rx_buffer rbuf;
unsigned short ID;
gcry_cipher_hd_t ctx;
gcry_error_t ctxerror;
rbuf.data = NULL;
/* Initialize all gcry_mpi_t variables, so they can all be uninitialized
* in an orderly manner later. */
p = gcry_mpi_new(0);
g = gcry_mpi_new(0);
Ra = gcry_mpi_new(0);
Ma = gcry_mpi_new(0);
Mb = gcry_mpi_new(0);
K = gcry_mpi_new(0);
nonce = gcry_mpi_new(0);
new_nonce = gcry_mpi_new(0);
/* Get p and g into a form that libgcrypt can use */
gcry_mpi_scan(&p, GCRYMPI_FMT_USG, p_binary, sizeof(p_binary), NULL);
gcry_mpi_scan(&g, GCRYMPI_FMT_USG, g_binary, sizeof(g_binary), NULL);
/* Get random bytes for Ra. */
gcry_randomize(Ra_binary, sizeof(Ra_binary), GCRY_STRONG_RANDOM);
/* Translate the binary form of Ra into libgcrypt's preferred form */
gcry_mpi_scan(&Ra, GCRYMPI_FMT_USG, Ra_binary, sizeof(Ra_binary), NULL);
/* Ma = g^Ra mod p <- This is our "public" key, which we exchange
* with the remote server to help make K, the session key. */
gcry_mpi_powm(Ma, g, Ra, p);
/* The first authinfo block, containing the username and our Ma value. */
d = ai = calloc(1, ai_len = 1 + strlen(username) + 1 + Ma_len);
if (ai == NULL)
goto dhx_noctx_fail;
d += copy_to_pascal(ai, username) + 1;
if (((long)d) % 2)
d++;
else
ai_len--;
/* Extract Ma to send to the server for the exchange. */
gcry_mpi_print(GCRYMPI_FMT_USG, d, Ma_len, &len, Ma);
if (len < Ma_len) {
memmove(d + Ma_len - len, d, len);
memset(d, 0, Ma_len - len);
}
/* 2 bytes for id, 16 bytes for Mb, 32 bytes of crypted message text */
d = rbuf.data = calloc(1, rbuf.maxsize = 2 + Mb_len + 32);
if (rbuf.data == NULL)
goto dhx_noctx_fail;
rbuf.size = 0;
/* Send the first FPLogin request, and see what happens. */
ret = afp_login(server, "DHCAST128", ai, ai_len, &rbuf);
free(ai);
ai = NULL;
if (ret != kFPAuthContinue)
goto dhx_noctx_cleanup;
/* The block returned from the server should always be 50 bytes.
* If it's not, for now, choke and die loudly so we know it. */
if (rbuf.size != rbuf.maxsize)
assert("size of data returned during dhx auth process was wrong size, should be 50 bytes!");
/* Extract the transaction ID from the server's reply block. */
ID = ntohs(*(unsigned short *)d);
d += sizeof(ID);
/* Now, extract Mb (the server's "public key" part) directly into
* a gcry_mpi_t. */
gcry_mpi_scan(&Mb, GCRYMPI_FMT_USG, d, Mb_len, NULL);
d += Mb_len;
/* d now points to the ciphertext, which we'll decrypt in a bit. */
/* K = Mb^Ra mod p <- This nets us the "session key", which we
* actually use to encrypt and decrypt data. */
gcry_mpi_powm(K, Mb, Ra, p);
gcry_mpi_print(GCRYMPI_FMT_USG, K_binary, sizeof(K_binary), &len, K);
if (len < sizeof(K_binary)) {
memmove(K_binary + (sizeof(K_binary) - len), K_binary, len);
memset(K_binary, 0, sizeof(K_binary) - len);
}
/* FIXME: To support the Reconnect UAM, we need to stash this key
* somewhere in the session data. We'll worry about doing that
* later, but this would be a prime spot to do that. */
/* Set up our encryption context. */
ctxerror = gcry_cipher_open(&ctx, GCRY_CIPHER_CAST5,
GCRY_CIPHER_MODE_CBC, 0);
if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
goto dhx_noctx_fail;
/* Set the binary form of K as our key for this encryption context. */
ctxerror = gcry_cipher_setkey(ctx, K_binary, sizeof(K_binary));
if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
goto dhx_fail;
/* Set the initialization vector for server->client transfer. */
ctxerror = gcry_cipher_setiv(ctx, dhx_s2civ, sizeof(dhx_s2civ));
if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
goto dhx_fail;
/* The plaintext will hold the nonce (16 bytes) and the server's
* signature (16 bytes - we don't actually look at it though). */
len = nonce_len + 16;
/* Decrypt the ciphertext from the server. */
ctxerror = gcry_cipher_decrypt(ctx, d, len, NULL, 0);
if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
goto dhx_fail;
/* Pull the binary form of the nonce into a form that libgcrypt can
* deal with. */
gcry_mpi_scan(&nonce, GCRYMPI_FMT_USG, d, nonce_len, NULL);
/* NOTE: The following 16 bytes of plaintext, which the docs indicate
* as the server signature, will always contain just 0 values - Apple's
* docs claim that due to an error in an early implementation, it will
* always be that way. No point in looking at that. */
/* d still points into rbuf.data, which is no longer needed. */
free(rbuf.data);
rbuf.data = NULL;
/* Increment the nonce by 1 for sending back to the server. */
gcry_mpi_add_ui(new_nonce, nonce, 1);
/* New plaintext is 16 bytes of nonce, and (up to) 64 bytes of
* password (filled out with NULL values). */
d = ai = calloc(1, ai_len = nonce_len + 64);
if (ai == NULL)
goto dhx_fail;
/* Pull the incremented nonce value back out into binary form. */
gcry_mpi_print(GCRYMPI_FMT_USG, d, nonce_len, &len, new_nonce);
if (len < nonce_len) {
memmove(d + nonce_len - len, d, len);
memset(d, 0, nonce_len - len);
}
d += nonce_len;
/* Copy the user's password into the plaintext. */
strncpy(d, passwd, 64);
/* Set the initialization vector for client->server transfer. */
ctxerror = gcry_cipher_setiv(ctx, dhx_c2siv, sizeof(dhx_c2siv));
if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
goto dhx_fail;
/* Encrypt the plaintext to create our new authinfo block. */
ctxerror = gcry_cipher_encrypt(ctx, ai, ai_len, NULL, 0);
if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
goto dhx_fail;
/* Send the FPLoginCont with the new authinfo block, sit back,
* cross fingers... */
ret = afp_logincont(server, ID, ai, ai_len, NULL);
goto dhx_cleanup;
dhx_noctx_fail:
ret = -1;
goto dhx_noctx_cleanup;
dhx_fail:
ret = -1;
dhx_cleanup:
gcry_cipher_close(ctx);
dhx_noctx_cleanup:
gcry_mpi_release(p);
gcry_mpi_release(g);
gcry_mpi_release(Ra);
gcry_mpi_release(Ma);
gcry_mpi_release(Mb);
gcry_mpi_release(K);
gcry_mpi_release(nonce);
gcry_mpi_release(new_nonce);
free(ai);
free(rbuf.data);
return ret;
}
static int dhx2_login(struct afp_server *server, char *username, char *passwd) {
gcry_mpi_t p, g, Ma, Mb, Ra, K, nonce, new_nonce;
char *ai = NULL, *d, *Ra_binary = NULL, *K_binary = NULL;
char *K_hash = NULL, nonce_binary[16];
int ai_len, hash_len, ret;
const int g_len = 4;
size_t len;
struct afp_rx_buffer rbuf;
unsigned short ID, bignum_len;
gcry_cipher_hd_t ctx;
gcry_error_t ctxerror;
rbuf.data = NULL;
p = gcry_mpi_new(0);
g = gcry_mpi_new(0);
Ra = gcry_mpi_new(0);
Ma = gcry_mpi_new(0);
Mb = gcry_mpi_new(0);
K = gcry_mpi_new(0);
nonce = gcry_mpi_new(0);
new_nonce = gcry_mpi_new(0);
d = ai = calloc(1, ai_len = strlen(username) + 1);
if (ai == NULL)
goto dhx2_noctx_fail;
d += copy_to_pascal(ai, username) + 1;
/* Reply block will contain:
* Transaction ID (2 bytes, MSB)
* g (4 bytes, MSB)
* length of large values in bytes (2 bytes, MSB)
* p (minimum 64 bytes, indicated by length value, MSB)
* Mb (minimum 64 bytes, indicated by length value, MSB)
* We'll reserve 256 bytes for each of p and Mb.
* FIXME: We need to retool this to handle any length for p and Mb;
* I've only ever seen it be 64 bytes, but it could easily be larger. */
d = rbuf.data = calloc(1, rbuf.maxsize = 2 + 4 + 2 + 256 * 2);
if (rbuf.data == NULL)
goto dhx2_noctx_fail;
rbuf.size = 0;
/* Send the initial request in the login sequence. */
ret = afp_login(server, "DHX2", ai, ai_len, &rbuf);
free(ai);
ai = NULL;
if (ret != kFPAuthContinue)
goto dhx2_noctx_cleanup;
/* Pull the transaction ID out of the reply block. */
ID = ntohs(*(unsigned short *)d);
d += sizeof(ID);
/* Pull the value of g out of the reply block and directly into an
* gcry_mpi_t container for later use with libgcrypt. */
gcry_mpi_scan(&g, GCRYMPI_FMT_USG, d, g_len, NULL);
d += g_len;
bignum_len = ntohs(*(unsigned short *)d);
d += sizeof(bignum_len);
if (bignum_len > 256)
assert("server indicates large number length too large for us (> 256 bytes)?");
/* Extract p into an gcry_mpi_t. */
gcry_mpi_scan(&p, GCRYMPI_FMT_USG, d, bignum_len, NULL);
d += bignum_len;
/* Extract Mb into an gcry_mpi_t. */
gcry_mpi_scan(&Mb, GCRYMPI_FMT_USG, d, bignum_len, NULL);
free(rbuf.data);
rbuf.data = NULL;
Ra_binary = calloc(1, bignum_len);
if (Ra_binary == NULL)
goto dhx2_noctx_fail;
/* Get random bytes for Ra. */
gcry_randomize(Ra_binary, bignum_len, GCRY_STRONG_RANDOM);
/* Pull the random value we just read into an gcry_mpi_t so we can do
* large-value exponentiation, and generate our Ma. */
gcry_mpi_scan(&Ra, GCRYMPI_FMT_USG, Ra_binary, bignum_len, NULL);
free(Ra_binary);
Ra_binary = NULL;
/* Ma = g^Ra mod p <- This is our "public" key, which we exchange
* with the remote server to help make K, the session key. */
gcry_mpi_powm(Ma, g, Ra, p);
/* K = Mb^Ra mod p <- This nets us the "session key", which we
* actually use to encrypt and decrypt data. */
gcry_mpi_powm(K, Mb, Ra, p);
K_binary = calloc(1, bignum_len);
if (K_binary == NULL)
goto dhx2_noctx_fail;
gcry_mpi_print(GCRYMPI_FMT_USG, K_binary, bignum_len, &len, K);
if (len < bignum_len) {
memmove(K_binary + bignum_len - len, K_binary, len);
memset(K_binary, 0, bignum_len - len);
}
/* Use a one-shot hash function to generate the MD5 hash of K. */
K_hash = calloc(1, hash_len = gcry_md_get_algo_dlen(GCRY_MD_MD5));
if (K_hash == NULL)
goto dhx2_noctx_fail;
gcry_md_hash_buffer(GCRY_MD_MD5, K_hash, K_binary, bignum_len);
/* FIXME: To support the Reconnect UAM, we need to stash this key
* somewhere in the session data. We'll worry about doing that
* later, but this would be a prime spot to do that. */
/* Use an internal gcrypt function to create the random number, so
* we can do things (more) portably... */
gcry_create_nonce(nonce_binary, sizeof(nonce_binary));
/* Set up our encryption context. */
ctxerror = gcry_cipher_open(&ctx, GCRY_CIPHER_CAST5,
GCRY_CIPHER_MODE_CBC, 0);
if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
goto dhx2_noctx_fail;
/* Set the hashed form of K as our key for this encryption context. */
ctxerror = gcry_cipher_setkey(ctx, K_hash, hash_len);
free(K_hash);
K_hash = NULL;
if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
goto dhx2_fail;
/* Set the initialization vector for client->server transfer. */
ctxerror = gcry_cipher_setiv(ctx, dhx_c2siv, sizeof(dhx_s2civ));
if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
goto dhx2_fail;
/* The new authinfo block will contain Ma (our "public" key part) and
* the encrypted form of our nonce. */
d = ai = calloc(1, ai_len = bignum_len + sizeof(nonce_binary));
if (ai == NULL)
goto dhx2_fail;
gcry_mpi_print(GCRYMPI_FMT_USG, d, bignum_len, &len, Ma);
if (len < bignum_len) {
memmove(d + bignum_len - len, d, len);
memset(d, 0, bignum_len - len);
}
d += bignum_len;
/* Encrypt our nonce into the new authinfo block. */
ctxerror = gcry_cipher_encrypt(ctx, d, sizeof(nonce_binary),
nonce_binary, sizeof(nonce_binary));
if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
goto dhx2_fail;
/* Reply block will contain ID, then the encrypted form of our
* nonce + 1 and the server's nonce. */
d = rbuf.data = calloc(1, rbuf.maxsize = sizeof(ID) +
sizeof(nonce_binary) * 2);
if (rbuf.data == NULL)
goto dhx2_fail;
rbuf.size = 0;
ret = afp_logincont(server, ID, ai, ai_len, &rbuf);
free(ai);
ai = NULL;
if (ret != kFPAuthContinue)
goto dhx2_cleanup;
/* Get the new transaction ID for the last portion of the exchange. */
ID = ntohs(*(unsigned short *)d);
d += sizeof(ID);
/* Set the initialization vector for server->client transfer. */
ctxerror = gcry_cipher_setiv(ctx, dhx_s2civ, sizeof(dhx_s2civ));
if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
goto dhx2_fail;
len = rbuf.maxsize - sizeof(ID);
/* Decrypt the ciphertext from the server. */
ctxerror = gcry_cipher_decrypt(ctx, d, len, NULL, 0);
if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
goto dhx2_fail;
/* Pull our nonce into an gcry_mpi_t so we can operate. */
gcry_mpi_scan(&nonce, GCRYMPI_FMT_USG, nonce_binary, sizeof(nonce_binary),
NULL);
/* Increment our nonce by one. */
gcry_mpi_add_ui(new_nonce, nonce, 1);
/* Pull the incremented nonce back out into binary form. */
gcry_mpi_print(GCRYMPI_FMT_USG, nonce_binary, sizeof(nonce_binary), &len,
new_nonce);
if (len < sizeof(nonce_binary)) {
memmove(nonce_binary + sizeof(nonce_binary) - len,
nonce_binary, len);
memset(nonce_binary, 0, sizeof(nonce_binary) - len);
}
/* Compare our incremented nonce to the server's incremented copy
* of our original nonce value; if they don't match, something
* terrible has happened. */
if (memcmp(nonce_binary, d, 16) != 0)
assert("nonce check failed while running dhx2 authentication");
d += sizeof(nonce_binary);
/* Pull the server's nonce value into an gcry_mpi_t. */
gcry_mpi_scan(&nonce, GCRYMPI_FMT_USG, d, sizeof(nonce_binary), NULL);
/* d still points into rbuf.data; let's dispose of it safely. */
free(rbuf.data);
rbuf.data = NULL;
/* Increment the server's nonce by one. */
gcry_mpi_add_ui(new_nonce, nonce, 1);
/* The new plaintext will need 16 bytes for the server nonce (after
* incrementing), followed by 256 bytes of null-filled space for the
* user's password. */
d = ai = calloc(1, ai_len = sizeof(nonce_binary) + 256);
if (ai == NULL)
goto dhx2_fail;
/* Extract the binary form of the incremented server nonce into
* the plaintext buffer. */
gcry_mpi_print(GCRYMPI_FMT_USG, d, sizeof(nonce_binary), &len, new_nonce);
if (len < sizeof(nonce_binary)) {
memmove(d + sizeof(nonce_binary) - len, d, len);
memset(d, 0, sizeof(nonce_binary) - len);
}
d += sizeof(nonce_binary);
/* Copy the user's password into the plaintext buffer. */
strncpy(d, passwd, 256);
/* Set the initialization vector for client->server transfer. */
ctxerror = gcry_cipher_setiv(ctx, dhx_c2siv, sizeof(dhx_s2civ));
if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
goto dhx2_fail;
/* Encrypt our nonce into the new authinfo block. */
ctxerror = gcry_cipher_encrypt(ctx, ai, ai_len, NULL, 0);
if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
goto dhx2_fail;
/* Send the FPLoginCont with the new authinfo block, sit back,
* cross fingers... */
ret = afp_logincont(server, ID, ai, ai_len, NULL);
goto dhx2_cleanup;
dhx2_noctx_fail:
ret = -1;
goto dhx2_noctx_cleanup;
dhx2_fail:
ret = -1;
dhx2_cleanup:
gcry_cipher_close(ctx);
dhx2_noctx_cleanup:
gcry_mpi_release(p);
gcry_mpi_release(g);
gcry_mpi_release(Ra);
gcry_mpi_release(Ma);
gcry_mpi_release(Mb);
gcry_mpi_release(K);
gcry_mpi_release(nonce);
gcry_mpi_release(new_nonce);
free(Ra_binary);
free(K_binary);
free(K_hash);
free(ai);
free(rbuf.data);
return ret;
}
#endif /* HAVE_LIBGCRYPT */
int afp_dologin(struct afp_server *server,
unsigned int uam, char * username, char * passwd)
{
struct afp_uam * u;
if ((u=find_uam_by_bitmap(uam))==NULL) {
log_for_client(NULL,AFPFSD,LOG_WARNING,
"Unknown uam\n");
return -1;
}
return u->do_server_login(server, username, passwd);
}
int afp_dopasswd(struct afp_server *server,
unsigned int uam, char * username,
char * oldpasswd, char * newpasswd)
{
struct afp_uam * u;
if ((u=find_uam_by_bitmap(uam))==NULL) {
log_for_client(NULL,AFPFSD,LOG_WARNING,
"Unknown uam\n");
return -1;
}
if (u->do_server_passwd)
return u->do_server_passwd(server,username,oldpasswd,newpasswd);
else return 0;
}