Consuming a web service using digest authentication - c#

We are using C# to send XML data via SOAP. The service requires HttpDigest authentication with #PasswordDigest and #Base64Binary Nonce. Our binding code:
protected BasicHttpBinding binding = new BasicHttpBinding()
{
Name = "ShipmentServiceSoapBinding",
CloseTimeout = new TimeSpan(0, 01, 0),
OpenTimeout = new TimeSpan(0, 01, 0),
ReceiveTimeout = new TimeSpan(0, 10, 0),
SendTimeout = new TimeSpan(0, 5, 0),
AllowCookies = false,
BypassProxyOnLocal = false,
HostNameComparisonMode = HostNameComparisonMode.StrongWildcard,
MaxBufferPoolSize = 5242880,
MaxReceivedMessageSize = 655360,
MessageEncoding = WSMessageEncoding.Text ,
TextEncoding = new UTF8Encoding(),
UseDefaultWebProxy = true,
ReaderQuotas = new XmlDictionaryReaderQuotas() { MaxDepth = 32, MaxStringContentLength = 81920, MaxArrayLength = 1638400, MaxBytesPerRead = 409600, MaxNameTableCharCount = 163840 },
Security = new BasicHttpSecurity() { Mode = BasicHttpSecurityMode.TransportWithMessageCredential,
//Message = new BasicHttpMessageSecurity() { AlgorithmSuite = SecurityAlgorithmSuite.Default, ClientCredentialType = BasicHttpMessageCredentialType.UserName},
Transport = new HttpTransportSecurity(){ ClientCredentialType = HttpClientCredentialType.Digest}},
};
We are encountering 3 different problems based on what type of BasicHttpSecurityMode we are choosing.
Transport - The XML does not include any security information
TransportCredentialOnly - The error we get states that the endpoint cannot be https://
TransportWithMessagecredential - This isn't using digest
Now their ServiceReference allows us to use ClientCredentials class, so here is how we tried using HttpDigest:
typeClient.ClientCredentials.HttpDigest.ClientCredential.UserName = "username";
typeClient.ClientCredentials.HttpDigest.ClientCredential.Password = "password";
I've read on other StackOverflow question that for digest we should be using SoapHeader with AuthHeader, but there is no way for us to match it with what they give is in the API. Is there any other way of doing it? Or is their API not written correctly for C#?

It is much more complicated to use digest auth in this scenario - you will need to implement IClientMessageInspector to get it working... this enables you to modify the http headers in a way that is needed for digest auth.
Helpful links:
https://stackoverflow.com/a/3257760/847363
http://benpowell.org/supporting-the-ws-i-basic-profile-password-digest-in-a-wcf-client-proxy/
http://social.msdn.microsoft.com/Forums/en/wcf/thread/0f09954e-3cef-45b3-a00d-f0f579a06bf7
http://msdn.microsoft.com/en-us/library/system.servicemodel.dispatcher.iclientmessageinspector.aspx
http://msdn.microsoft.com/en-us/library/system.servicemodel.dispatcher.iclientmessageinspector.beforesendrequest.aspx
http://yuzhangqi.itpub.net/post/37475/500654
http://wcfpro.wordpress.com/category/wcf-extensions/
http://social.technet.microsoft.com/wiki/contents/articles/1322.how-to-inspect-wcf-message-headers-using-iclientmessageinspector-en-us.aspx
http://weblogs.asp.net/paolopia/archive/2007/08/23/writing-a-wcf-message-inspector.aspx
http://wcfpro.wordpress.com/2011/03/29/iclientmessageinspector/

Related

WCF service run very slow with reliable session

