MSDN Magazine > Home > Todas as edições > 2007 > March >  NET Security: Tenha suporte a certificados em s...
NET Security
Tenha suporte a certificados em seus aplicativos com o .NET Framework 2.0
Dominick Baier

Este artigo aborda:
  • O Windows Certificate Store
  • Classes de certificado no .NET
  • Validação, SSL, serviços da Web e assinatura de código
  • Assinando e criptografando dados
Este artigo utiliza as seguintes tecnologias:
.NET Framework 2.0
Faça o download do código deste artigo:: Certificates2007_03.exe (160 KB)
Procurar código online
Certificados são usados em muitos lugares no Microsoft® .NET Framework, da comunicação segura à assinatura de código para políticas de segurança. O .NET Framework 2.0 introduziu o suporte remodelado dos certificados e adicionou um novo namespace completamente novo nas operações criptográficas com certificados compatíveis com os padrões . Neste artigo, discutirei o que há por trás dos certificados e do Windows® Certificate Store. Também lhe mostrarei como trabalhar com as APIs de certificado e como elas são usadas pelo Framework para implementar recursos de segurança.
Um "certificado" é realmente um arquivo codificado ASN.1 (Abstract Syntax Notation One) que contém uma chave pública e informações adicionais sobre a chave e o seu proprietário. Além disso, um certificado tem um período de validade e é assinado com outra chave (o chamado emissor) que é usada para fornecer uma garantia de autenticidade desses atributos e, mais importante, a própria chave pública. Você pode pensar na ASN.1 como um tipo de XML binária. Como a XML, ela também possui regras de codificação, tipos fortes e marcas; entretanto, tratam-se de valores binários que freqüentemente não correspondem a qualquer caractere imprimível.
Para que um arquivo como esse seja intercambiável entre os sistemas, é necessário um formato padrão. Ele é o X.509 (atualmente na versão 3), que é descrito no RFC 3280 (tools.ietf.org/html/rfc3280 - em inglês). O X.509 não dita o tipo de chave incorporada no certificado, mas o algoritmo RSA é atualmente o algoritmo criptográfico assimétrico mais popularmente usado.
Começarei com uma pequena história. O nome RSA é um acrônimo dos sobrenomes dos três inventores deste algoritmo: Ron Rivest, Adi Shamir e Len Adleman. Ele tinham uma empresa, a RSA Security, que publicou vários documentos de padrão chamados PKCS (Public Key Cryptography Standards). Esses documentos descrevem vários aspectos da criptografia.
Um dos mais populares desses documentos, PKCS nº 7, define um formato binário de dados assinados e criptografados chamados CMS (Cryptographic Message Syntax). O CMS é atualmente usado em muitos protocolos de segurança populares, incluindo SSL (Secure Sockets Layer) e S/MIME (Secure Multipurpose Internet Mail Extensions). Por ser um padrão, ele também é o formato escolhido para quando os aplicativos precisam trocar dados assinados e criptografados entre várias partes. Os documentos PKCS estão disponíveis no site dos laboratórios RSA (www.rsasecurity.com/rsalabs/node.asp?id=2124 - em inglês).

Como obter um certificado
Há várias formas de adquirir um certificado. Quando os arquivos estiverem sendo trocados, os certificados irão geralmente aparecer em um de dois formatos. Arquivos com a extensão .cer são assinados como arquivos ASN.1 no formato X.509v3. Eles contêm uma chave pública e as informações extras que mencionei anteriormente. Isso é o que você dá aos parceiros comerciais ou amigos para que eles possam usar a chave pública para criptografar dados para você.
Você também pode encontrar arquivos com uma extensão .pfx (Personal Information Exchange). Um arquivo .pfx contém um certificado e a chave privada correspondente (o formato é descrito no padrão PKCS nº 12). Tais arquivos são altamente confidenciais e são geralmente usados para importar pares de chave em um servidor ou para fins de backup. Ao exportar pares de chaves, o Windows se oferece para criptografar o arquivo .pfx com uma senha; você tem que fornecer essa senha novamente ao importar o par de chaves.
Você também pode gerar seus próprios certificados. A forma como você os gera normalmente depende de como eles serão usados. Para cenários normais da Internet, onde você não sabe quem são seus parceiros, você geralmente solicita um certificado de uma CA (autoridade de certificação comercial). Essa abordagem tem a vantagem de que essas CAs conhecidas já possuem a confiança do Windows e de quaisquer outros sistemas operacionais (e navegador) que ofereçam suporte a certificados e SSL. Conseqüentemente, você não precisa fazer uma troca de chave de CA .
Em cenários entre empresa e de intranet, você pode usar uma CA interna. Os Certificate Services estão incluídos no Windows 2000 e no Windows Server® 2003. Combinada com o Active Directory®, essa funcionalidade permite que você distribua facilmente os certificados em uma organização. (Daqui a pouco, vou lhe mostrar como solicitar certificados de uma CA privada).
Às vezes, durante o desenvolvimento, você pode se encontrar em uma situação em que as abordagens que acabei de descrever simplesmente não funcionam. Por exemplo, se você precisar de um certificado rapidamente para fins de teste, poderá usar makecert.exe. Incluída no SDK do .NET Framework, essa ferramenta gera certificados e pares de chaves. Existe uma ferramenta semelhante, chamada selfssl.exe, no kit de recursos do IIS; ela é especializada na criação de pares de chave SSL, e ela também pode configurar o IIS com um par de chaves como tal em uma única etapa.

