Schannel

安全通道(Schannel)安全包(其身份验证服务标识符为RPC_C_AUTHN_GSS_SCHANNEL)支持以下基于公钥的协议:SSL(安全套接字层)版本 2.0 和 3.0、传输层安全性 (TLS) 1.0 和专用通信技术 (PCT) 1.0。 TLS 1.0 是 1999 年 1 月由 Internet 工程工作队 (IETF) 发布的标准化、略带修改的 SSL 3.0 版本,RFC 2246。 由于 TLS 已标准化,因此鼓励开发人员使用 TLS 而不是 SSL。 仅包含用于向后兼容性的 PCT,不应用于新开发。 使用 Schannel 安全包时,DCOM 会根据客户端和服务器功能自动协商最佳协议。

以下主题简要介绍了 TLS 协议及其与 DCOM 配合使用的方式。

注意

这些部分中有关 TLS 协议的所有信息也适用于 SSL 和 PCT 协议。

 

何时使用 TLS

当服务器需要向匿名客户端证明其身份时,TLS 是唯一可用的安全选项。 对于想要参与电子商务的网站来说,这尤其重要,因为它有助于保护敏感信息(如信用卡号)的传输。 TLS 确保电子商务客户可以确定他们正在与谁开展业务,因为他们得到了服务器的标识证明。 它还使电子商务服务器的效率不必担心对每个客户的标识进行身份验证。

TLS 要求所有服务器向客户端证明其身份。 此外,TLS 还提供让客户端向服务器证明其标识的选项。 这种相互身份验证可用于限制大型企业 Intranet 中某些网页的访问。

TLS 支持最强大的身份验证级别,并提供一个开放体系结构,允许加密强度随时间推移而增加,以跟上技术创新。 TLS 是传输中数据所需的最高安全级别的环境的最佳选择。

TLS 工作原理的简要概述

TLS 基于公钥基础结构(PKI)构建,该基础结构使用公钥/私钥对来启用数据加密和建立数据完整性,并使用 X.509 证书进行身份验证。

许多安全协议(如 Kerberos v5 协议)依赖于一个密钥来加密和解密数据。 因此,此类协议取决于加密密钥的安全交换;在 Kerberos 协议中,这是通过从密钥分发中心(KDC)获取的票证完成的。 这要求使用 Kerberos 协议的每个人都注册到 KDC,这对电子商务 Web 服务器来说是一个不切实际的限制,旨在吸引来自世界各地的数百万客户。 因此,TLS 依赖于 PKI,它使用两个密钥进行数据加密。“当对的一个密钥加密数据时,只有对的另一个密钥可以解密它。 此设计的主要优点是,无需安全交换加密密钥即可执行加密。

PKI 使用一种技术,其中一个密钥保持私有,并且仅可供注册的主体使用,而另一个密钥则公开供任何人访问。 如果有人想要向密钥对的所有者发送私钥,则可以使用公钥加密该消息,并且只能使用私钥来解密消息。

密钥对还用于验证要发送的数据的完整性。 为此,密钥对的所有者可以在发送数据之前将数字签名附加到数据。 创建数字签名涉及计算数据的哈希,并使用私钥加密哈希。 任何使用公钥解密数字签名的人都确信数字签名必须只来自拥有私钥的人员。 此外,收件人可以使用与发送方相同的算法来计算数据的哈希,如果计算哈希与数字签名中发送的哈希匹配,则收件人可以确定数据在经过数字签名后未修改。

使用 PKI 进行大容量数据加密的一个缺点是其性能相对较慢。 由于涉及大量数学,使用依赖于密钥对的非对称密码加密和解密数据的速度可能比使用仅依赖于单个密钥的对称密码慢 1,000 倍。 因此,TLS 仅使用 PKI 生成数字签名,并协商客户端和服务器将用于批量数据加密和解密的特定于会话的单一密钥。 TLS 支持各种单密钥对称密码,将来可能会添加其他密码。

