RPC相互认证

时间:2014-05-08 08:45:13

标签: authentication rpc

我想实现安全的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

任何人都可以分享工作示例来演示身份验证机制。

1 个答案:

答案 0 :(得分:2)

一些评论,然后是一些有效的代码片段。

首先,在进行Kerberos时, REALLY 有助于了解SPN是什么,以及它为何如此重要。一旦你在脑海中清楚地了解了它的用途,那么其余的很多内容将会更有意义。

简单地说,Kerberos身份验证本质上是客户端计算机,服务器计算机和KDC(域控制器)之间的3向对话。在一般情况下,客户端对服务器计算机上的服务器应用程序的配置知之甚少(也不需要) - 特别是它不知道服务器应用程序在哪个用户帐户下运行。但是使用Kerberos时,必须知道这一点 - 客户端本质上需要向KDC询问它可以传递给服务器机器的数据块。服务器计算机可以使用此数据blob(再次通过联系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()。