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

비밀번호

댓글 작성자
 




[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13608정성태4/26/2024225닷넷: 2249. C# - 부모의 필드/프로퍼티에 대해 서로 다른 자식 클래스 간에 Reflection 접근이 동작할까요?파일 다운로드1
13607정성태4/25/2024222닷넷: 2248. C# - 인터페이스 타입의 다중 포인터를 인자로 갖는 C/C++ 함수 연동
13606정성태4/24/2024253닷넷: 2247. C# - tensorflow 연동 (MNIST 예제)파일 다운로드1
13605정성태4/23/2024563닷넷: 2246. C# - Python.NET을 이용한 파이썬 소스코드 연동파일 다운로드1
13604정성태4/22/2024613오류 유형: 901. Visual Studio - Unable to set the next statement. Set next statement cannot be used in '[Exception]' call stack frames.
13603정성태4/21/2024806닷넷: 2245. C# - IronPython을 이용한 파이썬 소스코드 연동파일 다운로드1
13602정성태4/20/2024858닷넷: 2244. C# - PCM 오디오 데이터를 연속(Streaming) 재생 (Windows Multimedia)파일 다운로드1
13601정성태4/19/2024891닷넷: 2243. C# - PCM 사운드 재생(NAudio)파일 다운로드1
13600정성태4/18/2024912닷넷: 2242. C# - 관리 스레드와 비관리 스레드
13599정성태4/17/2024878닷넷: 2241. C# - WAV 파일의 PCM 사운드 재생(Windows Multimedia)파일 다운로드1
13598정성태4/16/2024915닷넷: 2240. C# - WAV 파일 포맷 + LIST 헤더파일 다운로드2
13597정성태4/15/2024897닷넷: 2239. C# - WAV 파일의 PCM 데이터 생성 및 출력파일 다운로드1
13596정성태4/14/20241077닷넷: 2238. C# - WAV 기본 파일 포맷파일 다운로드1
13595정성태4/13/20241064닷넷: 2237. C# - Audio 장치 열기 (Windows Multimedia, NAudio)파일 다운로드1
13594정성태4/12/20241081닷넷: 2236. C# - Audio 장치 열람 (Windows Multimedia, NAudio)파일 다운로드1
13593정성태4/8/20241089닷넷: 2235. MSBuild - AccelerateBuildsInVisualStudio 옵션
13592정성태4/2/20241226C/C++: 165. CLion으로 만든 Rust Win32 DLL을 C#과 연동
13591정성태4/2/20241201닷넷: 2234. C# - WPF 응용 프로그램에 Blazor App 통합파일 다운로드1
13590정성태3/31/20241081Linux: 70. Python - uwsgi 응용 프로그램이 k8s 환경에서 OOM 발생하는 문제
13589정성태3/29/20241158닷넷: 2233. C# - 프로세스 CPU 사용량을 나타내는 성능 카운터와 Win32 API파일 다운로드1
13588정성태3/28/20241274닷넷: 2232. C# - Unity + 닷넷 App(WinForms/WPF) 간의 Named Pipe 통신 [2]파일 다운로드1
13587정성태3/27/20241358오류 유형: 900. Windows Update 오류 - 8024402C, 80070643
13586정성태3/27/20241532Windows: 263. Windows - 복구 파티션(Recovery Partition) 용량을 늘리는 방법
13585정성태3/26/20241324Windows: 262. PerformanceCounter의 InstanceName에 pid를 추가한 "Process V2"
13584정성태3/26/20241289개발 환경 구성: 708. Unity3D - C# Windows Forms / WPF Application에 통합하는 방법파일 다운로드1
[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...