Archive for the ‘Security’ Category.

해킹 추적 경과

dgkim.net 이야기입니다.
(사실, 자랑스럽게 얘기할 것은 아니지만, dgkim은 자랑스럽지 않은 것도 자랑하는지라, 글을 써 봅니다. 하도 당황당해서,)

10월 1일.
잘 쓰고 있던 hulk 서버가 장애 상황에 들어갑니다.
가장 먼저 인지한 순간은, 서버에 뭐때문에(?) ssh 접속을 하려는데 접속이 안 되는 것입니다.
그래서, 내가 뭐 password 틀린적이 있나?, 뭐 가벼운 오류가 있나?
( 근래에, 서버에 자주 접속하면서 작업하다 보니, … 서버를 비우고 서비스를 이전하는 등 )
가벼운 문제겠거니 하고, ….
엇? 웹 페이지를 띄웠다? 500에러가 난다? 헐. 심각하네….

서버로 달려가서(사실은 내 방 바깥에 바로..)
모니터와 키보드를 연결합니다. .. 늦은시간 사람들은 자고 있고, 잠이 확 달아날 만큼, 서버를 고쳐야겠다는 생각으로…

듀얼 Video에 USB도 잘 안 쓰고, 하다 보니, 연결해도 화면이 안 뜹니다.
어쩔 수 없다. 재부팅이다. RESET.
console로 로그인 합니다.
tty에 로그인 합니다. (물론 여기까지는 아무 문제가 없지요.)
root로 가야지? sudo 합니다. … 음? 뭔가 메시지가 뜨네(일반 사용자 sudo 메시지) … 뭐지?
사용자, 그룹, 권한 확인하다가, ….
으악!!! 뭐여, 왜 파일들의 권한이 www-data:www-data 인 것 이지?

악!!
해킹인가? 서버 하드웨어 장애인가? …
한 잔 한 상태고, 요즘 내 바이오 리듬이 안 좋구나, … 자다가 콱,, … 하면서, 걍 자자 …

다음날, …
네트워크를 복구하자, hulk 서버가 원래 home gateway였으나, 공유기로 대체 합니다.
서버를 제 정신으로 둘러봅니다. (몇 시간 동안)

음, 파일시스템의 전체(99%)가 www-data:www-data 권한이네?
음, last, .bash_history, wtmp, btmp, syslog 등등 포렌직이란 것을 수행합니다.

그리고, 대망의 요즘 대세(?) 해킹 기법(?)인 웹 쉘 해킹인가? 버그(?) 인가? 고민하면서,
다시, 웹 액세스 로그를 차분히 쳐다 봅니다.
다른 로그들에서 에러가 나온 시점을 통해, 해킹 시점은 98% 추정했는데,
웹 액세스 로그에서는, 딱, 그 시점에 걸리는 것은 없습니다.

그리고 깨름칙 한 것은, 로그 파일의 뒷 부분이 ????로 짤린(?) 것…

결국, 버그인지, 웹쉘인지, 다른 초고수 해킹인지 밝히지 못하고, ….

우선, … 데이터나 이전하자 싶어서, 몇 일간 데이터를 클라우드와 서버 이전을 통해 이전 합니다.

그리고, 서버는 , … 해킹일 것이다!! 가정을 하고, 꺼둡니다.

그리고, 몇 일 뒤, … 서버를 정리하다가, 드디어 찾아내는데, …

startssl 이제, 완전히 맛이 갔나?

dgkim.net은 startssl을 사용하고 있었습니다.

그리고, 사고가 있었지요.

Distrusting New WoSign and StartCom Certificates

그 후, 제 사이트는 SSL이 좀 바뀌었습니다. ( comodo, letsencrypt, startssl 혼용 … )

그러다가, 오늘 startssl에서 뭔가 조치를 취했을까? 기대를 하며,

로그인을 시도해 보았습니다.
그러나, … 두둥, … 로그인이 안 됩니다.
startssl은 원래 client certificate로 로그인을 해왔고, 지금 제 인증서도 유효기간이 남아 있으나,
인증서 선택창이 뜨지 않았습니다. 그래서, verification code 받는 과정을 거쳐 봤으나, …
인증서를 새로 만든다는둥, …
그리고, 로그인 화면에 아래와 같은 충격적인 이미지가 있었습니다.

‘StartCom / StartSSL is supported by: [IE] [Edge] [Android] [Windows]’
참조. https://www.startcomca.com/auth

결론은, 사고 전으로 복구가 되지 않은 상태, …

startssl 가격이나 정책 그리고 상품이 좋아서, 계속 사용하고 싶었으나, …
letsencrypt로 이전을 해야하나 고민을 하고 있습니다.
( client certificate나 application signing certificate도 발급이 되려나 모르겠네 … )

앞으로도 당분간은 www는 제대로 복구하지 못하고, 가야 하나, …

ps. 2017/07/01 결국, 오늘 www 서버도 letsencrypt로 바꾸고야 만다…

OCSP 검증해보기

오늘 StartSSL에서 인증서를 발급 받았습니다.

그런데, firefox에서 접근하니 OCSP 검증이 잘 안되는지 에러페이지가 아래와 같이 뜨는 것입니다.


보안 연결 실패

www.dgkim.net에 접속하는 중에 오류가 발생했습니다.

OCSP 서버가 인증서에 대한 상태를 유지하고 있지 않습니다.

