Skip to content

Commit d4fa39f

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

File tree

11 files changed

+309
-43
lines changed

11 files changed

+309
-43
lines changed

src/auth.c

Lines changed: 106 additions & 23 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);
@@ -250,8 +251,12 @@ _handle_features(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata)
250251
conn->sasl_support |= SASL_MASK_EXTERNAL;
251252
else if (strcasecmp(text, "DIGEST-MD5") == 0)
252253
conn->sasl_support |= SASL_MASK_DIGESTMD5;
254+
else if (strcasecmp(text, "SCRAM-SHA-1-PLUS") == 0)
255+
conn->sasl_support |= SASL_MASK_SCRAMSHA1_PLUS;
253256
else if (strcasecmp(text, "SCRAM-SHA-1") == 0)
254257
conn->sasl_support |= SASL_MASK_SCRAMSHA1;
258+
else if (strcasecmp(text, "SCRAM-SHA-256-PLUS") == 0)
259+
conn->sasl_support |= SASL_MASK_SCRAMSHA256_PLUS;
255260
else if (strcasecmp(text, "SCRAM-SHA-256") == 0)
256261
conn->sasl_support |= SASL_MASK_SCRAMSHA256;
257262
else if (strcasecmp(text, "SCRAM-SHA-512") == 0)
@@ -439,7 +444,11 @@ static int _handle_digestmd5_rspauth(xmpp_conn_t *conn,
439444
}
440445

441446
struct scram_user_data {
447+
xmpp_conn_t *conn;
448+
int sasl_plus;
442449
char *scram_init;
450+
char *channel_binding;
451+
const char *first_bare;
443452
const struct hash_alg *alg;
444453
};
445454

@@ -471,8 +480,9 @@ static int _handle_scram_challenge(xmpp_conn_t *conn,
471480
if (!challenge)
472481
goto err;
473482

474-
response = sasl_scram(conn->ctx, scram_ctx->alg, challenge,
475-
scram_ctx->scram_init, conn->jid, conn->pass);
483+
response =
484+
sasl_scram(conn->ctx, scram_ctx->alg, scram_ctx->channel_binding,
485+
challenge, scram_ctx->first_bare, conn->jid, conn->pass);
476486
strophe_free(conn->ctx, challenge);
477487
if (!response)
478488
goto err;
@@ -506,7 +516,8 @@ static int _handle_scram_challenge(xmpp_conn_t *conn,
506516
*/
507517
rc = _handle_sasl_result(conn, stanza,
508518
(void *)scram_ctx->alg->scram_name);
509-
strophe_free(conn->ctx, scram_ctx->scram_init);
519+
strophe_free_and_null(conn->ctx, scram_ctx->channel_binding);
520+
strophe_free_and_null(conn->ctx, scram_ctx->scram_init);
510521
strophe_free(conn->ctx, scram_ctx);
511522
}
512523

@@ -517,33 +528,97 @@ static int _handle_scram_challenge(xmpp_conn_t *conn,
517528
err_free_response:
518529
strophe_free(conn->ctx, response);
519530
err:
520-
strophe_free(conn->ctx, scram_ctx->scram_init);
531+
strophe_free_and_null(conn->ctx, scram_ctx->channel_binding);
532+
strophe_free_and_null(conn->ctx, scram_ctx->scram_init);
521533
strophe_free(conn->ctx, scram_ctx);
522534
disconnect_mem_error(conn);
523535
return 0;
524536
}
525537

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

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

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

549624
static xmpp_stanza_t *_make_starttls(xmpp_conn_t *conn)
@@ -636,7 +711,7 @@ static void _auth(xmpp_conn_t *conn)
636711
return;
637712
}
638713

