Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
부모글 보이기/감추기
(연관된 글이 2개 있습니다.)
5. CardWriter.csproj와 함께 알아보는 인증서 식별 방법


원래는 "스마트 카드에 기반한 Managed Card - STS 구현"을 이번에 살펴보려고 했는데, 도저히 ^^ 인증서 식별 방법을 모르고서는 오히려 설명이 산만해 질 것 같아서 별도로 이렇게 빼서 설명을 하게 되었습니다.

CardWriter 예제는 "Simple STS.zip"에서 구할 수 있습니다. 이 파일의 압축을 풀면 "CardWriter" 폴더에 "CardWriter.csproj"가 있고 VS.NET 2005에서 열 수가 있습니다. "11.3.1 Managed Card 발행에 대한 Microsoft 예제 실습 (1) - CardWriter"을 실습해 보신 분은 아시겠지만, "CardWriter" 예제는 "Managed Card"를 생성하기 위한 프로그램입니다. 방법은 입력 INI 파일을 받아서 해당 파일에 설정된 값들에 기반해서 확장자가 "CRD"인 "Managed Card"를 생성해 냅니다.

CardWriter 예제에서는 Managed Card를 만들기 위한 방법으로 다음과 같이 4가지 유형을 지원해 주고 있습니다.

  1. SelfIssuedAuth
  2. SmartCard
  3. UserNamePassword
  4. KerberosAuth

1번 방법은 이미"11.3.2 Managed Card 발행에 대한 Microsoft 예제 실습 (2) - STS 구현" 토픽을 통해서 살펴봤었지요. 다시 한번 개략적인 순서를 설명해 보자면,

1. 사용자가 임의로 "개인 카드"를 생성.
2. 그 "개인 카드"의 "유일한 식별자 - PPID" 값을 Managed Card를 발행할 기관에 전송.
3. 카드 발행 기관에서는 해당 PPID 값을 포함한 XML 형식의 Managed Card 내용을 카드 발행 기관이 가진 "인증서"의 개인키로 서명해서 CRD 확장자를 가진 파일로 사용자에게 전달.
4. 사용자는 전달받은 CRD 파일을 자신의 컴퓨터에 Managed Card로 등록.

와 같이 동작하게 됩니다. 비슷하게, "2. SmartCard"에 기반한 Managed Card 발행 방법은 다음과 같은 순서로 이뤄집니다.

1. 사용자는 스마트 카드와 그 카드에 연결된 "개인키가 포함된 인증서"를 클라이언트에 설치된 상태여야 합니다.
2. 사용자 측 "인증서"의 sha1 방식으로 해쉬된 Thumbprint 값을 Managed Card 발행 기관에 전송.
3. 카드 발행 기관은 전달된 Thumbprint 값을 포함한 XML 형식의 Managed Card 내용을 카드 발행 기관이 가진 "인증서"의 개인키로 서명하여 CRD 확장자를 가진 파일로 사용자에게 전달.
4. 사용자는 전달받은 CRD 파일을 자신의 컴퓨터에 Managed Card로 등록.

(3번과 4번 방법은 나중에 시간 나면 또 살펴보도록 하고,) 오늘은 위의 1번과 2번 방법을 적용하는 데 있어 "인증서"와 관계된 부분만을 특별히 집중적으로 알아보도록 하겠습니다.



[Issuer] 값 지정 방법

우선, 알아봐야 할 것이 "카드 발행 기관이 가진 인증서의 개인키로 서명"하도록 하는 부분에서의 인증서 Subject 이름 지정입니다. CardWriter는 INI 파일에 기재된 입력 내용을 기반으로 하기 때문에 "카드 발행 기관이 가진 인증서" 역시 INI 파일에 지정되어져 있어야 합니다. 그 부분은 바로 "[Issuer]" 절입니다. 참고로, "Simple STS.zip"의 압축을 풀면 "SampleCards" 폴더의 하위에 "SelfIssuedAuth" 방식의 예제인 "FabrikamSelfIssued.ini" 파일과 "UserNamePassword" 방식의 예제인 "FabrikamUP.ini", "SmartCard" 방식의 예제인 "FabrikamCertificate.ini" 파일을 찾아볼 수 있습니다. INI 파일의 모든 예제는 다음과 같이 공통적으로 "Issuer" 절을 포함하고 있습니다.

