Разобрать и проверить XML-токен доверия WS - PullRequest
0 голосов
/ 14 сентября 2018

У меня есть веб-сервис, написанный на c # / .NET, который перенаправляет неаутентифицированных пользователей провайдеру идентификации федерации WS, который затем перенаправляет обратно на мой веб-сервис с токеном SAML, который выполняет роли этого пользователя.Это согласно спецификации пассивной федерации WS - http://docs.oasis -open.org / wsfed / federation / v1.2 / os / ws-federation-1.2-spec-os.html # _Toc223175008

Получив это, я получаю запрос, в котором wresult установлен как токен.В моем коде у меня есть string wresult, который является строкой для XML-документа.То, что я знаю, это сфера, в которой я работаю, отпечаток провайдера идентификации, wctx (если он был отправлен).

Маркер безопасности - это стандартный токен WS-Trust, описанный здесь: http://specs.xmlsoap.org/ws/2005/02/trust/WS-Trust.pdf

Я хочу получить SecurityToken и, в конечном итоге, IPrincipal для этого пользователя только из этой строки который является XML-документом / токеном безопасности.

Примером строки может быть (с некоторыми вещами запутанными).

<?xml version="1.0"?>
<t:RequestSecurityTokenResponse xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">
  <t:Lifetime>
    <wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2018-09-14T13:40:25.164Z</wsu:Created>
    <wsu:Expires xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2018-09-14T14:40:25.164Z</wsu:Expires>
  </t:Lifetime>
  <wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
    <wsa:EndpointReference xmlns:wsa="http://www.w3.org/2005/08/addressing">
      <wsa:Address>https://localhost:44366/</wsa:Address>
    </wsa:EndpointReference>
  </wsp:AppliesTo>
  <t:RequestedSecurityToken>
    <saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" MajorVersion="1" MinorVersion="1" AssertionID="_e1580903-02ac-453d-a157-ae27c8614cc9" Issuer="http://adfs.ORGANISATION.com/adfs/services/trust" IssueInstant="2018-09-14T13:40:25.164Z">
      <saml:Conditions NotBefore="2018-09-14T13:40:25.164Z" NotOnOrAfter="2018-09-14T14:40:25.164Z">
        <saml:AudienceRestrictionCondition>
          <saml:Audience>https://localhost:44366/</saml:Audience>
        </saml:AudienceRestrictionCondition>
      </saml:Conditions>
      <saml:AttributeStatement>
        <saml:Subject>
          <saml:SubjectConfirmation>
            <saml:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:bearer</saml:ConfirmationMethod>
          </saml:SubjectConfirmation>
        </saml:Subject>
        <saml:Attribute AttributeName="emailaddress" AttributeNamespace="http://schemas.xmlsoap.org/ws/2005/05/identity/claims">
          <saml:AttributeValue>person@stuff.com</saml:AttributeValue>
        </saml:Attribute>
        <saml:Attribute AttributeName="givenname" AttributeNamespace="http://schemas.xmlsoap.org/ws/2005/05/identity/claims">
          <saml:AttributeValue>Jeff</saml:AttributeValue>
        </saml:Attribute>
        <saml:Attribute AttributeName="surname" AttributeNamespace="http://schemas.xmlsoap.org/ws/2005/05/identity/claims">
          <saml:AttributeValue>Mandelson</saml:AttributeValue>
        </saml:Attribute>
        <saml:Attribute AttributeName="windowsaccountname" AttributeNamespace="http://schemas.microsoft.com/ws/2008/06/identity/claims">
          <saml:AttributeValue>jeff.mandelson</saml:AttributeValue>
        </saml:Attribute>
        <saml:Attribute AttributeName="role" AttributeNamespace="http://schemas.microsoft.com/ws/2008/06/identity/claims">
          <saml:AttributeValue>Stuff\Domain Users</saml:AttributeValue>
          <saml:AttributeValue>Stuff\DevTeam</saml:AttributeValue>
          <saml:AttributeValue>Stuff\RDS-MSSQLDEV-RW</saml:AttributeValue>
        </saml:Attribute>
        <saml:Attribute AttributeName="upn" AttributeNamespace="http://schemas.xmlsoap.org/ws/2005/05/identity/claims">
          <saml:AttributeValue>stuff@local.com</saml:AttributeValue>
        </saml:Attribute>
        <saml:Attribute AttributeName="name" AttributeNamespace="http://schemas.xmlsoap.org/ws/2005/05/identity/claims">
          <saml:AttributeValue>Jeff Mandelson</saml:AttributeValue>
        </saml:Attribute>
      </saml:AttributeStatement>
      <saml:AuthenticationStatement AuthenticationMethod="urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport" AuthenticationInstant="2018-09-14T11:59:16.147Z">
        <saml:Subject>
          <saml:SubjectConfirmation>
            <saml:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:bearer</saml:ConfirmationMethod>
          </saml:SubjectConfirmation>
        </saml:Subject>
      </saml:AuthenticationStatement>
      <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
        <ds:SignedInfo>
          <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
          <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
          <ds:Reference URI="#_e1580903-02ac-453d-a157-ae27c8614cc9">
            <ds:Transforms>
              <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
              <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
            </ds:Transforms>
            <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
            <ds:DigestValue>a_digest_value_removed</ds:DigestValue>
          </ds:Reference>
        </ds:SignedInfo>
        <ds:SignatureValue>signature</ds:SignatureValue>
        <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
          <X509Data>
            <X509Certificate>certificate</X509Certificate>
          </X509Data>
        </KeyInfo>
      </ds:Signature>
    </saml:Assertion>
  </t:RequestedSecurityToken>
  <t:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</t:TokenType>
  <t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType>
  <t:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</t:KeyType>
