/* $Id:  XmlEnc_EncryptXml.cs $ 
 *   Last updated:
 *   $Date: 2020-12-07 03:17:00 $
 *   $Version: 1.0.0 $
 */

/* Ref: 
 * [Using CryptoSys PKI to encrypt and decrypt using XMLENC](https://www.cryptosys.net/pki/xmlenc-encrypt-decrypt.html)
 */

using System;
using System.Text;
using System.Diagnostics;
using CryptoSysPKI;

/******************************* LICENSE ***********************************
 * Copyright (C) 2020 David Ireland, DI Management Services Pty Limited.
 * All rights reserved. <www.di-mgt.com.au> <www.cryptosys.net>
 * The code in this module is licensed under the terms of the MIT license.  
 * For a copy, see <http://opensource.org/licenses/MIT>
****************************************************************************
*/

namespace XmlEnc
{
    class XmlEnc_EncryptXml
    {
        static void Main(string[] args)
        {
            string cipherValue;
            Console.WriteLine("Ver={0}", General.Version());

            /////////////////////////////////
            // Encrypt block cipher CBC mode
            /////////////////////////////////

            // NOTE: these won't reproduce the exact same CipherValues as the examples, because of the "arbitrary" bytes in the W3C padding.
            // The output will be the same at the start but not at the end. Hey, it shows you are on the right track!

            cipherValue = xmlenc_encrypt_cbc_data("top secret message\n", "6162636465666768696A6B6C6D6E6F70", CipherAlgorithm.Aes128, "40CA71857AB50ED05EC82F4A7D268C41");
            Console.WriteLine(cipherValue);
            cipherValue = xmlenc_encrypt_cbc_data("top secret message\n", "6162636465666768696A6B6C6D6E6F70", CipherAlgorithm.Aes128);
            Console.WriteLine(cipherValue);
            cipherValue = xmlenc_encrypt_cbc_data("top secret message\n", "6162636465666768696A6B6C6D6E6F707172737475767778797A303132333435", CipherAlgorithm.Aes256, "E7496FF7877F0C5262AC95D83945DA06");
            Console.WriteLine(cipherValue);
            string cv = @"0wkECpTy60/FDwbVM4zgd9qJVjR4h0q4PLm5pyyIxAuhbEh0art03yEikmbWBt2H
        7qOk2G9iufUdwwqNPuZV5Qw5Rg2FMvTx234lDERGn5p+hhjOTcss5JF9QDzgdiec
        KABX3vbCESi/f3uwQ8BYDT+6SnxTR+xtcNv5xhbUCIFk/TaenSWx6p6fntTwTl1e
        lpwnI0EtM1yf4a9tBiH9PNd36BUv2rvSi4cZvJqSB3ZKvGtuwwyRzOzlzl259d1u
        QuoYysTBEAHw/WIop8eAexU9PUv7UbTkQAQag1yStda+GepVdpXEpu4hcxXQcvfs
        9AQgkAgh4JKrnY4Bhz2B/e4CHHfbEedDOi+FVYlZuLn0CzrKMnM+1nUmqxJVWHz7
        hytidpuqNRw3gcMkYvgH6g==";
            Console.WriteLine("CV=" + Cnv.HexFromBase64(cv));
            string elemPaymentInfo =
@"<PaymentInfo>
    <BillingAddress>
      Dig PLC, 1 First Ave, Dublin 1, Ireland
    </BillingAddress>
    <CreditCard Type=""Amex"">
      <Name>Foo B Baz</Name>
      <Number>1234 567890 12345</Number>
      <Expires Month=""1"" Year=""2005"" />
    </CreditCard>
  </PaymentInfo>";
            cipherValue = xmlenc_encrypt_cbc_data(elemPaymentInfo, "493185CE8177AC09C4DE6C7C75524B16", CipherAlgorithm.Aes128, "D309040A94F2EB4FC50F06D5338CE077");
            Console.WriteLine(cipherValue);



            /////////////////////////////////////
            // Encrypt block cipher AES-GCM mode
            /////////////////////////////////////

            cipherValue = xmlenc_encrypt_gcm("top secret message\n", "6162636465666768696A6B6C6D6E6F707172737475767778797A303132333435", AeadAlgorithm.Aes_256_Gcm, "0102030405060708090A0B0C");
            Console.WriteLine("CipherValue={0}", cipherValue);

            // Encrypt CEK with key wrap KEK
            cipherValue = xmlenc_encrypt_kw("F65CD2C48C1DA5565ED0AAB2D042713373C74C8E69332F2F", "6162636465666768696A6B6C6D6E6F707172737475767778797A303132333435", CipherAlgorithm.Aes256);
            Console.WriteLine("CipherValue(CEK)={0}", cipherValue);
            Debug.Assert(cipherValue.Length > 0);

            cipherValue = xmlenc_encrypt_rsa_15("493185CE8177AC09C4DE6C7C75524B16", "rsa-w3c.crt");
            Console.WriteLine("CipherValue(CEK)={0}", cipherValue);
            Debug.Assert(cipherValue.Length > 0);

            // RSA-OAEP
            cipherValue = xmlenc_encrypt_rsa_oaep("E2493529B46E23907FC77AB0C4DD74BE", "bob-smime.crt", Rsa.HashAlg.Sha256, useMgf1SHA1 : true);
            Console.WriteLine("CipherValue(RSA-OAEP)={0}", cipherValue);
            Debug.Assert(cipherValue.Length > 0);
            cipherValue = xmlenc_encrypt_rsa_oaep("F45F70F9645F73B3CB88DBD17EBA881B", "bob-smime.crt", Rsa.HashAlg.Sha256);
            Console.WriteLine("CipherValue(RSA-OAEP)={0}", cipherValue);
            Debug.Assert(cipherValue.Length > 0);

        }

