/* SRP SASL plugin * Ken Murchison * Tim Martin 3/17/00 * $Id: srp.c,v 1.2 2002/05/22 17:57:03 snsimon Exp $ */ /* * Copyright (c) 2001 Carnegie Mellon University. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The name "Carnegie Mellon University" must not be used to * endorse or promote products derived from this software without * prior written permission. For permission or any other legal * details, please contact * Office of Technology Transfer * Carnegie Mellon University * 5000 Forbes Avenue * Pittsburgh, PA 15213-3890 * (412) 268-4387, fax: (412) 268-7395 * tech-transfer@andrew.cmu.edu * * 4. Redistributions of any form whatsoever must retain the following * acknowledgment: * "This product includes software developed by Computing Services * at Carnegie Mellon University (http://www.cmu.edu/computing/)." * * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include /* for big number support */ #include /* for digest and cipher support */ #include #include #include #if OPENSSL_VERSION_NUMBER < 0x00907000L #define MD5_H /* suppress internal MD5 */ #endif #include #include "plugin_common.h" #include "sasldb.h" #ifdef WIN32 /* This must be after sasl.h, saslutil.h */ #include "saslSRP.h" #endif #ifdef macintosh #include #endif /***************************** Common Section *****************************/ static const char plugin_id[] = "$Id: srp.c,v 1.2 2002/05/22 17:57:03 snsimon Exp $"; /* Size of diffie-hellman secrets a and b */ #define BITSFORab 64 /* How many bytes big should the salt be? */ #define SRP_SALT_SIZE 16 /* Size limit of SRP buffer */ #define MAXBUFFERSIZE 2147483643 #define DEFAULT_MDA "SHA-1" #define OPTION_MDA "mda=" #define OPTION_REPLAY_DETECTION "replay detection" #define OPTION_INTEGRITY "integrity=" #define OPTION_CONFIDENTIALITY "confidentiality=" #define OPTION_MANDATORY "mandatory=" #define OPTION_MAXBUFFERSIZE "maxbuffersize=" /* Table of recommended Modulus (base 16) and Generator pairs */ struct Ng { char *N; unsigned long g; } Ng_tab[] = { /* [264 bits] */ { "115B8B692E0E045692CF280B436735C77A5A9E8A9E7ED56C965F87DB5B2A2ECE3", 2 }, /* [384 bits] */ { "8025363296FB943FCE54BE717E0E2958A02A9672EF561953B2BAA3BAACC3ED5754EB764C7AB7184578C57D5949CCB41B", 2 }, /* [512 bits] */ { "D4C7F8A2B32C11B8FBA9581EC4BA4F1B04215642EF7355E37C0FC0443EF756EA2C6B8EEB755A1C723027663CAA265EF785B8FF6A9B35227A52D86633DBDFCA43", 2 }, /* [640 bits] */ { "C94D67EB5B1A2346E8AB422FC6A0EDAEDA8C7F894C9EEEC42F9ED250FD7F0046E5AF2CF73D6B2FA26BB08033DA4DE322E144E7A8E9B12A0E4637F6371F34A2071C4B3836CBEEAB15034460FAA7ADF483", 2 }, /* [768 bits] */ { "B344C7C4F8C495031BB4E04FF8F84EE95008163940B9558276744D91F7CC9F402653BE7147F00F576B93754BCDDF71B636F2099E6FFF90E79575F3D0DE694AFF737D9BE9713CEF8D837ADA6380B1093E94B6A529A8C6C2BE33E0867C60C3262B", 2 }, /* [1024 bits] */ { "EEAF0AB9ADB38DD69C33F80AFA8FC5E86072618775FF3C0B9EA2314C9C256576D674DF7496EA81D3383B4813D692C6E0E0D5D8E250B98BE48E495C1D6089DAD15DC7D7B46154D6B6CE8EF4AD69B15D4982559B297BCF1885C529F566660E57EC68EDBC3C05726CC02FD4CBF4976EAA9AFD5138FE8376435B9FC61D2FC0EB06E3", 2 }, /* [1280 bits] */ { "D77946826E811914B39401D56A0A7843A8E7575D738C672A090AB1187D690DC43872FC06A7B6A43F3B95BEAEC7DF04B9D242EBDC481111283216CE816E004B786C5FCE856780D41837D95AD787A50BBE90BD3A9C98AC0F5FC0DE744B1CDE1891690894BC1F65E00DE15B4B2AA6D87100C9ECC2527E45EB849DEB14BB2049B163EA04187FD27C1BD9C7958CD40CE7067A9C024F9B7C5A0B4F5003686161F0605B", 2 }, /* [1536 bits] */ { "9DEF3CAFB939277AB1F12A8617A47BBBDBA51DF499AC4C80BEEEA9614B19CC4D5F4F5F556E27CBDE51C6A94BE4607A291558903BA0D0F84380B655BB9A22E8DCDF028A7CEC67F0D08134B1C8B97989149B609E0BE3BAB63D47548381DBC5B1FC764E3F4B53DD9DA1158BFD3E2B9C8CF56EDF019539349627DB2FD53D24B7C48665772E437D6C7F8CE442734AF7CCB7AE837C264AE3A9BEB87F8A2FE9B8B5292E5A021FFF5E91479E8CE7A28C2442C6F315180F93499A234DCF76E3FED135F9BB", 2 }, /* [2048 bits] */ { "AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050A37329CBB4A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0CF6095179A163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF747359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A436C6481F1D2B9078717461A5B9D32E688F87748544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB3786160279004E57AE6AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DBFBB694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73", 2 } }; #define NUM_Ng (sizeof(Ng_tab) / sizeof(struct Ng)) typedef struct layer_option_s { const char *name; /* name used in option strings */ unsigned enabled; /* enabled? determined at run-time */ unsigned bit; /* unique bit in bitmask */ sasl_ssf_t ssf; /* ssf of layer */ const char *evp_name; /* name used for lookup in EVP table */ } layer_option_t; static layer_option_t digest_options[] = { {"SHA-1", 0, (1<<0), 1, "sha1"}, {"RIPEMD-160", 0, (1<<1), 1, "rmd160"}, {"MD5", 0, (1<<2), 1, "md5"}, {NULL, 0, (0<<0), 0, NULL} }; static layer_option_t *default_digest = &digest_options[0]; static layer_option_t *server_mda = NULL; static layer_option_t cipher_options[] = { {"DES", 0, (1<<0), 56, "des-ofb"}, {"3DES", 0, (1<<1), 112, "des-ede-ofb"}, {"AES", 0, (1<<2), 128, "aes-128-ofb"}, {"Blowfish", 0, (1<<3), 128, "bf-ofb"}, {"CAST-128", 0, (1<<4), 128, "cast5-ofb"}, {"IDEA", 0, (1<<5), 128, "idea-ofb"}, {NULL, 0, (0<<0), 0, NULL} }; /* XXX Hack until OpenSSL 0.9.7 */ #if OPENSSL_VERSION_NUMBER < 0x00907000L static layer_option_t *default_cipher = &cipher_options[0]; #else static layer_option_t *default_cipher = &cipher_options[2]; #endif enum { BIT_REPLAY_DETECTION= (1<<0), BIT_INTEGRITY= (1<<1), BIT_CONFIDENTIALITY= (1<<2) }; typedef struct srp_options_s { unsigned mda; /* bitmask of MDAs */ unsigned replay_detection; /* replay detection on/off flag */ unsigned integrity; /* bitmask of integrity layers */ unsigned confidentiality; /* bitmask of confidentiality layers */ unsigned mandatory; /* bitmask of mandatory layers */ unsigned long maxbufsize; /* max # bytes processed by security layer */ } srp_options_t; /* The main SRP context */ typedef struct context { int state; BIGNUM N; BIGNUM g; BIGNUM v; /* verifier */ BIGNUM B; BIGNUM a; BIGNUM A; char *K; int Klen; char *M1; int M1len; char *authid; /* authentication id (server) */ char *userid; /* authorization id (server) */ sasl_secret_t *password; /* user secret (client) */ unsigned int free_password; /* set if we need to free password */ char *client_options; char *server_options; srp_options_t client_opts; char *salt; int saltlen; const EVP_MD *md; /* underlying MDA */ /* copy of utils from the params structures */ const sasl_utils_t *utils; /* per-step mem management */ char *out_buf; unsigned out_buf_len; /* Layer foo */ unsigned enabled; /* bitmask of enabled layers */ const EVP_MD *hmac_md; /* HMAC for integrity */ const EVP_CIPHER *cipher; /* cipher for confidentiality */ /* replay detection sequence numbers */ int seqnum_out; int seqnum_in; /* for encoding/decoding mem management */ buffer_info_t *enc_in_buf; char *encode_buf, *decode_buf, *decode_once_buf; unsigned encode_buf_len, decode_buf_len, decode_once_buf_len; char *encode_tmp_buf, *decode_tmp_buf; unsigned encode_tmp_buf_len, decode_tmp_buf_len; /* layers buffering */ char *buffer; int bufsize; char sizebuf[4]; int cursize; int size; int needsize; } context_t; static int srp_encode(void *context, const struct iovec *invec, unsigned numiov, const char **output, unsigned *outputlen) { context_t *text = (context_t *) context; int hashlen = 0; char hash[EVP_MAX_MD_SIZE+1]; /* 1 for os() count */ int tmpnum; struct buffer_info *inblob, bufinfo; char *input; unsigned inputlen; int ret; if (!context || !invec || !numiov || !output || !outputlen) { PARAMERROR( text->utils ); return SASL_BADPARAM; } if (numiov > 1) { ret = _plug_iovec_to_buf(text->utils, invec, numiov, &text->enc_in_buf); if (ret != SASL_OK) return ret; inblob = text->enc_in_buf; } else { /* avoid the data copy */ bufinfo.data = invec[0].iov_base; bufinfo.curlen = invec[0].iov_len; inblob = &bufinfo; } input = inblob->data; inputlen = inblob->curlen; if (text->enabled & BIT_CONFIDENTIALITY) { EVP_CIPHER_CTX ctx; unsigned char IV[EVP_MAX_IV_LENGTH]; unsigned char block1[EVP_MAX_IV_LENGTH]; unsigned k = 8; /* EVP_CIPHER_CTX_block_size() isn't working */ unsigned enclen = 0; unsigned tmplen; ret = _plug_buf_alloc(text->utils, &(text->encode_tmp_buf), &(text->encode_tmp_buf_len), inputlen + 2 * k); if (ret != SASL_OK) return ret; EVP_CIPHER_CTX_init(&ctx); memset(IV, 0, sizeof(IV)); EVP_EncryptInit(&ctx, text->cipher, text->K, IV); /* construct the first block so that octets #k-1 and #k * are exact copies of octets #1 and #2 */ text->utils->rand(text->utils->rpool, block1, k - 2); memcpy(block1 + k-2, block1, 2); EVP_EncryptUpdate(&ctx, text->encode_tmp_buf, &tmplen, block1, k); enclen += tmplen; EVP_EncryptUpdate(&ctx, text->encode_tmp_buf + enclen, &tmplen, input, inputlen); enclen += tmplen; EVP_EncryptFinal(&ctx, text->encode_tmp_buf + enclen, &tmplen); enclen += tmplen; EVP_CIPHER_CTX_cleanup(&ctx); input = text->encode_tmp_buf; inputlen = enclen; } if (text->enabled & BIT_INTEGRITY) { HMAC_CTX hmac_ctx; HMAC_Init(&hmac_ctx, text->K, text->Klen, text->hmac_md); HMAC_Update(&hmac_ctx, input, inputlen); if (text->enabled & BIT_REPLAY_DETECTION) { tmpnum = htonl(text->seqnum_out); HMAC_Update(&hmac_ctx, (char *) &tmpnum, 4); text->seqnum_out++; } HMAC_Final(&hmac_ctx, hash+1, &hashlen); hash[0] = hashlen++ & 0xFF; /* set os() count */ } /* 4 for length + input size + hashlen for integrity (could be zero) */ *outputlen = 4 + inputlen + hashlen; ret = _plug_buf_alloc(text->utils, &(text->encode_buf), &(text->encode_buf_len), *outputlen); if (ret != SASL_OK) return ret; tmpnum = inputlen+hashlen; tmpnum = htonl(tmpnum); memcpy(text->encode_buf, &tmpnum, 4); memcpy(text->encode_buf+4, input, inputlen); memcpy(text->encode_buf+4+inputlen, hash, hashlen); *output = text->encode_buf; return SASL_OK; } /* decode a single SRP packet */ static int srp_decode_once(void *context, const char **input, unsigned *inputlen, char **output, unsigned *outputlen) { context_t *text = (context_t *) context; int tocopy; unsigned diff; int ret; if (text->needsize > 0) { /* 4 bytes for how long message is */ /* if less than 4 bytes just copy those we have into text->size */ if (*inputlen < 4) tocopy = *inputlen; else tocopy = 4; if (tocopy > text->needsize) tocopy = text->needsize; memcpy(text->sizebuf + 4 - text->needsize, *input, tocopy); text->needsize-=tocopy; *input += tocopy; *inputlen -= tocopy; if (text->needsize == 0) { /* got all of size */ memcpy(&(text->size), text->sizebuf, 4); text->cursize = 0; text->size = ntohl(text->size); if ((text->size > 0xFFFF) || (text->size < 0)) { return SASL_FAIL; /* too big probably error */ } if (!text->buffer) text->buffer=text->utils->malloc(text->size+5); else text->buffer=text->utils->realloc(text->buffer,text->size+5); if (text->buffer == NULL) return SASL_NOMEM; } *outputlen = 0; *output = NULL; if (*inputlen == 0) /* have to wait until next time for data */ return SASL_OK; if (text->size==0) /* should never happen */ return SASL_FAIL; } diff = text->size - text->cursize; /* bytes need for full message */ if (!text->buffer) return SASL_FAIL; if (*inputlen < diff) { /* not enough for a decode */ memcpy(text->buffer + text->cursize, *input, *inputlen); text->cursize += *inputlen; *inputlen = 0; *outputlen = 0; *output = NULL; return SASL_OK; } else { memcpy(text->buffer+text->cursize, *input, diff); *input += diff; *inputlen -= diff; } { char *buf = text->buffer; int buflen = text->size; int hashlen = 0; if (text->enabled & BIT_INTEGRITY) { HMAC_CTX hmac_ctx; char hash[EVP_MAX_MD_SIZE+1]; /* 1 for os() count */ int tmpnum; int i; HMAC_Init(&hmac_ctx, text->K, text->Klen, text->hmac_md); hashlen = EVP_MD_size(text->hmac_md) + 1; /* 1 for os() count */ if (buflen < hashlen) { text->utils->seterror(text->utils->conn, 0, "SRP input is smaller" "than hash length: %d vs %d\n", buflen, hashlen); return SASL_BADPROT; } /* create my version of the hash */ HMAC_Update(&hmac_ctx, buf, buflen - hashlen); if (text->enabled & BIT_REPLAY_DETECTION) { tmpnum = htonl(text->seqnum_in); HMAC_Update(&hmac_ctx, (char *) &tmpnum, 4); text->seqnum_in ++; } HMAC_Final(&hmac_ctx, hash+1, NULL); hash[0] = (hashlen-1) & 0xFF; /* set os() count */ /* compare to hash given */ for (i = 0; i < hashlen; i++) { if (hash[i] != buf[buflen - hashlen + i]) { SETERROR(text->utils, "Hash is incorrect\n"); return SASL_BADMAC; } } } if (text->enabled & BIT_CONFIDENTIALITY) { EVP_CIPHER_CTX ctx; unsigned char IV[EVP_MAX_IV_LENGTH]; unsigned char block1[EVP_MAX_IV_LENGTH]; unsigned k = 8; /* EVP_CIPHER_CTX_block_size() isn't working */ unsigned declen = 0; unsigned tmplen; ret = _plug_buf_alloc(text->utils, &text->decode_tmp_buf, &text->decode_tmp_buf_len, buflen - hashlen); if (ret != SASL_OK) return ret; EVP_CIPHER_CTX_init(&ctx); memset(IV, 0, sizeof(IV)); EVP_DecryptInit(&ctx, text->cipher, text->K, IV); /* check the first block and see if octets #k-1 and #k * are exact copies of octects #1 and #2 */ EVP_DecryptUpdate(&ctx, block1, &tmplen, buf, k); if ((block1[0] != block1[k-2]) || (block1[1] != block1[k-1])) { return SASL_BADAUTH; } EVP_DecryptUpdate(&ctx, text->decode_tmp_buf, &tmplen, buf + k, buflen - k - hashlen); declen += tmplen; EVP_DecryptFinal(&ctx, text->decode_tmp_buf + declen, &tmplen); declen += tmplen; EVP_CIPHER_CTX_cleanup(&ctx); buf = text->decode_tmp_buf; *outputlen = declen; } else { *outputlen = buflen - hashlen; } ret = _plug_buf_alloc(text->utils, &(text->decode_once_buf), &(text->decode_once_buf_len), *outputlen); if (ret != SASL_OK) return ret; memcpy(text->decode_once_buf, buf, *outputlen); *output = text->decode_once_buf; } text->size = -1; text->needsize = 4; return SASL_OK; } /* decode and concatenate multiple SRP packets */ static int srp_decode(void *context, const char *input, unsigned inputlen, const char **output, unsigned *outputlen) { context_t *text = (context_t *) context; int ret; ret = _plug_decode(text->utils, context, input, inputlen, &text->decode_buf, &text->decode_buf_len, outputlen, srp_decode_once); *output = text->decode_buf; return ret; } #define MAX_BUFFER_LEN 2147483643 #define MAX_UTF8_LEN 65535 #define MAX_OS_LEN 255 /* * Make a SRP buffer * * in1 must exist but the rest may be NULL * */ static int MakeBuffer(context_t *text, char *in1, int in1len, char *in2, int in2len, char *in3, int in3len, char *in4, int in4len, const char **out, unsigned *outlen) { int result; int len; int inbyteorder; char *out2; if (!in1) { text->utils->log(NULL, SASL_LOG_ERR, "At least one buffer must be active\n"); return SASL_FAIL; } len = in1len + in2len + in3len + in4len; if (len > MAX_BUFFER_LEN) { text->utils->log(NULL, SASL_LOG_ERR, "String too long to create SRP buffer string\n"); return SASL_FAIL; } result = _plug_buf_alloc(text->utils, &text->out_buf, &text->out_buf_len, len + 4); if (result != SASL_OK) return result; out2 = text->out_buf; /* put length in */ inbyteorder = htonl(len); memcpy(out2, &inbyteorder, 4); /* copy in data */ memcpy((out2)+4, in1, in1len); if (in2len) memcpy((out2)+4+in1len, in2, in2len); if (in3len) memcpy((out2)+4+in1len+in2len, in3, in3len); if (in4len) memcpy((out2)+4+in1len+in2len+in3len, in4, in4len); *outlen = len + 4; *out = out2; return SASL_OK; } /* Un'buffer' a string * * 'out' becomes a pointer into 'in' not an allocation */ static int UnBuffer(const sasl_utils_t *utils, char *in, int inlen, char **out, int *outlen) { int lenbyteorder; int len; if ((!in) || (inlen < 4)) { utils->seterror(utils->conn, 0, "Buffer is not big enough to be SRP buffer: %d\n", inlen); return SASL_BADPROT; } /* get the length */ memcpy(&lenbyteorder, in, 4); len = ntohl(lenbyteorder); /* make sure it's right */ if (len + 4 != inlen) { SETERROR(utils, "SRP Buffer isn't of the right length\n"); return SASL_BADPROT; } *out = in+4; *outlen = len; return SASL_OK; } static int MakeUTF8(const sasl_utils_t *utils, char *in, char **out, int *outlen) { int llen; short len; short inbyteorder; if (!in) { utils->log(NULL, SASL_LOG_ERR, "Can't create utf8 string from null"); return SASL_FAIL; } /* xxx actual utf8 conversion */ llen = strlen(in); if (llen > MAX_UTF8_LEN) { utils->log(NULL, SASL_LOG_ERR, "String too long to create utf8 string\n"); return SASL_FAIL; } len = (short)llen; *out = utils->malloc(len+2); if (!*out) return SASL_NOMEM; /* put in len */ inbyteorder = htons(len); memcpy(*out, &inbyteorder, 2); /* put in data */ memcpy((*out)+2, in, len); *outlen = len+2; return SASL_OK; } static int GetUTF8(const sasl_utils_t *utils, char *data, int datalen, char **outstr, char **left, int *leftlen) { short lenbyteorder; int len; if ((!data) || (datalen < 2)) { SETERROR(utils, "Buffer is not big enough to be SRP UTF8\n"); return SASL_BADPROT; } /* get the length */ memcpy(&lenbyteorder, data, 2); len = ntohs(lenbyteorder); /* make sure it's right */ if (len + 2 > datalen) { SETERROR(utils, "Not enough data for this SRP UTF8\n"); return SASL_BADPROT; } *outstr = (char *)utils->malloc(len+1); if (!*outstr) return SASL_NOMEM; memcpy(*outstr, data+2, len); (*outstr)[len] = '\0'; *left = data+len+2; *leftlen = datalen - (len+2); return SASL_OK; } static int MakeOS(const sasl_utils_t *utils, char *in, int inlen, char **out, int *outlen) { if (!in) { utils->log(NULL, SASL_LOG_ERR, "Can't create SRP os string from null"); return SASL_FAIL; } if (inlen > MAX_OS_LEN) { utils->log(NULL, SASL_LOG_ERR, "String too long to create SRP os string\n"); return SASL_FAIL; } *out = utils->malloc(inlen+1); if (!*out) return SASL_NOMEM; /* put in len */ (*out)[0] = inlen & 0xFF; /* put in data */ memcpy((*out)+1, in, inlen); *outlen = inlen+1; return SASL_OK; } static int GetOS(const sasl_utils_t *utils, char *data, int datalen, char **outstr, int *outlen, char **left, int *leftlen) { int len; if ((!data) || (datalen < 1)) { SETERROR(utils, "Buffer is not big enough to be SRP os\n"); return SASL_BADPROT; } /* get the length */ len = (unsigned char)data[0]; /* make sure it's right */ if (len + 1 > datalen) { SETERROR(utils, "Not enough data for this SRP os\n"); return SASL_FAIL; } *outstr = (char *)utils->malloc(len+1); if (!*outstr) return SASL_NOMEM; memcpy(*outstr, data+1, len); (*outstr)[len] = '\0'; *outlen = len; *left = data+len+1; *leftlen = datalen - (len+1); return SASL_OK; } /* * Convert a big integer to it's byte representation */ static int BigIntToBytes(BIGNUM *num, char *out, int maxoutlen, int *outlen) { int len; len = BN_num_bytes(num); if (len > maxoutlen) return SASL_FAIL; *outlen = BN_bn2bin(num, out); return SASL_OK; } static int BigIntCmpWord(BIGNUM *a, BN_ULONG w) { BIGNUM *b = BN_new(); int r; BN_set_word(b, w); r = BN_cmp(a, b); BN_free(b); return r; } static int MakeMPI(const sasl_utils_t *utils, BIGNUM *num, char **out, int *outlen) { int shortlen; int len; short inbyteorder; int alloclen; int r; alloclen = BN_num_bytes(num); *out = utils->malloc(alloclen+2); if (!*out) return SASL_NOMEM; r = BigIntToBytes(num, (*out)+2, alloclen, &len); if (r) { utils->free(*out); return r; } *outlen = 2+len; /* put in len */ shortlen = len; inbyteorder = htons(shortlen); memcpy(*out, &inbyteorder, 2); return SASL_OK; } static int GetMPI(const sasl_utils_t *utils, unsigned char *data, int datalen, BIGNUM *outnum, char **left, int *leftlen) { short lenbyteorder; int len; if ((!data) || (datalen < 2)) { utils->seterror(utils->conn, 0, "Buffer is not big enough to be SRP MPI: %d\n", datalen); return SASL_BADPROT; } /* get the length */ memcpy(&lenbyteorder, data, 2); len = ntohs(lenbyteorder); /* make sure it's right */ if (len + 2 > datalen) { utils->seterror(utils->conn, 0, "Not enough data for this SRP MPI: we have %d; " "it says it's %d\n", datalen, len+2); return SASL_BADPROT; } BN_init(outnum); BN_bin2bn(data+2, len, outnum); *left = data+len+2; *leftlen = datalen - (len+2); return SASL_OK; } static void GetRandBigInt(BIGNUM *out) { BN_init(out); /* xxx likely should use sasl random funcs */ BN_rand(out, BITSFORab, 0, 0); } /* * Call the hash function on some data */ static void HashData(context_t *text, char *in, int inlen, unsigned char outhash[], int *outlen) { EVP_MD_CTX mdctx; EVP_DigestInit(&mdctx, text->md); EVP_DigestUpdate(&mdctx, in, inlen); EVP_DigestFinal(&mdctx, outhash, outlen); } /* * Call the hash function on the data of a BigInt */ static int HashBigInt(context_t *text, BIGNUM *in, unsigned char outhash[], int *outlen) { int r; char buf[4096]; int buflen; EVP_MD_CTX mdctx; r = BigIntToBytes(in, buf, sizeof(buf)-1, &buflen); if (r) return r; EVP_DigestInit(&mdctx, text->md); EVP_DigestUpdate(&mdctx, buf, buflen); EVP_DigestFinal(&mdctx, outhash, outlen); return 0; } static int HashInterleaveBigInt(context_t *text, BIGNUM *num, char **out, int *outlen) { int r; char buf[4096]; int buflen; int klen; int limit; int i; int offset; int j; EVP_MD_CTX mdEven; EVP_MD_CTX mdOdd; unsigned char Evenb[EVP_MAX_MD_SIZE]; unsigned char Oddb[EVP_MAX_MD_SIZE]; int hashlen; /* make bigint into bytes */ r = BigIntToBytes(num, buf, sizeof(buf)-1, &buflen); if (r) return r; limit = buflen; /* skip by leading zero's */ for (offset = 0; offset < limit && buf[offset] == 0x00; offset++) { /* nada */ } klen = (limit - offset) / 2; EVP_DigestInit(&mdEven, text->md); EVP_DigestInit(&mdOdd, text->md); j = limit - 1; for (i = 0; i < klen; i++) { EVP_DigestUpdate(&mdEven, buf + j, 1); j--; EVP_DigestUpdate(&mdOdd, buf + j, 1); j--; } EVP_DigestFinal(&mdEven, Evenb, NULL); EVP_DigestFinal(&mdOdd, Oddb, &hashlen); *outlen = 2 * hashlen; *out = text->utils->malloc(*outlen); if (!*out) return SASL_NOMEM; for (i = 0, j = 0; i < hashlen; i++) { (*out)[j++] = Evenb[i]; (*out)[j++] = Oddb[i]; } return SASL_OK; } /* * Calculate 'x' which is needed to calculate 'K' * */ static int CalculateX(context_t *text, const char *salt, int saltlen, const char *user, const char *pass, int passlen, BIGNUM *x) { EVP_MD_CTX mdctx; char hash[EVP_MAX_MD_SIZE]; int hashlen; /* x = H(salt | H(user | ':' | pass)) * */ EVP_DigestInit(&mdctx, text->md); EVP_DigestUpdate(&mdctx, (char*) user, strlen(user)); EVP_DigestUpdate(&mdctx, ":", 1); EVP_DigestUpdate(&mdctx, (char*) pass, passlen); EVP_DigestFinal(&mdctx, hash, &hashlen); EVP_DigestInit(&mdctx, text->md); EVP_DigestUpdate(&mdctx, (char*) salt, saltlen); EVP_DigestUpdate(&mdctx, hash, hashlen); EVP_DigestFinal(&mdctx, hash, &hashlen); BN_init(x); BN_bin2bn(hash, hashlen, x); return SASL_OK; } /* * H( * bytes(H( bytes(N) )) ^ bytes( H( bytes(g) ))) * | bytes(H( bytes(U) )) * | bytes(s) * | bytes(H( bytes(L) )) * | bytes(A) * | bytes(B) * | bytes(K) * ) * * H() is the result of digesting the designated input/data with the * underlying Message Digest Algorithm function (see Section 1). * * ^ is the bitwise XOR operator. */ static int CalculateM1(context_t *text, BIGNUM *N, BIGNUM *g, char *U, /* username */ char *salt, int saltlen, /* salt */ char *L, /* server's options */ BIGNUM *A, /* client's public key */ BIGNUM *B, /* server's public key */ char *K, int Klen, char **out, int *outlen) { int i; int r; unsigned char p1a[EVP_MAX_MD_SIZE]; unsigned char p1b[EVP_MAX_MD_SIZE]; unsigned char p1[EVP_MAX_MD_SIZE]; int p1len; char p2[EVP_MAX_MD_SIZE]; int p2len; char *p3; int p3len; char p4[1024]; int p4len; char p5[1024]; int p5len; char *p6; int p6len; char p7[EVP_MAX_MD_SIZE]; int p7len; char *tot; int totlen = 0; char *totp; /* p1) bytes(H( bytes(N) )) ^ bytes( H( bytes(g) )) */ r = HashBigInt(text, N, p1a, NULL); if (r) return r; r = HashBigInt(text, g, p1b, &p1len); if (r) return r; for (i = 0; i < p1len; i++) { p1[i] = (p1a[i] ^ p1b[i]); } /* p2) bytes(H( bytes(U) )) */ HashData(text, U, strlen(U), p2, &p2len); /* p3) bytes(s) */ p3 = salt; p3len = saltlen; /* p4) bytes(A) */ r = BigIntToBytes(A, p4, sizeof(p4), &p4len); if (r) return r; /* p5) bytes(B) */ r = BigIntToBytes(B, p5, sizeof(p5), &p5len); if (r) return r; /* p6) bytes(K) */ p6 = K; p6len = Klen; /* p7) bytes(H( bytes(L) )) */ HashData(text, L, strlen(L), p7, &p7len); /* merge p1-p7 together */ totlen = p1len + p2len + p3len + p4len + p5len + p6len + p7len; tot = text->utils->malloc(totlen); if (!tot) return SASL_NOMEM; totp = tot; memcpy(totp, p1, p1len); totp+=p1len; memcpy(totp, p2, p2len); totp+=p2len; memcpy(totp, p3, p3len); totp+=p3len; memcpy(totp, p4, p4len); totp+=p4len; memcpy(totp, p5, p5len); totp+=p5len; memcpy(totp, p6, p6len); totp+=p6len; memcpy(totp, p7, p7len); totp+=p7len; /* do the hash over the whole thing */ *out = text->utils->malloc(EVP_MAX_MD_SIZE); if (!*out) { text->utils->free(tot); return SASL_NOMEM; } HashData(text, tot, totlen, *out, outlen); text->utils->free(tot); return SASL_OK; } /* * H( * bytes(A) * | bytes(H( bytes(U) )) * | bytes(H( bytes(I) )) * | bytes(H( bytes(o) )) * | bytes(M1) * | bytes(K) * ) * * * where: * * H() is the result of digesting the designated input/data with the * underlying Message Digest Algorithm function (see Section 1) * */ static int CalculateM2(context_t *text, BIGNUM *A, char *U, char *I, char *o, char *M1, int M1len, char *K, int Klen, char **out, int *outlen) { int r; unsigned char p1[1024]; int p1len; char *p2; int p2len; char *p3; int p3len; char p4[EVP_MAX_MD_SIZE]; int p4len; char p5[EVP_MAX_MD_SIZE]; int p5len; char p6[EVP_MAX_MD_SIZE]; int p6len; char *tot; int totlen = 0; char *totp; /* p1) bytes(A) */ r = BigIntToBytes(A, p1, sizeof(p1), &p1len); if (r) return r; /* p2) bytes(M1) */ p2 = M1; p2len = M1len; /* p3) bytes(K) */ p3 = K; p3len = Klen; /* p4) bytes(H( bytes(U) )) */ HashData(text, U, strlen(U), p4, &p4len); /* p5) bytes(H( bytes(I) )) */ HashData(text, I, strlen(I), p5, &p5len); /* p6) bytes(H( bytes(o) )) */ HashData(text, o, strlen(o), p6, &p6len); /* merge p1-p6 together */ totlen = p1len + p2len + p3len + p4len + p5len + p6len; tot = text->utils->malloc(totlen); if (!tot) return SASL_NOMEM; totp = tot; memcpy(totp, p1, p1len); totp+=p1len; memcpy(totp, p2, p2len); totp+=p2len; memcpy(totp, p3, p3len); totp+=p3len; memcpy(totp, p4, p4len); totp+=p4len; memcpy(totp, p5, p5len); totp+=p5len; memcpy(totp, p6, p6len); totp+=p6len; /* do the hash over the whole thing */ *out = text->utils->malloc(EVP_MAX_MD_SIZE); if (!*out) { return SASL_NOMEM; text->utils->free(tot); } HashData(text, tot, totlen, *out, outlen); text->utils->free(tot); return SASL_OK; } /* Parse an option out of an option string * Place found option in 'option' * 'nextptr' points to rest of string or NULL if at end */ static int ParseOption(const sasl_utils_t *utils, char *in, char **option, char **nextptr) { char *comma; int len; int i; if (strlen(in) == 0) { *option = NULL; return SASL_OK; } comma = strchr(in,','); if (comma == NULL) comma = in + strlen(in); len = comma - in; *option = utils->malloc(len + 1); if (!*option) return SASL_NOMEM; /* lowercase string */ for (i = 0; i < len; i++) { (*option)[i] = tolower((int)in[i]); } (*option)[len] = '\0'; if (*comma) { *nextptr = comma+1; } else { *nextptr = NULL; } return SASL_OK; } static int FindBit(char *name, layer_option_t *opts) { while (opts->name) { if (!strcasecmp(name, opts->name)) { return opts->bit; } opts++; } return 0; } static layer_option_t *FindOptionFromBit(unsigned bit, layer_option_t *opts) { while (opts->name) { if (opts->bit == bit) { return opts; } opts++; } return NULL; } static int ParseOptionString(const sasl_utils_t *utils, char *str, srp_options_t *opts, int isserver) { if (!strncasecmp(str, OPTION_MDA, strlen(OPTION_MDA))) { int bit = FindBit(str+strlen(OPTION_MDA), digest_options); if (isserver && (!bit || opts->mda)) { opts->mda = -1; if (!bit) utils->seterror(utils->conn, 0, "SRP MDA %s not supported\n", str+strlen(OPTION_MDA)); else SETERROR(utils, "Multiple SRP MDAs given\n"); return SASL_BADPROT; } opts->mda = opts->mda | bit; } else if (!strcasecmp(str, OPTION_REPLAY_DETECTION)) { if (opts->replay_detection) { SETERROR(utils, "SRP Replay Detection option appears twice\n"); return SASL_BADPROT; } opts->replay_detection = 1; } else if (!strncasecmp(str, OPTION_INTEGRITY, strlen(OPTION_INTEGRITY)) && !strncasecmp(str+strlen(OPTION_INTEGRITY), "HMAC-", 5)) { int bit = FindBit(str+strlen(OPTION_INTEGRITY)+5, digest_options); if (isserver && (!bit || opts->integrity)) { opts->integrity = -1; if (!bit) utils->seterror(utils->conn, 0, "SRP Integrity option %s not supported\n", str+strlen(OPTION_INTEGRITY)); else SETERROR(utils, "Multiple SRP Integrity options given\n"); return SASL_BADPROT; } opts->integrity = opts->integrity | bit; } else if (!strncasecmp(str, OPTION_CONFIDENTIALITY, strlen(OPTION_CONFIDENTIALITY))) { int bit = FindBit(str+strlen(OPTION_CONFIDENTIALITY), cipher_options); if (isserver && (!bit || opts->confidentiality)) { opts->confidentiality = -1; if (!bit) utils->seterror(utils->conn, 0, "SRP Confidentiality option %s not supported\n", str+strlen(OPTION_CONFIDENTIALITY)); else SETERROR(utils, "Multiple SRP Confidentiality options given\n"); return SASL_FAIL; } opts->confidentiality = opts->confidentiality | bit; } else if (!isserver && !strncasecmp(str, OPTION_MANDATORY, strlen(OPTION_MANDATORY))) { char *layer = str+strlen(OPTION_MANDATORY); if (!strcasecmp(layer, OPTION_REPLAY_DETECTION)) opts->mandatory |= BIT_REPLAY_DETECTION; else if (!strncasecmp(layer, OPTION_INTEGRITY, strlen(OPTION_INTEGRITY)-1)) opts->mandatory |= BIT_INTEGRITY; else if (!strncasecmp(layer, OPTION_CONFIDENTIALITY, strlen(OPTION_CONFIDENTIALITY)-1)) opts->mandatory |= BIT_CONFIDENTIALITY; else { utils->seterror(utils->conn, 0, "Mandatory SRP option %s not supported\n", layer); return SASL_BADPROT; } } else if (!strncasecmp(str, OPTION_MAXBUFFERSIZE, strlen(OPTION_MAXBUFFERSIZE))) { opts->maxbufsize = strtoul(str+strlen(OPTION_MAXBUFFERSIZE), NULL, 10); if (opts->maxbufsize > MAXBUFFERSIZE) { utils->seterror(utils->conn, 0, "SRP Maxbuffersize %lu too big (> %lu)\n", opts->maxbufsize, MAXBUFFERSIZE); return SASL_BADPROT; } } else { /* Ignore unknown options */ } return SASL_OK; } static int ParseOptions(const sasl_utils_t *utils, char *in, srp_options_t *out, int isserver) { int r; memset(out, 0, sizeof(srp_options_t)); out->maxbufsize = MAXBUFFERSIZE; while (in) { char *opt; r = ParseOption(utils, in, &opt, &in); if (r) return r; if (opt == NULL) return SASL_OK; utils->log(NULL, SASL_LOG_DEBUG, "Got option: [%s]\n",opt); r = ParseOptionString(utils, opt, out, isserver); utils->free(opt); if (r) return r; } return SASL_OK; } static layer_option_t *FindBest(int available, sasl_ssf_t min_ssf, sasl_ssf_t max_ssf, layer_option_t *opts) { layer_option_t *best = NULL; if (!available) return NULL; while (opts->name) { if (opts->enabled && (available & opts->bit) && (opts->ssf >= min_ssf) && (opts->ssf <= max_ssf) && (!best || (opts->ssf > best->ssf))) { best = opts; } opts++; } return best; } static int OptionsToString(const sasl_utils_t *utils, srp_options_t *opts, char **out) { char *ret = NULL; int alloced = 0; int first = 1; layer_option_t *optlist; ret = utils->malloc(1); if (!ret) return SASL_NOMEM; alloced = 1; ret[0] = '\0'; optlist = digest_options; while(optlist->name) { if (opts->mda & optlist->bit) { alloced += strlen(OPTION_MDA)+strlen(optlist->name)+1; ret = utils->realloc(ret, alloced); if (!ret) return SASL_NOMEM; if (!first) strcat(ret, ","); strcat(ret, OPTION_MDA); strcat(ret, optlist->name); first = 0; } optlist++; } if (opts->replay_detection) { alloced += strlen(OPTION_REPLAY_DETECTION)+1; ret = utils->realloc(ret, alloced); if (!ret) return SASL_NOMEM; if (!first) strcat(ret, ","); strcat(ret, OPTION_REPLAY_DETECTION); first = 0; } optlist = digest_options; while(optlist->name) { if (opts->integrity & optlist->bit) { alloced += strlen(OPTION_INTEGRITY)+5+strlen(optlist->name)+1; ret = utils->realloc(ret, alloced); if (!ret) return SASL_NOMEM; if (!first) strcat(ret, ","); strcat(ret, OPTION_INTEGRITY); strcat(ret, "HMAC-"); strcat(ret, optlist->name); first = 0; } optlist++; } optlist = cipher_options; while(optlist->name) { if (opts->confidentiality & optlist->bit) { alloced += strlen(OPTION_CONFIDENTIALITY)+strlen(optlist->name)+1; ret = utils->realloc(ret, alloced); if (!ret) return SASL_NOMEM; if (!first) strcat(ret, ","); strcat(ret, OPTION_CONFIDENTIALITY); strcat(ret, optlist->name); first = 0; } optlist++; } if ((opts->integrity || opts->confidentiality) && opts->maxbufsize < MAXBUFFERSIZE) { alloced += strlen(OPTION_MAXBUFFERSIZE)+10+1; ret = utils->realloc(ret, alloced); if (!ret) return SASL_NOMEM; if (!first) strcat(ret, ","); strcat(ret, OPTION_MAXBUFFERSIZE); sprintf(ret+strlen(ret), "%lu", opts->maxbufsize); first = 0; } if (opts->mandatory & BIT_REPLAY_DETECTION) { alloced += strlen(OPTION_MANDATORY)+strlen(OPTION_REPLAY_DETECTION)+1; ret = utils->realloc(ret, alloced); if (!ret) return SASL_NOMEM; if (!first) strcat(ret, ","); strcat(ret, OPTION_MANDATORY); strcat(ret, OPTION_REPLAY_DETECTION); first = 0; } if (opts->mandatory & BIT_INTEGRITY) { alloced += strlen(OPTION_MANDATORY)+strlen(OPTION_INTEGRITY)-1+1; ret = utils->realloc(ret, alloced); if (!ret) return SASL_NOMEM; if (!first) strcat(ret, ","); strcat(ret, OPTION_MANDATORY); strncat(ret, OPTION_INTEGRITY, strlen(OPTION_INTEGRITY)-1); /* terminate string */ ret[alloced-1] = '\0'; first = 0; } if (opts->mandatory & BIT_CONFIDENTIALITY) { alloced += strlen(OPTION_MANDATORY)+strlen(OPTION_CONFIDENTIALITY)-1+1; ret = utils->realloc(ret, alloced); if (!ret) return SASL_NOMEM; if (!first) strcat(ret, ","); strcat(ret, OPTION_MANDATORY); strncat(ret, OPTION_CONFIDENTIALITY, strlen(OPTION_CONFIDENTIALITY)-1); /* terminate string */ ret[alloced-1] = '\0'; first = 0; } *out = ret; return SASL_OK; } /* Set the options (called by client and server) * * Set up variables/hashes/that sorta thing so layers * will operate properly */ static int SetOptions(srp_options_t *opts, context_t *text, const sasl_utils_t *utils, sasl_out_params_t *oparams) { layer_option_t *opt; opt = FindOptionFromBit(opts->mda, digest_options); if (!opt) { utils->log(NULL, SASL_LOG_ERR, "Unable to find SRP MDA option now\n"); return SASL_FAIL; } text->md = EVP_get_digestbyname(opt->evp_name); text->size = -1; text->needsize = 4; if ((opts->integrity == 0) && (opts->confidentiality == 0)) { oparams->encode = NULL; oparams->decode = NULL; oparams->mech_ssf = 0; utils->log(NULL, SASL_LOG_DEBUG, "Using no layer\n"); return SASL_OK; } oparams->encode = &srp_encode; oparams->decode = &srp_decode; oparams->maxoutbuf = opts->maxbufsize - 4; /* account for eos() count */ if (opts->replay_detection) { text->enabled |= BIT_REPLAY_DETECTION; /* If no integrity layer specified, use default */ if (!opts->integrity) opts->integrity = default_digest->bit; } if (opts->integrity) { text->enabled |= BIT_INTEGRITY; opt = FindOptionFromBit(opts->integrity, digest_options); if (!opt) { utils->log(NULL, SASL_LOG_ERR, "Unable to find SRP integrity layer option now\n"); return SASL_FAIL; } oparams->mech_ssf = opt->ssf; text->hmac_md = EVP_get_digestbyname(opt->evp_name); /* account for os() */ oparams->maxoutbuf -= (EVP_MD_size(text->hmac_md) + 1); } if (opts->confidentiality) { text->enabled |= BIT_CONFIDENTIALITY; opt = FindOptionFromBit(opts->confidentiality, cipher_options); if (!opt) { utils->log(NULL, SASL_LOG_ERR, "Unable to find SRP confidentiality layer option now\n"); return SASL_FAIL; } oparams->mech_ssf = opt->ssf; text->cipher = EVP_get_cipherbyname(opt->evp_name); } return SASL_OK; } /* * Dispose of a SRP context (could be server or client) */ static void srp_common_mech_dispose(void *conn_context, const sasl_utils_t *utils) { context_t *text = (context_t *) conn_context; if (!text) return; BN_clear_free(&text->N); BN_clear_free(&text->g); BN_clear_free(&text->v); BN_clear_free(&text->B); BN_clear_free(&text->a); BN_clear_free(&text->A); if (text->K) utils->free(text->K); if (text->M1) utils->free(text->M1); if (text->authid) utils->free(text->authid); if (text->userid) utils->free(text->userid); if (text->free_password) _plug_free_secret(utils, &(text->password)); if (text->salt) utils->free(text->salt); if (text->client_options) utils->free(text->client_options); if (text->server_options) utils->free(text->server_options); if (text->buffer) utils->free(text->buffer); if (text->encode_buf) utils->free(text->encode_buf); if (text->encode_tmp_buf) utils->free(text->encode_tmp_buf); if (text->decode_buf) utils->free(text->decode_buf); if (text->decode_once_buf) utils->free(text->decode_once_buf); if (text->decode_tmp_buf) utils->free(text->decode_tmp_buf); if (text->out_buf) utils->free(text->out_buf); if (text->enc_in_buf) { if (text->enc_in_buf->data) utils->free(text->enc_in_buf->data); utils->free(text->enc_in_buf); } utils->free(text); } static void srp_common_mech_free(void *global_context __attribute__((unused)), const sasl_utils_t *utils __attribute__((unused))) { EVP_cleanup(); } /***************************** Server Section *****************************/ /* A large safe prime (N = 2q+1, where q is prime) * * Use N with the most bits from our table. * * All arithmetic is done modulo N */ static int generate_N_and_g(BIGNUM *N, BIGNUM *g) { int result; BN_init(N); result = BN_hex2bn(&N, Ng_tab[NUM_Ng-1].N); if (!result) return SASL_FAIL; BN_init(g); BN_set_word(g, Ng_tab[NUM_Ng-1].g); return SASL_OK; } static int CalculateV(context_t *text, BIGNUM *N, BIGNUM *g, const char *user, const char *pass, unsigned passlen, BIGNUM *v, char **salt, int *saltlen) { BIGNUM x; BN_CTX *ctx = BN_CTX_new(); int r; /* generate */ *saltlen = SRP_SALT_SIZE; *salt = (char *)text->utils->malloc(*saltlen); if (!*salt) return SASL_NOMEM; text->utils->rand(text->utils->rpool, *salt, *saltlen); r = CalculateX(text, *salt, *saltlen, user, pass, passlen, &x); if (r) { text->utils->seterror(text->utils->conn, 0, "Error calculating 'x'"); return r; } /* v = g^x % N */ BN_init(v); BN_mod_exp(v, g, &x, N, ctx); BN_CTX_free(ctx); BN_clear_free(&x); return r; } static int ServerCalculateK(context_t *text, BIGNUM *v, BIGNUM *N, BIGNUM *g, BIGNUM *B, BIGNUM *A, char **key, int *keylen) { unsigned char hash[EVP_MAX_MD_SIZE]; BIGNUM b; BIGNUM u; BIGNUM base; BIGNUM S; BN_CTX *ctx = BN_CTX_new(); int r; do { /* Generate b */ GetRandBigInt(&b); /* Per [SRP]: make sure b > log[g](N) -- g is always 2 */ BN_add_word(&b, BN_num_bits(N)); /* B = (v + g^b) % N */ BN_init(B); BN_mod_exp(B, g, &b, N, ctx); #if OPENSSL_VERSION_NUMBER >= 0x00907000L BN_mod_add(B, B, v, N, ctx); #else BN_add(B, B, v); BN_mod(B, B, N, ctx); #endif /* u is first 32 bits of B hashed; MSB first */ r = HashBigInt(text, B, hash, NULL); if (r) return r; BN_init(&u); BN_bin2bn(hash, 4, &u); } while (BN_is_zero(&u)); /* Per Tom Wu: make sure u != 0 */ /* calculate K * * Host: S = (Av^u) ^ b % N (computes session key) * Host: K = Hi(S) */ BN_init(&base); BN_mod_exp(&base, v, &u, N, ctx); BN_mod_mul(&base, &base, A, N, ctx); BN_init(&S); BN_mod_exp(&S, &base, &b, N, ctx); /* per Tom Wu: make sure Av^u != 1 (mod N) */ if (BN_is_one(&base)) { SETERROR(text->utils, "Unsafe SRP value for 'Av^u'\n"); r = SASL_BADPROT; goto err; } /* per Tom Wu: make sure Av^u != -1 (mod N) */ BN_add_word(&base, 1); if (BN_cmp(&S, N) == 0) { SETERROR(text->utils, "Unsafe SRP value for 'Av^u'\n"); r = SASL_BADPROT; goto err; } /* K = Hi(S) */ r = HashInterleaveBigInt(text, &S, key, keylen); if (r) goto err; r = SASL_OK; err: BN_CTX_free(ctx); BN_clear_free(&b); BN_clear_free(&u); BN_clear_free(&base); BN_clear_free(&S); return r; } static int ParseUserSecret(const sasl_utils_t *utils, char *secret, size_t seclen, char **mda, BIGNUM *v, char **salt, int *saltlen) { int r; char *data; int datalen; /* The secret data is stored as suggested in RFC 2945: * * mda - utf8 * v - mpi * salt - os */ r = UnBuffer(utils, secret, seclen, &data, &datalen); if (r) { utils->seterror(utils->conn, 0, "Error UnBuffering secret data"); return r; } r = GetUTF8(utils, data, datalen, mda, &data, &datalen); if (r) { utils->seterror(utils->conn, 0, "Error parsing out 'mda'"); return r; } r = GetMPI(utils, data, datalen, v, &data, &datalen); if (r) { utils->seterror(utils->conn, 0, "Error parsing out 'v'"); return r; } r = GetOS(utils, data, datalen, salt, saltlen, &data, &datalen); if (r) { utils->seterror(utils->conn, 0, "Error parsing out salt"); return r; } if (datalen != 0) { utils->seterror(utils->conn, 0, "Extra data in request step 2"); r = SASL_FAIL; } return r; } static int CreateServerOptions(sasl_server_params_t *sparams, char **out) { srp_options_t opts; sasl_ssf_t limitssf, requiressf; layer_option_t *optlist; /* zero out options */ memset(&opts,0,sizeof(srp_options_t)); /* Add mda */ opts.mda = server_mda->bit; if (sparams->props.max_ssf < sparams->external_ssf) { limitssf = 0; } else { limitssf = sparams->props.max_ssf - sparams->external_ssf; } if (sparams->props.min_ssf < sparams->external_ssf) { requiressf = 0; } else { requiressf = sparams->props.min_ssf - sparams->external_ssf; } /* * Add integrity options * Can't advertise integrity w/o support for default HMAC */ if (default_digest->enabled) { optlist = digest_options; while(optlist->name) { if (optlist->enabled && /*(requiressf <= 1) &&*/ (limitssf >= 1)) { opts.integrity |= optlist->bit; } optlist++; } } /* if we set any integrity options we can advertise replay detection */ if (opts.integrity) { opts.replay_detection = 1; } /* * Add confidentiality options * Can't advertise confidentiality w/o support for default cipher */ if (default_cipher->enabled) { optlist = cipher_options; while(optlist->name) { if (optlist->enabled && (requiressf <= optlist->ssf) && (limitssf >= optlist->ssf)) { opts.confidentiality |= optlist->bit; } optlist++; } } /* Add mandatory options */ if (requiressf >= 1) opts.mandatory = BIT_REPLAY_DETECTION | BIT_INTEGRITY; if (requiressf > 1) opts.mandatory |= BIT_CONFIDENTIALITY; /* Add maxbuffersize */ opts.maxbufsize = MAXBUFFERSIZE; if (sparams->props.maxbufsize && sparams->props.maxbufsize < opts.maxbufsize) opts.maxbufsize = sparams->props.maxbufsize; return OptionsToString(sparams->utils, &opts, out); } static int srp_server_mech_new(void *glob_context __attribute__((unused)), sasl_server_params_t *params, const char *challenge __attribute__((unused)), unsigned challen __attribute__((unused)), void **conn_context) { context_t *text; /* holds state are in */ text = params->utils->malloc(sizeof(context_t)); if (text == NULL) { MEMERROR(params->utils); return SASL_NOMEM; } memset(text, 0, sizeof(context_t)); text->state = 1; text->utils = params->utils; text->md = EVP_get_digestbyname(server_mda->evp_name); *conn_context = text; return SASL_OK; } static int srp_server_mech_step1(context_t *text, sasl_server_params_t *params, const char *clientin, unsigned clientinlen, const char **serverout, unsigned *serveroutlen, sasl_out_params_t *oparams) { char *data; int datalen; int result; char *mpiN = NULL; int mpiNlen; char *mpig = NULL; int mpiglen; char *osS = NULL; int osSlen; char *utf8L = NULL; int utf8Llen; char *realm = NULL; char *user = NULL; const char *password_request[] = { SASL_AUX_PASSWORD, "*cmusaslsecretSRP", NULL }; struct propval auxprop_values[3]; /* Expect: * * U - authentication identity * I - authorization identity * * { utf8(U) utf8(I) } * */ result = UnBuffer(params->utils, (char *) clientin, clientinlen, &data, &datalen); if (result) { params->utils->seterror(params->utils->conn, 0, "Error 'unbuffer'ing input for step 1"); return result; } result = GetUTF8(params->utils, data, datalen, &text->authid, &data, &datalen); if (result) { params->utils->seterror(params->utils->conn, 0, "Error getting UTF8 string from input"); return result; } result = GetUTF8(params->utils, data, datalen, &text->userid, &data, &datalen); if (result) { params->utils->seterror(params->utils->conn, 0, "Error parsing out userid"); return result; } if (datalen != 0) { params->utils->seterror(params->utils->conn, 0, "Extra data to SRP step 1"); return SASL_FAIL; } /* Get the realm */ result = _plug_parseuser(params->utils, &user, &realm, params->user_realm, params->serverFQDN, text->authid); if (result) { params->utils->seterror(params->utils->conn, 0, "Error getting realm"); goto cleanup; } /* Generate N and g */ result = generate_N_and_g(&text->N, &text->g); if (result) { params->utils->seterror(text->utils->conn, 0, "Error calculating N and g"); return result; } /* Get user secret */ result = params->utils->prop_request(params->propctx, password_request); if (result != SASL_OK) goto cleanup; /* this will trigger the getting of the aux properties */ result = params->canon_user(params->utils->conn, user, 0, SASL_CU_AUTHID, oparams); if (result != SASL_OK) goto cleanup; result = params->canon_user(params->utils->conn, text->userid, 0, SASL_CU_AUTHZID, oparams); if (result != SASL_OK) goto cleanup; result = params->utils->prop_getnames(params->propctx, password_request, auxprop_values); if (result < 0 || ((!auxprop_values[0].name || !auxprop_values[0].values) && (!auxprop_values[1].name || !auxprop_values[1].values))) { /* We didn't find this username */ params->utils->seterror(params->utils->conn,0, "no secret in database"); result = SASL_NOUSER; goto cleanup; } if (auxprop_values[1].name && auxprop_values[1].values) { char *mda = NULL; /* We have a precomputed verifier */ result = ParseUserSecret(params->utils, (char*) auxprop_values[1].values[0], auxprop_values[1].valsize, &mda, &text->v, &text->salt, &text->saltlen); if (result) { /* ParseUserSecret sets error, if any */ if (mda) params->utils->free(mda); goto cleanup; } /* find mda */ server_mda = digest_options; while (server_mda->name) { if (!strcasecmp(server_mda->name, mda)) break; server_mda++; } if (!server_mda->name) { params->utils->seterror(params->utils->conn, 0, "unknown SRP mda '%s'", mda); params->utils->free(mda); result = SASL_FAIL; goto cleanup; } params->utils->free(mda); } else if (auxprop_values[0].name && auxprop_values[0].values) { /* We only have the password -- calculate the verifier */ int len = strlen(auxprop_values[0].values[0]); if (len == 0) { params->utils->seterror(params->utils->conn,0, "empty secret"); result = SASL_FAIL; goto cleanup; } result = CalculateV(text, &text->N, &text->g, user, auxprop_values[0].values[0], len, &text->v, &text->salt, &text->saltlen); if (result) { params->utils->seterror(params->utils->conn, 0, "Error calculating v"); goto cleanup; } } else { params->utils->seterror(params->utils->conn, 0, "Have neither type of secret"); result = SASL_FAIL; goto cleanup; } params->utils->prop_clear(params->propctx, 1); result = CreateServerOptions(params, &text->server_options); if (result) { params->utils->seterror(params->utils->conn, 0, "Error creating server options"); goto cleanup; } /* Send out: * * N - safe prime modulus * g - generator * s - salt * L - server options (available layers etc) * * { mpi(N) mpi(g) os(s) utf8(L) } * */ result = MakeMPI(params->utils, &text->N, &mpiN, &mpiNlen); if (result) { params->utils->seterror(params->utils->conn, 0, "Error creating 'mpi' string for N"); goto cleanup; } result = MakeMPI(params->utils, &text->g, &mpig, &mpiglen); if (result) { params->utils->seterror(params->utils->conn, 0, "Error creating 'mpi' string for g"); goto cleanup; } result = MakeOS(params->utils, text->salt, text->saltlen, &osS, &osSlen); if (result) { params->utils->seterror(params->utils->conn, 0, "Error turning salt into 'os' string"); goto cleanup; } result = MakeUTF8(params->utils, text->server_options, &utf8L, &utf8Llen); if (result) { params->utils->seterror(params->utils->conn, 0, "Error creating 'UTF8' string for L (server options)"); goto cleanup; } result = MakeBuffer(text, mpiN, mpiNlen, mpig, mpiglen, osS, osSlen, utf8L, utf8Llen, serverout, serveroutlen); if (result) { params->utils->seterror(params->utils->conn, 0, "Error creating SRP buffer from data in step 1"); goto cleanup; } text->state = 2; result = SASL_CONTINUE; cleanup: if (user) params->utils->free(user); if (realm) params->utils->free(realm); if (mpiN) params->utils->free(mpiN); if (mpig) params->utils->free(mpig); if (osS) params->utils->free(osS); if (utf8L) params->utils->free(utf8L); return result; } static int srp_server_mech_step2(context_t *text, sasl_server_params_t *params, const char *clientin, unsigned clientinlen, const char **serverout, unsigned *serveroutlen, sasl_out_params_t *oparams) { char *data; int datalen; int result; char *mpiB = NULL; int mpiBlen; srp_options_t client_opts; /* Expect: * * A - client's public key * o - client option list * * { mpi(A) utf8(o) } * */ result = UnBuffer(params->utils, (char *) clientin, clientinlen, &data, &datalen); if (result) { params->utils->seterror(params->utils->conn, 0, "Error UnBuffering input in step 2"); return result; } result = GetMPI(params->utils, data, datalen, &text->A, &data, &datalen); if (result) { params->utils->seterror(params->utils->conn, 0, "Error parsing out 'A'"); return result; } /* Per [SRP]: reject A <= 0 */ if (BigIntCmpWord(&text->A, 0) <= 0) { SETERROR(params->utils, "Illegal value for 'A'\n"); return SASL_BADPROT; } result = GetUTF8(params->utils, data, datalen, &text->client_options, &data, &datalen); if (result) { params->utils->seterror(params->utils->conn, 0, "Error parsing out SRP client options 'o'"); return result; } params->utils->log(NULL, SASL_LOG_DEBUG, "o: '%s'", text->client_options); if (datalen != 0) { params->utils->seterror(params->utils->conn, 0, "Extra data in request step 2"); return SASL_FAIL; } /* parse client options */ result = ParseOptions(params->utils, text->client_options, &client_opts, 1); if (result) { params->utils->seterror(params->utils->conn, 0, "Error parsing user's options"); if (client_opts.confidentiality) { /* Mark that we attempted confidentiality layer negotiation */ oparams->mech_ssf = 2; } else if (client_opts.integrity || client_opts.replay_detection) { /* Mark that we attempted integrity layer negotiation */ oparams->mech_ssf = 1; } return result; } result = SetOptions(&client_opts, text, params->utils, oparams); if (result) { params->utils->seterror(params->utils->conn, 0, "Error setting options"); return result; } /* Calculate K (and B) */ result = ServerCalculateK(text, &text->v, &text->N, &text->g, &text->B, &text->A, &text->K, &text->Klen); if (result) { params->utils->seterror(params->utils->conn, 0, "Error calculating K"); return result; } /* Send out: * * B - server's public key * * { mpi(B) } */ result = MakeMPI(params->utils, &text->B, &mpiB, &mpiBlen); if (result) { params->utils->seterror(params->utils->conn, 0, "Error turning 'B' into 'mpi' string"); goto cleanup; } result = MakeBuffer(text, mpiB, mpiBlen, NULL, 0, NULL, 0, NULL, 0, serverout, serveroutlen); if (result) { params->utils->seterror(params->utils->conn, 0, "Error putting all the data together in step 2"); goto cleanup; } text->state = 3; result = SASL_CONTINUE; cleanup: if (mpiB) params->utils->free(mpiB); return result; } static int srp_server_mech_step3(context_t *text, sasl_server_params_t *params, const char *clientin, unsigned clientinlen, const char **serverout, unsigned *serveroutlen, sasl_out_params_t *oparams) { char *data; int datalen; int result; char *M1 = NULL; int M1len; char *myM1 = NULL; int myM1len; char *M2 = NULL; int M2len; int i; char *osM2 = NULL; int osM2len; /* Expect: * * M1 = client evidence * * { os(M1) } * */ result = UnBuffer(params->utils, (char *) clientin, clientinlen, &data, &datalen); if (result) { params->utils->seterror(params->utils->conn, 0, "Error parsing input buffer in step 3"); goto cleanup; } result = GetOS(params->utils, data, datalen, &M1,&M1len, &data, &datalen); if (result) { params->utils->seterror(params->utils->conn, 0, "Error getting 'os' M1 (client evidenice)"); goto cleanup; } if (datalen != 0) { result = SASL_FAIL; params->utils->seterror(params->utils->conn, 0, "Extra data in input SRP step 3"); goto cleanup; } /* See if M1 is correct */ result = CalculateM1(text, &text->N, &text->g, text->authid, text->salt, text->saltlen, text->server_options, &text->A, &text->B, text->K, text->Klen, &myM1, &myM1len); if (result) { params->utils->seterror(params->utils->conn, 0, "Error calculating M1"); goto cleanup; } if (myM1len != M1len) { params->utils->seterror(params->utils->conn, 0, "SRP M1 lengths do not match"); result = SASL_BADAUTH; goto cleanup; } for (i = 0; i < myM1len; i++) { if (myM1[i] != M1[i]) { params->utils->seterror(params->utils->conn, 0, "client evidence does not match what we " "calculated. Probably a password error"); result = SASL_BADAUTH; goto cleanup; } } /* if we have a confidentiality layer we're done - send nothing */ if (text->enabled & BIT_CONFIDENTIALITY) { /* set oparams */ oparams->doneflag = 1; oparams->param_version = 0; result = SASL_OK; goto cleanup; } /* calculate M2 to send */ result = CalculateM2(text, &text->A, text->authid, text->userid, text->client_options, myM1, myM1len, text->K, text->Klen, &M2, &M2len); if (result) { params->utils->seterror(params->utils->conn, 0, "Error calculating M2 (server evidence)"); goto cleanup; } /* Send out: * * M2 = server evidence * * { os(M2) } */ result = MakeOS(params->utils, M2, M2len, &osM2, &osM2len); if (result) { params->utils->seterror(params->utils->conn, 0, "Error making 'os' string from M2 (server evidence)"); goto cleanup; } result = MakeBuffer(text, osM2, osM2len, NULL, 0, NULL, 0, NULL, 0, serverout, serveroutlen); if (result) { params->utils->seterror(params->utils->conn, 0, "Error making output buffer in SRP step 3"); goto cleanup; } /* set oparams */ oparams->doneflag = 1; oparams->param_version = 0; result = SASL_OK; cleanup: if (osM2) params->utils->free(osM2); if (M2) params->utils->free(M2); if (myM1) params->utils->free(myM1); if (M1) params->utils->free(M1); return result; } static int srp_server_mech_step(void *conn_context, sasl_server_params_t *sparams, const char *clientin, unsigned clientinlen, const char **serverout, unsigned *serveroutlen, sasl_out_params_t *oparams) { context_t *text = (context_t *) conn_context; if (!sparams || !serverout || !serveroutlen || !oparams) return SASL_BADPARAM; sparams->utils->log(NULL, SASL_LOG_DEBUG, "SRP server step %d\n", text->state); *serverout = NULL; *serveroutlen = 0; switch (text->state) { case 1: return srp_server_mech_step1(text, sparams, clientin, clientinlen, serverout, serveroutlen, oparams); case 2: return srp_server_mech_step2(text, sparams, clientin, clientinlen, serverout, serveroutlen, oparams); case 3: return srp_server_mech_step3(text, sparams, clientin, clientinlen, serverout, serveroutlen, oparams); default: sparams->utils->seterror(sparams->utils->conn, 0, "Invalid SRP server step %d", text->state); return SASL_FAIL; } return SASL_FAIL; /* should never get here */ } #ifdef DO_SRP_SETPASS static int srp_setpass(void *glob_context __attribute__((unused)), sasl_server_params_t *sparams, const char *userstr, const char *pass, unsigned passlen __attribute__((unused)), const char *oldpass __attribute__((unused)), unsigned oldpasslen __attribute__((unused)), unsigned flags) { int r; char *user = NULL; char *realm = NULL; sasl_secret_t *sec = NULL; /* Do we have database support? */ /* Note that we can use a NULL sasl_conn_t because our * sasl_utils_t is "blessed" with the global callbacks */ if(_sasl_check_db(sparams->utils, NULL) != SASL_OK) { SETERROR(sparams->utils, "No database support"); return SASL_FAIL; } r = _plug_parseuser(sparams->utils, &user, &realm, sparams->user_realm, sparams->serverFQDN, userstr); if (r) { sparams->utils->seterror(sparams->utils->conn, 0, "Error parsing user"); return r; } if ((flags & SASL_SET_DISABLE) || pass == NULL) { sec = NULL; } else { context_t *text; BIGNUM N; BIGNUM g; BIGNUM v; char *salt; int saltlen; char *utf8mda = NULL; int utf8mdalen; char *mpiv = NULL; int mpivlen; char *osSalt = NULL; int osSaltlen; const char *buffer = NULL; int bufferlen; text = sparams->utils->malloc(sizeof(context_t)); if (text==NULL) { MEMERROR(sparams->utils); return SASL_NOMEM; } memset(text, 0, sizeof(context_t)); text->utils = sparams->utils; text->md = EVP_get_digestbyname(server_mda->evp_name); r = generate_N_and_g(&N, &g); if (r) { sparams->utils->seterror(sparams->utils->conn, 0, "Error calculating N and g"); goto end; } r = CalculateV(text, &N, &g, user, pass, passlen, &v, &salt, &saltlen); if (r) { sparams->utils->seterror(sparams->utils->conn, 0, "Error calculating v"); goto end; } /* The secret data is stored as suggested in RFC 2945: * * mda - utf8 * v - mpi * salt - os */ r = MakeUTF8(sparams->utils, (char*) server_mda->name, &utf8mda, &utf8mdalen); if (r) { sparams->utils->seterror(sparams->utils->conn, 0, "Error turning 'mda' into 'utf8' string"); goto end; } r = MakeMPI(sparams->utils, &v, &mpiv, &mpivlen); if (r) { sparams->utils->seterror(sparams->utils->conn, 0, "Error turning 'v' into 'mpi' string"); goto end; } r = MakeOS(sparams->utils, salt, saltlen, &osSalt, &osSaltlen); if (r) { sparams->utils->seterror(sparams->utils->conn, 0, "Error turning salt into 'os' string"); goto end; } r = MakeBuffer(text, utf8mda, utf8mdalen, mpiv, mpivlen, osSalt, osSaltlen, NULL, 0, &buffer, &bufferlen); if (r) { sparams->utils->seterror(sparams->utils->conn, 0, "Error making buffer for secret"); goto end; } /* Put 'buffer' into sasl_secret_t */ sec = sparams->utils->malloc(sizeof(sasl_secret_t)+bufferlen+1); if (!sec) { r = SASL_NOMEM; goto end; } memcpy(sec->data, buffer, bufferlen); sec->len = bufferlen; /* Clean everything up */ end: if (mpiv) sparams->utils->free(mpiv); if (osSalt) sparams->utils->free(osSalt); if (buffer) sparams->utils->free((void *) buffer); BN_clear_free(&N); BN_clear_free(&g); BN_clear_free(&v); sparams->utils->free(text); if (r) return r; } /* do the store */ r = (*_sasldb_putdata)(sparams->utils, sparams->utils->conn, user, realm, "cmusaslsecretSRP", (sec ? sec->data : NULL), (sec ? sec->len : 0)); if (r) { sparams->utils->seterror(sparams->utils->conn, 0, "Error putting secret"); goto cleanup; } sparams->utils->log(NULL, SASL_LOG_DEBUG, "Setpass for SRP successful\n"); cleanup: if (user) sparams->utils->free(user); if (realm) sparams->utils->free(realm); if (sec) sparams->utils->free(sec); return r; } #endif /* DO_SRP_SETPASS */ static int srp_mech_avail(void *glob_context __attribute__((unused)), sasl_server_params_t *sparams, void **conn_context __attribute__((unused))) { /* Do we have access to the selected MDA? */ if (!server_mda || !server_mda->enabled) { SETERROR(sparams->utils, "SRP unavailable due to selected MDA unavailable"); return SASL_NOMECH; } return SASL_OK; } static sasl_server_plug_t srp_server_plugins[] = { { "SRP", /* mech_name */ 0, /* max_ssf */ SASL_SEC_NOPLAINTEXT | SASL_SEC_NOANONYMOUS | SASL_SEC_NOACTIVE | SASL_SEC_NODICTIONARY | SASL_SEC_FORWARD_SECRECY | SASL_SEC_MUTUAL_AUTH, /* security_flags */ SASL_FEAT_WANT_CLIENT_FIRST, /* features */ NULL, /* glob_context */ &srp_server_mech_new, /* mech_new */ &srp_server_mech_step, /* mech_step */ &srp_common_mech_dispose, /* mech_dispose */ &srp_common_mech_free, /* mech_free */ #if DO_SRP_SETPASS &srp_setpass, /* setpass */ #else NULL, #endif NULL, /* user_query */ NULL, /* idle */ &srp_mech_avail, /* mech avail */ NULL /* spare */ } }; int srp_server_plug_init(const sasl_utils_t *utils, int maxversion, int *out_version, const sasl_server_plug_t **pluglist, int *plugcount, const char *plugname __attribute__((unused))) { const char *mda; unsigned int len; layer_option_t *opts; if (maxversion < SASL_SERVER_PLUG_VERSION) { SETERROR(utils, "SRP version mismatch"); return SASL_BADVERS; } utils->getopt(utils->getopt_context, "SRP", "srp_mda", &mda, &len); if (!mda) mda = DEFAULT_MDA; /* Add all digests and ciphers */ OpenSSL_add_all_algorithms(); /* See which digests we have available and set max_ssf accordingly */ opts = digest_options; while (opts->name) { if (EVP_get_digestbyname(opts->evp_name)) { opts->enabled = 1; srp_server_plugins[0].max_ssf = opts->ssf; } /* Locate the server MDA */ if (!strcasecmp(opts->name, mda) || !strcasecmp(opts->evp_name, mda)) { server_mda = opts; } opts++; } /* See which ciphers we have available and set max_ssf accordingly */ opts = cipher_options; while (opts->name) { if (EVP_get_cipherbyname(opts->evp_name)) { opts->enabled = 1; if (opts->ssf > srp_server_plugins[0].max_ssf) { srp_server_plugins[0].max_ssf = opts->ssf; } } opts++; } *out_version = SASL_SERVER_PLUG_VERSION; *pluglist = srp_server_plugins; *plugcount = 1; return SASL_OK; } /***************************** Client Section *****************************/ /* Check to see if N,g is in the recommended list */ static int check_N_and_g(const sasl_utils_t *utils, BIGNUM *N, BIGNUM *g) { char *N_prime; unsigned long g_prime; unsigned i; int r = SASL_FAIL; N_prime = BN_bn2hex(N); g_prime = BN_get_word(g); for (i = 0; i < NUM_Ng; i++) { if (!strcasecmp(N_prime, Ng_tab[i].N) && (g_prime == Ng_tab[i].g)) { r = SASL_OK; break; } } if (N_prime) utils->free(N_prime); return r; } /* Calculate shared context key K * * User: x = H(s, password) * User: S = (B - g^x) ^ (a + ux) % N * * User: K = Hi(S) * */ static int CalculateK_client(context_t *text, char *salt, int saltlen, char *user, char *pass, int passlen, char **key, int *keylen) { int r; unsigned char hash[EVP_MAX_MD_SIZE]; BIGNUM x; BIGNUM u; BIGNUM aux; BIGNUM gx; BIGNUM base; BIGNUM S; BN_CTX *ctx = BN_CTX_new(); /* u is first 32 bits of B hashed; MSB first */ r = HashBigInt(text, &text->B, hash, NULL); if (r) goto err; BN_init(&u); BN_bin2bn(hash, 4, &u); /* per Tom Wu: make sure u != 0 */ if (BN_is_zero(&u)) { SETERROR(text->utils, "SRP: Illegal value for 'u'\n"); r = SASL_BADPROT; goto err; } r = CalculateX(text, salt, saltlen, user, pass, passlen, &x); if (r) return r; /* a + ux */ BN_init(&aux); BN_mul(&aux, &u, &x, ctx); BN_add(&aux, &aux, &text->a); /* gx = g^x % N */ BN_init(&gx); BN_mod_exp(&gx, &text->g, &x, &text->N, ctx); /* base = (B - g^x) % N */ BN_init(&base); #if OPENSSL_VERSION_NUMBER >= 0x00907000L BN_mod_sub(&base, &text->B, &gx, &text->N, ctx); #else BN_sub(&base, &text->B, &gx); BN_mod(&base, &base, &text->N, ctx); if (BigIntCmpWord(&base, 0) < 0) { BN_add(&base, &base, &text->N); } #endif /* S = base^aux % N */ BN_init(&S); BN_mod_exp(&S, &base, &aux, &text->N, ctx); /* K = Hi(S) */ r = HashInterleaveBigInt(text, &S, key, keylen); if (r) goto err; r = SASL_OK; err: BN_CTX_free(ctx); BN_clear_free(&x); BN_clear_free(&u); BN_clear_free(&aux); BN_clear_free(&gx); BN_clear_free(&base); BN_clear_free(&S); return r; } static int CreateClientOpts(sasl_client_params_t *params, srp_options_t *available, srp_options_t *out) { layer_option_t *opt; sasl_ssf_t external; sasl_ssf_t limit; sasl_ssf_t musthave; /* zero out output */ memset(out, 0, sizeof(srp_options_t)); params->utils->log(NULL, SASL_LOG_DEBUG, "Available MDA = %d\n",available->mda); /* mda */ opt = FindBest(available->mda, 0, 256, digest_options); if (opt) { out->mda = opt->bit; } else { SETERROR(params->utils, "Can't find an acceptable SRP MDA\n"); return SASL_BADAUTH; } /* get requested ssf */ external = params->external_ssf; /* what do we _need_? how much is too much? */ if (params->props.max_ssf > external) { limit = params->props.max_ssf - external; } else { limit = 0; } if (params->props.min_ssf > external) { musthave = params->props.min_ssf - external; } else { musthave = 0; } /* we now go searching for an option that gives us at least "musthave" and at most "limit" bits of ssf. */ params->utils->log(NULL, SASL_LOG_DEBUG, "Available confidentiality = %d\n", available->confidentiality); /* confidentiality */ if (limit > 1) { opt = FindBest(available->confidentiality, musthave, limit, cipher_options); if (opt) { out->confidentiality = opt->bit; /* we've already satisfied the SSF with the confidentiality * layer, but we'll also use an integrity layer if we can */ musthave = 0; } else if (musthave > 1) { SETERROR(params->utils, "Can't find an acceptable SRP confidentiality layer\n"); return SASL_TOOWEAK; } } params->utils->log(NULL, SASL_LOG_DEBUG, "Available integrity = %d\n",available->integrity); /* integrity */ if ((limit >= 1) && (musthave <= 1)) { opt = FindBest(available->integrity, musthave, limit, digest_options); if (opt) { out->integrity = opt->bit; /* if we set an integrity option we can set replay detection */ out->replay_detection = available->replay_detection; } else if (musthave > 0) { SETERROR(params->utils, "Can't find an acceptable SRP integrity layer\n"); return SASL_TOOWEAK; } } /* Check to see if we've satisfied all of the servers mandatory layers */ params->utils->log(NULL, SASL_LOG_DEBUG, "Mandatory layers = %d\n",available->mandatory); if ((!out->replay_detection && (available->mandatory & BIT_REPLAY_DETECTION)) || (!out->integrity && (available->mandatory & BIT_INTEGRITY)) || (!out->confidentiality && (available->mandatory & BIT_CONFIDENTIALITY))) { SETERROR(params->utils, "Mandatory SRP layer not supported\n"); return SASL_BADAUTH; } /* Add maxbuffersize */ out->maxbufsize = MAXBUFFERSIZE; if (params->props.maxbufsize && params->props.maxbufsize < out->maxbufsize) out->maxbufsize = params->props.maxbufsize; return SASL_OK; } static int srp_client_mech_new(void *glob_context __attribute__((unused)), sasl_client_params_t *params, void **conn_context) { context_t *text; /* holds state are in */ text = params->utils->malloc(sizeof(context_t)); if (text == NULL) { MEMERROR( params->utils ); return SASL_NOMEM; } memset(text, 0, sizeof(context_t)); text->state = 1; text->utils = params->utils; *conn_context = text; return SASL_OK; } static int srp_client_mech_step1(context_t *text, sasl_client_params_t *params, const char *serverin __attribute__((unused)), unsigned serverinlen, sasl_interact_t **prompt_need, const char **clientout, unsigned *clientoutlen, sasl_out_params_t *oparams) { const char *authid, *userid; int auth_result = SASL_OK; int pass_result = SASL_OK; int user_result = SASL_OK; int result; char *utf8U = NULL, *utf8I = NULL; int utf8Ulen, utf8Ilen; /* Expect: * absolutely nothing * */ if (serverinlen > 0) { SETERROR(params->utils, "Invalid input to first step of SRP\n"); return SASL_BADPROT; } /* try to get the authid */ if (oparams->authid==NULL) { auth_result = _plug_get_authid(params->utils, &authid, prompt_need); if ((auth_result != SASL_OK) && (auth_result != SASL_INTERACT)) return auth_result; } /* try to get the userid */ if (oparams->user == NULL) { user_result = _plug_get_userid(params->utils, &userid, prompt_need); if ((user_result != SASL_OK) && (user_result != SASL_INTERACT)) return user_result; } /* try to get the password */ if (text->password == NULL) { pass_result=_plug_get_password(params->utils, &text->password, &text->free_password, prompt_need); if ((pass_result != SASL_OK) && (pass_result != SASL_INTERACT)) return pass_result; } /* free prompts we got */ if (prompt_need && *prompt_need) { params->utils->free(*prompt_need); *prompt_need = NULL; } /* if there are prompts not filled in */ if ((auth_result == SASL_INTERACT) || (user_result == SASL_INTERACT) || (pass_result == SASL_INTERACT)) { /* make the prompt list */ result = _plug_make_prompts(params->utils, prompt_need, user_result == SASL_INTERACT ? "Please enter your authorization name" : NULL, NULL, auth_result == SASL_INTERACT ? "Please enter your authentication name" : NULL, NULL, pass_result == SASL_INTERACT ? "Please enter your password" : NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); if (result != SASL_OK) return result; return SASL_INTERACT; } result = params->canon_user(params->utils->conn, authid, 0, SASL_CU_AUTHID, oparams); if (result != SASL_OK) return result; result = params->canon_user(params->utils->conn, userid, 0, SASL_CU_AUTHZID, oparams); if (result != SASL_OK) return result; /* Send out: * * U - authentication identity * I - authorization identity * * { utf8(U) utf8(I) } */ result = MakeUTF8(params->utils, (char *) oparams->authid, &utf8U, &utf8Ulen); if (result) { params->utils->log(NULL, SASL_LOG_ERR, "Error making UTF8 string from authid ('U')\n"); goto cleanup; } result = MakeUTF8(params->utils, (char *) oparams->user, &utf8I, &utf8Ilen); if (result) { params->utils->log(NULL, SASL_LOG_ERR, "Error making UTF8 string from userid ('I')\n"); goto cleanup; } result = MakeBuffer(text, utf8U, utf8Ulen, utf8I, utf8Ilen, NULL, 0, NULL, 0, clientout, clientoutlen); if (result) { params->utils->log(NULL, SASL_LOG_ERR, "Error making output buffer\n"); goto cleanup; } text->state = 2; result = SASL_CONTINUE; cleanup: if (utf8U) params->utils->free(utf8U); if (utf8I) params->utils->free(utf8I); return result; } static int srp_client_mech_step2(context_t *text, sasl_client_params_t *params, const char *serverin, unsigned serverinlen, sasl_interact_t **prompt_need __attribute__((unused)), const char **clientout, unsigned *clientoutlen, sasl_out_params_t *oparams) { char *data; int datalen; int result; char *mpiA = NULL, *utf8o = NULL; int mpiAlen, utf8olen; srp_options_t server_opts; /* expect: * { mpi(N) mpi(g) os(s) utf8(L) } * */ result = UnBuffer(params->utils, (char *) serverin, serverinlen, &data, &datalen); if (result) return result; result = GetMPI(params->utils, (unsigned char *)data, datalen, &text->N, &data, &datalen); if (result) { params->utils->log(NULL, SASL_LOG_ERR, "Error getting MPI string for 'N'\n"); goto cleanup; } result = GetMPI(params->utils, (unsigned char *) data, datalen, &text->g, &data, &datalen); if (result) { params->utils->log(NULL, SASL_LOG_ERR, "Error getting MPI string for 'g'\n"); goto cleanup; } /* Check N and g to see if they are one of the recommended pairs */ result = check_N_and_g(params->utils, &text->N, &text->g); if (result) { params->utils->log(NULL, SASL_LOG_ERR, "Values of 'N' and 'g' are not recommended\n"); goto cleanup; } result = GetOS(params->utils, (unsigned char *) data, datalen, &text->salt, &text->saltlen, &data, &datalen); if (result) { params->utils->log(NULL, SASL_LOG_ERR, "Error getting OS string for 's'\n"); goto cleanup; } result = GetUTF8(params->utils, data, datalen, &text->server_options, &data, &datalen); if (result) { params->utils->log(NULL, SASL_LOG_ERR, "Error getting UTF8 string for 'L'"); goto cleanup; } params->utils->log(NULL, SASL_LOG_DEBUG, "L: '%s'", text->server_options); if (datalen != 0) { params->utils->log(NULL, SASL_LOG_ERR, "Extra data parsing buffer\n"); goto cleanup; } /* parse server options */ memset(&server_opts, 0, sizeof(srp_options_t)); result = ParseOptions(params->utils, text->server_options, &server_opts, 0); if (result) { params->utils->log(NULL, SASL_LOG_ERR, "Error parsing SRP server options\n"); goto cleanup; } /* create an 'a' */ GetRandBigInt(&text->a); /* Per [SRP]: make sure a > log[g](N) -- g is always 2 */ BN_add_word(&text->a, BN_num_bits(&text->N)); /* calculate 'A' * * A = g^a % N */ { BN_CTX *ctx = BN_CTX_new(); BN_init(&text->A); BN_mod_exp(&text->A, &text->g, &text->a, &text->N, ctx); BN_CTX_free(ctx); } /* make o */ result = CreateClientOpts(params, &server_opts, &text->client_opts); if (result) { params->utils->log(NULL, SASL_LOG_ERR, "Error creating client options\n"); goto cleanup; } result = OptionsToString(params->utils, &text->client_opts, &text->client_options); if (result) { params->utils->log(NULL, SASL_LOG_ERR, "Error converting client options to an option string\n"); goto cleanup; } result = SetOptions(&text->client_opts, text, params->utils, oparams); if (result) { params->utils->seterror(params->utils->conn, 0, "Error setting options"); goto cleanup; } /* Send out: * * A - client's public key * o - client option list * * { mpi(A) utf8(o) } */ result = MakeMPI(params->utils, &text->A, &mpiA, &mpiAlen); if (result) { params->utils->log(NULL, SASL_LOG_ERR, "Error making MPI string from A\n"); goto cleanup; } result = MakeUTF8(params->utils, text->client_options, &utf8o, &utf8olen); if (result) { params->utils->log(NULL, SASL_LOG_ERR, "Error making UTF8 string from client options ('o')\n"); goto cleanup; } result = MakeBuffer(text, mpiA, mpiAlen, utf8o, utf8olen, NULL, 0, NULL, 0, clientout, clientoutlen); if (result) { params->utils->log(NULL, SASL_LOG_ERR, "Error making output buffer\n"); goto cleanup; } text->state = 3; result = SASL_CONTINUE; cleanup: if (mpiA) params->utils->free(mpiA); if (utf8o) params->utils->free(utf8o); return result; } static int srp_client_mech_step3(context_t *text, sasl_client_params_t *params, const char *serverin, unsigned serverinlen, sasl_interact_t **prompt_need __attribute__((unused)), const char **clientout, unsigned *clientoutlen, sasl_out_params_t *oparams) { char *data; int datalen; int result; char *osM1 = NULL; int osM1len; /* Expect: * { mpi(B) } * */ result = UnBuffer(params->utils, (char *) serverin, serverinlen, &data, &datalen); if (result) return result; result = GetMPI(params->utils, (unsigned char *) data, datalen, &text->B, &data, &datalen); if (result) return result; /* Per [SRP]: reject B <= 0, B >= N */ if (BigIntCmpWord(&text->B, 0) <= 0 || BN_cmp(&text->B, &text->N) >= 0) { SETERROR(params->utils, "Illegal value for 'B'\n"); return SASL_BADPROT; } if (datalen != 0) { SETERROR(params->utils, "Extra data parsing buffer\n"); return SASL_BADPROT; } /* Calculate shared context key K * */ result = CalculateK_client(text, text->salt, text->saltlen, (char *) oparams->authid, text->password->data, text->password->len, &text->K, &text->Klen); if (result) return result; /* Now calculate M1 (client evidence) * */ result = CalculateM1(text, &text->N, &text->g, (char *) oparams->authid, text->salt, text->saltlen, text->server_options, &text->A, &text->B, text->K, text->Klen, &text->M1, &text->M1len); if (result) return result; /* Send: * * { os(M1) } */ result = MakeOS(params->utils, text->M1, text->M1len, &osM1, &osM1len); if (result) { params->utils->log(NULL, SASL_LOG_ERR, "Error creating OS string for M1\n"); goto cleanup; } result = MakeBuffer(text, osM1, osM1len, NULL, 0, NULL, 0, NULL, 0, clientout, clientoutlen); if (result) { params->utils->log(NULL, SASL_LOG_ERR, "Error creating buffer in step 3\n"); goto cleanup; } /* if we have a confidentiality layer we're done */ if (text->enabled & BIT_CONFIDENTIALITY) { /* set oparams */ oparams->doneflag = 1; oparams->param_version = 0; result = SASL_OK; goto cleanup; } text->state = 4; result = SASL_CONTINUE; cleanup: if (osM1) params->utils->free(osM1); return result; } static int srp_client_mech_step4(context_t *text, sasl_client_params_t *params, const char *serverin, unsigned serverinlen, sasl_interact_t **prompt_need __attribute__((unused)), const char **clientout __attribute__((unused)), unsigned *clientoutlen __attribute__((unused)), sasl_out_params_t *oparams) { char *data; int datalen; int result; char *serverM2 = NULL; int serverM2len; int i; char *myM2 = NULL; int myM2len; /* Input: * * M2 - server evidence * * { os(M2) } */ result = UnBuffer(params->utils, (char *) serverin, serverinlen, &data, &datalen); if (result) return result; result = GetOS(params->utils, (unsigned char *)data, datalen, &serverM2, &serverM2len, &data, &datalen); if (result) return result; if (datalen != 0) { SETERROR(params->utils, "Extra data parsing buffer\n"); result = SASL_BADPROT; goto cleanup; } /* calculate our own M2 */ result = CalculateM2(text, &text->A, (char *) oparams->authid, (char *) oparams->user, text->client_options, text->M1, text->M1len, text->K, text->Klen, &myM2, &myM2len); if (result) { params->utils->log(NULL, SASL_LOG_ERR, "Error calculating our own M2 (server evidence)\n"); goto cleanup; } /* compare to see if is server spoof */ if (myM2len != serverM2len) { SETERROR(params->utils, "SRP Server M2 length wrong\n"); result = SASL_BADSERV; goto cleanup; } for (i = 0; i < myM2len; i++) { if (serverM2[i] != myM2[i]) { SETERROR(params->utils, "SRP Server spoof detected. M2 incorrect\n"); result = SASL_BADSERV; goto cleanup; } } /* * Send out: nothing */ /* set oparams */ oparams->doneflag = 1; oparams->param_version = 0; result = SASL_OK; cleanup: if (serverM2) params->utils->free(serverM2); if (myM2) params->utils->free(myM2); return result; } static int srp_client_mech_step(void *conn_context, sasl_client_params_t *params, const char *serverin, unsigned serverinlen, sasl_interact_t **prompt_need, const char **clientout, unsigned *clientoutlen, sasl_out_params_t *oparams) { context_t *text = (context_t *) conn_context; params->utils->log(NULL, SASL_LOG_DEBUG, "SRP client step %d\n", text->state); *clientout = NULL; *clientoutlen = 0; switch (text->state) { case 1: return srp_client_mech_step1(text, params, serverin, serverinlen, prompt_need, clientout, clientoutlen, oparams); case 2: return srp_client_mech_step2(text, params, serverin, serverinlen, prompt_need, clientout, clientoutlen, oparams); case 3: return srp_client_mech_step3(text, params, serverin, serverinlen, prompt_need, clientout, clientoutlen, oparams); case 4: return srp_client_mech_step4(text, params, serverin, serverinlen, prompt_need, clientout, clientoutlen, oparams); default: params->utils->log(NULL, SASL_LOG_ERR, "Invalid SRP client step %d\n", text->state); return SASL_FAIL; } return SASL_FAIL; /* should never get here */ } static sasl_client_plug_t srp_client_plugins[] = { { "SRP", /* mech_name */ 0, /* max_ssf */ SASL_SEC_NOPLAINTEXT | SASL_SEC_NOANONYMOUS | SASL_SEC_NOACTIVE | SASL_SEC_NODICTIONARY | SASL_SEC_FORWARD_SECRECY | SASL_SEC_MUTUAL_AUTH, /* security_flags */ SASL_FEAT_WANT_CLIENT_FIRST, /* features */ NULL, /* required_prompts */ NULL, /* glob_context */ &srp_client_mech_new, /* mech_new */ &srp_client_mech_step, /* mech_step */ &srp_common_mech_dispose, /* mech_dispose */ &srp_common_mech_free, /* mech_free */ NULL, /* idle */ NULL, /* spare */ NULL /* spare */ } }; int srp_client_plug_init(const sasl_utils_t *utils __attribute__((unused)), int maxversion, int *out_version, const sasl_client_plug_t **pluglist, int *plugcount, const char *plugname __attribute__((unused))) { layer_option_t *opts; if (maxversion < SASL_CLIENT_PLUG_VERSION) { SETERROR(utils, "SRP version mismatch"); return SASL_BADVERS; } /* Add all digests and ciphers */ OpenSSL_add_all_algorithms(); /* See which digests we have available and set max_ssf accordingly */ opts = digest_options; while (opts->name) { if (EVP_get_digestbyname(opts->evp_name)) { opts->enabled = 1; srp_client_plugins[0].max_ssf = opts->ssf; } opts++; } /* See which ciphers we have available and set max_ssf accordingly */ opts = cipher_options; while (opts->name) { if (EVP_get_cipherbyname(opts->evp_name)) { opts->enabled = 1; if (opts->ssf > srp_client_plugins[0].max_ssf) { srp_client_plugins[0].max_ssf = opts->ssf; } } opts++; } *out_version = SASL_CLIENT_PLUG_VERSION; *pluglist = srp_client_plugins; *plugcount=1; return SASL_OK; }