【问题标题】:RPC mutual authenticationRPC相互认证
【发布时间】:2014-05-08 08:45:12
【问题描述】:

我想实现安全的 RPC,它将进行相互(客户端和服务器)身份验证。我想使用 RPC_C_AUTHN_GSS_KERBEROS 身份验证服务。所以我尝试通过以下方式设置身份验证信息:
在客户端-
1) 使用 RpcBindingFromStringBinding
创建新的绑定句柄 2) 使用 RpcBindingSetAuthInfo
设置身份验证信息 在服务器端-
1) 在安全回调中,尝试使用 RpcBindingInqAuthClient 或 RpcServerInqCallAttributes 验证/交叉检查身份验证信息。

我的问题是:
1) RpcBindingSetAuthInfo 为 RPC_C_AUTHN_GSS_KERBEROS 返回 RPC_S_UNKNOWN_AUTHN_SERVICE。如果我使用 RPC_C_AUTHN_WINNT,API 可以工作。
2) 即使我使用 RPC_C_AUTHN_WINNT。我在客户端设置的服务器端没有获得相同的信息(身份验证级别、serverPrincName、身份验证服务等)。
3) 即使我没有在客户端调用 RpcBindingSetAuthInfo,我也会得到一些默认的身份验证值。

所以我不确定如何进行 RPC_C_AUTHN_GSS_KERBEROS 身份验证以及如何在服务器端进行验证。我试图找到解决方案,但找不到任何东西。 我可以在
How to use Secure RPC?
RPC Authentication

找到类似的未回答问题 任何人都可以分享工作示例来演示身份验证机制。

