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
173 stars 33 forks source link

Assinar XML com Cartão de Cidadão em C# #51

Closed RicardoSoares97 closed 3 years ago

RicardoSoares97 commented 3 years ago

Bom dia,

Atualmente estamos com um problema na assinatura de um XML com o cartão de cidadão para a SPMS.

Estamos a gerar o signedInfo através do request que nos é pedido, deixo abaixo o que estamos atualmente a fazer. Enviamos dois certificados pela ordem como está nos parâmetros para que sejam incluídos dentro do SignedInfo, o ROOT_SIGN e o TYPE_SIGNATURE que são obtidos através do cartão de cidadão.

X509Certificate2 cert = ROOT_SIGN X509Certificate2 certChild = TYPE_SIGNATURE

`

    public static SignedXml SignXmlFile(string xml, X509Certificate2 cert, X509Certificate2 certChild)
    {
        if (null == xml)
            throw new ArgumentNullException("xml");

        // Create a new XML document.
        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);

        RSA Key = RSA.Create();

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

        // 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));
        keyInfo.AddClause(new KeyInfoX509Data(certChild));

        // 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;
    }`

Conseguimos obter o SignedInfo juntamente com o DigestValue que é suposto e na qual já nos foi dito que está correto. Exemplo abaixo: `

    <SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">

      <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments" />

      <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>EQthbQKn1PRTpp3I7LtIw7WYEYQ=</DigestValue>

      </Reference>

    </SignedInfo>`

Segundo a SPMS temos de gerar uma HASH com o SignedInfo fui ver a vossa documentação e atualmente vocês têm na descrição do método Sign(PTEIDByteArray, secutiryKey), que no PTEIDByteArray temos de enviar uma HASH convertida em SHA256 algo que foi pelo meu entender.

O Algoritmo de assinatura suportado é o RSA-SHA256 mas o smartcard apenas implementa o algoritmo RSA e como tal o bloco de input deve ser o hash SHA-256 dos dados que se pretende assinar.

Na qual estou a efetuar o seguinte para obter esse resultado, no parâmetro de entrada

String xml = Aqui envio apenas o elemento SignedInfo do XML

`

   public static Byte[] ComputeHashSHA256(string xml)
    {
        XmlDocument xmlDoc = new XmlDocument();
        xmlDoc.LoadXml(xml);
        MemoryStream xmlStream = new MemoryStream();
        xmlDoc.Save(xmlStream);

        xmlStream.Flush();//Adjust this if you want read your data 
        xmlStream.Position = 0;

        SHA256 sha = SHA256.Create();
        return sha.ComputeHash(xmlStream);
    }

`

Após obter este hash devolvo para então enviar ao vosso método Sign que deixo exemplo abaixo do que estou a efetuar `

        var hash = PEMXML.ComputeHashSHA256(requestSPMS.GetXml().ChildNodes[0].OuterXml);
        PTEID_ReaderSet.initSDK();
        PTEID_ReaderSet readerSet = PTEID_ReaderSet.instance();
        PTEID_ReaderContext context = readerSet.getReader();
        PTEID_EIDCard card = context.getEIDCard();

        var signatureValue = card.Sign(new PTEID_ByteArray(hash, ((uint)hash.Length)), true);

`

Apos isto surge-me uma janela para inserir o PIN de assinatura e devolve o signature value na qual eu insiro esse signature value no SignedInfo para ser enviado então à SPMS, deixo o resultado final abaixo:

`

   <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
          <SignedInfo>
               <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"></CanonicalizationMethod>
               <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"></SignatureMethod>
               <Reference>
                  <Transforms>
                     <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"></Transform>
                   </Transforms>
                   <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></DigestMethod>
                   <DigestValue>FBOtLGrPK/izAHlrGNsntPMCnX0=</DigestValue>
                </Reference>
          </SignedInfo>
          <SignatureValue>
                    VARIAVEL SIGNATUREVALUE
          </SignatureValue>
          <KeyInfo>
              <X509Data>
                <X509Certificate>
                          CERTIFICADO TYPE-SIGNATURE
               </X509Certificate>
                <X509Certificate>
                          CERTIFICADO ROOT-SIGNATURE
               </X509Certificate>
              </X509Data>
        </KeyInfo>
    </Signature>

`