The server is hosted on a WPF application and use net.tcp binding for duplex channel. We have the client (.NET 4.0) to send a burst of async request to the server. After reliable session is on, the service become really slow (approx. 50-100 call/s) and callback messages aren't sent to the client.
The issue happens in two cases.
We send approx. 1000 messages per seconds to the server. If we send 300-500 messages is all OK.
We send a single message to the server with size 10-20 MB. And in this case messages from the server callbacks wait until the big message is sending.
Below are the codes for the server and client.
Server's binding:
(Also we tried to use standart net.tcp binding istead of custom one but it didn't help)
var customBinding= new CustomBinding()
{
CloseTimeout = new TimeSpan(0, 1, 10),
OpenTimeout = new TimeSpan(0, 1, 10),
ReceiveTimeout = new TimeSpan(0, 5, 30),
SendTimeout = new TimeSpan(0, 2, 0)
};
customBinding.Elements.Add(new BinaryMessageEncodingBindingElement()
{
MessageVersion = MessageVersion.Default,
ReaderQuotas =
{
MaxBytesPerRead = int.MaxValue,
MaxDepth = int.MaxValue,
MaxNameTableCharCount = int.MaxValue,
MaxStringContentLength = int.MaxValue,
MaxArrayLength = int.MaxValue
}
});
customBinding.Elements.Add(new ReliableSessionBindingElement()
{
AcknowledgementInterval = TimeSpan.FromMilliseconds(1),
FlowControlEnabled = true,
InactivityTimeout = new TimeSpan(0, 30, 0),
MaxPendingChannels = 1000,
MaxRetryCount = 10,
MaxTransferWindowSize = 4096,
Ordered = true,
ReliableMessagingVersion = ReliableMessagingVersion.Default
});
var tcpTransport = new TcpTransportBindingElement()
{
ChannelInitializationTimeout = TimeSpan.FromSeconds(30),
ConnectionBufferSize =8192,
ListenBacklog = 10000000,
MaxBufferPoolSize = int.MaxValue,
MaxBufferSize = int.MaxValue,
MaxOutputDelay= TimeSpan.FromMilliseconds(1000),
MaxPendingAccepts = int.MaxValue,
MaxPendingConnections = 40000,
MaxReceivedMessageSize = int.MaxValue,
PortSharingEnabled = true,
TransferMode = TransferMode.Buffered,
};
tcpTransport.ConnectionPoolSettings.GroupName = "OnlineList";
tcpTransport.ConnectionPoolSettings.IdleTimeout = TimeSpan.FromMinutes(5);
tcpTransport.ConnectionPoolSettings.LeaseTimeout = TimeSpan.FromMinutes(5);
tcpTransport.ConnectionPoolSettings.MaxOutboundConnectionsPerEndpoint = 40000;
customBinding.Elements.Add(tcpTransport);
Client side configuration:
BaseTimeout = 7000;
int.MaxValue = 2147483647;
///Common
CloseTimeout = new TimeSpan(0, 0, BaseTimeout),
OpenTimeout = new TimeSpan(0, 0, BaseTimeout),
ReceiveTimeout = new TimeSpan(0, 0, BaseTimeout*4),
SendTimeout = new TimeSpan(0, 0, BaseTimeout),
///Security
Security = { Mode = SecurityMode.None },
///Session
ReliableSession =
{
Enabled = true,
InactivityTimeout = new TimeSpan(1, 10, 0),
Ordered = true
},
///Buffer and message
MaxBufferPoolSize = int.MaxValue,
MaxBufferSize = int.MaxValue,
MaxReceivedMessageSize = int.MaxValue,
///ReaderQuotas
ReaderQuotas =
{
MaxBytesPerRead = int.MaxValue,
MaxDepth = int.MaxValue,
MaxNameTableCharCount = int.MaxValue,
MaxStringContentLength = int.MaxValue,
MaxArrayLength = int.MaxValue
},
//Other
PortSharingEnabled = true,
MaxConnections = 1000,
TransactionFlow = true,
TransferMode = TransferMode.Buffered,

WCF Client Sign Soap Message with Smart Card

I have forms application with service reference by using CustomBinding for signing soap request (Only need soap body will be signed). If I try to sign request with private key included pfx wcf client succesfully sign Basic256Sha256Rsa15.
Succesfull case below:
private CustomBinding GetCustomHttpBinding()
{
CustomBinding binding = new CustomBinding();
// Open and Close = 20s
binding.OpenTimeout = new TimeSpan(0, 0, 20);
binding.CloseTimeout = new TimeSpan(0, 0, 20);
// Send and Receive = 300s
binding.SendTimeout = new TimeSpan(0, 5, 0);
binding.ReceiveTimeout = new TimeSpan(0, 5, 0);
// ++ Setting security binding ++
var param = new X509SecurityTokenParameters();
param.X509ReferenceStyle = X509KeyIdentifierClauseType.IssuerSerial;
param.ReferenceStyle = SecurityTokenReferenceStyle.Internal;
param.InclusionMode = SecurityTokenInclusionMode.AlwaysToRecipient;
param.RequireDerivedKeys = false;
var userNameToken = new UserNameSecurityTokenParameters();
userNameToken.InclusionMode = SecurityTokenInclusionMode.AlwaysToRecipient;
var securityElement = new AsymmetricSecurityBindingElement();
securityElement.EnableUnsecuredResponse = true;
securityElement.IncludeTimestamp = true;
securityElement.RecipientTokenParameters = new X509SecurityTokenParameters(X509KeyIdentifierClauseType.IssuerSerial, SecurityTokenInclusionMode.Never);
securityElement.InitiatorTokenParameters = new X509SecurityTokenParameters(X509KeyIdentifierClauseType.IssuerSerial, SecurityTokenInclusionMode.AlwaysToRecipient);
securityElement.DefaultAlgorithmSuite = SecurityAlgorithmSuite.Basic256Sha256Rsa15;
securityElement.SecurityHeaderLayout = SecurityHeaderLayout.Strict;
securityElement.SetKeyDerivation(false);
securityElement.EndpointSupportingTokenParameters.Signed.Add(param);
//securityElement.MessageProtectionOrder = MessageProtectionOrder.SignBeforeEncrypt;
securityElement.MessageSecurityVersion = MessageSecurityVersion.WSSecurity11WSTrust13WSSecureConversation13WSSecurityPolicy12BasicSecurityProfile10;
binding.Elements.Add(securityElement);
// ++ Setting message encoding binding ++
var encodingElement = new TextMessageEncodingBindingElement();
encodingElement.MessageVersion = MessageVersion.Soap12;
encodingElement.WriteEncoding = Encoding.UTF8;
//encodingElement.MaxReadPoolSize = 50000000;
//encodingElement.MaxWritePoolSize = 50000000;
encodingElement.ReaderQuotas.MaxArrayLength = 50000000;
encodingElement.ReaderQuotas.MaxStringContentLength = 50000000;
binding.Elements.Add(encodingElement);
// ++ Setting https transport binding ++
var httpsElement = new HttpsTransportBindingElement();
// Messagge buffer size
httpsElement.MaxBufferSize = 50000000;
httpsElement.MaxReceivedMessageSize = 50000000;
httpsElement.MaxBufferPoolSize = 50000000;
httpsElement.RequireClientCertificate = true;
// Others
httpsElement.UseDefaultWebProxy = true;
binding.Elements.Add(httpsElement);
return binding;
}
And Service Sertificate set as
client.ClientCredentials.ClientCertificate.Certificate=cert;// From pfx file
client.ClientCredentials.ServiceCertificate.DefaultCertificate =serverCert;//from server certificate
I try to sign with my company smart card which is ACS38 smart card. If I use DefaultAlgotihmSuite Basic128 or Basic128Rsa15 then smartCard certificate succesfully sign body elements. I change algorihmsuite Basic256Sha256Rsa15 for requirement then I get KeySet does not exist. In this Smart Card have private key but WCF does not reach that private key.
Is there way to sign to Sign Soap Body with Sha256Rsa on SmartCard ?

The 'Action', 'http://www.w3.org/2005/08/addressing' required message part was not signed

I am accessing an external java-based web service I have no control over from a WCF client, using dual certificates for encryption and signature as well as custom binding. I am getting a successful response from the server but WCF is throwing a MessageSecurityException : The 'Action', 'http://www.w3.org/2005/08/addressing' required message part was not signed.
My custom binding:
private CustomBinding GetCustomBinding()
{
CustomBinding binding = new CustomBinding();
binding.OpenTimeout = new TimeSpan(0, 0, 20);
binding.CloseTimeout = new TimeSpan(0, 0, 20);
binding.SendTimeout = new TimeSpan(0, 5, 0);
binding.ReceiveTimeout = new TimeSpan(0, 5, 0);
var userNameToken = new UserNameSecurityTokenParameters();
userNameToken.InclusionMode = SecurityTokenInclusionMode.AlwaysToRecipient;
var securityElement = new AsymmetricSecurityBindingElement();
securityElement.EnableUnsecuredResponse = true;
securityElement.IncludeTimestamp = true;
securityElement.RecipientTokenParameters = new X509SecurityTokenParameters(X509KeyIdentifierClauseType.IssuerSerial, SecurityTokenInclusionMode.Never);
securityElement.InitiatorTokenParameters = new X509SecurityTokenParameters(X509KeyIdentifierClauseType.IssuerSerial, SecurityTokenInclusionMode.AlwaysToRecipient);
securityElement.DefaultAlgorithmSuite = SecurityAlgorithmSuite.Basic128Rsa15;
securityElement.SecurityHeaderLayout = SecurityHeaderLayout.Strict;
securityElement.SetKeyDerivation(false);
securityElement.EndpointSupportingTokenParameters.Signed.Add(userNameToken);
securityElement.MessageProtectionOrder = System.ServiceModel.Security.MessageProtectionOrder.SignBeforeEncrypt;
securityElement.MessageSecurityVersion = MessageSecurityVersion.WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10;
binding.Elements.Add(securityElement);
var encodingElement = new TextMessageEncodingBindingElement();
encodingElement.MessageVersion = MessageVersion.Soap11WSAddressing10;
encodingElement.WriteEncoding = Encoding.UTF8;
encodingElement.ReaderQuotas.MaxArrayLength = 50000000;
encodingElement.ReaderQuotas.MaxStringContentLength = 50000000;
binding.Elements.Add(encodingElement);
var httpsElement = new HttpsTransportBindingElement();
httpsElement.MaxBufferSize = 50000000;
httpsElement.MaxReceivedMessageSize = 50000000;
httpsElement.MaxBufferPoolSize = 50000000;
httpsElement.UseDefaultWebProxy = true;
binding.Elements.Add(httpsElement);
return binding;
}
Now I don't care if that Action element is signed or not, or even if it's not there at all, but hacking the response to remove the tag altogether results in a 'No signature message parts were specified for messages with the '' action.' exception.
How can I configure my client to accept the Action and other addressing elements in the response message as they are? Alternatively, what can I change them to so WCF will let them pass?
To override the default checking of the remote Secure Sockets Layer (SSL) certificate used for authentication, specify this on client:
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; });
To investigate the certificate errors, check the sslPolicyErrors parameter of the RemoteCertificateValidationCallback delegate (Link to MSDN manual page).

