// validat6.cpp - originally written and placed in the public domain by Wei Dai
//                CryptoPP::Test namespace added by JW in February 2017.
//                Source files split in July 2018 to expedite compiles.

#include "pch.h"

#define CRYPTOPP_ENABLE_NAMESPACE_WEAK 1

#include "cryptlib.h"
#include "cpu.h"
#include "validate.h"

#include "asn.h"
#include "oids.h"
#include "blumshub.h"
#include "eccrypto.h"

#include <iostream>
#include <iomanip>
#include <sstream>

// Aggressive stack checking with VS2005 SP1 and above.
#if (_MSC_FULL_VER >= 140050727)
# pragma strict_gs_check (on)
#endif

#if CRYPTOPP_MSC_VERSION
# pragma warning(disable: 4505 4355)
#endif

NAMESPACE_BEGIN(CryptoPP)
NAMESPACE_BEGIN(Test)

bool CryptoSystemValidate(PK_Decryptor &priv, PK_Encryptor &pub, bool thorough)
{
	bool pass = true, fail;

	fail = !pub.GetMaterial().Validate(GlobalRNG(), thorough ? 3 : 2) || !priv.GetMaterial().Validate(GlobalRNG(), thorough ? 3 : 2);
	pass = pass && !fail;

	std::cout << (fail ? "FAILED    " : "passed    ");
	std::cout << "cryptosystem key validation\n";

	const byte *message = (byte *)"test message";
	const int messageLen = 12;
	SecByteBlock ciphertext(priv.CiphertextLength(messageLen));
	SecByteBlock plaintext(priv.MaxPlaintextLength(ciphertext.size()));

	pub.Encrypt(GlobalRNG(), message, messageLen, ciphertext);
	fail = priv.Decrypt(GlobalRNG(), ciphertext, priv.CiphertextLength(messageLen), plaintext) != DecodingResult(messageLen);
	fail = fail || memcmp(message, plaintext, messageLen);
	pass = pass && !fail;

	std::cout << (fail ? "FAILED    " : "passed    ");
	std::cout << "encryption and decryption\n";

	return pass;
}

bool SimpleKeyAgreementValidate(SimpleKeyAgreementDomain &d)
{
	if (d.GetCryptoParameters().Validate(GlobalRNG(), 3))
		std::cout << "passed    simple key agreement domain parameters validation" << std::endl;
	else
	{
		std::cout << "FAILED    simple key agreement domain parameters invalid" << std::endl;
		return false;
	}

	SecByteBlock priv1(d.PrivateKeyLength()), priv2(d.PrivateKeyLength());
	SecByteBlock pub1(d.PublicKeyLength()), pub2(d.PublicKeyLength());
	SecByteBlock val1(d.AgreedValueLength()), val2(d.AgreedValueLength());

	d.GenerateKeyPair(GlobalRNG(), priv1, pub1);
	d.GenerateKeyPair(GlobalRNG(), priv2, pub2);

	memset(val1.begin(), 0x10, val1.size());
	memset(val2.begin(), 0x11, val2.size());

	if (!(d.Agree(val1, priv1, pub2) && d.Agree(val2, priv2, pub1)))
	{
		std::cout << "FAILED    simple key agreement failed" << std::endl;
		return false;
	}

	if (memcmp(val1.begin(), val2.begin(), d.AgreedValueLength()))
	{
		std::cout << "FAILED    simple agreed values not equal" << std::endl;
		return false;
	}

	std::cout << "passed    simple key agreement" << std::endl;
	return true;
}