Ao enviar isto para a SPMS eles dizem-nos que do lado deles o que ocorre

Signature Encoding Error

Com isto eles disseram-nos que o problema estava em que não estaríamos a efetuar algum dos encodings, já demos voltas e mais voltas ao código e não conseguimos encontrar onde de facto está o problema. Recorro a vossa ajuda para tentar perceber o que de facto está a ser feito mal neste processo e de que maneira podemos resolver isto.

miguelblcfigueira commented 3 years ago

Boa tarde,

O digest no SignedInfo é o SHA1. Não deveria ser SHA256?

A seguinte alteração em SignXmlFile ajuda?

SignedXml signedXml = new SignedXml(doc);
signedXml.SigningKey = Key;
signedXml.SignedInfo.SignatureMethod = SignedXml.XmlDsigRSASHA256Url; // <-- Adicionar esta linha (http://www.w3.org/2001/04/xmldsig-more#rsa-sha256)
RicardoSoares97 commented 3 years ago

Boa tarde,

Eu tentei usar esse 256 e dá-me erro, só tenho acesso ao SignedXml.XmlDsigRSASHA1Url. De facto acho estranho o porque de a SPMS pede-me uma signature value com length de 256, então para isso necessito do SHA256, mas no XML deles usam o SHA1.

No signedInfo só tenho o SHA1 e não consigo colocar o algoritmo do SHA256 da-me erro.

RicardoSoares97 commented 3 years ago

Boa tarde,

Descobri agora que para usar SHA256 no SignedXML, tenho de alterar a versão do .Net Framework do 4.5.2 para o 4.7.2, efetuei a alteração, já me aparece o SignedXml.XmlDsigRSASHA256Url;

Vou usar, vou testar isto, enviar para a SPMS e depois deixo aqui o feedback. Obrigado.

RicardoSoares97 commented 3 years ago

Bom dia,

Após efetuar-mos a alteração para a SignedXml.XmlDsigRSASHA256Url, eles devolvem-me a dizer que não é permitido usar o SignedXml.XmlDsigRSASHA256Url mas sim o SignedXml.XmlDsigRSASHA1Url.

Após acrescentar isso na qual tenho o meu código desta maneira.

SignedXml signedXml = new SignedXml(doc);

signedXml.SigningKey = Key;

signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigCanonicalizationWithCommentsUrl; 

signedXml.SignedInfo.SignatureMethod = SignedXml.XmlDsigRSASHA1Url; 

Mas devolvem-me o erro Signature Encoding Error não sei o que fazer.

miguelblcfigueira commented 3 years ago

Boa tarde,

A possibilidade de selecionar o hash usado no método Sign do SDK é uma melhoria que está a ser considerada. O cartão suporta mais opções de hash, mas não estão expostas através do SDK (para já).

Entretanto, a 1ª abordagem sugerida em https://github.com/amagovpt/autenticacao.gov/issues/42#issuecomment-700812760 é uma possível solução.

Não estou familiarizado com esta API mas penso que o exemplo em https://docs.microsoft.com/en-us/dotnet/standard/security/how-to-access-hardware-encryption-devices possa ajudar.

RicardoSoares97 commented 3 years ago

Bom dia,

Esse issue já foi postado por mim e na altura o problema ate seria outro que ficou de facto resolvido com essa 1ª abordagem e na qual ainda se mantém esse código.

Eis o que a SPMS me disse:

o método de assinatura do CC ou Ordem já devolve tudo direitinho, é só concatenar ao xml original

A ideia que eu tenho é que tenho de enviar o meu XML convertido para byteArray e após isso HASH para SHA256 de modo a enviar para um método do middleware do Cartão de Cidadão.

Existem dois tipos de métodos que me parecem os indicados: 1º Sign(PTEID_Bytearray, securityKey) 2º SignSHA256(PTEID_Bytearray, securityKey)

Ambos devolvem um PTEID_Bytearray, o que os métodos devolvem não será um XML para que eu possa concatenar ao meu XML original ?

miguelblcfigueira commented 3 years ago

Bom dia,