O Windows Certificate Store
Os certificados e suas chaves privadas correspondentes podem ser armazenados em uma variedade de dispositivos, tais como discos rígidos, cartões inteligentes e tokens USB. O Windows fornece uma camada de abstração, chamada armazenamento de certificado, para unificar a forma como você acessa os certificados, independentemente de onde eles estão armazenados. Desde que o dispositivo de hardware tenha um CSP (Cryptographic Service Provider) aceito pelo Windows, você pode acessar os dados armazenados nele usando a API do Certificate Store.
O armazenamento de certificado é inserido no perfil do usuário. Isso permite o uso de ACLs nas chaves de uma conta específica. Cada armazenamento é particionado em recipientes. Por exemplo, existe um recipiente chamado Pessoal, onde você armazena seus próprios certificados (aqueles que têm uma chave privada associada). O recipiente Autoridades de Certificação de Raiz Confiáveis mantém os certificados de todas as CAs em que você confia. O recipiente Outras Pessoas mantém os certificados das pessoas com as quais você se comunica de forma segura. E assim por diante. A forma mais fácil de obter seu armazenamento de certificado é executar certmgr.msc.
Também existe um armazenamento para todo o computador, que é usado pelas contas de máquina do Windows (NETWORK, LOCAL SERVICE e LOCAL SYSTEM) ou se você deseja compartilhar certificados ou chaves entre contas. Os aplicativos ASP.NET sempre usam o armazenamento de computador; em aplicativos da área de trabalho, você geralmente instala certificados no armazenamento do usuário.
Somente os administradores podem gerenciar o computador e os armazenamentos de conta de serviço. Para essa finalidade, você tem que iniciar o mmc.exe (Microsoft Management Console) e adicionar o snap-in Certificados. Lá, você pode escolher o armazenamento a ser administrado. A Figura 1 mostra um instantâneo do snap-in MMC.
Figura 1 Snap-In MMC Certificados (Clique na imagem para aumentar a exibição)
Além de permitir que você importe, exporte e pesquise certificados, o snap-in também permite que você solicite certificados de uma CA empresarial interna. Basta clicar com o botão direito do mouse no recipiente pessoal e selecionar Todas as Tarefas | Solicitar Certificado. O computador local gera um par de chaves RSA e envia a parte da chave pública para a CA assinar. O Windows adiciona o certificado assinado ao armazenamento de certificado e a chave privada correspondente a um recipiente de chaves. O certificado é vinculado ao recipiente de chaves através de um atributo de armazenamento.
Os recipientes de chaves privadas são fortemente protegidos pela lista de controle de acesso (ACL) na conta correspondente ou em LOCAL SYSTEM. Isso é um problema quando você deseja acessar chaves armazenadas no perfil do computador a partir do ASP.NET ou de outras contas do usuário. Escrevi uma ferramenta que você pode usar para modificar as ACLs do arquivo de recipiente (ela está disponível em www.leastprivilege.com/HowToGetToThePrivateKeyFileFromACertificate.aspx - em inglês).
As CAs comerciais e do Windows possuem interfaces da Web para a solicitação de certificados. Nesses cenários, geralmente um controle ActiveX® no Internet Explorer® gera as chaves e as importa para o armazenamento do usuário atual. Via de regra, quando você deseja tornar um certificado acessível para um usuário ou serviço, há duas opções: importá-lo para seu armazenamento ou solicitá-lo enquanto se conecta como esse usuário.

