/// <summary>
        /// Validates the X509 certificate signature.
        /// </summary>
        /// <param name="xmlDoc">The XML document.</param>
        /// <param name="options">The options.</param>
        /// <returns></returns>
        public bool ValidateX509CertificateSignature(XmlDocument xmlDoc, Saml2Options options)
        {
            XmlNodeList XMLSignatures = xmlDoc.GetElementsByTagName(Saml2Constants.Parameters.Signature, Saml2Constants.Namespaces.DsNamespace);

            //XmlNodeList XMLSignatures = xnlDoc.GetElementsByTagName("Signature", "http://www.w3.org/2000/09/xmldsig#");

            // Checking If the Response or the Assertion has been signed once and only once.
            if (XMLSignatures.Count != 1)
            {
                return(false);
            }

            var signedXmlDoc = new SignedXml(xmlDoc);

            signedXmlDoc.LoadXml((XmlElement)XMLSignatures[0]);

            //IDP might have multiple siing certs. Get the correct one and check it
            KeyInfoX509Data  x509data     = signedXmlDoc.Signature.KeyInfo.OfType <KeyInfoX509Data>().First();
            X509Certificate2 cert         = (X509Certificate2)x509data.Certificates[0];
            string           serialNumber = cert.SerialNumber;

            //bool verified = cert != null && signedXmlDoc.CheckSignature(cert, false);
            //var t= signedXmlDoc.CheckSignature(GetIdentityProviderCertficate(options, serialNumber), false);
            return(signedXmlDoc.CheckSignature(GetIdentityProviderCertficate(options, serialNumber), false));
        }
Beispiel #2
0
        /// <summary>
        /// Gets the identity provider certficate.
        /// </summary>
        /// <param name="options">The options.</param>
        /// <param name="serialNumber">The serial number.</param>
        /// <returns></returns>
        private X509Certificate2 GetIdentityProviderCertficate(Saml2Options options, string serialNumber)
        {
            X509Certificate2 cert = options.Configuration.X509Certificate2.Where(c => c.SerialNumber == serialNumber).FirstOrDefault();

            if (cert == null)
            {
                throw new Exception("No matching certificate found. Assertion is not from known Idp.");
            }
            return(cert);
        }
Beispiel #3
0
        /// <summary>
        /// Validates the X509 certificate signature.
        /// </summary>
        /// <param name="xmlDoc">The XML document.</param>
        /// <param name="options">The options.</param>
        /// <returns></returns>
        public bool ValidateX509CertificateSignature(XmlDocument xmlDoc, Saml2Options options)
        {
            XmlNodeList XMLSignatures = xmlDoc.GetElementsByTagName(Saml2Constants.Parameters.Signature, Saml2Constants.Namespaces.DsNamespace);

            // Checking If the Response and/or the Assertion has been signed once and only once.
            if (XMLSignatures.Count <= Convert.ToInt32(options.RequireMessageSigned) + Convert.ToInt32(options.WantAssertionsSigned))
            {
                return(false);
            }

            var signedXmlDoc = new SignedXml(xmlDoc);

            signedXmlDoc.LoadXml((XmlElement)XMLSignatures[0]);

            //IDP might have multiple siing certs. Get the correct one and check it
            KeyInfoX509Data  x509data     = signedXmlDoc.Signature.KeyInfo.OfType <KeyInfoX509Data>().First();
            X509Certificate2 cert         = (X509Certificate2)x509data.Certificates[0];
            string           serialNumber = cert.SerialNumber;

            return(signedXmlDoc.CheckSignature(GetIdentityProviderCertficate(options, serialNumber), options.VerifySignatureOnly));
        }