bool AuthenticatedKeyAgreementValidate(AuthenticatedKeyAgreementDomain &d)
{
	if (d.GetCryptoParameters().Validate(GlobalRNG(), 3))
		std::cout << "passed    authenticated key agreement domain parameters validation" << std::endl;
	else
	{
		std::cout << "FAILED    authenticated key agreement domain parameters invalid" << std::endl;
		return false;
	}

	SecByteBlock spriv1(d.StaticPrivateKeyLength()), spriv2(d.StaticPrivateKeyLength());
	SecByteBlock epriv1(d.EphemeralPrivateKeyLength()), epriv2(d.EphemeralPrivateKeyLength());
	SecByteBlock spub1(d.StaticPublicKeyLength()), spub2(d.StaticPublicKeyLength());
	SecByteBlock epub1(d.EphemeralPublicKeyLength()), epub2(d.EphemeralPublicKeyLength());
	SecByteBlock val1(d.AgreedValueLength()), val2(d.AgreedValueLength());

	d.GenerateStaticKeyPair(GlobalRNG(), spriv1, spub1);
	d.GenerateStaticKeyPair(GlobalRNG(), spriv2, spub2);
	d.GenerateEphemeralKeyPair(GlobalRNG(), epriv1, epub1);
	d.GenerateEphemeralKeyPair(GlobalRNG(), epriv2, epub2);

	memset(val1.begin(), 0x10, val1.size());
	memset(val2.begin(), 0x11, val2.size());

	if (!(d.Agree(val1, spriv1, epriv1, spub2, epub2) && d.Agree(val2, spriv2, epriv2, spub1, epub1)))
	{
		std::cout << "FAILED    authenticated key agreement failed" << std::endl;
		return false;
	}

	if (memcmp(val1.begin(), val2.begin(), d.AgreedValueLength()))
	{
		std::cout << "FAILED    authenticated agreed values not equal" << std::endl;
		return false;
	}

	std::cout << "passed    authenticated key agreement" << std::endl;
	return true;
}

bool SignatureValidate(PK_Signer &priv, PK_Verifier &pub, bool thorough)
{
	bool pass = true, fail;

	fail = !pub.GetMaterial().Validate(GlobalRNG(), thorough ? 3 : 2) || !priv.GetMaterial().Validate(GlobalRNG(), thorough ? 3 : 2);
	pass = pass && !fail;

	std::cout << (fail ? "FAILED    " : "passed    ");
	std::cout << "signature key validation\n";

	const byte message[] = "test message";
	const int messageLen = 12;

	SecByteBlock signature(priv.MaxSignatureLength());
	size_t signatureLength = priv.SignMessage(GlobalRNG(), message, messageLen, signature);
	fail = !pub.VerifyMessage(message, messageLen, signature, signatureLength);
	pass = pass && !fail;

	std::cout << (fail ? "FAILED    " : "passed    ");
	std::cout << "signature and verification\n";

	++signature[0];
	fail = pub.VerifyMessage(message, messageLen, signature, signatureLength);
	pass = pass && !fail;

	std::cout << (fail ? "FAILED    " : "passed    ");
	std::cout << "checking invalid signature" << std::endl;

	if (priv.MaxRecoverableLength() > 0)
	{
		signatureLength = priv.SignMessageWithRecovery(GlobalRNG(), message, messageLen, NULLPTR, 0, signature);
		SecByteBlock recovered(priv.MaxRecoverableLengthFromSignatureLength(signatureLength));
		DecodingResult result = pub.RecoverMessage(recovered, NULLPTR, 0, signature, signatureLength);
		fail = !(result.isValidCoding && result.messageLength == messageLen && memcmp(recovered, message, messageLen) == 0);
		pass = pass && !fail;

		std::cout << (fail ? "FAILED    " : "passed    ");
		std::cout << "signature and verification with recovery" << std::endl;

		++signature[0];
		result = pub.RecoverMessage(recovered, NULLPTR, 0, signature, signatureLength);
		fail = result.isValidCoding;
		pass = pass && !fail;

		std::cout << (fail ? "FAILED    " : "passed    ");
		std::cout << "recovery with invalid signature" << std::endl;
	}

	return pass;
}

