SalesForce JWT in C# - private key issue with ForceDotNetJwtCompanion - c#

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

Related

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.

When I use localhost connect, if I use the IP it doesn't connect

I run the server and the client in the same computer, for testing. If in the client I use the address localhost it works, but if I use the IP of the computer "192.16.1.33" it fails. The error message is this:
D0725 20:25:53.870862 Grpc.Core.Internal.NativeExtension gRPC native library loaded successfully.
D0725 20:25:53.872652 0 T:\src\github\grpc\workspace_csharp_ext_windows_x64\src\core\ext\filters\client_channel\lb_policy_registry.cc:39: registering LB policy factory for "grpclb"
D0725 20:25:53.872779 0 T:\src\github\grpc\workspace_csharp_ext_windows_x64\src\core\ext\filters\client_channel\lb_policy_registry.cc:39: registering LB policy factory for "cds_experimental"
D0725 20:25:53.872871 0 T:\src\github\grpc\workspace_csharp_ext_windows_x64\src\core\ext\filters\client_channel\lb_policy_registry.cc:39: registering LB policy factory for "eds_experimental"
D0725 20:25:53.872962 0 T:\src\github\grpc\workspace_csharp_ext_windows_x64\src\core\ext\filters\client_channel\lb_policy_registry.cc:39: registering LB policy factory for "lrs_experimental"
D0725 20:25:53.873053 0 T:\src\github\grpc\workspace_csharp_ext_windows_x64\src\core\ext\filters\client_channel\lb_policy_registry.cc:39: registering LB policy factory for "priority_experimental"
D0725 20:25:53.873118 0 T:\src\github\grpc\workspace_csharp_ext_windows_x64\src\core\ext\filters\client_channel\lb_policy_registry.cc:39: registering LB policy factory for "weighted_target_experimental"
D0725 20:25:53.873181 0 T:\src\github\grpc\workspace_csharp_ext_windows_x64\src\core\ext\filters\client_channel\lb_policy_registry.cc:39: registering LB policy factory for "xds_routing_experimental"
D0725 20:25:53.873249 0 T:\src\github\grpc\workspace_csharp_ext_windows_x64\src\core\ext\filters\client_channel\lb_policy_registry.cc:39: registering LB policy factory for "pick_first"
D0725 20:25:53.873311 0 T:\src\github\grpc\workspace_csharp_ext_windows_x64\src\core\ext\filters\client_channel\lb_policy_registry.cc:39: registering LB policy factory for "round_robin"
D0725 20:25:53.873377 0 T:\src\github\grpc\workspace_csharp_ext_windows_x64\src\core\ext\filters\client_channel\resolver\dns\c_ares\dns_resolver_ares.cc:504: Using ares dns resolver
I0725 20:25:55.903532 0 T:\src\github\grpc\workspace_csharp_ext_windows_x64\src\core\ext\filters\client_channel\subchannel.cc:1033: Connect failed: {"created":"#1595701555.902000000","description":"OS Error","file":"T:\src\github\grpc\workspace_csharp_ext_windows_x64\src\core\lib\iomgr\tcp_client_windows.cc","file_line":105,"os_error":"Unable to retrieve error string","syscall":"ConnectEx","wsa_error":10061}
I0725 20:25:55.904211 0 T:\src\github\grpc\workspace_csharp_ext_windows_x64\src\core\ext\filters\client_channel\subchannel.cc:970: Subchannel 0000016821801D70: Retry immediately
I0725 20:25:55.904411 0 T:\src\github\grpc\workspace_csharp_ext_windows_x64\src\core\ext\filters\client_channel\subchannel.cc:997: Failed to connect to channel, retryin
It seems that the channel is created, because it is the subchannel 0000016821801D70, but for some reason can't get the response from the server.
The code of the server is this:
const int miPort = 5001;
var cacert = File.ReadAllText("Keys/ca.crt");
var cert = File.ReadAllText("Keys/server.crt");
var key = File.ReadAllText("Keys/server.key");
var keypair = new KeyCertificatePair(cert, key);
var sslCreds = new SslServerCredentials(new List<KeyCertificatePair>
{
keypair
}, cacert, false);
Server server = new Server()
{
Services = { Gestor.BindService(new GestorAplicacionesService()) },
//Ports = { new ServerPort("0.0.0.0", miPort, sslCreds) },
Ports = { new ServerPort("localhost", miPort, sslCreds) },
};
server.Start();
Console.WriteLine("Started server on port " + miPort);
server.ShutdownTask.Wait();
The code of my client is this:
var miCaCertificate = File.ReadAllText("Keys/ca.crt");
var miClientCertificate = File.ReadAllText("Keys/client.crt");
var miClientPrivateKey = File.ReadAllText("Keys/client.key");
var keypair = new KeyCertificatePair(_clientCertificate, _clientPrivateKey);
var sslCreds = new SslCredentials(_caCertificate, keypair);
var channel = new Channel(_serviceAddress, _port, sslCreds);
var client = new Gestor.GestorClient(channel);
await client.MiSaludoAsync();
The certificates works, because with localhost it works, but this is the script that I use to create a self signed certificates with OpenSSL:
# Generate valid CA
openssl genrsa -passout pass:1234 -des3 -out ca.key 4096
openssl req -passin pass:1234 -new -x509 -days 365 -key ca.key -out ca.crt -subj "/C=SP/ST=Spain/L=Valdepenias/O=Test/OU=Test/CN=Root CA"
# Generate valid Server Key/Cert
openssl genrsa -passout pass:1234 -des3 -out server.key 4096
openssl req -passin pass:1234 -new -key server.key -out server.csr -subj "/C=SP/ST=Spain/L=Valdepenias/O=Test/OU=Server/CN=localhost"
openssl x509 -req -passin pass:1234 -days 365 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt
# Remove passphrase from the Server Key
openssl rsa -passin pass:1234 -in server.key -out server.key
# Generate valid Client Key/Cert
openssl genrsa -passout pass:1234 -des3 -out client.key 4096
openssl req -passin pass:1234 -new -key client.key -out client.csr -subj "/C=SP/ST=Spain/L=Valdepenias/O=Test/OU=Client/CN=localhost"
openssl x509 -passin pass:1234 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt
# Remove passphrase from Client Key
openssl rsa -passin pass:1234 -in client.key -out client.key
pause
I have tried with 2 servers certificates. One I use as common name "localhost", this works when I run the client in the same computer, but it doesn't work when in the client I use the IP.
Later I have tried another server certificate, with the IP as common name, but I can't still connect to the server.
What I have read is that in the certificate server
Creating a certificate for an IP is a bit different than creating a certificate for a name. For a certificate you can add an alternative name (this is how you create wildcard or multi-domain certificates) or a list of IP addresses.
Check the configuration of OpenSSL if the [alt_names] is correct. See https://medium.com/#antelle/how-to-generate-a-self-signed-ssl-certificate-for-an-ip-address-f0dd8dddf754 for a bit more information
To make sure that your certificate is for a domain name or an ip address, use the command openssl x509 -in client.crt -text -noout, in the output of this command you will see something as follow:
X509v3 extensions:
X509v3 Subject Alternative Name:
IP Address:127.0.0.1
if it starts with 'DNS:' instead of 'IP Address', then you need to update the creation of your certificate.

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

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

