SAML重定向签名或验证无法生成正确的签名

时间:2017-06-22 14:12:33

标签: c# saml-2.0

我正在使用以下代码为我的SAML成功生成签名。我使用XML符号对POST进行了排序,但REDIRECT完全不同。我无法生成与https://www.samltool.com/sign_logout_req.php相同的签名,当我尝试验证真正的签名时,它就失败了。

我把它带回基础,并试图看看我是否可以用同样的方式签名,但我不能指出我形成数据的方式有问题。

以下详细信息(进入samltool.com):

<saml:LogoutRequest ID="_02380F63816E0E92D6537758C37FE05F" Version="2.0" IssueInstant="2017-06-21T15:34:59.911Z" Destination="https://myteststs.net/appname/auth/" xmlns:saml="urn:oasis:names:tc:SAML:2.0:protocol"><saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">https://w.sp-app.com</saml:Issuer><saml:NameID xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">1869374313</saml:NameID></saml:LogoutRequest>

私钥(testcert)

MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAL13oqZ+pNa/fYIc+GYBrENOQ9rKWMeQEL9iDJyj7DFrQA40jOCY1UiOT2uLYvIwfqTHMuPmmlOLSyqCumZbKYP6KIM0pe+vJcJO6Nxg81gmN3jx3GbnDsmhi54oAmj3JC/Z/WbliqUXjlIAUlzLmMll7/vy2V5ec/gxHBpuRWBjAgMBAAECgYAWiWn/+vV5k9qGhRKo8479jIw2tLX9uu27Dhso8LiyIitC5U5Skutfz4mz5acV3t3ZlNZBVJdL07hTrKwma7aSx1r6UwTtW002ZZzytEVn7G7ytOIXkT+q/TuooCR8aa88vwhUFPqCSOuZgOPH9ytqAkzDCaNgVKdhQgRgjxfOBQJBAOSu4t5AgFJUBOcYOEOm6+v8R1CqedfyOgya3g1gsA4VnuG+ms233ZxWSPkiMnoUpEh8gBnZyk6ZZSlk668rwBcCQQDUGYg7wVLqhPAyjfM74oaJgohyQfQK6rPnzlKoGbdDR0QRN545ATBsETi2GSIYAHgkgLDJw3/lw1wX1dXzFuWVAkABmR9IwlajPKcUHl02S9JWQdsVuztCwRSaxfJLUaOpVYlYtoZKbcCEuS2lYBHOPJqxTv1uMNFzHytP0L686KddAkART6gr4GKJG6KTLbzNhXafoJTMZo+pmHBomhFrAPZROm7WzOhQFMXD/D/ZtQFwXhFwQUSsoxU8Ro6sr1pQBe1lAkBlXndo3Bm6AITDDsJZYg10XiBMNj4743t0pV6jayf9UTRZHu2GI9AWoU3/FTQt34zbPz6TjlNuJnwMHwfCFk1F

X.509

MIICMTCCAZqgAwIBAgIQcuFBQn5d27JBvbkCO+utKjANBgkqhkiG9w0BAQUFADBXMVUwUwYDVQQDHkwAewAyAEYAOAA3ADkANQA4ADUALQA3AEMANQA0AC0ANAA1ADAARAAtADgAOABGAEIALQBBADMARgA3ADEAMwA2ADQANgBFAEMANgB9MB4XDTE2MDEwODExMTU0OFoXDTE3MDEwNzE3MTU0OFowVzFVMFMGA1UEAx5MAHsAMgBGADgANwA5ADUAOAA1AC0ANwBDADUANAAtADQANQAwAEQALQA4ADgARgBCAC0AQQAzAEYANwAxADMANgA0ADYARQBDADYAfTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAvXeipn6k1r99ghz4ZgGsQ05D2spYx5AQv2IMnKPsMWtADjSM4JjVSI5Pa4ti8jB+pMcy4+aaU4tLKoK6Zlspg/oogzSl768lwk7o3GDzWCY3ePHcZucOyaGLnigCaPckL9n9ZuWKpReOUgBSXMuYyWXv+/LZXl5z+DEcGm5FYGMCAwEAATANBgkqhkiG9w0BAQUFAAOBgQBDXaccjXBrBhxp1fcEDm7MotKvgh8DxQAACk/Uxb4r2R6+LcePUxQTcxWVmyCQO0NR017FRf/fLFHmM9HZI3lwx5ka4xBnSOu8mejQ0KOYt4yf2VQG6pWGa046Ntip+KB/yDQKXQ3RHprsshe33MFlEWpDJyo6jyDpDUqLjPBvtg==

