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
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
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.