public OutgoingSamlMessageHandlerTests()
 {
     _factory = new FakeSamlMessageFactory();
     _binding = new FakeBinding();
     _target  = new FakeOutgoingSamlMessageHandler(_factory, new [] { _binding });
     _options = new SamlOptions();
 }
        public void ShouldCreateFailureMessage()
        {
            var options = new SamlOptions {
                SignOutgoingMessages = false
            };

            var samlLogoutResponseMessage = new SamlLogoutResponseMessage {
                Success = false
            };

            var xmlDocument = _logoutResponseFactory.CreateMessage(options, samlLogoutResponseMessage);

            var mgr = new XmlNamespaceManager(xmlDocument.NameTable);

            mgr.AddNamespace("samlp", "urn:oasis:names:tc:SAML:2.0:protocol");
            mgr.AddNamespace("saml", "urn:oasis:names:tc:SAML:2.0:assertion");

            var logoutResponseNode = xmlDocument.SelectSingleNode("/samlp:LogoutResponse", mgr);
            var issuerNode         = xmlDocument.SelectSingleNode("/samlp:LogoutResponse/saml:Issuer", mgr);
            var statusNode         = xmlDocument.SelectSingleNode("/samlp:LogoutResponse/samlp:Status", mgr);
            var statusCodeNode     = xmlDocument.SelectSingleNode("/samlp:LogoutResponse/samlp:Status/samlp:StatusCode", mgr);

            Assert.NotNull(logoutResponseNode);
            Assert.NotNull(issuerNode);
            Assert.NotNull(statusNode);
            Assert.NotNull(statusCodeNode);

            Assert.Equal("", logoutResponseNode.Attributes["Destination"].Value);
            Assert.Equal("2.0", logoutResponseNode.Attributes["Version"].Value);

            Assert.Equal("urn:oasis:names:tc:SAML:2.0:status:Responder", statusCodeNode.Attributes["Value"].Value);
        }
Example #3
0
        public void ShouldCreateSignedMessage()
        {
            var privateCert = new X509Certificate2(File.ReadAllBytes("PrivateTestCert.pfx"), "test");
            var options     = new SamlOptions {
                SignOutgoingMessages = true, ServiceProviderCertificate = privateCert
            };

            var authnRequestMessage = new SamlLogoutRequestMessage {
                NameId = "test"
            };

            var xmlDocument = _logoutRequestFactory.CreateMessage(options, authnRequestMessage);

            var mgr = new XmlNamespaceManager(xmlDocument.NameTable);

            mgr.AddNamespace("samlp", "urn:oasis:names:tc:SAML:2.0:protocol");
            mgr.AddNamespace("saml", "urn:oasis:names:tc:SAML:2.0:assertion");

            var logoutResponseNode = xmlDocument.SelectSingleNode("/samlp:LogoutRequest", mgr);
            var issuerNode         = xmlDocument.SelectSingleNode("/samlp:LogoutRequest/saml:Issuer", mgr);
            var nameIDNode         = xmlDocument.SelectSingleNode("/samlp:LogoutRequest/saml:NameID", mgr);
            var sessionIndexNode   = xmlDocument.SelectSingleNode("/samlp:LogoutRequest/samlp:SessionIndex", mgr);

            Assert.NotNull(logoutResponseNode);
            Assert.NotNull(issuerNode);
            Assert.NotNull(nameIDNode);
            Assert.NotNull(sessionIndexNode);

            Assert.Equal("", logoutResponseNode.Attributes["Destination"].Value);
            Assert.Equal("2.0", logoutResponseNode.Attributes["Version"].Value);

            Assert.Equal("test", nameIDNode.InnerText);
        }
        public TMessageContext Parse(XmlDocument message, SamlOptions options)
        {
            var msg = new TMessageContext();

            var encryptedXml = new X509EncryptedXmlAdapter(message, options.ServiceProviderCertificate);

            encryptedXml.DecryptDocument();

            var rootNode = message.SelectSingleNode($"{SamlAuthenticationDefaults.SamlProtocolNsPrefix}:{RootElementName}", SamlXmlExtensions.NamespaceManager);

            if (rootNode == null)
            {
                throw new ParsingException($"Element '{RootElementName}' missing.");
            }

            SamlXmlExtensions.ParseStandardElements((XmlElement)rootNode, msg);

            var signatureNode = message.DocumentElement.SelectSingleNode("ds:Signature", SamlXmlExtensions.NamespaceManager);

            if (signatureNode != null && options.IdentityProviderCertificate != null)
            {
                var signedXml = new SignedXml(message);
                signedXml.LoadXml((XmlElement)signatureNode);
                msg.HasValidSignature = signedXml.CheckSignature(options.IdentityProviderCertificate, false);
            }

            return(ParseInternal(rootNode, msg, options));
        }
