Páginas

domingo, 1 de outubro de 2017

Testes automatizados em sistemas autenticados com certificados digitais, usando Selenium e PhantomJS

A automatização de testes é uma disciplina muito importante hoje em dia. Entre várias técnicas e ferramentas diferentes, uma das que podemos utilizar para termos um conjunto de testes funcionais que possam ser facilmente repetidos é o Selenium. O Selenium é uma ferramenta que permite que a execução de passos que uma pessoa faria em um browser web (Chrome, Edge, etc.) possa ser programada. Com isso, toda vez que for necessária a validação de uma nova versão de sistema ou correção, este grupo de testes pode ser rodado de forma automática.

Ainda falando sobre a execução do Selenium, ele trabalha criando uma instância do respectivo browser e enviando comandos para ele. Dessa forma é feita a simulação de preenchimento de caixas de texto, cliques em botões, etc. Quando um teste automatizado com Selenium é rodado, pode-se ver todo o teste acontecendo. Entretanto, há um problema nesta abordagem. Caso a sessão do usuário que está executando os testes seja bloqueada, o Selenium não consegue mais simular a interação com a interface de usuário do browser.

Para resolver esse tipo de situação, é possível utilizar um browser sem interface. A ideia é que este programa possa renderizar em memória a interface das páginas web sem efetivamente criar janelas com estes componentes. Assim, é possível executar o conjunto de testes sem uma sessão de usuário. É seguindo este conceito que o Selenium faz o uso do PhantomJS.

Toda essa introdução serviu para chegar em um problema que tive recentemente ao trabalhar desta forma. Alguns sistemas web são programados para considerar autenticação via certificado digital, seja um certificado instalado na máquina (por exemplo, A1) ou um smart card (A3). Quando um sistema com essa característica é acessado, o browser exibe uma janela pop-up para a seleção de qual certificado digital deve ser utilizado (e no caso dos smart cards, é solicitado também a digitação do PIN associado). Só assim o browser envia essas informações ao servidor para que se possa ser feita a autenticação.

Browser Chrome solicitando um certificado digital para acesso de sistema.


Só que as implementações dos drivers disponíveis para trabalhar com o Selenium e os principais browsers não permite o controle dessa caixa de seleção de certificados. Ou seja, o Selenium por si só não tem a capacidade de fazer com que o browser seja autenticado dessa forma. Com o driver do Internet Explorer até é possível fazer os passos de seleção de certificado utilizando uma outra ferramenta chamada AutoIT (que trabalha com automatização de interação em janelas nativas do Windows), mas com o Chrome isso não funciona.

Já o PhantomJS na versão 2.1 permite que passemos um certificado para autenticação. Para que isso possa ser feito, é necessário termos em mãos um arquivo de certificado digital de extensão PFX. Esse PFX deve ser um certificado válido para acesso ao sistema, ou seja, deve estar dentro da data de validade, possuir a cadeira de validação correta, entre outros requisitos. Não é simplesmente usar um certificado digital qualquer para acesso ao sistema, o sistema deve estar preparado para aceitar os certificados corretos.

Somente o arquivo PFX não é suficiente para trabalharmos. O PhantomJS exige que seja passado o certificado digital e a sua chave em arquivos separados. Para isso, precisamos utilizar uma ferramenta que faça a exportação desses dados em arquivos separados. Essa ferramenta é o OpenSSL e pode ser baixado em http://gnuwin32.sourceforge.net/packages/openssl.htm.

Tendo em posse essa ferramenta, o certificado digital e a senha deste certificado, é necessária a execução dos comandos abaixo para a extração e geração dos arquivos necessários:

fabiogouw@SHIVA D:\Tools\OpenSSL\bin
$ openssl pkcs12 -nokeys -in C:\certs\client-certificate.pfx -out c:\certs\client-certificate.crt
WARNING: can't open config file: C:/OpenSSL/openssl.cnf
Enter Import Password:
MAC verified OK

