Skip to content

Commit 914f63b

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

File tree

11 files changed

+358
-59
lines changed

11 files changed

+358
-59
lines changed

src/auth.c

Lines changed: 115 additions & 37 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,97 @@ 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+
char *node, *message, *binding_type;
543+
size_t message_len, binding_type_len = 0, binding_data_len;
544+
int l, is_secured = xmpp_conn_is_secured(conn);
545+
char buf[64];
546+
547+
if (scram->sasl_plus) {
548+
if (!is_secured) {
549+
strophe_error(
550+
ctx, "xmpp",
551+
"SASL: Server requested a -PLUS variant to authenticate, "
552+
"but the connection is not secured. This is an error on "
553+
"the server side we can't do anything about.");
554+
return -1;
555+
}
556+
if (tls_init_channel_binding(conn->tls, &binding_type,
557+
&binding_type_len)) {
558+
return -1;
559+
}
560+
/* directly account for the '=' char in 'p=<binding-type>' */
561+
binding_type_len += 1;
562+
}
533563

534564
node = xmpp_jid_node(ctx, conn->jid);
535565
if (!node) {
536-
return NULL;
566+
return -1;
537567
}
538-
xmpp_rand_nonce(ctx->rand, nonce, sizeof(nonce));
539-
message_len = strlen(node) + strlen(nonce) + 8 + 1;
568+
xmpp_rand_nonce(ctx->rand, buf, sizeof(buf));
569+
message_len = strlen(node) + strlen(buf) + 8 + binding_type_len + 1;
540570
message = strophe_alloc(ctx, message_len);
541571
if (message) {
542-
strophe_snprintf(message, message_len, "n,,n=%s,r=%s", node, nonce);
572+
/* increase length to account for 'y,,', 'n,,' or 'p,,'.
573+
* In the 'p' case the '=' sign has already been accounted for above.
574+
*/
575+
binding_type_len += 3;
576+
if (scram->sasl_plus) {
577+
l = strophe_snprintf(message, message_len, "p=%s,,n=%s,r=%s",
578+
binding_type, node, buf);
579+
} else {
580+
l = strophe_snprintf(message, message_len, "%c,,n=%s,r=%s",
581+
is_secured ? 'y' : 'n', node, buf);
582+
}
583+
if (l < 0 || (size_t)l >= message_len) {
584+
goto err_out;
585+
} else {
586+
/* Make `first_bare` point to the 'n' of the client-first-message */
587+
scram->first_bare = message + binding_type_len;
588+
memcpy(buf, message, binding_type_len);
589+
if (scram->sasl_plus) {
590+
binding_data =
591+
tls_get_channel_binding_data(conn->tls, &binding_data_len);
592+
if (!binding_data) {
593+
goto err_out;
594+
}
595+
if (binding_data_len > sizeof(buf) - binding_type_len) {
596+
strophe_error(ctx, "xmpp",
597+
"Channel binding data is too long (%zu)",
598+
binding_data_len);
599+
goto err_out;
600+
}
601+
memcpy(&buf[binding_type_len], binding_data, binding_data_len);
602+
binding_type_len += binding_data_len;
603+
}
604+
if (scram->channel_binding)
605+
strophe_free(ctx, scram->channel_binding);
606+
scram->channel_binding =
607+
xmpp_base64_encode(ctx, (void *)buf, binding_type_len);
608+
memset(buf, 0, binding_type_len);
609+
}
543610
}
544611
strophe_free(ctx, node);
612+
scram->scram_init = message;
545613

546-
return message;
614+
return message == NULL ? -1 : 0;
615+
err_out:
616+
strophe_free(ctx, node);
617+
strophe_free(ctx, message);
618+
scram->first_bare = NULL;
619+
scram->scram_init = NULL;
620+
return -1;
547621
}
548622

549623
static xmpp_stanza_t *_make_starttls(xmpp_conn_t *conn)
@@ -636,7 +710,7 @@ static void _auth(xmpp_conn_t *conn)
636710
return;
637711
}
638712

639-
if (anonjid && conn->sasl_support & SASL_MASK_ANONYMOUS) {
713+
if (anonjid && (conn->sasl_support & SASL_MASK_ANONYMOUS)) {
640714
/* some crap here */
641715
auth = _make_sasl_auth(conn, "ANONYMOUS");
642716
if (!auth) {
@@ -702,22 +776,26 @@ static void _auth(xmpp_conn_t *conn)
702776
"Password hasn't been set, and SASL ANONYMOUS unsupported.");
703777
xmpp_disconnect(conn);
704778
} else if (conn->sasl_support & SASL_MASK_SCRAM) {
779+
size_t n;
705780
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;
781+
memset(scram_ctx, 0, sizeof(*scram_ctx));
782+
for (n = 0; n < scram_algs_num; ++n) {
783+
if (conn->sasl_support & scram_algs[n]->mask) {
784+
scram_ctx->alg = scram_algs[n];
785+
break;
786+
}
787+
}
788+
712789
auth = _make_sasl_auth(conn, scram_ctx->alg->scram_name);
713790
if (!auth) {
714791
disconnect_mem_error(conn);
715792
return;
716793
}
717794

718-
/* don't free scram_init on success */
719-
scram_ctx->scram_init = _make_scram_init_msg(conn);
720-
if (!scram_ctx->scram_init) {
795+
scram_ctx->conn = conn;
796+
scram_ctx->sasl_plus =
797+
scram_ctx->alg->mask & SASL_MASK_SCRAM_PLUS ? 1 : 0;
798+
if (_make_scram_init_msg(scram_ctx)) {
721799
strophe_free(conn->ctx, scram_ctx);
722800
xmpp_stanza_release(auth);
723801
disconnect_mem_error(conn);
@@ -753,7 +831,7 @@ static void _auth(xmpp_conn_t *conn)
753831

754832
send_stanza(conn, auth, XMPP_QUEUE_STROPHE);
755833

756-
/* SASL SCRAM-SHA-1 was tried, unset flag */
834+
/* SASL algorithm was tried, unset flag */
757835
conn->sasl_support &= ~scram_ctx->alg->mask;
758836
} else if (conn->sasl_support & SASL_MASK_DIGESTMD5) {
759837
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)