Example #5
0
        protected internal override HandleRequestResult HandleInternal(SamlOptions options, HttpContext httpContext, SamlAuthnResponseMessage messageContext, string relayState = null)
        {
            var principal = new ClaimsPrincipal();

            foreach (var assertion in messageContext.Assertions)
            {
                var claims = new List <Claim>();
                if (assertion.SessionIndex != null)
                {
                    claims.Add(new Claim(SamlAuthenticationDefaults.SessionIndexClaimType, assertion.SessionIndex, ClaimValueTypes.String));
                }

                if (assertion.SubjectNameId != null)
                {
                    claims.Add(new Claim(ClaimTypes.NameIdentifier, assertion.SubjectNameId));
                }

                claims.AddRange(options.ClaimsSelector(assertion.Attributes.ToList()));

                principal.AddIdentity(new ClaimsIdentity(claims, assertion.Issuer));
            }

            var props = new AuthenticationProperties
            {
                RedirectUri = relayState,
                IssuedUtc   = DateTime.Now
            };

            var authTicket = new AuthenticationTicket(principal, props, SamlAuthenticationDefaults.AuthenticationScheme);

            return(HandleRequestResult.Success(authTicket));
        }
Example #6
0
        public void CreatingOptionsShouldReturnMostSecureOptions()
        {
            var options = new SamlOptions();

            Assert.True(options.AcceptSignedMessagesOnly);
            Assert.True(options.SignOutgoingMessages);
        }
        public void ShouldCreateMessage()
        {
            var options = new SamlOptions();

            options.SignOutgoingMessages = false;

            var authnRequestMessage = new SamlAuthnRequestMessage();

            authnRequestMessage.AssertionConsumerServiceUrl = "test";

            var xmlDocument = _authnRequestFactory.CreateMessage(options, authnRequestMessage);

            XmlNamespaceManager mgr = new XmlNamespaceManager(xmlDocument.NameTable);

            mgr.AddNamespace("samlp", "urn:oasis:names:tc:SAML:2.0:protocol");
            mgr.AddNamespace("saml", "urn:oasis:names:tc:SAML:2.0:assertion");

            var authnRequestNode = xmlDocument.SelectSingleNode("/samlp:AuthnRequest", mgr);
            var issuerNode       = xmlDocument.SelectSingleNode("/samlp:AuthnRequest/saml:Issuer", mgr);

            Assert.NotNull(authnRequestNode);
            Assert.NotNull(issuerNode);

            Assert.Equal("test", authnRequestNode.Attributes["AssertionConsumerServiceURL"].Value);
            Assert.Equal("", authnRequestNode.Attributes["Destination"].Value);
            Assert.Equal("2.0", authnRequestNode.Attributes["Version"].Value);
        }
        public void ShouldCreateSignedMessage()
        {
            var privateCert = new X509Certificate2(File.ReadAllBytes("PrivateTestCert.pfx"), "test");
            var options     = new SamlOptions {
                SignOutgoingMessages = true, ServiceProviderCertificate = privateCert
            };

            var authnRequestMessage = new SamlAuthnRequestMessage();

            authnRequestMessage.AssertionConsumerServiceUrl = "test";

            var xmlDocument = _authnRequestFactory.CreateMessage(options, authnRequestMessage);

            XmlNamespaceManager mgr = new XmlNamespaceManager(xmlDocument.NameTable);

            mgr.AddNamespace("samlp", "urn:oasis:names:tc:SAML:2.0:protocol");
            mgr.AddNamespace("saml", "urn:oasis:names:tc:SAML:2.0:assertion");

            var authnRequestNode = xmlDocument.SelectSingleNode("/samlp:AuthnRequest", mgr);
            var issuerNode       = xmlDocument.SelectSingleNode("/samlp:AuthnRequest/saml:Issuer", mgr);

            Assert.NotNull(authnRequestNode);
            Assert.NotNull(issuerNode);

            Assert.Equal("test", authnRequestNode.Attributes["AssertionConsumerServiceURL"].Value);
            Assert.Equal("", authnRequestNode.Attributes["Destination"].Value);
            Assert.Equal("2.0", authnRequestNode.Attributes["Version"].Value);
        }
        public XmlDocument CreateMessage(SamlOptions options, SamlLogoutRequestMessage message)
        {
            var doc = new XmlDocument();

            var logoutRequestElement = doc.CreateElement(SamlAuthenticationDefaults.SamlProtocolNsPrefix, "LogoutRequest", SamlAuthenticationDefaults.SamlProtocolNamespace);

            SamlXmlExtensions.PropagateStandardElements(logoutRequestElement, message);

            var nameIdElement = doc.CreateElement(SamlAuthenticationDefaults.SamlAssertionNsPrefix, "NameID",
                                                  SamlAuthenticationDefaults.SamlAssertionNamespace);

            nameIdElement.InnerText = message.NameId;

            var sessionIndexElement = doc.CreateElement(SamlAuthenticationDefaults.SamlProtocolNsPrefix, "SessionIndex",
                                                        SamlAuthenticationDefaults.SamlProtocolNamespace);

            sessionIndexElement.InnerText = message.SessionIndex;


            logoutRequestElement.AppendChild(nameIdElement);
            logoutRequestElement.AppendChild(sessionIndexElement);

            doc.AppendChild(logoutRequestElement);

            if (options.SignOutgoingMessages)
            {
                SamlXmlExtensions.SignElement(logoutRequestElement, options);
            }

            return(doc);
        }
