Web service call with a SHA-384 ECDSA certificate - c#

For the project I am working on I have to convert the following legacy code to use a SHA-384 ECDSA certificate, .NET Framework 4.8 as target.
Is it possible natively in C# or do I need some help like BouncyCastle lib and how?
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
[...]
// Certificate is in PKCS#12 format and contains a private key, certificate is password protected with the session id
var certificateBytes = Convert.FromBase64String(base64Certificate.ToString());
var certificate = new X509Certificate2();
certificate.Import(certificateBytes, sessionId, X509KeyStorageFlags.DefaultKeySet);
var result = (HttpWebRequest)WebRequest.Create( url );
result.Headers.Add( "x-jwt-authorization", $"Bearer {JSONWebToken}" );
result.ClientCertificates.Add( Certificate );
var response = (HttpWebResponse)request.GetResponse();
I tried to find a solution/explanation online but didn't work (keep getting a "Not supported" for the PrivateKey property when trying to import the certificate, and a 403 Forbidden as server response).
Also if I want to keep backward compatibility, how do I distinguish if the Base64 string contains a X509 or a SHA one to support both?

Related

C# RestSharp Client Certificate getting exception "The SSL connection could not be established"

I programming in C# .Net Core 3.1 and .Net Standard library where this code is put.
I am not getting my http requests with RestSharp with a client certificate that are NOT installed on the server/computer i am not going to have access to the server/computer where this is going to be running later on..
My code:
// Create a RestSharp RestClient objhect with the base URL
var client = new RestClient(_baseAPIUrl);
// Create a request object with the path to the payment requests
var request = new RestRequest("swish-cpcapi/api/v1/paymentrequests");
// Create up a client certificate collection and import the certificate to it
X509Certificate2Collection clientCertificates = new X509Certificate2Collection();
clientCertificates.Import(_certDataBytes, _certificatePassword, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
// Add client certificate collection to the RestClient
client.ClientCertificates = clientCertificates;
//X509Certificate2 certificates = new X509Certificate2(_certDataBytes, _certificatePassword);
//client.ClientCertificates = new X509CertificateCollection() { certificates };
// Add payment request data
request.AddJsonBody(requestData);
var response = client.Post(request);
var content = response.Content;
I have done what ever i can find on internet and i have used the service test environment with there own generated certificate and generated my own in there production environment with the same result..
I have tested set TLS 1.1 or TLS 1.2 to test, they specified TLS 1.1 on there own curl examples.
Anyone got any idea?
Update 2020-01-08 - I have got information from the service tech support that they only see me sending one certificate but when i trying to debug and checking the X509Certificate2Collection i finding 3 certificate. But they saying they only see one?
IMHO, if the service you are trying to access is protected via SSL cert with expecting public key/private key, then without the cert, you cant access it. If thats the intent of the service to be protected, i think you may only do to check the service health check or at max, check if the service is accessible (without having the client cert)
But just as HTTPS, then you can try to download the cert from the browser if you can access the service URL from your browser, say, to access their meta or a simple GET or something. In chrome, you may see a lock symbol (security icon) just before to the URL. click that, and you may possibly download the public cert. You can install that on your machine and try that.
In case, if that service has a pub cert and the access doesnt need the client cert, then above option may work. and i hope you are installing the CERT and access that via your code from the account that has access to the cert. Lets say, you install the cert under the LocalSystem, then you may need administrator access from code/solution to access that cert path. Or install the cert under the current_user path.
I would Check if am able to access the service from browser as the first step.
This is, just from what i have understood from your question. again, if you can explain whether the service is a protected service based on public key/private key, you need to have the access/cert to use that service.
UPDATE:
I have tried few options on my end, by creating a demo api and a demo client to use client certs. With what i have understood from your query, i assume below may be some of your options to try out.
// --- using the cert path if you have the path (instead of the byte[])
var myCert = new X509Certificate2("<path to the client cert.cer/.pfx>", "secure-password", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
X509CertificateCollection clientCerts = new X509CertificateCollection();
clientCerts.Add(myCert);
OR
var certStore = new X509Store(StoreName.Root, StoreLocation.LocalMachine); //replace with appropriate values based on your cert configuration
certStore.Open(OpenFlags.ReadOnly);
//option 1 (ideally a client must have the cert thumbprint instead of a password
var cert = certStore.Certificates.Find(X509FindType.FindByThumbprint, "<cert Thumbprint>", false);
//option 2 (explore other options based on X509NameTypes
var cert = certStore.Certificates.OfType<X509Certificate2>()
.FirstOrDefault(cert => cert.GetNameInfo(X509NameType.DnsName, false) == "mycompany.dns.name.given in the cert");
client.ClientCertificates = new X509CertificateCollection(cert);
I recommend generate your own certificate
public static void MakeCert()
{
var ecdsa = ECDsa.Create(); // generate asymmetric key pair
var req = new CertificateRequest("cn=foobar", ecdsa, HashAlgorithmName.SHA256);
var cert = req.CreateSelfSigned(DateTimeOffset.Now, DateTimeOffset.Now.AddYears(10));
// Create PFX (PKCS #12) with private key
File.WriteAllBytes("c:\\temp\\mycert.pfx", cert.Export(X509ContentType.Pfx, "P#55w0rd"));
// Create Base 64 encoded CER (public key only)
File.WriteAllText("c:\\temp\\mycert.cer",
"-----BEGIN CERTIFICATE-----\r\n"
+ Convert.ToBase64String(cert.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks)
+ "\r\n-----END CERTIFICATE-----");
}
and then add to request
var client = new RestClient(url);
client.ClientCertificates = new X509CertificateCollection();
client.ClientCertificates.Add(new X509Certificate2("c:\\temp\\mycert.cer"));

How do you decode/decrypt a SignedCMS/PKCS#7 in C# PCL

So, I have an WebAPI that is returning a PKCS#7 file to a client. The client is written as a C# PCL so it can be used in Xamarin iOS and Android projects.
My initial tests worked fine because I was encoding and decoding in my unit tests and could use the Pkcs library. It seems I can't find any way of decoding the data on the client because I don't know of any Pkcs library that works with a PCL.
Can someone tell me how/if this can be done?
So, I did end up switching my project to .netstandard 1.4 and using Portable.BouncyCastle to decode the Cms created on the server side.
Here is the code that I used to decode the Cms. I'm sort of trusting that this is also checking the signature since there is no explicit method for doing that in BouncyCastle like there is via the framework code i.e. CheckSignature().
var cmsParser = new Org.BouncyCastle.Cms.CmsSignedDataParser(dataBytes);
var cmsSignedContent = cmsParser.GetSignedContent();
var contentStream = cmsSignedContent.ContentStream;
var memoryStream = new MemoryStream();
contentStream.CopyTo(memoryStream);
byte[] contentBytes = memoryStream.ToArray();
var decodedContent = Encoding.UTF8.GetString(contentBytes);
In addition I added this to verify the signer info:
cmsParser.GetSignedContent().Drain();
var certStore = cmsParser.GetCertificates("Collection");
var signerInfos = cmsParser.GetSignerInfos();
var signers = signerInfos.GetSigners();
foreach (SignerInformation signer in signers)
{
var certCollection = certStore.GetMatches(signer.SignerID);
foreach (Org.BouncyCastle.X509.X509Certificate cert in certCollection)
{
var result = signer.Verify(cert);
if (!result)
{
throw new Exception("Certificate verification error, the signer could not be verified.");
}
}
}
I'm not 100% sure if this is all I need to do but my client's will communicate via SSL and they are using an HMAC auth with an appId and client secret so I'm not so concerned with in transit issues. I'm basically transferring a "license" file and I want to make sure the contents are not tampered with after it has been saved on the client device.
If anyone has any suggestions or concerns with this please let me know.
Thanks.
I currently use PCLCrypto to encrypt and decrypt using PKCS#7 on the client (in a Xamarin Forms project). I assume it will do what you need as well.
The PKCS#7 wiki example can be found here

C# Generate a non self signed client CX509Certificate Request without a CA using the certenroll.dll

I have a self signed root certificate that I generated in C# using CERTENROLL.dll's CX509CertificateRequest Certificate functionality.
I would like to write a function that generates client certificates signed by my root using the same API. However the only CertEnroll option I can find that does not generate a self signed certificate requires a authenticated CA.
There seems to be a flag for setting a SignerCertificate but it always fails to initialize.
//Initialize cert
var cert = new CX509CertificateRequestCertificate();
//take care of signer
cert.Issuer = issuen;
CSignerCertificate sc = new CSignerCertificate();
var raw = SEScert.GetRawCertData();
var rawStr=Convert.ToBase64String(raw);
sc.Initialize(false, X509PrivateKeyVerify.VerifyNone,
EncodingType.XCN_CRYPT_STRING_BASE64, rawStr); //fails here
cert.SignerCertificate = sc;
Does anyone know how I can generate a client CX509CertificateRequest signed by my root?
Any help or advice would be greatly appreciated.
I was able to solve this.
The encoding of SEScert is a hex string not base64 also the machine context should be set to true not false the correct code looks as follows:
ISignerCertificate signerCertificate = new CSignerCertificate();
signerCertificate.Initialize(true, X509PrivateKeyVerify.VerifyNone,EncodingType.XCN_CRYPT_STRING_HEX, SEScert.GetRawCertDataString());
cert.SignerCertificate = (CSignerCertificate)signerCertificate;
Hope this helps others in the future.

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.

Using client certificate not in certificate store

I'm trying to authenticate myself against WebService using my client certificate, but, for some reasons (I explain), I don't want to load certificate from store, rather read it from disc.
The following:
// gw is teh WebService client
X509Certificate cert = new X509Certificate(PathToCertificate);
_gw.ClientCertificates.Add(ClientCertificate());
ServicePointManager.ServerCertificateValidationCallback = (a,b,c,d) => true;
_gw.DoSomeCall();
returns always 403 - the Service doesn't authorize me. But, when I save that certificate into CertStore, it works. (As stated in MSDN.)
Is it possible to use certificate not in store?
(the reason is, that I got windows service(client) sometimes calling webservice(server), and after unspecified amount of time the service 'forgets' my certificates and doesnt authorize against server, with no apparent reason)
What type of file is PathToCertificate? If it's just a .cer file, it will not contain the private key for the certificate and trying to use that certificate for SSL/TLS will fail.
However, if you have a PKCS7 or PKCS12 file that includes the public and private key for the certificate, your code will work (you might need to use the overload that takes a password if the private key has one).
To test this, I went to http://www.mono-project.com/UsingClientCertificatesWithXSP and created my client.p12 file following those instructions. I also created a simple HTTPS server using HttpListener for testing.
Then I compiled the following program into 'client.exe' and run like:
client.exe https://<MYSSLSERVER>/ client.p12 password
where client.p12 is the PKCS12 file generated before and 'password' is the password I set for the private key of the certificate.
using System;
using System.IO;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Text;
public class HttpWebRequestClientCertificateTest : ICertificatePolicy {
public bool CheckValidationResult (ServicePoint sp, X509Certificate certificate,
WebRequest request, int error)
{
return true; // server certificate's CA is not known to windows.
}
static void Main (string[] args)
{
string host = "https://localhost:1234/";
if (args.Length > 0)
host = args[0];
X509Certificate2 certificate = null;
if (args.Length > 1) {
string password = null;
if (args.Length > 2)
password = args [2];
certificate = new X509Certificate2 (args[1], password);
}
ServicePointManager.CertificatePolicy = new HttpWebRequestClientCertificateTest ();
HttpWebRequest req = (HttpWebRequest) WebRequest.Create (host);
if (certificate != null)
req.ClientCertificates.Add (certificate);
WebResponse resp = req.GetResponse ();
Stream stream = resp.GetResponseStream ();
StreamReader sr = new StreamReader (stream, Encoding.UTF8);
Console.WriteLine (sr.ReadToEnd ());
}
}
Let me know if you want me to upload the server code and the certificates used on both sides of the test.
The potential problem could be caching of SSL sessions (Schannel cache). Only first request negotiates the SSL handshake. Subsequent requests will use the same session ID and hope that the server accept it. If the server clears the SessionId, the requests will fail with 403 error. To disable local ssl session caching (and force SSL negotiation for each request) you have to open windows registry folder:
[HKEY_LOCAL_MACHINE][System][CurrentControlSet][Control][SecurityProviders][SCHANNEL]
and add the key named ClientCacheTime (DWORD) with value 0.
This issue is covered here:
http://support.microsoft.com/?id=247658
You have the potential for at least two problems...
First...
Your client certificate file cannot contain a private key unless it's accessed with a password. You should be using a PKCS #12 (*.pfx) certificate with a password so that your client has access to the private key. You client code will have to provide the password when opening the certificate as others have already posted. There are several ways to create this, the easiest is to use the following command-line to first generate the certificate, then use the MMC certificate manager to export the certificates private key:
Process p = Process.Start(
"makecert.exe",
String.Join(" ", new string[] {
"-r",// Create a self signed certificate
"-pe",// Mark generated private key as exportable
"-n", "CN=" + myHostName,// Certificate subject X500 name (eg: CN=Fred Dews)
"-b", "01/01/2000",// Start of the validity period; default to now.
"-e", "01/01/2036",// End of validity period; defaults to 2039
"-eku",// Comma separated enhanced key usage OIDs
"1.3.6.1.5.5.7.3.1," +// Server Authentication (1.3.6.1.5.5.7.3.1)
"1.3.6.1.5.5.7.3.2", // Client Authentication (1.3.6.1.5.5.7.3.2)
"-ss", "my",// Subject's certificate store name that stores the output certificate
"-sr", "LocalMachine",// Subject's certificate store location.
"-sky", "exchange",// Subject key type <signature|exchange|<integer>>.
"-sp",// Subject's CryptoAPI provider's name
"Microsoft RSA SChannel Cryptographic Provider",
"-sy", "12",// Subject's CryptoAPI provider's type
myHostName + ".cer"// [outputCertificateFile]
})
);
Second...
Your next problem is going to be server-side. The server has to allow this certificate. You have the right logic, but on the wrong side of the wire, move this line to the web server handling the request. If you cannot, you must then take the '.cer' file saved above to the server and add it to the server computer's trust list:
ServicePointManager.ServerCertificateValidationCallback = (a,b,c,d) => true;
Do you need a password for the certificate? If so, there is a field for it in the constructor.
X509Certificate cert = new X509Certificate(PathToCertificate,YourPassword);

Categories

Resources