Trabalhando com certificados
Os certificados são usados em vários lugares no .NET Framework e, em determinado nível, toda essa funcionalidade depende da classe X509Certificate do namespace System.Security.X509Certificates. Se olhar mais a fundo, você também encontrará uma classe de certificado que termina com 2. Isso se deve ao fato de o .NET Framework 1.x ter tido uma representação dos certificados X.509 chamada X509Certificate. Essa classe tinha funcionalidade limitada e nenhum suporte a operações criptográficas. Na versão 2.0, foi adicionada uma nova classe chamada X509Certificate2. Ela é derivada de X509Certificate e adiciona muitas funcionalidades. Você pode fazer conversões entre elas, se necessário, mas sempre que possível, use a versão mais recente.

Acessando certificados
Você pode recuperar certificados diretamente do sistema de arquivos. Entretanto, é melhor recuperá-los a partir do armazenamento de certificado. Para criar uma instância do X509Certificate2 a partir de um arquivo .cer, simplesmente passe o nome do arquivo para o construtor:
X509Certificate2 cert1 = new X509Certificate2("alice.cer");
Você também pode carregar certificados a partir de arquivos .pfx. Entretanto, como mencionei anteriormente, os arquivos .pfx podem ser protegidos por senha, e você deve fornecer essa senha como uma SecureString. SecureString criptografa a senha internamente e tenta minimizar a exposição da mesma na memória, arquivos de página e despejos de memória. Por isso, você só pode adicionar um único caractere (tipo de valor) de cada vez à seqüência de caracteres. O código na Figura 2, que desabilita o eco do console e retorna uma SecureString, é útil se você deseja solicitar aos seus usuários uma senha do console.
private SecureString GetSecureStringFromConsole()
{
    SecureString password = new SecureString();

    Console.Write("Enter Password: ");
    while (true)
    {
        ConsoleKeyInfo cki = Console.ReadKey(true);

        if (cki.Key == ConsoleKey.Enter) break;
        else if (cki.Key == ConsoleKey.Escape) 
        {
            password.Dispose();
            return null;
        }
        else if (cki.Key == ConsoleKey.Backspace)
        {
            if (password.Length != 0)
                password.RemoveAt(password.Length - 1);
        }
        else password.AppendChar(cki.KeyChar);
    }

    return password;
}

No artigo "Credential Management with the .NET Framework 2.0" (disponível em msdn.microsoft.com/library/en-us/dnnetsec/html/credmgmt.asp - em inglês), Kenny Kerr incluiu o código para converter o resultado da caixa de diálogo de credenciais usual do Windows em uma SecureString. Independentemente de como você a obtém, a SecureString pode depois ser passada para o construtor X509Certificate2 a fim de carregar o arquivo .pfx, da seguinte forma:
X509Certificate2 cert2 = new X509Certificate2("alice.pfx", password);
para acessar o armazenamento de certificado do Windows, use a classe X509Store. Em seu construtor, você fornece o local do armazenamento (usuário ou computador atual) e o nome do armazenamento. Você pode usar uma seqüência de caracteres ou a enumeração StoreName para especificar o recipiente que deseja abrir. Tenha cuidado porque os nomes internos nem sempre correspondem aos nomes que você encontra no snap-in MMC. O recipiente Pessoal é mapeado para o nome Meu, onde Outras Pessoas se torna AddressBook.
Quando você tiver uma instância válida do X509Store, poderá pesquisar, recuperar, excluir e adicionar certificados. Com a exceção dos cenários de desenvolvimento, você provavelmente usará a funcionalidade de pesquisa com mais freqüência. Você pode pesquisar certificados com base em uma variedade de critérios, incluindo nome do assunto, número de série, impressão digital, emissor e período de validade. Se você recuperar programaticamente os certificados em seus aplicativos a partir do armazenamento, use uma propriedade exclusiva – o identificador de chave de assunto, por exemplo. A impressão digital também é exclusiva, mas tenha em mente que esse é um valor hash SHA-1 do certificado e que será alterado se, por exemplo, o certificado for renovado. O código na Figura 3 mostra uma forma genérica de pesquisar certificados.
static void Main(string[] args)
{
    // search for the subject key id
    X509Certificate2 cert = FindCertificate(
      StoreLocation.CurrentUser, StoreName.My, 
      X509FindType.FindBySubjectKeyIdentifier, 
      "21f2bf447298e83056a69eb02ebe9085ed97f10a");
}

