As assinaturas de nome forte – strong name signatures - (e assinaturas em geral) são um peça chave de segurança do Microsoft® .NET Framework. Porém, não importa quão bem projetadas as assinaturas .NET possam ser, não trarão o máximo benefício se não soubermos como utilizá-los apropriadamente. Este artigo do CLR Inside Out aborda nomes fortes, assinaturas de nome forte e como utilizá-los.

As assinaturas digitais são utilizadas para verificar a integridade dos dados que são passados do criador (o assinante) para um recipiente (o verificador). As assinaturas são geradas e verificadas utilizando-se criptografia de chave pública. O assinante de uma mensagem tem um par de chaves criptográficas: uma chave pública, a qual é do conhecimento do público em geral e uma chave privada, a qual é conhecida somente pelo assinante. O verificador conhece somente a chave pública, a qual é utilizada para verificar que o assinante conhecia a chave privada e a mensagem.

Em alguns casos, quando alguma infra-estrutura adicional estiver disponível, as assinaturas digitais também podem ser utilizadas para conhecer com segurança o nome do assinante e garantir que um determinado bloco de dados (uma mensagem, um código ou qualquer coisa semelhante), não foi adulterada depois que o assinante criou a assinatura dos dados.

Vários mecanismos são utilizados para implementar assinaturas digitais. A implementação atual de nomes fortes no .NET Framework baseia-se no algoritmo de chave pública RSA e no algoritmo hash SHA-1.

Assinatura de Nome Forte

Os nomes fortes oferecem um mecanismo poderoso para dar aos assemblys do .NET Framework uma identidade unívoca. O nome forte de um assembly compõe-se de cinco partes: uma chave pública, um nome simples, uma versão, uma cultura opcional e uma arquitetura de processador opcional. A chave pública é uma chave pública RSA. O nome simples é apenas um string de texto - normalmente o nome do arquivo (sem a extensão) que contém o assembly. A versão é um número de versão de quatro partes, na forma Major.Minor.Build.Revision (por exemplo, 1.0.0.1).

Para obtermos um nome forte válido, um assembly é assinado com nome forte durante o processo de construção. Isto é feito utilizando-se a chave privada que corresponde à chave pública no nome forte. A seguir, a assinatura de nome forte pode ser verificada utilizando-se a chave pública. Existem algumas técnicas diferentes para criar uma assinatura de nome forte e para gerenciar a chave privada utilizada na assinatura. Abordaremos essas várias técnicas posteriormente neste artigo.

Quando um assembly referencia um assembly fortemente nomeado, o assembly que faz a referência captura informações sobre o nome forte do assembly referenciado. Quando o .NET Framework carrega um assembly fortemente nomeado, faz a verificação da assinatura de nome forte. Se a assinatura de nome forte do assembly não puder ser verificada, o .NET Framework não carregará o assembly.

Uma exceção a este processo tem a ver com assemblys fortemente nomeados que vêm do Global Assembly Cache (GAC). Estes não são verificados toda vez que o .NET Framework os carrega. Isto acontece porque os assemblys no GAC foram verificados quando foram instalados no mesmo. Desde que o GAC é bloqueado e acessível apenas aos administradores, os assemblys localizados nele não precisam ser verificados toda vez que são carregados.

Por que Utilizar Nomes Fortes?

Os nomes fortes evitam que o código seja modificado por terceiros (isto é, naturalmente, caso guardemos a chave privada de forma segura). Como mencionado anteriormente, o .NET Framework verifica a assinatura carregando o assembly ou instalando-o no GAC. Sem acessar a chave privada, um usuário malicioso não poderá modificar o código e assiná-lo novamente com sucesso.

Uma vez que nomes fortes evitam modificações maliciosas, poderão ser utilizados para tomar certas decisões de segurança. Uma empresa, por exemplo, pode decidir confiar em todo o código que vem da intranet da empresa e foi assinado com a chave de nome forte da empresa. Porém, as assinaturas de nome forte não contêm nenhuma informação confiável a respeito do publicador, portanto, enquanto é seguro confiar em chaves que estão sob nosso controle, é arriscado confiar em chaves de outras organizações a menos que disponhamos de um canal seguro para obter a sua chave pública.