Example #10
0
        public XmlDocument CreateMessage(SamlOptions options, SamlLogoutResponseMessage message)
        {
            var doc = new XmlDocument();
            var logoutResponseElement = doc.CreateElement(SamlAuthenticationDefaults.SamlProtocolNsPrefix, "LogoutResponse", SamlAuthenticationDefaults.SamlProtocolNamespace);

            SamlXmlExtensions.PropagateStandardElements(logoutResponseElement, message);


            if (message.InResponseTo != null)
            {
                logoutResponseElement.SetAttribute("InResponseTo", message.InResponseTo);
            }

            var statusvalue = (message.Success)
                                    ? "urn:oasis:names:tc:SAML:2.0:status:Success"
                                    : "urn:oasis:names:tc:SAML:2.0:status:Responder";

            var statusElement = doc.CreateElement(SamlAuthenticationDefaults.SamlProtocolNsPrefix, "Status", SamlAuthenticationDefaults.SamlProtocolNamespace);

            var statusCodeElement = doc.CreateElement(SamlAuthenticationDefaults.SamlProtocolNsPrefix, "StatusCode", SamlAuthenticationDefaults.SamlProtocolNamespace);

            statusCodeElement.SetAttribute("Value", statusvalue);

            statusElement.AppendChild(statusCodeElement);
            logoutResponseElement.AppendChild(statusElement);
            doc.AppendChild(logoutResponseElement);

            if (options.SignOutgoingMessages)
            {
                SamlXmlExtensions.SignElement(logoutResponseElement, options);
            }

            return(doc);
        }
Example #11
0
 protected internal override void Validate(SamlOptions options, ExtractionResult extractionResult,
                                           SamlAuthnResponseMessage messageContext)
 {
     if ((DateTime.UtcNow - messageContext.IssueInstant) > options.IssueInstantExpiration)
     {
         throw new SamlException("Issue instant is too long ago.");
     }
 }
 protected internal virtual void Validate(SamlOptions options, ExtractionResult extractionResult,
                                          TMessageContext messageContext)
 {
     if (options.AcceptSignedMessagesOnly && (!extractionResult.HasValidSignature && !messageContext.HasValidSignature))
     {
         throw new SamlException("Messages without or with invalid signatures are not accepted.");
     }
 }
        public void Handle(SamlOptions options, HttpContext context, TMessageContext messageContext, string target, SamlBindingBehavior bindingBehavior, string relayState = null)
        {
            //TODO: Extract creation and preparation of messageContext to a separate method.

            var document = MessageFactory.CreateMessage(options, messageContext);

            GetBinding(bindingBehavior).BindMessage(document, context, target, FlowKey, options, relayState);
        }
Example #14
0
        public void ShouldThrowOnMissingServiceProviderEntityId()
        {
            var options = new SamlOptions
            {
                ServiceProviderEntityId = null,
            };

            Assert.Throws <ArgumentException>(() => _target.PostConfigure(string.Empty, options));
        }
        public EncodingHelperTests()
        {
            _options = new SamlOptions();

            _message = new XmlDocument();
            var el = _message.CreateElement("Message");

            _message.AppendChild(el);
        }