639-
if (anonjid && conn->sasl_support & SASL_MASK_ANONYMOUS) {
714+
if (anonjid && (conn->sasl_support & SASL_MASK_ANONYMOUS)) {
640715
/* some crap here */
641716
auth = _make_sasl_auth(conn, "ANONYMOUS");
642717
if (!auth) {
@@ -703,21 +778,29 @@ static void _auth(xmpp_conn_t *conn)
703778
xmpp_disconnect(conn);
704779
} else if (conn->sasl_support & SASL_MASK_SCRAM) {
705780
scram_ctx = strophe_alloc(conn->ctx, sizeof(*scram_ctx));
706-
if (conn->sasl_support & SASL_MASK_SCRAMSHA512)
781+
memset(scram_ctx, 0, sizeof(*scram_ctx));
782+
if (conn->sasl_support & SASL_MASK_SCRAMSHA256_PLUS) {
783+
scram_ctx->alg = &scram_sha256_plus;
784+
} else if (conn->sasl_support & SASL_MASK_SCRAMSHA1_PLUS) {
785+
scram_ctx->alg = &scram_sha1_plus;
786+
} else if (conn->sasl_support & SASL_MASK_SCRAMSHA512) {
707787
scram_ctx->alg = &scram_sha512;
708-
else if (conn->sasl_support & SASL_MASK_SCRAMSHA256)
788+
} else if (conn->sasl_support & SASL_MASK_SCRAMSHA256) {
709789
scram_ctx->alg = &scram_sha256;
710-
else if (conn->sasl_support & SASL_MASK_SCRAMSHA1)
790+
} else if (conn->sasl_support & SASL_MASK_SCRAMSHA1) {
711791
scram_ctx->alg = &scram_sha1;
792+
}
793+
712794
auth = _make_sasl_auth(conn, scram_ctx->alg->scram_name);
713795
if (!auth) {
714796
disconnect_mem_error(conn);
715797
return;
716798
}
717799

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

754837
send_stanza(conn, auth, XMPP_QUEUE_STROPHE);
755838

756-
/* SASL SCRAM-SHA-1 was tried, unset flag */
839+
/* SASL algorithm was tried, unset flag */
757840
conn->sasl_support &= ~scram_ctx->alg->mask;
758841
} else if (conn->sasl_support & SASL_MASK_DIGESTMD5) {
759842
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: 26 additions & 17 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);
432-
auth = strophe_alloc(ctx, auth_len);
433-
if (!auth) {
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) {
434438
goto out_sval;
435439
}
436440

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) {
441-
goto out_auth;
441+
auth_len = 3 + response_len + strlen(first_bare) + strlen(challenge);
442+
auth = strophe_alloc(ctx, auth_len);
443+
if (!auth) {
444+
goto out_response;
442445
}
443446

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);
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_response;
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) {
454+
goto out_response;
455+
}
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,

src/scram.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,15 @@ const struct hash_alg scram_sha1 = {
4242
(void (*)(void *, const uint8_t *, size_t))crypto_SHA1_Update,
4343
(void (*)(void *, uint8_t *))crypto_SHA1_Final};
4444

45+
const struct hash_alg scram_sha1_plus = {
46+
"SCRAM-SHA-1-PLUS",
47+
SASL_MASK_SCRAMSHA1_PLUS,
48+
SHA1_DIGEST_SIZE,
49+
(void (*)(const uint8_t *, size_t, uint8_t *))crypto_SHA1,
50+
(void (*)(void *))crypto_SHA1_Init,
51+
(void (*)(void *, const uint8_t *, size_t))crypto_SHA1_Update,
52+
(void (*)(void *, uint8_t *))crypto_SHA1_Final};
53+
4554
const struct hash_alg scram_sha256 = {
4655
"SCRAM-SHA-256",
4756
SASL_MASK_SCRAMSHA256,
@@ -51,6 +60,15 @@ const struct hash_alg scram_sha256 = {
5160
(void (*)(void *, const uint8_t *, size_t))sha256_process,
5261
(void (*)(void *, uint8_t *))sha256_done};
5362

63+
const struct hash_alg scram_sha256_plus = {
64+
"SCRAM-SHA-256-PLUS",
65+
SASL_MASK_SCRAMSHA256_PLUS,
66+
SHA256_DIGEST_SIZE,
67+
(void (*)(const uint8_t *, size_t, uint8_t *))sha256_hash,
68+
(void (*)(void *))sha256_init,
69+
(void (*)(void *, const uint8_t *, size_t))sha256_process,
70+
(void (*)(void *, uint8_t *))sha256_done};
71+
5472
const struct hash_alg scram_sha512 = {
5573
"SCRAM-SHA-512",
5674
SASL_MASK_SCRAMSHA512,

src/scram.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ struct hash_alg {
3535
};
3636

3737
extern const struct hash_alg scram_sha1;
38+
extern const struct hash_alg scram_sha1_plus;
3839
extern const struct hash_alg scram_sha256;
40+
extern const struct hash_alg scram_sha256_plus;
3941
extern const struct hash_alg scram_sha512;
4042

4143
void SCRAM_ClientKey(const struct hash_alg *alg,

src/tls.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ unsigned int tls_id_on_xmppaddr_num(xmpp_conn_t *conn);
4545

4646
xmpp_tlscert_t *tls_peer_cert(xmpp_conn_t *conn);
4747
int tls_set_credentials(tls_t *tls, const char *cafilename);
48+
int tls_init_channel_binding(tls_t *tls,
49+
char **binding_prefix,
50+
size_t *binding_prefix_len);
51+
void *tls_get_channel_binding_data(tls_t *tls, size_t *size);
4852

4953
int tls_start(tls_t *tls);
5054
int tls_stop(tls_t *tls);

0 commit comments

Comments
 (0)