【问题标题】:How to fix Access Denied while reading X509Certificate2如何在读取 X509Certificate2 时修复拒绝访问
【发布时间】:2019-08-09 10:07:47
【问题描述】:

我正在创建一个 API 以通过私有应用程序登录 Xero,因此我按照他们的说明和 Xero API 文档来了解它是如何工作的。链接here

因此,我创建了一个应用程序,并使用 OpenSSL 生成了一个公共证书(.cer 文件),并为此创建了“.pfx 文件”,并将该文件附加到了 Notes 实体中的 Dynamics CRM 中。

现在,当我运行我的 C# 代码以使用相同的公钥/私钥对登录时,它按预期工作。 (从 CRM 中检索到私钥/公钥对)并按预期传递给登录代码(代码已粘贴在下面)

我的下一步是使用 C# 以编程方式创建这些公钥/私钥对,并使用以下代码创建它。

// Generate a RSA Key
RSA RSAKey = RSA.Create(1024);

// Creating Certificate Request with relevant parameters
var req = new CertificateRequest(subjectNameDN, RSAKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);

// Creating self-signed certificate with an expiry defined in app settings
X509Certificate2 cert = req.CreateSelfSigned(DateTimeOffset.Now, DateTimeOffset.Now.AddYears(expiryDurationInYears));

subjectNameDN 变量以字符串格式包含我的 OrganizationName、OrganizationUnitName 和 CommonName。

在这一步之后,我要做的是创建一个 .pfx 文件和一个 .zip 文件(zip 文件中包含 .cert 文件)并将这两个文件附加到 Dynamics CRM 中的备注记录。

下面是代码

创建 .pfx 文件并附加到 CRM 中的注释:

Entity annotationPfx = new Entity("annotation");
annotationPfx.Attributes["subject"] = "public_privatekey pfx";
annotationPfx.Attributes["filename"] = "public_privatekey.pfx";
annotationPfx.Attributes["documentbody"] = Convert.ToBase64String(cert .Export(X509ContentType.Pfx, myPassword));
annotationPfx.Attributes["mimetype"] = "application/x-pkcs12";
annotationPfx.Attributes["isdocument"] = true;
_crmServiceClient.Create(annotationPfx);

创建包含 .pfx 文件的 zip 文件,并附加到 CRM 中的注释:

 var certVal = "-----BEGIN CERTIFICATE-----" + Environment.NewLine + Convert.ToBase64String(cert .Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks) + Environment.NewLine + "-----END CERTIFICATE-----";
 byte[] certificateInBytes = System.Text.Encoding.UTF8.GetBytes(certVal);
 byte[] compressedCertInBytes;
 string certFileName = "MyCertificate.cer";
 using (var outStream = new MemoryStream())
    {
       using (var archive = new ZipArchive(outStream, ZipArchiveMode.Create, true))
       {
           var fileInArchive = archive.CreateEntry(certFileName, CompressionLevel.Optimal);
           using (var entryStream = fileInArchive.Open())
           using (var fileToCompressStream = new MemoryStream(certificateInBytes))
            {
                fileToCompressStream.CopyTo(entryStream);
            }
        }
            compressedCertInBytes = outStream.ToArray();
    }

 Entity annotationZip = new Entity("annotation");
 annotationZip.Attributes["subject"] = "publickey zip";
 annotationZip.Attributes["filename"] = "publickey.zip";                       
 annotationZip.Attributes["documentbody"] = Convert.ToBase64String(compressedCertInBytes);
 annotationZip.Attributes["mimetype"] = "application/zip";
 annotationZip.Attributes["isdocument"] = true;

在此之后,我要做的是下载 zip 文件,将其解压缩,然后将其粘贴到我的 Xero 的私有应用程序中。

然后,我运行我的代码登录到 Xero(粘贴在下面)

var baseApiUrl = "https://api.xero.com";
var consumerKey = myConsumerKey;
var privateConsumer = new Consumer(consumerKey, consumerSecret);

certificate = new X509Certificate2(Convert.FromBase64String(retrieveBase64FromCRM), 
             myPassword, 
             X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);

var privateAuthenticator = new PrivateAuthenticator(certificate);

var privateApplicationSettings = new ApplicationSettings
{
   BaseApiUrl = baseApiUrl,
   Consumer = privateConsumer,
   Authenticator = privateAuthenticator
 };

