Problems authenticating server for SSLStream negotiation

  • Thread starter Thread starter Lee Gillie
  • Start date Start date
L

Lee Gillie

This is absolutely driving me nuts. Any hints would be GREATLY appreciated.

I am trying to negotiate a clear channel socket to SSL. Thing is it
works great on two development computers, but falls flat on its face
when I go to deploy to a server.

I have written both CLIENT and SERVER for FTP. When the client issues
the appropriate command I call the SSLStream.AuthenticateAsClient, while
at the same time the server is calling SSLStream.AuthenticateAsServer.

I have played with using a number of different host names on the client
side, to no avail.

On the server side I reconstitute the server certificate from the
registry, where I have it stored as a binary block of data. A utility
loads the PFX file, which includes the private key, then stores that in
the registry. Later, when the FTP server starts I load the binary data,
and use it to construct the Certificate, which is eventually used to
negotiate SSL. The PFX was built at the server by importing our
certificate, and then exporting to PFX, supplying the password, choosing
to include the private key, and to NOT delete the key after export.

On the two development computers that I went through all of these steps
on (using the PFX generated at the production server), it all works
wonderfully. When I do the same at two different servers, the
negotiation fails. SChannel errors are logged in the system event log.
Event ID 36870, "A fatal error occurred when attempting to access the
SSL server credential private key. The error code returned from the
cryptographic module is 0x80090016."

