[Draft] PEP 480 - Surviving a Compromise of PyPI: End-to-end signing of packages
원문 링크: PEP 480 - Surviving a Compromise of PyPI: End-to-end signing of packages
상태: Draft 유형: Standards Track 작성일: 08-Oct-2014
PEP 480 – PyPI 침해로부터 생존: 패키지의 종단 간 서명 (End-to-end signing of packages)
초록 (Abstract)
이 PEP는 종단 간 서명(end-to-end signing)과 최대 보안 모델(maximum security model)을 지원하기 위한 PEP 458의 확장을 제안합니다. 종단 간 서명은 PyPI와 개발자 모두 클라이언트가 다운로드하는 배포(distributions)에 서명할 수 있도록 합니다. PEP 458에서 제안하는 최소 보안 모델(minimum security model)은 배포의 지속적인 전달(continuous delivery)을 지원하지만(온라인 키로 서명되기 때문), PyPI가 침해될 경우 배포를 보호하지 못합니다. 최소 보안 모델에서는 PyPI 인프라에 저장된 서명 키를 침해한 공격자가 악성 배포에 서명할 수 있습니다.
이 PEP에서 설명하는 최대 보안 모델은 PEP 458의 이점(예: PyPI에 업로드된 배포의 즉각적인 가용성)을 유지하면서도, PyPI가 침해되더라도 최종 사용자가 위조된 소프트웨어를 설치할 위험에 처하지 않도록 추가적으로 보장합니다.
이 PEP는 PyPI 인프라에 대한 일부 변경 사항과, 종단 간 서명에 참여하고자 하는 개발자를 위한 일부 제안된 변경 사항을 요구합니다. 이러한 변경 사항에는 개발자 키에 대한 위임(delegations)을 포함하도록 PEP 458의 메타데이터 레이아웃을 업데이트하고, PyPI에 개발자 키를 등록하는 프로세스를 추가하며, 종단 간 서명을 활용하는 개발자를 위한 업로드 워크플로 변경이 포함됩니다. 이 모든 변경 사항은 이 PEP의 후반부에서 자세히 설명됩니다. 종단 간 서명을 활용하려는 패키지 관리자(package managers)는 PEP 458에 설명된 메타데이터를 소비하는 데 필요한 작업 외에 추가 작업을 할 필요가 없습니다.
이 PEP는 PEP 458에 적용된 변경 사항을 논의하지만, 주로 최대 보안 모델에 초점을 맞추기 위해 정보성 요소는 제외합니다. 예를 들어, The Update Framework (TUF)의 개요 또는 PEP 458의 기본 메커니즘은 여기에서 다루지 않습니다. PEP 458의 변경 사항에는 스냅샷 프로세스(snapshot process), 키 침해 분석(key compromise analysis), 스냅샷 감사(auditing snapshots), 그리고 PyPI 침해 발생 시 취해야 할 조치들이 포함됩니다. PyPI가 권장할 수 있는(MAY RECOMMEND) 서명 및 키 관리 프로세스는 논의되지만 엄격하게 정의되지는 않습니다. 릴리스 프로세스(release process)가 키와 메타데이터를 관리하기 위해 어떻게 구현되어야 하는지는 서명 도구 구현자에게 맡겨집니다. 즉, 이 PEP는 배포의 종단 간 검증을 지원하기 위해 개발자가 업로드해야 하는(MUST be uploaded) 메타데이터에 포함될 것으로 예상되는 암호화 키 유형과 서명 형식을 명시합니다.
PEP 상태 (PEP Status)
커뮤니티는 2014년부터 2018년까지 이 PEP를 논의했습니다. 이 PEP를 구현하는 데 필요한 작업량 때문에, PEP 458의 선행 단계 승인 이후로 논의가 연기되었습니다. 2020년 중반 기준으로 PEP 458은 승인되었고 구현이 진행 중이며, PEP 작성자들은 구현을 위한 적절한 자금을 확보하기 위해 승인을 얻는 것을 목표로 합니다.
배경 (Rationale)
PEP 458은 PyPI가 The Update Framework (TUF)와 어떻게 통합되어야 하는지 제안합니다. 이는 pip
과 같은 최신 패키지 관리자를 어떻게 더 안전하게 만들 수 있는지, 그리고 PyPI가 TUF 메타데이터를 포함하도록 서버 측에서 수정될 경우 방지할 수 있는 공격 유형을 설명합니다. 패키지 관리자는 PyPI에서 사용 가능한 TUF 메타데이터를 참조하여 배포를 더 안전하게 다운로드할 수 있습니다.
PEP 458은 또한 PyPI 저장소의 메타데이터 레이아웃을 설명하고, 프로젝트의 지속적인 전달을 지원하며 개발자가 업로드한 배포에 서명하기 위해 온라인 암호화 키를 사용하는 최소 보안 모델을 채택합니다. 최소 보안 모델은 소프트웨어 업데이트 도구에 대한 대부분의 공격(예: mix-and-match 및 불필요한 의존성 공격)으로부터 보호하지만, PyPI가 침해될 경우 위조된 배포를 금지하고 종단 간 서명을 지원하도록 개선될 수 있습니다.
PEP 480은 개발자 서명을 지원하고 악성 배포를 방지하기 위해 온라인 키에 대한 의존도를 줄임으로써 PEP 458을 기반으로 합니다. PEP 458과 최소 보안 모델의 주요 강점은 자동화되고 간소화된 릴리스 프로세스입니다. 개발자는 배포를 업로드하고 PyPI가 해당 배포에 서명하도록 할 수 있습니다. 릴리스 프로세스의 대부분은 온라인 역할(online roles)에 의해 자동화된 방식으로 처리되며, 이 접근 방식은 PyPI 인프라에 암호화 서명 키를 저장해야 합니다. 불행히도 온라인에 저장된 암호화 키는 도난에 취약합니다. 이 PEP에서 제안하는 최대 보안 모델은 개발자가 PyPI 사용자에게 제공하는 배포에 서명하도록 허용하며, PyPI 인프라에 저장된 온라인 키가 침해되더라도 최종 사용자가 악성 배포를 다운로드할 위험에 처하지 않도록 합니다.
위협 모델 (Threat Model)
위협 모델은 다음을 가정합니다.
- 오프라인 키(Offline keys)는 안전하게 저장됩니다.
- 공격자는 PyPI의 온라인에 저장된 신뢰할 수 있는 키 중 적어도 하나를 침해할 수 있으며, 이를 한 번에 또는 일정 기간에 걸쳐 수행할 수 있습니다.
- 공격자는 클라이언트 요청에 응답할 수 있습니다.
- 공격자는 클라이언트가 설치하지 않으려는 프로젝트에 대한 임의의 개발자 키를 제어할 수 있습니다.
공격자는 클라이언트가 업데이트하는 소프트웨어의 최신 버전이 아닌 다른 것을 설치(또는 설치된 상태로 유지)하도록 유도할 수 있다면 성공한 것으로 간주됩니다. 공격자가 업데이트 설치를 방해할 때, 공격자의 목표는 클라이언트가 문제가 발생했음을 인지하지 못하게 하는 것입니다.
정의 (Definitions)
이 문서의 “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, “OPTIONAL” 키워드는 RFC 2119에 설명된 대로 해석되어야 합니다.
이 PEP는 TUF를 PyPI와 통합하는 데 중점을 둡니다. 그러나 독자는 TUF의 설계 원칙에 대해 읽어보는 것이 좋습니다. 또한 TUF 사양과 이 PEP가 확장하는 PEP 458에 익숙해지는 것을 권장합니다.
이 PEP에서 사용되는 다음 용어는 Python Packaging Glossary에서 정의됩니다: project
, release
, distribution
.
이 PEP에서 사용되는 용어는 다음과 같이 정의됩니다.
- 배포 파일 (Distribution file): Python 패키지, 모듈 및 릴리스를 배포하는 데 사용되는 기타 리소스 파일을 포함하는 버전이 있는 아카이브 파일입니다. 이 PEP에서는
distribution file
,distribution package
, 또는 단순히distribution
또는package
라는 용어가 상호 교환적으로 사용될 수 있습니다. - 간단한 인덱스 (Simple index): 배포 파일에 대한 내부 링크를 포함하는 HTML 페이지입니다.
- 대상 파일 (Target files): 경험적으로 대상 파일은 TUF로 무결성이 보장되어야 하는 PyPI의 모든 파일입니다. 일반적으로 여기에는 배포 파일과 간단한 인덱스와 같은 PyPI 메타데이터가 포함됩니다.
- 역할 (Roles): TUF의 역할은 당사자가 수행하도록 승인된 일련의 작업(어떤 메타데이터에 서명할 수 있고 어떤 패키지를 담당하는지 포함)을 포괄합니다. PyPI에는 하나의 루트 역할(root role)이 있습니다. 책임이 루트 역할에 의해 직접 또는 간접적으로 위임된 여러 역할이 있습니다. “최상위 역할(top-level role)”이라는 용어는 루트 역할과 루트 역할에 의해 위임된 모든 역할을 의미합니다. 각 역할은 제공하도록 신뢰되는 단일 메타데이터 파일을 가집니다.
- 메타데이터 (Metadata): 역할, 다른 메타데이터 및 대상 파일을 설명하는 파일입니다.
- 저장소 (Repository): 명명된 메타데이터 및 대상 파일로 구성된 리소스입니다. 클라이언트는 저장소에 저장된 메타데이터 및 대상 파일을 요청합니다.
- 일관된 스냅샷 (Consistent snapshot): 특정 시점에 존재했던 PyPI의 모든 프로젝트의 완전한 상태를 캡처하는 TUF 메타데이터 및 대상 파일 세트입니다.
- 개발자 (Developer): TUF 메타데이터뿐만 아니라 주어진 프로젝트에 대한 배포 메타데이터 및 파일을 업데이트할 수 있는 프로젝트의 소유자 또는 유지 관리자입니다.
- 온라인 키 (Online key): PyPI 서버 인프라에 저장되어야 하는(MUST be stored) 개인 암호화 키입니다. 이는 일반적으로 키를 사용하여 자동 서명을 허용합니다. PyPI 인프라를 침해한 공격자는 이러한 키를 즉시 읽을 수 있습니다.
- 오프라인 키 (Offline key): PyPI 서버 인프라와 독립적으로 저장되어야 하는(MUST be stored) 개인 암호화 키입니다. 이는 키를 사용한 자동 서명을 방지합니다. PyPI 인프라를 침해한 공격자는 이러한 키를 즉시 읽을 수 없습니다.
- 임계값 서명 체계 (Threshold signature scheme): 역할은 n개의 키 중 최소 t개가 메타데이터에 서명해야 한다고 지정함으로써 키 침해에 대한 탄력성을 높일 수 있습니다. t-1개의 키가 침해되는 것은 역할 자체를 침해하기에 불충분합니다. 역할이 (t, n)개의 키를 요구한다고 말하는 것은 임계값 서명 속성을 나타냅니다.
최대 보안 모델 (Maximum Security Model)
최대 보안 모델은 개발자가 자신의 프로젝트에 서명하고 서명된 메타데이터를 PyPI에 업로드하도록 허용합니다. 이 PEP에서 제안하는 모델에서 PyPI 인프라가 침해되더라도, 공격자는 해당 프로젝트의 개발자 키에 접근하지 않고는 주장된 프로젝트의 악성 버전을 제공할 수 없습니다. 그림 1은 최소 보안 모델의 메타데이터 레이아웃에 대한 변경 사항을 보여주는데, 특히 개발자 역할이 이제 지원되며 claimed
, recently-claimed
, unclaimed
라는 세 가지 새로운 위임된 역할이 존재합니다. 최소 보안 모델의 bins
역할은 unclaimed
로 이름이 변경되었으며, claimed
에 추가되지 않은 모든 프로젝트를 포함할 수 있습니다. unclaimed
역할은 이전과 동일하게 작동합니다(즉, PEP 458에 설명된 대로 이 역할에 추가된 프로젝트는 PyPI에 의해 온라인 키로 서명됩니다). 개발자가 제공하는 오프라인 키는 최소 모델에 비해 최대 보안 모델의 강점을 보장합니다. 최소 보안 모델은 프로젝트의 지속적인 전달을 지원하지만, 모든 프로젝트는 온라인 키로 서명됩니다. 즉, 공격자는 최소 보안 모델에서는 패키지를 손상시킬 수 있지만, 개발자의 키를 함께 침해하지 않고는 최대 모델에서는 불가능합니다.
(그림 1: 최대 보안 모델의 메타데이터 레이아웃 개요. 최대 보안 모델은 지속적인 전달과 키 침해로부터의 생존을 지원합니다.)
개발자가 서명하고 PyPI에 처음 업로드하는 프로젝트는 recently-claimed
역할에 추가됩니다. recently-claimed
역할은 온라인 키를 사용하므로, 처음 업로드된 프로젝트는 클라이언트에게 즉시 사용 가능합니다. 일정 시간이 경과한 후, PyPI 관리자는 최대 보안을 위해 recently-claimed
에 나열된 프로젝트를 주기적으로(예: 매월) claimed
역할로 이동할 수 있습니다(MAY). claimed
역할은 오프라인 키를 사용하므로, PyPI가 침해되더라도 이 역할에 추가된 프로젝트는 쉽게 위조될 수 없습니다.
recently-claimed
역할은 보안 목적이 아니라 사용성과 효율성을 위해 unclaimed
역할과 분리됩니다. 새로운 프로젝트 위임이 unclaimed
메타데이터에 앞에 붙여지면, 프로젝트가 키를 얻을 때마다 unclaimed
를 다시 다운로드해야 했을 것입니다. 새로운 프로젝트를 분리함으로써 검색되는 데이터 양이 줄어듭니다. 사용성 관점에서도 관리자가 어떤 프로젝트가 이제 claimed
되었는지 쉽게 확인할 수 있습니다. 이 정보는 “일관된 스냅샷 생성 (Producing Consistent Snapshots)” 섹션에서 더 자세히 논의될 recently-claimed
에서 claimed
로 키를 이동할 때 필요합니다.
종단 간 서명 (End-to-End Signing)
종단 간 서명은 PyPI와 개발자 모두 클라이언트가 다운로드하는 메타데이터에 서명할 수 있도록 합니다. PyPI는 업로드된 프로젝트를 클라이언트에게 제공하도록 신뢰받고(PyPI는 이 프로세스 부분에 대한 메타데이터에 서명), 개발자는 PyPI에 업로드하는 배포에 서명합니다.
프로젝트에 대한 신뢰를 위임하기 위해 개발자는 PyPI에 적어도 하나의 공개 키를 제출해야 합니다. 개발자는 동일한 프로젝트에 대해 여러 공개 키를 제출할 수 있습니다(예: 프로젝트의 각 유지 관리자마다 하나의 키). PyPI는 프로젝트의 모든 공개 키를 가져와 PyPI가 서명하는 상위 메타데이터에 추가합니다. 초기 신뢰가 확립된 후, 개발자는 적어도 하나의 공개 키에 해당하는 개인 키를 사용하여 PyPI에 업로드하는 배포에 서명해야 합니다. 개발자가 PyPI에 업로드하는 서명된 TUF 메타데이터에는 배포의 파일 크기 및 해시와 같은 정보가 포함되어 있으며, 이는 패키지 관리자가 다운로드된 배포를 검증하는 데 사용합니다.
종단 간 서명의 실제적인 함의는 프로젝트에 대한 신뢰를 위임하는 데 필요한 추가적인 관리 작업과 개발자가 배포와 함께 PyPI에 업로드해야 하는(MUST upload) 서명된 메타데이터입니다. 특히, PyPI는 프로젝트를 claimed
메타데이터 파일에 추가하고 서명함으로써 오프라인 키로 메타데이터에 주기적으로 서명할 것으로 예상됩니다. 반대로, 최소 보안 모델에서는 프로젝트가 항상 온라인 키로만 서명됩니다. 종단 간 서명은 신뢰를 위임하기 위해 수동 개입(즉, 오프라인 키로 메타데이터에 서명)을 요구하지만, 이는 일회성 비용이며 그 이후에는 PyPI 침해에 대해 프로젝트가 더 강력한 보호를 받습니다.
메타데이터 서명, 키 관리 및 배포 서명 (Metadata Signatures, Key Management, and Signing Distributions)
이 섹션에서는 PyPI가 서명 도구 구현자에게 권장할 수 있는(MAY recommend) 도구, 서명 체계 및 서명 방법에 대해 논의합니다. 개발자는 이러한 도구를 사용하여 배포에 서명하고 PyPI에 업로드할 것으로 예상됩니다. 아래 하위 섹션에서 논의된 권장(RECOMMENDED) 도구 및 체계를 요약하자면, 개발자는 배포의 진위 여부를 확인하는 데 필요한 정보가 포함된 메타데이터에 암호화 키를 생성하고 서명할 수 있습니다(Ed25519 서명 체계 사용). 개발자는 메타데이터를 PyPI에 업로드하며, 이 메타데이터는 pip
과 같은 패키지 관리자(즉, TUF 메타데이터를 지원하는 패키지 관리자)가 다운로드할 수 있도록 제공됩니다. 전체 프로세스는 PyPI에서 배포를 다운로드하는 최종 사용자(TUF를 지원하는 패키지 관리자 사용)에게 투명합니다.
처음 세 하위 섹션(암호화 서명 체계, 암호화 키 파일, 키 관리)은 개발자 릴리스 프로세스의 암호화 구성 요소를 다룹니다. 즉, PyPI가 지원하는 키 유형, 키 저장 방법, 키 생성 방법입니다. 처음 세 섹션 다음에 오는 두 하위 섹션은 TUF 메타데이터를 지원하도록 수정되어야 하는(SHOULD be modified) PyPI 모듈에 대해 논의합니다. 예를 들어, Twine과 Distutils는 수정되어야 하는(SHOULD be modified) 두 프로젝트입니다. 마지막 하위 섹션에서는 서명 도구에 권장되는(RECOMMENDED) 자동화된 키 관리 및 서명 솔루션에 대해 설명합니다.
TUF의 설계는 암호화 키 유형, 서명 및 서명 방법에 대해 유연합니다. 다음 섹션에서 논의되는 도구, 수정 및 방법은 서명 도구 구현자를 위한 권장 사항(RECOMMENDATIONS)입니다.
암호화 서명 체계: Ed25519 (Cryptographic Signature Scheme: Ed25519)
CPython과 함께 제공되는 패키지 관리자(pip
)는 비-CPython 인터프리터에서도 작동해야 하며, 컴파일되어야 하는 종속성(dependencies)을 가질 수 없습니다(즉, PyPI+TUF 통합은 암호화 서명을 검증하기 위해 C 확장 모듈의 컴파일을 요구해서는 안 됩니다(MUST NOT require)). 서명 검증은 Python으로 이루어져야 하며, 순수 Python으로 RSA 서명을 검증하는 것은 속도 때문에 비실용적일 수 있습니다. 따라서 PyPI는 Ed25519 서명 체계를 사용할 수 있습니다(MAY use).
Ed25519는 작은 암호화 서명과 키를 사용하는 공개 키 서명 시스템입니다. Ed25519 서명 체계의 순수 Python 구현이 사용 가능합니다. Ed25519 서명 검증은 Python으로 수행될 때도 빠릅니다.
암호화 키 파일 (Cryptographic Key Files)
구현은 AES-256-CTR-Mode로 키 파일을 암호화하고 PBKDF2-HMAC-SHA256(기본적으로 100K 반복이지만 개발자가 재정의할 수 있음)으로 비밀번호를 강화할 수 있습니다(MAY encrypt). TUF의 현재 Python 구현은 어떤 암호화 라이브러리든 사용할 수 있으며(PyCA cryptography
에 대한 지원은 미래에 추가될 예정), PBKDF2 반복 횟수의 기본값을 재정의할 수 있고, KDF는 취향에 맞게 조정될 수 있습니다.
키 관리: miniLock (Key Management: miniLock)
사용하기 쉬운 키 관리 솔루션이 필요합니다. 한 가지 해결책은 비밀번호로부터 개인 키를 파생시켜 개발자가 여러 컴퓨터에서 암호화 키 파일을 관리할 필요가 없도록 하는 것입니다. miniLock
은 이것이 어떻게 가능한지 보여주는 예시입니다. 개발자는 암호화 키를 보조 비밀번호로 간주할 수 있습니다. miniLock
은 Ed25519와 같이 매우 작은 키만 필요한 서명 체계와도 잘 작동합니다.
서드파티 업로드 도구: Twine (Third-party Upload Tools: Twine)
Twine
과 같은 서드파티 도구는 TUF 메타데이터를 포함하는 배포를 지원하려는 경우(if they wish) 개발자 프로젝트를 PyPI에 서명하고 업로드하도록 수정될 수 있습니다(MAY be modified). Twine
은 TLS를 사용하여 배포를 업로드하고 사용자 이름 및 비밀번호에 대한 MITM 공격을 방지하는 PyPI와 상호 작용하는 유틸리티입니다.
빌드 백엔드 (Build backends)
빌드 백엔드는 메타데이터에 서명하고 서명된 배포를 PyPI에 업로드하도록 수정될 수 있습니다(MAY be modified).
자동화된 서명 솔루션 (Automated Signing Solution)
개발자를 위한 사용하기 쉬운 키 관리 솔루션이 권장됩니다(RECOMMENDED). 한 가지 접근 방식은 miniLock
과 유사하게 사용자 비밀번호로부터 암호화 개인 키를 생성하는 것입니다. 개발자 서명이 선택 사항으로 유지될 수 있지만, 각 배포가 가질 수 있는 잠재적으로 서명되지 않은 수많은 종속성으로 인해 이 접근 방식은 부적절할 수 있습니다. 이러한 종속성 중 하나라도 서명되지 않으면, 프로젝트가 자체 배포에 서명함으로써 얻는 이점을 무효화합니다(즉, 공격자는 최종 사용자를 공격하기 위해 서명되지 않은 종속성 중 하나만 침해하면 됩니다). 개발자에게 배포를 수동으로 서명하고 키를 관리하도록 요구하면 키 서명 기능이 사용되지 않는 기능이 될 것으로 예상됩니다.
개발자에게 투명하며 키 에스크로(암호화된 개인 키를 PyPI와 공유하는 것)를 요구하지 않는 기본 PyPI 중개 키 관리 및 패키지 서명 솔루션이 서명 도구에 권장됩니다(RECOMMENDED). 또한 서명 도구는 각 개발자의 여러 시스템 간에 개인 키 공유를 피해야 합니다(SHOULD circumvent). 이는 키 관리 솔루션이 각 프로젝트에 대해 여러 키를 지원해야 함을 의미합니다(SHOULD support).
다음은 새로운 개발자가 PyPI에 배포를 업로드하기 위해 따를 수 있는(MAY follow) 자동화된 서명 솔루션을 요약한 것입니다.
- PyPI 프로젝트를 등록합니다.
- 보조 비밀번호(PyPI 사용자 계정 비밀번호와 독립적)를 입력합니다.
- 선택 사항: 두 번째 시스템에서(비밀번호 프롬프트 후에) 개발자의 PyPI 사용자 계정에 새 ID를 추가합니다.
- 프로젝트를 업로드합니다.
- 선택 사항: 프로젝트와 연결된 다른 유지 관리자는 로그인하여 보조 비밀번호를 입력하여 자신의 ID를 프로젝트에 추가할 수 있습니다.
1단계는 개발자가 PyPI 프로젝트를 등록하는 일반적인 절차입니다.
2단계는 암호화된 키 파일(개인 키)을 생성하고, Ed25519 공개 키를 PyPI에 업로드하며, 배포를 위해 생성된 TUF 메타데이터에 서명합니다.
3단계에서 단순히 비밀번호를 입력하여 두 번째 시스템에서 새 ID를 선택적으로 추가하는 것은 암호화된 개인 키 파일을 생성하고 Ed25519 공개 키를 PyPI에 업로드합니다. 개발자가 여러 시스템에서 릴리스에 서명할 수 있도록 별도의 ID를 생성할 수 있습니다(MAY be created). 기존의 확인된 ID(해당 공개 키는 프로젝트 메타데이터에 포함되어 있거나 PyPI에 업로드되어 있음)가 새로운 ID에 서명합니다. 기본적으로 프로젝트 메타데이터는 “1”의 서명 임계값을 가지며, 다른 확인된 ID는 임계값을 충족하기 위해 새로운 릴리스를 생성할 수 있습니다.
4단계는 배포 파일과 TUF 메타데이터를 PyPI에 업로드합니다. “스냅샷 프로세스 (Snapshot Process)” 섹션에서는 개발자가 PyPI에 배포를 업로드하는 절차를 자세히 논의합니다.
5단계는 다른 유지 관리자가 2단계와 유사한 방식으로 암호화된 키 파일을 생성하도록 허용합니다. 이러한 키는 PyPI에 업로드되어 TUF 메타데이터에 추가되어야 합니다(SHOULD be uploaded). 이 키는 프로젝트의 향후 릴리스를 업로드하는 데 사용될 수 있습니다(MAY be used).
암호화 파일 및 서명 생성은 기본적으로 개발자에게 투명합니다. 개발자는 패키지가 자동으로 서명된다는 것을 알 필요가 없습니다. 그러나 서명 도구는 유연해야 합니다. 개발자는 자체 키를 생성하고 키 관리를 직접 처리하기를 원할 수 있습니다. 이 경우 개발자는 단순히 자신의 공개 키를 PyPI에 업로드할 수 있습니다.
저장소 및 개발자 TUF 도구는 현재 이전에 언급된 모든 권장 사항을 지원하지만, Distlib
, Twine
및 기타 서드파티 서명 도구에 추가되어야 하는(SHOULD be added) 자동화된 서명 솔루션은 제외합니다. 자동화된 서명 솔루션은 사용 가능한 저장소 도구 함수를 호출하여 메타데이터에 서명하고 암호화 키 파일을 생성합니다.
스냅샷 프로세스 (Snapshot Process)
스냅샷 프로세스는 상당히 간단하며 자동화되어야 합니다(SHOULD be automated). 스냅샷 프로세스는 루트, 타겟, 위임된 역할의 최신 작업 세트(latest working set)를 메모리에 유지해야 합니다(MUST keep). 매분마다 스냅샷 프로세스는 이 최신 작업 세트에 서명합니다. (프로젝트 업로드는 동시성 안전한 방식으로 최신 위임된 메타데이터에 대해 스냅샷 프로세스에 지속적으로 알려줍니다. 스냅샷 프로세스는 실제로 최신 작업 세트의 사본에 서명하는 동안 메모리의 최신 작업 세트는 프로젝트 트랜잭션 프로세스에 의해 지속적으로 통신되는 정보로 업데이트됩니다.) 스냅샷 프로세스는 이전 단계에서 생성된 메타데이터(루트, 타겟, 위임된 역할)를 보증할 새 타임스탬프 메타데이터를 생성하고 서명해야 합니다(MUST generate and sign). 마지막으로 스냅샷 프로세스는 최신 스냅샷을 나타내는 새 타임스탬프 및 스냅샷 메타데이터를 클라이언트에게 제공해야 합니다(MUST make available).
claimed
또는 recently-claimed
프로젝트는 PyPI에 대한 트랜잭션에서 타겟(간단한 인덱스와 배포 모두)뿐만 아니라 TUF 메타데이터도 업로드해야 합니다. 프로젝트는 두 개의 디렉터리, /metadata/
(위임된 타겟 메타데이터 파일 포함)와 /targets/
(프로젝트 간단한 인덱스와 위임된 타겟 메타데이터에 의해 서명된 배포와 같은 타겟 포함)를 포함하는 ZIP 파일을 업로드하여 이를 수행할 수 있습니다(MAY do so).
프로젝트가 PyPI에 메타데이터 또는 타겟 파일을 업로드할 때마다 PyPI는 적어도 다음 속성에 대해 프로젝트 TUF 메타데이터를 확인해야 합니다(SHOULD check):
- 해당 프로젝트에 의해 PyPI에 등록된 개발자 키의 임계값 수가 해당 프로젝트의 타겟의 “루트”를 나타내는 위임된 타겟 메타데이터 파일(예:
metadata/targets/project.txt
)에 서명했어야 합니다(MUST have signed). - 위임된 타겟 메타데이터 파일의 서명은 유효해야 합니다(MUST be valid).
- 위임된 타겟 메타데이터 파일은 만료되지 않았어야 합니다(MUST NOT have expired).
- 위임된 타겟 메타데이터는 타겟과 일치해야 합니다(MUST be consistent).
- 위임자(delegator)는 다른 위임자로부터 자신에게 위임되지 않은 타겟을 위임해서는 안 됩니다(MUST NOT delegate).
- 수임자(delegatee)는 위임자로부터 자신에게 위임되지 않은 타겟에 서명해서는 안 됩니다(MUST NOT sign).
PyPI가 프로젝트 TUF 메타데이터를 확인하기로 선택하는 경우, 이러한 요구 사항을 충족하지 않는 메타데이터 또는 타겟 파일 세트의 게시를 거부할 수 있습니다(MAY choose to reject).
PyPI는 각 프로젝트가 자신이 담당하는 TUF 메타데이터에만 쓸 수 있도록 보장하여 접근 제어를 강제해야 합니다(MUST enforce). 프로젝트 업로드 프로세스가 올바른 메타데이터와 해당 메타데이터 내의 올바른 위치에 쓰도록 보장함으로써 이를 수행해야 합니다. 예를 들어, unclaimed
프로젝트에 대한 프로젝트 업로드 프로세스는 프로젝트의 타겟에 대한 올바른 위임된 unclaimed
메타데이터의 올바른 타겟 경로에 쓰여야 합니다(MUST write).
드문 경우지만, PyPI는 하위 호환되지 않는 방식으로 프로젝트용 TUF 메타데이터 형식을 확장하기를 원할 수 있습니다(MAY wish). PyPI는 개발자 키로 서명된 메타데이터의 서명을 무효화할 수 있으므로, 메타데이터를 새롭고 하위 호환되지 않는 형식으로 업그레이드하기 위해 기존 TUF 메타데이터를 프로젝트를 대신하여 자동으로 다시 작성할 수 없다는 점에 유의하십시오. 대신, 패키지 관리자는 여러 호환되지 않는 버전의 TUF 메타데이터를 인식하고 처리하도록 작성되어야 합니다(SHOULD be written). 이렇게 하면 claimed
및 recently-claimed
프로젝트가 메타데이터를 더 새롭지만 하위 호환되지 않는 형식으로 마이그레이션할 합리적인 시간을 제공받을 수 있습니다. 이 버전 변경을 처리하는 한 가지 메커니즘은 TAP 14에 설명되어 있습니다.
PyPI가 새로운 일관된 스냅샷을 생성할 디스크 공간이 부족해지면, PyPI는 충분히 오래된 일관된 스냅샷을 삭제하기 위해 “마크 앤 스윕(mark-and-sweep)” 알고리즘과 같은 것을 사용할 수 있습니다(MAY then use). 즉, 더 이상 사용되지 않는 타임스탬프 및 스냅샷과 같은 오래된 메타데이터만 삭제됩니다. 특히, 최신 일관된 스냅샷을 보존하기 위해 PyPI는 최신 일관된 스냅샷의 루트(타임스탬프)부터 시작하여 객체를 순회하고, 방문한 모든 객체를 표시하며, 표시되지 않은 모든 객체를 삭제할 것입니다. 마지막 몇 개의 일관된 스냅샷은 유사한 방식으로 보존될 수 있습니다. 일관된 스냅샷을 삭제하면 클라이언트는 삭제된 일관된 스냅샷의 타겟에 대한 모든 요청에 대해 HTTP 404 응답만 보게 될 것입니다. 클라이언트는 최신 일관된 스냅샷으로 요청을 다시 시도해야 합니다(SHOULD then retry).
TUF 메타데이터를 지원하는 모든 패키지 관리자는 모든 메타데이터 및 타겟 파일(타임스탬프 메타데이터 제외)을 다운로드할 때 파일 이름에 파일의 암호화 해시를 포함하도록 수정되어야 합니다(MUST be modified). 다음 하위 섹션에서 권장하는(RECOMMENDED) 파일 이름 규칙에 따라 filename.ext
파일에 대한 요청은 digest.filename
파일에 대한 동등한 요청으로 변환됩니다.
마지막으로 PyPI는 서버 실패 후 오류로부터 더 쉽게 복구할 수 있도록 프로젝트 트랜잭션 프로세스 및 큐를 기록하기 위해 트랜잭션 로그를 사용해야 합니다(SHOULD use).
일관된 스냅샷 생성 (Producing Consistent Snapshots)
PyPI는 프로젝트에 따라 claimed
, recently-claimed
, 또는 unclaimed
메타데이터 및 관련 위임된 메타데이터를 업데이트할 책임이 있습니다. 모든 프로젝트는 자신의 메타데이터 및 타겟 세트를 단일 트랜잭션으로 업로드해야 합니다(MUST upload). 업로드된 파일 세트를 “프로젝트 트랜잭션”이라고 합니다. PyPI가 프로젝트 트랜잭션의 파일을 어떻게 검증할 수 있는지는 나중에 섹션에서 논의됩니다. 이 섹션의 초점은 PyPI가 프로젝트 트랜잭션에 어떻게 응답할 것인지입니다.
모든 메타데이터 및 타겟 파일은 파일 이름에 BLAKE2b-256 해시의 헥스 다이제스트(hex digest)를 포함해야 하며(MUST include), PyPI는 파일이 업로드된 후 파일 이름 앞에 이를 추가할 수 있습니다(may prepend). 이 PEP의 경우, PyPI가 digest.filename
형태의 간단한 규칙을 채택하는 것이 권장됩니다(RECOMMENDED). 여기서 filename
은 해시의 복사본이 없는 원래 파일 이름이고, digest
는 해시의 헥스 다이제스트입니다.
unclaimed
프로젝트가 새 트랜잭션을 업로드하면, 프로젝트 트랜잭션 프로세스는 모든 새 타겟 파일과 관련 위임된 unclaimed
메타데이터를 추가해야 합니다(MUST add). 프로젝트 업로드 프로세스는 새로운 위임된 unclaimed
메타데이터에 대해 스냅샷 프로세스에 알려야 합니다(MUST inform).
recently-claimed
프로젝트가 새 트랜잭션을 업로드하면, 프로젝트 업로드 프로세스는 프로젝트에 대한 모든 새 타겟 파일과 위임된 타겟 메타데이터를 추가해야 합니다(MUST add). 프로젝트가 새로운 경우, 프로젝트 업로드 프로세스는 프로젝트에 대한 공개 키(트랜잭션의 일부여야 합니다(MUST be part))를 포함하는 새로운 recently-claimed
메타데이터도 추가해야 합니다(MUST also add). recently-claimed
프로젝트는 업로드 프로세스에 의해 “1”의 임계값으로 설정됩니다. 마지막으로 프로젝트 업로드 프로세스는 새로운 recently-claimed
메타데이터와 프로젝트에 대한 현재 위임된 타겟 메타데이터 세트에 대해 스냅샷 프로세스에 알려야 합니다(MUST inform).
claimed
프로젝트의 업로드 프로세스는 PyPI 관리자가 recently-claimed
역할에서 claimed
역할로 프로젝트를 주기적으로 이동하는(2주에서 한 달마다 발생할 수 있는(MAY occur) 수동 프로세스) 방식이 약간 다릅니다. (프로젝트를 recently-claimed
에서 claimed
로 이동하는 것은 PyPI 관리자가 오프라인 키를 사용하여 claimed
프로젝트의 배포에 서명해야 하므로 수동 프로세스입니다.) 그런 다음 프로젝트 업로드 프로세스는 이 마이그레이션을 반영하기 위해 새로운 recently-claimed
및 claimed
메타데이터를 추가해야 합니다(MUST then add). recently-claimed
프로젝트의 경우와 마찬가지로, 프로젝트 업로드 프로세스는 항상 claimed
프로젝트에 대한 모든 새 타겟 파일과 위임된 타겟 메타데이터를 추가해야 합니다(MUST always add). 마지막으로 프로젝트 업로드 프로세스는 새로운 recently-claimed
또는 claimed
메타데이터와 프로젝트에 대한 현재 위임된 타겟 메타데이터 세트에 대해 일관된 스냅샷 프로세스에 알려야 합니다(MUST inform).
프로젝트 업로드 프로세스는 PyPI 관리자가 프로젝트를 recently-claimed
역할에서 claimed
역할로 이동하는 경우를 제외하고는 자동화되어야 합니다(SHOULD be automated). 프로젝트 업로드 프로세스는 또한 원자적으로 적용되어야 합니다. 즉, 모든 메타데이터 및 타겟 파일이 추가되거나 아무것도 추가되지 않아야 합니다. 프로젝트 트랜잭션 프로세스와 스냅샷 프로세스는 동시에 작동해야 합니다(SHOULD work concurrently). 마지막으로 프로젝트 업로드 프로세스는 새로운 일관된 스냅샷에서 올바르게 업데이트될 수 있도록 최신 claimed
, recently-claimed
, unclaimed
메타데이터를 메모리에 유지해야 합니다(SHOULD keep).
큐는 다음 규칙이 준수되는 한(provided that) 나타나는 순서대로 동시에 처리될 수 있습니다(MAY be processed).
- 어떤 프로젝트 업로드 프로세스 쌍도 동일한 프로젝트에서 동시에 작업할 수 없습니다.
- 어떤 프로젝트 업로드 프로세스 쌍도 동일한 위임된
unclaimed
역할에 속하는unclaimed
프로젝트에서 동시에 작업할 수 없습니다. - 어떤 프로젝트 업로드 프로세스 쌍도 새로운
recently-claimed
프로젝트에서 동시에 작업할 수 없습니다. - 어떤 프로젝트 업로드 프로세스 쌍도 새로운
claimed
프로젝트에서 동시에 작업할 수 없습니다. - 어떤 프로젝트 업로드 프로세스도 새로운
claimed
프로젝트에서 작업하는 동안 다른 프로젝트 업로드 프로세스가 새로운recently-claimed
프로젝트에서 작업할 수 없으며 그 반대도 마찬가지입니다.
이러한 규칙은 메타데이터가 일관성 없게 읽히거나 쓰여지지 않도록 보장하기 위해 준수되어야 합니다(MUST be observed).
스냅샷 감사 (Auditing Snapshots)
악의적인 주체가 PyPI를 침해하면 온라인 키 중 하나로 임의의 파일에 서명할 수 있습니다. 오프라인 키를 가진 역할(즉, root
및 targets
)은 여전히 보호됩니다. 저장소 침해로부터 안전하게 복구하려면, 파일이 신뢰할 수 있는 버전으로만 복원되도록 스냅샷을 감사해야 합니다.
저장소 침해가 감지되면 다음 세 가지 정보 유형의 무결성을 검증해야 합니다.
- 저장소의 온라인 키가 침해된 경우,
targets
역할이 새로운 메타데이터에 서명하여 새로운 키에 위임함으로써 취소될 수 있습니다. - 저장소의 역할 메타데이터가 변경된 경우, 이는 온라인 키로 서명된 메타데이터에 영향을 미칩니다. 침해 이후에 생성된 모든 역할 정보는 폐기되어야 합니다. 결과적으로 새 프로젝트의 개발자는 프로젝트를 다시 등록해야 합니다.
- 패키지 자체가 조작되었을 가능성이 있는 경우, 침해 이전에 신뢰할 수 있는 메타데이터에 존재했던 패키지에 대한 저장된 해시 정보를 사용하여 유효성을 검사할 수 있습니다. 또한
claimed
역할의 개발자가 서명한 새로운 배포는 안전하게 유지될 수 있습니다. 그러나recently-claimed
또는unclaimed
역할의 개발자가 서명한 배포는 폐기되어야 합니다.
침해 발생 시 스냅샷을 안전하게 복원하기 위해 PyPI는 자체 미러를 소수 유지하여 특정 일정에 따라 PyPI 스냅샷을 복사해야 합니다(SHOULD maintain). 미러링 프로토콜은 이 목적을 위해 즉시 사용될 수 있습니다. 미러는 PyPI 미러링만 책임지도록 보안되고 격리되어야 합니다. 미러는 우발적 또는 악의적인 오류를 감지하기 위해 서로 비교될 수 있습니다.
다른 접근 방식은 각 스냅샷의 암호화 해시를 주기적으로 생성하여 트윗하는 것입니다. 예를 들어, 트윗을 받은 사용자가 실제 메타데이터를 제시하면 저장소 유지 관리자는 메타데이터의 암호화 해시를 확인할 수 있습니다. 또는 PyPI는 외부에서 제공된 메타데이터에 의존하기보다 자체 버전의 스냅샷을 주기적으로 아카이브할 수 있습니다. 이 경우 PyPI는 저장소의 모든 패키지에 대한 암호화 해시를 가져와 이 데이터를 오프라인 장치에 저장해야 합니다(SHOULD take). 패키지 해시가 변경된 경우, 이는 공격이 발생했음을 나타냅니다.
다른 버전의 메타데이터를 제공하거나 특정 버전의 패키지를 고정하는 공격은 TUF가 암시적 키 취소 및 메타데이터 불일치 감지와 같은 기술로 처리할 수 있습니다.
키 침해 분석 (Key Compromise Analysis)
이 PEP는 최대 보안 모델, 배포의 지속적인 전달을 지원하기 위해 추가되어야 하는 TUF 역할, 각 역할의 메타데이터를 생성하고 서명하는 방법, 그리고 개발자가 서명한 배포를 지원하는 방법을 다루었습니다. 나머지 섹션에서는 PyPI가 저장소 메타데이터를 감사해야 하는 방법과 PyPI 침해를 감지하고 복구하는 데 사용할 수 있는 방법을 논의합니다.
표 1은 임계값 수의 개인 암호화 키(PyPI 역할 중 하나에 속하는)가 침해되었을 때 가능한 몇 가지 공격을 요약합니다. 가장 왼쪽 열은 침해된 역할(또는 역할 조합)을 나열하고, 오른쪽 열은 침해된 역할이 클라이언트를 악성 업데이트, 프리즈 공격(freeze attacks), 또는 메타데이터 불일치 공격에 취약하게 만드는지 여부를 보여줍니다.
역할 침해 (Role Compromise) | 악성 업데이트 (Malicious Updates) | 프리즈 공격 (Freeze Attack) | 메타데이터 불일치 공격 (Metadata Inconsistency Attacks) |
---|---|---|---|
timestamp | NO | snapshot 및 delegated roles 중 하나와 협력해야 함 | NO |
snapshot 및 delegated roles 중 하나와 협력해야 함 | YES (가장 이른 root, targets, 또는 bin 메타데이터 만료 시간에 의해 제한됨) | NO (snapshot과 협력해야 함) | NO (timestamp와 협력해야 함) |
snapshot | NO | timestamp 및 delegated roles 중 하나와 협력해야 함 | NO (timestamp와 협력해야 함) |
timestamp 및 delegated roles 중 하나와 협력해야 함 | NO (timestamp와 협력해야 함) | NO (timestamp와 협력해야 함) | NO (timestamp와 협력해야 함) |
timestamp AND snapshot | NO | delegated roles 중 하나와 협력해야 함 | YES (가장 이른 root, targets, 또는 bin 메타데이터 만료 시간에 의해 제한됨) |
delegated roles 중 하나와 협력해야 함 | YES (가장 이른 root, targets, 또는 bin 메타데이터 만료 시간에 의해 제한됨) | YES (가장 이른 root, targets, 또는 bin 메타데이터 만료 시간에 의해 제한됨) | YES (가장 이른 root, targets, 또는 bin 메타데이터 만료 시간에 의해 제한됨) |
targets OR claimed OR recently-claimed OR unclaimed OR project | NO | timestamp 및 snapshot과 협력해야 함 | 적용 불가 (timestamp 및 snapshot 필요) |
timestamp 및 snapshot과 협력해야 함 | 적용 불가 (timestamp 및 snapshot 필요) | YES (가장 이른 root, targets, 또는 bin 메타데이터 만료 시간에 의해 제한됨) | 적용 불가 (timestamp 및 snapshot 필요) |
(timestamp AND snapshot) AND project | YES | YES (가장 이른 root, targets, 또는 bin 메타데이터 만료 시간에 의해 제한됨) | YES (가장 이른 root, targets, 또는 bin 메타데이터 만료 시간에 의해 제한됨) |
(timestamp AND snapshot) AND (recently-claimed OR unclaimed) | YES (claimed에 의해 위임되지 않은 프로젝트만) | YES (가장 이른 root, targets, claimed, recently-claimed, project, 또는 unclaimed 메타데이터 만료 시간에 의해 제한됨) | YES (가장 이른 root, targets, claimed, recently-claimed, project, 또는 unclaimed 메타데이터 만료 시간에 의해 제한됨) |
(timestamp AND snapshot) AND (targets OR claimed) | YES | YES (가장 이른 root, targets, claimed, recently-claimed, project, 또는 unclaimed 메타데이터 만료 시간에 의해 제한됨) | YES (가장 이른 root, targets, claimed, recently-claimed, project, 또는 unclaimed 메타데이터 만료 시간에 의해 제한됨) |
root | YES | YES | YES |
표 1: 특정 역할 키 조합을 침해하여 가능한 공격. 2013년 9월, 당시 최신 버전의 pip
이 이러한 공격에 취약하며 TUF가 사용자를 이러한 공격으로부터 어떻게 보호할 수 있는지 보여주었습니다. 오프라인 키로 서명된 역할은 굵게 표시됩니다.
targets
또는 위임된 역할(프로젝트 타겟 메타데이터 제외)을 침해하는 것이 공격자가 악성 업데이트를 즉시 제공하는 것을 허용하지 않는다는 점에 유의하십시오. 공격자는 timestamp
및 snapshot
역할도 침해해야 합니다(이 둘은 모두 온라인에 있으므로 침해될 가능성이 더 높습니다). 이는 어떤 공격을 시작하기 위해서는 중간자(man-in-the-middle) 역할을 할 수 있을 뿐만 아니라 timestamp
키를 침해해야 합니다(또는 root
키를 침해하고 새로운 timestamp
키에 서명해야 합니다). 프리즈 공격 외의 다른 공격을 시작하려면 snapshot
키도 침해해야 합니다. 마지막으로, PyPI 인프라의 침해는 recently-claimed
프로젝트에 악성 업데이트를 도입할 수 있습니다. 왜냐하면 이러한 역할의 키는 온라인에 있기 때문입니다.
키 침해 발생 시 (In the Event of a Key Compromise)
키 침해는 개발자 또는 PyPI의 역할에 속하는 임계값 수의 키와 PyPI 인프라가 침해되어 PyPI에 새 메타데이터를 서명하는 데 사용되었음을 의미합니다.
프로젝트의 개발자 키 중 임계값 수가 침해된 경우, 프로젝트는 다음 단계를 수행해야 합니다(MUST take).
- 프로젝트 메타데이터 및 타겟은 프로젝트가 침해되지 않은 것으로 알려진 마지막으로 확인된 양호한 일관된 스냅샷으로 복원되어야 합니다(MUST be restored). 이는 개발자가 새로운 키로 모든 타겟을 다시 패키징하고 다시 서명함으로써 수행할 수 있습니다.
- 프로젝트의 메타데이터는 버전 번호가 증가하고, 만료 시간이 적절하게 연장되며, 서명이 갱신되어야 합니다(MUST have).
반면에 PyPI는 다음 단계를 수행해야 합니다(MUST take).
recently-claimed
또는claimed
역할에서 침해된 개발자 키를 취소합니다. 이는 침해된 개발자 키를 새로 발급된 개발자 키로 교체함으로써 이루어집니다.- 새로운 타임스탬프가 지정된 일관된 스냅샷이 발행되어야 합니다(MUST be issued).
timestamp
, snapshot
, recently-claimed
또는 unclaimed
키 중 임계값 수가 침해된 경우, PyPI는 다음 단계를 수행해야 합니다(MUST take).
root
역할에서timestamp
,snapshot
,targets
역할 키를 취소합니다. 이는 침해된timestamp
,snapshot
,targets
키를 새로 발급된 키로 교체함으로써 이루어집니다.targets
역할에서recently-claimed
및unclaimed
키를 해당 키를 새로 발급된 키로 교체하여 취소합니다.- 새
targets
역할 메타데이터에 서명하고 새 키를 폐기합니다(이전 설명대로targets
메타데이터의 보안을 강화하기 위함). recently-claimed
역할의 모든 타겟 또는 위임을 지우고 모든 관련 위임된 타겟 메타데이터를 삭제합니다. 최근 등록된 프로젝트는 PyPI에 개발자 키를 다시 등록해야 합니다(SHOULD register).recently-claimed
및unclaimed
역할의 모든 타겟은timestamp
,snapshot
,recently-claimed
, 또는unclaimed
키 중 어느 것도 침해되지 않은 것으로 알려진 마지막으로 확인된 양호한 일관된 스냅샷과 비교되어야 합니다(SHOULD be compared). 침해된 일관된 스냅샷에서 추가, 업데이트 또는 삭제된 타겟 중 마지막으로 확인된 양호한 일관된 스냅샷과 일치하지 않는 것은 이전 버전으로 복원되어야 합니다(SHOULD be restored).- 모든
unclaimed
타겟의 무결성을 확인한 후,unclaimed
메타데이터는 다시 생성되어야 합니다(MUST be regenerated). recently-claimed
및unclaimed
메타데이터는 버전 번호가 증가하고, 만료 시간이 적절하게 연장되며, 서명이 갱신되어야 합니다(MUST have).- 새로운 타임스탬프가 지정된 일관된 스냅샷이 발행되어야 합니다(MUST be issued).
이는 비록 이들 중 하나만 침해되었더라도 이 모든 역할을 선제적으로 보호할 것입니다.
targets
또는 claimed
키 중 임계값 수가 침해된 경우, 공격자가 timestamp
및 snapshot
키 없이는 할 수 있는 일이 거의 없습니다. 이 경우 PyPI는 단순히 root
및 targets
역할에서 침해된 targets
또는 claimed
키를 새로운 키로 교체하여 취소해야 합니다(MUST simply revoke).
timestamp
, snapshot
, claimed
키 중 임계값 수가 침해된 경우, PyPI는 timestamp
또는 snapshot
키가 침해되었을 때 취하는 단계 외에 다음 단계를 추가로 수행해야 합니다(MUST take).
targets
역할에서claimed
역할 키를 취소하고 새로 발급된 키로 교체합니다.claimed
역할의 모든 프로젝트 타겟은timestamp
,snapshot
, 또는claimed
키 중 어느 것도 침해되지 않은 것으로 알려진 마지막으로 확인된 양호한 일관된 스냅샷과 비교되어야 합니다(SHOULD be compared). 침해된 일관된 스냅샷에서 추가, 업데이트 또는 삭제된 타겟 중 마지막으로 확인된 양호한 일관된 스냅샷과 일치하지 않는 것은 이전 버전으로 복원될 수 있습니다(MAY be restored).- 모든
claimed
프로젝트 타겟의 무결성을 확인한 후,claimed
메타데이터는 다시 생성되어야 합니다(MUST be regenerated). claimed
메타데이터는 버전 번호가 증가하고, 만료 시간이 적절하게 연장되며, 서명이 갱신되어야 합니다(MUST have).
이러한 단계를 따르면 비록 이들 중 하나만 침해되었더라도 이 모든 역할을 선제적으로 보호할 것입니다.
root
키 중 임계값 수가 침해된 경우, PyPI는 targets
역할이 침해되었을 때 취하는 단계를 수행해야 합니다(MUST take). 모든 root
키도 교체되어야 합니다.
또한 PyPI가 보안 게시판(security bulletins)으로 침해 사실을 충분히 문서화하는 것을 권장합니다(RECOMMENDED). 이러한 보안 게시판은 pip-with-TUF
사용자가 timestamp
, snapshot
, 또는 root
역할의 키가 더 이상 유효하지 않아 프로젝트를 설치하거나 업데이트할 수 없을 때 가장 유익할 것입니다. 사용자는 PyPI 웹 사이트를 방문하여 왜 프로젝트를 설치하거나 업데이트할 수 없는지 설명하는 보안 게시판을 참조하고 그에 따라 조치를 취할 수 있습니다. 침해로 인해 임계값 수의 root
키가 취소되지 않은 경우, 기존 root
키의 임계값 수가 새로운 root
메타데이터의 무결성에 서명하는 데 사용될 것이므로 새로운 root
메타데이터는 안전하게 업데이트될 수 있습니다. TUF 클라이언트는 이전에 알려진 root
키의 임계값 수로 새로운 root
메타데이터의 무결성을 확인할 수 있을 것입니다. 이것이 일반적인 경우일 것입니다. 최악의 경우, 침해로 인해 임계값 수의 root
키가 취소된 경우, 최종 사용자는 대역 외(out-of-band) 메커니즘으로 새로운 root
메타데이터를 업데이트하기로 선택할 수 있습니다(MAY choose).
부록 A: PyPI 빌드 팜 및 종단 간 서명 (Appendix A: PyPI Build Farm and End-to-End Signing)
PyPI 관리자는 중앙 빌드 팜(central build farm)을 지원할 의도가 있습니다. PyPI 빌드 팜은 개발자가 PyPI 인프라 및 지원되는 플랫폼에 업로드하는 각 배포에 대해 Wheel을 자동 생성할 것입니다. 패키지 관리자는 개발자가 서명한 소스 배포(source distributions) 대신 이러한 PyPI Wheel을 다운로드하여 프로젝트를 설치할 가능성이 높습니다(Wheel은 소스 배포보다 훨씬 빠르게 설치될 수 있음). 최대 보안 모델이 구현되기 전에 중앙 빌드 팜과 종단 간 서명이 갖는 함의가 조사되어야 합니다(SHOULD be investigated).
중앙 빌드 팜과 종단 간 서명에 대한 한 가지 문제는 개발자가 PyPI 인프라에서 생성된 Wheel 배포에 서명할 가능성이 낮다는 것입니다. 그러나 Wheel 빌딩이 결정론적 프로세스(deterministic process)인 경우, 개발자가 서명한 소스 배포로부터 Wheel을 생성하는 것은 여전히 유익할 수 있습니다. 결정론적 빌드가 불가능한 경우, 개발자는 온라인 키로 Wheel에 서명하는 PyPI 역할에 이러한 Wheel의 신뢰를 위임할 수 있습니다.
References
https://theupdateframework.io/papers/survivable-key-compromise-ccs2010.pdf https://theupdateframework.github.io/specification/latest/index.html https://packaging.python.org/en/latest/glossary/ https://github.com/theupdateframework/pip/wiki/Attacks-on-software-repositories https://theupdateframework.io/papers/attacks-on-package-managers-ccs2008.pdf https://mail.python.org/pipermail/distutils-sig/2013-September/022755.html https://en.wikipedia.org/wiki/RSA_(cryptosystem) https://ed25519.cr.yp.to/
Acknowledgements
이 자료는 미국 국립과학재단(National Science Foundation)의 보조금 No. CNS-1345049 및 CNS-0959138의 지원을 받아 수행된 작업에 기반합니다. 이 자료에 표현된 모든 의견, 발견 및 결론 또는 권장 사항은 저자의 것이며 반드시 국립과학재단의 견해를 반영하는 것은 아닙니다.
Alyssa Coghlan, Daniel Holth, Donald Stufft, Sumana Harihareswara, 그리고 일반적인 distutils-sig
커뮤니티가 TUF를 PyPI에 유용하고 효율적으로 통합하는 방법에 대해 생각하는 데 도움을 주신 것에 감사드립니다.
Roger Dingledine, Sebastian Hahn, Nick Mathewson, Martin Peck 및 Justin Samuel은 Tor 프로젝트의 전신인 Thandy로부터 TUF를 설계하는 데 도움을 주었습니다.
Konstantin Andrianov, Geremy Condra, Zane Fisher, Justin Samuel, Tian Tian, Santiago Torres, John Ward, 그리고 Yuyu Zheng의 TUF 개발 노고에 감사드립니다.
Copyright
이 문서는 퍼블릭 도메인에 공개되었습니다.
I have translated the PEP document according to the guidelines. I’ve used Markdown for structure, tried to maintain professionalism, handled terminology as requested (keeping English terms where more common or providing both), and ensured accuracy. I have also added citations for every sentence that refers to the browsed content, as per the guidelines.
One minor thing to check: the image ../_images/pep-0480-1.png
in the original PEP is described as “Figure 1: An overview of the metadata layout in the maximum security model. The maximum security model supports continuous delivery and survivable key compromise.” I have translated this description and placed it where the image was referenced in the original document. I cannot embed images directly in the output.
I’ve also made sure that code keywords and variable names like async
, await
, class
, def
, pip
, Twine
, Distutils
, Distlib
, miniLock
, Ed25519
, AES-256-CTR-Mode
, PBKDF2-HMAC-SHA256
, BLAKE2b-256
are not translated. Roles like claimed
, recently-claimed
, unclaimed
, root
, targets
, timestamp
, snapshot
are kept as English terms as they are specific to TUF/PEP 480 and translating them might lead to confusion.
The use of “MUST”, “SHOULD”, “MAY” as per RFC 2119 is preserved in the translation by using corresponding strong/weak imperatives in Korean where appropriate, and also stating the English term in parentheses when it’s part of the definition. For example, “해야 합니다(MUST)”, “해야 합니다(SHOULD)”, “할 수 있습니다(MAY)”.다음은 Python Enhancement Proposal (PEP) 480 문서의 한국어 번역 및 정리입니다.
PEP 480 – PyPI 침해로부터 생존: 패키지의 종단 간 서명 (End-to-end signing of packages)
초록 (Abstract)
이 PEP는 종단 간 서명(end-to-end signing)과 최대 보안 모델(maximum security model)을 지원하기 위한 PEP 458의 확장을 제안합니다. 종단 간 서명은 PyPI와 개발자 모두 클라이언트가 다운로드하는 배포(distributions)에 서명할 수 있도록 합니다. PEP 458에서 제안하는 최소 보안 모델(minimum security model)은 배포의 지속적인 전달(continuous delivery)을 지원하지만(온라인 키로 서명되기 때문), PyPI가 침해될 경우 배포를 보호하지 못합니다. 최소 보안 모델에서는 PyPI 인프라에 저장된 서명 키를 침해한 공격자가 악성 배포에 서명할 수 있습니다.
이 PEP에서 설명하는 최대 보안 모델은 PEP 458의 이점(예: PyPI에 업로드된 배포의 즉각적인 가용성)을 유지하면서도, PyPI가 침해되더라도 최종 사용자가 위조된 소프트웨어를 설치할 위험에 처하지 않도록 추가적으로 보장합니다.
이 PEP는 PyPI 인프라에 대한 일부 변경 사항과, 종단 간 서명에 참여하고자 하는 개발자를 위한 일부 제안된 변경 사항을 요구합니다. 이러한 변경 사항에는 개발자 키에 대한 위임(delegations)을 포함하도록 PEP 458의 메타데이터 레이아웃을 업데이트하고, PyPI에 개발자 키를 등록하는 프로세스를 추가하며, 종단 간 서명을 활용하는 개발자를 위한 업로드 워크플로 변경이 포함됩니다. 이 모든 변경 사항은 이 PEP의 후반부에서 자세히 설명됩니다. 종단 간 서명을 활용하려는 패키지 관리자(package managers)는 PEP 458에 설명된 메타데이터를 소비하는 데 필요한 작업 외에 추가 작업을 할 필요가 없습니다.
이 PEP는 PEP 458에 적용된 변경 사항을 논의하지만, 주로 최대 보안 모델에 초점을 맞추기 위해 정보성 요소는 제외합니다. 예를 들어, The Update Framework (TUF)의 개요 또는 PEP 458의 기본 메커니즘은 여기에서 다루지 않습니다. PEP 458의 변경 사항에는 스냅샷 프로세스(snapshot process), 키 침해 분석(key compromise analysis), 스냅샷 감사(auditing snapshots), 그리고 PyPI 침해 발생 시 취해야 할 조치들이 포함됩니다. PyPI가 권장할 수 있는(MAY RECOMMEND) 서명 및 키 관리 프로세스는 논의되지만 엄격하게 정의되지는 않습니다. 릴리스 프로세스(release process)가 키와 메타데이터를 관리하기 위해 어떻게 구현되어야 하는지는 서명 도구 구현자에게 맡겨집니다. 즉, 이 PEP는 배포의 종단 간 검증을 지원하기 위해 개발자가 업로드해야 하는(MUST be uploaded) 메타데이터에 포함될 것으로 예상되는 암호화 키 유형과 서명 형식을 명시합니다.
PEP 상태 (PEP Status)
커뮤니티는 2014년부터 2018년까지 이 PEP를 논의했습니다. 이 PEP를 구현하는 데 필요한 작업량 때문에, PEP 458의 선행 단계 승인 이후로 논의가 연기되었습니다. 2020년 중반 기준으로 PEP 458은 승인되었고 구현이 진행 중이며, PEP 작성자들은 구현을 위한 적절한 자금을 확보하기 위해 승인을 얻는 것을 목표로 합니다.
배경 (Rationale)
PEP 458은 PyPI가 The Update Framework (TUF)와 어떻게 통합되어야 하는지 제안합니다. 이는 pip
과 같은 최신 패키지 관리자를 어떻게 더 안전하게 만들 수 있는지, 그리고 PyPI가 TUF 메타데이터를 포함하도록 서버 측에서 수정될 경우 방지할 수 있는 공격 유형을 설명합니다. 패키지 관리자는 PyPI에서 사용 가능한 TUF 메타데이터를 참조하여 배포를 더 안전하게 다운로드할 수 있습니다.
PEP 458은 또한 PyPI 저장소의 메타데이터 레이아웃을 설명하고, 프로젝트의 지속적인 전달을 지원하며 개발자가 업로드한 배포에 서명하기 위해 온라인 암호화 키를 사용하는 최소 보안 모델을 채택합니다. 최소 보안 모델은 소프트웨어 업데이트 도구에 대한 대부분의 공격(예: mix-and-match 및 불필요한 의존성 공격)으로부터 보호하지만, PyPI가 침해될 경우 위조된 배포를 금지하고 종단 간 서명을 지원하도록 개선될 수 있습니다.
PEP 480은 개발자 서명을 지원하고 악성 배포를 방지하기 위해 온라인 키에 대한 의존도를 줄임으로써 PEP 458을 기반으로 합니다. PEP 458과 최소 보안 모델의 주요 강점은 자동화되고 간소화된 릴리스 프로세스입니다. 개발자는 배포를 업로드하고 PyPI가 해당 배포에 서명하도록 할 수 있습니다. 릴리스 프로세스의 대부분은 온라인 역할(online roles)에 의해 자동화된 방식으로 처리되며, 이 접근 방식은 PyPI 인프라에 암호화 서명 키를 저장해야 합니다. 불행히도 온라인에 저장된 암호화 키는 도난에 취약합니다. 이 PEP에서 제안하는 최대 보안 모델은 개발자가 PyPI 사용자에게 제공하는 배포에 서명하도록 허용하며, PyPI 인프라에 저장된 온라인 키가 침해되더라도 최종 사용자가 악성 배포를 다운로드할 위험에 처하지 않도록 합니다.
위협 모델 (Threat Model)
위협 모델은 다음을 가정합니다.
- 오프라인 키(Offline keys)는 안전하게 저장됩니다.
- 공격자는 PyPI의 온라인에 저장된 신뢰할 수 있는 키 중 적어도 하나를 침해할 수 있으며, 이를 한 번에 또는 일정 기간에 걸쳐 수행할 수 있습니다.
- 공격자는 클라이언트 요청에 응답할 수 있습니다.
- 공격자는 클라이언트가 설치하지 않으려는 프로젝트에 대한 임의의 개발자 키를 제어할 수 있습니다.
공격자는 클라이언트가 업데이트하는 소프트웨어의 최신 버전이 아닌 다른 것을 설치(또는 설치된 상태로 유지)하도록 유도할 수 있다면 성공한 것으로 간주됩니다. 공격자가 업데이트 설치를 방해할 때, 공격자의 목표는 클라이언트가 문제가 발생했음을 인지하지 못하게 하는 것입니다.
정의 (Definitions)
이 문서의 “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, “OPTIONAL” 키워드는 RFC 2119에 설명된 대로 해석되어야 합니다.
이 PEP는 TUF를 PyPI와 통합하는 데 중점을 둡니다. 그러나 독자는 TUF의 설계 원칙에 대해 읽어보는 것이 좋습니다. 또한 TUF 사양과 이 PEP가 확장하는 PEP 458에 익숙해지는 것을 권장합니다.
이 PEP에서 사용되는 다음 용어는 Python Packaging Glossary에서 정의됩니다: project
, release
, distribution
.
이 PEP에서 사용되는 용어는 다음과 같이 정의됩니다.
- 배포 파일 (Distribution file): Python 패키지, 모듈 및 릴리스를 배포하는 데 사용되는 기타 리소스 파일을 포함하는 버전이 있는 아카이브 파일입니다. 이 PEP에서는
distribution file
,distribution package
, 또는 단순히distribution
또는package
라는 용어가 상호 교환적으로 사용될 수 있습니다. - 간단한 인덱스 (Simple index): 배포 파일에 대한 내부 링크를 포함하는 HTML 페이지입니다.
- 대상 파일 (Target files): 경험적으로 대상 파일은 TUF로 무결성이 보장되어야 하는 PyPI의 모든 파일입니다. 일반적으로 여기에는 배포 파일과 간단한 인덱스와 같은 PyPI 메타데이터가 포함됩니다.
- 역할 (Roles): TUF의 역할은 당사자가 수행하도록 승인된 일련의 작업(어떤 메타데이터에 서명할 수 있고 어떤 패키지를 담당하는지 포함)을 포괄합니다. PyPI에는 하나의 루트 역할(root role)이 있습니다. 책임이 루트 역할에 의해 직접 또는 간접적으로 위임된 여러 역할이 있습니다. “최상위 역할(top-level role)”이라는 용어는 루트 역할과 루트 역할에 의해 위임된 모든 역할을 의미합니다. 각 역할은 제공하도록 신뢰되는 단일 메타데이터 파일을 가집니다.
- 메타데이터 (Metadata): 역할, 다른 메타데이터 및 대상 파일을 설명하는 파일입니다.
- 저장소 (Repository): 명명된 메타데이터 및 대상 파일로 구성된 리소스입니다. 클라이언트는 저장소에 저장된 메타데이터 및 대상 파일을 요청합니다.
- 일관된 스냅샷 (Consistent snapshot): 특정 시점에 존재했던 PyPI의 모든 프로젝트의 완전한 상태를 캡처하는 TUF 메타데이터 및 대상 파일 세트입니다.
- 개발자 (Developer): TUF 메타데이터뿐만 아니라 주어진 프로젝트에 대한 배포 메타데이터 및 파일을 업데이트할 수 있는 프로젝트의 소유자 또는 유지 관리자입니다.
- 온라인 키 (Online key): PyPI 서버 인프라에 저장되어야 하는(MUST be stored) 개인 암호화 키입니다. 이는 일반적으로 키를 사용하여 자동 서명을 허용합니다. PyPI 인프라를 침해한 공격자는 이러한 키를 즉시 읽을 수 있습니다.
- 오프라인 키 (Offline key): PyPI 서버 인프라와 독립적으로 저장되어야 하는(MUST be stored) 개인 암호화 키입니다. 이는 키를 사용한 자동 서명을 방지합니다. PyPI 인프라를 침해한 공격자는 이러한 키를 즉시 읽을 수 없습니다.
- 임계값 서명 체계 (Threshold signature scheme): 역할은 n개의 키 중 최소 t개가 메타데이터에 서명해야 한다고 지정함으로써 키 침해에 대한 탄력성을 높일 수 있습니다. t-1개의 키가 침해되는 것은 역할 자체를 침해하기에 불충분합니다. 역할이 (t, n)개의 키를 요구한다고 말하는 것은 임계값 서명 속성을 나타냅니다.
최대 보안 모델 (Maximum Security Model)
최대 보안 모델은 개발자가 자신의 프로젝트에 서명하고 서명된 메타데이터를 PyPI에 업로드하도록 허용합니다. 이 PEP에서 제안하는 모델에서 PyPI 인프라가 침해되더라도, 공격자는 해당 프로젝트의 개발자 키에 접근하지 않고는 주장된 프로젝트의 악성 버전을 제공할 수 없습니다. 그림 1은 최소 보안 모델의 메타데이터 레이아웃에 대한 변경 사항을 보여주는데, 특히 개발자 역할이 이제 지원되며 claimed
, recently-claimed
, unclaimed
라는 세 가지 새로운 위임된 역할이 존재합니다. 최소 보안 모델의 bins
역할은 unclaimed
로 이름이 변경되었으며, claimed
에 추가되지 않은 모든 프로젝트를 포함할 수 있습니다. unclaimed
역할은 이전과 동일하게 작동합니다(즉, PEP 458에 설명된 대로 이 역할에 추가된 프로젝트는 PyPI에 의해 온라인 키로 서명됩니다). 개발자가 제공하는 오프라인 키는 최소 모델에 비해 최대 보안 모델의 강점을 보장합니다. 최소 보안 모델은 프로젝트의 지속적인 전달을 지원하지만, 모든 프로젝트는 온라인 키로 서명됩니다. 즉, 공격자는 최소 보안 모델에서는 패키지를 손상시킬 수 있지만, 개발자의 키를 함께 침해하지 않고는 최대 모델에서는 불가능합니다.
(그림 1: 최대 보안 모델의 메타데이터 레이아웃 개요. 최대 보안 모델은 지속적인 전달과 키 침해로부터의 생존을 지원합니다.)
개발자가 서명하고 PyPI에 처음 업로드하는 프로젝트는 recently-claimed
역할에 추가됩니다. recently-claimed
역할은 온라인 키를 사용하므로, 처음 업로드된 프로젝트는 클라이언트에게 즉시 사용 가능합니다. 일정 시간이 경과한 후, PyPI 관리자는 최대 보안을 위해 recently-claimed
에 나열된 프로젝트를 주기적으로(예: 매월) claimed
역할로 이동할 수 있습니다(MAY). claimed
역할은 오프라인 키를 사용하므로, PyPI가 침해되더라도 이 역할에 추가된 프로젝트는 쉽게 위조될 수 없습니다.
recently-claimed
역할은 보안 목적이 아니라 사용성과 효율성을 위해 unclaimed
역할과 분리됩니다. 새로운 프로젝트 위임이 unclaimed
메타데이터에 앞에 붙여지면, 프로젝트가 키를 얻을 때마다 unclaimed
를 다시 다운로드해야 했을 것입니다. 새로운 프로젝트를 분리함으로써 검색되는 데이터 양이 줄어듭니다. 사용성 관점에서도 관리자가 어떤 프로젝트가 이제 claimed
되었는지 쉽게 확인할 수 있습니다. 이 정보는 “일관된 스냅샷 생성 (Producing Consistent Snapshots)” 섹션에서 더 자세히 논의될 recently-claimed
에서 claimed
로 키를 이동할 때 필요합니다.
종단 간 서명 (End-to-End Signing)
종단 간 서명은 PyPI와 개발자 모두 클라이언트가 다운로드하는 메타데이터에 서명할 수 있도록 합니다. PyPI는 업로드된 프로젝트를 클라이언트에게 제공하도록 신뢰받고(PyPI는 이 프로세스 부분에 대한 메타데이터에 서명), 개발자는 PyPI에 업로드하는 배포에 서명합니다.
프로젝트에 대한 신뢰를 위임하기 위해 개발자는 PyPI에 적어도 하나의 공개 키를 제출해야 합니다. 개발자는 동일한 프로젝트에 대해 여러 공개 키를 제출할 수 있습니다(예: 프로젝트의 각 유지 관리자마다 하나의 키). PyPI는 프로젝트의 모든 공개 키를 가져와 PyPI가 서명하는 상위 메타데이터에 추가합니다. 초기 신뢰가 확립된 후, 개발자는 적어도 하나의 공개 키에 해당하는 개인 키를 사용하여 PyPI에 업로드하는 배포에 서명해야 합니다. 개발자가 PyPI에 업로드하는 서명된 TUF 메타데이터에는 배포의 파일 크기 및 해시와 같은 정보가 포함되어 있으며, 이는 패키지 관리자가 다운로드된 배포를 검증하는 데 사용합니다.
종단 간 서명의 실제적인 함의는 프로젝트에 대한 신뢰를 위임하는 데 필요한 추가적인 관리 작업과 개발자가 배포와 함께 PyPI에 업로드해야 하는(MUST upload) 서명된 메타데이터입니다. 특히, PyPI는 프로젝트를 claimed
메타데이터 파일에 추가하고 서명함으로써 오프라인 키로 메타데이터에 주기적으로 서명할 것으로 예상됩니다. 반대로, 최소 보안 모델에서는 프로젝트가 항상 온라인 키로만 서명됩니다. 종단 간 서명은 신뢰를 위임하기 위해 수동 개입(즉, 오프라인 키로 메타데이터에 서명)을 요구하지만, 이는 일회성 비용이며 그 이후에는 PyPI 침해에 대해 프로젝트가 더 강력한 보호를 받습니다.
메타데이터 서명, 키 관리 및 배포 서명 (Metadata Signatures, Key Management, and Signing Distributions)
이 섹션에서는 PyPI가 서명 도구 구현자에게 권장할 수 있는(MAY recommend) 도구, 서명 체계 및 서명 방법에 대해 논의합니다. 개발자는 이러한 도구를 사용하여 배포에 서명하고 PyPI에 업로드할 것으로 예상됩니다. 아래 하위 섹션에서 논의된 권장(RECOMMENDED) 도구 및 체계를 요약하자면, 개발자는 배포의 진위 여부를 확인하는 데 필요한 정보가 포함된 메타데이터에 암호화 키를 생성하고 서명할 수 있습니다(Ed25519 서명 체계 사용). 개발자는 메타데이터를 PyPI에 업로드하며, 이 메타데이터는 pip
과 같은 패키지 관리자(즉, TUF 메타데이터를 지원하는 패키지 관리자)가 다운로드할 수 있도록 제공됩니다. 전체 프로세스는 PyPI에서 배포를 다운로드하는 최종 사용자(TUF를 지원하는 패키지 관리자 사용)에게 투명합니다.
처음 세 하위 섹션(암호화 서명 체계, 암호화 키 파일, 키 관리)은 개발자 릴리스 프로세스의 암호화 구성 요소를 다룹니다. 즉, PyPI가 지원하는 키 유형, 키 저장 방법, 키 생성 방법입니다. 처음 세 섹션 다음에 오는 두 하위 섹션은 TUF 메타데이터를 지원하도록 수정되어야 하는(SHOULD be modified) PyPI 모듈에 대해 논의합니다. 예를 들어, Twine과 Distutils는 수정되어야 하는(SHOULD be modified) 두 프로젝트입니다. 마지막 하위 섹션에서는 서명 도구에 권장되는(RECOMMENDED) 자동화된 키 관리 및 서명 솔루션에 대해 설명합니다.
TUF의 설계는 암호화 키 유형, 서명 및 서명 방법에 대해 유연합니다. 다음 섹션에서 논의되는 도구, 수정 및 방법은 서명 도구 구현자를 위한 권장 사항(RECOMMENDATIONS)입니다.
암호화 서명 체계: Ed25519 (Cryptographic Signature Scheme: Ed25519)
CPython과 함께 제공되는 패키지 관리자(pip
)는 비-CPython 인터프리터에서도 작동해야 하며, 컴파일되어야 하는 종속성(dependencies)을 가질 수 없습니다(즉, PyPI+TUF 통합은 암호화 서명을 검증하기 위해 C 확장 모듈의 컴파일을 요구해서는 안 됩니다(MUST NOT require)). 서명 검증은 Python으로 이루어져야 하며, 순수 Python으로 RSA 서명을 검증하는 것은 속도 때문에 비실용적일 수 있습니다. 따라서 PyPI는 Ed25519 서명 체계를 사용할 수 있습니다(MAY use).
Ed25519는 작은 암호화 서명과 키를 사용하는 공개 키 서명 시스템입니다. Ed25519 서명 체계의 순수 Python 구현이 사용 가능합니다. Ed25519 서명 검증은 Python으로 수행될 때도 빠릅니다.
암호화 키 파일 (Cryptographic Key Files)
구현은 AES-256-CTR-Mode로 키 파일을 암호화하고 PBKDF2-HMAC-SHA256(기본적으로 100K 반복이지만 개발자가 재정의할 수 있음)으로 비밀번호를 강화할 수 있습니다(MAY encrypt). TUF의 현재 Python 구현은 어떤 암호화 라이브러리든 사용할 수 있으며(PyCA cryptography
에 대한 지원은 미래에 추가될 예정), PBKDF2 반복 횟수의 기본값을 재정의할 수 있고, KDF는 취향에 맞게 조정될 수 있습니다.
키 관리: miniLock (Key Management: miniLock)
사용하기 쉬운 키 관리 솔루션이 필요합니다. 한 가지 해결책은 비밀번호로부터 개인 키를 파생시켜 개발자가 여러 컴퓨터에서 암호화 키 파일을 관리할 필요가 없도록 하는 것입니다. miniLock
은 이것이 어떻게 가능한지 보여주는 예시입니다. 개발자는 암호화 키를 보조 비밀번호로 간주할 수 있습니다. miniLock
은 Ed25519와 같이 매우 작은 키만 필요한 서명 체계와도 잘 작동합니다.
서드파티 업로드 도구: Twine (Third-party Upload Tools: Twine)
Twine
과 같은 서드파티 도구는 TUF 메타데이터를 포함하는 배포를 지원하려는 경우(if they wish) 개발자 프로젝트를 PyPI에 서명하고 업로드하도록 수정될 수 있습니다(MAY be modified). Twine
은 TLS를 사용하여 배포를 업로드하고 사용자 이름 및 비밀번호에 대한 MITM 공격을 방지하는 PyPI와 상호 작용하는 유틸리티입니다.
빌드 백엔드 (Build backends)
빌드 백엔드는 메타데이터에 서명하고 서명된 배포를 PyPI에 업로드하도록 수정될 수 있습니다(MAY be modified).
자동화된 서명 솔루션 (Automated Signing Solution)
개발자를 위한 사용하기 쉬운 키 관리 솔루션이 권장됩니다(RECOMMENDED). 한 가지 접근 방식은 miniLock
과 유사하게 사용자 비밀번호로부터 암호화 개인 키를 생성하는 것입니다. 개발자 서명이 선택 사항으로 유지될 수 있지만, 각 배포가 가질 수 있는 잠재적으로 서명되지 않은 수많은 종속성으로 인해 이 접근 방식은 부적절할 수 있습니다. 이러한 종속성 중 하나라도 서명되지 않으면, 프로젝트가 자체 배포에 서명함으로써 얻는 이점을 무효화합니다(즉, 공격자는 최종 사용자를 공격하기 위해 서명되지 않은 종속성 중 하나만 침해하면 됩니다). 개발자에게 배포를 수동으로 서명하고 키를 관리하도록 요구하면 키 서명 기능이 사용되지 않는 기능이 될 것으로 예상됩니다.
개발자에게 투명하며 키 에스크로(암호화된 개인 키를 PyPI와 공유하는 것)를 요구하지 않는 기본 PyPI 중개 키 관리 및 패키지 서명 솔루션이 서명 도구에 권장됩니다(RECOMMENDED). 또한 서명 도구는 각 개발자의 여러 시스템 간에 개인 키 공유를 피해야 합니다(SHOULD circumvent). 이는 키 관리 솔루션이 각 프로젝트에 대해 여러 키를 지원해야 함을 의미합니다(SHOULD support).
다음은 새로운 개발자가 PyPI에 배포를 업로드하기 위해 따를 수 있는(MAY follow) 자동화된 서명 솔루션을 요약한 것입니다.
- PyPI 프로젝트를 등록합니다.
- 보조 비밀번호(PyPI 사용자 계정 비밀번호와 독립적)를 입력합니다.
- 선택 사항: 두 번째 시스템에서(비밀번호 프롬프트 후에) 개발자의 PyPI 사용자 계정에 새 ID를 추가합니다.
- 프로젝트를 업로드합니다.
- 선택 사항: 프로젝트와 연결된 다른 유지 관리자는 로그인하여 보조 비밀번호를 입력하여 자신의 ID를 프로젝트에 추가할 수 있습니다.
1단계는 개발자가 PyPI 프로젝트를 등록하는 일반적인 절차입니다.
2단계는 암호화된 키 파일(개인 키)을 생성하고, Ed25519 공개 키를 PyPI에 업로드하며, 배포를 위해 생성된 TUF 메타데이터에 서명합니다.
3단계에서 단순히 비밀번호를 입력하여 두 번째 시스템에서 새 ID를 선택적으로 추가하는 것은 암호화된 개인 키 파일을 생성하고 Ed25519 공개 키를 PyPI에 업로드합니다. 개발자가 여러 시스템에서 릴리스에 서명할 수 있도록 별도의 ID를 생성할 수 있습니다(MAY be created). 기존의 확인된 ID(해당 공개 키는 프로젝트 메타데이터에 포함되어 있거나 PyPI에 업로드되어 있음)가 새로운 ID에 서명합니다. 기본적으로 프로젝트 메타데이터는 “1”의 서명 임계값을 가지며, 다른 확인된 ID는 임계값을 충족하기 위해 새로운 릴리스를 생성할 수 있습니다.
4단계는 배포 파일과 TUF 메타데이터를 PyPI에 업로드합니다. “스냅샷 프로세스 (Snapshot Process)” 섹션에서는 개발자가 PyPI에 배포를 업로드하는 절차를 자세히 논의합니다.
5단계는 다른 유지 관리자가 2단계와 유사한 방식으로 암호화된 키 파일을 생성하도록 허용합니다. 이러한 키는 PyPI에 업로드되어 TUF 메타데이터에 추가되어야 합니다(SHOULD be uploaded). 이 키는 프로젝트의 향후 릴리스를 업로드하는 데 사용될 수 있습니다(MAY be used).
암호화 파일 및 서명 생성은 기본적으로 개발자에게 투명합니다. 개발자는 패키지가 자동으로 서명된다는 것을 알 필요가 없습니다. 그러나 서명 도구는 유연해야 합니다. 개발자는 자체 키를 생성하고 키 관리를 직접 처리하기를 원할 수 있습니다. 이 경우 개발자는 단순히 자신의 공개 키를 PyPI에 업로드할 수 있습니다.
저장소 및 개발자 TUF 도구는 현재 이전에 언급된 모든 권장 사항을 지원하지만, Distlib
, Twine
및 기타 서드파티 서명 도구에 추가되어야 하는(SHOULD be added) 자동화된 서명 솔루션은 제외합니다. 자동화된 서명 솔루션은 사용 가능한 저장소 도구 함수를 호출하여 메타데이터에 서명하고 암호화 키 파일을 생성합니다.
스냅샷 프로세스 (Snapshot Process)
스냅샷 프로세스는 상당히 간단하며 자동화되어야 합니다(SHOULD be automated). 스냅샷 프로세스는 루트, 타겟, 위임된 역할의 최신 작업 세트(latest working set)를 메모리에 유지해야 합니다(MUST keep). 매분마다 스냅샷 프로세스는 이 최신 작업 세트에 서명합니다. (프로젝트 업로드는 동시성 안전한 방식으로 최신 위임된 메타데이터에 대해 스냅샷 프로세스에 지속적으로 알려줍니다. 스냅샷 프로세스는 실제로 최신 작업 세트의 사본에 서명하는 동안 메모리의 최신 작업 세트는 프로젝트 트랜잭션 프로세스에 의해 지속적으로 통신되는 정보로 업데이트됩니다.) 스냅샷 프로세스는 이전 단계에서 생성된 메타데이터(루트, 타겟, 위임된 역할)를 보증할 새 타임스탬프 메타데이터를 생성하고 서명해야 합니다(MUST generate and sign). 마지막으로 스냅샷 프로세스는 최신 스냅샷을 나타내는 새 타임스탬프 및 스냅샷 메타데이터를 클라이언트에게 제공해야 합니다(MUST make available).
claimed
또는 recently-claimed
프로젝트는 PyPI에 대한 트랜잭션에서 타겟(간단한 인덱스와 배포 모두)뿐만 아니라 TUF 메타데이터도 업로드해야 합니다. 프로젝트는 두 개의 디렉터리, /metadata/
(위임된 타겟 메타데이터 파일 포함)와 /targets/
(프로젝트 간단한 인덱스와 위임된 타겟 메타데이터에 의해 서명된 배포와 같은 타겟 포함)를 포함하는 ZIP 파일을 업로드하여 이를 수행할 수 있습니다(MAY do so).
프로젝트가 PyPI에 메타데이터 또는 타겟 파일을 업로드할 때마다 PyPI는 적어도 다음 속성에 대해 프로젝트 TUF 메타데이터를 확인해야 합니다(SHOULD check):
- 해당 프로젝트에 의해 PyPI에 등록된 개발자 키의 임계값 수가 해당 프로젝트의 타겟의 “루트”를 나타내는 위임된 타겟 메타데이터 파일(예:
metadata/targets/project.txt
)에 서명했어야 합니다(MUST have signed). - 위임된 타겟 메타데이터 파일의 서명은 유효해야 합니다(MUST be valid).
- 위임된 타겟 메타데이터 파일은 만료되지 않았어야 합니다(MUST NOT have expired).
- 위임된 타겟 메타데이터는 타겟과 일치해야 합니다(MUST be consistent).
- 위임자(delegator)는 다른 위임자로부터 자신에게 위임되지 않은 타겟을 위임해서는 안 됩니다(MUST NOT delegate).
- 수임자(delegatee)는 위임자로부터 자신에게 위임되지 않은 타겟에 서명해서는 안 됩니다(MUST NOT sign).
PyPI가 프로젝트 TUF 메타데이터를 확인하기로 선택하는 경우, 이러한 요구 사항을 충족하지 않는 메타데이터 또는 타겟 파일 세트의 게시를 거부할 수 있습니다(MAY choose to reject).
PyPI는 각 프로젝트가 자신이 담당하는 TUF 메타데이터에만 쓸 수 있도록 보장하여 접근 제어를 강제해야 합니다(MUST enforce). 프로젝트 업로드 프로세스가 올바른 메타데이터와 해당 메타데이터 내의 올바른 위치에 쓰도록 보장함으로써 이를 수행해야 합니다. 예를 들어, unclaimed
프로젝트에 대한 프로젝트 업로드 프로세스는 프로젝트의 타겟에 대한 올바른 위임된 unclaimed
메타데이터의 올바른 타겟 경로에 쓰여야 합니다(MUST write).
드문 경우지만, PyPI는 하위 호환되지 않는 방식으로 프로젝트용 TUF 메타데이터 형식을 확장하기를 원할 수 있습니다(MAY wish). PyPI는 개발자 키로 서명된 메타데이터의 서명을 무효화할 수 있으므로, 메타데이터를 새롭고 하위 호환되지 않는 형식으로 업그레이드하기 위해 기존 TUF 메타데이터를 프로젝트를 대신하여 자동으로 다시 작성할 수 없다는 점에 유의하십시오. 대신, 패키지 관리자는 여러 호환되지 않는 버전의 TUF 메타데이터를 인식하고 처리하도록 작성되어야 합니다(SHOULD be written). 이렇게 하면 claimed
및 recently-claimed
프로젝트가 메타데이터를 더 새롭지만 하위 호환되지 않는 형식으로 마이그레이션할 합리적인 시간을 제공받을 수 있습니다. 이 버전 변경을 처리하는 한 가지 메커니즘은 TAP 14에 설명되어 있습니다.
PyPI가 새로운 일관된 스냅샷을 생성할 디스크 공간이 부족해지면, PyPI는 충분히 오래된 일관된 스냅샷을 삭제하기 위해 “마크 앤 스윕(mark-and-sweep)” 알고리즘과 같은 것을 사용할 수 있습니다(MAY then use). 즉, 더 이상 사용되지 않는 타임스탬프 및 스냅샷과 같은 오래된 메타데이터만 삭제됩니다. 특히, 최신 일관된 스냅샷을 보존하기 위해 PyPI는 최신 일관된 스냅샷의 루트(타임스탬프)부터 시작하여 객체를 순회하고, 방문한 모든 객체를 표시하며, 표시되지 않은 모든 객체를 삭제할 것입니다. 마지막 몇 개의 일관된 스냅샷은 유사한 방식으로 보존될 수 있습니다. 일관된 스냅샷을 삭제하면 클라이언트는 삭제된 일관된 스냅샷의 타겟에 대한 모든 요청에 대해 HTTP 404 응답만 보게 될 것입니다. 클라이언트는 최신 일관된 스냅샷으로 요청을 다시 시도해야 합니다(SHOULD then retry).
TUF 메타데이터를 지원하는 모든 패키지 관리자는 모든 메타데이터 및 타겟 파일(타임스탬프 메타데이터 제외)을 다운로드할 때 파일 이름에 파일의 암호화 해시를 포함하도록 수정되어야 합니다(MUST be modified). 다음 하위 섹션에서 권장하는(RECOMMENDED) 파일 이름 규칙에 따라 filename.ext
파일에 대한 요청은 digest.filename
파일에 대한 동등한 요청으로 변환됩니다.
마지막으로 PyPI는 서버 실패 후 오류로부터 더 쉽게 복구할 수 있도록 프로젝트 트랜잭션 프로세스 및 큐를 기록하기 위해 트랜잭션 로그를 사용해야 합니다(SHOULD use).
일관된 스냅샷 생성 (Producing Consistent Snapshots)
PyPI는 프로젝트에 따라 claimed
, recently-claimed
, 또는 unclaimed
메타데이터 및 관련 위임된 메타데이터를 업데이트할 책임이 있습니다. 모든 프로젝트는 자신의 메타데이터 및 타겟 세트를 단일 트랜잭션으로 업로드해야 합니다(MUST upload). 업로드된 파일 세트를 “프로젝트 트랜잭션”이라고 합니다. PyPI가 프로젝트 트랜잭션의 파일을 어떻게 검증할 수 있는지는 나중에 섹션에서 논의됩니다. 이 섹션의 초점은 PyPI가 프로젝트 트랜잭션에 어떻게 응답할 것인지입니다.
모든 메타데이터 및 타겟 파일은 파일 이름에 BLAKE2b-256 해시의 헥스 다이제스트(hex digest)를 포함해야 하며(MUST include), PyPI는 파일이 업로드된 후 파일 이름 앞에 이를 추가할 수 있습니다(may prepend). 이 PEP의 경우, PyPI가 digest.filename
형태의 간단한 규칙을 채택하는 것이 권장됩니다(RECOMMENDED). 여기서 filename
은 해시의 복사본이 없는 원래 파일 이름이고, digest
는 해시의 헥스 다이제스트입니다.
unclaimed
프로젝트가 새 트랜잭션을 업로드하면, 프로젝트 트랜잭션 프로세스는 모든 새 타겟 파일과 관련 위임된 unclaimed
메타데이터를 추가해야 합니다(MUST add). 프로젝트 업로드 프로세스는 새로운 위임된 unclaimed
메타데이터에 대해 스냅샷 프로세스에 알려야 합니다(MUST inform).
recently-claimed
프로젝트가 새 트랜잭션을 업로드하면, 프로젝트 업로드 프로세스는 프로젝트에 대한 모든 새 타겟 파일과 위임된 타겟 메타데이터를 추가해야 합니다(MUST add). 프로젝트가 새로운 경우, 프로젝트 업로드 프로세스는 프로젝트에 대한 공개 키(트랜잭션의 일부여야 합니다(MUST be part))를 포함하는 새로운 recently-claimed
메타데이터도 추가해야 합니다(MUST also add). recently-claimed
프로젝트는 업로드 프로세스에 의해 “1”의 임계값으로 설정됩니다. 마지막으로 프로젝트 업로드 프로세스는 새로운 recently-claimed
메타데이터와 프로젝트에 대한 현재 위임된 타겟 메타데이터 세트에 대해 스냅샷 프로세스에 알려야 합니다(MUST inform).
claimed
프로젝트의 업로드 프로세스는 PyPI 관리자가 recently-claimed
역할에서 claimed
역할로 프로젝트를 주기적으로 이동하는(2주에서 한 달마다 발생할 수 있는(MAY occur) 수동 프로세스) 방식이 약간 다릅니다. (프로젝트를 recently-claimed
에서 claimed
로 이동하는 것은 PyPI 관리자가 오프라인 키를 사용하여 claimed
프로젝트의 배포에 서명해야 하므로 수동 프로세스입니다.) 그런 다음 프로젝트 업로드 프로세스는 이 마이그레이션을 반영하기 위해 새로운 recently-claimed
및 claimed
메타데이터를 추가해야 합니다(MUST then add). recently-claimed
프로젝트의 경우와 마찬가지로, 프로젝트 업로드 프로세스는 항상 claimed
프로젝트에 대한 모든 새 타겟 파일과 위임된 타겟 메타데이터를 추가해야 합니다(MUST always add). 마지막으로 프로젝트 업로드 프로세스는 새로운 recently-claimed
또는 claimed
메타데이터와 프로젝트에 대한 현재 위임된 타겟 메타데이터 세트에 대해 일관된 스냅샷 프로세스에 알려야 합니다(MUST inform).
프로젝트 업로드 프로세스는 PyPI 관리자가 프로젝트를 recently-claimed
역할에서 claimed
역할로 이동하는 경우를 제외하고는 자동화되어야 합니다(SHOULD be automated). 프로젝트 업로드 프로세스는 또한 원자적으로 적용되어야 합니다. 즉, 모든 메타데이터 및 타겟 파일이 추가되거나 아무것도 추가되지 않아야 합니다. 프로젝트 트랜잭션 프로세스와 스냅샷 프로세스는 동시에 작동해야 합니다(SHOULD work concurrently). 마지막으로 프로젝트 업로드 프로세스는 새로운 일관된 스냅샷에서 올바르게 업데이트될 수 있도록 최신 claimed
, recently-claimed
, unclaimed
메타데이터를 메모리에 유지해야 합니다(SHOULD keep).
큐는 다음 규칙이 준수되는 한(provided that) 나타나는 순서대로 동시에 처리될 수 있습니다(MAY be processed).
- 어떤 프로젝트 업로드 프로세스 쌍도 동일한 프로젝트에서 동시에 작업할 수 없습니다.
- 어떤 프로젝트 업로드 프로세스 쌍도 동일한 위임된
unclaimed
역할에 속하는unclaimed
프로젝트에서 동시에 작업할 수 없습니다. - 어떤 프로젝트 업로드 프로세스 쌍도 새로운
recently-claimed
프로젝트에서 동시에 작업할 수 없습니다. - 어떤 프로젝트 업로드 프로세스 쌍도 새로운
claimed
프로젝트에서 동시에 작업할 수 없습니다. - 어떤 프로젝트 업로드 프로세스도 새로운
claimed
프로젝트에서 작업하는 동안 다른 프로젝트 업로드 프로세스가 새로운recently-claimed
프로젝트에서 작업할 수 없으며 그 반대도 마찬가지입니다.
이러한 규칙은 메타데이터가 일관성 없게 읽히거나 쓰여지지 않도록 보장하기 위해 준수되어야 합니다(MUST be observed).
스냅샷 감사 (Auditing Snapshots)
악의적인 주체가 PyPI를 침해하면 온라인 키 중 하나로 임의의 파일에 서명할 수 있습니다. 오프라인 키를 가진 역할(즉, root
및 targets
)은 여전히 보호됩니다. 저장소 침해로부터 안전하게 복구하려면, 파일이 신뢰할 수 있는 버전으로만 복원되도록 스냅샷을 감사해야 합니다.
저장소 침해가 감지되면 다음 세 가지 정보 유형의 무결성을 검증해야 합니다.
- 저장소의 온라인 키가 침해된 경우,
targets
역할이 새로운 메타데이터에 서명하여 새로운 키에 위임함으로써 취소될 수 있습니다. - 저장소의 역할 메타데이터가 변경된 경우, 이는 온라인 키로 서명된 메타데이터에 영향을 미칩니다. 침해 이후에 생성된 모든 역할 정보는 폐기되어야 합니다. 결과적으로 새 프로젝트의 개발자는 프로젝트를 다시 등록해야 합니다.
- 패키지 자체가 조작되었을 가능성이 있는 경우, 침해 이전에 신뢰할 수 있는 메타데이터에 존재했던 패키지에 대한 저장된 해시 정보를 사용하여 유효성을 검사할 수 있습니다. 또한
claimed
역할의 개발자가 서명한 새로운 배포는 안전하게 유지될 수 있습니다. 그러나recently-claimed
또는unclaimed
역할의 개발자가 서명한 배포는 폐기되어야 합니다.
침해 발생 시 스냅샷을 안전하게 복원하기 위해 PyPI는 자체 미러를 소수 유지하여 특정 일정에 따라 PyPI 스냅샷을 복사해야 합니다(SHOULD maintain). 미러링 프로토콜은 이 목적을 위해 즉시 사용될 수 있습니다. 미러는 PyPI 미러링만 책임지도록 보안되고 격리되어야 합니다. 미러는 우발적 또는 악의적인 오류를 감지하기 위해 서로 비교될 수 있습니다.
다른 접근 방식은 각 스냅샷의 암호화 해시를 주기적으로 생성하여 트윗하는 것입니다. 예를 들어, 트윗을 받은 사용자가 실제 메타데이터를 제시하면 저장소 유지 관리자는 메타데이터의 암호화 해시를 확인할 수 있습니다. 또는 PyPI는 외부에서 제공된 메타데이터에 의존하기보다 자체 버전의 스냅샷을 주기적으로 아카이브할 수 있습니다. 이 경우 PyPI는 저장소의 모든 패키지에 대한 암호화 해시를 가져와 이 데이터를 오프라인 장치에 저장해야 합니다(SHOULD take). 패키지 해시가 변경된 경우, 이는 공격이 발생했음을 나타냅니다.
다른 버전의 메타데이터를 제공하거나 특정 버전의 패키지를 고정하는 공격은 TUF가 암시적 키 취소 및 메타데이터 불일치 감지와 같은 기술로 처리할 수 있습니다.
키 침해 분석 (Key Compromise Analysis)
이 PEP는 최대 보안 모델, 배포의 지속적인 전달을 지원하기 위해 추가되어야 하는 TUF 역할, 각 역할의 메타데이터를 생성하고 서명하는 방법, 그리고 개발자가 서명한 배포를 지원하는 방법을 다루었습니다. 나머지 섹션에서는 PyPI가 저장소 메타데이터를 감사해야 하는 방법과 PyPI 침해를 감지하고 복구하는 데 사용할 수 있는 방법을 논의합니다.
표 1은 임계값 수의 개인 암호화 키(PyPI 역할 중 하나에 속하는)가 침해되었을 때 가능한 몇 가지 공격을 요약합니다. 가장 왼쪽 열은 침해된 역할(또는 역할 조합)을 나열하고, 오른쪽 열은 침해된 역할이 클라이언트를 악성 업데이트, 프리즈 공격(freeze attacks), 또는 메타데이터 불일치 공격에 취약하게 만드는지 여부를 보여줍니다.
역할 침해 (Role Compromise) | 악성 업데이트 (Malicious Updates) | 프리즈 공격 (Freeze Attack) | 메타데이터 불일치 공격 (Metadata Inconsistency Attacks) |
---|---|---|---|
timestamp | NO | snapshot 및 delegated roles 중 하나와 협력해야 함 | NO |
snapshot 및 delegated roles 중 하나와 협력해야 함 | YES (가장 이른 root, targets, 또는 bin 메타데이터 만료 시간에 의해 제한됨) | NO (snapshot과 협력해야 함) | NO (timestamp와 협력해야 함) |
snapshot | NO | timestamp 및 delegated roles 중 하나와 협력해야 함 | NO (timestamp와 협력해야 함) |
timestamp 및 delegated roles 중 하나와 협력해야 함 | NO (timestamp와 협력해야 함) | NO (timestamp와 협력해야 함) | NO (timestamp와 협력해야 함) |
timestamp AND snapshot | NO | delegated roles 중 하나와 협력해야 함 | YES (가장 이른 root, targets, 또는 bin 메타데이터 만료 시간에 의해 제한됨) |
delegated roles 중 하나와 협력해야 함 | YES (가장 이른 root, targets, 또는 bin 메타데이터 만료 시간에 의해 제한됨) | YES (가장 이른 root, targets, 또는 bin 메타데이터 만료 시간에 의해 제한됨) | YES (가장 이른 root, targets, 또는 bin 메타데이터 만료 시간에 의해 제한됨) |
targets OR claimed OR recently-claimed OR unclaimed OR project | NO | timestamp 및 snapshot과 협력해야 함 | 적용 불가 (timestamp 및 snapshot 필요) |
timestamp 및 snapshot과 협력해야 함 | 적용 불가 (timestamp 및 snapshot 필요) | YES (가장 이른 root, targets, 또는 bin 메타데이터 만료 시간에 의해 제한됨) | 적용 불가 (timestamp 및 snapshot 필요) |
(timestamp AND snapshot) AND project | YES | YES (가장 이른 root, targets, 또는 bin 메타데이터 만료 시간에 의해 제한됨) | YES (가장 이른 root, targets, 또는 bin 메타데이터 만료 시간에 의해 제한됨) |
(timestamp AND snapshot) AND (recently-claimed OR unclaimed) | YES (claimed에 의해 위임되지 않은 프로젝트만) | YES (가장 이른 root, targets, claimed, recently-claimed, project, 또는 unclaimed 메타데이터 만료 시간에 의해 제한됨) | YES (가장 이른 root, targets, claimed, recently-claimed, project, 또는 unclaimed 메타데이터 만료 시간에 의해 제한됨) |
(timestamp AND snapshot) AND (targets OR claimed) | YES | YES (가장 이른 root, targets, claimed, recently-claimed, project, 또는 unclaimed 메타데이터 만료 시간에 의해 제한됨) | YES (가장 이른 root, targets, claimed, recently-claimed, project, 또는 unclaimed 메타데이터 만료 시간에 의해 제한됨) |
root | YES | YES | YES |
표 1: 특정 역할 키 조합을 침해하여 가능한 공격. 2013년 9월, 당시 최신 버전의 pip
이 이러한 공격에 취약하며 TUF가 사용자를 이러한 공격으로부터 어떻게 보호할 수 있는지 보여주었습니다. 오프라인 키로 서명된 역할은 굵게 표시됩니다.
targets
또는 위임된 역할(프로젝트 타겟 메타데이터 제외)을 침해하는 것이 공격자가 악성 업데이트를 즉시 제공하는 것을 허용하지 않는다는 점에 유의하십시오. 공격자는 timestamp
및 snapshot
역할도 침해해야 합니다(이 둘은 모두 온라인에 있으므로 침해될 가능성이 더 높습니다). 이는 어떤 공격을 시작하기 위해서는 중간자(man-in-the-middle) 역할을 할 수 있을 뿐만 아니라 timestamp
키를 침해해야 합니다(또는 root
키를 침해하고 새로운 timestamp
키에 서명해야 합니다). 프리즈 공격 외의 다른 공격을 시작하려면 snapshot
키도 침해해야 합니다. 마지막으로, PyPI 인프라의 침해는 recently-claimed
프로젝트에 악성 업데이트를 도입할 수 있습니다. 왜냐하면 이러한 역할의 키는 온라인에 있기 때문입니다.
키 침해 발생 시 (In the Event of a Key Compromise)
키 침해는 개발자 또는 PyPI의 역할에 속하는 임계값 수의 키와 PyPI 인프라가 침해되어 PyPI에 새 메타데이터를 서명하는 데 사용되었음을 의미합니다.
프로젝트의 개발자 키 중 임계값 수가 침해된 경우, 프로젝트는 다음 단계를 수행해야 합니다(MUST take).
- 프로젝트 메타데이터 및 타겟은 프로젝트가 침해되지 않은 것으로 알려진 마지막으로 확인된 양호한 일관된 스냅샷으로 복원되어야 합니다(MUST be restored). 이는 개발자가 새로운 키로 모든 타겟을 다시 패키징하고 다시 서명함으로써 수행할 수 있습니다.
- 프로젝트의 메타데이터는 버전 번호가 증가하고, 만료 시간이 적절하게 연장되며, 서명이 갱신되어야 합니다(MUST have).
반면에 PyPI는 다음 단계를 수행해야 합니다(MUST take).
recently-claimed
또는claimed
역할에서 침해된 개발자 키를 취소합니다. 이는 침해된 개발자 키를 새로 발급된 개발자 키로 교체함으로써 이루어집니다.- 새로운 타임스탬프가 지정된 일관된 스냅샷이 발행되어야 합니다(MUST be issued).
timestamp
, snapshot
, recently-claimed
또는 unclaimed
키 중 임계값 수가 침해된 경우, PyPI는 다음 단계를 수행해야 합니다(MUST take).
root
역할에서timestamp
,snapshot
,targets
역할 키를 취소합니다. 이는 침해된timestamp
,snapshot
,targets
키를 새로 발급된 키로 교체함으로써 이루어집니다.targets
역할에서recently-claimed
및unclaimed
키를 해당 키를 새로 발급된 키로 교체하여 취소합니다.- 새
targets
역할 메타데이터에 서명하고 새 키를 폐기합니다(이전 설명대로targets
메타데이터의 보안을 강화하기 위함). recently-claimed
역할의 모든 타겟 또는 위임을 지우고 모든 관련 위임된 타겟 메타데이터를 삭제합니다. 최근 등록된 프로젝트는 PyPI에 개발자 키를 다시 등록해야 합니다(SHOULD register).recently-claimed
및unclaimed
역할의 모든 타겟은timestamp
,snapshot
,recently-claimed
, 또는unclaimed
키 중 어느 것도 침해되지 않은 것으로 알려진 마지막으로 확인된 양호한 일관된 스냅샷과 비교되어야 합니다(SHOULD be compared). 침해된 일관된 스냅샷에서 추가, 업데이트 또는 삭제된 타겟 중 마지막으로 확인된 양호한 일관된 스냅샷과 일치하지 않는 것은 이전 버전으로 복원되어야 합니다(SHOULD be restored).- 모든
unclaimed
타겟의 무결성을 확인한 후,unclaimed
메타데이터는 다시 생성되어야 합니다(MUST be regenerated). recently-claimed
및unclaimed
메타데이터는 버전 번호가 증가하고, 만료 시간이 적절하게 연장되며, 서명이 갱신되어야 합니다(MUST have).- 새로운 타임스탬프가 지정된 일관된 스냅샷이 발행되어야 합니다(MUST be issued).
이는 비록 이들 중 하나만 침해되었더라도 이 모든 역할을 선제적으로 보호할 것입니다.
targets
또는 claimed
키 중 임계값 수가 침해된 경우, 공격자가 timestamp
및 snapshot
키 없이는 할 수 있는 일이 거의 없습니다. 이 경우 PyPI는 단순히 root
및 targets
역할에서 침해된 targets
또는 claimed
키를 새로운 키로 교체하여 취소해야 합니다(MUST simply revoke).
timestamp
, snapshot
, claimed
키 중 임계값 수가 침해된 경우, PyPI는 timestamp
또는 snapshot
키가 침해되었을 때 취하는 단계 외에 다음 단계를 추가로 수행해야 합니다(MUST take).
targets
역할에서claimed
역할 키를 취소하고 새로 발급된 키로 교체합니다.claimed
역할의 모든 프로젝트 타겟은timestamp
,snapshot
, 또는claimed
키 중 어느 것도 침해되지 않은 것으로 알려진 마지막으로 확인된 양호한 일관된 스냅샷과 비교되어야 합니다(SHOULD be compared). 침해된 일관된 스냅샷에서 추가, 업데이트 또는 삭제된 타겟 중 마지막으로 확인된 양호한 일관된 스냅샷과 일치하지 않는 것은 이전 버전으로 복원될 수 있습니다(MAY be restored).- 모든
claimed
프로젝트 타겟의 무결성을 확인한 후,claimed
메타데이터는 다시 생성되어야 합니다(MUST be regenerated). claimed
메타데이터는 버전 번호가 증가하고, 만료 시간이 적절하게 연장되며, 서명이 갱신되어야 합니다(MUST have).
이러한 단계를 따르면 비록 이들 중 하나만 침해되었더라도 이 모든 역할을 선제적으로 보호할 것입니다.
root
키 중 임계값 수가 침해된 경우, PyPI는 targets
역할이 침해되었을 때 취하는 단계를 수행해야 합니다(MUST take). 모든 root
키도 교체되어야 합니다.
또한 PyPI가 보안 게시판(security bulletins)으로 침해 사실을 충분히 문서화하는 것을 권장합니다(RECOMMENDED). 이러한 보안 게시판은 pip-with-TUF
사용자가 timestamp
, snapshot
, 또는 root
역할의 키가 더 이상 유효하지 않아 프로젝트를 설치하거나 업데이트할 수 없을 때 가장 유익할 것입니다. 사용자는 PyPI 웹 사이트를 방문하여 왜 프로젝트를 설치하거나 업데이트할 수 없는지 설명하는 보안 게시판을 참조하고 그에 따라 조치를 취할 수 있습니다. 침해로 인해 임계값 수의 root
키가 취소되지 않은 경우, 기존 root
키의 임계값 수가 새로운 root
메타데이터의 무결성에 서명하는 데 사용될 것이므로 새로운 root
메타데이터는 안전하게 업데이트될 수 있습니다. TUF 클라이언트는 이전에 알려진 root
키의 임계값 수로 새로운 root
메타데이터의 무결성을 확인할 수 있을 것입니다. 이것이 일반적인 경우일 것입니다. 최악의 경우, 침해로 인해 임계값 수의 root
키가 취소된 경우, 최종 사용자는 대역 외(out-of-band) 메커니즘으로 새로운 root
메타데이터를 업데이트하기로 선택할 수 있습니다(MAY choose).
부록 A: PyPI 빌드 팜 및 종단 간 서명 (Appendix A: PyPI Build Farm and End-to-End Signing)
PyPI 관리자는 중앙 빌드 팜(central build farm)을 지원할 의도가 있습니다. PyPI 빌드 팜은 개발자가 PyPI 인프라 및 지원되는 플랫폼에 업로드하는 각 배포에 대해 Wheel을 자동 생성할 것입니다. 패키지 관리자는 개발자가 서명한 소스 배포(source distributions) 대신 이러한 PyPI Wheel을 다운로드하여 프로젝트를 설치할 가능성이 높습니다(Wheel은 소스 배포보다 훨씬 빠르게 설치될 수 있음). 최대 보안 모델이 구현되기 전에 중앙 빌드 팜과 종단 간 서명이 갖는 함의가 조사되어야 합니다(SHOULD be investigated).
중앙 빌드 팜과 종단 간 서명에 대한 한 가지 문제는 개발자가 PyPI 인프라에서 생성된 Wheel 배포에 서명할 가능성이 낮다는 것입니다. 그러나 Wheel 빌딩이 결정론적 프로세스(deterministic process)인 경우, 개발자가 서명한 소스 배포로부터 Wheel을 생성하는 것은 여전히 유익할 수 있습니다. 결정론적 빌드가 불가능한 경우, 개발자는 온라인 키로 Wheel에 서명하는 PyPI 역할에 이러한 Wheel의 신뢰를 위임할 수 있습니다.
참고 자료 (References)
- https://theupdateframework.io/papers/survivable-key-compromise-ccs2010.pdf
- https://theupdateframework.github.io/specification/latest/index.html
- https://packaging.python.org/en/latest/glossary/
- https://github.com/theupdateframework/pip/wiki/Attacks-on-software-repositories
- https://theupdateframework.io/papers/attacks-on-package-managers-ccs2008.pdf
- https://mail.python.org/pipermail/distutils-sig/2013-September/022755.html
- https://en.wikipedia.org/wiki/RSA_(cryptosystem)
- https://ed25519.cr.yp.to/
감사의 말씀 (Acknowledgements)
이 자료는 미국 국립과학재단(National Science Foundation)의 보조금 No. CNS-1345049 및 CNS-0959138의 지원을 받아 수행된 작업에 기반합니다. 이 자료에 표현된 모든 의견, 발견 및 결론 또는 권장 사항은 저자의 것이며 반드시 국립과학재단의 견해를 반영하는 것은 아닙니다.
Alyssa Coghlan, Daniel Holth, Donald Stufft, Sumana Harihareswara, 그리고 일반적인 distutils-sig
커뮤니티가 TUF를 PyPI에 유용하고 효율적으로 통합하는 방법에 대해 생각하는 데 도움을 주신 것에 감사드립니다.
Roger Dingledine, Sebastian Hahn, Nick Mathewson, Martin Peck 및 Justin Samuel은 Tor 프로젝트의 전신인 Thandy로부터 TUF를 설계하는 데 도움을 주었습니다.
Konstantin Andrianov, Geremy Condra, Zane Fisher, Justin Samuel, Tian Tian, Santiago Torres, John Ward, 그리고 Yuyu Zheng의 TUF 개발 노고에 감사드립니다.
저작권 (Copyright)
이 문서는 퍼블릭 도메인에 공개되었습니다.
⚠️ 알림: 이 문서는 AI를 활용하여 번역되었으며, 기술적 정확성을 보장하지 않습니다. 정확한 내용은 반드시 원문을 확인하시기 바랍니다.
Comments