인증서와 PKI — X.509, CA 체인, 신뢰 구축

공개키 인프라(PKI)의 구조, X.509 인증서 필드 해부, Root CA/Intermediate CA/Leaf 인증서 신뢰 체인, CRL과 OCSP 폐기 메커니즘을 완전 정리합니다.

· 6 min read · PALDYN Team

지난 글에서 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 신뢰 가능!

인증서 신뢰 체인

PKI 인증서 체인 구조

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 인증서 구조

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를 더하면


읽어주셔서 감사합니다. 😊