//------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ namespace System.IdentityModel.Tokens { using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.IdentityModel.Configuration; using System.IdentityModel.Diagnostics; using System.IdentityModel.Protocols.WSTrust; using System.IdentityModel.Selectors; using System.IO; using System.Linq; using System.Runtime; using System.Security.Claims; using System.Security.Cryptography; using System.Security.Principal; using System.Text; using System.Xml; using System.Xml.Schema; using Claim = System.Security.Claims.Claim; using ClaimTypes = System.Security.Claims.ClaimTypes; /// /// This class implements a SecurityTokenHandler for a Saml11 token. It contains functionality for: Creating, Serializing and Validating /// a Saml 11 Token. /// public class SamlSecurityTokenHandler : SecurityTokenHandler { #pragma warning disable 1591 public const string Namespace = "urn:oasis:names:tc:SAML:1.0"; public const string BearerConfirmationMethod = Namespace + ":cm:bearer"; public const string UnspecifiedAuthenticationMethod = Namespace + ":am:unspecified"; public const string Assertion = Namespace + ":assertion"; #pragma warning restore 1591 const string Attribute = "saml:Attribute"; const string Actor = "Actor"; const string ClaimType2009Namespace = "http://schemas.xmlsoap.org/ws/2009/09/identity/claims"; // Below are WCF DateTime values for Min and Max. SamlConditions when new'ed up will // have these values as default. To maintin compatability with WCF behavior we will // not write out SamlConditions NotBefore and NotOnOrAfter times which match the below // values. static DateTime WCFMinValue = new DateTime(DateTime.MinValue.Ticks + TimeSpan.TicksPerDay, DateTimeKind.Utc); static DateTime WCFMaxValue = new DateTime(DateTime.MaxValue.Ticks - TimeSpan.TicksPerDay, DateTimeKind.Utc); static string[] _tokenTypeIdentifiers = new string[] { SecurityTokenTypes.SamlTokenProfile11, SecurityTokenTypes.OasisWssSamlTokenProfile11 }; SamlSecurityTokenRequirement _samlSecurityTokenRequirement; SecurityTokenSerializer _keyInfoSerializer; object _syncObject = new object(); /// /// Initializes an instance of /// public SamlSecurityTokenHandler() : this(new SamlSecurityTokenRequirement()) { } /// /// Initializes an instance of /// /// The SamlSecurityTokenRequirement to be used by the Saml11SecurityTokenHandler instance when validating tokens. public SamlSecurityTokenHandler(SamlSecurityTokenRequirement samlSecurityTokenRequirement) { if (samlSecurityTokenRequirement == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("samlSecurityTokenRequirement"); } _samlSecurityTokenRequirement = samlSecurityTokenRequirement; } /// /// Load custom configuration from Xml /// /// Custom configuration that describes SamlSecurityTokenRequirement. /// Input parameter 'customConfigElements' is null. /// Custom configuration specified was invalid. public override void LoadCustomConfiguration(XmlNodeList customConfigElements) { if (customConfigElements == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("customConfigElements"); } List configNodes = XmlUtil.GetXmlElements(customConfigElements); bool foundValidConfig = false; foreach (XmlElement configElement in configNodes) { if (configElement.LocalName != ConfigurationStrings.SamlSecurityTokenRequirement) { continue; } if (foundValidConfig) { throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID7026, ConfigurationStrings.SamlSecurityTokenRequirement)); } _samlSecurityTokenRequirement = new SamlSecurityTokenRequirement(configElement); foundValidConfig = true; } if (!foundValidConfig) { _samlSecurityTokenRequirement = new SamlSecurityTokenRequirement(); } } #region TokenCreation /// /// Creates the security token based on the tokenDescriptor passed in. /// /// The security token descriptor that contains the information to build a token. /// Thrown if 'tokenDescriptor' is null. public override SecurityToken CreateToken(SecurityTokenDescriptor tokenDescriptor) { if (tokenDescriptor == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("tokenDescriptor"); } IEnumerable statements = CreateStatements(tokenDescriptor); // - NotBefore / NotAfter // - Audience Restriction SamlConditions conditions = CreateConditions(tokenDescriptor.Lifetime, tokenDescriptor.AppliesToAddress, tokenDescriptor); SamlAdvice advice = CreateAdvice(tokenDescriptor); string issuerName = tokenDescriptor.TokenIssuerName; SamlAssertion assertion = CreateAssertion(issuerName, conditions, advice, statements); if (assertion == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID4013))); } assertion.SigningCredentials = GetSigningCredentials(tokenDescriptor); SecurityToken token = new SamlSecurityToken(assertion); // // Encrypt the token if encrypting credentials are set // EncryptingCredentials encryptingCredentials = GetEncryptingCredentials(tokenDescriptor); if (encryptingCredentials != null) { token = new EncryptedSecurityToken(token, encryptingCredentials); } return token; } /// /// Gets the credentials for encrypting the token. Override this method to provide custom encrypting credentials. /// /// The Scope property provides access to the encrypting credentials. /// The token encrypting credentials. /// Thrown when 'tokenDescriptor' is null. /// The default behavior is to return the SecurityTokenDescriptor.Scope.EncryptingCredentials /// If this key is ----ymmetric, a symmetric key will be generated and wrapped with the asymmetric key. protected virtual EncryptingCredentials GetEncryptingCredentials(SecurityTokenDescriptor tokenDescriptor) { if (null == tokenDescriptor) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("tokenDescriptor"); } EncryptingCredentials encryptingCredentials = null; if (null != tokenDescriptor.EncryptingCredentials) { encryptingCredentials = tokenDescriptor.EncryptingCredentials; if (encryptingCredentials.SecurityKey is AsymmetricSecurityKey) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new SecurityTokenException(SR.GetString(SR.ID4178))); } } return encryptingCredentials; } /// /// Gets the credentials for the signing the assertion. Override this method to provide custom signing credentials. /// /// The Scope property provides access to the signing credentials. /// Thrown when 'tokenDescriptor' is null. /// The assertion signing credentials. /// The default behavior is to return the SecurityTokenDescriptor.Scope.SigningCredentials. protected virtual SigningCredentials GetSigningCredentials(SecurityTokenDescriptor tokenDescriptor) { if (null == tokenDescriptor) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("tokenDescriptor"); } return tokenDescriptor.SigningCredentials; } /// /// Override this method to provide a SamlAdvice to place in the Samltoken. /// /// Contains informaiton about the token. /// SamlAdvice, default is null. protected virtual SamlAdvice CreateAdvice(SecurityTokenDescriptor tokenDescriptor) { return null; } /// /// Override this method to customize the parameters to create a SamlAssertion. /// /// The Issuer of the Assertion. /// The SamlConditions to add. /// The SamlAdvice to add. /// The SamlStatements to add. /// A SamlAssertion. /// A unique random id is created for the assertion /// IssueInstance is set to DateTime.UtcNow. protected virtual SamlAssertion CreateAssertion(string issuer, SamlConditions conditions, SamlAdvice advice, IEnumerable statements) { return new SamlAssertion(System.IdentityModel.UniqueId.CreateRandomId(), issuer, DateTime.UtcNow, conditions, advice, statements); } /// /// Creates the security token reference when the token is not attached to the message. /// /// The saml token. /// Boolean that indicates if a attached or unattached /// reference needs to be created. /// A SamlAssertionKeyIdentifierClause. public override SecurityKeyIdentifierClause CreateSecurityTokenReference(SecurityToken token, bool attached) { if (token == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("token"); } return token.CreateKeyIdentifierClause(); } /// /// Generates all the conditions for saml /// /// 1. Lifetime condition /// 2. AudienceRestriction condition /// /// /// Lifetime of the Token. /// The endpoint address to who the token is created. The address /// is modelled as an AudienceRestriction condition. /// Contains all the other information that is used in token issuance. /// SamlConditions protected virtual SamlConditions CreateConditions(Lifetime tokenLifetime, string relyingPartyAddress, SecurityTokenDescriptor tokenDescriptor) { SamlConditions conditions = new SamlConditions(); if (tokenLifetime != null) { if (tokenLifetime.Created != null) { conditions.NotBefore = tokenLifetime.Created.Value; } if (tokenLifetime.Expires != null) { conditions.NotOnOrAfter = tokenLifetime.Expires.Value; } } if (!string.IsNullOrEmpty(relyingPartyAddress)) { conditions.Conditions.Add(new SamlAudienceRestrictionCondition(new Uri[] { new Uri(relyingPartyAddress) })); } return conditions; } /// /// Generates an enumeration of SamlStatements from a SecurityTokenDescriptor. /// Only SamlAttributeStatements and SamlAuthenticationStatements are generated. /// Overwrite this method to customize the creation of statements. /// /// Calls in order (all are virtual): /// 1. CreateSamlSubject /// 2. CreateAttributeStatements /// 3. CreateAuthenticationStatements /// /// /// The SecurityTokenDescriptor to use to build the statements. /// An enumeration of SamlStatement. protected virtual IEnumerable CreateStatements(SecurityTokenDescriptor tokenDescriptor) { Collection statements = new Collection(); SamlSubject subject = CreateSamlSubject(tokenDescriptor); SamlAttributeStatement attributeStatement = CreateAttributeStatement(subject, tokenDescriptor.Subject, tokenDescriptor); if (attributeStatement != null) { statements.Add(attributeStatement); } SamlAuthenticationStatement authnStatement = CreateAuthenticationStatement(subject, tokenDescriptor.AuthenticationInfo, tokenDescriptor); if (authnStatement != null) { statements.Add(authnStatement); } return statements; } /// /// Creates a SamlAuthenticationStatement for each AuthenticationInformation found in AuthenticationInformation. /// Override this method to provide a custom implementation. /// /// The SamlSubject of the Statement. /// AuthenticationInformation from which to generate the SAML Authentication statement. /// Contains all the other information that is used in token issuance. /// SamlAuthenticationStatement /// Thrown when 'samlSubject' or 'authInfo' is null. protected virtual SamlAuthenticationStatement CreateAuthenticationStatement( SamlSubject samlSubject, AuthenticationInformation authInfo, SecurityTokenDescriptor tokenDescriptor) { if (samlSubject == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("samlSubject"); } if (tokenDescriptor == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("tokenDescriptor"); } if (tokenDescriptor.Subject == null) { return null; } string authenticationMethod = null; string authenticationInstant = null; // Search for an Authentication Claim. IEnumerable claimCollection = (from c in tokenDescriptor.Subject.Claims where c.Type == ClaimTypes.AuthenticationMethod select c); if (claimCollection.Count() > 0) { // We support only one authentication statement and hence we just pick the first authentication type // claim found in the claim collection. Since the spec allows multiple Auth Statements // we do not throw an error. authenticationMethod = claimCollection.First().Value; } claimCollection = (from c in tokenDescriptor.Subject.Claims where c.Type == ClaimTypes.AuthenticationInstant select c); if (claimCollection.Count() > 0) { authenticationInstant = claimCollection.First().Value; } if (authenticationMethod == null && authenticationInstant == null) { return null; } else if (authenticationMethod == null) { throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4270, "AuthenticationMethod", "SAML11")); } else if (authenticationInstant == null) { throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4270, "AuthenticationInstant", "SAML11")); } DateTime authInstantTime = DateTime.ParseExact(authenticationInstant, DateTimeFormats.Accepted, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None).ToUniversalTime(); if (authInfo == null) { return new SamlAuthenticationStatement(samlSubject, DenormalizeAuthenticationType(authenticationMethod), authInstantTime, null, null, null); } else { return new SamlAuthenticationStatement(samlSubject, DenormalizeAuthenticationType(authenticationMethod), authInstantTime, authInfo.DnsName, authInfo.Address, null); } } /// /// Creates SamlAttributeStatements and adds them to a collection. /// Override this method to provide a custom implementation. /// /// Default behavior is to create a new SamlAttributeStatement for each Subject in the tokenDescriptor.Subjects collection. /// /// /// The SamlSubject to use in the SamlAttributeStatement that are created. /// The ClaimsIdentity that contains claims which will be converted to SAML Attributes. /// Contains all the other information that is used in token issuance. /// SamlAttributeStatement /// Thrown when 'samlSubject' is null. protected virtual SamlAttributeStatement CreateAttributeStatement( SamlSubject samlSubject, ClaimsIdentity subject, SecurityTokenDescriptor tokenDescriptor) { if (subject == null) { return null; } if (samlSubject == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("samlSubject"); } if (subject.Claims != null) { List attributes = new List(); foreach (Claim claim in subject.Claims) { if (claim != null && claim.Type != ClaimTypes.NameIdentifier) { // // NameIdentifier claim is already processed while creating the samlsubject // AuthenticationInstant and AuthenticationType are not converted to Claims // switch (claim.Type) { case ClaimTypes.AuthenticationInstant: case ClaimTypes.AuthenticationMethod: break; default: attributes.Add(CreateAttribute(claim, tokenDescriptor)); break; } } } AddDelegateToAttributes(subject, attributes, tokenDescriptor); ICollection collectedAttributes = CollectAttributeValues(attributes); if (collectedAttributes.Count > 0) { return new SamlAttributeStatement(samlSubject, collectedAttributes); } } return null; } /// /// Collects attributes with a common claim type, claim value type, and original issuer into a /// single attribute with multiple values. /// /// List of attributes generated from claims. /// List of attribute values with common attributes collected into value lists. protected virtual ICollection CollectAttributeValues(ICollection attributes) { Dictionary distinctAttributes = new Dictionary(attributes.Count, new SamlAttributeKeyComparer()); foreach (SamlAttribute attribute in attributes) { SamlAttribute SamlAttribute = attribute as SamlAttribute; if (SamlAttribute != null) { // Use unique attribute if name, value type, or issuer differ SamlAttributeKeyComparer.AttributeKey attributeKey = new SamlAttributeKeyComparer.AttributeKey(SamlAttribute); if (distinctAttributes.ContainsKey(attributeKey)) { foreach (string attributeValue in SamlAttribute.AttributeValues) { distinctAttributes[attributeKey].AttributeValues.Add(attributeValue); } } else { distinctAttributes.Add(attributeKey, SamlAttribute); } } } return distinctAttributes.Values; } /// /// Adds all the delegates associated with the ActAs subject into the attribute collection. /// /// The delegate of this ClaimsIdentity will be serialized into a SamlAttribute. /// Attribute collection to which the ActAs token will be serialized. /// Contains all the information that is used in token issuance. protected virtual void AddDelegateToAttributes( ClaimsIdentity subject, ICollection attributes, SecurityTokenDescriptor tokenDescriptor) { if (subject == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("subject"); } if (tokenDescriptor == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("tokenDescriptor"); } if (subject.Actor == null) { return; } List actingAsAttributes = new List(); foreach (Claim claim in subject.Actor.Claims) { if (claim != null) { actingAsAttributes.Add(CreateAttribute(claim, tokenDescriptor)); } } // perform depth first recursion AddDelegateToAttributes(subject.Actor, actingAsAttributes, tokenDescriptor); ICollection collectedAttributes = CollectAttributeValues(actingAsAttributes); attributes.Add(CreateAttribute(new Claim(ClaimTypes.Actor, CreateXmlStringFromAttributes(collectedAttributes), ClaimValueTypes.String), tokenDescriptor)); } /// /// Returns the SamlSubject to use for all the statements that will be created. /// Overwrite this method to customize the creation of the SamlSubject. /// /// Contains all the information that is used in token issuance. /// A SamlSubject created from the first subject found in the tokenDescriptor as follows: /// /// 1. Claim of Type NameIdentifier is searched. If found, SamlSubject.Name is set to claim.Value. /// 2. If a non-null tokenDescriptor.proof is found then SamlSubject.KeyIdentifier = tokenDescriptor.Proof.KeyIdentifier AND SamlSubject.ConfirmationMethod is set to 'HolderOfKey'. /// 3. If a null tokenDescriptor.proof is found then SamlSubject.ConfirmationMethod is set to 'BearerKey'. /// /// /// Thrown when 'tokenDescriptor' is null. protected virtual SamlSubject CreateSamlSubject(SecurityTokenDescriptor tokenDescriptor) { if (tokenDescriptor == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("tokenDescriptor"); } SamlSubject samlSubject = new SamlSubject(); Claim identityClaim = null; if (tokenDescriptor.Subject != null && tokenDescriptor.Subject.Claims != null) { foreach (Claim claim in tokenDescriptor.Subject.Claims) { if (claim.Type == ClaimTypes.NameIdentifier) { // Do not allow multiple name identifier claim. if (null != identityClaim) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new InvalidOperationException(SR.GetString(SR.ID4139))); } identityClaim = claim; } } } if (identityClaim != null) { samlSubject.Name = identityClaim.Value; if (identityClaim.Properties.ContainsKey(ClaimProperties.SamlNameIdentifierFormat)) { samlSubject.NameFormat = identityClaim.Properties[ClaimProperties.SamlNameIdentifierFormat]; } if (identityClaim.Properties.ContainsKey(ClaimProperties.SamlNameIdentifierNameQualifier)) { samlSubject.NameQualifier = identityClaim.Properties[ClaimProperties.SamlNameIdentifierNameQualifier]; } } if (tokenDescriptor.Proof != null) { // // Add the key and the Holder-Of-Key confirmation method // for both symmetric and asymmetric key case // samlSubject.KeyIdentifier = tokenDescriptor.Proof.KeyIdentifier; samlSubject.ConfirmationMethods.Add(SamlConstants.HolderOfKey); } else { // // This is a bearer token // samlSubject.ConfirmationMethods.Add(BearerConfirmationMethod); } return samlSubject; } /// /// Builds an XML formated string from a collection of saml attributes that represend the Actor. /// /// An enumeration of Saml Attributes. /// A well formed XML string. /// The string is of the form "<Actor><SamlAttribute name, ns><SamlAttributeValue>...</SamlAttributeValue>, ...</SamlAttribute>...</Actor>" protected virtual string CreateXmlStringFromAttributes(IEnumerable attributes) { bool actorElementWritten = false; using (MemoryStream ms = new MemoryStream()) { using (XmlDictionaryWriter dicWriter = XmlDictionaryWriter.CreateTextWriter(ms, Encoding.UTF8, false)) { foreach (SamlAttribute samlAttribute in attributes) { if (samlAttribute != null) { if (!actorElementWritten) { dicWriter.WriteStartElement(Actor); actorElementWritten = true; } WriteAttribute(dicWriter, samlAttribute); } } if (actorElementWritten) { dicWriter.WriteEndElement(); } dicWriter.Flush(); } return Encoding.UTF8.GetString(ms.ToArray()); } } /// /// Generates a SamlAttribute from a claim. /// /// Claim from which to generate a SamlAttribute. /// Contains all the information that is used in token issuance. /// The SamlAttribute. /// The parameter 'claim' is null. protected virtual SamlAttribute CreateAttribute(Claim claim, SecurityTokenDescriptor tokenDescriptor) { if (claim == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("claim"); } int lastSlashIndex = claim.Type.LastIndexOf('/'); string attributeNamespace = null; string attributeName = null; if ((lastSlashIndex == 0) || (lastSlashIndex == -1)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("claimType", SR.GetString(SR.ID4216, claim.Type)); } else if (lastSlashIndex == claim.Type.Length - 1) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("claimType", SR.GetString(SR.ID4216, claim.Type)); } else { attributeNamespace = claim.Type.Substring(0, lastSlashIndex); // // The WCF SamlAttribute requires that the attributeNamespace and attributeName are both non-null and non-empty. // Furthermore, on deserialization / construction it considers the claimType associated with the SamlAttribute to be attributeNamespace + "/" + attributeName. // // IDFX extends the WCF SamlAttribute and hence has to work with an attributeNamespace and attributeName that are both non-null and non-empty. // On serialization, we identify the last slash in the claimtype, and treat everything before the slash as the attributeNamespace and everything after the slash as the attributeName. // On deserialization, we don't always insert a "/" between the attributeNamespace and attributeName (like WCF does); we only do so if the attributeNamespace doesn't have a trailing slash. // // Send Receive Behavior // ============================= // WCF WCF Works as expected // // WCF IDFX In the common case (http://www.claimtypes.com/foo), WCF will not send a trailing slash in the attributeNamespace. IDFX will add one upon deserialization. // In the edge case (http://www.claimtypes.com//foo), WCF will send a trailing slash in the attributeNamespace. IDFX will not add one upon deserialization. // // IDFX WCF In the common case (http://www.claimtypes.com/foo), IDFX will not send a trailing slash. WCF will add one upon deserialization. // In the edge case (http://www.claimtypes.com//foo), IDFX will throw (which is what the fix for FIP 6301 is about). // // IDFX IDFX Works as expected // if (attributeNamespace.EndsWith("/", StringComparison.Ordinal)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("claim", SR.GetString(SR.ID4213, claim.Type)); } attributeName = claim.Type.Substring(lastSlashIndex + 1, claim.Type.Length - (lastSlashIndex + 1)); } SamlAttribute attribute = new SamlAttribute(attributeNamespace, attributeName, new string[] { claim.Value }); if (!StringComparer.Ordinal.Equals(ClaimsIdentity.DefaultIssuer, claim.OriginalIssuer)) { attribute.OriginalIssuer = claim.OriginalIssuer; } attribute.AttributeValueXsiType = claim.ValueType; return attribute; } #endregion #region TokenValidation /// /// Returns value indicates if this handler can validate tokens of type /// SamlSecurityToken. /// public override bool CanValidateToken { get { return true; } } /// /// Gets or sets the X509CeritificateValidator that is used by the current instance. /// public X509CertificateValidator CertificateValidator { get { if (_samlSecurityTokenRequirement.CertificateValidator == null) { if (Configuration != null) { return Configuration.CertificateValidator; } else { return null; } } else { return _samlSecurityTokenRequirement.CertificateValidator; } } set { _samlSecurityTokenRequirement.CertificateValidator = value; } } /// /// Throws if a token is detected as being replayed. If the token is not found it is added to the . /// /// The input argument 'token' is null. /// Configuration or Configuration.TokenReplayCache property is null. /// The input argument 'token' is not a SamlSecurityToken. /// SamlSecurityToken.Assertion.Id is null or empty. /// If the token is found in the . /// The default behavior is to only check tokens bearer tokens (tokens that do not have keys). protected override void DetectReplayedToken(SecurityToken token) { if (token == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("token"); } SamlSecurityToken samlToken = token as SamlSecurityToken; if (null == samlToken) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("token", SR.GetString(SR.ID1067, token.GetType().ToString())); } // // by default we only check bearer tokens. // if (samlToken.SecurityKeys.Count != 0) { return; } if (Configuration == null) { throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4274)); } if (Configuration.Caches.TokenReplayCache == null) { throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4278)); } if (string.IsNullOrEmpty(samlToken.Assertion.AssertionId)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenValidationException(SR.GetString(SR.ID1063))); } StringBuilder stringBuilder = new StringBuilder(); string key; using (HashAlgorithm hashAlgorithm = CryptoHelper.NewSha256HashAlgorithm()) { if (string.IsNullOrEmpty(samlToken.Assertion.Issuer)) { stringBuilder.AppendFormat("{0}{1}", samlToken.Assertion.AssertionId, _tokenTypeIdentifiers[0]); } else { stringBuilder.AppendFormat("{0}{1}{2}", samlToken.Assertion.AssertionId, samlToken.Assertion.Issuer, _tokenTypeIdentifiers[0]); } key = Convert.ToBase64String(hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes(stringBuilder.ToString()))); } if (Configuration.Caches.TokenReplayCache.Contains(key)) { if (string.IsNullOrEmpty(samlToken.Assertion.Issuer)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new SecurityTokenReplayDetectedException(SR.GetString(SR.ID1062, typeof(SamlSecurityToken).ToString(), samlToken.Assertion.AssertionId, ""))); } else { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new SecurityTokenReplayDetectedException(SR.GetString(SR.ID1062, typeof(SamlSecurityToken).ToString(), samlToken.Assertion.AssertionId, samlToken.Assertion.Issuer))); } } else { Configuration.Caches.TokenReplayCache.AddOrUpdate(key, token, DateTimeUtil.Add(GetTokenReplayCacheEntryExpirationTime(samlToken), Configuration.MaxClockSkew)); } } /// /// Returns the time until which the token should be held in the token replay cache. /// /// The token to return an expiration time for. /// The input argument 'token' is null. /// The SamlSecurityToken's validity period is greater than the expiration period set to TokenReplayCache. /// A DateTime representing the expiration time. protected virtual DateTime GetTokenReplayCacheEntryExpirationTime(SamlSecurityToken token) { if (token == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("token"); } // // DateTimeUtil handles overflows // DateTime maximumExpirationTime = DateTimeUtil.Add(DateTime.UtcNow, Configuration.TokenReplayCacheExpirationPeriod); // If the token validity period is greater than the TokenReplayCacheExpirationPeriod, throw if (DateTime.Compare(maximumExpirationTime, token.ValidTo) < 0) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new SecurityTokenValidationException(SR.GetString(SR.ID1069, token.ValidTo.ToString(), Configuration.TokenReplayCacheExpirationPeriod.ToString()))); } return token.ValidTo; } /// /// Rejects tokens that are not valid. /// /// /// The token may be invalid for a number of reasons. For example, the /// current time may not be within the token's validity period, the /// token may contain invalid or contradictory data, or the token /// may contain unsupported SAML elements. /// /// SAML condition to be validated. /// True to check for Audience Restriction condition. protected virtual void ValidateConditions(SamlConditions conditions, bool enforceAudienceRestriction) { if (null != conditions) { DateTime now = DateTime.UtcNow; if (null != conditions.NotBefore && DateTimeUtil.Add(now, Configuration.MaxClockSkew) < conditions.NotBefore) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new SecurityTokenNotYetValidException(SR.GetString(SR.ID4222, conditions.NotBefore, now))); } if (null != conditions.NotOnOrAfter && DateTimeUtil.Add(now, Configuration.MaxClockSkew.Negate()) >= conditions.NotOnOrAfter) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new SecurityTokenExpiredException(SR.GetString(SR.ID4223, conditions.NotOnOrAfter, now))); } } // // Enforce the audience restriction // if (enforceAudienceRestriction) { if (this.Configuration == null || this.Configuration.AudienceRestriction.AllowedAudienceUris.Count == 0) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID1032))); } // // Process each condition, enforcing the AudienceRestrictionConditions // bool foundAudienceRestriction = false; if (null != conditions && null != conditions.Conditions) { foreach (SamlCondition condition in conditions.Conditions) { SamlAudienceRestrictionCondition audienceRestriction = condition as SamlAudienceRestrictionCondition; if (null == audienceRestriction) { // Skip other conditions continue; } _samlSecurityTokenRequirement.ValidateAudienceRestriction(this.Configuration.AudienceRestriction.AllowedAudienceUris, audienceRestriction.Audiences); foundAudienceRestriction = true; } } if (!foundAudienceRestriction) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new AudienceUriValidationFailedException(SR.GetString(SR.ID1035))); } } } /// /// Validates a . /// /// The to validate. /// The of representing the identities contained in the token. /// The parameter 'token' is null. /// The token is not assignable from . /// Configuration is null. /// SamlSecurityToken.Assertion is null. /// Thrown if SamlSecurityToken.Assertion.SigningToken is null. /// Thrown if the certificate associated with the token issuer does not pass validation. public override ReadOnlyCollection ValidateToken(SecurityToken token) { if (token == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("token"); } SamlSecurityToken samlToken = token as SamlSecurityToken; if (samlToken == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("token", SR.GetString(SR.ID1033, token.GetType().ToString())); } if (this.Configuration == null) { throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4274)); } try { if (samlToken.Assertion == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("token", SR.GetString(SR.ID1034)); } TraceUtility.TraceEvent(TraceEventType.Verbose, TraceCode.Diagnostics, SR.GetString(SR.TraceValidateToken), new SecurityTraceRecordHelper.TokenTraceRecord(token), null, null); // Ensure token was signed and verified at some point if (samlToken.Assertion.SigningToken == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenValidationException(SR.GetString(SR.ID4220))); } this.ValidateConditions(samlToken.Assertion.Conditions, _samlSecurityTokenRequirement.ShouldEnforceAudienceRestriction(this.Configuration.AudienceRestriction.AudienceMode, samlToken)); // We need something like AudienceUriMode and have a setting on Configuration to allow extensibility and custom settings // By default we only check bearer tokens if (this.Configuration.DetectReplayedTokens) { this.DetectReplayedToken(samlToken); } // // If the backing token is x509, validate trust // X509SecurityToken x509IssuerToken = samlToken.Assertion.SigningToken as X509SecurityToken; if (x509IssuerToken != null) { try { CertificateValidator.Validate(x509IssuerToken.Certificate); } catch (SecurityTokenValidationException e) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenValidationException(SR.GetString(SR.ID4257, X509Util.GetCertificateId(x509IssuerToken.Certificate)), e)); } } // // Create the claims // ClaimsIdentity claimsIdentity = CreateClaims(samlToken); if (_samlSecurityTokenRequirement.MapToWindows) { // TFS: 153865, Microsoft WindowsIdentity does not set Authtype. I don't think that authtype should be set here anyway. // The authtype will be S4U (kerberos) it doesn't really matter that the upn arrived in a SAML token. WindowsIdentity windowsIdentity = CreateWindowsIdentity(FindUpn(claimsIdentity)); // PARTIAL TRUST: will fail when adding claims, AddClaims is SecurityCritical. windowsIdentity.AddClaims(claimsIdentity.Claims); claimsIdentity = windowsIdentity; } if (this.Configuration.SaveBootstrapContext) { claimsIdentity.BootstrapContext = new BootstrapContext(token, this); } this.TraceTokenValidationSuccess(token); List identities = new List(1); identities.Add(claimsIdentity); return identities.AsReadOnly(); } catch (Exception e) { if (Fx.IsFatal(e)) { throw; } this.TraceTokenValidationFailure(token, e.Message); throw e; } } /// /// Creates a object using the value. /// /// The upn name. /// A object. /// If is null or empty. protected virtual WindowsIdentity CreateWindowsIdentity(string upn) { if (string.IsNullOrEmpty(upn)) { throw DiagnosticUtility.ThrowHelperArgumentNullOrEmptyString("upn"); } WindowsIdentity wi = new WindowsIdentity(upn); return new WindowsIdentity(wi.Token, AuthenticationTypes.Federation, WindowsAccountType.Normal, true); } /// /// Finds the UPN claim value in the provided object for the purpose /// of mapping the identity to a object. /// /// The claims identity object containing the desired UPN claim. /// The UPN claim value found. /// If more than one UPN claim is contained in /// protected virtual string FindUpn(ClaimsIdentity claimsIdentity) { return ClaimsHelper.FindUpn(claimsIdentity); } /// /// Generates SubjectCollection that represents a SamlToken. /// Only SamlAttributeStatements processed. /// Overwrite this method to customize the creation of statements. /// /// Calls: /// 1. ProcessAttributeStatement for SamlAttributeStatements. /// 2. ProcessAuthenticationStatement for SamlAuthenticationStatements. /// 3. ProcessAuthorizationDecisionStatement for SamlAuthorizationDecisionStatements. /// 4. ProcessCustomStatement for other SamlStatements. /// /// /// The token used to generate the SubjectCollection. /// ClaimsIdentity representing the subject of the SamlToken. /// Thrown if 'samlSecurityToken' is null. protected virtual ClaimsIdentity CreateClaims(SamlSecurityToken samlSecurityToken) { if (samlSecurityToken == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("samlSecurityToken"); } if (samlSecurityToken.Assertion == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("samlSecurityToken", SR.GetString(SR.ID1034)); } // // Construct the subject and issuer identities. // Use claim types specified in the security token requirements used for IPrincipal.Role and IIdentity.Name // ClaimsIdentity subject = new ClaimsIdentity(AuthenticationTypes.Federation, _samlSecurityTokenRequirement.NameClaimType, _samlSecurityTokenRequirement.RoleClaimType); string issuer = null; if (this.Configuration == null) { throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4274)); } if (this.Configuration.IssuerNameRegistry == null) { throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4277)); } // SamlAssertion. The SigningToken may or may not be null. // The default IssuerNameRegistry will throw if null. // This callout is provided for extensibility scenarios with custom IssuerNameRegistry. issuer = this.Configuration.IssuerNameRegistry.GetIssuerName(samlSecurityToken.Assertion.SigningToken, samlSecurityToken.Assertion.Issuer); if (string.IsNullOrEmpty(issuer)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4175))); } ProcessStatement(samlSecurityToken.Assertion.Statements, subject, issuer); return subject; } /// /// Returns the Saml11 AuthenticationMethod matching a normalized value. /// /// Normalized value. /// protected virtual string DenormalizeAuthenticationType(string normalizedAuthenticationType) { return AuthenticationTypeMaps.Denormalize(normalizedAuthenticationType, AuthenticationTypeMaps.Saml); } /// /// Returns the normalized value matching a Saml11 AuthenticationMethod. /// /// /// Normalized value. protected virtual string NormalizeAuthenticationType(string saml11AuthenticationMethod) { return AuthenticationTypeMaps.Normalize(saml11AuthenticationMethod, AuthenticationTypeMaps.Saml); } /// /// Processes all statements to generate claims. /// /// A collection of Saml2Statement. /// The subject. /// The issuer. protected virtual void ProcessStatement(IList statements, ClaimsIdentity subject, string issuer) { if (statements == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("statements"); } Collection authStatementCollection = new Collection(); // // Validate that the Saml subjects in all the statements are the same. // ValidateStatements(statements); foreach (SamlStatement samlStatement in statements) { SamlAttributeStatement attrStatement = samlStatement as SamlAttributeStatement; if (attrStatement != null) { ProcessAttributeStatement(attrStatement, subject, issuer); } else { SamlAuthenticationStatement authenStatement = samlStatement as SamlAuthenticationStatement; if (authenStatement != null) { authStatementCollection.Add(authenStatement); } else { SamlAuthorizationDecisionStatement decisionStatement = samlStatement as SamlAuthorizationDecisionStatement; if (decisionStatement != null) { ProcessAuthorizationDecisionStatement(decisionStatement, subject, issuer); } else { // We don't process custom statements. Just fall through. } } } } // Processing Authentication statement(s) should be done at the last phase to add the authentication // information as claims to the ClaimsIdentity foreach (SamlAuthenticationStatement authStatement in authStatementCollection) { if (authStatement != null) { ProcessAuthenticationStatement(authStatement, subject, issuer); } } } /// /// Override this virtual to provide custom processing of SamlAttributeStatements. /// /// The SamlAttributeStatement to process. /// The identity that should be modified to reflect the statement. /// The subject that identifies the issuer. /// The input parameter 'samlStatement' or 'subject' is null. protected virtual void ProcessAttributeStatement(SamlAttributeStatement samlStatement, ClaimsIdentity subject, string issuer) { if (samlStatement == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("samlStatement"); } if (subject == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("subject"); } // We will be adding the nameid claim only once for multiple attribute and/or authn statements. // As of now, we put the nameId claim both inside the saml subject and the saml attribute statement as assertion. // When generating claims, we will only pick up the saml subject of a saml statement, not the attribute statement value. ProcessSamlSubject(samlStatement.SamlSubject, subject, issuer); foreach (SamlAttribute attr in samlStatement.Attributes) { string claimType = null; if (string.IsNullOrEmpty(attr.Namespace)) { claimType = attr.Name; } else if (StringComparer.Ordinal.Equals(attr.Name, SamlConstants.ElementNames.NameIdentifier)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.ID4094))); } else { // check if Namespace end with slash don't add it // If no slash or last char is not a slash, add it. int lastSlashIndex = attr.Namespace.LastIndexOf('/'); if ((lastSlashIndex == -1) || (!(lastSlashIndex == attr.Namespace.Length - 1))) { claimType = attr.Namespace + "/" + attr.Name; } else { claimType = attr.Namespace + attr.Name; } } if (claimType == ClaimTypes.Actor) { if (subject.Actor != null) { throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4034)); } SetDelegateFromAttribute(attr, subject, issuer); } else { for (int k = 0; k < attr.AttributeValues.Count; ++k) { // Check if we already have a nameId claim. if (StringComparer.Ordinal.Equals(ClaimTypes.NameIdentifier, claimType) && GetClaim(subject, ClaimTypes.NameIdentifier) != null) { continue; } string originalIssuer = issuer; SamlAttribute SamlAttribute = attr as SamlAttribute; if ((SamlAttribute != null) && (SamlAttribute.OriginalIssuer != null)) { originalIssuer = SamlAttribute.OriginalIssuer; } string claimValueType = ClaimValueTypes.String; if (SamlAttribute != null) { claimValueType = SamlAttribute.AttributeValueXsiType; } subject.AddClaim(new Claim(claimType, attr.AttributeValues[k], claimValueType, issuer, originalIssuer)); } } } } /// /// Gets a specific claim of type claimType from the subject's claims collection. /// /// The subject. /// The type of the claim. /// The claim of type claimType if present, else null. private static Claim GetClaim(ClaimsIdentity subject, string claimType) { foreach (Claim claim in subject.Claims) { if (StringComparer.Ordinal.Equals(claimType, claim.Type)) { return claim; } } return null; } /// /// For each saml statement (attribute/authentication/authz/custom), we will check if we need to create /// a nameid claim or a key identifier claim out of its SamlSubject. /// /// /// To make sure that the saml subject within each saml statement are the same, this method does the following comparisons. /// 1. All the saml subjects' contents are the same. /// 2. The name identifiers (if present) are the same. The name identifier comparison is done for the name identifier value, /// name identifier format (if present), and name identifier qualifier (if present). /// 3. The key identifiers (if present) are the same. /// /// The SamlSubject to extract claims from. /// The identity that should be modified to reflect the SamlSubject. /// The Issuer claims of the SAML token. /// The parameter 'samlSubject' is null. protected virtual void ProcessSamlSubject(SamlSubject samlSubject, ClaimsIdentity subject, string issuer) { if (samlSubject == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("samlSubject"); } Claim nameIdentifierClaim = GetClaim(subject, ClaimTypes.NameIdentifier); if (nameIdentifierClaim == null) { // first saml subject. so we will create claims for this subject. // subsequent subjects must have the same content. // add name identifier claim if present. if (!string.IsNullOrEmpty(samlSubject.Name)) { Claim claim = new Claim(ClaimTypes.NameIdentifier, samlSubject.Name, ClaimValueTypes.String, issuer); if (samlSubject.NameFormat != null) { claim.Properties[ClaimProperties.SamlNameIdentifierFormat] = samlSubject.NameFormat; } if (samlSubject.NameQualifier != null) { claim.Properties[ClaimProperties.SamlNameIdentifierNameQualifier] = samlSubject.NameQualifier; } subject.AddClaim(claim); } } } /// /// Override this virtual to provide custom processing of the SamlAuthenticationStatement. /// By default it adds authentication type and instant to each claim. /// /// The SamlAuthenticationStatement to process /// The identity that should be modified to reflect the statement /// issuer Identity. /// The parameter 'samlSubject' or 'subject' is null. protected virtual void ProcessAuthenticationStatement(SamlAuthenticationStatement samlStatement, ClaimsIdentity subject, string issuer) { if (samlStatement == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("samlStatement"); } if (subject == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("subject"); } // When there is only a authentication statement present inside a saml assertion, we need to generate // a nameId claim. See FIP 4848. We do not support any saml assertion without a attribute statement, but // we might receive a saml assertion with only a authentication statement. ProcessSamlSubject(samlStatement.SamlSubject, subject, issuer); subject.AddClaim(new Claim(ClaimTypes.AuthenticationMethod, NormalizeAuthenticationType(samlStatement.AuthenticationMethod), ClaimValueTypes.String, issuer)); subject.AddClaim(new Claim(ClaimTypes.AuthenticationInstant, XmlConvert.ToString(samlStatement.AuthenticationInstant.ToUniversalTime(), DateTimeFormats.Generated), ClaimValueTypes.DateTime, issuer)); } /// /// Override this virtual to provide custom processing of SamlAuthorizationDecisionStatement. /// By default no processing is performed, you will need to access the token for SamlAuthorizationDecisionStatement information. /// /// The SamlAuthorizationDecisionStatement to process. /// The identity that should be modified to reflect the statement. /// The subject that identifies the issuer. protected virtual void ProcessAuthorizationDecisionStatement(SamlAuthorizationDecisionStatement samlStatement, ClaimsIdentity subject, string issuer) { } /// /// This method gets called when a special type of SamlAttribute is detected. The SamlAttribute passed in wraps a SamlAttribute /// that contains a collection of AttributeValues, each of which are mapped to a claim. All of the claims will be returned /// in an ClaimsIdentity with the specified issuer. /// /// The SamlAttribute to be processed. /// The identity that should be modified to reflect the SamlAttribute. /// Issuer Identity. /// Will be thrown if the SamlAttribute does not contain any valid SamlAttributeValues. protected virtual void SetDelegateFromAttribute(SamlAttribute attribute, ClaimsIdentity subject, string issuer) { // bail here nothing to add. if (subject == null || attribute == null || attribute.AttributeValues == null || attribute.AttributeValues.Count < 1) { return; } Collection claims = new Collection(); SamlAttribute actingAsAttribute = null; foreach (string attributeValue in attribute.AttributeValues) { if (attributeValue != null && attributeValue.Length > 0) { using (XmlDictionaryReader xmlReader = XmlDictionaryReader.CreateTextReader(Encoding.UTF8.GetBytes(attributeValue), XmlDictionaryReaderQuotas.Max)) { xmlReader.MoveToContent(); xmlReader.ReadStartElement(Actor); while (xmlReader.IsStartElement(Attribute)) { SamlAttribute innerAttribute = ReadAttribute(xmlReader); if (innerAttribute != null) { string claimType = string.IsNullOrEmpty(innerAttribute.Namespace) ? innerAttribute.Name : innerAttribute.Namespace + "/" + innerAttribute.Name; if (claimType == ClaimTypes.Actor) { // In this case we have two delegates acting as an identity, we do not allow this if (actingAsAttribute != null) { throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4034)); } actingAsAttribute = innerAttribute; } else { string claimValueType = ClaimValueTypes.String; string originalIssuer = null; SamlAttribute SamlAttribute = innerAttribute as SamlAttribute; if (SamlAttribute != null) { claimValueType = SamlAttribute.AttributeValueXsiType; originalIssuer = SamlAttribute.OriginalIssuer; } for (int k = 0; k < innerAttribute.AttributeValues.Count; ++k) { Claim claim = null; if (string.IsNullOrEmpty(originalIssuer)) { claim = new Claim(claimType, innerAttribute.AttributeValues[k], claimValueType, issuer); } else { claim = new Claim(claimType, innerAttribute.AttributeValues[k], claimValueType, issuer, originalIssuer); } claims.Add(claim); } } } } xmlReader.ReadEndElement(); // Actor } } } subject.Actor = new ClaimsIdentity(claims, AuthenticationTypes.Federation); SetDelegateFromAttribute(actingAsAttribute, subject.Actor, issuer); } #endregion #region TokenSerialization /// /// Indicates whether the current XML element can be read as a token /// of the type handled by this instance. /// /// An XML reader positioned at a start /// element. The reader should not be advanced. /// 'True' if the ReadToken method can the element. public override bool CanReadToken(XmlReader reader) { if (reader == null) { return false; } return reader.IsStartElement(SamlConstants.ElementNames.Assertion, SamlConstants.Namespace); } /// /// Deserializes from XML a token of the type handled by this instance. /// /// An XML reader positioned at the token's start /// element. /// An instance of . /// Is thrown if 'Configuration' or 'Configruation.IssuerTokenResolver' is null. public override SecurityToken ReadToken(XmlReader reader) { if (Configuration == null) { throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4274)); } if (Configuration.IssuerTokenResolver == null) { throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4275)); } SamlAssertion assertion = ReadAssertion(reader); // // Resolve signing token if one is present. It may be deferred and signed by reference. // SecurityToken token; TryResolveIssuerToken(assertion, Configuration.IssuerTokenResolver, out token); assertion.SigningToken = token; return new SamlSecurityToken(assertion); } /// /// Read saml:Action element. /// /// XmlReader positioned at saml:Action element. /// SamlAction /// The parameter 'reader' is null. /// The saml:Action element contains unknown elements. protected virtual SamlAction ReadAction(XmlReader reader) { if (reader == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } if (reader.IsStartElement(SamlConstants.ElementNames.Action, SamlConstants.Namespace)) { // The Namespace attribute is optional. string ns = reader.GetAttribute(SamlConstants.AttributeNames.Namespace, null); reader.MoveToContent(); string action = reader.ReadString(); if (string.IsNullOrEmpty(action)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4073))); } reader.MoveToContent(); reader.ReadEndElement(); return new SamlAction(action, ns); } else { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4065, SamlConstants.ElementNames.Action, SamlConstants.Namespace, reader.LocalName, reader.NamespaceURI))); } } /// /// Writes the given SamlAction to the XmlWriter. /// /// XmlWriter to serialize the SamlAction into. /// SamlAction to serialize. /// The parameter 'writer' or 'action' is null. protected virtual void WriteAction(XmlWriter writer, SamlAction action) { if (writer == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (action == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("action"); } writer.WriteStartElement(SamlConstants.Prefix, SamlConstants.ElementNames.Action, SamlConstants.Namespace); if (!string.IsNullOrEmpty(action.Namespace)) { writer.WriteAttributeString(SamlConstants.AttributeNames.Namespace, null, action.Namespace); } writer.WriteString(action.Action); writer.WriteEndElement(); } /// /// Read saml:Advice element from the given XmlReader. /// /// XmlReader positioned at a SAML Advice element. /// SamlAdvice /// Parameter 'reader' is null. /// The reder is not positioned at a saml:Advice element. protected virtual SamlAdvice ReadAdvice(XmlReader reader) { if (reader == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } if (!reader.IsStartElement(SamlConstants.ElementNames.Advice, SamlConstants.Namespace)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4065, SamlConstants.ElementNames.Advice, SamlConstants.Namespace, reader.LocalName, reader.NamespaceURI))); } // SAML Advice is an optional element and all its child elements are optional // too. So we may have an empty saml:Advice element in the saml token. if (reader.IsEmptyElement) { // Just issue a read for the empty element. reader.MoveToContent(); reader.Read(); return new SamlAdvice(); } reader.MoveToContent(); reader.Read(); Collection assertionIdReferences = new Collection(); Collection assertions = new Collection(); while (reader.IsStartElement()) { if (reader.IsStartElement(SamlConstants.ElementNames.AssertionIdReference, SamlConstants.Namespace)) { assertionIdReferences.Add(reader.ReadString()); reader.ReadEndElement(); } else if (reader.IsStartElement(SamlConstants.ElementNames.Assertion, SamlConstants.Namespace)) { SamlAssertion assertion = ReadAssertion(reader); assertions.Add(assertion); } else { TraceUtility.TraceString(TraceEventType.Warning, SR.GetString(SR.ID8005, reader.LocalName, reader.NamespaceURI)); reader.Skip(); } } reader.MoveToContent(); reader.ReadEndElement(); return new SamlAdvice(assertionIdReferences, assertions); } /// /// Serialize the given SamlAdvice to the given XmlWriter. /// /// XmlWriter to serialize the SamlAdvice. /// SamlAdvice to be serialized. /// The input parameter 'writer' or 'advice' is null. protected virtual void WriteAdvice(XmlWriter writer, SamlAdvice advice) { if (writer == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (advice == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("advice"); } writer.WriteStartElement(SamlConstants.Prefix, SamlConstants.ElementNames.Advice, SamlConstants.Namespace); if (advice.AssertionIdReferences.Count > 0) { foreach (string assertionIdReference in advice.AssertionIdReferences) { if (string.IsNullOrEmpty(assertionIdReference)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4079))); } writer.WriteElementString(SamlConstants.Prefix, SamlConstants.ElementNames.AssertionIdReference, SamlConstants.Namespace, assertionIdReference); } } if (advice.Assertions.Count > 0) { foreach (SamlAssertion assertion in advice.Assertions) { WriteAssertion(writer, assertion); } } writer.WriteEndElement(); } /// /// Read saml:Assertion element from the given reader. /// /// XmlReader to deserialize the Assertion from. /// SamlAssertion /// The parameter 'reader' is null. /// The XmlReader is not positioned at a saml:Assertion element or the Assertion /// contains unknown child elements. protected virtual SamlAssertion ReadAssertion(XmlReader reader) { if (reader == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } if (this.Configuration == null) { throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4274)); } if (this.Configuration.IssuerTokenResolver == null) { throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4275)); } SamlAssertion assertion = new SamlAssertion(); EnvelopedSignatureReader wrappedReader = new EnvelopedSignatureReader(reader, new WrappedSerializer(this, assertion), this.Configuration.IssuerTokenResolver, false, true, false); if (!wrappedReader.IsStartElement(SamlConstants.ElementNames.Assertion, SamlConstants.Namespace)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4065, SamlConstants.ElementNames.Assertion, SamlConstants.Namespace, wrappedReader.LocalName, wrappedReader.NamespaceURI))); } string attributeValue = wrappedReader.GetAttribute(SamlConstants.AttributeNames.MajorVersion, null); if (string.IsNullOrEmpty(attributeValue)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4075, SamlConstants.AttributeNames.MajorVersion))); } int majorVersion = XmlConvert.ToInt32(attributeValue); attributeValue = wrappedReader.GetAttribute(SamlConstants.AttributeNames.MinorVersion, null); if (string.IsNullOrEmpty(attributeValue)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4075, SamlConstants.AttributeNames.MinorVersion))); } int minorVersion = XmlConvert.ToInt32(attributeValue); if ((majorVersion != SamlConstants.MajorVersionValue) || (minorVersion != SamlConstants.MinorVersionValue)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4076, majorVersion, minorVersion, SamlConstants.MajorVersionValue, SamlConstants.MinorVersionValue))); } attributeValue = wrappedReader.GetAttribute(SamlConstants.AttributeNames.AssertionId, null); if (string.IsNullOrEmpty(attributeValue)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4075, SamlConstants.AttributeNames.AssertionId))); } if (!XmlUtil.IsValidXmlIDValue(attributeValue)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4077, attributeValue))); } assertion.AssertionId = attributeValue; attributeValue = wrappedReader.GetAttribute(SamlConstants.AttributeNames.Issuer, null); if (string.IsNullOrEmpty(attributeValue)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4075, SamlConstants.AttributeNames.Issuer))); } assertion.Issuer = attributeValue; attributeValue = wrappedReader.GetAttribute(SamlConstants.AttributeNames.IssueInstant, null); if (!string.IsNullOrEmpty(attributeValue)) { assertion.IssueInstant = DateTime.ParseExact( attributeValue, DateTimeFormats.Accepted, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None).ToUniversalTime(); } wrappedReader.MoveToContent(); wrappedReader.Read(); if (wrappedReader.IsStartElement(SamlConstants.ElementNames.Conditions, SamlConstants.Namespace)) { assertion.Conditions = ReadConditions(wrappedReader); } if (wrappedReader.IsStartElement(SamlConstants.ElementNames.Advice, SamlConstants.Namespace)) { assertion.Advice = ReadAdvice(wrappedReader); } while (wrappedReader.IsStartElement()) { assertion.Statements.Add(ReadStatement(wrappedReader)); } if (assertion.Statements.Count == 0) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4078))); } wrappedReader.MoveToContent(); wrappedReader.ReadEndElement(); // Reading the end element will complete the signature; // capture the signing creds assertion.SigningCredentials = wrappedReader.SigningCredentials; // Save the captured on-the-wire data, which can then be used // to re-emit this assertion, preserving the same signature. assertion.CaptureSourceData(wrappedReader); return assertion; } /// /// Serializes a given SamlAssertion to the XmlWriter. /// /// XmlWriter to use for the serialization. /// Assertion to be serialized into the XmlWriter. /// The input parameter 'writer' or 'assertion' is null. protected virtual void WriteAssertion(XmlWriter writer, SamlAssertion assertion) { if (writer == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (assertion == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("assertion"); } SamlAssertion SamlAssertion = assertion as SamlAssertion; if (SamlAssertion != null) { if (SamlAssertion.CanWriteSourceData) { SamlAssertion.WriteSourceData(writer); return; } } if (assertion.SigningCredentials != null) { writer = new EnvelopedSignatureWriter(writer, assertion.SigningCredentials, assertion.AssertionId, new WrappedSerializer(this, assertion)); } writer.WriteStartElement(SamlConstants.Prefix, SamlConstants.ElementNames.Assertion, SamlConstants.Namespace); writer.WriteAttributeString(SamlConstants.AttributeNames.MajorVersion, null, Convert.ToString(SamlConstants.MajorVersionValue, CultureInfo.InvariantCulture)); writer.WriteAttributeString(SamlConstants.AttributeNames.MinorVersion, null, Convert.ToString(SamlConstants.MinorVersionValue, CultureInfo.InvariantCulture)); writer.WriteAttributeString(SamlConstants.AttributeNames.AssertionId, null, assertion.AssertionId); writer.WriteAttributeString(SamlConstants.AttributeNames.Issuer, null, assertion.Issuer); writer.WriteAttributeString(SamlConstants.AttributeNames.IssueInstant, null, assertion.IssueInstant.ToUniversalTime().ToString(DateTimeFormats.Generated, CultureInfo.InvariantCulture)); // Write out conditions if (assertion.Conditions != null) { WriteConditions(writer, assertion.Conditions); } // Write out advice if there is one if (assertion.Advice != null) { WriteAdvice(writer, assertion.Advice); } // Write statements. for (int i = 0; i < assertion.Statements.Count; i++) { WriteStatement(writer, assertion.Statements[i]); } writer.WriteEndElement(); } /// /// Read saml:Conditions from the given XmlReader. /// /// XmlReader to read the SAML conditions from. /// SamlConditions /// The parameter 'reader' is null. /// The reader is not positioned at saml:Conditions element or contains /// elements that are not recognized. protected virtual SamlConditions ReadConditions(XmlReader reader) { if (reader == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } SamlConditions conditions = new SamlConditions(); string time = reader.GetAttribute(SamlConstants.AttributeNames.NotBefore, null); if (!string.IsNullOrEmpty(time)) { conditions.NotBefore = DateTime.ParseExact( time, DateTimeFormats.Accepted, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None).ToUniversalTime(); } time = reader.GetAttribute(SamlConstants.AttributeNames.NotOnOrAfter, null); if (!string.IsNullOrEmpty(time)) { conditions.NotOnOrAfter = DateTime.ParseExact( time, DateTimeFormats.Accepted, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None).ToUniversalTime(); } // Saml Conditions element is an optional element and all its child element // are optional as well. So we can have a empty element // in a valid Saml token. if (reader.IsEmptyElement) { // Just issue a read to read the Empty element. reader.MoveToContent(); reader.Read(); return conditions; } reader.ReadStartElement(); while (reader.IsStartElement()) { conditions.Conditions.Add(ReadCondition(reader)); } reader.ReadEndElement(); return conditions; } /// /// Serialize SamlConditions to the given XmlWriter. /// /// XmlWriter to which the SamlConditions is serialized. /// SamlConditions to be serialized. /// The parameter 'writer' or 'conditions' is null. protected virtual void WriteConditions(XmlWriter writer, SamlConditions conditions) { if (writer == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (conditions == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("conditions"); } writer.WriteStartElement(SamlConstants.Prefix, SamlConstants.ElementNames.Conditions, SamlConstants.Namespace); // SamlConditions when new'ed up will have the min and max values defined in WCF // which is different than our defaults. To maintin compatability with WCF behavior we will // not write out SamlConditions NotBefore and NotOnOrAfter times which match the WCF // min and max default values as well. if (conditions.NotBefore != DateTimeUtil.GetMinValue(DateTimeKind.Utc) && conditions.NotBefore != WCFMinValue) { writer.WriteAttributeString( SamlConstants.AttributeNames.NotBefore, null, conditions.NotBefore.ToUniversalTime().ToString(DateTimeFormats.Generated, DateTimeFormatInfo.InvariantInfo)); } if (conditions.NotOnOrAfter != DateTimeUtil.GetMaxValue(DateTimeKind.Utc) && conditions.NotOnOrAfter != WCFMaxValue) { writer.WriteAttributeString( SamlConstants.AttributeNames.NotOnOrAfter, null, conditions.NotOnOrAfter.ToUniversalTime().ToString(DateTimeFormats.Generated, DateTimeFormatInfo.InvariantInfo)); } for (int i = 0; i < conditions.Conditions.Count; i++) { WriteCondition(writer, conditions.Conditions[i]); } writer.WriteEndElement(); } /// /// Read saml:AudienceRestrictionCondition or saml:DoNotCacheCondition from the given reader. /// /// XmlReader to read the SamlCondition from. /// SamlCondition /// The parameter 'reader' is null. /// XmlReader is positioned at an unknown element. protected virtual SamlCondition ReadCondition(XmlReader reader) { if (reader == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } if (reader.IsStartElement(SamlConstants.ElementNames.AudienceRestrictionCondition, SamlConstants.Namespace)) { return ReadAudienceRestrictionCondition(reader); } else if (reader.IsStartElement(SamlConstants.ElementNames.DoNotCacheCondition, SamlConstants.Namespace)) { return ReadDoNotCacheCondition(reader); } else { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4080, reader.LocalName, reader.NamespaceURI))); } } /// /// Serializes the given SamlCondition to the given XmlWriter. /// /// XmlWriter to serialize the condition. /// SamlConditon to be serialized. /// The parameter 'condition' is null. /// The given condition is unknown. By default only SamlAudienceRestrictionCondition /// and SamlDoNotCacheCondition are serialized. protected virtual void WriteCondition(XmlWriter writer, SamlCondition condition) { if (condition == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("condition"); } SamlAudienceRestrictionCondition audienceRestrictionCondition = condition as SamlAudienceRestrictionCondition; if (audienceRestrictionCondition != null) { WriteAudienceRestrictionCondition(writer, audienceRestrictionCondition); return; } SamlDoNotCacheCondition doNotCacheCondition = condition as SamlDoNotCacheCondition; if (doNotCacheCondition != null) { WriteDoNotCacheCondition(writer, doNotCacheCondition); return; } throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4081, condition.GetType()))); } /// /// Read saml:AudienceRestrictionCondition from the given XmlReader. /// /// XmlReader positioned at a saml:AudienceRestrictionCondition. /// SamlAudienceRestrictionCondition /// The inpur parameter 'reader' is null. /// The XmlReader is not positioned at saml:AudienceRestrictionCondition. protected virtual SamlAudienceRestrictionCondition ReadAudienceRestrictionCondition(XmlReader reader) { if (reader == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } if (!reader.IsStartElement(SamlConstants.ElementNames.AudienceRestrictionCondition, SamlConstants.Namespace)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4082, SamlConstants.ElementNames.AudienceRestrictionCondition, SamlConstants.Namespace, reader.LocalName, reader.NamespaceURI))); } reader.ReadStartElement(); SamlAudienceRestrictionCondition audienceRestrictionCondition = new SamlAudienceRestrictionCondition(); while (reader.IsStartElement()) { if (reader.IsStartElement(SamlConstants.ElementNames.Audience, SamlConstants.Namespace)) { string audience = reader.ReadString(); if (string.IsNullOrEmpty(audience)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4083))); } audienceRestrictionCondition.Audiences.Add(new Uri(audience, UriKind.RelativeOrAbsolute)); reader.MoveToContent(); reader.ReadEndElement(); } else { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4082, SamlConstants.ElementNames.Audience, SamlConstants.Namespace, reader.LocalName, reader.NamespaceURI))); } } if (audienceRestrictionCondition.Audiences.Count == 0) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4084))); } reader.MoveToContent(); reader.ReadEndElement(); return audienceRestrictionCondition; } /// /// Serialize SamlAudienceRestrictionCondition to a XmlWriter. /// /// XmlWriter to serialize the SamlAudienceRestrictionCondition. /// SamlAudienceRestrictionCondition to serialize. /// The parameter 'writer' or 'condition' is null. protected virtual void WriteAudienceRestrictionCondition(XmlWriter writer, SamlAudienceRestrictionCondition condition) { if (writer == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (condition == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("condition"); } // Schema requires at least one audience. if (condition.Audiences == null || condition.Audiences.Count == 0) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new InvalidOperationException(SR.GetString(SR.ID4269))); } writer.WriteStartElement(SamlConstants.Prefix, SamlConstants.ElementNames.AudienceRestrictionCondition, SamlConstants.Namespace); for (int i = 0; i < condition.Audiences.Count; i++) { // When writing out the audience uri we use the OriginalString property to preserve the value that was initially passed down during token creation as-is. writer.WriteElementString(SamlConstants.ElementNames.Audience, SamlConstants.Namespace, condition.Audiences[i].OriginalString); } writer.WriteEndElement(); } /// /// Read saml:DoNotCacheCondition from the given XmlReader. /// /// XmlReader positioned at a saml:DoNotCacheCondition element. /// SamlDoNotCacheCondition /// The inpur parameter 'reader' is null. /// The XmlReader is not positioned at saml:DoNotCacheCondition. protected virtual SamlDoNotCacheCondition ReadDoNotCacheCondition(XmlReader reader) { if (reader == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } if (!reader.IsStartElement(SamlConstants.ElementNames.DoNotCacheCondition, SamlConstants.Namespace)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4082, SamlConstants.ElementNames.DoNotCacheCondition, SamlConstants.Namespace, reader.LocalName, reader.NamespaceURI))); } SamlDoNotCacheCondition doNotCacheCondition = new SamlDoNotCacheCondition(); // saml:DoNotCacheCondition is a empty element. So just issue a read for // the empty element. if (reader.IsEmptyElement) { reader.MoveToContent(); reader.Read(); return doNotCacheCondition; } reader.MoveToContent(); reader.ReadStartElement(); reader.ReadEndElement(); return doNotCacheCondition; } /// /// Serialize SamlDoNotCacheCondition to a XmlWriter. /// /// XmlWriter to serialize the SamlDoNotCacheCondition. /// SamlDoNotCacheCondition to serialize. /// The parameter 'writer' or 'condition' is null. protected virtual void WriteDoNotCacheCondition(XmlWriter writer, SamlDoNotCacheCondition condition) { if (writer == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (condition == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("condition"); } writer.WriteStartElement(SamlConstants.Prefix, SamlConstants.ElementNames.DoNotCacheCondition, SamlConstants.Namespace); writer.WriteEndElement(); } /// /// Read a SamlStatement from the given XmlReader. /// /// XmlReader positioned at a SamlStatement. /// SamlStatement /// The inpur parameter 'reader' is null. /// The XmlReader is not positioned at recognized SamlStatement. By default, /// only saml:AuthenticationStatement, saml:AttributeStatement and saml:AuthorizationDecisionStatement. protected virtual SamlStatement ReadStatement(XmlReader reader) { if (reader == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } if (reader.IsStartElement(SamlConstants.ElementNames.AuthenticationStatement, SamlConstants.Namespace)) { return ReadAuthenticationStatement(reader); } else if (reader.IsStartElement(SamlConstants.ElementNames.AttributeStatement, SamlConstants.Namespace)) { return ReadAttributeStatement(reader); } else if (reader.IsStartElement(SamlConstants.ElementNames.AuthorizationDecisionStatement, SamlConstants.Namespace)) { return ReadAuthorizationDecisionStatement(reader); } else { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4085, reader.LocalName, reader.NamespaceURI))); } } /// /// Serialize the SamlStatement to the XmlWriter. /// /// XmlWriter to serialize the SamlStatement. /// The SamlStatement to serialize. /// The parameter 'writer' or 'statement' is null. /// The SamlStatement is not recognized. Only SamlAuthenticationStatement, /// SamlAuthorizationStatement and SamlAttributeStatement are recognized. protected virtual void WriteStatement(XmlWriter writer, SamlStatement statement) { if (writer == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (statement == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("statement"); } SamlAuthenticationStatement authnStatement = statement as SamlAuthenticationStatement; if (authnStatement != null) { WriteAuthenticationStatement(writer, authnStatement); return; } SamlAuthorizationDecisionStatement authzStatement = statement as SamlAuthorizationDecisionStatement; if (authzStatement != null) { WriteAuthorizationDecisionStatement(writer, authzStatement); return; } SamlAttributeStatement attributeStatement = statement as SamlAttributeStatement; if (attributeStatement != null) { WriteAttributeStatement(writer, attributeStatement); return; } throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4086, statement.GetType()))); } /// /// Read the SamlSubject from the XmlReader. /// /// XmlReader to read the SamlSubject from. /// SamlSubject /// The input argument 'reader' is null. /// The reader is not positioned at a SamlSubject. protected virtual SamlSubject ReadSubject(XmlReader reader) { if (reader == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } if (!reader.IsStartElement(SamlConstants.ElementNames.Subject, SamlConstants.Namespace)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4082, SamlConstants.ElementNames.Subject, SamlConstants.Namespace, reader.LocalName, reader.NamespaceURI))); } SamlSubject subject = new SamlSubject(); reader.ReadStartElement(SamlConstants.ElementNames.Subject, SamlConstants.Namespace); if (reader.IsStartElement(SamlConstants.ElementNames.NameIdentifier, SamlConstants.Namespace)) { subject.NameFormat = reader.GetAttribute(SamlConstants.AttributeNames.NameIdentifierFormat, null); subject.NameQualifier = reader.GetAttribute(SamlConstants.AttributeNames.NameIdentifierNameQualifier, null); reader.MoveToContent(); subject.Name = reader.ReadElementString(); if (string.IsNullOrEmpty(subject.Name)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4087))); } } if (reader.IsStartElement(SamlConstants.ElementNames.SubjectConfirmation, SamlConstants.Namespace)) { reader.ReadStartElement(); while (reader.IsStartElement(SamlConstants.ElementNames.SubjectConfirmationMethod, SamlConstants.Namespace)) { string method = reader.ReadElementString(); if (string.IsNullOrEmpty(method)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4088))); } subject.ConfirmationMethods.Add(method); } if (subject.ConfirmationMethods.Count == 0) { // A SubjectConfirmaton clause should specify at least one // ConfirmationMethod. throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4088))); } if (reader.IsStartElement(SamlConstants.ElementNames.SubjectConfirmationData, SamlConstants.Namespace)) { // An Authentication protocol specified in the confirmation method might need this // data. Just store this content value as string. subject.SubjectConfirmationData = reader.ReadElementString(); } if (reader.IsStartElement(XmlSignatureConstants.Elements.KeyInfo, XmlSignatureConstants.Namespace)) { subject.KeyIdentifier = ReadSubjectKeyInfo(reader); SecurityKey key = ResolveSubjectKeyIdentifier(subject.KeyIdentifier); if (key != null) { subject.Crypto = key; } else { subject.Crypto = new SecurityKeyElement(subject.KeyIdentifier, this.Configuration.ServiceTokenResolver); } } if ((subject.ConfirmationMethods.Count == 0) && (string.IsNullOrEmpty(subject.Name))) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4089))); } reader.MoveToContent(); reader.ReadEndElement(); } reader.MoveToContent(); reader.ReadEndElement(); return subject; } /// /// Serialize the given SamlSubject into an XmlWriter. /// /// XmlWriter into which the SamlSubject is serialized. /// SamlSubject to be serialized. /// The input parameter 'subject' or 'writer' is null. protected virtual void WriteSubject(XmlWriter writer, SamlSubject subject) { if (writer == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (subject == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("subject"); } writer.WriteStartElement(SamlConstants.Prefix, SamlConstants.ElementNames.Subject, SamlConstants.Namespace); if (!string.IsNullOrEmpty(subject.Name)) { writer.WriteStartElement(SamlConstants.Prefix, SamlConstants.ElementNames.NameIdentifier, SamlConstants.Namespace); if (!string.IsNullOrEmpty(subject.NameFormat)) { writer.WriteAttributeString(SamlConstants.AttributeNames.NameIdentifierFormat, null, subject.NameFormat); } if (subject.NameQualifier != null) { writer.WriteAttributeString(SamlConstants.AttributeNames.NameIdentifierNameQualifier, null, subject.NameQualifier); } writer.WriteString(subject.Name); writer.WriteEndElement(); } if (subject.ConfirmationMethods.Count > 0) { writer.WriteStartElement(SamlConstants.Prefix, SamlConstants.ElementNames.SubjectConfirmation, SamlConstants.Namespace); foreach (string method in subject.ConfirmationMethods) { writer.WriteElementString(SamlConstants.ElementNames.SubjectConfirmationMethod, SamlConstants.Namespace, method); } if (!string.IsNullOrEmpty(subject.SubjectConfirmationData)) { writer.WriteElementString(SamlConstants.ElementNames.SubjectConfirmationData, SamlConstants.Namespace, subject.SubjectConfirmationData); } if (subject.KeyIdentifier != null) { WriteSubjectKeyInfo(writer, subject.KeyIdentifier); } writer.WriteEndElement(); } writer.WriteEndElement(); } /// /// Read the SamlSubject KeyIdentifier from a XmlReader. /// /// XmlReader positioned at the SamlSubject KeyIdentifier. /// SamlSubject Key as a SecurityKeyIdentifier. /// Input parameter 'reader' is null. /// XmlReader is not positioned at a valid SecurityKeyIdentifier. protected virtual SecurityKeyIdentifier ReadSubjectKeyInfo(XmlReader reader) { if (reader == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } if (KeyInfoSerializer.CanReadKeyIdentifier(reader)) { return KeyInfoSerializer.ReadKeyIdentifier(reader); } throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4090))); } /// /// Write the SamlSubject SecurityKeyIdentifier to the XmlWriter. /// /// XmlWriter to write the SecurityKeyIdentifier. /// SecurityKeyIdentifier to serialize. /// The inpur parameter 'writer' or 'subjectSki' is null. protected virtual void WriteSubjectKeyInfo(XmlWriter writer, SecurityKeyIdentifier subjectSki) { if (writer == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (subjectSki == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("subjectSki"); } if (KeyInfoSerializer.CanWriteKeyIdentifier(subjectSki)) { KeyInfoSerializer.WriteKeyIdentifier(writer, subjectSki); return; } throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("subjectSki", SR.GetString(SR.ID4091, subjectSki.GetType())); } /// /// Read saml:AttributeStatement from the given XmlReader. /// /// XmlReader positioned at a saml:AttributeStatement element. /// SamlAttributeStatement /// Input parameter 'reader' is null. /// XmlReader is not positioned at a saml:AttributeStatement element or /// the AttributeStatement contains unrecognized elements. protected virtual SamlAttributeStatement ReadAttributeStatement(XmlReader reader) { if (reader == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } if (!reader.IsStartElement(SamlConstants.ElementNames.AttributeStatement, SamlConstants.Namespace)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4082, SamlConstants.ElementNames.AttributeStatement, SamlConstants.Namespace, reader.LocalName, reader.NamespaceURI))); } reader.ReadStartElement(); SamlAttributeStatement attributeStatement = new SamlAttributeStatement(); if (reader.IsStartElement(SamlConstants.ElementNames.Subject, SamlConstants.Namespace)) { attributeStatement.SamlSubject = ReadSubject(reader); } else { // SAML Subject is a required Attribute Statement clause. throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4092))); } while (reader.IsStartElement()) { if (reader.IsStartElement(SamlConstants.ElementNames.Attribute, SamlConstants.Namespace)) { // SAML Attribute is a extensibility point. So ask the SAML serializer // to load this part. attributeStatement.Attributes.Add(ReadAttribute(reader)); } else { break; } } if (attributeStatement.Attributes.Count == 0) { // Each Attribute statement should have at least one attribute. throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4093))); } reader.MoveToContent(); reader.ReadEndElement(); return attributeStatement; } /// /// Serialize a SamlAttributeStatement. /// /// XmlWriter to serialize the given statement. /// SamlAttributeStatement to write to the XmlWriter. /// The input parameter 'writer' or 'statement' is null. protected virtual void WriteAttributeStatement(XmlWriter writer, SamlAttributeStatement statement) { if (writer == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (statement == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("statement"); } writer.WriteStartElement(SamlConstants.Prefix, SamlConstants.ElementNames.AttributeStatement, SamlConstants.Namespace); WriteSubject(writer, statement.SamlSubject); for (int i = 0; i < statement.Attributes.Count; i++) { WriteAttribute(writer, statement.Attributes[i]); } writer.WriteEndElement(); } /// /// Read an saml:Attribute element. /// /// XmlReader positioned at a saml:Attribute element. /// SamlAttribute /// The input parameter 'reader' is null. /// The XmlReader is not positioned on a valid saml:Attribute element. protected virtual SamlAttribute ReadAttribute(XmlReader reader) { if (reader == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } SamlAttribute attribute = new SamlAttribute(); attribute.Name = reader.GetAttribute(SamlConstants.AttributeNames.AttributeName, null); if (string.IsNullOrEmpty(attribute.Name)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4094))); } attribute.Namespace = reader.GetAttribute(SamlConstants.AttributeNames.AttributeNamespace, null); if (string.IsNullOrEmpty(attribute.Namespace)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4095))); } // // OriginalIssuer is an optional attribute. // We are lax on read here, and will accept the following namespaces for original issuer, in order: // http://schemas.xmlsoap.org/ws/2009/09/identity/claims // http://schemas.microsoft.com/ws/2008/06/identity // string originalIssuer = reader.GetAttribute(SamlConstants.AttributeNames.OriginalIssuer, ClaimType2009Namespace); if (originalIssuer == null) { originalIssuer = reader.GetAttribute(SamlConstants.AttributeNames.OriginalIssuer, ProductConstants.NamespaceUri); } if (originalIssuer == String.Empty) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4252))); } attribute.OriginalIssuer = originalIssuer; reader.MoveToContent(); reader.Read(); while (reader.IsStartElement(SamlConstants.ElementNames.AttributeValue, SamlConstants.Namespace)) { // FIP 9570 - ENTERPRISE SCENARIO: Saml11SecurityTokenHandler.ReadAttribute is not checking the AttributeValue XSI type correctly. // Lax on receive. If we dont find the AttributeValueXsiType in the format we are looking for in the xml, we default to string. // Read the xsi:type. We are expecting a value of the form "some-non-empty-string" or "some-non-empty-local-prefix:some-non-empty-string". // ":some-non-empty-string" and "some-non-empty-string:" are edge-cases where defaulting to string is reasonable. string attributeValueXsiTypePrefix = null; string attributeValueXsiTypeSuffix = null; string attributeValueXsiTypeSuffixWithLocalPrefix = reader.GetAttribute("type", XmlSchema.InstanceNamespace); if (!string.IsNullOrEmpty(attributeValueXsiTypeSuffixWithLocalPrefix)) { if (attributeValueXsiTypeSuffixWithLocalPrefix.IndexOf(":", StringComparison.Ordinal) == -1) // "some-non-empty-string" case { attributeValueXsiTypePrefix = reader.LookupNamespace(String.Empty); attributeValueXsiTypeSuffix = attributeValueXsiTypeSuffixWithLocalPrefix; } else if (attributeValueXsiTypeSuffixWithLocalPrefix.IndexOf(":", StringComparison.Ordinal) > 0 && attributeValueXsiTypeSuffixWithLocalPrefix.IndexOf(":", StringComparison.Ordinal) < attributeValueXsiTypeSuffixWithLocalPrefix.Length - 1) // "some-non-empty-local-prefix:some-non-empty-string" case { string localPrefix = attributeValueXsiTypeSuffixWithLocalPrefix.Substring(0, attributeValueXsiTypeSuffixWithLocalPrefix.IndexOf(":", StringComparison.Ordinal)); attributeValueXsiTypePrefix = reader.LookupNamespace(localPrefix); // For attributeValueXsiTypeSuffix, we want the portion after the local prefix in "some-non-empty-local-prefix:some-non-empty-string" attributeValueXsiTypeSuffix = attributeValueXsiTypeSuffixWithLocalPrefix.Substring(attributeValueXsiTypeSuffixWithLocalPrefix.IndexOf(":", StringComparison.Ordinal) + 1); } } if (attributeValueXsiTypePrefix != null && attributeValueXsiTypeSuffix != null) { attribute.AttributeValueXsiType = String.Concat(attributeValueXsiTypePrefix, "#", attributeValueXsiTypeSuffix); } if (reader.IsEmptyElement) { reader.Read(); attribute.AttributeValues.Add(string.Empty); } else { attribute.AttributeValues.Add(ReadAttributeValue(reader, attribute)); } } if (attribute.AttributeValues.Count == 0) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4212))); } reader.MoveToContent(); reader.ReadEndElement(); return attribute; } /// /// Reads an attribute value. /// /// XmlReader to read from. /// The current attribute that is being read. /// The attribute value as a string. /// The input parameter 'reader' is null. protected virtual string ReadAttributeValue(XmlReader reader, SamlAttribute attribute) { // This code was designed realizing that the writter of the xml controls how our // reader will report the NodeType. A completely differnet system (IBM, etc) could write the values. // Considering NodeType is important, because we need to read the entire value, end element and not loose anything significant. // // Couple of cases to help understand the design choices. // // 1. // "complexvalue" // Could result in the our reader reporting the NodeType as Text OR Element, depending if '<' was entitized to '<' // // 2. // " complexvalue" // Could result in the our reader reporting the NodeType as Text OR Whitespace. Post Whitespace processing, the NodeType could be // reported as Text or Element, depending if '<' was entitized to '<' // // 3. // "/r/n/t " // Could result in the our reader reporting the NodeType as whitespace. // // Since an AttributeValue with ONLY Whitespace and a complex Element proceeded by whitespace are reported as the same NodeType (2. and 3.) // the whitespace is remembered and discarded if an found is found, otherwise it becomes the value. This is to help users who accidently put a space when adding claims in ADFS // If we just skipped the Whitespace, then an AttributeValue that started with Whitespace would loose that part and claims generated from the AttributeValue // would be missing that part. // if (reader == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } string result = String.Empty; string whiteSpace = String.Empty; reader.ReadStartElement(Saml2Constants.Elements.AttributeValue, SamlConstants.Namespace); while (reader.NodeType == XmlNodeType.Whitespace) { whiteSpace += reader.Value; reader.Read(); } reader.MoveToContent(); if (reader.NodeType == XmlNodeType.Element) { while (reader.NodeType == XmlNodeType.Element) { result += reader.ReadOuterXml(); reader.MoveToContent(); } } else { result = whiteSpace; result += reader.ReadContentAsString(); } reader.ReadEndElement(); return result; } /// /// Serializes a given SamlAttribute. /// /// XmlWriter to serialize the SamlAttribute. /// SamlAttribute to be serialized. /// The input parameter 'writer' or 'attribute' is null. protected virtual void WriteAttribute(XmlWriter writer, SamlAttribute attribute) { if (writer == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (attribute == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("attribute"); } writer.WriteStartElement(SamlConstants.Prefix, SamlConstants.ElementNames.Attribute, SamlConstants.Namespace); writer.WriteAttributeString(SamlConstants.AttributeNames.AttributeName, null, attribute.Name); writer.WriteAttributeString(SamlConstants.AttributeNames.AttributeNamespace, null, attribute.Namespace); SamlAttribute SamlAttribute = attribute as SamlAttribute; if ((SamlAttribute != null) && (SamlAttribute.OriginalIssuer != null)) { writer.WriteAttributeString(SamlConstants.AttributeNames.OriginalIssuer, ClaimType2009Namespace, SamlAttribute.OriginalIssuer); } string xsiTypePrefix = null; string xsiTypeSuffix = null; if (SamlAttribute != null && !StringComparer.Ordinal.Equals(SamlAttribute.AttributeValueXsiType, ClaimValueTypes.String)) { // ClaimValueTypes are URIs of the form prefix#suffix, while xsi:type should be a QName. // Hence, the tokens-to-claims spec requires that ClaimValueTypes be serialized as xmlns:tn="prefix" xsi:type="tn:suffix" int indexOfHash = SamlAttribute.AttributeValueXsiType.IndexOf('#'); xsiTypePrefix = SamlAttribute.AttributeValueXsiType.Substring(0, indexOfHash); xsiTypeSuffix = SamlAttribute.AttributeValueXsiType.Substring(indexOfHash + 1); } for (int i = 0; i < attribute.AttributeValues.Count; i++) { if (attribute.AttributeValues[i] == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4096))); } writer.WriteStartElement(SamlConstants.Prefix, SamlConstants.ElementNames.AttributeValue, SamlConstants.Namespace); if ((xsiTypePrefix != null) && (xsiTypeSuffix != null)) { writer.WriteAttributeString("xmlns", ProductConstants.ClaimValueTypeSerializationPrefix, null, xsiTypePrefix); writer.WriteAttributeString("type", XmlSchema.InstanceNamespace, String.Concat(ProductConstants.ClaimValueTypeSerializationPrefixWithColon, xsiTypeSuffix)); } WriteAttributeValue(writer, attribute.AttributeValues[i], attribute); writer.WriteEndElement(); } writer.WriteEndElement(); } /// /// Writes the saml:Attribute value. /// /// XmlWriter to which to write. /// Attribute value to be written. /// The SAML attribute whose value is being written. /// By default the method writes the value as a string. /// The input parameter 'writer' is null. protected virtual void WriteAttributeValue(XmlWriter writer, string value, SamlAttribute attribute) { if (writer == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } writer.WriteString(value); } /// /// Read the saml:AuthenticationStatement. /// /// XmlReader positioned at a saml:AuthenticationStatement. /// SamlAuthenticationStatement /// The input parameter 'reader' is null. /// The XmlReader is not positioned on a saml:AuthenticationStatement /// or the statement contains a unknown child element. protected virtual SamlAuthenticationStatement ReadAuthenticationStatement(XmlReader reader) { if (reader == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } if (!reader.IsStartElement(SamlConstants.ElementNames.AuthenticationStatement, SamlConstants.Namespace)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4082, SamlConstants.ElementNames.AuthenticationStatement, SamlConstants.Namespace, reader.LocalName, reader.NamespaceURI))); } SamlAuthenticationStatement authnStatement = new SamlAuthenticationStatement(); string authInstance = reader.GetAttribute(SamlConstants.AttributeNames.AuthenticationInstant, null); if (string.IsNullOrEmpty(authInstance)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4097))); } authnStatement.AuthenticationInstant = DateTime.ParseExact( authInstance, DateTimeFormats.Accepted, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None).ToUniversalTime(); authnStatement.AuthenticationMethod = reader.GetAttribute(SamlConstants.AttributeNames.AuthenticationMethod, null); if (string.IsNullOrEmpty(authnStatement.AuthenticationMethod)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4098))); } reader.MoveToContent(); reader.Read(); if (reader.IsStartElement(SamlConstants.ElementNames.Subject, SamlConstants.Namespace)) { authnStatement.SamlSubject = ReadSubject(reader); } else { // Subject is a required element for a Authentication Statement clause. throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4099))); } if (reader.IsStartElement(SamlConstants.ElementNames.SubjectLocality, SamlConstants.Namespace)) { authnStatement.DnsAddress = reader.GetAttribute(SamlConstants.AttributeNames.SubjectLocalityDNSAddress, null); authnStatement.IPAddress = reader.GetAttribute(SamlConstants.AttributeNames.SubjectLocalityIPAddress, null); if (reader.IsEmptyElement) { reader.MoveToContent(); reader.Read(); } else { reader.MoveToContent(); reader.Read(); reader.ReadEndElement(); } } while (reader.IsStartElement()) { if (reader.IsStartElement(SamlConstants.ElementNames.AuthorityBinding, SamlConstants.Namespace)) { authnStatement.AuthorityBindings.Add(ReadAuthorityBinding(reader)); } else { // We do not understand this element. throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4082, SamlConstants.ElementNames.AuthorityBinding, SamlConstants.Namespace, reader.LocalName, reader.NamespaceURI))); } } reader.MoveToContent(); reader.ReadEndElement(); return authnStatement; } /// /// Serializes a given SamlAuthenticationStatement. /// /// XmlWriter to which SamlAuthenticationStatement is serialized. /// SamlAuthenticationStatement to be serialized. /// The input parameter 'writer' or 'statement' is null. protected virtual void WriteAuthenticationStatement(XmlWriter writer, SamlAuthenticationStatement statement) { if (writer == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (statement == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("statement"); } writer.WriteStartElement(SamlConstants.Prefix, SamlConstants.ElementNames.AuthenticationStatement, SamlConstants.Namespace); writer.WriteAttributeString(SamlConstants.AttributeNames.AuthenticationMethod, null, statement.AuthenticationMethod); writer.WriteAttributeString(SamlConstants.AttributeNames.AuthenticationInstant, null, XmlConvert.ToString(statement.AuthenticationInstant.ToUniversalTime(), DateTimeFormats.Generated)); WriteSubject(writer, statement.SamlSubject); if ((statement.IPAddress != null) || (statement.DnsAddress != null)) { writer.WriteStartElement(SamlConstants.Prefix, SamlConstants.ElementNames.SubjectLocality, SamlConstants.Namespace); if (statement.IPAddress != null) { writer.WriteAttributeString(SamlConstants.AttributeNames.SubjectLocalityIPAddress, null, statement.IPAddress); } if (statement.DnsAddress != null) { writer.WriteAttributeString(SamlConstants.AttributeNames.SubjectLocalityDNSAddress, null, statement.DnsAddress); } writer.WriteEndElement(); } for (int i = 0; i < statement.AuthorityBindings.Count; i++) { WriteAuthorityBinding(writer, statement.AuthorityBindings[i]); } writer.WriteEndElement(); } /// /// Read the saml:AuthorityBinding element. /// /// XmlReader positioned at the saml:AuthorityBinding element. /// SamlAuthorityBinding /// The inpur parameter 'reader' is null. /// XmlReader is not positioned at a saml:AuthorityBinding element or /// contains a unrecognized or invalid child element. protected virtual SamlAuthorityBinding ReadAuthorityBinding(XmlReader reader) { if (reader == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } SamlAuthorityBinding authorityBinding = new SamlAuthorityBinding(); string authKind = reader.GetAttribute(SamlConstants.AttributeNames.AuthorityKind, null); if (string.IsNullOrEmpty(authKind)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4200))); } string[] authKindParts = authKind.Split(':'); if (authKindParts.Length > 2) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4201, authKind))); } string localName; string prefix; string nameSpace; if (authKindParts.Length == 2) { prefix = authKindParts[0]; localName = authKindParts[1]; } else { prefix = String.Empty; localName = authKindParts[0]; } nameSpace = reader.LookupNamespace(prefix); authorityBinding.AuthorityKind = new XmlQualifiedName(localName, nameSpace); authorityBinding.Binding = reader.GetAttribute(SamlConstants.AttributeNames.Binding, null); if (string.IsNullOrEmpty(authorityBinding.Binding)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4202))); } authorityBinding.Location = reader.GetAttribute(SamlConstants.AttributeNames.Location, null); if (string.IsNullOrEmpty(authorityBinding.Location)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4203))); } if (reader.IsEmptyElement) { reader.MoveToContent(); reader.Read(); } else { reader.MoveToContent(); reader.Read(); reader.ReadEndElement(); } return authorityBinding; } /// /// Serialize a SamlAuthorityBinding. /// /// XmlWriter to serialize the SamlAuthorityBinding /// SamlAuthoriyBinding to be serialized. protected virtual void WriteAuthorityBinding(XmlWriter writer, SamlAuthorityBinding authorityBinding) { if (writer == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (authorityBinding == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("statement"); } writer.WriteStartElement(SamlConstants.Prefix, SamlConstants.ElementNames.AuthorityBinding, SamlConstants.Namespace); string prefix = null; if (!string.IsNullOrEmpty(authorityBinding.AuthorityKind.Namespace)) { writer.WriteAttributeString(String.Empty, SamlConstants.AttributeNames.NamespaceAttributePrefix, null, authorityBinding.AuthorityKind.Namespace); prefix = writer.LookupPrefix(authorityBinding.AuthorityKind.Namespace); } writer.WriteStartAttribute(SamlConstants.AttributeNames.AuthorityKind, null); if (string.IsNullOrEmpty(prefix)) { writer.WriteString(authorityBinding.AuthorityKind.Name); } else { writer.WriteString(prefix + ":" + authorityBinding.AuthorityKind.Name); } writer.WriteEndAttribute(); writer.WriteAttributeString(SamlConstants.AttributeNames.Location, null, authorityBinding.Location); writer.WriteAttributeString(SamlConstants.AttributeNames.Binding, null, authorityBinding.Binding); writer.WriteEndElement(); } /// /// Gets a boolean indicating if the SecurityTokenHandler can Serialize Tokens. Return true by default. /// public override bool CanWriteToken { get { return true; } } /// /// Serializes the given SecurityToken to the XmlWriter. /// /// XmlWriter into which the token is serialized. /// SecurityToken to be serialized. /// Input parameter 'writer' or 'token' is null. /// The given 'token' is not a SamlSecurityToken. public override void WriteToken(XmlWriter writer, SecurityToken token) { if (writer == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (token == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("token"); } SamlSecurityToken samlSecurityToken = token as SamlSecurityToken; if (samlSecurityToken == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4217, token.GetType(), typeof(SamlSecurityToken)))); } WriteAssertion(writer, samlSecurityToken.Assertion); } /// /// Read the saml:AuthorizationDecisionStatement element. /// /// XmlReader position at saml:AuthorizationDecisionStatement. /// SamlAuthorizationDecisionStatement /// The inpur parameter 'reader' is null. /// The XmlReader is not positioned at a saml:AuthorizationDecisionStatement or /// the statement contains child elments that are unknown or invalid. protected virtual SamlAuthorizationDecisionStatement ReadAuthorizationDecisionStatement(XmlReader reader) { if (reader == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } if (!reader.IsStartElement(SamlConstants.ElementNames.AuthorizationDecisionStatement, SamlConstants.Namespace)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4082, SamlConstants.ElementNames.AuthorizationDecisionStatement, SamlConstants.Namespace, reader.LocalName, reader.NamespaceURI))); } SamlAuthorizationDecisionStatement authzStatement = new SamlAuthorizationDecisionStatement(); authzStatement.Resource = reader.GetAttribute(SamlConstants.AttributeNames.Resource, null); if (string.IsNullOrEmpty(authzStatement.Resource)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4205))); } string decisionString = reader.GetAttribute(SamlConstants.AttributeNames.Decision, null); if (string.IsNullOrEmpty(decisionString)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4204))); } if (decisionString.Equals(SamlAccessDecision.Deny.ToString(), StringComparison.OrdinalIgnoreCase)) { authzStatement.AccessDecision = SamlAccessDecision.Deny; } else if (decisionString.Equals(SamlAccessDecision.Permit.ToString(), StringComparison.OrdinalIgnoreCase)) { authzStatement.AccessDecision = SamlAccessDecision.Permit; } else { authzStatement.AccessDecision = SamlAccessDecision.Indeterminate; } reader.MoveToContent(); reader.Read(); if (reader.IsStartElement(SamlConstants.ElementNames.Subject, SamlConstants.Namespace)) { authzStatement.SamlSubject = ReadSubject(reader); } else { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4206))); } while (reader.IsStartElement()) { if (reader.IsStartElement(SamlConstants.ElementNames.Action, SamlConstants.Namespace)) { authzStatement.SamlActions.Add(ReadAction(reader)); } else if (reader.IsStartElement(SamlConstants.ElementNames.Evidence, SamlConstants.Namespace)) { if (authzStatement.Evidence != null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4207))); } authzStatement.Evidence = ReadEvidence(reader); } else { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4208, reader.LocalName, reader.NamespaceURI))); } } if (authzStatement.SamlActions.Count == 0) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4209))); } reader.MoveToContent(); reader.ReadEndElement(); return authzStatement; } /// /// Serialize a SamlAuthorizationDecisionStatement. /// /// XmlWriter to which the SamlAuthorizationStatement is serialized. /// SamlAuthorizationDecisionStatement to serialize. /// The input parameter 'writer' or 'statement' is null. protected virtual void WriteAuthorizationDecisionStatement(XmlWriter writer, SamlAuthorizationDecisionStatement statement) { if (writer == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (statement == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("statement"); } writer.WriteStartElement(SamlConstants.Prefix, SamlConstants.ElementNames.AuthorizationDecisionStatement, SamlConstants.Namespace); writer.WriteAttributeString(SamlConstants.AttributeNames.Decision, null, statement.AccessDecision.ToString()); writer.WriteAttributeString(SamlConstants.AttributeNames.Resource, null, statement.Resource); WriteSubject(writer, statement.SamlSubject); foreach (SamlAction action in statement.SamlActions) { WriteAction(writer, action); } if (statement.Evidence != null) { WriteEvidence(writer, statement.Evidence); } writer.WriteEndElement(); } /// /// Read the saml:Evidence element. /// /// XmlReader positioned at saml:Evidence element. /// SamlEvidence /// The input parameter 'reader' is null. /// The XmlReader is not positioned at a saml:Evidence element or /// the element contains unrecognized or invalid child elements. protected virtual SamlEvidence ReadEvidence(XmlReader reader) { if (reader == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } if (!reader.IsStartElement(SamlConstants.ElementNames.Evidence, SamlConstants.Namespace)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4082, SamlConstants.ElementNames.Evidence, SamlConstants.Namespace, reader.LocalName, reader.NamespaceURI))); } SamlEvidence evidence = new SamlEvidence(); reader.ReadStartElement(); while (reader.IsStartElement()) { if (reader.IsStartElement(SamlConstants.ElementNames.AssertionIdReference, SamlConstants.Namespace)) { evidence.AssertionIdReferences.Add(reader.ReadElementString()); } else if (reader.IsStartElement(SamlConstants.ElementNames.Assertion, SamlConstants.Namespace)) { evidence.Assertions.Add(ReadAssertion(reader)); } else { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4210, reader.LocalName, reader.NamespaceURI))); } } if ((evidence.AssertionIdReferences.Count == 0) && (evidence.Assertions.Count == 0)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4211))); } reader.MoveToContent(); reader.ReadEndElement(); return evidence; } /// /// Serializes a given SamlEvidence. /// /// XmlWriter to serialize the SamlEvidence. /// SamlEvidence to be serialized. /// The input parameter 'evidence' is null. protected virtual void WriteEvidence(XmlWriter writer, SamlEvidence evidence) { if (writer == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (evidence == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("evidence"); } writer.WriteStartElement(SamlConstants.Prefix, SamlConstants.ElementNames.Evidence, SamlConstants.Namespace); for (int i = 0; i < evidence.AssertionIdReferences.Count; i++) { writer.WriteElementString(SamlConstants.Prefix, SamlConstants.ElementNames.AssertionIdReference, SamlConstants.Namespace, evidence.AssertionIdReferences[i]); } for (int i = 0; i < evidence.Assertions.Count; i++) { WriteAssertion(writer, evidence.Assertions[i]); } writer.WriteEndElement(); } /// /// Resolves the SecurityKeyIdentifier specified in a saml:Subject element. /// /// SecurityKeyIdentifier to resolve into a key. /// SecurityKey /// The input parameter 'subjectKeyIdentifier' is null. protected virtual SecurityKey ResolveSubjectKeyIdentifier(SecurityKeyIdentifier subjectKeyIdentifier) { if (subjectKeyIdentifier == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("subjectKeyIdentifier"); } if (this.Configuration == null) { throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4274)); } if (this.Configuration.ServiceTokenResolver == null) { throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4276)); } SecurityKey key = null; foreach (SecurityKeyIdentifierClause clause in subjectKeyIdentifier) { if (this.Configuration.ServiceTokenResolver.TryResolveSecurityKey(clause, out key)) { return key; } } if (subjectKeyIdentifier.CanCreateKey) { return subjectKeyIdentifier.CreateKey(); } return null; } /// /// Resolves the Signing Key Identifier to a SecurityToken. /// /// The Assertion for which the Issuer token is to be resolved. /// The current SecurityTokenResolver associated with this handler. /// Instance of SecurityToken /// Input parameter 'assertion' is null. /// Input parameter 'issuerResolver' is null./// /// Unable to resolve token. protected virtual SecurityToken ResolveIssuerToken(SamlAssertion assertion, SecurityTokenResolver issuerResolver) { if (null == assertion) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("assertion"); } if (null == issuerResolver) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("issuerResolver"); } SecurityToken token; if (TryResolveIssuerToken(assertion, issuerResolver, out token)) { return token; } else { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4220))); } } /// /// Resolves the Signing Key Identifier to a SecurityToken. /// /// The Assertion for which the Issuer token is to be resolved. /// The current SecurityTokenResolver associated with this handler. /// Resolved token. /// True if token is resolved. /// Input parameter 'assertion' is null. protected virtual bool TryResolveIssuerToken(SamlAssertion assertion, SecurityTokenResolver issuerResolver, out SecurityToken token) { if (null == assertion) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("assertion"); } if (assertion.SigningCredentials != null && assertion.SigningCredentials.SigningKeyIdentifier != null && issuerResolver != null) { SecurityKeyIdentifier keyIdentifier = assertion.SigningCredentials.SigningKeyIdentifier; return issuerResolver.TryResolveToken(keyIdentifier, out token); } else { token = null; return false; } } /// /// Reads the ds:KeyInfo element inside the Saml Signature. /// /// An XmlReader that can be positioned at a ds:KeyInfo element. /// The assertion that is having the signature checked. /// The that defines the key to use to check the signature. /// The input parameter 'reader' is null. /// Unable to read the KeyIdentifier from the XmlReader. /// If the reader is not positioned at a ds:KeyInfo element, the returned will /// contain a single of type protected virtual SecurityKeyIdentifier ReadSigningKeyInfo(XmlReader reader, SamlAssertion assertion) { if (reader == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } SecurityKeyIdentifier ski; if (KeyInfoSerializer.CanReadKeyIdentifier(reader)) { ski = KeyInfoSerializer.ReadKeyIdentifier(reader); } else { KeyInfo keyInfo = new KeyInfo(KeyInfoSerializer); keyInfo.ReadXml(XmlDictionaryReader.CreateDictionaryReader(reader)); ski = keyInfo.KeyIdentifier; } // no key info if (ski.Count == 0) { return new SecurityKeyIdentifier(new SamlSecurityKeyIdentifierClause(assertion)); } return ski; } /// /// Serializes the Signing SecurityKeyIdentifier. /// /// XmlWriter to serialize the SecurityKeyIdentifier. /// Signing SecurityKeyIdentifier. /// The input parameter 'writer' or 'signingKeyIdentifier' is null. protected virtual void WriteSigningKeyInfo(XmlWriter writer, SecurityKeyIdentifier signingKeyIdentifier) { if (writer == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (signingKeyIdentifier == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("signingKeyIdentifier"); } if (KeyInfoSerializer.CanWriteKeyIdentifier(signingKeyIdentifier)) { KeyInfoSerializer.WriteKeyIdentifier(writer, signingKeyIdentifier); return; } throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4221, signingKeyIdentifier)); } /// /// Validates the subject in each statement in the collection of Saml statements. /// /// private void ValidateStatements(IList statements) { if (statements == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("statements"); } List subjects = new List(); foreach (SamlStatement statement in statements) { if (statement is SamlAttributeStatement) { subjects.Add((statement as SamlAttributeStatement).SamlSubject); } if (statement is SamlAuthenticationStatement) { subjects.Add((statement as SamlAuthenticationStatement).SamlSubject); } if (statement is SamlAuthorizationDecisionStatement) { subjects.Add((statement as SamlAuthorizationDecisionStatement).SamlSubject); } // // skip all custom statements // } if (subjects.Count == 0) { // // All statements are custom and we cannot validate // return; } string requiredSubjectName = subjects[0].Name; string requiredSubjectFormat = subjects[0].NameFormat; string requiredSubjectQualifier = subjects[0].NameQualifier; foreach (SamlSubject subject in subjects) { if (!StringComparer.Ordinal.Equals(subject.Name, requiredSubjectName) || !StringComparer.Ordinal.Equals(subject.NameFormat, requiredSubjectFormat) || !StringComparer.Ordinal.Equals(subject.NameQualifier, requiredSubjectQualifier)) { // // The SamlSubjects in the statements do not match // throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4225, subject)); } } } #endregion /// /// Returns the saml token's token type that is supported by this handler. /// public override string[] GetTokenTypeIdentifiers() { return _tokenTypeIdentifiers; } /// /// Gets or Sets a SecurityTokenSerializers that will be used to serialize and deserializer /// SecurtyKeyIdentifier. For example, SamlSubject SecurityKeyIdentifier or Signature /// SecurityKeyIdentifier. /// public SecurityTokenSerializer KeyInfoSerializer { get { if (_keyInfoSerializer == null) { lock (_syncObject) { if (_keyInfoSerializer == null) { SecurityTokenHandlerCollection sthc = (ContainingCollection != null) ? ContainingCollection : SecurityTokenHandlerCollection.CreateDefaultSecurityTokenHandlerCollection(); _keyInfoSerializer = new SecurityTokenSerializerAdapter(sthc); } } } return _keyInfoSerializer; } set { if (value == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("value"); } _keyInfoSerializer = value; } } /// /// Gets the System.Type of the SecurityToken is supported by ththis handler. /// public override Type TokenType { get { return typeof(SamlSecurityToken); } } /// /// Gets or sets the /// public SamlSecurityTokenRequirement SamlSecurityTokenRequirement { get { return _samlSecurityTokenRequirement; } set { if (value == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("value"); } _samlSecurityTokenRequirement = value; } } // This thin wrapper is used to pass a serializer down into the // EnvelopedSignatureReader that will use the Saml11SecurityTokenHandlers's // ReadKeyInfo method to read the KeyInfo. class WrappedSerializer : SecurityTokenSerializer { SamlSecurityTokenHandler _parent; SamlAssertion _assertion; public WrappedSerializer(SamlSecurityTokenHandler parent, SamlAssertion assertion) { if (parent == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("parent"); } _parent = parent; _assertion = assertion; } protected override bool CanReadKeyIdentifierClauseCore(XmlReader reader) { return false; } protected override bool CanReadKeyIdentifierCore(XmlReader reader) { return true; } protected override bool CanReadTokenCore(XmlReader reader) { return false; } protected override bool CanWriteKeyIdentifierClauseCore(SecurityKeyIdentifierClause keyIdentifierClause) { return false; } protected override bool CanWriteKeyIdentifierCore(SecurityKeyIdentifier keyIdentifier) { return false; } protected override bool CanWriteTokenCore(SecurityToken token) { return false; } protected override SecurityKeyIdentifierClause ReadKeyIdentifierClauseCore(XmlReader reader) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException()); } protected override SecurityKeyIdentifier ReadKeyIdentifierCore(XmlReader reader) { return _parent.ReadSigningKeyInfo(reader, _assertion); } protected override SecurityToken ReadTokenCore(XmlReader reader, SecurityTokenResolver tokenResolver) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException()); } protected override void WriteKeyIdentifierClauseCore(XmlWriter writer, SecurityKeyIdentifierClause keyIdentifierClause) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException()); } protected override void WriteKeyIdentifierCore(XmlWriter writer, SecurityKeyIdentifier keyIdentifier) { _parent.WriteSigningKeyInfo(writer, keyIdentifier); } protected override void WriteTokenCore(XmlWriter writer, SecurityToken token) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException()); } } } }