/// <summary>
        /// This gets called on the STS to serialize the token
        /// The token looks like this
        ///     <MyDecisionToken Id="someId here">
        ///         <Decision>True</Decision>
        ///         <Key>key bytes</Key>
        ///         <Signature>...</Signature>
        ///     </MyDecisionToken>
        /// </summary>
        /// <param name="writer">The xml writer.</param>
        /// <param name="token">The security token that will be written.</param>
        /// <exception cref="ArgumentException">When the token is null.</exception>
        public override void WriteToken(XmlWriter writer, SecurityToken token)
        {
            Console.WriteLine("CustomTokenHandler.WriteToken called");
            MyDecisionToken decisionToken = token as MyDecisionToken;

            if (decisionToken == null)
            {
                throw new ArgumentException("The given token must be a MyDecisionToken", "token");
            }

            EnvelopedSignatureWriter envWriter = new EnvelopedSignatureWriter(writer, decisionToken.SigningCredentials, decisionToken.Id, WSSecurityTokenSerializer.DefaultInstance);

            // Start the tokenName
            envWriter.WriteStartElement(TokenName, TokenNamespace);
            envWriter.WriteAttributeString(Id, token.Id);

            // Write the decision element
            Console.WriteLine("- decision being written: {0}", decisionToken.Decision);
            envWriter.WriteElementString(Decision, TokenNamespace, Convert.ToString(decisionToken.Decision));

            // Write the key
            envWriter.WriteElementString(Key, TokenNamespace, Convert.ToBase64String(((MyDecisionToken)token).RetrieveKeyBytes()));

            // Close the TokenName element
            envWriter.WriteEndElement();
        }
示例#2
0
        public void CreateSignatureWithoutSpecifyingDigest(EnvelopedSignatureTheoryData theoryData)
        {
            var context = TestUtilities.WriteHeader($"{this}.CreateSignatureWithoutSpecifyingDigest", theoryData);

            try
            {
                using (var buffer = new MemoryStream())
                {
                    var writer = new EnvelopedSignatureWriter(XmlWriter.Create(buffer), theoryData.SigningCredentials, theoryData.ReferenceId);
                    writer.WriteStartElement("EntityDescriptor", "urn:oasis:names:tc:SAML:2.0:metadata");
                    writer.WriteAttributeString("entityID", "issuer");
                    writer.WriteEndElement();

                    // read and verify signatures
                    EnvelopedSignatureReader envelopedReader = new EnvelopedSignatureReader(XmlUtilities.CreateDictionaryReader(Encoding.UTF8.GetString(buffer.ToArray())));
                    while (envelopedReader.Read())
                    {
                        ;
                    }

                    envelopedReader.Signature.Verify(theoryData.SigningCredentials.Key, theoryData.SigningCredentials.Key.CryptoProviderFactory);
                    theoryData.ExpectedException.ProcessNoException(context);
                }
            }
            catch (Exception ex)
            {
                theoryData.ExpectedException.ProcessException(ex, context);
            }

            TestUtilities.AssertFailIfErrors(context);
        }
示例#3
0
        public void RoundTripWsMetadata(EnvelopedSignatureTheoryData theoryData)
        {
            var context = TestUtilities.WriteHeader($"{this}.RoundTripWsMetadata", theoryData);

            try
            {
                var settings = new XmlWriterSettings
                {
                    Encoding = new UTF8Encoding(false)
                };

                var buffer = new MemoryStream();
                var esw    = new EnvelopedSignatureWriter(XmlWriter.Create(buffer, settings), theoryData.SigningCredentials, theoryData.ReferenceId);

                theoryData.Action.DynamicInvoke(esw);

                var metadata      = Encoding.UTF8.GetString(buffer.ToArray());
                var configuration = new WsFederationConfiguration();
                var reader        = XmlReader.Create(new StringReader(metadata));
                configuration = new WsFederationMetadataSerializer().ReadMetadata(reader);
                configuration.Signature.Verify(theoryData.SigningCredentials.Key, theoryData.SigningCredentials.Key.CryptoProviderFactory);
                theoryData.ExpectedException.ProcessNoException(context);
            }
            catch (Exception ex)
            {
                theoryData.ExpectedException.ProcessException(ex, context);
            }

            TestUtilities.AssertFailIfErrors(context);
        }