[Issuer]
Name=Fabrikam Auto Group
Address=http://www.fabrikam.com:7000/sample/trust/usernamepassword/sts
MexAddress=https://www.fabrikam.com:7001/sample/trust/usernamepassword/mex
PrivacyPolicy=http://www.fabrikam.com/PrivacyPolicy.xml
; certificate should be either a STORELOCATION/STORE/Subject name
; or 
; c:\path\to\cert.pfx -- in which case you also need a CertificatePassword=
Certificate=LOCALMACHINE/MY/www.fabrikam.com
;CertificatePassword=foo

Issuer는 "Security Token Service"를 지정하는 것입니다. Name은 임의의 이름이고, Address, MexAddress는 Security Token Service와 통신을 하기 위한 주소입니다. PrivacyPolicy 값은 나중에 "Windows CardSpace"에서 제공되는 사용자 인터페이스에 별도로 연결되어 보여지게 됩니다. 자, 이제 중요한 것은 "카드 발행 기관이 가진 인증서의 개인키로 서명"하는 단계에 해당하는 "인증서"를 지정하는 값인 "Certificate" 요소입니다.

이 요소는, 주석처리된 예제에서 확인이 되는 것처럼 "STORELOCATION/STORE/Subject name"의 형식이거나 별도로 pfx 파일로 빠져 있는 파일 경로를 지정해 주어야 합니다. pfx 파일을 지정할 때는 "CertificatePassword"를 함께 지정해서 반드시 암호를 명시해 주어야 합니다. 이에 대해서는 다음의 토픽을 읽으시면 이해가 되실 것입니다.

18.5.1 인증서 관리 (1) - 내보내기/가져오기
; https://www.sysnet.pe.kr/2/0/392

pfx 파일인 경우에는 그렇게 지정이 가능하지만, 문제는 "STORELOCATION/STORE/Subject name"와 같이 지정할때 입니다. 먼저, "STORELOCATION"은 "CurrentUser", "LocalMachine" 같은 값들입니다. 이 값은 .NET BCL에서도 "System.Security.Cryptography.X509Certificates" 네임스페이스에서 enum으로 다음과 같이 정의되어져 있기 때문에 프로그래밍 시에는 이 값을 사용해 주시면 됩니다.

  public enum StoreLocation
  {
    CurrentUser = 1,  // The X.509 certificate store used by the current user.
    LocalMachine = 2, // The X.509 certificate store assigned to the local machine.
  }

다음으로 "STORE" 부분은 "My", "Root" 같은 값을 가집니다. 이 부분 역시 .NET BCL에서는 "System.Security.Cryptography.X509Certificates" 네임스페이스에 enum으로 다음과 같이 정의되어져 있습니다.

  // Specifies the name of the X.509 certificate store to open.
  public enum StoreName
  {
    AddressBook = 1,			// The X.509 certificate store for other users.
    AuthRoot = 2,				// The X.509 certificate store for third-party certificate authorities (CAs).
    CertificateAuthority = 3,	// The X.509 certificate store for intermediate certificate authorities (CAs).
    Disallowed = 4,				// The X.509 certificate store for revoked certificates.
    My = 5,						// The X.509 certificate store for personal certificates.
    Root = 6,					// The X.509 certificate store for trusted root certificate authorities (CAs).
    TrustedPeople = 7,			// The X.509 certificate store for directly trusted people and resources.
    TrustedPublisher = 8,		// The X.509 certificate store for directly trusted publishers.
  }

위의 값과 주석만 보셔도 "Internet Explorer" 또는 "인증서 MMC 관리자"의 어느 부분에 인증서가 위치해 있어야 하는지 아실 것입니다.