(오류 코드: sec_error_ocsp_unknown_cert)

  • 받은 데이터의 내용 사실 검증을 할 수 없기 때문에 보려고 시도하신 페이지를 보여드릴 수 없습니다.
  • 웹 사이트 관리자에게 현재 문제를 알려 주시거나, 다른 방법으로 도움말 메뉴의 웹 사이트 문제 보고를 이용해 주시기 바랍니다.

그래서, ocsp 검증을 해보기로 했습니다. [1]

$ openssl ocsp \
> -issuer level1.crt \
> -url http://ocsp.startssl.com/sub/class1/server/ca \
> -no_nonce \
> -cert level0.crt
Error querying OCSP responsder
17384:error:27075072:OCSP routines:PARSE_HTTP_LINE1:server response error:/SourceCache/OpenSSL098/OpenSSL098-50/src/crypto/ocsp/ocsp_ht.c:224:Code=400,Reason=Bad Request
$

level1.crt : intermediate CA인증서
level0.crt : 검증해볼 서버인증서
url : 서버인증서에 ocsp url이 있습니다.

서버가 400 bad request라고 하네요. 잘되는 kldp사이트 인증서로 해도 에러가 나는 것입니다.
startssl openssl ocsp 확인해 보니, openssl과 startssl 호환 문제가 있는 것이 확인됩니다. [2]
openssl이 Host헤더를 보내지 않아서, akamai를 쓰는 startssl에서 안 되는 것입니다.

tcpdump를 떠보니 http요청이지만, post내용이 바이너리입니다.
즉, 간단한 telnet으로는 할 수 없다.

openssl에서는 아래 방법으로 ocsp request를 파일로 떨굴수 있습니다.

$ openssl ocsp \
> -issuer level1.crt \
> -url http://ocsp.startssl.com/sub/class1/server/ca \
> -no_nonce \
> -cert level0.crt \
> -reqout req.der

그러면, 이제 위에서 떨군 파일을 http post 해봅니다. telnet으로는 안되고, curl을 사용합니다.

$ curl \
> --header "Host: ocsp.startssl.com" \
> --header "Content-Type: application/ocsp-request" \
> http://ocsp.startssl.com/sub/class1/server/ca \
> -v \
> --upload-file req.der \
> --request POST > response.der

host 헤더를 넣어주었습니다.
content-type을 tcpdump에서 확인했던 것으로 넣었습니다.
upload-file을 통해서 binary 내용을 올릴 수 있습니다.
request에서 POST를 넣어준 것은 upload-file이 PUT을 사용하기 때문입니다.

이제, ocsp응답을 response.der 파일로 받았습니다.

다시 한번 ocsp명령으로 response를 까봅니다. (물론 바이너리 파일입니다.)

$ openssl ocsp \
> -respin response.der \
> -CAfile cabundle.pem \
> -text
OCSP Response Data:
    OCSP Response Status: successful (0x0)
    Response Type: Basic OCSP Response
    Version: 1 (0x0)
    Responder Id: C = IL, O = StartCom Ltd. (Start Commercial Limited), CN = StartCom Class 1 Server OCSP Signer
    Produced At: Nov 18 00:34:46 2013 GMT
    Responses:
    Certificate ID:
      Hash Algorithm: sha1
      Issuer Name Hash: 6568874F40750F016A3475625E1F5C93E5A26D58
      Issuer Key Hash: EB4234D098B0AB9FF41B6B08F7CC642EEF0E2C45
      Serial Number: 0CFD8F
    Cert Status: unknown
    This Update: Nov 18 00:34:46 2013 GMT
    Next Update: Nov 20 00:34:46 2013 GMT

응답을 받았습니다. 헛, 그런데 cert status가 unknown이네요?

  1. http://backreference.org/2010/05/09/ocsp-verification-with-openssl/
  2. https://forum.startcom.org/viewtopic.php?f=15&t=2661

비밀번호의 진화

비밀번호의 진화과정을 한번 정리해 봅니다.

저장형태

비밀번호를 시스템에 저장하는 형태의 변천을 알아봅니다.

평문 저장

근래에도 아직 소규모 조직에서는 사용하는 방법입니다.
RDBMS에 아이디, 비밀번호 컬럼을 두거나, 일반 텍스트 파일에 아이디, 비밀번호를 저장하는 형태입니다.

이 경우는 보안 대책으로는 해당 DB나 비밀번호 파일에 대한 접근 권한을 제어하는 정도로만 관리하였습니다.

crypt 혹은 PBE기법

오래된 버전의 UNIX에서는 현재도 볼 수 있습니다.
UNIX에서는 기본적으로 crypt라는 비밀번호 암호화 함수를 제공하였습니다. 참고 Crypt_(UNIX)
crypt함수는 비밀번호의 길이가 8자리로 제한이 있으며,
passwd파일에 저장된 경우, 파일이 유출되면 쉽게 패스워드를 찾아낼 수 있습니다.
물론, passwd파일은 OS에서 일반 사용자가 내용을 볼 수 있게 되어 있습니다.
( 추후, shadow파일로 비밀번호를 따로 저장하는 강화 방법이 나왔습니다. )
간단히, a-z0-9{8}조합으로 짧은 시간에 알아낼 수 있겠습니다. 참고 brute force 공격
물론, 영대소문자, 특수문자를 조합하면 조금 더 걸리긴 하겠지만,
8자리 이하에서는 요즘 시스템으로 얼마 안걸립니다.

다른 형태로 개발자가 간단한 대칭키 암호화 기법을 쓰는 경우도 있습니다.
대칭키를 통해 암호화를 하면, 저장된 비밀번호는 사람이 알아보기는 어렵지만,
암호화에 사용되는 대칭키가 노출되면 마찬가지로 쉽게 복호화가 가능할 것입니다.