static X509Certificate2 FindCertificate(
    StoreLocation location, StoreName name,
    X509FindType findType, string findValue)
{
    X509Store store = new X509Store(name, location);
    try
    {
        // create and open store for read-only access
        store.Open(OpenFlags.ReadOnly);

        // search store
        X509Certificate2Collection col = store.Certificates.Find(
          findType, findValue, true);

        // return first certificate found
        return col[0];
    }
        // always close the store
    finally { store.Close(); }
}

Quando você tiver uma instância do X509 Certificate2, poderá inspecionar as várias propriedades do certificado (tais como nome do assunto, datas de expiração, emissor e o nome amigável). A propriedade HasPrivateKey lhe informa se existe uma chave privada associada. As propriedades PrivateKey e PublicKey retornam a chave correspondente como uma instância RSACryptoServiceProvider.
Para importar um certificado, chame o método Add na instância X509Store. Quando você especificar um nome de armazenamento que não existe no construtor do armazenamento, um novo recipiente será criado. Eis aqui como você importaria um certificado em um arquivo chamado alice.cer para um novo recipiente chamado Teste:
static void ImportCert()
{
    X509Certificate2 cert = new X509Certificate2("alice.cer");
    X509Store store = new X509Store("Test", StoreLocation.CurrentUser);
    try
    {
        store.Open(OpenFlags.ReadWrite);
        store.Add(cert);
    } 
    finally { store.Close(); }
}

Exibir detalhes do certificado e o selecionador de certificados
O Windows oferece duas caixas de diálogo padrão para trabalhar com certificados: uma para mostrar os detalhes do certificado (propriedades e caminho de certificação) e um para permitir que os usuários selecionem um certificado em uma lista. Você pode acessar essas caixas de diálogo a partir de dois métodos estáticos da classe X509Certificate2UI: SelectFromCollection e DisplayCertificate.
Para mostrar uma lista de certificados, você deve preencher um X509Certificate2Collection e passá-lo para SelectFromCollection. É muito comum deixar um usuário escolher dentre um dos seus certificados pessoais no armazenamento. Para isso, basta que você passe a propriedade Certificates de um X509Store aberto. Você também pode controlar a legenda da caixa de diálogo, uma mensagem, e se várias seleções são permitidas. O método DisplayCertificate mostra a mesma caixa de diálogo que você usa quando clica duas vezes em um arquivo .cer no Windows Explorer. A Figura 4 mostra a caixa de diálogo usada para selecionar um certificado e a Figura 5 fornece o código correspondente.
private static X509Certificate2 PickCertificate(
  StoreLocation location, StoreName name)
{
    X509Store store = new X509Store(name, location);
    try
    {
        store.Open(OpenFlags.ReadOnly);
        
        // pick a certificate from the store
        X509Certificate2 cert =  
            X509Certificate2UI.SelectFromCollection(
                store.Certificates, "Caption", 
                "Message", X509SelectionFlag.SingleSelection)[0];

        // show certificate details dialog
        X509Certificate2UI.DisplayCertificate(cert);
        return cert;
    }
    finally { store.Close(); }
}

Figura 4 Caixa de diálogo para escolher certificados (Clique na imagem para aumentar a exibição)

