2
* scod - a minimal sasl implementation for jabberd2
3
* Copyright (c) 2003 Robert Norris
5
* This program is free software; you can redistribute it and/or modify
6
* it under the terms of the GNU General Public License as published by
7
* the Free Software Foundation; either version 2 of the License, or
8
* (at your option) any later version.
10
* This program is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
13
* GNU General Public License for more details.
15
* You should have received a copy of the GNU General Public License
16
* along with this program; if not, write to the Free Software
17
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA02111-1307USA
20
/* DIGEST-MD5 mechanism */
32
/* unions to comply with strict-alias rules for gcc3 */
45
static char *_opt_quote(char *in) {
52
if(*r == '"' || *r == '\\')
57
out = (char *) malloc(sizeof(char) * (strlen(in) + nesc + 3));
65
if(*r == '"' || *r == '\\') {
78
log_debug(ZONE, "escaped '%s' into '%s'", in, out);
83
/** the list parser is based on code from cyrus-sasl. I love open source ;) */
84
static char *_opt_skip_lws(char *c) {
88
while(*c == ' ' || *c == HT || *c == CR || *c == LF) {
97
static char *_opt_skip_token(char *c, int ci) {
102
if(*c == DEL || *c == '(' || *c == ')' || *c == '<' || *c == '>' ||
103
*c == '@' || *c == ',' || *c == ';' || *c == ':' || *c == '\\' ||
104
*c == '\'' || *c == '/' || *c == '[' || *c == ']' || *c == '?' ||
105
*c == '=' || *c == '{' || *c == '}') {
107
if(!isupper((unsigned char) *c))
118
static char *_opt_unquote(char *in) {
122
/* if its not quoted, there's nothing to do */
124
return _opt_skip_token(in, 0);
129
for(end = in; *end != '\0'; end++, out++) {
134
else if(*end == '\\') {
157
static void _opt_get_pair(char **in, char **key, char **val) {
158
char *end, *cur = *in;
166
cur = _opt_skip_lws(cur);
170
cur = _opt_skip_token(cur, 1);
172
if(*cur != '=' && *cur != '\0') {
177
cur = _opt_skip_lws(cur);
187
cur = _opt_skip_lws(cur);
189
*val = (*cur == '"') ? cur + 1 : cur;
191
end = _opt_unquote(cur);
197
if(*end != ',' && *end != '\0') {
202
end = _opt_skip_lws(end);
208
else if(*end != '\0') {
216
static xht _digest_md5_parse_options(const char *buf, int buflen) {
218
char *nbuf, *in, *key, *val;
220
nbuf = (char *) malloc(sizeof(char) * (buflen + 1));
221
strncpy(nbuf, buf, buflen);
224
hash = xhash_new(101);
228
_opt_get_pair(&in, &key, &val);
232
sub = xhash_get(hash, key);
235
xhash_put(hash, pstrdup(xhash_pool(hash), key), sub);
236
pool_cleanup(xhash_pool(hash), (void (*)(void *)) xhash_free, sub);
239
xhash_put(sub, pstrdup(xhash_pool(hash), val), (void *) 1);
241
log_debug(ZONE, "got key '%s' val '%s'", key, val);
249
static char *_digest_md5_gen_nonce(void) {
251
char nonce[65], hnonce[41];
253
for(i = 0; i < 64; i++) {
254
r = (int) (36.0 * rand() / RAND_MAX);
255
nonce[i] = (r >= 0 && r <= 9) ? (r + 48) : (r + 87);
259
shahash_r(nonce, hnonce);
261
log_debug(ZONE, "generated nonce: %s", hnonce);
263
return strdup(hnonce);
266
typedef struct _digest_md5_st {
276
static int _digest_md5_client_start(scod_mech_t mech, scod_t sd, char **resp, int *resplen) {
277
log_debug(ZONE, "DIGEST-MD5 client start");
282
static int _digest_md5_client_step(scod_mech_t mech, scod_t sd, const char *chal, int challen, char **resp, int *resplen) {
284
char *key, *realm, *nonce, *qop, *charset, *algorithm, *cnonce, *c;
287
char ha1[33], ha2[33], hrsp[33];
293
log_debug(ZONE, "DIGEST-MD5 client step; challenge: %.*s", challen, chal);
295
if(sd->mech_data != NULL) {
296
/* !!! check rspauth */
297
sd->mech_data = NULL;
301
realm = nonce = qop = charset = algorithm = NULL;
303
attrs = _digest_md5_parse_options(chal, challen);
304
if(xhash_iter_first(attrs))
307
xhash_iter_get(attrs, (const char **) &key, xhv.val);
308
log_debug(ZONE, "extracting '%s'", key);
310
if(xhash_iter_first(sub)) {
311
if(strcmp(key, "realm") == 0) {
312
su.char_val = &realm;
313
(mech->ctx->cb)(sd, sd_cb_DIGEST_MD5_CHOOSE_REALM, (void *) sub, su.val, mech->ctx->cbarg);
315
else if(strcmp(key, "nonce") == 0)
316
xhash_iter_get(sub, (const char **) &nonce, NULL);
317
else if(strcmp(key, "qop") == 0)
318
xhash_iter_get(sub, (const char **) &qop, NULL);
319
else if(strcmp(key, "charset") == 0)
320
xhash_iter_get(sub, (const char **) &charset, NULL);
321
else if(strcmp(key, "algorithm") == 0)
322
xhash_iter_get(sub, (const char **) &algorithm, NULL);
324
} while(xhash_iter_next(attrs));
326
if(nonce == NULL || qop == NULL || charset == NULL || algorithm == NULL) {
327
log_debug(ZONE, "missing attribute");
329
return sd_auth_MALFORMED_DATA;
332
cnonce = _digest_md5_gen_nonce();
335
md5_append(&md5, sd->authnid, strlen(sd->authnid));
336
md5_append(&md5, ":", 1);
337
if(realm != NULL) md5_append(&md5, realm, strlen(realm));
338
md5_append(&md5, ":", 1);
339
md5_append(&md5, sd->pass, strlen(sd->pass));
340
md5_finish(&md5, hash);
343
md5_append(&md5, hash, 16);
344
md5_append(&md5, ":", 1);
345
md5_append(&md5, nonce, strlen(nonce));
346
md5_append(&md5, ":", 1);
347
md5_append(&md5, cnonce, 40);
348
if(sd->authzid != NULL) {
349
md5_append(&md5, ":", 1);
350
md5_append(&md5, sd->authzid, strlen(sd->authzid));
352
md5_finish(&md5, hash); /* A1 */
354
hex_from_raw(hash, 16, ha1);
356
log_debug(ZONE, "HEX(H(A1)) = %s", ha1);
359
md5_append(&md5, "AUTHENTICATE:", 13);
360
md5_append(&md5, "xmpp/", 5); /* !!! make this configurable */
361
md5_finish(&md5, hash); /* A2 */
363
hex_from_raw(hash, 16, ha2);
365
log_debug(ZONE, "HEX(H(A2)) = %s", ha2);
368
md5_append(&md5, ha1, 32);
369
md5_append(&md5, ":", 1);
370
md5_append(&md5, nonce, strlen(nonce));
371
md5_append(&md5, ":", 1);
372
md5_append(&md5, "00000001", 8);
373
md5_append(&md5, ":", 1);
374
md5_append(&md5, cnonce, 40);
375
md5_append(&md5, ":auth:", 6);
376
md5_append(&md5, ha2, 32);
377
md5_finish(&md5, hash); /* KD(HA1, foo, HA2) */
379
hex_from_raw(hash, 16, hrsp);
381
log_debug(ZONE, "response is %s", hrsp);
383
/* !!! generate rspauth and save it for later so we can validate */
388
c = _opt_quote(sd->authnid);
389
spooler(s, "username=", c, ",", s);
392
c = _opt_quote(nonce);
393
spooler(s, "nonce=", c, ",", s);
396
c = _opt_quote(cnonce);
397
spooler(s, "cnonce=", c, ",", s);
400
if(sd->authzid != NULL) {
401
c = _opt_quote(sd->authzid);
402
spooler(s, "authzid=", c, ",", s);
407
c = _opt_quote(realm);
408
spooler(s, "realm=", c, ",", s);
412
spooler(s, "nc=00000001,qop=auth,digest-uri=\"xmpp/\",charset=utf-8,response=", hrsp, s);
414
*resp = strdup(spool_print(s));
415
*resplen = strlen(*resp);
422
log_debug(ZONE, "generated initial response: %.*s", *resplen, *resp);
424
sd->mech_data = (void *) 1;
429
static int _digest_md5_server_start(scod_mech_t mech, scod_t sd, const char *resp, int resplen, char **chal, int *challen) {
435
log_debug(ZONE, "DIGEST-MD5 server start");
438
md = (digest_md5_t) pmalloco(p, sizeof(struct _digest_md5_st));
445
if(sd->realm != NULL) {
446
c = _opt_quote(sd->realm);
447
spooler(s, "realm=", c, ",", s);
451
nonce = _digest_md5_gen_nonce();
452
md->nonce = pstrdup(md->p, nonce);
455
c = _opt_quote(md->nonce);
456
spooler(s, "nonce=", c, ",qop=\"auth\",charset=utf-8,algorithm=md5-sess", s);
459
*chal = strdup(spool_print(s));
460
*challen = strlen(*chal);
464
log_debug(ZONE, "generated initial challenge: %.*s", *challen, *chal);
469
static int _digest_md5_server_step(scod_mech_t mech, scod_t sd, const char *resp, int resplen, char **chal, int *challen) {
470
digest_md5_t md = (digest_md5_t) sd->mech_data;
472
char *key, *username, *realm, *nonce, *cnonce, *nc, *qop, *digest_uri, *response, *charset, *pass, buf[257], *c, authzid[3072];
476
char ha1[33], ha2[33], hrsp[33];
477
struct _scod_cb_creds_st creds;
481
log_debug(ZONE, "DIGEST-MD5 server step; response: %.*s", resplen, resp);
486
sd->mech_data = NULL;
490
username = realm = nonce = cnonce = nc = qop = digest_uri = response = charset = NULL;
493
attrs = _digest_md5_parse_options(resp, resplen);
494
if(xhash_iter_first(attrs))
497
xhash_iter_get(attrs, (const char **) &key, xhv.val);
498
log_debug(ZONE, "extracting '%s'", key);
500
if(xhash_iter_first(sub)) {
501
if(strcmp(key, "username") == 0)
502
xhash_iter_get(sub, (const char **) &username, NULL);
503
else if(strcmp(key, "realm") == 0)
504
xhash_iter_get(sub, (const char **) &realm, NULL);
505
else if(strcmp(key, "nonce") == 0)
506
xhash_iter_get(sub, (const char **) &nonce, NULL);
507
else if(strcmp(key, "cnonce") == 0)
508
xhash_iter_get(sub, (const char **) &cnonce, NULL);
509
else if(strcmp(key, "nc") == 0)
510
xhash_iter_get(sub, (const char **) &nc, NULL);
511
else if(strcmp(key, "qop") == 0)
512
xhash_iter_get(sub, (const char **) &qop, NULL);
513
else if(strcmp(key, "digest-uri") == 0)
514
xhash_iter_get(sub, (const char **) &digest_uri, NULL);
515
else if(strcmp(key, "response") == 0)
516
xhash_iter_get(sub, (const char **) &response, NULL);
517
else if(strcmp(key, "charset") == 0)
518
xhash_iter_get(sub, (const char **) &charset, NULL);
519
else if(strcmp(key, "authzid") == 0) {
520
xhash_iter_get(sub, (const char **) &c, NULL);
521
strncpy(authzid, c, sizeof(authzid));
524
} while(xhash_iter_next(attrs));
527
if(username == NULL || nonce == NULL || cnonce == NULL || nc == NULL || qop == NULL || digest_uri == NULL || response == NULL)
528
err = sd_auth_MALFORMED_DATA;
529
else if(strcmp(nonce, md->nonce) != 0)
530
err = sd_auth_MISMATCH;
531
else if(strcmp(qop, "auth") != 0)
532
err = sd_auth_NOT_OFFERED;
534
if(err != sd_SUCCESS) {
535
log_debug(ZONE, "returning error %d", err);
539
sd->mech_data = NULL;
544
/* !!! verify realm? */
546
creds.authnid = username;
551
if((mech->ctx->cb)(sd, sd_cb_GET_PASS, &creds, su.val, mech->ctx->cbarg) != 0) {
552
log_debug(ZONE, "user not found (or some other error getting password), failing");
556
sd->mech_data = NULL;
558
return sd_auth_USER_UNKNOWN;
561
md->cnonce = pstrdup(md->p, cnonce);
562
md->nc = pstrdup(md->p, nc);
565
md5_append(&md5, username, strlen(username));
566
md5_append(&md5, ":", 1);
567
if(realm != NULL) md5_append(&md5, realm, strlen(realm));
568
md5_append(&md5, ":", 1);
569
if(pass != NULL) md5_append(&md5, pass, strlen(pass));
570
md5_finish(&md5, hash);
573
md5_append(&md5, hash, 16);
574
md5_append(&md5, ":", 1);
575
md5_append(&md5, md->nonce, strlen(md->nonce));
576
md5_append(&md5, ":", 1);
577
md5_append(&md5, md->cnonce, strlen(md->cnonce));
578
if(authzid[0] != '\0') {
579
md5_append(&md5, ":", 1);
580
md5_append(&md5, authzid, strlen(authzid));
582
md5_finish(&md5, hash); /* A1 */
584
hex_from_raw(hash, 16, ha1);
586
log_debug(ZONE, "HEX(H(A1)) = %s", ha1);
589
md5_append(&md5, "AUTHENTICATE:", 13);
590
md5_append(&md5, digest_uri, strlen(digest_uri));
591
md5_finish(&md5, hash); /* A2 */
593
hex_from_raw(hash, 16, ha2);
595
log_debug(ZONE, "HEX(H(A2)) = %s", ha2);
598
md5_append(&md5, ha1, 32);
599
md5_append(&md5, ":", 1);
600
md5_append(&md5, nonce, strlen(nonce));
601
md5_append(&md5, ":", 1);
602
md5_append(&md5, nc, strlen(nc));
603
md5_append(&md5, ":", 1);
604
md5_append(&md5, cnonce, strlen(cnonce));
605
md5_append(&md5, ":auth:", 6);
606
md5_append(&md5, ha2, 32);
607
md5_finish(&md5, hash); /* KD(HA1, foo, HA2) */
609
hex_from_raw(hash, 16, hrsp);
611
log_debug(ZONE, "our response is %s, theirs is %s", hrsp, response);
613
if(strcmp(hrsp, response) != 0) {
614
log_debug(ZONE, "not matched, denied");
618
sd->mech_data = NULL;
620
return sd_auth_AUTH_FAILED;
623
log_debug(ZONE, "matched, they're authenticated");
625
creds.authnid = username;
629
creds.authzid = authzid;
631
if((mech->ctx->cb)(sd, sd_cb_CHECK_AUTHZID, &creds, NULL, mech->ctx->cbarg) != 0) {
632
log_debug(ZONE, "authzid is invalid (app policy said so)");
636
sd->mech_data = NULL;
638
return sd_auth_AUTHZID_POLICY;
641
sd->authzid = strdup(creds.authzid);
644
md5_append(&md5, ":", 1);
645
md5_append(&md5, digest_uri, strlen(digest_uri));
646
md5_finish(&md5, hash); /* rspauth A2 */
648
hex_from_raw(hash, 16, ha2);
650
log_debug(ZONE, "HEX(H(rspauth A2)) = %s", ha2);
653
md5_append(&md5, ha1, 32);
654
md5_append(&md5, ":", 1);
655
md5_append(&md5, nonce, strlen(nonce));
656
md5_append(&md5, ":", 1);
657
md5_append(&md5, nc, strlen(nc));
658
md5_append(&md5, ":", 1);
659
md5_append(&md5, cnonce, strlen(cnonce));
660
md5_append(&md5, ":auth:", 6);
661
md5_append(&md5, ha2, 32);
662
md5_finish(&md5, hash); /* KD(HA1, foo, HA2) */
664
hex_from_raw(hash, 16, hrsp);
666
log_debug(ZONE, "rspauth: %s", hrsp);
668
*chal = (char *) malloc(sizeof(char) * 41);
669
snprintf(*chal, 41, "rspauth=%s", hrsp);
672
log_debug(ZONE, "generated final challenge: %.*s", *challen, *chal);
681
static void _digest_md5_free(scod_mech_t mech) {
682
xhash_free((xht) mech->private);
685
int scod_mech_digest_md5_init(scod_mech_t mech) {
686
log_debug(ZONE, "initialising DIGEST-MD5 mechanism");
688
mech->name = "DIGEST-MD5";
690
mech->flags = sd_flag_GET_PASS;
692
mech->client_start = _digest_md5_client_start;
693
mech->client_step = _digest_md5_client_step;
694
mech->server_start = _digest_md5_server_start;
695
mech->server_step = _digest_md5_server_step;
696
mech->free = _digest_md5_free;