NET::ERR_CERT_COMMON_NAME_INVALID 원인별 해결
이 글의 순서 12개 절
NET::ERR_CERT_COMMON_NAME_INVALID는 이름과 달리 Common Name을 보는 에러가 아니다. Chrome은
인증서의 Common Name을 안 본 지 오래다. 이 에러는 서버가 내민 인증서의 SAN(subjectAltName)
목록에 주소창의 이름이 없다는 뜻이다.
발급자를 믿을 수 있는지와는 상관없다. 공인 CA가 발급한 멀쩡한 인증서도 이름이 안 맞으면 이
화면이 뜬다. 그래서 AUTHORITY_INVALID와 달리 원인은 대개 서버 쪽에 있다. 인증서에 이름이
빠졌거나, 서버가 맞는 인증서를 두고 엉뚱한 인증서를 내보내고 있다.
에러 코드 -200
Chrome 내부 코드는 -200이다. 바로 옆 번호와 같이 보면 이렇다.
| 에러 코드 | 번호 | 의미 |
|---|---|---|
ERR_CERT_COMMON_NAME_INVALID |
-200 | 접속한 이름이 인증서에 없음 |
ERR_CERT_DATE_INVALID |
-201 | 유효기간 밖 |
ERR_CERT_AUTHORITY_INVALID |
-202 | 신뢰 경로를 못 만듦 |
-202와 헷갈린다면 NET::ERR_CERT_AUTHORITY_INVALID 원인별 해결의 구분표부터 본다.
Chromium 소스에서 이 에러를 정의한 곳에는 원인 후보가 주석으로 달려 있다.
- 공격자가 트래픽을 다른 곳으로 돌렸다
- 서버 설정이 잘못돼 엉뚱한 인증서로 응답한다
- 무선망 로그인 페이지(캡티브 포털)로 끌려갔다
- OS가 DNS 검색 접미사를 붙였는데, 서버에 주소창의 짧은 이름용 인증서가 없다
서버 운영자가 고칠 수 있는 건 2번과 4번이다. 3번은 호텔이나 카페 와이파이에서 망 로그인 전에 HTTPS 사이트를 열 때 나온다. 망 로그인을 마치고 다시 열어 본다.
판별 순서
경고 화면, SAN 목록, 주소창 순서로 본다. 원인 번호는 아래 절 번호와 같다.
1. 경고 화면의 두 이름 ("could not prove that it is A; its security certificate is from B")
└ "does not specify Subject Alternative Names" → CN 전용 (원인 2)
└ B 가 우리 도메인의 변형 → 2번으로
└ B 가 플랫폼 기본 도메인 → CDN·호스팅 연결 (원인 6)
└ B 가 같은 서버의 다른 사이트 → 기본 vhost·SNI (원인 5)
2. s_client -connect A:443 -servername A | x509 -ext subjectAltName
└ SAN 없음 → CN 전용, SAN 넣어 재발급 (원인 2)
└ www/apex 한쪽만 → 이름 추가 재발급 (원인 1)
└ 와일드카드뿐 → 범위·형태 확인 (원인 3)
└ A 가 있음 → 3번으로
3. 주소창의 A 가 어떤 형태인가
└ IP 주소 → iPAddress SAN 또는 이름 접속 (원인 4)
└ 점 없는 단축 이름 → FQDN 접속 또는 SAN 추가 (원인 7)
└ 정상 FQDN → 실패하는 PC 의 DNS·망(캡티브 포털) 확인
OpenSSL이나 curl이 “일치”라고 해도 이 순서에서는 믿지 않는다. 이름 검사가 Chrome보다 느슨해서 결론이 갈린다.
경고 화면의 두 이름
경고 화면의 상세 문구는 이렇다(영문 UI 기준).
This server could not prove that it is www.example.com;
its security certificate is from example.com.
앞은 주소창의 이름이고, 뒤는 서버가 실제로 내민 인증서의 이름 중 대표 하나(CN과 같은 SAN, 없으면
첫 번째 SAN)다. 뒤쪽 이름을 보면 방향이 잡힌다. example.com과 www.example.com처럼 우리
도메인의 변형이면 인증서에 이름이 빠진 것이다. 호스팅 플랫폼 기본 도메인이나 같은 서버의 다른
사이트처럼 전혀 모르는 이름이면 서버가 엉뚱한 인증서를 내보내고 있다.
인증서에 DNS SAN이 아예 없으면 문구가 달라진다.
This server could not prove that it is www.example.com;
its security certificate does not specify Subject Alternative Names.
이 문구면 CN 전용 인증서다(원인 2).
SAN 목록 확인
openssl s_client -connect www.example.com:443 -servername www.example.com </dev/null 2>/dev/null \
| openssl x509 -noout -subject -ext subjectAltName
subject=CN=example.com
X509v3 Subject Alternative Name:
DNS:example.com
OpenSSL 1.1.1 이상이 필요하다. macOS 기본 /usr/bin/openssl은 LibreSSL이라 -ext를 지원하지
않으니 Homebrew의 openssl@3를 쓴다. OpenSSL 3.1 이하에서는 subject=CN = example.com처럼 =
앞뒤에 공백이 붙는다.
-connect와 -servername 둘 다 주소창에 친 이름을 그대로 넣는다. -servername은 브라우저가
SNI로 보내는 값이다. SNI는 TLS 접속 첫머리에 어느 도메인으로 왔는지 서버에 알리는 값이고, 서버는
이걸 보고 내줄 인증서를 고른다. -connect에도 같은 이름을 줘야 브라우저가 DNS로 찾아가는 바로 그 IP에
붙는다. www와 apex(www 없는 루트 도메인, 예: example.com)가 서로 다른 서버(CDN과 원서버)를 가리키는 구성이면 이름 하나만 달라도 엉뚱한
서버를 검사하게 된다. 단, 주소창이 IP면 -servername을 빼고 -connect IP:443만 쓴다(원인 4).
출력은 이렇게 읽는다.
| SAN 출력 | 원인 |
|---|---|
www와 apex 중 한쪽만 있음 |
이름 누락 (원인 1) |
SAN 줄이 없음, No extensions in certificate(1.1.1은 No extensions matched with subjectAltName) |
CN 전용 인증서 (원인 2) |
*.example.com뿐인데 접속은 example.com이나 a.b.example.com |
와일드카드 범위 밖 (원인 3) |
w*.example.com, *.intranet 같은 형태 |
Chrome이 거부하는 와일드카드 (원인 3) |
IP로 접속했는데 IP Address: 항목이 없음 |
IP 접속 (원인 4) |
| 전혀 다른 도메인 | 기본 인증서 응답 (원인 5·6) |
| 접속한 이름이 있음 | SAN은 정상. 실패하는 PC의 DNS·망, 또는 주소창의 이름을 본다 (원인 7) |
OpenSSL과 curl은 Chrome보다 너그럽다
호스트명 검사 옵션으로 한 번에 끝내고 싶어진다. OpenSSL에는 두 가지가 있다.
# 파일로 받은 인증서 검사
openssl x509 -in cert.pem -noout -checkhost www.example.com
# 핸드셰이크 중 검사
openssl s_client -connect www.example.com:443 -servername www.example.com \
-verify_hostname www.example.com -verify_return_error -brief </dev/null
이름이 안 맞으면 verify error:num=62:hostname mismatch, 맞으면 Verified peername: www.example.com이
나온다. 공인 인증서일 때 얘기다. 사설 CA나 자체 서명이면 체인 검증이 먼저 실패해
num=18(self-signed), 19, 20에서 끝나고 이름 판정까지 가지 않는다. -CAfile <루트.pem>을
같이 줘야 62인지가 나온다.
그런데 이 결과를 Chrome의 판정으로 읽으면 안 된다. OpenSSL의 호스트명 검사는 기본값에서 Chrome보다 느슨하다.
CN으로 폴백한다. DNS 타입 SAN이 하나도 없으면(IP SAN만 있어도) subject의 CN을 본다. OpenSSL 백엔드 curl도 SAN(dNSName, iPAddress)이 둘 다 없으면 CN으로 폴백한다.
부분 와일드카드를 받아 준다. w*.example.com이 www.example.com에 맞는다고 판정한다. curl은
부분 와일드카드를 거부한다.
공개 접미사 와일드카드도 받아 준다. *.co.uk가 example.co.uk에 맞는다고 판정한다. Chrome은
거부한다.
그래서 CN 전용 인증서를 검사하면 이런 결과가 나온다.
$ openssl x509 -in cn-only.pem -noout -checkhost www.example.com
Hostname www.example.com does match certificate
$ openssl x509 -in cn-only.pem -noout -ext subjectAltName
No extensions in certificate
OpenSSL은 맞다고 하는데 Chrome은 -200을 낸다. 판단은 -ext subjectAltName 출력으로 한다. 접속
이름이 SAN에 글자 그대로 있거나, 왼쪽 첫 레이블 전체가 *인 와일드카드로 덮여야 Chrome에서
통과한다.
원인 1. www나 apex가 빠졌다
가장 흔한 경우다. example.com으로만 받은 인증서에 www.example.com으로 들어오거나 그 반대다.
서브도메인을 새로 열면서 인증서에 넣는 걸 잊은 것도 같다.
Let’s Encrypt라면 인증서마다 들어간 이름부터 본다.
sudo certbot certificates
Certificate Name: example.com
...
Identifiers: example.com
Certbot 5.1 이하는 이 줄이 Domains:로 나온다. Identifiers(구버전 Domains)에 빠진 이름이
있으면 같은 인증서 이름으로 목록을 다시 지정한다.
sudo certbot certonly --cert-name example.com -d example.com,www.example.com
sudo systemctl reload nginx # apache 면 apache2
-d는 기존 목록에 이름을 보태지 않고 목록 전체를 새로 지정한다. 원래 있던 이름도 빠짐없이 다시
적는다. 빠뜨린 이름은 새 인증서에서 사라진다.
certonly는 인증서 파일만 바꾼다. 웹서버를 reload하기 전까지는 옛 인증서가 계속 나가서
“재발급했는데 그대로”라고 오진하기 쉽다. 갱신 때마다 자동으로 하려면
--deploy-hook "systemctl reload nginx"를 붙인다.
상용 CA 인증서도 원리는 같다. 인증서는 서명된 문서라 SAN만 고칠 수 없다. 두 이름을 모두 넣어 새로 발급받는다. 발급 절차는 인증서 발급 공통 가이드에 있다.
Chrome이 이 문제를 가려 준다
www나 apex 누락은 Chrome에서 안 보일 때가 있다. 아래 조건이 모두 맞으면 Chrome은 경고 화면 대신 다른 URL로 조용히 이동한다.
- 에러가 이름 불일치 하나뿐이고, HSTS 등으로 경고 우회가 막힌 상태가 아니다
- 인증서 SAN에 주소창 이름에서
www를 붙이거나 뗀 이름이 있다 - 그 이름의 같은 경로 URL에 보낸 HEAD 요청이 3초 안에 HTTPS 200으로 응답한다 (301·302 리다이렉트면 해당 없음)
www.example.com 인증서만 있는 서버에 https://example.com으로 들어가면 경고 없이 www로
넘어간다. 주소창이 www로 바뀌고, 이유는 개발자 도구 콘솔에 남는다.
Redirecting navigation example.com -> www.example.com because the server
presented a certificate valid for www.example.com but not for example.com.
그래서 Chrome에서는 되는데 curl, 앱, 서버 간 API 호출에서만 실패하는 일이 생긴다. 다른 클라이언트는 이런 보정을 하지 않는다. curl(OpenSSL 백엔드)은 이렇게 끝난다.
curl: (60) SSL: no alternative certificate subject name matches target hostname 'example.com'
curl 8.8 이하는 target host name으로 나오고, Windows 기본 curl(Schannel)은
SEC_E_WRONG_PRINCIPAL처럼 문구가 아예 다르다.
원인 2. 이름이 CN에만 있다
-ext subjectAltName에 아무것도 안 나오고 subject의 CN에만 도메인이 있는 인증서다. CN이 접속
이름과 한 글자도 안 틀려도 Chrome에서는 실패한다.
지금 Chrome의 호스트명 검증은 SAN의 dNSName과 iPAddress가 하나도 없으면 CN은 열어 보지도 않고 바로 실패한다. Chrome 58(데스크톱 stable 2017년 4월 19일)에서 CN 매칭을 뺐다. 갑자기 바뀐 건 아니다.
| 시점 | 문서·제품 | 내용 |
|---|---|---|
| 2000 | RFC 2818 | dNSName SAN이 있으면 그것을 쓰고, 없을 때만 CN. CN 사용은 deprecated |
| 2012~ | CA/B Forum Baseline Requirements | 공인 인증서에 SAN 필수 |
| 2017 | Chrome 58 | CN 매칭 제거 |
| 2023 | RFC 9525 | RFC 6125 대체. CN으로 서비스를 식별하면 안 된다(MUST NOT) |
Firefox도 48부터 새 공인 인증서에 SAN을 요구했다.
제거를 예고할 때 Chrome 팀이 집계한 바로는 공인 인증서 검증의 0.002% 미만, 사설 CA 인증서의 1.57%(전체 검증 대비 0.1%)가 CN 폴백에 기대고 있었다. 공인 CA는 BR상 SAN 없이 발급할 수 없으니, 지금 CN 전용 인증서가 나오는 곳은 사설 CA와 자체 서명 쪽이다. 오래된 스크립트로 만든 내부 인증서, 장비 관리 페이지 인증서가 대표적이다.
SAN을 넣어 다시 발급한다. OpenSSL로 CSR을 만든다면 -addext로 넣는다.
openssl req -new -newkey rsa:2048 -nodes -keyout app.key -out app.csr \
-subj "/CN=app.corp.example.com" \
-addext "subjectAltName=DNS:app.corp.example.com"
-nodes는 OpenSSL 3.0부터 deprecated이고 -noenc가 대체다. 동작은 같다.
사설 CA가 CSR의 SAN을 그대로 반영하는지는 CA 설정에 따라 다르다. 발급된 인증서에서
-ext subjectAltName을 다시 확인한다.
자체 서명이면 같은 명령에 -x509 -days 365를 붙인다. CSR 대신 SAN이 들어간 인증서가 바로
나온다(-out 파일명은 바꿔 둔다). -days를 빼면 기본 30일이라 한 달 뒤 DATE_INVALID로 다시
깨진다.
CN은 남겨도 된다. BR은 CN을 NOT RECOMMENDED로 두고, 넣는다면 SAN 값 중 하나를 그대로 써야 한다고 정한다. 검증에는 쓰이지 않는다.
원인 3. 와일드카드 범위와 형태
와일드카드가 apex를 안 덮고 한 단계만 덮는다는 기본 규칙은 와일드카드 인증서와 SAN 인증서, 언제 무엇을 쓰나에 있다. 그 밖에도 인증서는 만들어지는데 Chrome에서만 막히는 형태가 있다.
Chrome의 와일드카드 규칙은 짧다. 왼쪽 첫 레이블 전체가 정확히 *일 때만 인정하고, 나머지는
글자 그대로 일치해야 한다.
| SAN 값 | 접속 이름 | Chrome |
|---|---|---|
*.example.com |
www.example.com |
✅ |
*.corp.example.com |
wiki.corp.example.com |
✅ |
*.example.com |
example.com |
❌ apex 미포함 |
*.example.com |
a.b.example.com |
❌ 한 단계만 |
w*.example.com |
www.example.com |
❌ 부분 와일드카드 |
*.co.uk |
example.co.uk |
❌ 공개 접미사 |
*.intranet |
wiki.intranet |
❌ 미등록 TLD |
부분 와일드카드
w*.example.com 같은 형태는 RFC 2818이 허용했다. f*.com이 foo.com에 맞는다는 예시까지 있다.
RFC 6125(2011)는 왼쪽 첫 레이블이 아닌 곳의 와일드카드를 SHOULD NOT으로 막았지만, 첫 레이블 안의
부분 와일드카드(baz*.example.net)는 여전히 MAY로 허용했다. 이를 대체한 RFC 9525 §6.3에서야
금지됐다. RFC 9525의 규칙은 이렇다.
- 와일드카드 문자는 하나뿐이다
- 왼쪽 첫 레이블의 전체 내용으로만 나타난다
- 레이블 하나만 매칭한다
Chrome은 부분 와일드카드를 막는 점에서 이 규칙과 같고, 아래 제약을 더 얹는다. 반면 OpenSSL은 기본값에서 아직 부분 와일드카드를 받아 준다. OpenSSL 통과를 믿으면 안 되는 이유가 여기서 하나 더 나온다.
공개 접미사와 미등록 TLD
Chrome은 공개 레지스트리 도메인(*.com, *.co.uk)에 대한 와일드카드를 인정하지 않는다. OpenSSL은
*.co.uk도 통과시킨다. 레지스트리 데이터셋에 없는 TLD(사내에서 쓰는 intranet 같은 이름)는 그
마지막 레이블을 레지스트리로 취급하므로 *.intranet처럼 TLD 바로 아래 와일드카드가 막힌다.
*.corp.intranet처럼 한 단계 아래면 Chrome도 받아 준다. 숫자로만 된 호스트명과 점이 없는 단일
레이블 이름에도 와일드카드 매칭을 하지 않는다.
공인 CA는 이런 인증서를 애초에 못 낸다. BR이 *.co.uk, *.local 같은 발급을 금지한다. BR의
와일드카드 도메인명 정의도 *. 바로 뒤에 FQDN이 오는 형태뿐이라 w*. 같은 부분 와일드카드가
공인 인증서에 들어갈 자리는 없다. 그래서 표 아래쪽 행들은 사설 CA로 사내 도메인 전체를 와일드카드
하나로 덮으려 할 때 나온다.
사내 전용 TLD를 쓰고 있다면 호스트 이름을 SAN에 나열하거나 한 단계 아래 와일드카드를 쓴다. 다만
미등록 TLD는 나중에 공개 TLD와 이름이 겹칠 위험이 있으니 실제로 소유한 도메인
아래(*.corp.example.com)로 옮기는 편이 낫다.
원인 4. IP 주소로 접속했다
https://203.0.113.10/처럼 IP로 들어가면 규칙이 달라진다. Chrome은 IP로 접속하면 SAN의
iPAddress 항목만 비교한다. dNSName에 203.0.113.10이라는 문자열을 넣어도 매칭되지 않는다. 게다가
SNI에는 IP를 넣을 수 없다(RFC 6066). 서버는 어떤 이름으로 들어왔는지 모른 채 기본 인증서를 준다.
그래서 IP 접속은 거의 항상 엉뚱한 인증서를 받는다. 직접 확인할 수 있다.
openssl s_client -connect 203.0.113.10:443 </dev/null 2>/dev/null \
| openssl x509 -noout -subject -ext subjectAltName
s_client는 -connect가 IP면 SNI를 보내지 않는다. 브라우저의 IP 접속과 같은 조건이다. 인증서
파일이 있다면 IP 매칭만 따로 검사한다.
openssl x509 -in cert.pem -noout -checkip 203.0.113.10
가장 간단한 건 이름으로 접속하게 하는 것이다. 모니터링 대상, 연동 설정, 북마크에 IP가 박혀 있지 않은지 본다.
IP로 접속해야만 한다면 iPAddress SAN이 든 인증서를 쓴다. 사설 CA라면 CSR에
-addext "subjectAltName=IP:203.0.113.10" 식으로 넣는다.
공인 인증서도 선택지가 됐다. Let’s Encrypt는 2026년 1월 15일부터 IP 주소 인증서를 정식으로 제공한다. 다만 IP 인증서는 shortlived 프로필(유효기간 160시간, 6일 남짓)로만 나온다. 자동 갱신이 멈추면 일주일 안에 만료되니 만료 감시 없이 쓰면 안 된다. 만료일 보는 법은 SSL 인증서 만료일 확인하는 법에 있다.
원인 5. SNI가 없어 기본 인증서가 나간다
한 서버에 여러 도메인이 올라가 있으면 서버는 TLS 핸드셰이크의 SNI를 보고 줄 인증서를 고른다. TLS 연결은 HTTP 요청(Host 헤더)보다 먼저 맺어지므로 SNI가 유일한 단서다.
SNI가 없거나 SNI 이름과 맞는 server 블록이 없으면 nginx는 그 listen의 default server 인증서를
준다. 새 도메인의 server_name에 오타가 있으면 기본 사이트의 인증서가 나간다. SNI를 안 보내는
구형이나 특수 클라이언트만 실패하고 브라우저는 멀쩡하다면 이 경우다.
SNI를 끄고 재현한다.
openssl s_client -connect example.com:443 -noservername </dev/null 2>/dev/null \
| openssl x509 -noout -subject -ext subjectAltName
여기서 우리 도메인이 아닌 인증서가 나오면 SNI에 기대는 구성이다. CDN이나 호스팅 위의 사이트는 SNI 없이 붙으면 플랫폼 기본 인증서가 오거나 연결이 끊기는 경우가 흔하다. 한 CDN 문서는 SNI로 도메인을 정하지 못하면 연결을 끊는다고 적는다.
테스트 도구 버전도 본다. s_client는 OpenSSL 1.1.1부터 -servername을 안 줘도 -connect의
이름을 SNI로 보낸다. 그보다 오래된 OpenSSL로 -servername 없이 테스트하면 SNI 없이 붙어서 멀쩡한
서버를 오진한다.
nginx 빌드가 SNI를 지원하는지는 이렇게 본다.
nginx -V 2>&1 | grep -i 'TLS SNI support'
# TLS SNI support enabled
고칠 때는 server_name과 인증서 SAN이 짝이 맞는지부터 본다. SAN 확인 명령의 -connect와
-servername을 도메인마다 바꿔 가며 돌리면 어느 이름이 기본 인증서로 떨어지는지 보인다.
기본 server가 엉뚱한 인증서를 주는 것 자체를 막을 수도 있다. nginx 1.19.4부터
ssl_reject_handshake가 있다.
server {
listen 443 ssl default_server;
ssl_reject_handshake on;
}
기존 server 블록에 이미 default_server가 있으면 duplicate default server 오류로 설정 로드가
실패하니 그쪽을 떼고 추가한다. IPv6로도 listen한다면 listen [::]:443 ssl default_server;를 같은
블록에 함께 넣는다.
이렇게 두면 SNI가 어떤 server_name과도 안 맞을 때 다른 사이트 인증서를 내미는 대신 핸드셰이크를
거부한다. 사용자는 이름 불일치 경고 대신 분명한 연결 실패를 보고, 다른 도메인 이름이 인증서로
드러나지도 않는다. 대신 SNI를 안 보내는 클라이언트는 확실히 끊긴다. 그런 클라이언트가 없는지 먼저
확인한다. nginx 설정 전반은 Nginx 가이드에 있다.
원인 6. CDN이나 호스팅에 커스텀 도메인이 안 붙었다
경고 화면 뒤쪽 이름이 *.<플랫폼 기본 도메인> 형태면 여기다. CDN과 정적 호스팅은 자기 기본
도메인용 와일드카드 인증서를 기본으로 내보낸다. 커스텀 도메인용 인증서는 연결 설정을 마쳐야
나간다.
흔히 이렇게 된다.
- DNS의 CNAME은 이미 CDN을 가리키는데, CDN 쪽에 대체 도메인 등록이나 인증서 연결을 안 했다
- CDN에 붙인 인증서가 그 도메인을 SAN으로 덮지 못한다. 주요 CDN 문서도 대체 도메인을 추가하려면
그 이름을 SAN으로 덮는 인증서가 필요하다고 명시하고,
*.example.com으로는example.com이나marketing.product.example.com을 추가할 수 없다고 적는다 - SNI를 안 보내는 클라이언트다. CDN도 원인 5와 같은 이유로 플랫폼 기본 인증서를 주거나 연결을 끊는다
- 정적 호스팅에 커스텀 도메인을 막 연결했다. 한 호스팅 서비스 문서는 HTTPS가 붙기까지 최대 1시간이 걸릴 수 있고, DNS를 바꾼 뒤에는 커스텀 도메인을 지웠다 다시 추가해야 인증서 발급이 시작될 수 있다고 안내한다
DNS를 전환하기 전에 새 엣지가 이 이름에 맞는 인증서를 주는지 확인한다. --resolve는 DNS를 거치지
않고 지정한 IP에 이 이름으로 붙는다.
curl -sSv -o /dev/null --resolve www.example.com:443:203.0.113.10 https://www.example.com/
이름이 안 맞으면 curl: (60) SSL: no alternative certificate subject name matches target hostname으로
끝난다. 버전과 백엔드에 따라 문구가 다른 건 원인 1에 적었다. -S를 빼고 -s만 주면 이 마지막
에러 줄이 숨고 종료 코드 60만 남는다. 전환한 뒤 사용자 브라우저에서 알게 되는 것보다 훨씬 싸다.
원인 7. 사내 단축 호스트명
사내에서 주소창에 wiki만 치고 들어가는 경우다. OS가 DNS 검색 접미사(corp.example.com)를 붙여
wiki.corp.example.com으로 찾아가니 서버에는 잘 닿는다. 하지만 Chrome이 인증서와 비교하는 건
주소창의 이름 wiki다. 인증서에 wiki.corp.example.com만 있으면 -200이다. Chromium 주석의 4번
원인이 바로 이 경우다.
공인 인증서로는 풀 수 없다. BR은 IANA 루트존에 등록된 TLD로 끝나지 않는 이름(Internal Name)이 든
인증서 발급을 금지한다. wiki 같은 단일 레이블 이름은 공인 CA가 넣어 주지 않는다.
FQDN으로 접속하게 하는 게 먼저다. 사내 포털 링크, 북마크, 문서의 주소를
https://wiki.corp.example.com/으로 바꿔 둔다. 이러면 공인 인증서도 쓸 수 있다. 단축 이름도
살려야 하면 사설 CA로 SAN에 둘 다 넣는다.
openssl req -new -newkey rsa:2048 -nodes -keyout wiki.key -out wiki.csr \
-subj "/CN=wiki.corp.example.com" \
-addext "subjectAltName=DNS:wiki.corp.example.com,DNS:wiki"
단일 레이블 이름은 와일드카드로 덮을 수 없으니(원인 3) 이름마다 SAN에 넣는다. 사설 CA 루트를
PC들이 신뢰하게 만드는 건 별개 문제다. 그쪽은 AUTHORITY_INVALID로 나오고,
NET::ERR_CERT_AUTHORITY_INVALID 원인별 해결의 사내 CA
항목에 적었다.
재발급한 인증서에 이름이 제대로 들어갔는지는 무료 진단 툴로도 볼 수 있다. CT 로그
기준으로 이 도메인에 발급된 최신 공인 인증서의 SAN과 만료일을 보여 준다. 서버가 실제로 내보내는
인증서는 위의 openssl s_client 명령으로 확인한다.
참조
- Chromium: net_error_list.h (CERT_COMMON_NAME_INVALID -200)
- Chromium: ssl_errors_strings.grdp (경고 화면 문구)
- Chromium: x509_certificate.cc (VerifyHostname)
- Chromium: ssl_error_handler.cc (www 제안 URL 이동)
- Chromium: common_name_mismatch_handler.cc (제안 URL HEAD 확인)
- Chromium: error_info.cc (경고 화면 이름 표시)
- Chromium: EnableCommonNameFallbackForLocalAnchors 정책 정의
- Chrome Platform Status: Support for commonName matching in Certificates
- Chrome Releases: Stable Channel Update for Desktop (2017-04-19)
- Chrome for Developers: Chrome 58 deprecations
- Chromium security-dev: commonName 매칭 제거 공지
- RFC 2818: HTTP Over TLS
- RFC 6125: Representation and Verification of Domain-Based Application Service Identity
- RFC 9525: Service Identity in TLS
- RFC 6066: TLS Extensions (Server Name Indication)
- CA/Browser Forum: Baseline Requirements
- nginx: Configuring HTTPS servers
- nginx: ngx_http_ssl_module (ssl_reject_handshake)
- OpenSSL: openssl-s_client
- OpenSSL 1.1.1: s_client
- OpenSSL 3.0: openssl-x509
- OpenSSL 1.1.1: x509
- OpenSSL 3.0: X509_check_host
- OpenSSL 3.0: openssl-verification-options
- OpenSSL 3.0: openssl-req
- curl: lib/vtls/openssl.c
- curl: lib/vtls/hostcheck.c
- curl: manpage (-s)
- curl: manpage (–resolve)
- CDN 개발자 문서: 대체 도메인 이름과 인증서 요건
- CDN 개발자 문서: SNI와 전용 IP로 HTTPS 제공
- 정적 호스팅 문서: 커스텀 도메인 문제 해결
- Certbot: User Guide
- Certbot: cert_manager.py (certificates 출력 형식)
- Let’s Encrypt: 6-day and IP address certificates general availability
- Let’s Encrypt: Profiles