//----------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // //----------------------------------------------------------------------- namespace System.IdentityModel.Tokens { using System; 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 SAML2 = System.IdentityModel.Tokens.Saml2Constants; using WSC = System.IdentityModel.WSSecureConversationFeb2005Constants; using WSC13 = System.IdentityModel.WSSecureConversation13Constants; using WSSE = System.IdentityModel.WSSecurity10Constants; using WSSE11 = System.IdentityModel.WSSecurity11Constants; /// /// Creates SAML2 assertion-based security tokens /// public class Saml2SecurityTokenHandler : SecurityTokenHandler { /// /// The key identifier value type for SAML 2.0 assertion IDs, as defined /// by the OASIS Web Services Security SAML Token Profile 1.1. /// public const string TokenProfile11ValueType = "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLID"; private const string Actor = "Actor"; private const string Attribute = "Attribute"; private static string[] tokenTypeIdentifiers = new string[] { SecurityTokenTypes.Saml2TokenProfile11, SecurityTokenTypes.OasisWssSaml2TokenProfile11 }; private SamlSecurityTokenRequirement samlSecurityTokenRequirement; private SecurityTokenSerializer keyInfoSerializer; const string ClaimType2009Namespace = "http://schemas.xmlsoap.org/ws/2009/09/identity/claims"; object syncObject = new object(); /// /// Creates an instance of /// public Saml2SecurityTokenHandler() : this(new SamlSecurityTokenRequirement()) { } /// /// Creates an instance of /// /// The SamlSecurityTokenRequirement to be used by the Saml2SecurityTokenHandler instance when validating tokens. public Saml2SecurityTokenHandler(SamlSecurityTokenRequirement samlSecurityTokenRequirement) { if (samlSecurityTokenRequirement == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("samlSecurityTokenRequirement"); } this.samlSecurityTokenRequirement = samlSecurityTokenRequirement; } /// /// Load custom configuration from Xml /// /// SAML token authentication requirements. /// 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)); } this.samlSecurityTokenRequirement = new SamlSecurityTokenRequirement(configElement); foundValidConfig = true; } if (!foundValidConfig) { this.samlSecurityTokenRequirement = new SamlSecurityTokenRequirement(); } } /// /// Returns a value that indicates if this handler can validate . /// /// 'True', indicating this instance can validate . public override bool CanValidateToken { get { return true; } } /// /// Gets the token type supported by this handler. /// public override Type TokenType { get { return typeof(Saml2SecurityToken); } } /// /// Gets or sets the that is used by the current instance to validate /// certificates that have signed the . /// public X509CertificateValidator CertificateValidator { get { if (this.samlSecurityTokenRequirement.CertificateValidator == null) { if (Configuration != null) { return Configuration.CertificateValidator; } else { return null; } } else { return this.samlSecurityTokenRequirement.CertificateValidator; } } set { this.samlSecurityTokenRequirement.CertificateValidator = value; } } /// /// Gets or sets a that will be used to serialize and deserialize /// a . For example, SamlSubject SecurityKeyIdentifier or Signature /// SecurityKeyIdentifier. /// public SecurityTokenSerializer KeyInfoSerializer { get { if ( this.keyInfoSerializer == null ) { lock ( this.syncObject ) { if ( this.keyInfoSerializer == null ) { SecurityTokenHandlerCollection sthc = ( ContainingCollection != null ) ? ContainingCollection : SecurityTokenHandlerCollection.CreateDefaultSecurityTokenHandlerCollection(); this.keyInfoSerializer = new SecurityTokenSerializerAdapter(sthc); } } } return this.keyInfoSerializer; } set { if (value == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("value"); } this.keyInfoSerializer = value; } } /// /// Gets the value if this instance can write a token. /// public override bool CanWriteToken { get { return true; } } /// /// Gets or sets the . /// public SamlSecurityTokenRequirement SamlSecurityTokenRequirement { get { return this.samlSecurityTokenRequirement; } set { if (value == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("value"); } this.samlSecurityTokenRequirement = value; } } /// /// Creates a to be used as 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 instance. public override SecurityKeyIdentifierClause CreateSecurityTokenReference(SecurityToken token, bool attached) { if (null == token) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("token"); } return token.CreateKeyIdentifierClause(); } /// /// Creates a based on a information contained in the . /// /// The that has creation information. /// A instance. /// Thrown if 'tokenDescriptor' is null. public override SecurityToken CreateToken(SecurityTokenDescriptor tokenDescriptor) { if (null == tokenDescriptor) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("tokenDescriptor"); } // Assertion/issuer Saml2Assertion assertion = new Saml2Assertion(this.CreateIssuerNameIdentifier(tokenDescriptor)); // Subject assertion.Subject = this.CreateSamlSubject(tokenDescriptor); // Signature assertion.SigningCredentials = this.GetSigningCredentials(tokenDescriptor); // Conditions assertion.Conditions = this.CreateConditions(tokenDescriptor.Lifetime, tokenDescriptor.AppliesToAddress, tokenDescriptor); // Advice assertion.Advice = this.CreateAdvice(tokenDescriptor); // Statements IEnumerable statements = this.CreateStatements(tokenDescriptor); if (null != statements) { foreach (Saml2Statement statement in statements) { assertion.Statements.Add(statement); } } // encrypting credentials assertion.EncryptingCredentials = this.GetEncryptingCredentials(tokenDescriptor); SecurityToken token = new Saml2SecurityToken(assertion); return token; } /// /// Gets the token type identifier(s) supported by this handler. /// /// A collection of strings that identify the tokens this instance can handle. public override string[] GetTokenTypeIdentifiers() { return tokenTypeIdentifiers; } /// /// Validates a . /// /// The to validate. /// A of representing the identities contained in the token. /// The parameter 'token' is null. /// The token is not of assignable from . /// Configuration is null. /// Thrown if Saml2SecurityToken.Assertion.IssuerToken is null. /// Thrown if Saml2SecurityToken.Assertion.SigningToken is null. /// Saml2SecurityToken.Assertion is null. public override ReadOnlyCollection ValidateToken(SecurityToken token) { if (token == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("token"); } Saml2SecurityToken samlToken = token as Saml2SecurityToken; if (samlToken == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("token", SR.GetString(SR.ID4151)); } if (this.Configuration == null) { throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4274)); } try { TraceUtility.TraceEvent(TraceEventType.Verbose, TraceCode.Diagnostics, SR.GetString(SR.TraceValidateToken), new SecurityTraceRecordHelper.TokenTraceRecord(token), null, null); if (samlToken.IssuerToken == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenValidationException(SR.GetString(SR.ID4152))); } if (samlToken.Assertion == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("token", SR.GetString(SR.ID1034)); } 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); } Saml2SubjectConfirmation subjectConfirmation = samlToken.Assertion.Subject.SubjectConfirmations[0]; if (subjectConfirmation.SubjectConfirmationData != null) { this.ValidateConfirmationData(subjectConfirmation.SubjectConfirmationData); } // If the backing token is x509, validate trust X509SecurityToken issuerToken = samlToken.IssuerToken as X509SecurityToken; if (issuerToken != null) { this.CertificateValidator.Validate(issuerToken.Certificate); } ClaimsIdentity claimsIdentity = null; if (this.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. claimsIdentity = this.CreateWindowsIdentity(this.FindUpn(claimsIdentity)); // PARTIAL TRUST: will fail when adding claims, AddClaims is SecurityCritical. claimsIdentity.AddClaims(this.CreateClaims(samlToken).Claims); } else { claimsIdentity = this.CreateClaims(samlToken); } 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); } /// /// Writes a Saml2 Token using the XmlWriter. /// /// A to serialize the . /// The to serialize. /// The input argument 'writer' or 'token' is null. /// The input argument 'token' is not a . public override void WriteToken(XmlWriter writer, SecurityToken token) { if (writer == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (token == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("token"); } Saml2SecurityToken samlToken = token as Saml2SecurityToken; if (null != samlToken) { this.WriteAssertion(writer, samlToken.Assertion); } else { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("token", SR.GetString(SR.ID4160)); } } /// /// Indicates whether the current XML element can be read as a token of the type handled by this instance. /// /// An reader positioned at a start element. The reader should not be advanced. /// 'True' if the ReadToken method can read the element. public override bool CanReadToken(XmlReader reader) { if (reader == null) { return false; } return reader.IsStartElement(SAML2.Elements.Assertion, SAML2.Namespace) || reader.IsStartElement(SAML2.Elements.EncryptedAssertion, SAML2.Namespace); } /// /// Indicates if the current XML element is pointing to a KeyIdentifierClause that /// can be serialized by this instance. /// /// An reader positioned at a start element. The reader should not be advanced. /// 'True' if the ReadKeyIdentifierClause can read the element. 'False' otherwise. public override bool CanReadKeyIdentifierClause(XmlReader reader) { return IsSaml2KeyIdentifierClause(reader); } /// /// Indicates if the given SecurityKeyIdentifierClause can be serialized by this /// instance. /// /// SecurityKeyIdentifierClause to be serialized. /// "True' if the given SecurityKeyIdentifierClause can be serialized. 'False' otherwise. public override bool CanWriteKeyIdentifierClause(SecurityKeyIdentifierClause securityKeyIdentifierClause) { return (securityKeyIdentifierClause is Saml2AssertionKeyIdentifierClause) || (securityKeyIdentifierClause is WrappedSaml2AssertionKeyIdentifierClause); } /// /// Reads a SecurityKeyIdentifierClause. /// /// A reader positioned at a element. /// A instance. /// Input parameter 'reader' is null. public override SecurityKeyIdentifierClause ReadKeyIdentifierClause(XmlReader reader) { if (reader == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } if (!IsSaml2KeyIdentifierClause(reader)) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4161)); } // disallow empty if (reader.IsEmptyElement) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID3061, WSSE.Elements.SecurityTokenReference, WSSE.Namespace)); } try { // @attributes string value; string id; byte[] nonce = null; int length = 0; // @wsse11:TokenType is checked by IsSaml2KeyIdentifierClause // @wsc:Nonce and @wsc:Length, first try WSCFeb2005 value = reader.GetAttribute(WSC.Attributes.Nonce, WSC.Namespace); if (!string.IsNullOrEmpty(value)) { nonce = Convert.FromBase64String(value); value = reader.GetAttribute(WSC.Attributes.Length, WSC.Namespace); if (!string.IsNullOrEmpty(value)) { length = XmlConvert.ToInt32(value); } else { length = WSC.DefaultDerivedKeyLength; } } // @wsc:Nonce and @wsc:Length, now try WSC13 if (null == nonce) { value = reader.GetAttribute(WSC13.Attributes.Nonce, WSC13.Namespace); if (!string.IsNullOrEmpty(value)) { nonce = Convert.FromBase64String(value); value = reader.GetAttribute(WSC13.Attributes.Length, WSC13.Namespace); if (!string.IsNullOrEmpty(value)) { length = XmlConvert.ToInt32(value); } else { length = WSC13.DefaultDerivedKeyLength; } } } // content begins reader.Read(); // - throw exception if (reader.IsStartElement(WSSE.Elements.Reference, WSSE.Namespace)) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4126)); } // if (!reader.IsStartElement(WSSE.Elements.KeyIdentifier, WSSE.Namespace)) { reader.ReadStartElement(WSSE.Elements.KeyIdentifier, WSSE.Namespace); } // @ValueType - required value = reader.GetAttribute(WSSE.Attributes.ValueType); if (string.IsNullOrEmpty(value)) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID0001, WSSE.Attributes.ValueType, WSSE.Elements.KeyIdentifier)); } if (!StringComparer.Ordinal.Equals(TokenProfile11ValueType, value)) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4127, value)); } // Content is string id = reader.ReadElementString(); // reader.ReadEndElement(); return new Saml2AssertionKeyIdentifierClause(id, nonce, length); } catch (Exception inner) { // Wrap common data-validation exceptions that may have bubbled up if (inner is FormatException || inner is ArgumentException || inner is InvalidOperationException || inner is OverflowException) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4125), inner); } else { throw; } } } /// /// Reads a SAML 2.0 token from the XmlReader. /// /// A reader positioned at a element. /// An instance of . /// Is thrown if 'Configuration', 'Configruation.IssuerTokenResolver' or 'Configuration.ServiceTokenResolver 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)); } if (Configuration.ServiceTokenResolver == null) { throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4276)); } Saml2Assertion assertion = this.ReadAssertion(reader); ReadOnlyCollection keys = this.ResolveSecurityKeys(assertion, Configuration.ServiceTokenResolver); // Resolve signing token if one is present. It may be deferred and signed by reference. SecurityToken issuerToken; this.TryResolveIssuerToken(assertion, Configuration.IssuerTokenResolver, out issuerToken); return new Saml2SecurityToken(assertion, keys, issuerToken); } /// /// Serializes a Saml2AssertionKeyIdentifierClause to the XmlWriter. /// /// A to serialize the . /// The to be serialized. /// Input parameter 'writer' or 'securityKeyIdentifierClause' is null. public override void WriteKeyIdentifierClause(XmlWriter writer, SecurityKeyIdentifierClause securityKeyIdentifierClause) { if (writer == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (securityKeyIdentifierClause == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("keyIdentifierClause"); } Saml2AssertionKeyIdentifierClause samlClause = null; WrappedSaml2AssertionKeyIdentifierClause wrappedClause = securityKeyIdentifierClause as WrappedSaml2AssertionKeyIdentifierClause; if (wrappedClause != null) { samlClause = wrappedClause.WrappedClause; } else { samlClause = securityKeyIdentifierClause as Saml2AssertionKeyIdentifierClause; } if (null == samlClause) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("keyIdentifierClause", SR.GetString(SR.ID4162)); } // writer.WriteStartElement(WSSE.Elements.SecurityTokenReference, WSSE.Namespace); // @wsc:Nonce byte[] nonce = samlClause.GetDerivationNonce(); if (null != nonce) { writer.WriteAttributeString(WSC.Attributes.Nonce, WSC.Namespace, Convert.ToBase64String(nonce)); int length = samlClause.DerivationLength; // Don't emit @wsc:Length since it's not actually in the spec/schema if (length != 0 && length != WSC.DefaultDerivedKeyLength) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new InvalidOperationException(SR.GetString(SR.ID4129))); } } // @wsse11:TokenType writer.WriteAttributeString(WSSE11.Attributes.TokenType, WSSE11.Namespace, SecurityTokenTypes.OasisWssSaml2TokenProfile11); // writer.WriteStartElement(WSSE.Elements.KeyIdentifier, WSSE.Namespace); // @ValueType writer.WriteAttributeString(WSSE.Attributes.ValueType, TokenProfile11ValueType); // ID is the string content writer.WriteString(samlClause.Id); // writer.WriteEndElement(); // writer.WriteEndElement(); } internal static XmlDictionaryReader CreatePlaintextReaderFromEncryptedData( XmlDictionaryReader reader, SecurityTokenResolver serviceTokenResolver, SecurityTokenSerializer keyInfoSerializer, Collection clauses, out EncryptingCredentials encryptingCredentials) { if (reader == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } reader.MoveToContent(); if (reader.IsEmptyElement) { #pragma warning suppress 56504 // bogus - thinks reader.LocalName, reader.NamespaceURI need validation throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID3061, reader.LocalName, reader.NamespaceURI)); } encryptingCredentials = null; XmlUtil.ValidateXsiType(reader, Saml2Constants.Types.EncryptedElementType, Saml2Constants.Namespace); reader.ReadStartElement(); EncryptedDataElement encryptedData = new EncryptedDataElement(keyInfoSerializer); // 1 encryptedData.ReadXml(reader); // 0-oo reader.MoveToContent(); while (reader.IsStartElement(XmlEncryptionConstants.Elements.EncryptedKey, XmlEncryptionConstants.Namespace)) { SecurityKeyIdentifierClause skic; if (keyInfoSerializer.CanReadKeyIdentifierClause(reader)) { skic = keyInfoSerializer.ReadKeyIdentifierClause(reader); } else { EncryptedKeyElement encryptedKey = new EncryptedKeyElement(keyInfoSerializer); encryptedKey.ReadXml(reader); skic = encryptedKey.GetClause(); } EncryptedKeyIdentifierClause encryptedKeyClause = skic as EncryptedKeyIdentifierClause; if (null == encryptedKeyClause) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4172)); } clauses.Add(encryptedKeyClause); } reader.ReadEndElement(); // Try to resolve the decryption key from both the embedded // KeyInfo and any external clauses SecurityKey decryptionKey = null; SecurityKeyIdentifierClause matchingClause = null; foreach (SecurityKeyIdentifierClause clause in encryptedData.KeyIdentifier) { if (serviceTokenResolver.TryResolveSecurityKey(clause, out decryptionKey)) { matchingClause = clause; break; } } if (null == decryptionKey) { foreach (SecurityKeyIdentifierClause clause in clauses) { if (serviceTokenResolver.TryResolveSecurityKey(clause, out decryptionKey)) { matchingClause = clause; break; } } } if (null == decryptionKey) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new EncryptedTokenDecryptionFailedException()); } // Need a symmetric key SymmetricSecurityKey symmetricKey = decryptionKey as SymmetricSecurityKey; if (null == symmetricKey) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new SecurityTokenException(SR.GetString(SR.ID4023))); } // Do the actual decryption SymmetricAlgorithm decryptor = symmetricKey.GetSymmetricAlgorithm(encryptedData.Algorithm); byte[] plainText = encryptedData.Decrypt(decryptor); // Save off the encrypting credentials for roundtrip encryptingCredentials = new ReceivedEncryptingCredentials(decryptionKey, new SecurityKeyIdentifier(matchingClause), encryptedData.Algorithm); return XmlDictionaryReader.CreateTextReader(plainText, reader.Quotas); } // Wraps common data validation exceptions with an XmlException // associated with the failing reader internal static Exception TryWrapReadException(XmlReader reader, Exception inner) { if (inner is FormatException || inner is ArgumentException || inner is InvalidOperationException || inner is OverflowException) { return DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4125), inner); } return null; } /// /// Indicates if the current XML element is pointing to a Saml2SecurityKeyIdentifierClause. /// /// An reader. /// 'True' if reader contains a . 'False' otherwise. internal static bool IsSaml2KeyIdentifierClause(XmlReader reader) { if (!reader.IsStartElement(WSSE.Elements.SecurityTokenReference, WSSE.Namespace)) { return false; } string tokenType = reader.GetAttribute(WSSE11.Attributes.TokenType, WSSE11.Namespace); return tokenTypeIdentifiers.Contains(tokenType); } /// /// Indicates if the current XML element is pointing to a Saml2Assertion. /// /// A reader that may contain a . /// 'True' if reader contains a . 'False' otherwise. internal static bool IsSaml2Assertion(XmlReader reader) { return reader.IsStartElement(SAML2.Elements.Assertion, SAML2.Namespace) || reader.IsStartElement(SAML2.Elements.EncryptedAssertion, SAML2.Namespace); } // Read an element that must not contain content. internal static void ReadEmptyContentElement(XmlReader reader) { bool isEmpty = reader.IsEmptyElement; reader.Read(); if (!isEmpty) { reader.ReadEndElement(); } } internal static Saml2Id ReadSimpleNCNameElement(XmlReader reader) { Fx.Assert(reader.IsStartElement(), "reader is not on start element"); try { if (reader.IsEmptyElement) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID3061, reader.LocalName, reader.NamespaceURI)); } XmlUtil.ValidateXsiType(reader, "NCName", XmlSchema.Namespace); reader.MoveToElement(); string value = reader.ReadElementContentAsString(); return new Saml2Id(value); } catch (Exception e) { if (System.Runtime.Fx.IsFatal(e)) throw; Exception wrapped = TryWrapReadException(reader, e); if (null == wrapped) { throw; } else { throw wrapped; } } } // Reads an element with simple content anyURI. Since this is SAML, // restricts the URI to absolute. internal static Uri ReadSimpleUriElement(XmlReader reader) { return ReadSimpleUriElement(reader, UriKind.Absolute); } // Reads an element with simple content anyURI where a UriKind can be specified internal static Uri ReadSimpleUriElement(XmlReader reader, UriKind kind) { return ReadSimpleUriElement(reader, kind, false); } // allow lax reading of relative URIs in some instances for interop internal static Uri ReadSimpleUriElement(XmlReader reader, UriKind kind, bool allowLaxReading) { Fx.Assert(reader.IsStartElement(), "reader is not on start element"); try { if (reader.IsEmptyElement) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID3061, reader.LocalName, reader.NamespaceURI)); } XmlUtil.ValidateXsiType(reader, "anyURI", XmlSchema.Namespace); reader.MoveToElement(); string value = reader.ReadElementContentAsString(); if (string.IsNullOrEmpty(value)) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID0022)); } if (!allowLaxReading && !UriUtil.CanCreateValidUri(value, kind)) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(kind == UriKind.RelativeOrAbsolute ? SR.ID0019 : SR.ID0013)); } return new Uri(value, kind); } catch (Exception e) { if (System.Runtime.Fx.IsFatal(e)) throw; Exception wrapped = TryWrapReadException(reader, e); if (null == wrapped) { throw; } else { throw wrapped; } } } /// /// Creates the conditions for the assertion. /// /// /// /// Generally, conditions should be included in assertions to limit the /// impact of misuse of the assertion. Specifying the NotBefore and /// NotOnOrAfter conditions can limit the period of vulnerability in /// the case of a compromised assertion. The AudienceRestrictionCondition /// can be used to explicitly state the intended relying party or parties /// of the assertion, which coupled with appropriate audience restriction /// enforcement at relying parties can help to mitigate spoofing attacks /// between relying parties. /// /// /// The default implementation creates NotBefore and NotOnOrAfter conditions /// based on the tokenDescriptor.Lifetime. It will also generate an /// AudienceRestrictionCondition limiting consumption of the assertion to /// tokenDescriptor.Scope.Address. /// /// /// Lifetime of the Token. /// The endpoint address to who the token is created. The address /// is modeled as an AudienceRestriction condition. /// The token descriptor. /// A Saml2Conditions object. protected virtual Saml2Conditions CreateConditions(Lifetime tokenLifetime, string relyingPartyAddress, SecurityTokenDescriptor tokenDescriptor) { bool hasLifetime = null != tokenLifetime; bool hasScope = !string.IsNullOrEmpty(relyingPartyAddress); if (!hasLifetime && !hasScope) { return null; } Saml2Conditions conditions = new Saml2Conditions(); if (hasLifetime) { conditions.NotBefore = tokenLifetime.Created; conditions.NotOnOrAfter = tokenLifetime.Expires; } if (hasScope) { conditions.AudienceRestrictions.Add(new Saml2AudienceRestriction(new Uri(relyingPartyAddress))); } return conditions; } /// /// Creates the advice for the assertion. /// /// /// By default, this method returns null. /// /// The token descriptor. /// A Saml2Advice object, default is null. protected virtual Saml2Advice CreateAdvice(SecurityTokenDescriptor tokenDescriptor) { return null; } /// /// Creates a name identifier that identifies the assertion issuer. /// /// /// /// SAML2 assertions must contain a name identifier for the issuer. /// This method may not return null. /// /// /// The default implementation creates a simple name identifier /// from the tokenDescriptor.Issuer. /// /// /// The token descriptor. /// A from the tokenDescriptor protected virtual Saml2NameIdentifier CreateIssuerNameIdentifier(SecurityTokenDescriptor tokenDescriptor) { if (null == tokenDescriptor) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("tokenDescriptor"); } string issuerName = tokenDescriptor.TokenIssuerName; // Must have an issuer if (string.IsNullOrEmpty(issuerName)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID4138))); } return new Saml2NameIdentifier(issuerName); } /// /// Generates a Saml2Attribute from a claim. /// /// The from which to generate a . /// Contains all the information that is used in token issuance. /// A based on the claim. /// The parameter 'claim' is null. protected virtual Saml2Attribute CreateAttribute(Claim claim, SecurityTokenDescriptor tokenDescriptor) { if (claim == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("claim"); } Saml2Attribute attribute = new Saml2Attribute(claim.Type, claim.Value); if (!StringComparer.Ordinal.Equals(ClaimsIdentity.DefaultIssuer, claim.OriginalIssuer)) { attribute.OriginalIssuer = claim.OriginalIssuer; } attribute.AttributeValueXsiType = claim.ValueType; if (claim.Properties.ContainsKey(ClaimProperties.SamlAttributeNameFormat)) { string nameFormat = claim.Properties[ClaimProperties.SamlAttributeNameFormat]; if (!UriUtil.CanCreateValidUri(nameFormat, UriKind.Absolute)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("nameFormat", SR.GetString(SR.ID0013)); } attribute.NameFormat = new Uri(nameFormat); } if (claim.Properties.ContainsKey(ClaimProperties.SamlAttributeDisplayName)) { attribute.FriendlyName = claim.Properties[ClaimProperties.SamlAttributeDisplayName]; } return attribute; } /// /// Creates from a and a /// /// This method may return null if the token descriptor does not contain any subject or the subject does not have any claims. /// /// The that contains claims which will be converted to SAML Attributes. /// The that contains information on building the . /// A Saml2AttributeStatement. protected virtual Saml2AttributeStatement CreateAttributeStatement(ClaimsIdentity subject, SecurityTokenDescriptor tokenDescriptor) { if (subject == null) { return null; } // We treat everything else as an Attribute except the nameId claim, which is already processed // for saml2subject // AuthenticationInstant and AuthenticationType are not converted to Claims if (subject.Claims != null) { List attributes = new List(); foreach (Claim claim in subject.Claims) { if (claim != null && claim.Type != ClaimTypes.NameIdentifier) { switch (claim.Type) { case ClaimTypes.AuthenticationInstant: case ClaimTypes.AuthenticationMethod: break; default: attributes.Add(this.CreateAttribute(claim, tokenDescriptor)); break; } } } this.AddDelegateToAttributes(subject, attributes, tokenDescriptor); ICollection collectedAttributes = this.CollectAttributeValues(attributes); if (collectedAttributes.Count > 0) { return new Saml2AttributeStatement(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. /// A of with common attributes collected into value lists. protected virtual ICollection CollectAttributeValues(ICollection attributes) { Dictionary distinctAttributes = new Dictionary(attributes.Count, new SamlAttributeKeyComparer()); // Use unique attribute if name, value type, or issuer differ foreach (Saml2Attribute saml2Attribute in attributes) { if (saml2Attribute != null) { SamlAttributeKeyComparer.AttributeKey attributeKey = new SamlAttributeKeyComparer.AttributeKey(saml2Attribute); if (distinctAttributes.ContainsKey(attributeKey)) { foreach (string attributeValue in saml2Attribute.Values) { distinctAttributes[attributeKey].Values.Add(attributeValue); } } else { distinctAttributes.Add(attributeKey, saml2Attribute); } } } return distinctAttributes.Values; } /// /// Adds all the delegates associated with the subject into the attribute collection. /// /// The delegate of this will be serialized into a . /// A of . /// The that contains information on building the delegate. 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(this.CreateAttribute(claim, tokenDescriptor)); } } this.AddDelegateToAttributes(subject.Actor, actingAsAttributes, tokenDescriptor); ICollection collectedAttributes = this.CollectAttributeValues(actingAsAttributes); attributes.Add(this.CreateAttribute(new Claim(ClaimTypes.Actor, this.CreateXmlStringFromAttributes(collectedAttributes), ClaimValueTypes.String), tokenDescriptor)); } /// /// Builds an XML formatted string from a collection of SAML attributes that represent the Actor. /// /// An enumeration of Saml2Attributes. /// A well-formed XML string. /// The string is of the form ""<Actor><Attribute name, ns><AttributeValue>...</AttributeValue>, ...</Attribute>...</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 (Saml2Attribute samlAttribute in attributes) { if (samlAttribute != null) { if (!actorElementWritten) { dicWriter.WriteStartElement(Actor); actorElementWritten = true; } this.WriteAttribute(dicWriter, samlAttribute); } } if (actorElementWritten) { dicWriter.WriteEndElement(); } dicWriter.Flush(); } return Encoding.UTF8.GetString(ms.ToArray()); } } /// /// Creates an of to be included in the assertion. /// /// /// /// Statements are not required in a SAML2 assertion. This method may /// return an empty collection. /// /// /// The that contains information on creating the . /// An enumeration of Saml2Statements. protected virtual IEnumerable CreateStatements(SecurityTokenDescriptor tokenDescriptor) { if (tokenDescriptor == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("tokenDescriptor"); } Collection statements = new Collection(); Saml2AttributeStatement attributeStatement = this.CreateAttributeStatement(tokenDescriptor.Subject, tokenDescriptor); if (attributeStatement != null) { statements.Add(attributeStatement); } Saml2AuthenticationStatement authenticationStatement = this.CreateAuthenticationStatement(tokenDescriptor.AuthenticationInfo, tokenDescriptor); if (authenticationStatement != null) { statements.Add(authenticationStatement); } return statements; } /// /// Given an AuthenticationInformation object, this routine creates a Saml2AuthenticationStatement /// to be added to the Saml2Assertion that is produced by the factory. /// /// /// An AuthenticationInformation object containing the state to be wrapped as a Saml2AuthenticationStatement /// object. /// /// The token descriptor. /// /// The Saml2AuthenticationStatement to add to the assertion being created or null to ignore the AuthenticationInformation /// being wrapped as a statement. /// protected virtual Saml2AuthenticationStatement CreateAuthenticationStatement(AuthenticationInformation authInfo, SecurityTokenDescriptor tokenDescriptor) { 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", "SAML2")); } else if (authenticationInstant == null) { throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4270, "AuthenticationInstant", "SAML2")); } Uri saml2AuthenticationClass; if (!UriUtil.TryCreateValidUri(this.DenormalizeAuthenticationType(authenticationMethod), UriKind.Absolute, out saml2AuthenticationClass)) { throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4185, authenticationMethod)); } Saml2AuthenticationContext authCtx = new Saml2AuthenticationContext(saml2AuthenticationClass); DateTime authInstantTime = DateTime.ParseExact(authenticationInstant, DateTimeFormats.Accepted, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None).ToUniversalTime(); Saml2AuthenticationStatement authnStatement = new Saml2AuthenticationStatement(authCtx, authInstantTime); if (authInfo != null) { if (!string.IsNullOrEmpty(authInfo.DnsName) || !string.IsNullOrEmpty(authInfo.Address)) { authnStatement.SubjectLocality = new Saml2SubjectLocality(authInfo.Address, authInfo.DnsName); } if (!string.IsNullOrEmpty(authInfo.Session)) { authnStatement.SessionIndex = authInfo.Session; } authnStatement.SessionNotOnOrAfter = authInfo.NotOnOrAfter; } return authnStatement; } /// /// Creates a SAML2 subject of the assertion. /// /// The security token descriptor to create the subject. /// Thrown when 'tokenDescriptor' is null. /// A Saml2Subject. protected virtual Saml2Subject CreateSamlSubject(SecurityTokenDescriptor tokenDescriptor) { if (null == tokenDescriptor) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("tokenDescriptor"); } Saml2Subject saml2Subject = new Saml2Subject(); // Look for name identifier claims string nameIdentifierClaim = null; string nameIdentifierFormat = null; string nameIdentifierNameQualifier = null; string nameIdentifierSpProviderId = null; string nameIdentifierSpNameQualifier = 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 != nameIdentifierClaim) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID4139))); } nameIdentifierClaim = claim.Value; if (claim.Properties.ContainsKey(ClaimProperties.SamlNameIdentifierFormat)) { nameIdentifierFormat = claim.Properties[ClaimProperties.SamlNameIdentifierFormat]; } if (claim.Properties.ContainsKey(ClaimProperties.SamlNameIdentifierNameQualifier)) { nameIdentifierNameQualifier = claim.Properties[ClaimProperties.SamlNameIdentifierNameQualifier]; } if (claim.Properties.ContainsKey(ClaimProperties.SamlNameIdentifierSPNameQualifier)) { nameIdentifierSpNameQualifier = claim.Properties[ClaimProperties.SamlNameIdentifierSPNameQualifier]; } if (claim.Properties.ContainsKey(ClaimProperties.SamlNameIdentifierSPProvidedId)) { nameIdentifierSpProviderId = claim.Properties[ClaimProperties.SamlNameIdentifierSPProvidedId]; } } } } if (nameIdentifierClaim != null) { Saml2NameIdentifier nameIdentifier = new Saml2NameIdentifier(nameIdentifierClaim); if (nameIdentifierFormat != null && UriUtil.CanCreateValidUri(nameIdentifierFormat, UriKind.Absolute)) { nameIdentifier.Format = new Uri(nameIdentifierFormat); } nameIdentifier.NameQualifier = nameIdentifierNameQualifier; nameIdentifier.SPNameQualifier = nameIdentifierSpNameQualifier; nameIdentifier.SPProvidedId = nameIdentifierSpProviderId; saml2Subject.NameId = nameIdentifier; } // Add subject confirmation data Saml2SubjectConfirmation subjectConfirmation; if (null == tokenDescriptor.Proof) { subjectConfirmation = new Saml2SubjectConfirmation(Saml2Constants.ConfirmationMethods.Bearer); } else { subjectConfirmation = new Saml2SubjectConfirmation(Saml2Constants.ConfirmationMethods.HolderOfKey, new Saml2SubjectConfirmationData()); subjectConfirmation.SubjectConfirmationData.KeyIdentifiers.Add(tokenDescriptor.Proof.KeyIdentifier); } saml2Subject.SubjectConfirmations.Add(subjectConfirmation); return saml2Subject; } /// /// Override this method to change the token encrypting credentials. /// /// Retrieve some scope encrypting credentials from the Scope object /// the token encrypting credentials /// When the given tokenDescriptor is null 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. /// /// /// /// SAML2 assertions used as security tokens should be signed. /// /// /// The default implementation uses the /// tokenDescriptor.Scope.SigningCredentials. /// /// /// The token descriptor. /// The signing credential. protected virtual SigningCredentials GetSigningCredentials(SecurityTokenDescriptor tokenDescriptor) { if (null == tokenDescriptor) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("tokenDescriptor"); } return tokenDescriptor.SigningCredentials; } /// /// Rejects tokens that are not valid. /// /// /// The token may not be valid for a number of reasons. For example, the /// current time may not be within the token's validity period, the /// token may contain data that is contradictory or not valid, or the token /// may contain unsupported SAML2 elements. /// /// SAML 2.0 condition to be validated. /// True to check for Audience Restriction condition. protected virtual void ValidateConditions(Saml2Conditions conditions, bool enforceAudienceRestriction) { if (conditions != null) { DateTime now = DateTime.UtcNow; if (conditions.NotBefore != null && DateTimeUtil.Add(now, Configuration.MaxClockSkew) < conditions.NotBefore.Value) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenNotYetValidException(SR.GetString(SR.ID4147, conditions.NotBefore.Value, now))); } if (conditions.NotOnOrAfter != null && DateTimeUtil.Add(now, Configuration.MaxClockSkew.Negate()) >= conditions.NotOnOrAfter.Value) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenExpiredException(SR.GetString(SR.ID4148, conditions.NotOnOrAfter.Value, now))); } if (conditions.OneTimeUse) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenValidationException(SR.GetString(SR.ID4149))); } if (conditions.ProxyRestriction != null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenValidationException(SR.GetString(SR.ID4150))); } } if (enforceAudienceRestriction) { if (this.Configuration == null || this.Configuration.AudienceRestriction.AllowedAudienceUris.Count == 0) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID1032))); } if (conditions == null || conditions.AudienceRestrictions.Count == 0) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new AudienceUriValidationFailedException(SR.GetString(SR.ID1035))); } else { foreach (Saml2AudienceRestriction audienceRestriction in conditions.AudienceRestrictions) { SamlSecurityTokenRequirement.ValidateAudienceRestriction(this.Configuration.AudienceRestriction.AllowedAudienceUris, audienceRestriction.Audiences); } } } } /// /// 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. protected virtual string FindUpn(ClaimsIdentity claimsIdentity) { return ClaimsHelper.FindUpn(claimsIdentity); } /// /// Returns the Saml2 AuthenticationContext matching a normalized value. /// /// Normalized value. /// A string that represents the denormalized authentication type used to obtain the token. protected virtual string DenormalizeAuthenticationType(string normalizedAuthenticationType) { return AuthenticationTypeMaps.Denormalize(normalizedAuthenticationType, AuthenticationTypeMaps.Saml2); } /// /// Throws if a token is detected as being replayed. If the token is not found, it is added to the /// . /// /// The token to detect for replay. /// The input argument 'token' is null. /// Configuration or Configuration.TokenReplayCache property is null. /// The input argument 'token' can not be cast as a 'Saml2SecurityToken'. /// The Saml2SecurityToken.Assertion.Id.Value is null or empty. /// 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"); } Saml2SecurityToken samlToken = token as Saml2SecurityToken; if (null == samlToken) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("token", SR.GetString(SR.ID1064, 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.Id.Value)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenValidationException(SR.GetString(SR.ID1065))); } StringBuilder stringBuilder = new StringBuilder(); string key; using (HashAlgorithm hashAlgorithm = CryptoHelper.NewSha256HashAlgorithm()) { if (string.IsNullOrEmpty(samlToken.Assertion.Issuer.Value)) { stringBuilder.AppendFormat("{0}{1}", samlToken.Assertion.Id.Value, tokenTypeIdentifiers[0]); } else { stringBuilder.AppendFormat("{0}{1}{2}", samlToken.Assertion.Id.Value, samlToken.Assertion.Issuer.Value, tokenTypeIdentifiers[0]); } key = Convert.ToBase64String(hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes(stringBuilder.ToString()))); } if (Configuration.Caches.TokenReplayCache.Contains(key)) { string issuer = (samlToken.Assertion.Issuer.Value != null) ? samlToken.Assertion.Issuer.Value : String.Empty; throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenReplayDetectedException(SR.GetString(SR.ID1066, typeof(Saml2SecurityToken).ToString(), samlToken.Assertion.Id.Value, issuer))); } else { Configuration.Caches.TokenReplayCache.AddOrUpdate(key, token, DateTimeUtil.Add(this.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 Saml2SecurityToken's validity period is greater than the expiration period set to TokenReplayCache. /// A DateTime representing the expiration time. /// By default, this function returns the NotOnOrAfter of the SAML Condition if present. /// If that value does not exist, it returns the NotOnOrAfter of the first SubjectConfirmationData. /// This function will never return a value further from now than Configuration.TokenReplayCacheExpirationPeriod. protected virtual DateTime GetTokenReplayCacheEntryExpirationTime(Saml2SecurityToken token) { if (token == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("token"); } DateTime? tokenExpiration = null; Saml2Assertion assertion = token.Assertion; if (assertion != null) { if (assertion.Conditions != null && assertion.Conditions.NotOnOrAfter.HasValue) { // The Condition has a NotOnOrAfter set, use that. tokenExpiration = assertion.Conditions.NotOnOrAfter.Value; } else if (assertion.Subject != null && assertion.Subject.SubjectConfirmations != null && assertion.Subject.SubjectConfirmations.Count != 0 && assertion.Subject.SubjectConfirmations[0].SubjectConfirmationData != null && assertion.Subject.SubjectConfirmations[0].SubjectConfirmationData.NotOnOrAfter.HasValue) { // The condition did not have NotOnOrAfter set, but SCD[0] has a NotOnOrAfter set, use that. tokenExpiration = assertion.Subject.SubjectConfirmations[0].SubjectConfirmationData.NotOnOrAfter.Value; } } // DateTimeUtil handles overflows DateTime maximumExpirationTime = DateTimeUtil.Add(DateTime.UtcNow, Configuration.TokenReplayCacheExpirationPeriod); // Use DateTime.MaxValue as expiration value for tokens without expiration tokenExpiration = tokenExpiration ?? DateTime.MaxValue; // If the refined token validity period is greater than the TokenReplayCacheExpirationPeriod, throw if (DateTime.Compare(maximumExpirationTime, tokenExpiration.Value) < 0) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new SecurityTokenValidationException(SR.GetString(SR.ID1069, tokenExpiration.Value.ToString(), Configuration.TokenReplayCacheExpirationPeriod.ToString()))); } return tokenExpiration.Value; } /// /// Returns the normalized value matching a SAML2 AuthenticationContext class reference. /// /// A string representing the /// Normalized value. protected virtual string NormalizeAuthenticationContextClassReference(string saml2AuthenticationContextClassReference) { return AuthenticationTypeMaps.Normalize(saml2AuthenticationContextClassReference, AuthenticationTypeMaps.Saml2); } /// /// Creates claims from the Saml2Subject. /// /// The Saml2Subject. /// The ClaimsIdentity subject. /// The issuer. protected virtual void ProcessSamlSubject(Saml2Subject assertionSubject, ClaimsIdentity subject, string issuer) { if (assertionSubject == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("assertionSubject"); } if (subject == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("subject"); } Saml2NameIdentifier nameId = assertionSubject.NameId; if (nameId != null) { Claim claim = new Claim(ClaimTypes.NameIdentifier, nameId.Value, ClaimValueTypes.String, issuer); if (nameId.Format != null) { claim.Properties[ClaimProperties.SamlNameIdentifierFormat] = nameId.Format.AbsoluteUri; } if (nameId.NameQualifier != null) { claim.Properties[ClaimProperties.SamlNameIdentifierNameQualifier] = nameId.NameQualifier; } if (nameId.SPNameQualifier != null) { claim.Properties[ClaimProperties.SamlNameIdentifierSPNameQualifier] = nameId.SPNameQualifier; } if (nameId.SPProvidedId != null) { claim.Properties[ClaimProperties.SamlNameIdentifierSPProvidedId] = nameId.SPProvidedId; } subject.AddClaim(claim); } } /// /// Creates claims from a Saml2AttributeStatement. /// /// The Saml2AttributeStatement. /// The subject. /// The issuer. protected virtual void ProcessAttributeStatement(Saml2AttributeStatement statement, ClaimsIdentity subject, string issuer) { if (statement == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("statement"); } if (subject == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("subject"); } foreach (Saml2Attribute attribute in statement.Attributes) { if (StringComparer.Ordinal.Equals(attribute.Name, ClaimTypes.Actor)) { if (subject.Actor != null) { throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4218)); } this.SetDelegateFromAttribute(attribute, subject, issuer); } else { foreach (string value in attribute.Values) { if (value != null) { string originalIssuer = issuer; if (attribute.OriginalIssuer != null) { originalIssuer = attribute.OriginalIssuer; } Claim claim = new Claim(attribute.Name, value, attribute.AttributeValueXsiType, issuer, originalIssuer); if (attribute.NameFormat != null) { claim.Properties[ClaimProperties.SamlAttributeNameFormat] = attribute.NameFormat.AbsoluteUri; } if (attribute.FriendlyName != null) { claim.Properties[ClaimProperties.SamlAttributeDisplayName] = attribute.FriendlyName; } subject.AddClaim(claim); } } } } } /// /// This method gets called when a special type of Saml2Attribute is detected. The Saml2Attribute passed in /// wraps a Saml2Attribute that contains a collection of AttributeValues, each of which will get mapped to a /// claim. All of the claims will be returned in an ClaimsIdentity with the specified issuer. /// /// The to use. /// The that is the subject of this token. /// The issuer of the claim. /// Will be thrown if the Saml2Attribute does not contain any /// valid Saml2AttributeValues. /// protected virtual void SetDelegateFromAttribute(Saml2Attribute attribute, ClaimsIdentity subject, string issuer) { // bail here; nothing to add. if (subject == null || attribute == null || attribute.Values == null || attribute.Values.Count < 1) { return; } Saml2Attribute actingAsAttribute = null; Collection claims = new Collection(); foreach (string attributeValue in attribute.Values) { if (attributeValue != null) { using (XmlDictionaryReader dicReader = XmlDictionaryReader.CreateTextReader(Encoding.UTF8.GetBytes(attributeValue), XmlDictionaryReaderQuotas.Max)) { dicReader.MoveToContent(); dicReader.ReadStartElement(Actor); while (dicReader.IsStartElement(Attribute)) { Saml2Attribute innerAttribute = this.ReadAttribute(dicReader); if (innerAttribute != null) { if (innerAttribute.Name == 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.ID4218)); } actingAsAttribute = innerAttribute; } else { string originalIssuer = innerAttribute.OriginalIssuer; for (int k = 0; k < innerAttribute.Values.Count; ++k) { Claim claim = null; if (string.IsNullOrEmpty(originalIssuer)) { claim = new Claim(innerAttribute.Name, innerAttribute.Values[k], innerAttribute.AttributeValueXsiType, issuer); } else { claim = new Claim(innerAttribute.Name, innerAttribute.Values[k], innerAttribute.AttributeValueXsiType, issuer, originalIssuer); } if (innerAttribute.NameFormat != null) { claim.Properties[ClaimProperties.SamlAttributeNameFormat] = innerAttribute.NameFormat.AbsoluteUri; } if (innerAttribute.FriendlyName != null) { claim.Properties[ClaimProperties.SamlAttributeDisplayName] = innerAttribute.FriendlyName; } claims.Add(claim); } } } } dicReader.ReadEndElement(); // Actor } } } subject.Actor = new ClaimsIdentity(claims, AuthenticationTypes.Federation); this.SetDelegateFromAttribute(actingAsAttribute, subject.Actor, issuer); } /// /// Creates claims from a Saml2AuthenticationStatement. /// /// The Saml2AuthenticationStatement. /// The subject. /// The issuer. protected virtual void ProcessAuthenticationStatement(Saml2AuthenticationStatement statement, ClaimsIdentity subject, string issuer) { if (subject == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("subject"); } if (statement.AuthenticationContext.DeclarationReference != null) { throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4180)); } if (statement.AuthenticationContext.ClassReference != null) { subject.AddClaim( new Claim( ClaimTypes.AuthenticationMethod, this.NormalizeAuthenticationContextClassReference(statement.AuthenticationContext.ClassReference.AbsoluteUri), ClaimValueTypes.String, issuer)); } subject.AddClaim(new Claim(ClaimTypes.AuthenticationInstant, XmlConvert.ToString(statement.AuthenticationInstant.ToUniversalTime(), DateTimeFormats.Generated), ClaimValueTypes.DateTime, issuer)); } /// /// Creates claims from a Saml2AuthorizationDecisionStatement. /// /// The Saml2AuthorizationDecisionStatement. /// The subject. /// The issuer. protected virtual void ProcessAuthorizationDecisionStatement(Saml2AuthorizationDecisionStatement statement, ClaimsIdentity subject, string issuer) { } /// /// Processes all statements to generate claims. /// /// A collection of Saml2Statement. /// The subject. /// The issuer. protected virtual void ProcessStatement(Collection statements, ClaimsIdentity subject, string issuer) { Collection authnStatements = new Collection(); foreach (Saml2Statement statement in statements) { Saml2AttributeStatement attrStatement = statement as Saml2AttributeStatement; if (attrStatement != null) { this.ProcessAttributeStatement(attrStatement, subject, issuer); continue; } Saml2AuthenticationStatement authnStatement = statement as Saml2AuthenticationStatement; if (authnStatement != null) { authnStatements.Add(authnStatement); continue; } Saml2AuthorizationDecisionStatement authzStatement = statement as Saml2AuthorizationDecisionStatement; if (authzStatement != null) { this.ProcessAuthorizationDecisionStatement(authzStatement, subject, issuer); continue; } // We don't process custom statements. Just fall through. } foreach (Saml2AuthenticationStatement authStatement in authnStatements) { if (authStatement != null) { this.ProcessAuthenticationStatement(authStatement, subject, issuer); } } } /// /// Creates claims from a Saml2 token. /// /// The Saml2SecurityToken. /// An IClaimIdentity. protected virtual ClaimsIdentity CreateClaims(Saml2SecurityToken samlToken) { if (samlToken == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("samlToken"); } ClaimsIdentity subject = new ClaimsIdentity(AuthenticationTypes.Federation, SamlSecurityTokenRequirement.NameClaimType, SamlSecurityTokenRequirement.RoleClaimType); Saml2Assertion assertion = samlToken.Assertion; if (assertion == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("samlToken", SR.GetString(SR.ID1034)); } if (this.Configuration == null) { throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4274)); } if (this.Configuration.IssuerNameRegistry == null) { throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4277)); } string issuer = this.Configuration.IssuerNameRegistry.GetIssuerName(samlToken.IssuerToken, assertion.Issuer.Value); if (string.IsNullOrEmpty(issuer)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4175))); } this.ProcessSamlSubject(assertion.Subject, subject, issuer); this.ProcessStatement(assertion.Statements, subject, issuer); return subject; } /// /// Validates the Saml2SubjectConfirmation data. /// /// The Saml2 subject confirmation data. protected virtual void ValidateConfirmationData(Saml2SubjectConfirmationData confirmationData) { if (null == confirmationData) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("confirmationData"); } if (null != confirmationData.Address) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4153))); } if (null != confirmationData.InResponseTo) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4154))); } if (null != confirmationData.Recipient) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4157))); } DateTime now = DateTime.UtcNow; if (null != confirmationData.NotBefore && DateTimeUtil.Add(now, Configuration.MaxClockSkew) < confirmationData.NotBefore.Value) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4176, confirmationData.NotBefore.Value, now))); } if (null != confirmationData.NotOnOrAfter && DateTimeUtil.Add(now, Configuration.MaxClockSkew.Negate()) >= confirmationData.NotOnOrAfter.Value) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4177, confirmationData.NotOnOrAfter.Value, now))); } } /// /// Resolves the collection of referenced in a . /// /// to process. /// to use in resolving the . /// A read only collection of contained in the assertion. protected virtual ReadOnlyCollection ResolveSecurityKeys(Saml2Assertion assertion, SecurityTokenResolver resolver) { if (null == assertion) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("assertion"); } // Must have Subject Saml2Subject subject = assertion.Subject; if (null == subject) { // No Subject throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4130))); } // Must have one SubjectConfirmation if (0 == subject.SubjectConfirmations.Count) { // No SubjectConfirmation throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4131))); } if (subject.SubjectConfirmations.Count > 1) { // More than one SubjectConfirmation throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4132))); } // Extract the keys for the given method ReadOnlyCollection securityKeys; Saml2SubjectConfirmation subjectConfirmation = subject.SubjectConfirmations[0]; // For bearer, ensure there are no keys, set the collection to empty // For HolderOfKey, ensure there is at least one key, resolve and create collection if (Saml2Constants.ConfirmationMethods.Bearer == subjectConfirmation.Method) { if (null != subjectConfirmation.SubjectConfirmationData && 0 != subjectConfirmation.SubjectConfirmationData.KeyIdentifiers.Count) { // Bearer but has keys throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4133))); } securityKeys = EmptyReadOnlyCollection.Instance; } else if (Saml2Constants.ConfirmationMethods.HolderOfKey == subjectConfirmation.Method) { if (null == subjectConfirmation.SubjectConfirmationData || 0 == subjectConfirmation.SubjectConfirmationData.KeyIdentifiers.Count) { // Holder-of-key but no keys throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4134))); } List holderKeys = new List(); SecurityKey key; foreach (SecurityKeyIdentifier keyIdentifier in subjectConfirmation.SubjectConfirmationData.KeyIdentifiers) { key = null; // Try the resolver first foreach (SecurityKeyIdentifierClause clause in keyIdentifier) { if (null != resolver && resolver.TryResolveSecurityKey(clause, out key)) { holderKeys.Add(key); break; } } // If that doesn't work, try to create the key (e.g. bare RSA or X509 raw) if (null == key) { if (keyIdentifier.CanCreateKey) { key = keyIdentifier.CreateKey(); holderKeys.Add(key); } else { holderKeys.Add(new SecurityKeyElement(keyIdentifier, resolver)); } } } securityKeys = holderKeys.AsReadOnly(); } else { // SenderVouches, as well as other random things, aren't accepted throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4136, subjectConfirmation.Method))); } return securityKeys; } /// /// 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. /// Unable to resolve token. protected virtual SecurityToken ResolveIssuerToken(Saml2Assertion assertion, SecurityTokenResolver issuerResolver) { if (null == assertion) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("assertion"); } SecurityToken token; if (this.TryResolveIssuerToken(assertion, issuerResolver, out token)) { return token; } else { string exceptionMessage = SR.GetString(assertion.SigningCredentials == null ? SR.ID4141 : SR.ID4142); throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(exceptionMessage)); } } /// /// 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. protected virtual bool TryResolveIssuerToken(Saml2Assertion 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; } } /// /// This handles the construct used in <Subject> and <SubjectConfirmation> for ID: /// /// /// /// /// /// /// A reader positioned at a element. /// The parent element this SubjectID is part of. /// A constructed from the XML. protected virtual Saml2NameIdentifier ReadSubjectId(XmlReader reader, string parentElement) { // , , if (reader.IsStartElement(Saml2Constants.Elements.NameID, Saml2Constants.Namespace)) { return this.ReadNameId(reader); } else if (reader.IsStartElement(Saml2Constants.Elements.EncryptedID, Saml2Constants.Namespace)) { return this.ReadEncryptedId(reader); } else if (reader.IsStartElement(Saml2Constants.Elements.BaseID, Saml2Constants.Namespace)) { // Since BaseID is an abstract type, we have to switch off the xsi:type declaration XmlQualifiedName declaredType = XmlUtil.GetXsiType(reader); // No declaration, or declaring that this is just a "BaseID", is invalid since // statement is abstract if (null == declaredType || XmlUtil.EqualsQName(declaredType, Saml2Constants.Types.BaseIDAbstractType, Saml2Constants.Namespace)) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4104, reader.LocalName, reader.NamespaceURI)); } // If it's NameID we can handle it if (XmlUtil.EqualsQName(declaredType, Saml2Constants.Types.NameIDType, Saml2Constants.Namespace)) { return this.ReadNameIdType(reader); } else { // Instruct the user to override to handle custom throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4110, parentElement, declaredType.Name, declaredType.Namespace)); } } return null; } /// /// Reads the <saml:Action> element. /// /// A positioned at a element. /// A instance. protected virtual Saml2Action ReadAction(XmlReader reader) { if (null == reader) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } // throw if wrong element if (!reader.IsStartElement(Saml2Constants.Elements.Action, Saml2Constants.Namespace)) { reader.ReadStartElement(Saml2Constants.Elements.Action, Saml2Constants.Namespace); } // disallow empty if (reader.IsEmptyElement) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID3061, Saml2Constants.Elements.Action, Saml2Constants.Namespace)); } try { // Need the content to instantiate, so use locals Uri actionNamespace; // @attributes string attributeValue; // @xsi:type XmlUtil.ValidateXsiType(reader, Saml2Constants.Types.ActionType, Saml2Constants.Namespace); // @Namespace - required attributeValue = reader.GetAttribute(Saml2Constants.Attributes.Namespace); if (string.IsNullOrEmpty(attributeValue)) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID0001, Saml2Constants.Attributes.Namespace, Saml2Constants.Elements.Action)); } if (!UriUtil.CanCreateValidUri(attributeValue, UriKind.Absolute)) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID0011, Saml2Constants.Attributes.Namespace, Saml2Constants.Elements.Action)); } actionNamespace = new Uri(attributeValue); // Content is string return new Saml2Action(reader.ReadElementString(), actionNamespace); } catch (Exception e) { if (System.Runtime.Fx.IsFatal(e)) throw; Exception wrapped = TryWrapReadException(reader, e); if (null == wrapped) { throw; } else { throw wrapped; } } } /// /// Writes the <saml:Action> element. /// /// A to serialize the . /// The to serialize. protected virtual void WriteAction(XmlWriter writer, Saml2Action data) { if (null == writer) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (null == data) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data"); } if (null == data.Namespace) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data.Namespace"); } if (string.IsNullOrEmpty(data.Namespace.ToString())) { throw DiagnosticUtility.ThrowHelperArgumentNullOrEmptyString("data.Namespace"); } // writer.WriteStartElement(Saml2Constants.Elements.Action, Saml2Constants.Namespace); // @Namespace - required writer.WriteAttributeString(Saml2Constants.Attributes.Namespace, data.Namespace.AbsoluteUri); // String content writer.WriteString(data.Value); // writer.WriteEndElement(); } /// /// Reads the <saml:Advice> element. /// /// /// /// The Advice element has an extensibility point to allow XML elements /// from non-SAML2 namespaces to be included. By default, because the /// Advice may be ignored without affecting the semantics of the /// assertion, any such elements are ignored. To handle the processing /// of those elements, override this method. /// /// /// A positioned at a element. /// A instance. protected virtual Saml2Advice ReadAdvice(XmlReader reader) { if (null == reader) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } // throw if wrong element if (!reader.IsStartElement(Saml2Constants.Elements.Advice, Saml2Constants.Namespace)) { reader.ReadStartElement(Saml2Constants.Elements.Advice, Saml2Constants.Namespace); } try { Saml2Advice advice = new Saml2Advice(); bool isEmpty = reader.IsEmptyElement; // @attributes // @xsi:type XmlUtil.ValidateXsiType(reader, Saml2Constants.Types.AdviceType, Saml2Constants.Namespace); reader.Read(); if (!isEmpty) { // 0-OO while (reader.IsStartElement()) { // , , , if (reader.IsStartElement(Saml2Constants.Elements.AssertionIDRef, Saml2Constants.Namespace)) { advice.AssertionIdReferences.Add(ReadSimpleNCNameElement(reader)); } else if (reader.IsStartElement(Saml2Constants.Elements.AssertionURIRef, Saml2Constants.Namespace)) { advice.AssertionUriReferences.Add(ReadSimpleUriElement(reader)); } else if (reader.IsStartElement(Saml2Constants.Elements.Assertion, Saml2Constants.Namespace)) { advice.Assertions.Add(this.ReadAssertion(reader)); } else if (reader.IsStartElement(Saml2Constants.Elements.EncryptedAssertion, Saml2Constants.Namespace)) { advice.Assertions.Add(this.ReadAssertion(reader)); } else { TraceUtility.TraceString(TraceEventType.Warning, SR.GetString(SR.ID8006, reader.LocalName, reader.NamespaceURI)); reader.Skip(); } } reader.ReadEndElement(); } return advice; } catch (Exception e) { if (System.Runtime.Fx.IsFatal(e)) throw; Exception wrapped = TryWrapReadException(reader, e); if (null == wrapped) { throw; } else { throw wrapped; } } } /// /// Writes the <saml:Advice> element. /// /// A to serialize the . /// The to serialize. protected virtual void WriteAdvice(XmlWriter writer, Saml2Advice data) { if (null == writer) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (null == data) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data"); } // writer.WriteStartElement(Saml2Constants.Elements.Advice, Saml2Constants.Namespace); // 0-OO foreach (Saml2Id id in data.AssertionIdReferences) { writer.WriteElementString(Saml2Constants.Elements.AssertionIDRef, Saml2Constants.Namespace, id.Value); } // 0-OO foreach (Uri uri in data.AssertionUriReferences) { writer.WriteElementString(Saml2Constants.Elements.AssertionURIRef, Saml2Constants.Namespace, uri.AbsoluteUri); } // 0-OO foreach (Saml2Assertion assertion in data.Assertions) { this.WriteAssertion(writer, assertion); } // writer.WriteEndElement(); } /// /// Reads the <saml:Assertion> element. /// /// A positioned at a element. /// A instance. protected virtual Saml2Assertion ReadAssertion(XmlReader reader) { if (null == reader) { 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)); } if (this.Configuration.ServiceTokenResolver == null) { throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4276)); } XmlDictionaryReader plaintextReader = XmlDictionaryReader.CreateDictionaryReader(reader); Saml2Assertion assertion = new Saml2Assertion(new Saml2NameIdentifier("__TemporaryIssuer__")); // If it's an EncryptedAssertion, we need to retrieve the plaintext // and repoint our reader if (reader.IsStartElement(Saml2Constants.Elements.EncryptedAssertion, Saml2Constants.Namespace)) { EncryptingCredentials encryptingCredentials = null; plaintextReader = CreatePlaintextReaderFromEncryptedData( plaintextReader, Configuration.ServiceTokenResolver, this.KeyInfoSerializer, assertion.ExternalEncryptedKeys, out encryptingCredentials); assertion.EncryptingCredentials = encryptingCredentials; } // Throw if wrong element if (!plaintextReader.IsStartElement(Saml2Constants.Elements.Assertion, Saml2Constants.Namespace)) { plaintextReader.ReadStartElement(Saml2Constants.Elements.Assertion, Saml2Constants.Namespace); } // disallow empty if (plaintextReader.IsEmptyElement) { #pragma warning suppress 56504 // bogus - thinks plaintextReader.LocalName, plaintextReader.NamespaceURI need validation throw DiagnosticUtility.ThrowHelperXml(plaintextReader, SR.GetString(SR.ID3061, plaintextReader.LocalName, plaintextReader.NamespaceURI)); } // Construct a wrapped serializer so that the EnvelopedSignatureReader's // attempt to read the will hit our ReadKeyInfo virtual. WrappedSerializer wrappedSerializer = new WrappedSerializer(this, assertion); // SAML supports enveloped signature, so we need to wrap our reader. // We do not dispose this reader, since as a delegating reader it would // dispose the inner reader, which we don't properly own. EnvelopedSignatureReader realReader = new EnvelopedSignatureReader(plaintextReader, wrappedSerializer, this.Configuration.IssuerTokenResolver, false, false, false); try { // Process @attributes string value; // @xsi:type XmlUtil.ValidateXsiType(realReader, Saml2Constants.Types.AssertionType, Saml2Constants.Namespace); // @Version - required - must be "2.0" string version = realReader.GetAttribute(Saml2Constants.Attributes.Version); if (string.IsNullOrEmpty(version)) { throw DiagnosticUtility.ThrowHelperXml(realReader, SR.GetString(SR.ID0001, Saml2Constants.Attributes.Version, Saml2Constants.Elements.Assertion)); } if (!StringComparer.Ordinal.Equals(assertion.Version, version)) { throw DiagnosticUtility.ThrowHelperXml(realReader, SR.GetString(SR.ID4100, version)); } // @ID - required value = realReader.GetAttribute(Saml2Constants.Attributes.ID); if (string.IsNullOrEmpty(value)) { throw DiagnosticUtility.ThrowHelperXml(realReader, SR.GetString(SR.ID0001, Saml2Constants.Attributes.ID, Saml2Constants.Elements.Assertion)); } assertion.Id = new Saml2Id(value); // @IssueInstant - required value = realReader.GetAttribute(Saml2Constants.Attributes.IssueInstant); if (string.IsNullOrEmpty(value)) { throw DiagnosticUtility.ThrowHelperXml(realReader, SR.GetString(SR.ID0001, Saml2Constants.Attributes.IssueInstant, Saml2Constants.Elements.Assertion)); } assertion.IssueInstant = XmlConvert.ToDateTime(value, DateTimeFormats.Accepted); // Process realReader.Read(); // 1 assertion.Issuer = this.ReadIssuer(realReader); // 0-1 realReader.TryReadSignature(); // 0-1 if (realReader.IsStartElement(Saml2Constants.Elements.Subject, Saml2Constants.Namespace)) { assertion.Subject = this.ReadSubject(realReader); } // 0-1 if (realReader.IsStartElement(Saml2Constants.Elements.Conditions, Saml2Constants.Namespace)) { assertion.Conditions = this.ReadConditions(realReader); } // 0-1 if (realReader.IsStartElement(Saml2Constants.Elements.Advice, Saml2Constants.Namespace)) { assertion.Advice = this.ReadAdvice(realReader); } // , 0-OO while (realReader.IsStartElement()) { Saml2Statement statement; if (realReader.IsStartElement(Saml2Constants.Elements.Statement, Saml2Constants.Namespace)) { statement = this.ReadStatement(realReader); } else if (realReader.IsStartElement(Saml2Constants.Elements.AttributeStatement, Saml2Constants.Namespace)) { statement = this.ReadAttributeStatement(realReader); } else if (realReader.IsStartElement(Saml2Constants.Elements.AuthnStatement, Saml2Constants.Namespace)) { statement = this.ReadAuthenticationStatement(realReader); } else if (realReader.IsStartElement(Saml2Constants.Elements.AuthzDecisionStatement, Saml2Constants.Namespace)) { statement = this.ReadAuthorizationDecisionStatement(realReader); } else { break; } assertion.Statements.Add(statement); } realReader.ReadEndElement(); if (null == assertion.Subject) { // An assertion with no statements MUST contain a element. [Saml2Core, line 585] if (0 == assertion.Statements.Count) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4106)); } // Furthermore, the built-in statement types all require the presence of a subject. // [Saml2Core, lines 1050, 1168, 1280] foreach (Saml2Statement statement in assertion.Statements) { if (statement is Saml2AuthenticationStatement || statement is Saml2AttributeStatement || statement is Saml2AuthorizationDecisionStatement) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4119)); } } } // Reading the end element will complete the signature; // capture the signing creds assertion.SigningCredentials = realReader.SigningCredentials; // Save the captured on-the-wire data, which can then be used // to re-emit this assertion, preserving the same signature. assertion.CaptureSourceData(realReader); return assertion; } catch (Exception e) { if (System.Runtime.Fx.IsFatal(e)) throw; Exception wrapped = TryWrapReadException(realReader, e); if (null == wrapped) { throw; } else { throw wrapped; } } } /// /// Serializes the provided SamlAssertion to the XmlWriter. /// /// A to serialize the . /// The to serialize. /// The or parameters are null. /// The has both and properties null. /// The must have a if no are present. /// The SAML2 authentication, attribute, and authorization decision require a . /// Token encrypting credentials must have a Symmetric Key specified. protected virtual void WriteAssertion(XmlWriter writer, Saml2Assertion data) { if (null == writer) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (null == data) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data"); } XmlWriter originalWriter = writer; MemoryStream plaintextStream = null; XmlDictionaryWriter plaintextWriter = null; // If an EncryptingCredentials is present then check if this is not of type ReceivedEncryptinCredentials. // ReceivedEncryptingCredentials mean that it was credentials that were hydrated from a token received // on the wire. We should not directly use this while re-serializing a token. if ((null != data.EncryptingCredentials) && !(data.EncryptingCredentials is ReceivedEncryptingCredentials)) { plaintextStream = new MemoryStream(); writer = plaintextWriter = XmlDictionaryWriter.CreateTextWriter(plaintextStream, Encoding.UTF8, false); } else if (data.ExternalEncryptedKeys == null || data.ExternalEncryptedKeys.Count > 0) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID4173))); } // If we've saved off the token stream, re-emit it. if (data.CanWriteSourceData) { data.WriteSourceData(writer); } else { // Wrap the writer if necessary for a signature // We do not dispose this writer, since as a delegating writer it would // dispose the inner writer, which we don't properly own. EnvelopedSignatureWriter signatureWriter = null; if (null != data.SigningCredentials) { #pragma warning suppress 56506 writer = signatureWriter = new EnvelopedSignatureWriter(writer, data.SigningCredentials, data.Id.Value, new WrappedSerializer(this, data)); } if (null == data.Subject) { // An assertion with no statements MUST contain a element. [Saml2Core, line 585] if (data.Statements == null || 0 == data.Statements.Count) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID4106))); } // Furthermore, the built-in statement types all require the presence of a subject. // [Saml2Core, lines 1050, 1168, 1280] foreach (Saml2Statement statement in data.Statements) { if (statement is Saml2AuthenticationStatement || statement is Saml2AttributeStatement || statement is Saml2AuthorizationDecisionStatement) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new InvalidOperationException(SR.GetString(SR.ID4119))); } } } // writer.WriteStartElement(Saml2Constants.Elements.Assertion, Saml2Constants.Namespace); // @ID - required writer.WriteAttributeString(Saml2Constants.Attributes.ID, data.Id.Value); // @IssueInstant - required writer.WriteAttributeString(Saml2Constants.Attributes.IssueInstant, XmlConvert.ToString(data.IssueInstant.ToUniversalTime(), DateTimeFormats.Generated)); // @Version - required writer.WriteAttributeString(Saml2Constants.Attributes.Version, data.Version); // 1 this.WriteIssuer(writer, data.Issuer); // 0-1 if (null != signatureWriter) { signatureWriter.WriteSignature(); } // 0-1 if (null != data.Subject) { this.WriteSubject(writer, data.Subject); } // 0-1 if (null != data.Conditions) { this.WriteConditions(writer, data.Conditions); } // 0-1 if (null != data.Advice) { this.WriteAdvice(writer, data.Advice); } // , 0-OO foreach (Saml2Statement statement in data.Statements) { this.WriteStatement(writer, statement); } writer.WriteEndElement(); } // Finish off the encryption if (null != plaintextWriter) { ((IDisposable)plaintextWriter).Dispose(); plaintextWriter = null; EncryptedDataElement encryptedData = new EncryptedDataElement(); encryptedData.Type = XmlEncryptionConstants.EncryptedDataTypes.Element; encryptedData.Algorithm = data.EncryptingCredentials.Algorithm; encryptedData.KeyIdentifier = data.EncryptingCredentials.SecurityKeyIdentifier; // Get the encryption key, which must be symmetric SymmetricSecurityKey encryptingKey = data.EncryptingCredentials.SecurityKey as SymmetricSecurityKey; if (encryptingKey == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CryptographicException(SR.GetString(SR.ID3064))); } // Do the actual encryption SymmetricAlgorithm symmetricAlgorithm = encryptingKey.GetSymmetricAlgorithm(data.EncryptingCredentials.Algorithm); encryptedData.Encrypt(symmetricAlgorithm, plaintextStream.GetBuffer(), 0, (int)plaintextStream.Length); ((IDisposable)plaintextStream).Dispose(); originalWriter.WriteStartElement(Saml2Constants.Elements.EncryptedAssertion, Saml2Constants.Namespace); encryptedData.WriteXml(originalWriter, this.KeyInfoSerializer); foreach (EncryptedKeyIdentifierClause clause in data.ExternalEncryptedKeys) { this.KeyInfoSerializer.WriteKeyIdentifierClause(originalWriter, clause); } originalWriter.WriteEndElement(); } } /// /// Reads the <saml:Attribute> element. /// /// /// The default implementation requires that the content of the /// Attribute element be a simple string. To handle complex content /// or content of declared simple types other than xs:string, override /// this method. /// /// An positioned at a element. /// A instance. protected virtual Saml2Attribute ReadAttribute(XmlReader reader) { if (null == reader) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } // throw if wrong element if (!reader.IsStartElement(Saml2Constants.Elements.Attribute, Saml2Constants.Namespace)) { reader.ReadStartElement(Saml2Constants.Elements.Attribute, Saml2Constants.Namespace); } try { Saml2Attribute attribute; bool isEmpty = reader.IsEmptyElement; // @attributes string value; // @xsi:type XmlUtil.ValidateXsiType(reader, Saml2Constants.Types.AttributeType, Saml2Constants.Namespace); // @Name - required value = reader.GetAttribute(Saml2Constants.Attributes.Name); if (string.IsNullOrEmpty(value)) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID0001, Saml2Constants.Attributes.Name, Saml2Constants.Elements.Attribute)); } attribute = new Saml2Attribute(value); // @NameFormat - optional value = reader.GetAttribute(Saml2Constants.Attributes.NameFormat); if (!string.IsNullOrEmpty(value)) { if (!UriUtil.CanCreateValidUri(value, UriKind.Absolute)) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID0011, Saml2Constants.Attributes.Namespace, Saml2Constants.Elements.Action)); } attribute.NameFormat = new Uri(value); } // @FriendlyName - optional attribute.FriendlyName = reader.GetAttribute(Saml2Constants.Attributes.FriendlyName); // @OriginalIssuer - optional. // 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(Saml2Constants.Attributes.OriginalIssuer, ClaimType2009Namespace); if (originalIssuer == null) { originalIssuer = reader.GetAttribute(Saml2Constants.Attributes.OriginalIssuer, ProductConstants.NamespaceUri); } if (originalIssuer == String.Empty) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4252))); } attribute.OriginalIssuer = originalIssuer; // content reader.Read(); if (!isEmpty) { while (reader.IsStartElement(Saml2Constants.Elements.AttributeValue, Saml2Constants.Namespace)) { bool isEmptyValue = reader.IsEmptyElement; bool isNil = XmlUtil.IsNil(reader); // 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. // For attributeValueXsiTypeSuffix, we want the portion after the local prefix in "some-non-empty-local-prefix:some-non-empty-string" // "some-non-empty-local-prefix:some-non-empty-string" case string attributeValueXsiTypePrefix = null; string attributeValueXsiTypeSuffix = null; string attributeValueXsiTypeSuffixWithLocalPrefix = reader.GetAttribute("type", XmlSchema.InstanceNamespace); if (!string.IsNullOrEmpty(attributeValueXsiTypeSuffixWithLocalPrefix)) { // "some-non-empty-string" case if (attributeValueXsiTypeSuffixWithLocalPrefix.IndexOf(":", StringComparison.Ordinal) == -1) { attributeValueXsiTypePrefix = reader.LookupNamespace(String.Empty); attributeValueXsiTypeSuffix = attributeValueXsiTypeSuffixWithLocalPrefix; } else if (attributeValueXsiTypeSuffixWithLocalPrefix.IndexOf(":", StringComparison.Ordinal) > 0 && attributeValueXsiTypeSuffixWithLocalPrefix.IndexOf(":", StringComparison.Ordinal) < attributeValueXsiTypeSuffixWithLocalPrefix.Length - 1) { string localPrefix = attributeValueXsiTypeSuffixWithLocalPrefix.Substring(0, attributeValueXsiTypeSuffixWithLocalPrefix.IndexOf(":", StringComparison.Ordinal)); attributeValueXsiTypePrefix = reader.LookupNamespace(localPrefix); attributeValueXsiTypeSuffix = attributeValueXsiTypeSuffixWithLocalPrefix.Substring(attributeValueXsiTypeSuffixWithLocalPrefix.IndexOf(":", StringComparison.Ordinal) + 1); } } if (attributeValueXsiTypePrefix != null && attributeValueXsiTypeSuffix != null) { attribute.AttributeValueXsiType = String.Concat(attributeValueXsiTypePrefix, "#", attributeValueXsiTypeSuffix); } if (isNil) { reader.Read(); if (!isEmptyValue) { reader.ReadEndElement(); } attribute.Values.Add(null); } else if (isEmptyValue) { reader.Read(); attribute.Values.Add(string.Empty); } else { attribute.Values.Add(this.ReadAttributeValue(reader, attribute)); } } reader.ReadEndElement(); } return attribute; } catch (Exception e) { if (System.Runtime.Fx.IsFatal(e)) throw; Exception wrapped = TryWrapReadException(reader, e); if (null == wrapped) { throw; } else { throw wrapped; } } } /// /// Reads an attribute value. /// /// A positioned at a . /// The . /// The attribute value as a string. /// The input parameter 'reader' is null. protected virtual string ReadAttributeValue(XmlReader reader, Saml2Attribute 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, Saml2Constants.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; } /// /// Writes the <saml:Attribute> element. /// /// A to serialize the . /// The to serialize. protected virtual void WriteAttribute(XmlWriter writer, Saml2Attribute data) { if (null == writer) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (null == data) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data"); } // writer.WriteStartElement(Saml2Constants.Elements.Attribute, Saml2Constants.Namespace); // @Name - required writer.WriteAttributeString(Saml2Constants.Attributes.Name, data.Name); // @NameFormat - optional if (null != data.NameFormat) { writer.WriteAttributeString(Saml2Constants.Attributes.NameFormat, data.NameFormat.AbsoluteUri); } // @FriendlyName - optional if (null != data.FriendlyName) { writer.WriteAttributeString(Saml2Constants.Attributes.FriendlyName, data.FriendlyName); } // @OriginalIssuer - optional if (null != data.OriginalIssuer) { writer.WriteAttributeString(Saml2Constants.Attributes.OriginalIssuer, ClaimType2009Namespace, data.OriginalIssuer); } string xsiTypePrefix = null; string xsiTypeSuffix = null; if (!StringComparer.Ordinal.Equals(data.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 = data.AttributeValueXsiType.IndexOf('#'); xsiTypePrefix = data.AttributeValueXsiType.Substring(0, indexOfHash); xsiTypeSuffix = data.AttributeValueXsiType.Substring(indexOfHash + 1); } // 0-OO (nillable) foreach (string value in data.Values) { writer.WriteStartElement(Saml2Constants.Elements.AttributeValue, Saml2Constants.Namespace); if (null == value) { writer.WriteAttributeString("nil", XmlSchema.InstanceNamespace, XmlConvert.ToString(true)); } else if (value.Length > 0) { if ((xsiTypePrefix != null) && (xsiTypeSuffix != null)) { writer.WriteAttributeString("xmlns", ProductConstants.ClaimValueTypeSerializationPrefix, null, xsiTypePrefix); writer.WriteAttributeString("type", XmlSchema.InstanceNamespace, String.Concat(ProductConstants.ClaimValueTypeSerializationPrefixWithColon, xsiTypeSuffix)); } this.WriteAttributeValue(writer, value, data); } writer.WriteEndElement(); } // writer.WriteEndElement(); } /// /// Writes the saml:Attribute value. /// /// A to serialize the . /// The value of the attribute being serialized. /// The to serialize. /// By default the method writes the value as a string. /// The input parameter 'writer' is null. protected virtual void WriteAttributeValue(XmlWriter writer, string value, Saml2Attribute attribute) { if (writer == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } writer.WriteString(value); } /// /// Reads the <saml:AttributeStatement> element, or a /// <saml:Statement element that specifies an xsi:type of /// saml:AttributeStatementType. /// /// A positioned at a element. /// A instance. protected virtual Saml2AttributeStatement ReadAttributeStatement(XmlReader reader) { if (null == reader) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } // throw if wrong element bool isStatementElement = false; if (reader.IsStartElement(Saml2Constants.Elements.Statement, Saml2Constants.Namespace)) { isStatementElement = true; } else if (!reader.IsStartElement(Saml2Constants.Elements.AttributeStatement, Saml2Constants.Namespace)) { reader.ReadStartElement(Saml2Constants.Elements.AttributeStatement, Saml2Constants.Namespace); } try { // defer disallowing empty element until checking xsi:type bool isEmpty = reader.IsEmptyElement; // @attributes // @xsi:type XmlUtil.ValidateXsiType(reader, Saml2Constants.Types.AttributeStatementType, Saml2Constants.Namespace, isStatementElement); // disallow empty element, since xsi:type is ok if (isEmpty) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID3061, Saml2Constants.Elements.AttributeStatement, Saml2Constants.Namespace)); } // Content Saml2AttributeStatement statement = new Saml2AttributeStatement(); reader.Read(); // 1-OO while (reader.IsStartElement()) { if (reader.IsStartElement(Saml2Constants.Elements.EncryptedAttribute, Saml2Constants.Namespace)) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4158)); } else if (reader.IsStartElement(Saml2Constants.Elements.Attribute, Saml2Constants.Namespace)) { statement.Attributes.Add(this.ReadAttribute(reader)); } else { break; } } // At least one attribute expected if (0 == statement.Attributes.Count) { reader.ReadStartElement(Saml2Constants.Elements.Attribute, Saml2Constants.Namespace); } reader.ReadEndElement(); return statement; } catch (Exception e) { if (System.Runtime.Fx.IsFatal(e)) throw; Exception wrapped = TryWrapReadException(reader, e); if (null == wrapped) { throw; } else { throw wrapped; } } } /// /// Writes the <saml:AttributeStatement> element. /// /// A to serialize the . /// The to serialize. protected virtual void WriteAttributeStatement(XmlWriter writer, Saml2AttributeStatement data) { if (null == writer) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (null == data) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data"); } if (data.Attributes == null || 0 == data.Attributes.Count) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID4124))); } // writer.WriteStartElement(Saml2Constants.Elements.AttributeStatement, Saml2Constants.Namespace); // 1-OO foreach (Saml2Attribute attribute in data.Attributes) { this.WriteAttribute(writer, attribute); } // writer.WriteEndElement(); } /// /// Reads the <saml:AudienceRestriction> element or a /// <saml:Condition> element that specifies an xsi:type /// of saml:AudienceRestrictionType. /// /// A positioned at a element. /// A instance. protected virtual Saml2AudienceRestriction ReadAudienceRestriction(XmlReader reader) { if (null == reader) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } // throw if wrong element bool isConditionElement = false; if (reader.IsStartElement(Saml2Constants.Elements.Condition, Saml2Constants.Namespace)) { isConditionElement = true; } else if (!reader.IsStartElement(Saml2Constants.Elements.AudienceRestriction, Saml2Constants.Namespace)) { reader.ReadStartElement(Saml2Constants.Elements.AudienceRestriction, Saml2Constants.Namespace); } try { Saml2AudienceRestriction audienceRestriction; bool isEmpty = reader.IsEmptyElement; // @attributes // @xsi:type -- if we're a element, this declaration must be present XmlUtil.ValidateXsiType(reader, Saml2Constants.Types.AudienceRestrictionType, Saml2Constants.Namespace, isConditionElement); // disallow empty if (isEmpty) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID3061, reader.LocalName, reader.NamespaceURI)); } // content reader.Read(); // - 1-OO if (!reader.IsStartElement(Saml2Constants.Elements.Audience, Saml2Constants.Namespace)) { reader.ReadStartElement(Saml2Constants.Elements.Audience, Saml2Constants.Namespace); } // We are now laxing the uri check for audience restriction to support interop partners // This is a specific request from server : Bug 11850 // ReadSimpleUriElement now has a flag that turns lax reading ON/OFF. audienceRestriction = new Saml2AudienceRestriction(ReadSimpleUriElement(reader, UriKind.RelativeOrAbsolute, true)); while (reader.IsStartElement(Saml2Constants.Elements.Audience, Saml2Constants.Namespace)) { audienceRestriction.Audiences.Add(ReadSimpleUriElement(reader, UriKind.RelativeOrAbsolute, true)); } reader.ReadEndElement(); return audienceRestriction; } catch (Exception e) { if (System.Runtime.Fx.IsFatal(e)) throw; Exception wrapped = TryWrapReadException(reader, e); if (null == wrapped) { throw; } else { throw wrapped; } } } /// /// Writes the <saml:AudienceRestriction> element. /// /// A to serialize the . /// The to serialize. protected virtual void WriteAudienceRestriction(XmlWriter writer, Saml2AudienceRestriction data) { if (null == writer) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (null == data) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data"); } // Schema requires at least one audience. if (data.Audiences == null || 0 == data.Audiences.Count) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID4159))); } // writer.WriteStartElement(Saml2Constants.Elements.AudienceRestriction, Saml2Constants.Namespace); // - 1-OO foreach (Uri audience in data.Audiences) { // 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(Saml2Constants.Elements.Audience, Saml2Constants.Namespace, audience.OriginalString); } // writer.WriteEndElement(); } /// /// Reads the <saml:AuthnContext> element. /// /// /// The default implementation does not handle the optional /// <saml:AuthnContextDecl> element. To handle by-value /// authentication context declarations, override this method. /// /// A positioned at a element. /// A instance. protected virtual Saml2AuthenticationContext ReadAuthenticationContext(XmlReader reader) { if (null == reader) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } // throw if wrong element if (!reader.IsStartElement(Saml2Constants.Elements.AuthnContext, Saml2Constants.Namespace)) { reader.ReadStartElement(Saml2Constants.Elements.AuthnContext, Saml2Constants.Namespace); } try { // Disallow empty if (reader.IsEmptyElement) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID3061, Saml2Constants.Elements.AuthnContext, Saml2Constants.Namespace)); } // @attributes // @xsi:type XmlUtil.ValidateXsiType(reader, Saml2Constants.Types.AuthnContextType, Saml2Constants.Namespace); // Content reader.ReadStartElement(); // At least one of ClassRef and ( Decl XOR DeclRef) must be present // At this time, we do not support Decl, which is a by-value // authentication context declaration. Uri classRef = null; Uri declRef = null; // - see comment above if (reader.IsStartElement(Saml2Constants.Elements.AuthnContextClassRef, Saml2Constants.Namespace)) { classRef = ReadSimpleUriElement(reader); } // - see comment above if (reader.IsStartElement(Saml2Constants.Elements.AuthnContextDecl, Saml2Constants.Namespace)) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4118)); } // - see comment above // If there was no ClassRef, there must be a DeclRef if (reader.IsStartElement(Saml2Constants.Elements.AuthnContextDeclRef, Saml2Constants.Namespace)) { declRef = ReadSimpleUriElement(reader); } else if (null == classRef) { reader.ReadStartElement(Saml2Constants.Elements.AuthnContextDeclRef, Saml2Constants.Namespace); } // Now we have enough data to create the object Saml2AuthenticationContext authnContext = new Saml2AuthenticationContext(classRef, declRef); // - 0-OO while (reader.IsStartElement(Saml2Constants.Elements.AuthenticatingAuthority, Saml2Constants.Namespace)) { authnContext.AuthenticatingAuthorities.Add(ReadSimpleUriElement(reader)); } reader.ReadEndElement(); return authnContext; } catch (Exception e) { if (System.Runtime.Fx.IsFatal(e)) throw; Exception wrapped = TryWrapReadException(reader, e); if (null == wrapped) { throw; } else { throw wrapped; } } } /// /// Writes the <saml:AuthnContext> element. /// /// A to serialize the . /// The to serialize. protected virtual void WriteAuthenticationContext(XmlWriter writer, Saml2AuthenticationContext data) { if (null == writer) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (null == data) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data"); } // One of ClassRef and DeclRef must be present. if (null == data.ClassReference && null == data.DeclarationReference) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new InvalidOperationException(SR.GetString(SR.ID4117))); } // writer.WriteStartElement(Saml2Constants.Elements.AuthnContext, Saml2Constants.Namespace); // 0-1 if (null != data.ClassReference) { writer.WriteElementString(Saml2Constants.Elements.AuthnContextClassRef, Saml2Constants.Namespace, data.ClassReference.AbsoluteUri); } // 0-1 if (null != data.DeclarationReference) { writer.WriteElementString(Saml2Constants.Elements.AuthnContextDeclRef, Saml2Constants.Namespace, data.DeclarationReference.AbsoluteUri); } // 0-OO foreach (Uri authority in data.AuthenticatingAuthorities) { writer.WriteElementString(Saml2Constants.Elements.AuthenticatingAuthority, Saml2Constants.Namespace, authority.AbsoluteUri); } // writer.WriteEndElement(); } /// /// Reads the <saml:AuthnStatement> element or a <saml:Statement> /// element that specifies an xsi:type of saml:AuthnStatementType. /// /// A positioned at a element. /// A instance. protected virtual Saml2AuthenticationStatement ReadAuthenticationStatement(XmlReader reader) { if (null == reader) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } // throw if wrong element bool isStatementElement = false; if (reader.IsStartElement(Saml2Constants.Elements.Statement, Saml2Constants.Namespace)) { isStatementElement = true; } else if (!reader.IsStartElement(Saml2Constants.Elements.AuthnStatement, Saml2Constants.Namespace)) { reader.ReadStartElement(Saml2Constants.Elements.AuthnStatement, Saml2Constants.Namespace); } try { // Must cache the individual data since the required // AuthnContext comes last DateTime authnInstant; Saml2AuthenticationContext authnContext; string sessionIndex; DateTime? sessionNotOnOrAfter = null; Saml2SubjectLocality subjectLocality = null; // defer disallowing empty until after xsi:type bool isEmpty = reader.IsEmptyElement; // @attributes string value; // @xsi:type -- if we're a element, this declaration must be present XmlUtil.ValidateXsiType(reader, Saml2Constants.Types.AuthnStatementType, Saml2Constants.Namespace, isStatementElement); // disallow empty, since xsi:type is ok if (isEmpty) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID3061, Saml2Constants.Elements.AuthnStatement, Saml2Constants.Namespace)); } // @AuthnInstant - required value = reader.GetAttribute(Saml2Constants.Attributes.AuthnInstant); if (string.IsNullOrEmpty(value)) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID0001, Saml2Constants.Attributes.AuthnInstant, Saml2Constants.Elements.AuthnStatement)); } authnInstant = XmlConvert.ToDateTime(value, DateTimeFormats.Accepted); // @SessionIndex - optional sessionIndex = reader.GetAttribute(Saml2Constants.Attributes.SessionIndex); // @SessionNotOnOrAfter - optional value = reader.GetAttribute(Saml2Constants.Attributes.SessionNotOnOrAfter); if (!string.IsNullOrEmpty(value)) { sessionNotOnOrAfter = XmlConvert.ToDateTime(value, DateTimeFormats.Accepted); } // Content reader.Read(); // 0-1 if (reader.IsStartElement(Saml2Constants.Elements.SubjectLocality, Saml2Constants.Namespace)) { subjectLocality = this.ReadSubjectLocality(reader); } // 1 authnContext = this.ReadAuthenticationContext(reader); reader.ReadEndElement(); // Construct the actual object Saml2AuthenticationStatement authnStatement = new Saml2AuthenticationStatement(authnContext, authnInstant); authnStatement.SessionIndex = sessionIndex; authnStatement.SessionNotOnOrAfter = sessionNotOnOrAfter; authnStatement.SubjectLocality = subjectLocality; return authnStatement; } catch (Exception e) { if (System.Runtime.Fx.IsFatal(e)) throw; Exception wrapped = TryWrapReadException(reader, e); if (null == wrapped) { throw; } else { throw wrapped; } } } /// /// Writes the <saml:AuthnStatement> element. /// /// A to serialize the . /// The to serialize. protected virtual void WriteAuthenticationStatement(XmlWriter writer, Saml2AuthenticationStatement data) { if (null == writer) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (null == data) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data"); } // writer.WriteStartElement(Saml2Constants.Elements.AuthnStatement, Saml2Constants.Namespace); // @AuthnInstant - required writer.WriteAttributeString(Saml2Constants.Attributes.AuthnInstant, XmlConvert.ToString(data.AuthenticationInstant.ToUniversalTime(), DateTimeFormats.Generated)); // @SessionIndex - optional if (null != data.SessionIndex) { writer.WriteAttributeString(Saml2Constants.Attributes.SessionIndex, data.SessionIndex); } // @SessionNotOnOrAfter - optional if (null != data.SessionNotOnOrAfter) { writer.WriteAttributeString(Saml2Constants.Attributes.SessionNotOnOrAfter, XmlConvert.ToString(data.SessionNotOnOrAfter.Value.ToUniversalTime(), DateTimeFormats.Generated)); } // 0-1 if (null != data.SubjectLocality) { this.WriteSubjectLocality(writer, data.SubjectLocality); } // 1 this.WriteAuthenticationContext(writer, data.AuthenticationContext); // writer.WriteEndElement(); } /// /// Reads the <saml:AuthzDecisionStatement> element or a /// <saml:Statement element that specifies an xsi:type of /// saml:AuthzDecisionStatementType. /// /// A positioned at a element. /// A instance. protected virtual Saml2AuthorizationDecisionStatement ReadAuthorizationDecisionStatement(XmlReader reader) { if (null == reader) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } // throw if wrong element bool isStatementElement = false; if (reader.IsStartElement(Saml2Constants.Elements.Statement, Saml2Constants.Namespace)) { isStatementElement = true; } else if (!reader.IsStartElement(Saml2Constants.Elements.AuthzDecisionStatement, Saml2Constants.Namespace)) { reader.ReadStartElement(Saml2Constants.Elements.AuthzDecisionStatement, Saml2Constants.Namespace); } try { // Need the attributes before we can instantiate Saml2AuthorizationDecisionStatement statement; SamlAccessDecision decision; Uri resource; // defer rejecting empty until processing xsi:type bool isEmpty = reader.IsEmptyElement; // @attributes string value; // @xsi:type XmlUtil.ValidateXsiType(reader, Saml2Constants.Types.AuthzDecisionStatementType, Saml2Constants.Namespace, isStatementElement); // disallow empty, since xsi:type is ok if (isEmpty) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID3061, Saml2Constants.Elements.AuthzDecisionStatement, Saml2Constants.Namespace)); } // @Decision - required value = reader.GetAttribute(Saml2Constants.Attributes.Decision); if (string.IsNullOrEmpty(value)) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID0001, Saml2Constants.Attributes.Decision, Saml2Constants.Elements.AuthzDecisionStatement)); } else if (StringComparer.Ordinal.Equals(SamlAccessDecision.Permit.ToString(), value)) { decision = SamlAccessDecision.Permit; } else if (StringComparer.Ordinal.Equals(SamlAccessDecision.Deny.ToString(), value)) { decision = SamlAccessDecision.Deny; } else if (StringComparer.Ordinal.Equals(SamlAccessDecision.Indeterminate.ToString(), value)) { decision = SamlAccessDecision.Indeterminate; } else { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4123, value)); } // @Resource - required value = reader.GetAttribute(Saml2Constants.Attributes.Resource); if (null == value) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID0001, Saml2Constants.Attributes.Resource, Saml2Constants.Elements.AuthzDecisionStatement)); } else if (0 == value.Length) { resource = Saml2AuthorizationDecisionStatement.EmptyResource; } else { if (!UriUtil.CanCreateValidUri(value, UriKind.Absolute)) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4121)); } resource = new Uri(value); } // Content statement = new Saml2AuthorizationDecisionStatement(resource, decision); reader.Read(); // 1-OO do { statement.Actions.Add(this.ReadAction(reader)); } while (reader.IsStartElement(Saml2Constants.Elements.Action, Saml2Constants.Namespace)); // 0-1 if (reader.IsStartElement(Saml2Constants.Elements.Evidence, Saml2Constants.Namespace)) { statement.Evidence = this.ReadEvidence(reader); } reader.ReadEndElement(); return statement; } catch (Exception e) { if (System.Runtime.Fx.IsFatal(e)) throw; Exception wrapped = TryWrapReadException(reader, e); if (null == wrapped) { throw; } else { throw wrapped; } } } /// /// Writes the <saml:AuthzDecisionStatement> element. /// /// A to serialize the . /// The to serialize. protected virtual void WriteAuthorizationDecisionStatement(XmlWriter writer, Saml2AuthorizationDecisionStatement data) { if (null == writer) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (null == data) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data"); } #pragma warning suppress 56506 // actions are never null if (0 == data.Actions.Count) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new InvalidOperationException(SR.GetString(SR.ID4122))); } // writer.WriteStartElement(Saml2Constants.Elements.AuthzDecisionStatement, Saml2Constants.Namespace); // @Decision - required writer.WriteAttributeString(Saml2Constants.Attributes.Decision, data.Decision.ToString()); // @Resource - required #pragma warning suppress 56506 // Resource are never null writer.WriteAttributeString(Saml2Constants.Attributes.Resource, data.Resource.Equals(Saml2AuthorizationDecisionStatement.EmptyResource) ? data.Resource.ToString() : data.Resource.AbsoluteUri); // @Action 1-OO foreach (Saml2Action action in data.Actions) { this.WriteAction(writer, action); } // Evidence 0-1 if (null != data.Evidence) { this.WriteEvidence(writer, data.Evidence); } // writer.WriteEndElement(); } /// /// Reads the <saml:Conditions> element. /// /// /// To handle custom <saml:Condition> elements, override this /// method. /// /// A positioned at a element. /// A instance. protected virtual Saml2Conditions ReadConditions(XmlReader reader) { if (null == reader) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } // throw if wrong element if (!reader.IsStartElement(Saml2Constants.Elements.Conditions, Saml2Constants.Namespace)) { reader.ReadStartElement(Saml2Constants.Elements.Conditions, Saml2Constants.Namespace); } try { Saml2Conditions conditions = new Saml2Conditions(); bool isEmpty = reader.IsEmptyElement; // @attributes string value; // @xsi:type XmlUtil.ValidateXsiType(reader, Saml2Constants.Types.ConditionsType, Saml2Constants.Namespace); // @NotBefore - optional value = reader.GetAttribute(Saml2Constants.Attributes.NotBefore); if (!string.IsNullOrEmpty(value)) { conditions.NotBefore = XmlConvert.ToDateTime(value, DateTimeFormats.Accepted); } // @NotOnOrAfter - optional value = reader.GetAttribute(Saml2Constants.Attributes.NotOnOrAfter); if (!string.IsNullOrEmpty(value)) { conditions.NotOnOrAfter = XmlConvert.ToDateTime(value, DateTimeFormats.Accepted); } // Content reader.ReadStartElement(); if (!isEmpty) { // , 0-OO while (reader.IsStartElement()) { // - 0-OO if (reader.IsStartElement(Saml2Constants.Elements.Condition, Saml2Constants.Namespace)) { // Since Condition is abstract, must process based on xsi:type XmlQualifiedName declaredType = XmlUtil.GetXsiType(reader); // No type, throw if (null == declaredType || XmlUtil.EqualsQName(declaredType, Saml2Constants.Types.ConditionAbstractType, Saml2Constants.Namespace)) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4104, reader.LocalName, reader.NamespaceURI)); } else if (XmlUtil.EqualsQName(declaredType, Saml2Constants.Types.AudienceRestrictionType, Saml2Constants.Namespace)) { conditions.AudienceRestrictions.Add(this.ReadAudienceRestriction(reader)); } else if (XmlUtil.EqualsQName(declaredType, Saml2Constants.Types.OneTimeUseType, Saml2Constants.Namespace)) { if (conditions.OneTimeUse) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4115, Saml2Constants.Elements.OneTimeUse)); } ReadEmptyContentElement(reader); conditions.OneTimeUse = true; } else if (XmlUtil.EqualsQName(declaredType, Saml2Constants.Types.ProxyRestrictionType, Saml2Constants.Namespace)) { if (null != conditions.ProxyRestriction) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4115, Saml2Constants.Elements.ProxyRestricton)); } conditions.ProxyRestriction = this.ReadProxyRestriction(reader); } else { // Unknown type - Instruct the user to override to handle custom throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4113)); } } else if (reader.IsStartElement(Saml2Constants.Elements.AudienceRestriction, Saml2Constants.Namespace)) { conditions.AudienceRestrictions.Add(this.ReadAudienceRestriction(reader)); } else if (reader.IsStartElement(Saml2Constants.Elements.OneTimeUse, Saml2Constants.Namespace)) { if (conditions.OneTimeUse) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4115, Saml2Constants.Elements.OneTimeUse)); } ReadEmptyContentElement(reader); conditions.OneTimeUse = true; } else if (reader.IsStartElement(Saml2Constants.Elements.ProxyRestricton, Saml2Constants.Namespace)) { if (null != conditions.ProxyRestriction) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4115, Saml2Constants.Elements.ProxyRestricton)); } conditions.ProxyRestriction = this.ReadProxyRestriction(reader); } else { break; } } reader.ReadEndElement(); } return conditions; } catch (Exception e) { if (System.Runtime.Fx.IsFatal(e)) throw; Exception wrapped = TryWrapReadException(reader, e); if (null == wrapped) { throw; } else { throw wrapped; } } } /// /// Writes the <saml:Conditions> element. /// /// A to serialize the . /// The to serialize. protected virtual void WriteConditions(XmlWriter writer, Saml2Conditions data) { if (null == writer) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (null == data) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data"); } // writer.WriteStartElement(Saml2Constants.Elements.Conditions, Saml2Constants.Namespace); // @NotBefore - optional if (null != data.NotBefore) { writer.WriteAttributeString(Saml2Constants.Attributes.NotBefore, XmlConvert.ToString(data.NotBefore.Value.ToUniversalTime(), DateTimeFormats.Generated)); } // @NotOnOrAfter - optional if (null != data.NotOnOrAfter) { writer.WriteAttributeString(Saml2Constants.Attributes.NotOnOrAfter, XmlConvert.ToString(data.NotOnOrAfter.Value.ToUniversalTime(), DateTimeFormats.Generated)); } // 0-OO foreach (Saml2AudienceRestriction audienceRestriction in data.AudienceRestrictions) { this.WriteAudienceRestriction(writer, audienceRestriction); } // - limited to one in SAML spec if (data.OneTimeUse) { writer.WriteStartElement(Saml2Constants.Elements.OneTimeUse, Saml2Constants.Namespace); writer.WriteEndElement(); } // - limited to one in SAML spec if (null != data.ProxyRestriction) { this.WriteProxyRestriction(writer, data.ProxyRestriction); } // writer.WriteEndElement(); } /// /// Reads the <saml:Evidence> element. /// /// A positioned at a element. /// A instance. protected virtual Saml2Evidence ReadEvidence(XmlReader reader) { if (null == reader) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } // throw if wrong element if (!reader.IsStartElement(Saml2Constants.Elements.Evidence, Saml2Constants.Namespace)) { reader.ReadStartElement(Saml2Constants.Elements.Evidence, Saml2Constants.Namespace); } // disallow empty if (reader.IsEmptyElement) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID3061, Saml2Constants.Elements.Evidence, Saml2Constants.Namespace)); } try { Saml2Evidence evidence = new Saml2Evidence(); // @attributes // @xsi:type XmlUtil.ValidateXsiType(reader, Saml2Constants.Types.EvidenceType, Saml2Constants.Namespace); reader.Read(); // 0-OO while (reader.IsStartElement()) { if (reader.IsStartElement(Saml2Constants.Elements.AssertionIDRef, Saml2Constants.Namespace)) { evidence.AssertionIdReferences.Add(ReadSimpleNCNameElement(reader)); } else if (reader.IsStartElement(Saml2Constants.Elements.AssertionURIRef, Saml2Constants.Namespace)) { evidence.AssertionUriReferences.Add(ReadSimpleUriElement(reader)); } else if (reader.IsStartElement(Saml2Constants.Elements.Assertion, Saml2Constants.Namespace)) { evidence.Assertions.Add(this.ReadAssertion(reader)); } else if (reader.IsStartElement(Saml2Constants.Elements.EncryptedAssertion, Saml2Constants.Namespace)) { evidence.Assertions.Add(this.ReadAssertion(reader)); } } if (0 == evidence.AssertionIdReferences.Count && 0 == evidence.Assertions.Count && 0 == evidence.AssertionUriReferences.Count) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4120)); } reader.ReadEndElement(); return evidence; } catch (Exception e) { if (System.Runtime.Fx.IsFatal(e)) throw; Exception wrapped = TryWrapReadException(reader, e); if (null == wrapped) { throw; } else { throw wrapped; } } } /// /// Writes the <saml:Evidence> element. /// /// A to serialize the . /// The to serialize. protected virtual void WriteEvidence(XmlWriter writer, Saml2Evidence data) { if (null == writer) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (null == data) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data"); } if ((data.AssertionIdReferences == null || 0 == data.AssertionIdReferences.Count) && (data.Assertions == null || 0 == data.Assertions.Count) && (data.AssertionUriReferences == null || 0 == data.AssertionUriReferences.Count)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new InvalidOperationException(SR.GetString(SR.ID4120))); } // writer.WriteStartElement(Saml2Constants.Elements.Evidence, Saml2Constants.Namespace); // 0-OO foreach (Saml2Id id in data.AssertionIdReferences) { writer.WriteElementString(Saml2Constants.Elements.AssertionIDRef, Saml2Constants.Namespace, id.Value); } // 0-OO foreach (Uri uri in data.AssertionUriReferences) { writer.WriteElementString(Saml2Constants.Elements.AssertionURIRef, Saml2Constants.Namespace, uri.AbsoluteUri); } // 0-OO foreach (Saml2Assertion assertion in data.Assertions) { this.WriteAssertion(writer, assertion); } // writer.WriteEndElement(); } /// /// Reads the <saml:Issuer> element. /// /// A positioned at a element. /// A instance. protected virtual Saml2NameIdentifier ReadIssuer(XmlReader reader) { if (null == reader) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } // throw if wrong element if (!reader.IsStartElement(Saml2Constants.Elements.Issuer, Saml2Constants.Namespace)) { reader.ReadStartElement(Saml2Constants.Elements.Issuer, Saml2Constants.Namespace); } return this.ReadNameIdType(reader); } /// /// Writes the <saml:Issuer> element. /// /// A to serialize the . /// The to serialize. protected virtual void WriteIssuer(XmlWriter writer, Saml2NameIdentifier data) { if (null == writer) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (null == data) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data"); } writer.WriteStartElement(Saml2Constants.Elements.Issuer, Saml2Constants.Namespace); this.WriteNameIdType(writer, data); writer.WriteEndElement(); } /// /// Deserializes the SAML Subject KeyInfo. /// /// XmlReader positioned at a ds:KeyInfo element. /// A instance. /// Input parameter 'reader' is null. protected virtual SecurityKeyIdentifier ReadSubjectKeyInfo(XmlReader reader) { if (null == reader) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } return this.KeyInfoSerializer.ReadKeyIdentifier(reader); } /// /// Deserializes the SAML Signing KeyInfo /// /// A positioned at a than can be positioned at a ds:KeyInfo element. /// The that is having the signature checked. /// The that defines the key to use to check the signature. /// Input parameter 'reader' is null. protected virtual SecurityKeyIdentifier ReadSigningKeyInfo(XmlReader reader, Saml2Assertion assertion) { if (null == reader) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } SecurityKeyIdentifier ski; if (this.KeyInfoSerializer.CanReadKeyIdentifier(reader)) { ski = this.KeyInfoSerializer.ReadKeyIdentifier(reader); } else { KeyInfo keyInfo = new KeyInfo(this.KeyInfoSerializer); keyInfo.ReadXml(XmlDictionaryReader.CreateDictionaryReader(reader)); ski = keyInfo.KeyIdentifier; } // no key info if (ski.Count == 0) { return new SecurityKeyIdentifier(new Saml2SecurityKeyIdentifierClause(assertion)); } return ski; } /// /// Serializes the Subject KeyInfo into the given XmlWriter. /// /// A to serialize the . /// The to serialize. /// The input parameter 'writer' or 'data' is null. protected virtual void WriteSubjectKeyInfo(XmlWriter writer, SecurityKeyIdentifier data) { if (null == writer) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (null == data) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data"); } this.KeyInfoSerializer.WriteKeyIdentifier(writer, data); } /// /// Serializes the Signing KeyInfo into the given XmlWriter. /// /// A to serialize the . /// The to serialize. /// The input parameter 'writer' or 'signingKeyIdentifier' is null. protected virtual void WriteSigningKeyInfo(XmlWriter writer, SecurityKeyIdentifier data) { if (null == writer) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (null == data) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data"); } if (this.KeyInfoSerializer.CanWriteKeyIdentifier(data)) { this.KeyInfoSerializer.WriteKeyIdentifier(writer, data); return; } throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4221, data)); } /// /// Reads the <saml:NameID> element. /// /// A positioned at a element. /// An instance of /// The input parameter 'reader' is null. protected virtual Saml2NameIdentifier ReadNameId(XmlReader reader) { if (null == reader) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } // throw if wrong element if (!reader.IsStartElement(Saml2Constants.Elements.NameID, Saml2Constants.Namespace)) { reader.ReadStartElement(Saml2Constants.Elements.NameID, Saml2Constants.Namespace); } return this.ReadNameIdType(reader); } /// /// Writes the <saml:NameID> element. /// /// A to serialize the . /// The to serialize. /// The input parameter 'writer' or 'data' is null. /// Saml2NameIdentifier encrypting credentials must have a Symmetric Key specified. protected virtual void WriteNameId(XmlWriter writer, Saml2NameIdentifier data) { if (null == writer) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (null == data) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data"); } // If there are encrypting credentials, then we need to encrypt the name identifier if (data.EncryptingCredentials != null) { EncryptingCredentials encryptingCredentials = data.EncryptingCredentials; // Get the encryption key, which must be symmetric SymmetricSecurityKey encryptingKey = encryptingCredentials.SecurityKey as SymmetricSecurityKey; if (encryptingKey == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CryptographicException(SR.GetString(SR.ID3284))); } MemoryStream plaintextStream = null; try { // Serialize an encrypted name ID plaintextStream = new MemoryStream(); using (XmlWriter plaintextWriter = XmlDictionaryWriter.CreateTextWriter(plaintextStream, Encoding.UTF8, false)) { plaintextWriter.WriteStartElement(Saml2Constants.Elements.NameID, Saml2Constants.Namespace); this.WriteNameIdType(plaintextWriter, data); plaintextWriter.WriteEndElement(); } EncryptedDataElement encryptedData = new EncryptedDataElement(); encryptedData.Type = XmlEncryptionConstants.EncryptedDataTypes.Element; encryptedData.Algorithm = encryptingCredentials.Algorithm; encryptedData.KeyIdentifier = encryptingCredentials.SecurityKeyIdentifier; // Perform encryption SymmetricAlgorithm symmetricAlgorithm = encryptingKey.GetSymmetricAlgorithm(encryptingCredentials.Algorithm); encryptedData.Encrypt(symmetricAlgorithm, plaintextStream.GetBuffer(), 0, (int)plaintextStream.Length); ((IDisposable)plaintextStream).Dispose(); writer.WriteStartElement(Saml2Constants.Elements.EncryptedID, Saml2Constants.Namespace); encryptedData.WriteXml(writer, this.KeyInfoSerializer); foreach (EncryptedKeyIdentifierClause clause in data.ExternalEncryptedKeys) { this.KeyInfoSerializer.WriteKeyIdentifierClause(writer, clause); } writer.WriteEndElement(); } finally { if (plaintextStream != null) { plaintextStream.Dispose(); plaintextStream = null; } } } else { writer.WriteStartElement(Saml2Constants.Elements.NameID, Saml2Constants.Namespace); this.WriteNameIdType(writer, data); writer.WriteEndElement(); } } /// /// Both <Issuer> and <NameID> are of NameIDType. This method reads /// the content of either one of those elements. /// /// A positioned at a element. /// An instance of protected virtual Saml2NameIdentifier ReadNameIdType(XmlReader reader) { try { reader.MoveToContent(); Saml2NameIdentifier nameIdentifier = new Saml2NameIdentifier("__TemporaryName__"); // @attributes string value; // @xsi:type XmlUtil.ValidateXsiType(reader, Saml2Constants.Types.NameIDType, Saml2Constants.Namespace); // @Format - optional value = reader.GetAttribute(Saml2Constants.Attributes.Format); if (!string.IsNullOrEmpty(value)) { if (!UriUtil.CanCreateValidUri(value, UriKind.Absolute)) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID0011, Saml2Constants.Attributes.Format, Saml2Constants.Elements.NameID)); } nameIdentifier.Format = new Uri(value); } // @NameQualifier - optional value = reader.GetAttribute(Saml2Constants.Attributes.NameQualifier); if (!string.IsNullOrEmpty(value)) { nameIdentifier.NameQualifier = value; } // @SPNameQualifier - optional value = reader.GetAttribute(Saml2Constants.Attributes.SPNameQualifier); if (!string.IsNullOrEmpty(value)) { nameIdentifier.SPNameQualifier = value; } // @SPProvidedID - optional value = reader.GetAttribute(Saml2Constants.Attributes.SPProvidedID); if (!string.IsNullOrEmpty(value)) { nameIdentifier.SPProvidedId = value; } // Content is string nameIdentifier.Value = reader.ReadElementString(); // According to section 8.3.6, if the name identifier format is of type 'urn:oasis:names:tc:SAML:2.0:nameid-format:entity' // the name identifier value must be a uri and name qualifier, spname qualifier, and spproded id must be omitted. if (nameIdentifier.Format != null && StringComparer.Ordinal.Equals(nameIdentifier.Format.AbsoluteUri, Saml2Constants.NameIdentifierFormats.Entity.AbsoluteUri)) { if (!UriUtil.CanCreateValidUri(nameIdentifier.Value, UriKind.Absolute)) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4262, nameIdentifier.Value, Saml2Constants.NameIdentifierFormats.Entity.AbsoluteUri)); } if (!string.IsNullOrEmpty(nameIdentifier.NameQualifier) || !string.IsNullOrEmpty(nameIdentifier.SPNameQualifier) || !string.IsNullOrEmpty(nameIdentifier.SPProvidedId)) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4263, nameIdentifier.Value, Saml2Constants.NameIdentifierFormats.Entity.AbsoluteUri)); } } return nameIdentifier; } catch (Exception e) { if (System.Runtime.Fx.IsFatal(e)) throw; Exception wrapped = TryWrapReadException(reader, e); if (null == wrapped) { throw; } else { throw wrapped; } } } /// /// Reads the <saml:EncryptedId> element. /// /// pointing at the XML EncryptedId element /// An instance of representing the EncryptedId that was read /// The input parameter 'reader' is null. /// The 'reader' is not positioned at an 'EncryptedID' element. protected virtual Saml2NameIdentifier ReadEncryptedId(XmlReader reader) { if (null == reader) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } reader.MoveToContent(); if (!reader.IsStartElement(Saml2Constants.Elements.EncryptedID, Saml2Constants.Namespace)) { // throw if wrong element reader.ReadStartElement(Saml2Constants.Elements.EncryptedID, Saml2Constants.Namespace); } Collection clauses = new Collection(); EncryptingCredentials encryptingCredentials = null; Saml2NameIdentifier saml2NameIdentifier = null; using (StringReader sr = new StringReader(reader.ReadOuterXml())) { using (XmlDictionaryReader wrappedReader = new WrappedXmlDictionaryReader(XmlReader.Create(sr), XmlDictionaryReaderQuotas.Max)) { XmlReader plaintextReader = CreatePlaintextReaderFromEncryptedData( wrappedReader, Configuration.ServiceTokenResolver, this.KeyInfoSerializer, clauses, out encryptingCredentials); saml2NameIdentifier = this.ReadNameIdType(plaintextReader); saml2NameIdentifier.EncryptingCredentials = encryptingCredentials; foreach (EncryptedKeyIdentifierClause clause in clauses) { saml2NameIdentifier.ExternalEncryptedKeys.Add(clause); } } } return saml2NameIdentifier; } /// /// Both <Issuer> and <NameID> are of NameIDType. This method writes /// the content of either one of those elements. /// /// A to serialize the . /// The to serialize. protected virtual void WriteNameIdType(XmlWriter writer, Saml2NameIdentifier data) { // @Format - optional if (null != data.Format) { writer.WriteAttributeString(Saml2Constants.Attributes.Format, data.Format.AbsoluteUri); } // @NameQualifier - optional if (!string.IsNullOrEmpty(data.NameQualifier)) { writer.WriteAttributeString(Saml2Constants.Attributes.NameQualifier, data.NameQualifier); } // @SPNameQualifier - optional if (!string.IsNullOrEmpty(data.SPNameQualifier)) { writer.WriteAttributeString(Saml2Constants.Attributes.SPNameQualifier, data.SPNameQualifier); } // @SPProvidedId - optional if (!string.IsNullOrEmpty(data.SPProvidedId)) { writer.WriteAttributeString(Saml2Constants.Attributes.SPProvidedID, data.SPProvidedId); } // Content is string writer.WriteString(data.Value); } /// /// Reads the <saml:ProxyRestriction> element, or a <saml:Condition> /// element that specifies an xsi:type of saml:ProxyRestrictionType. /// /// /// In the default implementation, the maximum value of the Count attribute /// is limited to Int32.MaxValue. /// /// A positioned at a element. /// An instance of protected virtual Saml2ProxyRestriction ReadProxyRestriction(XmlReader reader) { if (null == reader) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } // throw if wrong element bool isConditionElement = false; if (reader.IsStartElement(Saml2Constants.Elements.Condition, Saml2Constants.Namespace)) { isConditionElement = true; } else if (!reader.IsStartElement(Saml2Constants.Elements.ProxyRestricton, Saml2Constants.Namespace)) { reader.ReadStartElement(Saml2Constants.Elements.ProxyRestricton, Saml2Constants.Namespace); } try { Saml2ProxyRestriction proxyRestriction = new Saml2ProxyRestriction(); bool isEmpty = reader.IsEmptyElement; // @attributes string value; // @xsi:type -- if we're a element, this declaration must be present XmlUtil.ValidateXsiType(reader, Saml2Constants.Types.ProxyRestrictionType, Saml2Constants.Namespace, isConditionElement); // @Count - optional value = reader.GetAttribute(Saml2Constants.Attributes.Count); if (!string.IsNullOrEmpty(value)) { proxyRestriction.Count = XmlConvert.ToInt32(value); } // content reader.Read(); if (!isEmpty) { // - 0-OO while (reader.IsStartElement(Saml2Constants.Elements.Audience, Saml2Constants.Namespace)) { proxyRestriction.Audiences.Add(ReadSimpleUriElement(reader)); } reader.ReadEndElement(); } return proxyRestriction; } catch (Exception e) { if (System.Runtime.Fx.IsFatal(e)) throw; Exception wrapped = TryWrapReadException(reader, e); if (null == wrapped) { throw; } else { throw wrapped; } } } /// /// Writes the <saml:ProxyRestriction> element. /// /// A to serialize the . /// The to serialize. protected virtual void WriteProxyRestriction(XmlWriter writer, Saml2ProxyRestriction data) { if (null == writer) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (null == data) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data"); } writer.WriteStartElement(Saml2Constants.Elements.ProxyRestricton, Saml2Constants.Namespace); // @Count - optional if (null != data.Count) { writer.WriteAttributeString(Saml2Constants.Attributes.Count, XmlConvert.ToString(data.Count.Value)); } // - 0-OO foreach (Uri uri in data.Audiences) { writer.WriteElementString(Saml2Constants.Elements.Audience, uri.AbsoluteUri); } writer.WriteEndElement(); } /// /// Reads the <saml:Statement> element. /// /// A positioned at a element. /// An instance of derived type. /// /// The default implementation only handles Statement elements which /// specify an xsi:type of saml:AttributeStatementType, /// saml:AuthnStatementType, and saml:AuthzDecisionStatementType. To /// handle custom statements, override this method. /// protected virtual Saml2Statement ReadStatement(XmlReader reader) { if (null == reader) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } // throw if wrong element if (!reader.IsStartElement(Saml2Constants.Elements.Statement, Saml2Constants.Namespace)) { reader.ReadStartElement(Saml2Constants.Elements.Statement, Saml2Constants.Namespace); } // Since Statement is an abstract type, we have to switch off the xsi:type declaration XmlQualifiedName declaredType = XmlUtil.GetXsiType(reader); // No declaration, or declaring that this is just a "Statement", is invalid since // statement is abstract if (null == declaredType || XmlUtil.EqualsQName(declaredType, Saml2Constants.Types.StatementAbstractType, Saml2Constants.Namespace)) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4104, reader.LocalName, reader.NamespaceURI)); } // Reroute to the known statement types if applicable if (XmlUtil.EqualsQName(declaredType, Saml2Constants.Types.AttributeStatementType, Saml2Constants.Namespace)) { return this.ReadAttributeStatement(reader); } else if (XmlUtil.EqualsQName(declaredType, Saml2Constants.Types.AuthnStatementType, Saml2Constants.Namespace)) { return this.ReadAuthenticationStatement(reader); } else if (XmlUtil.EqualsQName(declaredType, Saml2Constants.Types.AuthzDecisionStatementType, Saml2Constants.Namespace)) { return this.ReadAuthorizationDecisionStatement(reader); } else { // Throw if we encounter an unknown concrete type throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4105, declaredType.Name, declaredType.Namespace)); } } /// /// Writes a Saml2Statement. /// /// /// This method may write a <saml:AttributeStatement>, <saml:AuthnStatement> /// or <saml:AuthzDecisionStatement> element. To handle custom Saml2Statement /// classes for writing a <saml:Statement> element, override this method. /// /// A to serialize the . /// The to serialize. protected virtual void WriteStatement(XmlWriter writer, Saml2Statement data) { if (null == writer) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (null == data) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data"); } Saml2AttributeStatement attributeStatement = data as Saml2AttributeStatement; if (null != attributeStatement) { this.WriteAttributeStatement(writer, attributeStatement); return; } Saml2AuthenticationStatement authnStatement = data as Saml2AuthenticationStatement; if (null != authnStatement) { this.WriteAuthenticationStatement(writer, authnStatement); return; } Saml2AuthorizationDecisionStatement authzStatement = data as Saml2AuthorizationDecisionStatement; if (null != authzStatement) { this.WriteAuthorizationDecisionStatement(writer, authzStatement); return; } throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new InvalidOperationException(SR.GetString(SR.ID4107, data.GetType().AssemblyQualifiedName))); } /// /// Reads the <saml:Subject> element. /// /// A positioned at a element. /// An instance of . /// /// The default implementation does not handle the optional /// <EncryptedID> element. To handle encryped IDs in the Subject, /// override this method. /// protected virtual Saml2Subject ReadSubject(XmlReader reader) { if (null == reader) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } // throw if wrong element if (!reader.IsStartElement(Saml2Constants.Elements.Subject, Saml2Constants.Namespace)) { reader.ReadStartElement(Saml2Constants.Elements.Subject, Saml2Constants.Namespace); } try { // disallow empty if (reader.IsEmptyElement) { #pragma warning suppress 56504 // bogus - thinks reader.LocalName, reader.NamespaceURI need validation throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID3061, reader.LocalName, reader.NamespaceURI)); } // @attributes // @xsi:type XmlUtil.ValidateXsiType(reader, Saml2Constants.Types.SubjectType, Saml2Constants.Namespace); // Saml2Subject subject = new Saml2Subject(); reader.Read(); // | | 0-1 subject.NameId = this.ReadSubjectId(reader, Saml2Constants.Elements.Subject); // 0-OO while (reader.IsStartElement(Saml2Constants.Elements.SubjectConfirmation, Saml2Constants.Namespace)) { subject.SubjectConfirmations.Add(this.ReadSubjectConfirmation(reader)); } reader.ReadEndElement(); // Must have a NameID or a SubjectConfirmation if (null == subject.NameId && 0 == subject.SubjectConfirmations.Count) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4108)); } return subject; } catch (Exception e) { if (System.Runtime.Fx.IsFatal(e)) throw; Exception wrapped = TryWrapReadException(reader, e); if (null == wrapped) { throw; } else { throw wrapped; } } } /// /// Writes the <saml:Subject> element. /// /// A to serialize the . /// The to serialize. protected virtual void WriteSubject(XmlWriter writer, Saml2Subject data) { if (null == writer) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (null == data) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data"); } // If there's no ID, there has to be a SubjectConfirmation #pragma warning suppress 56506 // SubjectConfirmations is never null if (null == data.NameId && 0 == data.SubjectConfirmations.Count) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID4108))); } // writer.WriteStartElement(Saml2Constants.Elements.Subject, Saml2Constants.Namespace); // no attributes // 0-1 if (null != data.NameId) { this.WriteNameId(writer, data.NameId); } // 0-OO foreach (Saml2SubjectConfirmation subjectConfirmation in data.SubjectConfirmations) { this.WriteSubjectConfirmation(writer, subjectConfirmation); } // writer.WriteEndElement(); } /// /// Reads the <SubjectConfirmation> element. /// /// A positioned at a element. /// An instance of . protected virtual Saml2SubjectConfirmation ReadSubjectConfirmation(XmlReader reader) { if (null == reader) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } // throw if wrong element if (!reader.IsStartElement(Saml2Constants.Elements.SubjectConfirmation, Saml2Constants.Namespace)) { reader.ReadStartElement(Saml2Constants.Elements.SubjectConfirmation, Saml2Constants.Namespace); } try { bool isEmpty = reader.IsEmptyElement; // @attributes // @xsi:type XmlUtil.ValidateXsiType(reader, Saml2Constants.Types.SubjectConfirmationType, Saml2Constants.Namespace); // @Method - required string method = reader.GetAttribute(Saml2Constants.Attributes.Method); if (string.IsNullOrEmpty(method)) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID0001, Saml2Constants.Attributes.Method, Saml2Constants.Elements.SubjectConfirmation)); } if (!UriUtil.CanCreateValidUri(method, UriKind.Absolute)) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID0011, Saml2Constants.Attributes.Method, Saml2Constants.Elements.SubjectConfirmation)); } // Construct the appropriate SubjectConfirmation based on the method Saml2SubjectConfirmation subjectConfirmation = new Saml2SubjectConfirmation(new Uri(method)); // reader.Read(); if (!isEmpty) { // | | 0-1 subjectConfirmation.NameIdentifier = this.ReadSubjectId(reader, Saml2Constants.Elements.SubjectConfirmation); // 0-1 if (reader.IsStartElement(Saml2Constants.Elements.SubjectConfirmationData, Saml2Constants.Namespace)) { subjectConfirmation.SubjectConfirmationData = this.ReadSubjectConfirmationData(reader); } reader.ReadEndElement(); } return subjectConfirmation; } catch (Exception e) { if (System.Runtime.Fx.IsFatal(e)) throw; Exception wrapped = TryWrapReadException(reader, e); if (null == wrapped) { throw; } else { throw wrapped; } } } /// /// Writes the <saml:SubjectConfirmation> element. /// /// A to serialize the . /// The to serialize. protected virtual void WriteSubjectConfirmation(XmlWriter writer, Saml2SubjectConfirmation data) { if (null == writer) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (null == data) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data"); } if (null == data.Method) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data.Method"); } if (string.IsNullOrEmpty(data.Method.ToString())) { throw DiagnosticUtility.ThrowHelperArgumentNullOrEmptyString("data.Method"); } // writer.WriteStartElement(Saml2Constants.Elements.SubjectConfirmation, Saml2Constants.Namespace); // @Method - required writer.WriteAttributeString(Saml2Constants.Attributes.Method, data.Method.AbsoluteUri); // 0-1 if (null != data.NameIdentifier) { this.WriteNameId(writer, data.NameIdentifier); } // 0-1 if (null != data.SubjectConfirmationData) { this.WriteSubjectConfirmationData(writer, data.SubjectConfirmationData); } // writer.WriteEndElement(); } /// /// Reads the <saml:SubjectConfirmationData> element. /// /// A positioned at a element. /// An instance of . /// /// The default implementation handles the unextended element /// as well as the extended type saml:KeyInfoConfirmationDataType. /// protected virtual Saml2SubjectConfirmationData ReadSubjectConfirmationData(XmlReader reader) { if (null == reader) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } if (!reader.IsStartElement(Saml2Constants.Elements.SubjectConfirmationData, Saml2Constants.Namespace)) { reader.ReadStartElement(Saml2Constants.Elements.SubjectConfirmationData, Saml2Constants.Namespace); } try { Saml2SubjectConfirmationData confirmationData = new Saml2SubjectConfirmationData(); bool isEmpty = reader.IsEmptyElement; // @attributes string value; // @xsi:type bool requireKeyInfo = false; XmlQualifiedName type = XmlUtil.GetXsiType(reader); if (null != type) { if (XmlUtil.EqualsQName(type, Saml2Constants.Types.KeyInfoConfirmationDataType, Saml2Constants.Namespace)) { requireKeyInfo = true; } else if (!XmlUtil.EqualsQName(type, Saml2Constants.Types.SubjectConfirmationDataType, Saml2Constants.Namespace)) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4112, type.Name, type.Namespace)); } } // KeyInfoConfirmationData cannot be empty if (requireKeyInfo && isEmpty) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.GetString(SR.ID4111))); } // @Address - optional value = reader.GetAttribute(Saml2Constants.Attributes.Address); if (!string.IsNullOrEmpty(value)) { confirmationData.Address = value; } // @InResponseTo - optional value = reader.GetAttribute(Saml2Constants.Attributes.InResponseTo); if (!string.IsNullOrEmpty(value)) { confirmationData.InResponseTo = new Saml2Id(value); } // @NotBefore - optional value = reader.GetAttribute(Saml2Constants.Attributes.NotBefore); if (!string.IsNullOrEmpty(value)) { confirmationData.NotBefore = XmlConvert.ToDateTime(value, DateTimeFormats.Accepted); } // @NotOnOrAfter - optional value = reader.GetAttribute(Saml2Constants.Attributes.NotOnOrAfter); if (!string.IsNullOrEmpty(value)) { confirmationData.NotOnOrAfter = XmlConvert.ToDateTime(value, DateTimeFormats.Accepted); } // @Recipient - optional value = reader.GetAttribute(Saml2Constants.Attributes.Recipient); if (!string.IsNullOrEmpty(value)) { if (!UriUtil.CanCreateValidUri(value, UriKind.Absolute)) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID0011, Saml2Constants.Attributes.Recipient, Saml2Constants.Elements.SubjectConfirmationData)); } confirmationData.Recipient = new Uri(value); } // Contents reader.Read(); if (!isEmpty) { // 0-OO OR 1-OO if (requireKeyInfo) { confirmationData.KeyIdentifiers.Add(this.ReadSubjectKeyInfo(reader)); } while (reader.IsStartElement(XmlSignatureConstants.Elements.KeyInfo, XmlSignatureConstants.Namespace)) { confirmationData.KeyIdentifiers.Add(this.ReadSubjectKeyInfo(reader)); } // If this isn't KeyInfo restricted, there might be open content here ... if (!requireKeyInfo && XmlNodeType.EndElement != reader.NodeType) { // So throw and tell the user how to handle the open content throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4114, Saml2Constants.Elements.SubjectConfirmationData)); } reader.ReadEndElement(); } return confirmationData; } catch (Exception e) { if (System.Runtime.Fx.IsFatal(e)) throw; Exception wrapped = TryWrapReadException(reader, e); if (null == wrapped) { throw; } else { throw wrapped; } } } /// /// Writes the <saml:SubjectConfirmationData> element. /// /// /// When the data.KeyIdentifiers collection is not empty, an xsi:type /// attribute will be written specifying saml:KeyInfoConfirmationDataType. /// /// A to serialize the . /// The to serialize. protected virtual void WriteSubjectConfirmationData(XmlWriter writer, Saml2SubjectConfirmationData data) { if (null == writer) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (null == data) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data"); } // writer.WriteStartElement(Saml2Constants.Elements.SubjectConfirmationData, Saml2Constants.Namespace); // @attributes // @xsi:type if (data.KeyIdentifiers != null && data.KeyIdentifiers.Count > 0) { writer.WriteAttributeString("type", XmlSchema.InstanceNamespace, Saml2Constants.Types.KeyInfoConfirmationDataType); } // @Address - optional if (!string.IsNullOrEmpty(data.Address)) { writer.WriteAttributeString(Saml2Constants.Attributes.Address, data.Address); } // @InResponseTo - optional if (null != data.InResponseTo) { writer.WriteAttributeString(Saml2Constants.Attributes.InResponseTo, data.InResponseTo.Value); } // @NotBefore - optional if (null != data.NotBefore) { writer.WriteAttributeString(Saml2Constants.Attributes.NotBefore, XmlConvert.ToString(data.NotBefore.Value.ToUniversalTime(), DateTimeFormats.Generated)); } // @NotOnOrAfter - optional if (null != data.NotOnOrAfter) { writer.WriteAttributeString(Saml2Constants.Attributes.NotOnOrAfter, XmlConvert.ToString(data.NotOnOrAfter.Value.ToUniversalTime(), DateTimeFormats.Generated)); } // @Recipient - optional if (null != data.Recipient) { writer.WriteAttributeString(Saml2Constants.Attributes.Recipient, data.Recipient.OriginalString); } // Content // 0-OO foreach (SecurityKeyIdentifier keyIdentifier in data.KeyIdentifiers) { this.WriteSubjectKeyInfo(writer, keyIdentifier); } // writer.WriteEndElement(); } /// /// Reads the <saml:SubjectLocality> element. /// /// A positioned at a element. /// An instance of . protected virtual Saml2SubjectLocality ReadSubjectLocality(XmlReader reader) { if (null == reader) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } // throw if wrong element if (!reader.IsStartElement(Saml2Constants.Elements.SubjectLocality, Saml2Constants.Namespace)) { reader.ReadStartElement(Saml2Constants.Elements.SubjectLocality, Saml2Constants.Namespace); } try { Saml2SubjectLocality subjectLocality = new Saml2SubjectLocality(); bool isEmpty = reader.IsEmptyElement; // @attributes // @xsi:type XmlUtil.ValidateXsiType(reader, Saml2Constants.Types.SubjectLocalityType, Saml2Constants.Namespace); // @Address - optional subjectLocality.Address = reader.GetAttribute(Saml2Constants.Attributes.Address); // @DNSName - optional subjectLocality.DnsName = reader.GetAttribute(Saml2Constants.Attributes.DNSName); // Empty content reader.Read(); if (!isEmpty) { reader.ReadEndElement(); } return subjectLocality; } catch (Exception e) { if (System.Runtime.Fx.IsFatal(e)) throw; Exception wrapped = TryWrapReadException(reader, e); if (null == wrapped) { throw; } else { throw wrapped; } } } /// /// Writes the <saml:SubjectLocality> element. /// /// A to serialize the . /// The to serialize. protected virtual void WriteSubjectLocality(XmlWriter writer, Saml2SubjectLocality data) { if (null == writer) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (null == data) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data"); } // writer.WriteStartElement(Saml2Constants.Elements.SubjectLocality, Saml2Constants.Namespace); // @Address - optional if (null != data.Address) { writer.WriteAttributeString(Saml2Constants.Attributes.Address, data.Address); } // @DNSName - optional if (null != data.DnsName) { writer.WriteAttributeString(Saml2Constants.Attributes.DNSName, data.DnsName); } // writer.WriteEndElement(); } // This thin wrapper is used to pass a serializer down into the // EnvelpoedSignatureReader that will use the Saml2AssertionSerializer's // ReadKeyInfo method to read the KeyInfo. internal class WrappedSerializer : SecurityTokenSerializer { private Saml2SecurityTokenHandler parent; private Saml2Assertion assertion; public WrappedSerializer(Saml2SecurityTokenHandler parent, Saml2Assertion assertion) { this.assertion = assertion; this.parent = parent; } 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 this.parent.ReadSigningKeyInfo(reader, this.assertion); } protected override SecurityToken ReadTokenCore(XmlReader reader, SecurityTokenResolver tokenResolver) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException()); } /// /// Extensibility point for providing custom serialization. /// /// A to serialize the . /// The to serialize. /// This is not supported. protected override void WriteKeyIdentifierClauseCore(XmlWriter writer, SecurityKeyIdentifierClause keyIdentifierClause) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException()); } /// /// Extensibility point for providing custom serialization. /// /// A to serialize the . /// The to serialize. protected override void WriteKeyIdentifierCore(XmlWriter writer, SecurityKeyIdentifier keyIdentifier) { this.parent.WriteSigningKeyInfo(writer, keyIdentifier); } /// /// Extensibility point for providing custom serialization. /// /// A to serialize the . /// The to serialize. protected override void WriteTokenCore(XmlWriter writer, SecurityToken token) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException()); } } /// /// When encrypted SAML 2.0 token is received, the credentials that are used /// to encrypt the token will be hydrated as a ReceivedEncryptingCredentials. /// This is to distinguish the case between a user explicitly setting an /// encrypting credentials and a re-serialize case where a received token /// is re-serialized by a proxy to a backend service, in which case the token /// should not be encrypted. /// internal class ReceivedEncryptingCredentials : EncryptingCredentials { /// /// Constructs an ReceivedEncryptingCredentials with a security key, a security key identifier and /// the encryption algorithm. /// /// A security key for encryption. /// A security key identifier for the encryption key. /// The encryption algorithm. public ReceivedEncryptingCredentials(SecurityKey key, SecurityKeyIdentifier keyIdentifier, string algorithm) : base(key, keyIdentifier, algorithm) { } } } }