비밀번호를 저장할 때, 쉽게 알아낼 수 없는 방법이 도입되었습니다.
하지만, 컴퓨터의 연산능력 향상에 따라 지금은 사용하지말아야할 방법입니다.

MD5

이제, 비밀번호를 암호화할 때, 단방향 함수 해시 방식을 쓰기 시작합니다.
crypt에 비하여 향상된 보안성을 가집니다.
사용자가 입력한 비밀번호를 단방향 함수를 사용해 나온 결과값을 저장합니다.
사용자가 입력한 비밀번호를 일정 길이로 맞게 자른후(crypt같이 8자리 이후를 무시하는 형태가 아님),
그 값을 단방향 함수를 써서 다시 암호화를 합니다.
그 결과 값은 의미있는 길이를 가집니다.

메시지 요약과정과 단방향 함수의 실행에 따른 연산 증가로 crypt 시절과 같은
brute force형태의 비밀번호 찾기는 어려워 집니다.

하지만, 마찬가지로 노출되면 현재 연산능력으로 비밀번호를 찾을 수 있으며,
해시값을 찾는데는, brute force에서 사용하던 무작위 입력값에 대한 해시를 미리 쌓아두고,
비밀번호 찾기에서는 해당 해시를 비교하여 역으로 비밀번호를 찾을 수 있습니다.

SHA1, SHA2

MD5가 현대 연산능력으로 쉽게 생성 가능하자,
좀더 길이가 긴 값(물론 알고리즘도 강화했겠지요?)을 가지는 SHA1이 나옵니다.
( MD5의 길이나, SHA1, SHA2의 길이나 산술적 안전성은 따로 찾아 보세요 )
현재 나오는 많은 소프트웨어 들이 SHA1을 지원하고 있습니다.
SSL에 사용되는 인증서의 경우도 SHA1이 대부분입니다.
올해 내년을 기하여 SHA1이 저물고 SHA2로 넘어가려고 합니다.
SHA2 알고리즘의 경우, 윈도우의 경우 XP는 서비스팩3에서 탑재되었습니다.
Oracle Application Server 10g의 경우, SHA2인증서를 지원하지 않습니다.

이제 많은 사람들이 개발할 때, SHA1 정도는 기본적으로 사용하기 시작했습니다.
일부에서 SHA2를 사용하는 곳도 있습니다.
SHA2는 512bit도 있네요.

그리고, 앞으로는 SHA3도 개발하고 있나 봅니다.

SHA1, SHA2(보편적인 256이라면)도 MD5시절의 공격에서는 자유롭지 못합니다.

이제는 저장방식보다는 사용자가 실제 사용하는 비밀번호를 복잡하게 사용할 때가 되었습니다.
8자리이상으로 쓰고, 문자조합을 좀더 복잡하게 하면,
해시DB에서 발견될 확률이 좀더 낮아지겠지요?

SSHA(Salted SHA1)

해시값을 비밀번호로 저장하는데, 무작정 긴출력값을 가지는 것으로 가는 것보다,
알고리즘은 SHA1정도로 하되, 해시DB에는 쉽게 노출될 수 없는 방법을 강구하였습니다.

Salted SHA1인데, 해시를 치기 전에 소금을 뿌린다?
간단하게 생각하면, 사용자가 입력한 비밀번호에 시스템에서 임의의 값을 추가로 넣어버리는 것입니다.
그러면, 사용자가 ‘hackernono’같은 비밀번호를 사용하면,
SHA1에서는 4ee0d16a918d8f95f5b2027d38c57c9c76094a51 값인데,
hackernono에 salt ‘dgkim’을 넣으면 아래와 같은 값이 나오지요. ( dgkimhackernono )
‘f6b19f964a78bb35ddf12b72c3c8e20ffeeebbb3’
이 값과 ‘dgkim’을 같이 저장하면, 향후 비밀번호 검증시에는 검증이 가능하지만,
위 값으로는 원래 비밀번호인 hackernono를 찾을 수 없게 됩니다.

salt값을 복잡하고 어느 정도 길이를 가지면, 해시DB에 있을 확률을 확 줄일 수 있겠습니다.

OpenLDAP에서 비밀번호를 저장할 때, SSHA를 사용하고 있습니다.

여기까지가 제가 알고 있는 비밀번호의 진화과정입니다.

인증의 수행

사용자가 입력한 아이디 비밀번호를 확인하는 과정

비밀번호 평문 비교

가장 단순한 형태로, 비밀번호가 평문으로 저장된 경우, SQL에서 아이디 비밀번호를 WHERE조건으로 비교합니다.

비밀번호 해시값 비교

비밀번호 해시 연산을 프로그램에서 수행하는 형태입니다.
개발한 프로그램에서 해시 알고리즘을 정의하고, 인증 및 관리를 하는 형태입니다.
현재 많은 프로그램에서 이런 방식을 사용하고 있습니다.

LDAP Bind 형태

아이디 비밀번호를 사용자가 개발한 프로그램에서 수행하는 것이 아닌,
인증 서버를 통한 방식입니다.
Microsoft AD가 대표적인 형태입니다.
AD는 사용자 비밀번호 속성의 값을 LDAP인터페이스를 통해 제공하지 않습니다.
그러므로, 비밀번호가 저장된 형태(해시인지 Salt는 있는지)를 AD에서 관리하고,
프로그램에서는 알 수 없게 됩니다.
아이디 비밀번호 인증 부분을 인증서버로 위임할 수 있습니다.

