File: System\ServiceModel\Federation\WSTrustUtilities.cs
Web Access
Project: src\src\System.ServiceModel.Federation\src\System.ServiceModel.Federation.csproj (System.ServiceModel.Federation)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System.Diagnostics.Tracing;
using System.IdentityModel.Tokens;
using System.ServiceModel.Security;
using System.ServiceModel.Security.Tokens;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.WsSecurity;
using Microsoft.IdentityModel.Protocols.WsTrust;
using WCFSecurityToken = System.IdentityModel.Tokens.SecurityToken;
 
namespace System.ServiceModel.Federation
{
    /// <summary>
    /// WSTrustUtilities are shared between <see cref="WSTrustChannelSecurityTokenProvider"/> with <see cref="WSFederationHttpBinding"/>
    /// and <see cref="WSTrustChannelFactory"/> and <see cref="WSTrustChannel"/> to send a WsTrust message to obtain a SecurityToken from a STS.
    /// The SecurityToken can be added as an IssuedToken on the outbound WCF message OR be used to call a WebAPI.
    /// </summary>
    internal static class WSTrustUtilities
    {
        /// <summary>
        /// Get a proof token from a WsTrust request/response pair based on section 4.4.3 of the WS-Trust 1.3 spec.
        /// How the proof token is retrieved depends on whether the requester or issuer provide key material:
        /// Requester   |   Issuer                  | Results
        /// -------------------------------------------------
        /// Entropy     | No key material           | No proof token returned, requester entropy used
        /// Entropy     | Entropy                   | Computed key algorithm returned and key computed based on request and response entropy
        /// Entropy     | Rejects requester entropy | Proof token in response used as key
        /// No entropy  | Issues key                | Proof token in response used as key
        /// No entropy  | No key material           | No proof token
        /// </summary>
        /// <param name="request">The WS-Trust request (RST).</param>
        /// <param name="response">The WS-Trust response (RSTR).</param>
        /// <param name="serializationContext">The serialization context.</param>
        /// <param name="algorithmSuite">The algorithm suite.</param>
        /// <returns>The proof token or null if there is no proof token.</returns>
        internal static BinarySecretSecurityToken GetProofToken(WsTrustRequest request, Microsoft.IdentityModel.Protocols.WsTrust.RequestSecurityTokenResponse response, WsSerializationContext serializationContext, SecurityAlgorithmSuite algorithmSuite)
        {
            // According to the WS-Trust 1.3 spec, symmetric is the default key type
            string keyType = response.KeyType ?? request.KeyType ?? serializationContext.TrustKeyTypes.Symmetric;
 
            // Encrypted keys and encrypted entropy are not supported, currently, as they should
            // only be needed by unsupported message security scenarios.
            if (response.RequestedProofToken?.EncryptedKey != null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelper(new NotSupportedException(SR.GetResourceString(SR.EncryptedKeysForProofTokensNotSupported)), EventLevel.Error);
 
            // Bearer scenarios have no proof token
            if (string.Equals(keyType, serializationContext.TrustKeyTypes.Bearer, StringComparison.Ordinal))
            {
                if (response.RequestedProofToken != null || response.Entropy != null)
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelper(new InvalidOperationException(SR.GetResourceString(SR.BearerKeyShouldNotIincludeAProofToken)), EventLevel.Error);
 
                return null;
            }
 
            // If the response includes a proof token, use it as the security token's proof.
            // This scenario will occur if the request does not include entropy or if the issuer rejects the requester's entropy.
            if (response.RequestedProofToken?.BinarySecret != null)
            {
                // Confirm that a computed key algorithm isn't also specified
                if (!string.IsNullOrEmpty(response.RequestedProofToken.ComputedKeyAlgorithm) || response.Entropy != null)
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelper(new InvalidOperationException(SR.GetResourceString(SR.RSTRProofTokenShouldNotHaveAComputedKeyAlgorithmOrIssuerEntropy)), EventLevel.Error);
 
                return new BinarySecretSecurityToken(response.RequestedProofToken.BinarySecret.Data);
            }
            // If the response includes a computed key algorithm, compute the proof token based on requester and issuer entropy.
            // This scenario will occur if the requester and issuer both provide key material.
            else if (response.RequestedProofToken?.ComputedKeyAlgorithm != null)
            {
                if (!string.Equals(keyType, serializationContext.TrustKeyTypes.Symmetric, StringComparison.Ordinal))
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelper(new InvalidOperationException(SR.GetResourceString(SR.ComputedKeyProofTokensAreOnlySupportedWithSymmetricKeyTypes)), EventLevel.Error);
 
                if (string.Equals(response.RequestedProofToken.ComputedKeyAlgorithm, serializationContext.TrustKeyTypes.PSHA1, StringComparison.Ordinal))
                {
                    // Confirm that no encrypted entropy was provided as that is currently not supported.
                    // If we wish to support it in the future, most of the work will be in the WSTrust serializer;
                    // this code would just have to use protected key's .Secret property to get the key material.
                    if (response.Entropy?.ProtectedKey != null || request.Entropy?.ProtectedKey != null)
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelper( new NotSupportedException(SR.GetResourceString(SR.ProtectedKeyEntropyIsNotSupported)), EventLevel.Error);
 
                    // Get issuer and requester entropy
                    byte[] issuerEntropy = response.Entropy?.BinarySecret?.Data;
                    if (issuerEntropy == null)
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelper(new InvalidOperationException(SR.GetResourceString(SR.ComputedKeyProofTokensRequireIssuerToSupplyKeyMaterialViaEntropy)), EventLevel.Error);
 
                    byte[] requestorEntropy = request.Entropy?.BinarySecret?.Data;
                    if (requestorEntropy == null)
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelper(new InvalidOperationException(SR.GetResourceString(SR.ComputedKeyProofTokensRequireRequesterToSupplyKeyMaterialViaEntropy)), EventLevel.Error);
 
                    // Get key size
                    int keySizeInBits = response.KeySizeInBits ?? 0; // RSTR key size has precedence
                    if (keySizeInBits == 0)
                        keySizeInBits = request.KeySizeInBits ?? 0; // Followed by RST
 
                    if (keySizeInBits == 0)
                        keySizeInBits = algorithmSuite?.DefaultSymmetricKeyLength ?? 0; // Symmetric keys should default to a length corresponding to the algorithm in use
 
                    if (keySizeInBits == 0)
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelper(new InvalidOperationException(SR.GetResourceString(SR.NoKeySizeProvided)), EventLevel.Error);
 
                    return new BinarySecretSecurityToken(Psha1KeyGenerator.ComputeCombinedKey(issuerEntropy, requestorEntropy, keySizeInBits));
                }
                else
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelper(new NotSupportedException(SR.GetResourceString(SR.OnlyPSHA1ComputedKeysAreSupported)), EventLevel.Error);
                }
            }
            // If the response does not have a proof token or computed key value, but the request proposed entropy,
            // then the requester's entropy is used as the proof token.
            else if (request.Entropy != null)
            {
                if (request.Entropy.ProtectedKey != null)
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelper(new NotSupportedException(SR.GetResourceString(SR.ProtectedKeyEntropyIsNotSupported)), EventLevel.Error);
 
                if (request.Entropy.BinarySecret != null)
                    return new BinarySecretSecurityToken(request.Entropy.BinarySecret.Data);
            }
 
            // If we get here, then no key material has been supplied (by either issuer or requester), so there is no proof token.
            return null;
        }
 