How to enable server side SSL for gRPC?

New to gRPC and couldn't really find any example on how to enable SSL on the server side. I generated a key pair using openssl but it complains that the private key is invalid.
D0608 16:18:31.390303 Grpc.Core.Internal.UnmanagedLibrary Attempting to load native library "...\grpc_csharp_ext.dll"
D0608 16:18:31.424331 Grpc.Core.Internal.NativeExtension gRPC native library loaded successfully.
E0608 16:18:43.307324 0 ..\src\core\lib\tsi\ssl_transport_security.c:644: Invalid private key.
E0608 16:18:43.307824 0 ..\src\core\lib\security\security_connector.c:821: Handshaker factory creation failed with TSI_INVALID_ARGUMENT.
E0608 16:18:43.307824 0 ..\src\core\ext\transport\chttp2\server\secure\server_secure_chttp2.c:188: Unable to create secure server with credentials of type Ssl.
Here's my code
var keypair = new KeyCertificatePair(
File.ReadAllText(#"root-ca.pem"),
File.ReadAllText(#"ssl-private.key"));
SslServerCredentials creds = new SslServerCredentials(new List<KeyCertificatePair>() {keypair});
Server server = new Server
{
Services = { GrpcTest.BindService(new GrpcTestImpl()) },
Ports = { new ServerPort("127.0.0.1", Port, creds) }
};
Here's what I did.
Using OpenSSL, generate certificates with the following:
#echo off
set OPENSSL_CONF=c:\OpenSSL-Win64\bin\openssl.cfg
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 365 -key ca.key -out ca.crt -subj "/C=US/ST=CA/L=Cupertino/O=YourCompany/OU=YourApp/CN=MyRootCA"
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 365 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt
echo Remove passphrase from server key:
openssl rsa -passin pass:1111 -in server.key -out server.key
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 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt
echo Remove passphrase from client key:
openssl rsa -passin pass:1111 -in client.key -out client.key
Change password 1111 to anything you like
Server:
var cacert = File.ReadAllText(#"ca.crt");
var servercert = File.ReadAllText(#"server.crt");
var serverkey = File.ReadAllText(#"server.key");
var keypair = new KeyCertificatePair(servercert, serverkey);
var sslCredentials = new SslServerCredentials(new List<KeyCertificatePair>() { keypair }, cacert, false);
var server = new Server
{
Services = { GrpcTest.BindService(new GrpcTestImpl(writeToDisk)) },
Ports = { new ServerPort("0.0.0.0", 555, sslCredentials) }
};
server.Start();
Client:
var cacert = File.ReadAllText(#"ca.crt");
var clientcert = File.ReadAllText(#"client.crt");
var clientkey = File.ReadAllText(#"client.key");
var ssl = new SslCredentials(cacert, new KeyCertificatePair(clientcert, clientkey));
channel = new Channel("localhost", 555, ssl);
client = new GrpcTest.GrpcTestClient(channel);
If "localhost" doesn't work, use the host name instead.
If usage of Certificate Authority (CA) and Certificate Signing Request (CSR) is too sophisticated for your task, you can use self-signed certificates.
Let say, there is 1 server and 2 (or more) clients.
Execute at client1:
openssl req -x509 -newkey rsa:4096 -nodes -keyout client.key -out client.crt -days 3650 -subj '/CN=client1' # generate client1 cert and key
sudo bash -c 'echo "192.168.1.101 my.server" >> /etc/hosts' # create domain for server - if necessary only
scp client.crt server-user#my.server:/path/to/certs/client1.crt # copy public cert client1 to server machine
Execute at client2:
openssl req -x509 -newkey rsa:4096 -nodes -keyout client.key -out client.crt -days 3650 -subj '/CN=client2' # generate client2 cert and key
sudo bash -c 'echo "192.168.1.101 my.server" >> /etc/hosts' # create domain for server- if necessary only
scp client.crt server-user#my.server:/path/to/certs/client2.crt # copy public cert client2 to server machine
Execute at server:
openssl req -x509 -newkey rsa:4096 -nodes -keyout server.key -out server.crt -days 3650 -subj '/CN=my.server' # generate server cert and key
scp server.crt client1-user#client1-addr:/path/to/certs # copy public cert server to client1 machine
scp server.crt client2-user#client2-addr:/path/to/certs # copy public cert server to client2 machine
cat client1.crt client2.crt > client.crt # combine client certs into the single file
Server code:
var clientCert = File.ReadAllText(Path.Combine(certPath, "client.crt"));
var serverCert = File.ReadAllText(Path.Combine(certPath, "server.crt"));
var serverKey = File.ReadAllText(Path.Combine(certPath, "server.key"));
var keyPair = new KeyCertificatePair(serverCert, serverKey);
var credentials = new SslServerCredentials(new List<KeyCertificatePair> { keyPair }, clientCert, true);
var server = new Server
{
Services = { MyService.BindService(new MyAdminService()) },
Ports = { new ServerPort("0.0.0.0", 54321, credentials) }
};
Client code:
var serverCert = File.ReadAllText(Path.Combine(_certPath, "server.crt"));
var clientCert = File.ReadAllText(Path.Combine(_certPath, "client.crt"));
var clientKey = File.ReadAllText(Path.Combine(_certPath, "client.key"));
var credentials = new SslCredentials(serverCert, new KeyCertificatePair(clientCert, clientKey));
var channel = new Channel("my.server:54321", credentials);
var client = new MyService.MyServiceClient(channel);
IMPORTANT!
To use TLS certificates, use a domain name when generate server certificate.
Client certificates can use any unique string.
Domain name should contain at least 1 dot (.), e.g. my.server or my.server.customzone
If use top-level domain like my-server, it causes a long waiting to resolve it (for me it always about 76 seconds).
Pros:
- no need to generate CSR, pass it to machine with CA, sign it there and copy back to originating machine
Cons:
- adding new client requires adding certificate to server
If you have tried what #qmo has suggested and still not working and you're getting the same error saying "StatusCode=Unavailable, Detail="DNS resolution failed" I fixed it by adding a new record in my host file (located in C:\Windows\System32\drivers\etc for Windows).
127.0.0.1 DESKTOP-QNCI7UN
Where DESKTOP-QNCI7UN is the name of my machine. Then in the client I'm using:
channel = new Channel("DESKTOP-QNCI7UN", 50000, ssl);
By using "locahost" it was not working. So by using the machine name in the client + added record in the host file fixed the issue.

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