1. PJeOffice

PJeOffice é o software disponibilizado pelo CNJ para assinatura eletrônica de documentos para o sistema PJe. O objetivo do aplicativo é garantir a validade jurídica dos documentos e processos, além de substituir a necessidade do plugin Oracle Java Runtime Environment no navegador de internet e gerar maior praticidade na utilização do sistema. Além da assinatura de documentos também é possível realizar autenticação por certificado digital através do PJeOffice.

Para utilização do PJeOffice é necessário que o usuário faça o download e instalação do aplicativo em sua máquina. O PJeOffice precisa estar instalado na máquina do usuário para que faça a leitura do certificado, que será utilizado para a assinatura dos documentos. Atualmente o PJeOffice está disponível para utilização nos sitemas Windows, MacOS, e linux. Abaixo os links para download.

Sistema Operacional Link

Windows

pje-office.exe

MacOS 64Bits

pje-office_x64.dmg

Debian 32Bits

pje-office_i386.deb

Debian 64Bits

pje-office_amd64.deb

Unix

pje-office_unix_no_embedded.tar.gz

Para mais informações acerca da instalação do PJeOffice clique aqui.

2. Como utilizar o PJeOffice na minha aplicação?

Assim como é utilizado pelo PJe para assinatura e autenticação com certificado digital, o PJeOffice também pode ser utilizado por outras applicações para o mesmo propósito. O PJeOffice não possui nenhuma dependência do PJe e roda localmente na máquina do usuário, isso nos permite expandir seu potencial de utilização para outras aplicações.

Na máquina do usuário o PJeOffice inicia um serviço local rodando na porta 8800. Uma página web pode realizar chamadas a esse serviço para realização de tarefas. O PJeOffice responde às solicitações em uma URL de callback que irá receber o resultado do processamento dessas tarefas através de um HTTP POST.

2.1. Tarefas

O serviço local do PJeOffice é capaz de realizar tarefas. Cada tarefa tem um propósito único, recebe um tipo específico de payload e responde com o conjunto de informações referentes àquela tarefa. Cada tarefa possui um identificador, é por meio deste identificador que o PJeOffice sabe qual tarefa executar. Este identificador é enviado no payload da requisição, junto com outras informações, inclusive dados referentes a tarefa que se deseja executar.

Exemplo de payload da requisição
{
    // Cookies
    sessao         : document.cookie,
    // Nome da aplicação
    aplicacao       : "PJe",
    // Código de segurança gerado para a url do backend
    codigoSeguranca : this.util.codigoSeguranca(),
    // Tarefa a ser executada pelo pjeOffice
    tarefaId : "cnj.assinadorHash",
    // Raiz da url do servidor
    servidor        : this.util.webRoot(),
    // Dados para a execução da tarefa do pjeOffice
    tarefa : {
        // Objeto JSON da respectiva tarefa
    }
}

Atualmente o PJeOffice oferece duas tarefas que podem ser chamdas de uma aplicação Web:

2.1.1. Assinador de hash

Objetivo: Permite a assinatura do hash com o certificado escolhido pelo usuário.

Identificador:: cnj.assinadorHash

Dados relevantes: Algoritimo de assinatura, URL de callback e lista com os hashes a serem assinados

Exemplo de payload da requisição para tarefa de assinatura de hash
{
    // Cookies
    sessao         : document.cookie,
    // Nome da aplicação
    aplicacao       : "PJe",
    // Código de segurança gerado para a url do backend
    codigoSeguranca : this.util.codigoSeguranca(),
    // Tarefa a ser executada pelo pjeOffice
    tarefaId : "cnj.assinadorHash",
    // Raiz da url do servidor
    servidor        : this.util.webRoot(),
    // Dados para a execução da tarefa do pjeOffice
    tarefa : {
        // Objeto JSON da respectiva tarefa
        // Algoritimo utilizado para assinatura
        algoritmoAssinatura : "ASN1MD5withRSA",
        // Indicador para utilização de assinatura em modo de teste
        modoTeste : "false",
        // Url de callback, será concatenada à url raiz do servidor backend
        uploadUrl : PJE_LEGACY + "/painelUsuario/uploadArquivoAssinado",
        // Lista de objetos contendo os dados para assinatura
        // Lista de arquivos com o seguinte modelo
        /*
            id, identificador do documento
            hash, hash do documento
            codIni, data de criação do documento com formato "HHmmssSSS"
            isBin, indica se o documento é binário;
        */
        arquivos : arquivos
    }
}

2.1.2. Autenticador

Objetivo: Permite a autenticação de usuário por meio de frase desafio

Identificador:: cnj.autenticador

Dados relevantes: Url de callback e frase desafio

