6

I'm using this little class which returns me a pfx file in byte array.

Server side:

byte[] serverCertificatebyte;
var date = new DateTime(DateTime.Today.Year, DateTime.Today.Month, DateTime.Today.Day);
serverCertificatebyte = Certificate.CreateSelfSignCertificatePfx("CN=RustBuster" + RandomString(5),
    date,
    date.AddDays(7));

Then I send It to the client (length: 1654):

tcpClient.GetStream().Write(serverCertificatebyte , 0, serverCertificatebyte .Length);

Once the client read It, I would like to convert It to a certificate class: (Length is also 1654 here)

I try to do a new X509Certificate2(data); and I get the error down below. This works on server side. What's the matter?

I also tried It with new X509Certificate2(data, string.Empty); and got the same error

Error System.Security.Cryptography.CryptographicException: Unable to decode certificate. ---> System.Security.Cryptography.CryptographicException: Input data cannot be coded as a valid certificate. ---> System.ArgumentOutOfRangeException: Cannot be negative.

Parameter name: length

at System.String.Substring (Int32 startIndex, Int32 length) [0x00000] in :0

at Mono.Security.X509.X509Certificate.PEM (System.String type, System.Byte[] data) [0x00000] in :0

at Mono.Security.X509.X509Certificate..ctor (System.Byte[] data) [0x00000] in :0

DreTaX
  • 760
  • 2
  • 9
  • 22
  • 1
    You are using Mono. It is not a blackbox so try to step in. I remember there was a bug in X509Certificate2 class. Not exactly sure what it was but I think it had to do with reading pfx from file vs from byte[]. Try to save the bytes to a file and then use `new X509Certificate2(filepath, String.Empty)` or `new X509Certificate2(filepath, null)`. – pepo Dec 29 '15 at 07:13
  • Wait. Are you saying I should make the byte a file then read It? – DreTaX Dec 29 '15 at 11:38
  • Yes exactly. Write byte[] to a file and then use the file in X509Certificate2. – pepo Dec 29 '15 at 11:52
  • Alright. I hope that works. Will tell the results soon. What should be the file name? .pfx? – DreTaX Dec 29 '15 at 13:06
  • It does not matter to X509Certificate2 class what extension the file has. But usually the extension is .p12 or .pfx. – pepo Dec 29 '15 at 13:57
  • Alright, will try in a few mins. – DreTaX Dec 29 '15 at 16:58
  • Alright so I did what you suggested, wrote the received file using "File.WriteAllBytes(string path, byte[] bytes)" and the output: http://pastie.org/private/krps21snoe0dj0lrbhmgw | Then I initialized X509Certificate2 using the path and the premade password, I also tried this with import, both in try catch. I received "Input data cannot be coded as a valid certificate." which is different than the error I was receiving. – DreTaX Dec 29 '15 at 19:18
  • Full: http://pastie.org/private/dkmokyltaezfy50hnr4zxq – DreTaX Dec 29 '15 at 19:24
  • Try to load the pfx on server side. Either you are creating pfx that is not pfx, or there is some coding error when sending pfx to the client. Also, if you could provide base64 encoded dump of byte[] it would be helpful. I would also try to open the pfx by doubleclicking the file.pfx in windows (if windows can open it then it has correct structure and the problem is elsewhere). – pepo Dec 29 '15 at 21:31
  • Loading the byte It self on the server side works fine. How can I provide that dump? Btw: https://db.tt/a19g5vEc <- I guess It's invalid. – DreTaX Dec 29 '15 at 23:40
  • Yes you are totally right. http://pastie.org/private/0acswikxfyxkm7yakbpftg The certificate byte im sending to the client is totally different than the server's. What could be the problem? – DreTaX Dec 30 '15 at 13:54
  • I don't know, you did not write that part of the code :) Do you use Encoding.ACSII or Encoding.UTF8 somewhere in the process? – pepo Dec 30 '15 at 15:47
  • To be honest I may know where the problem is. Currently I use a for cycle to read the amount of bytes. First I used a while, It read the bytes until the length of the byte 1654, but after that the client just froze, and the loop didn't stop running, while I was checking the amount of data I read. I'm gonna try this: http://stackoverflow.com/questions/26058594/how-to-get-all-data-from-networkstream If It doesn't work, I will paste the code in and let you know about It. – DreTaX Dec 30 '15 at 16:48
  • http://stackoverflow.com/questions/6958255/what-are-some-reasons-networkstream-read-would-hang-block Oh crap.... – DreTaX Dec 31 '15 at 15:48

2 Answers2

3

There is a bug in Mono when using X509Certificate2 constructor to load PKCS#12 with empty password. A patch has been submitted to fix this issue but it is (probably) not released in your version of mono.

Try to save the bytes to a file and then use new X509Certificate2(filepath, String.Empty) or new X509Certificate2(filepath, null) or create pfx with some default non-empty password.

pepo
  • 8,644
  • 2
  • 27
  • 42
  • I would go with default non-empty password. – jariq Dec 29 '15 at 07:41
  • 1
    Nope. I already know about that and as I mentioned above.. "I also tried It with new X509Certificate2(data, string.Empty); and got the same error" This is a PFX byte and not a pkcs – DreTaX Dec 29 '15 at 09:47
  • @DreTaX pfx is IMHO PKCS#12. You tried loading byte[] in constructor not filename. Its differently implemented. If you want to use byte[] in constructor of X509Certificate2 then create PFX with non-emtpy password and it should work. – pepo Dec 29 '15 at 09:56
  • Mhm. But giving a password will encrypt the data. Wouldn't the password give access to the private key? Btw: https://db.tt/KzjxiQ4t – DreTaX Dec 29 '15 at 09:58
  • Well I tried giving a password at the server side, and then using It on client side to access It, but I still get the same error. This thing is going on my mind. – DreTaX Dec 29 '15 at 10:07
2

Long thoughts and help requests finally lead me to a solution.

The following way or examples to a persistent connections that I had DID NOT WORK AT ALL.

At the server side you first must get the length of the byte you would like to send, and write It to the stream. This most likely going to be a byte having the length of 4.

byte[] intBytes = BitConverter.GetBytes(serverCertificatebyte.Length);
Array.Reverse(intBytes);

On the client side, read that byte, and convert It to an int:

byte[] length = new byte[4];
int red = 0;
while (true)
{
    red = stream.Read(length, 0, length.Length);
    if (red != 0)
    {
        break;
    }
}
if (BitConverter.IsLittleEndian)
{
    Array.Reverse(length);
}
int i = (BitConverter.ToInt32(length, 0)); // The length of the byte that the server sent
// By this time your server already sent the byte (Right after you sent the length) tcpClient.GetStream().Write(byte, 0, byte.Length);
byte[] data = ByteReader(i);

This method is going to read the byte that you send from the server until It's possible

internal byte[] ByteReader(int length)
{
    using (NetworkStream stream = client.GetStream())
    {
        byte[] data = new byte[length];
        using (MemoryStream ms = new MemoryStream())
        {
            int numBytesRead;
            int numBytesReadsofar = 0;
            while (true)
            {
                numBytesRead = stream.Read(data, 0, data.Length);
                numBytesReadsofar += numBytesRead;
                ms.Write(data, 0, numBytesRead);
                if (numBytesReadsofar == length)
                {
                    break;
                }
            }
            return ms.ToArray();
        }
    }
}

This solution seems to be working pretty well unlike the other examples that were provided on the microsoft documentation page. I hope this will help others too.

DreTaX
  • 760
  • 2
  • 9
  • 22