When the FTP service starts I pull the certificate from the registry as
described above, and verify the chain. All 3 elements on the chain are
valid. The server call to AuthenticateAsServer crashes as such:
09:16:44.97 NegotiateSSLServer unable to authenticate.
09:16:44.99 - System.ComponentModel.Win32Exception: The credentials
supplied to the package were not recognized
09:16:44.99 at
System.Net.SSPIWrapper.AcquireCredentialsHandle(SSPIInterface SecModule,
String package, CredentialUse intent, SecureCredential scc)
09:16:44.99 at
System.Net.Security.SecureChannel.AcquireCredentialsHandle(CredentialUse
credUsage, SecureCredential& secureCredential)
09:16:44.99 at
System.Net.Security.SecureChannel.AcquireServerCredentials(Byte[]&
thumbPrint)
09:16:44.99 at System.Net.Security.SecureChannel.GenerateToken(Byte[]
input, Int32 offset, Int32 count, Byte[]& output)
09:16:44.99 at System.Net.Security.SecureChannel.NextMessage(Byte[]
incoming, Int32 offset, Int32 count)
09:16:44.99 at System.Net.Security.SslState.StartSendBlob(Byte[]
incoming, Int32 count, AsyncProtocolRequest asyncRequest)
09:16:44.99 at
System.Net.Security.SslState.ProcessReceivedBlob(Byte[] buffer, Int32
count, AsyncProtocolRequest asyncRequest)
09:16:44.99 at System.Net.Security.SslState.StartReadFrame(Byte[]
buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
09:16:44.99 at System.Net.Security.SslState.StartReceiveBlob(Byte[]
buffer, AsyncProtocolRequest asyncRequest)
09:16:44.99 at
System.Net.Security.SslState.ForceAuthentication(Boolean receiveFirst,
Byte[] buffer, AsyncProtocolRequest asyncRequest)
09:16:44.99 at
System.Net.Security.SslState.ProcessAuthentication(LazyAsyncResult
lazyResult)
09:16:44.99 at
System.Net.Security.SslStream.AuthenticateAsServer(X509Certificate
serverCertificate, Boolean clientCertificateRequired, SslProtocols
enabledSslProtocols, Boolean checkCertificateRevocation)
09:16:44.99 at
ODP.NMServe.TCPClientSSL.NegotiateSSLServer(X509Certificate2
Certificate, Boolean ClearReceiveBuffer)
 
Lee Gillie said:
On the server side I reconstitute the server certificate from the
registry, where I have it stored as a binary block of data. A utility
loads the PFX file, which includes the private key, then stores that in
the registry. Later, when the FTP server starts I load the binary data,
and use it to construct the Certificate, which is eventually used to
negotiate SSL. The PFX was built at the server by importing our
certificate, and then exporting to PFX, supplying the password, choosing
to include the private key, and to NOT delete the key after export.

Is there any reason why you're not using the usual SSPI certificate stores?

The description above just suggests that you may have a problem in your
export to PFX, or your import from PFX to the registry, or your reading from
the registry, or your conversion from a binary object to an in-memory
certificate.

If you install your PFX in the regular certificate stores, you can test
using any of a number of other implementations, such as my own WFTPD Pro
(http://www.wftpd.com), to see if your certificate works there. That would
narrow it down to either a problem in your SSL code, or in your import /
export of the certificate.

Come to that, why are you writing a program that's already been written by
so many others?

Alun.
~~~~
 
I'd suggest bumping up your SChannel logging level to debug using the steps
here:

http://support.microsoft.com/?id=260729

You might also consider running filemon and regmon while your code is
executing to see if you can find any access denied events on specific files
or registry keys. It could be a permissions problem with the way you've
deployed the private key.

Joe K.

Lee Gillie said:
This is absolutely driving me nuts. Any hints would be GREATLY
appreciated.

I am trying to negotiate a clear channel socket to SSL. Thing is it works
great on two development computers, but falls flat on its face when I go
to deploy to a server.

I have written both CLIENT and SERVER for FTP. When the client issues the
appropriate command I call the SSLStream.AuthenticateAsClient, while at
the same time the server is calling SSLStream.AuthenticateAsServer.

I have played with using a number of different host names on the client
side, to no avail.

On the server side I reconstitute the server certificate from the
registry, where I have it stored as a binary block of data. A utility
loads the PFX file, which includes the private key, then stores that in
the registry. Later, when the FTP server starts I load the binary data,
and use it to construct the Certificate, which is eventually used to
negotiate SSL. The PFX was built at the server by importing our
certificate, and then exporting to PFX, supplying the password, choosing
to include the private key, and to NOT delete the key after export.

On the two development computers that I went through all of these steps on
(using the PFX generated at the production server), it all works
wonderfully. When I do the same at two different servers, the negotiation
fails. SChannel errors are logged in the system event log. Event ID 36870,
"A fatal error occurred when attempting to access the SSL server
credential private key. The error code returned from the cryptographic
module is 0x80090016."

When the FTP service starts I pull the certificate from the registry as
described above, and verify the chain. All 3 elements on the chain are
valid. The server call to AuthenticateAsServer crashes as such:
09:16:44.97 NegotiateSSLServer unable to authenticate.
09:16:44.99 - System.ComponentModel.Win32Exception: The credentials
supplied to the package were not recognized
09:16:44.99 at
System.Net.SSPIWrapper.AcquireCredentialsHandle(SSPIInterface SecModule,
String package, CredentialUse intent, SecureCredential scc)
09:16:44.99 at
System.Net.Security.SecureChannel.AcquireCredentialsHandle(CredentialUse
credUsage, SecureCredential& secureCredential)
09:16:44.99 at
System.Net.Security.SecureChannel.AcquireServerCredentials(Byte[]&
thumbPrint)
09:16:44.99 at System.Net.Security.SecureChannel.GenerateToken(Byte[]
input, Int32 offset, Int32 count, Byte[]& output)
09:16:44.99 at System.Net.Security.SecureChannel.NextMessage(Byte[]
incoming, Int32 offset, Int32 count)
09:16:44.99 at System.Net.Security.SslState.StartSendBlob(Byte[]
incoming, Int32 count, AsyncProtocolRequest asyncRequest)
09:16:44.99 at System.Net.Security.SslState.ProcessReceivedBlob(Byte[]
buffer, Int32 count, AsyncProtocolRequest asyncRequest)
09:16:44.99 at System.Net.Security.SslState.StartReadFrame(Byte[]
buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
09:16:44.99 at System.Net.Security.SslState.StartReceiveBlob(Byte[]
buffer, AsyncProtocolRequest asyncRequest)
09:16:44.99 at System.Net.Security.SslState.ForceAuthentication(Boolean
receiveFirst, Byte[] buffer, AsyncProtocolRequest asyncRequest)
09:16:44.99 at
System.Net.Security.SslState.ProcessAuthentication(LazyAsyncResult
lazyResult)
09:16:44.99 at
System.Net.Security.SslStream.AuthenticateAsServer(X509Certificate
serverCertificate, Boolean clientCertificateRequired, SslProtocols
enabledSslProtocols, Boolean checkCertificateRevocation)
09:16:44.99 at
ODP.NMServe.TCPClientSSL.NegotiateSSLServer(X509Certificate2 Certificate,
Boolean ClearReceiveBuffer)
 
Alun said:
Is there any reason why you're not using the usual SSPI certificate stores?

The description above just suggests that you may have a problem in your
export to PFX, or your import from PFX to the registry, or your reading from
the registry, or your conversion from a binary object to an in-memory
certificate.

If you install your PFX in the regular certificate stores, you can test
using any of a number of other implementations, such as my own WFTPD Pro
(http://www.wftpd.com), to see if your certificate works there. That would
narrow it down to either a problem in your SSL code, or in your import /
export of the certificate.

Come to that, why are you writing a program that's already been written by
so many others?

Alun.
~~~~

Again, the registry store and retrieval on the development workstation
runs fine, so it sounds like you are suggesting this behaves differently
on servers. I will put your theory to the test.

I have tested the service running on the workstation against some free
public SSL/TLS FTP client applications, and that works fine, as does our
own client, but of course, none for the server installations of the service.

You are assuming all this client and server do is standard FTP. A very
small part of the overall system. We have tried many client/server
programs over the years, but no one has written a client/server pair
that do what our proprietary applications do. We have been maintaining
incarnations of this since DOS and DIALUP. We need to have 100% ability
to extend and customize the client experience, backed up by custom
server side features.

Thanks Alun.
 
Remember that even if you store the certificate in some strange place,
SChannel uses CAPI and it will look for the private key in a CAPI key store.
Generally, when you pass an arbitrary certificate in, the underlying code
just uses that certificate to try to find a matching private key.

If you never installed the certificate on the server with the private key,
then I don't think this will ever work.

If you don't want to deploy keys the CAPI way, you probably can't use
something that is based on SChannel like SslStream.

Joe K.
 
Again, the registry store and retrieval on the development workstation
runs fine, so it sounds like you are suggesting this behaves differently
on servers. I will put your theory to the test.

Indeed.

I load from the store, and it works fine. I suspect a differing behavior
for servers and workstations in this regard, in that it may require the
cert to come from a store. Regardless, I am going to abandon my own
storage mechanism.

Thanks again - Lee
 
Lee Gillie said:
Indeed.

I load from the store, and it works fine. I suspect a differing behavior
for servers and workstations in this regard, in that it may require the
cert to come from a store. Regardless, I am going to abandon my own
storage mechanism.

My cube-mate has just pointed out that XP and Server 2003 do indeed differ
on where they place private key information.

Alun.
~~~~
 
This sounds like a good approach to me. That particular schannel error code
is only returned when the server certificate and private key are indeed
present, but something goes wrong when the private key is accessed
(CryptAcquireContext). This is typically one of two things. First of all, it
could be that there was a problem decrypting the private key. This happens
more often for user private keys than for machine/service ones, due to user
password change complications. The most likely cause of the problem is a
permissions problem accessing the private key file. As I recall, machine
private key files are ACL'ed for Local Machine and Administrator access only
by default. If your service is running under a different service account,
then some adjustment of the permissions is probably necessary.

Is it possible that your server is running under a different account on the
productionm machines than the ones you use for development?

Regards,
John

Joe Kaplan (MVP - ADSI) said:
I'd suggest bumping up your SChannel logging level to debug using the
steps here:

http://support.microsoft.com/?id=260729

You might also consider running filemon and regmon while your code is
executing to see if you can find any access denied events on specific
files or registry keys. It could be a permissions problem with the way
you've deployed the private key.

Joe K.

Lee Gillie said:
This is absolutely driving me nuts. Any hints would be GREATLY
appreciated.

I am trying to negotiate a clear channel socket to SSL. Thing is it works
great on two development computers, but falls flat on its face when I go
to deploy to a server.

I have written both CLIENT and SERVER for FTP. When the client issues the
appropriate command I call the SSLStream.AuthenticateAsClient, while at
the same time the server is calling SSLStream.AuthenticateAsServer.

I have played with using a number of different host names on the client
side, to no avail.

On the server side I reconstitute the server certificate from the
registry, where I have it stored as a binary block of data. A utility
loads the PFX file, which includes the private key, then stores that in
the registry. Later, when the FTP server starts I load the binary data,
and use it to construct the Certificate, which is eventually used to
negotiate SSL. The PFX was built at the server by importing our
certificate, and then exporting to PFX, supplying the password, choosing
to include the private key, and to NOT delete the key after export.

On the two development computers that I went through all of these steps
on (using the PFX generated at the production server), it all works
wonderfully. When I do the same at two different servers, the negotiation
fails. SChannel errors are logged in the system event log. Event ID
36870, "A fatal error occurred when attempting to access the SSL server
credential private key. The error code returned from the cryptographic
module is 0x80090016."

When the FTP service starts I pull the certificate from the registry as
described above, and verify the chain. All 3 elements on the chain are
valid. The server call to AuthenticateAsServer crashes as such:
09:16:44.97 NegotiateSSLServer unable to authenticate.
09:16:44.99 - System.ComponentModel.Win32Exception: The credentials
supplied to the package were not recognized
09:16:44.99 at
System.Net.SSPIWrapper.AcquireCredentialsHandle(SSPIInterface SecModule,
String package, CredentialUse intent, SecureCredential scc)
09:16:44.99 at
System.Net.Security.SecureChannel.AcquireCredentialsHandle(CredentialUse
credUsage, SecureCredential& secureCredential)
09:16:44.99 at
System.Net.Security.SecureChannel.AcquireServerCredentials(Byte[]&
thumbPrint)
09:16:44.99 at System.Net.Security.SecureChannel.GenerateToken(Byte[]
input, Int32 offset, Int32 count, Byte[]& output)
09:16:44.99 at System.Net.Security.SecureChannel.NextMessage(Byte[]
incoming, Int32 offset, Int32 count)
09:16:44.99 at System.Net.Security.SslState.StartSendBlob(Byte[]
incoming, Int32 count, AsyncProtocolRequest asyncRequest)
09:16:44.99 at System.Net.Security.SslState.ProcessReceivedBlob(Byte[]
buffer, Int32 count, AsyncProtocolRequest asyncRequest)
09:16:44.99 at System.Net.Security.SslState.StartReadFrame(Byte[]
buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
09:16:44.99 at System.Net.Security.SslState.StartReceiveBlob(Byte[]
buffer, AsyncProtocolRequest asyncRequest)
09:16:44.99 at
System.Net.Security.SslState.ForceAuthentication(Boolean receiveFirst,
Byte[] buffer, AsyncProtocolRequest asyncRequest)
09:16:44.99 at
System.Net.Security.SslState.ProcessAuthentication(LazyAsyncResult
lazyResult)
09:16:44.99 at
System.Net.Security.SslStream.AuthenticateAsServer(X509Certificate
serverCertificate, Boolean clientCertificateRequired, SslProtocols
enabledSslProtocols, Boolean checkCertificateRevocation)
09:16:44.99 at
ODP.NMServe.TCPClientSSL.NegotiateSSLServer(X509Certificate2 Certificate,
Boolean ClearReceiveBuffer)
 
Back
Top