amagovpt / autenticacao.gov

Middleware Oficial de Identificação Eletrónica em Portugal - Cartão de Cidadão, da Chave Móvel Digital e Sistema de Certificação de atributos profissionais
https://www.autenticacao.gov.pt
European Union Public License 1.2
168 stars 33 forks source link

Certificados de Autenticação para o PEM (SPMS) em C# #42

Closed RicardoSoares97 closed 3 years ago

RicardoSoares97 commented 3 years ago

Bom dia,

Na empresa onde trabalho estamos a desenvolver de momento o PEM (Prescrição Electrónica de Medicamentos), um dos requisitos para o médico trabalhar com o PEM é que todas as chamadas têm de conter o certificado digital qualificado incluído no request.

A minha experiência para trabalhar nestes tipos de certificados é muito pouca.

Actualmente estou com dificuldades a enviar o certificado de autenticação para a chamada do PEM que fornece o token de autenticação, estou constantemente a receber "Certificado Inválido", falei com a SPMS para expor o problema e eles disseram-me o seguinte:

Não consigo perceber que tipo de certificados do cartão de cidadão são esses, se me pudessem esclarecer agradecia, pois digamos que já entro em desespero.

Na chamada que eu estou a fazer para obter o token do PEM eu tenho de assinar um XML de request, deixo abaixo o código que estou a usar para assinar o XML.

` private void SignXmlFile(string FileName, string SignedFileName, X509Certificate2 cert) { XmlDocument doc = new XmlDocument();

        // Format the document to ignore white spaces.
        doc.PreserveWhitespace = false;

        // Load the passed XML file using it's name.
        doc.LoadXml(xml);

        // Create a SignedXml object.
        SignedXml signedXml = new SignedXml(doc);

        signedXml.SigningKey = cert.PrivateKey;

        // Create a reference to be signed.
        Reference reference = new Reference();
        reference.Uri = "";

        // Add an enveloped transformation to the reference.
        XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
        reference.AddTransform(env);

        // Add the reference to the SignedXml object.
        signedXml.AddReference(reference);

        // Create a new KeyInfo object.
        KeyInfo keyInfo = new KeyInfo();

        // Load the certificate into a KeyInfoX509Data object
        // and add it to the KeyInfo object.
        keyInfo.AddClause(new KeyInfoX509Data(cert));

        // Add the KeyInfo object to the SignedXml object.
        signedXml.KeyInfo = keyInfo;

        // Compute the signature.
        signedXml.ComputeSignature();

        // Get the XML representation of the signature and save
        // it to an XmlElement object.
        XmlElement xmlDigitalSignature = signedXml.GetXml();

        // Append the element to the XML document.
        doc.DocumentElement.AppendChild(doc.ImportNode(xmlDigitalSignature, true));

        if (doc.FirstChild is XmlDeclaration)
        {
            doc.RemoveChild(doc.FirstChild);
        }

        return signedXml;
    }

`

Para obter o certificado atualmente estou a fazer o seguinte:

`
var authenticationCertificate = CartaoCidadao.CertificadoAuthentication(); X509Certificate2 cert = new X509Certificate2(authenticationCertificate.getCertData().GetBytes()); var xmlFile = ToXML(request);

            var certAutentication = SignXmlFile(xmlFile, cert);

`

Se eu assinar o documento desta maneira vai me dar um erro porque o PrivateKey é null no método SignXmlFile. Se eu usar o SmartCard do Windows consigo de facto obter o certificado com o PrivateKey mas estão constantemente a dizer-me que está o certificado inválido e sendo que estou a enviar o certificado de autenticação.

Precisava seriamente que alguém me desse alguma dica do que poderá ser, se estou a fazer algo de errado no ato de assinar o XML, se nao estou a obter o certificado correto sendo que já vi que existe o getCA(), getRoot(), getSignature().

Obrigado e espero que não tenha complicado muito a minha questão.

ACamposPT commented 3 years ago

Bom dia, Teremos todo o gosto em ajudar. No entanto, para podermos ajudar teríamos de ver o conteúdo deste método var authenticationCertificate = CartaoCidadao.CertificadoAuthentication() para ver como estão a seleccionar o certificado.

No entanto, surge uma questão no imediato, não é suposto estarem a usar o certificado de assinatura em vez do certificado de autenticação?

Sobre a leitura de certificados, pode sempre consultar a documentação do SDK:

https://amagovpt.github.io/autenticacao.gov/manual_sdk.html#certificados-digitais

Ou mesmo a documentação para C#:

https://amagovpt.github.io/autenticacao.gov/sdk/cpp/

RicardoSoares97 commented 3 years ago

Olá, Obrigado pela disponibilidade. A minha questão tambem é essa nós estamos a usar o Certificado de Autenticação porque supomos que como a chamada da SPMS é para uma autenticação, seja a mesma coisa. Tenho tido bastantes dificuldades pela falta de documentação/explicação por parte da SPMS que existe relacionada a estes tópicos.

