demoiselle / signer

Repositório que contém os componentes para facilitar a implementação de assinatura digital nos padrões da ICP-BRASIL
https://www.frameworkdemoiselle.gov.br/v3/signer/
GNU Lesser General Public License v3.0
142 stars 73 forks source link

Suporte a assinatura de arquivos grandes > 1.5GB #383

Open yurix opened 1 year ago

yurix commented 1 year ago

Estava substituindo a minha própria implementação de CAdEs pelo Demoiselle Signer quando me deparei com a exceção "java.lang.OutOfMemoryError: Java heap space" na assinatura de arquivos grandes. Minha solicitação é que na próxima versão o componente altere o seu design e permita o uso de Streams na leitura do conteúdo para assinatura e no armazenamento da saída (seja detached ou attached), evitando a todo custo a cópia de todo conteúdo para memória byte[].

Para contornar e suportar grandes arquivos fiz a seguinte alteração:

  1. Alteração do tipo de gerador de pacote CMS do ByteArray para o CMSProcessableFile.
    CMSTypedData cmsTypedData;
    // para assinatura do hash, content nulo
    if (content == null) {
    cmsTypedData = new CMSAbsentContent();
    } else {
    cmsTypedData = new CMSProcessableFile(content);
    }

Observação: A Bouncycastle implementa de forma pública apenas estes dois, desta forma exigindo que API receba um File. Isso faz sentido, pois a partir de um arquivo é possível "consumir" vários InputStream, como me parece que é o caso. Entretanto, em termos de API me parece inadequado pois é melhor trabalhar com a abstração InputStream do que a File. Um caminho talvez para implementar desta forma seria criando uma CMSProcessableInputStream[^1] e trabalhando ela com as classes TeeInputStream da própria BouncyCastle. Como o tempo ficou muito curto para atender este refactoring, acabei seguindo um caminho mais simples.

  1. No trecho de geração dos atributos assinados ou não interceptei a geração do Atributo MessageDigest (Que trabalha todo em memória) por uma implementação de geração de hash por um stream. Abaixo o código da interceptação.
    
    SignedOrUnsignedAttribute signedOrUnsignedAttribute = attributeFactory .factory(objectIdentifier.getValue());

if (signedOrUnsignedAttribute instanceof org.demoiselle.signer.policy.impl.cades.pkcs7.attribute.impl.MessageDigest) { StreamMessageDigest smd = new StreamMessageDigest(); smd.initializeStream(this.pkcs1.getPrivateKey(), certificateChain, new FileInputStream(content), signaturePolicy, this.hash); signedOrUnsignedAttribute = smd; } else { signedOrUnsignedAttribute.initialize(this.pkcs1.getPrivateKey(), certificateChain, null, signaturePolicy, this.hash); }

3. Abaixo o trecho da classe StreamMessageDigest, que é nada mais de uma alteração simples na classe MessageDigest. 

@Override public Attribute getValue() { try { if (this.hash == null) { String hashAlgorithm = signaturePolicy.getSignPolicyHashAlg().getAlgorithm().getValue(); if (this.contentInputStream!=null) { java.security.MessageDigest md = java.security.MessageDigest.getInstance(hashAlgorithm);

                byte[] buffer = new byte[8192]; // Tamanho do buffer para leitura dos dados

                int bytesRead;
                while ((bytesRead = this.contentInputStream.read(buffer)) != -1) {
                    md.update(buffer, 0, bytesRead);
                }

                this.hash = md.digest();
                this.contentInputStream.close();
            } else {
                java.security.MessageDigest md = java.security.MessageDigest.getInstance(hashAlgorithm);
                this.hash = md.digest(content);
            }
        }
        return new Attribute(identifier, new DERSet(new DEROctetString(this.hash)));
    } catch (NoSuchAlgorithmException | IOException ex) {
        logger.info(ex.getMessage());
        return null;
    }
}


[^1]: https://github.com/bcgit/bc-java/blob/1.72/pkix/src/main/java/org/bouncycastle/cms/CMSProcessableInputStream.java