ApplicationSettings applicationSettings = privateApplicationSettings;

if (applicationSettings.Authenticator is IAuthenticator)
{
                IXeroCoreApi myXeroApi = new XeroCoreApi(applicationSettings.BaseApiUrl, applicationSettings.Authenticator as IAuthenticator,
                applicationSettings.Consumer, User(), new DefaultMapper(), new DefaultMapper());
}

string getXeroOrgName = myXeroApi != null && !string.IsNullOrEmpty(myXeroApi.Organisation.Name.ToString()) ? myXeroApi.Organisation.Name.ToString() : string.Empty;

此代码是出现问题的地方。我收到以下错误。

当我传递参数'X509KeyStorageFlags.MachineKeySet | 时出错X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable' 获取证书

我收到拒绝访问错误。

堆栈跟踪:-

    System.Security.Cryptography.CryptographicException: Access denied.

   at System.Security.Cryptography.CryptographicException.ThrowCryptographicException(Int32 hr)
   at System.Security.Cryptography.X509Certificates.X509Utils._LoadCertFromBlob(Byte[] rawData, IntPtr password, UInt32 dwFlags, Boolean persistKeySet, SafeCertContextHandle& pCertCtx)
   at System.Security.Cryptography.X509Certificates.X509Certificate.LoadCertificateFromBlob(Byte[] rawData, Object password, X509KeyStorageFlags keyStorageFlags)
   at System.Security.Cryptography.X509Certificates.X509Certificate2..ctor(Byte[] rawData, String password, X509KeyStorageFlags keyStorageFlags)
   at myFunctionName(Dictionary`2 myParameters)

没有这些参数,我得到的错误是

堆栈跟踪:

System.Security.Cryptography.CryptographicException: Invalid provider type specified.

      at System.Security.Cryptography.Utils.CreateProvHandle(CspParameters parameters, Boolean randomKeyContainer)
at System.Security.Cryptography.Utils.GetKeyPairHelper(CspAlgorithmType keyType, CspParameters parameters, Boolean randomKeyContainer, Int32 dwKeySize, SafeProvHandle& safeProvHandle, SafeKeyHandle& safeKeyHandle)
at System.Security.Cryptography.RSACryptoServiceProvider.GetKeyPair()
at System.Security.Cryptography.RSACryptoServiceProvider..ctor(Int32 dwKeySize, CspParameters parameters, Boolean useDefaultKeySize)
at System.Security.Cryptography.X509Certificates.X509Certificate2.get_PrivateKey()
at Xero.Api.Infrastructure.ThirdParty.Dust.RsaSha1.Sign(SHA1CryptoServiceProvider hash)
at Xero.Api.Infrastructure.ThirdParty.Dust.RsaSha1.SignCore(String baseString)
at Xero.Api.Infrastructure.OAuth.Signing.RsaSha1Signer.CreateSignature(X509Certificate2 certificate, IToken token, Uri uri, String verb, String verifier, Boolean renewToken, String callback)
at Xero.Api.Example.Applications.Private.PrivateAuthenticator.GetSignature(IConsumer consumer, IUser user, Uri uri, String verb, IConsumer consumer1)
at Xero.Api.Infrastructure.Http.HttpClient.CreateRequest(String endPoint, String method, String accept, String query)
at Xero.Api.Infrastructure.Http.HttpClient.Get(String endpoint, String query)
at Xero.Api.Infrastructure.Http.XeroHttpClient.Get[TResult,TResponse](String endPoint)
at Xero.Api.Common.XeroReadEndpoint`3.Get(String endpoint, String child)
at Xero.Api.Common.XeroReadEndpoint`3.Find()
at Xero.Api.Core.XeroCoreApi.get_Organisation()
at myFunctionName(Dictionary`2 myParameters)

但令我惊讶的是,当我在 Azure 上托管这个 API 时,它按预期工作,没有任何问题,我能够登录到 Xero,但我无法在我的本地机器上调试它。

如果我使用使用 OpenSSL 创建的证书的值,我可以调试代码,但如果我使用以编程方式创建的值,则会出错。

对此的任何帮助将不胜感激。

