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:
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.
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.
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:
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.
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); }
@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);