/* SRP SASL plugin * Ken Murchison * Tim Martin 3/17/00 * $Id: srp.c,v 1.6 2006/01/24 20:37:26 snsimon Exp $ */ /* * Copyright (c) 1998-2003 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. */ /* * Notes: * * - The authentication exchanges *should* be correct (per draft -08) * but we won't know until we do some interop testing. * * - The security layers don't conform to draft -08: * o We don't use eos() and os() elements in an SRP buffer, we send * just the bare octets. * o We don't yet use the PRNG() and KDF() primatives described in * section 5.1. * * - Are we using cIV and sIV correctly for encrypt/decrypt? * * - We don't implement fast reauth. */ #include #include #include #include #include #include #ifndef UINT32_MAX #define UINT32_MAX 4294967295U #endif #if UINT_MAX == UINT32_MAX typedef unsigned int uint32; #elif ULONG_MAX == UINT32_MAX typedef unsigned long uint32; #elif USHRT_MAX == UINT32_MAX typedef unsigned short uint32; #else #error dont know what to use for uint32 #endif /* for big number support */ #include /* for digest and cipher support */ #include #include #include #define MD5_H /* suppress internal MD5 */ #include #include "plugin_common.h" #ifdef macintosh #include #endif /***************************** Common Section *****************************/ static const char plugin_id[] = "$Id: srp.c,v 1.6 2006/01/24 20:37:26 snsimon Exp $"; /* Size limit of cipher block size */ #define SRP_MAXBLOCKSIZE 16 /* Size limit of SRP buffer */ #define SRP_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, 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, 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; /* safe prime modulus */ BIGNUM g; /* generator */ BIGNUM v; /* password verifier */ BIGNUM b; /* server private key */ BIGNUM B; /* server public key */ BIGNUM a; /* client private key */ BIGNUM A; /* client public key */ char K[EVP_MAX_MD_SIZE]; /* shared context key */ int Klen; char M1[EVP_MAX_MD_SIZE]; /* client evidence */ 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; /* cache between client steps */ char cIV[SRP_MAXBLOCKSIZE]; /* cache between client steps */ char *salt; /* password 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 layer; /* bitmask of enabled layers */ const EVP_MD *hmac_md; /* HMAC for integrity */ HMAC_CTX hmac_send_ctx; HMAC_CTX hmac_recv_ctx; const EVP_CIPHER *cipher; /* cipher for confidentiality */ EVP_CIPHER_CTX cipher_enc_ctx; EVP_CIPHER_CTX cipher_dec_ctx; /* replay detection sequence numbers */ int seqnum_out; int seqnum_in; /* for encoding/decoding mem management */ char *encode_buf, *decode_buf, *decode_pkt_buf; unsigned encode_buf_len, decode_buf_len, decode_pkt_buf_len; /* layers buffering */ decode_context_t decode_context; } 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; unsigned i; char *input; unsigned long inputlen, tmpnum; int ret; if (!context || !invec || !numiov || !output || !outputlen) { PARAMERROR( text->utils ); return SASL_BADPARAM; } /* calculate total size of input */ for (i = 0, inputlen = 0; i < numiov; i++) inputlen += invec[i].iov_len; /* allocate a buffer for the output */ ret = _plug_buf_alloc(text->utils, &text->encode_buf, &text->encode_buf_len, 4 + /* for length */ inputlen + /* for content */ SRP_MAXBLOCKSIZE + /* for PKCS padding */ EVP_MAX_MD_SIZE); /* for HMAC */ if (ret != SASL_OK) return ret; *outputlen = 4; /* length */ /* operate on each iovec */ for (i = 0; i < numiov; i++) { input = invec[i].iov_base; inputlen = invec[i].iov_len; if (text->layer & BIT_CONFIDENTIALITY) { unsigned enclen; /* encrypt the data into the output buffer */ EVP_EncryptUpdate(&text->cipher_enc_ctx, text->encode_buf + *outputlen, &enclen, input, inputlen); *outputlen += enclen; /* switch the input to the encrypted data */ input = text->encode_buf + 4; inputlen = *outputlen - 4; } else { /* copy the raw input to the output */ memcpy(text->encode_buf + *outputlen, input, inputlen); *outputlen += inputlen; } } if (text->layer & BIT_CONFIDENTIALITY) { unsigned enclen; /* encrypt the last block of data into the output buffer */ EVP_EncryptFinal(&text->cipher_enc_ctx, text->encode_buf + *outputlen, &enclen); *outputlen += enclen; } if (text->layer & BIT_INTEGRITY) { unsigned hashlen; /* hash the content */ HMAC_Update(&text->hmac_send_ctx, text->encode_buf+4, *outputlen-4); if (text->layer & BIT_REPLAY_DETECTION) { /* hash the sequence number */ tmpnum = htonl(text->seqnum_out); HMAC_Update(&text->hmac_send_ctx, (char *) &tmpnum, 4); text->seqnum_out++; } /* append the HMAC into the output buffer */ HMAC_Final(&text->hmac_send_ctx, text->encode_buf + *outputlen, &hashlen); *outputlen += hashlen; } /* prepend the length of the output */ tmpnum = *outputlen - 4; tmpnum = htonl(tmpnum); memcpy(text->encode_buf, &tmpnum, 4); *output = text->encode_buf; return SASL_OK; } /* decode a single SRP packet */ static int srp_decode_packet(void *context, const char *input, unsigned inputlen, char **output, unsigned *outputlen) { context_t *text = (context_t *) context; int ret; if (text->layer & BIT_INTEGRITY) { const char *hash; char myhash[EVP_MAX_MD_SIZE]; unsigned hashlen, myhashlen, i; unsigned long tmpnum; hashlen = EVP_MD_size(text->hmac_md); if (inputlen < hashlen) { text->utils->seterror(text->utils->conn, 0, "SRP input is smaller " "than hash length: %d vs %d\n", inputlen, hashlen); return SASL_BADPROT; } inputlen -= hashlen; hash = input + inputlen; /* create our own hash from the input */ HMAC_Update(&text->hmac_recv_ctx, input, inputlen); if (text->layer & BIT_REPLAY_DETECTION) { /* hash the sequence number */ tmpnum = htonl(text->seqnum_in); HMAC_Update(&text->hmac_recv_ctx, (char *) &tmpnum, 4); text->seqnum_in++; } HMAC_Final(&text->hmac_recv_ctx, myhash, &myhashlen); /* compare hashes */ for (i = 0; i < hashlen; i++) { if ((myhashlen != hashlen) || (myhash[i] != hash[i])) { SETERROR(text->utils, "Hash is incorrect\n"); return SASL_BADMAC; } } } ret = _plug_buf_alloc(text->utils, &(text->decode_pkt_buf), &(text->decode_pkt_buf_len), inputlen); if (ret != SASL_OK) return ret; if (text->layer & BIT_CONFIDENTIALITY) { unsigned declen; /* decrypt the data into the output buffer */ EVP_DecryptUpdate(&text->cipher_dec_ctx, text->decode_pkt_buf, &declen, (char *) input, inputlen); *outputlen = declen; EVP_DecryptFinal(&text->cipher_dec_ctx, text->decode_pkt_buf + declen, &declen); *outputlen += declen; } else { /* copy the raw input to the output */ memcpy(text->decode_pkt_buf, input, inputlen); *outputlen = inputlen; } *output = text->decode_pkt_buf; 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->decode_context, input, inputlen, &text->decode_buf, &text->decode_buf_len, outputlen, srp_decode_packet, text); *output = text->decode_buf; return ret; } /* * 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; } /* * Compare a big integer against a word. */ 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; } /* * Generate a random big integer. */ static void GetRandBigInt(BIGNUM *out) { BN_init(out); /* xxx likely should use sasl random funcs */ BN_rand(out, SRP_MAXBLOCKSIZE*8, 0, 0); } #define MAX_BUFFER_LEN 2147483643 #define MAX_MPI_LEN 65535 #define MAX_UTF8_LEN 65535 #define MAX_OS_LEN 255 /* * Make an SRP buffer from the data specified by the fmt string. */ static int MakeBuffer(const sasl_utils_t *utils, char **buf, unsigned *buflen, unsigned *outlen, const char *fmt, ...) { va_list ap; char *p, *out = NULL; int r, alloclen, len; BIGNUM *mpi; char *os, *str, c; uint32 u; short ns; long totlen; /* first pass to calculate size of buffer */ va_start(ap, fmt); for (p = (char *) fmt, alloclen = 0; *p; p++) { if (*p != '%') { alloclen++; continue; } switch (*++p) { case 'm': /* MPI */ mpi = va_arg(ap, BIGNUM *); len = BN_num_bytes(mpi); if (len > MAX_MPI_LEN) { utils->log(NULL, SASL_LOG_ERR, "String too long to create mpi string\n"); r = SASL_FAIL; goto done; } alloclen += len + 2; break; case 'o': /* octet sequence (len followed by data) */ len = va_arg(ap, int); if (len > MAX_OS_LEN) { utils->log(NULL, SASL_LOG_ERR, "String too long to create os string\n"); r = SASL_FAIL; goto done; } alloclen += len + 1; os = va_arg(ap, char *); break; case 's': /* string */ str = va_arg(ap, char *); len = strlen(str); if (len > MAX_UTF8_LEN) { utils->log(NULL, SASL_LOG_ERR, "String too long to create utf8 string\n"); r = SASL_FAIL; goto done; } alloclen += len + 2; break; case 'u': /* unsigned int */ u = va_arg(ap, uint32); alloclen += sizeof(uint32); break; case 'c': /* char */ c = va_arg(ap, int) & 0xFF; alloclen += 1; break; default: alloclen += 1; break; } } va_end(ap); if (alloclen > MAX_BUFFER_LEN) { utils->log(NULL, SASL_LOG_ERR, "String too long to create SRP buffer string\n"); return SASL_FAIL; } alloclen += 4; r = _plug_buf_alloc(utils, buf, buflen, alloclen); if (r != SASL_OK) return r; out = *buf + 4; /* skip size for now */ /* second pass to fill buffer */ va_start(ap, fmt); for (p = (char *) fmt; *p; p++) { if (*p != '%') { *out = *p; out++; continue; } switch (*++p) { case 'm': /* MPI */ mpi = va_arg(ap, BIGNUM *); r = BigIntToBytes(mpi, out+2, BN_num_bytes(mpi), &len); if (r) goto done; ns = htons(len); memcpy(out, &ns, 2); /* add 2 byte len (network order) */ out += len + 2; break; case 'o': /* octet sequence (len followed by data) */ len = va_arg(ap, int); os = va_arg(ap, char *); *out = len & 0xFF; /* add 1 byte len */ memcpy(out+1, os, len); /* add data */ out += len+1; break; case 's': /* string */ str = va_arg(ap, char *); /* xxx do actual utf8 conversion */ len = strlen(str); ns = htons(len); memcpy(out, &ns, 2); /* add 2 byte len (network order) */ memcpy(out+2, str, len); /* add string */ out += len + 2; break; case 'u': /* unsigned int */ u = va_arg(ap, uint32); u = htonl(u); memcpy(out, &u, sizeof(uint32)); out += sizeof(uint32); break; case 'c': /* char */ c = va_arg(ap, int) & 0xFF; *out = c; out++; break; default: *out = *p; out++; break; } } done: va_end(ap); *outlen = out - *buf; /* add 4 byte len (network order) */ totlen = htonl(*outlen - 4); memcpy(*buf, &totlen, 4); return r; } /* * Extract an SRP buffer into the data specified by the fmt string. * * A '-' flag means don't allocate memory for the data ('o' only). */ static int UnBuffer(const sasl_utils_t *utils, const char *buf, unsigned buflen, const char *fmt, ...) { va_list ap; char *p; int r = SASL_OK, noalloc; BIGNUM *mpi; char **os, **str; uint32 *u; unsigned short ns; unsigned len; if (!buf || buflen < 4) { utils->seterror(utils->conn, 0, "Buffer is not big enough to be SRP buffer: %d\n", buflen); return SASL_BADPROT; } /* get the length */ memcpy(&len, buf, 4); len = ntohl(len); buf += 4; buflen -= 4; /* make sure it's right */ if (len != buflen) { SETERROR(utils, "SRP Buffer isn't of the right length\n"); return SASL_BADPROT; } va_start(ap, fmt); for (p = (char *) fmt; *p; p++) { if (*p != '%') { if (*buf != *p) { r = SASL_BADPROT; goto done; } buf++; buflen--; continue; } /* check for noalloc flag */ if ((noalloc = (*++p == '-'))) ++p; switch (*p) { case 'm': /* MPI */ if (buflen < 2) { SETERROR(utils, "Buffer is not big enough to be SRP MPI\n"); r = SASL_BADPROT; goto done; } /* get the length */ memcpy(&ns, buf, 2); len = ntohs(ns); buf += 2; buflen -= 2; /* make sure it's right */ if (len > buflen) { SETERROR(utils, "Not enough data for this SRP MPI\n"); r = SASL_BADPROT; goto done; } mpi = va_arg(ap, BIGNUM *); BN_init(mpi); BN_bin2bn(buf, len, mpi); break; case 'o': /* octet sequence (len followed by data) */ if (buflen < 1) { SETERROR(utils, "Buffer is not big enough to be SRP os\n"); r = SASL_BADPROT; goto done; } /* get the length */ len = (unsigned char) *buf; buf++; buflen--; /* make sure it's right */ if (len > buflen) { SETERROR(utils, "Not enough data for this SRP os\n"); r = SASL_BADPROT; goto done; } *(va_arg(ap, int *)) = len; os = va_arg(ap, char **); if (noalloc) *os = (char *) buf; else { *os = (char *) utils->malloc(len); if (!*os) { r = SASL_NOMEM; goto done; } memcpy(*os, buf, len); } break; case 's': /* string */ if (buflen < 2) { SETERROR(utils, "Buffer is not big enough to be SRP UTF8\n"); r = SASL_BADPROT; goto done; } /* get the length */ memcpy(&ns, buf, 2); len = ntohs(ns); buf += 2; buflen -= 2; /* make sure it's right */ if (len > buflen) { SETERROR(utils, "Not enough data for this SRP UTF8\n"); r = SASL_BADPROT; goto done; } str = va_arg(ap, char **); *str = (char *) utils->malloc(len+1); /* +1 for NUL */ if (!*str) { r = SASL_NOMEM; goto done; } memcpy(*str, buf, len); (*str)[len] = '\0'; break; case 'u': /* unsigned int */ if (buflen < sizeof(uint32)) { SETERROR(utils, "Buffer is not big enough to be SRP uint\n"); r = SASL_BADPROT; goto done; } len = sizeof(uint32); u = va_arg(ap, uint32*); memcpy(u, buf, len); *u = ntohs(*u); break; case 'c': /* char */ if (buflen < 1) { SETERROR(utils, "Buffer is not big enough to be SRP char\n"); r = SASL_BADPROT; goto done; } len = 1; *(va_arg(ap, char *)) = *buf; break; default: len = 1; if (*buf != *p) { r = SASL_BADPROT; goto done; } break; } buf += len; buflen -= len; } done: va_end(ap); if (buflen != 0) { SETERROR(utils, "Extra data in SRP buffer\n"); r = SASL_BADPROT; } return r; } /* * Apply the hash function to the data specifed by the fmt string. */ static int MakeHash(const EVP_MD *md, unsigned char hash[], int *hashlen, const char *fmt, ...) { va_list ap; char *p, buf[4096], *in; int inlen; EVP_MD_CTX mdctx; int r = 0, hflag; EVP_DigestInit(&mdctx, md); va_start(ap, fmt); for (p = (char *) fmt; *p; p++) { if (*p != '%') { in = p; inlen = 1; hflag = 0; } else { if ((hflag = (*++p == 'h'))) ++p; switch (*p) { case 'm': { /* MPI */ BIGNUM *mval = va_arg(ap, BIGNUM *); in = buf; r = BigIntToBytes(mval, buf, sizeof(buf)-1, &inlen); if (r) goto done; break; } case 'o': { /* octet sequence (len followed by data) */ inlen = va_arg(ap, int); in = va_arg(ap, char *); break; } case 's': /* string */ in = va_arg(ap, char *); inlen = strlen(in); break; case 'u': { /* unsigned int */ uint32 uval = va_arg(ap, uint32); in = buf; inlen = sizeof(uint32); *((uint32 *) buf) = htonl(uval); break; } default: in = p; inlen = 1; break; } } if (hflag) { /* hash data separately before adding to current hash */ EVP_MD_CTX tmpctx; EVP_DigestInit(&tmpctx, md); EVP_DigestUpdate(&tmpctx, in, inlen); EVP_DigestFinal(&tmpctx, buf, &inlen); in = buf; } EVP_DigestUpdate(&mdctx, in, inlen); } done: va_end(ap); EVP_DigestFinal(&mdctx, hash, hashlen); return r; } static int CalculateX(context_t *text, const char *salt, int saltlen, const char *user, const char *pass, int passlen, BIGNUM *x) { char hash[EVP_MAX_MD_SIZE]; int hashlen; /* x = H(salt | H(user | ':' | pass)) */ MakeHash(text->md, hash, &hashlen, "%s:%o", user, passlen, pass); MakeHash(text->md, hash, &hashlen, "%o%o", saltlen, salt, hashlen, hash); BN_init(x); BN_bin2bn(hash, hashlen, x); return SASL_OK; } static int CalculateM1(context_t *text, BIGNUM *N, BIGNUM *g, char *U, char *salt, int saltlen, BIGNUM *A, BIGNUM *B, char *K, int Klen, char *I, char *L, char *M1, int *M1len) { int r, i, len; unsigned char Nhash[EVP_MAX_MD_SIZE]; unsigned char ghash[EVP_MAX_MD_SIZE]; unsigned char Ng[EVP_MAX_MD_SIZE]; /* bytes(H( bytes(N) )) ^ bytes( H( bytes(g) )) ^ is the bitwise XOR operator. */ r = MakeHash(text->md, Nhash, &len, "%m", N); if (r) return r; r = MakeHash(text->md, ghash, &len, "%m", g); if (r) return r; for (i = 0; i < len; i++) { Ng[i] = (Nhash[i] ^ ghash[i]); } r = MakeHash(text->md, M1, M1len, "%o%hs%o%m%m%o%hs%hs", len, Ng, U, saltlen, salt, A, B, Klen, K, I, L); return r; } static int CalculateM2(context_t *text, BIGNUM *A, char *M1, int M1len, char *K, int Klen, char *I, char *o, char *sid, uint32 ttl, char *M2, int *M2len) { int r; r = MakeHash(text->md, M2, M2len, "%m%o%o%hs%hs%s%u", A, M1len, M1, Klen, K, I, o, sid, ttl); return r; } /* 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 |= 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 |= 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 |= 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 > SRP_MAXBUFFERSIZE) { utils->seterror(utils->conn, 0, "SRP Maxbuffersize %lu too big (> %lu)\n", opts->maxbufsize, SRP_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 = SRP_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 < SRP_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 selected MDA. */ static int SetMDA(srp_options_t *opts, context_t *text) { layer_option_t *opt; opt = FindOptionFromBit(opts->mda, digest_options); if (!opt) { text->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); return SASL_OK; } /* * Setup the selected security layer. */ static int LayerInit(srp_options_t *opts, context_t *text, sasl_out_params_t *oparams, char *enc_IV, char *dec_IV, unsigned maxbufsize) { layer_option_t *opt; if ((opts->integrity == 0) && (opts->confidentiality == 0)) { oparams->encode = NULL; oparams->decode = NULL; oparams->mech_ssf = 0; text->utils->log(NULL, SASL_LOG_DEBUG, "Using no protection\n"); return SASL_OK; } oparams->encode = &srp_encode; oparams->decode = &srp_decode; oparams->maxoutbuf = opts->maxbufsize - 4; /* account for 4-byte length */ _plug_decode_init(&text->decode_context, text->utils, maxbufsize); if (opts->replay_detection) { text->utils->log(NULL, SASL_LOG_DEBUG, "Using replay detection\n"); text->layer |= BIT_REPLAY_DETECTION; /* If no integrity layer specified, use default */ if (!opts->integrity) opts->integrity = default_digest->bit; } if (opts->integrity) { text->utils->log(NULL, SASL_LOG_DEBUG, "Using integrity protection\n"); text->layer |= BIT_INTEGRITY; opt = FindOptionFromBit(opts->integrity, digest_options); if (!opt) { text->utils->log(NULL, SASL_LOG_ERR, "Unable to find SRP integrity layer option\n"); return SASL_FAIL; } oparams->mech_ssf = opt->ssf; /* Initialize the HMACs */ text->hmac_md = EVP_get_digestbyname(opt->evp_name); HMAC_Init(&text->hmac_send_ctx, text->K, text->Klen, text->hmac_md); HMAC_Init(&text->hmac_recv_ctx, text->K, text->Klen, text->hmac_md); /* account for HMAC */ oparams->maxoutbuf -= EVP_MD_size(text->hmac_md); } if (opts->confidentiality) { text->utils->log(NULL, SASL_LOG_DEBUG, "Using confidentiality protection\n"); text->layer |= BIT_CONFIDENTIALITY; opt = FindOptionFromBit(opts->confidentiality, cipher_options); if (!opt) { text->utils->log(NULL, SASL_LOG_ERR, "Unable to find SRP confidentiality layer option\n"); return SASL_FAIL; } oparams->mech_ssf = opt->ssf; /* Initialize the ciphers */ text->cipher = EVP_get_cipherbyname(opt->evp_name); EVP_CIPHER_CTX_init(&text->cipher_enc_ctx); EVP_EncryptInit(&text->cipher_enc_ctx, text->cipher, text->K, enc_IV); EVP_CIPHER_CTX_init(&text->cipher_dec_ctx); EVP_DecryptInit(&text->cipher_dec_ctx, text->cipher, text->K, dec_IV); } return SASL_OK; } static void LayerCleanup(context_t *text) { if (text->layer & BIT_INTEGRITY) { HMAC_cleanup(&text->hmac_send_ctx); HMAC_cleanup(&text->hmac_recv_ctx); } if (text->layer & BIT_CONFIDENTIALITY) { EVP_CIPHER_CTX_cleanup(&text->cipher_enc_ctx); EVP_CIPHER_CTX_cleanup(&text->cipher_dec_ctx); } } /* * 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->B); BN_clear_free(&text->a); BN_clear_free(&text->A); 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); LayerCleanup(text); _plug_decode_free(&text->decode_context); if (text->encode_buf) utils->free(text->encode_buf); if (text->decode_buf) utils->free(text->decode_buf); if (text->decode_pkt_buf) utils->free(text->decode_pkt_buf); if (text->out_buf) utils->free(text->out_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_MAXBLOCKSIZE; *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 CalculateB(context_t *text __attribute__((unused)), BIGNUM *v, BIGNUM *N, BIGNUM *g, BIGNUM *b, BIGNUM *B) { BIGNUM v3; BN_CTX *ctx = BN_CTX_new(); /* 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 = (3v + g^b) % N */ BN_init(&v3); BN_set_word(&v3, 3); BN_mod_mul(&v3, &v3, v, N, ctx); BN_init(B); BN_mod_exp(B, g, b, N, ctx); #if OPENSSL_VERSION_NUMBER >= 0x00907000L BN_mod_add(B, B, &v3, N, ctx); #else BN_add(B, B, &v3); BN_mod(B, B, N, ctx); #endif BN_CTX_free(ctx); return SASL_OK; } static int ServerCalculateK(context_t *text, BIGNUM *v, BIGNUM *N, BIGNUM *A, BIGNUM *b, BIGNUM *B, char *K, int *Klen) { unsigned char hash[EVP_MAX_MD_SIZE]; int hashlen; BIGNUM u; BIGNUM base; BIGNUM S; BN_CTX *ctx = BN_CTX_new(); int r; /* u = H(A | B) */ r = MakeHash(text->md, hash, &hashlen, "%m%m", A, B); if (r) return r; BN_init(&u); BN_bin2bn(hash, hashlen, &u); /* S = (Av^u) ^ b % N */ 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 = H(S) */ r = MakeHash(text->md, K, Klen, "%m", &S); if (r) goto err; r = SASL_OK; err: BN_CTX_free(ctx); 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; /* The secret data is stored as suggested in RFC 2945: * * { utf8(mda) mpi(v) os(salt) } (base64 encoded) */ r = utils->decode64(secret, seclen, secret, seclen, &seclen); if (!r) r = UnBuffer(utils, secret, seclen, "%s%m%o", mda, v, saltlen, salt); if (r) { utils->seterror(utils->conn, 0, "Error UnBuffering user secret"); } 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.maxbufsize == 0) { limitssf = 0; requiressf = 0; } else { 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 = SRP_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) { int result; char *sid = NULL; char *cn = NULL; int cnlen; char *realm = NULL; char *user = NULL; const char *password_request[] = { "*cmusaslsecretSRP", SASL_AUX_PASSWORD, NULL }; struct propval auxprop_values[3]; /* Expect: * * U - authentication identity * I - authorization identity * sid - session id * cn - client nonce * * { utf8(U) utf8(I) utf8(sid) os(cn) } * */ result = UnBuffer(params->utils, clientin, clientinlen, "%s%s%s%o", &text->authid, &text->userid, &sid, &cnlen, &cn); if (result) { params->utils->seterror(params->utils->conn, 0, "Error UnBuffering input in step 1"); return result; } /* 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, text->authid, 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 = params->transition ? SASL_TRANS : SASL_NOUSER; goto cleanup; } if (auxprop_values[0].name && auxprop_values[0].values) { char *mda = NULL; /* We have a precomputed verifier */ result = ParseUserSecret(params->utils, (char*) auxprop_values[0].values[0], auxprop_values[0].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[1].name && auxprop_values[1].values) { /* We only have the password -- calculate the verifier */ int len = strlen(auxprop_values[1].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, text->authid, auxprop_values[1].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; } /* erase the plaintext password */ params->utils->prop_erase(params->propctx, password_request[1]); /* Calculate B */ result = CalculateB(text, &text->v, &text->N, &text->g, &text->b, &text->B); if (result) { params->utils->seterror(params->utils->conn, 0, "Error calculating B"); return result; } /* Create L */ 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 * B - server's public key * L - server options (available layers etc) * * { 0x00 mpi(N) mpi(g) os(s) mpi(B) utf8(L) } * */ result = MakeBuffer(text->utils, &text->out_buf, &text->out_buf_len, serveroutlen, "%c%m%m%o%m%s", 0x00, &text->N, &text->g, text->saltlen, text->salt, &text->B, text->server_options); if (result) { params->utils->seterror(params->utils->conn, 0, "Error creating SRP buffer from data in step 1"); goto cleanup; } *serverout = text->out_buf; text->state = 2; result = SASL_CONTINUE; cleanup: if (sid) params->utils->free(sid); if (cn) params->utils->free(cn); if (user) params->utils->free(user); if (realm) params->utils->free(realm); 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) { int result; char *M1 = NULL, *cIV = NULL; /* don't free */ int M1len, cIVlen; srp_options_t client_opts; char myM1[EVP_MAX_MD_SIZE]; int myM1len; int i; char M2[EVP_MAX_MD_SIZE]; int M2len; char sIV[SRP_MAXBLOCKSIZE]; /* Expect: * * A - client's public key * M1 - client evidence * o - client option list * cIV - client's initial vector * * { mpi(A) os(M1) utf8(o) os(cIV) } * */ result = UnBuffer(params->utils, clientin, clientinlen, "%m%-o%s%-o", &text->A, &M1len, &M1, &text->client_options, &cIVlen, &cIV); if (result) { params->utils->seterror(params->utils->conn, 0, "Error UnBuffering input in step 2"); goto cleanup; } /* Per [SRP]: reject A <= 0 */ if (BigIntCmpWord(&text->A, 0) <= 0) { SETERROR(params->utils, "Illegal value for 'A'\n"); result = SASL_BADPROT; goto cleanup; } /* 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 = SetMDA(&client_opts, text); if (result) { params->utils->seterror(params->utils->conn, 0, "Error setting options"); return result; } /* Calculate K */ result = ServerCalculateK(text, &text->v, &text->N, &text->A, &text->b, &text->B, text->K, &text->Klen); if (result) { params->utils->seterror(params->utils->conn, 0, "Error calculating K"); return result; } /* See if M1 is correct */ result = CalculateM1(text, &text->N, &text->g, text->authid, text->salt, text->saltlen, &text->A, &text->B, text->K, text->Klen, text->userid, text->server_options, 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; } } /* calculate M2 to send */ result = CalculateM2(text, &text->A, M1, M1len, text->K, text->Klen, text->userid, text->client_options, "", 0, M2, &M2len); if (result) { params->utils->seterror(params->utils->conn, 0, "Error calculating M2 (server evidence)"); goto cleanup; } /* Create sIV (server initial vector) */ text->utils->rand(text->utils->rpool, sIV, sizeof(sIV)); /* * Send out: * M2 - server evidence * sIV - server's initial vector * sid - session id * ttl - time to live * * { os(M2) os(sIV) utf8(sid) uint(ttl) } */ result = MakeBuffer(text->utils, &text->out_buf, &text->out_buf_len, serveroutlen, "%o%o%s%u", M2len, M2, sizeof(sIV), sIV, "", 0); if (result) { params->utils->seterror(params->utils->conn, 0, "Error making output buffer in SRP step 3"); goto cleanup; } *serverout = text->out_buf; /* configure security layer */ result = LayerInit(&client_opts, text, oparams, cIV, sIV, params->props.maxbufsize); if (result) { params->utils->seterror(params->utils->conn, 0, "Error initializing security layer"); return result; } /* set oparams */ oparams->doneflag = 1; oparams->param_version = 0; result = SASL_OK; cleanup: 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); 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 *user_only = NULL; char *realm = NULL; sasl_secret_t *sec = NULL; struct propctx *propctx = NULL; const char *store_request[] = { "cmusaslsecretSRP", NULL }; /* Do we have a backend that can store properties? */ if (!sparams->utils->auxprop_store || sparams->utils->auxprop_store(NULL, NULL, NULL) != SASL_OK) { SETERROR(sparams->utils, "SRP: auxprop backend can't store properties"); return SASL_NOMECH; } /* NB: Ideally we need to canonicalize userstr here */ r = _plug_parseuser(sparams->utils, &user_only, &realm, sparams->user_realm, sparams->serverFQDN, userstr); if (r) { sparams->utils->seterror(sparams->utils->conn, 0, "Error parsing user"); return r; } r = _plug_make_fulluser(sparams->utils, &user, user_only, realm); if (r) { goto end; } if ((flags & SASL_SET_DISABLE) || pass == NULL) { sec = NULL; } else { context_t *text; BIGNUM N; BIGNUM g; BIGNUM v; char *salt; int saltlen; char *buffer = NULL; int bufferlen, alloclen, encodelen; 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; } /* user is a full username here */ 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: * * { utf8(mda) mpi(v) os(salt) } (base64 encoded) */ r = MakeBuffer(text->utils, &text->out_buf, &text->out_buf_len, &bufferlen, "%s%m%o", server_mda->name, &v, saltlen, salt); if (r) { sparams->utils->seterror(sparams->utils->conn, 0, "Error making buffer for secret"); goto end; } buffer = text->out_buf; /* Put 'buffer' into sasl_secret_t. * This will be base64 encoded, so make sure its big enough. */ alloclen = (bufferlen/3 + 1) * 4 + 1; sec = sparams->utils->malloc(sizeof(sasl_secret_t)+alloclen); if (!sec) { r = SASL_NOMEM; goto end; } sparams->utils->encode64(buffer, bufferlen, sec->data, alloclen, &encodelen); sec->len = encodelen; /* Clean everything up */ end: 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 */ propctx = sparams->utils->prop_new(0); if (!propctx) r = SASL_FAIL; if (!r) r = sparams->utils->prop_request(propctx, store_request); if (!r) r = sparams->utils->prop_set(propctx, "cmusaslsecretSRP", (sec ? sec->data : NULL), (sec ? sec->len : 0)); if (!r) r = sparams->utils->auxprop_store(sparams->utils->conn, propctx, user); if (propctx) sparams->utils->prop_dispose(&propctx); if (r) { sparams->utils->seterror(sparams->utils->conn, 0, "Error putting SRP secret"); goto cleanup; } sparams->utils->log(NULL, SASL_LOG_DEBUG, "Setpass for SRP successful\n"); cleanup: if (user) _plug_free_string(sparams->utils, &user); if (user_only) _plug_free_string(sparams->utils, &user_only); if (realm) _plug_free_string(sparams->utils, &realm); if (sec) _plug_free_secret(sparams->utils, &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 | SASL_FEAT_ALLOWS_PROXY, /* 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 */ #ifdef 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; } static int CalculateA(context_t *text __attribute__((unused)), BIGNUM *N, BIGNUM *g, BIGNUM *a, BIGNUM *A) { BN_CTX *ctx = BN_CTX_new(); /* Generate a */ GetRandBigInt(a); /* Per [SRP]: make sure a > log[g](N) -- g is always 2 */ BN_add_word(a, BN_num_bits(N)); /* A = g^a % N */ BN_init(A); BN_mod_exp(A, g, a, N, ctx); BN_CTX_free(ctx); return SASL_OK; } static int ClientCalculateK(context_t *text, char *salt, int saltlen, char *user, char *pass, int passlen, BIGNUM *N, BIGNUM *g, BIGNUM *a, BIGNUM *A, BIGNUM *B, char *K, int *Klen) { int r; unsigned char hash[EVP_MAX_MD_SIZE]; int hashlen; BIGNUM x; BIGNUM u; BIGNUM aux; BIGNUM gx; BIGNUM gx3; BIGNUM base; BIGNUM S; BN_CTX *ctx = BN_CTX_new(); /* u = H(A | B) */ r = MakeHash(text->md, hash, &hashlen, "%m%m", A, B); if (r) goto err; BN_init(&u); BN_bin2bn(hash, hashlen, &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; } /* S = (B - 3(g^x)) ^ (a + ux) % N */ 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, a); /* gx3 = 3(g^x) % N */ BN_init(&gx); BN_mod_exp(&gx, g, &x, N, ctx); BN_init(&gx3); BN_set_word(&gx3, 3); BN_mod_mul(&gx3, &gx3, &gx, N, ctx); /* base = (B - 3(g^x)) % N */ BN_init(&base); #if OPENSSL_VERSION_NUMBER >= 0x00907000L BN_mod_sub(&base, B, &gx3, N, ctx); #else BN_sub(&base, B, &gx3); BN_mod(&base, &base, N, ctx); if (BigIntCmpWord(&base, 0) < 0) { BN_add(&base, &base, N); } #endif /* S = base^aux % N */ BN_init(&S); BN_mod_exp(&S, &base, &aux, N, ctx); /* K = H(S) */ r = MakeHash(text->md, K, Klen, "%m", &S); 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(&gx3); 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.maxbufsize == 0) { musthave = 0; limit = 0; } else { 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 " "musthave = %d limit = %d", available->confidentiality, musthave, limit); /* 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 " "musthave = %d limit = %d", available->integrity, musthave, limit); /* 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 = SRP_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 = NULL, *userid = NULL; int auth_result = SASL_OK; int pass_result = SASL_OK; int user_result = SASL_OK; int result; /* 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; } if (!userid || !*userid) { result = params->canon_user(params->utils->conn, authid, 0, SASL_CU_AUTHID | SASL_CU_AUTHZID, oparams); } else { 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 * sid - previous session id * cn - client nonce * * { utf8(U) utf8(I) utf8(sid) os(cn) } */ result = MakeBuffer(text->utils, &text->out_buf, &text->out_buf_len, clientoutlen, "%s%s%s%o", (char *) oparams->authid, (char *) oparams->user, "", 0, ""); if (result) { params->utils->log(NULL, SASL_LOG_ERR, "Error making output buffer\n"); goto cleanup; } *clientout = text->out_buf; text->state = 2; result = SASL_CONTINUE; cleanup: 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) { int result; char reuse; srp_options_t server_opts; /* Expect: * * { 0x00 mpi(N) mpi(g) os(s) mpi(B) utf8(L) } */ result = UnBuffer(params->utils, serverin, serverinlen, "%c%m%m%o%m%s", &reuse, &text->N, &text->g, &text->saltlen, &text->salt, &text->B, &text->server_options); if (result) { params->utils->seterror(params->utils->conn, 0, "Error UnBuffering input in step 2"); 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; } /* 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"); result = SASL_BADPROT; 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 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 = SetMDA(&text->client_opts, text); if (result) { params->utils->seterror(params->utils->conn, 0, "Error setting MDA"); goto cleanup; } /* Calculate A */ result = CalculateA(text, &text->N, &text->g, &text->a, &text->A); if (result) { params->utils->seterror(params->utils->conn, 0, "Error calculating A"); return result; } /* Calculate shared context key K */ result = ClientCalculateK(text, text->salt, text->saltlen, (char *) oparams->authid, text->password->data, text->password->len, &text->N, &text->g, &text->a, &text->A, &text->B, text->K, &text->Klen); if (result) { params->utils->log(NULL, SASL_LOG_ERR, "Error creating K\n"); goto cleanup; } /* Calculate M1 (client evidence) */ result = CalculateM1(text, &text->N, &text->g, (char *) oparams->authid, text->salt, text->saltlen, &text->A, &text->B, text->K, text->Klen, (char *) oparams->user, text->server_options, text->M1, &text->M1len); if (result) { params->utils->log(NULL, SASL_LOG_ERR, "Error creating M1\n"); goto cleanup; } /* Create cIV (client initial vector) */ text->utils->rand(text->utils->rpool, text->cIV, sizeof(text->cIV)); /* Send out: * * A - client's public key * M1 - client evidence * o - client option list * cIV - client initial vector * * { mpi(A) os(M1) utf8(o) os(cIV) } */ result = MakeBuffer(text->utils, &text->out_buf, &text->out_buf_len, clientoutlen, "%m%o%s%o", &text->A, text->M1len, text->M1, text->client_options, sizeof(text->cIV), text->cIV); if (result) { params->utils->log(NULL, SASL_LOG_ERR, "Error making output buffer\n"); goto cleanup; } *clientout = text->out_buf; text->state = 3; result = SASL_CONTINUE; cleanup: 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 __attribute__((unused)), unsigned *clientoutlen __attribute__((unused)), sasl_out_params_t *oparams) { int result; char *M2 = NULL, *sIV = NULL; /* don't free */ char *sid = NULL; int M2len, sIVlen; uint32 ttl; int i; char myM2[EVP_MAX_MD_SIZE]; int myM2len; /* Expect: * * M2 - server evidence * sIV - server initial vector * sid - session id * ttl - time to live * * { os(M2) os(sIV) utf8(sid) uint(ttl) } */ result = UnBuffer(params->utils, serverin, serverinlen, "%-o%-o%s%u", &M2len, &M2, &sIVlen, &sIV, &sid, &ttl); if (result) { params->utils->seterror(params->utils->conn, 0, "Error UnBuffering input in step 3"); goto cleanup; } /* calculate our own M2 */ result = CalculateM2(text, &text->A, text->M1, text->M1len, text->K, text->Klen, (char *) oparams->user, text->client_options, "", 0, 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 != M2len) { SETERROR(params->utils, "SRP Server M2 length wrong\n"); result = SASL_BADSERV; goto cleanup; } for (i = 0; i < myM2len; i++) { if (M2[i] != myM2[i]) { SETERROR(params->utils, "SRP Server spoof detected. M2 incorrect\n"); result = SASL_BADSERV; goto cleanup; } } /* * Send out: nothing */ /* configure security layer */ result = LayerInit(&text->client_opts, text, oparams, sIV, text->cIV, params->props.maxbufsize); if (result) { params->utils->seterror(params->utils->conn, 0, "Error initializing security layer"); return result; } /* set oparams */ oparams->doneflag = 1; oparams->param_version = 0; result = SASL_OK; cleanup: if (sid) params->utils->free(sid); 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); 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 | SASL_FEAT_ALLOWS_PROXY, /* 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; }