Tridion 2011 Core Service: Unable to connect in a SSO environment

While trying to connect to the Core Service I get the following error:
The HTTP request was forbidden with client authentication scheme 'Anonymous'
The Tridion environment is configured with SSO from SiteMinder.
Here's my code:
public static ICoreService2010 GetTridionClient()
{
var binding = new BasicHttpBinding()
{
Name = "BasicHttpBinding_TridionCoreService",
CloseTimeout = new TimeSpan(0, 1, 0),
OpenTimeout = new TimeSpan(0, 1, 0),
ReceiveTimeout = new TimeSpan(0, 10, 0),
SendTimeout = new TimeSpan(0, 1, 0),
AllowCookies = false,
BypassProxyOnLocal = false,
HostNameComparisonMode = HostNameComparisonMode.StrongWildcard,
MaxBufferSize = 4194304, // 4MB
MaxBufferPoolSize = 4194304,
MaxReceivedMessageSize = 4194304,
MessageEncoding = WSMessageEncoding.Text,
TextEncoding = System.Text.Encoding.UTF8,
TransferMode = TransferMode.Buffered,
UseDefaultWebProxy = true,
ReaderQuotas = new System.Xml.XmlDictionaryReaderQuotas()
{
MaxDepth = 32,
MaxStringContentLength = 4194304, // 4MB
MaxArrayLength = 4194304,
MaxBytesPerRead = 4194304,
MaxNameTableCharCount = 16384
},
Security = new BasicHttpSecurity()
{
Mode = BasicHttpSecurityMode.TransportCredentialOnly,
Transport = new HttpTransportSecurity()
{
ClientCredentialType = HttpClientCredentialType.None,
},
Message = new BasicHttpMessageSecurity()
{
ClientCredentialType = BasicHttpMessageCredentialType.UserName
}
}
};
string hostname = ConfigurationManager.AppSettings["TridionUrl"];
string username = ConfigurationManager.AppSettings["TridionUsername"];
hostname = string.Format("{0}{1}{2}",
hostname.StartsWith("http") ? "" : "http://",
hostname,
hostname.EndsWith("/") ? "" : "/");
var endpoint = new EndpointAddress(hostname +
"/webservices/CoreService.svc/basicHttp_2010");
var factory = new ChannelFactory<ICoreService2010>(binding, endpoint);
factory.Credentials.UserName.UserName = username;
return factory.CreateChannel();
}
Does anybody have experience of interacting with the Core Service with an authentication type other than Windows?
UPDATE:
I now get the error:
The HTTP request was forbidden with client authentication scheme 'Basic'.
What clientCredentialType should be used in the /webservices/web.config for the bindings?
When I uncomment the SsoAgentHttpModule in the /webservies/web.config we get a 500 error on the webservice, so SDL told us to leave this commented out.
I take it that this module is required for the CoreService to authenticate with authentication scheme 'Basic'?
There are 2 problems with your code:
You have set authentiction to anonymous on the server and assumed that same should be set on the client, but it's not the case. You have also enabled LDAP SSO module on the server that kicks in as soon as you turn the anonymous authentication on. On the client side it will look like plain basic authentication, so you client security code should be like this:
Security = new BasicHttpSecurity()
{
Mode = BasicHttpSecurityMode.TransportCredentialOnly,
Transport = new HttpTransportSecurity()
{
ClientCredentialType = HttpClientCredentialType.Basic,
}
}
You have set username, but not password, so:
factory.Credentials.UserName.UserName = username;
factory.Credentials.UserName.Password = password;
Also, keep in mind that you might need to specify User Name Qualifier (SSO by default) when setting user, like SSO\user
This should move you a step closer, if you will still have problems - please update your question with recent exception.

