Skip to content

Commit bfd0872

Browse files
committed
Add support for SCRAM-*-PLUS SASL mechanisms
This fixes #133 Signed-off-by: Steffen Jaeckel <[email protected]>
1 parent c5b6026 commit bfd0872

File tree

11 files changed

+365
-60
lines changed

11 files changed

+365
-60
lines changed

src/auth.c

Lines changed: 122 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ static int _handle_digestmd5_rspauth(xmpp_conn_t *conn,
8383
static int _handle_scram_challenge(xmpp_conn_t *conn,
8484
xmpp_stanza_t *stanza,
8585
void *userdata);
86-
static char *_make_scram_init_msg(xmpp_conn_t *conn);
86+
struct scram_user_data;
87+
static int _make_scram_init_msg(struct scram_user_data *scram);
8788

8889
static int _handle_missing_features_sasl(xmpp_conn_t *conn, void *userdata);
8990
static int _handle_missing_bind(xmpp_conn_t *conn, void *userdata);
@@ -243,21 +244,24 @@ _handle_features(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata)
243244
if (text == NULL)
244245
continue;
245246

246-
if (strcasecmp(text, "PLAIN") == 0)
247+
if (strcasecmp(text, "PLAIN") == 0) {
247248
conn->sasl_support |= SASL_MASK_PLAIN;
248-
else if (strcasecmp(text, "EXTERNAL") == 0 &&
249-
(conn->tls_client_cert || conn->tls_client_key))
249+
} else if (strcasecmp(text, "EXTERNAL") == 0 &&
250+
(conn->tls_client_cert || conn->tls_client_key)) {
250251
conn->sasl_support |= SASL_MASK_EXTERNAL;
251-
else if (strcasecmp(text, "DIGEST-MD5") == 0)
252+
} else if (strcasecmp(text, "DIGEST-MD5") == 0) {
252253
conn->sasl_support |= SASL_MASK_DIGESTMD5;
253-
else if (strcasecmp(text, "SCRAM-SHA-1") == 0)
254-
conn->sasl_support |= SASL_MASK_SCRAMSHA1;
255-
else if (strcasecmp(text, "SCRAM-SHA-256") == 0)
256-
conn->sasl_support |= SASL_MASK_SCRAMSHA256;
257-
else if (strcasecmp(text, "SCRAM-SHA-512") == 0)
258-
conn->sasl_support |= SASL_MASK_SCRAMSHA512;
259-
else if (strcasecmp(text, "ANONYMOUS") == 0)
254+
} else if (strcasecmp(text, "ANONYMOUS") == 0) {
260255
conn->sasl_support |= SASL_MASK_ANONYMOUS;
256+
} else {
257+
size_t n;
258+
for (n = 0; n < scram_algs_num; ++n) {
259+
if (strcasecmp(text, scram_algs[n]->scram_name) == 0) {
260+
conn->sasl_support |= scram_algs[n]->mask;
261+
break;
262+
}
263+
}
264+
}
261265

262266
strophe_free(conn->ctx, text);
263267
}
@@ -439,7 +443,11 @@ static int _handle_digestmd5_rspauth(xmpp_conn_t *conn,
439443
}
440444

441445
struct scram_user_data {
446+
xmpp_conn_t *conn;
447+
int sasl_plus;
442448
char *scram_init;
449+
char *channel_binding;
450+
const char *first_bare;
443451
const struct hash_alg *alg;
444452
};
445453