아이디 비밀번호 확인은 LDAP에 두고, LDAP에 인증주체를 Bind하는 방법이 가장 이상적이라 생각합니다.

인증 수단 다양화

kerberos SSO 등

기본적으로 사용자 인증을 위해서는 해당 시스템에 아이디 비밀번호를 관리하는 것이 기초적입니다.
이에서 나아가, 인증정보를 중앙 관리를 하고, 인증 시스템을 통한 인증을 하는 방식입니다.
이럴 경우, 중앙 집중된 인증 서버를 안전하게 관리하고, 정책을 일원화 하며,
개별 시스템의 인증이 쉽게 침입당하지 않도록 합니다.

kerberos는 많이 들어보았는데, 운이 없어서 아직 구현해 보지는 못했습니다.

SSO는 꼭 필요한 것 같기도 한데, 솔루션들에서는 SSO를 아직 지원하지 않고 있습니다.

NPKI

국내에서는 NPKI라는 것을 만들어, 국민에게 개인 인증서를 발급하고,
시스템에서 그 개인 인증서를 통해 인증할 수 있도록 하였습니다.

국가에서 개인에게 부여한 인증서이므로, 내부 통제에는 적절하지 않고,
주민등록번호 시스템과 긴밀하게 연결되어 있고,
그 외 다른 시스템적 연계가 어려우며,
인증기관 특징적인 인증 체계를 가질 수 없게 되어 있습니다.

또한, OS, HTTPS, SMIME등 인프라에 적용할 수 없는 것이 한계입니다.
( 물론, 플러그인 프로그램으로 비슷한 흉내를 내긴합니다. )

ssh-key, SSL Client Cert

원래 UNIX에서는 rsh, telnet등의 명령이 있었으며,
하나의 시스템에서 다른 시스템으로 접근을 하게 되면,
시스템 식별과 사용자 식별로 인증을 수행하여 접근을 허가하였습니다.

rsh가 보편적이지 않고, telnet을 사용할 때는
해당 시스템의 아이디 비밀번호가 인증 수단이며,
rsh는 시스템과 아이디만으로 식별 인증하여 사용할 수 있습니다.

ssh는 rsh에 보안성을 강화하고 확장한 프로그램이며,
ssh에서는 ssh-keygen을 통해서 비밀번호를 대신하여 개인키를 사용할 수 있습니다.

https에서도 역시 개인인증서를 통해 인증을 할 수 있습니다.

하지만, 윈도우에서는 아직도 비밀번호를 대체하는 수단이 기본 제공되지 않는 것 같습니다.
( AWS에서 linux는 ssh-key가 기본인데, 윈도우는 비밀번호를 줍니다. )

비밀번호를 넘어

개인의 비밀번호 관리 방법 개선

brute force공격이나, 해시DB를 통한 비밀번호 유추를 방지하기 위하여,
개인에게는 아래와 같은 개선 사항이 떨어졌습니다.
1. 조금더 긴 비밀번호 사용( 4자리 -> 6자리 -> 8자리 -> 이상 )
2. 복잡한 문자 조합 사용( 숫자 -> 영문숫자 -> 영문숫자특수문자 -> 영대소문자숫자특수문자 )

일부 기관에서는 영대소문자숫자 조합을 강제하는 경우도 있지요.

현 시점에서는 영문 숫자 특수문자 조합으로 8자리 정도면 기본적인 안전성을 가지지 않겠나 생각합니다.

또한, 주기적인 변경을 요구하기도 하지요.
하지만, 비밀번호 정책의 경우는 특정 시스템에서만 가능한 정책보다는,
전체 시스템에 일괄적으로 적용할 수 있는 정책이 우선이라 생각합니다.

인증서 비밀번호도 주기적으로 변경하는 것이 좋긴한데, 잘 바꾸지 않지요.

비밀번호의 변경은 가능하면, 개인의 판단에 맡겨주세요.

보안카드

일반적으로 비밀번호라고하면, 로그인시 사용하는 비밀번호를 얘기하는데,
보안성을 강화한다면, 로그인 비밀번호 뿐 아니라, 추가 보안이 필요한 경우 다른 비밀번호를 추가로 입력받던가,
은행과 같이 ‘이체’와 같은 보안이 더 필요한 작업에
‘보안카드’와 같은 추가 수단을 사용할 수 있습니다.
전자결재시스템의 경우, ‘결재 비밀번호’를 따로 사용하기도 합니다.

로그인 비밀번호를 통해 한번 인증되면 끝이 아니라,
보안이 필요한 부분에서는 비밀번호를 한번 더 넣게 하던가,
추가적인 보안 요소를 고려합시다.

OTP

현재 가장 안전하다고 생각하는 인증 수단입니다.
일회용 비밀번호.
기술적으로 완전한 보안은 없겠지만, 아직까지는 (보편적인)’비밀번호’보다는 안전해 보입니다.

PKCS12 인증서를 Java keystore에 넣기.

PKCS12 인증서를 Java keystore에 넣기.

JDK1.5까지는 제공되지 않았던 기능으로 보이는데, JDK1.6에서는 keytool 명령으로 pkcs12인증서(*.p12)를 java keystore에 넣는 것이 가능하도록 바뀌었습니다.

keytool \
-importkeystore \
-srckeystore cert.p12 \
-destkeystore keystore.jks \
-srcstoretype pkcs12 \
-deststoretype jks \
-srcalias mycert \
-destalias mycert

브라우저에서 제공하는 기능(javascript)으로 전자서명하기.

