代码之家  ›  专栏  ›  技术社区  ›  Nick

解析并验证WS-Trust XML令牌

  •  0
  • Nick  · 技术社区  · 7 年前

    我有一个用c#/.NET编写的webservice,它将未经身份验证的用户重定向到WS-Federation身份提供程序,然后该提供程序使用具有该用户角色的SAML令牌重定向回我的webservice。这符合被动WS-federation规范- http://docs.oasis-open.org/wsfed/federation/v1.2/os/ws-federation-1.2-spec-os.html#_Toc223175008

    得到这个之后,我得到一个请求,该请求将wresult设置为令牌。在我的代码里我有一个 wresult,它是xml文档的字符串。我知道的是im的领域,身份提供者的指纹,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 回复  |  直到 7 年前
        1
  •  2
  •   Wiktor Zychla    7 年前

    解决方案是将令牌看作是一个常规的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;
        }
    }
    

    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

    它记录了我们在应用程序内部使用的方法。我计划去的 你的问题只是我需要的一个导火索。

        2
  •  1
  •   Nick    7 年前

    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!");
                    }
                }
            }