다음을 통해 공유


SChannel

인증 서비스 식별자가 RPC_C_AUTHN_GSS_SCHANNEL 보안 채널(Schannel) 보안 패키지는 SSL(Secure Sockets Layer) 버전 2.0 및 3.0, TLS(전송 계층 보안) 1.0 및 PCT(Private Communication Technology) 1.0과 같은 공개 키 기반 프로토콜을 지원합니다. TLS 1.0은 1999년 1월 문서 RFC 2246에서 IETF(인터넷 엔지니어링 태스크 포스)에서 발행한 표준화된 약간 수정된 SSL 3.0 버전입니다. TLS가 표준화되었으므로 개발자는 SSL 대신 TLS를 사용하는 것이 좋습니다. PCT는 이전 버전과의 호환성을 위해서만 포함되며 새 개발에는 사용하지 않아야 합니다. Schannel 보안 패키지를 사용하는 경우 DCOM은 클라이언트 및 서버 기능에 따라 최상의 프로토콜을 자동으로 협상합니다.

다음 topics TLS 프로토콜과 DCOM의 작동 방식을 간략하게 설명합니다.

참고

이 섹션의 TLS 프로토콜에 대한 모든 정보는 SSL 및 PCT 프로토콜에도 적용됩니다.

 

TLS를 사용해야 하는 경우

TLS는 서버가 익명 클라이언트에 대한 ID를 증명해야 하는 경우 사용할 수 있는 유일한 보안 옵션입니다. 이는 신용 카드 번호와 같은 중요한 정보의 전송을 보호하는 데 도움이 되므로 전자 상거래에 참여하려는 웹 사이트에 특히 중요합니다. TLS는 전자 상거래 고객이 서버의 ID에 대한 증거를 제공하므로 누구와 거래하고 있는지 확신할 수 있음을 보장합니다. 또한 전자 상거래 서버는 각 고객의 ID를 인증하는 데 관심을 가질 필요가 없는 효율성을 제공합니다.

TLS를 사용하려면 모든 서버가 클라이언트에 대한 ID를 증명해야 합니다. 또한 TLS는 클라이언트가 서버에 대한 ID를 증명하도록 하는 옵션을 제공합니다. 이 상호 인증은 대규모 회사 인트라넷에서 특정 웹 페이지의 액세스를 제한하는 데 유용할 수 있습니다.

TLS는 가장 강력한 인증 수준을 지원하며 기술 혁신을 따라잡기 위해 시간이 지남에 따라 암호화 강도를 높일 수 있는 개방형 아키텍처를 제공합니다. TLS는 전송 중인 데이터에 대해 가장 높은 수준의 보안이 필요한 환경에 가장 적합한 선택입니다.

TLS 작동 방식에 대한 간략한 개요

TLS는 퍼블릭/프라이빗 키 쌍을 사용하여 데이터 암호화를 사용하도록 설정하고 데이터 무결성을 설정하는 PKI(공개 키 인프라)를 기반으로 하며 인증에 X.509 인증서를 사용합니다.

Kerberos v5 프로토콜과 같은 많은 보안 프로토콜은 데이터를 암호화하고 암호 해독하는 단일 키에 의존합니다. 따라서 이러한 프로토콜은 암호화 키의 보안 교환에 따라 달라집니다. Kerberos 프로토콜에서 이 작업은 KDC(키 배포 센터)에서 가져온 티켓을 통해 수행됩니다. 이를 위해서는 Kerberos 프로토콜을 사용하는 모든 사람이 KDC에 등록되어야 하며, 이는 전 세계에서 수백만 명의 고객을 유치하기 위한 전자 상거래 웹 서버에 대한 비현실적인 제한 사항입니다. 따라서 TLS는 쌍의 한 키가 데이터를 암호화할 때 데이터 암호화에 두 개의 키를 사용하는 PKI를 사용하며, 쌍의 다른 키만 암호를 해독할 수 있습니다. 이 디자인의 주요 이점은 암호화 키를 안전하게 교환할 필요 없이 암호화를 수행할 수 있다는 것입니다.

PKI는 키 중 하나가 비공개로 유지되고 등록된 보안 주체만 사용할 수 있는 기술을 사용하며, 다른 키는 누구나 액세스할 수 있도록 공개됩니다. 누군가가 키 쌍의 소유자에게 프라이빗 메시지를 보내려는 경우 공개 키로 메시지를 암호화할 수 있으며 프라이빗 키만 사용하여 메시지의 암호를 해독할 수 있습니다.

키 쌍은 전송되는 데이터의 무결성을 확인하는 데도 사용됩니다. 이를 위해 키 쌍의 소유자는 디지털 서명을 전송하기 전에 데이터에 연결할 수 있습니다. 디지털 서명을 만들려면 데이터의 해시를 계산하고 프라이빗 키로 해시를 암호화해야 합니다. 공개 키를 사용하여 디지털 서명을 해독하는 사람은 누구나 디지털 서명이 프라이빗 키를 소유한 사람만 왔어야 한다고 확신합니다. 또한 받는 사람은 보낸 사람과 동일한 알고리즘을 사용하여 데이터의 해시를 계산할 수 있으며, 계산된 해시가 디지털 서명에서 보낸 해시와 일치하는 경우 수신자는 데이터가 디지털 서명된 후 수정되지 않았다고 확신할 수 있습니다.

