I am trying to generate a client assertion from a X509Certificate2 (here is the documentation). The fact is that my code is working perfectly when I get the certificate from a file as follows :
string signinCertPath = Path.Combine(Directory.GetCurrentDirectory(), "Cert", "anAwesomeCertificate.pfx");
X509Certificate2 certificate = new X509Certificate2(signinCertPath, "MySuperCertificatePassword", X509KeyStorageFlags.EphemeralKeySet);
string clientAssertion = GenerateClientAssertion(certificate, tenantId, clientId); //clientAssertion handles the value I am looking for.
Here is the GenerateClientAssertion method, that I've copied from the MSAL documentation :
private string GenerateClientAssertion(X509Certificate2 certificate, string tenantId, string clientId)
{
// Get the RSA with the private key, used for signing.
var rsa = certificate.GetRSAPrivateKey();
//alg represents the desired signing algorithm, which is SHA-256 in this case
//x5t represents the certificate thumbprint base64 url encoded
var header = new Dictionary<string, string>()
{
{ "alg", "RS256"},
{ "typ", "JWT" },
{ "x5t", Base64UrlEncode(certificate.GetCertHash()) }
};
var claims = GetClaims(tenantId, clientId);
var headerBytes = JsonConvert.SerializeObject(header);
var claimsBytes = JsonConvert.SerializeObject(claims);
string token = Base64UrlEncode(Encoding.UTF8.GetBytes(Newtonsoft.Json.Linq.JObject.FromObject(header).ToString())) + "." + Base64UrlEncode(Encoding.UTF8.GetBytes(Newtonsoft.Json.Linq.JObject.FromObject(claims).ToString()));
string signature = Base64UrlEncode(rsa.SignData(Encoding.UTF8.GetBytes(token), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1));
string signedClientAssertion = string.Concat(token, ".", signature);
return signedClientAssertion;
}
The problem is that I am not able to access the certificate file in real conditions, that is why I copied it in a byte[] and try to do the same thing by creating the certificate with the byte[], like that :
X509Certificate2 certificate = new X509Certificate2(variables.cert, "MySuperCertificatePassword", X509KeyStorageFlags.EphemeralKeySet); //variables.cert is the byte[]
string clientAssertion = GenerateClientAssertion(certificate, tenantId, clientId);
And here comes the issue : my code throw me an error as the rsa variable is not set to an instance of an object, meaning that I am not getting the certificate private key.
So does anybody know how I can get this private key?
Any contribution would be nice. Thanks by advance.
Related
I'm trying to make a soap service.
To send request I need to use X509 Certificate. I used X509Certificate2 class for that purpose.
The problem is that its keys are System.Security.Cryptography.RSACryptoServiceProvider class that accepts only "http://www.w3.org/2000/09/xmldsig#rsa-sha1" signature algorithm.
In our case sha256 signature algorithm is needed.
Have you got any idea how can I deal with that?
Code looks like this:
SoapService.GetOrderStatusRequest request = new SoapService.GetOrderStatusRequest()
{
orderId = Int32.Parse(txtID.Text),
requestHeader = new SoapService.RequestHeader()
{
institutionId = 123,
requestId = "aeacbff8-ba6d-4a01-8e76-0b4384c24721",
system = "Test"
}
};
var cert = new X509Certificate2(AppDomain.CurrentDomain.BaseDirectory + "//cert.p12", "Passs123");
client.ChannelFactory.Credentials.ClientCertificate.Certificate = cert;
client.ClientCredentials.ClientCertificate.Certificate = cert;
SoapService.GetOrderStatusResponse response = client.getOrderStatus(request);
txtResult.Text = response.order.name;
Edit1.
I found out this is not problem with X509Certificate2 configuration, but with binding configuration... with message security to be exac.
Unfortunately I still have some errors :/
I have a client who encrypts data using the public key of a SSL certificate. Details:
IIS 10.
.Net 4.8, C#
SSL Certificate - 4096 bit, issued by CA.
Client connects to my web API and sends encrypted data as parameter as a raw string using the public key of above certificate installed on my server.
Client uses RSAEncryptionPadding.OaepSHA256 for padding.
I have tried (Code 1):
public string DecryptRsa(string encrypted, X509Certificate2 x509Certificate2, RSAEncryptionPadding rSAEncryptionPadding)
{
var text = string.Empty;
using (RSA csp = (RSA)x509Certificate2.PrivateKey)
{
byte[] bytesEncrypted = Convert.FromBase64String(encrypted);
byte[] bytesDecrypted = csp.Decrypt(bytesEncrypted, rSAEncryptionPadding);
text = Encoding.UTF8.GetString(bytesDecrypted);
}
return text;
}
This gives error: Specified padding mode is not valid for this algorithm.
Code 2:
public string DecryptRsa1(string encrypted, X509Certificate2 x509Certificate2, RSAEncryptionPadding rSAEncryptionPadding)
{
var text = string.Empty;
var privateKey = x509Certificate2.GetRSAPrivateKey();
var bytesData = Convert.FromBase64String(encrypted);
var dataByte = privateKey.Decrypt(bytesData,rSAEncryptionPadding);
text = Encoding.UTF8.GetString(dataByte);
return text;
}
This gives the error: Parameter is incorrect.
I have tested using my public key for encryption and private key for decryption using OaepSHA1 padding and both above code work fine.
What am I not getting/doing wrong?
The idea of the web app is to sign documents with a digital signature that is loaded from a smart card.
It is published and set to work on a local user machine. I am using IIS for that matter to set the bindings and enable to accept client certificates.
It communicates with a web app that is hosted on the cloud.
I am successfully getting the certificate from the smart card and the private key as well.
I use the private key to sign the document.
private InvoiceResult SignDocument(XmlDocument doc)
{
InvoiceResult resultValue;
try
{
var (resultValue2, certificate) = GetDefaultCertificateStoredOnTheCard();
resultValue = resultValue2;
SignXmlDocumentWithCertificate(doc, certificate);
resultValue = InvoiceResult.Success;
}
catch (Exception ex)
{
_log.TraceInformation($"Error when compute signature and it is : {ex.Message}");
_log.TraceInformation($"Additional info => stack trace : {ex.StackTrace}");
resultValue = InvoiceResult.CannotSignXmlFiles;
}
return resultValue;
}
public (InvoiceResult resultValue, X509Certificate2 cert) GetDefaultCertificateStoredOnTheCard()
{
var resultValue = InvoiceResult.Success;
using X509Store x509Store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
X509Store store = x509Store;
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
X509Certificate2Collection certs = store.Certificates.Find(X509FindType.FindByTimeValid, DateTime.Now, true);
certs = certs.Find(X509FindType.FindByThumbprint, Settings.Default.Thumbprint, true);
if (certs.Count == 0)
{
resultValue = InvoiceResult.CannotFindSignature;
}
X509Certificate2 cert = certs[0];
if (cert.HasPrivateKey)
{
// software cert
_ = cert.PrivateKey as RSACryptoServiceProvider;
}
else
{
// certificate from smartcard
CspParameters csp = new CspParameters(1, "Microsoft Base Smart Card Crypto Provider")
{
Flags = CspProviderFlags.UseDefaultKeyContainer
};
_ = new RSACryptoServiceProvider(csp);
}
return (resultValue, cert);
}
private InvoiceResult SignXmlDocumentWithCertificate(XmlDocument xmlDoc, X509Certificate2 cert)
{
InvoiceResult resultValue = InvoiceResult.Success;
SignedXml signedXml = new SignedXml(xmlDoc)
{
//we will sign it with private key
SigningKey = cert.PrivateKey
};
if (cert.PrivateKey == null)
{
resultValue = InvoiceResult.CannotSignXmlFiles;
// throw new ArgumentException("Please make sure the application for electronic signatures is installed, so the private key can be obtained from the smart card!");
}
Reference reference = new Reference
{
//sign the entire doc
Uri = ""
};
XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
reference.AddTransform(env);
signedXml.AddReference(reference);
//PublicKey part
RSACryptoServiceProvider rsaprovider = (RSACryptoServiceProvider)cert.PublicKey.Key;
RSAKeyValue rkv = new RSAKeyValue(rsaprovider);
KeyInfo keyInfo = new KeyInfo();
keyInfo.AddClause(new KeyInfoX509Data(cert));
//We add the public key here
keyInfo.AddClause(rkv);
signedXml.KeyInfo = keyInfo;
_log.TraceInformation($"Cert has private key or not? {cert.HasPrivateKey}");
signedXml.ComputeSignature();
// Get the XML representation of the signature and save
// it to an XmlElement object.
_log.TraceInformation($"It computes the signature succesfully");
XmlElement xmlDigitalSignature = signedXml.GetXml();
// Append the element to the XML document.
xmlDoc.DocumentElement.AppendChild(xmlDoc.ImportNode(xmlDigitalSignature, true));
_log.TraceInformation($"It appends the signature succesfully");
return resultValue;
}
It works fine on Release/Debug but not in Publish. It gets a popup, asks for a PIN and once the PIN has been entered the docs are signed.
It gets to the signedxml.ComputeSignature and it returns an error :
The operation was canceled by the user.
Here is the exception that has been thrown :
System.Security.Cryptography.CryptographicException.ThrowCryptographicException(Int32 hr)
at System.Security.Cryptography.Utils.SignValue(SafeKeyHandle hKey, Int32 keyNumber, Int32 calgKey, Int32 calgHash, Byte[] hash, Int32 cbHash, ObjectHandleOnStack retSignature)
at System.Security.Cryptography.Utils.SignValue(SafeKeyHandle hKey, Int32 keyNumber, Int32 calgKey, Int32 calgHash, Byte[] hash)
at System.Security.Cryptography.RSACryptoServiceProvider.SignHash(Byte[] rgbHash, Int32 calgHash)
at System.Security.Cryptography.Xml.SignedXml.ComputeSignature()
The only way I get this error on release/debug is if I cancel the window which asks for a PIN.
Is there another way to compute the signature and apply it to the XML? This is the only one I was able to find so far.
It potentially could be an IIS setting, but I have tried various things to no avail. The certificate can be found if I require SSL on Client Side is ticked and set it to Accept as in the image :
I have also tried exporting the private key which I saw on various posts, however, because it is a smart card I am unable to export the Private Key, I can only use it which is what I am doing with my code.
Once I start this part of the application it asks initially for the certificate and the PIN and returns the same error. On subsequent attempts , it never asks for the PIN or the certificate.
Change app pool identity to windows account
When the line to extract the public key is executed, an LDAP request is sent:
this.certificate = new X509Certificate2(buffer);
System.Security.Cryptography.X509Certificates.PublicKey key = this.certificate.PublicKey;
50 0.853745000 xxx.xxx.xxx.xxx xxx.xxx.xxx.xxx LDAP 404 searchRequest(1) "" baseObject
...which I believe is authenticating the currently logged on user. I really need to avoid this call, as on a customer system this causes a long delay because of network configuration.
I'm assuming it's attempting to do some authentication around some kind of key store, but as in this case the certificate is all contained in the buffer provided, all I want is for the key to be used without this request being sent.
All I actually want is to create an RSACryptoServiceProvider from the private key in the certificate. I tried a few methods I've found on here involving GetPrivateKey, but struggled to get anything to work.
Thanks in advance!
EDIT Test program:
static void Main( string[] args )
{
var certificate = new System.Security.Cryptography.X509Certificates.X509Certificate2(#"E:\Temp\Cert.cer");
System.Security.Cryptography.X509Certificates.PublicKey key = certificate.PublicKey;
}
The certificate I tested with can be found here: Cert.cer
Yes, it's not the strongest signature or key, before I get comments!
Thanks again.
EDIT: I actually worked around this by using a suggestion to use BouncyCastle. I use this to parse the certificate:
X509CertificateParser parser = new X509CertificateParser();
Org.BouncyCastle.X509.X509Certificate cert = parser.ReadCertificate(buffer);
I then extract the modulus and exponent and push them into a Microsoft RSAParameters:
RsaKeyParameters key = (RsaKeyParameters)cert.GetPublicKey();
// Construct a microsoft RSA crypto service provider using the public key in the certificate
RSAParameters param = new RSAParameters();
param.Exponent = key.Exponent.ToByteArrayUnsigned();
param.Modulus = key.Modulus.ToByteArrayUnsigned();
I can then construct the Microsoft RSACryptoServiceProvider from this:
using (RSACryptoServiceProvider provider = new RSACryptoServiceProvider())
{
provider.ImportParameters(param);
byte[] rsaBlock = provider.Encrypt(preMasterSecret, false);
this.Client.Writer.Write(rsaBlock);
}
I never got any other response, so here's the Bouncycastle implementation I used.
X509CertificateParser parser = new X509CertificateParser();
Org.BouncyCastle.X509.X509Certificate cert = parser.ReadCertificate(buffer);
I then extract the modulus and exponent and push them into a Microsoft RSAParameters:
RsaKeyParameters key = (RsaKeyParameters)cert.GetPublicKey();
// Construct a microsoft RSA crypto service provider using the public key in the certificate
RSAParameters param = new RSAParameters();
param.Exponent = key.Exponent.ToByteArrayUnsigned();
param.Modulus = key.Modulus.ToByteArrayUnsigned();
I can then construct the Microsoft RSACryptoServiceProvider from this:
using (RSACryptoServiceProvider provider = new RSACryptoServiceProvider())
{
provider.ImportParameters(param);
byte[] rsaBlock = provider.Encrypt(preMasterSecret, false);
this.Client.Writer.Write(rsaBlock);
}
I'm not able to sign data with the Service Application private key I downloaded from the Google Developer console. I get the following error:
OAuthTests.TestCrypto.testSha256SignWithGoogleKey:
System.Security.Cryptography.CryptographicException : Invalid algorithm specified.
at System.Security.Cryptography.CryptographicException.ThrowCryptogaphicException(Int32 hr)
at System.Security.Cryptography.Utils._SignValue(SafeKeyHandle hKey, Int32 keyNumber, Int32 calgKey, Int32 calgHash, Byte[] hash, Int32 dwFlags)
at System.Security.Cryptography.RSACryptoServiceProvider.SignHash(Byte[] rgbHash, String str)
at OAuthTests.TestCrypto.testSha256SignWithGoogleKey() in e:\Development\MiscellaneousProjects\RSSNewsFeeder\Oauth\OAuthTests.cs:line 43
Yes, I've asked this question before but not getting much help and since Stack's forum model is not easy to add onto an existing thread it seems that my best best to reword the question is to do just do that; reword and ask a new question.
I have written three unit tests (code below). The first unit test shows that I can sign and verify data using RSACryptoServiceProvider with SHA256, but this test does not use my Google certificate's private key.
When I use the Google's private key certificate and test (2nd test below), the code errors (error message above).
The 3rd test demonstrates using Google's private key and testing using SHA1 and this works! But not valid according to the specs.
Is there something wrong with the code below or is there something wrong with the certificate, or perhaps its my OS or other environmental issue? I'm developing in Windows C# 3.5 on a Windows 8.1 machine.
** THIS WORKS **
Not using Google Certificate
var cert = new X509Certificate2(#"E:\Development\MiscellaneousProjects\RSSNewsFeeder\Samples\9d16ba9bd04468b4cd0dd241e34b980643fd5b21-privatekey.p12", "notasecret", X509KeyStorageFlags.Exportable);
byte[] data = new byte[] { 0, 1, 2, 3, 4, 5 };
using (RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)cert.PrivateKey)
{
byte[] signature = rsa.SignData(data, "SHA256");
if (rsa.VerifyData(data, "SHA256", signature))
{
Console.WriteLine("RSA-SHA256 signature verified");
}
else
{
Console.WriteLine("RSA-SHA256 signature failed to verify");
}
}
** THIS FAILS **
Using Google certificate and SHA256
Fails At: byte[] signature = rsa.SignData(data, "SHA256");
[Test]
public void testSha256SignWithGoogleKey()
{
var cert = new X509Certificate2(#"....41e34b980643fd5b21-privatekey.p12", "notasecret", X509KeyStorageFlags.Exportable);
byte[] data = new byte[] { 0, 1, 2, 3, 4, 5 };
using (RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)cert.PublicKey.Key)
{
byte[] signature = rsa.SignData(data, "SHA256");
if (rsa.VerifyData(data, "SHA256", signature))
{
Console.WriteLine("RSA-SHA256 signature verified");
}
else
{
Console.WriteLine("RSA-SHA256 signature failed to verify");
}
}
}
** THIS WORKS **
Using Google certificate but SHA1
[Test]
public void testShaSignWithGoogleKey()
{
var cert = new X509Certificate2(#"....dd241e34b980643fd5b21-privatekey.p12", "notasecret", X509KeyStorageFlags.Exportable);
byte[] data = new byte[] { 0, 1, 2, 3, 4, 5 };
using (RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)cert.PrivateKey)
{
byte[] signature = rsa.SignData(data, "SHA1");
if (rsa.VerifyData(data, "SHA1", signature))
{
Console.WriteLine("RSA-SHA1 signature verified");
}
else
{
Console.WriteLine("RSA-SHA1 signature failed to verify");
}
}
}
First of all, there's a mistake in your example #2: you are trying to use public key for signing. And you should get the error: "Object contains only the public half of a key pair. A private key must also be provided."
But I suppose it was just a copy/paste mistake, and you already tried with private key.
The RSACryptoServiceProvider obtained from Google's certificate PrivateKey uses "Microsoft Base Cryptographic Provider v1.0", while newly created RSACryptoServiceProvider object uses "Microsoft Enhanced RSA and AES Cryptographic Provider".
The trick to workaround this is to export the bare math from cert's RSACSP to a new RSACSP object:
[Test]
public void testSha256SignWithGoogleKey()
{
var cert = new X509Certificate2(#"....41e34b980643fd5b21-privatekey.p12", "notasecret", X509KeyStorageFlags.Exportable);
byte[] data = new byte[] { 0, 1, 2, 3, 4, 5 };
using (RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)cert.PrivateKey)
{
using (RSACryptoServiceProvider myRsa = new RSACryptoServiceProvider())
{
myRsa.ImportParameters(rsa.ExportParameters(true));
byte[] signature = myRsa.SignData(data, "SHA256");
if (myRsa.VerifyData(data, "SHA256", signature))
{
Console.WriteLine("RSA-SHA256 signature verified");
}
else
{
Console.WriteLine("RSA-SHA256 signature failed to verify");
}
}
}
}