wcf icon indicating copy to clipboard operation
wcf copied to clipboard

InternalTokenReference mismatch when using SecurityTokenProvider GetToken vs WSTrustChannelFactory Issue

Open eiki25 opened this issue 2 years ago • 1 comments

GetToken SAML token using SecurityTokenProvider GetToken returns a GenericXmlSecurityToken with different type of Internal/External Identifiers then WSTrustChannelFactory Issue did in the full framework.

Using .NET 6 - Issued with SecurityTokenProvider System.IdentityModel.Tokens.GenericXmlSecurityKeyIdentifierClause is returned.

  •   **InternalTokenReference**	{System.IdentityModel.Tokens.GenericXmlSecurityKeyIdentifierClause}	System.IdentityModel.Tokens.SecurityKeyIdentifierClause {System.IdentityModel.Tokens.GenericXmlSecurityKeyIdentifierClause}
      CanCreateKey	false	bool
      ClauseType	null	string
      DerivationLength	0	int
      Id	null	string
    

Using .NET Framework - Issued with WSTrustChannelFactory

  •   InternalTokenReference	{SamlAssertionKeyIdentifierClause(AssertionId = '_cbbf45eb-d99b-4ee0-bf9b-9ab6dc3d7d91')}	System.IdentityModel.Tokens.SecurityKeyIdentifierClause {System.IdentityModel.Tokens.SamlAssertionKeyIdentifierClause}
      AssertionId	"_cbbf45eb-d99b-4ee0-bf9b-9ab6dc3d7d91"	string
      CanCreateKey	false	bool
      ClauseType	null	string
      DerivationLength	32	int
      Id	null	string
    

To Reproduce

            binding.Security.Mode = SecurityMode.TransportWithMessageCredential;
            binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
            binding.Security.Message.EstablishSecurityContext = false;

            // Credentials - UserPass
            var clientCredentials = new ClientCredentials();
            clientCredentials.UserName.UserName = "Username";
            clientCredentials.UserName.Password = "Pass";
            var trustCredentials = new WSTrustChannelClientCredentials(clientCredentials);

            var securityKeyType = SecurityKeyType.SymmetricKey;

            var issuerAddress = new EndpointAddress(new Uri("https://sts.example.com/adfs/services/trust/13/usernamemixed"));
            var tokenParams = WSTrustTokenParameters.CreateWS2007FederationTokenParameters(binding, issuerAddress);
            tokenParams.KeyType = securityKeyType;

            var tokenRequirement = new SecurityTokenRequirement();
            tokenRequirement.Properties[IssuedSecurityTokenParametersProperty] = tokenParams;
            tokenRequirement.Properties[TargetAddressProperty] = new EndpointAddress(appliesTo); //Realm
            tokenRequirement.Properties[SecurityAlgorithmSuiteProperty] = SecurityAlgorithmSuite.Basic256;

            var tokenManager = trustCredentials.CreateSecurityTokenManager();
            var tokenProvider = tokenManager.CreateSecurityTokenProvider(tokenRequirement);
            ((ICommunicationObject)tokenProvider).Open();

            var token = tokenProvider.GetToken(Timeout.InfiniteTimeSpan);
            return token;

Expected behavior a GenericXmlSecurityToken with SamlAssertionKeyIdentifierClause should be expected. The AssertionId/DerivationLength field is missing from GenericXmlSecurityKeyIdentifierClause type. GenericXmlSecurityKeyIdentifierClause has Id field but it´s null.

** Additional Information ** The token works when using it to connect to ADFS to get another token, and using it for other WCF Services. But we cannot Serialize/Deserialize like before without AsserationId

** Serialize **

            {

                XmlRepresentation = genericToken.TokenXml,
                ValidFrom = genericToken.ValidFrom,
                ValidTo = genericToken.ValidTo,
                ProofToken = ((BinarySecretSecurityToken) genericToken.ProofToken)?.GetKeyBytes(),
#if NETFRAMEWORK
                AssertionId = ((System.IdentityModel.Tokens.SamlAssertionKeyIdentifierClause)genericToken.InternalTokenReference).AssertionId,
                AssertionDerivationKeyLength = ((System.IdentityModel.Tokens.SamlAssertionKeyIdentifierClause)genericToken.InternalTokenReference).DerivationLength
#else
                AssertionId = ((SamlAssertionKeyIdentifierClause)genericToken.InternalTokenReference).Id,
                AssertionDerivationKeyLength = ((SamlAssertionKeyIdentifierClause)genericToken.InternalTokenReference).DerivationLength
#endif
            };
        }

The above serialize example throws an exception: System.InvalidCastException: Unable to cast object of type 'System.IdentityModel.Tokens.GenericXmlSecurityKeyIdentifierClause' to type 'System.IdentityModel.Tokens.SamlAssertionKeyIdentifierClause'.

Deserialize

 var assertionKeyIdentifier = new SamlAssertionKeyIdentifierClause(tokenSerialized.AssertionId);
            var genericToken = new GenericXmlSecurityToken(
                tokenSerialized.XmlRepresentation,
                tokenSerialized.ProofToken != null ? new BinarySecretSecurityToken(tokenSerialized.ProofToken) : null,
                tokenSerialized.ValidFrom,
                tokenSerialized.ValidTo,
                assertionKeyIdentifier,
                assertionKeyIdentifier,
                null);

eiki25 avatar Sep 19 '22 16:09 eiki25

Can you check GenericXmlSecurityToken.Id? I believe it should have the same Id as AssertionId. The code here looks like it should pick up the AssertionId from the token and on .NET Framework SamlSecurityToken.Id just returns the SamlAssertion.AssertionId.
If that doesn't work, I think you can get the raw XML out but getting GenericXmlSecurityToken.TokenXml. You can then either use something like XPath or Linq2XML or something similar to extract the assertion Id, or you could reference the Microsoft.IdentityModel.Protocols.WsTrust package which we use and use one of the serializers there to deserialize the token into their object model. Code for that would look something like this:

var samlTokenHandler = new Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler();
Microsoft.IdentityModel.Tokens.Saml.SamlSecurityToken samlSecurityToken = samlTokenHandler.ReadSamlToken(GenericXmlSecurityToken.TokenXml.OuterXml);
var samlAssertion = samlSecurityToken.Assertion;
var assertionId = samlAssertion.AssertionId;

mconnew avatar Sep 28 '22 22:09 mconnew

Close this since no response. Feel free to re-activate if this is still an issue.

HongGit avatar Jun 21 '23 21:06 HongGit