É também importante observar que nomes fortes não têm nenhum mecanismo de revogação que possa ser utilizado caso a chave privada estiver comprometida. É desnecessário dizer que se planejamos utilizar o nome forte da empresa como elemento de confiança, devemos conservar o chave privada realmente privada! (Veremos algumas dicas quanto a isto posteriormente neste artigo). É interessante que o alcance da confiabilidade de um nome forte esteja restrito a um determinado local (como o servidor de aplicações da empresa, por exemplo) ou pelo menos a uma zona (como a nossa zona da intranet). Isto provê alguma mitigação para o caso em que a nossa chave privada esteja comprometida ou que uma versão com bug de um assembly assinado seja maliciosamente utilizado, e que pessoas mal intencionadas tentem atrair os usuários rodando o assembly de fora da intranet.

O que os Nomes Fortes não Podem Fazer

A assinatura de nome forte é útil para impedir que um usuário malicioso adultere um assembly que o assine novamente com a chave do assinante original. No entanto, não poderá impedir que um usuário malicioso extraia a assinatura de nome forte completa, modifique o assembly, o assine novamente com a sua própria chave e a seguir o faça passar como o seu próprio código. As assinaturas digitais em geral e as assinaturas de nome forte especialmente, não conseguem resolver este problema. O Watermarking é uma solução mais apropriada para proteger-nos contra este cenário.

Os nomes fortes são seguros somente quando a chave privada de nome forte é eficientemente guardada. Como mencionado anteriormente, estas não têm nenhuma capacidade de revogação e não tem data para vencimento. Nem os nomes fortes fornecem inerentemente qualquer caminho para fazer com segurança o mapeamento de uma chave pública para um determinado publicador. Assim, os nomes fortes devem ser utilizados muito cautelosamente ao tomarmos decisões de segurança. O ideal é que estas decisões de segurança só devem ser tomadas quando estivermos certos de que a chave pública de nome forte corresponde a um determinado publicador (por exemplo, quando as chaves públicas são geradas pelo departamento de TI da nossa organização). Devemos observar que os esquemas de assinatura mais sofisticados disponíveis (Authenticode®, por exemplo), oferecem revogação e capacidades de verificação do publicador.

Trabalhando com Nomes Fortes

A utilização da assinatura de nome forte é uma boa idéia para a maioria das aplicações, especialmente para aquelas que são distribuídos através de uma rede ou por qualquer meio não totalmente controlado pelo distribuidor. Os benefícios obtidos com os mecanismos de anti-logro e de anti-adulteração são bastante valiosos. No entanto, devemos estar conscientes em relação a alguns desafios que surgem da utilização da assinatura de nome forte.

Em primeiro lugar, todos os assemblys referenciados por um assembly fortemente nomeado também devem ser fortemente nomeados. Se referenciarmos um assembly escrito por terceiros que não for assinado com nome forte, não poderemos assinar com nome forte o nosso assembly.

Em segundo lugar, a assinatura de nome forte torna a manutenção mais complicada. Sob a política de versionamento atual, um assembly sempre tentará carregar a versão exata do assembly com o qual foi construído. Se, por exemplo, nossa aplicação foi construída com a versão 1.0.0.0 de um assembly fortemente nomeado e reparamos um bug no assembly, incrementando o número de versão para 1.0.0.1, a aplicação existente não encontrará o assembly atualizado.

Existem algumas maneiras de lidar com esta situação. Poderíamos, por exemplo, re-construir a aplicação com o novo assembly. Obviamente, este não seria um processo recomendável apenas para reparar um bug e em algumas situações poderia não ser uma opção. Entretanto, funciona perfeitamente bem quando o nosso código não é amplamente distribuído ou o assembly que foi reparado não é amplamente compartilhado pelas aplicações.

Outra opção é instalar o assembly no GAC. Neste caso, poderíamos utilizar a política de publicador (publisher policy) para redirecionar a carga da versão 1.0.0.0 para a versão 1.0.0.1. No entanto, a política de publicador é complicada.

Outra opção ainda, seria reparar o bug e não alterar o número da versão do assembly. Desta maneira, as aplicações existentes ainda poderiam encontrar o assembly. Esta é a abordagem que o .NET Framework utiliza para o reparo de bugs, embora não seja apropriada para novas funcionalidades ou qualquer coisa que quebre a compatibilidade. Se estivermos adicionando novas funcionalidades, devemos incrementar a versão do assembly e fazer com que as aplicações sejam re-construídas para poderem optar pelas novas funcionalidades.

Obviamente, a assinatura de nome forte acrescenta alguma complexidade ao nosso processo de desenvolvimento e ao processo de construção. Porém, isto é necessário para que a chave privada permaneça segura. Logo mais, abordaremos como lidar com esta complexidade.

