인증서와 PKI — X.509, CA 체인, 신뢰 구축
공개키 인프라(PKI)의 구조, X.509 인증서 필드 해부, Root CA/Intermediate CA/Leaf 인증서 신뢰 체인, CRL과 OCSP 폐기 메커니즘을 완전 정리합니다.
지난 글에서 mTLS와 상호 인증을 살펴봤습니다. TLS에서 서버(또는 클라이언트)의 신원을 증명하는 핵심은 X.509 인증서입니다. 인증서를 발급하고 관리하는 체계가 **PKI(Public Key Infrastructure)**입니다. 이번 글에서는 PKI의 신뢰 모델과 인증서 구조를 해부합니다.
PKI의 신뢰 문제
인터넷에서 처음 만나는 서버가 “나는 example.com입니다”라고 말하면 어떻게 믿을 수 있을까요? PKI는 **신뢰할 수 있는 제3자(Certificate Authority)**를 통해 이 문제를 해결합니다.
신뢰 체인의 원리:
브라우저/OS: "나는 DigiCert의 Root CA를 신뢰한다" (미리 내장)
DigiCert: "나는 Intermediate CA를 신뢰한다" (Root CA 서명)
Int. CA: "나는 example.com 인증서를 신뢰한다" (Intermediate 서명)
따라서: 브라우저 → example.com 신뢰 가능!
인증서 신뢰 체인
Root CA
최상위 신뢰 기관입니다.
특징:
- 자체 서명(Self-Signed): 발급자 = 주체
- OS/브라우저에 사전 설치
- 오프라인 보관 (물리적 보안 최우선)
- 유효기간: 20~40년
예) DigiCert Global Root CA, ISRG Root X1 (Let's Encrypt)
# 시스템 신뢰 Root CA 목록 확인
# Linux (Debian/Ubuntu)
ls /etc/ssl/certs/ | head -10
# macOS
security find-certificate -a -p /System/Library/Keychains/SystemRootCertificates.keychain | \
openssl x509 -noout -subject | head -10
Intermediate CA
Root CA와 Leaf 인증서 사이에 위치합니다.
역할:
- 실제 서버/클라이언트 인증서 발급
- Root CA 대신 위험 노출
- 침해 시 Root를 건드리지 않고 폐기 가능
개수: Root CA 하나에 여러 Intermediate CA 가능
(예: DigiCert TLS RSA SHA256 2020 CA1)
Leaf (Server) Certificate
실제 도메인에 발급되는 인증서입니다.
X.509 인증서 구조
# 인증서 전체 내용 출력
openssl x509 -in cert.pem -noout -text
# 주요 필드만 추출
openssl x509 -in cert.pem -noout \
-subject -issuer -dates -fingerprint -serial
# 출력 예시:
# subject=CN=example.com, O=Example Inc.
# issuer=CN=DigiCert TLS RSA SHA256 2020 CA1
# notBefore=Jan 1 00:00:00 2026 GMT
# notAfter=Jan 1 00:00:00 2027 GMT
# SHA256 Fingerprint=AB:CD:EF:...
핵심 필드 설명
Subject Alternative Name (SAN)
현대 TLS에서 가장 중요한 필드입니다. CN(Common Name)은 레거시가 됐고, 실제 도메인 검증은 SAN으로 합니다.
# SAN 확인
openssl x509 -in cert.pem -noout \
-ext subjectAltName
# 출력 예시:
# X509v3 Subject Alternative Name:
# DNS:example.com, DNS:www.example.com, DNS:*.example.com
Key Usage와 Extended Key Usage
Key Usage: digitalSignature, keyEncipherment
→ 서명과 키 암호화에 사용 가능
Extended Key Usage:
serverAuth (1.3.6.1.5.5.7.3.1) → HTTPS 서버
clientAuth (1.3.6.1.5.5.7.3.2) → mTLS 클라이언트
emailProtection → S/MIME
인증서 폐기 메커니즘
인증서가 만료 전에 무효화되어야 할 때(개인키 유출, 오발급 등)가 있습니다.
CRL (Certificate Revocation List)
# 인증서의 CRL 배포 URL 확인
openssl x509 -in cert.pem -noout \
-ext cRLDistributionPoints
# CRL 다운로드 및 확인
curl -s http://crl.example.com/issuer.crl | \
openssl crl -inform DER -text -noout | head -30
문제: CRL 파일이 커질 수 있고, 주기적 다운로드로 실시간성이 떨어집니다.
OCSP (Online Certificate Status Protocol)
# 인증서의 OCSP 서버 URL 확인
openssl x509 -in cert.pem -noout \
-ext authorityInfoAccess
# OCSP 직접 조회
openssl ocsp \
-issuer intermediate-ca.pem \
-cert server-cert.pem \
-url http://ocsp.example.com \
-text
# 결과: good / revoked / unknown
OCSP Stapling
서버가 OCSP 응답을 미리 가져와 TLS 핸드셰이크에 포함합니다.
# Nginx OCSP Stapling 활성화
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/nginx/ca-chain.pem;
resolver 8.8.8.8 valid=300s;
resolver_timeout 5s;
# 확인
openssl s_client -connect example.com:443 -status 2>/dev/null | \
grep -A 20 "OCSP response:"
인증서 발급 과정
# CSR (Certificate Signing Request) 생성
openssl req -new \
-key server-key.pem \
-out server.csr \
-subj "/CN=example.com/O=Example Inc/C=KR"
# SAN 포함 CSR
cat > san.ext << EOF
[req_ext]
subjectAltName = @alt_names
[alt_names]
DNS.1 = example.com
DNS.2 = www.example.com
EOF
openssl req -new -key server-key.pem \
-out server.csr \
-config san.ext
# CA 서명 (자체 서명 CA 사용 시)
openssl x509 -req \
-in server.csr \
-CA ca-cert.pem -CAkey ca-key.pem \
-CAcreateserial \
-out server-cert.pem \
-days 365 \
-extfile san.ext \
-extensions req_ext
Let’s Encrypt와 ACME 프로토콜
무료 인증서를 자동으로 발급·갱신하는 표준 프로토콜입니다.
# certbot으로 Let's Encrypt 인증서 발급
certbot certonly \
--webroot -w /var/www/html \
-d example.com -d www.example.com \
--email admin@example.com
# 자동 갱신 (cron 또는 systemd timer)
certbot renew --quiet --deploy-hook "nginx -s reload"
# DNS 챌린지로 와일드카드 인증서
certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /etc/cloudflare.ini \
-d "*.example.com"
인증서 모니터링
# 만료일 확인 스크립트
check_expiry() {
local domain=$1
local days_remaining
days_remaining=$(openssl s_client -connect "${domain}:443" \
-servername "${domain}" < /dev/null 2>/dev/null \
| openssl x509 -noout -enddate \
| cut -d= -f2 \
| awk '{print (mktime(strftime("%Y %m %d %H %M %S", strptime($0, "%b %d %T %Y %Z"))) - systime()) / 86400}')
echo "${domain}: ${days_remaining} days remaining"
}
check_expiry example.com
지난 글: mTLS — 상호 TLS 인증 완전 정리
다음 글: HTTPS 완전 정복 — HTTP에 TLS를 더하면
읽어주셔서 감사합니다. 😊