브라우저에서 제공하는 기능(javascript)으로 전자서명하기.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" dir="ltr" lang="ko-KR">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <title>전자서명 테스트</title>
  <script language="javascript" type="text/javascript" src="js/sign.js"></script>
</head>
<body>
  <h2>전자서명 테스트</h2>
  <form name="form0" action="#">
    서명을 위한 원문<br />
    <textarea id="plain" type="text" name="plain"></textarea><br />
    <input type="button" onclick="document.getElementById('signed_msg').value = signDigest(document.getElementById('plain').value);" value="전자서명" /><br />
    <hr />
    전자서명문<br />
    <textarea id="signed_msg"></textarea>
  </form>
</body>
</html>

위에 포함된. sign.js 파일

function signDigest(text) {
    if ( window.event ) {
        window.event.cancelBubble = true;
    }

    var dest = sign(text); //TODO
    //alert(dest);
    return dest;
}

// CAPICOM constants
var CAPICOM_STORE_OPEN_READ_ONLY = 0;
var CAPICOM_CURRENT_USER_STORE = 2;
var CAPICOM_CERTIFICATE_FIND_SHA1_HASH = 0;
var CAPICOM_CERTIFICATE_FIND_EXTENDED_PROPERTY = 6;
var CAPICOM_CERTIFICATE_FIND_TIME_VALID = 9;
var CAPICOM_CERTIFICATE_FIND_KEY_USAGE = 12;
var CAPICOM_DIGITAL_SIGNATURE_KEY_USAGE = 0x00000080;
var CAPICOM_AUTHENTICATED_ATTRIBUTE_SIGNING_TIME = 0;
var CAPICOM_INFO_SUBJECT_SIMPLE_NAME = 0;
var CAPICOM_ENCODE_BASE64 = 0;
var CAPICOM_E_CANCELLED = -2138568446;
var CERT_KEY_SPEC_PROP_ID = 6;

function IsCAPICOMInstalled() {
    if ( typeof(oCAPICOM) == 'object' ) {
        if( ( oCAPICOM.object != null ) ) {
            // We found CAPICOM!
            return true;
        }
    }
}

function FindCertificateByHash() {
    try {
        // instantiate the CAPICOM objects
        var MyStore = new ActiveXObject('CAPICOM.Store');
        // open the current users personal certificate store
        MyStore.Open(CAPICOM_CURRENT_USER_STORE, 'My', CAPICOM_STORE_OPEN_READ_ONLY);

        // find all of the certificates that have the specified hash
        var FilteredCertificates = MyStore.Certificates.Find(CAPICOM_CERTIFICATE_FIND_SHA1_HASH, strUserCertigicateThumbprint);

        var Signer = new ActiveXObject('CAPICOM.Signer');
        Signer.Certificate = FilteredCertificates.Item(1);
        return Signer;

        // Clean Up
        MyStore = null;
        FilteredCertificates = null;
    } catch ( e ) {
        if (e.number != CAPICOM_E_CANCELLED) {
            return new ActiveXObject('CAPICOM.Signer');
        }
    }
}

function sign(src) {
    if ( window.crypto && window.crypto.signText ) {
        return sign_NS(src);
    }

    return sign_IE(src);
}

function sign_NS(src) {
    var s = crypto.signText(src, 'ask' );
    return s;
}

function sign_IE(src) {
    try {
        // instantiate the CAPICOM objects
        var SignedData = new ActiveXObject('CAPICOM.SignedData');
        var TimeAttribute = new ActiveXObject('CAPICOM.Attribute');

        // Set the data that we want to sign
        SignedData.Content = src;
        var Signer = FindCertificateByHash();

        // Set the time in which we are applying the signature
        var Today = new Date();
        TimeAttribute.Name = CAPICOM_AUTHENTICATED_ATTRIBUTE_SIGNING_TIME;
        TimeAttribute.Value = Today.getVarDate();
        Today = null;
        Signer.AuthenticatedAttributes.Add(TimeAttribute);

        // Do the Sign operation
        var szSignature = SignedData.Sign(Signer, true, CAPICOM_ENCODE_BASE64);
        return szSignature;
    } catch ( e ) {
        if (e.number != CAPICOM_E_CANCELLED) {
            alert('An error occurred when attempting to sign the content, the errot was: ' + e.description);
        }
    }
    return '';
}

위 함수를 사용하면 PKCS#7 으로 인코딩된 전자서명문을 생성할 수 있으며, 서버에서는 bouncy castle라이브러리 등을 통해서 검증을 할 수 있습니다.

crypto.signText 문법

Syntax
crypto.signText
   (text, selectionStyle [, authority1 [, ... authorityN]])

Parameters
text
    A string evaluating to the text you want a user to sign.
selectionStyle
    A string evaluating to either of the following:
        * ask specifies that a dialog box will present a user with a list of possible certificates.
        * auto specifies that Navigator automatically selects a certificate from authority1 through authorityN.
authority1... authorityN
    Optional strings evaluating to Certificate Authorities accepted by the server using the signed text.

LDAP 서버에서 CRL 받는 방법

LDAP 서버에서 인증서 폐기 목록(CRL) 받는 방법 노트.

일반적인 LDAP 조회로는 CRL을 접근할 수 없으므로 아래와 같은 명령으로 base 서치를 통해 받을 수 있습니다.

# Unix 명령
ldapsearch \
-x \
-h ds.yessign.or.kr \
-b ou=dp3p49695,ou=AccreditedCA,o=yessign,c=kr \
-t \
-s base \
-v
# or Windows 명령
ldapsearch ^
-x ^
-h ds.yessign.or.kr ^
-b ou=dp3p49695,ou=AccreditedCA,o=yessign,c=kr ^
-t ^
-s base ^
-v

