size_t stun_agent_finish_message (StunAgent *agent, StunMessage *msg, const uint8_t *key, size_t key_len) { uint8_t *ptr; uint32_t fpr; int saved_id_idx = 0; uint8_t md5[16]; if (stun_message_get_class (msg) == STUN_REQUEST) { for (saved_id_idx = 0; saved_id_idx < STUN_AGENT_MAX_SAVED_IDS; saved_id_idx++) { if (agent->sent_ids[saved_id_idx].valid == FALSE) { break; } } } if (saved_id_idx == STUN_AGENT_MAX_SAVED_IDS) { stun_debug ("Saved ids full"); return 0; } if (msg->key != NULL) { key = msg->key; key_len = msg->key_len; } if (key != NULL) { bool skip = FALSE; if (msg->long_term_valid) { memcpy (md5, msg->long_term_key, sizeof(msg->long_term_key)); } else if (agent->usage_flags & STUN_AGENT_USAGE_LONG_TERM_CREDENTIALS) { uint8_t *realm = NULL; uint8_t *username = NULL; uint16_t realm_len; uint16_t username_len; realm = (uint8_t *) stun_message_find (msg, STUN_ATTRIBUTE_REALM, &realm_len); username = (uint8_t *) stun_message_find (msg, STUN_ATTRIBUTE_USERNAME, &username_len); if (username == NULL || realm == NULL) { skip = TRUE; } else { stun_hash_creds (realm, realm_len, username, username_len, key, key_len, md5); } memcpy (msg->long_term_key, md5, sizeof(msg->long_term_key)); msg->long_term_valid = TRUE; } /* If no realm/username and long term credentials, then don't send the message integrity */ if (skip == FALSE) { ptr = stun_message_append (msg, STUN_ATTRIBUTE_MESSAGE_INTEGRITY, 20); if (ptr == NULL) { return 0; } if (agent->usage_flags & STUN_AGENT_USAGE_LONG_TERM_CREDENTIALS) { if (agent->compatibility == STUN_COMPATIBILITY_RFC3489) { stun_sha1 (msg->buffer, stun_message_length (msg), stun_message_length (msg) - 20, ptr, md5, sizeof(md5), TRUE); } else if (agent->compatibility == STUN_COMPATIBILITY_WLM2009) { size_t minus = 20; if (agent->usage_flags & STUN_AGENT_USAGE_USE_FINGERPRINT) minus -= 8; stun_sha1 (msg->buffer, stun_message_length (msg), stun_message_length (msg) - minus, ptr, md5, sizeof(md5), TRUE); } else { stun_sha1 (msg->buffer, stun_message_length (msg), stun_message_length (msg) - 20, ptr, md5, sizeof(md5), FALSE); } } else { if (agent->compatibility == STUN_COMPATIBILITY_RFC3489) { stun_sha1 (msg->buffer, stun_message_length (msg), stun_message_length (msg) - 20, ptr, key, key_len, TRUE); } else if (agent->compatibility == STUN_COMPATIBILITY_WLM2009) { size_t minus = 20; if (agent->usage_flags & STUN_AGENT_USAGE_USE_FINGERPRINT) minus -= 8; stun_sha1 (msg->buffer, stun_message_length (msg), stun_message_length (msg) - minus, ptr, key, key_len, TRUE); } else { stun_sha1 (msg->buffer, stun_message_length (msg), stun_message_length (msg) - 20, ptr, key, key_len, FALSE); } } stun_debug (" Message HMAC-SHA1 message integrity:" "\n key : "); stun_debug_bytes (key, key_len); stun_debug ("\n sent : "); stun_debug_bytes (ptr, 20); stun_debug ("\n"); } } if ((agent->compatibility == STUN_COMPATIBILITY_RFC5389 || agent->compatibility == STUN_COMPATIBILITY_WLM2009) && agent->usage_flags & STUN_AGENT_USAGE_USE_FINGERPRINT) { ptr = stun_message_append (msg, STUN_ATTRIBUTE_FINGERPRINT, 4); if (ptr == NULL) { return 0; } fpr = stun_fingerprint (msg->buffer, stun_message_length (msg), agent->compatibility == STUN_COMPATIBILITY_WLM2009); memcpy (ptr, &fpr, sizeof (fpr)); stun_debug (" Message HMAC-SHA1 fingerprint: "); stun_debug_bytes (ptr, 4); stun_debug ("\n"); } if (stun_message_get_class (msg) == STUN_REQUEST) { stun_message_id (msg, agent->sent_ids[saved_id_idx].id); agent->sent_ids[saved_id_idx].method = stun_message_get_method (msg); agent->sent_ids[saved_id_idx].key = (uint8_t *) key; agent->sent_ids[saved_id_idx].key_len = key_len; memcpy (agent->sent_ids[saved_id_idx].long_term_key, msg->long_term_key, sizeof(msg->long_term_key)); agent->sent_ids[saved_id_idx].long_term_valid = msg->long_term_valid; agent->sent_ids[saved_id_idx].valid = TRUE; } msg->key = (uint8_t *) key; msg->key_len = key_len; return stun_message_length (msg); }
StunValidationStatus stun_agent_validate (StunAgent *agent, StunMessage *msg, const uint8_t *buffer, size_t buffer_len, StunMessageIntegrityValidate validater, void * validater_data) { StunTransactionId msg_id; uint32_t fpr; uint32_t crc32; int len; uint8_t *username = NULL; uint16_t username_len; uint8_t *key = NULL; size_t key_len; uint8_t *hash; uint8_t sha[20]; uint16_t hlen; int sent_id_idx = -1; uint16_t unknown; int error_code; int ignore_credentials = 0; uint8_t long_term_key[16]; bool long_term_key_valid = FALSE; len = stun_message_validate_buffer_length (buffer, buffer_len); if (len == STUN_MESSAGE_BUFFER_INVALID) { return STUN_VALIDATION_NOT_STUN; } else if (len == STUN_MESSAGE_BUFFER_INCOMPLETE) { return STUN_VALIDATION_INCOMPLETE_STUN; } else if (len != (int) buffer_len) { return STUN_VALIDATION_NOT_STUN; } msg->buffer = (uint8_t *) buffer; msg->buffer_len = buffer_len; msg->agent = agent; msg->key = NULL; msg->key_len = 0; msg->long_term_valid = FALSE; /* TODO: reject it or not ? */ if ((agent->compatibility == STUN_COMPATIBILITY_RFC5389 || agent->compatibility == STUN_COMPATIBILITY_WLM2009) && !stun_message_has_cookie (msg)) { stun_debug ("STUN demux error: no cookie!\n"); return STUN_VALIDATION_BAD_REQUEST; } if ((agent->compatibility == STUN_COMPATIBILITY_RFC5389 || agent->compatibility == STUN_COMPATIBILITY_WLM2009) && agent->usage_flags & STUN_AGENT_USAGE_USE_FINGERPRINT) { /* Looks for FINGERPRINT */ if (stun_message_find32 (msg, STUN_ATTRIBUTE_FINGERPRINT, &fpr) != STUN_MESSAGE_RETURN_SUCCESS) { stun_debug ("STUN demux error: no FINGERPRINT attribute!\n"); return STUN_VALIDATION_BAD_REQUEST; } /* Checks FINGERPRINT */ crc32 = stun_fingerprint (msg->buffer, stun_message_length (msg), agent->compatibility == STUN_COMPATIBILITY_WLM2009); fpr = ntohl (fpr); if (fpr != crc32) { stun_debug ("STUN demux error: bad fingerprint: 0x%08x," " expected: 0x%08x!\n", fpr, crc32); return STUN_VALIDATION_BAD_REQUEST; } stun_debug ("STUN demux: OK!\n"); } if (stun_message_get_class (msg) == STUN_RESPONSE || stun_message_get_class (msg) == STUN_ERROR) { stun_message_id (msg, msg_id); for (sent_id_idx = 0; sent_id_idx < STUN_AGENT_MAX_SAVED_IDS; sent_id_idx++) { if (agent->sent_ids[sent_id_idx].valid == TRUE && agent->sent_ids[sent_id_idx].method == stun_message_get_method (msg) && memcmp (msg_id, agent->sent_ids[sent_id_idx].id, sizeof(StunTransactionId)) == 0) { key = agent->sent_ids[sent_id_idx].key; key_len = agent->sent_ids[sent_id_idx].key_len; memcpy (long_term_key, agent->sent_ids[sent_id_idx].long_term_key, sizeof(long_term_key)); long_term_key_valid = agent->sent_ids[sent_id_idx].long_term_valid; break; } } if (sent_id_idx == STUN_AGENT_MAX_SAVED_IDS) { return STUN_VALIDATION_UNMATCHED_RESPONSE; } } ignore_credentials = (agent->usage_flags & STUN_AGENT_USAGE_IGNORE_CREDENTIALS) || (stun_message_get_class (msg) == STUN_ERROR && stun_message_find_error (msg, &error_code) == STUN_MESSAGE_RETURN_SUCCESS && (error_code == 400 || error_code == 401)) || (stun_message_get_class (msg) == STUN_INDICATION && (agent->usage_flags & STUN_AGENT_USAGE_NO_INDICATION_AUTH)); if (key == NULL && ignore_credentials == 0 && (stun_message_get_class (msg) == STUN_REQUEST || stun_message_get_class (msg) == STUN_INDICATION) && (((agent->usage_flags & STUN_AGENT_USAGE_SHORT_TERM_CREDENTIALS) && (!stun_message_has_attribute (msg, STUN_ATTRIBUTE_USERNAME) || !stun_message_has_attribute (msg, STUN_ATTRIBUTE_MESSAGE_INTEGRITY))) || ((agent->usage_flags & STUN_AGENT_USAGE_LONG_TERM_CREDENTIALS) && stun_message_get_class (msg) == STUN_REQUEST && (!stun_message_has_attribute (msg, STUN_ATTRIBUTE_USERNAME) || !stun_message_has_attribute (msg, STUN_ATTRIBUTE_MESSAGE_INTEGRITY) || !stun_message_has_attribute (msg, STUN_ATTRIBUTE_NONCE) || !stun_message_has_attribute (msg, STUN_ATTRIBUTE_REALM))) || ((agent->usage_flags & STUN_AGENT_USAGE_IGNORE_CREDENTIALS) == 0 && stun_message_has_attribute (msg, STUN_ATTRIBUTE_USERNAME) && !stun_message_has_attribute (msg, STUN_ATTRIBUTE_MESSAGE_INTEGRITY)))) { return STUN_VALIDATION_UNAUTHORIZED_BAD_REQUEST; } if (stun_message_has_attribute (msg, STUN_ATTRIBUTE_MESSAGE_INTEGRITY) && ((key == NULL && ignore_credentials == 0) || (agent->usage_flags & STUN_AGENT_USAGE_FORCE_VALIDATER))) { username_len = 0; username = (uint8_t *) stun_message_find (msg, STUN_ATTRIBUTE_USERNAME, &username_len); if (validater == NULL || validater (agent, msg, username, username_len, &key, &key_len, validater_data) == FALSE) { return STUN_VALIDATION_UNAUTHORIZED; } } if (ignore_credentials == 0 && key != NULL && key_len > 0) { hash = (uint8_t *) stun_message_find (msg, STUN_ATTRIBUTE_MESSAGE_INTEGRITY, &hlen); if (hash) { /* We must give the size from start to the end of the attribute because you might have a FINGERPRINT attribute after it... */ if (agent->usage_flags & STUN_AGENT_USAGE_LONG_TERM_CREDENTIALS) { uint8_t *realm = NULL; uint8_t *username = NULL; uint16_t realm_len; uint16_t username_len; uint8_t md5[16]; if (long_term_key_valid) { memcpy (md5, long_term_key, sizeof (md5)); } else { realm = (uint8_t *) stun_message_find (msg, STUN_ATTRIBUTE_REALM, &realm_len); username = (uint8_t *) stun_message_find (msg, STUN_ATTRIBUTE_USERNAME, &username_len); if (username == NULL || realm == NULL) { return STUN_VALIDATION_UNAUTHORIZED; } stun_hash_creds (realm, realm_len, username, username_len, key, key_len, md5); } memcpy (msg->long_term_key, md5, sizeof(md5)); msg->long_term_valid = TRUE; if (agent->compatibility == STUN_COMPATIBILITY_RFC3489) { stun_sha1 (msg->buffer, hash + 20 - msg->buffer, hash - msg->buffer, sha, md5, sizeof(md5), TRUE); } else if (agent->compatibility == STUN_COMPATIBILITY_WLM2009) { stun_sha1 (msg->buffer, hash + 20 - msg->buffer, stun_message_length (msg) - 20, sha, md5, sizeof(md5), TRUE); } else { stun_sha1 (msg->buffer, hash + 20 - msg->buffer, hash - msg->buffer, sha, md5, sizeof(md5), FALSE); } } else { if (agent->compatibility == STUN_COMPATIBILITY_RFC3489) { stun_sha1 (msg->buffer, hash + 20 - msg->buffer, hash - msg->buffer, sha, key, key_len, TRUE); } else if (agent->compatibility == STUN_COMPATIBILITY_WLM2009) { stun_sha1 (msg->buffer, hash + 20 - msg->buffer, stun_message_length (msg) - 20, sha, key, key_len, TRUE); } else { stun_sha1 (msg->buffer, hash + 20 - msg->buffer, hash - msg->buffer, sha, key, key_len, FALSE); } } stun_debug (" Message HMAC-SHA1 fingerprint:"); stun_debug ("\nkey : "); stun_debug_bytes (key, key_len); stun_debug ("\n expected: "); stun_debug_bytes (sha, sizeof (sha)); stun_debug ("\n received: "); stun_debug_bytes (hash, sizeof (sha)); stun_debug ("\n"); if (memcmp (sha, hash, sizeof (sha))) { stun_debug ("STUN auth error: SHA1 fingerprint mismatch!\n"); return STUN_VALIDATION_UNAUTHORIZED; } stun_debug ("STUN auth: OK!\n"); msg->key = key; msg->key_len = key_len; } else if (!(stun_message_get_class (msg) == STUN_ERROR && stun_message_find_error (msg, &error_code) == STUN_MESSAGE_RETURN_SUCCESS && (error_code == 400 || error_code == 401))) { stun_debug ("STUN auth error: No message integrity attribute!\n"); return STUN_VALIDATION_UNAUTHORIZED; } } if (sent_id_idx != -1 && sent_id_idx < STUN_AGENT_MAX_SAVED_IDS) { agent->sent_ids[sent_id_idx].valid = FALSE; } if (stun_agent_find_unknowns (agent, msg, &unknown, 1) > 0) { if (stun_message_get_class (msg) == STUN_REQUEST) return STUN_VALIDATION_UNKNOWN_REQUEST_ATTRIBUTE; else return STUN_VALIDATION_UNKNOWN_ATTRIBUTE; } return STUN_VALIDATION_SUCCESS; }
size_t stun_agent_finish_message (StunAgent *agent, StunMessage *msg, const uint8_t *key, size_t key_len) { uint8_t *ptr; uint32_t fpr; int saved_id_idx = 0; uint8_t md5[16]; bool remember_transaction; remember_transaction = (stun_message_get_class (msg) == STUN_REQUEST); if (agent->compatibility == STUN_COMPATIBILITY_OC2007 && stun_message_get_method (msg) == STUN_SEND) { /* As per [MS-TURN] Section 2.2.1, the TURN server doesn't send responses to * STUN_SEND requests, so don't bother waiting for them. More details at * https://msdn.microsoft.com/en-us/library/dd946797%28v=office.12%29.aspx. */ remember_transaction = FALSE; } if (remember_transaction) { for (saved_id_idx = 0; saved_id_idx < STUN_AGENT_MAX_SAVED_IDS; saved_id_idx++) { if (agent->sent_ids[saved_id_idx].valid == FALSE) { break; } } } if (saved_id_idx == STUN_AGENT_MAX_SAVED_IDS) { stun_debug ("WARNING: Saved IDs full. STUN message dropped."); return 0; } if (msg->key != NULL) { key = msg->key; key_len = msg->key_len; } if (key != NULL) { bool skip = FALSE; if (msg->long_term_valid) { memcpy (md5, msg->long_term_key, sizeof(msg->long_term_key)); } else if (agent->usage_flags & STUN_AGENT_USAGE_LONG_TERM_CREDENTIALS) { uint8_t *realm = NULL; uint8_t *username = NULL; uint16_t realm_len; uint16_t username_len; realm = (uint8_t *) stun_message_find (msg, STUN_ATTRIBUTE_REALM, &realm_len); username = (uint8_t *) stun_message_find (msg, STUN_ATTRIBUTE_USERNAME, &username_len); if (username == NULL || realm == NULL) { skip = TRUE; } else { stun_hash_creds (realm, realm_len, username, username_len, key, key_len, md5); memcpy (msg->long_term_key, md5, sizeof(msg->long_term_key)); msg->long_term_valid = TRUE; } } /* If no realm/username and long term credentials, then don't send the message integrity */ if (skip == FALSE) { ptr = stun_message_append (msg, STUN_ATTRIBUTE_MESSAGE_INTEGRITY, 20); if (ptr == NULL) { return 0; } if (agent->usage_flags & STUN_AGENT_USAGE_LONG_TERM_CREDENTIALS) { if (agent->compatibility == STUN_COMPATIBILITY_RFC3489 || agent->compatibility == STUN_COMPATIBILITY_OC2007) { stun_sha1 (msg->buffer, stun_message_length (msg), stun_message_length (msg) - 20, ptr, md5, sizeof(md5), TRUE); } else if (agent->compatibility == STUN_COMPATIBILITY_WLM2009) { size_t minus = 20; if (agent->usage_flags & STUN_AGENT_USAGE_USE_FINGERPRINT) minus -= 8; stun_sha1 (msg->buffer, stun_message_length (msg), stun_message_length (msg) - minus, ptr, md5, sizeof(md5), TRUE); } else { stun_sha1 (msg->buffer, stun_message_length (msg), stun_message_length (msg) - 20, ptr, md5, sizeof(md5), FALSE); } } else { if (agent->compatibility == STUN_COMPATIBILITY_RFC3489 || agent->compatibility == STUN_COMPATIBILITY_OC2007) { stun_sha1 (msg->buffer, stun_message_length (msg), stun_message_length (msg) - 20, ptr, key, key_len, TRUE); } else if (agent->compatibility == STUN_COMPATIBILITY_WLM2009) { size_t minus = 20; if (agent->usage_flags & STUN_AGENT_USAGE_USE_FINGERPRINT) minus -= 8; stun_sha1 (msg->buffer, stun_message_length (msg), stun_message_length (msg) - minus, ptr, key, key_len, TRUE); } else { stun_sha1 (msg->buffer, stun_message_length (msg), stun_message_length (msg) - 20, ptr, key, key_len, FALSE); } } stun_debug (" Message HMAC-SHA1 message integrity:"); stun_debug_bytes (" key : ", key, key_len); stun_debug_bytes (" sent : ", ptr, 20); } } if ((agent->compatibility == STUN_COMPATIBILITY_RFC5389 || agent->compatibility == STUN_COMPATIBILITY_WLM2009) && agent->usage_flags & STUN_AGENT_USAGE_USE_FINGERPRINT) { ptr = stun_message_append (msg, STUN_ATTRIBUTE_FINGERPRINT, 4); if (ptr == NULL) { return 0; } fpr = stun_fingerprint (msg->buffer, stun_message_length (msg), agent->compatibility == STUN_COMPATIBILITY_WLM2009); memcpy (ptr, &fpr, sizeof (fpr)); stun_debug_bytes (" Message HMAC-SHA1 fingerprint: ", ptr, 4); } if (remember_transaction) { stun_message_id (msg, agent->sent_ids[saved_id_idx].id); agent->sent_ids[saved_id_idx].method = stun_message_get_method (msg); agent->sent_ids[saved_id_idx].key = (uint8_t *) key; agent->sent_ids[saved_id_idx].key_len = key_len; memcpy (agent->sent_ids[saved_id_idx].long_term_key, msg->long_term_key, sizeof(msg->long_term_key)); agent->sent_ids[saved_id_idx].long_term_valid = msg->long_term_valid; agent->sent_ids[saved_id_idx].valid = TRUE; } msg->key = (uint8_t *) key; msg->key_len = key_len; return stun_message_length (msg); }