Comunicação de documentos de transporte à AT a partir da .NET Compact Framework [reactualizado a 2013/06/17]

(post revisto a 2013/06/17, alterado código exemplo e link para componente)


A partir de 1 de Maio, os documentos de transporte terão de ser comunicados previamente à AT, sendo uma das formas disponibilizadas e aquela que se afigura mais prática, a comunicação via webservices, que permite na resposta obter o código atribuío pela AT ao documento transmitido.


Com base na informação num tópico no Portugal-a-programar, comecei a semana passada um projecto de testes para validar se a comunicação em causa poderia ser feita directamente a partir da .NET Compact Framework, o que se afigurava difícil dadas as limitações da .NET CF a lidar com certificados, nomeadamente o carregamento de .pfx e a utilização de .cer para encriptar a informação. Depois de dias a investigar Platform Invokes para o efeito sem sucesso, e de ter confirmado que uma ou outra solução candidata não produzia resultados válidos quando comunicados aos serviços da AT, acabei por fazer aquilo que deveria ter feito em 1º lugar: procurar um componente de terceiros que fizesse o serviço, e assim no www.componentsource.com descobri um componente que, não sendo caro, resolve as duas limitações que tinha encontrado.


O seguinte método em C# devolve o resultado da chamada ao serviço:


public static string testa()
{
    string endpoint = “https://servicos.portaldasfinancas.gov.pt:401/sgdtws/documentosTransporte”;
    string senhaCertificado = “xxxxxxxxxx”;

    string userPortal = “xxxxxxxxx/x”;
    string passwordPortal = “xxxxxxx”;


    string horaCriacaoCifrada;
    string senhaCifrada;
    string nonce;

    SBUtils.Unit.SetLicenseKey(colocar aqui a chave incluída no ficheiro C:\Program Files (x86)\EldoS\SecureBlackbox.NET\Assemblies\NET_CF20\LicenceKey.txt);

    String DataCriacao = DateTime.Now.ToUniversalTime().ToString(“yyyy-MM-ddTHH:mm:ss.ff”) + “Z”;

   
RijndaelManaged rijndaelCipher = new RijndaelManaged();
    rijndaelCipher.GenerateKey();
    rijndaelCipher.Mode = CipherMode.ECB;
    rijndaelCipher.Padding = PaddingMode.PKCS7;
    SymmetricAlgorithm rijn = SymmetricAlgorithm.Create();
    rijn.Key = rijndaelCipher.IV;
    rijn.IV = rijndaelCipher.IV;
    rijn.Mode = CipherMode.ECB;
    MemoryStream msPassFinancas = new MemoryStream();
    CryptoStream csPassFinancas = new CryptoStream(msPassFinancas, rijn.CreateEncryptor(rijn.Key, rijn.IV), CryptoStreamMode.Write);
    using (StreamWriter swPassFinancas = new StreamWriter(csPassFinancas))
    {
        swPassFinancas.Write(passwordPortal);
    }
    MemoryStream msDataCriacao = new MemoryStream();
    CryptoStream csDataCriacao = new CryptoStream(msDataCriacao, rijn.CreateEncryptor(rijn.Key, rijn.IV), CryptoStreamMode.Write);
    using (StreamWriter swDataCriacao = new StreamWriter(csDataCriacao))
    {
        swDataCriacao.Write(DataCriacao);
    }
    senhaCifrada = Convert.ToBase64String(msPassFinancas.ToArray());
    horaCriacaoCifrada = Convert.ToBase64String(msDataCriacao.ToArray());
    RSACryptoServiceProvider AlgRSA = new RSACryptoServiceProvider();

    byte[] rsaModulus = null;
    byte[] rsaPublicKey = null;
    using (TElX509Certificate x509tt = new TElX509Certificate(null))
    {
        //carregar certificado a parir de array de bytes previamente preenchido
        x509tt.LoadFromBufferAuto(_publicCertificate, 0, _publicCertificate.Length,“”);
        int rsaModulusSize = 0;
        int rsaPublicKeySize = 0;
        x509tt.GetRSAParams(ref rsaModulus, ref rsaModulusSize, ref rsaPublicKey, ref rsaPublicKeySize);
        rsaModulus = new byte[rsaModulusSize];
        rsaPublicKey = new byte[rsaPublicKeySize];
        x509tt.GetRSAParams(ref rsaModulus, ref rsaModulusSize, ref rsaPublicKey, ref rsaPublicKeySize);
    }

    RSAParameters rsaP = new RSAParameters()
    {
        Modulus = rsaModulus,
        Exponent = rsaPublicKey
    };
    AlgRSA.ImportParameters(rsaP);
    Byte[] Chave = AlgRSA.Encrypt(rijn.Key, false);
    nonce = Convert.ToBase64String(Chave);

    StringBuilder sb = new StringBuilder();
    sb.Append(“<?xml version=\”1.0\” encoding=\”UTF-8\”?>”);
    sb.Append(“<S:Envelope xmlns:wss=\”http://schemas.xmlsoap.org/ws/2002/12/secext\” xmlns:ns2=\”https://servicos.portaldasfinancas.gov.pt/sgdtws/documentosTransporte/\” xmlns:S=\”http://schemas.xmlsoap.org/soap/envelope/\”>”);
    sb.Append(“<S:Header>”);
    sb.Append(“<wss:Security>”);
    sb.Append(“<wss:UsernameToken>”);
    sb.AppendFormat(“<wss:Username>{0}</wss:Username>”, userPortal);
    sb.AppendFormat(“<wss:Password>{0}</wss:Password>”, senhaCifrada);
    sb.AppendFormat(“<wss:Nonce>{0}</wss:Nonce>”, nonce);
    sb.AppendFormat(“<wss:Created>{0}</wss:Created>”, horaCriacaoCifrada);
    sb.Append(“</wss:UsernameToken>”);
    sb.Append(“</wss:Security>”);
    sb.Append(“</S:Header>”);
    sb.Append(“<S:Body>”);
   
sb.Append(“<ns2:envioDocumentoTransporteRequestElem>”);
    sb.Append(
    @”<TaxRegistrationNumber>xxxxxxxxx</TaxRegistrationNumber>
      <CompanyName>Empresa para teste Lda.</CompanyName>
      <CompanyAddress>
        <Addressdetail>Centro de Empresas de Taveiro</Addressdetail>
       
<City>Taveiro</City>
        <PostalCode>3045-123</PostalCode>
        <Country>PT</Country>
      </CompanyAddress>
      <DocumentNumber>GT AB/1</DocumentNumber>
      <MovementStatus>N</MovementStatus>
      <MovementDate>2013-05-30</MovementDate>
      <MovementType>GT</MovementType>
        <CustomerTaxID>999999990</CustomerTaxID>
    <CustomerAddress>
        <Addressdetail>Urb. Atras Sol Posto</Addressdetail>
        <City>Faro</City>
        <PostalCode>6000-123</PostalCode>
        <Country>PT</Country>
      </CustomerAddress>
      <CustomerName>Empresa Cliente, LDa.</CustomerName>
      <AddressTo>
        <Addressdetail>Urb. Atras Sol Posto</Addressdetail>
        <City>Faro</City>
        <PostalCode>6000-123</PostalCode>
        <Country>PT</Country>
      </AddressTo>
      <AddressFrom>
        <Addressdetail>Centro de Empresas de Taveiro</Addressdetail>
        <City>Taveiro</City>
        <PostalCode>3045-123</PostalCode>
        <Country>PT</Country>
      </AddressFrom>
      <MovementEndTime>2013-05-30T23:04:36.8111658+01:00</MovementEndTime>
      <MovementStartTime>2013-05-30T22:04:36.8101653+01:00</MovementStartTime>
      <VehicleID>00-AA-11</VehicleID>
      <Line>
        <ProductDescription>Ovos</ProductDescription>
        <Quantity>12</Quantity>
        <UnitOfMeasure>UN</UnitOfMeasure>
        <UnitPrice>0.25</UnitPrice>
      </Line>”
);
    sb.Append(“</ns2:envioDocumentoTransporteRequestElem>”);
    sb.Append(“</S:Body>”);
    sb.Append(“</S:Envelope>”);

    TElX509Certificate x509t = new TElX509Certificate(null);
    // carregar o certificado a partir de um array de bytes previamente preenchido
    x509t.LoadFromBufferPFX(_privateCertificate,senhaCertificado);

    TElHTTPSClient cli = new TElHTTPSClient();
    cli.OnCertificateValidate += new SBSSLCommon.TSBCertificateValidateEvent(cli_OnCertificateValidate);
    cli.RequestParameters.CustomHeaders.Add(“SOAPAction=https://servicos.portaldasfinancas.gov.pt/sgdtws/documentosTransporte/”);
    cli.RequestParameters.ContentType = “text/xml; charset=utf-8″;
    cli.RequestParameters.Accept = “text/xml”;

    TElMemoryCertStorage mc = new TElMemoryCertStorage();
    mc.Add(x509t, true); // client certificate
    cli.ClientCertStorage = mc;

    MemoryStream response = new MemoryStream();
    cli.OutputStream = response;

    byte[] byteArray = Encoding.UTF8.GetBytes(sb.ToString());

    cli.Post(endpoint, byteArray);
    response.Position = 0;

    StreamReader reader = new StreamReader(response);
    string responseFromServer = reader.ReadToEnd();
    reader.Close();
    return responseFromServer;
}

static void cli_OnCertificateValidate(object Sender, TElX509Certificate X509Certificate, ref bool Validate)

{

    Validate = true;

}


Com esta abordagem não necessário instalar os certificados na Certificate Store dos dispositivos.

Para compilar, será necessário adicionar uma referência às assemblies SecureBlackBox, SecureBlackBox.HTTP, SecureBlackBox.HTTPCommon, SecureBlackBox.SSL e SecureBlackBox.SSLCommon, instaladas na pasta C:\Program Files (x86)\EldoS\SecureBlackbox.NET\Assemblies\NET_CF20\ e mudar o valor da chave passada na chamada ao método SetLicenseKey. Ter em atenção as chaves no XML que reportam a datas, pois a hora início tem de ser posterior à hora actual, a hora fim quando fornecida tem de ser superior à hora início e suponho que a data do documento tenha que ser igual ou anterior à actual.

10 thoughts on “Comunicação de documentos de transporte à AT a partir da .NET Compact Framework [reactualizado a 2013/06/17]”

  1. Boa tarde,
    Venho assim tentar informar-me acerca de uma das tantas dúvidas que me atingem neste momento.
    Sou prestador de serviços, presto assistência tecnica a edifícios.
    Diariamente ando com uma viatura “cheia” de Material que aplico ou não, nos edifícios onde intervenho.
    Como é que vou fazer a guia de transporte desse material, se não sei qual vai ser o seu destino e se o mesmo terá um destino?
    Que validade maxima terá essa guia? (actualmente realizamos guias de 30 dias, e vamos justificando a saída de material com as folhas de obra)
    Como é que justificamos o material que nesse data ( validade da guia)não foi aplicado?
    Poderá haver algum regime especial para estas situações?
    Desde já agradeço toda a informação que me puderem dispensar.

  2. Olá Lucas,
    A informação que te vou dar não tem qualquer vínculo legal, é apenas a minha opinião.
    Nesse cenário penso que deves fazer uma Guia de Transporte diariamente sem identificares o destinatário, utilizando os teus próprios dados como “destino”, com todo o material circulante. Ao longo do dia, conforme vás utilizando os materiais, deves gerar um documento de venda ou folha de obra onde sejam assinalados esses consumos, e que servirão de justificação perante as autoridades fiscalizadoras, caos mandem parar a viatura. Esses documentos de venda e folhas de obra devem ter uma referência ao nº da Guia de Transporte desse dia, bem como ser previstos na geração do SAF-T. Caso pretenda avaliar a nossa solução de mobilidade para PDAs que permite gerar estes documentos, contacte-nos, 239983900 e peça para falar com Marco Rangel ou Alberto Silva

  3. Boa tarde.

    Estive a experimentar a função o codigo que partilhou mas dado que não tenho a limitação do mobile substitui as classes que lêem os certificados pelas que o .net já tem. Respectivamente:

    X509Certificate2 x509 = new X509Certificate2(caminhoCertificadoPFX, senhaCertificado);

    e

    X509Certificate2 xCer = new X509Certificate2(caminhoCertificadoCER);
    Byte[] Chave = (RSACryptoServiceProvider)xCer.PublicKey.Key).Encrypt(rijn.Key, false);

    Isto é correcto?

    Pergunto porque o servidor responde sempre Internal Error(Client).

    Presumo que o erro seja a cifrar o nonce, dado que se tentar um RSACryptoServiceProvider como crio para o SAFT através dos dados da PublicKey ele dá erro… Estarei a ler mal o certificado?

    cumprimentos
    N. Agapito

  4. Boa Tarde Alberto,
    Consegue utilizar o TElHTTPSClient?

    Assim que o inicializo devolve-me o erro de System.DllNotFoundException.

    Com oe melhores cumprimentos,
    Paula

  5. Olá Paula,
    Adicionou a referência às seguintes 5 dlls: SecureBlackBox, SecureBlackBox.HTTP, SecureBlackBox.HTTPCommon, SecureBlackBox.SSL e SecureBlackBox.SSLCommon?

    Pode verificar que estão as 5 na pasta da aplicação no PDA/emulador?

  6. good evening
    I’m trying to implement the call to service in Delphi SOAP, but I encounter some problems with the creation of the header
    I have the certificate sent to me by AT but I can not use it to generate the symmetric key (H.2 – Nonce)
    “Can anyone give me the link to read up on Função digit chave de simétrica?”
    thanks

  7. Olá Alberto Silva,

    Antes de mais permita-me felicitá-lo por partilhar esta investigação e respetiva conclusão.

    Vou iniciar agora a implementação de uma aplicação em Windows Mobile e pretendo usar este mecanismo.

    Este código está atualizado e completamente funcional à data, ou existe uma nova versão?

    Tem conhecimento de alguma alternativa que não use o componente “SecureBlackBox”? Isto porque seria vantajoso não ter que comprar o componente.

  8. Olá Carlos,
    Sim, este código funciona, uso-o quando preciso de testar alguma coisa. Tenha em atenção que o certificado original de testes já não é válido, sendo necessário obter um actualizado (na thread do Portugal-a-Programar procure por teste).
    Como já tive oportunidade de partilhar com algumas pessoas, só me arrependo de ter gasto tanto tempo a tentar fazer uma comunicação com sucesso antes de me ter lembrado de procurar um componente de terceiros, pois teria poupado uma semana de experiências. O maior inconveniente quanto a mim em usar o referido componente nem é o seu custo em si, mas o tamanho que as respectivas DLL ocupam no dispositivo.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>