From 17718981cfbb15d3e5c4eaa36df2e0362ebbd4b7 Mon Sep 17 00:00:00 2001 From: Ykkrosh Date: Sun, 10 Jan 2010 19:29:27 +0000 Subject: [PATCH] Remove Crypto++ because it causes too much trouble. Replace with a custom MD5 implementation for simulation state hashing. This was SVN commit r7269. --- build/premake/premake.lua | 3 - source/lib/self_test.h | 3 + source/maths/MD5.cpp | 202 ++++++++++++++++++ source/maths/MD5.h | 42 ++++ source/maths/tests/test_MD5.h | 142 ++++++++++++ .../serialization/HashSerializer.h | 7 +- .../simulation2/tests/test_ComponentManager.h | 6 +- source/simulation2/tests/test_Serializer.h | 6 +- 8 files changed, 398 insertions(+), 13 deletions(-) create mode 100644 source/maths/MD5.cpp create mode 100644 source/maths/MD5.h create mode 100644 source/maths/tests/test_MD5.h diff --git a/build/premake/premake.lua b/build/premake/premake.lua index b0bb784c6e..bb092a08d8 100755 --- a/build/premake/premake.lua +++ b/build/premake/premake.lua @@ -417,7 +417,6 @@ function setup_all_libs () } extern_libs = { "boost", - "cryptopp", "spidermonkey", } setup_static_lib_package("simulation2", source_dirs, extern_libs, {}) @@ -542,7 +541,6 @@ function setup_all_libs () "openal", "vorbis", "libjpg", - "cryptopp", "valgrind", "cxxtest", } @@ -615,7 +613,6 @@ used_extern_libs = { "cxxtest", "comsuppw", "enet", - "cryptopp", } -- Bundles static libs together with main.cpp and builds game executable. diff --git a/source/lib/self_test.h b/source/lib/self_test.h index b5611c7af1..8f9a6d0dba 100644 --- a/source/lib/self_test.h +++ b/source/lib/self_test.h @@ -255,8 +255,11 @@ namespace CxxTest } #define TS_ASSERT_OK(expr) TS_ASSERT_EQUALS((expr), INFO::OK) +#define TSM_ASSERT_OK(m, expr) TSM_ASSERT_EQUALS(m, (expr), INFO::OK) #define TS_ASSERT_STR_EQUALS(str1, str2) TS_ASSERT_EQUALS(std::string(str1), std::string(str2)) +#define TSM_ASSERT_STR_EQUALS(m, str1, str2) TSM_ASSERT_EQUALS(m, std::string(str1), std::string(str2)) #define TS_ASSERT_WSTR_EQUALS(str1, str2) TS_ASSERT_EQUALS(std::wstring(str1), std::wstring(str2)) +#define TSM_ASSERT_WSTR_EQUALS(m, str1, str2) TSM_ASSERT_EQUALS(m, std::wstring(str1), std::wstring(str2)) bool ts_str_contains(const std::wstring& str1, const std::wstring& str2); // defined in test_setup.cpp #define TS_ASSERT_WSTR_CONTAINS(str1, str2) TSM_ASSERT(str1, ts_str_contains(str1, str2)) diff --git a/source/maths/MD5.cpp b/source/maths/MD5.cpp new file mode 100644 index 0000000000..b77439323d --- /dev/null +++ b/source/maths/MD5.cpp @@ -0,0 +1,202 @@ +/* Copyright (C) 2010 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +// Based on md5.cpp from Crypto++ 5.6.0: +// "md5.cpp - modified by Wei Dai from Colin Plumb's public domain md5.c" +// "any modifications are placed in the public domain" + +#include "MD5.h" + +MD5::MD5() +{ + InitState(); +} + +void MD5::InitState() +{ + m_Digest[0] = 0x67452301L; + m_Digest[1] = 0xefcdab89L; + m_Digest[2] = 0x98badcfeL; + m_Digest[3] = 0x10325476L; + m_BufLen = 0; + m_InputLen = 0; + memset(m_Buf, 0xcc, sizeof(m_Buf)); +} + +void MD5::Update(const u8* data, size_t len) +{ + const size_t CHUNK_SIZE = sizeof(m_Buf); + debug_assert(m_BufLen < CHUNK_SIZE); + + m_InputLen += len; + + // If we have enough space in m_Buf and won't flush, simply append the input + if (m_BufLen + len < CHUNK_SIZE) + { + memcpy(m_Buf + m_BufLen, data, len); + m_BufLen += len; + return; + } + + // Add as much data as possible to the buffer + size_t n = CHUNK_SIZE - m_BufLen; + debug_assert(len >= n); + memcpy(m_Buf + m_BufLen, data, n); + data += n; + len -= n; + + // Flush the (now full) buffer + Transform((const u32*)m_Buf); // assumes little-endian + + // Process whole chunks of the input + while (len >= CHUNK_SIZE) + { + Transform((const u32*)data); // assumes little-endian; ignores alignment + data += CHUNK_SIZE; + len -= CHUNK_SIZE; + } + + // Add the remainder to the buffer + memcpy(m_Buf, data, len); + m_BufLen = len; +} + +void MD5::Final(u8* digest) +{ + // Compute the message length in bits (before padding) + u64 len = m_InputLen * 8; + + // Pad with 1-bit + const u8 pad = 0x80; + Update(&pad, 1); + + // Fill with zeros until length % 64 = 56 (bytes) + while (m_BufLen % 64 != 56) + { + const u8 zero = 0; + Update(&zero, 1); + } + + // Append the length (assumes little-endian) + Update((const u8*)&len, 8); + + // Return the digest (assumes little-endian) + memcpy(digest, m_Digest, DIGESTSIZE); + + // Reset + InitState(); +} + +template inline T rotlFixed(T x, unsigned int y) +{ + debug_assert(y < sizeof(T)*8); + return T((x<>(sizeof(T)*8-y))); +} +// TODO: Crypto++ has an overload using _lrotl on MSVC - is that worthwhile? + +void MD5::Transform(const u32* in) +{ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +#define MD5STEP(f, w, x, y, z, data, s) \ + w = rotlFixed(w + f(x, y, z) + data, s) + x + + u32* digest = m_Digest; + + u32 a, b, c, d; + + a = digest[0]; + b = digest[1]; + c = digest[2]; + d = digest[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + digest[0] += a; + digest[1] += b; + digest[2] += c; + digest[3] += d; +} diff --git a/source/maths/MD5.h b/source/maths/MD5.h new file mode 100644 index 0000000000..b9972f90db --- /dev/null +++ b/source/maths/MD5.h @@ -0,0 +1,42 @@ +/* Copyright (C) 2010 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_MD5 +#define INCLUDED_MD5 + +/** + * MD5 hashing algorithm. Note that MD5 is broken and must not be used for + * anything that requires security. + */ +class MD5 +{ +public: + static const size_t DIGESTSIZE = 16; + + MD5(); + void Update(const u8* data, size_t len); + void Final(u8* digest); +private: + void InitState(); + void Transform(const u32* in); + u32 m_Digest[4]; // internal state + u8 m_Buf[64]; // buffered input bytes + size_t m_BufLen; // bytes in m_Buf that are valid + u64 m_InputLen; // bytes +}; + +#endif // INCLUDED_MD5 diff --git a/source/maths/tests/test_MD5.h b/source/maths/tests/test_MD5.h new file mode 100644 index 0000000000..c833d4239c --- /dev/null +++ b/source/maths/tests/test_MD5.h @@ -0,0 +1,142 @@ +/* Copyright (C) 2010 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "lib/self_test.h" + +#include "maths/MD5.h" + +class TestMD5 : public CxxTest::TestSuite +{ +public: + std::string decode(u8* digest) + { + char digeststr[MD5::DIGESTSIZE*2+1]; + for (size_t i = 0; i < MD5::DIGESTSIZE; ++i) + sprintf_s(digeststr+2*i, 4, "%02x", digest[i]); + return digeststr; + } + + void compare(const char* input, const char* expected) + { + u8 digest[MD5::DIGESTSIZE]; + + MD5 m; + m.Update((const u8*)input, strlen(input)); + m.Final(digest); + + TSM_ASSERT_STR_EQUALS(input, decode(digest), expected); + } + + void test_rfc() + { + compare("", "d41d8cd98f00b204e9800998ecf8427e"); + compare("a", "0cc175b9c0f1b6a831c399e269772661"); + compare("abc", "900150983cd24fb0d6963f7d28e17f72"); + compare("message digest", "f96b697d7cb7938d525a2f31aaf161d0"); + compare("abcdefghijklmnopqrstuvwxyz", "c3fcd3d76192e4007dfb496cca67e13b"); + compare("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", + "d174ab98d277d9f5a5611c2c9f419d9f"); + compare("12345678901234567890123456789012345678901234567890123456789012345678901234567890", + "57edf4a22be3c955ac49da2e2107b67a"); + } + + void test_align() + { + // Make sure it's not sensitive to alignment + const char* a0 = "a"; + const char* a1 = "?a"; + const char* a2 = "??a"; + const char* a3 = "???a"; + compare(a0+0, "0cc175b9c0f1b6a831c399e269772661"); + compare(a1+1, "0cc175b9c0f1b6a831c399e269772661"); + compare(a2+2, "0cc175b9c0f1b6a831c399e269772661"); + compare(a3+3, "0cc175b9c0f1b6a831c399e269772661"); + } + + void test_align_long() + { + // Make sure it's not sensitive to alignment + // when processing long chunks (where it won't memcpy to an intermediate buffer) + std::string a0 (1000, 'a'); + std::string a1 ("?" + a0); + std::string a2 ("??" + a0); + std::string a3 ("???" + a0); + compare(a0.c_str()+0, "cabe45dcc9ae5b66ba86600cca6b8ba8"); + compare(a1.c_str()+1, "cabe45dcc9ae5b66ba86600cca6b8ba8"); + compare(a2.c_str()+2, "cabe45dcc9ae5b66ba86600cca6b8ba8"); + compare(a3.c_str()+3, "cabe45dcc9ae5b66ba86600cca6b8ba8"); + // Matches output from: + // perl -e'print "a" x 1000'|openssl md5 + } + + void test_padding() + { + // Edge cases for padding + compare(std::string(54, 'x').c_str(), "61ea0974c662328da964d977a8253873"); + compare(std::string(55, 'x').c_str(), "04364420e25c512fd958a70738aa8f72"); + compare(std::string(56, 'x').c_str(), "668a72d5ba17f08e62dabcafad6db14b"); + compare(std::string(57, 'x').c_str(), "693037871c4a9d3d8685018905cb530a"); + } + + void test_chunks() + { + u8 digest[MD5::DIGESTSIZE]; + + const u8* in = (const u8*)"12345678901234567890123456789012345678901234567890123456789012345678901234567890"; + size_t len = 80; + const char* expected = "57edf4a22be3c955ac49da2e2107b67a"; + + // Process in one chunk + { + MD5 m; + m.Update(in, len); + m.Final(digest); + TS_ASSERT_STR_EQUALS(decode(digest), expected); + } + + // Process one byte at a time + { + MD5 m; + for (size_t i = 0; i < len; ++i) + m.Update(in+i, 1); + m.Final(digest); + TS_ASSERT_STR_EQUALS(decode(digest), expected); + } + + // Zero-length updates + { + MD5 m; + m.Update(in+0, 1); + m.Update(in+1, 0); + m.Update(in+1, len-1); + m.Update(in+len, 0); + m.Final(digest); + TS_ASSERT_STR_EQUALS(decode(digest), expected); + } + + // Split at various points + for (size_t i = 0; i <= len; ++i) + { + MD5 m; + m.Update(in, i); + m.Update(in+i, len-i); + m.Final(digest); + TS_ASSERT_STR_EQUALS(decode(digest), expected); + } + } +}; + diff --git a/source/simulation2/serialization/HashSerializer.h b/source/simulation2/serialization/HashSerializer.h index 903c375b95..8092c7e80e 100644 --- a/source/simulation2/serialization/HashSerializer.h +++ b/source/simulation2/serialization/HashSerializer.h @@ -20,14 +20,13 @@ #include "BinarySerializer.h" -#include "lib/external_libraries/cryptopp.h" +#include "maths/MD5.h" class CHashSerializer : public CBinarySerializer { // We don't care about cryptographic strength, just about detection of - // unintended changes and about performance, but we do want to use an algorithm - // that's included in Crypto++'s Windows DLL, so SHA1 is an adequate choice - typedef CryptoPP::SHA1 HashFunc; + // unintended changes and about performance, so MD5 is an adequate choice + typedef MD5 HashFunc; public: CHashSerializer(ScriptInterface& scriptInterface); size_t GetHashLength(); diff --git a/source/simulation2/tests/test_ComponentManager.h b/source/simulation2/tests/test_ComponentManager.h index efada553da..46bed3dda0 100644 --- a/source/simulation2/tests/test_ComponentManager.h +++ b/source/simulation2/tests/test_ComponentManager.h @@ -434,9 +434,9 @@ public: std::string hash; TS_ASSERT(man.ComputeStateHash(hash)); - TS_ASSERT_EQUALS(hash.length(), (size_t)20); - TS_ASSERT_SAME_DATA(hash.data(), "\x32\x73\x30\x3a\xf2\x52\xda\x23\x5a\x25\xca\xc8\x1e\xe3\x57\xa7\x63\xc9\x5f\x0f", 20); - // echo -en "\x01\0\0\0\x01\0\0\0\xf8\x2a\0\0\x02\0\0\0\xd2\x04\0\0\x04\0\0\0\x01\0\0\0\x08\x52\0\0" | openssl dgst -sha1 -hex | perl -pe 's/(..)/\\x$1/g' + TS_ASSERT_EQUALS(hash.length(), (size_t)16); + TS_ASSERT_SAME_DATA(hash.data(), "\xea\xd8\xe6\x94\xc0\x6b\x2a\xa1\xcc\x6d\x5d\xab\x48\x45\x75\xed", 16); + // echo -en "\x01\0\0\0\x01\0\0\0\xf8\x2a\0\0\x02\0\0\0\xd2\x04\0\0\x04\0\0\0\x01\0\0\0\x08\x52\0\0" | openssl md5 | perl -pe 's/(..)/\\x$1/g' // ^^Test1A^^ ^^^ent1^^ ^^^11000^^^ ^^^ent2^^ ^^^1234^^^ ^^Test2A^^ ^^ent1^^ ^^^21000^^^ std::stringstream stateStream; diff --git a/source/simulation2/tests/test_Serializer.h b/source/simulation2/tests/test_Serializer.h index d94aff790e..738737f72c 100644 --- a/source/simulation2/tests/test_Serializer.h +++ b/source/simulation2/tests/test_Serializer.h @@ -221,9 +221,9 @@ public: serialize.NumberU32_Unbounded("y", 1234); serialize.NumberI32("z", 12345, 0, 65535); - TS_ASSERT_EQUALS(serialize.GetHashLength(), (size_t)20); - TS_ASSERT_SAME_DATA(serialize.ComputeHash(), "\x22\xb6\x38\xc5\xc5\x6d\x70\x2b\xa7\xc6\x22\x1a\xde\x3b\x97\x1a\xdc\x27\x3d\x0b", 20); - // echo -en "\x85\xff\xff\xff\xd2\x04\x00\x00\x39\x30\x00\x00" | openssl dgst -sha1 -hex | perl -pe 's/(..)/\\x$1/g' + TS_ASSERT_EQUALS(serialize.GetHashLength(), (size_t)16); + TS_ASSERT_SAME_DATA(serialize.ComputeHash(), "\xa0\x3a\xe5\x3e\x9b\xd7\xfb\x11\x88\x35\xc6\xfb\xb9\x94\xa9\x72", 16); + // echo -en "\x85\xff\xff\xff\xd2\x04\x00\x00\x39\x30\x00\x00" | openssl md5 | perl -pe 's/(..)/\\x$1/g' } void test_bounds()