如何正确地对SAML2.0 AuthnRequest进行数字签名?

时间:2014-10-14 16:26:27

标签: digital-signature saml-2.0

我在express / nodejs上有一个内置coffeescript的SAML2.0单点登录系统。

我使用SSOCircle来测试我的SSO,并且可以使用HTTP-POST或HTTP-REDIRECT绑定成功进行身份验证。到目前为止一切都很好。

现在我需要对我的身份验证请求进行数字签名。

为此,我使用了nodejs xml-crytpo库。这导致SAML看起来像这样:

<?xml version="1.0"?>
<samlp:AuthnRequest Version="2.0" ID="_1C8A2EFA-2644-4330-9A36-2B45547ECFAF" IssueInstant="2014-10-14T16:06:27.769Z" Destination="https://idp.ssocircle.com:443/sso/SSOPOST/metaAlias/ssocircle" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
    <saml:Issuer>http://app.localhost</saml:Issuer>
    <samlp:NameIDPolicy AllowCreate="true" />
    <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
        <SignedInfo>
            <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
            <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
            <Reference URI="#_1C8A2EFA-2644-4330-9A36-2B45547ECFAF">
                <Transforms>
                    <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
                </Transforms>
                <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
                <DigestValue>m4qHiXM82TuxY31l6+QSECHEHc0=</DigestValue>
            </Reference>
        </SignedInfo>
        <SignatureValue>fps0I0Rp02qDK0BPTK7Lh+ ...</SignatureValue>
        <KeyInfo>
            <X509Data>MIICuDCCAaCgAwIBAgIQEVFtJk ...</X509Data>
        </KeyInfo>
    </Signature>
</samlp:AuthnRequest>

为简洁起见,已删除数字签名中包含的证书信息,但它来自我在本地生成的pem文件(自签名)。

我还更新了提供给SSOCircle的服务提供商元数据,以包含SAML属性AuthnRequestsSignedWantAssertionsSigned,如下所示:

<md:EntityDescriptor entityID="http://app.localhost" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata">
    <md:SPSSODescriptor AuthnRequestsSigned="true" WantAssertionsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
        ...
    </md:SPSSODescriptor>
</md:EntityDescriptor>

在没有数字签名的情况下发送我的AuthnRequest可以正常工作。当我加入数字签名时,它会失败,状态代码为500,而且通用的“无效请求”会被删除。消息。

如何正确地对我的AuthnRequest进行数字签名,以便它能够对抗SSOCircle?

感谢。

编辑1 - 将证书密钥添加到SP元数据

根据评论中的建议,我现在已将相应的KeyDescriptor属性添加到我的sp-metadata中,以包含我的自签名证书的公钥。

<md:EntityDescriptor entityID="http://app.localhost" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata">
    <md:SPSSODescriptor AuthnRequestsSigned="true" WantAssertionsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
        <md:KeyDescriptor use="signing">
            <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
                <ds:X509Data>
                    <ds:X509SubjectName>CN=app.localhost</ds:X509SubjectName>
                    <ds:X509Certificate>MIICu ... wg==</ds:X509Certificate>
                </ds:X509Data>
            </ds:KeyInfo>
        </md:KeyDescriptor>
        <md:KeyDescriptor use="encryption">
            <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
                <ds:X509Data>
                    <ds:X509Certificate>MIICu ... wg==</ds:X509Certificate>
                </ds:X509Data>
            </ds:KeyInfo>
        </md:KeyDescriptor>
        ...
</md:EntityDescriptor>

我添加了两个KeyDescriptor元素,一个指定use = signing,另一个指定use = encryption,但我认为后者是多余的(因为我正在运行TLS)并且将被IdP忽略。

这不起作用,我得到同样的错误:

  

原因:SAML请求无效。

编辑2 - 自签名证书

理论:我不能使用自签名证书。相反,我必须使用SSOCircle已知并信任的证书

结果:这被驳斥了。只要sp元数据中包含正确的公钥,就可以使用自签名证书。

编辑3 - 添加了正确的Transform算法

我现在已更新我的签名以指定#enveloped-signature转换算法。

        <Transforms>
            <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
        </Transforms>