Validando certificados
Existem alguns critérios a serem considerados ao validar um certificado, especialmente a parte emissora (geralmente, você confia somente em certificados que foram emitidos por uma CA de sua lista de CAs confiáveis) e a sua validade atual (certificados podem se tornar inválidos, tal como quando eles expiram ou são revogados pela CA emissora). Você pode usar a classe X509Chain para verificar essas várias propriedades. Usando essa classe, você pode especificar uma política de verificação de validade – por exemplo, você pode pedir uma CA de raiz confiável ou especificar se devem ser verificadas listas de revogação online ou locais. Se você precisar verificar os certificados que foram usados para assinar dados, é importante verificar se o certificado era válido quando a assinatura foi feita – para isso, o X509Chain permite que você altere a hora de verificação.
Depois de criar uma política, chame o método Build para obter informações sobre o resultado da validação na propriedade ChainStatus. Se houver vários erros de validação, você poderá fazer iteração na coleção ChainElement para obter mais detalhes. A Figura 6 mostra como realizar uma validação estrita de um certificado e seu emissor em relação a listas de revogação offline e online.
static void ValidateCert(X509Certificate2 cert)
{
    X509Chain chain = new X509Chain();
    
    // check entire chain for revocation
    chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;

    // check online and offline revocation lists
    chain.ChainPolicy.RevocationMode = 
        X509RevocationMode.Online | X509RevocationMode.Offline;

    // timeout for online revocation list
    chain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 0, 30);

    // no exceptions, check all properties
    chain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;

    // modify time of verification
    //chain.ChainPolicy.VerificationTime = new DateTime(1999, 1, 1);

    chain.Build(cert);

    if (chain.ChainStatus.Length != 0)
        Console.WriteLine(chain.ChainStatus[0].Status);
}


Suporte a SSL
O protocolo de autenticação SSL depende de certificados. O suporte a SSL no .NET Framework consiste em duas partes. O caso especial (porém o mais amplamente usado) do SSL sobre HTTP é implementado pela classe HttpWebRequest (essa também é usada em última instância por proxies de cliente de serviço da Web). Para habilitar o SSL, você não precisa fazer nada especial além de especificar uma URL que use o protocolo https.
Ao conectar-se a um ponto de extremidade protegido por SSL, o certificado do servidor é validado no cliente. Se uma validação falhar, por padrão, a conexão é imediatamente fechada. Você pode anular esse comportamento fornecendo um retorno de chamada para uma classe chamada ServicePointManager. Sempre que a pilha do cliente HTTP certifica a validação, ela primeiro verifica se um retorno de chamada foi fornecido – se este for o caso, ela executa o seu código. Para acionar o retorno de chamada, você precisa fornecer um delegado do tipo RemoteCertificateValidationCallback:
// override default certificate policy 
// (for example, for testing purposes) 
ServicePointManager.ServerCertificateValidationCallback =  
    new RemoteCertificateValidationCallback(VerifyServerCertificate);
Em seu retorno de chamada, você obtém o certificado do servidor, um código de erro, e um objeto de canal passado. Você pode, em seguida, fazer a sua própria verificação e retornar true ou false. Pode ser útil desativar uma dessas verificações se, por exemplo, seu certificado tiver expirado durante o desenvolvimento ou o teste. Por outro lado, isso também permite que você implemente políticas de validação mais estritas do que as fornecidas por padrão. A Figura 7 fornece um retorno de chamada de validação de amostra.
private bool VerifyServerCertificate(
    object sender, X509Certificate certificate, 
    X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
    if (sslPolicyErrors == SslPolicyErrors.None) return true;

    foreach (X509ChainStatus s in chain.ChainStatus)
    {
        // allows expired certificates
        if (string.Equals(s.Status.ToString(), "NotTimeValid", 
            StringComparison.OrdinalIgnoreCase))
                return true;
    }

    return false;
}

O SSL também aceita autenticação de cliente através de um certificado. Se o site ou serviço que você deseja acessar necessita de um certificado de cliente, o proxy do cliente de serviço da Web e HttpWebRequest fornecem uma propriedade ClientCertificates do tipo X509Certicate:
  proxy.Url = 
    "https://server/app/service.asmx";
  proxy.ClientCertificates.Add(
    PickCertificate(...));
Além disso, o .NET Framework 2.0 introduz uma nova classe chamada SslStream. Isso permite que você coloque o SSL em camada sobre qualquer fluxo, não apenas HTTP, o que torna possível para o SSL habilitar um soquete personalizado baseado em protocolo. SslStream usa o suporte a certificados .NET padrão de várias formas, por exemplo, usando o mecanismo de retorno de chamada de validação que abordei:
public SslStream(Stream innerStream, bool leaveInnerStreamOpen, 
    RemoteCertificateValidationCallback ValidationCallback) {...}
E para iniciar uma autenticação SSL com SslStream, passe um X509Certificate para seu método AuthenticateAsServer:
ssl.AuthenticateAsServer(PickCertificate(...));

