int main(int argc, char *argv[]) { xmpp_ctx_t *ctx; unsigned char *udec; char *dec; char *enc; size_t len; int i; printf("BASE64 tests.\n"); ctx = xmpp_ctx_new(NULL, NULL); if (ctx == NULL) { fprintf(stderr, "failed to create context\n"); return 1; } for (i = 0; i < ARRAY_SIZE(tests); ++i) { printf("Test #%d: ", (int)i + 1); enc = xmpp_base64_encode(ctx, (unsigned char *)tests[i].raw, strlen(tests[i].raw)); assert(enc != NULL); COMPARE(tests[i].base64, enc); xmpp_free(ctx, enc); dec = xmpp_base64_decode_str(ctx, tests[i].base64, strlen(tests[i].base64)); assert(dec != NULL); COMPARE_BUF(tests[i].raw, strlen(tests[i].raw), dec, strlen(dec)); xmpp_free(ctx, dec); printf("ok\n"); } printf("Test with binary data: "); enc = xmpp_base64_encode(ctx, bin_data, sizeof(bin_data)); assert(enc != NULL); xmpp_base64_decode_bin(ctx, enc, strlen(enc), &udec, &len); assert(udec != NULL); assert(len != 0); assert(len == sizeof(bin_data)); COMPARE_BUF(bin_data, sizeof(bin_data), udec, len); xmpp_free(ctx, udec); xmpp_free(ctx, enc); printf("ok\n"); xmpp_ctx_free(ctx); return 0; }
/** generate authentication string for the SASL PLAIN mechanism */ char *sasl_plain(xmpp_ctx_t *ctx, const char *authid, const char *password) { size_t idlen, passlen; size_t msglen; char *result = NULL; char *msg; /* our message is Base64(authzid,\0,authid,\0,password) if there is no authzid, that field is left empty */ idlen = strlen(authid); passlen = strlen(password); msglen = 2 + idlen + passlen; msg = xmpp_alloc(ctx, msglen); if (msg != NULL) { msg[0] = '\0'; memcpy(msg+1, authid, idlen); msg[1+idlen] = '\0'; memcpy(msg+1+idlen+1, password, passlen); result = xmpp_base64_encode(ctx, (unsigned char *)msg, msglen); xmpp_free(ctx, msg); } return result; }
/** generate auth response string for the SASL SCRAM-SHA-1 mechanism */ char *sasl_scram_sha1(xmpp_ctx_t *ctx, const char *challenge, const char *first_bare, const char *jid, const char *password) { uint8_t key[SHA1_DIGEST_SIZE]; uint8_t sign[SHA1_DIGEST_SIZE]; char *r = NULL; char *s = NULL; char *i = NULL; unsigned char *sval; size_t sval_len; long ival; char *tmp; char *ptr; char *saveptr = NULL; char *response; char *auth; char *response_b64; char *sign_b64; char *result = NULL; size_t response_len; size_t auth_len; int j; tmp = xmpp_strdup(ctx, challenge); if (!tmp) { return NULL; } ptr = strtok_r(tmp, ",", &saveptr); while (ptr) { if (strncmp(ptr, "r=", 2) == 0) { r = ptr; } else if (strncmp(ptr, "s=", 2) == 0) { s = ptr + 2; } else if (strncmp(ptr, "i=", 2) == 0) { i = ptr + 2; } ptr = strtok_r(NULL, ",", &saveptr); } if (!r || !s || !i) { goto out; } xmpp_base64_decode_bin(ctx, s, strlen(s), &sval, &sval_len); if (!sval) { goto out; } ival = strtol(i, &saveptr, 10); auth_len = 10 + strlen(r) + strlen(first_bare) + strlen(challenge); auth = xmpp_alloc(ctx, auth_len); if (!auth) { goto out_sval; } response_len = 39 + strlen(r); response = xmpp_alloc(ctx, response_len); if (!response) { goto out_auth; } xmpp_snprintf(response, response_len, "c=biws,%s", r); xmpp_snprintf(auth, auth_len, "%s,%s,%s", first_bare + 3, challenge, response); SCRAM_SHA1_ClientKey((uint8_t *)password, strlen(password), (uint8_t *)sval, sval_len, (uint32_t)ival, key); SCRAM_SHA1_ClientSignature(key, (uint8_t *)auth, strlen(auth), sign); for (j = 0; j < SHA1_DIGEST_SIZE; j++) { sign[j] ^= key[j]; } sign_b64 = xmpp_base64_encode(ctx, sign, sizeof(sign)); if (!sign_b64) { goto out_response; } if (strlen(response) + strlen(sign_b64) + 3 + 1 > response_len) { xmpp_free(ctx, sign_b64); goto out_response; } strcat(response, ",p="); strcat(response, sign_b64); xmpp_free(ctx, sign_b64); response_b64 = xmpp_base64_encode(ctx, (unsigned char *)response, strlen(response)); if (!response_b64) { goto out_response; } result = response_b64; out_response: xmpp_free(ctx, response); out_auth: xmpp_free(ctx, auth); out_sval: xmpp_free(ctx, sval); out: xmpp_free(ctx, tmp); return result; }
/** generate auth response string for the SASL DIGEST-MD5 mechanism */ char *sasl_digest_md5(xmpp_ctx_t *ctx, const char *challenge, const char *jid, const char *password) { hash_t *table; char *result = NULL; char *node, *domain, *realm; char *value; char *response; int rlen; struct MD5Context MD5; unsigned char digest[16], HA1[16], HA2[16]; char hex[32]; char cnonce[13]; /* our digest response is Hex( KD( HEX(MD5(A1)), nonce ':' nc ':' cnonce ':' qop ':' HEX(MD5(A2)) )) where KD(k, s) = MD5(k ':' s), A1 = MD5( node ':' realm ':' password ) ':' nonce ':' cnonce A2 = "AUTHENTICATE" ':' "xmpp/" domain If there is an authzid it is ':'-appended to A1 */ /* parse the challenge */ table = _parse_digest_challenge(ctx, challenge); if (table == NULL) { xmpp_error(ctx, "SASL", "couldn't parse digest challenge"); return NULL; } node = xmpp_jid_node(ctx, jid); domain = xmpp_jid_domain(ctx, jid); /* generate default realm of domain if one didn't come from the server */ realm = hash_get(table, "realm"); if (realm == NULL || strlen(realm) == 0) { hash_add(table, "realm", xmpp_strdup(ctx, domain)); realm = hash_get(table, "realm"); } /* add our response fields */ hash_add(table, "username", xmpp_strdup(ctx, node)); xmpp_rand_nonce(ctx->rand, cnonce, sizeof(cnonce)); hash_add(table, "cnonce", xmpp_strdup(ctx, cnonce)); hash_add(table, "nc", xmpp_strdup(ctx, "00000001")); hash_add(table, "qop", xmpp_strdup(ctx, "auth")); value = xmpp_alloc(ctx, 5 + strlen(domain) + 1); memcpy(value, "xmpp/", 5); memcpy(value+5, domain, strlen(domain)); value[5+strlen(domain)] = '\0'; hash_add(table, "digest-uri", value); /* generate response */ /* construct MD5(node : realm : password) */ MD5Init(&MD5); MD5Update(&MD5, (unsigned char *)node, strlen(node)); MD5Update(&MD5, (unsigned char *)":", 1); MD5Update(&MD5, (unsigned char *)realm, strlen(realm)); MD5Update(&MD5, (unsigned char *)":", 1); MD5Update(&MD5, (unsigned char *)password, strlen(password)); MD5Final(digest, &MD5); /* digest now contains the first field of A1 */ MD5Init(&MD5); MD5Update(&MD5, digest, 16); MD5Update(&MD5, (unsigned char *)":", 1); value = hash_get(table, "nonce"); MD5Update(&MD5, (unsigned char *)value, strlen(value)); MD5Update(&MD5, (unsigned char *)":", 1); value = hash_get(table, "cnonce"); MD5Update(&MD5, (unsigned char *)value, strlen(value)); MD5Final(digest, &MD5); /* now digest is MD5(A1) */ memcpy(HA1, digest, 16); /* construct MD5(A2) */ MD5Init(&MD5); MD5Update(&MD5, (unsigned char *)"AUTHENTICATE:", 13); value = hash_get(table, "digest-uri"); MD5Update(&MD5, (unsigned char *)value, strlen(value)); if (strcmp(hash_get(table, "qop"), "auth") != 0) { MD5Update(&MD5, (unsigned char *)":00000000000000000000000000000000", 33); } MD5Final(digest, &MD5); memcpy(HA2, digest, 16); /* construct response */ MD5Init(&MD5); _digest_to_hex((char *)HA1, hex); MD5Update(&MD5, (unsigned char *)hex, 32); MD5Update(&MD5, (unsigned char *)":", 1); value = hash_get(table, "nonce"); MD5Update(&MD5, (unsigned char *)value, strlen(value)); MD5Update(&MD5, (unsigned char *)":", 1); value = hash_get(table, "nc"); MD5Update(&MD5, (unsigned char *)value, strlen(value)); MD5Update(&MD5, (unsigned char *)":", 1); value = hash_get(table, "cnonce"); MD5Update(&MD5, (unsigned char *)value, strlen(value)); MD5Update(&MD5, (unsigned char *)":", 1); value = hash_get(table, "qop"); MD5Update(&MD5, (unsigned char *)value, strlen(value)); MD5Update(&MD5, (unsigned char *)":", 1); _digest_to_hex((char *)HA2, hex); MD5Update(&MD5, (unsigned char *)hex, 32); MD5Final(digest, &MD5); response = xmpp_alloc(ctx, 32+1); _digest_to_hex((char *)digest, hex); memcpy(response, hex, 32); response[32] = '\0'; hash_add(table, "response", response); /* construct reply */ result = NULL; rlen = 0; result = _add_key(ctx, table, "username", result, &rlen, 1); result = _add_key(ctx, table, "realm", result, &rlen, 1); result = _add_key(ctx, table, "nonce", result, &rlen, 1); result = _add_key(ctx, table, "cnonce", result, &rlen, 1); result = _add_key(ctx, table, "nc", result, &rlen, 0); result = _add_key(ctx, table, "qop", result, &rlen, 0); result = _add_key(ctx, table, "digest-uri", result, &rlen, 1); result = _add_key(ctx, table, "response", result, &rlen, 0); result = _add_key(ctx, table, "charset", result, &rlen, 0); xmpp_free(ctx, node); xmpp_free(ctx, domain); hash_release(table); /* also frees value strings */ /* reuse response for the base64 encode of our result */ response = xmpp_base64_encode(ctx, (unsigned char *)result, strlen(result)); xmpp_free(ctx, result); return response; }
/* authenticate the connection * this may get called multiple times. if any auth method fails, * this will get called again until one auth method succeeds or every * method fails */ static void _auth(xmpp_conn_t * const conn) { xmpp_stanza_t *auth, *authdata, *query, *child, *iq; char *str, *authid; char *scram_init; int anonjid; /* if there is no node in conn->jid, we assume anonymous connect */ str = xmpp_jid_node(conn->ctx, conn->jid); if (str == NULL) { anonjid = 1; } else { xmpp_free(conn->ctx, str); anonjid = 0; } if (conn->tls_support) { tls_t *tls = tls_new(conn->ctx, conn->sock, conn->certfail_handler, conn->tls_cert_path); /* If we couldn't init tls, it isn't there, so go on */ if (!tls) { conn->tls_support = 0; _auth(conn); return; } else { tls_free(tls); } auth = _make_starttls(conn); if (!auth) { disconnect_mem_error(conn); return; } handler_add(conn, _handle_proceedtls_default, XMPP_NS_TLS, NULL, NULL, NULL); xmpp_send(conn, auth); xmpp_stanza_release(auth); /* TLS was tried, unset flag */ conn->tls_support = 0; /* _auth() will be called later */ return; } if (conn->tls_mandatory && !xmpp_conn_is_secured(conn)) { xmpp_error(conn->ctx, "xmpp", "TLS is not supported, but set as " "mandatory for this connection"); conn_disconnect(conn); return; } if (anonjid && conn->sasl_support & SASL_MASK_ANONYMOUS) { /* some crap here */ auth = _make_sasl_auth(conn, "ANONYMOUS"); if (!auth) { disconnect_mem_error(conn); return; } handler_add(conn, _handle_sasl_result, XMPP_NS_SASL, NULL, NULL, "ANONYMOUS"); xmpp_send(conn, auth); xmpp_stanza_release(auth); /* SASL ANONYMOUS was tried, unset flag */ conn->sasl_support &= ~SASL_MASK_ANONYMOUS; } else if (anonjid) { xmpp_error(conn->ctx, "auth", "No node in JID, and SASL ANONYMOUS unsupported."); xmpp_disconnect(conn); } else if (conn->sasl_support & SASL_MASK_SCRAMSHA1) { auth = _make_sasl_auth(conn, "SCRAM-SHA-1"); if (!auth) { disconnect_mem_error(conn); return; } /* don't free scram_init on success */ scram_init = _make_scram_sha1_init_msg(conn); if (!scram_init) { xmpp_stanza_release(auth); disconnect_mem_error(conn); return; } str = xmpp_base64_encode(conn->ctx, (unsigned char *)scram_init, strlen(scram_init)); if (!str) { xmpp_free(conn->ctx, scram_init); xmpp_stanza_release(auth); disconnect_mem_error(conn); return; } authdata = xmpp_stanza_new(conn->ctx); if (!authdata) { xmpp_free(conn->ctx, str); xmpp_free(conn->ctx, scram_init); xmpp_stanza_release(auth); disconnect_mem_error(conn); return; } xmpp_stanza_set_text(authdata, str); xmpp_free(conn->ctx, str); xmpp_stanza_add_child(auth, authdata); xmpp_stanza_release(authdata); handler_add(conn, _handle_scram_sha1_challenge, XMPP_NS_SASL, NULL, NULL, (void *)scram_init); xmpp_send(conn, auth); xmpp_stanza_release(auth); /* SASL SCRAM-SHA-1 was tried, unset flag */ conn->sasl_support &= ~SASL_MASK_SCRAMSHA1; } else if (conn->sasl_support & SASL_MASK_DIGESTMD5) { auth = _make_sasl_auth(conn, "DIGEST-MD5"); if (!auth) { disconnect_mem_error(conn); return; } handler_add(conn, _handle_digestmd5_challenge, XMPP_NS_SASL, NULL, NULL, NULL); xmpp_send(conn, auth); xmpp_stanza_release(auth); /* SASL DIGEST-MD5 was tried, unset flag */ conn->sasl_support &= ~SASL_MASK_DIGESTMD5; } else if (conn->sasl_support & SASL_MASK_PLAIN) { auth = _make_sasl_auth(conn, "PLAIN"); if (!auth) { disconnect_mem_error(conn); return; } authdata = xmpp_stanza_new(conn->ctx); if (!authdata) { disconnect_mem_error(conn); return; } authid = _get_authid(conn); if (!authid) { disconnect_mem_error(conn); return; } str = sasl_plain(conn->ctx, authid, conn->pass); if (!str) { disconnect_mem_error(conn); return; } xmpp_stanza_set_text(authdata, str); xmpp_free(conn->ctx, str); xmpp_free(conn->ctx, authid); xmpp_stanza_add_child(auth, authdata); xmpp_stanza_release(authdata); handler_add(conn, _handle_sasl_result, XMPP_NS_SASL, NULL, NULL, "PLAIN"); xmpp_send(conn, auth); xmpp_stanza_release(auth); /* SASL PLAIN was tried */ conn->sasl_support &= ~SASL_MASK_PLAIN; } else if (conn->type == XMPP_CLIENT) { /* legacy client authentication */ iq = xmpp_stanza_new(conn->ctx); if (!iq) { disconnect_mem_error(conn); return; } xmpp_stanza_set_name(iq, "iq"); xmpp_stanza_set_type(iq, "set"); xmpp_stanza_set_id(iq, "_xmpp_auth1"); query = xmpp_stanza_new(conn->ctx); if (!query) { xmpp_stanza_release(iq); disconnect_mem_error(conn); return; } xmpp_stanza_set_name(query, "query"); xmpp_stanza_set_ns(query, XMPP_NS_AUTH); xmpp_stanza_add_child(iq, query); xmpp_stanza_release(query); child = xmpp_stanza_new(conn->ctx); if (!child) { xmpp_stanza_release(iq); disconnect_mem_error(conn); return; } xmpp_stanza_set_name(child, "username"); xmpp_stanza_add_child(query, child); xmpp_stanza_release(child); authdata = xmpp_stanza_new(conn->ctx); if (!authdata) { xmpp_stanza_release(iq); disconnect_mem_error(conn); return; } str = xmpp_jid_node(conn->ctx, conn->jid); xmpp_stanza_set_text(authdata, str); xmpp_free(conn->ctx, str); xmpp_stanza_add_child(child, authdata); xmpp_stanza_release(authdata); child = xmpp_stanza_new(conn->ctx); if (!child) { xmpp_stanza_release(iq); disconnect_mem_error(conn); return; } xmpp_stanza_set_name(child, "password"); xmpp_stanza_add_child(query, child); xmpp_stanza_release(child); authdata = xmpp_stanza_new(conn->ctx); if (!authdata) { xmpp_stanza_release(iq); disconnect_mem_error(conn); return; } xmpp_stanza_set_text(authdata, conn->pass); xmpp_stanza_add_child(child, authdata); xmpp_stanza_release(authdata); child = xmpp_stanza_new(conn->ctx); if (!child) { xmpp_stanza_release(iq); disconnect_mem_error(conn); return; } xmpp_stanza_set_name(child, "resource"); xmpp_stanza_add_child(query, child); xmpp_stanza_release(child); authdata = xmpp_stanza_new(conn->ctx); if (!authdata) { xmpp_stanza_release(iq); disconnect_mem_error(conn); return; } str = xmpp_jid_resource(conn->ctx, conn->jid); if (str) { xmpp_stanza_set_text(authdata, str); xmpp_free(conn->ctx, str); } else { xmpp_stanza_release(authdata); xmpp_stanza_release(iq); xmpp_error(conn->ctx, "auth", "Cannot authenticate without resource"); xmpp_disconnect(conn); return; } xmpp_stanza_add_child(child, authdata); xmpp_stanza_release(authdata); handler_add_id(conn, _handle_legacy, "_xmpp_auth1", NULL); handler_add_timed(conn, _handle_missing_legacy, LEGACY_TIMEOUT, NULL); xmpp_send(conn, iq); xmpp_stanza_release(iq); } }