        internal static GenericXmlSecurityKeyIdentifierClause GetSecurityKeyIdentifierForTokenReference(SecurityTokenReference securityTokenReference)
        {
            if (securityTokenReference == null)
                return null;
 
            return new GenericXmlSecurityKeyIdentifierClause(WsSecuritySerializer.CreateXmlElement(securityTokenReference));
        }
 
        internal static WCFSecurityToken CreateGenericXmlSecurityToken(WsTrustRequest request, WsTrustResponse trustResponse, WsSerializationContext serializationContext, SecurityAlgorithmSuite algorithmSuite)
        {
            // Create GenericXmlSecurityToken
            // Assumes that token is first and Saml2SecurityToken.
            Microsoft.IdentityModel.Protocols.WsTrust.RequestSecurityTokenResponse response = trustResponse.RequestSecurityTokenResponseCollection[0];
 
            // Get attached and unattached references
            GenericXmlSecurityKeyIdentifierClause internalSecurityKeyIdentifierClause = null;
            if (response.AttachedReference != null)
                internalSecurityKeyIdentifierClause = GetSecurityKeyIdentifierForTokenReference(response.AttachedReference);
 
            GenericXmlSecurityKeyIdentifierClause externalSecurityKeyIdentifierClause = null;
            if (response.UnattachedReference != null)
                externalSecurityKeyIdentifierClause = GetSecurityKeyIdentifierForTokenReference(response.UnattachedReference);
 
            // Get proof token
            WCFSecurityToken proofToken = GetProofToken(request, response, serializationContext, algorithmSuite);
 
            // Get lifetime
            DateTime created = response.Lifetime?.Created ?? DateTime.UtcNow;
            DateTime expires = response.Lifetime?.Expires ?? created.AddDays(1);
 
            return new GenericXmlSecurityToken(response.RequestedSecurityToken.TokenElement,
                                               proofToken,
                                               created,
                                               expires,
                                               internalSecurityKeyIdentifierClause,
                                               externalSecurityKeyIdentifierClause,
                                               null);
        }
    }
}