/*  $Id: PopulateKeyInfo.cs $ 
 *   Last updated:
 *   $Date: 2022-02-13 10:23:00 $
 *   $Version: 1.0.0 $
 */

/******************************* LICENSE ***********************************
 * Copyright (C) 2022 David Ireland, DI Management Services Pty Limited.
 * All rights reserved. <https://di-mgt.com.au> <https://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>
****************************************************************************
*/

using System;
using System.Text;
using System.IO;
using System.Diagnostics;

/*
 * Requires DI Management CryptoSys Library available from <https://cryptosys.net/>.
 * 1. CryptoSys PKI Pro: https://cryptosys.net/pki/
 * EITHER add reference to
 * `diCrSysPKINet.dll` (installed by default in `C:\Program Files (x86)\CryptoSysPKI\DotNet`)
 * OR add the C# source code file `CryptoSysPKI.cs`
 * directly to your project.
 */
using Pki = CryptoSysPKI;

namespace DIManagement.PopulateKeyInfo
{
    class PopulateKeyInfo
    {
        // INLINE TEMPLATES...

        // Edit these to suit requirements, e.g. remove "ds:" prefix, change white-space, remove unwanted nodes, etc.
        const string KeyInfoTemplate = @"<ds:KeyInfo Id=""xmldsig-@!GUID!@-keyinfo"">
<ds:KeyValue>
<ds:RSAKeyValue>
<ds:Modulus>@!RSA-MOD!@</ds:Modulus>
<ds:Exponent>@!RSA-EXP!@</ds:Exponent>
</ds:RSAKeyValue>
</ds:KeyValue>
<ds:X509Data>
<ds:X509Certificate>@!X509-CERT!@</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
";

        const string xadesSigningCertTemplate = @"<xades:SigningCertificate>
<xades:Cert>
<xades:CertDigest>
<ds:DigestMethod Algorithm=""@!CERT-DIG-ALG!@"" />
<ds:DigestValue>@!DIG-CERT!@</ds:DigestValue>
</xades:CertDigest>
<xades:IssuerSerial>
<ds:X509IssuerName>@!X509-ISSUERNAME!@</ds:X509IssuerName>
<ds:X509SerialNumber>@!X509-SERIALNUMBER!@</ds:X509SerialNumber>
</xades:IssuerSerial>
</xades:Cert>
</xades:SigningCertificate>
";

        static void Main(string[] args)
        {
            string s;
            s = MakeKeyInfo("AliceRSASignByCarl.cer");
            Console.WriteLine(s);
            s = MakeXadesSigningCert("AliceRSASignByCarl.cer");
            Console.WriteLine(s);
            s = MakeXadesSigningCert("AliceRSASignByCarl.cer", useSha256: true);
            Console.WriteLine(s);
        }

        /// <summary>
        /// Complete the KeyInfo template for given X.509 certificate
        /// </summary>
        /// <param name="certFileOrString">Filename of X.509 certificate or string containing base64 or PEM representation</param>
        /// <returns>String containing completed XML element, or message starting with "**ERROR"</returns>
        static string MakeKeyInfo(string certFileOrString)
        {
            string strMsg;
            string s, xs;
            string pubKey, certStr;

            // Get template
            s = KeyInfoTemplate;

            // Read in certificate as a one-line base64 string
            certStr = Pki.X509.ReadStringFromFile(certFileOrString);
            if (certStr.Length == 0) {
                strMsg = String.Format("**ERROR: Failed to read X.509 certificate in '{0}'", certFileOrString);
                return strMsg;
            }
            // We will use the cert string from now on...

            // Extract the RSA public key from the certificate
            pubKey = Pki.Rsa.ReadPublicKey(certStr).ToString();
            if (pubKey.Length == 0) {
                strMsg = String.Format("**ERROR: Could not extract public key from certificate");
                return strMsg;
            }

            // Compute and substitute values for placeholders
            s = s.Replace("@!X509-CERT!@", certStr);

            xs = Pki.Rng.Guid(); // NB different each time
            s = s.Replace("@!GUID!@", xs);

            // Compute and substitute RSAKeyValue components
            xs = Pki.Rsa.KeyValue(pubKey, "Modulus");
            s = s.Replace("@!RSA-MOD!@", xs);
            xs = Pki.Rsa.KeyValue(pubKey, "Exponent");
            s = s.Replace("@!RSA-EXP!@", xs);

            return s;
        }
        /// <summary>
        /// Complete the <c>xades:SigningCertificate</c> template for given X.509 certificate
        /// </summary>
        /// <param name="certFileOrString">Filename of X.509 certificate or string containing base64 or PEM representation</param>
        /// <param name="useSha256">If true, use SHA-256 algorithm for CertDigest value (default = use SHA-1)</param>
        /// <returns>String containing completed XML element, or message starting with "**ERROR"</returns>
        static string MakeXadesSigningCert(string certFileOrString, bool useSha256 = false)
        {
            const string DIGALG = "http://www.w3.org/2000/09/xmldsig#sha1";
            const string DIGALG256 = "http://www.w3.org/2001/04/xmlenc#sha256";

            string strMsg;
            string s, xs;
            string certStr;
            Pki.HashAlgorithm hashAlg = Pki.HashAlgorithm.Sha1;

            // Get template
            s = xadesSigningCertTemplate;

            // Read in certificate as a one-line base64 string
            certStr = Pki.X509.ReadStringFromFile(certFileOrString);
            if (certStr.Length == 0) {
                strMsg = String.Format("**ERROR: Failed to read X.509 certificate in '{0}'", certFileOrString);
                return strMsg;
            }

            // Extract and substitute values for IssuerSerial
            xs = Pki.X509.QueryCert(certStr, "serialNumber", Pki.X509.OutputOpts.Decimal);
            s = s.Replace("@!X509-SERIALNUMBER!@", xs);
            xs = Pki.X509.QueryCert(certStr, "issuerName", Pki.X509.OutputOpts.Ldap);
            s = s.Replace("@!X509-ISSUERNAME!@", xs);

            // Compute CertDigest value
            if (useSha256) {
                // Use SHA-256 algorithm
                s = s.Replace("@!CERT-DIG-ALG!@", DIGALG256);
                hashAlg = Pki.HashAlgorithm.Sha256;
            } else {
                // Default SHA-1
                s = s.Replace("@!CERT-DIG-ALG!@", DIGALG);
            }
            xs = Pki.Cnv.ToBase64(Pki.Cnv.FromHex(Pki.X509.CertThumb(certStr, hashAlg)));
            s = s.Replace("@!DIG-CERT!@", xs);


            return s;
        }
    }
}