I need to connect my NET Framework 4 Client App, to a Webservice, deployed on a Apache CXF, with WS Security. That service is out of my control.
The service is added to the project as a "Service Reference".
This is the proxy:
ServicePointManager.ServerCertificateValidationCallback = New System.Net.Security.RemoteCertificateValidationCallback(AddressOf AcceptAllCertifications)
Dim oBinding As New CustomBinding()
Dim oSecurity As SecurityBindingElement
oSecurity = AsymmetricSecurityBindingElement.CreateCertificateOverTransportBindingElement(MessageSecurityVersion.WSSecurity11WSTrust13WSSecureConversation13WSSecurityPolicy12BasicSecurityProfile10)
oSecurity.IncludeTimestamp = True
oBinding.Elements.Add(oSecurity)
oBinding.Elements.Add(New CertFixEscapedComma.CertRefEncodingBindingElement())
( This a custom message encoder)
CertFixEscapedComma.CertRefEncoder.CERTIFICADO = Convert.ToBase64String(oCertificado.RawData)
oBinding.CloseTimeout = New TimeSpan(0, 2, 0)
Dim oTransport As New HttpsTransportBindingElement()
oBinding.Elements.Add(oTransport)
Dim oProxyClient As New NameServiceClient(oBinding, New System.ServiceModel.EndpointAddress(New Uri("https://url_service")))
Dim oCertificado As X509Certificate2
oCertificado = function_client_certificate() ' this get the proper cert
oProxyClient.ClientCredentials.ClientCertificate.Certificate = oCertificado
oProxyClient.name_function(params) 'call to the remote service
Well. The server accepts my Request, and send the the response, in this way:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" soap:mustUnderstand="1" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<xenc:EncryptedKey xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" Id="EK-4A5A4F8820EFD673E7152328322340610394">
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p" />
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<wsse:SecurityTokenReference>
<ds:X509Data>
<ds:X509IssuerSerial>
<ds:X509IssuerName>issuer name etc etc cetc </ds:X509IssuerName>
<ds:X509SerialNumber>62535066537829860999033107852056725154</ds:X509SerialNumber>
</ds:X509IssuerSerial>
</ds:X509Data>
</wsse:SecurityTokenReference>
</ds:KeyInfo>
<xenc:CipherData>
<xenc:CipherValue>SlU4B4BlMhsEc0ek ... ==</xenc:CipherValue>
</xenc:CipherData>
<xenc:ReferenceList>
<xenc:DataReference URI="#ED-4A5A4F8820EFD673E7152328322340710395" />
</xenc:ReferenceList>
</xenc:EncryptedKey>
<wsu:Timestamp wsu:Id="TS-4A5A4F8820EFD673E7152328322340510393">
<wsu:Created>2018-04-09T14:13:43.405Z</wsu:Created>
<wsu:Expires>2018-04-09T14:18:43.405Z</wsu:Expires>
</wsu:Timestamp>
</wsse:Security>
</soap:Header>
<soap:Body>
<xenc:EncryptedData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" Id="ED-4A5A4F8820EFD673E7152328322340710395" Type="http://www.w3.org/2001/04/xmlenc#Content">
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc" />
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<wsse:SecurityTokenReference xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsse11="http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd" wsse11:TokenType="http://docs.oasis-open.org/wss/oasis-wss-soap-message-security-1.1#EncryptedKey">
<wsse:Reference URI="#EK-4A5A4F8820EFD673E7152328322340610394" />
</wsse:SecurityTokenReference>
</ds:KeyInfo>
<xenc:CipherData>
<xenc:CipherValue>ZB7P3tYgRE4R7RZc0TONazc93t.... W5VoHVw5ywRj4D2hb9dIAaE8PQClm2vw==</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedData>
</soap:Body>
</soap:Envelope>
I get the error "Cannot resolve KeyInfo for unwrapping key". Reading OASIS doc about that kind of soap messages, I think the message it's ok.
I have tried, with the custom encoder, changing "X509IssuerSerial" node, for a ". Same error.
I can, reading the message directly, perform a manual key decryption, using the cert. Then, with the key, I can decrypt the data. So data is correct.
BUT, I don't wan't this. I want use the Service Reference.
Going through NET Code, I see that stack trace:
System.ServiceModel.dll!System.ServiceModel.Security.WSSecurityJan2004.WrappedKeyTokenEntry.ReadTokenCore(System.Xml.XmlDictionaryReader reader, System.IdentityModel.Selectors.SecurityTokenResolver tokenResolver)
System.ServiceModel.dll!System.ServiceModel.Security.WSSecurityTokenSerializer.ReadTokenCore(System.Xml.XmlReader reader, System.IdentityModel.Selectors.SecurityTokenResolver tokenResolver)
System.ServiceModel.dll!System.ServiceModel.Security.WSSecurityOneDotZeroReceiveSecurityHeader.DecryptWrappedKey(System.Xml.XmlDictionaryReader reader)
System.ServiceModel.dll!System.ServiceModel.Security.ReceiveSecurityHeader.ReadEncryptedKey(System.Xml.XmlDictionaryReader reader, bool processReferenceListIfPresent)
System.ServiceModel.dll!System.ServiceModel.Security.ReceiveSecurityHeader.ExecuteFullPass(System.Xml.XmlDictionaryReader reader)
System.ServiceModel.dll!System.ServiceModel.Security.ReceiveSecurityHeader.Process(System.TimeSpan timeout, System.Security.Authentication.ExtendedProtection.ChannelBinding channelBinding, System.Security.Authentication.ExtendedProtection.ExtendedProtectionPolicy extendedProtectionPolicy)
System.ServiceModel.dll!System.ServiceModel.Security.TransportSecurityProtocol.VerifyIncomingMessageCore(ref System.ServiceModel.Channels.Message message, System.TimeSpan timeout)
System.ServiceModel.dll!System.ServiceModel.Security.TransportSecurityProtocol.VerifyIncomingMessage(ref System.ServiceModel.Channels.Message message, System.TimeSpan timeout)
System.ServiceModel.dll!System.ServiceModel.Security.SecurityProtocol.VerifyIncomingMessage(ref System.ServiceModel.Channels.Message message, System.TimeSpan timeout, System.ServiceModel.Security.SecurityProtocolCorrelationState[] correlationStates)
System.ServiceModel.dll!System.ServiceModel.Channels.SecurityChannelFactory<System.ServiceModel.Channels.IRequestChannel>.SecurityRequestChannel.ProcessReply(System.ServiceModel.Channels.Message reply, System.ServiceModel.Security.SecurityProtocolCorrelationState correlationState, System.TimeSpan timeout)
System.ServiceModel.dll!System.ServiceModel.Channels.SecurityChannelFactory<System.__Canon>.SecurityRequestChannel.Request(System.ServiceModel.Channels.Message message, System.TimeSpan timeout)
System.ServiceModel.dll!System.ServiceModel.Dispatcher.RequestChannelBinder.Request(System.ServiceModel.Channels.Message message, System.TimeSpan timeout)
System.ServiceModel.dll!System.ServiceModel.Channels.ServiceChannel.Call(string action, bool oneway, System.ServiceModel.Dispatcher.ProxyOperationRuntime operation, object[] ins, object[] outs, System.TimeSpan timeout)
System.ServiceModel.dll!System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(System.Runtime.Remoting.Messaging.IMethodCallMessage methodCall, System.ServiceModel.Dispatcher.ProxyOperationRuntime operation)
System.ServiceModel.dll!System.ServiceModel.Channels.ServiceChannelProxy.Invoke(System.Runtime.Remoting.Messaging.IMessage message)
mscorlib.dll!System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(ref System.Runtime.Remoting.Proxies.MessageData msgData, int type)
... mycode_calling_the_service()...
So, in "System.ServiceModel.Security.WSSecurityJan2004.WrappedKeyTokenEntry.ReadTokenCore", it's trying to "CreateWrappedKeyToken", and the Exception it's throw here:
WrappedKeySecurityToken CreateWrappedKeyToken(string id, string encryptionMethod, string carriedKeyName,
SecurityKeyIdentifier unwrappingTokenIdentifier, byte[] wrappedKey, SecurityTokenResolver tokenResolver)
{
ISspiNegotiationInfo sspiResolver = tokenResolver as ISspiNegotiationInfo;
if (sspiResolver != null)
{
ISspiNegotiation unwrappingSspiContext = sspiResolver.SspiNegotiation;
// ensure that the encryption algorithm is compatible
if (encryptionMethod != unwrappingSspiContext.KeyEncryptionAlgorithm)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new MessageSecurityException(SR.GetString(SR.BadKeyEncryptionAlgorithm, encryptionMethod)));
}
byte[] unwrappedKey = unwrappingSspiContext.Decrypt(wrappedKey);
return new WrappedKeySecurityToken(id, unwrappedKey, encryptionMethod, unwrappingSspiContext, unwrappedKey);
}
else
{
if (tokenResolver == null)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentNullException("tokenResolver"));
}
if (unwrappingTokenIdentifier == null || unwrappingTokenIdentifier.Count == 0)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new MessageSecurityException(SR.GetString(SR.MissingKeyInfoInEncryptedKey)));
}
SecurityToken unwrappingToken;
SecurityHeaderTokenResolver resolver = tokenResolver as SecurityHeaderTokenResolver;
if (resolver != null)
{
unwrappingToken = resolver.ExpectedWrapper; if (unwrappingToken != null)
{
if (!resolver.CheckExternalWrapperMatch(unwrappingTokenIdentifier))
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new MessageSecurityException(
SR.GetString(SR.EncryptedKeyWasNotEncryptedWithTheRequiredEncryptingToken, unwrappingToken)));
}
}
else
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new MessageSecurityException(
SR.GetString(SR.UnableToResolveKeyInfoForUnwrappingToken, unwrappingTokenIdentifier, resolver)));
}
}
...
So, in "unwrappingToken = resolver.ExpectedWrapper", I get a "null".
This could be some kind of "messages namespaces mismatch", or something like that, that I do not see?
Certificate it's ok and valid. It has all the x509v3 properties, and the issuer it's a trusted issuer...
Help me guys, my diopters are increasing with this ...
Well, finally I could get rid of this message. You need to ensure if the service is using same certificate for auth and encription. You can get the public key's cert using fiddler, for example.
If 'NO' is the answer, you will need custom client credentials, something like this:
Public Class MyClientCredentials
Inherits ClientCredentials
Public Sub New()
End Sub
' Perform client credentials initialization.
Protected Sub New(ByVal other As MyClientCredentials)
MyBase.New(other)
End Sub
''' <summary>
''' Link to token manager
''' </summary>
''' <returns></returns>
Public Overrides Function CreateSecurityTokenManager() As SecurityTokenManager
' Return your implementation of the SecurityTokenManager.
Return New MyClientCredentialsSecurityTokenManager(Me)
End Function
Where, the custom token manager is doing something like this:
Public Class MyClientCredentialsSecurityTokenManager
Inherits ClientCredentialsSecurityTokenManager
Private _oCredenciales As MyClientCredentials
Public Sub New(ByVal credentials As MyClientCredentials)
MyBase.New(credentials)
Me._oCredenciales = credentials
End Sub
''' <summary>
''' Custom token for each operation
''' </summary>
''' <param name="p_oRequirement"></param>
''' <returns></returns>
Public Overrides Function CreateSecurityTokenProvider(ByVal p_oRequirement As SecurityTokenRequirement) As SecurityTokenProvider
Dim oRes As SecurityTokenProvider = Nothing
If p_oRequirement.TokenType = SecurityTokenTypes.X509Certificate Then
Dim direction = p_oRequirement.GetProperty(Of MessageDirection)(ServiceModelSecurityTokenRequirement.MessageDirectionProperty)
If direction = MessageDirection.Output Then
If p_oRequirement.KeyUsage = SecurityKeyUsage.Signature Then
oRes = New X509SecurityTokenProvider(Me._oCredenciales.ClientCertificate.Certificate)
Else
oRes = New X509SecurityTokenProvider(Me._oCredenciales.ServiceCertificate.DefaultCertificate())
End If
End If
Else
oRes = MyBase.CreateSecurityTokenProvider(p_oRequirement)
End If
Return oRes
End Function
Now, apply this to channel:
oServicio.ChannelFactory.Endpoint.Behaviors.Remove(Of ClientCredentials)()
oServicio.ChannelFactory.Endpoint.Behaviors.Add(oCred)
I hope this will help someone on future.