Agora vamos dar uma rápida olhada em um cenário de assinatura simples: a criação de um par de chaves aleatório e o seu armazenamento em um arquivo. Estamos utilizando esta abordagem como uma forma de apresentar o funcionamento da a assinatura de nome forte. Obviamente, armazenar a chave privada em um arquivo junto com o nosso código não é uma boa maneira de proteger a nossa chave, portanto não deveremos utilizar esta abordagem em ambiente de produção.

Se estivermos utilizando ferramentas de linha de comando, poderemos criam um par de chaves com o sn.exe do SDK. A seguir, passamos o par de chaves para o compilador quando compilamos o nosso código. Os seguintes comandos criam um novo par de chaves aleatório utilizando o sn.exe e constroem um assembly assinado com nome forte com o csc.exe:

sn-k mykey.snk
 csc/keyfile:mykey.snk myapp.cs

Isto produz a aplicação myapp.exe assinada com nome forte. Poderíamos fazer a mesma coisa com o Visual Studio® 2005. O painel Signing na página de propriedades de projeto, controla as assinaturas de nome forte para o nosso projeto .Para criar um arquivo chave simples, selecionamos a opção "Sign the assembly" e criamos um novo arquivo chave que não é protegido por senha. O Visual Studio adicionará o arquivo chave ao nosso projeta e o utilizará para assinar a nossa aplicação.

Como mencionado anteriormente, armazenar a chave privada em um arquivo junto com o nosso código não é uma forma segura de proteger a nossa chave privada . Felizmente, o Visual Studio nos permite proteger o arquivo chave com senha. Quando criamos uma nova chave de nome forte para o nosso projeto, basta apenas deixar a opção "Protect my key file with a password" selecionada e introduzir uma senha para nosso arquivo chave . O Visual Studio adicionará um certificado pessoal - um arquivo PFX - ao nosso projeto. Este arquivo inclui a nossa chave privada encriptada, protegida pela senha que foi introduzida.

Apesar de que a proteção por senha do nosso arquivo chave representa uma solução muito melhor do que armazená-lo desprotegido, isto ainda não é o ideal. Ainda teríamos que distribuir o arquivo PFX para todos os nossos desenvolvedores e estes teriam que conhecer a senha do arquivo PFX. Segredos que são amplamente compartilhados como este, não conseguem ficar secretos por muito tempo. O ideal seria não distribuir a chave privada para construir e testar o nosso código durante o desenvolvimento.

Assinatura Atrasada

É aqui que entra em cena a assinatura atrasada. O atraso da assinatura nos permite gerar uma assinatura parcial durante o desenvolvimento com acesso apenas à chave pública. A chave privada pode ser armazenada com segurança fora das mãos dos desenvolvedores e utilizada para aplicar a assinatura de nome forte final exatamente antes da distribuição do nosso código.

Para utilizarmos a assinatura atrasada, seguimos os seguintes passos:

  1. Extraímos a chave pública do par de chaves. A chave pública pode ser distribuída para os desenvolvedores da empresa sem qualquer risco de segurança.
  2. Utilizamos a chave pública para atrasar a assinatura dos nossos assemblys durante o desenvolvimento. Como os assemblys não são totalmente assinadas ainda, também teremos que configurar nossas máquinas de desenvolvimento para omitir a verificação de assinatura de nome forte para a nossa chave - de outra maneira, o .NET Framework não permitirá que sejam carregados os assemblys assinados com atraso.
  3. Quando estivermos prontos para distribuir o nosso código, utilizamos a (bem guardada) chave privada para aplicar a assinatura de nome forte final ao nosso assembly assinado com atraso.

Para o primeiro passo, poderíamos utilizar a ferramenta sn.exe para extrair a chave pública de um par de chaves. Por exemplo, digamos que temos o nosso par de chaves armazenado em um recipiente de chaves seguro chamado EnterpriseKey, em uma máquina bloqueada. utilizaríamos o seguinte comando para extrair a chave pública para o arquivo EnterprisePublicKey.pk:

sn -pc EnterpriseKey EnterprisePublicKey.pk

A seguir, poderíamos distribuir EnterprisePublicKey.pk para os desenvolvedores da empresa. Normalmente, poderíamos utilizar a mesma chave pública para múltiplos assemblys, pois cada um terá um nome simples diferente.

