/* * Copyright 2004 The WebRTC Project Authors. All rights reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree.
*/
// This HMAC differs from the RFC 5769 SampleRequest message. This differs // because spec uses 0x20 for the padding where as our implementation uses 0. staticconst uint8_t kCalculatedHmac1[] = {
0x79, 0x07, 0xc2, 0xd2, // }
0xed, 0xbf, 0xea, 0x48, // }
0x0e, 0x4c, 0x76, 0xd8, // } HMAC-SHA1 fingerprint
0x29, 0x62, 0xd5, 0xc3, // }
0x74, 0x2a, 0xf9, 0xe3 // }
};
// This truncated HMAC differs from kCalculatedHmac1 // above since the sum is computed including header // and the header is different since the message is shorter // than when MESSAGE-INTEGRITY is used. staticconst uint8_t kCalculatedHmac1_32[] = {
0xda, 0x39, 0xde, 0x5d, // }
};
// Length parameter is changed to 0x1c from 0x3c. // AddMessageIntegrity will add MI information and update the length param // accordingly. staticconst uint8_t kRfc5769SampleResponseWithoutMI[] = {
0x01, 0x01, 0x00, 0x1c, // Response type and message length
0x21, 0x12, 0xa4, 0x42, // Magic cookie
0xb7, 0xe7, 0xa7, 0x01, // }
0xbc, 0x34, 0xd6, 0x86, // } Transaction ID
0xfa, 0x87, 0xdf, 0xae, // }
0x80, 0x22, 0x00, 0x0b, // SOFTWARE attribute header
0x74, 0x65, 0x73, 0x74, // }
0x20, 0x76, 0x65, 0x63, // } UTF-8 server name
0x74, 0x6f, 0x72, 0x20, // }
0x00, 0x20, 0x00, 0x08, // XOR-MAPPED-ADDRESS attribute header
0x00, 0x01, 0xa1, 0x47, // Address family (IPv4) and xor'd mapped port
0xe1, 0x12, 0xa6, 0x43 // Xor'd mapped IPv4 address
};
// This HMAC differs from the RFC 5769 SampleResponse message. This differs // because spec uses 0x20 for the padding where as our implementation uses 0. staticconst uint8_t kCalculatedHmac2[] = {
0x5d, 0x6b, 0x58, 0xbe, // }
0xad, 0x94, 0xe0, 0x7e, // }
0xef, 0x0d, 0xfc, 0x12, // } HMAC-SHA1 fingerprint
0x82, 0xa2, 0xbd, 0x08, // }
0x43, 0x14, 0x10, 0x28 // }
};
// This truncated HMAC differs from kCalculatedHmac2 // above since the sum is computed including header // and the header is different since the message is shorter // than when MESSAGE-INTEGRITY is used. staticconst uint8_t kCalculatedHmac2_32[] = {
0xe7, 0x5c, 0xd3, 0x16, // }
};
// clang-format on
// A transaction ID without the 'magic cookie' portion // pjnat's test programs use this transaction ID a lot. const uint8_t kTestTransactionId1[] = {0x029, 0x01f, 0x0cd, 0x07c,
0x0ba, 0x058, 0x0ab, 0x0d7,
0x0f2, 0x041, 0x001, 0x000};
// They use this one sometimes too. const uint8_t kTestTransactionId2[] = {0x0e3, 0x0a9, 0x046, 0x0e1,
0x07c, 0x000, 0x0c2, 0x062,
0x054, 0x008, 0x001, 0x000};
#define ReadStunMessage(X, Y) ReadStunMessageTestCase(X, Y, sizeof(Y));
// Test that the GetStun*Type and IsStun*Type methods work as expected.
TEST_F(StunTest, MessageTypes) {
EXPECT_EQ(STUN_BINDING_RESPONSE,
GetStunSuccessResponseType(STUN_BINDING_REQUEST));
EXPECT_EQ(STUN_BINDING_ERROR_RESPONSE,
GetStunErrorResponseType(STUN_BINDING_REQUEST));
EXPECT_EQ(-1, GetStunSuccessResponseType(STUN_BINDING_INDICATION));
EXPECT_EQ(-1, GetStunSuccessResponseType(STUN_BINDING_RESPONSE));
EXPECT_EQ(-1, GetStunSuccessResponseType(STUN_BINDING_ERROR_RESPONSE));
EXPECT_EQ(-1, GetStunErrorResponseType(STUN_BINDING_INDICATION));
EXPECT_EQ(-1, GetStunErrorResponseType(STUN_BINDING_RESPONSE));
EXPECT_EQ(-1, GetStunErrorResponseType(STUN_BINDING_ERROR_RESPONSE));
int types[] = {STUN_BINDING_REQUEST, STUN_BINDING_INDICATION,
STUN_BINDING_RESPONSE, STUN_BINDING_ERROR_RESPONSE}; for (size_t i = 0; i < arraysize(types); ++i) {
EXPECT_EQ(i == 0U, IsStunRequestType(types[i]));
EXPECT_EQ(i == 1U, IsStunIndicationType(types[i]));
EXPECT_EQ(i == 2U, IsStunSuccessResponseType(types[i]));
EXPECT_EQ(i == 3U, IsStunErrorResponseType(types[i]));
EXPECT_EQ(1, types[i] & 0xFEEF);
}
}
// Actual M-I value checked in a later test.
ASSERT_TRUE(msg.GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) != NULL);
// Fingerprint checked in a later test, but double-check the value here. const StunUInt32Attribute* fingerprint = msg.GetUInt32(STUN_ATTR_FINGERPRINT);
ASSERT_TRUE(fingerprint != NULL);
EXPECT_EQ(0xe57a3bcf, fingerprint->value());
}
// Read the RFC5389 fields from the RFC5769 sample STUN response.
TEST_F(StunTest, ReadRfc5769ResponseMessage) {
StunMessage msg;
size_t size = ReadStunMessage(&msg, kRfc5769SampleResponse);
CheckStunHeader(msg, STUN_BINDING_RESPONSE, size);
CheckStunTransactionID(msg, kRfc5769SampleMsgTransactionId,
kStunTransactionIdLength);
// Actual M-I and fingerprint checked in later tests.
ASSERT_TRUE(msg.GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) != NULL);
ASSERT_TRUE(msg.GetUInt32(STUN_ATTR_FINGERPRINT) != NULL);
}
// Read the RFC5389 fields from the RFC5769 sample STUN response for IPv6.
TEST_F(StunTest, ReadRfc5769ResponseMessageIPv6) {
StunMessage msg;
size_t size = ReadStunMessage(&msg, kRfc5769SampleResponseIPv6);
CheckStunHeader(msg, STUN_BINDING_RESPONSE, size);
CheckStunTransactionID(msg, kRfc5769SampleMsgTransactionId,
kStunTransactionIdLength);
// Actual M-I and fingerprint checked in later tests.
ASSERT_TRUE(msg.GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) != NULL);
ASSERT_TRUE(msg.GetUInt32(STUN_ATTR_FINGERPRINT) != NULL);
}
// Read the RFC5389 fields from the RFC5769 sample STUN response with auth.
TEST_F(StunTest, ReadRfc5769RequestMessageLongTermAuth) {
StunMessage msg;
size_t size = ReadStunMessage(&msg, kRfc5769SampleRequestLongTermAuth);
CheckStunHeader(msg, STUN_BINDING_REQUEST, size);
CheckStunTransactionID(msg, kRfc5769SampleMsgWithAuthTransactionId,
kStunTransactionIdLength);
// No fingerprint, actual M-I checked in later tests.
ASSERT_TRUE(msg.GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) != NULL);
ASSERT_TRUE(msg.GetUInt32(STUN_ATTR_FINGERPRINT) == NULL);
}
// The RFC3489 packet in this test is the same as // kStunMessageWithIPv4MappedAddress, but with a different value where the // magic cookie was.
TEST_F(StunTest, ReadLegacyMessage) {
uint8_t rfc3489_packet[sizeof(kStunMessageWithIPv4MappedAddress)];
memcpy(rfc3489_packet, kStunMessageWithIPv4MappedAddress, sizeof(kStunMessageWithIPv4MappedAddress)); // Overwrite the magic cookie here.
memcpy(&rfc3489_packet[4], "ABCD", 4);
// Owner with a different transaction ID.
StunMessage msg2(STUN_INVALID_MESSAGE_TYPE, "ABCDABCDABCD");
StunXorAddressAttribute addr2(STUN_ATTR_XOR_MAPPED_ADDRESS, 20, NULL);
addr2.SetIP(addr->ipaddr());
addr2.SetPort(addr->port());
addr2.SetOwner(&msg2); // The internal IP address shouldn't change.
ASSERT_EQ(addr2.ipaddr(), addr->ipaddr());
rtc::ByteBufferWriter correct_buf;
rtc::ByteBufferWriter wrong_buf;
EXPECT_TRUE(addr->Write(&correct_buf));
EXPECT_TRUE(addr2.Write(&wrong_buf)); // But when written out, the buffers should look different.
ASSERT_NE(0,
memcmp(correct_buf.Data(), wrong_buf.Data(), wrong_buf.Length())); // And when reading a known good value, the address should be wrong.
rtc::ByteBufferReader read_buf(correct_buf);
addr2.Read(&read_buf);
ASSERT_NE(addr->ipaddr(), addr2.ipaddr());
addr2.SetIP(addr->ipaddr());
addr2.SetPort(addr->port()); // Try writing with no owner at all, should fail and write nothing.
addr2.SetOwner(NULL);
ASSERT_EQ(addr2.ipaddr(), addr->ipaddr());
wrong_buf.Clear();
EXPECT_FALSE(addr2.Write(&wrong_buf));
ASSERT_EQ(0U, wrong_buf.Length());
}
TEST_F(StunTest, SetIPv4XorAddressAttributeOwner) { // Unlike the IPv6XorAddressAttributeOwner test, IPv4 XOR address attributes // should _not_ be affected by a change in owner. IPv4 XOR address uses the // magic cookie value which is fixed.
StunMessage msg;
size_t size = ReadStunMessage(&msg, kStunMessageWithIPv4XorMappedAddress);
// Owner with a different transaction ID.
StunMessage msg2(STUN_INVALID_MESSAGE_TYPE, "ABCDABCDABCD");
StunXorAddressAttribute addr2(STUN_ATTR_XOR_MAPPED_ADDRESS, 20, NULL);
addr2.SetIP(addr->ipaddr());
addr2.SetPort(addr->port());
addr2.SetOwner(&msg2); // The internal IP address shouldn't change.
ASSERT_EQ(addr2.ipaddr(), addr->ipaddr());
rtc::ByteBufferWriter correct_buf;
rtc::ByteBufferWriter wrong_buf;
EXPECT_TRUE(addr->Write(&correct_buf));
EXPECT_TRUE(addr2.Write(&wrong_buf)); // The same address data should be written.
ASSERT_EQ(0,
memcmp(correct_buf.Data(), wrong_buf.Data(), wrong_buf.Length())); // And an attribute should be able to un-XOR an address belonging to a message // with a different transaction ID.
rtc::ByteBufferReader read_buf(correct_buf);
EXPECT_TRUE(addr2.Read(&read_buf));
ASSERT_EQ(addr->ipaddr(), addr2.ipaddr());
// However, no owner is still an error, should fail and write nothing.
addr2.SetOwner(NULL);
ASSERT_EQ(addr2.ipaddr(), addr->ipaddr());
wrong_buf.Clear();
EXPECT_FALSE(addr2.Write(&wrong_buf));
}
// Test that we don't care what order we set the parts of an address
TEST_F(StunTest, CreateAddressInArbitraryOrder) { auto addr = StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS); // Port first
addr->SetPort(kTestMessagePort1);
addr->SetIP(rtc::IPAddress(kIPv4TestAddress1));
ASSERT_EQ(kTestMessagePort1, addr->port());
ASSERT_EQ(rtc::IPAddress(kIPv4TestAddress1), addr->ipaddr());
auto addr2 = StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS); // IP first
addr2->SetIP(rtc::IPAddress(kIPv4TestAddress1));
addr2->SetPort(kTestMessagePort2);
ASSERT_EQ(kTestMessagePort2, addr2->port());
ASSERT_EQ(rtc::IPAddress(kIPv4TestAddress1), addr2->ipaddr());
}
// Test that GetErrorCodeValue returns STUN_ERROR_GLOBAL_FAILURE if the message // in question doesn't have an error code attribute, rather than crashing.
TEST_F(StunTest, GetErrorCodeValueWithNoErrorAttribute) {
StunMessage msg;
ReadStunMessage(&msg, kStunMessageWithIPv6MappedAddress);
EXPECT_EQ(STUN_ERROR_GLOBAL_FAILURE, msg.GetErrorCodeValue());
}
// Parsing should have succeeded and there should be a USERNAME attribute const StunByteStringAttribute* username =
msg.GetByteString(STUN_ATTR_USERNAME);
ASSERT_TRUE(username != NULL);
EXPECT_EQ(kTestUserName2, username->string_view());
}
rtc::ByteBufferWriter out;
EXPECT_TRUE(msg.Write(&out));
ASSERT_EQ(size, out.Length()); // Check everything up to the padding.
ASSERT_EQ(0,
memcmp(out.Data(), kStunMessageWithUInt16ListAttribute, size - 2));
}
// Test that we fail to read messages with invalid lengths. void CheckFailureToRead(const uint8_t* testcase, size_t length) {
StunMessage msg;
rtc::ByteBufferReader buf(rtc::MakeArrayView(testcase, length));
ASSERT_FALSE(msg.Read(&buf));
}
// Test that we properly fail to read a non-STUN message.
TEST_F(StunTest, FailToReadRtcpPacket) {
CheckFailureToRead(kRtcpPacket, sizeof(kRtcpPacket));
}
// Check our STUN message validation code against the RFC5769 test messages.
TEST_F(StunTest, ValidateMessageIntegrity) { // Try the messages from RFC 5769.
EXPECT_TRUE(StunMessage::ValidateMessageIntegrityForTesting( reinterpret_cast<constchar*>(kRfc5769SampleRequest), sizeof(kRfc5769SampleRequest), kRfc5769SampleMsgPassword));
EXPECT_FALSE(StunMessage::ValidateMessageIntegrityForTesting( reinterpret_cast<constchar*>(kRfc5769SampleRequest), sizeof(kRfc5769SampleRequest), "InvalidPassword"));
// We first need to compute the key for the long-term authentication HMAC.
std::string key;
ComputeStunCredentialHash(kRfc5769SampleMsgWithAuthUsername,
kRfc5769SampleMsgWithAuthRealm,
kRfc5769SampleMsgWithAuthPassword, &key);
EXPECT_TRUE(StunMessage::ValidateMessageIntegrityForTesting( reinterpret_cast<constchar*>(kRfc5769SampleRequestLongTermAuth), sizeof(kRfc5769SampleRequestLongTermAuth), key));
EXPECT_FALSE(StunMessage::ValidateMessageIntegrityForTesting( reinterpret_cast<constchar*>(kRfc5769SampleRequestLongTermAuth), sizeof(kRfc5769SampleRequestLongTermAuth), "InvalidPassword"));
// Again, but with the lengths matching what is claimed in the headers.
EXPECT_FALSE(StunMessage::ValidateMessageIntegrityForTesting( reinterpret_cast<constchar*>(kStunMessageWithZeroLength),
kStunHeaderSize + rtc::GetBE16(&kStunMessageWithZeroLength[2]),
kRfc5769SampleMsgPassword));
EXPECT_FALSE(StunMessage::ValidateMessageIntegrityForTesting( reinterpret_cast<constchar*>(kStunMessageWithExcessLength),
kStunHeaderSize + rtc::GetBE16(&kStunMessageWithExcessLength[2]),
kRfc5769SampleMsgPassword));
EXPECT_FALSE(StunMessage::ValidateMessageIntegrityForTesting( reinterpret_cast<constchar*>(kStunMessageWithSmallLength),
kStunHeaderSize + rtc::GetBE16(&kStunMessageWithSmallLength[2]),
kRfc5769SampleMsgPassword));
// Check that a too-short HMAC doesn't cause buffer overflow.
EXPECT_FALSE(StunMessage::ValidateMessageIntegrityForTesting( reinterpret_cast<constchar*>(kStunMessageWithBadHmacAtEnd), sizeof(kStunMessageWithBadHmacAtEnd), kRfc5769SampleMsgPassword));
// Test that munging a single bit anywhere in the message causes the // message-integrity check to fail, unless it is after the M-I attribute. char buf[sizeof(kRfc5769SampleRequest)];
memcpy(buf, kRfc5769SampleRequest, sizeof(kRfc5769SampleRequest)); for (size_t i = 0; i < sizeof(buf); ++i) {
buf[i] ^= 0x01; if (i > 0)
buf[i - 1] ^= 0x01;
EXPECT_EQ(i >= sizeof(buf) - 8,
StunMessage::ValidateMessageIntegrityForTesting(
buf, sizeof(buf), kRfc5769SampleMsgPassword));
}
}
// Validate that we generate correct MESSAGE-INTEGRITY attributes. // Note the use of IceMessage instead of StunMessage; this is necessary because // the RFC5769 test messages used include attributes not found in basic STUN.
TEST_F(StunTest, AddMessageIntegrity) {
IceMessage msg;
rtc::ByteBufferReader buf(kRfc5769SampleRequestWithoutMI);
EXPECT_TRUE(msg.Read(&buf));
EXPECT_TRUE(msg.AddMessageIntegrity(kRfc5769SampleMsgPassword)); const StunByteStringAttribute* mi_attr =
msg.GetByteString(STUN_ATTR_MESSAGE_INTEGRITY);
EXPECT_EQ(20U, mi_attr->length());
EXPECT_EQ(0, memcmp(mi_attr->array_view().data(), kCalculatedHmac1, sizeof(kCalculatedHmac1)));
// Again, but with the lengths matching what is claimed in the headers.
EXPECT_FALSE(StunMessage::ValidateMessageIntegrity32ForTesting( reinterpret_cast<constchar*>(kStunMessageWithZeroLength),
kStunHeaderSize + rtc::GetBE16(&kStunMessageWithZeroLength[2]),
kRfc5769SampleMsgPassword));
EXPECT_FALSE(StunMessage::ValidateMessageIntegrity32ForTesting( reinterpret_cast<constchar*>(kStunMessageWithExcessLength),
kStunHeaderSize + rtc::GetBE16(&kStunMessageWithExcessLength[2]),
kRfc5769SampleMsgPassword));
EXPECT_FALSE(StunMessage::ValidateMessageIntegrity32ForTesting( reinterpret_cast<constchar*>(kStunMessageWithSmallLength),
kStunHeaderSize + rtc::GetBE16(&kStunMessageWithSmallLength[2]),
kRfc5769SampleMsgPassword));
// Check that a too-short HMAC doesn't cause buffer overflow.
EXPECT_FALSE(StunMessage::ValidateMessageIntegrity32ForTesting( reinterpret_cast<constchar*>(kStunMessageWithBadHmacAtEnd), sizeof(kStunMessageWithBadHmacAtEnd), kRfc5769SampleMsgPassword));
// Test that munging a single bit anywhere in the message causes the // message-integrity check to fail, unless it is after the M-I attribute. char buf[sizeof(kSampleRequestMI32)];
memcpy(buf, kSampleRequestMI32, sizeof(kSampleRequestMI32)); for (size_t i = 0; i < sizeof(buf); ++i) {
buf[i] ^= 0x01; if (i > 0)
buf[i - 1] ^= 0x01;
EXPECT_EQ(i >= sizeof(buf) - 8,
StunMessage::ValidateMessageIntegrity32ForTesting(
buf, sizeof(buf), kRfc5769SampleMsgPassword));
}
}
// Validate that the message validates if both MESSAGE-INTEGRITY-32 and // MESSAGE-INTEGRITY are present in the message. // This is not expected to be used, but is not forbidden.
TEST_F(StunTest, AddMessageIntegrity32AndMessageIntegrity) {
IceMessage msg; auto attr = StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
attr->CopyBytes("keso", sizeof("keso"));
msg.AddAttribute(std::move(attr));
msg.AddMessageIntegrity32("password1");
msg.AddMessageIntegrity("password2");
// Test that munging a single bit anywhere in the message causes the // fingerprint check to fail. char buf[sizeof(kRfc5769SampleRequest)];
memcpy(buf, kRfc5769SampleRequest, sizeof(kRfc5769SampleRequest)); for (size_t i = 0; i < sizeof(buf); ++i) {
buf[i] ^= 0x01; if (i > 0)
buf[i - 1] ^= 0x01;
EXPECT_FALSE(StunMessage::ValidateFingerprint(buf, sizeof(buf)));
} // Put them all back to normal and the check should pass again.
buf[sizeof(buf) - 1] ^= 0x01;
EXPECT_TRUE(StunMessage::ValidateFingerprint(buf, sizeof(buf)));
}
// Test that we can remove attribute from a message.
TEST_F(StunTest, RemoveAttribute) {
StunMessage msg;
// Removing something that does exist should return nullptr.
EXPECT_EQ(msg.RemoveAttribute(STUN_ATTR_USERNAME), nullptr);
{ auto attr = StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
attr->CopyBytes("kes", sizeof("kes"));
msg.AddAttribute(std::move(attr));
}
size_t len = msg.length();
{ auto attr = msg.RemoveAttribute(STUN_ATTR_USERNAME);
ASSERT_NE(attr, nullptr);
EXPECT_EQ(attr->type(), STUN_ATTR_USERNAME);
EXPECT_STREQ("kes", static_cast<StunByteStringAttribute*>(attr.get())
->string_view()
.data());
EXPECT_LT(msg.length(), len);
}
// Now add same attribute type twice.
{ auto attr = StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
attr->CopyBytes("kes", sizeof("kes"));
msg.AddAttribute(std::move(attr));
}
{ auto attr = StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
attr->CopyBytes("kenta", sizeof("kenta"));
msg.AddAttribute(std::move(attr));
}
// Remove should remove the last added occurrence.
{ auto attr = msg.RemoveAttribute(STUN_ATTR_USERNAME);
ASSERT_NE(attr, nullptr);
EXPECT_EQ(attr->type(), STUN_ATTR_USERNAME);
EXPECT_STREQ("kenta", static_cast<StunByteStringAttribute*>(attr.get())
->string_view()
.data());
}
// Remove should remove the last added occurrence.
{ auto attr = msg.RemoveAttribute(STUN_ATTR_USERNAME);
ASSERT_NE(attr, nullptr);
EXPECT_EQ(attr->type(), STUN_ATTR_USERNAME);
EXPECT_STREQ("kes", static_cast<StunByteStringAttribute*>(attr.get())
->string_view()
.data());
}
// Removing something that does exist should return nullptr.
EXPECT_EQ(msg.RemoveAttribute(STUN_ATTR_USERNAME), nullptr);
}
// Test that we can remove attribute from a message.
TEST_F(StunTest, ClearAttributes) {
StunMessage msg;
auto attr = StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
attr->CopyBytes("kes", sizeof("kes"));
msg.AddAttribute(std::move(attr));
size_t len = msg.length();
// Test CopyStunAttribute
TEST_F(StunTest, CopyAttribute) {
rtc::ByteBufferWriter buf;
rtc::ByteBufferWriter* buffer_ptrs[] = {&buf, nullptr}; // Test both with and without supplied ByteBufferWriter. for (auto buffer_ptr : buffer_ptrs) {
{ // Test StunByteStringAttribute. auto attr = StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
attr->CopyBytes("kes", sizeof("kes"));