Java tutorial
/* 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. * Date: 11/01/11 * You may contact the copyright holder at: soporte.afirma5@mpt.es */ package es.gob.afirma.signers.cms; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.Signature; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Map; import org.bouncycastle.asn1.ASN1Encodable; import org.bouncycastle.asn1.ASN1EncodableVector; import org.bouncycastle.asn1.ASN1Encoding; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.ASN1OctetString; import org.bouncycastle.asn1.ASN1Primitive; import org.bouncycastle.asn1.ASN1Set; import org.bouncycastle.asn1.ASN1UTCTime; import org.bouncycastle.asn1.BEROctetString; import org.bouncycastle.asn1.DEROctetString; import org.bouncycastle.asn1.DERPrintableString; import org.bouncycastle.asn1.DERSet; import org.bouncycastle.asn1.cms.Attribute; import org.bouncycastle.asn1.cms.AttributeTable; import org.bouncycastle.asn1.cms.CMSAttributes; import org.bouncycastle.asn1.cms.ContentInfo; import org.bouncycastle.asn1.cms.IssuerAndSerialNumber; import org.bouncycastle.asn1.cms.SignedData; import org.bouncycastle.asn1.cms.SignerIdentifier; import org.bouncycastle.asn1.cms.SignerInfo; import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x509.AlgorithmIdentifier; import org.bouncycastle.asn1.x509.Certificate; import org.bouncycastle.asn1.x509.TBSCertificateStructure; import org.bouncycastle.cms.CMSProcessable; import org.bouncycastle.cms.CMSProcessableByteArray; import es.gob.afirma.core.AOException; 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; /** Clase que implementa firma digital PKCS#7/CMS SignedData. La Estructura del * mensaje es la siguiente:<br> * * <pre> * <code> * SignedData ::= SEQUENCE { * version Version, * digestAlgorithms DigestAlgorithmIdentifiers, * contentInfo ContentInfo, * certificates [0] CertificateSet OPTIONAL, * crls [1] CertificateRevocationLists OPTIONAL, * signerInfos SignerInfos * } * * Donde signerInfo: * * SignerInfo ::= SEQUENCE { * version Version, * signerIdentifier SignerIdentifier, * digestAlgorithm DigestAlgorithmIdentifier, * authenticatedAttributes [0] Attributes OPTIONAL, * digestEncryptionAlgorithm DigestEncryptionAlgorithmIdentifier, * encryptedDigest EncryptedDigest, * unauthenticatedAttributes [1] Attributes OPTIONAL * } * </code> * </pre> * * La implementación del código ha seguido los pasos necesarios * para crear un mensaje SignedData de BouncyCastle: <a * href="http://www.bouncycastle.org/">www.bouncycastle.org</a> */ final class GenSignedData { private ASN1Set signedAttr2; /** Genera una firma digital usando el sistema conocido como * <code>SignedData</code> y que podrá ser con el contenido del fichero codificado * o sólo como referencia del fichero. * @param parameters Parámetros necesarios para obtener los datos de * <code>SignedData</code>. * @param omitContent Parámetro que indica si en la firma va el contenido del * fichero o sólo va de forma referenciada. * @param applyTimestamp Si se aplica la marca de tiempo o no. * @param dataType Identifica el tipo del contenido a firmar. * @param key Clave privada del firmante. * @param certChain Cadena de certificados del firmante. * @param atrib Atributos firmados opcionales. * @param uatrib Atributos no autenticados firmados opcionales. * @param messageDigest Huella digital a aplicar en la firma. * @return La firma generada codificada. * @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. * @throws java.io.IOException Cuando ocurre un error durante el proceso de descifrado * (formato o clave incorrecto,...) * @throws AOException Cuando ocurre un error durante el proceso de descifrado * (formato o clave incorrecto,...) */ byte[] generateSignedData(final P7ContentSignerParameters parameters, final boolean omitContent, final boolean applyTimestamp, final String dataType, final PrivateKey key, final java.security.cert.Certificate[] certChain, final Map<String, byte[]> atrib, final Map<String, byte[]> uatrib, final byte[] messageDigest) throws NoSuchAlgorithmException, CertificateException, IOException, AOException { if (parameters == null) { throw new IllegalArgumentException("Los parametros no pueden ser nulos"); //$NON-NLS-1$ } // 1. VERSION // la version se mete en el constructor del signedData y es 1 // 2. DIGESTALGORITM // buscamos que timo de algoritmo es y lo codificamos con su OID final ASN1EncodableVector digestAlgs = new ASN1EncodableVector(); final String signatureAlgorithm = parameters.getSignatureAlgorithm(); final String digestAlgorithm = AOSignConstants.getDigestAlgorithmName(signatureAlgorithm); final AlgorithmIdentifier digAlgId = SigUtils.makeAlgId(AOAlgorithmID.getOID(digestAlgorithm)); digestAlgs.add(digAlgId); // 3. CONTENTINFO // si se introduce el contenido o no ContentInfo encInfo; final ASN1ObjectIdentifier contentTypeOID = new ASN1ObjectIdentifier(dataType); // Ya que el contenido puede ser grande, lo recuperamos solo una vez byte[] content2 = null; if (!omitContent) { final ByteArrayOutputStream bOut = new ByteArrayOutputStream(); content2 = parameters.getContent(); final CMSProcessable msg = new CMSProcessableByteArray(content2); try { msg.write(bOut); } catch (final Exception ex) { throw new IOException("Error en la escritura del procesable CMS: " + ex, ex); //$NON-NLS-1$ } encInfo = new ContentInfo(contentTypeOID, new BEROctetString(bOut.toByteArray())); } else { encInfo = new ContentInfo(contentTypeOID, null); } // 4. CERTIFICADOS // obtenemos la lista de certificados ASN1Set certificates = null; if (certChain.length != 0) { final List<ASN1Encodable> ce = new ArrayList<ASN1Encodable>(); for (final java.security.cert.Certificate element : certChain) { ce.add(Certificate.getInstance(ASN1Primitive.fromByteArray(element.getEncoded()))); } certificates = SigUtils.createBerSetFromList(ce); } final ASN1Set certrevlist = null; // 5. SIGNERINFO // raiz de la secuencia de SignerInfo final ASN1EncodableVector signerInfos = new ASN1EncodableVector(); final TBSCertificateStructure tbs = TBSCertificateStructure .getInstance(ASN1Primitive.fromByteArray(((X509Certificate) certChain[0]).getTBSCertificate())); final IssuerAndSerialNumber encSid = new IssuerAndSerialNumber(X500Name.getInstance(tbs.getIssuer()), tbs.getSerialNumber().getValue()); final SignerIdentifier identifier = new SignerIdentifier(encSid); // // ATRIBUTOS // ATRIBUTOS FIRMADOS final ASN1Set signedAttr = generateSignedInfo(digestAlgorithm, content2 != null ? content2 : parameters.getContent(), dataType, applyTimestamp, atrib, messageDigest); // ATRIBUTOS NO FIRMADOS. final ASN1Set unSignedAttr = generateUnsignedInfo(uatrib); // // FIN ATRIBUTOS // digEncryptionAlgorithm final AlgorithmIdentifier encAlgId; try { encAlgId = SigUtils.makeAlgId(AOAlgorithmID.getOID("RSA")); //$NON-NLS-1$ } catch (final Exception e) { throw new IOException("Error de codificacion: " + e, e); //$NON-NLS-1$ } final ASN1OctetString sign2 = firma(signatureAlgorithm, key); signerInfos.add(new SignerInfo(identifier, digAlgId, signedAttr, encAlgId, sign2, unSignedAttr// null //unsignedAttr )); // construimos el Signed Data y lo devolvemos return new ContentInfo(PKCSObjectIdentifiers.signedData, new SignedData(new DERSet(digestAlgs), encInfo, certificates, certrevlist, new DERSet(signerInfos))) .getEncoded(ASN1Encoding.DER); } /** Genera los atributos firmados. * @param digestAlgorithm Algoritmo Firmado. * @param datos Datos firmados. * @param dataType Identifica el tipo del contenido a firmar. * @param timestamp Introducir el momento de la firma como atributo firmado (no confundir con un sello de tiempo reconocido) * @param atrib Lista de atributos firmados que se insertarán dentro * del archivo de firma. * @param messageDigest Huella digital. * @return Los atributos firmados de la firma. * @throws java.security.NoSuchAlgorithmException Cuando el JRE no soporta algún algoritmo necesario. */ private ASN1Set generateSignedInfo(final String digestAlgorithm, final byte[] datos, final String dataType, final boolean timestamp, final Map<String, byte[]> atrib, final byte[] messageDigest) 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 if (timestamp) { contexExpecific.add(new Attribute(CMSAttributes.signingTime, new DERSet(new ASN1UTCTime(new Date())))); } // Si nos viene el hash de fuera no lo calculamos final byte[] md; if (messageDigest == null || messageDigest.length < 1) { md = MessageDigest.getInstance(AOSignConstants.getDigestAlgorithmName(digestAlgorithm)).digest(datos); } else { md = messageDigest; } // 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<Map.Entry<String, byte[]>> it = atrib.entrySet().iterator(); while (it.hasNext()) { final Map.Entry<String, byte[]> e = it.next(); contexExpecific.add(new Attribute(new ASN1ObjectIdentifier(e.getKey()), // el oid new DERSet(new DERPrintableString(new String(e.getValue()))) // el array de bytes en formato string )); } } 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 no firmados. * @param uatrib * Lista de atributos no firmados que se insertarán dentro * del archivo de firma. * @return Los atributos no firmados de la firma. */ private static ASN1Set generateUnsignedInfo(final Map<String, byte[]> uatrib) { // // ATRIBUTOS // authenticatedAttributes final ASN1EncodableVector contexExpecific = new ASN1EncodableVector(); // agregamos la lista de atributos a mayores. if (uatrib.size() != 0) { final Iterator<Map.Entry<String, byte[]>> it = uatrib.entrySet().iterator(); while (it.hasNext()) { final Map.Entry<String, byte[]> 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()))))); } } else { return null; } return SigUtils.getAttributeSet(new AttributeTable(contexExpecific)); } /** Realiza la firma usando los atributos del firmante. * @param signatureAlgorithm Algoritmo para la firma. * @param key Clave para firmar. * @return Firma de los atributos. * @throws AOException Si ocurre cualquier problema durante el proceso */ private ASN1OctetString firma(final String signatureAlgorithm, final PrivateKey key) throws AOException { final Signature sig; try { sig = Signature.getInstance(signatureAlgorithm); } catch (final Exception e) { throw new AOException("Error obteniendo la clase de firma para el algoritmo " + signatureAlgorithm, e); //$NON-NLS-1$ } // Indicar clave privada para la firma try { sig.initSign(key); } catch (final Exception e) { throw new AOException("Error al inicializar la firma con la clave privada", e); //$NON-NLS-1$ } // Actualizamos la configuracion de firma try { sig.update(this.signedAttr2.getEncoded(ASN1Encoding.DER)); } catch (final Exception e) { throw new AOException("Error al configurar la informacion de firma o al obtener los atributos a firmar", //$NON-NLS-1$ e); } // firmamos. final byte[] realSig; try { realSig = sig.sign(); } catch (final Exception e) { throw new AOException("Error durante el proceso de firma", e); //$NON-NLS-1$ } return new DEROctetString(realSig); } }