的RelayState:

RELAYTEST

SigAlg:

#rsa-sha1

所以...使用ssotool它会产生签名:

  

IG4VDmVwQRZWa75NmwjtqKlPVdCx6tm73gL7j3xvrqXsfirunUtr626SBmQJ4mke77bYzXg8D1hAy5EREOhz2QH23j47XexqbVSNTtAkZV7KP1 / lO8K01tiQr8SGJqzdFor / FZZscIDFlw3cBLXhGSwWK9i0qO / e55qkgxJS9OA =

然而..使用下面的代码(以及许多..很多..变体)我无法让它产生相同的签名。请注意,samlrequest是base64编码的,建议使用压缩(但您可以将其缩小为相同的输出)。我也遵循了这个规范(3.4.4.1):https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf

static byte[] Sign(string data, string certSubject)
    {

        // Access Personal (MY) certificate store of current user
        X509Store my = new X509Store(StoreName.My, StoreLocation.LocalMachine);
        my.Open(OpenFlags.ReadOnly);


        // Find the certificate we’ll use to sign
        RSACryptoServiceProvider csp = null;

        foreach (X509Certificate2 cert in my.Certificates)
        {
            if (cert.Subject.Contains(certSubject))
            {
                // Get its associated CSP and private key
                csp = (RSACryptoServiceProvider)cert.PrivateKey;

            }
        }

        if (csp == null)
        {
            throw new Exception("No valid cert was found");
        }

        string certAlgorithm = csp.SignatureAlgorithm;


        // Hash the data
        SHA1Managed sha1 = new SHA1Managed();
        UnicodeEncoding encoding = new UnicodeEncoding();

        byte[] dataRaw = encoding.GetBytes(data);
        byte[] hash = sha1.ComputeHash(dataRaw);


        // Sign the hash
        return csp.SignHash(hash, CryptoConfig.MapNameToOID("SHA1"));
    }

    static bool Verify(string text, byte[] signature, string certPublic)
    {

        // Load the certificate we’ll use to verify the signature from a file
        X509Certificate2 appSigningX509Certificate = null;
        var appSigningCertificateBytes = Convert.FromBase64String(certPublic);
        appSigningX509Certificate = new X509Certificate2(appSigningCertificateBytes);

        // Get its associated CSP and public key
        RSACryptoServiceProvider csp = (RSACryptoServiceProvider)appSigningX509Certificate.PublicKey.Key;


        // Hash the data
        SHA1Managed sha1 = new SHA1Managed();
        UnicodeEncoding encoding = new UnicodeEncoding();

        byte[] data = encoding.GetBytes(text);
        byte[] hash = sha1.ComputeHash(data);


        // Verify the signature with the hash
        return csp.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA1"), signature);
    }


    static void Main(string[] args)
    {

        // Usage sample
        try
        {

            string cert = "MIICMTCCAZqgAwIBAgIQcuFBQn5d27JBvbkCO+utKjANBgkqhkiG9w0BAQUFADBXMVUwUwYDVQQDHkwAewAyAEYAOAA3ADkANQA4ADUALQA3AEMANQA0AC0ANAA1ADAARAAtADgAOABGAEIALQBBADMARgA3ADEAMwA2ADQANgBFAEMANgB9MB4XDTE2MDEwODExMTU0OFoXDTE3MDEwNzE3MTU0OFowVzFVMFMGA1UEAx5MAHsAMgBGADgANwA5ADUAOAA1AC0ANwBDADUANAAtADQANQAwAEQALQA4ADgARgBCAC0AQQAzAEYANwAxADMANgA0ADYARQBDADYAfTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAvXeipn6k1r99ghz4ZgGsQ05D2spYx5AQv2IMnKPsMWtADjSM4JjVSI5Pa4ti8jB+pMcy4+aaU4tLKoK6Zlspg/oogzSl768lwk7o3GDzWCY3ePHcZucOyaGLnigCaPckL9n9ZuWKpReOUgBSXMuYyWXv+/LZXl5z+DEcGm5FYGMCAwEAATANBgkqhkiG9w0BAQUFAAOBgQBDXaccjXBrBhxp1fcEDm7MotKvgh8DxQAACk/Uxb4r2R6+LcePUxQTcxWVmyCQO0NR017FRf/fLFHmM9HZI3lwx5ka4xBnSOu8mejQ0KOYt4yf2VQG6pWGa046Ntip+KB/yDQKXQ3RHprsshe33MFlEWpDJyo6jyDpDUqLjPBvtg==";

            string samlRequestCompressed = "nZFPS8QwEMW/Ssl906TZ/gttQWwLhdWDyh68SKjBLbRJ7ExQv71traA38TiPee/9hilATaM82Rfr8U6/eg0YdHVJnlgkMtYmIuNJw5o8qpNYpGmcXYu0bVjckuCsZxisKUlEGQk6AK87A6gMLhLj6YElh4g/8FiKo4xzmnP+SIJ6aRiMws15QXQgwxC1Uab/MGrSdFBTP1r/TI3GUDm3iqE7KI+XkATv02hArtAl8bORVsEAct0Bib28v7o5yYVHutmi7e1IqmK7cMOb/2xXAHpeGUn1zfhGYaFwjvZ2KsIfoXvD7RLS1f9p4FmSi/QouNhzv6Kqffr1nOoT";
            string relaystate = "RELAYTEST";
            string algorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";

            string data = String.Empty;
            if (String.IsNullOrEmpty(relaystate))
            {
                data = String.Format("SAMLRequest={0}&SigAlg={1}", HttpUtility.UrlEncode(samlRequestCompressed), HttpUtility.UrlEncode(algorithm));
            }
            else
            {
                data = String.Format("SAMLRequest={0}&RelayState={1}&SigAlg={2}", HttpUtility.UrlEncode(samlRequestCompressed,Encoding.UTF8), HttpUtility.UrlEncode(relaystate,Encoding.UTF8), HttpUtility.UrlEncode(algorithm,Encoding.UTF8));
            }

            // Sign text
            byte[] signature = Sign(data, "{2F879585-7C54-450D-88FB-A3F713646EC6}");

            string b64encodedSig = Convert.ToBase64String(signature);
            string expectedSig = "IG4VDmVwQRZWa75NmwjtqKlPVdCx6tm73gL7j3xvrqXsfirunUtr626SBmQJ4mke77bYzXg8D1hAy5EREOhz2QH23j47XexqbVSNTtAkZV7KP1/lO8K01tiQr8SGJqzdFor/FZZscIDFlw3cBLXhGSwWK9i0qO/e55qkgxJS9OA=";

            if (b64encodedSig != expectedSig)
            {
                Console.WriteLine("Not what i expected");
                Environment.Exit(0);
            }

            // Verify signature. Testcert.cer corresponds to “cn=my cert subject”
            if (Verify(data, signature, cert))
            {
                Console.WriteLine("Signature verified");
            }
            else
            {
                Console.WriteLine("ERROR: Signature not valid!");
            }
        }

        catch (Exception ex)
        {
            Console.WriteLine("EXCEPTION: " +ex.Message);
        }

        Console.ReadKey();
    }