WCF SSL certificate using an enterprise CA

For an application, I need to have a SSL certificate for a WCF service,
So we installed it. If I go with an internet browser with a web browser trough https, I've got no problem, no warning, nothing, so I suppose that this certificate is considered as valid for windows.
The problem is that when I'm trying to connect to my WCF server, I got this error:
The X.509 certificate CN=myHostName, OU=tom, O=myDomainName,
L=MyLocation, S=SO, C=CH chain building failed. The certificate that
was used has a trust chain that cannot be verified. Replace the
certificate or change the certificateValidationMode. The revocation
function was unable to check revocation because the revocation server
was offline.
What can be wrong? How can I know which part of the chain is unvalid? Is there any way to know what is the missing part?
Here is my code
The server:
ServiceHost myHost = new ServiceHost(typeof(MyService));
WSHttpBinding binding = new WSHttpBinding
{
ReaderQuotas = { MaxStringContentLength = int.MaxValue, MaxArrayLength = int.MaxValue, MaxDepth = int.MaxValue, MaxBytesPerRead = int.MaxValue, MaxNameTableCharCount = int.MaxValue },
MaxReceivedMessageSize = int.MaxValue
};
TimeSpan timeoutSpan = TimeSpan.FromMilliseconds(timeout);
binding.CloseTimeout = timeoutSpan;
binding.OpenTimeout = timeoutSpan;
binding.ReceiveTimeout = timeoutSpan;
binding.SendTimeout = timeoutSpan;
binding.ReliableSession.InactivityTimeout = timeoutSpan;
binding.MaxBufferPoolSize = int.MaxValue;
//we set the security type
binding.Security.Mode = SecurityMode.Message;
binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
//we set the server's certificate
myHost.Credentials.ServiceCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindBySubjectName, ConfigurationManager.AppSettings["Hostname"]);
myHost.Credentials.ClientCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
//we add the endPoint(and we indicate which methods are exposed through the interface
myHost.AddServiceEndpoint(services[port], binding, String.Format("http://localhost:{0}", port));
//Some services doesn't need an authentication
if (!servicesWithoutAuth.Contains(services[port]))
{
//We set the authentifier:
myHost.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom;
myHost.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new CustomUserNameValidator();
myHost.Authorization.PrincipalPermissionMode = PrincipalPermissionMode.Custom;
//we set the AuthorizationPolicy
List<IAuthorizationPolicy> policies = new List<IAuthorizationPolicy> { new CustomAuthorizationPolicy() };
myHost.Authorization.ExternalAuthorizationPolicies = policies.AsReadOnly();
}
else
{
//We set the authentifier:
myHost.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom;
myHost.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new NoUserNamePasswordValidator();
}
//We bypass the certificate verification(our certificate is only self signed)
//HACK Only to desactivate the SSL check:
ServicePointManager.ServerCertificateValidationCallback += ValidateCertificate;
//HACK: Remove when debug finished
private static bool ValidateCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslpolicyerrors)
{
return true;
}
My client side:
// the remote adress is of the form "net.tcp://localhost:8000"
string remoteAddress = String.Format("{0}://{1}:{2}", Tools.GetDescription(accessInfo.ServiceHost.Protocol), accessInfo.ServiceHost.HostName, accessInfo.PortNumber);
// HACK: binding depends on protocol -> switch over accessInfo.ServiceHost.Protocol
// avoid seralization/deserialization problems with large XML's
WSHttpBinding binding = new WSHttpBinding();
binding.ReaderQuotas.MaxStringContentLength = int.MaxValue;
binding.ReaderQuotas.MaxArrayLength = int.MaxValue;
binding.MaxReceivedMessageSize = int.MaxValue;
binding.ReaderQuotas.MaxStringContentLength = int.MaxValue;
binding.ReaderQuotas.MaxArrayLength = int.MaxValue;
binding.ReaderQuotas.MaxDepth = int.MaxValue;
binding.ReaderQuotas.MaxBytesPerRead = int.MaxValue;
binding.ReaderQuotas.MaxNameTableCharCount = int.MaxValue;
TimeSpan timeoutSpan = DateTime.Now.AddMinutes(30) - DateTime.Now;
binding.CloseTimeout = timeoutSpan;
binding.OpenTimeout = timeoutSpan;
binding.ReceiveTimeout = timeoutSpan;
binding.SendTimeout = timeoutSpan;
binding.ReliableSession.InactivityTimeout = timeoutSpan;
//++
binding.MaxBufferPoolSize = int.MaxValue;
//we set the security type
binding.Security.Mode = SecurityMode.Message;
binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
ChannelFactory<TService> channelFactory = new ChannelFactory<TService>(binding, remoteAddress);
channelFactory.Credentials.UserName.UserName = ((UsernamePasswordAuthentication)authInfos).Username;
channelFactory.Credentials.UserName.Password = ((UsernamePasswordAuthentication)authInfos).Password;
//We set the maxItemsInObjectGraph
foreach (OperationDescription op in channelFactory.Endpoint.Contract.Operations)
{
DataContractSerializerOperationBehavior dataContractBehavior = op.Behaviors.Find<DataContractSerializerOperationBehavior>();
if (dataContractBehavior != null)
{
dataContractBehavior.MaxItemsInObjectGraph = int.MaxValue;
}
}
SamlSecurityTokenAuthenticator authenticator = new SamlSecurityTokenAuthenticator(new List<SecurityTokenAuthenticator>(new SecurityTokenAuthenticator[] { new RsaSecurityTokenAuthenticator(), new X509SecurityTokenAuthenticator(X509CertificateValidator.None) }), TimeSpan.FromDays(5));
_service = channelFactory.CreateChannel();
How can I know which part of the chain is unvalid? Is there any way to
know what is the missing part?
Well, from my experience, if you open your keystore and view it, you should see that your certificate clearly forms a chain. I don't know what tool you use to view your keystore (or if your using the windows keystore), but when you view your key, you should see a chain of some kind. If a chain is formed correctly, it will appear correctly, and doesn't have any missing parts.
My guess is that when you imported your certificate reply, it didn't form the chain for some reason. In other words, your certificate is in your keystore as a "unchained" public key.

Categories