首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >RPC相互认证

RPC相互认证
EN

Stack Overflow用户
提问于 2014-05-08 08:45:12
回答 1查看 1.8K关注 0票数 1

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

是否有人可以共享这个工作示例来演示身份验证机制。

EN

回答 1

Stack Overflow用户

发布于 2014-10-02 19:59:34

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

首先,在执行Kerberos时,了解SPN是什么以及它为什么重要确实很有帮助。一旦你在脑海中有了一个清晰的想法,那么剩下的很多事情就会变得更有意义了。

简单地说,Kerberos身份验证本质上是客户机、服务器机器和KDC (域控制器)之间的3路会话。在一般情况下,客户端对服务器机器上服务器应用程序的配置不太了解(也不应该需要)--具体来说,它不知道服务器应用程序在哪个用户帐户下运行。但是对于Kerberos来说,了解这一点是非常重要的--客户端实际上需要向KDC请求一小块数据,以便它可以传递给服务器机器。服务器机器可以使用这个data (同样通过与KDC联系)来创建运行RPC调用的安全上下文。但是,只有在正确的用户帐户下运行的进程才能在KDC中赎回此数据blob。

于是,问题出现了--客户端如何知道服务器进程在哪个帐户下运行。这就是SPN出现的地方--我想您可以把它看作是存储在KDC/DC中的昵称,客户端进程可以指定这个昵称。例如,假设您希望对http服务器进行身份验证-- SPN的形式可能是:

代码语言:javascript
复制
http/hostname.mydomain.com

这本身并不说明HTTP服务器在哪个用户帐户下运行,但是域控制器可以在active目录中查找该帐户,并找出运行http服务器的真实帐户。

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

如果不能注册SPN,客户端只需使用user@domain.com,这是运行RPC服务器的帐户的用户名。

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

代码语言:javascript
复制
#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;
}

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

代码语言:javascript
复制
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;
}

注意,您可以使用不同的身份验证级别--请参阅客户端传递给RpcBindingSetAuthInfo()的RpcBindingSetAuthInfo标志。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/23536907

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档