Os métodos do SDK referidos devolvem um PTEID_ByteArray que deve ser inserido no XML em Base64 (parece-me que a classe SignedXml trate deste último passo).

Tal como foi dito no meu comentário anterior, os métodos do SDK (ainda) não permitem usar SHA1 conforme pedido pelo SPMS.

No entanto, existem outras APIs: PKCS#11 e Microsoft CryptoAPI (https://docs.microsoft.com/en-us/windows-hardware/drivers/smartcard/smart-card-minidrivers).

A ideia de seguir a 1ª abordagem sugerida em https://github.com/amagovpt/autenticacao.gov/issues/42#issuecomment-700812760 seria usar a CryptoAPI para gerar a assinatura com SHA1.

RicardoSoares97 commented 3 years ago

Bom dia,

Obrigado, vou verificar os desenvolvimentos a fazer e como efetua-los. Através das documentações todas.

Obrigado pela ajuda.

RicardoSoares97 commented 3 years ago

Boa tarde,

Já consegui com que o XML seja assinado e a assinatura seja válida após validação da SPMS. Basicamente deixei de usar os métodos Sign tal como o @miguelblcfigueira informou e usei as api's de criptografia da microsoft.

Basicamente apenas uso o SDK para obter o ROOT Certificate da assinatura visto que é uma obrigatoriedade da SPMS.

Acrescentei um método novo a ser chamado pelo software de modo a usar o Smart Card do Windows para obter o certificado de assinatura

1º Método:

public static SignedXml SignData(string xml, X509Certificate2 certRoot)
{
    X509Certificate2 myCert = null;
    X509Certificate2 oX509Cert = new X509Certificate2();
    X509Store store = new X509Store("MY", StoreLocation.CurrentUser);
    store.Open(OpenFlags.ReadOnly);
    X509Certificate2Collection collection = store.Certificates;
    X509Certificate2Collection collection2 = collection.Find(X509FindType.FindByIssuerName, "EC de Assinatura Digital Qualificada do Cartão de Cidadão", false);
    X509Certificate2Collection scollection = X509Certificate2UI.SelectFromCollection(collection2, "Certificados digitais disponíveis", "Selecione o certificado digital", X509SelectionFlag.SingleSelection);
    if (scollection.Count == 0)
        myCert = null;
    else
        myCert = scollection[0];

    var signedXML = SignXmlFileWithCertificate(xml, certRoot, myCert);
    return signedXML;
}

Com este método ele pede-me o PIN de assinatura e devolve-me o certificado com a privateKey de modo a que seja colocada no SignedXML para gerar o elemento Signature completo com tudo o que é necessário.

No método onde de facto assino o XML efetuei a alteração em que o SignedXML.SigningKey fosse igual à privateKey do certificado que o X509Store me retorna.

public static SignedXml SignXmlFileWithCertificate(string xml, X509Certificate2 certRoot, X509Certificate2 certChild)
{
    if (null == xml)
        throw new ArgumentNullException("xml");

    XmlDocument doc = new XmlDocument();
    doc.PreserveWhitespace = false;
    doc.LoadXml(xml);

    SignedXml signedXml = new SignedXml(doc);
    signedXml.SigningKey = certChild.PrivateKey;
    signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigCanonicalizationWithCommentsUrl; // "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments";
    signedXml.SignedInfo.SignatureMethod = SignedXml.XmlDsigRSASHA1Url; // "http://www.w3.org/2000/09/xmldsig#rsa-sha1";

    Reference reference = new Reference();
    reference.Uri = "";

    XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
    XmlDsigC14NTransform env2 = new XmlDsigC14NTransform();
    reference.AddTransform(env);
    reference.AddTransform(env2);

    signedXml.AddReference(reference);

    KeyInfo keyInfo = new KeyInfo();

    keyInfo.AddClause(new KeyInfoX509Data(certChild));
    keyInfo.AddClause(new KeyInfoX509Data(certRoot));

    signedXml.KeyInfo = keyInfo;

    signedXml.ComputeSignature();
    XmlElement xmlDigitalSignature = signedXml.GetXml();

    doc.DocumentElement.AppendChild(doc.ImportNode(xmlDigitalSignature, true));

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

    return signedXml;
}

