Compartilhar via


Classe System.Security.Cryptography.Xml.SignedXml

Este artigo fornece comentários complementares à documentação de referência para esta API.

A SignedXml classe é a implementação do .NET da Sintaxe de Assinatura XML do W3C (World Wide Web Consortium), também conhecida como XMLDSIG (Assinatura Digital XML). O XMLDSIG é uma maneira interoperável baseada em padrões de assinar e verificar tudo ou parte de um documento XML ou outros dados que podem ser endereçáveis de um URI (Uniform Resource Identifier).

Use a SignedXml classe sempre que precisar compartilhar dados XML assinados entre aplicativos ou organizações de maneira padrão. Todos os dados assinados usando essa classe podem ser verificados por qualquer implementação em conformidade da especificação W3C para XMLDSIG.

A SignedXml classe permite que você crie os três tipos de assinaturas digitais XML a seguir:

Tipo de assinatura Descrição
Assinatura envelopada A assinatura está contida no elemento XML que está sendo assinado.
Envolvendo a assinatura O XML assinado está contido no <Signature> elemento.
Assinatura desanexada interna A assinatura e o XML assinado estão no mesmo documento, mas nenhum dos elementos contém o outro.

Há também um quarto tipo de assinatura chamado uma assinatura desanexada externa, que é quando os dados e a assinatura estão em documentos XML separados. Não há suporte para assinaturas externas desanexadas pela classe SignedXml.

Estrutura de uma assinatura XML

XMLDSIG cria um <Signature> elemento, que contém uma assinatura digital de um documento XML ou outros dados que podem ser endereçáveis de um URI. Opcionalmente <Signature> , o elemento pode conter informações sobre onde encontrar uma chave que verificará a assinatura e qual algoritmo criptográfico foi usado para assinatura. A estrutura básica é a seguinte:

<Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
    <SignedInfo>
      <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
      <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
      <Reference URI="">
        <Transforms>
          <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
        </Transforms>
        <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
        <DigestValue>Base64EncodedValue==</DigestValue>
      </Reference>
    </SignedInfo>
    <SignatureValue>AnotherBase64EncodedValue===</SignatureValue>
</Signature>

As principais partes dessa estrutura são:

  • O elemento <CanonicalizationMethod>

    Especifica as regras para reescrever o Signature elemento de XML/texto em bytes para validação de assinatura. O valor padrão no .NET é http://www.w3.org/TR/2001/REC-xml-c14n-20010315, que identifica um algoritmo confiável. Esse elemento é representado pela SignedInfo.CanonicalizationMethod propriedade.

  • O elemento <SignatureMethod>

    Especifica o algoritmo usado para geração e validação de assinatura, que foi aplicado ao <Signature> elemento para produzir o valor em <SignatureValue>. No exemplo anterior, o valor http://www.w3.org/2000/09/xmldsig#rsa-sha1 identifica uma assinatura RSA PKCS1 SHA-1. Devido a problemas de colisão com SHA-1, a Microsoft recomenda um modelo de segurança baseado no SHA-256 ou superior. Esse elemento é representado pela SignatureMethod propriedade.

  • O elemento <SignatureValue>

    Especifica a assinatura criptográfica para o <Signature> elemento. Se essa assinatura não for verificada, parte do <Signature> bloco foi adulterada e o documento será considerado inválido. Desde que o <CanonicalizationMethod> valor seja confiável, esse valor é altamente resistente à adulteração. Esse elemento é representado pela SignatureValue propriedade.

  • O URI atributo do <Reference> elemento

    Especifica um objeto de dados usando uma referência de URI. Esse atributo é representado pela Reference.Uri propriedade.

    • Não especificar o URI atributo, ou seja, definir a Reference.Uri propriedade como null, significa que o aplicativo receptor deve saber a identidade do objeto. Na maioria dos casos, um null URI resultará na geração de uma exceção. Não use um null URI, a menos que seu aplicativo esteja interoperando com um protocolo que o exija.

    • Definir o URI atributo como uma string vazia indica que o elemento raiz do documento está sendo assinado, uma forma de assinatura envelopada.

    • Se o valor do URI atributo começar com #, o valor deverá ser resolvido para um elemento no documento atual. Esse formulário pode ser usado com qualquer um dos tipos de assinatura com suporte (assinatura envelopada, assinatura envelopante ou assinatura desanexada interna).

    • Qualquer outra coisa é considerada uma assinatura desvinculada de recursos externos e não tem suporte pela classe SignedXml.

  • O elemento <Transforms>

    Contém uma lista ordenada de <Transform> elementos que descrevem como o signatário obteve o objeto de dados que foi digerido. Um algoritmo de transformação é semelhante ao método de canonicalização, mas em vez de reescrever o <Signature> elemento, ele reescreve o conteúdo identificado pelo URI atributo do <Reference> elemento. O <Transforms> elemento é representado pela TransformChain classe.

    • Cada algoritmo de transformação é definido como usar XML (um conjunto de nós XPath) ou bytes como entrada. Se o formato dos dados atuais for diferente dos requisitos de entrada de transformação, as regras de conversão serão aplicadas.

    • Cada algoritmo de transformação é definido como produzindo XML ou bytes como saída.

    • Se a saída do algoritmo de última transformação não for definida em bytes (ou nenhuma transformação foi especificada), o método de canonicalização será usado como uma transformação implícita (mesmo que um algoritmo diferente tenha sido especificado no <CanonicalizationMethod> elemento).

    • Um valor de http://www.w3.org/2000/09/xmldsig#enveloped-signature do algoritmo de transformação codifica uma regra que é interpretada como remover o elemento <Signature> do documento. Caso contrário, um verificador de uma assinatura envelopada digerirá o documento, incluindo a assinatura; no entanto, o signatário já teria digerido o documento antes de a assinatura ser aplicada, resultando em respostas diferentes.

  • O elemento <DigestMethod>

    Identifica o método digest (hash criptográfico) a ser aplicado ao conteúdo transformado identificado pelo atributo URI do elemento <Reference>. Isso é representado pela propriedade Reference.DigestMethod.