有关 TLS 握手协议的详细信息,请参阅 TLS 握手协议

有关 TLS 协议背后的加密的更多详细信息,请参阅 加密概要

X.509 证书

PKI 必须处理的关键问题是能够信任正在使用的公钥的真实性。 使用要与之开展业务的公司颁发的公钥时,需要确定密钥实际上属于公司,而不是想要发现信用卡号的小偷。

为了保证持有密钥对的主体的身份,该主体由证书颁发机构 (CA) 颁发 X.509 证书。 此证书包含标识主体的信息,包含主体的公钥,并由 CA 进行数字签名。 此数字签名表示 CA 认为证书中包含的公钥确实属于证书标识的主体。

你如何信任 CA? 因为 CA 本身包含由更高级别的 CA 签名的 X.509 证书。 此证书签名链一直持续到它到达根 CA,这是对其自己的证书进行签名的 CA。 如果信任证书根 CA 的完整性,则应能够信任证书本身的真实性。 因此,选择愿意信任的根 CA 是系统管理员的重要职责。

客户端证书

首次出现安全传输层协议时,其主要目的是保证客户端连接到正宗服务器,并帮助在传输过程中保护数据的隐私。 但是,SSL 3.0 和 TLS 1.0 还包括在协议握手期间传输客户端证书的支持。 此可选功能可实现客户端和服务器的相互身份验证。

是否应在应用程序的上下文中决定是否使用客户端证书。 如果主要要求对服务器进行身份验证,则不需要客户端证书。 但是,如果客户端身份验证至关重要,则可以使用客户端的证书,而不是依赖于应用程序中的自定义身份验证。 使用客户端证书优于自定义身份验证,因为它为用户提供了单一登录方案。

在 COM 中使用 TLS

TLS 仅支持模拟(RPC_C_IMP_LEVEL_IMPERSONATE)级别的模拟。 如果 COM 协商 TLS 作为代理上的身份验证服务,COM 会将模拟级别设置为模拟,而不考虑进程默认值。 要使模拟在 TLS 中正常工作,客户端必须向服务器提供 X.509 证书,并且服务器必须将该证书映射到服务器上的特定用户帐户。 有关详细信息,请参阅 分步指南,将证书映射到用户帐户

TLS 不支持 遮盖。 如果在 CoInitializeSecurityIClientSecurity::SetBlanket 调用中指定了遮盖标志和 TLS,将返回E_INVALIDARG。

TLS 不适用于设置为“无”的身份验证级别。 客户端和服务器之间的握手将检查每个客户端设置的身份验证级别,并选择连接的安全设置。

可以通过调用 CoInitializeSecurityCoSetProxyBlanket来设置 TLS 的安全参数。 以下部分介绍了进行这些调用所涉及的细微差别。

服务器如何设置安全毛毯

如果服务器想要使用 TLS,则必须将 Schannel (RPC_C_AUTHN_GSS_SCHANNEL) 指定为 CoInitializeSecurityasAuthSvc 参数中的身份验证服务。 为了防止客户端通过使用不太安全的身份验证服务连接到服务器,服务器应在调用 CoInitializeSecurity时仅将 Schannel 指定为身份验证服务。 服务器在调用 CoInitializeSecurity后无法更改安全毯子。