마지막으로 "Subject name" 부분을 지정하기 위해서는 해당 인증서에 대한 "속성" 창을 띄워야 합니다. 예를 들어 "Simple STS.zip" 예제에서 제공되는 "www.fabrikam.com" 인증서는 아래와 같은데,

www.fabrikam.com 인증서

이 인증서의 Subject Name 값은 "CN=www.fabrikam.com, O=Fabrikam, L=Redmond, S=Washington, C=US" 문자열 값이 됩니다. 실제로 이 값은 "X509Certificate2" 클래스의 "Subject" 속성값으로 구할 수도 있습니다. 보시면 순서가 "CN="으로 시작하게 되는데요. CardWriter는 Subject Name으로 지정된 경우 다음과 같은 방식으로 해당 인증서 저장소에 설치된 모든 인증서를 열람하면서 "CN=" 이후의 Subject 이름이 같은 것을 기준으로 인증서를 찾습니다. (따라서, 만약 같은 CN 값으로 시작하면서 이후의 값들이 다르다거나 심지어 Thumbprint 값이 틀림에도 불구하고 Subject Name이 같은 경우라면, 첫 번째 인증서를 매핑시키는 오류를 유발할 수도 있습니다.)

X509Certificate2 smartcardcertificate;
foreach (X509Certificate2 xCert in s.Certificates)
{
	// 만약 Subject Name 값이 같은 인증서를 구분해야 한다면,
	// 아래의 xCert.Subject 문자열 비교 코드를 변경해 주어야 합니다.
    if (xCert.Subject.StartsWith("CN=" + certspec[2]))
    {
        smartcardcertificate = xCert;
        break;
    }
}

마지막으로 "[Issuer]"에 대해서 주의점을 설명드린다면, 반드시 여기에 지정되는 인증서는 "개인키"를 가진 인증서여야 한다는 것입니다. 왜냐하면 이 인증서로 "InfoCard"의 원본 내용을 서명해서 CRD 파일로 만들어 주기 때문입니다.




"[Credentials]" 값 지정 방법

"[Issuer]" 절은 모든 형식의 INI 파일에서 나타날 수 있습니다. 그 외에 인증서와 관련되어 값이 나올 수 있는 부분은 "SmartCard"를 기반으로 하는 Managed Card를 발행할 때에 "[Credentials]"절에 지정되는 값이 있습니다.

[Credentials]
; if the Auth type is UserNamePassword the value is the Username 
; if the Auth type is SmartCard the value is the Certificate Path(Localmachine/my/www.fabrikam.com), hash, filename (in which case you may need certificatepassword=)
; if the Auth type is SelfIssuedAut the value is the PPID
;value=currentUser/My/GEPK00000728
value=3ce25c9ef8b2d5d99a227e1ea1a28d7f001a3355
Hint=Insert your smartcard please.

보시는 것처럼, 만약 "Auth Type"의 유형이 "SmartCard"라면 다음과 같이 3가지 방식으로 해당 SmartCard와 연결된 인증서를 지정해 주어야 합니다.

1. "Localmachine/my/www.fabrikam.com"처럼 인증서 저장소와 Subject Name을 기준으로 인증서 경로를 지정
2. hash
3. PFX 파일 경로.

1번은 이미 위에서 "[Issuer]" 절을 다루면서 살펴보았습니다. 2번 hash 방식은 "8.1 WCF에 SSL 적용 (1) - Httpcfg.exe 도구를 이용한 SSL 설정"에서 살펴본 것처럼 "Thumbprint" 값을 지정하는 것입니다. (주의해야 할 것은 반드시 "Thumbprint algorithm" 값이 "sha1" 방식이어야 합니다.) 3번 pfx 파일 경로는 "5.1 인증서 관리 - 내보내기/가져오기" 에서 설명한 "내보내기" 된 pfx 파일의 경로값과 그 과정에서 사용된 암호값을 지정해야 합니다.