Escolhendo um método de canonicalização

A menos que interopere com uma especificação que exija o uso de um valor diferente, recomendamos que você use o método de canonicalização padrão do .NET, que é o algoritmo XML-C14N 1.0, cujo valor é http://www.w3.org/TR/2001/REC-xml-c14n-20010315. O algoritmo XML-C14N 1.0 é necessário para ser suportado por todas as implementações de XMLDSIG, especialmente porque é uma transformação final implícita a ser aplicada.

Há versões de algoritmos de canonicalização que dão suporte à preservação de comentários. Métodos de canonicalização que preservam comentários não são recomendados porque violam o princípio "assinar o que é visto". Ou seja, os comentários em um <Signature> elemento não alterarão a lógica de processamento de como a assinatura é executada, apenas qual é o valor da assinatura. Quando combinado com um algoritmo de assinatura fraco, permitir que os comentários sejam incluídos dá a um invasor liberdade desnecessária para forçar uma colisão de hash, fazendo com que um documento adulterado pareça legítimo. No .NET Framework, há suporte apenas para canonizadores internos por padrão. Para dar suporte a canonizadores adicionais ou personalizados, consulte a SafeCanonicalizationMethods propriedade. Se o documento usar um método de canonicalização que não está na coleção representada pela SafeCanonicalizationMethods propriedade, o CheckSignature método retornará false.

Observação

Um aplicativo extremamente defensivo pode remover todos os valores que não espera que os signatários usem da SafeCanonicalizationMethods coleção.

Os valores de referência estão a salvo de adulteração?

Sim, os <Reference> valores estão a salvo de adulteração. O .NET verifica a <SignatureValue> computação antes de processar os <Reference> valores e suas transformações associadas, e interromperá antecipadamente para evitar instruções de processamento potencialmente mal-intencionadas.

Escolha os elementos a serem assinados

Recomendamos que você use o valor de "" para o URI atributo (ou defina a Uri propriedade como uma cadeia de caracteres vazia), se possível. Isso significa que todo o documento é considerado para a computação de resumo, o que significa que todo o documento está protegido contra adulteração.

É muito comum ver URI valores na forma de âncoras como #foo, referindo-se a um elemento cujo atributo de ID é "foo". Infelizmente, é fácil que isso seja adulterado porque isso inclui apenas o conteúdo do elemento de destino, não o contexto. Abusar dessa distinção é conhecido como XSW (Encapsulamento de Assinatura XML).

Se seu aplicativo considerar os comentários semânticos (o que não é comum ao lidar com XML), você deverá usar "#xpointer(/)" em vez de "" e "#xpointer(id('foo'))" em vez de "#foo". As versões #xpointer são interpretadas como incluindo comentários, enquanto as formas de nome abreviado excluem comentários.

Se você precisar aceitar documentos que estão apenas parcialmente protegidos e quiser garantir que você esteja lendo o mesmo conteúdo protegido pela assinatura, use o GetIdElement método.

Considerações de segurança sobre o elemento KeyInfo

Os dados no elemento opcional <KeyInfo> (ou seja, a KeyInfo propriedade), que contém uma chave para validar a assinatura, não devem ser confiáveis.

