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

bio*_*tal 7 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>
Run Code Online (Sandbox Code Playgroud)

为简洁起见,已删除数字签名中包含的证书信息,但它来自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>
Run Code Online (Sandbox Code Playgroud)

AuthnRequest没有数字签名发送我的工作正常.当我包含数字签名时,它失败的状态代码500和通用的"无效请求"消息.

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

谢谢.

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

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

<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>
Run Code Online (Sandbox Code Playgroud)

我已经包含了两个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>
Run Code Online (Sandbox Code Playgroud)

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

<KeyInfo>
    <X509Data>
        <X509Certificate>MIID...MMxZ</X509Certificate>
    </X509Data>
</KeyInfo>
Run Code Online (Sandbox Code Playgroud)

我现在正在使用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>
Run Code Online (Sandbox Code Playgroud)

这仍然会导致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**
Run Code Online (Sandbox Code Playgroud)

在我读到这篇文章时,用于验证签名的公钥是从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]
Run Code Online (Sandbox Code Playgroud)

但是实际验证失败了,大概是因为摘要值不匹配.这意味着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
Run Code Online (Sandbox Code Playgroud)

编辑5 - 请求跟踪

以下是SAML与SSOCircle握手期间发出的请求的跟踪.输出由nodejs 请求模块与请求调试模块一起生成.

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

{ 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
Run Code Online (Sandbox Code Playgroud)

Request debugId= 17:这是我的客户端调用我的nodejs代理服务器进行的SAML握手操作.

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

Request debugId= 18:SAMLRequest被POST到SSOCircle SAML端点

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

Hos*_*Hos 2

关于 Edit2:自签名证书对于 SSOCircle 来说就足够了,只要它包含在服务提供商的元数据中即可。

关于 Edit3:请将签名元素移到发行者元素之后。请求必须符合架构:

<sequence>
<element ref="saml:Issuer" minOccurs="0"/>
<element ref="ds:Signature" minOccurs="0"/>
<element ref="samlp:Extensions" minOccurs="0"/>
</sequence> 
Run Code Online (Sandbox Code Playgroud)