Práticas recomendadas de TLS/SSL
O TLS (Transport Layer Security) é um protocolo criptográfico projetado para proteger a comunicação entre dois computadores pela Internet. O protocolo TLS é exposto no .NET por meio da classe SslStream.
Este artigo apresenta as práticas recomendadas para configurar a comunicação segura entre o cliente e o servidor e pressupõe o uso do .NET. Para obter práticas recomendadas com .NET Framework, confira Melhores práticas de protocolo TLS com o .NET Framework.
Selecionar a versão do TLS
Embora seja possível especificar a versão do protocolo TLS a ser usada por meio da propriedade EnabledSslProtocols, é recomendável adiar para as configurações do sistema operacional usando o valor None (o padrão).
Adiar a decisão para o sistema operacional usa automaticamente a versão mais recente do TLS disponível e permite que o aplicativo selecione as alterações após as atualizações do sistema operacional. O sistema operacional também pode impedir o uso de versões TLS que não são mais consideradas seguras.
Selecionar conjuntos de criptografia
O SslStream
permite que os usuários especifiquem quais conjuntos de criptografia podem ser negociados pelo handshake do TLS por meio da classe CipherSuitesPolicy. Assim como nas versões TLS, é recomendável deixar o sistema operacional decidir quais são os melhores conjuntos de cifras para negociar e, portanto, é recomendável evitar o uso de CipherSuitesPolicy.
Observação
O CipherSuitesPolicy não tem suporte no Windows e as tentativas de instanciá-lo farão com que NotSupportedException seja lançado.
Especificar um certificado de servidor
Ao autenticar como um servidor, o SslStream requer uma instância do X509Certificate2. É recomendável sempre usar uma instância do X509Certificate2 que também contenha a chave privada.
Há várias maneiras pelas quais um certificado de servidor pode ser passado para SslStream:
- Diretamente como um parâmetro, para SslStream.AuthenticateAsServerAsync, ou por meio da propriedade SslServerAuthenticationOptions.ServerCertificate
- Via retorno de chamada de seleção na propriedade SslServerAuthenticationOptions.ServerCertificateSelectionCallback
- Passando um SslStreamCertificateContext na propriedade SslServerAuthenticationOptions.ServerCertificateContext
A abordagem recomendada é usar a propriedade SslServerAuthenticationOptions.ServerCertificateContext. Quando o certificado é obtido por uma das outras duas formas, uma instância de SslStreamCertificateContext é criada internamente pela implementação deSslStream. A criação de um SslStreamCertificateContext envolve a criação de um X509Chain que é uma operação intensiva de CPU. É mais eficiente criar um SslStreamCertificateContext uma vez e reutilizá-lo para várias instâncias de SslStream.
Reutilizar instâncias de SslStreamCertificateContext também permite o uso de recursos adicionais, como retomada da sessão TLS em servidores Linux.
Validação de X509Certificate
personalizada
Há certos cenários em que o procedimento de validação de certificado padrão não é adequado e é necessário aplicar alguma lógica de validação personalizada. Partes da lógica de validação podem ser personalizadas especificando SslClientAuthenticationOptions.CertificateChainPolicy ou SslServerAuthenticationOptions.CertificateChainPolicy. Como alternativa, a lógica completamente personalizada pode ser fornecida por meio da propriedade <System.Net.Security.SslClientAuthenticationOptions.RemoteCertificateValidationCallback>. Para obter mais informações, confira Confiança de certificado personalizada.
Confiança de certificado personalizada
Ao encontrar um certificado que não foi emitido por nenhuma autoridade de certificação confiável para a máquina (incluindo certificados autoassinados), o procedimento padrão de validação do certificado falhará. Uma maneira possível de resolver isso é adicionar os certificados de emissor necessários ao armazenamento confiável da máquina. Isso, no entanto, pode afetar outros aplicativos no sistema e nem sempre é possível.
A solução alternativa é especificar certificados raiz confiáveis personalizados por meio de um X509ChainPolicy. Para especificar uma lista de confiança personalizada que será usada em vez da lista de confiança do sistema durante a validação, considere o seguinte exemplo:
SslClientAuthenticationOptions clientOptions = new();
clientOptions.CertificateChainPolicy = new X509ChainPolicy()
{
TrustMode = X509ChainTrustMode.CustomRootTrust,
CustomTrustStore =
{
customIssuerCert
}
};
Os clientes configurados com a política anterior só aceitariam certificados confiáveis por customIssuerCert
.
Ignorar erros de validação específicos
Considere um dispositivo IoT sem um relógio persistente. Depois de ligado, o relógio do dispositivo começaria muitos anos no passado e, portanto, todos os certificados seriam considerados "ainda não válidos". Considere o código a seguir que mostra uma implementação de retorno de chamada de validação ignorando as violações do período de validade.
static bool CustomCertificateValidationCallback(
object sender,
X509Certificate? certificate,
X509Chain? chain,
SslPolicyErrors sslPolicyErrors)
{
// Anything that would have been accepted by default is OK
if (sslPolicyErrors == SslPolicyErrors.None)
{
return true;
}
// If there is something wrong other than a chain processing error, don't trust it.
if (sslPolicyErrors != SslPolicyErrors.RemoteCertificateChainErrors)
{
return false;
}
Debug.Assert(chain is not null);
// If the reason for RemoteCertificateChainError is that the chain built empty, don't trust it.
if (chain.ChainStatus.Length == 0)
{
return false;
}
foreach (X509ChainStatus status in chain.ChainStatus)
{
// If an error other than `NotTimeValid` (or `NoError`) is present, don't trust it.
if ((status.Status & ~X509ChainStatusFlags.NotTimeValid) != X509ChainStatusFlags.NoError)
{
return false;
}
}
return true;
}
Anexação de certificado
Outra situação em que a validação de certificado personalizado é necessária é quando os clientes esperam que os servidores usem um certificado específico ou um certificado de um pequeno conjunto de certificados conhecidos. Essa prática é conhecida como anexação de certificado. O snippet de código a seguir mostra um retorno de chamada de validação que verifica se o servidor apresenta um certificado com uma chave pública conhecida específica.
static bool CustomCertificateValidationCallback(
object sender,
X509Certificate? certificate,
X509Chain? chain,
SslPolicyErrors sslPolicyErrors)
{
// If there is something wrong other than a chain processing error, don't trust it.
if ((sslPolicyErrors & ~SslPolicyErrors.RemoteCertificateChainErrors) != 0)
{
return false;
}
Debug.Assert(certificate is not null);
const string ExpectedPublicKey =
"3082010A0282010100C204ECF88CEE04C2B3D850D57058CC9318EB5C" +
"A86849B022B5F9959EB12B2C763E6CC04B604C4CEAB2B4C00F80B6B0" +
"F972C98602F95C415D132B7F71C44BBCE9942E5037A6671C618CF641" +
"42C546D31687279F74EB0A9D11522621736C844C7955E4D16BE8063D" +
"481552ADB328DBAAFF6EFF60954A776B39F124D131B6DD4DC0C4FC53" +
"B96D42ADB57CFEAEF515D23348E72271C7C2147A6C28EA374ADFEA6C" +
"B572B47E5AA216DC69B15744DB0A12ABDEC30F47745C4122E19AF91B" +
"93E6AD2206292EB1BA491C0C279EA3FB8BF7407200AC9208D98C5784" +
"538105CBE6FE6B5498402785C710BB7370EF6918410745557CF9643F" +
"3D2CC3A97CEB931A4C86D1CA850203010001";
return certificate.GetPublicKeyString().Equals(ExpectedPublicKey);
}
Considerações para validação de certificado de cliente
Os aplicativos de servidor precisam ter cuidado ao exigir e validar certificados de cliente. Os certificados podem conter a extensão AIA (Acesso à Informação de Autoridade) que especifica onde o certificado do emissor pode ser baixado. O servidor pode, portanto, tentar baixar o certificado do emissor do servidor externo ao criar o X509Chain do certificado do cliente. Da mesma forma, os servidores podem precisar entrar em contato com servidores externos para garantir que o certificado do cliente não tenha sido revogado.
A necessidade de contatar servidores externos ao compilar e validar o X509Chain poderá expor o aplicativo a ataques de negação de serviço se os servidores externos tiverem lentidão na resposta. Portanto, os aplicativos de servidor devem configurar o comportamento de compilação de X509Chain usando o CertificateChainPolicy.