2014-05-08 74 views
0

我想實現安全RPC,它將執行相互(客戶端&服務器)身份驗證。我想要使​​用RPC_C_AUTHN_GSS_KERBEROS身份驗證服務。所以,我想在設置方式如下驗證信息:
在客戶副作用
1)使用RpcBindingFromStringBinding
2)設置使用RpcBindingSetAuthInfo
AT服務器副作用
1)內部安全回調認證信息創建新的綁定句柄,嘗試使用RpcBindingInqAuthClient或RpcServerInqCallAttributes驗證/交叉檢查身份驗證信息。RPC相互身份驗證

我的問題是:
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

找到類似的懸而未決的問題任何人都可以共享工作的例子來演示驗證機制。

回答

2

一些評論,然後一些工作代碼片段。

首先,在做Kerberos時,它真的有助於瞭解SPN是什麼,以及它爲什麼重要。一旦你在腦海中有了一個清晰的想法是什麼,那麼其餘的很多事情會讓你更有意義。

簡而言之,Kerberos身份驗證本質上是客戶端計算機,服務器計算機和KDC(域控制器)之間的三方會話。在一般情況下,客戶端不太瞭解服務器應用程序在服務器機器上的配置(也不需要) - 特別是它不知道服務器應用程序正在運行的用戶帳戶。但對於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,則客戶端可以簡單地使用[email protected],其中這是運行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: 
     // 
     // [email protected] 
     // 
     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 "[email protected]". 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()。