using System.Text;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Crypto.Prng;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities;
using Org.BouncyCastle.X509;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Operators;
using Org.BouncyCastle.Crypto.Signers;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.X509.Extension;
using X509Certificate = Org.BouncyCastle.X509.X509Certificate;

namespace Sophia.Net.DRM
{
	public static class SophiaNetDrmApi
	{

		public static X509Certificate CreateSelfSignedCertificate(string commonName, AsymmetricCipherKeyPair keyPair, string sophiaNetExtension = null, bool addSophiaNetPurpose = false)
		{
			CryptoApiRandomGenerator randomGenerator = new CryptoApiRandomGenerator();
			SecureRandom random = new SecureRandom(randomGenerator);
			
			//When acting as a certificate authority, use a proper serial number here - as an argument to this function!
			BigInteger serial = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), random);		
			
			X509V3CertificateGenerator certificateGenerator = new X509V3CertificateGenerator();
			certificateGenerator.SetSerialNumber(serial);

			X509Name subjectDn = new X509Name(String.Format("CN={0}", commonName));
			X509Name issuerDn = subjectDn;
			certificateGenerator.SetSubjectDN(subjectDn);
			certificateGenerator.SetIssuerDN(issuerDn);
			certificateGenerator.SetPublicKey(keyPair.Public);

			DateTime notBefore = DateTime.Now.Date;
			DateTime notAfter = notBefore.AddYears(100);
			certificateGenerator.SetNotBefore(notBefore);
			certificateGenerator.SetNotAfter(notAfter);

			certificateGenerator.AddExtension(X509Extensions.BasicConstraints.Id, true, new BasicConstraints(false));


			AsymmetricCipherKeyPair subjectKeyPair = keyPair;
			AsymmetricCipherKeyPair issuerKeyPair = subjectKeyPair;

			SubjectKeyIdentifier subjectKeyIdentifier = X509ExtensionUtilities.CreateSubjectKeyIdentifier(keyPair.Public);
			certificateGenerator.AddExtension(X509Extensions.SubjectKeyIdentifier.Id, false, subjectKeyIdentifier);

			AuthorityKeyIdentifier authorityKeyIdentifier = X509ExtensionUtilities.CreateAuthorityKeyIdentifier(issuerKeyPair.Public);
			certificateGenerator.AddExtension(X509Extensions.AuthorityKeyIdentifier.Id, false, authorityKeyIdentifier);

			if (!string.IsNullOrEmpty(sophiaNetExtension))
			{
				DerObjectIdentifier derObjectIdentifier = new DerObjectIdentifier("1.3.6.1.3.545127833961986897372532.1");
				certificateGenerator.AddExtension(derObjectIdentifier, false, System.Text.Encoding.UTF8.GetBytes("sophia.net self-signed certificate"));
			}

			List<DerObjectIdentifier> usageList = new List<DerObjectIdentifier>();
			usageList.Add(KeyPurposeID.id_kp_serverAuth);
			usageList.Add(KeyPurposeID.id_kp_codeSigning);
			if (addSophiaNetPurpose)
			{
				DerObjectIdentifier doi = new DerObjectIdentifier("1.3.6.1.3.545127833961986897372532.2");
				usageList.Add(doi);
			}

			certificateGenerator.AddExtension(X509Extensions.ExtendedKeyUsage.Id, false, new ExtendedKeyUsage(usageList.ToArray()));