Segurança de servidor da Web
O padrão WS-Security especifica a autenticação de cliente e de servidor e protege a comunicação usando certificados. Kits de ferramenta como o WSE (Web Services Enhancements) do .NET Framework e tecnologias como o Windows Communication Foundation oferecem suporte completo a ele. Novamente, ele também se concentra em fornecer um certificado em código ou através de configuração. O trecho de código seguinte mostra como adicionar um cliente a um proxy de serviço da Web usando WSE3:
X509SecurityToken token = new X509SecurityToken(PickCertificate(...));
proxy.RequestSoapContext.Security.Tokens.Add(token);
Com o Windows Communication Foundation, você geralmente fornece uma referência a um armazenamento de certificado em um arquivo de configuração (consulte a Figura 8). Como você pode ver, todos os atributos de configuração são mapeados diretamente para os enums usados anteriormente no código.
<system.serviceModel>
  <behaviors>
    <serviceBehaviors>
      <behavior name="ServiceBehavior">
        <serviceCredentials>
          <serviceCertificate storeLocation="LocalMachine"
            storeName="My" x509FindType="FindBySubjectKeyIdentifier"
            findValue="1a7b..." />
        </serviceCredentials>
      </behavior>
    </serviceBehaviors>
  </behaviors>
</system.serviceModel>


Diretiva de segurança e assinatura de código
Os certificados também são usados na assinatura de código Authenticode®. Assinando um binário, você pode adicionar informações sobre o editor e verificar se o arquivo assinado pode ser validado confiavelmente após ser assinado. Você pode usar a ferramenta signtool.exe a partir do SDK do .NET Framework para assinar arquivos .exe e .dll. Em seguida, você pode verificar a assinatura e exibir o certificado usando a caixa de diálogo de propriedades no Windows Explorer. Observe que as assinaturas Authenticode e de nome forte vão ser usadas, a assinatura de nome forte precisará ser aplicada primeiro. Além disso, assemblies Authenticode assinados podem passar por atrasos no tempo de carga, que se traduzirá em um tempo mais longo de inicialização do aplicativo, caso ele seja o executável de ponto de entrada que está sendo assinado.
Arquivos assinados também podem ser usados para fins de segurança. Usando as diretivas de restrição de software, você pode restringir a execução de executáveis não gerenciados com base em assinaturas ou na ausência de assinaturas (viste o site microsoft.com/technet/prodtechnol/winxppro/maintain/rstrplcy.mspx - em inglês). E a diretiva de CAS (Segurança de Acesso a Código) do .NET Framework oferece suporte a grupos de código baseados no certificado de um editor.
Para criar uma diretiva de CAS, use mscorcfg.msc para criar um novo grupo de códigos baseado em uma condição de associação do editor. Você pode, em seguida, assinar uma permissão definida para todos os aplicativos assinados por esse editor (consulte a Figura 9).
Figura 9 Assinando permissões para um editor (Clique na imagem para aumentar a exibição)

Manifestos ClickOnce
Outra tecnologia que usa certificados de informações do editor é ClickOnce. Quando publica um aplicativo ClickOnce, você tem que assinar o manifesto da implantação e do aplicativo. Isso adiciona novamente as informações do editor ao aplicativo e garante que informações confidenciais nos manifestos (tais como diretiva de segurança e dependências de aplicativo) não possam ser modificadas sem a invalidação da assinatura. O ClickOnce disponibiliza as informações do editor para os clientes durante a instalação para que eles possam tomar decisões inteligentes sobre a confiabilidade do aplicativo. Dependendo dos certificados (e do resultado de sua validação) o instalador de ClickOnce também usa indicações visuais diferentes. A Figura 10 mostra a caixa de diálogo de assinatura de manifesto do Visual Studio®.
Figura 10 Caixa de diálogo de assinatura de manifesto do Visual Studio (Clique na imagem para aumentar a exibição)

