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)
13331정성태4/27/20233887오류 유형: 856. dockerfile - 구 버전의 .NET Core 이미지 사용 시 apt update 오류
13330정성태4/26/20233529Windows: 247. Win32 C/C++ - CS_GLOBALCLASS 설명
13329정성태4/24/20233733Windows: 246. Win32 C/C++ - 직접 띄운 대화창 템플릿을 위한 Modal 메시지 루프 생성파일 다운로드1
13328정성태4/19/20233403VS.NET IDE: 184. Visual Studio - Fine Code Coverage에서 동작하지 않는 Fake/Shim 테스트
13327정성태4/19/20233809VS.NET IDE: 183. C# - .NET Core/5+ 환경에서 Fakes를 이용한 단위 테스트 방법
13326정성태4/18/20235236.NET Framework: 2109. C# - 닷넷 응용 프로그램에서 SQLite 사용 (System.Data.SQLite) [1]파일 다운로드1
13325정성태4/18/20234527스크립트: 48. 파이썬 - PostgreSQL의 with 문을 사용한 경우 연결 개체 누수
13324정성태4/17/20234360.NET Framework: 2108. C# - Octave의 "save -binary ..."로 생성한 바이너리 파일 분석파일 다운로드1
13323정성태4/16/20234290개발 환경 구성: 677. Octave에서 Excel read/write를 위한 io 패키지 설치
13322정성태4/15/20235067VS.NET IDE: 182. Visual Studio - 32비트로만 빌드된 ActiveX와 작업해야 한다면?
13321정성태4/14/20233894개발 환경 구성: 676. WSL/Linux Octave - Python 스크립트 연동
13320정성태4/13/20233872개발 환경 구성: 675. Windows Octave 8.1.0 - Python 스크립트 연동
13319정성태4/12/20234319개발 환경 구성: 674. WSL 2 환경에서 GNU Octave 설치
13318정성태4/11/20234153개발 환경 구성: 673. JetBrains IDE에서 "Squash Commits..." 메뉴가 비활성화된 경우
13317정성태4/11/20234240오류 유형: 855. WSL 2 Ubuntu 20.04 - error: cannot communicate with server: Post http://localhost/v2/snaps/...
13316정성태4/10/20233567오류 유형: 854. docker-compose 시 "json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)" 오류 발생
13315정성태4/10/20233778Windows: 245. Win32 - 시간 만료를 갖는 컨텍스트 메뉴와 윈도우 메시지의 영역별 정의파일 다운로드1
13314정성태4/9/20233861개발 환경 구성: 672. DosBox를 이용한 Turbo C, Windows 3.1 설치
13313정성태4/9/20233938개발 환경 구성: 671. Hyper-V VM에 Turbo C 2.0 설치 [2]
13312정성태4/8/20233952Windows: 244. Win32 - 시간 만료를 갖는 MessageBox 대화창 구현 (개선된 버전)파일 다운로드1
13311정성태4/7/20234464C/C++: 163. Visual Studio 2022 - DirectShow 예제 컴파일(WAV Dest)
13310정성태4/6/20234064C/C++: 162. Visual Studio - /NODEFAULTLIB 옵션 설정 후 수동으로 추가해야 할 library
13309정성태4/5/20234231.NET Framework: 2107. .NET 6+ FileStream의 구조 변화
13308정성태4/4/20234124스크립트: 47. 파이썬의 time.time() 실숫값을 GoLang / C#에서 사용하는 방법
13307정성태4/4/20233888.NET Framework: 2106. C# - .NET Core/5+ 환경의 Windows Forms 응용 프로그램에서 HINSTANCE 구하는 방법
13306정성태4/3/20233681Windows: 243. Win32 - 윈도우(cbWndExtra) 및 윈도우 클래스(cbClsExtra) 저장소 사용 방법
1  2  3  4  5  6  7  8  9  10  11  [12]  13  14  15  ...