Beispiel #4
0
        /// <summary>
        /// Creates the authn request.
        /// </summary>
        /// <param name="options">The options.</param>
        /// <param name="authnRequestId">The authn request identifier.</param>
        /// <param name="relayState">State of the relay.</param>
        /// <param name="assertionConsumerServiceUrl">The assertion consumer service URL.</param>
        /// <returns></returns>
        /// <exception cref="ArgumentException">Signing key must be an instance of either RSA or DSA.</exception>
        public string CreateAuthnRequest(Saml2Options options, string authnRequestId, string relayState, string assertionConsumerServiceUrl)
        {
            NameIDType entityID = new NameIDType()
            {
                Value = options.ServiceProvider.EntityId
            };

            var singleSignOnService        = options.Configuration.SingleSignOnServices.FirstOrDefault(x => x.Binding == options.AssertionConsumerServiceProtocolBinding);
            X509Certificate2 spCertificate = GetServiceProviderCertficate(options);

            AuthnRequest authnRequest = new AuthnRequest()
            {
                ID                  = authnRequestId,
                Issuer              = entityID,
                Version             = Saml2Constants.Version,
                ForceAuthn          = options.ForceAuthn,
                ForceAuthnSpecified = true,
                IsPassive           = options.IsPassive,
                IsPassiveSpecified  = true,
                NameIDPolicy        = new NameIDPolicyType()
                {
                    Format               = options.NameIDType.Format,
                    SPNameQualifier      = options.NameIDType.SPNameQualifier,
                    AllowCreate          = true,
                    AllowCreateSpecified = true
                },
                Destination                 = singleSignOnService.Location.ToString(),
                ProtocolBinding             = singleSignOnService.Binding.ToString(),
                IssueInstant                = DateTime.UtcNow,
                AssertionConsumerServiceURL = assertionConsumerServiceUrl
            };

            string singleSignOnUrl = options.Configuration.SingleSignOnServices.FirstOrDefault().Location;

            //serialize AuthnRequest to xml string
            string        xmlTemplate   = string.Empty;
            XmlSerializer xmlSerializer = new XmlSerializer(typeof(AuthnRequest));

            using (MemoryStream memStm = new MemoryStream())
            {
                xmlSerializer.Serialize(memStm, authnRequest);
                memStm.Position = 0;
                xmlTemplate     = new StreamReader(memStm).ReadToEnd();
            }

            //create xml document from string
            XmlDocument xmlDoc = new XmlDocument();

            xmlDoc.LoadXml(xmlTemplate);
            xmlDoc.PreserveWhitespace = false;
            string request = xmlDoc.OuterXml;

            var result = new StringBuilder();

            result.AddMessageParameter(request, null);
            result.AddRelayState(request, relayState);
            if (options.ServiceProvider.X509Certificate2 != null)
            {
                AsymmetricAlgorithm spPrivateKey = spCertificate.PrivateKey;
                string hashingAlgorithm          = options.Configuration.Signature.SignedInfo.SignatureMethod;
                // Check if the key is of a supported type. [SAMLBind] sect. 3.4.4.1 specifies this.
                if (!(spPrivateKey is RSA || spPrivateKey is DSA || spPrivateKey == null))
                {
                    throw new ArgumentException("Signing key must be an instance of either RSA or DSA.");
                }

                AddSignature(result, spPrivateKey, hashingAlgorithm, options.ServiceProvider.HashAlgorithm);
            }
            return($"{singleSignOnUrl}?{result}");
        }
Beispiel #5
0
 /// <summary>
 /// Gets the service provider certficate.
 /// </summary>
 /// <param name="options">The options.</param>
 /// <returns></returns>
 private X509Certificate2 GetServiceProviderCertficate(Saml2Options options)
 {
     return(options.ServiceProvider.X509Certificate2);
 }