Assinando e criptografando dados
Até agora, mantive o foco nas APIs fundamentais relacionadas a certificado e como outras tecnologias as utilizam. Agora eu quero discutir sobre operações criptográficas, como a criptografia e a assinatura de dados com certificados, e a nova implementação PKCS nº 7 encontrada no .NET Framework 2.0.
A proteção de dados é sempre um processo de duas etapas. Primeiro, você assina os dados para torná-los à prova de adulteração. Em seguida, criptografe os dados para protegê-los contra divulgação imprópria. Entretanto, antes de realizar qualquer operação criptográfica com as classes PKCS nº 7, você primeiro precisa empacotar os dados em um objeto ContentInfo, representando uma estrutura de dados CMS. A partir daí, você pode transformar os dados em assinados ou criptografados, representados respectivamente pelas classes SignedCms e EnvelopedCms.
Tecnicamente, uma assinatura digital é o hash dos dados que depois são criptografados com a sua chave privada. Isso significa que você precisa de um certificado com uma chave privada associada ou um arquivo .pfx. Com base em tal certificado, você pode criar um objeto CmsSigner, que representa o assinante dos dados. A classe SignedCms, por sua vez, computa a assinatura e gera uma saída PKCS nº 7, matriz de byte compatível com CMS. A Figura 11 mostra o código correspondente. A matriz de byte codificada contém seus dados, a assinatura e o certificado usado para assinar os dados.
byte[] Sign(byte[] data, X509Certificate2 signingCert)
{
    // create ContentInfo
    ContentInfo content = new ContentInfo(data);

    // SignedCms represents signed data
    SignedCms signedMessage = new SignedCms(content);

    // create a signer
    CmsSigner signer = new CmsSigner(signingCert);

    // sign the data
    signedMessage.ComputeSignature(signer);

    // create PKCS #7 byte array
    byte[] signedBytes = signedMessage.Encode();

    // return signed data
    return signedBytes;
}

Isso poderá não ser crítico, se você estiver assinando grandes quantidades de dados, mas se a quantidade de dados for pequena, isso adiciona alguma sobrecarga. Por exemplo, assinar uma matriz de 10 bytes com uma chave pública de 2KB resulta basicamente em uma matriz de 2.400 bytes. Tenha isso em mente, se você quiser armazenar os dados assinados em, digamos, um banco de dados. Uma abordagem alternativa seria usar a chamada assinatura desanexada. Isso permite que você remova seus dados da assinatura e armazene-os separadamente. Por exemplo, você poderia primeiro combinar várias pequenas partes de dados e assiná-las juntas. Para criar uma assinatura desanexada, você precisa passar um true adicional para o construtor SignedCms, conforme mostrado na Figura 12.
byte[] SignDetached(byte[] data, X509Certificate2 signingCert)
{
    // create ContentInfo
    ContentInfo content = new ContentInfo(data);

    // pass true to the constructor to indicate
    // we want to sign detached
    SignedCms signedMessage = new SignedCms(content, true);

    // these steps are the same 
    CmsSigner signer = new CmsSigner(signingCert);
    signedMessage.ComputeSignature(signer);
    byte[] signedBytes = signedMessage.Encode();

    // return only the signature (not the data)
    return signedBytes;
}

Quando tiver assinado os dados, você poderá criptografá-los. Você precisará das chaves públicas dos destinatários que devem estar aptos a descriptografar os dados. Você geralmente as obtém em seu armazenamento Outras Pessoas (ou em um arquivo .cer, se não quiser usar o armazenamento do certificado). Dessa vez, a classe EnvelopedCms faz todo o trabalho pesado. Especifique as chaves públicas usadas para criptografia em CmsRecipientCollection, que você passa para o método Encrypt. Como acontece com SignedCms, aqui o método Encode cria o PKCS nº 7, matriz de byte compatível com CMS (consulte a Figura 13).
byte[] Encrypt(byte[] data, X509Certificate2 encryptingCert)
{
    // create ContentInfo
    ContentInfo plainContent = new ContentInfo(data);

    // EnvelopedCms represents encrypted data
    EnvelopedCms encryptedData = new EnvelopedCms(plainContent);

    // add a recipient
    CmsRecipient recipient = new CmsRecipient(encryptingCert);

    // encrypt data with public key of recipient
    encryptedData.Encrypt(recipient);

    // create PKCS #7 byte array
    byte[] encryptedBytes = encryptedMessage.Encode();

    // return encrypted data
    return encryptedBytes;
}

Internamente, EnvelopedCms gera uma chave de sessão aleatória com a qual os dados são criptografados simetricamente. Em seguida, a chave de sessão é criptografada com a chave pública de cada destinatário. Portanto, você não precisa de uma versão criptografada separada dos dados em cada um dos seus destinatários. Além disso, algumas informações extras são incorporadas, permitindo que o destinatário encontre a chave privada correspondente para descriptografar em seu armazenamento de certificado.