</t:RequestSecurityTokenResponse>

Я пытался использовать встроенные методы, такие как WSFederationAuthenticationModule, однако, похоже, что возникают проблемы, если вы не используете System.Web.Request.Встроенная функция .NET / C # предпочтительнее!

Ответы [ 2 ]

0 голосов
/ 17 сентября 2018

Другой способ - использовать IdentityModel SamlTokenVerifier и Parser:

public static bool AuthenticateXmlToken(String wresult)
        {


            String pstrXML = wresult;

            // write it down 
            File.WriteAllText("C:\\Users\\USER\\Downloads\\asdf4.xml", wresult);

            // extract the SAML Assertion
            XmlReader reader = XmlReader.Create(new StringReader(pstrXML));
            reader.ReadToFollowing("Assertion", "urn:oasis:names:tc:SAML:1.0:assertion");

            // saml requirements 
            SamlSecurityTokenRequirement pRequirements = new SamlSecurityTokenRequirement();
            pRequirements.CertificateValidator = new CertificateValidator();

            SecurityTokenHandlerConfiguration pConfig = new SecurityTokenHandlerConfiguration();
            pConfig.AudienceRestriction = new AudienceRestriction(AudienceUriMode.Never);
            pConfig.IssuerNameRegistry = new IssuerNames();

            //pRequirements.ValidateAudienceRestriction()
            SamlSecurityTokenHandler pHandler = new SamlSecurityTokenHandler(pRequirements);
            pHandler.Configuration = pConfig;


            SecurityTokenHandlerCollection tokenHandlerCollection = SecurityTokenHandlerCollection.CreateDefaultSecurityTokenHandlerCollection();
            SamlSecurityToken token = (SamlSecurityToken)pHandler.ReadToken(reader.ReadSubtree());

            ReadOnlyCollection<ClaimsIdentity> pClaims = pHandler.ValidateToken(token);

            return pClaims.Count > 0;
        }


        public class IssuerNames : IssuerNameRegistry
        {
            public override string GetIssuerName(SecurityToken securityToken)
            {

                return "Issuer";
                throw new NotImplementedException();
            }
        }


        public class CertificateValidator : X509CertificateValidator
        {
            public override void Validate(X509Certificate2 certificate)
            {
                if (certificate == null)
                {
                    throw new Exception("certificate is null");
                }

                if (certificate.Thumbprint.ToLower() != "mythumprint")
                {
                    throw new Exception("X509 certficate is signed with the wrong public key!");
                }
            }
        }
0 голосов
/ 17 сентября 2018

Решение состоит в том, чтобы думать о токене, как о обычном XMLDsig-подписанном XML - узел подтверждения подписан, а ссылка на подпись указывает на него.Код довольно прост, однако интересно то, что класс SignedXml должен быть унаследован, чтобы иметь валидатор подписи, следующий за атрибутом AssertionID (соглашение по умолчанию состоит в том, что атрибут id подписанного узла называется просто ID ивалидатор по умолчанию просто не найдет узел с атрибутом id, называемым по-другому).

public class SamlSignedXml : SignedXml
{
    public SamlSignedXml(XmlElement e) : base(e) { }