示例#4
0
        public void WriteSaml2ProxyRestriction(Saml2TheoryData theoryData)
        {
            TestUtilities.WriteHeader($"{this}.WriteSaml2ProxyRestriction", theoryData);
            var context = new CompareContext($"{this}.WriteSaml2ProxyRestriction, {theoryData.TestId}");

            try
            {
                var ms              = new MemoryStream();
                var writer          = XmlDictionaryWriter.CreateTextWriter(ms, Encoding.UTF8, false);
                var envelopedWriter = new EnvelopedSignatureWriter(writer, Default.AsymmetricSigningCredentials, "ref#1");
                (theoryData.Saml2Serializer as Saml2SerializerPublic).WriteProxyRestrictionPublic(writer, theoryData.ProxyRestriction);

                writer.Flush();
                var xml = Encoding.UTF8.GetString(ms.ToArray());
                IdentityComparer.AreEqual(xml, theoryData.Xml, context);

                theoryData.ExpectedException.ProcessNoException();
            }
            catch (Exception ex)
            {
                theoryData.ExpectedException.ProcessException(ex);
            }

            TestUtilities.AssertFailIfErrors(context);
        }
    /// <summary>
    ///
    /// </summary>
    /// <param name="writer"></param>
    /// <param name="configuration"></param>
    /// <returns></returns>
    public static void WriteMetadata(this WsFederationMetadataSerializer serializer, XmlWriter writer, WsFederationConfiguration configuration)
    {
        if (writer == null)
        {
            throw new ArgumentNullException(nameof(writer));
        }

        if (configuration == null)
        {
            throw new ArgumentNullException(nameof(configuration));
        }

        if (string.IsNullOrEmpty(configuration.Issuer))
        {
            throw XmlUtil.LogWriteException(nameof(configuration.Issuer) + " is null or empty");
        }

        if (string.IsNullOrEmpty(configuration.TokenEndpoint))
        {
            throw XmlUtil.LogWriteException(nameof(configuration.TokenEndpoint) + " is null or empty");
        }

        X509SecurityKey          securityKey        = configuration.SigningKeys.FirstOrDefault() as X509SecurityKey;
        var                      entityDescriptorId = "_" + Guid.NewGuid().ToString();
        EnvelopedSignatureWriter envelopeWriter     = null;

        if (securityKey != null)
        {
            envelopeWriter = new EnvelopedSignatureWriter(
                writer,
                configuration.SigningCredentials,
                "#" + entityDescriptorId);
            writer = envelopeWriter;
        }

        writer.WriteStartDocument();

        // <EntityDescriptor>
        writer.WriteStartElement(Elements.EntityDescriptor, WsFederationConstants.MetadataNamespace);
        // @entityID
        writer.WriteAttributeString(Attributes.EntityId, configuration.Issuer);
        // @ID
        writer.WriteAttributeString(Attributes.Id, entityDescriptorId);

        // if (envelopeWriter != null)
        //     envelopeWriter.WriteSignature();

        WriteSecurityTokenServiceTypeRoleDescriptor(configuration, writer);

        // </EntityDescriptor>
        writer.WriteEndElement();

        writer.WriteEndDocument();
    }
示例#6
0
        protected virtual void WriteEntitiesDescriptor(XmlWriter writer, EntitiesDescriptor entitiesDescriptor)
        {
            if (writer == null)
            {
                throw new ArgumentNullException(nameof(writer));
            }
            if (entitiesDescriptor == null)
            {
                throw new ArgumentNullException(nameof(entitiesDescriptor));
            }
            if (!entitiesDescriptor.ChildEntities.Any() && !entitiesDescriptor.ChildEntityGroups.Any())
            {
                throw new ArgumentNullException(nameof(entitiesDescriptor));
            }

            var entityReference = "_" + Guid.NewGuid();

            if (entitiesDescriptor.SigningCredentials != null)
            {
                writer = new EnvelopedSignatureWriter(writer, entitiesDescriptor.SigningCredentials, entityReference);
            }

            writer.WriteStartElement(Saml2MetadataConstants.Elements.EntitiesDescriptor, Saml2MetadataConstants.Namespace);
            writer.WriteAttributeString(Saml2MetadataConstants.Attributes.Id, null, entityReference);

            foreach (var entity in entitiesDescriptor.ChildEntities)
            {
                if (!string.IsNullOrEmpty(entity.FederationId))
                {
                    if (!StringComparer.Ordinal.Equals(entity.FederationId, entitiesDescriptor.Name))
                    {
                        throw new MetadataSerializationException($"Invalid federation ID of {entity.FederationId}");
                    }
                }
            }

            writer.WriteAttributeIfPresent(Saml2MetadataConstants.Attributes.EntityGroupName, null, entitiesDescriptor.Name);

            WriteCustomAttributes(writer, entitiesDescriptor);

            foreach (var childEntity in entitiesDescriptor.ChildEntities)
            {
                WriteEntityDescriptor(writer, childEntity);
            }

            foreach (var childEntityDescriptor in entitiesDescriptor.ChildEntityGroups)
            {
                WriteEntitiesDescriptor(writer, childEntityDescriptor);
            }

            WriteCustomElements(writer, entitiesDescriptor);

            writer.WriteEndElement();
        }