@miguelblcfigueira Obrigado pela dica, de facto como foi a primeira vez que se desenvolveu tal coisa na empresa ocorreram-se imensas dificuldades. Obrigado e bom ano.

KeyDays commented 2 years ago

Boa tarde,

Já consegui com que o XML seja assinado e a assinatura seja válida após validação da SPMS. Basicamente deixei de usar os métodos Sign tal como o @miguelblcfigueira informou e usei as api's de criptografia da microsoft.

Basicamente apenas uso o SDK para obter o ROOT Certificate da assinatura visto que é uma obrigatoriedade da SPMS.

Acrescentei um método novo a ser chamado pelo software de modo a usar o Smart Card do Windows para obter o certificado de assinatura

1º Método:

public static SignedXml SignData(string xml, X509Certificate2 certRoot)
{
    X509Certificate2 myCert = null;
    X509Certificate2 oX509Cert = new X509Certificate2();
    X509Store store = new X509Store("MY", StoreLocation.CurrentUser);
    store.Open(OpenFlags.ReadOnly);
    X509Certificate2Collection collection = store.Certificates;
    X509Certificate2Collection collection2 = collection.Find(X509FindType.FindByIssuerName, "EC de Assinatura Digital Qualificada do Cartão de Cidadão", false);
    X509Certificate2Collection scollection = X509Certificate2UI.SelectFromCollection(collection2, "Certificados digitais disponíveis", "Selecione o certificado digital", X509SelectionFlag.SingleSelection);
    if (scollection.Count == 0)
        myCert = null;
    else
        myCert = scollection[0];

    var signedXML = SignXmlFileWithCertificate(xml, certRoot, myCert);
    return signedXML;
}

Com este método ele pede-me o PIN de assinatura e devolve-me o certificado com a privateKey de modo a que seja colocada no SignedXML para gerar o elemento Signature completo com tudo o que é necessário.

No método onde de facto assino o XML efetuei a alteração em que o SignedXML.SigningKey fosse igual à privateKey do certificado que o X509Store me retorna.

public static SignedXml SignXmlFileWithCertificate(string xml, X509Certificate2 certRoot, X509Certificate2 certChild)
{
    if (null == xml)
        throw new ArgumentNullException("xml");

    XmlDocument doc = new XmlDocument();
    doc.PreserveWhitespace = false;
    doc.LoadXml(xml);

    SignedXml signedXml = new SignedXml(doc);
    signedXml.SigningKey = certChild.PrivateKey;
    signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigCanonicalizationWithCommentsUrl; // "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments";
    signedXml.SignedInfo.SignatureMethod = SignedXml.XmlDsigRSASHA1Url; // "http://www.w3.org/2000/09/xmldsig#rsa-sha1";

    Reference reference = new Reference();
    reference.Uri = "";

    XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
    XmlDsigC14NTransform env2 = new XmlDsigC14NTransform();
    reference.AddTransform(env);
    reference.AddTransform(env2);

    signedXml.AddReference(reference);

    KeyInfo keyInfo = new KeyInfo();

    keyInfo.AddClause(new KeyInfoX509Data(certChild));
    keyInfo.AddClause(new KeyInfoX509Data(certRoot));

    signedXml.KeyInfo = keyInfo;

    signedXml.ComputeSignature();
    XmlElement xmlDigitalSignature = signedXml.GetXml();

    doc.DocumentElement.AppendChild(doc.ImportNode(xmlDigitalSignature, true));

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

    return signedXml;
}

@miguelblcfigueira Obrigado pela dica, de facto como foi a primeira vez que se desenvolveu tal coisa na empresa ocorreram-se imensas dificuldades. Obrigado e bom ano.

Olá Ricardo,

Será que podia partilhar o código completo. Sou nova tá nesta situação. E preciso também ler o CC e assinar um xml para ser enviado.

RicardoSoares97 commented 2 years ago

Boa tarde, Já consegui com que o XML seja assinado e a assinatura seja válida após validação da SPMS. Basicamente deixei de usar os métodos Sign tal como o @miguelblcfigueira informou e usei as api's de criptografia da microsoft. Basicamente apenas uso o SDK para obter o ROOT Certificate da assinatura visto que é uma obrigatoriedade da SPMS. Acrescentei um método novo a ser chamado pelo software de modo a usar o Smart Card do Windows para obter o certificado de assinatura 1º Método:

public static SignedXml SignData(string xml, X509Certificate2 certRoot)
{
    X509Certificate2 myCert = null;
    X509Certificate2 oX509Cert = new X509Certificate2();
    X509Store store = new X509Store("MY", StoreLocation.CurrentUser);
    store.Open(OpenFlags.ReadOnly);
    X509Certificate2Collection collection = store.Certificates;
    X509Certificate2Collection collection2 = collection.Find(X509FindType.FindByIssuerName, "EC de Assinatura Digital Qualificada do Cartão de Cidadão", false);
    X509Certificate2Collection scollection = X509Certificate2UI.SelectFromCollection(collection2, "Certificados digitais disponíveis", "Selecione o certificado digital", X509SelectionFlag.SingleSelection);
    if (scollection.Count == 0)
        myCert = null;
    else
        myCert = scollection[0];

    var signedXML = SignXmlFileWithCertificate(xml, certRoot, myCert);
    return signedXML;
}

Com este método ele pede-me o PIN de assinatura e devolve-me o certificado com a privateKey de modo a que seja colocada no SignedXML para gerar o elemento Signature completo com tudo o que é necessário. No método onde de facto assino o XML efetuei a alteração em que o SignedXML.SigningKey fosse igual à privateKey do certificado que o X509Store me retorna.

public static SignedXml SignXmlFileWithCertificate(string xml, X509Certificate2 certRoot, X509Certificate2 certChild)
{
    if (null == xml)
        throw new ArgumentNullException("xml");

    XmlDocument doc = new XmlDocument();
    doc.PreserveWhitespace = false;
    doc.LoadXml(xml);

    SignedXml signedXml = new SignedXml(doc);
    signedXml.SigningKey = certChild.PrivateKey;
    signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigCanonicalizationWithCommentsUrl; // "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments";
    signedXml.SignedInfo.SignatureMethod = SignedXml.XmlDsigRSASHA1Url; // "http://www.w3.org/2000/09/xmldsig#rsa-sha1";

    Reference reference = new Reference();
    reference.Uri = "";

    XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
    XmlDsigC14NTransform env2 = new XmlDsigC14NTransform();
    reference.AddTransform(env);
    reference.AddTransform(env2);

    signedXml.AddReference(reference);

    KeyInfo keyInfo = new KeyInfo();

    keyInfo.AddClause(new KeyInfoX509Data(certChild));
    keyInfo.AddClause(new KeyInfoX509Data(certRoot));

    signedXml.KeyInfo = keyInfo;

    signedXml.ComputeSignature();
    XmlElement xmlDigitalSignature = signedXml.GetXml();

    doc.DocumentElement.AppendChild(doc.ImportNode(xmlDigitalSignature, true));

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

    return signedXml;
}

@miguelblcfigueira Obrigado pela dica, de facto como foi a primeira vez que se desenvolveu tal coisa na empresa ocorreram-se imensas dificuldades. Obrigado e bom ano.

Olá Ricardo,

Será que podia partilhar o código completo. Sou nova tá nesta situação. E preciso também ler o CC e assinar um xml para ser enviado.

Olá Sara

Tenho uma library que permite fazer isto, posso enviar-te o projeto e tentas perceber, eu otimizei o código, queres que te envie?

KeyDays commented 2 years ago

Olá @RicardoSoares97,

Eu agradecia imenso toda ajuda possível.

Antes dessas funções fazes ainda referencia: "SDK para obter o ROOT Certificate da assinatura", trata-se disto https://amagovpt.github.io/docs.autenticacao.gov/manual_sdk.html?

RicardoSoares97 commented 2 years ago

Olá @RicardoSoares97,

Eu agradecia imenso toda ajuda possível.

Antes dessas funções fazes ainda referencia: "SDK para obter o ROOT Certificate da assinatura", trata-se disto https://amagovpt.github.io/docs.autenticacao.gov/manual_sdk.html?

Olá @sarachavescosta

