Decrypt using private key of SSL certificate installed on IIS in C# - c#

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?

Related

Why my X509Certificate2 loses its private key?

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.

C# Store Public-Key X.509 encoded key in ANS.1

i am trying to implement an API. (https://www.loxone.com/dede/wp-content/uploads/sites/2/2016/08/0903_Communicating-with-the-Miniserver.pdf)
When you go to Page 5 you will see under 2.b) that I need to store a public key in Format: X.509 encoded key in ANS.1.
The problem is I have no idea how to do this in C#...
Can anyone please help me with that problem :)
Thanks in advance!
Here is an example for the publicKey that I get:
-----BEGIN CERTIFICATE-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC393RoCVFixg3cZicByw05GYqlIkmqfx1lylJqBaF7BMI/FffD5BBoFpFurj4N1pDdMF/QhBEj1Pb3XGOkrHFCinYc0lCMbSjreE+QPjmnWnZeaPd3mtBfdcTSF6hYH0regFU8nvFVgLsoBUCMjuhfsvuaW4sLylU6KhZ9hCqVQwIDAQAB-----END CERTIFICATE-----
I think you need to export this public key as X.509 encoded as ANS.1 which can be a .pem file.
From https://serverfault.com/questions/9708/what-is-a-pem-file-and-how-does-it-differ-from-other-openssl-generated-key-file/9717#9717 :
PEM on it's own isn't a certificate, it's just a way of encoding data. X.509 certificates are one type of data that is commonly encoded using PEM.
PEM is a X.509 certificate (whose structure is defined using ASN.1), encoded using the ASN.1 DER (distinguished encoding rules), then run through Base64 encoding and stuck between plain-text anchor lines (BEGIN CERTIFICATE and END CERTIFICATE).
You can use openssl to make a .pem file which is ANS.1 compliant.
openssl req -new -x509 -days 365 -nodes -out ./cert.pem -keyout ./cert.pem
Presumably, by "ANS.1" they meant "ASN.1" (Abstract Syntax Notation 1), and by "ASN.1" they meant "DER" (Distinguished Encoding Rules for Abstract Syntax Notation 1).
To convert your current PEM certificate into a "DER" certificate you either a) openssl x509 -in cert.pem -out cert.der -outform der or b) read the base64 string between "-----BEGIN CERTIFICATE-----" and "-----END CERTIFICATE-----" and convert it to bytes (e.g. Convert.FromBase64String).
If you still need it after 3 years, you can use the following C# (.NET 6) method:
public static async Task<RSA> GetPublicKey(string certStr)
{
string base64 = certStr;
if (base64.StartsWith("-----BEGIN CERTIFICATE-----"))
{
base64 = base64.Substring(27);
}
if (base64.EndsWith("-----END CERTIFICATE-----"))
{
base64 = base64.Substring(0, base64.Length - 25);
}
var asn = new AsnReader(Convert.FromBase64String(base64), AsnEncodingRules.DER);
asn = asn.ReadSequence();
asn.ReadSequence();
//string objectIdentifier = asn.ReadObjectIdentifier(Asn1Tag.ObjectIdentifier);
//asn.ReadNull();
var data2 = asn.ReadBitString(out int bitCount);
asn = new AsnReader(data2, AsnEncodingRules.DER);
asn = asn.ReadSequence();
var modulus = asn.ReadInteger();
var exponent = asn.ReadInteger();
var rsa = new RSACng();
rsa.ImportParameters(new RSAParameters
{
Modulus = modulus.ToByteArray(true, true),
Exponent = exponent.ToByteArray(true, true),
});
return rsa;
}
Not the best and no error handling, but works.
Then you don't need an X.509 certificate, just this RSA object:
var aes = Aes.Create();
string s = ByteArrayToHex(aes.Key) + ":" + ByteArrayToHex(aes.IV);
var encData = rsa.Encrypt(Encoding.ASCII.GetBytes(s), RSAEncryptionPadding.Pkcs1);
s = "jdev/sys/keyexchange/" + Convert.ToBase64String(encData);
Probably you can implement the ByteArrayToHex yourself :)

How can i send a RsaKeyParameters to a server or vice versa?

I am using the Bouncy Castle on Xamarin Forms to asymmectric encrypt. But i am working on a Client <-> Web Api structure. How can i send the public Key to the other side ? Because the type is RsaKeyParameters .
RsaKeyPairGenerator rsaKeyPairGnr = new RsaKeyPairGenerator();
rsaKeyPairGnr.Init(new KeyGenerationParameters(new SecureRandom(), 512));
AsymmetricCipherKeyPair keyPair = rsaKeyPairGnr.GenerateKeyPair();
RsaKeyParameters publicKey = (RsaKeyParameters)keyPair.Public;
If I have a RsaKeyParameters publicKey and I need to send this to a server, I can convert using:
//convert from key to string
SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(publicKey);
byte[] serializedPublicBytes = publicKeyInfo.ToAsn1Object().GetDerEncoded();
string serializedPublic = Convert.ToBase64String(serializedPublicBytes);
And then, i can convert the serializedPublic to RsaKeyParameters publicKey
//convert from string to key
RsaKeyParameters publicKey2 = (RsaKeyParameters)PublicKeyFactory.CreateKey(Convert.FromBase64String(serializedPublic));
But could someone tell me if passing the public key by querystring via Http library is a good idea ?

Constructing RSACryptoServiceProvider from X509Certificate2 without LDAP request being made

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);
}

Signing Data with Google Service Account Private Key Fails

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");
}
}
}
}

Categories