PKCS12, PEM, DER: VPN 클라이언트가 실제로 필요한 것
포장 방식이 클라이언트가 신원을 가져오는지를 결정하지, 그 신원을 신뢰하는지를 결정하지 않는다. PKCS12, PEM, DER을 비교하고 나서 검증하라.
인증서 논의에는 두 가지 주장이 돌아다닌다. 첫째는 .p12가 강한 선택이고 .pem은 느슨한 선택이라는 것, 둘째는 읽을 수 있으니 텍스트 파일을 보내도 안전하다는 것이다. 어느 쪽도 일 분의 점검을 견디지 못한다.
PKCS12, PEM, DER은 인증서 객체가 어떻게 포장되고 인코딩되는지를 설명한다. 상대가 누구인지, 발급자를 신뢰하는지, 파일에 암호가 필요한지는 결정하지 않는다.
클라이언트가 무엇을 받아들이는지는 호환성 문제다. 파일이 무엇을 담고 있는지는 신원 문제다. 이 둘을 분리하면 대부분의 가져오기 오류가 더 이상 미스터리가 아니다.
세 가지 컨테이너, 하나의 객체 모델
실제로 가진 것부터 시작하라. 인증서는 공개 키에 대한 서명된 진술이다. 개인 키는 그 키를 통제한다는 것을 증명하는 별도의 자료다. 체인은 인증서를 클라이언트가 신뢰하는 루트에 연결하는 발급자 집합이다. 셋 모두 포장 방식과 무관하게 존재한다.
PKCS12 파일은 보통 .p12 또는 .pfx이며, RFC 7292에 정의된 컨테이너로 개인 키, 하나 이상의 인증서, 체인을 암호로 보호된 하나의 덩어리에 담을 수 있고 신원을 이름 붙이는 별칭도 가진다. PEM 파일은 RFC 7468에 정의된 텍스트 틀로, BEGIN과 END 줄 사이의 base64 본문이며 한 파일에 여러 블록을 연속으로 담을 수 있다. DER은 같은 ASN.1 구조의 바이너리 인코딩이고, PEM 본문은 말 그대로 base64로 감싼 DER이다.
마지막 문장은 기억할 가치가 있다. PEM을 DER로 변환하면 바뀌는 것은 틀이지 내용이 아니며, 되돌리면 원래 바이트가 나온다.
| Container | Can hold | Does not decide |
|---|---|---|
| PKCS12 (.p12, .pfx) | Key, leaf certificate, chain, alias, password | Which root the client trusts |
| PEM (.pem, .crt, .cer) | Any number of base64 blocks | Whether the private key is present |
| DER (.der, .cer) | One binary structure | The object type, whatever the extension says |
인코딩은 암호화가 아니다
base64는 전송용 문자 집합이다. 아무것도 숨기지 않으며 누구나 셸에서 PEM 블록을 디코딩할 수 있다. DER 파일은 바이너리이며 똑같이 공개적이다. 어느 속성도 보호가 아니고, 어느 것도 암호가 있다는 것을 암시하지 않는다.
진짜 보호는 키 자료에 있다. PKCS12 번들은 암호에서 파생한 키로 내용을 암호화할 수 있다. PEM 개인 키는 오래된 헤더 블록으로 암호화하거나 PKCS#8 EncryptedPrivateKeyInfo에 보관할 수 있다. 인증서 단독으로는 아무 비밀도 담지 않으므로, 인증서만 내보낸 내보내기는 어떤 형식으로 감싸도 클라이언트가 인증하지 못하게 만든다.
확장자는 힌트이지 계약이 아니다. .cer와 .crt로 끝나는 파일은 두 인코딩 모두에 나타나고, 장비는 DER 인증서를 .pem 이름으로 충분히 자주 내보내므로 접미사로 추측하면 반나절을 잃는다. 대신 첫 바이트를 읽어라. PEM 파일은 BEGIN 줄로 시작하고, DER은 바깥 구조가 SEQUENCE이므로 0x30으로 시작한다.
가져오기가 실제로 실패하는 이유
클라이언트 오류 메시지는 여러 다른 문제를 잘못된 인증서에 관한 한 문장으로 압축하므로 원인별로 나눠야 한다. 가장 흔한 것은 인코딩 불일치로, PEM을 파싱하는 클라이언트는 DER 파일에서 파싱 오류를 반환하며 해결책은 새 인증서가 아니라 변환이다. 두 번째는 키 누락으로, 인증서는 정상인데 신원을 증명할 수 없다. 세 번째는 체인으로, 중간 인증서가 없는 번들은 가져오기 대화상자에서는 올바르게 보이지만 핸드셰이크에서 실패한다.
암호화 매개변수가 네 번째 원인이며, 겉보기에는 잘못된 암호와 똑같다. 오래된 장비는 PKCS12 파일을 RC2-40과 3DES로 내보냈고, OpenSSL 3은 레거시 공급자를 불러오지 않으면 이를 거부한다. 장비에서 AES-256-CBC와 PBKDF2로 다시 내보내는 것이 항구적인 해결책이다. 파일 하나 때문에 클라이언트 컴퓨터에서 오래된 암호를 영구히 켜 두는 것은 나쁜 거래다.
다섯 번째는 모호성이다. 키 두 개와 인증서 두 개를 담은 컨테이너나 CERTIFICATE 블록 세 개를 담은 PEM 파일은 클라이언트가 선택하게 만들고, 그 선택은 대개 그저 첫 항목이다. 터널이 잘못된 신원으로 올라올 때 보통 이것이 이유다.
변환하기 전에 점검하라
비밀을 출력하지 않고 구조를 읽어라. openssl pkcs12 -in bundle.p12 -info -noout 은 암호화 알고리즘과 별칭을 알려 주고 키와 인증서 출력을 억제하며, 플래그는 openssl-pkcs12 설명서 에 문서화되어 있다. -noout을 빼고 -nodes를 더하는 것은 보관할 파일을 만들 때의 일이지 잠깐 보기 위한 것이 아니다. 개인 키를 평문으로 쓰기 때문이다.
인증서의 경우 openssl x509 -in cert.pem -noout -subject -issuer -dates 은 가져오기 대화상자가 답하지 못하는 것에 답하고, -ext subjectAltName 을 더하면 인증서가 실제로 유효한 이름이 표시된다. 파일이 DER이면 -inform DER 을 더하라. 이 하나의 플래그가 대부분의 unable-to-load 오류를 해결한다.
키를 인증서와 짝지어 확인하는 것은 기계적인 작업이며, 어떤 가져오기 전에도 해 둘 가치가 있다.
openssl x509 -in cert.pem -noout -pubkey | openssl sha256openssl pkey -in key.pem -pubout | openssl sha256
다이제스트가 같으면 그 짝은 함께 맞는 것이다. 다르면 키가 잘못된 것이며, 어떤 형식 변환도 그것을 바꾸지 않는다.
형식이 결코 바꾸지 않는 것
신뢰 결정은 컨테이너 위에서 이루어진다. 클라이언트는 저장소의 루트까지 체인을 따라가고, 유효 기간 창을 확인하고, 클라이언트 인증을 위한 확장 키 사용을 확인하며, 서버 쪽에서는 접속한 이름이 인증서에 있는지 확인한다. 이 단계 중 어느 것도 파일 확장자를 읽지 않는다.
그래서 오류가 사라질 때까지 파일 이름을 바꾸는 것이 이렇게 확실한 시간 낭비 방법이 된다. 가져오기는 성공하고 신원은 여전히 틀리며, 실패는 핸드셰이크에서 덜 도움이 되는 메시지와 함께 다시 나타난다. 이 년 전에 내보낸 번들이 깔끔하게 가져와지고도 거부되는 이유도 같다. 만료일이 파일 이름이 아니라 인증서 안에서 이동했기 때문이다.
재작업을 피하는 순서
클라이언트 문서에서 어떤 인코딩을 받아들이고 클라이언트 신원에 어떤 컨테이너를 기대하는지 물은 다음, 건네받은 것을 보라. 클라이언트가 가져올 수 있는 신원 하나를 원하고 당신에게 따로 된 PEM 파일이 있다면, 당신이 통제하는 암호로 한 번 번들을 만들고 원본은 보관하라. PEM을 원하면 키와 체인을 추출하고, 그런 다음 팀이 이미 사용하는 자격 증명 처리 절차로 중간 파일을 제거하라.
가져온 뒤에는 대화상자가 아니라 신원을 검증하라. 주체, 발급자, 만료, 그리고 클라이언트가 제시할 이름이다. 목록의 친근한 이름은 라벨이지 검증된 신원이 아니다. 그 뒤 터널이 실패하면 포장이 아니라 인증이나 라우팅을 디버깅하는 것이며, 훨씬 짧은 대화다.