我似乎无法理解如何以相同的方式生成符号数据。我还确认本地安装的证书与上面的证书完全相同。

2 个答案:

答案 0 :(得分:1)

为了获得与OneLogin相同的签名,您必须使用与它们相同的URL编码。其他URL编码将导致不同的签名,但它们也完全有效。

See the SAML specification(3.4.4.1):

  

此外,请注意,URL编码不是规范的。也就是说,给定值有多种合法编码。因此,依赖方必须使用它在查询字符串上收到的原始URL编码的值来执行验证步骤。在对参数进行软件处理之后,仅对它们进行重新编码是不够的,因为结果编码可能与签名者的编码不匹配。

another answer to this question中所述,OneLogin似乎在.NET中使用与System.Net.WebUtility.UrlEncode()匹配的URL编码-但也请注意,它们对URL进行base64编码的签名编码本身,即使它可以包含[+ / =]之类的字符。 SAML规范不是很清楚,但是似乎表明这是错误的,

  

请注意,以base64编码的签名值中的某些字符本身在添加之前可能需要URL编码。

最后一点对于使用他们的工具验证您的签名至关重要。

答案 1 :(得分:0)

我现在已经解决了这个问题。

解决方案是HttpUtility.URLEncode没有将其编码为与SAML标准(或OneLogin)相同的标准。我通过查看压缩数据找到了它,但匹配的却是一个不同的URL编码。

答案是使用Uri.EscapeString。