Exemplo de payload da requisição de autenticação
{
    // Cookies
    sessao         : document.cookie,
    // Nome da aplicação
    aplicacao       : "PJe",
    // Código de segurança gerado para a url do backend
    codigoSeguranca : this.util.codigoSeguranca(),
    // Tarefa a ser executada pelo pjeOffice
    tarefaId : "cnj.autenticador",
    // Raiz da url do servidor
    servidor        : this.util.webRoot(),
    // Dados para a execução da tarefa do pjeOffice
    tarefa : {
        // Objeto JSON da respectiva tarefa
        // URL de callback
        enviarPara        : "/callbackAutenticacao.do",
        // Frase desafio a ser assinada para validação na callback
        mensagem                : ASSINATURA_MENSAGEM
    }
}

2.2. Codigo de segurança

O código de segurança é uma chave gerada pelo CNJ a partir da URL do sistema cliente. A aplicação cliente deve enviar o código de segurança entre os atributos do objeto da requisição. O PJeOffice irá validar a autenticidade dessa chave, permitindo ou não a execução da tarefa. Esse mecanismo busca garantir que somente aplicações autorizadas terão acesso as tarefas do PJeOffice.

Para obter um código de segurança válido é necessário entrar em contato com a equipe técnica do CNJ responsável pelo PJe.

2.3. Habilitando o modo desenvolvedor

Para o desenvolvedor o PJeOffice permite algumas operações facilitadas. O modo desenvolvedor permite, por exemplo, a assinatura de documentos em modo de teste. Este recurso facilita a realização de testes na aplicação, porém não gera assinaturas válidas do ponto de vista formal.

Para utilizar o modo de desenvolvedor são necessárias duas ações:

  • Passar o atributo modoTeste no objeto da tarefa; (Obs: nem todas as tarefas possuem modo de teste)

  • Habilitar o modo desenvolvedor no PJeOffice

    • Para isso é necessário acessar o menu Sistema do PJeOffice no system tray. Uma janela com informações da aplicação será aberta. Clique no número da versão do PJeOffice até que o modo desenvolvedor seja habilitado.

2.4. Um componente Javascript para realizar as requisições

pjeOffice.js
/**
 * Classe responsavel por armazenar os componentes do PJeOffice
 *
 */
var PJeOffice = {};

PJeOffice.stringify = function (value) {
        var _json_stringify = JSON.stringify;
    var _array_tojson = Array.prototype.toJSON;
    delete Array.prototype.toJSON;
    var r=_json_stringify(value);
    Array.prototype.toJSON = _array_tojson;
    return r;
};

PJeOffice.executar = function(requisicao, onSucesso, onErro, onIndisponivel) {

        var t = requisicao.tarefa;

        requisicao.tarefa = PJeOffice.stringify(t);

        var r = PJeOffice.stringify(requisicao);
        r = encodeURIComponent(r);

        var image = new Image();
        image.onload = function() {
                // Quando o PJeOffice retornar uma imagem com 2px de largura e pq houve algum erro na execucao
                if (this.width == 2) {
                        onErro();
                }
                else {
                        // Quando o PJeOffice retornar uma imagem com 1px de largura e pq houve sucesso na execucao
                        onSucesso();
                }
        }
        image.onerror = onIndisponivel;
        image.src = "http://localhost:8800/pjeOffice/requisicao/?r=" + r + "&u=" + new Date().getTime();
}

PJeOffice.verificarDisponibilidade = function(onDisponivel, onIndisponivel) {

        var image = new Image();

        image.onload = onDisponivel;
        image.onerror = onIndisponivel;
        image.src = "http://localhost:8800/pjeOffice/?&u=" + new Date().getTime();
}

/**
 * Transforma a string "id=1581848&codIni=153335780&md5=92a9d63176ececbb35096529f3f5dc29&isBin=false"
 * em uma lista de objeto json { id: x, codIni: x, hash: x, isBin: x }
 */
PJeOffice.parseToListaArquivos = function(docsFields) {

        var arquivos = [];

        var itens = docsFields.split(",");

        for (var i=0; i < itens.length; i++) {

                var itemFields = itens[i].split("&");

                if (itemFields.length == 4) {
                        arquivos[i] = {
                                "id"                   : itemFields[0].substr(itemFields[0].indexOf("=") + 1),
                                "codIni"         : itemFields[1].substr(itemFields[1].indexOf("=") + 1),
                                "hash"                 : itemFields[2].substr(itemFields[2].indexOf("=") + 1),
                                "isBin"         : itemFields[3].substr(itemFields[3].indexOf("=") + 1)
                        }
                }
                else {
                        arquivos[i] = {
                                "hash"                 : itemFields[0].substr(itemFields[0].indexOf("=") + 1)
                        }
                }
        }

        return arquivos;
}

2.5. Enviando uma requisição de tarefa de assinatura

Utilizando o componente sugerido acima podemos exemplificar uma requisição da tarefa de assinatura de hash através do sequinte código javascript:

assinatura.js
var CODIGO_SEGURANCA = "CODIGO_SEGURANCA";

