Question Passing a Certificate with SSLstream

digitaldrew

Well-known member
Joined
Nov 10, 2012
Messages
151
Programming Experience
Beginner
Hey everyone, I'm trying to connect with a service through their EPP server on port 700. The service required me to go out and purchase a certificate. They told me I then had to upload the "public" certificate into my user account (which I did) and then pass the "private" key when making a request to the service. They also claim users tend to pass the public and private keys underneath each other when making a request..

I'm guessing they are basically taking my private key and comparing it to my public key as an added layer of security? Anyways, I've gone out and tried doing as they said - I took the public key and placed it under the private key and then named the file "FIcert.crt".. It contains nothing other than the two keys inside. I've then tried implementing it into my code:

VB.NET:
Dim sslCertificate As X509Certificate2 = New X509Certificate2("C:\Test\FIcert.crt")
Dim client As New TcpClient(DRShost, 700)
Dim sslStream As New Security.SslStream(client.GetStream(), False, New RemoteCertificateValidationCallback(AddressOf ValidateServerCertificate), Nothing)
sslStream.AuthenticateAsClient(sslCertificate.ToString)

VB.NET:
    Public Shared Function ValidateServerCertificate(ByVal sender As Object, ByVal certificate As X509Certificate, ByVal chain As X509Chain, ByVal sslPolicyErrors As SslPolicyErrors) As Boolean
        If sslPolicyErrors = SslPolicyErrors.None Then Return True
        MsgBox("Certificate error: {0}", sslPolicyErrors)
        Return False
    End Function

However, I get an error right away on this line:
VB.NET:
Dim sslCertificate As X509Certificate2 = New X509Certificate2("C:\Test\FIcert.crt")

The error is:
System.Security.Cryptography.CryptographicException: 'Cannot find the requested object.

I'm certain the path to the certificate is correct. So, I wonder what other kind of issue this could be related to? Any help or suggestions would be greatly appreciated!
 

digitaldrew

Well-known member
Joined
Nov 10, 2012
Messages
151
Programming Experience
Beginner
Thanks for your reply jmcilhinney! I've done some more searching and made some additional changes..
First, I found that the CRT file needed to be CER and needed to be converted to DER. I converted my certificate over to DER using a website online and then renamed it from .der to .cer in DOS. I then tried again, but now receive a new error on this line:
VB.NET:
Dim client As New TcpClient(DRShost, 700)

System.Net.Sockets.SocketException: 'A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond'

I know the hostname I'm using is valid, and so is the port. Also, I'm using an actual hostname here and not an IP address. Could this be happening because I'm not actually passing the newly converted CER certificate properly? Just to refresh, here is my up-to-date code:
VB.NET:
Dim sslCertificate As X509Certificate2 = New X509Certificate2("C:\\Test\\FIcert.cer")
Dim client As New TcpClient(DRShost, 700)
Dim sslStream As New Security.SslStream(client.GetStream(), False, New RemoteCertificateValidationCallback(AddressOf ValidateServerCertificate), Nothing)
sslStream.AuthenticateAsClient(sslCertificate.ToString) 'sslStream.AuthenticateAsClient(DRShost, sslCertificate.ToString)

VB.NET:
Public Shared Function ValidateServerCertificate(ByVal sender As Object, ByVal certificate As X509Certificate, ByVal chain As X509Chain, ByVal sslPolicyErrors As SslPolicyErrors) As Boolean
    If sslPolicyErrors = SslPolicyErrors.None Then Return True
    MsgBox("Certificate error: {0}", sslPolicyErrors)
    Return False
End Function
 

JohnH

VB.NET Forum Moderator
Staff member
Joined
Dec 17, 2005
Messages
15,540
Location
Norway
Programming Experience
10+
sslStream.AuthenticateAsClient(sslCertificate.ToString) 'sslStream.AuthenticateAsClient(DRShost, sslCertificate.ToString)
sslCertificate.ToString as argument is wrong, parameter is 'targetHost', which is one of the DNS names in SAN if present or else the CN in Subject of certificate.

Dim client As New TcpClient(DRShost, 700)
Why that fails I don't know, obviously your computer must be able to resolve the DNS name that is in DRShost.
 

digitaldrew

Well-known member
Joined
Nov 10, 2012
Messages
151
Programming Experience
Beginner
Thanks for taking the time to reply JohnH! I've been messing with this more over the last few hours and have gradually made it further and further. Here's an update to where I'm at now..

