Skip to content
This repository
Fetching contributors…

Cannot retrieve contributors at this time

file 213 lines (173 sloc) 5.351 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
/*
* Copyright (c) 2013 Atheme Development Group
* Rights to this code are as documented in doc/LICENSE.
*
* DH-AES mechanism provider
* Written by Elizabeth Myers, 14 Apr 2013.
*
* Basically the same as DH-BLOWFISH, with a couple alterations:
* 1) AES instead of blowfish. Blowfish suffers from weak keys, and the author
* of it (Bruce Schneier) recommends not using it.
* 2) Username is encrypted with password
* 3) CBC mode is used with AES. The IV is sent with the key by the client.
*
* Why this over ECDSA-NIST256p-CHALLENGE? Not all langs have good support for
* ECDSA. DH and AES support, on the other hand, are relatively ubiqutious. It
* may also be usable in environments where the convenience of a password
* outweighs the benefits of using challenge-response.
*
* Padding structure (sizes in big-endian/network byte order):
* Server sends: <size p><p><size g><g><size pubkey><pubkey>
* Client sends: <size pubkey><pubkey><iv (always 16 bytes)><ciphertext>
*
* Ciphertext is to be the following encrypted:
* <username>\0<password>\0<padding if necessary>
*
* The DH secret is used to encrypt and decrypt blocks as the key.
*
* Note: NEVER reuse IV's. I believe this goes without saying.
* Also ensure your padding is random. This is just good practise.
*/

#include "atheme.h"

#ifdef HAVE_OPENSSL

#include <openssl/bn.h>
#include <openssl/dh.h>
#include <openssl/aes.h>

DECLARE_MODULE_V1
(
"saslserv/dh-aes", false, _modinit, _moddeinit,
PACKAGE_STRING,
"Atheme Development Group <http://www.atheme.org>"
);

static DH *base_dhparams;

sasl_mech_register_func_t *regfuncs;

static int mech_start(sasl_session_t *p, char **out, size_t *out_len);
static int mech_step(sasl_session_t *p, char *message, size_t len, char **out, size_t *out_len);
static void mech_finish(sasl_session_t *p);
sasl_mechanism_t mech = {"DH-AES", &mech_start, &mech_step, &mech_finish};

void _modinit(module_t *m)
{
MODULE_TRY_REQUEST_SYMBOL(m, regfuncs, "saslserv/main", "sasl_mech_register_funcs");

if ((base_dhparams = DH_generate_parameters(256, 5, NULL, NULL)) == NULL)
return;

regfuncs->mech_register(&mech);
}

void _moddeinit(module_unload_intent_t intent)
{
DH_free(base_dhparams);

regfuncs->mech_unregister(&mech);
}

static inline DH *DH_clone(DH *dh)
{
DH *out = DH_new();

out->p = BN_dup(dh->p);
out->g = BN_dup(dh->g);

if (!DH_generate_key(out))
{
DH_free(out);
return NULL;
}

return out;
}

static int mech_start(sasl_session_t *p, char **out, size_t *out_len)
{
char *ptr;
DH *dh;

if ((dh = DH_clone(base_dhparams)) == NULL)
return ASASL_FAIL;

/* Serialize p, g, and pub_key */
*out_len = BN_num_bytes(dh->p) + BN_num_bytes(dh->g) + BN_num_bytes(dh->pub_key) + 6;
*out = malloc((size_t)*out_len);
ptr = *out;

/* p */
*((unsigned int *)ptr) = htons(BN_num_bytes(dh->p));
BN_bn2bin(dh->p, (unsigned char *)ptr + 2);
ptr += 2 + BN_num_bytes(dh->p);

/* g */
*((unsigned int *)ptr) = htons(BN_num_bytes(dh->g));
BN_bn2bin(dh->g, (unsigned char *)ptr + 2);
ptr += 2 + BN_num_bytes(dh->g);

/* pub_key */
*((unsigned int *)ptr) = htons(BN_num_bytes(dh->pub_key));
BN_bn2bin(dh->pub_key, (unsigned char *)ptr + 2);
ptr += 2 + BN_num_bytes(dh->pub_key);

p->mechdata = dh;
return ASASL_MORE;
}

static int mech_step(sasl_session_t *p, char *message, size_t len, char **out, size_t *out_len)
{
DH *dh = NULL;
AES_KEY key;
BIGNUM *their_key = NULL;
myuser_t *mu;
char *secret = NULL, *userpw = NULL, *ptr = NULL;
char iv[AES_BLOCK_SIZE];
int ret = ASASL_FAIL;
uint16_t size;
int secret_size;

if (!p->mechdata)
return ASASL_FAIL;
dh = (DH*)p->mechdata;

/* Their pub_key */
if (len <= 2)
goto end;

size = ntohs(*(uint16_t *)message);
message += 2;
len -= 2;

if (size >= len)
goto end;

if ((their_key = BN_bin2bn(message, size, NULL)) == NULL)
goto end;

message += size;
len -= size;

/* Data must be a multiple of the AES block size. (16)
* Verify we also have an IV and at least one block of data.
* Cap at a rather arbitrary limit of 272 (IV + 16 blocks of 16 each).
*/
if (len < sizeof(iv) + AES_BLOCK_SIZE || len % AES_BLOCK_SIZE || len > 272)
goto end;

/* Extract the IV */
memcpy(iv, message, sizeof(iv));
message += sizeof(iv);
len -= sizeof(iv);

/* Compute shared secret */
secret = malloc(DH_size(dh));
secret_size = DH_compute_key(secret, their_key, dh);
if (secret_size <= 0)
goto end;

/* Decrypt! (AES_set_decrypt_key takes bits not bytes, hence multiply
* by 8) */
AES_set_decrypt_key(secret, secret_size * 8, &key);

ptr = userpw = malloc(len + 1);
userpw[len] = '\0';
AES_cbc_encrypt(message, userpw, len, &key, iv, AES_DECRYPT);

/* Username */
size = strlen(ptr);
if (size++ >= NICKLEN) /* our base64 routines null-terminate - how polite */
goto end;
p->username = strdup(ptr);
ptr += size;
len -= size;
if ((mu = myuser_find_by_nick(p->username)) == NULL)
goto end;

/* Password remains */
if (verify_password(mu, ptr))
ret = ASASL_DONE;
end:
if (their_key)
BN_free(their_key);
free(secret);
free(userpw);
return ret;
}

static void mech_finish(sasl_session_t *p)
{
DH_free((DH *) p->mechdata);
p->mechdata = NULL;
}

#endif /* HAVE_OPENSSL */

/* vim:cinoptions=>s,e0,n0,f0,{0,}0,^0,=s,ps,t0,c3,+s,(2s,us,)20,*30,gs,hs
* vim:ts=8
* vim:sw=8
* vim:noexpandtab
*/
Something went wrong with that request. Please try again.