대용량 데이터 암호화에 PKI를 사용할 때의 단점 중 하나는 상대적으로 성능이 느리다는 것입니다. 관련된 집약적인 수학으로 인해 키 쌍에 의존하는 비대칭 암호화를 사용하는 데이터의 암호화 및 암호 해독은 단일 키에만 의존하는 대칭 암호화를 사용하는 암호화 및 암호 해독보다 최대 1,000배 느릴 수 있습니다. 따라서 TLS는 디지털 서명을 생성하고 대량 데이터 암호화 및 암호 해독을 위해 클라이언트와 서버 모두에서 사용할 세션별 단일 키를 협상하는 데만 PKI를 사용합니다. TLS는 다양한 단일 키 대칭 암호화를 지원하며 나중에 추가 암호화를 추가할 수 있습니다.

TLS 핸드셰이크 프로토콜에 대한 자세한 내용은 TLS 핸드셰이크 프로토콜을 참조하세요.

TLS 프로토콜 뒤에 있는 암호화에 대한 자세한 내용은 Cryptography Essentials를 참조하세요.

X.509 인증서

PKI에서 처리해야 하는 중요한 문제는 사용 중인 공개 키의 신뢰성을 신뢰하는 기능입니다. 비즈니스를 수행하려는 회사에 발급된 공개 키를 사용하는 경우 신용 카드 번호를 검색하려는 도둑이 아니라 키가 실제로 회사에 속해 있는지 확인하려고 합니다.

키 쌍을 보유하는 보안 주체의 ID를 보장하기 위해 보안 주체는 CA(인증 기관)에서 X.509 인증서를 발급합니다. 이 인증서에는 보안 주체를 식별하고, 보안 주체의 공개 키를 포함하며, CA에서 디지털 서명하는 정보가 포함되어 있습니다. 이 디지털 서명은 CA가 인증서에 포함된 공개 키가 인증서로 식별된 보안 주체에 속한다고 믿음을 나타냅니다.

그리고 CA를 어떻게 신뢰합니까? CA 자체는 상위 수준 CA에서 서명한 X.509 인증서를 보유하고 있기 때문입니다. 이 인증서 서명 체인은 자체 인증서에 서명하는 CA인 루트 CA에 도달할 때까지 계속됩니다. 인증서의 루트 CA 무결성을 신뢰하는 경우 인증서 자체의 신뢰성을 신뢰할 수 있어야 합니다. 따라서 신뢰할 수 있는 루트 CA를 선택하는 것은 시스템 관리자에게 중요한 의무입니다.

클라이언트 인증서

보안 전송 계층 프로토콜이 처음 등장했을 때 주요 목적은 클라이언트가 인증 서버에 연결되고 있음을 보장하고 전송 중에 데이터의 개인 정보를 보호하는 것이었습니다. 그러나 SSL 3.0 및 TLS 1.0에는 프로토콜 핸드셰이크 중에 클라이언트 인증서 전송에 대한 지원도 포함됩니다. 이 선택적 기능을 사용하면 클라이언트와 서버의 상호 인증을 사용할 수 있습니다.

클라이언트 인증서를 사용할지 여부는 애플리케이션의 컨텍스트에서 결정해야 합니다. 기본 요구 사항이 서버를 인증하는 경우 클라이언트 인증서는 필요하지 않습니다. 그러나 클라이언트 인증이 필수적인 경우 애플리케이션 내에서 사용자 지정 인증에 의존하지 않고 클라이언트의 인증서를 사용할 수 있습니다. 사용자에게 Single Sign-On 시나리오를 제공하기 때문에 클라이언트 인증서를 사용하는 것이 사용자 지정 인증보다 더 좋습니다.

COM에서 TLS 사용

TLS는 가장(RPC_C_IMP_LEVEL_IMPERSONATE) 수준의 가장만 지원합니다. COM이 프록시에서 TLS를 인증 서비스로 협상하는 경우 COM은 프로세스 기본값에 관계없이 가장 수준을 가장하도록 설정합니다. TLS에서 가장이 제대로 작동하려면 클라이언트가 서버에 X.509 인증서를 제공해야 하며 서버에 해당 인증서가 서버의 특정 사용자 계정에 매핑되어 있어야 합니다. 자세한 내용은 인증서를 사용자 계정에 매핑하는 단계별 가이드를 참조하세요.

TLS는 은폐를 지원하지 않습니다. CoInitializeSecurity 또는 IClientSecurity::SetBlanket 호출에 은폐 플래그 및 TLS가 지정된 경우 E_INVALIDARG 반환됩니다.

