Vamos colocar alguns termos aqui:
JCE (Java Cryptographic Extension) - Interface para adicionar extensões de providers que fornecem recursos de criptografia, assinatura digital, entre outros recursos de segurança
Provider - Empresa responsável por fornecer implementações para as interfaces do JCE criado pela SUN.
PKCS#7 - Padrão criado pela RSA Security para comportar conteúdo de textos assinados, certificados e a assinatura. Veja: RSA Security
O provider da SUN para JCE fornece uma interface e métodos simples para assinatura de texto. Vejamos um exemplo rápido para fazer a assinatura:
public byte[] assinar(PrivateKey chavePrivada,
byte[] textoParaAssinar) {
Signature signer = Signature.getIntance("SHA1withDSA");
signer.initSign(chavePrivada);
signer.update(textoParaAssinar);
byte[] dadosAssinador = signer.sign();
return dadosAssinador;
}
O problema deste tipo de assinatura é que apenas o hash é gerado e assim a assinatura não fica num padrão com o recomendado pela RSA (PKCS#7).
Para resolver este problema existe um provider que fornece um modo diferente, mas utilizando as mesma chamada internamente para fazer a assinatura no formato PKCS#7.
Este provider é o Bouncy Castle, ele contém recursos que não são fornecidos pela SUN, é livre e pode ser utilizado comercialmente.
Para a utilização de assinatura com este novo provider, o código fica um pouco diferente, veja:
public byte[] assinar(PrivateKey chavePrivada,
byte[] textoParaAssinar,
Certificate[] certificadosArray,
CRL[] crlsArray) {
Security.addProvider(new BouncyCastleProvider());
PKCS7SignedData signer = new PKCS7SignedData(chavePrivada,
certificadosArray,
crlsArray, "SHA1", "BC");
signer.update(textoParaAssinar, 0, textoParaAssinar.length);
byte[] dataSigned = signer.getEncoded();
return dataSigned;
}
Este processo gera um array de byte com a estrutura indicada pelo padrão PKCS#7, contendo os certificados, a lista de CRL, informações do assinador (SignerInfos) e o hash gerado.
Mas existe um problema...
Não é adicionado o conteúdo (textoParaAssinar) dentro do pacote da assinatura. Assim o texto que foi assinado fica de fora da estrutura e para conferir se a estrutura é válida, é necessário fornecer o texto junto com a assinatura.
Como este não é um modo ideal para enviar a assinatura apenas... Procurei o motivo de não haver o contúdo dentro da assinatura.
Então descobri que o provider Bouncy Castle ainda não implementa o conteúdo interno. Assim deixando ele em branco, veja: (extraído do fonte)
// Create the contentInfo. Empty, I didn't implement this bit
//
DERSequence contentinfo =
new DERSequence(new DERObjectIdentifier(ID_PKCS7_DATA));
Então para resolver este problema, decidi adicionar algumas linhas para incluir e recuperar o conteúdo do texto assinado.
Como o código da classe é um pouco grande (também não faz sentido colocar ele todo aqui), vou colocar apenas as parte que alterei.
Um resultado semelhante, encontrei nos arquivos da lista de discursão de desenvolvedores do Bouncy Castle. Mas pelo que vi, apenas recupera o conteúdo de um arquivo no formato PKCS#7 já existente. Ele não cria um com conteúdo.
Bom... Vamos aos códigos.
Todas as alterações foram feitas no arquivo org.bouncycastle.jce.PKCS7SignedData.java.
E para saber onde está inserido o código, coloque o comentário antes da nova entrada (// XXX Implementado por Dadario).
A primeira coisa foi adicionar uma variável global para armazenar o conteúdo, utilizei um java.util.List, mas estou pensando em trocar por armazenamento com Objetos do próprio Bouncy Castle.
***
private final String ID_DSA = "1.2.840.10040.4.1";
// XXX Implementado por Dadario
private List content = new ArrayList();
/**
* Read an existing PKCS#7 object from a DER encoded byte array using the BC
* provider.
*/
public PKCS7SignedData(byte[] in)
throws SecurityException, CRLException, InvalidKeyException,
CertificateException, NoSuchProviderException, NoSuchAlgorithmException {
***
Na leitura dos bytes do PKCS#7 foi colocado para extrair o conteúdo adicionar na variável de conteúdo
***
public PKCS7SignedData(byte[] in, String provider)
throws SecurityException, CRLException, InvalidKeyException,
CertificateException, NoSuchProviderException, NoSuchAlgorithmException {
DERInputStream din = new DERInputStream(new ByteArrayInputStream(in));
//
// Basic checks to make sure it's a PKCS#7 SignedData Object
//
DERObject pkcs;
try {
pkcs = din.readObject();
} catch (IOException e) {
throw new SecurityException("can't decode PKCS7SignedData object");
}
if (!(pkcs instanceof ASN1Sequence)) {
throw new SecurityException("Not a valid PKCS#7 object - not a sequence");
}
ContentInfo content = ContentInfo.getInstance(pkcs);
// XXX Implementado por Dadario
DEREncodable dataContent = content.getContent();
byte[] conteudo = extractContent(dataContent);
if(conteudo != null) {
for (int i = 0; i < conteudo.length; i++) {
this.content.add(new Byte(conteudo[i]));
}
}
if (!content.getContentType().equals(signedData)) {
throw new SecurityException("Not a valid PKCS#7 signed-data object"
+ " - wrong header "
+ content.getContentType().getId());
}
***
Quando recuperado os bytes com o formato PKCS#7, não era adicionado o conteúdo. Neste ponto fiz a alteração para que o conteúdo também fosse incluído:
***
public byte[] getEncoded() {
try {
digest = sig.sign();
// Create the set of Hash algorithms. I've assumed this is the
// set of all hash agorithms used to created the digest in the
// "signerInfo" structure. I may be wrong.
//
ASN1EncodableVector v = new ASN1EncodableVector();
for (Iterator i = digestalgos.iterator(); i.hasNext();) {
AlgorithmIdentifier a = new AlgorithmIdentifier(
new DERObjectIdentifier((String) i.next()), null);
v.add(a);
}
DERSet algos = new DERSet(v);
// Create the contentInfo. Empty, I didn't implement this bit
// XXX Implementado por Dadario
v = new ASN1EncodableVector();
v.add(new DERObjectIdentifier(ID_PKCS7_DATA));
DERTaggedObject contentObject = new DERTaggedObject(
true, 0, new DEROctetString(getContent()));
v.add(contentObject);
DERSequence contentinfo = new DERSequence(v);
// Chamada antiga do BouncyCastle
// DERSequence contentinfo =
new DERSequence(new DERObjectIdentifier(ID_PKCS7_DATA));
***
E no final da classe, adicione dois métodos, sendo que achei necessário ser na própria classe e um deles private só para fazer o serviço de extraír o conteúdo.
***
// XXX Implementado por Dadario
/**
* @return The content em byte format
*/
public byte[] getContent() {
byte[] conteudo = new byte[content.size()];
int i = 0;
for (Iterator iter = content.iterator(); iter.hasNext(); i++) {
Byte element = (Byte) iter.next();
conteudo[i] = element.byteValue();
}
return conteudo;
}
// XXX Implementado por Dadario
/**
* @param dataContent
*/
private byte[] extractContent(DEREncodable dataContent) {
if (dataContent instanceof ASN1Sequence) {
ASN1Sequence dataSequence = (ASN1Sequence) dataContent;
for (int i = 0; i < dataSequence.size(); i++) {
DEREncodable objectAt = dataSequence.getObjectAt(i);
byte[] retorno = extractContent(objectAt);
if(retorno != null) {
return retorno;
}
}
}
if(dataContent instanceof DERTaggedObject) {
DERTaggedObject ob = (DERTaggedObject) dataContent;
byte[] retorno = extractContent(ob.getObject());
if(retorno != null) {
return retorno;
}
}
if(dataContent instanceof DEROctetString) {
DEROctetString oc = (DEROctetString) dataContent;
return oc.getOctets();
}
return null;
}
} // Fim da classe
Se quizer o código já pronto do provider com a modificação, basta me pedir por e-mail.
Do mais... Fica minha contribuição de muito trabalho e quebra-cabeça para colocar este conteúdo dentro do padrão.
ATUALIZAÇÃO: Não tenho mais os códigos. Também é preciso verificar uma versão nova do Bouncy Castle para saber se eles já não estão tratando o conteúdo assinado.