Decrypt with PrivateKey X.509 Certificate - c#

I have a problem to decrypt a message usgin X.509 Certificate.
I generate my certificate with makecert with this options:
makecert -r -pe -n "CN=MyCertificate" -ss CA -sr CurrentUser -a sha1 -sky signature -cy authority -sv CA.pvk CA.cer
And the PrivateKey was "mypassword".
My problem is when I want to decrypt a message encrypt with previous certificate in c#.
I found this class http://blog.shutupandcode.net/?p=660, but in the X509Decrypt method allways the PrivateKey is null.
public static byte[] X509Decrypt(byte[] data, string certificateFile, string password)
{
// load the certificate and decrypt the specified data
using (var ss = new System.Security.SecureString())
{
foreach (var keyChar in password.ToCharArray())
ss.AppendChar(keyChar);
// load the password protected certificate file
X509Certificate2 cert = new X509Certificate2(certificateFile, ss);
using (RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)cert.PrivateKey)
{
return rsa.Decrypt(data, true);
}
}
}
I tried passing the certificate file (.cer)
X509DecryptString(token, #"c:\CA.cer", "mypassword");
And passing the pvk file (.pvk)
X509DecryptString(token, #"c:\CA.pvk", "mypassword");
But allways have that the PrivateKey property is null.
Can anyone guide me to decrypt the message using the pvk file?
Thanks,
Jose

The certificate itself only contains the public key (+ some data), but not the private key. (It's very unlikely that the RSA private key is "mypassword". The password that protects your private key may be "mypassword", but the private key itself (more specifically the private exponent, in RSA) will be a rather long number.)
As a result (because CA.cer only contains the certificate), X509DecryptString(token, #"c:\CA.cer", "mypassword") will almost certainly not work.
X509DecryptString(token, #"c:\CA.pvk", "mypassword"); could work in principle, but you're creating a X509Certificate2 object from it, and it still needs the certificate and the private key. You should be able to load that from a PKCS#12 container (.p12/.pfx).
To create this container, you can use pvk2pfx:
pvk2pfx -spc CA.cer -pvk CA.pvk -pfx CA.pfx
(If you don't specify -pfx CA.pfx, it will launch the interactive interface, in which case you need to tick the box to export the private key.)
Then, try to decrypt using that pfx/p12 file instead.

I think you should be using "-sky exchange" to generate a public/private key pair.

Related

X509 to Create JWT - "Cannot find the requested object"

I'll start by saying I know very little about cryptography. I know what public/private keys are in theory. I generated some using openSSL for Windows. My plan is to sign a JWT with the private key so it can be verified with the public key.
The lines I used to generate the keys are came from https://www.claudiobernasconi.ch/2016/04/17/creating-a-self-signed-x509-certificate-using-openssl-on-windows/
openssl genrsa 2048 > private.key
openssl req -new -x509 -nodes -sha1 -days 1000 -key private.key > public.cer
I opened the private key in notepad++ and copy/pasted the strings into a variable in C# (I tried removing line breaks, and keeping linebreaks with #"").
I try to create the x509 using the variable like so:
var x509 = new X509Certificate2( Convert.FromBase64String( privateKey ) );
However, I am getting a WindowsCryptographicException with the message "Cannot find the requested object".
I know what I want to do is possible, because we use that exact same line (with a different string) in our PROD code to read a JWT (I assume this string is my company's public key). If I pass that string into my x509 constructor, the cert generates successfully, but of course I can't sign the JWT with it because it knows it's public.
The x509 ctor also seems to be working for the author of this post: Trouble signing a JWT token with an x509 Certificate, but I can't get as far as him.
So, what am I doing wrong? What can't be found, and how do I "draw a map" to it?
==EDIT==
I thought there was enough of an example already, but I guess here you go:
using System;
using System.Security.Cryptography.X509Certificates;
namespace LocalTokenApi.JWT
{
public class JwtBuilder
{
public void GenerateJwt( string xmlUri )
{
var privateKey = #"
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDQrPEi4DwPJ65D
ZEybExHaslb2OZt+e/TRZAkO7LrlVf5aH7uiYYL42yGxhyBxQVbNDW8cAyD/r4o1
zRCCjBS1yk36YtZ+HzIc7X5c3YMmfC6k2r5GD6Ah9fkfhAbqzIiJo/GEJokCDkHS
tyniDDDnI6gteibuTRqS5qnA7YEhedqy5uOdb5TAKCLMCNJgkg9+lNTUPTg+D+Gj
94rSMJ9bpTEvU+sfta5UrDF2Owy15b9ExkELyJAWSTV/LcMrlhkZtkVQtNZM1xov
yNBPCu8+LsRnifjduS9MMh9z3RmnIE/MFl1XUnB/Ocf2HVQavP2U240kYrStVNz7
/twmPnUHAgMBAAECggEBAKw+Q9/ktM5Rk46+6FiMOg2JbSxaSpXxnReE+dEe5/nK
rGMZlFgpIuFkhwAxKD8zjoE82HyHvKIv8+YKuNj45VNUhF7rXF7IQyYLhmUC1nFa
yWl7wNi1pxjBHnu8D7WZVA5Ai2boI+jVedGDLIgQRgFTtkqrbB0A+bFNwcqkgBTv
MFYi8N5vJky3lnpivSY/hG7bOh9GacEDMqMy7aAe+15ppfHbzDcf5AUtHTwhtjc8
G/2VMdIUhozmz3b5XoSjuGeFSc/6CVsjKeLguRYr7Z4i0iaj89u60UPNwhxXTZlg
rrxfT7aZCgghSDLll/hH0Lqtwmd/ym9A2XRWxDabZtkCgYEA+Lt2P9CmoLun7/NN
i0IVnCaB4dpVaV7KgPtlWtE3oKB5wMhX18pJo5hB16UWfzBRRN3qL56iC1qJBAmW
pm/3ypcuzwm9PZrnPdb93cfWjW/GIMcK8oNUjpA2f/iLtZSO7iEua3Qtl1bJrJVv
MdVa+xdTViNR1TfhcJOnDmjH2mUCgYEA1sXaOYgi78S8m2LCyR5cJNTdW/7eBXVj
X48F1ZmpQGQ6+7258I43Gr2fHryAD9fidmi6YrRM+dDpk5FsIoVFK+Lqa4Oy79/y
oQt1HaByfvoBpDsCUY6yf/H4XhMUbZ/CF3tkceFvzBrrsAuuqh7OIsqe7hho9Sgd
/F8asP5exPsCgYA4wETtsISkPczGccPqlyxpEVwnFPLR9N/NaA6rFvtTOeots0hf
ovcETZQQSMmGQZb5WIy7Sr18S67hbfKijP+DiNUURguYh8RlFq2bsaHhaXRSPDfi
N1bOpFbbAfGWf4vRB18ZA0v3sMSZDQtu2lhE3ACWsb5VIMfeMMI4Bm47BQKBgQCP
/ljAB9D8lhepyj40HyHCI+FBg5ARctGsSLStr/c0z75n950Jdh/l0sozDkiB1sjj
gHWuJZoSR4nCwVYRku58bQekC8lVX/1JEeh0c5UwIqglFtcIHTb55x4Q3JPup5S2
r6j5XR7aZhYskriJIFwuIVEK6ty7uSjZgl3f2rtpLwKBgQC0BeuhdrQzrD8kg6cV
ZTvX12F5qJ1PFfbSpI9NwI5opqgCeGfUElEa32ig1v42taXNthWGGpFsUoSCoJG2
T2bDQ05TOItkg5/oVPJHS1ia26bxafTrXHtDoeuZ/G5oip2qULtQ62vUcazdsJ6x
zN6C1hsvJ4Kb3xPd2ZizjfDgAQ==";
var x509 = new X509Certificate2( Convert.FromBase64String( privateKey ) );
}
}
}
The data you provide to the constructor is not what the X509Certificate2 class expects. The X509Certificate2 expects the PKCS12 format.
In order to create data in the right format, the certificate including the private key as PKCS12, you missed one call to OpenSSL from the original source you linked to:
openssl pkcs12 -export -in public.cer -inkey private.key -out cert_key.p12
The data you have to pass to the X509Certificate2 constructor is then in the cert_key.p12 file. In case the private key is encrypted, you also have to specify the password as a second parameter to the X509Certificate2 constructor.

.net core get certificate private key to work with GRPC

I create a self sign certificate. Later I create a google GRPC server who needs a certificate and key file. I try to get the infos from a loaded .net certificate. I get the cert but I have problems with the key file. I dont get the correct key format from the stored cert.
That is only a shot example. Normally I want to store the cert in at the .net cert store and when the grpc service ic created I want to read the cert and key from the store. With that I will instantiate the service.
Cert creation:
openssl req -newkey rsa:4096 -x509 -sha256 -days 3650 -nodes -out myCert.crt -keyout myCert.key -config cert.conf
openssl pkcs12 -export -in myCert.crt -inkey myCert.key -out myCert.pfx -passin pass: -passout pass:
Then I load the cert as a .net certificate (normally I will get it from the store):
var cert = new X509Certificate2("myCert.pfx", "", X509KeyStorageFlags.Exportable);
// some stringbuild before to add -----BEGIN PRIVATE KEY-----
var privateKeyFromDotNetCert = Convert.ToBase64String(x509Certificate2.GetRSAPrivateKey().ExportRSAPrivateKey());
// for reference read the original key..only to shwo the problem
var serverkey = File.ReadAllText("myCert.key");
privateKeyFromDotNetCert is different to the original server key.
later I try to
var keypair = new KeyCertificatePair(cacert, serverkey);
var sslCredentials = new SslServerCredentials(new List<KeyCertificatePair> { keypair }, cacert, false);
with serverKey it works fine but not with the extracted key from the .net certificate. Is it possible to get the correct key from the .net certificate?
Cert creation from .net cert
With the first answer I tried:
StringBuilder builder = new StringBuilder();
builder.Append("-----BEGIN PRIVATE KEY-----");
builder.AppendLine(Convert.ToBase64String(certificate.GetRSAPrivateKey().ExportPkcs8PrivateKey(),Base64FormattingOptions.InsertLineBreaks));
builder.Append("-----END PRIVATE KEY-----");
privateKeyFromDotNetCert = builder.ToString();
This was working. privateKeyFromDotNetCert is not equal to serverkey. But accepted from the grpc service.
Your comment says you're prepending -----BEGIN PRIVATE KEY----- (and presumably appending -----END PRIVATE KEY-----), but the export method you chose (ExportRSAPrivateKey) doesn't match that format. Try ExportPkcs8PrivateKey, which exports in the PKCS#8 PrivateKeyInfo format, which matches the -----BEGIN PRIVATE KEY----- header. (ExportRSAPrivateKey produces a PKCS#1 RSAPrivateKey, which is -----BEGIN RSA PRIVATE KEY-----.)
You probably also want to use the Base64FormattingOptions.InsertLineBreaks option.

Verifying the chain of a self-signed certificate when using SslStream

I have a chain.pem
-----BEGIN CERTIFICATE-----
// My server cert signed by intemediate CA
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
// My intermediate cert signed by root CA
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
// My self signed root cert
-----END CERTIFICATE-----
as well as a server.key.pem
-----BEGIN RSA PRIVATE KEY-----
// Private key for server cert
-----END RSA PRIVATE KEY-----
From there, I generate a pfx file - which has the server cert with its private key along with the rest of the chain.
openssl pkcs12 -export -out certificate.pfx -inkey server.key.pem -in chain.pem
I leave the export password blank
Next I host an TcpListener with an SslStream
namespace fun_with_ssl
{
internal class Program
{
public static int Main(string[] args)
{
var serverCertificate = new X509Certificate2("certificate.pfx");
var listener = new TcpListener(IPAddress.Any, 1443);
listener.Start();
while (true)
{
using (var client = listener.AcceptTcpClient())
using (var sslStream = new SslStream(client.GetStream(), false))
{
sslStream.AuthenticateAsServer(serverCertificate, false, SslProtocols.Tls12, false);
//send/receive from the sslStream
}
}
}
}
}
But when I try to check the chain from openssl, it fails
openssl s_client -connect 127.0.0.1:1443 -CAfile ca.cert.pem
CONNECTED(00000005)
depth=0 CN = SERVER
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 CN = SERVER
verify error:num=21:unable to verify the first certificate
verify return:1
---
Certificate chain
0 s:CN = SERVER
i:CN = Intermediate
---
Server certificate
-----BEGIN CERTIFICATE-----
// My Server certificate
-----END CERTIFICATE-----
subject=CN = SERVER
issuer=CN = Intermediate
---
No client certificate CA names sent
Client Certificate Types: RSA sign, DSA sign, ECDSA sign
Requested Signature Algorithms: RSA+SHA256:RSA+SHA384:RSA+SHA1:ECDSA+SHA256:ECDSA+SHA384:ECDSA+SHA1:DSA+SHA1:RSA+SHA512:ECDSA+SHA512
Shared Requested Signature Algorithms: RSA+SHA256:RSA+SHA384:RSA+SHA1:ECDSA+SHA256:ECDSA+SHA384:ECDSA+SHA1:DSA+SHA1:RSA+SHA512:ECDSA+SHA512
Peer signing digest: SHA256
Peer signature type: RSA
Server Temp Key: ECDH, P-384, 384 bits
---
SSL handshake has read 1439 bytes and written 481 bytes
Verification error: unable to verify the first certificate
---
New, TLSv1.2, Cipher is ECDHE-RSA-AES256-GCM-SHA384
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
Protocol : TLSv1.2
Cipher : ECDHE-RSA-AES256-GCM-SHA384
Session-ID: E82C0000B86186D0051CFE6290C12F0D62C4D376B7E40437029B8B85687C4B18
Session-ID-ctx:
Master-Key: 13681EAE940F241726072A4586A96A9FEEEF29B8309B9122FA2F07AC7C9F949128CB66D0F9C430E1D2480E61E287C578
PSK identity: None
PSK identity hint: None
SRP username: None
Start Time: 1566533377
Timeout : 7200 (sec)
Verify return code: 21 (unable to verify the first certificate)
Extended master secret: yes
---
140266287337920:error:14094416:SSL routines:ssl3_read_bytes:sslv3 alert certificate unknown:../ssl/record/rec_layer_s3.c:1528:SSL alert number 46
I doesn't seem as though it is presenting the intermediate or the root certificate so that it can verify the chain. What am I missing here?
In my scenario, the client would have the root certificate public key.
Even if the PFX contained the entire chain, using the single-certificate constructor makes it only load the cert which had a private key, and the rest are discarded.
Even if the load-the-PFX-as-a-collection method is used, SslStream only uses a collection to find an acceptable server certificate (has a private key and the proper EKU value), then ignores the rest.
The intermediate certificate is only sent when it can be found, via system ambient context, by X509Chain. If your certificate is not public-trust then the best answer for your scenario is to add the intermediate (and, optionally, the root) to the CurrentUser\CA (X509StoreName.CertificateAuthority) certificate store. The "CA" store doesn't provide trust, it's just a grab bag of all the intermediate issuer CAs the system has seen, and the system uses it as a cache when building new chains.
You can do this programmatically at startup by
X509Certificate2 serverCertificate = null;
using (X509Store store = new X509Store(StoreName.CertificateAuthority, StoreLocation.CurrentUser))
{
store.Open(OpenFlags.ReadWrite);
X509Certificate2Collection coll = new X509Certificate2Collection();
coll.Import("certificate.pfx");
foreach (X509Certificate2 cert in coll)
{
if (cert.HasPrivateKey)
{
// Maybe apply more complex logic if you really expect multiple private-key certs.
if (serverCertificate == null)
{
serverCertificate = cert;
}
else
{
cert.Dispose();
}
}
else
{
// This handles duplicates (as long as no custom properties have been applied using MMC)
store.Add(cert);
cert.Dispose();
}
}
}
// tcpListener, et al.
Other options: Feed the whole collection into X509Chain.ChainPolicy.ExtraStore, call X509Chain.Build on serverCert, only add the certs after the first one (and optionally not the last one)... just depends on how much extra stuff is expected to be in the PFX.

Import BouncyCastle X509Certificate + Private Key (RSA) into Windows Certificate Store

I've tried about everything to import a BouncyCastle-based X509Certificate instance with the associated private key (RsaPrivateCrtKeyParameters) via a .NET X509Certificate2 + an RSACryptoServiceProvider instances and saved it into a certificate store (.NET's X509Store, My/CurrentUser).
In the Certificate Store MMC snapin, it seems like there is a private key associated with the certificate, and I've verified that a new key container is created in the appropriate place on disk, but when I try to export the certificate, I get the dreaded "Note: The associated private key cannot be found. Only the certificate can be exported" message.
If I run certutil -user -repairstore my THUMBPRINT, I get the following error:
ERROR: Certificate public key does NOT match stored keyset
From the other information it spits out, I can clearly see that the public keys differ, and that the Algorithm Parameters equals "05 00" on the Certificate Public Key, but not on the Container Public Key.
In fact, I was not aware that there was a concept of a container public key, so I'm just very confused now. Does anyone have some working code for doing this?
I found the solution in Cabadam's answer here:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/ad01b2eb-1890-431a-86ae-e5da0e02b5b0/cryptographicexception-key-does-not-exist-when-attempting-to-connect-to-remote-service
RSACryptoServiceProvider tempRcsp = (RSACryptoServiceProvider)DotNetUtilities.ToRSA((RsaPrivateCrtKeyParameters)keyPair.Private);
RSACryptoServiceProvider rcsp = new RSACryptoServiceProvider(new CspParameters(1, "Microsoft Strong Cryptographic Provider", new Guid().ToString(), new CryptoKeySecurity(), null));
rcsp.ImportCspBlob(tempRcsp.ExportCspBlob(true));
dotnetCertificate2.PrivateKey = rcsp;
// Save the certificate to the X509Store

How do you generate an x509Certificate based solely on a public key in RSA format?

I have an RSA public key in an XML format. I need to take this key and create an x.509 Certificate using that public key. I do not have access to the private key to complete the key pair. All examples that I have found involve either generating the key pair or having access to both the public and private keys.
Below is a small snippet from a test routine I have been working on to accomplish this.
RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
provider.FromXmlString("<RSAKeyValue><Modulus>puEVvRbrLAz.......c1W5j/vqJSUrXo16k=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>");
Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters key = Org.BouncyCastle.Security.DotNetUtilities.GetRsaPublicKey(provider);
//<!*** Do some code to take RsaKeyParameters and create an x.509 Certificate ***>
var fOut = new System.IO.StreamWriter(#"C:\certificate.pem", false);
var pw = new Org.BouncyCastle.OpenSsl.PemWriter(fOut);
pw.WriteObject(key);
Just to clarify the issue of impossibility of generating a certificate using a public key only.
AFAIK, certificate must be signed. And it must be done with a private key - that's why you need one for generating a certificate. If certificate is signed with the paired private key of the open key you are going to issue a certificate for, than it is a self-signed certificate. Otherwise you need a private key of so called Certificate Authority for signing.

Categories