RPC相互认证
Posted
技术标签:
【中文标题】RPC相互认证【英文标题】:RPC mutual authentication 【发布时间】: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 找到类似的未回答问题 任何人都可以分享工作示例来演示身份验证机制。
【问题讨论】:
【参考方案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() 中。
【讨论】:
以上是关于RPC相互认证的主要内容,如果未能解决你的问题,请参考以下文章
Linux CentOS7 两台机器之间免输入密码相互登录(密钥对认证)