Example #16
0
        internal static QueryString EncodeMessage(SamlOptions options, string encoding, XmlDocument message, string samlFlowKey, string relayState)
        {
            if (encoding == SamlAuthenticationDefaults.DeflateEncoding)
            {
                return(SamlDeflateEncoding(options, message, samlFlowKey, relayState));
            }

            throw new NotImplementedException();
        }
 protected internal override SamlLogoutResponseMessage HandleInternal(SamlOptions options, HttpContext httpContext, SamlLogoutRequestMessage messageContext, string relayState = null)
 {
     httpContext.SignOutAsync(options.SignInScheme).GetAwaiter().GetResult();
     return(new SamlLogoutResponseMessage
     {
         Destination = options.IdentityProviderLogOutUrl,
         Success = true,
         Issuer = options.ServiceProviderEntityId,
         InResponseTo = messageContext.Id
     });
 }
        public HttpRedirectBindingHandlerTests()
        {
            _target = new HttpRedirectBindingHandler();

            _message = new XmlDocument();
            var root = _message.CreateElement("Message");

            _message.AppendChild(root);

            _options = new SamlOptions();
            _ctx     = new DefaultHttpContext();
            _uri     = "http://test.com:8080/saml-idp";
        }
Example #19
0
        public void ShouldThrowOnMissingIdentityProviderCertificate()
        {
            var options = new SamlOptions
            {
                ServiceProviderEntityId     = "MyId",
                AcceptSignedMessagesOnly    = true,
                IdentityProviderCertificate = null,
            };

            var ex = Assert.Throws <InvalidOperationException>(() => _target.PostConfigure(string.Empty, options));

            Assert.Equal("Identity Provider Certificate needed for validating signatures of incoming messages. Set AcceptSignedResponsesOnly to false if signature validation is not intended.", ex.Message);
        }
Example #20
0
        protected internal override object HandleInternal(SamlOptions options, HttpContext httpContext, SamlLogoutResponseMessage messageContext, string relayState = null)
        {
            if (messageContext.Success)
            {
                httpContext.SignOutAsync(options.SignInScheme).Wait();
            }

            if (relayState != null)
            {
                httpContext.Response.Redirect(relayState);
            }

            return(null);
        }
        private static ExtractionResult ExtractMessage(HttpContext context, SamlOptions options)
        {
            if (HttpMethods.IsGet(context.Request.Method))
            {
                return(ExtractionHelper.ExtractHttpRedirect(context.Request.Query, options.IdentityProviderCertificate));
            }

            if (HttpMethods.IsPost(context.Request.Method))
            {
                return(ExtractionHelper.ExtractHttpPost(context.Request.Form, options.IdentityProviderCertificate));
            }

            //HTTP-Methods other than GET and POST are not supported
            throw new InvalidOperationException($"HTTP-Method {context.Request.Method} is not supported.");
        }
        public THandlingResult Handle(SamlOptions options, HttpContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            var result = ExtractMessage(context, options);

            var messageContext = MessageParser.Parse(result.Message, options);

            Validate(options, result, messageContext);

            return(HandleInternal(options, context, messageContext, result.RelayState));
        }
Example #23
0
        public void ShouldThrowOnMissingServiceProviderCertificate()
        {
            var options = new SamlOptions
            {
                ServiceProviderEntityId     = "MyId",
                ServiceProviderCertificate  = null,
                AcceptSignedMessagesOnly    = false,
                IdentityProviderCertificate = null,
                SignOutgoingMessages        = true
            };

            var ex = Assert.Throws <InvalidOperationException>(() => _target.PostConfigure(string.Empty, options));

            Assert.Equal("Service Provider Certificate needed for signing outgoing messages. Set SignOutgoingMessages to false if signing is not intended.", ex.Message);
        }
Example #24
0
        public void ShouldThrowOnMissingIdentityProviderSignOnUrl()
        {
            var options = new SamlOptions
            {
                ServiceProviderEntityId     = "MyId",
                ServiceProviderCertificate  = null,
                AcceptSignedMessagesOnly    = false,
                IdentityProviderCertificate = null,
                SignOutgoingMessages        = false,
                IdentityProviderSignOnUrl   = null,
                IdentityProviderLogOutUrl   = null,
            };

            var ex = Assert.Throws <InvalidOperationException>(() => _target.PostConfigure(string.Empty, options));

            Assert.Equal("URL for SignOn needed", ex.Message);
        }
Example #25
0
        public void ShouldSetClaimsSelector()
        {
            var options = new SamlOptions
            {
                ServiceProviderEntityId     = "MyId",
                ServiceProviderCertificate  = null,
                AcceptSignedMessagesOnly    = false,
                IdentityProviderCertificate = null,
                SignOutgoingMessages        = false,
                IdentityProviderSignOnUrl   = "ips-sign-on-url",
                IdentityProviderLogOutUrl   = "ips-log-out-url",
                ClaimsSelector = null,
            };

            _target.PostConfigure(string.Empty, options);
            Assert.NotNull(options.ClaimsSelector);
        }
