// From:
        //    http://blogs.interfacett.com/selecting-a-cryptographic-key-provider-in-windows-server-2012-ad-cs
        //    https://msdn.microsoft.com/en-us/library/windows/desktop/aa386983(v=vs.85).aspx
        //
        // Microsoft Base Smart Card Crypto Provider
        // Microsoft Enhanced Cryptographic Provider v1.0
        // ECDA_P256#Microsoft Smart Card Key Storage Provider
        // ECDA_P521#Microsoft Smart Card Key Storage Provider
        // RSA#Microsoft Software Key Storage Provider
        // Microsoft Base Cryptographic Provider v1.0
        // ECDA_P256#Microsoft Software Key Storage Provider
        // ECDA_P521#Microsoft Software Key Storage Provider
        // Microsoft Strong Cryptographic Provider
        // ECDA_P384#Microsoft Software Key Storage Provider
        // Microsoft Base DSS Cryptographic Provider
        // RSA#Microsoft Smart Card Key Storage Provider
        // DSA#Microsoft Software Key Storage Provider
        // ECDA_P384#Microsoft Smart Card Key Storage Provider


        public override PrivateKey GeneratePrivateKey(PrivateKeyParams pkp)
        {
            var rsaPkp = pkp as RsaPrivateKeyParams;
            var ecPkp  = pkp as EcPrivateKeyParams;

            var algId = new CERTENROLLLib.CObjectId();

            if (rsaPkp != null)
            {
                var oid = new System.Security.Cryptography.Oid("RSA");
                algId.InitializeFromValue(oid.Value);
            }
            else if (ecPkp != null)
            {
                throw new NotImplementedException("EC keys not implemented YET!");
            }
            else
            {
                throw new NotSupportedException("unsupported private key parameters type");
            }


            var cePk = new CERTENROLLLib.CX509PrivateKey();

            // MS_DEF_PROV
            //cePk.ProviderName = "Microsoft Base Cryptographic Provider";
            cePk.ProviderName = "Microsoft Enhanced Cryptographic Provider v1.0";

            //cePk.ProviderType = CERTENROLLLib.X509ProviderType.XCN_PROV_RSA_FULL;
            cePk.Algorithm = algId;
            cePk.KeySpec   = CERTENROLLLib.X509KeySpec.XCN_AT_KEYEXCHANGE;
            cePk.Length    = rsaPkp.NumBits;

            // Don't store in the machine's local cert store and allow exporting of private key
            cePk.MachineContext = false;
            cePk.ExportPolicy   = CERTENROLLLib.X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_EXPORT_FLAG;
            cePk.Create();

            var pk = new CeRsaPrivateKey(rsaPkp.NumBits, null, null)
            {
                Exported = cePk.Export(BCRYPT_PRIVATE_KEY_BLOB),
            };

            return(pk);
        }
        public override Csr GenerateCsr(CsrParams csrParams, PrivateKey pk, Crt.MessageDigest md)
        {
            var rsaPk = pk as CeRsaPrivateKey;

            if (rsaPk != null)
            {
                var cePk = new CERTENROLLLib.CX509PrivateKey();

                // MS_DEF_PROV
                //cePk.ProviderName = "Microsoft Base Cryptographic Provider";
                cePk.ProviderName = "Microsoft Enhanced Cryptographic Provider v1.0";

                // Don't store in the machine's local cert store and allow exporting of private key
                cePk.MachineContext = false;
                cePk.ExportPolicy   = CERTENROLLLib.X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_EXPORT_FLAG;

                cePk.Import(BCRYPT_PRIVATE_KEY_BLOB, rsaPk.Exported);

                var ceReq = new CERTENROLLLib.CX509CertificateRequestCertificate();
                ceReq.InitializeFromPrivateKey(
                    CERTENROLLLib.X509CertificateEnrollmentContext.ContextUser,
                    cePk, "");

                // CN=Test Cert, OU=Sandbox
                var subjParts = new[]
                {
                    new { name = "C", value = csrParams?.Details?.Country },
                    new { name = "ST", value = csrParams?.Details?.StateOrProvince },
                    new { name = "L", value = csrParams?.Details?.Locality },
                    new { name = "O", value = csrParams?.Details?.Organization },
                    new { name = "OU", value = csrParams?.Details?.OrganizationUnit },
                    new { name = "CN", value = csrParams?.Details?.CommonName },
                    new { name = "E", value = csrParams?.Details?.Email },
                };

                // Escape any non-standard character
                var re   = new Regex("[^A-Za-z0-9\\._-]");
                var subj = "";
                foreach (var sp in subjParts)
                {
                    if (!string.IsNullOrEmpty(sp.value))
                    {
                        var spVal = re.Replace(sp.value, "\\$0");
                        subj += $",{sp.name}={spVal}";
                    }
                }
                if (string.IsNullOrEmpty(subj))
                {
                    throw new InvalidOperationException("invalid CSR details");
                }
                subj = subj.Substring(1); // Skip over the first comma

                // http://msdn.microsoft.com/en-us/library/aa377051(VS.85).aspx
                var subjDN = new CERTENROLLLib.CX500DistinguishedName();
                subjDN.Encode(subj);
                ceReq.Subject = subjDN;

                if (csrParams.NotBefore != null)
                {
                    ceReq.NotBefore = csrParams.NotBefore.Value;
                }
                if (csrParams.NotAfter != null)
                {
                    ceReq.NotAfter = csrParams.NotAfter.Value;
                }

                var mdVal = Enum.GetName(typeof(Crt.MessageDigest), md);
                var mdOid = new System.Security.Cryptography.Oid(mdVal);
                var mdAlg = new CERTENROLLLib.CObjectId();
                mdAlg.InitializeFromValue(mdOid.Value);
                ceReq.SignatureInformation.HashAlgorithm = mdAlg;
                ceReq.Encode();

                var csr = new Csr(ceReq.RawData);
                return(csr);
            }
            else
            {
                throw new NotSupportedException("unsuppored private key type");
            }
        }