/// <summary> /// Creates a Version 1.1 Saml Assertion /// </summary> /// <param name="issuer">Issuer</param> /// <param name="subject">Subject</param> /// <param name="attributes">Attributes</param> /// <returns>returns a Version 1.1 Saml Assertion</returns> private static AssertionType CreateSamlAssertion(string issuer, string recipient, string domain, string subject, Dictionary <string, string> attributes) { // Here we create some SAML assertion with ID and Issuer name. AssertionType assertion = new AssertionType(); assertion.ID = "_" + Guid.NewGuid().ToString(); NameIDType issuerForAssertion = new NameIDType(); issuerForAssertion.Value = issuer.Trim(); assertion.Issuer = issuerForAssertion; assertion.Version = "2.0"; assertion.IssueInstant = System.DateTime.UtcNow; //Not before, not after conditions ConditionsType conditions = new ConditionsType(); conditions.NotBefore = DateTime.UtcNow; conditions.NotBeforeSpecified = true; conditions.NotOnOrAfter = DateTime.UtcNow.AddMinutes(5); conditions.NotOnOrAfterSpecified = true; AudienceRestrictionType audienceRestriction = new AudienceRestrictionType(); audienceRestriction.Audience = new string[] { domain.Trim() }; conditions.Items = new ConditionAbstractType[] { audienceRestriction }; //Name Identifier to be used in Saml Subject NameIDType nameIdentifier = new NameIDType(); nameIdentifier.NameQualifier = domain.Trim(); nameIdentifier.Value = subject.Trim(); SubjectConfirmationType subjectConfirmation = new SubjectConfirmationType(); SubjectConfirmationDataType subjectConfirmationData = new SubjectConfirmationDataType(); subjectConfirmation.Method = "urn:oasis:names:tc:SAML:2.0:cm:bearer"; subjectConfirmation.SubjectConfirmationData = subjectConfirmationData; // // Create some SAML subject. SubjectType samlSubject = new SubjectType(); AttributeStatementType attrStatement = new AttributeStatementType(); AuthnStatementType authStatement = new AuthnStatementType(); authStatement.AuthnInstant = DateTime.UtcNow; AuthnContextType context = new AuthnContextType(); context.ItemsElementName = new ItemsChoiceType5[] { ItemsChoiceType5.AuthnContextClassRef }; context.Items = new object[] { "AuthnContextClassRef" }; authStatement.AuthnContext = context; samlSubject.Items = new object[] { nameIdentifier, subjectConfirmation }; assertion.Subject = samlSubject; IPHostEntry ipEntry = Dns.GetHostEntry(System.Environment.MachineName); SubjectLocalityType subjectLocality = new SubjectLocalityType(); subjectLocality.Address = ipEntry.AddressList[0].ToString(); attrStatement.Items = new AttributeType[attributes.Count]; int i = 0; // Create userName SAML attributes. foreach (KeyValuePair <string, string> attribute in attributes) { AttributeType attr = new AttributeType(); attr.Name = attribute.Key; attr.NameFormat = "urn:oasis:names:tc:SAML:2.0:attrname-format:basic"; attr.AttributeValue = new object[] { attribute.Value }; attrStatement.Items[i] = attr; i++; } assertion.Conditions = conditions; assertion.Items = new StatementAbstractType[] { authStatement, attrStatement }; return(assertion); }
private void CreateSAMLResponse() { FormsIdentity id = null; if (HttpContext.Current.User != null) { if (HttpContext.Current.User.Identity.IsAuthenticated) { if (HttpContext.Current.User.Identity is FormsIdentity) { id = (FormsIdentity)HttpContext.Current.User.Identity; } } } DateTime notBefore = (id != null ? id.Ticket.IssueDate.ToUniversalTime() : DateTime.UtcNow); DateTime notOnOrAfter = (id != null ? id.Ticket.Expiration.ToUniversalTime() : DateTime.UtcNow.AddMinutes(30)); IDProvider config = IDProvider.GetConfig(); SAMLResponse.Status = new StatusType(); SAMLResponse.Status.StatusCode = new StatusCodeType(); SAMLResponse.Status.StatusCode.Value = SAMLUtility.StatusCodes.Success; AssertionType assert = new AssertionType(); assert.ID = SAMLUtility.GenerateID(); assert.IssueInstant = DateTime.UtcNow.AddMinutes(10); assert.Issuer = new NameIDType(); assert.Issuer.Value = config.id; SubjectConfirmationType subjectConfirmation = new SubjectConfirmationType(); subjectConfirmation.Method = "urn:oasis:names:tc:SAML:2.0:cm:bearer"; subjectConfirmation.SubjectConfirmationData = new SubjectConfirmationDataType(); subjectConfirmation.SubjectConfirmationData.Recipient = SAMLRequest.Issuer; subjectConfirmation.SubjectConfirmationData.InResponseTo = SAMLRequest.Request.ID; subjectConfirmation.SubjectConfirmationData.NotOnOrAfter = notOnOrAfter; NameIDType nameID = new NameIDType(); nameID.Format = SAMLUtility.NameIdentifierFormats.Transient; nameID.Value = (id != null ? id.Name : UtilBO.FormatNameFormsAuthentication(this.__SessionWEB.__UsuarioWEB.Usuario)); assert.Subject = new SubjectType(); assert.Subject.Items = new object[] { subjectConfirmation, nameID }; assert.Conditions = new ConditionsType(); assert.Conditions.NotBefore = notBefore; assert.Conditions.NotOnOrAfter = notOnOrAfter; assert.Conditions.NotBeforeSpecified = true; assert.Conditions.NotOnOrAfterSpecified = true; AudienceRestrictionType audienceRestriction = new AudienceRestrictionType(); audienceRestriction.Audience = new string[] { SAMLRequest.Issuer }; assert.Conditions.Items = new ConditionAbstractType[] { audienceRestriction }; AuthnStatementType authnStatement = new AuthnStatementType(); authnStatement.AuthnInstant = DateTime.UtcNow; authnStatement.SessionIndex = SAMLUtility.GenerateID(); authnStatement.AuthnContext = new AuthnContextType(); authnStatement.AuthnContext.Items = new object[] { "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport" }; authnStatement.AuthnContext.ItemsElementName = new ItemsChoiceType5[] { ItemsChoiceType5.AuthnContextClassRef }; StatementAbstractType[] statementAbstract = new StatementAbstractType[] { authnStatement }; assert.Items = statementAbstract; SAMLResponse.Items = new object[] { assert }; string xmlResponse = SAMLUtility.SerializeToXmlString(SAMLResponse); XmlDocument doc = new XmlDocument(); doc.LoadXml(xmlResponse); XmlSignatureUtils.SignDocument(doc, assert.ID); SAMLResponse = SAMLUtility.DeserializeFromXmlString <ResponseType>(doc.InnerXml); HttpPostBinding binding = new HttpPostBinding(SAMLResponse, HttpUtility.UrlDecode(Request[HttpBindingConstants.RelayState])); binding.SendResponse(this.Context, HttpUtility.UrlDecode(SAMLRequest.AssertionConsumerServiceURL), SAMLTypeSSO.signon); }
/// <summary> /// Handles the sign in. /// </summary> /// <returns></returns> /// <exception cref="SecurityTokenException">No token validator was found for the given token.</exception> private async Task <HandleRequestResult> HandleSignIn() { if (Request.Method != HttpMethods.Post) { return(HandleRequestResult.Fail("Request method must be an HTTP-Post Method")); } var form = await Request.ReadFormAsync(); var response = form[Saml2Constants.Parameters.SamlResponse]; var relayState = form[Saml2Constants.Parameters.RelayState].ToString()?.DeflateDecompress(); AuthenticationProperties authenticationProperties = Options.StateDataFormat.Unprotect(relayState); try { if (authenticationProperties == null) { if (!Options.AllowUnsolicitedLogins) { return(HandleRequestResult.Fail("Unsolicited logins are not allowed.")); } } if (authenticationProperties.Items.TryGetValue(CorrelationProperty, out string correlationId) && !ValidateCorrelationId(authenticationProperties)) { return(HandleRequestResult.Fail("Correlation failed.", authenticationProperties)); } string base64EncodedSamlResponse = response; ResponseType idpSamlResponseToken = _saml2Service.GetSamlResponseToken(base64EncodedSamlResponse, Saml2Constants.ResponseTypes.AuthnResponse, Options); IRequestCookieCollection cookies = Request.Cookies; string originalSamlRequestId = cookies[cookies.Keys.FirstOrDefault(key => key.StartsWith(Options.AuthenticationScheme))]; _saml2Service.CheckIfReplayAttack(idpSamlResponseToken.InResponseTo, originalSamlRequestId); _saml2Service.CheckStatus(idpSamlResponseToken); string token = _saml2Service.GetAssertion(idpSamlResponseToken, Options); AssertionType assertion = new AssertionType(); XmlSerializer xmlSerializer = new XmlSerializer(typeof(AssertionType)); using (MemoryStream memStm = new MemoryStream(Encoding.UTF8.GetBytes(token))) { assertion = (AssertionType)xmlSerializer.Deserialize(memStm); } if (Options.WantAssertionsSigned) { var doc = new XmlDocument { XmlResolver = null, PreserveWhitespace = true }; doc.LoadXml(token); if (!_saml2Service.ValidateX509CertificateSignature(doc, Options)) { throw new Exception("Assertion signature is not valid"); } } AuthnStatementType session = new AuthnStatementType(); if (assertion.Items.Any(x => x.GetType() == typeof(AuthnStatementType))) { session = (AuthnStatementType)assertion.Items.FirstOrDefault(x => x.GetType() == typeof(AuthnStatementType)); } if (assertion.Subject.Items.Any(x => x.GetType() == typeof(NameIDType))) { Options.NameIDType = (NameIDType)assertion.Subject.Items.FirstOrDefault(x => x.GetType() == typeof(NameIDType)); } if (_configuration == null) { _configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted); } var tvp = Options.TokenValidationParameters.Clone(); var validator = Options.Saml2SecurityTokenHandler; ClaimsPrincipal principal = null; SecurityToken parsedToken = null; var issuers = new[] { _configuration.Issuer }; tvp.ValidateIssuerSigningKey = Options.WantAssertionsSigned; tvp.ValidateTokenReplay = !Options.IsPassive; tvp.ValidateIssuer = true; tvp.ValidateAudience = true; tvp.ValidIssuers = (tvp.ValidIssuers == null ? issuers : tvp.ValidIssuers.Concat(issuers)); tvp.IssuerSigningKeys = (tvp.IssuerSigningKeys == null ? _configuration.SigningKeys : tvp.IssuerSigningKeys.Concat(_configuration.SigningKeys)); if (!Options.WantAssertionsSigned) // in case they aren't signed { tvp.RequireSignedTokens = false; } if (validator.CanReadToken(token)) { principal = validator.ValidateToken(token, tvp, out parsedToken); } if (principal == null) { throw new SecurityTokenException("No token validator was found for the given token."); } if (Options.UseTokenLifetime && parsedToken != null) { // Override any session persistence to match the token lifetime. var issued = parsedToken.ValidFrom; if (issued != DateTime.MinValue) { authenticationProperties.IssuedUtc = issued.ToUniversalTime(); } var expires = parsedToken.ValidTo; if (expires != DateTime.MinValue) { authenticationProperties.ExpiresUtc = expires.ToUniversalTime(); } authenticationProperties.AllowRefresh = false; } ClaimsIdentity identity = new ClaimsIdentity(principal.Claims, Scheme.Name); session.SessionIndex = !String.IsNullOrEmpty(session.SessionIndex) ? session.SessionIndex : assertion.ID; //get the session index from assertion so you can use it to logout later identity.AddClaim(new Claim(Saml2ClaimTypes.SessionIndex, session.SessionIndex)); identity.AddClaim(new Claim(ClaimTypes.Name, principal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value)); string redirectUrl = !string.IsNullOrEmpty(authenticationProperties.RedirectUri) ? authenticationProperties.RedirectUri : Options.CallbackPath.ToString(); Context.Response.Redirect(redirectUrl, true); Context.User = new ClaimsPrincipal(identity); await Context.SignInAsync(Options.SignInScheme, Context.User, authenticationProperties); return(HandleRequestResult.Success(new AuthenticationTicket(Context.User, authenticationProperties, Scheme.Name))); } catch (Exception exception) { return(HandleRequestResult.Fail(exception, authenticationProperties)); } }
/// <summary> /// Creates a SAML 2.0 Assertion Segment for a Response /// Simple implmenetation assuming a list of string key and value pairs /// </summary> /// <param name="Issuer"></param> /// <param name="AssertionExpirationMinutes"></param> /// <param name="Audience"></param> /// <param name="Subject"></param> /// <param name="Recipient"></param> /// <param name="Attributes">Dictionary of string key, string value pairs</param> /// <returns>Assertion to sign and include in Response</returns> private static AssertionType CreateSAML20Assertion(string Issuer, int AssertionExpirationMinutes, string Audience, string Subject, string Recipient, Dictionary <string, string> Attributes) { AssertionType NewAssertion = new AssertionType() { Version = "2.0", IssueInstant = DateTime.Now,//DateTime.UtcNow, ID = "_" + System.Guid.NewGuid().ToString() }; // Create Issuer NewAssertion.Issuer = new NameIDType() { Value = Issuer.Trim() }; // Create Assertion Subject SubjectType subject = new SubjectType(); NameIDType subjectNameIdentifier = new NameIDType() { Value = Subject.Trim(), Format = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" }; SubjectConfirmationType subjectConfirmation = new SubjectConfirmationType() { Method = "urn:oasis:names:tc:SAML:2.0:cm:bearer", SubjectConfirmationData = new SubjectConfirmationDataType() { NotOnOrAfter = DateTime.Now.AddMinutes(AssertionExpirationMinutes), Recipient = Recipient } }; //{ NotOnOrAfter = DateTime.UtcNow.AddMinutes(AssertionExpirationMinutes), Recipient = Recipient } }; subject.Items = new object[] { subjectNameIdentifier, subjectConfirmation }; NewAssertion.Subject = subject; // Create Assertion Conditions ConditionsType conditions = new ConditionsType(); conditions.NotBefore = DateTime.Now; //DateTime.UtcNow; conditions.NotBeforeSpecified = true; conditions.NotOnOrAfter = DateTime.Now.AddMinutes(AssertionExpirationMinutes); //DateTime.UtcNow.AddMinutes(AssertionExpirationMinutes); conditions.NotOnOrAfterSpecified = true; conditions.Items = new ConditionAbstractType[] { new AudienceRestrictionType() { Audience = new string[] { Audience.Trim() } } }; NewAssertion.Conditions = conditions; // Add AuthnStatement and Attributes as Items AuthnStatementType authStatement = new AuthnStatementType() { AuthnInstant = DateTime.Now, SessionIndex = NewAssertion.ID }; //{ AuthnInstant = DateTime.UtcNow, SessionIndex = NewAssertion.ID }; AuthnContextType context = new AuthnContextType(); context.ItemsElementName = new ItemsChoiceType5[] { ItemsChoiceType5.AuthnContextClassRef }; context.Items = new object[] { "urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified" }; authStatement.AuthnContext = context; AttributeStatementType attributeStatement = new AttributeStatementType(); attributeStatement.Items = new AttributeType[Attributes.Count]; int i = 0; foreach (KeyValuePair <string, string> attribute in Attributes) { attributeStatement.Items[i] = new AttributeType() { Name = attribute.Key, AttributeValue = new object[] { attribute.Value }, NameFormat = "urn:oasis:names:tc:SAML:2.0:attrname-format:basic" }; i++; } NewAssertion.Items = new StatementAbstractType[] { authStatement, attributeStatement }; return(NewAssertion); }
private static AssertionType CreateSamlAssertion(AuthnRequestType samlAuthRequest, string username) { var assertion = new AssertionType { Version = "2.0", IssueInstant = DateTime.UtcNow, ID = "_" + Guid.NewGuid(), Issuer = new NameIDType() { Value = $"{_context.Request.Scheme}://{_context.Request.Host}{_context.Request.PathBase}" } }; //Assertion Subject var subject = new SubjectType(); var subjectNameIdentifier = new NameIDType() { Value = username, Format = Saml2Constants.NameIdentifierFormats.Unspecified }; var subjectConfirmation = new SubjectConfirmationType() { Method = Saml2Constants.SubjectConfirmationMethods.HolderOfKey, SubjectConfirmationData = new SubjectConfirmationDataType() { NotOnOrAfter = DateTime.UtcNow.AddMinutes(ASSERTION_TIMEOUT_IN_MINUTES), Recipient = samlAuthRequest.AssertionConsumerServiceURL, InResponseTo = samlAuthRequest.ID } }; subject.Items = new object[] { subjectNameIdentifier, subjectConfirmation }; assertion.Subject = subject; //Assertion Conditions var conditions = new ConditionsType { NotBefore = DateTime.UtcNow, NotBeforeSpecified = true, NotOnOrAfter = DateTime.UtcNow.AddMinutes(ASSERTION_TIMEOUT_IN_MINUTES), NotOnOrAfterSpecified = true, //TODO: samlAuthRequest.Issuer.Value should be replaced with Items = new ConditionAbstractType[] { new AudienceRestrictionType() { Audience = new string[] { samlAuthRequest.Issuer.Value } } } }; assertion.Conditions = conditions; //Assertion AuthnStatement var authStatement = new AuthnStatementType() { AuthnInstant = DateTime.UtcNow, SessionIndex = assertion.ID }; var context = new AuthnContextType(); context.ItemsElementName = new[] { ItemsChoiceType5.AuthnContextClassRef }; context.Items = new object[] { "urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified" }; authStatement.AuthnContext = context; //Assertion AttributeStatement var attributeStatement = new AttributeStatementType(); attributeStatement.Items = new AttributeType[] { //Add as many attributes as you want here, these are the user details that service provider wants, we can customise the attributes required // on the basis of service provider that requires this assertion new AttributeType { Name = "username", AttributeValue = username, NameFormat = "urn:oasis:names:tc:SAML:2.0:attrname-format:basic" } }; assertion.Items = new StatementAbstractType[] { authStatement, attributeStatement }; return(assertion); }
/// <summary> /// Creates a SAML 2.0 Assertion Segment for a Response /// Simple implmenetation assuming a list of string key and value pairs /// </summary> /// <param name="issuer"></param> /// <param name="assertionExpirationMinutes"></param> /// <param name="audience"></param> /// <param name="subject"></param> /// <param name="recipient"></param> /// <param name="attributes">Dictionary of string key, string value pairs</param> /// <returns>Assertion to sign and include in Response</returns> private static AssertionType CreateSaml20Assertion(string issuer, int assertionExpirationMinutes, string audience, string subject, string recipient, Dictionary <string, string> attributes) { AssertionType newAssertion = new AssertionType { Version = "2.0", IssueInstant = DateTime.UtcNow, ID = "_" + Guid.NewGuid(), Issuer = new NameIDType { Value = issuer.Trim() } }; // Create Issuer // Create Assertion Subject SubjectType subjectType = new SubjectType(); NameIDType subjectNameIdentifier = new NameIDType { Value = subject.Trim(), Format = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" }; SubjectConfirmationType subjectConfirmation = new SubjectConfirmationType { Method = "urn:oasis:names:tc:SAML:2.0:cm:bearer", SubjectConfirmationData = new SubjectConfirmationDataType { NotOnOrAfter = DateTime.UtcNow.AddMinutes(assertionExpirationMinutes), Recipient = recipient } }; subjectType.Items = new object[] { subjectNameIdentifier, subjectConfirmation }; newAssertion.Subject = subjectType; // Create Assertion Conditions ConditionsType conditions = new ConditionsType { NotBefore = DateTime.UtcNow, NotBeforeSpecified = true, NotOnOrAfter = DateTime.UtcNow.AddMinutes(assertionExpirationMinutes), NotOnOrAfterSpecified = true, Items = new ConditionAbstractType[] { new AudienceRestrictionType { Audience = new[] { audience.Trim() } } } }; newAssertion.Conditions = conditions; // Add AuthnStatement and Attributes as Items AuthnStatementType authStatement = new AuthnStatementType { AuthnInstant = DateTime.UtcNow, SessionIndex = newAssertion.ID }; AuthnContextType context = new AuthnContextType { ItemsElementName = new[] { ItemsChoiceType5.AuthnContextClassRef }, Items = new object[] { "urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified" } }; authStatement.AuthnContext = context; AttributeStatementType attributeStatement = new AttributeStatementType { Items = new object[attributes.Count] }; int i = 0; foreach (KeyValuePair <string, string> attribute in attributes) { attributeStatement.Items[i] = new AttributeType { Name = attribute.Key, AttributeValue = new object[] { attribute.Value }, NameFormat = "urn:oasis:names:tc:SAML:2.0:attrname-format:basic" }; i++; } newAssertion.Items = new StatementAbstractType[] { authStatement, attributeStatement }; return(newAssertion); }
public void Deserialize(string samlResponse) { ResponseType response = new ResponseType(); try { using (TextReader sr = new StringReader(samlResponse)) { var serializer = new System.Xml.Serialization.XmlSerializer(typeof(ResponseType)); response = (ResponseType)serializer.Deserialize(sr); this.Version = response.Version; this.UUID = response.ID; this.SPUID = response.InResponseTo; this.Issuer = response.Issuer.Value; switch (response.Status.StatusCode.Value) { case "urn:oasis:names:tc:SAML:2.0:status:Success": this.RequestStatus = SamlRequestStatus.Success; break; case "urn:oasis:names:tc:SAML:2.0:status:Requester": this.RequestStatus = SamlRequestStatus.RequesterError; break; case "urn:oasis:names:tc:SAML:2.0:status:Responder": this.RequestStatus = SamlRequestStatus.ResponderError; break; case "urn:oasis:names:tc:SAML:2.0:status:VersionMismatch": this.RequestStatus = SamlRequestStatus.VersionMismatchError; break; case "urn:oasis:names:tc:SAML:2.0:status:AuthnFailed": this.RequestStatus = SamlRequestStatus.AuthnFailed; break; case "urn:oasis:names:tc:SAML:2.0:status:InvalidAttrNameOrValue": this.RequestStatus = SamlRequestStatus.InvalidAttrNameOrValue; break; case "urn:oasis:names:tc:SAML:2.0:status:InvalidNameIDPolicy": this.RequestStatus = SamlRequestStatus.InvalidNameIDPolicy; break; case "urn:oasis:names:tc:SAML:2.0:status:NoAuthnContext": this.RequestStatus = SamlRequestStatus.NoAuthnContext; break; case "urn:oasis:names:tc:SAML:2.0:status:NoAvailableIDP": this.RequestStatus = SamlRequestStatus.NoAvailableIDP; break; case "urn:oasis:names:tc:SAML:2.0:status:NoPassive": this.RequestStatus = SamlRequestStatus.NoPassive; break; case "urn:oasis:names:tc:SAML:2.0:status:NoSupportedIDP": this.RequestStatus = SamlRequestStatus.NoSupportedIDP; break; case "urn:oasis:names:tc:SAML:2.0:status:PartialLogout": this.RequestStatus = SamlRequestStatus.PartialLogout; break; case "urn:oasis:names:tc:SAML:2.0:status:ProxyCountExceeded": this.RequestStatus = SamlRequestStatus.ProxyCountExceeded; break; case "urn:oasis:names:tc:SAML:2.0:status:RequestDenied": this.RequestStatus = SamlRequestStatus.RequestDenied; break; case "urn:oasis:names:tc:SAML:2.0:status:RequestUnsupported": this.RequestStatus = SamlRequestStatus.RequestUnsupported; break; case "urn:oasis:names:tc:SAML:2.0:status:RequestVersionDeprecated": this.RequestStatus = SamlRequestStatus.RequestVersionDeprecated; break; case "urn:oasis:names:tc:SAML:2.0:status:RequestVersionTooHigh": this.RequestStatus = SamlRequestStatus.RequestVersionTooHigh; break; case "urn:oasis:names:tc:SAML:2.0:status:RequestVersionTooLow": this.RequestStatus = SamlRequestStatus.RequestVersionTooLow; break; case "urn:oasis:names:tc:SAML:2.0:status:ResourceNotRecognized": this.RequestStatus = SamlRequestStatus.ResourceNotRecognized; break; case "urn:oasis:names:tc:SAML:2.0:status:TooManyResponses": this.RequestStatus = SamlRequestStatus.TooManyResponses; break; case "urn:oasis:names:tc:SAML:2.0:status:UnknownAttrProfile": this.RequestStatus = SamlRequestStatus.UnknownAttrProfile; break; case "urn:oasis:names:tc:SAML:2.0:status:UnknownPrincipal": this.RequestStatus = SamlRequestStatus.UnknownPrincipal; break; case "urn:oasis:names:tc:SAML:2.0:status:UnsupportedBinding": this.RequestStatus = SamlRequestStatus.UnsupportedBinding; break; default: this.RequestStatus = SamlRequestStatus.GenericError; break; } if (this.RequestStatus == SamlRequestStatus.Success) { foreach (var item in response.Items) { if (item.GetType() == typeof(AssertionType)) { AssertionType ass = (AssertionType)item; this.SessionIdExpireDate = (ass.Conditions.NotOnOrAfter != null) ? ass.Conditions.NotOnOrAfter : DateTime.Now.AddMinutes(20); foreach (var subitem in ass.Subject.Items) { if (subitem.GetType() == typeof(NameIDType)) { NameIDType nameId = (NameIDType)subitem; this.SubjectNameId = nameId.Value; //.Replace("SPID-",""); } } foreach (var assItem in ass.Items) { if (assItem.GetType() == typeof(AuthnStatementType)) { AuthnStatementType authnStatement = (AuthnStatementType)assItem; this.SessionId = authnStatement.SessionIndex; this.SessionIdExpireDate = (authnStatement.SessionNotOnOrAfterSpecified) ? authnStatement.SessionNotOnOrAfter : this.SessionIdExpireDate; } if (assItem.GetType() == typeof(AttributeStatementType)) { AttributeStatementType statement = (AttributeStatementType)assItem; foreach (AttributeType attribute in statement.Items) { switch (attribute.Name) { case "spidCode": this.User.SpidCode = attribute.AttributeValue[0].ToString(); break; case "name": this.User.Name = attribute.AttributeValue[0].ToString(); break; case "familyName": this.User.FamilyName = attribute.AttributeValue[0].ToString(); break; case "gender": this.User.Gender = attribute.AttributeValue[0].ToString(); break; case "ivaCode": this.User.IvaCode = attribute.AttributeValue[0].ToString(); break; case "companyName": this.User.CompanyName = attribute.AttributeValue[0].ToString(); break; case "mobilePhone": this.User.MobilePhone = attribute.AttributeValue[0].ToString(); break; case "address": this.User.Address = attribute.AttributeValue[0].ToString(); break; case "fiscalNumber": this.User.FiscalNumber = attribute.AttributeValue[0].ToString(); break; case "dateOfBirth": this.User.DateOfBirth = attribute.AttributeValue[0].ToString(); break; case "placeOfBirth": this.User.PlaceOfBirth = attribute.AttributeValue[0].ToString(); break; case "countyOfBirth": this.User.CountyOfBirth = attribute.AttributeValue[0].ToString(); break; case "idCard": this.User.IdCard = attribute.AttributeValue[0].ToString(); break; case "registeredOffice": this.User.RegisteredOffice = attribute.AttributeValue[0].ToString(); break; case "email": this.User.Email = attribute.AttributeValue[0].ToString(); break; case "expirationDate": this.User.ExpirationDate = attribute.AttributeValue[0].ToString(); break; case "digitalAddress": this.User.DigitalAddress = attribute.AttributeValue[0].ToString(); break; default: break; } } } } } } } } } catch (Exception ex) { //TODO Log throw ex; } }
/// <summary> /// /// </summary> /// <param name="context"></param> /// <returns></returns> private XmlDocument GenerateResponseMetadata(SAMLContext context, string id) { DateTime now = DateTime.UtcNow; MemoryStream stream = new MemoryStream(); StreamReader reader; XmlTextReader xmlReader; ResponseType response = new ResponseType(); response.ID = id; response.InResponseTo = context.RequestID; response.Version = SAMLConstants.SAML_VERSION; response.IssueInstant = now; response.Destination = context.AssertionConsumer; response.Consent = SAMLConstants.CONSENT; response.Issuer = new NameIDType(); response.Issuer.Value = thisIssuer; response.Issuer.Format = SAMLConstants.ThisIssuerFormat; response.Status = new StatusType(); response.Status.StatusCode = new StatusCodeType(); response.Status.StatusCode.Value = SAMLConstants.StatusCode.statusCode[context.StatusCode]; if (context.StatusCode != SAMLConstants.StatusCode.SUCCESS) { response.Status.StatusCode.StatusCode = new StatusCodeType(); response.Status.StatusCode.StatusCode.Value = SAMLConstants.StatusCode.statusCode[context.SubStatusCode]; response.Status.StatusMessage = context.StatusMessage; } AssertionType assertion = new AssertionType(); assertion.ID = "_" + Guid.NewGuid().ToString(); assertion.Version = SAMLConstants.SAML_VERSION; assertion.IssueInstant = now; assertion.Issuer = new NameIDType(); assertion.Issuer.Value = thisIssuer; assertion.Issuer.Format = SAMLConstants.ThisIssuerFormat; assertion.Subject = new SubjectType(); NameIDType nameId = new NameIDType(); nameId.Format = "urn:oasis:names:tc:SAML:1.1:nameid- format:unspecified"; //nameId.NameQualifier = "http://C-PEPS.gov.xx"; nameId.Value = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"; SubjectConfirmationType subjectConfirmation = new SubjectConfirmationType(); subjectConfirmation.Method = "urn:oasis:names:tc:SAML:2.0:cm:bearer"; subjectConfirmation.SubjectConfirmationData = new SubjectConfirmationDataType(); subjectConfirmation.SubjectConfirmationData.Address = context.SubjectAddress; subjectConfirmation.SubjectConfirmationData.InResponseTo = context.RequestID; //subjectConfirmation.SubjectConfirmationData.NotBeforeString = "2010-02-03T17:06:18.099Z"; subjectConfirmation.SubjectConfirmationData.NotOnOrAfterString = String.Format("{0:yyyy-MM-ddTHH:mm:ssZ}", now.AddMinutes(validTimeframe)); subjectConfirmation.SubjectConfirmationData.Recipient = context.Issuer; assertion.Subject.Items = new object[] { nameId, subjectConfirmation }; assertion.Conditions = new ConditionsType(); assertion.Conditions.NotBeforeString = String.Format("{0:yyyy-MM-ddTHH:mm:ssZ}", now); assertion.Conditions.NotOnOrAfterString = String.Format("{0:yyyy-MM-ddTHH:mm:ssZ}", now.AddMinutes(validTimeframe)); AudienceRestrictionType audience = new AudienceRestrictionType(); audience.Audience = new string[] { context.Issuer }; // FIXME assertion.Conditions.Items = new ConditionAbstractType[] { audience, new OneTimeUseType() }; AuthnStatementType authnStatement = new AuthnStatementType(); authnStatement.AuthnInstant = now; authnStatement.AuthnContext = new AuthnContextType(); List <AttributeElement> attributes = context.GetAttributes(); object[] attributesDescription = new AttributeType[attributes.Count]; AttributeType attr; XmlAttribute statusAttr; int i = 0; foreach (AttributeElement element in attributes) { attr = new AttributeType(); attr.Name = element.AttrName; attr.NameFormat = element.NameFormat; if (context.StatusCode == SAMLConstants.StatusCode.SUCCESS) { if (element.AttrStatus == SAMLConstants.AttributeStatus.AVAILABLE && element.AttrValue != null) { attr.AttributeValue = new object[] { element.AttrValue } } ; if (element.AttrStatus >= 0) { statusAttr = new XmlDocument(). CreateAttribute(SAMLConstants.ATTRIBUTE_STATUS_STR, SAMLConstants.NS_STORK_ASSER); statusAttr.Value = element.Status; attr.AnyAttr = new XmlAttribute[] { statusAttr }; } } attributesDescription[i++] = attr; } AttributeStatementType attributeStatement = new AttributeStatementType(); attributeStatement.Items = attributesDescription; assertion.Items = new StatementAbstractType[] { authnStatement, attributeStatement }; response.Items = new object[] { assertion }; stream = new MemoryStream(); Serialize(response, stream); reader = new StreamReader(stream); stream.Seek(0, SeekOrigin.Begin); xmlReader = new XmlTextReader(new StringReader(reader.ReadToEnd())); return(Deserialize <XmlDocument>(xmlReader)); }
/// <summary> /// Handles the sign in. /// </summary> /// <returns></returns> /// <exception cref="SecurityTokenException">No token validator was found for the given token.</exception> private async Task <HandleRequestResult> HandleSignIn() { if (Request.Method != HttpMethods.Post) { return(HandleRequestResult.Fail("Request method must be an HTTP-Post Method")); } var form = await Request.ReadFormAsync(); var response = form[Saml2Constants.Parameters.SamlResponse]; var relayState = form[Saml2Constants.Parameters.RelayState].ToString()?.DeflateDecompress(); AuthenticationProperties authenticationProperties = Options.StateDataFormat.Unprotect(relayState); try { if (authenticationProperties == null) { if (!Options.AllowUnsolicitedLogins) { return(HandleRequestResult.Fail("Unsolicited logins are not allowed.")); } } if (authenticationProperties.Items.TryGetValue(CorrelationProperty, out string correlationId) && !ValidateCorrelationId(authenticationProperties)) { return(HandleRequestResult.Fail("Correlation failed.", authenticationProperties)); } string base64EncodedSamlResponse = response; ResponseType idpSamlResponseToken = _saml2Service.GetSamlResponseToken(base64EncodedSamlResponse, Saml2Constants.ResponseTypes.AuthnResponse, Options); // Write To XML Doc `_xmlDoc` Object, This Step Is Critical For The ValidateToken Call Below. LoadXmlFromBase64(response); IRequestCookieCollection cookies = Request.Cookies; string originalSamlRequestId = cookies[cookies.Keys.FirstOrDefault(key => key.StartsWith(Options.AuthenticationScheme))]; _saml2Service.CheckIfReplayAttack(idpSamlResponseToken.InResponseTo, originalSamlRequestId); _saml2Service.CheckStatus(idpSamlResponseToken); string token = _saml2Service.GetAssertion(idpSamlResponseToken, Options); AssertionType assertion = new AssertionType(); XmlSerializer xmlSerializer = new XmlSerializer(typeof(AssertionType)); using (MemoryStream memStm = new MemoryStream(Encoding.UTF8.GetBytes(token))) { assertion = (AssertionType)xmlSerializer.Deserialize(memStm); } if (Options.WantAssertionsSigned) { var doc = new XmlDocument { XmlResolver = null, PreserveWhitespace = true }; doc.LoadXml(token); if (!_saml2Service.ValidateX509CertificateSignature(doc, Options)) { throw new Exception("Assertion signature is not valid"); } } AuthnStatementType session = new AuthnStatementType(); if (assertion.Items.Any(x => x.GetType() == typeof(AuthnStatementType))) { session = (AuthnStatementType)assertion.Items.FirstOrDefault(x => x.GetType() == typeof(AuthnStatementType)); } if (assertion.Subject.Items.Any(x => x.GetType() == typeof(NameIDType))) { Options.NameIDType = (NameIDType)assertion.Subject.Items.FirstOrDefault(x => x.GetType() == typeof(NameIDType)); } if (_configuration == null) { _configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted); } var tvp = Options.TokenValidationParameters.Clone(); var validator = Options.Saml2SecurityTokenHandler; ClaimsPrincipal principal = null; SecurityToken parsedToken = null; var issuers = new[] { _configuration.Issuer }; tvp.ValidateIssuerSigningKey = Options.WantAssertionsSigned; tvp.ValidateTokenReplay = !Options.IsPassive; tvp.ValidateIssuer = true; tvp.ValidateAudience = true; tvp.ValidIssuers = (tvp.ValidIssuers == null ? issuers : tvp.ValidIssuers.Concat(issuers)); tvp.IssuerSigningKeys = (tvp.IssuerSigningKeys == null ? _configuration.SigningKeys : tvp.IssuerSigningKeys.Concat(_configuration.SigningKeys)); if (!Options.WantAssertionsSigned) // in case they aren't signed { tvp.RequireSignedTokens = false; } if (Options.EnablePIILogging) { IdentityModelEventSource.ShowPII = true; } if (validator.CanReadToken(token)) { // Our Token Is Valid, Now Check Signaures. var doc = new XmlDocument { XmlResolver = null, PreserveWhitespace = true }; doc.LoadXml(token); // Pull Signatures. XmlNodeList XMLSignatures = doc.GetElementsByTagName(Saml2Constants.Parameters.Signature, Saml2Constants.Namespaces.DsNamespace); var signedXmlDoc = new SignedXml(doc); signedXmlDoc.LoadXml((XmlElement)XMLSignatures[0]); KeyInfoX509Data x509data = signedXmlDoc.Signature.KeyInfo.OfType <KeyInfoX509Data>().First(); X509Certificate2 cert = (X509Certificate2)x509data.Certificates[0]; string serialNumber = cert.SerialNumber; X509Certificate2 _idpcert = Options.Configuration.X509Certificate2.Where(c => c.SerialNumber == serialNumber).FirstOrDefault(); var _xmlNameSpaceManager = GetNamespaceManager(); // Manager For XPath Queries. // Important: To Check Signatures We Pull The Signature XML Node. // Critically This Check Differers From The ValidateToken Call Below, See Note. XmlNodeList nodeList = _xmlDoc.SelectNodes(".//ds:Signature", _xmlNameSpaceManager); SignedXml signedXml = new SignedXml(_xmlDoc); signedXml.LoadXml((XmlElement)nodeList[0]); var check_reference = ValidateSignatureReference(signedXml); var check_signature = signedXml.CheckSignature(cert, true); if (check_reference && check_signature) { // Tech Note: At This Point We've: // 1. Verified The Token. // 2. Verified The Signature Reference. // 3. Verified The Signature For The Entire Document, And The Specific Assertion. // The Final Step Is To Create Our Security Principal Via The ValidateToken() Call. // The Big "Trick" Here Is This Call Is Known To Fail When The XML Source Is Not .NET. // Specifically, The Signature Validation Process Requires The XML Byte Stream To Be Identical, // And Unfortunately Our Token Call Above, Among Other Things, Strips XML Namespaces And Changes Line Feeds. // To Address This We Pass This Call The "Original" XML From The LoadXmlFromBase64() Call Above. try { XmlNodeList nodeListAssertion = _xmlDoc.SelectNodes(".//saml:Assertion", _xmlNameSpaceManager); // This Call Maps Our SAML <saml2:AttributeStatement> Items To Our Identity As Claims. principal = validator.ValidateToken(nodeListAssertion[0].OuterXml, tvp, out parsedToken); } catch (Exception e) { } } } if (principal == null) { throw new SecurityTokenException("No token validator was found for the given token."); } if (Options.UseTokenLifetime && parsedToken != null) { // Override any session persistence to match the token lifetime. var issued = parsedToken.ValidFrom; if (issued != DateTime.MinValue) { authenticationProperties.IssuedUtc = issued.ToUniversalTime(); } var expires = parsedToken.ValidTo; if (expires != DateTime.MinValue) { authenticationProperties.ExpiresUtc = expires.ToUniversalTime(); } authenticationProperties.AllowRefresh = false; } ClaimsIdentity identity = new ClaimsIdentity(principal.Claims, Scheme.Name); session.SessionIndex = !String.IsNullOrEmpty(session.SessionIndex) ? session.SessionIndex : assertion.ID; //get the session index from assertion so you can use it to logout later identity.AddClaim(new Claim(Saml2ClaimTypes.SessionIndex, session.SessionIndex)); // Create Entry For User.Identity.Name if (principal.Claims.Any(c => c.Type == ClaimTypes.NameIdentifier)) { identity.AddClaim(new Claim(ClaimTypes.Name, principal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value)); } string redirectUrl = !string.IsNullOrEmpty(authenticationProperties.RedirectUri) ? authenticationProperties.RedirectUri : Options.CallbackPath.ToString(); Context.Response.Redirect(redirectUrl, true); Context.User = new ClaimsPrincipal(identity); await Context.SignInAsync(Options.SignInScheme, Context.User, authenticationProperties); return(HandleRequestResult.Success(new AuthenticationTicket(Context.User, authenticationProperties, Scheme.Name))); } catch (Exception exception) { return(HandleRequestResult.Fail(exception, authenticationProperties)); } }