Generate self signed RSA-2048-SHA-256 certificate PFX file using openSSL - c#

I am trying to create a self signed RSA-2048-SHA-256 certificate PFX file, in order to use it for data signing in my WCF requests.
I used some openSSL examples in order to create a certificate PFX file, but even though I set the SHA algorithm to 256, when I load it in my .net app, I see that this certificate's private key, has these settings:
KeyExchangeAlgorithm = RSA-PKCS1-KeyEx
SignatureAlgorithm = http://www.w3.org/2000/09/xmldsig#rsa-sha1
and when I use the code below in order to consume this certificate, I am getting "Invalid algorithm specified exception", but if I change the SHA256CryptoServiceProvider to SHA1CryptoServiceProvider everything works fine.
string msg = "This is my test message";
X509Certificate2 privateCert = new X509Certificate2("C:\\TEMP\\private.pfx", "12345");
byte[] signature = (privateCert.PrivateKey as RSACryptoServiceProvider).SignData(new UTF8Encoding().GetBytes(msg), new SHA256CryptoServiceProvider());
I can only assume that my certificate file was not created with SHA256, but instead with some kind of default SHA1 algorithm.
Those are the steps I used in order to create my certificate:
openssl req -x509 -days 365 -newkey rsa:2048 -sha256 -keyout key.pem -out cert.pem
openssl pkcs12 -export -in cert.pem -inkey key.pem -out private.pfx
What am I doing wrong?

What am I doing wrong?
Believing that those two properties have meaning :).
The two values you're seeing are hard-coded into RSACryptoServiceProvider. Other RSA types (such as RSACng) have different, less confusing, hard-coded values.
The problem is that a key doesn't have either of those attributes. A key can be used for multiple purposes (though NIST recommends against it). A TLS session (or EnvelopedCMS document, etc) can have a key exchange algorithm. A certificate, SignedCMS document, or other such material can have a signature (and thus a signature algorithm).
To know that your certificate was signed with RSA-PKCS1-SHA256 you need to look at X509Certificate2.SignatureAlgorithm.
switch (cert.SignatureAlgorithm.Value)
{
case "1.2.840.113549.1.1.4":
return "RSA-PKCS1-MD5";
case "1.2.840.113549.1.1.5";
return "RSA-PKCS1-SHA1";
case "1.2.840.113549.1.1.11";
return "RSA-PKCS1-SHA2-256"; // Winner
case "1.2.840.113549.1.1.12":
return "RSA-PKCS1-SHA2-384";
case "1.2.840.113549.1.1.13":
return "RSA-PKCS1-SHA2-512";
default:
throw new SomethingFromTheFutureOrMaybeNotRSAException();
}

Related

SalesForce JWT in C# - private key issue with ForceDotNetJwtCompanion