示例#7
0
 public void Constructor(EnvelopedSignatureTheoryData theoryData)
 {
     TestUtilities.WriteHeader($"{this}.Constructor", theoryData);
     try
     {
         var envelopedWriter = new EnvelopedSignatureWriter(theoryData.XmlWriter, theoryData.SigningCredentials, theoryData.ReferenceId, theoryData.InclusiveNamespacesPrefixList);
         theoryData.ExpectedException.ProcessNoException();
     }
     catch (Exception ex)
     {
         theoryData.ExpectedException.ProcessException(ex);
     }
 }
        private static string CreateSignedXmlWithEmbededTokens(IList <SecurityToken> samlTokens, SigningCredentials xmlSigningCredentials, SigningCredentials tokenSigningCredentials)
        {
            var ms                = new MemoryStream();
            var writer            = XmlDictionaryWriter.CreateTextWriter(ms, Encoding.UTF8, false);
            var samlTokenHandler  = new SamlSecurityTokenHandler();
            var saml2TokenHandler = new Saml2SecurityTokenHandler();
            var envelopedWriter   = new EnvelopedSignatureWriter(writer, xmlSigningCredentials, "ref#1");

            envelopedWriter.WriteStartElement("local", "elementName", "http://elementnamespace");
            envelopedWriter.WriteElementString("localElement", "SamlWillBeEmbeded");

            foreach (var token in samlTokens)
            {
                if (token is SamlSecurityToken)
                {
                    samlTokenHandler.WriteToken(envelopedWriter, token);
                }
                else
                {
                    saml2TokenHandler.WriteToken(envelopedWriter, token);
                }
            }

            envelopedWriter.WriteStartElement("local", "elementName2", "http://elementnamespace");
            envelopedWriter.WriteElementString("localElement", "SamlWillBeEmbeded2");
            foreach (var token in samlTokens)
            {
                if (token is SamlSecurityToken)
                {
                    samlTokenHandler.WriteToken(envelopedWriter, token);
                }
                else
                {
                    saml2TokenHandler.WriteToken(envelopedWriter, token);
                }
            }

            envelopedWriter.WriteEndElement();
            envelopedWriter.WriteEndElement();
            envelopedWriter.Flush();
            var xml = Encoding.UTF8.GetString(ms.ToArray());

            return(xml);
        }