O meu método que devolve o certificado é o seguinte.

`

public static PTEID_Certificate CertificadoAuthentication() { PTEID_ReaderSet.initSDK(); PTEID_ReaderSet readerSet = PTEID_ReaderSet.instance(); PTEID_ReaderContext context = readerSet.getReader();

        bool available = CheckCartaoCidadaoAvailable();

        if (!available)
            return null;

        PTEID_EIDCard card = context.getEIDCard();

        return card.getAuthentication();
    }

`

ACamposPT commented 3 years ago

Olá Ricardo,

Existe aqui vários temas, mas vou tentar ajudar.

Tens várias formas de usar o CC para fazer assinaturas. Em ambientes Windows, podemos separar em:

1) Via certificados registados na store do windows, que nos parece ser o mais simples para o que pretendes. É a isto que te referes quando dizes "Se eu usar o SmartCard do Windows"? Para tentar ajudar, gostaria de saber qual certificado estás a usar e como é que fazes a selecção do certificado? É apresentado ao utilizador uma janela do Windows para o utilizador escolher o certificado ou és tu que pragmaticamente seleccionas o certificado?

Usando isto tu deverias conseguir assinar o XML, mas mesmo assim tu dizes que do lado serviço dizem que "certificado inválido". Aqui era necessário saber qual o motivo do certificado inválido, no entanto, segundo isto "Mais esclarecemos que devem ser usados os seguintes certificados do Cartão de Cidadão na emissão de receitas: End certificate e o sub ca.", o problema pode ser por não estares a adicionar o certificado sub ca do CC, penso que seria algo (não testado!) ao que já tens:

keyInfo.AddClause(new KeyInfoX509Data(cert));
keyInfo.AddClause(new KeyInfoX509Data(cert_sub_ca));

A questão é como vais ler o certificado sub ca do CC, aqui entre o Ponto b)

b) Via SDK do MW do CC. Com uma primeira analise desconfiamos que isto "X509Certificate2 cert = new X509Certificate2(authenticationCertificate.getCertData().GetBytes());" não vai funcionar. Vai conseguir ler o certificado mas a PrivateKey não vai estar disponível. A nossa recomendação seria usar o que tens no ponto 1) para fazer a assinatura e usar o SDK só para ler os certificados do CC e SubCC para adicionares ao XML. Assim:

Na class PTEID_EIDCard com o metodo getCert, consegues ler o certificado subca de autenticação (PTEID_CERTIF_TYPE_ROOT_AUTH) e assinatura (PTEID_CERTIF_TYPE_ROOT_SIGN). Os nomes dos enums (legacy) não são os mais correctos mas são esses. Tens aqui a documentação para java mas é semelhante ao que tens para C#.

https://amagovpt.github.io/autenticacao.gov/sdk/java/pt/gov/cartaodecidadao/PTEID_EIDCard.html#getCert-pt.gov.cartaodecidadao.PTEID_CertifType-

https://amagovpt.github.io/autenticacao.gov/sdk/java/pt/gov/cartaodecidadao/PTEID_CertifType.html

Considerações finais: 1) Em ambos os casos o MW tem de estar instalado no computador e para que os certificados fiquem registados (ponto A), a aplicação gráfica do MW tem de estar aberta (1 única vez) para que isso aconteça. Nas definições da aplicação é possível ligar/desligar o registo de certificados. 2) Recentemente existiu uma alteração da cadeia de certificação do CC e sabemos que algumas plataformas ainda não estão a aceitar essa nova cadeia. No site https://www.scee.gov.pt/rep/certificados/ tens a informação sobre as cadeias de certificados. Podes verificar se não estas a bater neste ponto, pela data de emissão do teu cartão que estas a testar, se for anterior a 2020 não é o caso.

Espero que tenha conseguido ajudar.

Adriano

ACamposPT commented 3 years ago

Ola @RicardoSoares97, como vai a integração?

RicardoSoares97 commented 3 years ago

Olá @ACamposPT peço desculpa já não ter respondido.

Em principio ficou tudo okey, por incrivel que pareça a SPMS respondeu-nos hoje a dizer que o Digest Value e o Signature Value não estão okey, mas é algo que preciso de discutir com eles por num dos serviços deles retornam mensagem de sucesso sobre os certificados mas é algo que agora é com eles e tenho de perceber ao certo o que eles precisam.

Coloquei os certificados na ordem que me aconselhou ou seja sub ca em último lugar no array de certificados que eles pedem e funcionou direitinho. Não tinha a noção que a ordem dos certificados influenciava. Obrigado desde já pela ajuda.

ACamposPT commented 3 years ago

Boa, problema resolvido. Não deve ter sido a ordem mas sim adicionares o certificado sub ca do CC.