【问题讨论】:

  • 发布异常文本,而不是异常截图。您发布的内容没有用,因为调用堆栈和内部异常不可见。您可以使用Exception.ToString() 轻松获取异常文本。在调试期间,Visual Studio 允许您在异常对话框中复制异常的详细信息。
  • @PanagiotisKanavos 我为这两个错误粘贴了堆栈跟踪。
  • 检查this question。这可能是因为应用程序池帐户没有将证书添加到机器密钥文件夹的权限,这很好。您不希望黑客以某种方式劫持您的网站并开始在那里添加证书。
  • 这些标志指定证书的存储位置。如果要将其存储在机器密钥文件夹中,则应在 Web 应用程序之外进行。
  • 检查this question too - 使用X509KeyStorageFlags.UserKeySet 将证书存储在用户的证书文件夹中

标签: c# asp.net-web-api dynamics-crm x509certificate2 xero-api


【解决方案1】:

访问被拒绝是因为您试图将私钥保存到机器密钥库 (MachineKeySet) 中并且不是管理员。

Invalid Provider Type 是因为 PFX 中的私钥没有表示密钥存储提供程序,因此当它打开时它保存在 CNG(与 CAPI 相比)中,并且 Xero 库使用 cert.PrivateKey 而不是cert.GetRSAPrivateKey().

如果您使用的是 Windows,则可以通过在 CAPI 中生成原始密钥来解决此问题:

CspParameters cspParams = new CspParameters
{
    KeyContainerName = Guid.NewGuid().ToString(),
};

using (var rsaCsp = new RSACryptoServiceProvider(2048, cspParams))
{
    rsaCsp.PersistKeyInCsp = false;

    // CertificateRequest stuff here
}

(或类似的,通过电话提交的答案)

【讨论】:

    【解决方案2】:
    ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; };   
    

    以上行将帮助您解决问题

    在我的 Try and catch 块中,我使用它如下

    try
                {
                    ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; };
                    WebProxy proxy = new WebProxy("proxy.xyz.local", 81) { UseDefaultCredentials = true };
                    WebRequest request = WebRequest.Create(globaConfigStatciValues.Url);
                    request.Proxy = proxy;
                    request.Method = "GET";
                    request.Credentials = new NetworkCredential(globaConfigStatciValues.username,
                        globaConfigStatciValues.password);
    
    
                    using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
                    {
                        try
                        {
                            if (response.StatusCode == HttpStatusCode.OK)
                            {
                                isConnectionSucessfull = true;
                                tracing.Trace($" Resposne is correct  {response.StatusCode}");
                                TracingMessage += $"Resposne is correct  {response.StatusCode} \n";
                            }
                            else
                            {
                                TracingMessage += $"Response from connecting to API {response.StatusCode} \n";
                                tracing.Trace($"Response from connecting to API {response.StatusCode}");
                            }
    
                        }
                        catch (Exception e)
                        {
                            TracingMessage += $" In catch block {e} \n";
                            tracing.Trace($" In catch block {e}");
                            createLogRecord( e.StackTrace,TracingMessage);
                           // throw new Exception($"There was an issue with connecting to API {e.Message}");
                        }
                    }
    
    
    
                }
                catch (Exception e)
                {
                    TracingMessage += $" In catch block of PerformGetMethodtoApi {e} \n";
                    tracing.Trace($" In catch block of PerformGetMethodtoApi {e}");
                    createLogRecord( e.StackTrace, TracingMessage);
                   // throw new Exception($"There was an issue with connecting to API {e.Message}");
                }
    

    【讨论】:

    • 问题是关于 Access Denied 错误,而不是证书验证。在生产中禁用验证是一个非常的坏主意,因为它允许黑客启用证书和 TLS 旨在防止的中间人攻击。如果您想在开发过程中使用自签名证书,请将其添加到受信任的证书中,不要禁用验证
    • 我应该在我的代码中的哪里添加该行?在以编程方式创建证书之前,还是在登录 Xero 时?
    • @Sohail 无处可去,除非你想被黑。在问题中发布异常,解释错误发生的位置。现在人们不得不从不完整的图像中猜测
    • @PanagiotisKanavos 我上面的代码在生产环境中运行良好,问题在于在本地机器上进行调试。那么如果我使用上面这行代码只是为了调试,会不会好呢?
    • @PanagiotisKanavos 感谢您提供信息 :) 我会尝试更新我的代码。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-08
    • 1970-01-01
    • 2011-12-29
    • 2015-12-31
    • 2013-07-10
    • 2017-06-24
    相关资源
    最近更新 更多