function assinadorHash() {
    var arquivos = PJeOffice.parseToListaArquivos(getUrlAssinaturas());

    PJeOffice.executar(
            {
                "aplicacao"                 : "SampleApp",
                "servidor"                         : window.location.origin,
                "sessao"                         : document.cookie,
                "codigoSeguranca"        : CODIGO_SEGURANCA,
                "tarefaId"                        : "cnj.assinadorHash",
                "tarefa"                        : {
                    "algoritmoAssinatura"        : "ASN1MD5withRSA",
                    "modoTeste"                                : "true",
                    "uploadUrl"                                : "/callback",
                    "arquivos"                                : arquivos
                }
            },
        function () {
            successFunc();
        },
        function () {
            errorFunc();
        },
        function () {
            unavaliableFunc();
        }
    );
}

function getUrlAssinaturas(){
    return "id=10075&codIni=164233865&md5=142745829f5287463d545740c54cd0f3&isBin=false"
}

function successFunc(){
    alert("Assinatura realizada com sucesso!");
}

function errorFunc(){
    alert("Ocorreu um erro na assinatuda do hash.");
}

function unavaliableFunc() {
    alert("O PJeOffice está indisponível ou inacessível");
}

Para executar a requisição será efetuado um HTTP GET no serviço do PJeOffice. Nesta requisição será enviado o payload. Caso a execução da tarefa resulte em sucesso a função successFunc() será executada. Os casos de erro e indisponibilidade do PJeOffice também são tratados em callbacks específicas.

As callbacks representadas acima em funções javascript não se misturam com a callback para o backend efetuada na execução da tarefa. As callbacks javascript não retornam nenhum tipo de dado resultantes da execução da tarefa.

2.6. Recebendo a resposta da tarefa de assinatura de hash

Ao executar a tarefa o PJeOffice irá fazer um POST na URL de callback informada no payload. O post realizado utiliza Content-Type application/x-www-form-urlencoded. No corpo da requisição será enviado o objeto resultado da execução da tarefa, que para o caso do assinador de hash pode ser representado pelo POJO abaixo:

ArquivoAssinadoHash.java
public class ArquivoAssinadoHash implements Serializable{
        /**
         *
         */
        private static final long serialVersionUID = 1L;

        /**
         * Define o id do arquivo assinado
         */
        private String id;

        /**
         * Define o cod ini do arquivo assinado
         */
        private String codIni;

        /**
         * Define o hash do arquivo assinado
         */
        private String hash;

        /**
         * Define a assinatura do arquivo assinado
         */
        private String assinatura;

        /**
         * Define a cadeia de certificado do signatario
         */
        private String cadeiaCertificado;



        public ArquivoAssinadoHash() {
                super();
        }

        public ArquivoAssinadoHash(String id, String codIni, String hash, String assinatura, String cadeiaCertificado) {
                super();
                this.id = id;
                this.codIni = codIni;
                this.hash = hash;
                this.assinatura = assinatura;
                this.cadeiaCertificado = cadeiaCertificado;
        }

        public String getId() {
                return id;
        }

        public void setId(String id) {
                this.id = id;
        }

        public String getCodIni() {
                return codIni;
        }

        public void setCodIni(String codIni) {
                this.codIni = codIni;
        }

        public String getHash() {
                return hash;
        }

        public void setHash(String hash) {
                this.hash = hash;
        }

        public String getAssinatura() {
                return assinatura;
        }

        public void setAssinatura(String assinatura) {
                this.assinatura = assinatura;
        }

        public String getCadeiaCertificado() {
                return cadeiaCertificado;
        }

        public void setCadeiaCertificado(String cadeiaCertificado) {
                this.cadeiaCertificado = cadeiaCertificado;
        }

        public String toString() {
                StringBuilder sb = new StringBuilder(300)
                                .append('#').append(getId()).append(' ').append(getCodIni()).append(" - ").append(getHash());
                return sb.toString();
        }
}

A aplicação que recebe o POST tratará esse objeto de acordo com suas próprias regras e deve retornar uma resposta em text/plain para o PJeOffice. Através dessa resposta o PJeOffice irá indicar para o frontend qual o resultado do processamento da tarefa. Abaixo um exemplo de recebimento e resposta da chamada do PJeOffice à URL de callback:

PjeOfficeCallback.java
@RestController
public class PjeOfficeCallback {

        @PostMapping(path = "/callback", produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
        public ResponseEntity<String> callbackAssinadorHash(@ModelAttribute ArquivoAssinadoHash arquivoAssinado) {
                if(arquivoAssinado != null) {
                        System.out.println(arquivoAssinado.toString());
                        return this.imprimirResposta("Sucesso");
                } else {
                        return this.imprimirResposta("Erro");
                }
        }

        private ResponseEntity<String> imprimirResposta(String mensagem) {
                ResponseEntity<String> response = ResponseEntity.ok(mensagem);

                return response;
        }

}