2020년 11월 Windows 10 버전 2004 이하의 누적 업데이트 미리 보기와 .NET Framework 4.8 용 2021년 1월 보안 및 품질 롤업 릴리스는 X509Certificate2 인증서에 대한 클린 프로세스를 개선하기 위한 변경 사항을 발표했습니다. 아래에서는 Windows의 프라이빗 키 수명에 대한 추가 설명을 제공합니다.
.NET Framework 2.0부터 PKCS#12 PFX 파일에서 인증서 및 관련 프라이빗 키를 로드하는 두 가지 방법이 있습니다. X509Certificate2 클래스의 멤버를 통해 하나의 인증서 개체로 또는 X509Certificate2Collection.Import 메서드를 통해 PFX에 있는 모든 인증서로.
기본 동작은 프라이빗 키가 디스크에 파일을 간접적으로 쓰는 시스템 암호화 라이브러리 중 하나를 통해 지속형(명명된) 키로 로드된다는 것입니다. PFX를 로드하는 대부분의 호출자는 인증서 개체만 일시적으로 사용하므로 .NET이 인증서와 연결된 네이티브 리소스를 해제할 때 프라이빗 키도 삭제합니다. 이 동작에 대한 기본 예외는 다음과 같습니다.
-
X509KeyStorageFlags.PersistKeySet 플래그는 파일을 작성하지만 삭제하지 않습니다.
-
X509KeyStorageFlags.EphemeralKetSet 플래그를 사용하면 프라이빗 키가 백업 파일 없이 메모리에 로드됩니다.
-
키 삭제가 보류 중일 때 발생할 수 있는 비정상적인 프로세스 종료입니다.
반직관적으로 인증서를 로드하는 두 가지 방법은 항상 프라이빗 키를 삭제하는 적절한 시간인 경우 추적을 위해 서로 다른 메커니즘을 사용했습니다. X509Certificate2 클래스를 통해 단일 인증서를 로드하면 .NET 런타임에만 표시되는 표식이 사용되며 해당 개체 참조 하나에만 적용됩니다. X509Certificate2Collection.Import를 통해 PFX를 로드하면 네이티브 인증서 개체에 표식이 사용되어 동일한 네이티브 인증서를 나타내는 관리되는 개체 간에 "삭제 책임"이 공유됩니다. 즉, 새 X509Certificate2(otherCert.Handle). Dispose()는 x509Certificate2Collection.Import를 통해 PFX에서 otherCert를 로드한 경우 Dispose()를 호출하는 동안 프라이빗 키 파일이 지워지지만 X509Certificate2 클래스 멤버를 통해 PFX에서 로드된 경우에는 삭제되지 않습니다. .NET의 일부 부분에서는 X509Chain 클래스 및 X509Certificate2Collection.Find 메서드와 같은 네이티브 인증서 핸들에서 새 관리형 X509Certificate2 개체를 내부적으로 만듭니다. 프레임워크의 이러한 부분과 직접 또는 간접적으로 사용하는 부분은 만드는 개체가 가비지를 수집할 때 조기 키 지우기가 발생할 수 있습니다.
.NET Framework 4.8의 초기 릴리스에서 버그가 도입되어 PersistKeySet 또는 EphemeralKeySet가 지정되지 않은 경우에도 삭제 마커를 적용하지 않는 X509Certificate2Collection.Import가 발생했습니다. .NET은 중단된 프라이빗 키 파일의 의도하지 않은 누적을 방지하기 위해 2021년 1월 보안 및 품질 롤업 에서 이 버그에 대한 수정 사항을 발표했습니다. 이 수정의 영향을 받는 호출자는 4.8 RTM 동작을 .NET Framework(의도치 않게 다른) 상태로 돌아갈 PersistKeySet 플래그를 지정할 수 있지만, 이렇게 하면 사용자 지정 정리 논리로 해결해야 하는 파일 누적이 발생합니다.
Windows용 .NET Core 및 Windows용 .NET 5 이상에서 X509Certificate2Collection.Import는 .NET Framework 2.0-4.7.2(및 모든 업데이트가 적용된 .NET Framework 4.8)와 동일한 방식으로 작동합니다. .NET 팀은 .NET 6(또는 이후 버전)에서 향상된 디자인으로 전환하는 것을 고려하고 있지만 이 디자인 업데이트는 .NET Framework, .NET Core 또는 .NET 5에는 적용되지 않습니다.