TLS는 인증 수준이 없음으로 설정된 상태에서 작동하지 않습니다. 클라이언트와 서버 간의 핸드셰이크는 각각에 의해 설정된 인증 수준을 검사하고 연결에 대해 더 높은 보안 설정을 선택합니다.

CoInitializeSecurityCoSetProxyBlanket을 호출하여 TLS에 대한 보안 매개 변수를 설정할 수 있습니다. 다음 섹션에서는 이러한 호출과 관련된 뉘앙스를 설명합니다.

서버에서 보안 담요를 설정하는 방법

서버에서 TLS를 사용하려는 경우 CoInitializeSecurityasAuthSvc 매개 변수에서 Schannel(RPC_C_AUTHN_GSS_SCHANNEL)을 인증 서비스로 지정해야 합니다. 클라이언트가 덜 안전한 인증 서비스를 사용하여 서버에 연결하지 못하도록 하려면 서버는 CoInitializeSecurity를 호출할 때 Schannel만 인증 서비스로 지정해야 합니다. 서버가 CoInitializeSecurity를 호출한 후에는 보안 담요를 변경할 수 없습니다.

TLS를 사용하려면 서버에서 CoInitializeSecurity를 호출할 때 다음 매개 변수를 지정해야 합니다.

  • pVoidIAccessControl 개체에 대한 포인터이거나 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 은 서버의 X.509 인증서를 나타내는 OLECHAR에 대한 포인터로 캐스팅된 CERT_CONTEXT 대한 포인터여야 합니다.
  • dwAuthnLevel 은 연결 성공에 대해 클라이언트에서 허용되는 최소 인증 수준을 나타냅니다. RPC_C_AUTHN_LEVEL_NONE 수 없습니다.
  • dwCapabilities 에는 EOAC_APPID 플래그가 설정되어 있지 않아야 합니다. pVoidIAccessControl 개체를 가리키는 경우 EOAC_ACCESS_CONTROL 플래그를 설정해야 합니다. pVoid가 SECURITY_DESCRIPTOR 가리키는 경우 설정하면 안 됩니다. 설정할 수 있는 다른 플래그는 CoInitializeSecurity를 참조하세요.

CoInitializeSecurity 사용에 대한 자세한 내용은 CoInitializeSecurity를 사용하여 Processwide 보안 설정을 참조하세요.

클라이언트가 보안 담요를 설정하는 방법

클라이언트가 TLS를 사용하려는 경우 CoInitializeSecuritypAuthList 매개 변수에 있는 인증 서비스 목록에서 Schannel(RPC_C_AUTHN_GSS_SCHANNEL)을 지정해야 합니다. CoInitializeSecurity가 호출될 때 Schannel을 가능한 인증 서비스로 지정하지 않으면 나중에 인증 서비스로 Schannel을 지정하려고 하면 CoSetProxyBlanket(또는 IClientSecurity::SetBlanket)에 대한 호출이 실패합니다.

클라이언트가 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 는 클라이언트의 X.509 인증서를 나타내는 void에 대한 포인터로 캐스팅된 CERT_CONTEXT 대한 포인터입니다. 클라이언트에 인증서가 없거나 인증서를 서버에 표시하지 않으려는 경우 pAuthInfoNULL 이어야 하며 서버와 익명 연결을 시도합니다.
  • dwCapabilities 는 추가 클라이언트 기능을 나타내는 플래그 집합입니다. 설정해야 하는 플래그에 대한 자세한 내용은 CoInitializeSecurity 를 참조하세요.

CoInitializeSecurity 사용에 대한 자세한 내용은 CoInitializeSecurity를 사용하여 Processwide 보안 설정을 참조하세요.

클라이언트가 보안 담요를 변경하는 방법

클라이언트가 TLS를 사용하되 CoInitializeSecurity를 호출한 후 보안 담요를 변경하려는 경우 CoInitializeSecurity 호출에 사용된 매개 변수와 유사한 매개 변수를 사용하여 CoSetProxyBlanket 또는 IClientSecurity::SetBlanket을 호출해야 합니다.

  • pServerPrincName 은 서버의 보안 주체 이름을 msstd 또는 fullsic 형식으로 나타냅니다. 이러한 형식에 대한 자세한 내용은 보안 주체 이름을 참조하세요. 클라이언트에 서버의 X.509 인증서가 있는 경우 RpcCertGeneratePrincipalName을 호출하여 보안 주체 이름을 찾을 수 있습니다.
  • pAuthInfo클라이언트의 X.509 인증서를 나타내는 RPC_AUTH_IDENTITY_HANDLE 대한 포인터로 캐스팅되는 CERT_CONTEXT 대한 포인터입니다. 클라이언트에 인증서가 없거나 인증서를 서버에 표시하지 않으려는 경우 pAuthInfoNULL 이어야 하며 서버와 익명 연결을 시도합니다.
  • 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 및 보안 패키지