ps. -t 옵션을 통해 CRL은 파일로 받아지며, 임시 디렉토리(tmp)에 저장됩니다.

pGina를 이용하여 WinXP에서 LDAP로그인 정보 활용하기

Windows XP에서 로그인시 로컬 계정이 아닌 LDAP에 있는 계정으로 로그인하는 것을 테스트해 보았습니다.

Windows XP에서는 기본적으로는 LDAP인증을 당연히 제공하지 않고 있지요. ( AD를 구성하면 디렉토리 서버를 통한 인증이 되겠지만 )

구글링한 결과 pGina라는 프로그램을 설치하여 LDAP을 통한 인증이 가능하다는 것을 테스트해 보았습니다.

아래 사이트에서 pGina XP용 버전인 1.8.8 버전을 받습니다.

http://www.pgina.org/

그리고, 아래 URL에서 LDAP 인증 플러그인을 받습니다.

http://www.pgina.org/index.php/Plugins:LDAP_Auth

받은 플러그인을 plugin 디렉토리에 넣고, 플러그인 설정에서 서버주소(서버명 or IP, SSL 여부, LDAP포트), PrePend(ex. uid=), Append(ex. ou=Users,dc=dgkim,dc=net)정도만 세팅해주면 테스트가 가능합니다.

재부팅을 하면, XP 자체 로그인화면이 아닌 pGina의 로그인 화면이 나오고, LDAP의 ID, Password로 인증이 가능합니다.

만약 처음 로그인인 사용자인 경우, 사용자 프로파일 생성과정을 거친후 로그인 됩니다.


제 PC에 사용해 보려고 했으나, 결정적으로 XP의 훌륭한 장점인 Fast User Switching이 사용할 수 없게 되어, 제거해 버렸습니다.

Fast User Switching 기능만 사용가능하다면, 한번 써볼만하다고 생각합니다.


2011/06/05 잠시 타인에게 노트북 사용권을 주고자 설치했습니다. Fast User Switching 기능이 안되더라도…… 그런데, 오늘 ‘작업관리자’에서 로그인한 사용자의 접속을 ‘연결 끊기’를 통해 사용자 전환이 가능하다는 것이 확인되었습니다. ( 조금 불편할 수도 있지만…… )

VPN 연동 시도 노트

리눅스에서 VPN 서버를 구축해 볼까 시도했습니다.

처음에는 PPTP 방식으로 시도를 했습니다. Poptop으로 시도를 했습니다. 연결은 정상적으로 하였으나, 인증정보를 LDAP으로 하려 했는데, 윈도우에서 Samba를 기준으로 쿼리를 하였고, Samba에 LDAP연동을 시도했으나, 기존의 LDAP에 Samba가 준비되지 않아 실패하였습니다.

다음으로 IPSEC을 시도했습니다. 윈도우의 경우 IPSEC만으로 VPN이 되지 않고, L2TP도 세팅해야 하는데, 마땅한 자료 및 구현이 없어 포기했습니다.

IPSEC은 마지막으로 IPHONE의 IPSEC으로 세팅해보고자 시도를 했습니다. Racoon 이란 것으로 시도를 했고, 접속간 키교환에 인증서를 사용하는 것 까지는 성공했습니다.

하지만, Ubuntu에서 제공하는 Racoon은 LDAP이나 RADIUS와 빌드되지 않아 인증을 수행하지 못해서 최종적으로 실패하였습니다.

이번 작업에서 IPSEC에 대해 좀 더 알게 되었고, RADIUS 서버를 구축하였습니다. ( RADIUS는 현재 활용할 클라이언트가 아직 없네요. )

IPSEC 관련 정보
http://www.ipsec-howto.org/ipsec-howto.pdf
http://lartc.org/lartc.pdf

LDAP 을 사용하여 계정관리 통합하기

Directory Server를 구축하여, 여러 가지 애플리케이션의 인증 관리를 통합할 수 있습니다.

저는 uid={id},ou=Users,dc=dgkim,dc=net 이란 이름의 인증 디렉토리를 구축하여 사용할 수 있습니다.

그리고, 아래와 같은 로그인에 LDAP의 ID, Password를 활용하고 있습니다.

웹애플리케이션 인증

JAAS를 활용하여 인증에 LDAP을 사용하고 있습니다.

서블릿 웹 모듈에서 JAAS 활용하기

아래 web.xml 설정을 통해서 웹모듈에서 컨테이너가 제공하는 UserPrincipal 과 Role 정보를 활용할 수 있습니다.

web.xml : admin, user 롤을 정의하고, user 롤에 속하는 사용자가 사용할 수 있도록 구성.

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5" xmlns="http://java.sun.com/xml/ns/javaee">
    <login-config>
        <auth-method>FORM</auth-method>
        <form-login-config>
            <form-login-page>/login_form.jsp</form-login-page>
            <form-error-page>/login_error.jsp</form-error-page>
        </form-login-config>
    </login-config>
    <security-role>
        <role-name>Admin</role-name>
    </security-role>
    <security-role>
        <role-name>User</role-name>
    </security-role>
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>test web application</web-resource-name>
            <url-pattern>/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>User</role-name>
        </auth-constraint>
    </security-constraint>
</web-app>

그리고, request.getUserPrincipal() 을 호출하면, 로그인에 사용한 ID를 추출할 수 있으며, request.isUserInRole(“User”) 형태로 사용자가 특정 롤을 가지는지 확인할 수 있습니다.