若要使用 TLS,应在服务器调用 CoInitializeSecurity时指定以下参数:

  • pVoid 应是指向 IAccessControl 对象的指针或指向 SECURITY_DESCRIPTOR的指针。 它不应 NULL 或指向 AppID 的指针。
  • cAuthSvc 不能为 0 或 -1。 cAuthSvc为 -1 时,COM 服务器永远不会选择 Schannel。
  • asAuthSvc 必须将 Schannel 指定为可能的身份验证服务。 为此,为此 SOLE_AUTHENTICATION_LIST的 Schannel 成员设置以下 SOLE_AUTHENTICATION_SERVICE 参数:
    • dwAuthnSvc 必须RPC_C_AUTHN_GSS_SCHANNEL。
    • dwAuthzSvc 应RPC_C_AUTHZ_NONE。 目前,它将被忽略。
    • pPrincipalName 必须是指向 CERT_CONTEXT的指针,强制转换为表示服务器的 X.509 证书的 OLECHAR 的指针。
  • dwAuthnLevel 指示从客户端接受的最小身份验证级别,以便成功连接。 它不能RPC_C_AUTHN_LEVEL_NONE。
  • dwCapabilities 不应设置EOAC_APPID标志。 如果 pVoid 指向 IAccessControl 对象,则应设置EOAC_ACCESS_CONTROL标志;如果 pVoid 指向SECURITY_DESCRIPTOR,则不应设置它。 有关可能设置的其他标志,请参阅 CoInitializeSecurity

有关使用 CoInitializeSecurity的详细信息,请参阅 使用 CoInitializeSecurity设置 Processwide Security。

客户端如何设置安全毛毯

如果客户端想要使用 TLS,则必须在 pAuthListCoInitializeSecurity参数的身份验证服务列表中指定 Schannel (RPC_C_AUTHN_GSS_SCHANNEL)。 如果在调用 CoInitializeSecurity 时未将 Schannel 指定为可能的身份验证服务,则稍后调用 CoSetProxyBlanket(或 IClientSecurity::SetBlanket)如果尝试将 Schannel 指定为身份验证服务,则失败。

当客户端调用 coInitializeSecurity时,应指定以下参数:

  • dwAuthnLevel 指定客户端要使用的默认身份验证级别。 它不能RPC_C_AUTHN_LEVEL_NONE。
  • dwImpLevel 必须RPC_C_IMP_LEVEL_IMPERSONATE。
  • pAuthList 必须具有以下 SOLE_AUTHENTICATION_INFO 参数作为列表的成员:
    • dwAuthnSvc 必须RPC_C_AUTHN_GSS_SCHANNEL。
    • dwAuthzSvc 必须RPC_C_AUTHZ_NONE。
    • pAuthInfo 是指向 CERT_CONTEXT的指针,转换为指向 void 的指针,表示客户端的 X.509 证书。 如果客户端没有证书或不希望将其证书呈现给服务器,pAuthInfo 必须 NULL,并且将尝试与服务器建立匿名连接。
  • dwCapabilities 是一组指示其他客户端功能的标志。 有关应设置哪些标志的信息,请参阅 CoInitializeSecurity

有关使用 CoInitializeSecurity的详细信息,请参阅 使用 CoInitializeSecurity设置 Processwide Security。

客户端如何更改安全毛毯

如果客户端想要使用 TLS,但在调用 CoInitializeSecurity后更改安全毛毯,则必须调用 CoSetProxyBlanketIClientSecurity::SetBlanket,其参数类似于调用 CoInitializeSecurity的参数,并具有以下差异:

  • pServerPrincName 以 msstd 或 fullsic 格式指示服务器的主体名称。 有关这些格式的信息,请参阅 主体名称。 如果客户端具有服务器的 X.509 证书,则可以通过调用 rpcCertGeneratePrincipalName来查找主体名称。
  • pAuthInfo 是指向 CERT_CONTEXT的指针,作为指向RPC_AUTH_IDENTITY_HANDLE的指针进行强制转换,表示客户端的 X.509 证书。 如果客户端没有证书或不希望将其证书呈现给服务器,pAuthInfo 必须 NULL,并且将尝试与服务器建立匿名连接。
  • dwCapabilities 由指示其他客户端功能的标志组成。 只有四个标志可用于更改安全毛毯设置:EOAC_DEFAULT、EOAC_MUTUAL_AUTH、EOAC_ANY_AUTHORITY(此标志已弃用),EOAC_MAKE_FULLSIC。 有关详细信息,请参阅 CoSetProxyBlanket