示例#9
0
        public void RoundTripSamlPSignatureAfterAssertion()
        {
            var context = new CompareContext($"{this}.RoundTripSamlPSignatureAfterAssertion");
            ExpectedException expectedException = ExpectedException.NoExceptionExpected;
            var samlpTokenKey = KeyingMaterial.RsaSigningCreds_4096_Public.Key;
            var samlpTokenSigningCredentials = KeyingMaterial.RsaSigningCreds_4096;
            var samlpKey = KeyingMaterial.RsaSigningCreds_2048_Public.Key;
            var samlpSigningCredentials = KeyingMaterial.RsaSigningCreds_2048;

            try
            {
                // write samlp
                var settings = new XmlWriterSettings
                {
                    Encoding = new UTF8Encoding(false)
                };
                var buffer = new MemoryStream();
                var esw    = new EnvelopedSignatureWriter(XmlWriter.Create(buffer, settings), samlpSigningCredentials, "id-uAOhNLe7abGB6WGPk");
                esw.WriteStartElement("ns0", "Response", "urn:oasis:names:tc:SAML:2.0:protocol");

                esw.WriteAttributeString("ns1", "urn:oasis:names:tc:SAML:2.0:assertion");
                esw.WriteAttributeString("ns2", "http://www.w3.org/2000/09/xmldsig#");
                esw.WriteAttributeString("Destination", "https://tnia.eidentita.cz/fpsts/processRequest.aspx");
                esw.WriteAttributeString("ID", "id-uAOhNLe7abGB6WGPk");
                esw.WriteAttributeString("InResponseTo", "ida5714d006fcc430c92aacf34ab30b166");
                esw.WriteAttributeString("IssueInstant", "2019-04-08T10:30:49Z");
                esw.WriteAttributeString("Version", "2.0");
                esw.WriteStartElement("ns1", "Issuer");
                esw.WriteAttributeString("Format", "urn:oasis:names:tc:SAML:2.0:nameid-format:entity");
                esw.WriteString("https://mojeid.regtest.nic.cz/saml/idp.xml");
                esw.WriteEndElement();
                esw.WriteStartElement("ns0", "Status", null);
                esw.WriteStartElement("ns0", "StatusCode", null);
                esw.WriteAttributeString("Value", "urn:oasis:names:tc:SAML:2.0:status:Success");
                esw.WriteEndElement();
                esw.WriteEndElement();
                Saml2Serializer samlSerializer = new Saml2Serializer();
                Saml2Assertion  assertion      = CreateAssertion(samlpTokenSigningCredentials);
                samlSerializer.WriteAssertion(esw, assertion);
                esw.WriteSignature();
                esw.WriteEndElement();
                var xml = Encoding.UTF8.GetString(buffer.ToArray());

                // read samlp and verify signatures
                XmlReader         reader       = XmlUtilities.CreateDictionaryReader(xml);
                IXmlElementReader tokenReaders = new TokenReaders(new List <SecurityTokenHandler> {
                    new Saml2SecurityTokenHandler()
                });
                EnvelopedSignatureReader envelopedReader = new EnvelopedSignatureReader(reader, tokenReaders);

                while (envelopedReader.Read())
                {
                    ;
                }

                foreach (var item in tokenReaders.Items)
                {
                    if (item is Saml2SecurityToken samlToken)
                    {
                        samlToken.Assertion.Signature.Verify(samlpTokenKey);
                    }
                }

                envelopedReader.Signature.Verify(samlpKey, samlpKey.CryptoProviderFactory);
                expectedException.ProcessNoException(context);
            }
            catch (Exception ex)
            {
                expectedException.ProcessException(ex, context);
            }

            TestUtilities.AssertFailIfErrors(context);
        }
        /// <summary>
        /// Serializes the provided SamlAssertion to the XmlWriter.
        /// </summary>
        /// <param name="writer">A <see cref="XmlWriter"/> to serialize the <see cref="Saml2Assertion"/>.</param>
        /// <param name="data">The <see cref="Saml2Assertion"/> to serialize.</param>
        /// <exception cref="ArgumentNullException">The <paramref name="writer"/> or <paramref name="data"/> parameters are null.</exception>
        /// <exception cref="InvalidOperationException"> The <paramref name="data"/>  has both <see cref="EncryptingCredentials"/> and <see cref="ReceivedEncryptingCredentials"/> properties null.</exception>
        /// <exception cref="InvalidOperationException">The <paramref name="data"/> must have a <see cref="Saml2Subject"/> if no <see cref="Saml2Statement"/> are present.</exception>
        /// <exception cref="InvalidOperationException">The SAML2 authentication, attribute, and authorization decision <see cref="Saml2Statement"/> require a <see cref="Saml2Subject"/>.</exception>
        /// <exception cref="CryptographicException">Token encrypting credentials must have a Symmetric Key specified.</exception>
        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 <Subject> 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)));
                        }
                    }
                }

                // <Assertion>
                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);

                // <Issuer> 1
                this.WriteIssuer(writer, data.Issuer);

                // <ds:Signature> 0-1
                if (null != signatureWriter)
                {
                    signatureWriter.WriteSignature();
                }

                // <Subject> 0-1
                if (null != data.Subject)
                {
                    this.WriteSubject(writer, data.Subject);
                }

                // <Conditions> 0-1
                if (null != data.Conditions)
                {
                    this.WriteConditions(writer, data.Conditions);
                }

                // <Advice> 0-1
                if (null != data.Advice)
                {
                    this.WriteAdvice(writer, data.Advice);
                }

                // <Statement|AuthnStatement|AuthzDecisionStatement|AttributeStatement>, 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();
            }
        }
        /// <summary>
        /// Serializes a given SamlAssertion to the XmlWriter.
        /// </summary>
        /// <param name="writer">XmlWriter to use for the serialization.</param>
        /// <param name="assertion">Assertion to be serialized into the XmlWriter.</param>
        /// <exception cref="ArgumentNullException">The input parameter 'writer' or 'assertion' is null.</exception>
        protected virtual void WriteAssertion(XmlWriter writer, SamlAssertion assertion)
        {
            if (writer == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
            }

            if (assertion == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("assertion");
            }

            SamlAssertion SamlAssertion = assertion as SamlAssertion;
            if (SamlAssertion != null)
            {
                if (SamlAssertion.CanWriteSourceData)
                {
                    SamlAssertion.WriteSourceData(writer);
                    return;
                }
            }

            if (assertion.SigningCredentials != null)
            {
                writer = new EnvelopedSignatureWriter(writer, assertion.SigningCredentials, assertion.AssertionId, new WrappedSerializer(this, assertion));
            }
            writer.WriteStartElement(SamlConstants.Prefix, SamlConstants.ElementNames.Assertion, SamlConstants.Namespace);

            writer.WriteAttributeString(SamlConstants.AttributeNames.MajorVersion, null, Convert.ToString(SamlConstants.MajorVersionValue, CultureInfo.InvariantCulture));
            writer.WriteAttributeString(SamlConstants.AttributeNames.MinorVersion, null, Convert.ToString(SamlConstants.MinorVersionValue, CultureInfo.InvariantCulture));
            writer.WriteAttributeString(SamlConstants.AttributeNames.AssertionId, null, assertion.AssertionId);
            writer.WriteAttributeString(SamlConstants.AttributeNames.Issuer, null, assertion.Issuer);
            writer.WriteAttributeString(SamlConstants.AttributeNames.IssueInstant, null, assertion.IssueInstant.ToUniversalTime().ToString(DateTimeFormats.Generated, CultureInfo.InvariantCulture));

            // Write out conditions
            if (assertion.Conditions != null)
            {
                WriteConditions(writer, assertion.Conditions);
            }

            // Write out advice if there is one
            if (assertion.Advice != null)
            {
                WriteAdvice(writer, assertion.Advice);
            }

            // Write statements.
            for (int i = 0; i < assertion.Statements.Count; i++)
            {
                WriteStatement(writer, assertion.Statements[i]);
            }

            writer.WriteEndElement();
        }