A funcionalidade que pretendes é simples, é dificil é chegar lá. Verifica, este meu repositório que criei de modo a disponibilizar o código ou dll já com os métodos desenvolvidos para aquilo que tu pretendes. https://github.com/RicardoSoares97/RDSignatures

KeyDays commented 2 years ago

Muito obrigada @RicardoSoares97, vou analisar e experimentar e alguma dúvida voltamos a falar.

ACamposPT commented 2 years ago

Muito bem @RicardoSoares97 :+1: Bom trabalho.

Umas dicas de melhoria:

RicardoSoares97 commented 2 years ago

Olá @ACamposPT

Obigado, vou pegar acrescentar essas dicas e inclui-las assim que possível :)

RicardoSoares97 commented 2 years ago

Olá @sarachavescosta

Atualizei a dll com as dicas do @ACamposPT , de modo facilitam na busca dos certificados tanto do cartão de cidadão, como o da ordem dos médicos caso seja tambem o caso.

Atualizei o meu repositório com exemplos atualizados com que tu podes seguir.

KeyDays commented 2 years ago

Olá @RicardoSoares97,

Tentei instalar mas deu-me este erro. Severity Code Description Project File Line Suppression State Error Could not install package 'RDSignatures 1.1.0'. You are trying to install this package into a project that targets '.NETFramework,Version=v4.6.2', but the package does not contain any assembly references or content files that are compatible with that framework. For more information, contact the package author.

Outra solução foi colocar diretamente as classes onde vou precisar delas. No entanto, estou com uma questão nos exemplos que dás: X509Certificate2 certChildAutenticacaoCC = RDSignaturesClass.ReturnCertificate("EC de Autenticação do Cartão de Cidadão 0017", "RICARDO MANUEL PATRÍCIO SOARES");

Como alimento essas duas strings, pois tenho que saber donde vêm o Ec..e o nome do utilizador?

Faço 1º algo do genero:

public void procuracertificados() {
X509Store store = new X509Store("MY", StoreLocation.CurrentUser);//.LocalMachine store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); // Obtém a coleção de certificados instalados X509Certificate2Collection collection = store.Certificates; // Excluí da coleção certificados vencidos, comparando a data corrente X509Certificate2Collection fcollection = collection.Find(X509FindType.FindByTimeValid, DateTime.Now, false); //Add_reference - SYSTEM.SECURITY X509Certificate2Collection scollection = X509Certificate2UI.SelectFromCollection(fcollection, "Certificados digitais disponíveis", "Selecione o certificado digital", X509SelectionFlag.MultiSelection);

    //X509Certificate2 cert = X509Certificate2UI.SelectFromCollection(store.Certificates, "Caption", "Message", X509SelectionFlag.SingleSelection)[0];
    string myCert = null;
    Console.WriteLine("Number of certificates: {0}{1}", scollection.Count, Environment.NewLine);
    if (scollection.Count != 0)
    {
        foreach (X509Certificate2 x509 in scollection)
        {
            myCert = x509.ToString();
            try
            { //Grava informações de certificado e cadeia de certificados no console. A saída depende do certificado que o utilizadpr seleciona.
                byte[] rawdata = x509.RawData;
                Console.WriteLine("Content Type: {0}{1}", X509Certificate2.GetCertContentType(rawdata), Environment.NewLine);
                Console.WriteLine("Friendly Name: {0}{1}", x509.FriendlyName, Environment.NewLine);
                Console.WriteLine("Certificate Verified?: {0}{1}", x509.Verify(), Environment.NewLine);
                Console.WriteLine("Simple Name: {0}{1}", x509.GetNameInfo(X509NameType.SimpleName, true), Environment.NewLine);
                Console.WriteLine("Signature Algorithm: {0}{1}", x509.SignatureAlgorithm.FriendlyName, Environment.NewLine);
                Console.WriteLine("Public Key: {0}{1}", x509.PublicKey.Key.ToXmlString(false), Environment.NewLine);
                Console.WriteLine("Certificate Archived?: {0}{1}", x509.Archived, Environment.NewLine);
                Console.WriteLine("Length of Raw Data: {0}{1}", x509.RawData.Length, Environment.NewLine);
                //X509Certificate2UI.DisplayCertificate(x509); //mostra detalhes do certificado
                x509.Reset();
            }
            catch (CryptographicException)
            {
                Console.WriteLine("Information could not be written out for this certificate.");
            }
           // SignData("xml", x509);
        }

        Response.Write(myCert);
    }
    else
        Response.Write("Preparar um painel, para voltar a seleccionar um certificado.. ou para passar a receita materializada, ou então cancelar impressão.");
    store.Close();

}