fabiogouw@SHIVA D:\Tools\OpenSSL\bin
$ openssl pkcs12 -nocerts -in C:\certs\client-certificate.pfx -out c:\certs\client-certificate.key
WARNING: can't open config file: C:/OpenSSL/openssl.cnf
Enter Import Password:
MAC verified OK
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:

fabiogouw@SHIVA D:\Tools\OpenSSL\bin
$

Veja que durante a execução a ferramenta pede a senha do certificado (nas linhas "Enter Import Password") e uma outra senha para o acesso da chave do certificado (na linha "Enter PEM pass phrase"). Como resultado final, teremos:
  • Um arquivo contendo o certificado digital, na extensão CRT.
  • Um arquivo contendo a chave do certificado digital, na extensão KEY.
  • Uma senha para a leitura da chave privada do arquivo de extensão KEY.
Com isso, podemos executar o teste abaixo no Visual Studio. Esse teste acessa um site hospedado no Azure que foi configurado para exigir autenticação via certificado digital (instruções de como fazer isso podem ser encontradas em https://docs.microsoft.com/en-us/azure/app-service/app-service-web-configure-tls-mutual-auth) que, depois de autenticado, exibe uma simples tela onde temos uma calculadora que soma dois números, via JavaScript.

using Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenQA.Selenium;
using OpenQA.Selenium.PhantomJS;
using System;

namespace ClientCertificate.UnitTest
{
    [TestClass]
    public class UnitTest1
    {
        private IWebDriver _driver;
        public TestContext TestContext { get; set; }

        [TestInitialize]
        public void Inicializar()
        {
            var service = PhantomJSDriverService.CreateDefaultService();
            service.IgnoreSslErrors = true;
            service.AddArgument($"--ssl-client-certificate-file=C:\\certs\\client-certificate.crt");
            service.AddArgument($"--ssl-client-key-file=C:\\certs\\client-certificate.key");
            service.AddArgument($"--ssl-client-key-passphrase=1234");
            _driver = new PhantomJSDriver(service);
            _driver.Manage().Timeouts().PageLoad = new TimeSpan(0, 0, 10);
            _driver.Manage().Timeouts().ImplicitWait = new TimeSpan(0, 0, 10);
            

        }

        [TestMethod]
        public void TestarFuncionalidadeComAutenticacaoViaCertificado()
        {
            _driver.Navigate().GoToUrl("https://clientcertificatewebapp331.azurewebsites.net/Home/Soma");
            try
            {
                _driver.FindElement(By.Id("valor1")).SendKeys("1");
                _driver.FindElement(By.Id("valor2")).SendKeys("2");
                _driver.FindElement(By.Id("btnSoma")).Click();
                Assert.AreEqual("3", _driver.FindElement(By.Id("resultado")).Text);
            }
            finally
            {
                TirarImagem();
            }
        }

        private void TirarImagem()
        {
            Screenshot ss = ((ITakesScreenshot)_driver).GetScreenshot();
            ss.SaveAsFile($"{TestContext.TestRunResultsDirectory}\\imagem.jpg", ScreenshotImageFormat.Png);
        }

        [TestCleanup]
        public void Finalizar()
        {
            if(_driver != null)
            {
                _driver.Dispose();
            }
        }
    }
}

Observe que na criação da instância do driver do PhantomJS os dois arquivos (CRT e KEY) e a senha são passados na criação do browser em memória, usando a classe PhantomJSDriverService. Sem esta linha, quando o teste executar e o browser tentar acessar o site, ele não conseguirá se autenticar e por consequência os próximos passos de seleção de campos não funcionarão, ocasionando na falha do teste. Notem que eu sempre tiro um print de tela no teste usando PhantomJS, para que seja possível identificar o problema de um teste ou mesmo apenas validar a sua execução. No caso de falha de autenticação (código sem a preparação que eu mostrei, ou com o certificado inválido), a imagem que apareceria é a abaixo.


Já quando tudo esta correto, o teste é executado sem falhas, exibindo como resultado final a soma da calculadora.

Até a próxima!

Nenhum comentário:

Postar um comentário