			const string signatureAlgorithm = "SHA256WithRSA";
			Asn1SignatureFactory asn1SignatureFactory = new Asn1SignatureFactory(signatureAlgorithm, issuerKeyPair.Private, random);
			X509Certificate x509Certificate = certificateGenerator.Generate(asn1SignatureFactory);
			return x509Certificate;
		}

		public static AsymmetricCipherKeyPair GenerateKeyPair(int strength = 9216, SecureRandom random = null)
		{
			if (random == null)
			{
				CryptoApiRandomGenerator randomGenerator = new CryptoApiRandomGenerator();
				random = new SecureRandom(randomGenerator);
			}
			KeyGenerationParameters keyGenerationParameters = new KeyGenerationParameters(random, strength);

			RsaKeyPairGenerator rsaKeyPairGenerator = new RsaKeyPairGenerator();
			rsaKeyPairGenerator.Init(keyGenerationParameters);
			return rsaKeyPairGenerator.GenerateKeyPair();
		}

		public static byte[] ExportPrivateKey(AsymmetricCipherKeyPair keypair)
		{
			MemoryStream memoryStream = new MemoryStream();
			StreamWriter streamWriter = new StreamWriter(memoryStream);
			PemWriter pemWriter = new PemWriter(streamWriter);
			pemWriter.WriteObject(keypair.Private);
			streamWriter.Flush();
			memoryStream.Flush();
			return memoryStream.ToArray();
		}

		public static byte[] ExportPublicKey(AsymmetricCipherKeyPair keypair)
		{
			MemoryStream memoryStream = new MemoryStream();
			StreamWriter streamWriter = new StreamWriter(memoryStream);
			PemWriter pemWriter = new PemWriter(streamWriter);
			pemWriter.WriteObject(keypair.Public);
			streamWriter.Flush();
			memoryStream.Flush();
			return memoryStream.ToArray();
		}

		public static AsymmetricCipherKeyPair ImportKeyPairFromPrivateKey(byte[] privateBytes)
		{
			MemoryStream memoryStream = new MemoryStream(privateBytes);
			StreamReader streamReader = new StreamReader(memoryStream);
			PemReader pemReader = new PemReader(streamReader);
			object privateKey = pemReader.ReadObject();
			
			AsymmetricCipherKeyPair result = privateKey as AsymmetricCipherKeyPair;
			return result;
		}

		public static byte[] ExportCertificate(X509Certificate certificate)
		{
			return certificate.GetEncoded();
		}

		public static byte[] ExportCertificateAsPkcs12(AsymmetricKeyParameter privateKey, string password, params X509Certificate[] certificate)
		{
			CryptoApiRandomGenerator randomGenerator = new CryptoApiRandomGenerator();
			SecureRandom random = new SecureRandom(randomGenerator);

			Pkcs12StoreBuilder builder = new Pkcs12StoreBuilder();
			Pkcs12Store store = builder.Build();

			string[] friendlyNames = new string[certificate.Length];
			X509CertificateEntry[] certificateEntries = new X509CertificateEntry[certificate.Length];
			for (int i = 0; i < certificate.Length; i++)
			{
				friendlyNames[i] = certificate[i].SubjectDN.ToString();
				certificateEntries[i] = new X509CertificateEntry(certificate[i]);
			}

			store.SetCertificateEntry(friendlyNames[0], certificateEntries[0]);
			store.SetKeyEntry(friendlyNames[0], new AsymmetricKeyEntry(privateKey), certificateEntries);
			
			MemoryStream ms = new MemoryStream();
			store.Save(ms, password.ToCharArray(), random);
			return ms.ToArray();
		}

		public static X509Certificate CreateRootCertificate(string commonName, AsymmetricCipherKeyPair keyPair)
		{
			CryptoApiRandomGenerator randomGenerator = new CryptoApiRandomGenerator();
			SecureRandom random = new SecureRandom(randomGenerator);

			//When issuing child certificates, use a proper serial number here - as an argument to this function!
			BigInteger serial = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), random);

			X509V3CertificateGenerator certificateGenerator = new X509V3CertificateGenerator();
			certificateGenerator.SetSerialNumber(serial);

			X509Name subjectDn = new X509Name(String.Format("CN={0}", commonName));
			X509Name issuerDn = subjectDn;
			certificateGenerator.SetSubjectDN(subjectDn);
			certificateGenerator.SetIssuerDN(issuerDn);
			certificateGenerator.SetPublicKey(keyPair.Public);

			DateTime notBefore = DateTime.Now.Date;
			DateTime notAfter = notBefore.AddYears(100);
			certificateGenerator.SetNotBefore(notBefore);
			certificateGenerator.SetNotAfter(notAfter);

			//BasicConstraints basicConstraints = new BasicConstraints(true);
			//certificateGenerator.AddExtension(X509Extensions.BasicConstraints.Id, true, new BasicConstraints(true));
			certificateGenerator.AddExtension(X509Extensions.BasicConstraints.Id, true, new BasicConstraints(2));

			AsymmetricCipherKeyPair subjectKeyPair = keyPair;
			AsymmetricCipherKeyPair issuerKeyPair = subjectKeyPair;

			SubjectKeyIdentifier subjectKeyIdentifier = X509ExtensionUtilities.CreateSubjectKeyIdentifier(keyPair.Public);
			certificateGenerator.AddExtension(X509Extensions.SubjectKeyIdentifier.Id, false, subjectKeyIdentifier);

			AuthorityKeyIdentifier authorityKeyIdentifier = X509ExtensionUtilities.CreateAuthorityKeyIdentifier(issuerKeyPair.Public);
			certificateGenerator.AddExtension(X509Extensions.AuthorityKeyIdentifier.Id, false, authorityKeyIdentifier);
			
			const string signatureAlgorithm = "SHA256WithRSA";
			Asn1SignatureFactory asn1SignatureFactory = new Asn1SignatureFactory(signatureAlgorithm, issuerKeyPair.Private, random);
			X509Certificate x509Certificate = certificateGenerator.Generate(asn1SignatureFactory);
			return x509Certificate;

		}

		public static X509Certificate CreateChildCertificate(string commonName, X509Certificate issuerCert, AsymmetricCipherKeyPair issuerPrivateKey, AsymmetricKeyParameter childPublicKey, long serialNumber, string sophiaNetExtensionValue)
		{
			return CreateChildCertificate(commonName, issuerCert, issuerPrivateKey, childPublicKey, serialNumber, Encoding.UTF8.GetBytes(sophiaNetExtensionValue));
		}

		public static X509Certificate CreateChildCertificate(string commonName, X509Certificate issuerCert, AsymmetricCipherKeyPair issuerPrivateKey, AsymmetricKeyParameter childPublicKey, long serialNumber, SophiaNetEntitlementCollection entitlements)
		{
			byte[] serialize = entitlements.Serialize();
			return CreateChildCertificate(commonName, issuerCert, issuerPrivateKey, childPublicKey, serialNumber, serialize);
		}
		
		public static X509Certificate CreateChildCertificate(string commonName, X509Certificate issuerCert, AsymmetricCipherKeyPair issuerPrivateKey, AsymmetricKeyParameter childPublicKey, long serialNumber, byte[] sophiaNetExtensionValue)
		{
			CryptoApiRandomGenerator randomGenerator = new CryptoApiRandomGenerator();
			SecureRandom random = new SecureRandom(randomGenerator);

			BigInteger serial = BigInteger.ValueOf(serialNumber);

			X509Name subjectDn = new X509Name(String.Format("CN={0}", commonName));

			DateTime notBefore = DateTime.Now.Date;
			DateTime notAfter = issuerCert.NotAfter;

			DerObjectIdentifier sophiaNetExtensionId = new DerObjectIdentifier("1.3.6.1.3.545127833961986897372532.1");

			X509V3CertificateGenerator certificateGenerator = new X509V3CertificateGenerator();
			certificateGenerator.SetSerialNumber(serial);
			certificateGenerator.SetSubjectDN(subjectDn);
			certificateGenerator.SetIssuerDN(issuerCert.SubjectDN);
			certificateGenerator.SetPublicKey(childPublicKey);
			certificateGenerator.SetNotBefore(notBefore);
			certificateGenerator.SetNotAfter(notAfter);
			certificateGenerator.AddExtension(X509Extensions.BasicConstraints.Id, true, new BasicConstraints(false));
			certificateGenerator.AddExtension(X509Extensions.ExtendedKeyUsage.Id, false, new ExtendedKeyUsage(KeyPurposeID.id_kp_clientAuth));
			certificateGenerator.AddExtension(sophiaNetExtensionId, false, sophiaNetExtensionValue);

			const string signatureAlgorithm = "SHA256WithRSA";
			Asn1SignatureFactory asn1SignatureFactory = new Asn1SignatureFactory(signatureAlgorithm, issuerPrivateKey.Private, random);
			X509Certificate x509Certificate = certificateGenerator.Generate(asn1SignatureFactory);
			return x509Certificate;
		}

		public static X509Certificate ImportCertificate(byte[] encoded)
		{
			X509Certificate result = new X509Certificate(encoded);
			return result;
		}

		public static bool ValidateCertificateChain(X509Certificate child, X509Certificate issuer)
		{
			try
			{
				child.Verify(issuer.GetPublicKey());
				return true;
			}
			catch (Exception e)
			{
				return false;
			}
		}

		public static X509Crl CreateCertificateRevocationList(X509Certificate issuer, AsymmetricCipherKeyPair issuerPrivateKey, params long[] revokedSerials)
		{
			CryptoApiRandomGenerator randomGenerator = new CryptoApiRandomGenerator();
			SecureRandom random = new SecureRandom(randomGenerator);

			long notAfterSeconds = issuer.NotAfter.Ticks / TimeSpan.TicksPerSecond;
			long nowSeconds = DateTime.Now.Ticks / TimeSpan.TicksPerSecond;
			long secondsDifference = notAfterSeconds - nowSeconds;
			if (secondsDifference > Int32.MaxValue)
				secondsDifference = Int32.MaxValue;
			long _30days = new TimeSpan(30, 0, 0, 0).Ticks / TimeSpan.TicksPerSecond;

			int max = (int)secondsDifference;
			int min = (int)_30days;
			int updateDelayInt = random.Next(min, max);
			TimeSpan updateDelayTimespan = TimeSpan.FromSeconds(updateDelayInt);

			X509V2CrlGenerator crlGenerator = new X509V2CrlGenerator();
			crlGenerator.SetIssuerDN(issuer.IssuerDN);
			crlGenerator.SetThisUpdate(DateTime.Now);
			crlGenerator.SetNextUpdate(DateTime.Now + updateDelayTimespan);
			crlGenerator.AddExtension(X509Extensions.AuthorityKeyIdentifier, false, X509ExtensionUtilities.CreateAuthorityKeyIdentifier(issuer));
			crlGenerator.AddExtension(X509Extensions.CrlNumber, false, new CrlNumber(BigInteger.One));
			
			foreach (long revokedSerial in revokedSerials)
			{
				crlGenerator.AddCrlEntry(BigInteger.ValueOf(revokedSerial),DateTime.Now, CrlReason.PrivilegeWithdrawn);
			}

			const string signatureAlgorithm = "SHA256WithRSA";
			Asn1SignatureFactory signatureFactory = new Asn1SignatureFactory(signatureAlgorithm, issuerPrivateKey.Private, random);
			X509Crl crl = crlGenerator.Generate(signatureFactory);
			return crl;
		}

		public static byte[] ExportCrl(X509Crl crl)
		{
			return crl.GetEncoded();
		}

		public static X509Crl ImportCrl(byte[] buffer)
		{
			X509Crl result = new X509Crl(buffer);
			return result;
		}

		public static bool ValidateCertificateAgainstCrl(X509Certificate certificate, X509Crl crl)
		{
			BigInteger mySerial = certificate.SerialNumber;
			ISet<X509CrlEntry> x509CrlEntries = crl.GetRevokedCertificates();
			foreach (X509CrlEntry entry in x509CrlEntries)
			{
				BigInteger entrySerial = entry.SerialNumber;
				if (entrySerial.Equals(mySerial))
					return false;
			}

			return true;
		}

		public static byte[] GetSophiaNetExtensionFromCertificate(X509Certificate cert)
		{
			DerObjectIdentifier sophiaNetExtensionId = new DerObjectIdentifier("1.3.6.1.3.545127833961986897372532.1");
			X509Extension x509Extension = cert.GetExtension(sophiaNetExtensionId);
			Asn1OctetString octetString = x509Extension.Value;
			Stream octetStream = octetString.Parser.GetOctetStream();
			Asn1InputStream asn1Reader = new Asn1InputStream(octetStream);
			DerOctetString readObject = (DerOctetString)asn1Reader.ReadObject();
			byte[] result = readObject.GetOctets();
			return result;
		}

		public static byte[] SignFile(byte[] toBeSigned, AsymmetricKeyParameter privateKey)
		{
			RsaDigestSigner signer = new RsaDigestSigner(new Sha256Digest());
			signer.Init(true, privateKey);
			signer.BlockUpdate(toBeSigned, 0, toBeSigned.Length);
			return signer.GenerateSignature();
		}

		public static bool ValidateSignedFile(byte[] signed, byte[] signature, AsymmetricKeyParameter publicKey)
		{
			RsaDigestSigner signer = new RsaDigestSigner(new Sha256Digest());
			signer.Init(false, publicKey);
			signer.BlockUpdate(signed, 0, signed.Length);

			bool isValid = signer.VerifySignature(signature);
			return isValid;
		}
	}
}