RicardoSoares97 commented 2 years ago

Olá @sarachavescosta

Esta library esta feita para .Net Core, podes copiar o código e colocar numa library tua com a versão certa que irá funcionar.

Como alimento essas duas strings, pois tenho que saber donde vêm o Ec..e o nome do utilizador? Podes usar o SDK da Autenticação gov para obteres o certificado "sem private key" para obteres essas informações.

Por exemplo no meu programa da empresa de clinicas, temos configurado nas contas dos médicos, 3 campos para serem preenchidos:

KeyDays commented 2 years ago

Ok, e assim sendo escusam de estar sempre a inserir o cartão certo? Uma vez que tem esses dados em backoffice.

RicardoSoares97 commented 2 years ago

Olá @sarachavescosta

O cartão tem de estar sempre inserido de modo a que o Smart Card do Windows consiga a obter os certificados (com private key, é necessário para assinatura) Eu configurei esses campos por médico (ou seja, alguém da administração da clinica configura) para evitar ter que fazer o seguinte fluxo:

Fluxo Sem Configuração:

  1. Obter o certificado do médico através da DLL do SDK da Autenticação Gov.PT
    • Obter o Issuer By
    • Obter o Subject Name
  2. Obter o certificado "filho" com a private key através do smart card, usando o Issuer By e o Subject Name obtidos acima
  3. Obter o certificado "pai" com a private key através do smart card, usando o Issuer By do certificado "filho"

Atualmente eu tenho: O meu Issuer By e o Subject Name estão configurados via conta do médico no programa, com isto não necessito de efetuar o primeiro passo acima mencionado.

  1. Obter o certificado "filho" com a private key através do smart card, usando o Issuer By e o Subject Name através da configuração da conta
  2. Obter o certificado "pai" com a private key através do smart card, usando o Issuer By do certificado "filho"

Após obteres os certificados "filho" e "pai" através do smart card Podes enviar então usar o método de assinatura com XML.

Conselho

O que eu faria

  1. Começa por obter o certificado "filho" e "pai" com o issuer by e o subject name fixos no código, tal como deixei no exemplo do meu repositório, (usa o teu cartão de cidadão)
  2. Constrói um XML muito simples e assina só para ver se te funciona direitinho

Se mesmo assim não conseguires chegar lá, se quiseres contacta-me via Linkedin e eu via chat tento ajudar-te com prints para chegares a tua funcionalidade ;)

KeyDays commented 2 years ago

Olá,

Apos ter posto localmente tudo a funcionar, tentamos colocar numa pagina de teste num servidor, mas não funciona.. Pode.m ajudar-me sobre o que esta em falta??

The current session is not interactive. Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.InvalidOperationException: The current session is not interactive. X509Certificate2Collection fcollection = collection.Find(X509FindType.FindByTimeValid, DateTime.Now, false); Line 63: //Add_reference - SYSTEM.SECURITY Line 64: X509Certificate2Collection scollection = X509Certificate2UI.SelectFromCollection(fcollection, "Certificados digitais disponíveis", "Selecione o certificado digital", X509SelectionFlag.MultiSelection); //SingleSelection

R2CristinaMoura commented 1 year ago

Olá @RicardoSoares97,

Estou com um problema idêntico ao da @sarachavescosta, isto é em modo local funciona (em C# Visual Studio), quando passamos para um servidor IIS, obtemos o erro "A referência de objecto não foi definida como uma instância de um objecto." quando tenta carregar os certificados.

certChild = RDSignatures.RDSignaturesClass.ReturnCertificate("EC de Assinatura Digital Qualificada do Cartão de Cidadão 0015", "NOME");
certRoot = RDSignatures.RDSignaturesClass.ReturnCertificate(certChild.IssuerName.Name);

Conseguem ajudar-me com esta situação?

Obrigada