package aalgTest;

import java.io.IOException;

import javax.crypto.interfaces.DHPrivateKey;
import javax.crypto.interfaces.DHPublicKey;


import org.junit.Assert;
import org.junit.Test;

import aalg.algebra.DlDomainParameters;
import aalg.algebra.PrimeField;
import aalg.algebra.PrimeFieldElement;
import aalg.algebra.SubGroupDefinition;
import aalg.crypto.AesCipher;
import aalg.crypto.AnonymousApplicant;
import aalg.crypto.AnonymousChallenge;
import aalg.crypto.AnonymousController;
import aalg.crypto.AnonymousEstablisher;
import aalg.crypto.AnonymousVerificationValues;
import aalg.crypto.CbcStreamCipher;
import aalg.crypto.CtrStreamCipher;
import aalg.crypto.DHStreamCipher;
import aalg.crypto.DirectAsymmetricPadding;
import aalg.crypto.DlKeyGenerator;
import aalg.crypto.DlKeyIo;
import aalg.crypto.DlPrivateKey;
import aalg.crypto.DlPublicKey;
import aalg.crypto.DsaSignature;
import aalg.crypto.DsaSigner;
import aalg.crypto.DsaVerifier;
import aalg.crypto.HashElementKeyExtractor;
import aalg.crypto.IAssymetricKeyIo;
import aalg.crypto.IAsymmetricPadding;
import aalg.crypto.IBlockCipher;
import aalg.crypto.IElementKeyExtractor;
import aalg.crypto.IHashFunction;
import aalg.crypto.IKeyGenerator;
import aalg.crypto.IPadding;
import aalg.crypto.IPublicKeyRegister;
import aalg.crypto.IRandomGenerator;
import aalg.crypto.ISignatureVerifier;
import aalg.crypto.ISigner;
import aalg.crypto.IStreamCipher;
import aalg.crypto.ISymmetricStreamCipher;
import aalg.crypto.InternalPublicKeyRegister;
import aalg.crypto.Pkcs7Padding;
import aalg.crypto.Sha512;
import aalg.crypto.TestRandomGenerator;

public class AnonymousAuthenticationTest {

	@Test
	public void testAuthentication() throws IOException {
		final IRandomGenerator rng = new TestRandomGenerator();
		final PrimeGroupCreator groupCreator = new PrimeGroupCreator();
		final DlDomainParameters<PrimeFieldElement> domainParameters = groupCreator.createGroup();
		final SubGroupDefinition<PrimeFieldElement> subGroupDefinition = domainParameters.getPrimeSubGroup();
		final IKeyGenerator<DlPrivateKey, DlPublicKey<PrimeFieldElement>> keyGenerator = new DlKeyGenerator<>(subGroupDefinition, rng);
		final IAssymetricKeyIo<DlPrivateKey, DlPublicKey<PrimeFieldElement>> keyIo = new DlKeyIo<>(subGroupDefinition);
		
		final IHashFunction hashFunction = new Sha512();
		final IAsymmetricPadding padding = new DirectAsymmetricPadding();
		final ISigner<DlPrivateKey> signer = new DsaSigner<>(subGroupDefinition, hashFunction, padding, rng);
		
		final IBlockCipher blockCipher = new AesCipher(AesCipher.AES_128_KEY_SIZE);
		final ISymmetricStreamCipher symmetricCipher = new CtrStreamCipher(blockCipher, blockCipher, rng);
		
		final IHashFunction extractorHashFunction = new Sha512();
		final IElementKeyExtractor<PrimeFieldElement> keyExtractor = new HashElementKeyExtractor<>(domainParameters.getGroup(), extractorHashFunction);
		final DHStreamCipher<PrimeFieldElement> assymetricCipher = new DHStreamCipher<>(subGroupDefinition, rng, keyExtractor, symmetricCipher);
		
		// Generate keys
		keyGenerator.generateKeys();
		final DlPrivateKey establisherPrivateKey = keyGenerator.getPrivateKey();
		final DlPublicKey<PrimeFieldElement> establisherPublicKey = keyGenerator.getPublicKey();

		keyGenerator.generateKeys();
		final DlPrivateKey applicantPrivateKey = keyGenerator.getPrivateKey();
		final DlPublicKey<PrimeFieldElement> applicantPublicKey = keyGenerator.getPublicKey();

		final long idApp = 1;
		final long idEst = 2;
		final Long payload = 1234L;
		
		// Establish authentication values
		final AnonymousEstablisher<DlPrivateKey, DlPublicKey<PrimeFieldElement>> establisher = new AnonymousEstablisher<>(keyGenerator, keyIo, signer, assymetricCipher, idEst, establisherPrivateKey);
		final AnonymousVerificationValues<DlPublicKey<PrimeFieldElement>> verificationValues = establisher.generateVerificationValues(idApp, applicantPublicKey);

		final AnonymousController<DlPublicKey<PrimeFieldElement>, Long> controller = new AnonymousController<>(rng, assymetricCipher);
		final long payloadId = controller.addPayload(payload, verificationValues);
		
		// Authentication
		final AnonymousChallenge challenge = controller.getChallenge(payloadId);
		final ISignatureVerifier<DsaSignature, DlPublicKey<PrimeFieldElement>> verifier = new DsaVerifier<>(subGroupDefinition, extractorHashFunction, padding);
		
		final InternalPublicKeyRegister<DlPublicKey<PrimeFieldElement>> publicKeyRegister = new InternalPublicKeyRegister<>();
		publicKeyRegister.registerPublicKey(idEst, establisherPublicKey);
		
		final AnonymousApplicant<DsaSignature, DlPrivateKey, DlPublicKey<PrimeFieldElement>>  applicant = new AnonymousApplicant<>(keyIo, publicKeyRegister, verifier, assymetricCipher, applicantPrivateKey);
		final byte[] response = applicant.calculateResponse(challenge);
		
		final Long receivedPayload = controller.getPayload(challenge.getChallengeId(), response);
		Assert.assertEquals(payload, receivedPayload);
	}
}