此外,我发现所包含的x509证书公钥的正确结构如下(请注意内部X509Certificate元素):

<KeyInfo>
    <X509Data>
        <X509Certificate>MIID...MMxZ</X509Certificate>
    </X509Data>
</KeyInfo>

我现在正在使用TestShib站点进行运行,因为这样可以访问IdP日志,从而提供更详细和有用的信息。

编辑4 - 交换SAML元素顺序以匹配模式

根据@Hos给出的答案(见下文),SAML元素的顺序必须与Schema匹配。因此,signature元素必须直接出现在issuer元素之后,如下所示:

<samlp:AuthnRequest Version="2.0" ID="_782F6F1E-1B80-4D7D-B26C-AC85030D9300" IssueInstant="2014-10-28T11:45:49.412Z" Destination="https://idp.ssocircle.com:443/sso/SSOPOST/metaAlias/ssocircle" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
    <saml:Issuer>http://app.localhost9de83841</saml:Issuer>
    <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
        <SignedInfo>
            <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
            <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
            <Reference URI="#_782F6F1E-1B80-4D7D-B26C-AC85030D9300">
                <Transforms>
                    <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
                </Transforms>
                <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
                <DigestValue>pjXEtbAFMJA3hWhD/f6lxJTshTg=</DigestValue>
            </Reference>
        </SignedInfo>
        <SignatureValue>FY1...Qg==</SignatureValue>
        <KeyInfo>
            <X509Data>
                <X509Certificate>MIID...MMxZ</X509Certificate>
            </X509Data>
        </KeyInfo>
    </Signature>
    <samlp:NameIDPolicy AllowCreate="true" />
</samlp:AuthnRequest>

这仍然会导致SSOCircle出现不透明的500错误。但是,TestShib显示以下日志:

11:02:37.292 - DEBUG [edu.internet2.middleware.shibboleth.common.security.MetadataPKIXValidationInformationResolver:631] - Write lock over cache acquired
11:02:37.292 - DEBUG [edu.internet2.middleware.shibboleth.common.security.MetadataPKIXValidationInformationResolver:634] - Added new PKIX info to entity cache with key: [http://app.localhost9de83841,{urn:oasis:names:tc:SAML:2.0:metadata}SPSSODescriptor,urn:oasis:names:tc:SAML:2.0:protocol,SIGNING]
11:02:37.292 - DEBUG [edu.internet2.middleware.shibboleth.common.security.MetadataPKIXValidationInformationResolver:637] - Write lock over cache released
11:02:37.293 - WARN [edu.internet2.middleware.shibboleth.idp.profile.saml2.SSOProfileHandler:406] - Message did not meet security requirements
org.opensaml.ws.security.SecurityPolicyException: **Validation of protocol message signature failed**

在我读到这篇文章时,用于验证签名的公钥是从sp元数据中提取的:

Added new PKIX info to entity cache with key: [http://app.localhost9de83841,{urn:oasis:names:tc:SAML:2.0:metadata}SPSSODescriptor,urn:oasis:names:tc:SAML:2.0:protocol,SIGNING]

然而,实际验证失败了,可能是因为摘要值不匹配。这意味着xmlcrypto计算的摘要与ShibTest计算的摘要不同。这肯定只是出于以下两个原因之一:

  • xmlcrypto和TestShib用于生成摘要的算法有些不同
  • 公钥不匹配。

对钥匙进行一分钟检查表明它们是相同的。所以这是我的代码,以防你发现任何事情。

注意:此代码依赖于nodejs xmlbuilderxml-crytpo库)

getSamlRequest:(idpUrl, requestId, next)->
    request = @xmlbuilder.create
        'samlp:AuthnRequest':
            '@xmlns:samlp':'urn:oasis:names:tc:SAML:2.0:protocol'
            '@xmlns:saml': 'urn:oasis:names:tc:SAML:2.0:assertion'
            '@Version': '2.0'
            '@ID': requestId
            '@IssueInstant': (new Date()).toISOString()
            '@Destination': idpUrl
            'saml:Issuer': '@@spEntityId'
        ,null
        ,headless: true
    request.comment 'insert-signature'
    request.element 'samlp:NameIDPolicy':
                        '@AllowCreate': 'true'
    saml = request.end()
    #Your self-signed pem file that contains both public and private keys. 
    #The public key must also be included in your sp-metadata
    certFilePath = "certs/sp-certificate.pem"
    @fs.readFile certFilePath, (err, certificate)=>
        signer = new @xmlcrypto.SignedXml()
        signer.signingKey = certificate
        signer.addReference "//*[local-name(.)='AuthnRequest']", ['http://www.w3.org/2000/09/xmldsig#enveloped-signature']
        signer.keyInfoProvider = new =>
            getKeyInfo: (key)=>
                public_key = /-----BEGIN CERTIFICATE-----([^-]*)-----END CERTIFICATE-----/g.exec(key)[1].replace /[\r\n|\n]/g, ''
                "<X509Data><X509Certificate>#{public_key}</X509Certificate></X509Data>"
        signer.computeSignature saml
        signature = signer.getSignatureXml()
        signed = saml.replace '<!-- insert-signature -->', signature
        return next null, signed

编辑5 - 请求跟踪

以下是SAML与SSOCircle握手期间发出的请求的跟踪。输出由nodejs request模块与request-debug模块一起生成。

我在此跟踪输出下面添加了一些澄清说明。

{ request: 
   { debugId: 17,
     uri: 'http://localhost:8082/security/sso/subscription/dev2/096b75a2-4a55-4eec-83c8-d2509e948b07',
     method: 'GET',
     headers: { host: 'localhost:8082' } } }
{ response: 
   { debugId: 17,
     headers: 
      { 'transfer-encoding': 'chunked',
        'content-type': 'application/json; charset=utf-8',
        server: 'Microsoft-HTTPAPI/2.0',
        'access-control-allow-origin': '*',
        'access-control-allow-headers': 'origin, x-requested-with, accept, content-type',
        'access-control-allow-methods': 'GET, POST, PATCH, PUT, DELETE',
        date: 'Wed, 05 Nov 2014 16:55:49 GMT' },
     statusCode: 200,
     body: '{"RequestId":"_F7DDDD24-32C6-420E-A550-95872D30033B","SubscriptionName":"Develop-2","SubscriptionURL":"dev2","IdentityProviderCertificateFile":"ssocircle.cer","IdentityProviderDestinationUrl":"https://idp.ssocircle.com:443/sso/SSOPOST/metaAlias/ssocircle","SAMLBinding":"HttpPost"}' } }
{ request: 
   { debugId: 18,
     uri: 'https://idp.ssocircle.com:443/sso/SSOPOST/metaAlias/ssocircle',
     method: 'POST',
     headers: 
      { host: 'idp.ssocircle.com:443',
        'content-type': 'application/x-www-form-urlencoded; charset=utf-8',
        'content-length': 3492 },
     body: 'SAMLRequest=PHNhbWxwOkF1dGhuUmVxdWVzdCB4bWxuczpzYW1scD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIiB4bWxuczpzYW1sPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIiBWZXJzaW9uPSIyLjAiIElEPSJfRjdEREREMjQtMzJDNi00MjBFLUE1NTAtOTU4NzJEMzAwMzNCIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMTEtMDVUMTY6NTU6NDkuODkwWiIgRGVzdGluYXRpb249Imh0dHBzOi8vaWRwLnNzb2NpcmNsZS5jb206NDQzL3Nzby9TU09QT1NUL21ldGFBbGlhcy9zc29jaXJjbGUiPjxzYW1sOklzc3Vlcj5odHRwOi8vYXBwLmxvY2FsaG9zdDlkZTgzODQxPC9zYW1sOklzc3Vlcj48U2lnbmF0dXJlIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj48U2lnbmVkSW5mbz48Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIgLz48U2lnbmF0dXJlTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3JzYS1zaGExIiAvPjxSZWZlcmVuY2UgVVJJPSIjX0Y3RERERDI0LTMyQzYtNDIwRS1BNTUwLTk1ODcyRDMwMDMzQiI%2BPFRyYW5zZm9ybXM%2BPFRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIiAvPjwvVHJhbnNmb3Jtcz48RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3NoYTEiIC8%2BPERpZ2VzdFZhbHVlPjBLdUtNVG1Zc29Wb1BlTXhtdDhuNml2M3RxZz08L0RpZ2VzdFZhbHVlPjwvUmVmZXJlbmNlPjwvU2lnbmVkSW5mbz48U2lnbmF0dXJlVmFsdWU%2BY1htVFJPWVBSWFZPNHpWSkxURXBBSWd0RTd1c2NsSG1QS0NFdTB0NXNzcmVYSjd1a0M4dWdGd2c4Zm1yWDZDenhib3FHNVl1enJkb2RWVVNsbU12bHZXMGpiaWtsQVBXMmtPNTcralB4TWV3UTdFdzJZdTJuRTZ2QkFhMUwxWGpsa0g0a1UrdlVWVEpFQnlsdXhOYjRQN0xoZnJTdnVrZDhWejJwbk5QTnJtY0tJZVp1LzFlZU8wWmRyRVZrdEY1REhVaFV0MEs5aFBhRXB5Z0xsYjVKYWhNZEttei9uQWk4OU04aTEyUTdrQ2hwb1UrcmhjYTQzaDBXdGk2SWI1N1lWRzgyMzE5MlNsdGR5UkZPdXl2bEJRL0FMSkdpN0hNYzRMRXVkQktOL3pxaUFpK1NZYm1ONVNuN0NucFJWbW13U3NJUElncWxCbmlrU2pIQzRQS1VBPT08L1NpZ25hdHVyZVZhbHVlPjxLZXlJbmZvPjxYNTA5RGF0YT48WDUwOUNlcnRpZmljYXRlPk1JSURnRENDQW1pZ0F3SUJBZ0lESUFTeE1BMEdDU3FHU0liM0RRRUJCUVVBTUM0eEN6QUpCZ05WQkFZVEFrUkZNUkl3RUFZRFZRUUtFd2xUVTA5RGFYSmpiR1V4Q3pBSkJnTlZCQU1UQWtOQk1CNFhEVEUwTVRBeE5qRTBOVFl3TlZvWERURTFNVEF4TmpFME5UWXdOVm93TmpFTE1Ba0dBMVVFQmhNQ1JFVXhFakFRQmdOVkJBb1RDVk5UVDBOcGNtTnNaVEVUTUJFR0ExVUVBeE1LWW1sdlpuSmhZM1JoYkRDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBS0Y0dDhzWVZNdHRVZkZ3eTZneFpQVjVWbWtCMDZwNXNqck5vUjUxUXZISmZFMkgyTnVTa0Nxa2paVmFwb1FRMStUVTNlelloMmxNVGNYSjU1Y0t2d0lUTlEvQWlLckxjaG4wdGxYZXFIRXhIdXBvazRGd1hqc20xSitpZTBvOUc1UDlRNTFXelRjRXYxSFRBV2Rkak9OK3Zsd3d0YndTaWRtNFBkN3hxZDdvQkhXTjJJSExQNlpGVHRPVWNpdzI1K0xtRk90V3dHdU41c1pNWDV6RDNUc216Y3ZNMFQwUzF0SVlHamhpaWNnM2UrbmhkWXhjSVNvZ1B1NjNlSWswKzM2OFU1TkhnYlJ5SVRnR2tPMFdtTU9PNDhpbFlFcWlPR1E3d3FxeTdrMzZzRGNxZXdiV1lvejFnQzBieGJBeXI1d3ZZUDdnaEVHYktSVXRWeDExZzdVQ0F3RUFBYU9CbmpDQm16QUpCZ05WSFJNRUFqQUFNQ3dHQ1dDR1NBR0crRUlCRFFRZkZoMVBjR1Z1VTFOTUlFZGxibVZ5WVhSbFpDQkRaWEowYVdacFkyRjBaVEFkQmdOVkhRNEVGZ1FVcTFyMnltMkRXb1NoYWgyRm11ekxJOWZjdDdZd1FRWURWUjBqQkRvd09LRXlwREF3TGpFTE1Ba0dBMVVFQmhNQ1JFVXhFakFRQmdOVkJBb1RDVk5UVDBOcGNtTnNaVEVMTUFrR0ExVUVBeE1DUTBHQ0FncVlNQTBHQ1NxR1NJYjNEUUVCQlFVQUE0SUJBUUFIRW5xTlVKN2VaYkkvNmozZTNmK2tFY3BQQUs2L3dXS0hSL1k2TWt1TGZpZERPREJ4eDRPTThQL1MrNFV4QkZSYWtBNFRkWkRNbjdJT2dTZ2Z3elQ0RjFxS1cvTkJKMzRtcjdndi9Yc0gxTDdHMlBEZFdUdmdGN1N5aFpCck9rbVYyZy9KYWg2U2pBREdabGdPWGJuTTlHUjlNN1NpVDBUcmFkay90dU9Zc2pxd2NJaE40eTVwSU9MNnZlemJKQThIeWZUY1lpMGFZVXFyMGl4Qkw1WWh5VDA1Qk13SUhkdFFNNGhqNjdyRDV4ME9QcmVrcTg0MjRkL0RGWmV5QTRBNG04Z3BOL0VDTFF0NzN3ajlqMVRudjBLdmlLRGZOcWJ4NngvL1o3MnN6VVhpWldETWJrNnhvdHVOZTV6Yy9xcXdhSWxLZ2lnWDJvcGRvbU9nTU14WjwvWDUwOUNlcnRpZmljYXRlPjwvWDUwOURhdGE%2BPC9LZXlJbmZvPjwvU2lnbmF0dXJlPjxzYW1scDpOYW1lSURQb2xpY3kgQWxsb3dDcmVhdGU9InRydWUiLz48L3NhbWxwOkF1dGhuUmVxdWVzdD4%3D' } }
{ response: 
   { debugId: 18,
     headers: 
      { server: '"SSOCircle Web Server"',
        date: 'Wed, 05 Nov 2014 16:55:48 GMT',
        'content-type': 'text/html;charset=UTF-8',
        connection: 'close',
        'set-cookie': [Object],
        'transfer-encoding': 'chunked' },
     statusCode: 500,
     body: '\n\n\n\n<html><head><title>\n            Error Page\n            \n        </title>\n    <link href="/css/bx.css" rel="stylesheet" type="text/css" /></head>\n    <body>\n        <div id="myheader">\n            <h1><img src="/logo.png" alt="IDPee - Put your LOGO here" height="65" width="180">&nbsp;</h1>\n        </div>\n        <div id="mycontent">\n            <div id="mynav">\n                <ul>\n                \n                </ul>\n            </div>\n            <div id="mybox">\n                <p>\n                    \n                    <h2>Error occurred</h2>\n<p>\nReason: Invalid signature in Request.\n</p>\n\n                    \n                </p>\n            </div>\n            \n        </div>\n        <div id="myfooter">\n            \n            Copyright &copy; SSOCircle/IDPee.com\n            \n            \n        </div>\n    </body>\n</html>\n\n\n\n' } }
error:  message=Something went wrong whilst trying to automatically log you in. Please try again., name=SSOFailure, message=An error occurred whilst processing the SSO Request., name=SSORequestFailure, message=The IdP failed to successfully process the SAMLRequest and returned an statuscode of 500., name=SamlRequestInvalid, url=http://app.localhost/security/saml2/request

请求debugId = 17 :这是我的客户端对我的nodejs代理服务器的调用,它启动了SAML握手。

响应debugId = 17 :从代理服务器返回客户端的200 OK响应。一旦SAML握手结束,客户端现在等待重定向。

请求debugId = 18 :SAMLRequest已发布到SSOCircle SAML端点

响应debugId = 18 :来自SSOCircle的500内部服务器错误响应以及HTML错误页面有效负载。

1 个答案:

答案 0 :(得分:1)

关于Edit2:只要SS​​OCircle包含在服务提供商的元数据中,自签名证书就足够了。

关于Edit3:请将签名元素移到issuer元素后面。请求必须符合架构:

<sequence>
<element ref="saml:Issuer" minOccurs="0"/>
<element ref="ds:Signature" minOccurs="0"/>
<element ref="samlp:Extensions" minOccurs="0"/>
</sequence>