int sess_auth_test(void) { pj_pool_t *pool; int rc; PJ_LOG(3,(THIS_FILE, " STUN session authentication test")); /* Init STUN config */ pj_stun_config_init(0, &stun_cfg, mem, 0, NULL, NULL); /* Create pool and timer heap */ pool = pj_pool_create(mem, "authtest", 200, 200, NULL); if (pj_timer_heap_create(pool, 20, &stun_cfg.timer_heap)) { pj_pool_release(pool); return -5; } /* Basic retransmission test */ rc = run_client_test("Retransmission", // title PJ_FALSE, // server responding PJ_STUN_AUTH_NONE, // server auth PJ_STUN_AUTH_NONE, // client auth NULL, // realm NULL, // username NULL, // nonce NULL, // password PJ_FALSE, // dummy MI PJ_TRUE, // expected error PJNATH_ESTUNTIMEDOUT,// expected code NULL, // expected realm NULL, // expected nonce &retransmit_check // more check ); if (rc != 0) { goto done; } /* * Short term credential. * draft-ietf-behave-rfc3489bis-15#section-10.1.2 */ /* * If the message does not contain both a MESSAGE-INTEGRITY and a * USERNAME attribute, If the message is a request, the server MUST * reject the request with an error response. This response MUST * use an error code of 400 (Bad Request). */ rc = run_client_test("Missing MESSAGE-INTEGRITY (short term)", // title PJ_TRUE, // server responding PJ_STUN_AUTH_SHORT_TERM, // server auth PJ_STUN_AUTH_NONE, // client auth NULL, // realm NULL, // username NULL, // nonce NULL, // password PJ_FALSE, // dummy MI PJ_TRUE, // expected error PJ_STATUS_FROM_STUN_CODE(400),// expected code NULL, // expected realm NULL, // expected nonce NULL // more check ); if (rc != 0) { goto done; } /* If the USERNAME does not contain a username value currently valid * within the server: If the message is a request, the server MUST * reject the request with an error response. This response MUST use * an error code of 401 (Unauthorized). */ rc = run_client_test("USERNAME mismatch (short term)", // title PJ_TRUE, // server responding PJ_STUN_AUTH_SHORT_TERM, // server auth PJ_STUN_AUTH_SHORT_TERM, // client auth NULL, // realm "anotheruser", // username NULL, // nonce "anotherpass", // password PJ_FALSE, // dummy MI PJ_TRUE, // expected error PJ_STATUS_FROM_STUN_CODE(401),// expected code NULL, // expected realm NULL, // expected nonce NULL // more check ); if (rc != 0) { goto done; } /* Using the password associated with the username, compute the value * for the message-integrity as described in Section 15.4. If the * resulting value does not match the contents of the MESSAGE- * INTEGRITY attribute: * * - If the message is a request, the server MUST reject the request * with an error response. This response MUST use an error code * of 401 (Unauthorized). */ rc = run_client_test("MESSAGE-INTEGRITY mismatch (short term)", // title PJ_TRUE, // server responding PJ_STUN_AUTH_SHORT_TERM, // server auth PJ_STUN_AUTH_SHORT_TERM, // client auth NULL, // realm USERNAME, // username NULL, // nonce "anotherpass", // password PJ_FALSE, // dummy MI PJ_TRUE, // expected error PJ_STATUS_FROM_STUN_CODE(401),// expected code NULL, // expected realm NULL, // expected nonce NULL // more check ); if (rc != 0) { goto done; } /* USERNAME is not present, server must respond with 400 (Bad * Request). */ rc = run_client_test("Missing USERNAME (short term)",// title PJ_TRUE, // server responding PJ_STUN_AUTH_SHORT_TERM, // server auth PJ_STUN_AUTH_NONE, // client auth NULL, // realm NULL, // username NULL, // nonce NULL, // password PJ_TRUE, // dummy MI PJ_TRUE, // expected error PJ_STATUS_FROM_STUN_CODE(400), // expected code NULL, // expected realm NULL, // expected nonce NULL // more check ); if (rc != 0) { goto done; } /* Successful short term authentication */ rc = run_client_test("Successful scenario (short term)", // title PJ_TRUE, // server responding PJ_STUN_AUTH_SHORT_TERM, // server auth PJ_STUN_AUTH_SHORT_TERM, // client auth NULL, // realm USERNAME, // username NULL, // nonce PASSWORD, // password PJ_FALSE, // dummy MI PJ_FALSE, // expected error PJ_SUCCESS, // expected code NULL, // expected realm NULL, // expected nonce NULL // more check ); if (rc != 0) { goto done; } /* * (our own) Extended tests for long term credential */ /* When server wants to use short term credential, but request has * REALM, reject with .... 401 ??? */ rc = run_client_test("Unwanted REALM (short term)", // title PJ_TRUE, // server responding PJ_STUN_AUTH_SHORT_TERM, // server auth PJ_STUN_AUTH_NONE, // client auth REALM, // realm USERNAME, // username NULL, // nonce PASSWORD, // password PJ_TRUE, // dummy MI PJ_TRUE, // expected error PJ_STATUS_FROM_STUN_CODE(401), // expected code NULL, // expected realm NULL, // expected nonce &long_term_check2 // more check ); if (rc != 0) { goto done; } /* * Long term credential. * draft-ietf-behave-rfc3489bis-15#section-10.2.2 */ /* If the message does not contain a MESSAGE-INTEGRITY attribute, the * server MUST generate an error response with an error code of 401 * (Unauthorized). This response MUST include a REALM value. It is * RECOMMENDED that the REALM value be the domain name of the * provider of the STUN server. The response MUST include a NONCE, * selected by the server. The response SHOULD NOT contain a * USERNAME or MESSAGE-INTEGRITY attribute. */ rc = run_client_test("Missing M-I (long term)", // title PJ_TRUE, // server responding PJ_STUN_AUTH_LONG_TERM, // server auth PJ_STUN_AUTH_NONE, // client auth NULL, // client realm NULL, // client username NULL, // client nonce NULL, // client password PJ_FALSE, // client dummy MI PJ_TRUE, // expected error PJ_STATUS_FROM_STUN_CODE(401), // expected code REALM, // expected realm NONCE, // expected nonce &long_term_check1 // more check ); if (rc != 0) { goto done; } /* If the message contains a MESSAGE-INTEGRITY attribute, but is * missing the USERNAME, REALM or NONCE attributes, the server MUST * generate an error response with an error code of 400 (Bad * Request). This response SHOULD NOT include a USERNAME, NONCE, * REALM or MESSAGE-INTEGRITY attribute. */ /* Missing USERNAME */ rc = run_client_test("Missing USERNAME (long term)", // title PJ_TRUE, // server responding PJ_STUN_AUTH_LONG_TERM, // server auth PJ_STUN_AUTH_NONE, // client auth REALM, // client realm NULL, // client username NONCE, // client nonce PASSWORD, // client password PJ_TRUE, // client dummy MI PJ_TRUE, // expected error PJ_STATUS_FROM_STUN_CODE(400), // expected code NULL, // expected realm NULL, // expected nonce &long_term_check2 // more check ); if (rc != 0) { goto done; } /* Missing REALM */ rc = run_client_test("Missing REALM (long term)", // title PJ_TRUE, // server responding PJ_STUN_AUTH_LONG_TERM, // server auth PJ_STUN_AUTH_NONE, // client auth NULL, // client realm USERNAME, // client username NONCE, // client nonce PASSWORD, // client password PJ_TRUE, // client dummy MI PJ_TRUE, // expected error PJ_STATUS_FROM_STUN_CODE(400), // expected code NULL, // expected realm NULL, // expected nonce &long_term_check2 // more check ); if (rc != 0) { goto done; } /* Missing NONCE */ rc = run_client_test("Missing NONCE (long term)", // title PJ_TRUE, // server responding PJ_STUN_AUTH_LONG_TERM, // server auth PJ_STUN_AUTH_NONE, // client auth REALM, // client realm USERNAME, // client username NULL, // client nonce PASSWORD, // client password PJ_TRUE, // client dummy MI PJ_TRUE, // expected error PJ_STATUS_FROM_STUN_CODE(400), // expected code NULL, // expected realm NULL, // expected nonce &long_term_check2 // more check ); if (rc != 0) { goto done; } /* If the NONCE is no longer valid, the server MUST generate an error * response with an error code of 438 (Stale Nonce). This response * MUST include a NONCE and REALM attribute and SHOULD NOT incude the * USERNAME or MESSAGE-INTEGRITY attribute. Servers can invalidate * nonces in order to provide additional security. See Section 4.3 * of [RFC2617] for guidelines. */ // how?? /* If the username in the USERNAME attribute is not valid, the server * MUST generate an error response with an error code of 401 * (Unauthorized). This response MUST include a REALM value. It is * RECOMMENDED that the REALM value be the domain name of the * provider of the STUN server. The response MUST include a NONCE, * selected by the server. The response SHOULD NOT contain a * USERNAME or MESSAGE-INTEGRITY attribute. */ rc = run_client_test("Invalid username (long term)", // title PJ_TRUE, // server responding PJ_STUN_AUTH_LONG_TERM, // server auth PJ_STUN_AUTH_LONG_TERM, // client auth REALM, // client realm "anotheruser", // client username "a nonce", // client nonce "somepassword", // client password PJ_FALSE, // client dummy MI PJ_TRUE, // expected error PJ_STATUS_FROM_STUN_CODE(401), // expected code REALM, // expected realm NONCE, // expected nonce &long_term_check1 // more check ); if (rc != 0) { goto done; } /* Successful long term authentication */ rc = run_client_test("Successful scenario (long term)", // title PJ_TRUE, // server responding PJ_STUN_AUTH_LONG_TERM, // server auth PJ_STUN_AUTH_LONG_TERM, // client auth REALM, // client realm USERNAME, // client username "anothernonce", // client nonce PASSWORD, // client password PJ_FALSE, // client dummy MI PJ_FALSE, // expected error 0, // expected code NULL, // expected realm NULL, // expected nonce &long_term_check3 // more check ); if (rc != 0) { goto done; } /* * (our own) Extended tests for long term credential */ /* If REALM doesn't match, server must respond with 401 */ #if 0 // STUN session now will just use the realm sent in the // response, so this test will fail because it will // authenticate successfully. rc = run_client_test("Invalid REALM (long term)", // title PJ_TRUE, // server responding PJ_STUN_AUTH_LONG_TERM, // server auth PJ_STUN_AUTH_LONG_TERM, // client auth "anotherrealm", // client realm USERNAME, // client username NONCE, // client nonce PASSWORD, // client password PJ_FALSE, // client dummy MI PJ_TRUE, // expected error PJ_STATUS_FROM_STUN_CODE(401), // expected code REALM, // expected realm NONCE, // expected nonce &long_term_check1 // more check ); if (rc != 0) { goto done; } #endif /* Invalid HMAC */ /* Valid static short term, without NONCE */ /* Valid static short term, WITH NONCE */ /* Valid static long term (with NONCE */ /* Valid dynamic short term (without NONCE) */ /* Valid dynamic short term (with NONCE) */ /* Valid dynamic long term (with NONCE) */ done: pj_timer_heap_destroy(stun_cfg.timer_heap); pj_pool_release(pool); return rc; }
static int run_client_test(const char *title, pj_bool_t server_responding, pj_stun_auth_type server_auth_type, pj_stun_auth_type client_auth_type, const char *realm, const char *username, const char *nonce, const char *password, pj_bool_t dummy_mi, pj_bool_t use_ipv6, pj_bool_t expected_error, pj_status_t expected_code, const char *expected_realm, const char *expected_nonce, int (*more_check)(void)) { pj_pool_t *pool; pj_stun_session_cb sess_cb; pj_stun_auth_cred cred; pj_stun_tx_data *tdata; pj_status_t status; pj_sockaddr addr; int rc = 0; PJ_LOG(3,(THIS_FILE, " %s test (%s)", title, use_ipv6?"IPv6":"IPv4")); /* Create client */ pool = pj_pool_create(mem, "client", 1000, 1000, NULL); client = PJ_POOL_ZALLOC_T(pool, struct client); client->pool = pool; client->responding = PJ_TRUE; /* Create STUN session */ pj_bzero(&sess_cb, sizeof(sess_cb)); sess_cb.on_request_complete = &client_on_request_complete; sess_cb.on_send_msg = &client_send_msg; status = pj_stun_session_create(&stun_cfg, "client", &sess_cb, PJ_FALSE, NULL, &client->sess); if (status != PJ_SUCCESS) { destroy_client_server(); return -200; } /* Create semaphore */ status = pj_sem_create(pool, "client", 0, 1, &client->test_complete); if (status != PJ_SUCCESS) { destroy_client_server(); return -205; } /* Create client socket */ status = pj_sock_socket(GET_AF(use_ipv6), pj_SOCK_DGRAM(), 0, &client->sock); if (status != PJ_SUCCESS) { destroy_client_server(); return -210; } /* Bind client socket */ pj_sockaddr_init(GET_AF(use_ipv6), &addr, NULL, 0); status = pj_sock_bind(client->sock, &addr, pj_sockaddr_get_len(&addr)); if (status != PJ_SUCCESS) { destroy_client_server(); return -220; } /* Create client thread */ status = pj_thread_create(pool, "client", &client_thread, NULL, 0, 0, &client->thread); if (status != PJ_SUCCESS) { destroy_client_server(); return -230; } /* Initialize credential */ pj_bzero(&cred, sizeof(cred)); cred.type = PJ_STUN_AUTH_CRED_STATIC; if (realm) cred.data.static_cred.realm = pj_str((char*)realm); if (username) cred.data.static_cred.username = pj_str((char*)username); if (nonce) cred.data.static_cred.nonce = pj_str((char*)nonce); if (password) cred.data.static_cred.data = pj_str((char*)password); cred.data.static_cred.data_type = PJ_STUN_PASSWD_PLAIN; status = pj_stun_session_set_credential(client->sess, client_auth_type, &cred); if (status != PJ_SUCCESS) { destroy_client_server(); return -240; } /* Create the server */ status = create_std_server(server_auth_type, server_responding, use_ipv6); if (status != 0) { destroy_client_server(); return status; } /* Create request */ status = pj_stun_session_create_req(client->sess, PJ_STUN_BINDING_REQUEST, PJ_STUN_MAGIC, NULL, &tdata); if (status != PJ_SUCCESS) { destroy_client_server(); return -250; } /* Add our own attributes if client authentication is set to none */ if (client_auth_type == PJ_STUN_AUTH_NONE) { pj_str_t tmp; if (realm) pj_stun_msg_add_string_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_REALM, pj_cstr(&tmp, realm)); if (username) pj_stun_msg_add_string_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_USERNAME, pj_cstr(&tmp, username)); if (nonce) pj_stun_msg_add_string_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_NONCE, pj_cstr(&tmp, nonce)); if (password) { // ignored } if (dummy_mi) { pj_stun_msgint_attr *mi; pj_stun_msgint_attr_create(tdata->pool, &mi); pj_stun_msg_add_attr(tdata->msg, &mi->hdr); } } /* Send the request */ status = pj_stun_session_send_msg(client->sess, NULL, PJ_FALSE, PJ_TRUE, &server->addr, pj_sockaddr_get_len(&server->addr), tdata); if (status != PJ_SUCCESS) { destroy_client_server(); return -270; } /* Wait until test complete */ pj_sem_wait(client->test_complete); /* Verify response */ if (expected_error) { if (expected_code != client->response_status) { char e1[PJ_ERR_MSG_SIZE], e2[PJ_ERR_MSG_SIZE]; pj_strerror(expected_code, e1, sizeof(e1)); pj_strerror(client->response_status, e2, sizeof(e2)); PJ_LOG(3,(THIS_FILE, " err: expecting %d (%s) but got %d (%s) response", expected_code, e1, client->response_status, e2)); rc = -500; } } else { int res_code = 0; pj_stun_realm_attr *arealm; pj_stun_nonce_attr *anonce; if (client->response_status != 0) { PJ_LOG(3,(THIS_FILE, " err: expecting successful operation but got error %d", client->response_status)); rc = -600; goto done; } if (PJ_STUN_IS_ERROR_RESPONSE(client->response->hdr.type)) { pj_stun_errcode_attr *aerr = NULL; aerr = (pj_stun_errcode_attr*) pj_stun_msg_find_attr(client->response, PJ_STUN_ATTR_ERROR_CODE, 0); if (aerr == NULL) { PJ_LOG(3,(THIS_FILE, " err: received error response without ERROR-CODE")); rc = -610; goto done; } res_code = aerr->err_code; } else { res_code = 0; } /* Check that code matches */ if (expected_code != res_code) { PJ_LOG(3,(THIS_FILE, " err: expecting response code %d but got %d", expected_code, res_code)); rc = -620; goto done; } /* Find REALM and NONCE attributes */ arealm = (pj_stun_realm_attr*) pj_stun_msg_find_attr(client->response, PJ_STUN_ATTR_REALM, 0); anonce = (pj_stun_nonce_attr*) pj_stun_msg_find_attr(client->response, PJ_STUN_ATTR_NONCE, 0); if (expected_realm) { if (arealm == NULL) { PJ_LOG(3,(THIS_FILE, " err: expecting REALM in esponse")); rc = -630; goto done; } if (pj_strcmp2(&arealm->value, expected_realm)!=0) { PJ_LOG(3,(THIS_FILE, " err: REALM mismatch in response")); rc = -640; goto done; } } else { if (arealm != NULL) { PJ_LOG(3,(THIS_FILE, " err: non expecting REALM in response")); rc = -650; goto done; } } if (expected_nonce) { if (anonce == NULL) { PJ_LOG(3,(THIS_FILE, " err: expecting NONCE in esponse")); rc = -660; goto done; } if (pj_strcmp2(&anonce->value, expected_nonce)!=0) { PJ_LOG(3,(THIS_FILE, " err: NONCE mismatch in response")); rc = -670; goto done; } } else { if (anonce != NULL) { PJ_LOG(3,(THIS_FILE, " err: non expecting NONCE in response")); rc = -680; goto done; } } } /* Our tests are okay so far. Let caller do some more tests if * it wants to. */ if (rc==0 && more_check) { rc = (*more_check)(); } done: destroy_client_server(); /* If IPv6 is enabled, test again for IPv4. */ if ((rc == 0) && use_ipv6) { rc = run_client_test(title, server_responding, server_auth_type, client_auth_type, realm, username, nonce, password, dummy_mi, 0, expected_error, expected_code, expected_realm, expected_nonce, more_check); } return rc; }