/// <summary> /// Initializes a new instance of the ArtifactResponse class. /// </summary> /// <param name="artifactResponse"> /// String representation of the ArtifactResponse xml. /// </param> public ArtifactResponse(string artifactResponse) { try { xml = new XmlDocument(); xml.PreserveWhitespace = true; xml.LoadXml(artifactResponse); nsMgr = new XmlNamespaceManager(xml.NameTable); nsMgr.AddNamespace("ds", "http://www.w3.org/2000/09/xmldsig#"); nsMgr.AddNamespace("saml", "urn:oasis:names:tc:SAML:2.0:assertion"); nsMgr.AddNamespace("samlp", "urn:oasis:names:tc:SAML:2.0:protocol"); string xpath = "/samlp:ArtifactResponse/samlp:Response"; XmlNode response = xml.DocumentElement.SelectSingleNode(xpath, nsMgr); if (response == null) { throw new Saml2Exception(Resources.ArtifactResponseMissingResponse); } authnResponse = new AuthnResponse(response.OuterXml); } catch (ArgumentNullException ane) { throw new Saml2Exception(Resources.ArtifactResponseNullArgument, ane); } catch (XmlException xe) { throw new Saml2Exception(Resources.ArtifactResponseXmlException, xe); } }
/// <summary> /// Initializes a new instance of the ArtifactResponse class. /// </summary> /// <param name="artifactResponse"> /// String representation of the ArtifactResponse xml. /// </param> public ArtifactResponse(string artifactResponse) { try { m_xml = new XmlDocument { PreserveWhitespace = true }; m_xml.LoadXml(artifactResponse); m_nsMgr = new XmlNamespaceManager(m_xml.NameTable); m_nsMgr.AddNamespace("ds", SignedXml.XmlDsigNamespaceUrl); m_nsMgr.AddNamespace("saml", "urn:oasis:names:tc:SAML:2.0:assertion"); m_nsMgr.AddNamespace("samlp", "urn:oasis:names:tc:SAML:2.0:protocol"); const string xpath = "/samlp:ArtifactResponse/samlp:Response"; var response = m_xml.DocumentElement?.SelectSingleNode(xpath, m_nsMgr); if (response == null) { throw new Saml2Exception(Resources.ArtifactResponseMissingResponse); } AuthnResponse = new AuthnResponse(response.OuterXml); } catch (ArgumentNullException ane) { throw new Saml2Exception(Resources.ArtifactResponseNullArgument, ane); } catch (XmlException xe) { throw new Saml2Exception(Resources.ArtifactResponseXmlException, xe); } }
/// <summary> /// Retrieve the AuthnResponse object found within the HttpRequest /// in the context of the HttpContext, performing validation of /// the AuthnResponse prior to returning to the user. /// </summary> /// <param name="context"> /// HttpContext containing session, request, and response objects. /// </param> /// <returns>AuthnResponse object</returns> public AuthnResponse GetAuthnResponse(HttpContext context) { ArtifactResponse artifactResponse = null; AuthnResponse authnResponse = null; ICollection authnRequests = AuthnRequestCache.GetSentAuthnRequests(context); HttpRequest request = context.Request; // Check if a saml response was received... if (string.IsNullOrEmpty(request[Saml2Constants.ResponseParameter]) && string.IsNullOrEmpty(request[Saml2Constants.ArtifactParameter])) { throw new ServiceProviderUtilityException(Resources.ServiceProviderUtilityNoSamlResponseReceived); } // Obtain AuthnResponse object from either HTTP-POST or HTTP-Artifact if (request[Saml2Constants.ResponseParameter] != null) { string samlResponse = Saml2Utils.ConvertFromBase64(request[Saml2Constants.ResponseParameter]); authnResponse = new AuthnResponse(samlResponse); XmlDocument xmlDoc = (XmlDocument)authnResponse.XmlDom; StringBuilder logMessage = new StringBuilder(); logMessage.Append("AuthnResponse:\r\n").Append(xmlDoc.OuterXml); FedletLogger.Info(logMessage.ToString()); } else if (request[Saml2Constants.ArtifactParameter] != null) { Artifact artifact = new Artifact(request[Saml2Constants.ArtifactParameter]); artifactResponse = this.GetArtifactResponse(artifact); authnResponse = artifactResponse.AuthnResponse; XmlDocument xmlDoc = (XmlDocument)artifactResponse.XmlDom; StringBuilder logMessage = new StringBuilder(); logMessage.Append("ArtifactResponse:\r\n").Append(xmlDoc.OuterXml); FedletLogger.Info(logMessage.ToString()); } string prevAuthnRequestId = authnResponse.InResponseTo; try { if (artifactResponse != null) { this.ValidateForArtifact(artifactResponse, authnRequests); } else { this.ValidateForPost(authnResponse, authnRequests); } } catch (Saml2Exception se) { // log and throw again... XmlDocument authnResponseXml = (XmlDocument)authnResponse.XmlDom; StringBuilder logMessage = new StringBuilder(); logMessage.Append(se.Message).Append("\r\n").Append(authnResponseXml.InnerXml); FedletLogger.Warning(logMessage.ToString()); throw; } finally { AuthnRequestCache.RemoveSentAuthnRequest(context, prevAuthnRequestId); } return authnResponse; }
/// <summary> /// Validates the given AuthnResponse object except for xml signature. /// XML signature checking is expected to be done prior to calling /// this method based on the appropriate profile. /// </summary> /// <param name="authnResponse">AuthnResponse object.</param> /// <param name="authnRequests"> /// Collection of previously sent authnRequests used to compare with /// the InResponseTo attribute of the AuthnResponse (if present). /// </param> /// <see cref="ServiceProviderUtility.ValidateForArtifact"/> /// <see cref="ServiceProviderUtility.ValidateForPost(AuthnResponse, ICollection)"/> private void Validate(AuthnResponse authnResponse, ICollection authnRequests) { if (authnResponse.InResponseTo != null) { ServiceProviderUtility.CheckInResponseTo(authnResponse, authnRequests); } this.CheckIssuer(authnResponse.Issuer); ServiceProviderUtility.CheckStatusCode(authnResponse.StatusCode); ServiceProviderUtility.CheckConditionWithTime(authnResponse); this.CheckConditionWithAudience(authnResponse); this.CheckCircleOfTrust(authnResponse.Issuer); }
/// <summary> /// Checks the signature of the given AuthnResponse used for the POST /// profile. /// </summary> /// <param name="authnResponse">AuthnResponse object.</param> /// <seealso cref="ServiceProviderUtility.ValidateForPost(AuthnResponse, ICollection)"/> private void CheckSignature(AuthnResponse authnResponse) { IdentityProvider identityProvider = (IdentityProvider)this.IdentityProviders[authnResponse.Issuer]; if (identityProvider == null) { throw new Saml2Exception(Resources.InvalidIssuer); } XmlElement responseSignature = (XmlElement)authnResponse.XmlResponseSignature; XmlElement assertionSignature = (XmlElement)authnResponse.XmlAssertionSignature; XmlElement validationSignature = null; string validationSignatureCert = null; string validationReferenceId = null; if (responseSignature == null && assertionSignature == null) { throw new Saml2Exception(Resources.AuthnResponseInvalidSignatureMissing); } else if (this.ServiceProvider.WantPostResponseSigned && responseSignature == null) { throw new Saml2Exception(Resources.AuthnResponseInvalidSignatureMissingOnResponse); } // pick the Response or the Assertion for further validation... if (responseSignature != null) { validationSignature = responseSignature; validationSignatureCert = authnResponse.ResponseSignatureCertificate; validationReferenceId = authnResponse.Id; } else { validationSignature = assertionSignature; validationSignatureCert = authnResponse.AssertionSignatureCertificate; validationReferenceId = authnResponse.AssertionId; } if (validationSignatureCert != null) { string idpCert = Regex.Replace(identityProvider.EncodedSigningCertificate, @"\s", string.Empty); validationSignatureCert = Regex.Replace(validationSignatureCert, @"\s", string.Empty); if (idpCert != validationSignatureCert) { throw new Saml2Exception(Resources.AuthnResponseInvalidSignatureCertsDontMatch); } } // check the signature of the xml document (always for post) Saml2Utils.ValidateSignedXml( identityProvider.SigningCertificate, (XmlDocument)authnResponse.XmlDom, validationSignature, validationReferenceId); }
/// <summary> /// Checks the audience condition of the given AuthnResponse. /// </summary> /// <param name="authnResponse">SAMLv2 AuthnResponse.</param> private void CheckConditionWithAudience(AuthnResponse authnResponse) { if (!authnResponse.ConditionAudiences.Contains(this.ServiceProvider.EntityId)) { throw new Saml2Exception(Resources.AuthnResponseInvalidConditionAudience); } }
/// <summary> /// Checks the InResponseTo field of the given AuthnResponse to /// see if it is one of the managed authn requests. /// </summary> /// <param name="authnResponse">SAMLv2 AuthnResponse.</param> /// <param name="authnRequests"> /// Collection of previously sent AuthnRequests. /// </param> private static void CheckInResponseTo(AuthnResponse authnResponse, ICollection authnRequests) { if (authnRequests != null && authnResponse.InResponseTo != null) { IEnumerator i = authnRequests.GetEnumerator(); while (i.MoveNext()) { AuthnRequest authnRequest = (AuthnRequest)i.Current; if (authnRequest.Id == authnResponse.InResponseTo) { // Found one, return quietly. return; } } } // Didn't find one, complain loudly. throw new Saml2Exception(Resources.AuthnResponseInvalidInResponseTo); }
/// <summary> /// Checks the time condition of the given AuthnResponse. /// </summary> /// <param name="authnResponse">SAMLv2 AuthnResponse.</param> private static void CheckConditionWithTime(AuthnResponse authnResponse) { DateTime utcNow = DateTime.UtcNow; DateTime utcBefore = TimeZoneInfo.ConvertTimeToUtc(authnResponse.ConditionNotBefore); DateTime utcOnOrAfter = TimeZoneInfo.ConvertTimeToUtc(authnResponse.ConditionNotOnOrAfter); if (utcNow < utcBefore || utcNow >= utcOnOrAfter) { throw new Saml2Exception(Resources.AuthnResponseInvalidConditionTime); } }
/// <summary> /// Validates the given AuthnResponse object. /// </summary> /// <param name="authnResponse">AuthnResponse object.</param> /// <param name="authnRequests"> /// Collection of previously sent authnRequests used to compare with /// the InResponseTo attribute (if present) of the AuthnResponse. /// </param> /// <see cref="ServiceProviderUtility.Validate(AuthnResponse, ICollection)"/> public void ValidateForPost(AuthnResponse authnResponse, ICollection authnRequests) { this.CheckSignature(authnResponse); this.Validate(authnResponse, authnRequests); }
/// <summary> /// Initializes a new instance of the ArtifactResponse class. /// </summary> /// <param name="artifactResponse"> /// String representation of the ArtifactResponse xml. /// </param> public ArtifactResponse(string artifactResponse) { try { this.xml = new XmlDocument(); this.xml.PreserveWhitespace = true; this.xml.LoadXml(artifactResponse); this.nsMgr = new XmlNamespaceManager(this.xml.NameTable); this.nsMgr.AddNamespace("ds", "http://www.w3.org/2000/09/xmldsig#"); this.nsMgr.AddNamespace("saml", "urn:oasis:names:tc:SAML:2.0:assertion"); this.nsMgr.AddNamespace("samlp", "urn:oasis:names:tc:SAML:2.0:protocol"); string xpath = "/samlp:ArtifactResponse/samlp:Response"; XmlNode response = this.xml.DocumentElement.SelectSingleNode(xpath, this.nsMgr); if (response == null) { throw new Saml2Exception(Resources.ArtifactResponseMissingResponse); } this.authnResponse = new AuthnResponse(response.OuterXml); } catch (ArgumentNullException ane) { throw new Saml2Exception(Resources.ArtifactResponseNullArgument, ane); } catch (XmlException xe) { throw new Saml2Exception(Resources.ArtifactResponseXmlException, xe); } }
/// <summary> /// Validates the given AuthnResponse object. /// </summary> /// <param name="authnResponse">AuthnResponse object.</param> /// <param name="authnRequests"> /// Collection of previously sent authnRequests used to compare with /// the InResponseTo attribute (if present) of the AuthnResponse. /// </param> /// <see cref="ServiceProviderUtility.Validate(AuthnResponse, ICollection)"/> public void ValidateForPost(AuthnResponse authnResponse, ICollection authnRequests) { if (authnResponse.XmlResponseSignature == null && authnResponse.isAssertionEncrypted()) { authnResponse.Decrypt(ServiceProvider); } this.CheckSignature(authnResponse); if (authnResponse.isAssertionEncrypted()) { authnResponse.Decrypt(ServiceProvider); } this.Validate(authnResponse, authnRequests); }
/// <summary> /// Retrieve the AuthnResponse object found within the HttpRequest /// in the context of the HttpContext, performing validation of /// the AuthnResponse prior to returning to the user. /// </summary> /// <param name="context"> /// HttpContext containing session, request, and response objects. /// </param> /// <returns>AuthnResponse object</returns> public AuthnResponse GetAuthnResponse(HttpContextBase context) { ArtifactResponse artifactResponse = null; AuthnResponse authnResponse; ICollection authnRequests = AuthnRequestCache.GetSentAuthnRequests(context); HttpRequestBase request = context.Request; // Obtain AuthnResponse object from either HTTP-POST or HTTP-Artifact if (!string.IsNullOrWhiteSpace(request[Saml2Constants.ResponseParameter])) { string samlResponse = _saml2Utils.ConvertFromBase64(request[Saml2Constants.ResponseParameter]); authnResponse = new AuthnResponse(samlResponse); var xmlDoc = (XmlDocument) authnResponse.XmlDom; _logger.Info("AuthnResponse:\r\n{0}", xmlDoc.OuterXml); } else if (!string.IsNullOrWhiteSpace(request[Saml2Constants.ArtifactParameter])) { var artifact = new Artifact(request[Saml2Constants.ArtifactParameter]); artifactResponse = GetArtifactResponse(artifact); authnResponse = artifactResponse.AuthnResponse; var xmlDoc = (XmlDocument) artifactResponse.XmlDom; _logger.Info("ArtifactResponse:\r\n{0}", xmlDoc.OuterXml); } else { throw new ServiceProviderUtilityException(Resources.ServiceProviderUtilityNoSamlResponseReceived); } string prevAuthnRequestId = authnResponse.InResponseTo; try { if (artifactResponse != null) { ValidateForArtifact(artifactResponse, authnRequests); } else { ValidateForPost(authnResponse, authnRequests); } } catch (Saml2Exception se) { // add some context var authnResponseXml = (XmlDocument) authnResponse.XmlDom; se.Data["xml"] = authnResponseXml.InnerXml; throw; } finally { AuthnRequestCache.RemoveSentAuthnRequest(context, prevAuthnRequestId); } return authnResponse; }
/// <summary> /// Validates the given AuthnResponse object except for xml signature. /// XML signature checking is expected to be done prior to calling /// this method based on the appropriate profile. /// </summary> /// <param name="authnResponse">AuthnResponse object.</param> /// <param name="authnRequests"> /// Collection of previously sent authnRequests used to compare with /// the InResponseTo attribute of the AuthnResponse (if present). /// </param> /// <see cref="ServiceProviderUtility.ValidateForArtifact"/> /// <see cref="ServiceProviderUtility.ValidateForPost(AuthnResponse, ICollection)"/> private void Validate(AuthnResponse authnResponse, ICollection authnRequests) { /* Commenting this section out since we had to remove the Session caching of Authnresponses due * to some session sharing issues if (authnResponse.InResponseTo != null) { CheckInResponseTo(authnResponse, authnRequests); }*/ CheckIssuer(authnResponse.Issuer); CheckStatusCode(authnResponse.StatusCode); CheckConditionWithTime(authnResponse); CheckConditionWithAudience(authnResponse); CheckCircleOfTrust(authnResponse.Issuer); }