본문 바로가기
카테고리 없음

Mac OS, python3 urllib 정상 인증서 SSL 에러 - SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed

by ahsung 2022. 9. 23.

\Mac OS에서 python3.10.5버전을 새로 설치후 urllib을 사용하여 개발중 아래 에러에 직면했다.

urllib은 python3의 기본 패키지로서, 개발물의 목적보다는 스크립트 느낌으로 작성하고 싶을 때 많이 사용하고 있다.

(모든 python3가 있는 시스템에서 실행하기 위해)

<urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self signed certificate in certificate chain (_ssl.c:997)>

 

보통 SSL 에러는 접속하는 site의 인증서를 client가 가지고 있지 않거나,

서버쪽의 ssl 정보를 믿을 수 없을 때 (client가 가지고있는 인증서 정보를 기반으로) 발생한다.

 

서버측에서 ssl/tls를 사용하나 공인 CA 회사에 등록되지 않은 커스텀 인증서를 사용하거나, 유효기간이 다된 경우도

위 설명에 해당되어 에러가 발생하게된다.

 

이런 케이스들은 애초에 인증 과정 자체에는 이슈가 없으므로 verify = False 옵션등을 사용하면 되며,

urllib의 경우 ssl 인증을 끄는 방법은 여러가지있다. (그외 더 좋은 방법들은 구글링하면 많이 나온다.)

# 모두 적용
import ssl

ssl._create_default_https_context = ssl._create_unverified_context()

# https 호출에만 적용
from urllib.request import urlopen

context = ssl._create_unverified_context()
result = urlopen("https://example1234", context=context)

 

나의 경우 위에서 말한 모든 사례에 해당하지 않았으나 SSL에러가 발생했다....

(서버쪽 인증서 문제 없고, Mac OS에도 Root CA 인증서를 잘 가지고있다..)

 

이상한건 urllib을 사용할 때만 발생하며, requests 모듈을 사용할 때는 정상적이였다.

 

 

원인

Mac 운영체제에 처음부터 포함된 python3는 Mac OS의 openssl을 바라보지만,

 

사용자가 직접 pyenv, 웹사이트 등 외부에서 따로 Mac OS용 python3를 설치(혹은 빌드)한 경우

Mac OS python3.6 버전부터 mac os의 openssl에 의존하지 않고 자체 openssl 번들을 사용한다.

(해당 정보의 공식문서 링크는 못 찾아서 stackoverflow 참조)  

 

Mac OS에 포함된 python3에서 default SSL 경로 확인

import ssl

print(ssl.get_default_verify_paths().openssl_capath)

>> '/usr/local/etc/openssl@1.1/certs'

 

직접 설치한 python3에서 default SSL 경로 확인

import ssl

print(ssl.get_default_verify_paths().openssl_capath)

>> '/Library/Frameworks/Python.framework/Versions/3.10/etc/openssl/certs'

 

ssl.get_default_verify_paths 함수는 설치된 python3의 c 라이브러리에 박혀있고

urllib은 기본 openssl CA 파일을 통해 인증을한다. (즉 설치되는 python3마다 다름)

 

지금까지 Mac OS의 기본 python3는 Mac OS의 openssl 번들을 사용하므로 문제 없었지만

직접 설치한 Python3들은 자체 openssl 번들을 쓰므로 공인된 Root CA들이 없을 수 있다는 것...

 

왜 requests는 성공했는가?

requests는 python3 자체 openssl 번들을 사용하지 않는다.

# site-packages/requests/utils
DEFAULT_CA_BUNDLE_PATH = certs.where()


## certifi 패키지의 인증서 위치
import certifi
print(certifi.where())
'/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/certifi/cacert.pem'

여기서 certs는 certifi 패키지이다.

 

requests는 certifi 패키지의 인증서를 사용하므로, certifi 패키지 버전에 의존적이다.

(최신 버전의 certifi 버전일수록 Root CA가 신뢰도 높게 담겨져있음)

 

참고로 certifi 패키지는 python3에 기본적으로 있다.

 

 

결론

내가 직접 설치한 Mac용 python3.10.5 자체 openssl 번들에는 Root CA가 제대로 담겨있지 않았고

certifi 패키지가 갖고있는 인증서에는 Root CA가 잘 담겨있었다.

 

requests는 certifi 패키지의 인증서를 사용

urllib은 python3의 자체 openssl을 사용한다. ( OS에 기본 포함된 python3는 OS의 openssl 번들을 바라봄)

 

 

해결 방법

다행히 Mac용 python3에는 거지같은 자체 인증서를 업데이트하는 스크립트가 함께 있다. 휴~

cd /Applications/Python\ 3.10/
./Install\ Certificates.command

 

'Install Certificates.command' 명령어는 간단한 python3 스크립트여서 cat으로 까서 내용을 살펴볼 수 있다.

 

대략 설명하자면,

1. certifi 패키지를 최신 버전으로 업데이트
2. 기본 ssl 인증서(ssl.get_default_verify_paths().openssl_capath) 삭제

3. 기본 ssl 인증서를 certifi 패키지 인증서로 link file 생성

 

i.e) python3 자체 기본 인증서를 최신 certifi 패키지 인증서로 변경함

 

 

PS,

왜 Mac용 python3과 requests 모듈은 Mac OS의 Root CA를 사용하지 않을까?

client 프로그램마다 어떤 Root CA를 사용하는지는 각각 다르다.

 

client 프로그램이 자체적으로 Root CA를 가지고 있는 경우도 있고 (ex, 크롬 브라우저)

사파리나 Mac OS 빌트인된 python3는 OS의 Root CA를 사용한다.

 

이전 애플은 자신들의 OS에서 특정 기관의 Root CA를 제거하겠다고 공지한 적이 있었고

OS의 인증서를 사용하는 사례는 위에서 말했듯이 많이있고, 스마트폰앱으로 넓히면 더욱 많다.

(스마트폰은 애플,구글과 같은 플랫폼 회사가 제공하는 라이브러리를 사용한 개발이 많기 때문에 더욱 강하게 OS의 Root CA에 종속적일 수 있다.)

이때 많은 IT 회사들이 급하게 서버측의 인증서를 교체하는 사단이 났다. 

 

이렇듯 운영체제 플랫폼에 종속적인 CA를 사용하지 않고

개발자가 패키지(certifi)의 버전을 선택하여, 원하는 CA를 사용할 수 있도록 하는 방안으로 보인다.

 

 

댓글