OC4J 10.1.3에서 JAAS 서비스를 제공하는 구성

OC4J에서 컨테이너의 보안 제공자와 서블릿간의 연동을 위한 정보를 구성합니다.

컨테이너 보안 제공자에서 제공하는 사용자, 그룹 정보를 서블릿의 UserPrincipal, Role 정보와 매핑해 줍니다.

<?xml version="1.0"?>

<orion-application  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://xmlns.oracle.com/oracleas/schema/orion-application-10_0.xsd"  deployment-version="10.1.3.3.0" default-data-source="jdbc/OracleDS" component-classification="external"
  schema-major-version="10" schema-minor-version="0" >
        <web-module id="certmanager" path="certmanager.war" />
        <persistence path="persistence" />
        <jazn provider="XML" >
                <property name="custom.ldap.provider" value="true" />
        </jazn>
        <jazn-loginconfig>
                <application>
                        <name>bisu</name>
                        <login-modules>
                                <login-module>
                                        <class>oracle.security.jazn.login.module.LDAPLoginModule</class>
                                        <control-flag>required</control-flag>
                                        <options>
                                                <option>
                                                        <name>oracle.security.jaas.ldap.user.object.class</name>
                                                        <value>inetOrgPerson</value>
                                                </option>
                                                <option>
                                                        <name>oracle.security.jaas.ldap.provider.connect.pool</name>
                                                        <value>true</value>
                                                </option>
                                                <option>
                                                        <name>oracle.security.jaas.ldap.provider.type</name>
                                                        <value>Other</value>
                                                </option>
                                                <option>
                                                        <name>oracle.security.jaas.ldap.provider.credential</name>
							<value>{password}</value>
                                                </option>
                                                <option>
                                                        <name>oracle.security.jaas.ldap.provider.url</name>
                                                        <value>ldap://localhost:389</value>
                                                </option>
                                                <option>
                                                        <name>oracle.security.jaas.ldap.role.searchscope</name>
                                                        <value>onelevel</value>
                                                </option>
                                                <option>
                                                        <name>oracle.security.jaas.ldap.user.searchscope</name>
                                                        <value>onelevel</value>
                                                </option>
                                                <option>
                                                        <name>oracle.security.jaas.ldap.role.searchbase</name>
                                                        <value>ou=Groups,dc=dgkim,dc=net</value>
                                                </option>
                                                <option>
                                                        <name>oracle.security.jaas.ldap.user.searchbase</name>
                                                        <value>ou=Users,dc=dgkim,dc=net</value>
                                                </option>
                                                <option>
                                                        <name>oracle.security.jaas.ldap.role.object.class</name>
                                                        <value>groupOfUniqueNames</value>
                                                </option>
                                                <option>
                                                        <name>oracle.security.jaas.ldap.role.name.attribute</name>
                                                        <value>cn</value>
                                                </option>
                                                <option>
                                                        <name>oracle.security.jaas.ldap.provider.user</name>
                                                        <value>uid=Administrator,ou=Users,dc=dgkim,dc=net</value>
                                                </option>
                                                <option>
                                                        <name>oracle.security.jaas.ldap.user.name.attribute</name>
                                                        <value>uid</value>
                                                </option>
                                                <option>
                                                        <name>oracle.security.jaas.ldap.membership.searchscope</name>
                                                        <value>direct</value>
                                                </option>
                                                <option>
                                                        <name>oracle.security.jaas.ldap.lm.cache_enabled</name>
                                                        <value>false</value>
                                                </option>
                                                <option>
                                                        <name>oracle.security.jaas.ldap.member.attribute</name>
                                                        <value>uniqueMember</value>
                                                </option>
                                        </options>
                                </login-module>
                        </login-modules>
                </application>
        </jazn-loginconfig>
        <log>
                <file path="application.log" />
        </log>
</orion-application>

웹로직 보안제공자를 사용한 LDAP인증

웹로직에서 도메인 단위 보안제공자에서 LDAP 인증을 활용하는 방법을 설명합니다.

웹로직 콘솔에서 LDAP 로그인 모듈 등록하기

  1. 보안영역 선택
  2. Realm 선택(기본 myrealm)
  3. 제공자 탭의 인증 탭 선택
  4. 새로만들기
  5. 이름을 주고, 유형을 LDAPAuthenticator 선택
  6. 생성한 제공자 선택
  7. 구성 탭의 제공자별 탭 선택
  8. LDAP 정보 등록 : 호스트, 포트, 사용자 기본 DN, 그룹 기본 DN
  9. 구성 탭의 공통 탭 선택
  10. 콘트롤 플래그를 SUFFICIENT 선택
  11. 제공자 탭의 인증 탭으로 이동
  12. 순서 재지정 선택
  13. 생성한 제공자를 최상위로 이동
  14. 도메인 내의 서버를 재시작

위 구성을 하면, LDAP 인증을 먼저 수행하고, weblogic의 내장 사용자를 검색하게 됩니다.

아래는 웹모듈에서 위에서 지정한 JAAS를 활용하는 것에 대한 구성파일입니다.

weblogic.xml : admin 롤에 대한 사용자 및 그룹을 Administrators로 지정. ( LDAP에 그룹이 Administrators인 경우 admin 롤을 가짐 )

<weblogic-web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  xsi:schemaLocation="http://www.bea.com/ns/weblogic/weblogic-web-app http://www.bea.com/ns/weblogic/weblogic-web-app/1.0/weblogic-web-app.xsd"
                  xmlns="http://www.bea.com/ns/weblogic/weblogic-web-app">
  <security-role-assignment>
    <role-name>Admin</role-name>
    <principal-name>Administrators</principal-name>
  </security-role-assignment>