Em particular, quando o KeyInfo valor representa uma chave pública RSA, DSA ou ECDSA nua, o documento pode ter sido adulterado, apesar do CheckSignature método relatar que a assinatura é válida. Isso pode acontecer porque a entidade que está fazendo a adulteração só precisa gerar uma nova chave e assinar novamente o documento adulterado com essa nova chave. Portanto, a menos que seu aplicativo verifique se a chave pública é um valor esperado, o documento deve ser tratado como se tivesse sido adulterado. Isso requer que seu aplicativo examine a chave pública inserida no documento e verifique-a em uma lista de valores conhecidos para o contexto do documento. Por exemplo, se o documento pudesse ser entendido como emitido por um usuário conhecido, você verificaria a chave em uma lista de chaves conhecidas usadas por esse usuário.

Você também pode verificar a chave depois de processar o documento usando o CheckSignatureReturningKey método, em vez de usar o CheckSignature método. Mas, para a segurança ideal, você deve verificar a chave com antecedência.

Como alternativa, considere tentar as chaves públicas registradas do usuário, em vez de ler o que está no <KeyInfo> elemento.

Considerações de segurança sobre o elemento X509Data

O elemento opcional <X509Data> é um filho do <KeyInfo> elemento e contém um ou mais certificados X509 ou identificadores para certificados X509. Os dados no <X509Data> elemento também não devem ser inerentemente confiáveis.

Ao verificar um documento com o elemento inserido <X509Data> , o .NET verifica apenas se os dados são resolvidos para um certificado X509 cuja chave pública pode ser usada com êxito para validar a assinatura do documento. Ao contrário de chamar o método CheckSignature com o parâmetro verifySignatureOnly definido como false, nenhuma verificação de revogação é executada, nenhuma relação de confiança de cadeia é verificada e nenhuma verificação de expiração é realizada. Mesmo que seu aplicativo extraia o certificado em si e o transmita para o método CheckSignature com o parâmetro verifySignatureOnly definido como false, isso ainda não é validação suficiente para evitar a adulteração de documentos. O certificado ainda precisa ser verificado como sendo apropriado para o documento que está sendo assinado.

O uso de um certificado de assinatura inserido pode fornecer estratégias úteis de rotação de chaves, seja na <X509Data> seção ou no conteúdo do documento. Ao usar essa abordagem, um aplicativo deve extrair o certificado manualmente e executar uma validação semelhante a:

  • O certificado foi emitido diretamente ou por meio de uma cadeia por uma AC (Autoridade de Certificação) cujo certificado público está inserido no aplicativo.

    Usar a lista de confiança fornecida pelo sistema operacional sem verificações adicionais, como um nome de assunto conhecido, não é suficiente para evitar a adulteração em SignedXml.

  • Verifica-se que o certificado não expirou no momento da assinatura do documento (ou "agora" para processamento quase em tempo real de documentos).

  • Para certificados de longa duração emitidos por uma CA que suporta revogação, verifique se o certificado não foi revogado.

  • O titular do certificado é verificado como sendo apropriado para o documento atual.

Escolhendo o algoritmo de transformação

Se você estiver interoperando com uma especificação que tenha determinado valores específicos (como XrML), será necessário seguir a especificação. Se você tiver uma assinatura envelopeada (como ao assinar o documento inteiro), precisará usar http://www.w3.org/2000/09/xmldsig#enveloped-signature (representado pela XmlDsigEnvelopedSignatureTransform classe). Você também pode especificar a transformação de XML-C14N implícita, mas não é necessário. Para uma assinatura envolvente ou desanexada, nenhuma transformação é necessária. A transformação XML-C14N implícita cuida de tudo.

Com a segurança atualizada introduzida pelo Boletim de Segurança da Microsoft MS16-035, o .NET restringiu quais transformações podem ser usadas na verificação de documentos por padrão, com transformações não confiáveis que fazem CheckSignature com que sempre retornem false. Em particular, transformações que exigem entrada adicional (especificadas como elementos filho no XML) não são mais permitidas devido à suscetibilidade de abuso por usuários mal-intencionados. O W3C aconselha evitar as transformações XPath e XSLT, que são as duas principais transformações afetadas por essas restrições.

O problema com referências externas

Se um aplicativo não verificar se as referências externas parecem apropriadas para o contexto atual, elas poderão ser abusadas de maneiras que resultam em muitas vulnerabilidades de segurança (incluindo negação de serviço, negação de serviço de reflexão distribuída, divulgação de informações, bypass de verificação de assinatura e execução remota de código). Mesmo se um aplicativo validasse o URI de referência externa, ainda haveria um problema de o recurso ser carregado duas vezes: uma vez quando seu aplicativo o lê e uma vez quando SignedXml o lê. Como não há garantia de que as etapas de verificação de documentos e leitura do aplicativo tenham o mesmo conteúdo, a assinatura não fornece confiabilidade.

Considerando os riscos de referências externas, SignedXml gerará uma exceção quando uma referência externa for encontrada. Para obter mais informações sobre esse problema, consulte o artigo do KB 3148821.