es.gob.afirma.envelopers.cms.CoSignerEnveloped Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of afirma-crypto-cms-enveloper Show documentation
Show all versions of afirma-crypto-cms-enveloper Show documentation
Modulo para la generacion de sobres digitales (Envolturas CMS/PKCS#7)
The newest version!
/* Copyright (C) 2011 [Gobierno de Espana]
* This file is part of "Cliente @Firma".
* "Cliente @Firma" is free software; you can redistribute it and/or modify it under the terms of:
* - the GNU General Public License as published by the Free Software Foundation;
* either version 2 of the License, or (at your option) any later version.
* - or The European Software License; either version 1.1 or (at your option) any later version.
* You may contact the copyright holder at: [email protected]
*/
package es.gob.afirma.envelopers.cms;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.spongycastle.asn1.ASN1Encodable;
import org.spongycastle.asn1.ASN1EncodableVector;
import org.spongycastle.asn1.ASN1Encoding;
import org.spongycastle.asn1.ASN1InputStream;
import org.spongycastle.asn1.ASN1ObjectIdentifier;
import org.spongycastle.asn1.ASN1OctetString;
import org.spongycastle.asn1.ASN1Primitive;
import org.spongycastle.asn1.ASN1Sequence;
import org.spongycastle.asn1.ASN1Set;
import org.spongycastle.asn1.ASN1TaggedObject;
import org.spongycastle.asn1.DEROctetString;
import org.spongycastle.asn1.DERPrintableString;
import org.spongycastle.asn1.DERSet;
import org.spongycastle.asn1.DERUTCTime;
import org.spongycastle.asn1.cms.Attribute;
import org.spongycastle.asn1.cms.AttributeTable;
import org.spongycastle.asn1.cms.CMSAttributes;
import org.spongycastle.asn1.cms.ContentInfo;
import org.spongycastle.asn1.cms.IssuerAndSerialNumber;
import org.spongycastle.asn1.cms.SignerIdentifier;
import org.spongycastle.asn1.cms.SignerInfo;
import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.spongycastle.asn1.x500.X500Name;
import org.spongycastle.asn1.x500.style.RFC4519Style;
import org.spongycastle.asn1.x509.AlgorithmIdentifier;
import org.spongycastle.asn1.x509.Certificate;
import org.spongycastle.asn1.x509.TBSCertificate;
import es.gob.afirma.core.signers.AOSignConstants;
import es.gob.afirma.signers.pkcs7.AOAlgorithmID;
import es.gob.afirma.signers.pkcs7.P7ContentSignerParameters;
import es.gob.afirma.signers.pkcs7.SigUtils;
import es.gob.afirma.signers.pkcs7.SignedAndEnvelopedData;
/** Clase que implementa la cofirma digital PKCS#7/CMS SignedAndEnvelopedData La
* implementación del código ha seguido los pasos necesarios para
* crear un mensaje SignedAndEnvelopedData pero con la peculiaridad de que es
* una Cofirma. */
final class CoSignerEnveloped {
private ASN1Set signedAttr2;
private static ASN1Set getCertificates(final SignedAndEnvelopedData sd,
final X509Certificate[] signerCertificateChain) throws CertificateEncodingException,
IOException {
ASN1Set certificates = null;
final ASN1Set certificatesSigned = sd.getCertificates();
final ASN1EncodableVector vCertsSig = new ASN1EncodableVector();
final Enumeration> certs = certificatesSigned.getObjects();
// COGEMOS LOS CERTIFICADOS EXISTENTES EN EL FICHERO
while (certs.hasMoreElements()) {
vCertsSig.add((ASN1Encodable) certs.nextElement());
}
if (signerCertificateChain.length != 0) {
final List ce = new ArrayList<>();
for (final X509Certificate element : signerCertificateChain) {
ce.add(Certificate.getInstance(ASN1Primitive.fromByteArray(element.getEncoded())));
}
certificates = SigUtils.fillRestCerts(ce, vCertsSig);
}
return certificates;
}
/** Constructor de la clase. Se crea una cofirma a partir de los datos del
* firmante, el archivo que se firma y del archivo que contiene las firmas.
* @param parameters parámetros necesarios que contienen tanto la firma del
* archivo a firmar como los datos del firmante.
* @param signerCertificateChain Cadena de certificados del firmante.
* @param sign Archivo que contiene las firmas.
* @param dataType Identifica el tipo del contenido a firmar.
* @param keyEntry Clave privada del firmante.
* @param atrib Atributos firmados opcion ales.
* @param uatrib Atributos no autenticados firmados opcionales.
* @param messageDigest Huella digital a aplicar en la firma.
* @return El archivo de firmas con la nueva firma.
* @throws java.io.IOException Si ocurre algún problema leyendo o escribiendo los
* datos
* @throws java.security.NoSuchAlgorithmException Si no se soporta alguno de los algoritmos de firma o huella
* digital
* @throws java.security.cert.CertificateException Si se produce alguna excepción con los certificados de
* firma. */
byte[] coSigner(final P7ContentSignerParameters parameters,
final X509Certificate[] signerCertificateChain,
final byte[] sign,
final String dataType,
final PrivateKeyEntry keyEntry,
final Map atrib,
final Map uatrib,
final byte[] messageDigest) throws IOException, NoSuchAlgorithmException, CertificateException {
final ASN1Sequence dsq;
try (
final ASN1InputStream is = new ASN1InputStream(sign);
) {
// LEEMOS EL FICHERO QUE NOS INTRODUCEN
dsq = (ASN1Sequence) is.readObject();
}
final Enumeration> e = dsq.getObjects();
// Elementos que contienen los elementos OID signedAndEnvelopedData
e.nextElement();
// Contenido de signedAndEnvelopedData
final ASN1TaggedObject doj = (ASN1TaggedObject) e.nextElement();
final ASN1Sequence contentSignedData = (ASN1Sequence) doj.getObject();// contenido
// del
// signedAndEnvelopedData
final SignedAndEnvelopedData sd = new SignedAndEnvelopedData(contentSignedData);
// 4. CERTIFICADOS
// obtenemos la lista de certificados
final ASN1Set certificates = getCertificates(sd, signerCertificateChain);
// buscamos que timo de algoritmo es y lo codificamos con su OID
final String signatureAlgorithm = parameters.getSignatureAlgorithm();
final String digestAlgorithm = AOSignConstants.getDigestAlgorithmName(signatureAlgorithm);
final AlgorithmIdentifier digAlgId = SigUtils.makeAlgId(AOAlgorithmID.getOID(digestAlgorithm));
// Identificador del firmante ISSUER AND SERIAL-NUMBER
final TBSCertificate tbs = TBSCertificate.getInstance(
ASN1Primitive.fromByteArray(signerCertificateChain[0].getTBSCertificate())
);
final IssuerAndSerialNumber encSid = new IssuerAndSerialNumber(
X500Name.getInstance(tbs.getIssuer()), tbs.getSerialNumber().getValue()
);
final SignerIdentifier identifier = new SignerIdentifier(encSid);
// // ATRIBUTOS
// atributos firmados
ASN1Set signedAttr = null;
if (messageDigest == null) {
signedAttr =
generateSignerInfo(digestAlgorithm, parameters.getContent(), dataType, atrib);
}
else {
signedAttr = generateSignerInfoFromHash(signerCertificateChain[0], messageDigest, dataType, atrib);
}
// atributos no firmados.
final ASN1Set unSignedAttr = EvelopUtils.generateUnsignedInfo(uatrib);
// // FIN ATRIBUTOS
// digEncryptionAlgorithm
//TODO: En RSA seria conveniente usar el OID del algoritmo de huella, y no solo el de RSA
final AlgorithmIdentifier encAlgId = SigUtils.makeAlgId(
signatureAlgorithm.contains("withRSA") ? //$NON-NLS-1$
AOAlgorithmID.getOID("RSA") : //$NON-NLS-1$
AOAlgorithmID.getOID(signatureAlgorithm)
);
// 5. SIGNERINFO
// raiz de la secuencia de SignerInfo
// Obtenemos los signerInfos del signedAndEnvelopedData
final ASN1Set signerInfosSd = sd.getSignerInfos();
// introducimos los SignerInfos Existentes
final ASN1EncodableVector signerInfos = new ASN1EncodableVector();
// introducimos el nuevo SignerInfo del firmante actual.
for (int i = 0; i < signerInfosSd.size(); i++) {
final SignerInfo si = SignerInfo.getInstance(signerInfosSd.getObjectAt(i));
signerInfos.add(si);
}
final ASN1OctetString sign2;
try {
sign2 = firma(signatureAlgorithm, keyEntry);
}
catch (final Exception ex) {
throw new IOException("Error al generar la firma: " + ex, ex); //$NON-NLS-1$
}
// Creamos los signerInfos del signedAndEnvelopedData
signerInfos.add(
new SignerInfo(
identifier, digAlgId, signedAttr, encAlgId, sign2, unSignedAttr
)
);
// construimos el Signed Data y lo devolvemos
return new ContentInfo(
PKCSObjectIdentifiers.signedAndEnvelopedData,
new SignedAndEnvelopedData(
sd.getRecipientInfos(),
sd.getDigestAlgorithms(),
sd.getEncryptedContentInfo(),
certificates,
null,
new DERSet(signerInfos)// unsignedAttr
)
).getEncoded(ASN1Encoding.DER);
}
/** Constructor de la clase. Se crea una cofirma a partir de los datos del
* firmante y el archivo que se firma.
* @param signatureAlgorithm
* Algoritmo para la firma
* @param signerCertificateChain
* Cadena de certificados para la construccion de los parametros
* de firma.
* @param sign
* Archivo que contiene las firmas.
* @param dataType
* Identifica el tipo del contenido a firmar.
* @param keyEntry
* Clave privada del firmante.
* @param atrib
* Atributos firmados adicionales.
* @param uatrib
* Atributos no firmados adicionales.
* @param messageDigest
* Hash a aplicar en la firma.
* @return El archivo de firmas con la nueva firma.
* @throws java.io.IOException
* Si ocurre algún problema leyendo o escribiendo los
* datos
* @throws java.security.cert.CertificateException
* Si se produce alguna excepción con los certificados de
* firma. */
byte[] coSigner(final String signatureAlgorithm,
final X509Certificate[] signerCertificateChain,
final byte[] sign,
final String dataType,
final PrivateKeyEntry keyEntry,
final Map atrib,
final Map uatrib,
final byte[] messageDigest) throws IOException, CertificateException {
final ASN1Sequence dsq;
try (
final ASN1InputStream is = new ASN1InputStream(sign);
) {
// LEEMOS EL FICHERO QUE NOS INTRODUCEN
dsq = (ASN1Sequence) is.readObject();
}
final Enumeration> e = dsq.getObjects();
// Elementos que contienen los elementos OID signedAndEnvelopedData
e.nextElement();
// Contenido de signedAndEnvelopedData
final ASN1TaggedObject doj = (ASN1TaggedObject) e.nextElement();
// Contenido del signedAndEnvelopedData
final ASN1Sequence contentSignedData = (ASN1Sequence) doj.getObject();
final SignedAndEnvelopedData sd = new SignedAndEnvelopedData(contentSignedData);
byte[] md = messageDigest != null ? messageDigest.clone() : null;
// 4. CERTIFICADOS
// obtenemos la lista de certificados
final ASN1Set certificates = getCertificates(sd, signerCertificateChain);
// buscamos que tipo de algoritmo es y lo codificamos con su OID
final String digestAlgorithm = AOSignConstants.getDigestAlgorithmName(signatureAlgorithm);
final AlgorithmIdentifier digAlgId =
SigUtils.makeAlgId(AOAlgorithmID.getOID(digestAlgorithm));
// Identificador del firmante ISSUER AND SERIAL-NUMBER
final TBSCertificate tbs = TBSCertificate.getInstance(
ASN1Primitive.fromByteArray(signerCertificateChain[0].getTBSCertificate())
);
final IssuerAndSerialNumber encSid = new IssuerAndSerialNumber(
X500Name.getInstance(tbs.getIssuer()), tbs.getSerialNumber().getValue()
);
final SignerIdentifier identifier = new SignerIdentifier(encSid);
// // ATRIBUTOS
// atributos firmados
ASN1Set signedAttr = null;
// atributos no firmados.
final ASN1Set unSignedAttr = EvelopUtils.generateUnsignedInfo(uatrib);
// // FIN ATRIBUTOS
// digEncryptionAlgorithm
//TODO: En RSA seria conveniente usar el OID del algoritmo de huella, y no solo el de RSA
final AlgorithmIdentifier encAlgId = SigUtils.makeAlgId(
signatureAlgorithm.contains("withRSA") ? //$NON-NLS-1$
AOAlgorithmID.getOID("RSA") : //$NON-NLS-1$
AOAlgorithmID.getOID(signatureAlgorithm)
);
// 5. SIGNERINFO
// raiz de la secuencia de SignerInfo
// Obtenemos los signerInfos del signedAndEnvelopedData
final ASN1Set signerInfosSd = sd.getSignerInfos();
// introducimos los SignerInfos Existentes
final ASN1EncodableVector signerInfos = new ASN1EncodableVector();
// introducimos el nuevo SignerInfo del firmante actual.
// Secuencia:
// 1.- Si cofirmamos sin datos en el mismo algoritmo de hash que la firma
// original sacamos el messagedigest de la firma previa.
// 2.- Si no es el mismo algoritmo, miramos si nos ha llegado un messagedigest
// como parametro del metodo, que quiere decir que se ha calculado externamente
// (en el fondo sera que no se ha sobreescrito el parametro, con lo que
// si llego != null, seguira siendo != null)
// 3.- Si no es ninguno de los dos casos, no podemos firmar
for (int i = 0; i < signerInfosSd.size(); i++) {
final SignerInfo si = SignerInfo.getInstance(signerInfosSd.getObjectAt(i));
final AlgorithmIdentifier algHash = si.getDigestAlgorithm();
// Solo si coninciden los algos puedo sacar el hash de dentro
if (algHash.getAlgorithm().toString().equals(AOAlgorithmID.getOID(digestAlgorithm))) {
final ASN1Set signedAttrib = si.getAuthenticatedAttributes();
for (int s = 0; s < signedAttrib.size(); s++) {
final ASN1Sequence elemento = (ASN1Sequence) signedAttrib.getObjectAt(s);
final ASN1ObjectIdentifier oids = (ASN1ObjectIdentifier) elemento.getObjectAt(0);
if (CMSAttributes.messageDigest.getId().toString().equals(oids.toString())) {
final DERSet derSetHash = (DERSet) elemento.getObjectAt(1);
final DEROctetString derHash = (DEROctetString) derSetHash.getObjectAt(0);
md = derHash.getOctets();
}
}
}
signerInfos.add(si);
}
// En este caso no puedo usar un hash de fuera, ya que no me han
// pasado datos ni huellas digitales, solo un fichero de firma
if (md == null) {
throw new IllegalStateException("No se puede crear la firma ya que no se ha encontrado un hash valido"); //$NON-NLS-1$
}
signedAttr =
generateSignerInfoFromHash(signerCertificateChain[0], messageDigest, dataType, atrib);
final ASN1OctetString sign2;
try {
sign2 = firma(signatureAlgorithm, keyEntry);
}
catch (final Exception ex) {
throw new IOException("Error al generar la firma: " + ex, ex); //$NON-NLS-1$
}
// Creamos los signerInfos del signedAndEnvelopedData
signerInfos.add(new SignerInfo(identifier, digAlgId, signedAttr, encAlgId, sign2, unSignedAttr// null //unsignedAttr
));
// construimos el Signed Data y lo devolvemos
return new ContentInfo(
PKCSObjectIdentifiers.signedAndEnvelopedData,
new SignedAndEnvelopedData(
sd.getRecipientInfos(),
sd.getDigestAlgorithms(),
sd.getEncryptedContentInfo(),
certificates,
null,
new DERSet(signerInfos)// unsignedAttr
)
).getEncoded(ASN1Encoding.DER);
}
/** Método que genera la parte que contiene la información del
* Usuario. Se generan los atributos que se necesitan para generar la firma.
* @param digestAlgorithm
* Algoritmo Firmado.
* @param datos
* Datos firmados.
* @param dataType
* Identifica el tipo del contenido a firmar.
* @param atrib
* Lista de atributos firmados que se insertarán dentro
* del archivo de firma.
* @return Los atributos firmados de la firma.
* @throws java.security.NoSuchAlgorithmException Si el JRE no soporta algún algoritmo necesario */
private ASN1Set generateSignerInfo(final String digestAlgorithm, final byte[] datos, final String dataType, final Map atrib) throws NoSuchAlgorithmException {
// // ATRIBUTOS
// authenticatedAttributes
final ASN1EncodableVector contexExpecific = new ASN1EncodableVector();
// tipo de contenido
contexExpecific.add(
new Attribute(
CMSAttributes.contentType,
new DERSet(
new ASN1ObjectIdentifier(dataType)
)
)
);
// fecha de firma
contexExpecific.add(new Attribute(CMSAttributes.signingTime, new DERSet(new DERUTCTime(new Date()))));
// Si nos viene el hash de fuera no lo calculamos
final byte[] md = MessageDigest.getInstance(
AOSignConstants.getDigestAlgorithmName(digestAlgorithm)).digest(datos);
// MessageDigest
contexExpecific.add(new Attribute(CMSAttributes.messageDigest, new DERSet(new DEROctetString(md.clone()))));
// agregamos la lista de atributos a mayores.
if (atrib.size() != 0) {
final Iterator> it = atrib.entrySet().iterator();
while (it.hasNext()) {
final Map.Entry e = it.next();
contexExpecific.add(new Attribute(
// el oid
new ASN1ObjectIdentifier(e.getKey().toString()),
// el array de bytes en formato string
new DERSet(new DERPrintableString(new String(e.getValue()))))
);
}
}
this.signedAttr2 = SigUtils.getAttributeSet(new AttributeTable(contexExpecific));
return SigUtils.getAttributeSet(new AttributeTable(contexExpecific));
}
/** Método que genera la parte que contiene la información del
* Usuario. Se generan los atributos que se necesitan para generar la firma.
* En este caso se introduce el hash directamente.
* @param cert Certificado necesario para la firma.
* @param datos Datos firmados.
* @param dataType Identifica el tipo del contenido a firmar.
* @param atrib Lista de atributos firmados que se insertarán dentro
* del archivo de firma.
* @return Los atributos firmados de la firma. */
private ASN1Set generateSignerInfoFromHash(final X509Certificate cert,
final byte[] datos,
final String dataType,
final Map atrib) {
// // ATRIBUTOS
// authenticatedAttributes
final ASN1EncodableVector contexExpecific = new ASN1EncodableVector();
// tipo de contenido
contexExpecific.add(
new Attribute(
CMSAttributes.contentType,
new DERSet(
new ASN1ObjectIdentifier(dataType)
)
)
);
// fecha de firma
contexExpecific.add(new Attribute(CMSAttributes.signingTime, new DERSet(new DERUTCTime(new Date()))));
// MessageDigest
contexExpecific.add(new Attribute(CMSAttributes.messageDigest, new DERSet(new DEROctetString(datos))));
// Serial Number
contexExpecific.add(new Attribute(RFC4519Style.serialNumber, new DERSet(new DERPrintableString(cert.getSerialNumber().toString()))));
// agregamos la lista de atributos a mayores.
if (atrib.size() != 0) {
final Iterator> it = atrib.entrySet().iterator();
while (it.hasNext()) {
final Map.Entry e = it.next();
contexExpecific.add(new Attribute(
// el oid
new ASN1ObjectIdentifier(e.getKey().toString()),
// el array de bytes en formato string
new DERSet(new DERPrintableString(new String(e.getValue()))))
);
}
}
this.signedAttr2 = SigUtils.getAttributeSet(new AttributeTable(contexExpecific));
return SigUtils.getAttributeSet(new AttributeTable(contexExpecific));
}
/** Realiza la firma usando los atributos del firmante.
* @param signatureAlgorithm Algoritmo para la firma.
* @param keyEntry Clave para firmar.
* @return Firma de los atributos.
* @throws InvalidKeyException Cuando hay problemas de adecuación de la clave.
* @throws NoSuchAlgorithmException Si el JRE no soporta algún algoritmo necesario
* @throws IOException Cuando hay problemas de entrada / salida.
* @throws SignatureException Cuando ocurren problemas en la firma PKCS#1 */
private ASN1OctetString firma(final String signatureAlgorithm,
final PrivateKeyEntry keyEntry) throws InvalidKeyException,
NoSuchAlgorithmException,
IOException,
SignatureException {
final Signature sig = Signature.getInstance(signatureAlgorithm);
final byte[] tmp = this.signedAttr2.getEncoded(ASN1Encoding.DER);
// Indicar clave privada para la firma
sig.initSign(keyEntry.getPrivateKey());
// Actualizamos la configuracion de firma
sig.update(tmp);
// firmamos y devolvemos.
return new DEROctetString(sig.sign());
}
}