</weblogic-web-app>

Tomcat 보안제공자를 사용한 LDAP인증

Tomcat에서 LDAP 인증을 활용하는 방법을 설명합니다.

Tomcat에서 server.xml 내부의 Engine설정 항목에 Realm 항목의 설정을 통해서 설정할 수 있습니다.
아래는 기본 설정의 내용입니다.

    <Engine name="Catalina" defaultHost="localhost">

      <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
             resourceName="UserDatabase"/>

위 구성은 기본적으로 제공되는 파일기반 보안 제공자를 사용하는 예입니다.

해당 보안제공자는 conf/tomcat-users.xml 파일에서 정의되어 있습니다.

아래 구성은 tomcat-users.xml과 추가로 LDAP 인증을 같이 사용하는 예제입니다.

    <Engine name="Catalina" defaultHost="localhost">

      <Realm className="org.apache.catalina.realm.CombinedRealm">
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
        <Realm className="org.apache.catalina.realm.JNDIRealm"
               connectionURL="ldap://localhost:389"
               userPattern="uid={0},ou=Users,dc=dgkim,dc=net"
               roleBase="ou=Groups,dc=dgkim,dc=net"
               roleName="cn"
               roleSearch="(uniqueMember={0})"/>
      </Realm>

위 구성을 하면, LDAP에 기록된 인증정보와 tomcat-users.xml에 정의된 인증정보를 사용할 수 있습니다.

아래는 웹모듈에서 위에서 지정한 JAAS를 활용하는 것에 대한 구성파일입니다.
( weblogic이나 oc4j와 달리 서블릿컨테이너 디플로이먼트 디스크립터를 사용하지 않고, 표준 디스크립터를 통해서 바로 사용가능합니다. )
/* 즉, 모든 리소스는 Users롤을 가진 사용자(ldap에서는 그룹)가 접근할 수 있도록 한 예입니다.


    
        BASIC
    
    
        Users
    
    
        
            allresources
            /*
        
        
            Users
        
    

TRAC 인증

Trac에 로그인 모듈은 Apache의 mod_auth_ldap을 사용하면 LDAP으로 로그인을 할 수 있습니다.

<Location /trac>
   SetHandler mod_python
   PythonInterpreter main_interpreter
   PythonHandler trac.web.modpython_frontend
   PythonOption TracEnvParentDir /TRAC
   PythonOption TracLocale ko_KR.utf8
   PythonOption TracUriRoot /trac

   AuthType Basic
   AuthName "LDAP Authentication Information"
   AuthBasicProvider "ldap"
   AuthLDAPURL "ldap://localhost:389/ou=Users,dc=dgkim,dc=net?uid?sub?(objectClass=*)"
   AuthzLDAPAuthoritative Off
   Require valid-user
</Location>

SVN 레포지토리 인증

Trac과 마찬가지로 mod_auth_ldap 모듈을 통해 LDAP 인증을 수행할 수 있습니다.

<Location /repository1>
    DAV svn
    SVNPath /repository1
    AuthType Basic
    AuthName "LDAP Authentication Information"
    AuthBasicProvider ldap
    AuthLDAPURL "ldap://localhost:389/ou=Users,dc=dgkim,dc=net?uid?sub?(objectClass=*)"
    AuthzLDAPAuthoritative Off
    Require valid-user
</Location>

MAIL 등 네트워크 서비스 인증

Cyrus IMAP 등 유닉스 기반의 네트워크 서비스들은 기본적으로 LDAP을 통한 인증이 가능하도록 준비되어 있습니다.
아래는 Cyrus IMAP에서 사용하는 SASL 인증용 설정 파일 내용입니다.

ldap_servers: ldap://127.0.0.1/
ldap_bind_dn: uid=Administrator, ou=Users, dc=dgkim, dc=net
ldap_bind_pw: {password}
ldap_default_domain: dgkim.net
ldap_search_base: ou=Users, dc=dgkim, dc=net
ldap_filter: (uid=%U)

Thunderbird에서 LDAP을 등록하면, 주소록 검색에도 활용할 수 있고, 만약 LDAP에 인증서가 등록되어 있다면, 보안메일 발송시에 Thunderbird가 자동으로 인증서를 검색해주는 특징도 활용할 수 있습니다.

OS 리눅스 인증

LDAP을 심지어 OS인증에도 사용할 수 있습니다.
Ubuntu linux에 LDAP 인증을 세팅해 본적이 있는데, 현재는 해당 시스템이 없어 설정을 소개할 수 없네요.

최근에 whity 마련과 함께 OS 인증을 테스트하였습니다.

ubuntu 에서 아래 명령으로 패키지 설치 만으로 어렵지 않게 세팅되었습니다. ( 단, 너무 간단하게 해버려서 상관관계는 잘 모르겠습니다. )

apt-get install libpam-ldapd

위 명령을 치면 libpam-ldapd 패키지와 함께 libnss-ldapd nscd nslcd 패키지가 추가로 설치됩니다.
설치후 기본적인 LDAP 정보만 알려주면 쉽게 사용할 수 있습니다. ( 심지어 search base도 일일이 지정할 필요가 없이, root dn만 주고 사용중입니다. )

그밖에…

LDAP 서버를 구축해 두면, 다양한 프로그램에서 인증에 활용할 수 있습니다. 본 블로그의 경우도 WordPress에 LDAP 플러그인을 설치하여, 로그인에 LDAP 서버를 통하고 있습니다.