Beispiel #6
0
        /// <summary>
        /// Gets the assertion.
        /// </summary>
        /// <param name="idpSamlResponseToken">The idp saml response token.</param>
        /// <param name="options">The options.</param>
        /// <returns></returns>
        /// <exception cref="Exception">Missing assertion
        /// or
        /// Unable to parse the decrypted assertion.</exception>
        /// <exception cref="System.Exception">Missing assertion
        /// or
        /// Unable to parse the decrypted assertion.</exception>
        public string GetAssertion(ResponseType idpSamlResponseToken, Saml2Options options)
        {
            string token;
            string xmlTemplate;
            var    assertion = idpSamlResponseToken.Items[0];

            if (assertion == null)
            {
                throw new Exception("Missing assertion");
            }

            //check if its a decrypted assertion
            if (assertion.GetType() == typeof(EncryptedElementType))
            {
                EncryptedElementType encryptedElement = (EncryptedElementType)assertion;
                SymmetricAlgorithm   sessionKey;

                if (encryptedElement.EncryptedData.EncryptionMethod != null)
                {
                    sessionKey = ExtractSessionKey(encryptedElement, options.ServiceProvider.X509Certificate2.PrivateKey);

                    var           encryptedXml  = new EncryptedXml();
                    XmlSerializer xmlSerializer = new XmlSerializer(typeof(EncryptedDataType));
                    using (MemoryStream memStm = new MemoryStream())
                    {
                        xmlSerializer.Serialize(memStm, encryptedElement.EncryptedData);
                        memStm.Position = 0;
                        xmlTemplate     = new StreamReader(memStm).ReadToEnd();
                    }

                    var doc = new XmlDocument();
                    doc.PreserveWhitespace = true;
                    doc.LoadXml(xmlTemplate);
                    var t             = doc.GetElementsByTagName("EncryptedData");
                    var encryptedData = new EncryptedData();
                    encryptedData.LoadXml((XmlElement)t[0]);

                    byte[] plaintext = encryptedXml.DecryptData(encryptedData, sessionKey);
                    token = Encoding.UTF8.GetString(plaintext);
                    return(token);
                }
            }
            else
            {
                XmlSerializer xmlSerializer = new XmlSerializer(typeof(AssertionType));
                using (MemoryStream memStm = new MemoryStream())
                {
                    xmlSerializer.Serialize(memStm, assertion);
                    memStm.Position = 0;
                    xmlTemplate     = new StreamReader(memStm).ReadToEnd();
                }

                var doc = new XmlDocument();
                doc.PreserveWhitespace = false;
                doc.LoadXml(xmlTemplate);

                string request = doc.OuterXml;
                return(request);
            }
            throw new Exception("Unable to parse the decrypted assertion.");
        }
Beispiel #7
0
        /// <summary>
        /// Gets the saml response token.
        /// </summary>
        /// <param name="base64EncodedSamlResponse">The base64 encoded saml response.</param>
        /// <param name="responseType">Type of the response.</param>
        /// <param name="options"></param>
        /// <returns></returns>
        /// <exception cref="Exception">Response signature is not valid</exception>
        /// <exception cref="ArgumentException">Cannot verify signature.</exception>
        public ResponseType GetSamlResponseToken(string base64EncodedSamlResponse, string responseType, Saml2Options options)
        {
            var doc = new XmlDocument
            {
                XmlResolver        = null,
                PreserveWhitespace = true
            };

            if (base64EncodedSamlResponse.Contains("%"))
            {
                base64EncodedSamlResponse = HttpUtility.UrlDecode(base64EncodedSamlResponse);
            }

            byte[] bytes        = Convert.FromBase64String(base64EncodedSamlResponse);
            string samlResponse = Encoding.UTF8.GetString(bytes);

            doc.LoadXml(samlResponse);

            if (options.RequireMessageSigned)
            {
                if (!ValidateX509CertificateSignature(doc, options))
                {
                    throw new Exception("Response signature is not valid");
                }
            }

            ResponseType  samlResponseToken;
            XmlSerializer xmlSerializers = new XmlSerializer(typeof(ResponseType), new XmlRootAttribute {
                ElementName = responseType, Namespace = Saml2Constants.Namespaces.Protocol, IsNullable = false
            });

            using (XmlReader reader = new XmlNodeReader(doc))
            {
                samlResponseToken = (ResponseType)xmlSerializers.Deserialize(reader);
            }
            return(samlResponseToken);
        }