bool ValidateBBS()
{
	std::cout << "\nBlumBlumShub validation suite running...\n\n";

	Integer p("212004934506826557583707108431463840565872545889679278744389317666981496005411448865750399674653351");
	Integer q("100677295735404212434355574418077394581488455772477016953458064183204108039226017738610663984508231");
	Integer seed("63239752671357255800299643604761065219897634268887145610573595874544114193025997412441121667211431");
	BlumBlumShub bbs(p, q, seed);
	bool pass = true, fail;
	int j;

	const byte output1[] = {
		0x49,0xEA,0x2C,0xFD,0xB0,0x10,0x64,0xA0,0xBB,0xB9,
		0x2A,0xF1,0x01,0xDA,0xC1,0x8A,0x94,0xF7,0xB7,0xCE};
	const byte output2[] = {
		0x74,0x45,0x48,0xAE,0xAC,0xB7,0x0E,0xDF,0xAF,0xD7,
		0xD5,0x0E,0x8E,0x29,0x83,0x75,0x6B,0x27,0x46,0xA1};

	byte buf[20];
	std::ostringstream oss;

	bbs.GenerateBlock(buf, 20);
	fail = memcmp(output1, buf, 20) != 0;
	pass = pass && !fail;

	oss << (fail ? "FAILED    " : "passed    ");
	for (j=0;j<20;j++)
		oss << std::setw(2) << std::setfill('0') << std::hex << (int)buf[j];
	oss << std::endl;

	bbs.Seek(10);
	bbs.GenerateBlock(buf, 10);
	fail = memcmp(output1+10, buf, 10) != 0;
	pass = pass && !fail;

	oss << (fail ? "FAILED    " : "passed    ");
	for (j=0;j<10;j++)
		oss << std::setw(2) << std::setfill('0') << std::hex << (int)buf[j];
	oss << std::endl;

	bbs.Seek(1234567);
	bbs.GenerateBlock(buf, 20);
	fail = memcmp(output2, buf, 20) != 0;
	pass = pass && !fail;

	oss << (fail ? "FAILED    " : "passed    ");
	for (j=0;j<20;j++)
		oss << std::setw(2) << std::setfill('0') << std::hex << (int)buf[j];
	oss << std::endl;

	std::cout << oss.str();
	return pass;
}

bool ValidateECP()
{
	// Remove word recommend. Some ECP curves may not be recommended depending
	// on whom you ask. ECP is more descriptive item in this case.
	std::cout << "\nTesting SEC 2, NIST and Brainpool ECP curves...\n\n";
	bool pass = true; OID oid;

	while (!(oid = DL_GroupParameters_EC<ECP>::GetNextRecommendedParametersOID(oid)).GetValues().empty())
	{
		DL_GroupParameters_EC<ECP> params(oid);
		bool fail = !params.Validate(GlobalRNG(), 2);
		std::cout << (fail ? "FAILED" : "passed") << "    " << std::dec << params.GetCurve().GetField().MaxElementBitLength() << " bits\n";
		pass = pass && !fail;
	}

	std::cout << "\nECP validation suite running...\n\n";
	return ValidateECP_Agreement() && ValidateECP_Encrypt() && ValidateECP_Sign() && pass;
}

bool ValidateEC2N()
{
	// Remove word recommend. Binary curves may not be recommended depending
	// on whom you ask. EC2N is more descriptive item in this case.
	std::cout << "\nTesting SEC 2 EC2N curves...\n\n";
	bool pass = true; OID oid;

#if 1	// TODO: turn this back on when I make EC2N faster for pentanomial basis
	while (!(oid = DL_GroupParameters_EC<EC2N>::GetNextRecommendedParametersOID(oid)).GetValues().empty())
	{
		DL_GroupParameters_EC<EC2N> params(oid);
		bool fail = !params.Validate(GlobalRNG(), 2);
		std::cout << (fail ? "FAILED" : "passed") << "    " << params.GetCurve().GetField().MaxElementBitLength() << " bits\n";
		pass = pass && !fail;
	}
#endif

	std::cout << "\nEC2N validation suite running...\n\n";
	return ValidateEC2N_Agreement() && ValidateEC2N_Encrypt() && ValidateEC2N_Sign() && pass;
}

bool ValidateRSA()
{
	std::cout << "\nRSA validation suite running...\n\n";
	return ValidateRSA_Encrypt() && ValidateRSA_Sign();
}

bool ValidateLUC()
{
	std::cout << "\nLUC validation suite running...\n\n";
	return ValidateLUC_Encrypt() && ValidateLUC_Sign();
}

bool ValidateLUC_DL()
{
	// Prologue printed in each function
	return ValidateLUC_DL_Encrypt() && ValidateLUC_DL_Sign();
}

bool ValidateRabin()
{
	std::cout << "\nRabin validation suite running...\n\n";
	return ValidateRabin_Encrypt() && ValidateRabin_Sign();
}

NAMESPACE_END  // Test
NAMESPACE_END  // CryptoPP