First, it appears their firewall had not updated with my whitelisted IP address which was the cause behind the timeout issue that was occurring. I also realized the server will be the one validating the certificate, so I removed the ValidateServerCertificate function and also needed to change around my SSLstream code. I was able to make some changes to that and then began receive this error: The remote certificate is invalid according to the validation procedure

I felt like I was making some progress now because it seems I am reaching the server and the error now appears to be related to the certificate. Rather than trying to pass the certificate with .ToString in sslStream.AuthenticateAsClient as I did above, I decided to create a collection, store it, and then pass the collection. I also went and changed around it to include the hostname, enableSSLProtocols set to True, and checkCertificateRevocation set to True. Upon making these changes it now seems like I am able to get connected. However, the minute I try to send the first request (A simple Hello request which should return a Welcome response) I get an exception:
Unable to write data to the transport connection: An established connection was aborted by the software in your host machine.

This exception actually occurs inside my SendRequest sub (posted below) and specifically on this line:
VB.NET:
strm.Write(msgLengthBytes, 0, msgLengthBytes.Length)

Is there anyway I could tell for sure that I'm connected since I can't even send a Welcome request? I can't seem to figure out why I'm not able to pass this one simple request. Could it be that I shouldn't be encoding in UTF-8? Below now my most up-to-date code:

VB.NET:
Dim collection = New X509Certificate2Collection()
collection.Import("C:\\Test\\FIcert.cer")
Dim store = New X509Store(StoreName.My, StoreLocation.CurrentUser)
store.Open(OpenFlags.ReadWrite)

Try
For Each certificate As X509Certificate2 In collection
store.Add(certificate)
Next
Finally
store.Close()
End Try

Dim client As New TcpClient(DRShost, 700)
Dim sslStream As New Security.SslStream(client.GetStream(), True)
sslStream.AuthenticateAsClient(DRShost, collection, True, True)
Dim requestResponse As String = String.Empty

Dim helloElement As XElement = <epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
    <hello/>
</epp>
SendRequest(helloElement.ToString, sslStream, System.Text.Encoding.UTF8)
'Doesn't Get Passed This Point
requestResponse = GetResponse(sslStream, System.Text.Encoding.UTF8)
MsgBox(requestResponse)

and my SendRequest sub:
VB.NET:
    Private Sub SendRequest(ByVal msg As String, ByVal strm As System.Net.Security.SslStream, ByVal encoding As System.Text.Encoding)
        Dim ret As String = Nothing
        ' get encoded message bytes
        Dim msgBytes As Byte() = encoding.GetBytes(msg)

        ' get encoded message length in Big Endian, Windows is Little Endian
        ' this array will be 4 bytes long
        Dim totalMessageLength As Int32 = msgBytes.Length + 4 ' add 4 for the length preface
        Dim msgLengthBytes As Byte() = Int32AsBigEndianBytes(totalMessageLength)

        ' write message length
        strm.Write(msgLengthBytes, 0, msgLengthBytes.Length)

        ' write message
        strm.Write(msgBytes, 0, msgBytes.Length)
        strm.Flush()
    End Sub
 

digitaldrew

Well-known member
Joined
Nov 10, 2012
Messages
151
Programming Experience
Beginner
Just an update..It appears I can get to the exact same spot with the exact same error even when trying a CER file I know will not match the one I have uploaded to the server. So, I'm either still not getting connected, or doing something else wrong. But, it doesn't appear the spot I'm at now has anything to do with the encoding..
 

digitaldrew

Well-known member
Joined
Nov 10, 2012
Messages
151
Programming Experience
Beginner
Still been messing with this. After speaking to one of their support guys and doing a bit more research online, I think I've made some more progress. However, the response I get is still blank..

I previously mentioned the CRT and KEY files needed to be together. I was taking these and trying to make a CER file with them, but apparently they need to be combined into a PFX file. At least, that's about the only way I've found to "combine" these two together. So basically, the CRT file goes in my account on their website, and then I combine the CRT and KEY into one PFX file and pass that with the password. Since I don't have OpenSSL installed I went out and used the same online converter I was using before, only to make the PFX this time. I was able to successfully select my CRT and KEY files and combine them with a password to make one PFX file.

Below is my most up-to-date code. Maybe @JohnH, @jmcilhinney or someone has an idea as to why I would still be getting a blank response? At this point nothing breaks between making a connection, calling SendRequest and then calling GetResponse. It's not until I try to read the response with an "If" statement that I now get an exception..