Beispiel #8
0
        /// <summary>
        /// Creates the logout request.
        /// The "LogoutRequest" message MUST be signed if the HTTP POST or Redirect binding is used.
        /// Logout request MUST be signed according to http://docs.oasis-open.org/security/saml/v2.0/saml-profiles-2.0-os.pdf Sect 4.4.3.1
        /// </summary>
        /// <param name="options">The options.</param>
        /// <param name="logoutRequestId">The logout request identifier.</param>
        /// <param name="sessionIndex">Index of the session.</param>
        /// <param name="nameId">The name identifier.</param>
        /// <param name="relayState">State of the relay.</param>
        /// <param name="sendSignoutTo">The send signout to.</param>
        /// <returns></returns>
        /// <exception cref="ArgumentException">Signing key must be an instance of either RSA or DSA.</exception>
        public string CreateLogoutRequest(Saml2Options options, string logoutRequestId, string sessionIndex, string nameId, string relayState, string sendSignoutTo)
        {
            NameIDType entityID = new NameIDType()
            {
                Value = options.ServiceProvider.EntityId
            };

            var singleLogoutService = options.Configuration.SingleLogoutServices.FirstOrDefault(x => x.Binding == options.SingleLogoutServiceProtocolBinding);

            LogoutRequest logoutRequest = new LogoutRequest()
            {
                ID           = logoutRequestId,
                Issuer       = entityID,
                Version      = Saml2Constants.Version,
                Reason       = Saml2Constants.Reasons.User,
                SessionIndex = new string[] { sessionIndex },
                Destination  = singleLogoutService.Location.ToString(),
                IssueInstant = DateTime.UtcNow,
                Item         = new NameIDType()
                {
                    Format          = options.NameIDType.Format,
                    NameQualifier   = options.NameIDType.NameQualifier,
                    SPProvidedID    = options.NameIDType.SPProvidedID,
                    SPNameQualifier = options.NameIDType.SPNameQualifier,
                    Value           = options.NameIDType.Value
                }
            };

            string singleLogoutUrl = options.Configuration.SingleLogoutServices.FirstOrDefault().Location;

            //serialize AuthnRequest to xml string
            string        xmlTemplate   = string.Empty;
            XmlSerializer xmlSerializer = new XmlSerializer(typeof(LogoutRequest));

            using (MemoryStream memStm = new MemoryStream())
            {
                xmlSerializer.Serialize(memStm, logoutRequest);
                memStm.Position = 0;
                xmlTemplate     = new StreamReader(memStm).ReadToEnd();
            }

            //create xml document from string
            XmlDocument xmlDoc = new XmlDocument();

            xmlDoc.LoadXml(xmlTemplate);
            xmlDoc.PreserveWhitespace = false;
            string request = xmlDoc.OuterXml;

            var result = new StringBuilder();

            result.AddMessageParameter(request, null);
            result.AddRelayState(request, relayState);
            if (options.hasCertificate)
            {
                X509Certificate2    spCertificate    = GetServiceProviderCertficate(options);
                string              hashingAlgorithm = options.Configuration.Signature.SignedInfo.SignatureMethod;
                AsymmetricAlgorithm spPrivateKey     = spCertificate.PrivateKey;
                // Check if the key is of a supported type. [SAMLBind] sect. 3.4.4.1 specifies this.
                if (!(spPrivateKey is RSA || spPrivateKey is DSA || spPrivateKey == null))
                {
                    throw new ArgumentException("Signing key must be an instance of either RSA or DSA.");
                }
                AddSignature(result, spPrivateKey, hashingAlgorithm, options.ServiceProvider.HashAlgorithm);
            }
            return($"{singleLogoutUrl}?{result}");
        }