Example #26
0
        private static QueryString SamlDeflateEncoding(SamlOptions options, XmlDocument message, string samlFlowKey, string relayState)
        {
            //See https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf for details
            var dict = new Dictionary <string, string>();

            var signatureElement = message.RemoveSignature();

            var messageBytes  = message.Deflate();
            var base64Message = Convert.ToBase64String(messageBytes);

            dict.Add(samlFlowKey, base64Message);

            if (!string.IsNullOrEmpty(relayState))
            {
                dict.Add(SamlAuthenticationDefaults.RelayStateKey, relayState);
            }

            if (signatureElement != null)
            {
                //Construct Signature
                byte[] signature;

                var stringBuilder = new StringBuilder();
                stringBuilder.Append($"{samlFlowKey}={WebUtility.UrlEncode(dict[samlFlowKey])}");

                if (!string.IsNullOrEmpty(relayState))
                {
                    stringBuilder.Append($"&{SamlAuthenticationDefaults.RelayStateKey}={WebUtility.UrlEncode(relayState)}");
                }

                dict.Add(SamlAuthenticationDefaults.SignatureAlgorithmKey, "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
                stringBuilder.Append($"&{SamlAuthenticationDefaults.SignatureAlgorithmKey}={WebUtility.UrlEncode(dict[SamlAuthenticationDefaults.SignatureAlgorithmKey])}");

                using (var rsa = options.ServiceProviderCertificate.GetRSAPrivateKey())
                {
                    var signatureData = Encoding.UTF8.GetBytes(stringBuilder.ToString());

                    signature = rsa.SignData(signatureData, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
                }

                dict.Add(SamlAuthenticationDefaults.SignatureKey, Convert.ToBase64String(signature));
            }

            return(QueryString.Create(dict));
        }
        public HttpPostBindingHandlerTests()
        {
            _target = new HttpPostBindingHandler();
            _ctx    = new DefaultHttpContext();

            _ctx.Features.Set <IHttpResponseFeature>(new HttpResponseFeature
            {
                Body = new MemoryStream()
            });

            _uri = "http://test.com/saml-idp";

            _message = new XmlDocument();
            var el = _message.CreateElement("Message");

            _message.AppendChild(el);

            _options = new SamlOptions();
        }
        public XmlDocument CreateMessage(SamlOptions options, SamlAuthnRequestMessage authnRequestMessage)
        {
            var doc = new XmlDocument();

            var authnRequestElement = doc.CreateElement(SamlAuthenticationDefaults.SamlProtocolNsPrefix, "AuthnRequest", SamlAuthenticationDefaults.SamlProtocolNamespace);

            SamlXmlExtensions.PropagateStandardElements(authnRequestElement, authnRequestMessage);

            authnRequestElement.SetAttribute("AssertionConsumerServiceURL", authnRequestMessage.AssertionConsumerServiceUrl);

            doc.AppendChild(authnRequestElement);

            if (options.SignOutgoingMessages)
            {
                SamlXmlExtensions.SignElement(authnRequestElement, options);
            }

            return(doc);
        }
Example #29
0
        public void ShouldDefaultCallpackPathAndLogoutPath()
        {
            var options = new SamlOptions
            {
                ServiceProviderEntityId     = "MyId",
                ServiceProviderCertificate  = null,
                AcceptSignedMessagesOnly    = false,
                AcceptSignedAssertionsOnly  = false,
                IdentityProviderCertificate = null,
                SignOutgoingMessages        = false,
                IdentityProviderSignOnUrl   = "ips-sign-on-url",
                IdentityProviderLogOutUrl   = "ips-log-out-url",
                CallbackPath = null,
                LogoutPath   = null,
            };

            _target.PostConfigure(string.Empty, options);
            Assert.Equal("/saml-auth", options.CallbackPath);
            Assert.Equal("/saml-logout", options.LogoutPath);
        }
        public IncomingSamlMessageHandlerTests()
        {
            _parser = new FakeSamlMessageParserBase {
                ParseResult = new FakeSamlMessage()
            };
            _target  = new FakeIncomingSamlMessageHandler(_parser);
            _options = new SamlOptions
            {
                AcceptSignedMessagesOnly = false
            };
            _context = new DefaultHttpContext();
            _context.Request.Method = "GET";

            var dict = new Dictionary <string, StringValues>
            {
                { SamlAuthenticationDefaults.SamlRequestKey, Convert.ToBase64String(Encoding.UTF8.GetBytes("<Payload/>")) }
            };

            _context.Request.Query = new QueryCollection(dict);
        }