Closed RicardoSoares97 closed 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)
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.
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.
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.
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.
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 ?
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.
Bom dia,
Obrigado, vou verificar os desenvolvimentos a fazer e como efetua-los. Através das documentações todas.
Obrigado pela ajuda.
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.
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.
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?
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á @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
Muito obrigada @RicardoSoares97, vou analisar e experimentar e alguma dúvida voltamos a falar.
Muito bem @RicardoSoares97 :+1: Bom trabalho.
Umas dicas de melhoria:
Olá @ACamposPT
Obigado, vou pegar acrescentar essas dicas e inclui-las assim que possível :)
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.
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();
}
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:
Ok, e assim sendo escusam de estar sempre a inserir o cartão certo? Uma vez que tem esses dados em backoffice.
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:
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.
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
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 ;)
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
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
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
`
Conseguimos obter o SignedInfo juntamente com o DigestValue que é suposto e na qual já nos foi dito que está correto. Exemplo abaixo: `
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.
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
`
`
Após obter este hash devolvo para então enviar ao vosso método Sign que deixo exemplo abaixo do que estou a efetuar `
`
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:
`
`
Ao enviar isto para a SPMS eles dizem-nos que do lado deles o que ocorre
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.