【问题讨论】:

    标签: authentication rpc


    【解决方案1】:

    几个 cmets,然后是一些工作代码片段。

    首先,在使用 Kerberos 时,确实有助于了解 SPN 是什么以及为什么它很重要。一旦你清楚地知道它的用途,那么剩下的很多事情就会变得更有意义。

    简单地说,Kerberos 身份验证本质上是客户端计算机、服务器计算机和 KDC(域控制器)之间的 3 向对话。在一般情况下,客户端不太了解服务器计算机上的服务器应用程序的配置(也不需要)——特别是它不知道服务器应用程序在哪个用户帐户下运行。但是对于 Kerberos,了解这一点很重要——客户端本质上需要向 KDC 请求可以传递给服务器机器的数据块。服务器机器可以使用这个数据块(同样通过联系 KDC)来创建运行 RPC 调用的安全上下文。但是这个数据 blob 只能通过在正确用户帐户下运行的进程在 KDC 上兑换。

    那么问题来了 - 客户端如何知道服务器进程正在运行的帐户。这就是 SPN 的用武之地——我想您可以将其视为存储在客户端进程可以指定的 KDC/DC 中的昵称。例如,假设您想对 http 服务器进行身份验证 - SPN 可能采用以下形式:

    http/hostname.mydomain.com
    

    这本身并没有说明运行 HTTP 服务器的用户帐户,但域控制器可以在活动目录中查找它并找出运行 http 服务器的真实帐户。

    要让所有这些工作正常进行,服务器端通常需要在启动时注册一个 SPN。我应该注意到,通常使用 DsRegisterServerSpn() 函数来执行此操作,但通常几乎所有用户帐户都没有足够的权限来执行此操作。一个例外是 LocalSystem 帐户 - 因此如果您的 RPC 服务器作为 Windows 服务运行,它将能够注册一个 SPN。请注意,域管理员可以为任何帐户注册 SPN。

    如果您无法注册 SPN,客户端可以简单地使用 user@domain.com,这是运行 RPC 服务器的帐户的用户名。

    现在,如何使用 RPC 完成所有这些工作。假设您有一个通过套接字进行通信的 RPC 服务器。初始化事物的服务器端代码如下所示:

    #define TCPPORT "1234"
    
    int rpcstart(void)
    {
        RPC_STATUS status;
        unsigned char * pszSecurity     = (unsigned char *) NULL;
        unsigned int    cMinCalls           = 1;
        unsigned int    cMaxCalls           = RPC_C_LISTEN_MAX_CALLS_DEFAULT;
        RPC_BINDING_VECTOR *pBindingVector;
        RPC_CSTR pSpn;
    
        status = RpcServerUseProtseqEp((RPC_CSTR) "ncacn_ip_tcp", cMaxCalls, (RPC_CSTR) TCPPORT, pszSecurity);  // Security descriptor
        if (status)
        {
            fprintf(outfile, "RpcServerUseProtseqEp failed\n");
            return status;
        }
    
        status = RpcServerInqBindings(&pBindingVector);
        if (status) {
            printf("Failed RpcServerInqBindings\n");
            exit(status);
        }
    
        status = RpcEpRegister(MyRemote_ServerIfHandle, pBindingVector, NULL, (RPC_CSTR) "build master remote");
        if (status) {
            printf("Failed RpcEpRegister\n");
            exit(status);
        }
    
        status = RpcServerRegisterIf(MyRemote_ServerIfHandle,  // interface to register
                        NULL,   // MgrTypeUuid
                        NULL);  // MgrEpv; null means use default
        if (status)
        {
            fprintf(outfile, "RpcServerRegisterIf failed\n");
            return status;
        }
    
        //
        // Register "remote/<hostname>" as a SPN.  Note that this call will fail
        // for normal user accounts as they typically do not have permissions to add
        // an SPN.  But for the computer account (i.e. running as a local service)
        // it will work.
        //
        // Failure code is usually ERROR_DS_INSUFF_ACCESS_RIGHTS if you aren't a computer
        // account (i.e. a service).
        //
        // Note that if one does this during service startup, one should also clean up
        // afterwards during service shutdown (use DS_SPN_DELETE_SPN_OP).
        //
        status = DsServerRegisterSpn(DS_SPN_ADD_SPN_OP,"remote",NULL);
        if( status )
        {
            //
            // If we did not have permissions to register a new SPN, then
            // use whatever the default would be.  Typically it would be:
            //
            // username@domain.com
            //
            status = RpcServerInqDefaultPrincName(RPC_C_AUTHN_GSS_KERBEROS, &pSpn);
            if( status )
            {
                fprintf(outfile, "RpcServerInqDefaultPrincName failed\n");
                return status;
            }
            fprintf(outfile, "SPN is %s\n", pSpn);
        }
        else
        {
            //
            // For our purposes here, this is good enough.
            //
            pSpn = (RPC_CSTR) "remote/localhost";
        }
    
        status = RpcServerRegisterAuthInfo(pSpn, RPC_C_AUTHN_GSS_KERBEROS, NULL, NULL);
        if( status )
        {
            fprintf(outfile, "RpcServerRegisterAuthInfo failed\n");
            return status;
        }
    
        status = RpcServerListen(cMinCalls, cMaxCalls, TRUE); /* Return immediately */
        if (status)
        {
            fprintf(outfile, "RpcServerListen failed\n");
            return status;
        }
    
        status = RpcMgmtWaitServerListen();  // wait operation
        if (status)
        {
            fprintf(outfile, "RpcMgmtWaitServerListen failed\n");
            return status;
        }
        return 0;
    }
    

    现在客户端需要这样的东西:

    BOOL MyInitRemoteRPC(const char * hostname, int port, const char * spn)
    {
        RPC_STATUS status;    
        unsigned sec_options = 0;
        DWORD cbSPN = MAX_PATH; char szSPN[MAX_PATH + 1]; 
        char Endpoint[100];
    
        sprintf(Endpoint, "ncacn_ip_tcp:%s[%d]", hostname, port);
        /* First create a valid (incomplete) binding handle */
        status = RpcBindingFromStringBinding((RPC_CSTR) Endpoint, &MyRemote_IfHandle);
        if (status)
        {
            fprintf(stderr, "Failed to calculate binding\n");
            return FALSE;
        }
    
        //
        // If no SPN is passed in, we assume this to mean that the RPC server was
        // running under the LocalSystem account, and was able to register the SPN
        // of the form "remote/hostname".
        //
        // For cases where no "remote/hostname" SPN was registered, one can always just
        // supply "<username>@<domain>" - for example "joe@foo.bar.com".  This can be useful
        // when the client/server is being tested outside of the service framework.
        //
        if( spn == NULL ) {
            status = DsMakeSpn("remote", hostname, NULL, 0, NULL, &cbSPN, szSPN);
            if( status )
            {
                printf("DsMakeSpn failed\n");
                exit(1);
            }
            spn = szSPN;
        }
    
        status = RpcBindingSetAuthInfo(MyRemote_IfHandle, 
                           (RPC_CSTR) spn,
                           sec_options,
                           RPC_C_AUTHN_GSS_KERBEROS,
                           NULL, 0);
        if (status) {
          printf ("RpcBindingSetAuthInfo failed: 0x%x\n", status);
          exit (1);
        }
    
        return TRUE;
    }
    

    请注意,您可以使用多种身份验证级别 - 请参阅 sec_options 标志 传递到客户端的 RpcBindingSetAuthInfo() 中。

    【讨论】:

      猜你喜欢
      • 2018-09-02
      • 2012-11-19
      • 2012-09-12
      • 2014-03-03
      • 1970-01-01
      • 2013-11-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多