#include #include #include #include #include #ifdef LINUX #include #endif // I copied these tests from the Steam branch. // A little compatibility glue so I don't have to make any changes to them. #define CHECK(x) do { bool _check_result; Assert( (_check_result = (x)) != false ); g_failed |= !_check_result; } while(0) #define CHECK_EQUAL(a,b) do { bool _check_eq_result; Assert( (_check_eq_result = ((a)==(b))) != false ); g_failed |= !_check_eq_result; } while(0) #define RETURNIFNOT(x) { if ( !(x) ) { AssertMsg( false, #x ); return; } } #define RETURNFALSEIFNOT(x) { if ( !(x) ) { AssertMsg( false, #x ); return false; } } const int k_cSmallBuff = 100; // smallish buffer const int k_cMedBuff = 1200; bool g_failed = false; static unsigned char Q_nibble( char c ) { if ( ( c >= '0' ) && ( c <= '9' ) ) { return (unsigned char)(c - '0'); } if ( ( c >= 'A' ) && ( c <= 'F' ) ) { return (unsigned char)(c - 'A' + 0x0a); } if ( ( c >= 'a' ) && ( c <= 'f' ) ) { return (unsigned char)(c - 'a' + 0x0a); } // received an invalid character, and no real way to return an error // AssertMsg1( false, "Q_nibble invalid hex character '%c' ", c ); return 0; } #define min(x,y) (((x) > (y)) ? (y) : (x)) //----------------------------------------------------------------------------- static void V_hextobinary( char const *in, int numchars, byte *out, int maxoutputbytes ) { int len = V_strlen( in ); numchars = min( len, numchars ); // Make sure it's even numchars = ( numchars ) & ~0x1; memset( out, 0x00, maxoutputbytes ); byte *p; int i; p = out; for ( i = 0; ( i < numchars ) && ( ( p - out ) < maxoutputbytes ); i+=2, p++ ) { *p = ( Q_nibble( in[i] ) << 4 ) | Q_nibble( in[i+1] ); } } //----------------------------------------------------------------------------- // Purpose: Tests cryptography hex encoding //----------------------------------------------------------------------------- void TestCryptoEncoding() { bool bRet; // If you change the source data you'll need to change the expected encoding // output strings further down! uint8 rgubData[] = { 0x14, 0xfe, 0x26, 0x19, 0x54, 0x78, 0x00, 0x35, 0x19, 0xa9, 0x54, 0x4e, 0x99 }; uint cubData = V_ARRAYSIZE( rgubData ); char rgchEncodedData[k_cMedBuff]; uint cchEncodedData = V_ARRAYSIZE( rgchEncodedData ); uint8 rgubDecodedData[k_cSmallBuff]; uint cubDecodedData = V_ARRAYSIZE( rgubDecodedData ); // HEX // encode some data bRet = CCrypto::HexEncode( rgubData, cubData, rgchEncodedData, cchEncodedData ); CHECK( bRet ); // must succeed // decode the data bRet = CCrypto::HexDecode( rgchEncodedData, rgubDecodedData, &cubDecodedData ); CHECK( bRet ); // must succeed CHECK( cubDecodedData == cubData ); // must be of correct size bRet = (0 == V_memcmp( rgubData, rgubDecodedData, cubData )); CHECK( bRet ); // decoded data must match original // test the documented, if questionable, permissiveness of hexdecode. Note that Crypto++ // documentation claims that the last partial byte should have been parsed as E0, but this // has been disproven by testing with 5.6.1 and 5.6.2. this test verifies that there is no // change in the behavior of partial strings, if the algorithm should be updated. cubDecodedData = V_ARRAYSIZE( rgubDecodedData ); bRet = CCrypto::HexDecode( "x,F\nF1\t ,2\t~E ", rgubDecodedData, &cubDecodedData ); CHECK( bRet ); // must succeed CHECK( cubDecodedData == 2 && rgubDecodedData[0] == 0xFF && rgubDecodedData[1] == 0x12 ); // this hilarious string is offered up for laughs to verify that we remain as broken as ever. // Crypto++'s documentation claims that it will "correctly" parse this string as FF 12 E0. // In reality, it does the dumb/obvious thing and discards the 'x' and ' ' characters and // parses the '0's, resulting in 4 bytes. There is no evidence that any version of Crypto++ // has ever matched the documentation and actually done "smart" prefix skipping. cubDecodedData = V_ARRAYSIZE( rgubDecodedData ); bRet = CCrypto::HexDecode( "0xFF 0x12 0xE", rgubDecodedData, &cubDecodedData ); CHECK( bRet ); CHECK( cubDecodedData == 4 && rgubDecodedData[0] == 0x0F && rgubDecodedData[1] == 0xF0 && rgubDecodedData[2] == 0x12 && rgubDecodedData[3] == 0x0E ); // BASE64 cchEncodedData = V_ARRAYSIZE( rgchEncodedData ); bRet = CCrypto::Base64Encode( rgubData, cubData, rgchEncodedData, &cchEncodedData ); CHECK( bRet ); // must succeed // decode the data cubDecodedData = V_ARRAYSIZE( rgubDecodedData ); bRet = CCrypto::Base64Decode( rgchEncodedData, cchEncodedData, rgubDecodedData, &cubDecodedData ); CHECK( bRet ); // must succeed CHECK( cubDecodedData == cubData ); // must be of correct size bRet = ( 0 == V_memcmp( rgubData, rgubDecodedData, cubData ) ); CHECK( bRet ); // decoded data must match original // Test empty string handling { char buf[4] = { 1, 1, 1, 1 }; uint32 bufSize = sizeof(buf); bRet = CCrypto::Base64Encode( NULL, 0, buf, &bufSize, NULL ); CHECK( bRet ); CHECK_EQUAL( bufSize, 0 ); // zero characters written CHECK_EQUAL( buf[0], 0 ); // terminating null written to buffer } { uint8 buf[4] = { 1, 1, 1, 1 }; uint32 bufSize = sizeof(buf); bRet = CCrypto::Base64Decode( NULL, 0, buf, &bufSize, true ); CHECK( bRet ); CHECK_EQUAL( bufSize, 0 ); CHECK_EQUAL( buf[0], 1 ); // shouldn't have written to buf at all bufSize = sizeof(buf); bRet = CCrypto::Base64Decode( "", 0, buf, &bufSize, true ); CHECK( bRet ); CHECK_EQUAL( bufSize, 0 ); CHECK_EQUAL( buf[0], 1 ); // shouldn't have written to buf at all } // Test decoding error { uint8 buf[4] = { 1, 1, 1, 1 }; uint32 bufSize = sizeof(buf); const char pchEncodedBogus[] = "AAAA!@#$%^&*()_+|<>?:;'[]{}\\/,."; bRet = CCrypto::Base64Decode( pchEncodedBogus, V_strlen(pchEncodedBogus), buf, &bufSize, false ); CHECK( bRet == false ); // Should have decoded 3 null bytes and then failed CHECK_EQUAL( bufSize, 3 ); CHECK_EQUAL( buf[0], 0 ); CHECK_EQUAL( buf[1], 0 ); CHECK_EQUAL( buf[2], 0 ); CHECK_EQUAL( buf[3], 1 ); } } // https://csrc.nist.gov/Projects/Cryptographic-Algorithm-Validation-Program/CAVP-TESTING-BLOCK-CIPHER-MODES // https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Algorithm-Validation-Program/documents/mac/gcmtestvectors.zip class NISTTestVectorFile { FILE *f = nullptr; public: NISTTestVectorFile( const char *pszFilename ) { f = fopen( pszFilename, "rt" ); CHECK(f); } ~NISTTestVectorFile() { if ( f ) fclose( f ); } bool FindNextTest() { if ( !f ) return false; char line[2048]; while ( fgets( line, sizeof(line), f ) ) { if ( V_strnicmp( line, "count", 5 ) == 0 ) return true; } return false; } bool GetBinaryBlob( const char *pszTag, std::string &blob ) { char line[2048]; RETURNFALSEIFNOT( fgets( line, sizeof(line), f ) ); int lTag = V_strlen(pszTag); RETURNFALSEIFNOT( V_strnicmp( line, pszTag, lTag ) == 0 ); const char *p = line + lTag; while ( isspace(*p) ) ++p; CHECK_EQUAL( *p, '=' ); ++p; while ( isspace(*p) ) ++p; uint8 decodedData[ 1024 ]; uint32 cbDecodedData = sizeof(decodedData); RETURNFALSEIFNOT( CCrypto::HexDecode( p, decodedData, &cbDecodedData ) ); blob.assign( (char *)decodedData, cbDecodedData ); return true; } }; void TestSymmetricAuthCrypto_EncryptTestVectorFile( const char *pszFilename ) { bool bRet; AES_GCM_EncryptContext ctxEnc; AES_GCM_DecryptContext ctxDec; NISTTestVectorFile file( pszFilename ); while ( file.FindNextTest() ) { std::string key; RETURNIFNOT( file.GetBinaryBlob( "key", key ) ); std::string iv; RETURNIFNOT( file.GetBinaryBlob( "iv", iv ) ); std::string pt; RETURNIFNOT( file.GetBinaryBlob( "pt", pt ) ); std::string aad; RETURNIFNOT( file.GetBinaryBlob( "aad", aad ) ); std::string ct; RETURNIFNOT( file.GetBinaryBlob( "ct", ct ) ); std::string tag; RETURNIFNOT( file.GetBinaryBlob( "tag", tag ) ); // With the Windows CNG API (BCryptEncrypt/BCryptDecrypt) there are no // other tag length or IV length options permitted. if ( tag.length() != 16 || iv.length() != 12 ) continue; ctxEnc.Init( key.c_str(), key.length(), iv.length(), tag.length() ); ctxDec.Init( key.c_str(), key.length(), iv.length(), tag.length() ); // Encrypt it uint8 encrypted[ 2048 ]; uint32 cbEncrypted = sizeof(encrypted); RETURNIFNOT( ct.length() <= sizeof(encrypted) ); bRet = ctxEnc.Encrypt( pt.c_str(), pt.length(), iv.c_str(), encrypted, &cbEncrypted, aad.c_str(), aad.length() ); CHECK( bRet ); // Confirm it matches the test vector CHECK( cbEncrypted == ct.length() + tag.length() ); CHECK( memcmp( ct.c_str(), encrypted, ct.length() ) == 0 ); CHECK( memcmp( tag.c_str(), encrypted+ct.length(), tag.length() ) == 0 ); // Make sure we can decrypt it successfully uint8 decrypted[ 2048 ]; uint32 cbDecrypted = sizeof(decrypted); bRet = ctxDec.Decrypt( encrypted, cbEncrypted, iv.c_str(), decrypted, &cbDecrypted, aad.c_str(), aad.length() ); CHECK( bRet ); CHECK( cbDecrypted == pt.length() ); CHECK( memcmp( pt.c_str(), decrypted, cbDecrypted ) == 0 ); // Flip a random bit in the ciphertext+tag blob encrypted[ rand() % cbEncrypted ] ^= ( 1 << (rand() & 7 ) ); // It should fail to decrypt cbDecrypted = sizeof(decrypted); bRet = ctxDec.Decrypt( encrypted, cbEncrypted, iv.c_str(), decrypted, &cbDecrypted, aad.c_str(), aad.length() ); CHECK( !bRet ); } } //----------------------------------------------------------------------------- // Purpose: Test AES-GCM crypto against known vectors //----------------------------------------------------------------------------- void TestSymmetricAuthCryptoVectors() { #ifndef TEST_VECTOR_DIR #define TEST_VECTOR_DIR "aesgcmtestvectors/" #endif // Check against known test vectors TestSymmetricAuthCrypto_EncryptTestVectorFile( TEST_VECTOR_DIR "gcmEncryptExtIV256.rsp" ); } //----------------------------------------------------------------------------- // Purpose: Test elliptic-curve primitives (ed25519 signing, curve25519 key exchange) //----------------------------------------------------------------------------- void TestSHA256() { // NIST FIPS 180-4 known-answer tests for SHA-256 struct { const char *pszData; const char *pszExpected; } rgTests[] = { // SHA-256("") { "", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" }, // SHA-256("abc") { "abc", "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" }, // SHA-256("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") { "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1" }, }; for ( auto &t : rgTests ) { SHA256Digest_t digest; CCrypto::GenerateSHA256Digest( t.pszData, strlen(t.pszData), &digest ); char hex[sizeof(SHA256Digest_t)*2+1]; CCrypto::HexEncode( digest, sizeof(digest), hex, sizeof(hex) ); // HexEncode gives uppercase; compare case-insensitively for ( int i = 0; hex[i]; ++i ) hex[i] = tolower(hex[i]); CHECK_EQUAL( 0, strcmp( hex, t.pszExpected ) ); } } void TestHMAC() { // RFC 2202 / RFC 4231 known-answer tests for HMAC-SHA1 and HMAC-SHA256 // HMAC-SHA1, Test Case 1 (RFC 2202): key=0x0b*20, data="Hi There" { uint8 key[20]; uint32 cbKey = 20; for ( int i = 0; i < 20; ++i ) key[i] = 0x0b; const char *pszData = "Hi There"; SHADigest_t digest; CCrypto::GenerateHMAC( (const uint8 *)pszData, (uint32)strlen(pszData), key, cbKey, &digest ); char hex[sizeof(SHADigest_t)*2+1]; CCrypto::HexEncode( digest, sizeof(digest), hex, sizeof(hex) ); for ( int i = 0; hex[i]; ++i ) hex[i] = tolower(hex[i]); CHECK_EQUAL( 0, strcmp( hex, "b617318655057264e28bc0b6fb378c8ef146be00" ) ); } // HMAC-SHA1, Test Case 2 (RFC 2202): key="Jefe", data="what do ya want for nothing?" { const char *pszKey = "Jefe"; const char *pszData = "what do ya want for nothing?"; SHADigest_t digest; CCrypto::GenerateHMAC( (const uint8 *)pszData, (uint32)strlen(pszData), (const uint8 *)pszKey, (uint32)strlen(pszKey), &digest ); char hex[sizeof(SHADigest_t)*2+1]; CCrypto::HexEncode( digest, sizeof(digest), hex, sizeof(hex) ); for ( int i = 0; hex[i]; ++i ) hex[i] = tolower(hex[i]); CHECK_EQUAL( 0, strcmp( hex, "effcdf6ae5eb2fa2d27416d5f184df9c259a7c79" ) ); } // HMAC-SHA256, Test Case 1 (RFC 4231): key=0x0b*20, data="Hi There" { uint8 key[20]; uint32 cbKey = 20; for ( int i = 0; i < 20; ++i ) key[i] = 0x0b; const char *pszData = "Hi There"; SHA256Digest_t digest; CCrypto::GenerateHMAC256( (const uint8 *)pszData, (uint32)strlen(pszData), key, cbKey, &digest ); char hex[sizeof(SHA256Digest_t)*2+1]; CCrypto::HexEncode( digest, sizeof(digest), hex, sizeof(hex) ); for ( int i = 0; hex[i]; ++i ) hex[i] = tolower(hex[i]); CHECK_EQUAL( 0, strcmp( hex, "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7" ) ); } // HMAC-SHA256, Test Case 2 (RFC 4231): key="Jefe", data="what do ya want for nothing?" { const char *pszKey = "Jefe"; const char *pszData = "what do ya want for nothing?"; SHA256Digest_t digest; CCrypto::GenerateHMAC256( (const uint8 *)pszData, (uint32)strlen(pszData), (const uint8 *)pszKey, (uint32)strlen(pszKey), &digest ); char hex[sizeof(SHA256Digest_t)*2+1]; CCrypto::HexEncode( digest, sizeof(digest), hex, sizeof(hex) ); for ( int i = 0; hex[i]; ++i ) hex[i] = tolower(hex[i]); CHECK_EQUAL( 0, strcmp( hex, "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843" ) ); } } void TestEllipticCrypto() { // test vectors from curve25519 reference impl const char rgchAlicePriv[] = "77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a"; const char rgchAlicePub[] = "8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a"; const char rgchBobPriv[] = "5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb"; const char rgchBobPub[] = "de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f"; const char rgchExpectSharedPreHash[] = "4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742"; uint8 buf[64]; CECKeyExchangePrivateKey alicePriv, bobPriv; CECKeyExchangePublicKey alicePub, bobPub; alicePriv.SetFromHexEncodedString( rgchAlicePriv ); alicePub.SetFromHexEncodedString( rgchAlicePub ); CHECK( alicePriv.MatchesPublicKey( alicePub ) ); bobPriv.SetFromHexEncodedString( rgchBobPriv ); bobPub.SetFromHexEncodedString( rgchBobPub ); CHECK( bobPriv.MatchesPublicKey( bobPub ) ); V_hextobinary( rgchExpectSharedPreHash, 64, buf, 32 ); SHA256Digest_t expectedResult; CCrypto::GenerateSHA256Digest( buf, 32, &expectedResult ); SHA256Digest_t aliceSharedSecret = {0}; SHA256Digest_t bobSharedSecret = {1}; CHECK( CCrypto::PerformKeyExchange( alicePriv, bobPub, &aliceSharedSecret ) ); CHECK( CCrypto::PerformKeyExchange( bobPriv, alicePub, &bobSharedSecret ) ); CHECK( V_memcmp( aliceSharedSecret, bobSharedSecret, sizeof(SHA256Digest_t) ) == 0 ); CHECK( V_memcmp( expectedResult, aliceSharedSecret, sizeof(SHA256Digest_t) ) == 0 ); CHECK( V_memcmp( expectedResult, bobSharedSecret, sizeof(SHA256Digest_t) ) == 0 ); // test key extraction and comparison operations CECKeyExchangePublicKey testPubFromPriv; alicePriv.GetPublicKey( &testPubFromPriv ); CHECK( testPubFromPriv == alicePub ); CHECK( testPubFromPriv != bobPub ); CHECK( alicePriv.MatchesPublicKey( testPubFromPriv ) ); CHECK( !bobPriv.MatchesPublicKey( testPubFromPriv ) ); // test key exchange with random keys alicePriv.Wipe(); alicePub.Wipe(); bobPriv.Wipe(); bobPub.Wipe(); memset( aliceSharedSecret, 0, sizeof( aliceSharedSecret ) ); memset( bobSharedSecret, 0xFF, sizeof( bobSharedSecret ) ); CCrypto::GenerateKeyExchangeKeyPair( &alicePub, &alicePriv ); CCrypto::GenerateKeyExchangeKeyPair( &bobPub, &bobPriv ); // alice and bob send each other only their public keys. CHECK( CCrypto::PerformKeyExchange( alicePriv, bobPub, &aliceSharedSecret ) ); CHECK( CCrypto::PerformKeyExchange( bobPriv, alicePub, &bobSharedSecret ) ); // alice and bob should have computed the same shared secret. CHECK( V_memcmp( aliceSharedSecret, bobSharedSecret, sizeof( bobSharedSecret ) ) == 0 ); // test vectors from ed25519 reference impl const char rgchSignPriv[] = "b18e1d0045995ec3d010c387ccfeb984d783af8fbb0f40fa7db126d889f6dadd"; const char rgchSignPub[] = "77f48b59caeda77751ed138b0ec667ff50f8768c25d48309a8f386a2bad187fb"; const char rgchMessageToSign[] = "916c7d1d268fc0e77c1bef238432573c39be577bbea0998936add2b50a653171" "ce18a542b0b7f96c1691a3be6031522894a8634183eda38798a0c5d5d79fbd01" "dd04a8646d71873b77b221998a81922d8105f892316369d5224c9983372d2313" "c6b1f4556ea26ba49d46e8b561e0fc76633ac9766e68e21fba7edca93c4c7460" "376d7f3ac22ff372c18f613f2ae2e856af40"; const char rgchExpected[] = "6bd710a368c1249923fc7a1610747403040f0cc30815a00f9ff548a896bbda0b" "4eb2ca19ebcf917f0f34200a9edbad3901b64ab09cc5ef7b9bcc3c40c0ff7509"; CECSigningPrivateKey signPriv; CECSigningPublicKey signPub; signPriv.SetFromHexEncodedString( rgchSignPriv ); signPub.SetFromHexEncodedString( rgchSignPub ); CHECK( signPriv.MatchesPublicKey( signPub ) ); uint8 bufMessageToSign[ (sizeof(rgchMessageToSign)-1)/2 ]; V_hextobinary( rgchMessageToSign, sizeof(rgchMessageToSign)-1, bufMessageToSign, sizeof(bufMessageToSign) ); uint8 bufExpected[64]; V_hextobinary( rgchExpected, 128, bufExpected, 64 ); CryptoSignature_t signature; CCrypto::GenerateSignature( bufMessageToSign, sizeof(bufMessageToSign), signPriv, &signature ); CHECK( V_memcmp( signature, bufExpected, sizeof(CryptoSignature_t) ) == 0 ); bufMessageToSign[5] ^= 1; CHECK( !CCrypto::VerifySignature( bufMessageToSign, sizeof( bufMessageToSign ), signPub, signature ) ); bufMessageToSign[5] ^= 1; signature[20] ^= 1; CHECK( !CCrypto::VerifySignature( bufMessageToSign, sizeof( bufMessageToSign ), signPub, signature ) ); signature[20] ^= 1; CHECK( CCrypto::VerifySignature( bufMessageToSign, sizeof( bufMessageToSign ), signPub, signature ) ); // test signing with random keys signPriv.Wipe(); signPub.Wipe(); CCrypto::GenerateSigningKeyPair( &signPub, &signPriv ); CCrypto::GenerateSignature( bufMessageToSign, sizeof( bufMessageToSign ), signPriv, &signature ); bufMessageToSign[5] ^= 1; CHECK( !CCrypto::VerifySignature( bufMessageToSign, sizeof( bufMessageToSign ), signPub, signature ) ); bufMessageToSign[5] ^= 1; signature[20] ^= 1; CHECK( !CCrypto::VerifySignature( bufMessageToSign, sizeof( bufMessageToSign ), signPub, signature ) ); signature[20] ^= 1; CHECK( CCrypto::VerifySignature( bufMessageToSign, sizeof( bufMessageToSign ), signPub, signature ) ); // test public/private key relationships and operators CECSigningPublicKey testSignPubFromPriv; signPriv.GetPublicKey( &testSignPubFromPriv ); CHECK( testSignPubFromPriv == signPub ); CHECK( signPriv.MatchesPublicKey( testSignPubFromPriv ) ); CCrypto::GenerateSigningKeyPair( &signPub, &signPriv ); CHECK( testSignPubFromPriv != signPub ); CHECK( !signPriv.MatchesPublicKey( testSignPubFromPriv ) ); } //----------------------------------------------------------------------------- // Purpose: Test parsing and re-writing of keys in OpenSSH formats //----------------------------------------------------------------------------- void TestOpenSSHEd25519() { char buf[ 2048 ]; // Generate some keys, and make sure we can round trip them { CECSigningPublicKey pubKey; CECSigningPrivateKey privKey; CCrypto::GenerateSigningKeyPair( &pubKey, &privKey ); uint32 cbBuf; // Get public key as authorized_keys format. // Should fail if we tell it the buffer is too small CHECK( !pubKey.GetAsOpenSSHAuthorizedKeys( buf, 16, &cbBuf, "" ) ); CHECK( pubKey.GetAsOpenSSHAuthorizedKeys( buf, sizeof(buf), &cbBuf, "" ) ); CHECK( (int)cbBuf == V_strlen( buf )+1 ); CHECK( 75 <= cbBuf && cbBuf <= 85 ); // typical size (assuming no password or key comment). Not necessarily a bug if this assert fires, but maybe something suspicious // Parse it back out, make sure it matches CECSigningPublicKey pubKey2; CHECK( pubKey2.LoadFromAndWipeBuffer( buf, cbBuf ) ); CHECK( pubKey2 == pubKey ); // Get private key in openSSH PEM-ish format. // Should fail if we tell it the buffer is too small CHECK( !privKey.GetAsPEM( buf, 64, &cbBuf ) ); CHECK( privKey.GetAsPEM( buf, sizeof(buf), &cbBuf ) ); CHECK( (int)cbBuf == V_strlen( buf )+1 ); CHECK( 370 <= cbBuf && cbBuf <= 390 ); // typical size (assuming no password or key comment). Not necessarily a bug if this assert fires, but maybe something suspicious // Parse it back out, make sure it matches CECSigningPrivateKey privKey2; CHECK( privKey2.LoadFromAndWipeBuffer( buf, cbBuf ) ); CHECK( privKey2 == privKey ); CHECK( privKey2.MatchesPublicKey( pubKey2 ) ); } // Parse some known keys { CUtlBuffer bufPrivKeyPEMA; bufPrivKeyPEMA.PutString( R"PEM( -----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW QyNTUxOQAAACC3vdN6llE0by4d7aFur0nBXdu5hXJb7LLkiC5UCEPFDgAAAJgJaJG1CWiR tQAAAAtzc2gtZWQyNTUxOQAAACC3vdN6llE0by4d7aFur0nBXdu5hXJb7LLkiC5UCEPFDg AAAECpUfg4C0BkgsCO+GlFAbcTQZUeFFQcamXzDA1tx7aNWre903qWUTRvLh3toW6vScFd 27mFclvssuSILlQIQ8UOAAAAEmZsZXRjaGVyZEBzcmNkczAwMwECAw== -----END OPENSSH PRIVATE KEY----- )PEM" ); CECSigningPrivateKey privKeyA; CHECK( privKeyA.LoadFromAndWipeBuffer( bufPrivKeyPEMA.Base(), bufPrivKeyPEMA.TellPut() ) ); CUtlBuffer bufPubKeyA; bufPubKeyA.PutString( "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILe903qWUTRvLh3toW6vScFd27mFclvssuSILlQIQ8UO" ); CECSigningPublicKey pubKeyA; CHECK( pubKeyA.LoadFromAndWipeBuffer( bufPubKeyA.Base(), bufPubKeyA.TellPut() ) ); CHECK( privKeyA.MatchesPublicKey( pubKeyA ) ); } } //----------------------------------------------------------------------------- // Purpose: Tests elliptic crypto perf //----------------------------------------------------------------------------- void TestEllipticPerf() { const int k_cubPktBig = 1024*1024*10; const int k_cubPktSmall = 128; const int k_cIterationsECDH = 500; const int k_cIterationsSignSmall = 500; const int k_cIterationsSignBig = 25; uint64 usecStart; CUtlBuffer bufData; bufData.EnsureCapacity( k_cubPktBig ); bufData.SeekPut( CUtlBuffer::SEEK_HEAD, k_cubPktBig ); CCrypto::GenerateRandomBlock(bufData.Base(), k_cubPktBig); CECKeyExchangePublicKey lastPub; CECKeyExchangePrivateKey lastPriv; CCrypto::GenerateKeyExchangeKeyPair( &lastPub, &lastPriv ); usecStart = Plat_USTime(); int x = 0; for ( int i = 0; i < k_cIterationsECDH; ++i ) { SHA256Digest_t sharedsecret; CECKeyExchangePublicKey pub; CECKeyExchangePrivateKey priv; CCrypto::GenerateKeyExchangeKeyPair( &pub, &priv ); CHECK( CCrypto::PerformKeyExchange( priv, lastPub, &sharedsecret ) ); x ^= sharedsecret[0] ^ sharedsecret[sizeof(sharedsecret)-1]; } double dMicrosecPerECDH = double( Plat_USTime() - usecStart ) / k_cIterationsECDH; CECSigningPublicKey signPub; CECSigningPrivateKey signPriv; CCrypto::GenerateSigningKeyPair( &signPub, &signPriv ); CryptoSignature_t signature; // small data sign usecStart = Plat_USTime(); for ( int i = 0; i < k_cIterationsSignSmall; ++i ) { CCrypto::GenerateSignature( (uint8*)bufData.Base(), k_cubPktSmall, signPriv, &signature ); x ^= signature[0] ^ signature[sizeof( signature ) - 1]; } double dMicrosecPerSignSmall = double( Plat_USTime() - usecStart ) / k_cIterationsSignSmall; // small data verify usecStart = Plat_USTime(); for ( int i = 0; i < k_cIterationsSignSmall; ++i ) { x ^= (int)CCrypto::VerifySignature( (uint8*)bufData.Base(), k_cubPktSmall, signPub, signature ); } double dMicrosecPerSignCheckSmall = double( Plat_USTime() - usecStart ) / k_cIterationsSignSmall; // large data sign usecStart = Plat_USTime(); for ( int i = 0; i < k_cIterationsSignBig; ++i ) { CCrypto::GenerateSignature( (uint8*)bufData.Base(), k_cubPktBig, signPriv, &signature ); x ^= signature[0] ^ signature[sizeof( signature ) - 1]; } double elapsed = Plat_USTime() - usecStart; double dMicrosecPerSignBig = elapsed / k_cIterationsSignBig; double dRateLargeMBPerSec = double( k_cubPktBig ) * k_cIterationsSignBig / elapsed; // large data verify usecStart = Plat_USTime(); for ( int i = 0; i < k_cIterationsSignBig; ++i ) { x ^= (int)CCrypto::VerifySignature( (uint8*)bufData.Base(), k_cubPktBig, signPub, signature ); } elapsed = Plat_USTime() - usecStart; double dMicrosecPerSignCheckBig = elapsed / k_cIterationsSignBig; double dRateLargeMBPerSecCheck = double( k_cubPktBig ) * k_cIterationsSignBig / elapsed; printf( "\tEphemeral curve25519 key exchange:\t\t\t%f microseconds each (%d iterations)\n", dMicrosecPerECDH, k_cIterationsSignSmall ); printf( "\tCalculate ed25519 signature (small):\t\t\t%f microseconds each (%d iterations)\n", dMicrosecPerSignSmall, k_cIterationsSignSmall ); printf( "\tCalculate ed25519 signature (big):\t\t\t%f microseconds each (%d iterations)\n", dMicrosecPerSignBig, k_cIterationsSignBig ); printf( "\tCalculate ed25519 signature (big):\t\t\t%f MB/sec (%d iterations)\n", dRateLargeMBPerSec, k_cIterationsSignBig ); printf( "\tVerify ed25519 signature (small):\t\t\t%f microseconds each (%d iterations)\n", dMicrosecPerSignCheckSmall, k_cIterationsSignSmall ); printf( "\tVerify ed25519 signature (big):\t\t\t%f microseconds each (%d iterations)\n", dMicrosecPerSignCheckBig, k_cIterationsSignBig ); printf( "\tVerify ed25519 signature (big):\t\t\t%f MB/sec (%d iterations)\n", dRateLargeMBPerSecCheck, k_cIterationsSignBig ); } //----------------------------------------------------------------------------- // Purpose: Performs specified # of symmetric encryptions //----------------------------------------------------------------------------- void SymmetricAuthEncryptRepeatedly( int cIterations, AES_GCM_EncryptContext &ctxEnc, uint8 *pubData, int cubToEncrypt, uint8 *pubIV ) { int nBufSize = cubToEncrypt + 32; // 16 = AES block size.. worst case for padded data uint8 *pEncrypted = new uint8[ nBufSize ]; // try a bunch of iterations of symmetric encrypting big packets for ( int iIteration = 0; iIteration < cIterations; iIteration++ ) { uint cubEncrypted = nBufSize; bool bRet = ctxEnc.Encrypt( &pubData[iIteration], cubToEncrypt, pubIV, pEncrypted, &cubEncrypted, nullptr, 0 ); CHECK( bRet ); // must succeed } delete [] pEncrypted; } //----------------------------------------------------------------------------- // Purpose: Performs specified # of symmetric descryptions //----------------------------------------------------------------------------- void SymmetricAuthDecryptRepeatedly( int cIterations, AES_GCM_DecryptContext &ctxDec, uint8 *pubEncrypted, int cubEncrypted, uint8 *pubIV ) { int nBufSize = cubEncrypted + 32; // 16 = AES block size.. worst case for padded data uint8 *pDecrypted = new uint8[ nBufSize ]; // try a bunch of iterations of symmetric encrypting big packets for ( int iIteration = 0; iIteration < cIterations; iIteration++ ) { uint cubOutput = nBufSize; bool bRet = ctxDec.Decrypt( pubEncrypted, cubEncrypted, pubIV, pDecrypted, &cubOutput, nullptr, 0 ); CHECK( bRet ); // must succeed } delete [] pDecrypted; } //----------------------------------------------------------------------------- // Purpose: Tests symmetric crypto perf //----------------------------------------------------------------------------- void TestSymmetricAuthCryptoPerf() { const int k_cIterations = 10000; const int k_cMaxData = 800; const int k_cBufs = 5; const int k_cubTestBuf = k_cMaxData * k_cBufs + k_cIterations; const int k_cubPktBig = 1200; const int k_cubPktSmall = 100; AES_GCM_EncryptContext ctxEnc; AES_GCM_DecryptContext ctxDec; uint64 usecStart; // generate a random key uint8 rgubKey[k_nSymmetricKeyLen]; uint8 rgubIV[k_nSymmetricIVSize]; CCrypto::GenerateRandomBlock( rgubKey, V_ARRAYSIZE( rgubKey ) ); CCrypto::GenerateRandomBlock( rgubIV, V_ARRAYSIZE( rgubIV ) ); uint8 rgubData[k_cubTestBuf]; // Initialize encrypt/decrypt contexts ctxEnc.Init( rgubKey, k_nSymmetricKeyLen, V_ARRAYSIZE(rgubIV), k_nSymmetricGCMTagSize ); ctxDec.Init( rgubKey, k_nSymmetricKeyLen, V_ARRAYSIZE(rgubIV), k_nSymmetricGCMTagSize ); // fill data buffer with arbitrary data uint8 rgubEncrypted[ k_cubPktBig + 32 ]; // 16 = AES block size.. worst case for padded data for ( int iubData = 0; iubData < V_ARRAYSIZE( rgubData ); iubData ++ ) rgubData[iubData] = (uint8) iubData; // try a bunch of iterations of symmetric encrypting small packets usecStart = Plat_USTime(); SymmetricAuthEncryptRepeatedly( k_cIterations, ctxEnc, rgubData, k_cubPktSmall, rgubIV ); int cMicroSecPerEncryptSmall = Plat_USTime() - usecStart; // try a bunch of iterations of symmetric encrypting big packets usecStart = Plat_USTime(); SymmetricAuthEncryptRepeatedly( k_cIterations, ctxEnc, rgubData, k_cubPktBig, rgubIV ); int cMicroSecPerEncryptBig = Plat_USTime() - usecStart; double dRateLargeEncrypt = double( k_cubPktBig ) * k_cIterations / cMicroSecPerEncryptBig; // try a bunch of iterations decrypting small packets uint cubEncrypted = V_ARRAYSIZE( rgubEncrypted ); bool bRet = ctxEnc.Encrypt( rgubData, k_cubPktSmall, rgubIV, rgubEncrypted, &cubEncrypted, nullptr, 0 ); CHECK( bRet ); usecStart = Plat_USTime(); SymmetricAuthDecryptRepeatedly( k_cIterations, ctxDec, rgubEncrypted, cubEncrypted, rgubIV ); int cMicroSecPerDecryptSmall = Plat_USTime() - usecStart; // try a bunch of iterations decrypting big packets cubEncrypted = V_ARRAYSIZE( rgubEncrypted ); bRet = ctxEnc.Encrypt( rgubData, k_cubPktBig, rgubIV, rgubEncrypted, &cubEncrypted, nullptr, 0 ); CHECK( bRet ); usecStart = Plat_USTime(); SymmetricAuthDecryptRepeatedly( k_cIterations, ctxDec, rgubEncrypted, cubEncrypted, rgubIV ); int cMicroSecPerDecryptBig = Plat_USTime() - usecStart; double dRateLargeDecrypt = double( k_cubPktBig ) * k_cIterations / cMicroSecPerDecryptBig; printf( "\tSymmetric GCM encrypt (small):\t\t%d microsec (%d iterations)\n", cMicroSecPerEncryptSmall, k_cIterations ); printf( "\tSymmetric GCM encrypt (big):\t\t%d microsec (%d iterations)\n", cMicroSecPerEncryptBig, k_cIterations ); printf( "\tSymmetric GCM encrypt (big):\t\t%f MB/sec (%d iterations)\n", dRateLargeEncrypt, k_cIterations ); printf( "\tSymmetric GCM decrypt (small):\t\t%d microsec (%d iterations)\n", cMicroSecPerDecryptSmall, k_cIterations ); printf( "\tSymmetric GCM decrypt (big):\t\t%d microsec (%d iterations)\n", cMicroSecPerDecryptBig, k_cIterations ); printf( "\tSymmetric GCM decrypt (big):\t\t%f MB/sec (%d iterations)\n", dRateLargeDecrypt, k_cIterations ); } bool chdir_to_bindir() { #ifdef LINUX char rgchPath[PATH_MAX], *pchPathTail; memset(rgchPath, 0, sizeof(rgchPath)); if (readlink("/proc/self/exe", rgchPath, sizeof(rgchPath)) == -1) return false; pchPathTail = strrchr(rgchPath, '/'); if (!pchPathTail) return false; *pchPathTail = 0; if (chdir(rgchPath) == -1) return false; #endif return true; } int main() { if (!chdir_to_bindir()) printf("WARNING: Could not change working directory, AES-GCM test vectors will probably break.\n"); CCrypto::Init(); TestCryptoEncoding(); TestSHA256(); TestHMAC(); TestSymmetricAuthCryptoVectors(); TestEllipticCrypto(); TestOpenSSHEd25519(); TestEllipticPerf(); TestSymmetricAuthCryptoPerf(); return g_failed ? 1 : 0; }