有关使用 CoSetProxyBlanket的详细信息,请参阅 在接口代理级别设置安全性。

示例:客户端更改安全毛毯

以下示例演示客户端如何更改安全毯以容纳来自服务器的请求,以便客户端提供其 X.509 证书。 为了简洁起见,省略了错误处理代码。

void ClientChangesSecurity ()
{
  HCRYPTPROV                   provider           = 0;
  HCERTSTORE                   cert_store         = NULL;
  PCCERT_CONTEXT               client_cert        = NULL;
  PCCERT_CONTEXT               server_cert        = NULL;
  WCHAR                        *server_princ_name = NULL;
  ISecret                      *pSecret           = NULL;
  MULTI_QI                     server_instance;
  COSERVERINFO                 server_machine;
  SOLE_AUTHENTICATION_LIST     auth_list;
  SOLE_AUTHENTICATION_INFO     auth_info[1];



  // Specify all the authentication info. 
  // The client is willing to connect using SChannel,
  //   with no client certificate.
  auth_list.cAuthInfo     = 1;
  auth_list.aAuthInfo     = auth_info;
  auth_info[0].dwAuthnSvc = RPC_C_AUTHN_GSS_SCHANNEL;
  auth_info[0].dwAuthzSvc = RPC_C_AUTHZ_NONE;
  auth_info[0].pAuthInfo  = NULL;  // No certificate

  // Initialize client security with no client certificate.
  CoInitializeSecurity( NULL, -1, NULL, NULL,
                        RPC_C_AUTHN_LEVEL_PKT,
                        RPC_C_IMP_LEVEL_IMPERSONATE, &auth_list,
                        EOAC_NONE, NULL );
  
  // Specify info for the proxy.
  server_instance = {&IID_ISecret, NULL, S_OK};
  server_machine  = {0, L"ServerMachineName", NULL, 0};
  
  // Create a proxy.
  CoCreateInstanceEx( CLSID_Secret, NULL, CLSCTX_REMOTE_SERVER, 
                      &server_machine, 1, &server_instance);
  pSecret = (ISecret *) server_instance.pItf;

  //** The client obtained the server's certificate during the handshake.
  //** The server requests a certificate from the client.

  // Get the default certificate provider.
  CryptAcquireContext( &provider, NULL, NULL, PROV_RSA_SCHANNEL, 0 );

  // Open the certificate store.
  cert_store = CertOpenSystemStore( provider, L"my" );

  // Find the client's certificate.
  client_cert = 
    CertFindCertificateInStore( cert_store,
                                X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
                                0,
                                CERT_FIND_SUBJECT_STR,
                                L"ClientName",  // Use the principal name
                                NULL );

  // Find the fullsic principal name of the server.
  RpcCertGeneratePrincipalName( server_cert, RPC_C_FULL_CERT_CHAIN, 
                                &server_princ_name );

  // Change the client's security: 
  // Increase the authentication level and attach a certificate.
  CoSetProxyBlanket( pSecret, RPC_C_AUTHN_GSS_SCHANNEL,
                     RPC_C_AUTHZ_NONE,
                     server_princ_name, RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
                     RPC_C_IMP_LEVEL_IMPERSONATE, 
                     (RPC_AUTH_IDENTITY_HANDLE *) client_cert,
                     EOAC_NONE );

  cleanup:
  if (server_princ_name != NULL)
    RpcStringFree( &server_princ_name );
  if (client_cert != NULL)
    CertFreeCertificateContext(client_cert);
  if (server_cert != NULL)
    CertFreeCertificateContext(server_cert);
  if (cert_store != NULL)
    CertCloseStore( cert_store, CERT_CLOSE_STORE_CHECK_FLAG );
  if (provider != 0 )
    CryptReleaseContext( provider, 0 );
  if (pSecret != NULL)
    pSecret->Release();
  CoUninitialize();
}

COM 和安全包