        /////////////////////////////////
        // GENERIC ENCRYPTION FUNCTIONS
        /////////////////////////////////

        /// <summary>
        /// Encrypt data using block cipher in CBC mode as per W3C XMLENC.
        /// </summary>
        /// <param name="strData">Data to be encrypted as Unicode string</param>
        /// <param name="keyHex">Key in hex encoding of exact required length for algorithm (16|24|32 bytes)</param>
        /// <param name="cipherAlg">Block cipher algorithm</param>
        /// <param name="ivHex">Optional IV in hex encoding for testing [default = generate random IV]</param>
        /// <returns>Base64-encoded CipherValue = BASE64(IV|CT) or empty string on error.</returns>
        static string xmlenc_encrypt_cbc_data(string strData, string keyHex, CipherAlgorithm cipherAlg, string ivHex = "")
        {
            byte[] iv;
            if (ivHex.Length > 0) {
                // IV provided for testing purposes...
                iv = Cnv.FromHex(ivHex);
            } else {
                // Generate a random IV...
                iv = Rng.Bytes(Cipher.BlockBytes(cipherAlg));
            }
            byte[] data = System.Text.Encoding.UTF8.GetBytes(strData); // Default character encoding for XML documents
            byte[] ct = Cipher.Encrypt(data, Cnv.FromHex(keyHex), iv, cipherAlg, Mode.CBC, Padding.W3CPadding, Cipher.Opts.PrefixIV);
            // Why isn't this the same as the example?
            return Cnv.ToBase64(ct);
        }
        
        /// <summary>
        /// Encrypt data using block cipher in AES-GCM mode as per W3C XMLENC.
        /// </summary>
        /// <param name="strData">Data to be encrypted as Unicode string.</param>
        /// <param name="keyHex">Key in hex encoding of exact required length for algorithm (16|24|32 bytes)</param>
        /// <param name="aeadAlg">Authenticated encryption algorithm</param>
        /// <param name="ivHex">Optional IV in hex encoding for testing [default = generate random IV]</param>
        /// <returns>Base64-encoded CipherValue = BASE64(IV|CT|TAG) or empty string on error.</returns>
        static string xmlenc_encrypt_gcm(string strData, string keyHex, AeadAlgorithm aeadAlg, string ivHex = "")
        {
            byte[] iv;
            if (ivHex.Length > 0) {
                // IV provided for testing purposes...
                iv = Cnv.FromHex(ivHex);
            } else {
                // Generate a random IV, always 12 bytes...
                iv = Rng.Bytes(12);
            }
            byte[] data = System.Text.Encoding.UTF8.GetBytes(strData); // Default character encoding for XML documents
            byte[] ct = Cipher.EncryptAEAD(data, Cnv.FromHex(keyHex), iv, null, aeadAlg, Cipher.Opts.PrefixIV);
            return Cnv.ToBase64(ct);
        }

        /// <summary>
        /// Encrypt key material using key wrap.
        /// </summary>
        /// <param name="cekHex">Hex-encoded key material to be wrapped.</param>
        /// <param name="kekHex">Key-encryption key in hex encoding of exact required length for algorithm (16|24|32 bytes).</param>
        /// <param name="cipherAlg">Cipher algorithm for key wrap.</param>
        /// <returns>Base64-encoded wrapped key or empty string on error.</returns>
        static string xmlenc_encrypt_kw(string cekHex, string kekHex, CipherAlgorithm cipherAlg)
        {
            byte[] encKey = Cipher.KeyWrap(Cnv.FromHex(cekHex), Cnv.FromHex(kekHex), cipherAlg);
            return Cnv.ToBase64(encKey);
        }

        /// <summary>
        /// Encrypt key material using RSA-v1.5
        /// </summary>
        /// <param name="cekHex">Hex-encoded key material to be wrapped.</param>
        /// <param name="pubkeyFile">File containing public key or X.509 certificate, or key or certificate as a PEM string.</param>
        /// <returns>Base64-encoded enveloped key or empty string on error.</returns>
        static string xmlenc_encrypt_rsa_15(string cekHex, string pubkeyFile)
        {
            byte[] encKey = Rsa.Encrypt(Cnv.FromHex(cekHex), pubkeyFile);
            return Cnv.ToBase64(encKey);
        }

        /// <summary>
        /// Encrypt key material using RSA-OAEP
        /// </summary>
        /// <param name="cekHex">Hex-encoded key material to be wrapped.</param>
        /// <param name="pubkeyFile">File containing public key or X.509 certificate, or key or certificate as a PEM string.</param>
        /// <param name="hashAlg">Message digest function to use for the DigestMethod</param>
        /// <param name="useMgf1SHA1">Use mask generation function <c>MGF1 with SHA1</c> [default = use same digest function as <c>hashAlg</c>]</param>
        /// <returns>Base64-encoded enveloped key or empty string on error.</returns>
        static string xmlenc_encrypt_rsa_oaep(string cekHex, string pubkeyFile, Rsa.HashAlg hashAlg, bool useMgf1SHA1 = false)
        {
            byte[] encKey = Rsa.Encrypt(Cnv.FromHex(cekHex), pubkeyFile, Rsa.EME.OAEP, hashAlg, (useMgf1SHA1 ? Rsa.AdvOptions.Mgf1_Sha1 : Rsa.AdvOptions.Default));
            return Cnv.ToBase64(encKey);
        }


    }
}