VB.NET:
        Dim collection = New X509Certificate2Collection()
        collection.Import("C:\\Test\\FIcert.pfx", "password", X509KeyStorageFlags.DefaultKeySet)
        Dim store = New X509Store(StoreName.My, StoreLocation.CurrentUser)
        store.Open(OpenFlags.ReadWrite)

        Try
            For Each certificate As X509Certificate2 In collection
                store.Add(certificate)
            Next
        Finally
            store.Close()
        End Try

        Dim requestResponse As String = String.Empty
        Dim client As New TcpClient(DRShost, 700)
        Dim sslStream As New Security.SslStream(client.GetStream(), True)
        sslStream.AuthenticateAsClient(DRShost, collection, System.Security.Authentication.SslProtocols.[Default], False)

        If client.Connected Then
            Dim helloElement As XElement = <epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
                                               <hello/>
                                           </epp>
            SendRequest(helloElement.ToString, sslStream, System.Text.Encoding.UTF8)
            requestResponse = GetResponse(sslStream, System.Text.Encoding.UTF8)

            If requestResponse.ToLower.Contains("<greeting>") = True Then
    ''''''IT NEVER MAKES IT PAST THIS POINT''''''
                MsgBox("Success!")
            Else
                MsgBox("Failed!")
            End If
        Else
            MsgBox("Client not connected!")
            e.Cancel = True
            Exit Sub
        End If

The specific line I am receiving an exception on now is here:
VB.NET:
If requestResponse.ToLower.Contains("<greeting>") = True Then

and here is the exception
'Object reference not set to an instance of an object.'

Could it be something related to X509KeyStorageFlags.DefaultKeySet? Or maybe System.Security.Authentication.SslProtocols.[Default] in my sslStream.AuthenticateAsClient? After speaking with their support guy I can now confirm I definitely have the right certificate on the server, and I appear to be combining the CRT and KEY files properly (it won't let you merge these two files if the keys don't match up). Just can't figure out why I'm still not getting the response!!


Also, below are my SendRequest and GetResponse subs/functions:

SendRequest
VB.NET:
    Private Sub SendRequest(ByVal msg As String, ByVal strm As System.Net.Security.SslStream, ByVal encoding As System.Text.Encoding)
        Dim ret As String = Nothing
        ' get encoded message bytes
        Dim msgBytes As Byte() = encoding.GetBytes(msg)

        ' get encoded message length in Big Endian, Windows is Little Endian
        ' this array will be 4 bytes long
        Dim totalMessageLength As Int32 = msgBytes.Length + 4 ' add 4 for the length preface
        Dim msgLengthBytes As Byte() = Int32AsBigEndianBytes(totalMessageLength)

        ' write message length
        strm.Write(msgLengthBytes, 0, msgLengthBytes.Length)

        ' write message
        strm.Write(msgBytes, 0, msgBytes.Length)
        strm.Flush()
    End Sub

GetResponse
VB.NET:
    Private Function GetResponse(ByVal strm As System.Net.Security.SslStream, ByVal encoding As System.Text.Encoding) As String
        Dim ret As String = Nothing
        Try
            ' get encoded response length in Big Endian, Windows is Little Endian
            ' this array will be 4 bytes long
            Dim msgLengthBytes(0 To 3) As Byte
            Dim readBytes As Int32 = strm.Read(msgLengthBytes, 0, 4)
            If readBytes = 4 Then
                ' should get back 4 bytes that are the BigEndian length
                Dim totalResponseLength As Int32 = BigEndianInt32BytesToInt32(msgLengthBytes)
                ' subtract the bytes that comprise the length
                Dim responseLength As Int32 = totalResponseLength - 4

                ' get encoded message bytes
                Dim msgBytes(0 To (responseLength - 1)) As Byte
                readBytes = strm.Read(msgBytes, 0, responseLength)
                ret = encoding.GetString(msgBytes)
            End If
            Return ret
        Catch ex As Exception
            Return ret
        End Try
    End Function

Int32AsBigEndianBytes
VB.NET:
    Private Function Int32AsBigEndianBytes(ByVal val As Int32) As Byte()
        Dim bytes As Byte() = BitConverter.GetBytes(val)
        Array.Reverse(bytes)
        Return bytes
    End Function

BigEndianInt32BytesToInt32
VB.NET:
    Private Function BigEndianInt32BytesToInt32(ByVal bytes As Byte()) As Int32
        Dim tmpBytes(0 To bytes.Length - 1) As Byte
        Array.Copy(bytes, tmpBytes, bytes.Length)
        Array.Reverse(tmpBytes)
        Return BitConverter.ToInt32(tmpBytes, 0)
    End Function
 
Top Bottom