Para o segundo passo, utilizamos as opções do compilador para atrasar a assinatura dos nossos assemblys com a chave pública ao construí-los. O exemplo a seguir, cria uma aplicação assinada com atraso, utilizando opções do compilador C#:

csc/delaysign +/keyfile:EnterprisePublicKey.pk myapp.cs

Agora ao tentarmos rodar myapp.exe verificamos que não roda porque não tem uma assinatura de nome forte completa válida. Devemos configurar o .NET Framework para omitir a verificação dos assemblys assinados com atraso utilizando a nossa chave pública. Para fazer isto, utilizamos novamente a ferramenta sn.exe. O seguinte exemplo configura o .NET Framework para omitir a verificação de assinatura de nome forte para o assembly myapp.exe em nossas máquinas de desenvolvimento:

sn -Vr myapp.exe

Poderíamos também utilizar a ferramenta sn.exe para omitir a verificação de assinatura de todos os assemblys assinadas com uma determinada chave. Por exemplo, se quisermos configurar a nossa máquina para omitir todo os assemblys assinados com atraso para a mesma chave da nossa aplicação, teremos antes que fazer o seguinte nas nossas máquinas de desenvolvimento:

sn -T myapp.exe

Isto imprimirá o valor do símbolo de chave pública, uma versão reduzida da chave pública que tem o seguinte aspecto:

Microsoft (R) .NET Framework Strong Name Utility  Version 2.0.50727.42
Copyright (c) Microsoft Corporation.  All rights reserved.
Public key token is b03f5f7f11d50a3a

Notar que o valor real do símbolo de chave pública variará dependendo da nossa chave pública. Em seguida, executamos a seguinte comando para omitir a verificação de nome forte de qualquer assembly utilizando este símbolo de chave pública:

sn -Vr *,b03f5f7f11d50a3a

Qualquer assembly assinado com atraso com esta chave pública omitirá agora a verificação de assinatura de nome forte e rodará na nossa máquina de desenvolvimento. Observe que a omissão da verificação de assinatura de nome forte é algo que só deve ser feito em máquinas de desenvolvimento. Nunca deve ser feita em computadores de produção, pois tornaria estas máquinas vulneráveis a ataques de adulteração de assembly.

Finalmente, para o terceiro passo, utilizamos a verdadeira chave privada para gerar a assinatura de nome forte completa final, antes de distribuir o nosso código. Novamente, utilizamos a ferramenta sn.exe para fazer isto. Por exemplo, se o nosso par de chave estiver armazenado em um recipiente de chaves chamado EnterpriseKey, no nosso computador que faz as assinaturas, utilizaríamos o seguinte comando para assinar um assembly assinado com atraso:

sn -Rc myapp.exe EnterpriseKey

Agora o assembly tem uma assinatura completa. Uma das boas coisas a respeito da assinatura atrasada, é que com ela, não será necessária a re-construção de nenhum assembly que utilize o assembly assinado com atraso. Qualquer assembly que faça referência ao assembly assinado por atraso, terá acesso à sua chave pública e será, assim, capaz de criar uma referencia de assembly completa, mesmo que o assembly não tenha uma assinatura completa.

Teste de Assinatura Chave

Existe uma nova funcionalidade no .NET Framework 2.0 chamada de teste de assinatura chave. Isto é semelhante ao atraso de assinatura. Com o teste de assinatura chave, assinamos o nosso assembly com uma chave privada alternativa durante o desenvolvimento e, a seguir, configuramos os nossos computadores de desenvolvimento para verificar as assinaturas contra a chave pública alternativa que corresponde a chave privada alternativa. O teste de assinaturas chave é valioso quando desejamos um comportamento quase exatamente igual ao comportamento de assinatura completa para efeitos de teste. Por exemplo, com a assinatura atrasada, as assemblys não são absolutamente verificados. Isto pode escamotear questões de desempenho, caso a nossa aplicação carregue muitos assemblys fortemente nomeados, pois nenhuma das assinaturas de nome forte seria verificada durante as rodadas de teste, quando o atrasado de assinatura estiver ativo.

O teste de assinaturas chave é composto dos seguintes passos:

  1. Atrasamos a assinatura do nosso assembly.
  2. Criamos um par de chaves de teste com o qual assinaremos nossos assemblys. Podemos fazer isto utilizando o sn.exe com a opção -k.
  3. Aplicamos o teste de assinatura ao nosso assembly. Podemos fazer isto utilizando o sn.exe com uma das opções -TS ou -TSc. Isto gerará uma assinatura para o assembly utilizando a chave privada do nossa par de chaves de teste.
  4. Configuramos as nossas máquinas de desenvolvimento para utilizar a chave pública de teste, para a verificação de assemblys assinadas com a chave de teste. Podemos utilizar a opção -Vr com o sn.exe para fazer isto.