    public override XmlElement GetIdElement(XmlDocument document, string idValue)
    {
        XmlNamespaceManager mgr = new XmlNamespaceManager(document.NameTable);
        mgr.AddNamespace("trust", "http://docs.oasis-open.org/ws-sx/ws-trust/200512");
        mgr.AddNamespace("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
        mgr.AddNamespace("saml", "urn:oasis:names:tc:SAML:1.0:assertion");

        XmlElement assertionNode = 
               (XmlElement)document.SelectSingleNode("//trust:RequestSecurityTokenResponseCollection/trust:RequestSecurityTokenResponse/"+
                                                     "trust:RequestedSecurityToken/saml:Assertion", mgr);

        if (assertionNode.Attributes["AssertionID"] != null &&
            string.Equals(assertionNode.Attributes["AssertionID"].Value, idValue, StringComparison.InvariantCultureIgnoreCase)
            )
            return assertionNode;

        return null;
    }
}

Обратите внимание, что XPath предполагает, что токен имеет RequestSecurityTokenResponseCollection в корне, убедитесь, что ваши токены следуют этомусоглашение (в случае одного токена узел сбора может отсутствовать, а корень токена может быть просто RequestSecurityTokenResponse, обновите код соответствующим образом).

Код проверки тогда будет

// token is the string representation of the SAML1 token
// expectedCertThumb is the expected certificate's thumbprint
protected bool ValidateToken( string token, string expectedCertThumb, out string userName )
{
 userName = string.Empty;

 if (string.IsNullOrEmpty(token)) return false;

 var xd = new XmlDocument();
 xd.PreserveWhitespace = true;
 xd.LoadXml(token);

 XmlNamespaceManager mgr = new XmlNamespaceManager(xd.NameTable);
 mgr.AddNamespace("trust", "http://docs.oasis-open.org/ws-sx/ws-trust/200512");
 mgr.AddNamespace("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
 mgr.AddNamespace("saml", "urn:oasis:names:tc:SAML:1.0:assertion");

 // assertion
 XmlElement assertionNode = (XmlElement)xd.SelectSingleNode("//trust:RequestSecurityTokenResponseCollection/trust:RequestSecurityTokenResponse/trust:RequestedSecurityToken/saml:Assertion", mgr);

 // signature
 XmlElement signatureNode = (XmlElement)xd.GetElementsByTagName("Signature")[0];

 var signedXml = new SamlSignedXml( assertionNode );
 signedXml.LoadXml(signatureNode);

 X509Certificate2 certificate = null;
 foreach (KeyInfoClause clause in signedXml.KeyInfo)
 {
  if (clause is KeyInfoX509Data)
  {
   if (((KeyInfoX509Data)clause).Certificates.Count > 0)
   {
    certificate =
    (X509Certificate2)((KeyInfoX509Data)clause).Certificates[0];
   }
  }
 }

 // cert node missing
 if (certificate == null) return false;

 // check the signature and return the result.
 var signatureValidationResult = signedXml.CheckSignature(certificate, true);

 if (signatureValidationResult == false) return false;

 // validate cert thumb
 if ( !string.IsNullOrEmpty( expectedCertThumb ) )
 {
  if ( !string.Equals( expectedCertThumb, certificate.Thumbprint ) )
   return false;
 }

 // retrieve username

 // expires = 
 var expNode = xd.SelectSingleNode("//trust:RequestSecurityTokenResponseCollection/trust:RequestSecurityTokenResponse/trust:Lifetime/wsu:Expires", mgr );

 DateTime expireDate;

 if (!DateTime.TryParse(expNode.InnerText, out expireDate)) return false; // wrong date

 if (DateTime.UtcNow > expireDate) return false; // token too old

 // claims
 var claimNodes =                 
   xd.SelectNodes("//trust:RequestSecurityTokenResponseCollection/trust:RequestSecurityTokenResponse/trust:RequestedSecurityToken/"+
                  "saml:Assertion/saml:AttributeStatement/saml:Attribute", mgr );
 foreach ( XmlNode claimNode in claimNodes )
 {
  if ( claimNode.Attributes["AttributeName"] != null && 
              claimNode.Attributes["AttributeNamespace"] != null &&
       string.Equals( claimNode.Attributes["AttributeName"].Value, "name", StringComparison.InvariantCultureIgnoreCase ) &&   
                     string.Equals( claimNode.Attributes["AttributeNamespace"].Value, "http://schemas.xmlsoap.org/ws/2005/05/identity/claims", StringComparison.InvariantCultureIgnoreCase ) &&
         claimNode.ChildNodes.Count == 1 
      )
  {
   userName = claimNode.ChildNodes[0].InnerText;
   return true;
  }
 }

 return false;
}

С некоторыми незначительными изменениями вы сможете делать то, что хотите.

Кстати.Большая часть ответа скопирована из моей записи в блоге

https://www.wiktorzychla.com/2018/09/parsing-saml-11-ws-federation-tokens.html

, которая документирует подход, который мы используем внутри нашего приложения.Я планировал сделать эту запись в течение некоторого времени, и ваш вопрос был всего лишь необходимым триггером.

...