Can't get C# SslStream to work with my X509 certs - c#

I am kind of new to doing security and encryption, so if I just made a very dumb mistake, sorry in advance.
I need a server and a client communicating through a secure connection using SslStream. But my certificates don't work. I get the following error: System.NotSupportedException: 'The server mode SSL must use a certificate with the associated private key.'
My code was the microsoft example given in the documentation: https://learn.microsoft.com/en-us/dotnet/api/system.net.security.sslstream?view=netframework-4.8ss
I tried:
Self singed certs using makecert (Like in this post: SSLStream example - how do I get certificates that work?)
Certificates with OpenSSL
The procedure on this website: http://www.reliablesoftware.com/DasBlog/PermaLink,guid,6507b2c6-473e-4ddc-9e66-8a161e5df6e9.aspx
Using the .pfx file instead of the .cer file (like in this post:X509Certificate2 the server mode SSL must use a certificate with the associated private key), but got the following exception: Win32Exception: The certificate chain was issued by an authority that is not trusted.
All except the last one gave the System.NotSupportedException: 'The server mode SSL must use a certificate with the associated private key.' exception.
Does this mean that self-signed certificates don't work? Do I need to buy a certificate?
Edit:
Here is the code I used. It is the modified example (Sorry if it is terribly coded on my part) and is executable, simulating server and client and throws the exception:
class Program
{
static void Main(string[] args)
{
//Temporarily added the arguments here for you to see
args = new string[2] { #"C:\Users\jacke\Documents\CA\TempCert.cer", "FakeServerName" };
Console.WriteLine("Starting server in seperate thread...");
Task t = Task.Run(() => { Server.Initialize(args[0]); });
Task.Delay(500).Wait();
Client.RunClient(args[1]);
}
}
public static class Server
{
private static X509Certificate cert;
private static TcpListener server;
public static void Initialize(string certificate)
{
cert = X509Certificate.CreateFromCertFile(certificate);
server = new TcpListener(IPAddress.Any, 12321);
server.Start();
while (true)
{
Console.WriteLine("Waiting for a client to connect...");
TcpClient client = server.AcceptTcpClient();
ProcessClient(client);
}
}
private static void ProcessClient(TcpClient client)
{
SslStream sslStream = new SslStream(client.GetStream(), false);
try
{
sslStream.AuthenticateAsServer(cert, clientCertificateRequired: false, checkCertificateRevocation: true);
sslStream.ReadTimeout = 5000;
sslStream.WriteTimeout = 5000;
Console.WriteLine("Waiting for client message...");
string messageData = Helpers.ReadMessage(sslStream);
byte[] message = Encoding.UTF8.GetBytes("Hello from the server.<EOF>");
Console.WriteLine("Sending hello message.");
sslStream.Write(message);
}
catch (AuthenticationException e)
{
Console.WriteLine("Exception: {0}", e.Message);
if (e.InnerException != null)
{
Console.WriteLine("Inner exception: {0}", e.InnerException.Message);
}
Console.WriteLine("Authentication failed - closing the connection.");
sslStream.Close();
client.Close();
return;
}
finally
{
sslStream.Close();
client.Close();
}
}
}
public static class Client
{
private static Hashtable certificateErrors = new Hashtable();
public static bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
return true;
Console.WriteLine("Certificate error: {0}", sslPolicyErrors);
return false;
}
public static void RunClient(string serverName)
{
TcpClient client = new TcpClient("localhost", 12321);
Console.WriteLine("Client connected.");
SslStream sslStream = new SslStream(
client.GetStream(),
false,
new RemoteCertificateValidationCallback(ValidateServerCertificate),
null
);
try
{
sslStream.AuthenticateAsClient(serverName);
}
catch (AuthenticationException e)
{
Console.WriteLine("Exception: {0}", e.Message);
if (e.InnerException != null)
{
Console.WriteLine("Inner exception: {0}", e.InnerException.Message);
}
Console.WriteLine("Authentication failed - closing the connection.");
client.Close();
return;
}
byte[] messsage = Encoding.UTF8.GetBytes("Hello from the client.<EOF>");
sslStream.Write(messsage);
string serverMessage = Helpers.ReadMessage(sslStream);
Console.WriteLine("Server says: {0}", serverMessage);
client.Close();
Console.WriteLine("Client closed.");
}
}
public static class Helpers
{
public static string ReadMessage(SslStream sslStream)
{
// Read the message sent by the server.
// The end of the message is signaled using the
// "<EOF>" marker.
byte[] buffer = new byte[2048];
StringBuilder messageData = new StringBuilder();
int bytes = -1;
do
{
bytes = sslStream.Read(buffer, 0, buffer.Length);
Decoder decoder = Encoding.UTF8.GetDecoder();
char[] chars = new char[decoder.GetCharCount(buffer, 0, bytes)];
decoder.GetChars(buffer, 0, bytes, chars, 0);
messageData.Append(chars);
// Check for EOF.
if (messageData.ToString().IndexOf("<EOF>") != -1)
{
break;
}
} while (bytes != 0);
return messageData.ToString();
}
}
And here is how I created the certificates (as explained in the post I linked above):
makecert -sv RootCATest.pvk -r -n "CN=FakeServerName" RootCATest.cer
makecert -ic RootCATest.cer -iv RootCATest.pvk -n "CN=FakeServerName" -sv
TempCert.pvk -pe -sky exchange TempCert.cer
cert2spc TempCert.cer TempCert.spc
pvkimprt -pfx TempCert.spc TempCert.pvk
Additional information I entered with the commands above:
When asked for a by the first 2 commands password I left it blank
I checked export private key and set 'A' as a password for the last command
Then I imported the .pfx file in the local certificate store (I also tried machine wide earlier) and let the program pick the right store. It warned me that all certificates by the CA will be trusted and I should contact the CA to check this was indeed their certificate, but I proceeded. Then I ran the code (using the 'TempCert.cer' file I just created) and got the error. Any advice is highly appreciated!