@@ -471,8 +479,9 @@ static int _handle_scram_challenge(xmpp_conn_t *conn,
471479
if (!challenge)
472480
goto err;
473481

474-
response = sasl_scram(conn->ctx, scram_ctx->alg, challenge,
475-
scram_ctx->scram_init, conn->jid, conn->pass);
482+
response =
483+
sasl_scram(conn->ctx, scram_ctx->alg, scram_ctx->channel_binding,
484+
challenge, scram_ctx->first_bare, conn->jid, conn->pass);
476485
strophe_free(conn->ctx, challenge);
477486
if (!response)
478487
goto err;
@@ -506,7 +515,8 @@ static int _handle_scram_challenge(xmpp_conn_t *conn,
506515
*/
507516
rc = _handle_sasl_result(conn, stanza,
508517
(void *)scram_ctx->alg->scram_name);
509-
strophe_free(conn->ctx, scram_ctx->scram_init);
518+
strophe_free_and_null(conn->ctx, scram_ctx->channel_binding);
519+
strophe_free_and_null(conn->ctx, scram_ctx->scram_init);
510520
strophe_free(conn->ctx, scram_ctx);
511521
}
512522

@@ -517,33 +527,103 @@ static int _handle_scram_challenge(xmpp_conn_t *conn,
517527
err_free_response:
518528
strophe_free(conn->ctx, response);
519529
err:
520-
strophe_free(conn->ctx, scram_ctx->scram_init);
530+
strophe_free_and_null(conn->ctx, scram_ctx->channel_binding);
531+
strophe_free_and_null(conn->ctx, scram_ctx->scram_init);
521532
strophe_free(conn->ctx, scram_ctx);
522533
disconnect_mem_error(conn);
523534
return 0;
524535
}
525536

526-
static char *_make_scram_init_msg(xmpp_conn_t *conn)
537+
static int _make_scram_init_msg(struct scram_user_data *scram)
527538
{
539+
xmpp_conn_t *conn = scram->conn;
528540
xmpp_ctx_t *ctx = conn->ctx;
529-
size_t message_len;
530-
char *node;
531-
char *message;
532-
char nonce[32];
541+
const void *binding_data;
542+
const char *binding_type;
543+
char *node, *message;
544+
size_t message_len, binding_type_len = 0, binding_data_len;
545+
int l, is_secured = xmpp_conn_is_secured(conn);
546+
/* This buffer must be able to hold:
547+
* "p=<10 bytes binding type>,,<36 bytes binding data>"
548+
* + alignment */
549+
char buf[56];
550+
551+
if (scram->sasl_plus) {
552+
if (!is_secured) {
553+
strophe_error(
554+
ctx, "xmpp",
555+
"SASL: Server requested a -PLUS variant to authenticate, "
556+
"but the connection is not secured. This is an error on "
557+
"the server side we can't do anything about.");
558+
return -1;
559+
}
560+
if (tls_init_channel_binding(conn->tls, &binding_type,
561+
&binding_type_len)) {
562+
return -1;
563+
}
564+
/* directly account for the '=' char in 'p=<binding-type>' */
565+
binding_type_len += 1;
566+
}
533567

534568
node = xmpp_jid_node(ctx, conn->jid);
535569
if (!node) {
536-
return NULL;
570+
return -1;
537571
}
538-
xmpp_rand_nonce(ctx->rand, nonce, sizeof(nonce));
539-
message_len = strlen(node) + strlen(nonce) + 8 + 1;
572+
/* 32 bytes nonce is enough */
573+
xmpp_rand_nonce(ctx->rand, buf, 33);
574+
message_len = strlen(node) + strlen(buf) + 8 + binding_type_len + 1;
540575
message = strophe_alloc(ctx, message_len);
541-
if (message) {
542-
strophe_snprintf(message, message_len, "n,,n=%s,r=%s", node, nonce);
576+
if (!message) {
577+
goto err_node;
543578
}
579+
/* increase length to account for 'y,,', 'n,,' or 'p,,'.
580+
* In the 'p' case the '=' sign has already been accounted for above.
581+
*/
582+
binding_type_len += 3;
583+
if (scram->sasl_plus) {
584+
l = strophe_snprintf(message, message_len, "p=%s,,n=%s,r=%s",
585+
binding_type, node, buf);
586+
} else {
587+
l = strophe_snprintf(message, message_len, "%c,,n=%s,r=%s",
588+
is_secured ? 'y' : 'n', node, buf);
589+
}
590+
if (l < 0 || (size_t)l >= message_len) {
591+
goto err_msg;
592+
}
593+
if (binding_type_len > sizeof(buf)) {
594+
goto err_msg;
595+
}
596+
/* Make `first_bare` point to the 'n' of 'n=<node>' of the
597+
* client-first-message */
598+
scram->first_bare = message + binding_type_len;
599+
memcpy(buf, message, binding_type_len);
600+
if (scram->sasl_plus) {
601+
binding_data =
602+
tls_get_channel_binding_data(conn->tls, &binding_data_len);
603+
if (!binding_data) {
604+
goto err_msg;
605+
}
606+
if (binding_data_len > sizeof(buf) - binding_type_len) {
607+
strophe_error(ctx, "xmpp", "Channel binding data is too long (%zu)",
608+
binding_data_len);
609+
goto err_msg;
610+
}
611+
memcpy(&buf[binding_type_len], binding_data, binding_data_len);
612+
binding_type_len += binding_data_len;
613+
}
614+
scram->channel_binding =
615+
xmpp_base64_encode(ctx, (void *)buf, binding_type_len);
616+
memset(buf, 0, binding_type_len);
544617
strophe_free(ctx, node);
618+
scram->scram_init = message;
619+
620+
return 0;
545621

546-
return message;
622+
err_msg:
623+
strophe_free(ctx, message);
624+
err_node:
625+
strophe_free(ctx, node);
626+
return -1;
547627
}
548628

549629
static xmpp_stanza_t *_make_starttls(xmpp_conn_t *conn)
@@ -636,7 +716,7 @@ static void _auth(xmpp_conn_t *conn)
636716
return;
637717
}
638718

639-
if (anonjid && conn->sasl_support & SASL_MASK_ANONYMOUS) {
719+
if (anonjid && (conn->sasl_support & SASL_MASK_ANONYMOUS)) {
640720
/* some crap here */
641721
auth = _make_sasl_auth(conn, "ANONYMOUS");
642722
if (!auth) {
@@ -702,22 +782,26 @@ static void _auth(xmpp_conn_t *conn)
702782
"Password hasn't been set, and SASL ANONYMOUS unsupported.");
703783
xmpp_disconnect(conn);
704784
} else if (conn->sasl_support & SASL_MASK_SCRAM) {
785+
size_t n;
705786
scram_ctx = strophe_alloc(conn->ctx, sizeof(*scram_ctx));
706-
if (conn->sasl_support & SASL_MASK_SCRAMSHA512)
707-
scram_ctx->alg = &scram_sha512;
708-
else if (conn->sasl_support & SASL_MASK_SCRAMSHA256)
709-
scram_ctx->alg = &scram_sha256;
710-
else if (conn->sasl_support & SASL_MASK_SCRAMSHA1)
711-
scram_ctx->alg = &scram_sha1;
787+
memset(scram_ctx, 0, sizeof(*scram_ctx));
788+
for (n = 0; n < scram_algs_num; ++n) {
789+
if (conn->sasl_support & scram_algs[n]->mask) {
790+
scram_ctx->alg = scram_algs[n];
791+
break;
792+
}
793+
}
794+
712795
auth = _make_sasl_auth(conn, scram_ctx->alg->scram_name);
713796
if (!auth) {
714797
disconnect_mem_error(conn);
715798
return;
716799
}
717800

718-
/* don't free scram_init on success */
719-
scram_ctx->scram_init = _make_scram_init_msg(conn);
720-
if (!scram_ctx->scram_init) {
801+
scram_ctx->conn = conn;
802+
scram_ctx->sasl_plus =
803+
scram_ctx->alg->mask & SASL_MASK_SCRAM_PLUS ? 1 : 0;
804+
if (_make_scram_init_msg(scram_ctx)) {
721805
strophe_free(conn->ctx, scram_ctx);
722806
xmpp_stanza_release(auth);
723807
disconnect_mem_error(conn);
@@ -753,7 +837,7 @@ static void _auth(xmpp_conn_t *conn)
753837

754838
send_stanza(conn, auth, XMPP_QUEUE_STROPHE);
755839

756-
/* SASL SCRAM-SHA-1 was tried, unset flag */
840+
/* SASL algorithm was tried, unset flag */
757841
conn->sasl_support &= ~scram_ctx->alg->mask;
758842
} else if (conn->sasl_support & SASL_MASK_DIGESTMD5) {
759843
auth = _make_sasl_auth(conn, "DIGEST-MD5");

src/common.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,9 +167,14 @@ struct _xmpp_send_queue_t {
167167
#define SASL_MASK_SCRAMSHA256 (1 << 4)
168168
#define SASL_MASK_SCRAMSHA512 (1 << 5)
169169
#define SASL_MASK_EXTERNAL (1 << 6)
170+
#define SASL_MASK_SCRAMSHA1_PLUS (1 << 7)
171+
#define SASL_MASK_SCRAMSHA256_PLUS (1 << 8)
170172

171-
#define SASL_MASK_SCRAM \
173+
#define SASL_MASK_SCRAM_PLUS \
174+
(SASL_MASK_SCRAMSHA1_PLUS | SASL_MASK_SCRAMSHA256_PLUS)
175+
#define SASL_MASK_SCRAM_WEAK \
172176
(SASL_MASK_SCRAMSHA1 | SASL_MASK_SCRAMSHA256 | SASL_MASK_SCRAMSHA512)
177+
#define SASL_MASK_SCRAM (SASL_MASK_SCRAM_PLUS | SASL_MASK_SCRAM_WEAK)
173178

174179
enum {
175180
XMPP_PORT_CLIENT = 5222,

src/sasl.c

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,7 @@ char *sasl_digest_md5(xmpp_ctx_t *ctx,
375375
/** generate auth response string for the SASL SCRAM mechanism */
376376
char *sasl_scram(xmpp_ctx_t *ctx,
377377
const struct hash_alg *alg,
378+
const char *channel_binding,
378379
const char *challenge,
379380
const char *first_bare,
380381
const char *jid,
@@ -398,6 +399,7 @@ char *sasl_scram(xmpp_ctx_t *ctx,
398399
char *result = NULL;
399400
size_t response_len;
400401
size_t auth_len;
402+
int l;
401403

402404
UNUSED(jid);
403405

@@ -428,37 +430,44 @@ char *sasl_scram(xmpp_ctx_t *ctx,
428430
}
429431
ival = strtol(i, &saveptr, 10);
430432

431-
auth_len = 10 + strlen(r) + strlen(first_bare) + strlen(challenge);
433+
/* "c=<channel_binding>," + r + ",p=" + sign_b64 + '\0' */
434+
response_len = 3 + strlen(channel_binding) + strlen(r) + 3 +
435+
((alg->digest_size + 2) / 3 * 4) + 1;
436+
response = strophe_alloc(ctx, response_len);
437+
if (!response) {
438+
goto out_sval;
439+
}
440+
441+
auth_len = 3 + response_len + strlen(first_bare) + strlen(challenge);
432442
auth = strophe_alloc(ctx, auth_len);
433443
if (!auth) {
434-
goto out_sval;
444+
goto out_response;
435445
}
436446

437-
/* "c=biws," + r + ",p=" + sign_b64 + '\0' */
438-
response_len = 7 + strlen(r) + 3 + ((alg->digest_size + 2) / 3 * 4) + 1;
439-
response = strophe_alloc(ctx, response_len);
440-
if (!response) {
447+
l = strophe_snprintf(response, response_len, "c=%s,%s", channel_binding, r);
448+
if (l < 0 || (size_t)l >= response_len) {
449+
goto out_auth;
450+
}
451+
l = strophe_snprintf(auth, auth_len, "%s,%s,%s", first_bare, challenge,
452+
response);
453+
if (l < 0 || (size_t)l >= auth_len) {
441454
goto out_auth;
442455
}
443-
444-
strophe_snprintf(response, response_len, "c=biws,%s", r);
445-
strophe_snprintf(auth, auth_len, "%s,%s,%s", first_bare + 3, challenge,
446-
response);
447456

448457
SCRAM_ClientKey(alg, (uint8_t *)password, strlen(password), (uint8_t *)sval,
449458
sval_len, (uint32_t)ival, key);
450459
SCRAM_ClientSignature(alg, key, (uint8_t *)auth, strlen(auth), sign);
451-
SCRAM_ClientProof(alg, sign, key, sign);
460+
SCRAM_ClientProof(alg, key, sign, sign);
452461

453462
sign_b64 = xmpp_base64_encode(ctx, sign, alg->digest_size);
454463
if (!sign_b64) {
455-
goto out_response;
464+
goto out_auth;
456465
}
457466

458467
/* Check for buffer overflow */
459468
if (strlen(response) + strlen(sign_b64) + 3 + 1 > response_len) {
460469
strophe_free(ctx, sign_b64);
461-
goto out_response;
470+
goto out_auth;
462471
}
463472
strcat(response, ",p=");
464473
strcat(response, sign_b64);
@@ -467,14 +476,14 @@ char *sasl_scram(xmpp_ctx_t *ctx,
467476
response_b64 =
468477
xmpp_base64_encode(ctx, (unsigned char *)response, strlen(response));
469478
if (!response_b64) {
470-
goto out_response;
479+
goto out_auth;
471480
}
472481
result = response_b64;
473482

474-
out_response:
475-
strophe_free(ctx, response);
476483
out_auth:
477484
strophe_free(ctx, auth);
485+
out_response:
486+
strophe_free(ctx, response);
478487
out_sval:
479488
strophe_free(ctx, sval);
480489
out:

src/sasl.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ char *sasl_digest_md5(xmpp_ctx_t *ctx,
2929
const char *password);
3030
char *sasl_scram(xmpp_ctx_t *ctx,
3131
const struct hash_alg *alg,
32+
const char *channel_binding,
3233
const char *challenge,
3334
const char *first_bare,
3435
const char *jid,

0 commit comments

Comments
 (0)