示例#12
0
        protected virtual void WriteEntityDescriptor(XmlWriter writer, EntityDescriptor entityDescriptor)
        {
            if (writer == null)
            {
                throw new ArgumentNullException(nameof(writer));
            }
            if (entityDescriptor == null)
            {
                throw new ArgumentNullException(nameof(entityDescriptor));
            }
            if (!entityDescriptor.RoleDescriptors.Any())
            {
                throw new MetadataSerializationException("Missing RoleDescriptors");
            }

            var entityReference = "_" + Guid.NewGuid();

            if (entityDescriptor.SigningCredentials != null)
            {
                writer = new EnvelopedSignatureWriter(writer, entityDescriptor.SigningCredentials, entityReference);
            }

            writer.WriteStartElement(Saml2MetadataConstants.Elements.EntityDescriptor, Saml2MetadataConstants.Namespace);
            writer.WriteAttributeIfPresent(Saml2MetadataConstants.Attributes.Id, null, entityReference);

            if (entityDescriptor.EntityId?.Id == null)
            {
                throw new MetadataSerializationException("Missing entity id");
            }
            writer.WriteAttributeString(Saml2MetadataConstants.Attributes.EntityId, null, entityDescriptor.EntityId.Id);

            writer.WriteAttributeIfPresent(WSFederationMetadataConstants.Attributes.FederationId, WSFederationMetadataConstants.Namespace,
                                           entityDescriptor.FederationId);
            WriteCustomAttributes(writer, entityDescriptor);

            foreach (var roleDescriptor in entityDescriptor.RoleDescriptors)
            {
                if (roleDescriptor is ServiceProviderSingleSignOnDescriptor spSsoDescriptor)
                {
                    WriteServiceProviderSingleSignOnDescriptor(writer, spSsoDescriptor);
                }

                if (roleDescriptor is IdentityProviderSingleSignOnDescriptor idpSsoDescriptor)
                {
                    WriteIdentityProviderSingleSignOnDescriptor(writer, idpSsoDescriptor);
                }

                if (roleDescriptor is ApplicationServiceDescriptor serviceDescriptor)
                {
                    WriteApplicationServiceDescriptor(writer, serviceDescriptor);
                }

                if (roleDescriptor is SecurityTokenServiceDescriptor secDescriptor)
                {
                    WriteSecurityTokenServiceDescriptor(writer, secDescriptor);
                }
            }

            if (entityDescriptor.Organization != null)
            {
                WriteOrganization(writer, entityDescriptor.Organization);
            }

            foreach (var person in entityDescriptor.Contacts)
            {
                WriteContactPerson(writer, person);
            }

            WriteCustomElements(writer, entityDescriptor);

            writer.WriteEndElement();
        }