Duração da chave privada no Windows e o Rollup de Segurança e Qualidade de .NET Framework de janeiro de 2021

A Pré-visualização da Atualização Cumulativa de novembro de 2020 para Windows 10 versão 2004 e inferiores, bem como a versão do Rollup de Segurança e Qualidade de janeiro de 2021 para .NET Framework 4.8, lançaram uma alteração para melhorar o processo de limpo-up para certificados X509Certificate2. Abaixo, fornecemos esclarecimentos adicionais sobre o tempo de vida útil da chave privada no Windows.

Desde .NET Framework 2.0, existem duas maneiras diferentes de carregar um certificado e sua chave privada associada a partir de um arquivo PKCS#12 PFX: como um objeto de certificado por meio de membros na classe X509Certificate2 ou como todos os certificados presentes no PFX por meio dos métodos X509Certificate2Collection.Import.

O comportamento padrão é que a chave privada é carregada em uma chave persistente (nomeada) por meio de uma das bibliotecas de criptografia do sistema, que indiretamente grava um arquivo no disco.  A maioria dos chamadores que carregam um PFX usam apenas o objeto de certificado temporariamente, portanto, quando o .NET está liberando os recursos nativos associados ao certificado, ele também exclui a chave privada.  As principais exceções a este comportamento são

  1. O sinalizador X509KeyStorageFlags.PersistKeySet, que resulta na escrita do ficheiro, mas não na eliminação,
  2. O sinalizador X509KeyStorageFlags.EphemeralKetSet, que resulta no carregamento da chave privada para a memória sem nenhum ficheiro de suporte,
  3. Encerramento anormal do processo, que pode ocorrer quando a eliminação de chave está pendente.

Contraintuitivamente, as duas maneiras diferentes de carregar certificados sempre usaram mecanismos diferentes para rastrear quando é um momento apropriado para excluir a chave privada.  O carregamento de um único certificado por meio da classe X509Certificate2 usa um marcador que só é visível para o tempo de execução do .NET e só se aplica a essa referência de objeto.  O carregamento de um PFX através de X509Certificate2Collection.Import utiliza um marcador no objeto de certificado nativo, o que resulta na partilha de "responsabilidade de eliminação" em todos os objetos geridos que representam o mesmo certificado nativo.  Isso significa que o novo X509Certificate2(otherCert.Handle). Dispose() fará com que o arquivo de chave privada seja apagado durante a chamada para Dispose() se otherCert foi carregado de um PFX via X509Certificate2Collection.Import, mas não se foi carregado de um PFX através de qualquer membro da classe X509Certificate2.  Algumas partes do .NET criam internamente novos objetos X509Certificate2 gerenciados a partir de identificadores de certificado nativos, como a classe X509Chain e o método X509Certificate2Collection.Find.  Essas partes da estrutura, e quaisquer partes que direta ou indiretamente as usam, podem resultar em apagamento prematuro de chaves à medida que os objetos criados são coletados lixo.

Foi introduzido um bug na versão inicial do .NET Framework 4.8 que resultou em X509Certificate2Collection.Import para não aplicar o marcador de exclusão, mesmo que nem PersistKeySet nem EphemeralKeySet foram especificados. O .NET lançou uma correção para este erro no Rollup de Segurança e Qualidade de janeiro de 2021 para evitar a acumulação não intencional de ficheiros de chave privada abandonados.  Os autores de chamadas afetados por esta correção podem especificar que o sinalizador PersistKeySet volte ao comportamento (involuntariamente diferente) .NET Framework 4.8 RTM, embora tal resulte numa acumulação de ficheiros que tem de ser resolvida com uma lógica de limpeza personalizada.

X509Certificate2Collection.Import no .NET Core para Windows e .NET 5+ para Windows comporta-se da mesma forma que o .NET Framework 2.0-4.7.2 (e .NET Framework 4.8 com todas as atualizações aplicadas).  A equipa do .NET está a ponderar mudar para uma estrutura melhorada no .NET 6 (ou em versões futuras), mas esta atualização de estrutura não se aplicará ao .NET Framework, .NET Core ou .NET 5.