Search code examples
c#vb.netwcfsoapws-security

Apache CXF WS Security WebService from NET Client - Cannot resolve KeyInfo for unwrapping key


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 ...


Solution

  • 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.