Quando estivermos prontos para distribuir nossos assemblys, os assinaremos com a verdadeira chave privada, utilizando o sn.exe com uma das opções -R ou -Rc, tal como faríamos com um assembly assinado por atraso. A seguinte série de comandos cria um par de chaves de teste, a chave de teste assina um assembly assinado por atraso, extrai a chave pública de teste e configura uma máquina de desenvolvimento para verificar o assembly contra a chave de teste:

sn -k TestKey.snk
sn -TS myapp.exe TestKey.snk
sn -p TestKey.snk TestPublicKey.pk
sn -Vr myapp.exe TestPublicKey.pk

Quando utilizarmos o teste de assinatura chave, deveremos ainda assim proteger a chave privada de teste, porém, não teremos que guardá-la com tanto zelo como faríamos com a verdadeira chave privada. Seria também uma boa idéia modificar o par de chaves de teste periodicamente.

Gerenciando a Chave Privada

É muito mais fácil manter a nossa chave privada segura se estivermos utilizando a assinatura atrasada ou testando a assinatura chave. Só temos que mandar instalar a chave privada em apenas um ou talvez em uma pequeno quantidade de computadores de assinatura bloqueados. Poderíamos instalar a chave privada em um recipiente de chave utilizando listas de controle de acesso restritivas (ACLs) para impedir o acesso não autorizado ao recipiente de chave. Alternativamente, poderíamos armazenar a chave privada em um cartão inteligente ou em qualquer outro dispositivo de hardware separado. a ferramenta sn.exe nos permite utilizar um provedor de serviço criptográfico (CSP – cryptographic server provider) diferente para a assinatura. Ver a opção -c na documentação do sn.exe.

Trabalhando com o Authenticode

As assinaturas Authenticode fornecem mais semântica de segurança do que assinaturas de nome forte. Para gerar uma assinatura Authenticode, temos que obter um certificado assinado por código de uma autoridade certificadora de confiança (CA – certificate authority), a qual pode ser uma CA comercial ou uma CA estabelecida pelo nosso departamento de TI. A CA fornece algum nível da garantia de que a pessoa ou a organização com o certificado são quem dizem que são, fornecendo-nos alguma confiabilidade quanto ao fato de que o software veio na verdade do publicador relacionado na assinatura. O Authenticode suporta também expiração e revogação, tornando-o mais flexível do que as assinaturas de nome forte. No entanto, como acontece com as assinaturas de nome forte, a mera presença de uma assinatura Authenticode não garante que o código seja confiável. O Authenticode simplesmente fornece alguma garantia a respeito de quem publicou o código e de que o código não foi adulterado desde que o publicador o assinou.

As assinaturas Authenticode podem coexistir com assinaturas de nome forte. No entanto, devemos realizar a assinatura de nome forte no nosso assembly antes de aplicar a assinatura Authenticode. A adição da assinatura Authenticode não invalidará a assinatura de nome forte.

As assinaturas Authenticode podem ser utilizadas para garantir as aplicações distribuídas utilizando a novo modelo ClickOnce do .NET Framework 2.0. Podemos utilizar ferramentas no Visual Studio 2005 ou no SDK para aplicar uma assinatura Authenticode no manifesto de distribuição da nossa aplicação ClickOnce. A seguir instalamos o certificado utilizado para criar a assinatura Authenticode na área de armazenamento de certificados de publicador confiáveis, nos computadores do cliente que rodarão a nossa aplicação; isto pode ser feito utilizando a Group Policy, o Systems Management Server (SMS) ou qualquer outro mecanismo de distribuição que tivermos disponível. Qualquer aplicação ClickOnce assinada com este certificado Authenticode, rodará sem qualquer problema. Esta é uma forma conveniente de tornar confiáveis as aplicações da nossa empresa.

As assinaturas de nome forte tem um papel valioso ao fornecer uma maneira de identificar assemblys gerenciados. Temos apenas que lembrar que a segurança de chave privada é crítica para nome forte. Se realmente decidirmos que as assinaturas de nome forte são apropriadas para a nossa organização, devemos nos certificar de utilizar algumas das técnicas apresentadas neste artigo para gerenciar e proteger a segurança das nossas chaves privadas.