If I recall correctly, a .cer file does not include a private key, and I assume by loading a certificate from file the certificate store is not checked. You could export a .pfx and include the private key in the export.
I don't know why the .pvk attempt failed though (never tried that format myself) - SslStream can definitely work with self-signed certificates; the client side just has to ignore the validation failure.
However if you've already imported it into the store, you should be able to load it directly:
using System.Security.Cryptography.X509Certificates;
// ...
using (X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.ReadOnly);
var certs = store.Certificates.Find(X509FindType.FindBySubjectName, "FakeServerName", false);
return new X509Certificate2(certs[0]);
}
Respoding to the error in your comment:
The credentials supplied to the package were not recognized
I don't know if I got exactly that message, but I do remember having to grant access to the user the app runs into. You'd have to open the correct certificates tool:
https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/how-to-view-certificates-with-the-mmc-snap-in
(run mmc, select file/add snap-in, choose certificates)
Then grant user(s) access to the private key:
https://docs.secureauth.com/display/KBA/Grant+Permission+to+Use+Signing+Certificate+Private+Key
(right click the certificate, select all tasks/manage private keys)

Related

Add a generated certificate to the store and update an IIS site binding

I'm running into the following and after feeling like I've exhausted various avenues of research on Google and Stack Overflow I decided to just ask my own question about it.
I'm trying to generate a personal certificate (using BouncyCastle) based on a CA certificate that I already have and own. After generating the certificate, placing it in the 'My' store, I then attempt to update my IIS website's SSL binding to use this new certificate.
What I'm noticing is that the updates to the IIS website (using ServerManager) are not throwing exception, yet when I go to the IIS Manager console I notice the website's binding has no SSL certificate selected. When I attempt to select the certificate that I created (shows up fine as a viable option) I get the following error message:
A specified logon session does not exist. It may already have been terminated. (Exception from HRESULT: 0x80070520)
As a test I exported my generated certificate (with the private key) and reinstalled it via the wizard and then once again tried setting up the binding (via IIS Manager) which worked.
Because of this behavior I assumed it was an issue with how I was generating or adding the certificate to the store. I was hoping someone may have some idea of what the issue I'm having may be. The following are the relevant functions (I believe) used in creating the certificate, adding it to the store, and updating the website's binding programmatically:
Main function the generates that get the CA certificate private key, generates the personal self-signed certificate, and updates the sites binding:
public static bool GenerateServerCertificate(
X509Certificate2 CACert,
bool addToStore,
DateTime validUntil)
{
try
{
if (CACert.PrivateKey == null)
{
throw new CryptoException("Authority certificate has no private key");
}
var key = DotNetUtilities.GetKeyPair(CACert.PrivateKey).Private;
byte[] certHash = GenerateCertificateBasedOnCAPrivateKey(
addToStore,
key,
validUntil);
using (ServerManager manager = new ServerManager())
{
Site site = manager.Sites.Where(q => q.Name == "My Site").FirstOrDefault();
if (site == null)
{
return false;
}
foreach (Binding binding in site.Bindings)
{
if (binding.Protocol == "https")
{
binding.CertificateHash = certHash;
binding.CertificateStoreName = "MY";
}
}
manager.CommitChanges();
}
}
catch(Exception ex)
{
LOG.Error("Error generating certitifcate", ex);
return false;
}
return true;
}
Generating the certificate based on the CA private key:
public static byte[] GenerateCertificateBasedOnCAPrivateKey(
bool addToStore,
AsymmetricKeyParameter issuerPrivKey,
DateTime validUntil,
int keyStrength = 2048)
{
string subjectName = $"CN={CertSubjectName}";
// Generating Random Numbers
CryptoApiRandomGenerator randomGenerator = new CryptoApiRandomGenerator();
SecureRandom random = new SecureRandom(randomGenerator);
ISignatureFactory signatureFactory = new Asn1SignatureFactory("SHA512WITHRSA", issuerPrivKey, random);
// The Certificate Generator
X509V3CertificateGenerator certificateGenerator = new X509V3CertificateGenerator();
certificateGenerator.AddExtension(
X509Extensions.ExtendedKeyUsage,
true,
new ExtendedKeyUsage((new List<DerObjectIdentifier> { new DerObjectIdentifier("1.3.6.1.5.5.7.3.1") })));
// Serial Number
BigInteger serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), random);
certificateGenerator.SetSerialNumber(serialNumber);
// Issuer and Subject Name
X509Name subjectDN = new X509Name(subjectName);
X509Name issuerDN = new X509Name(CACertificateName);
certificateGenerator.SetIssuerDN(issuerDN);
certificateGenerator.SetSubjectDN(subjectDN);
// Valid For
DateTime notBefore = DateTime.UtcNow.Date;
DateTime notAfter = validUntil > notBefore ? validUntil : notBefore.AddYears(1);
certificateGenerator.SetNotBefore(notBefore);
certificateGenerator.SetNotAfter(notAfter);
// Subject Public Key
AsymmetricCipherKeyPair subjectKeyPair;
var keyGenerationParameters = new KeyGenerationParameters(random, keyStrength);
var keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(keyGenerationParameters);
subjectKeyPair = keyPairGenerator.GenerateKeyPair();
certificateGenerator.SetPublicKey(subjectKeyPair.Public);
// Generating the Certificate
Org.BouncyCastle.X509.X509Certificate certificate = certificateGenerator.Generate(signatureFactory);
// correcponding private key
PrivateKeyInfo info = PrivateKeyInfoFactory.CreatePrivateKeyInfo(subjectKeyPair.Private);
// merge into X509Certificate2
X509Certificate2 x509 = new X509Certificate2(certificate.GetEncoded());
Asn1Sequence seq = (Asn1Sequence)Asn1Object.FromByteArray(info.ParsePrivateKey().GetDerEncoded());
if (seq.Count != 9)
{
throw new PemException("Malformed sequence in RSA private key");
}
RsaPrivateKeyStructure rsa = RsaPrivateKeyStructure.GetInstance(seq);
RsaPrivateCrtKeyParameters rsaparams = new RsaPrivateCrtKeyParameters(
rsa.Modulus,
rsa.PublicExponent,
rsa.PrivateExponent,
rsa.Prime1,
rsa.Prime2,
rsa.Exponent1,
rsa.Exponent2,
rsa.Coefficient);
x509.PrivateKey = DotNetUtilities.ToRSA(rsaparams);
if (addToStore)
{
// Add certificate to the Personal store
AddCertToStore(x509, StoreName.My, StoreLocation.LocalMachine, "Certificate Friendly Name");
}
return x509.GetCertHash();
}
Adding the certificate to the store:
private static void AddCertToStore(X509Certificate2 cert, StoreName storeName, StoreLocation storeLocation, string friendlyName)
{
X509Store store = new X509Store(storeName, storeLocation);
try
{
store.Open(OpenFlags.ReadWrite);
store.Add(cert);
if (!string.IsNullOrWhiteSpace(friendlyName)) {
var certs = store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, cert.Subject, true);
if (certs.Count > 0)
{
certs[0].FriendlyName = friendlyName;
}
}
}
finally
{
store.Close();
}
}
Just a final note, I have tried a few things from what I've seen on various sites in regards to that error (doesn't seem very clear what the issue is):
This works on a different box (my personal development machine) but I hit these snags on a server machine (running Windows Server 2012 R2)
The IIS Help dialog informs me the machine is running IIS 8.5
Verified the validity generated certificate and the CA certificate with CertUtil.exe
Verified the generated certificate and the CA certificate had a private key that could be found
Verified administrators (and eventually even my logged in account) had access to where the private key file for both the CA certificate and the generated certificate.
Any ideas what my issue could be?
Update:
I was able to get some results by doing the following:
Export my certificate to a file programmatically by doing File.WriteAllBytes(filePath, cert.Export(X509ContentType.Pkcs12, password));
Then I import this certificate file to the store by doing:
var cert = new X509Certificate2(certFilePath, certPassword, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
// My original AddCertToStore function
AddCertToStore(cert, StoreName.My, StoreLocation.LocalMachine, "Friendly Name");
Finally, I set the binding as I was doing earlier:
using (ServerManager manager = new ServerManager())
{
Site site = manager.Sites.Where(q => q.Name == "My Site").FirstOrDefault();
if (site == null)
{
return false;
}
foreach (Binding binding in site.Bindings)
{
if (binding.Protocol == "https")
{
binding.CertificateHash = certHash;
binding.CertificateStoreName = "MY";
}
}
manager.CommitChanges();
}
Doing it this way works, but I don't see why I would have export the certificate to a file, THEN load it into a X509Certificate2 object, add to the store, and finally set up the binding.
The ToRSA method most likely creates an ephemeral RSA key, so when the references are all gone the key gets deleted. Exporting the ephemeral structure into a PFX then re-importing it with PersistKeySet is one way to turn it into a persisted key. Others exist, but that one is one of the less convoluted ones.
You don't actually have to write it to a file, though.
byte[] pkcs12Blob = cert.Export(X509ContentType.Pkcs12, password);
ver certWithPersistedKey = new X509Certificate2(pkcs12Blob, password, allTheFlagsYouAlreadySet);
There are also other subtleties going on, like setting the PrivateKey property has different behaviors for a cert instance that was loaded from a store and one which was loaded from bytes... the PFX/PKCS#12 export/import works around all of those.
For us, it was related to an invalid certificate. We went to IIS >> Server Certificates and exported the certificate from there.
The certificate was correctly bound to IIS site after that.

Exception in ssl certificate code

I made a client server socket connection with sslStream but there is a a exception on server when the code reaches to line AuthenticateAsServer I searched in internet but I couldn't find a good answer why it happens.
I made the .pfx testfile in my project and made a simple password for it. I don't know the problem is from file or not.
the exception is in line: sslStream.AuthenticateAsServer(certificate);
basic exception is: a call to sspi failed
inner exception is: clientsThe client and server cannot communicate, because they do not possess a common algorithm
server is a little long and I add the part of code that exception happens and all of client code:
this is server:
public void AcceptCallBack(IAsyncResult ar)
{
// clients.Add(new myClient(server.EndAccept(ar)));
// try
// {
myClient c = new myClient();
// Socket handle = (Socket)ar.AsyncState;
TcpListener handle = (TcpListener)ar.AsyncState;
byte[] buff=new byte[2048] ;
// Socket hand = handle.EndAccept(out buff,ar);
TcpClient hand = handle.EndAcceptTcpClient(ar);
dowork.Set();
c.tcp = hand;
clients.Add(c);
// hand.BeginReceive(c.buffer, 0, c.buffer.Length, SocketFlags.None, new AsyncCallback(receiveIDCallBack), c);
using (SslStream sslStream = new SslStream(hand.GetStream()))
{
sslStream.AuthenticateAsServer(certificate);
// ... Send and read data over the stream
sslStream.BeginWrite(buff,0,buff.Length,new AsyncCallback(sendCallBack),c);
count++;
sslStream.BeginRead(c.buffer,0,c.buffer.Length,new AsyncCallback(receiveIDCallBack),c);
}
// }
// catch(Exception)
// {
// }
}//end of acceptcallback function
this is client:
using UnityEngine;
using System.Collections;
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Net.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
public class sslCode : MonoBehaviour {
// private Socket _clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
private byte[] _recieveBuffer = new byte[8142];
static string server = "127.0.0.1";
TcpClient client;
public string message;
public string receive;
public string send;
private void SetupServer()
{
try
{
// client.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 1500));
client = new TcpClient(server,1500);
message = "connected";
}
catch (SocketException ex)
{
Debug.Log(ex.Message);
message = ex.Message;
}
// _clientSocket.BeginReceive(_recieveBuffer, 0, _recieveBuffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), null);
// Create a secure stream
using (SslStream sslStream = new SslStream(client.GetStream(), false,
new RemoteCertificateValidationCallback(ValidateServerCertificate), null))
{
sslStream.AuthenticateAsClient(server);
// ... Send and read data over the stream
sslStream.BeginRead(_recieveBuffer, 0, _recieveBuffer.Length, new AsyncCallback(ReceiveCallback),null);
}
}
private bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
throw new NotImplementedException();
}// end of setup server
private void ReceiveCallback(IAsyncResult AR)
{
//Check how much bytes are recieved and call EndRecieve to finalize handshake
using (SslStream sslStream = new SslStream(client.GetStream(), false,
new RemoteCertificateValidationCallback(ValidateServerCertificate), null))
{
sslStream.AuthenticateAsClient(server);
// ... Send and read data over the stream
int recieved = sslStream.EndRead(AR);
if (recieved <= 0)
return;
//Copy the recieved data into new buffer , to avoid null bytes
byte[] recData = new byte[recieved];
Buffer.BlockCopy(_recieveBuffer, 0, recData, 0, recieved);
//Process data here the way you want , all your bytes will be stored in recData
receive = Encoding.ASCII.GetString(recData);
//Start receiving again
sslStream.BeginRead(_recieveBuffer, 0, _recieveBuffer.Length, new AsyncCallback(ReceiveCallback), null);
}
}// end of receiveCallBack
private void SendData(string dd)
{
using (SslStream sslStream = new SslStream(client.GetStream(), false,
new RemoteCertificateValidationCallback(ValidateServerCertificate), null))
{
sslStream.AuthenticateAsClient(server);
// ... Send and read data over the stream
byte[] data = Encoding.ASCII.GetBytes(dd);
SocketAsyncEventArgs socketAsyncData = new SocketAsyncEventArgs();
socketAsyncData.SetBuffer(data, 0, data.Length);
sslStream.BeginWrite(data,0,data.Length,new AsyncCallback(sendcallback),null);
send = dd;
sslStream.BeginRead(_recieveBuffer, 0, _recieveBuffer.Length, new AsyncCallback(ReceiveCallback), null);
}
}
private void sendcallback(IAsyncResult ar)
{
}// end of send data
can this be problem of certificate file generated in vs or options of windows?
I searched a little more on internet and and I think there should be probability of algorithm mismatch that I use for my certificate file and what windows 8.1 can understand. i really don't know....
that algorithms that vs let me make for my certificate are "sha256RSA" and "sha1RSA"
thanks for your help
i made the .pfx testfile in my project
That's a Big Red Flag. Without knowing anything about the tools you use, the best guess is that you created a signing certificate. It is not suitable for key exchange. A failure-mode covered by this blog post.
Without knowing anything about your OS, I'd have to guess that you use Linux. In which case this question ought to be helpful. If that's a wrong guess then help yourself by googling "create self signed ssl certificate, add the appropriate keywords to select your OS and/or tool chain.
thank you my friends, i finally could find my problem.
the code needed a little edit but the main problem wasnt the code.
the problem was from the way certificate files work. i just had generated a pfx file and gave its address to code below:
sslStream.AuthenticateAsServer(server);
but now i made the pfx format in internet options and imported it to personal section, after that exported it to trusted root section, so cer format of that pfx file will be generateed that only contains the public key of that pfx file.
so right now code runs very well.

Automated downloading of X509 certificate chain from remote host

I am building some .net code that will run unattended on a scheduled basis on one of our servers. Part of its execution requires that it execute a Java executable which talks to some web resources and services over SSL.
Java will absolutely throw a fit if it's doing something over SSL and it doesn't have every single certificate in the remote certificate's chain. So in .NET I need to be able to specify an https:// resource and need it to download the entire remote chain locally.
Why do I want to do this automatically? Because I want to make deployment simple and to not have to do this 4 times, and then again every time one of Oracle's certificates expires.
I am doing this:
X509Certificate2 lowestCert = SecurityHelper.DownloadSslCertificate(keyDownloadLocation);
X509Chain chain = new X509Chain();
chain.Build(lowestCert);
int counter = 0;
foreach (X509ChainElement el in chain.ChainElements) {
//Extract certificate
X509Certificate2 chainCert = el.Certificate;
//Come up with an alias and save location
string alias = certBaseName + counter;
string localLocation = Path.GetDirectoryName(
System.Reflection.Assembly.GetEntryAssembly().Location
) + #"\" +alias + ".cer";
//Save it locally
SecurityHelper.SaveCertificateToFile(chainCert, localLocation);
//Remove (if possible) and add to java's keystore
deleteKeyFromKeystore(
javaFolder,
alias,
certKeyStore,
SecurityHelper.GetEncryptedAppSetting("JavaKeystorePassword")
);
addKeytoKeyStore(
localLocation,
javaFolder,
alias,
certKeyStore,
SecurityHelper.GetEncryptedAppSetting("JavaKeystorePassword")
);
//Then delete
if (File.Exists(localLocation)) File.Delete(localLocation);
counter++;
}
SecurityHelper.DownloadSslCertificate is a custom method that creates an X509Certificate2 from a a website url.
cert is the lowest-level certificate and I can get that back, but what I can't do is get everything in the chain. chain.ChainElements is still only 1 level deep if the rest of the chain isn't already installed on the machine. My problem is that this is a brand new URL with totally unknown (to the machine) certificates, and I want to go up the stack and recursively download every one.
Is there a way to hit a URL unknown to the machine and programmatically downloading every certificate in the chain?
Edit: here is how I'm downloading the SSL Certificate:
public static X509Certificate2 DownloadSslCertificate(string strDNSEntry)
{
X509Certificate2 cert = null;
using (TcpClient client = new TcpClient())
{
client.Connect(strDNSEntry, 443);
SslStream ssl = new SslStream(client.GetStream(), false,
new RemoteCertificateValidationCallback(
(sender, certificate, chain, sslPolicyErrors) => {
return true;
}
), null);
try
{
ssl.AuthenticateAsClient(strDNSEntry);
}
catch (AuthenticationException e)
{
ssl.Close();
client.Close();
return cert;
}
catch (Exception e)
{
ssl.Close();
client.Close();
return cert;
}
cert = new X509Certificate2(ssl.RemoteCertificate);
ssl.Close();
client.Close();
return cert;
}
}
Edit #2 The solution to obtain remote SSL certificates that worked for me was to capture the chain in the validation callback that occurs during an SslStream.AuthenticateAsClient(url);
private static X509ChainElementCollection callbackChainElements = null;
private static bool CertificateValidationCallback(
Object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
callbackChainElements = chain.ChainElements;
return true;
}
And then to kickoff the validation with this callback, I had something like:
public static X509Certificate2 DownloadSslCertificate(string url)
{
X509Certificate2 cert = null;
using (TcpClient client = new TcpClient())
{
client.Connect(url, 443);
SslStream ssl = new SslStream(client.GetStream(), false, CertificateValidationCallback, null);
try
{
ssl.AuthenticateAsClient(url); //will call CertificateValidationCallback
}
... etc
Then by the time AuthenticateAsClient has finished, the local callbackChainElements variable is set to a collection of X509ChainElement.
Actually I did something very similar to this using Powershell.
One piece is changing ServicePointManager to ignore SSL validation (since you may not have the root certs locally, it sounds). It's ServerCertificateValidationCallback that you want to set to true, which tells it you're using a custom validation (or in your case, perhaps no validation).
From there, you're most of the way there with your chainelements. You can use the Export method to export the cert into a byte array:
foreach (X509ChainElement el in chain.ChainElements) {
var certToCreate = el.Certificate.Export(X509ContentType.Cert);
...
}
From there it's just a matter of doing what you want with the byte array. You can pass it to a constructor for another certificate object, which you can then import into the Java keystore.
Hope that helps, please let me know if you have further questions.
EDIT: Actually, I just noticed this post on tips working with X.509 certificates in .NET . The author recommends against writing directly from a byte array because it writes the temp file to a directory that doesn't get cleaned up. So his suggestion is to write the byte array to disk, and then manually clean it up when you're done.

SSL errors when using Npgsql and SSL certificate authentication

I'm having trouble establishing a connection to a PostgreSQL database that is configured only to accept a valid SSL certificate. I can connect using pgAdmin III with the appropriate certificate and key, but I cannot get it working with Npgsql. When I try to open a connection to the database, I get System.IO.IOException: The authentication or decryption has failed. Here is my code:
NpgsqlConnectionStringBuilder csb = new NpgsqlConnectionStringBuilder();
csb.Database = "database";
csb.Host = "psql-server";
csb.UserName = "dreamlax"; // must match Common Name of client certificate
csb.SSL = true;
csb.SslMode = SslMode.Require;
NpgsqlConnection conn = new NpgsqlConnection(csb.ConnectionString);
conn.ProvideClientCertificatesCallback += new ProvideClientCertificatesCallback(Database_ProvideClientCertificatesCallback);
conn.CertificateSelectionCallback += new CertificateSelectionCallback(Database_CertificateSelectionCallback);
conn.CertificateValidationCallback += new CertificateValidationCallback(Database_CertificateValidationCallback);
conn.PrivateKeySelectionCallback += new PrivateKeySelectionCallback(Database_PrivateKeySelectionCallback);
conn.Open(); //System.IO.IOException: The authentication or decryption has failed
The callbacks are defined like this:
static void Database_ProvideClientCertificates(X509CertificateCollection clienteCertis)
{
X509Certificate2 cert = new X509Certificate2("mycert.pfx", "passphrase");
clienteCertis.Add(cert);
}
static X509Certificate Database_CertificateSelectionCallback(X509CertificateCollection clientCerts, X509Certificate serverCert, string host, X509CertificateCollection serverRequestedCerts)
{
return clienteCertis[0];
}
static AsymmetricAlgorithm Database_PrivateKeySelectionCallback(X509Certificate cert, string host)
{
X509Cerficate2 thisCert = cert as X509Certificate2;
if (cert != null)
return cert.PrivateKey;
else
return null;
}
static bool MyCertificateValidationCallback(X509Certificate certificate, int[] certificateErrors)
{
// forego server validation for now
return true;
}
I set breakpoints confirming that each callback was returning something valid, but still the IOException is thrown.
I fixed this problem by modifying the Npgsql source. Rather than using Mono's Mono.Security.Protocol.Tls.SslClientStream, I changed it to use System.Net.Security.SslStream instead. These were the steps I took:
Modify NpgsqlClosedState.cs:
Remove the using Mono.Security.Protocol.Tls; directive and the one below it.
Add a using System.Net.Security; directive.
In the NpgsqlClosedState.Open() method, where it says if (response == 'S'), changed it to:
if (response == 'S')
{
//create empty collection
X509CertificateCollection clientCertificates = new X509CertificateCollection();
//trigger the callback to fetch some certificates
context.DefaultProvideClientCertificatesCallback(clientCertificates);
// Create SslStream, wrapping around NpgsqlStream
SslStream sstream = new SslStream(stream, true, delegate(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors errors)
{
// Create callback to validate server cert here
return true;
});
sstream.AuthenticateAsClient(context.Host, clientCertificates, System.Security.Authentication.SslProtocols.Default, false);
stream = sstream;
}
Modify NpgsqlConnection.cs, remove the using Mono... directives. This will cause a number of errors regarding missing types, in particular, these will be regarding 3 sets of delegate/event combos that use Mono types. The errors will always appear in groups of three because these callbacks all tie in with Mono's SslClientStream class. Remove each group of three and replace it with a single ValidateServerCertificate delegate/event. This single event should be used in the constructor for the SslStream class that was used in step 1.3 above.
The changes to NpgsqlConnection.cs will trigger more errors in other files NpgsqlConnector.cs, NpgsqlConnectorPool.cs etc. but the fix is the same, replace the 3 Mono-based callbacks with the new ValidateServerCertificate.
Once all that is done, Npgsql can be used without Mono components and with (for me) working SSL certificate authentication.
My pull request on github can be found here.

C# iPhone push server?

Im attempting to write a push server for the iPhone in C#. I have the following code:
// Create a TCP/IP client socket.
using (TcpClient client = new TcpClient())
{
client.Connect("gateway.sandbox.push.apple.com", 2195);
using (NetworkStream networkStream = client.GetStream())
{
Console.WriteLine("Client connected.");
X509Certificate clientCertificate = new X509Certificate(#"certfile.p12", passwordHere);
X509CertificateCollection clientCertificateCollection = new X509CertificateCollection(new X509Certificate[1] { clientCertificate });
// Create an SSL stream that will close the client's stream.
SslStream sslStream = new SslStream(
client.GetStream(),
false,
new RemoteCertificateValidationCallback(ValidateServerCertificate),
null
);
try
{
sslStream.AuthenticateAsClient("gateway.sandbox.push.apple.com");
}
catch (AuthenticationException e)
{
Console.WriteLine("Exception: {0}", e.Message);
if (e.InnerException != null)
{
Console.WriteLine("Inner exception: {0}", e.InnerException.Message);
}
Console.WriteLine("Authentication failed - closing the connection.");
client.Close();
return;
}
}
ect....
Only I keep receiving a exception:
"A call to SSPI failed, see Inner exception"
Inner Exception -> "The message received was unexpected or badly formatted."
Does anyone have any idea whats going wrong here?
Figured it out. Replaced sslStream.AuthenticateAsClient("gateway.sandbox.push.apple.com"); with sslStream.AuthenticateAsClient("gateway.sandbox.push.apple.com", clientCertificateCollection, SslProtocols.Default, false); And registered the certificates on the PC.
Edit: Here is the code for creating a payload as requested:
private static byte[] GeneratePayload(byte [] deviceToken, string message, string sound)
{
MemoryStream memoryStream = new MemoryStream();
// Command
memoryStream.WriteByte(0);
byte[] tokenLength = BitConverter.GetBytes((Int16)32);
Array.Reverse(tokenLength);
// device token length
memoryStream.Write(tokenLength, 0, 2);
// Token
memoryStream.Write(deviceToken, 0, 32);
// String length
string apnMessage = string.Format ( "{{\"aps\":{{\"alert\":{{\"body\":\"{0}\",\"action-loc-key\":null}},\"sound\":\"{1}\"}}}}",
message,
sound);
byte [] apnMessageLength = BitConverter.GetBytes((Int16)apnMessage.Length);
Array.Reverse ( apnMessageLength );
// message length
memoryStream.Write(apnMessageLength, 0, 2);
// Write the message
memoryStream.Write(System.Text.ASCIIEncoding.ASCII.GetBytes(apnMessage), 0, apnMessage.Length);
return memoryStream.ToArray();
} // End of GeneratePayload
From Zenox's comment:
use a different version of AuthenticateAsClient
sslStream.AuthenticateAsClient("gateway.sandbox.push.apple.com", clientCertificateCollection, SslProtocols.Default, false);
Other way is just to use X509Certificate2 and X509CertificateCollection2 classes.
I recently used Growl For Windows to push messages to the Prowl client on the IPhone from .Net code. So you might get your functionatlity without writing a push server yourself.
The "The message received was unexpected or badly formatted." error usually comes when you did not register the p12 certificate in Windows. (Under Vista, just double click on the p12 file and the import wizard will open)
In my case I had to delete all the certificate from my windows 8 and then re-install them in order to send push notifications to apple device.
I do not know why my certificates stop working, I am searching for the correct reason and will update here soon.

Categories

Resources