I am trying to connect to Salesforce via the JWT authentication method using the ForceDotNetJwtCompanion NuGet package and am running into issues. I am admittedly very new to cryptography so please forgive my lack of knowledge.
Based on the example given on the ForceDotNetJwtCompanion github page, I have attempted to implement the section under 'usage in code' https://github.com/claboran/ForceDotNetJwtCompanion
string certificatePath = AppDomain.CurrentDomain.BaseDirectory + "Resources\\cert.keys.p12";
X509Certificate2 certificate = new X509Certificate2(certificatePath, "pw", X509KeyStorageFlags.Exportable);
RSA certPrivateKey = certificate.GetRSAPrivateKey();
string privateKeyXML = certPrivateKey.ToXmlString(false);
var apiVersion = "v50.0";
var privateKey = privateKeyXML;
var passPhrase = "passphrase";
var isProd = true;
var authClient = new JwtAuthenticationClient(apiVersion, isProd);
await authClient.JwtPrivateKeyAsync(
"value_that_ive_removed",
privateKey,
passPhrase,
"user#company.com",
"https://login.salesforce.com/services/oauth2/token"
);
var accessToken = authClient.AccessToken;
To facilitate this, I've got a p12 certificate I've loaded and extracted the private key. I get an error on the call for await authClient.JwtPrivateKeyAsync:
System.NullReferenceException
HResult=0x80004003
Message=Object reference not set to an instance of an object.
Source=ForceDotNetJwtCompanion
StackTrace:
at ForceDotNetJwtCompanion.Util.KeyHelpers.CreatePrivateKeyWrapperWithPassPhrase(String key, String passphrase)
at ForceDotNetJwtCompanion.JwtAuthenticationClient.<JwtPrivateKeyAsync>d__29.MoveNext()
I believe it may be because my privatekey string is in XML format (I have verified there's a Modulus and Exponent in the XML data) but I am not sure. The method giving a null reference xception only deals with the key and passphrase values so I assume it does not like my privatekey although I don't see any documentation as to what it is actually expecting. Any assistance would be greatly appreciated.
The private key needs to be in PEM format. Please create your key with openssl like it has been described here:
openssl genrsa -des3 -passout pass:secret -out server.pass.key 2048
openssl rsa -passin pass:secret -in server.pass.key -out server.key
openssl req -new -key server.key -out server.csr
openssl x509 -req -sha256 -days 365 -in server.csr -signkey server.key -out server.crt

An HTTP/2 connection could not be established because the server did not complete the HTTP/2 handshake

I wanted to use a self signed certificate to can use gRPC dotnet, but I get this error when I call the service from my client: An HTTP/2 connection could not be established because the server did not complete the HTTP/2 handshake.
I have created the pfx certificate with this script:
#echo off
set path="C:\Program Files\OpenSSL-Win64\bin"
#set OPENSSL_CONF=D:\programas\OpenSSL-Win64\OpenSSL-Win64\bin\openssl.cfg
#CA
echo Generate CA key:
openssl genrsa -passout pass:1111 -des3 -out ca.key 4096
echo Generate CA certificate:
openssl req -passin pass:1111 -new -x509 -days 36500 -key ca.key -out ca.crt -subj "/C=US/ST=CA/L=Cupertino/O=YourCompany/OU=YourApp/CN=MyRootCA"
#SERVER
echo Generate server key:
openssl genrsa -passout pass:1111 -des3 -out server.key 4096
echo Generate server signing request:
openssl req -passin pass:1111 -new -key server.key -out server.csr -subj "/C=US/ST=CA/L=Cupertino/O=YourCompany/OU=YourApp/CN=%COMPUTERNAME%"
echo Self-sign server certificate:
openssl x509 -req -passin pass:1111 -days 36500 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt
#Se crea el certificado pfx
openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt
echo Remove passphrase from server key:
#openssl rsa -passin pass:1111 -in server.key -out server.key
#CLIENT
echo Generate client key
openssl genrsa -passout pass:1111 -des3 -out client.key 4096
echo Generate client signing request:
openssl req -passin pass:1111 -new -key client.key -out client.csr -subj "/C=US/ST=CA/L=Cupertino/O=YourCompany/OU=YourApp/CN=%CLIENT-COMPUTERNAME%"
echo Self-sign client certificate:
openssl x509 -passin pass:1111 -req -days 36500 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt
#Se crea el certificado pfx
openssl pkcs12 -export -out client.pfx -inkey client.key -in client.crt
echo Remove passphrase from client key:
#openssl rsa -passin pass:1111 -in client.key -out client.key
pause
In my service, I use this code:
webBuilder.ConfigureKestrel(options =>
{
options.Listen(IPAddress.Any, 5001, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http2;
listenOptions.UseHttps("server.pfx", "1111");
//listenOptions.UseHttps("<path to .pfx file>", "<certificate password>");
});
});
webBuilder.UseStartup<Startup>();
webBuilder.UseStartup<Startup>();
});
In my client I have this code:
X509Certificate2 miCertificado = new X509Certificate2("client.pfx", "1111");
HttpClientHandler miHandler = new HttpClientHandler();
miHandler.ClientCertificates.Add(miCertificado);
HttpClient miHttpClient = new HttpClient(miHandler);
GrpcChannelOptions misOpciones = new GrpcChannelOptions() { HttpClient = miHttpClient };
var miChannel = GrpcChannel.ForAddress("http://1.1.1.2:5001");
var miClient = MagicOnionClient.Create<IInterface>(miChannel);
ComponentesDto miDataResultado = await miClient.GetDataAsync();
I don't see how it could be the problem. What am I doing wrong?
Thanks.
This question is almost one and half year, and since that, I have done many tries until I could solve my problem.
I don't remember the exact way how I solved the problem, but I will modify this answer if it is needed according to the comments.
My solution is using .NET 6, that is the last stable verstion actually, but I guess that some parts could be work with .NET 5, but keep in mind that this solution is using .NET 6.
One important part in the handshake is the creation of the certificate, and more when it is self signed certificate. My solution works with .crt files instead of the pfx files that I tried to use in my original question.
About the server certificate, one important thing is that it has to have in the SAN the IP of the server, or the URL of the server. You can set both. If you open your .crt file with the notepad for example, you will see something like that:
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
X509v3 Subject Key Identifier:
44:C4:BD:F
X509v3 Authority Key Identifier:
keyid:0F:58:2
DirName:/CN=gRPC-CA
serial:22:A3
X509v3 Extended Key Usage:
TLS Web Server Authentication
X509v3 Key Usage:
Digital Signature, Key Encipherment
X509v3 Subject Alternative Name:
IP Address:192.168.1.100
Signature Algorithm: sha512WithRSAEncryption
In the last 3 lines, you can see that I have in the SAN (Subject Alternative Name), the IP of the server.
To create the certificates, I used easy rsa 3, that it is a tool that make easier to generate self signed certificates. You can find the tool here: https://github.com/OpenVPN/easy-rsa and you can download the binaries in the release section: https://github.com/OpenVPN/easy-rsa/releases.
The general steps are:
1.- copy the file vars.example in the same folder and call it "vars" (with no exetnsion).
2.- Edit the vars file with the information that you need.
3.- Run EasyRSA-Start.bat to start the shell of easy rsa.
4.- Run easyrsa init-pki to init the evironment. IMPORTANT: only do once, because if not you will delete all the certificates that you could have.
5.- Run easyrsa build-ca to create the CA certificate.
6.- Run easyrsa build-server-full 192.168.1.2 nopass to create the certificate for the server. IMPORTANT: change the IP for the IP of your server.
7.- Run easyrsa build-client-full Cliente01 nopass to create the certificate for the client. IMPORTANT: change the name Cliente01 to the name of your certificate. The name you put here, it will be the common name of the certificate.
All the certificates are created in the subfolder PKI.
I am hosting my service in a ASP application. Using minimal API, in the program.cs file of the ASP project I have to create a X509 certificate in this way:
builder.WebHost.ConfigureKestrel((context, options) =>
{
string miStrCertificado = File.ReadAllText("certificados/server.crt");
string miStrKey = File.ReadAllText("certificados/server.key");
X509Certificate2 miCertficadoX509 = X509Certificate2.CreateFromPem(miStrCertificado, miStrKey);
//it is needed to create a second certificate because if not, you will get an error.
//Here you can find information about the issue: https://github.com/dotnet/runtime/issues/74093
X509Certificate2 miCertificado2 = new X509Certificate2(miCertficadoX509.Export(X509ContentType.Pkcs12));
//For security, delete the first certificate.
miCertficadoX509.Dispose();
options.ListenAnyIP(5001, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
listenOptions.UseHttps(miCertificado2);
});
});
And this is the client of grpc:
public async Task<List<FacturasDTO>> MymethodAsync()
{
try
{
//Con certificados
string miStrCertificado = System.IO.File.ReadAllText(#"Certificados\Client.crt");
string miStrClave = System.IO.File.ReadAllText(#"Certificados\Client.key");
string miStrCA = System.IO.File.ReadAllText(#"Certificados\ca.crt");
X509Certificate2 cert = X509Certificate2.CreateFromPem(miStrCertificado, miStrClave);
HttpClientHandler miHttpHandler = new HttpClientHandler();
miHttpHandler.ClientCertificates.Add(cert);
miHttpHandler.ClientCertificateOptions = ClientCertificateOption.Manual;
miHttpHandler.ServerCertificateCustomValidationCallback = ServerCertificateCustomValidation;
HttpClient httpClient = new(miHttpHandler);
GrpcChannel channel = GrpcChannel.ForAddress("https://20.30.40.50:5001", new GrpcChannelOptions
{
HttpClient = httpClient
});
var client = channel.CreateGrpcService<IFacturasService>();
var reply = await client.GetFacturasDTOAsync();
return reply.Facturas;
}
catch (RpcException ex)
{
throw;
}
catch (Exception ex)
{
throw;
}
}
/// <summary>
/// This method has to implement the logic that ensure that server certificate is a trust certificate.
/// This is needed because the server certificate is a self signed certificate.
/// </summary>
/// <param name="requestMessage"></param>
/// <param name="paramCertificadoServidor"></param>
/// <param name="chain"></param>
/// <param name="sslErrors"></param>
/// <returns></returns>
private static bool ServerCertificateCustomValidation(HttpRequestMessage requestMessage, X509Certificate2 paramCertificadoServidor, X509Chain chain, SslPolicyErrors sslErrors)
{
return true;
//GetCerHasString devuelve la huella
//if (paramCertificadoServidor.GetCertHashString() == "xxxxxxxxxxxxxxxx")
//{
// return true;
//}
//else
//{
// return sslErrors == SslPolicyErrors.None;
//}
//// It is possible inpect the certificate provided by server
//Console.WriteLine($"Requested URI: {requestMessage.RequestUri}");
//Console.WriteLine($"Effective date: {certificate.GetEffectiveDateString()}");
//Console.WriteLine($"Exp date: {certificate.GetExpirationDateString()}");
//Console.WriteLine($"Issuer: {certificate.Issuer}");
//Console.WriteLine($"Subject: {certificate.Subject}");
//// Based on the custom logic it is possible to decide whether the client considers certificate valid or not
//Console.WriteLine($"Errors: {sslErrors}");
//return sslErrors == SslPolicyErrors.None;
}
Note that the method ServerCertificateCustomValidation() in the client always return true, it is for testing, but you should to implement the logic to ensure the certificate of the server is a trust certificate. I leave some comment code as example how could it be checked, but it is not tested, so I can't ensure it works or if it is the correct way to do. It is just to have some ideas.
I hope this could solve the problem. If not, leave any comment and I will update the solution.

Understanding self-signed certificates in c#

Recently I came across this c# code:
var dn = new X500DistinguishedName($"CN={_appSettings.CommonName};OU={_appSettings.OrganizationalUnit}", X500DistinguishedNameFlags.UseSemicolons);
SubjectAlternativeNameBuilder sanBuilder = new SubjectAlternativeNameBuilder();
sanBuilder.AddUri(new Uri($"urn:{_appSettings.ApplicationUri}"));
using (RSA rsa = RSA.Create(2048))
{
var request = new CertificateRequest(dn, rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
request.CertificateExtensions.Add(sanBuilder.Build());
var selfSignedCert = request.CreateSelfSigned(new DateTimeOffset(DateTime.UtcNow.AddDays(-1)), new DateTimeOffset(DateTime.UtcNow.AddDays(3650)));
...
}
...
Having a look closer at the CertificateRequest constructor parameters, the rsa key is described as:
A RSA key whose public key material will be included in the certificate or certificate request. If the CreateSelfSigned(DateTimeOffset, DateTimeOffset) method is called, this key is used as a private key.
The bold part is the one I don't really understand. Does that mean that when self signing the certificate, the certificate is signed using the given RSA key AND adds the same key as public key to the certificate?
In my understanding for TLS, we have two public-key pairs, one for signing and one for encryption. The CA signs a certificate with its private key and offers a public key to the clients to verify the signature by decrypting it with the public key, whereas the provider of a service offers a public key which the clients use to encrypt their keys first in the tls handshake which after that gets decrypted with the service providers private key.
However, in the above code sample, we create a certificate that contains what exactly? Server public key is for encryption, but what key for decryption of the signature?

Get signature from X509 certificate

I would like to verify an X509 certificate's signature using a public key from another CA X509 certificate (knowing this certificate was signed directly by this CA). I cannot add the CA certificate to the trusted root CA list on the system.
The X509Certificate or X509Certificate2 both contain the hash value of the certificate (and I can retrieve it using the GetCertHash() method). But neither of those contain a method to retrieve the certificate's signature. See my code below:
X509Certificate2 sslCert = new X509Certificate2(...);
X509Certificate2 CAcert = new X509Certificate2(...);
var rsa = CAcert.PublicKey.Key as RSACryptoServiceProvider;
rsa.VerifyHash(sslCert.GetCertHash(),CryptoConfig.MapNameToOID("SHA1"), ???)
Is there some (possibly different) way of doing this?

How to get the private key from a separate file?

I have an Apache (xampp/wamp) server that provides a SSL connection on port 443.
It uses two certificate files: server.cert and server.key when the latter conains the private key.
I have another server configured to listen to requests on port 843 (flash policy stuff) and response to a certain request with some text reply written in C# which runs separately.
In order to achieve SSL connectivity, i use a flex object called SecureSocket which allowes that, however, it uses the original servers certificate in order to encrypt the request.
My goal is to teach my 843 C# server to decrypt the sent data and encrypt the reply and for this i'm using the X509Certificate object in C#.
However, since the pub and priv keys are on different files, i'm getting FALSE on the following:
string text = System.IO.File.ReadAllText(#"C:\xampp\apache\conf\ssl.crt\server.crt");
UTF8Encoding encoding = new System.Text.UTF8Encoding();
byte[] byteCert = encoding.GetBytes(text);
X509Certificate2 uberCert = new X509Certificate2();
uberCert.Import(byteCert);
Console.WriteLine("Has privateKey:" + uberCert.HasPrivateKey.ToString());
Console.WriteLine("PrivateKey: \n" + uberCert.PrivateKey);
Obviously, the False on uberCert.HasPrivateKey comes from the fact that the private key is on a different file, so my questions are:
1.How can i read the private key using the X509CErtificate2 object?
2.How can i use the public key in order to decrypt the received message and how to re-encrypt it with the private key (in order to send the encrypted response back) ?
Thanks in advance,
Mike.
I've created a small helper NuGet package (based on opensslkey) to create a X509 certificate based on public key and private (rsa) key.
// Generate with: openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout private.key -out certificate_pub.crt
string certificateText = File.ReadAllText("certificate_pub.crt");
string privateKeyText = File.ReadAllText("private.key");
ICertificateProvider provider = new CertificateFromFileProvider(certificateText, privateKeyText);
X509Certificate2 certificate = provider.Certificate;
// Example: use the PrivateKey from the certificate above for signing a JWT token using Jose.Jwt:
string token = Jose.JWT.Encode(payload, certificate.PrivateKey, JwsAlgorithm.RS256);
See NuGet and Github-project for functionality and code-examples.
The private key is likely PEM encoded PKCS#8 structure.
The Mono project provides code to read this format (among other) in the Mono.Security.dll assembly. This assembly is purely managed and will work on Windows, Linux or OSX.
You can't in straight .NET.
You can either use BouncyCastle (see this answer: How to read a PEM RSA private key from .NET) or use a PKCS12 container instead for the username + password, which you can create using OpenSSL's tools from the PEM files.

Categories

Resources