我想实现安全的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
任何人都可以分享工作示例来演示身份验证机制。
答案 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()。