Descriptografando dados e verificando assinaturas
Na extremidade de recebimento, o processo inteiro é revertido. Isso significa que você primeiro descriptografa os dados e, em seguida, valida a assinatura e o certificado assinante. No código, você primeiro tem que chamar os métodos Decode das classes SignedCms e EnvelopedCms para desserializar a matriz de bytes CMS para uma representação do objeto. Em seguida, você chama Decrypt e CheckSignature, respectivamente.
O processo examina o pacote criptografado para ver se a chave de sessão pode ser descriptografada, procurando por uma chave privada correspondente no armazenamento do certificado. Em seguida, a chave de sessão descriptografada é usada para descriptografar os dados reais. Você também pode fornecer uma lista de certificados adicionais que devem ser levados em consideração durante a descriptografia, caso a chave privada não esteja no armazenamento de certificado:
static byte[] Decrypt(byte[] data)
{
    // create EnvelopedCms
    EnvelopedCms encryptedMessage = new EnvelopedCms();

    // deserialize PKCS#7 byte array
    encryptedMessage.Decode(data);

    // decryt data
    encryptedMessage.Decrypt();

    // return plain text data
    return encryptedMessage.ContentInfo.Content;
}
A verificação dos dados é um processo de duas etapas. Você primeiro verifica se a assinatura é válida, o que significa que os dados não foram violados. Em seguida, você verifica o certificado assinante. O método CheckSignature da classe SignedCms permite que você realize as duas etapas de uma vez. Neste caso, o certificado é validado em relação à diretiva padrão do sistema. Se você quiser ter mais controle sobre esse processo, faça a sua própria verificação usando um objeto X509Chain e um código semelhante ao da Figura 6. E a Figure 14 mostra o código usado para verificar e remover uma assinatura, enquanto a Figura 15 fornece o código usado para desanexar a validação da assinatura.
static bool VerifyDetached(byte[] data, byte[] signature)
{
    ContentInfo content = new ContentInfo(data);

    // pass true for detached
    SignedCms signedMessage = new SignedCms(content, true);

    // deserialize signature
    signedMessage.Decode(signature);

    try
    {
        // check if signature matches data
        // the certificate is also checked
        signedMessage.CheckSignature(false);
        return true;
    }
    catch { return false; }
}

byte[] VerifyAndRemoveSignature(byte[] data)
{
    // create SignedCms
    SignedCms signedMessage = new SignedCms();

    // deserialize PKCS #7 byte array
    signedMessage.Decode(data);

    // check signature
    // false checks signature and certificate
    // true only checks signature
    signedMessage.CheckSignature(false);

    // access signature certificates (if needed)
    foreach (SignerInfo signer in signedMessage.SignerInfos)
    {
        Console.WriteLine("Subject: {0}", 
          signer.Certificate.Subject);
    }

    // return plain data without signature
    return signedMessage.ContentInfo.Content;
}


Juntando as peças
Ao trabalhar com diretivas de segurança ou protocolos de comunicação, você encontrará certificados em todos os tipos de situações. Neste artigo, expliquei as APIs básicas usadas para recuperar e pesquisar certificados e mostrei como usá-las na criptografia e nas assinaturas digitais. Além disso, forneci alguns exemplos de serviços de aplicativo de mais alto nível que requerem que você entenda o armazenamento de certificado do Windows e o relacionamento entre as chaves públicas e privadas.
O código-fonte deste artigo, disponível para download no site da MSDN®Magazine, inclui um pequeno aplicativo Windows Forms que oferece suporte à assinatura e criptografia de arquivos. Ele usa muitas das técnicas que discuti neste artigo, como a seleção de certificados de armazenamentos diferentes e a proteção/verificação de dados usando criptografia e assinaturas.

Dominick Baier é um consultor de segurança autônomo na Alemanha. Ele ajuda as empresas no design e na arquitetura da segurança, no desenvolvimento do conteúdo, no teste da penetração e na auditoria do código. Ele também é o líder de currículo de segurança na DevelopMentor, uma MVP de segurança de desenvolvedor, e autor de Developing More-Secure Microsoft ASP.NET 2.0 Applications (Microsoft Press, 2006). Seu blog se encontra em www.leastprivilege.com.

Page view tracker