1

I am creating an API to login to Xero via Private Applications, so I followed their instructions and Xero API documentation to see how it works. Link here.

So I created an App, and generate a public certificate (.cer file) using OpenSSL, and also created '.pfx file' for the same, and attached that file in Dynamics CRM in Notes entity.

Now when I run my C# code to login using the same public/private-key pair it's working as expected. (The private/public-key pair were retrieved from CRM) and passed as expected to the login code (code has been pasted below)

My next step was to create these public/private-key pair programmatically using C#, and I used the below code to create it.

// 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 variable contains my OrganizationName, OrganizationUnitName, and CommonName in string format.

After this step, what I do is, I create a .pfx file, and a .zip file(zip file contains the .cert file in it) and attach both of these to note records in Dynamics CRM.

Below is the code

Create .pfx file and attach to note in 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);

Create zip file with .pfx file in it, and attach to note in 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;

After this, what I do is, I download the zip file, extract it, and paste the same in my Xero's Private Application.

Then, I run my code to login to Xero(pasted below)

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;

This code is where the trouble occurs. I get the below error.

Error when I pass the parameters 'X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable' to get the certificate

Access Denied

I get the error Access Denied.

Stack Trace:-

    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)

Without these parameters, the error I get is

Invalid provider type specified

Stack Trace:

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)

But to my surprise, when I hosted this API on Azure, it's working as expected with no issues, and I am able to login to Xero, but I can't debug it on my local machine.

I am able to debug the code if I use the values of the certificate created using OpenSSL, but if I use values of the ones created programmatically, I get error.

Any help on this would be appreciated.

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
Sohail
  • 43
  • 1
  • 12
  • Post the exception text,not a screenshot of the exception. What you posted isn't useful as the call stack and inner exceptions aren't visible. You can easily get the exception text with `Exception.ToString()`. During debugging, Visual Studio allows you to copy the exception's details in the exception dialog box. – Panagiotis Kanavos Aug 09 '19 at 11:04
  • @PanagiotisKanavos I pasted stack trace for both the errors. – Sohail Aug 09 '19 at 11:31
  • Check [this question](https://stackoverflow.com/questions/37998580/cryptographicexception-access-denied-how-to-give-access-on-user-store). It's probably caused because the application pool account doesn't have permission to add the certificate to the machine key folder, *which is just fine*. You wouldn't want a hacker that somehow hijacked your site to start adding certificates there. – Panagiotis Kanavos Aug 09 '19 at 12:49
  • Those flags specify where to store the certificate. If you want to store it in the machine key folder, you should do so outside the web application. – Panagiotis Kanavos Aug 09 '19 at 12:50
  • Check [this question too](https://stackoverflow.com/questions/15285046/why-do-i-get-an-access-denied-error-when-creating-an-x509certificate2-object) - use `X509KeyStorageFlags.UserKeySet` to store the certificate in the user's certificate folder – Panagiotis Kanavos Aug 09 '19 at 12:58

2 Answers2

1

The Access Denied is because you are trying to save the private key into the machine keystore (MachineKeySet) and are not an administrator.

The Invalid Provider Type is because the private key in the PFX didn’t express a key storage provider, and so when it got opened it got saved in CNG (vs CAPI) and the Xero library is using cert.PrivateKey instead of cert.GetRSAPrivateKey().

You can fix that by generating your original key in CAPI, if you’re on Windows:

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

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

    // CertificateRequest stuff here
}

(Or something like that, answer submitted from a phone)

bartonjs
  • 30,352
  • 2
  • 71
  • 111
-1
ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; };   

above line will help you solve issue

In my Try and catch block I used it as below

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}");
            }
AnkUser
  • 5,421
  • 2
  • 9
  • 25
  • 1
    The question is about an `Access Denied` error, not certificate validation. Disabling validation in production is a *very* bad idea as it allows hackers to enable the man-in-the-middle attacks that certificates and TLS are meant to protect against. If you want to use a self-signed certificate during development, add it to the trusted certificates, don't disable validation – Panagiotis Kanavos Aug 09 '19 at 11:10
  • Where am I supposed to add that line in my code? Before creating the certificate programmatically, or while login in to Xero? – Sohail Aug 09 '19 at 11:11
  • @Sohail nowhere, unless you want to get hacked. Post the exceptions in the question, explain *where* the error occurs. Right now people have to guess from an incomplete image – Panagiotis Kanavos Aug 09 '19 at 11:11
  • @PanagiotisKanavos My above code works very well on Production, the problem is with debugging on local machine. So if I use the above line of code just for debugging, would it be fine? – Sohail Aug 09 '19 at 11:32
  • @PanagiotisKanavos Thanks for info :) I will try to update my code. – AnkUser Aug 09 '19 at 12:38