그런데, 여기서 잠깐 현실적으로 생각해 볼 필요가 있겠습니다. 이미 말씀드린 것처럼 이 "[Credentials]"절에 기록된 인증서는 "사용자가 가진 스마트 카드"와 연결된 인증서를 지정하기 위해서 존재하는 것입니다. 실제로 "CardWriter" 프로젝트에서는 여기에 지정된 value 값을 이용해서 해당 인증서의 "Thumbprint" 값을 구해서 그 값을 "Managed Card" XML 구조에 넣고 나서 서명을 해서 CRD 파일로 출력해 줍니다.

결국, 사용자가 보유한 스마트 카드에 연결된 인증서의 "Thumbprint" 값을 구하는 것이 목적이기 때문에, 현실적으로 볼 때 카드를 발행해 주는 - CardWriter 프로그램이 구동되는 - 서버 측에서는 사용자의 (공개키만 포함된) 스마트 카드의 인증서를 "인증서 저장소"에 저장하고 있다는 것은 말도 안되는 경우입니다. 심지어 개인키를 포함한 "pfx" 파일과 그걸 풀수 있는 암호까지 가지고 있다는 것은 더더욱 말이 안되는 경우이지요. 즉, 가장 현실성이 있는 경우는, 클라이언트로 하여금 웹 브라우저에서 스마트 카드 인증서를 받아들여서 그 값에서 thumbprint 값을 얻어내거나 고급 사용자를 대상으로 hash 값을 직접 입력하도록 해서 활용할 수 있는 방식일 것입니다.

따라서, 대개의 경우 "[Credentials]"에 지정될 수 있는 값은 hash 유형일 것입니다.

그런데, 이 hash 값에 대해서 좀 더 짚고 넘어가야 겠습니다. 위의 "[Credentials]" 예제에서 지정된 "value=3ce25c9ef8b2d5d99a227e1ea1a28d7f001a3355" 예제값에서처럼 실제로는 "3ce25..."인 Thumbprint 값을 그대로 지정해서는 안됩니다. 이렇게 "Thumbprint" 값의 hex 문자열을 집어넣을 수 있는 경우는 실제로 클라이언트 측 스마트 카드 사용자의 인증서가 서버에 설치된 경우에만 해당이 됩니다. 그런 경우에는 CardWriter 예제는 Thumbprint 값이 "3ce25c9ef8b2d5d99a227e1ea1a28d7f001a3355" 인증서를 찾고 그 다음에 Thumbprint 바이트 배열값을 base64 인코딩시켜서 CRD 파일에 포함하게 됩니다.

만약 서버에 해당 스마트 카드 사용자의 인증서가 설치되어 있지 않은 경우에는 "3ce25c9ef8b2d5d99a227e1ea1a28d7f001a3355"와 같은 Thumbprint Hex 문자열 값을 입력하면 안되고, 직접 해당 바이트들의 base64 인코딩 값을 지정해 주어야 합니다. 예를 들어서, 다음과 같은 Thumbprint 값을 가지고 있는 인증서가 있다고 한다면,

예제 인증서

"Thumbprint" 값의 hex 문자열 값은 "5e827b6425ee3894ecd9a65d84b5b74b958fb3a7"이지만, 실제로 이 값들은 바이트 배열로서 "0x5e, 0x82, 0x7b, 0x64, 0x25, 0xee, 0x38, 0x94, 0xec, 0xd9, 0xa6, 0x5d, 0x84, 0xb5, 0xb7, 0x4b, 0x95, 0x8f, 0xb3, 0xa7" 값인데 이를 base64 인코딩하면 "XoJ7ZCXuOJTs2aZdhLW3S5WPs6c=" 값이 나오게 됩니다. 바로 이 값을 지정해 주어야 하는 것입니다.




[이 토픽에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 6/26/2021]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 61  62  63  64  65  66  67  68  69  70  71  72  [73]  74  75  ...
NoWriterDateCnt.TitleFile(s)
12109정성태1/10/202016562오류 유형: 588. Driver 프로젝트 빌드 오류 - Inf2Cat error -2: "Inf2Cat, signability test failed."
12108정성태1/10/202017397오류 유형: 587. Kernel Driver 시작 시 127(The specified procedure could not be found.) 오류 메시지 발생
12107정성태1/10/202018540.NET Framework: 877. C# - 프로세스의 모든 핸들을 열람 - 두 번째 이야기
12106정성태1/8/202019615VC++: 136. C++ - OSR Driver Loader와 같은 Legacy 커널 드라이버 설치 프로그램 제작 [1]
12105정성태1/8/202018129디버깅 기술: 153. C# - PEB를 조작해 로드된 DLL을 숨기는 방법
12104정성태1/7/202019313DDK: 9. 커널 메모리를 읽고 쓰는 NT Legacy driver와 C# 클라이언트 프로그램 [4]
12103정성태1/7/202022428DDK: 8. Visual Studio 2019 + WDK Legacy Driver 제작- Hello World 예제 [1]파일 다운로드2
12102정성태1/6/202018783디버깅 기술: 152. User 권한(Ring 3)의 프로그램에서 _ETHREAD 주소(및 커널 메모리를 읽을 수 있다면 _EPROCESS 주소) 구하는 방법
12101정성태1/5/202019002.NET Framework: 876. C# - PEB(Process Environment Block)를 통해 로드된 모듈 목록 열람
12100정성태1/3/202016499.NET Framework: 875. .NET 3.5 이하에서 IntPtr.Add 사용
12099정성태1/3/202019321디버깅 기술: 151. Windows 10 - Process Explorer로 확인한 Handle 정보를 windbg에서 조회 [1]
12098정성태1/2/202019084.NET Framework: 874. C# - 커널 구조체의 Offset 값을 하드 코딩하지 않고 사용하는 방법 [3]
12097정성태1/2/202017145디버깅 기술: 150. windbg - Wow64, x86, x64에서의 커널 구조체(예: TEB) 구조체 확인
12096정성태12/30/201919856디버깅 기술: 149. C# - DbgEng.dll을 이용한 간단한 디버거 제작 [1]
12095정성태12/27/201921565VC++: 135. C++ - string_view의 동작 방식
12094정성태12/26/201919284.NET Framework: 873. C# - 코드를 통해 PDB 심벌 파일 다운로드 방법
12093정성태12/26/201918859.NET Framework: 872. C# - 로딩된 Native DLL의 export 함수 목록 출력파일 다운로드1
12092정성태12/25/201917661디버깅 기술: 148. cdb.exe를 이용해 (ntdll.dll 등에 정의된) 커널 구조체 출력하는 방법
12091정성태12/25/201919945디버깅 기술: 147. pdb 파일을 다운로드하기 위한 symchk.exe 실행에 필요한 최소 파일 [1]
12090정성태12/24/201920048.NET Framework: 871. .NET AnyCPU로 빌드된 PE 헤더의 로딩 전/후 차이점 [1]파일 다운로드1
12089정성태12/23/201918959디버깅 기술: 146. gflags와 _CrtIsMemoryBlock을 이용한 Heap 메모리 손상 여부 체크
12088정성태12/23/201917949Linux: 28. Linux - 윈도우의 "Run as different user" 기능을 shell에서 실행하는 방법
12087정성태12/21/201918393디버깅 기술: 145. windbg/sos - Dictionary의 entries 배열 내용을 모두 덤프하는 방법 (do_hashtable.py) [1]
12086정성태12/20/201920888디버깅 기술: 144. windbg - Marshal.FreeHGlobal에서 발생한 덤프 분석 사례
12085정성태12/20/201918852오류 유형: 586. iisreset - The data is invalid. (2147942413, 8007000d) 오류 발생 - 두 번째 이야기 [1]
12084정성태12/19/201919295디버깅 기술: 143. windbg/sos - Hashtable의 buckets 배열 내용을 모두 덤프하는 방법 (do_hashtable.py) [1]
... 61  62  63  64  65  66  67  68  69  70  71  72  [73]  74  75  ...