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.envelopers.cms; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateEncodingException; 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 javax.crypto.BadPaddingException; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import org.bouncycastle.asn1.ASN1Encodable; import org.bouncycastle.asn1.ASN1EncodableVector; import org.bouncycastle.asn1.ASN1Encoding; import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.ASN1Set; import org.bouncycastle.asn1.ASN1TaggedObject; import org.bouncycastle.asn1.BEROctetString; import org.bouncycastle.asn1.DEROctetString; import org.bouncycastle.asn1.DERPrintableString; import org.bouncycastle.asn1.DERSet; import org.bouncycastle.asn1.DERTaggedObject; import org.bouncycastle.asn1.DERUTCTime; import org.bouncycastle.asn1.cms.Attribute; import org.bouncycastle.asn1.cms.AttributeTable; import org.bouncycastle.asn1.cms.AuthenticatedData; import org.bouncycastle.asn1.cms.CMSAttributes; import org.bouncycastle.asn1.cms.ContentInfo; import org.bouncycastle.asn1.cms.OriginatorInfo; import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; import org.bouncycastle.asn1.x500.style.RFC4519Style; import org.bouncycastle.asn1.x509.AlgorithmIdentifier; import org.bouncycastle.cms.CMSException; import org.bouncycastle.cms.CMSProcessable; import org.bouncycastle.cms.CMSProcessableByteArray; import es.gob.afirma.core.ciphers.AOCipherConfig; import es.gob.afirma.core.signers.AOSignConstants; import es.gob.afirma.signers.pkcs7.AOAlgorithmID; import es.gob.afirma.signers.pkcs7.P7ContentSignerParameters; /** Clase que implementa firma digital PKCS#7/CMS AuthenticatedData. La * Estructura del mensaje es la siguiente:<br> * * <pre> * <code> * * id-ct-authData OBJECT IDENTIFIER ::= { iso(1) member-body(2) * us(840) rsadsi(113549) pkcs(1) pkcs-9(9) smime(16) * ct(1) 2 } * * The authenticated-data content type shall have ASN.1 type * AuthenticatedData: * * AuthenticatedData ::= SEQUENCE { * version CMSVersion, * originatorInfo [0] IMPLICIT OriginatorInfo OPTIONAL, * recipientInfos RecipientInfos, * macAlgorithm MessageAuthenticationCodeAlgorithm, * digestAlgorithm [1] DigestAlgorithmIdentifier OPTIONAL, * encapContentInfo EncapsulatedContentInfo, * authAttrs [2] IMPLICIT AuthAttributes OPTIONAL, * mac MessageAuthenticationCode, * unauthAttrs [3] IMPLICIT UnauthAttributes OPTIONAL } * * AuthAttributes ::= SET SIZE (1..MAX) OF Attribute * * UnauthAttributes ::= SET SIZE (1..MAX) OF Attribute * * MessageAuthenticationCode ::= OCTET STRING * * </code> * </pre> * * La implementación del código ha seguido los pasos necesarios * para crear un mensaje AuthenticatedData de BouncyCastle: <a * href="http://www.bouncycastle.org/">www.bouncycastle.org</a> */ final class CMSAuthenticatedData { private CMSAuthenticatedData() { // No instanciable } /** Genera una estructura PKCS#7 <code>AuthenticatedData</code>. * @param parameters Parámetros necesarios que contienen tanto la firma del * archivo a firmar como los datos del firmante. * @param signerCertChain Cadena de certificados del firmante. * @param autenticationAlgorithm Algoritmo para los codigos de autenticación MAC * @param config Configuración del algoritmo para firmar * @param certDest Certificado del destino al cual va dirigido la firma. * @param dataType Identifica el tipo del contenido a firmar. * @param applyTimestamp Si se aplica el Timestamp o no. * @param atrib Atributos firmados opcionales. * @param uatrib Atributos no autenticados firmados opcionales. * @param keySize Tamaño de la clave AES. * @return Firma de tipo AuthenticatedData. * @throws IOException Si ocurre algún problema leyendo o escribiendo los * datos * @throws CertificateEncodingException Si se produce alguna excepción con los certificados de * firma. * @throws NoSuchAlgorithmException Si no se encuentra un algoritmo válido. * @throws InvalidKeyException Cuando hay problemas de adecuación de la clave. * @throws BadPaddingException Cuando hay problemas con un relleno de datos. * @throws IllegalBlockSizeException Cuando hay problemas internos con los tamaños de bloque de cifrado. * @throws InvalidAlgorithmParameterException Si no se soporta un parámetro necesario para un algoritmo. * @throws NoSuchPaddingException Cuando no se soporta un tipo de relleno necesario. */ static byte[] genAuthenticatedData(final P7ContentSignerParameters parameters, final X509Certificate[] signerCertChain, final String autenticationAlgorithm, final AOCipherConfig config, final X509Certificate[] certDest, final String dataType, final boolean applyTimestamp, final Map<String, byte[]> atrib, final Map<String, byte[]> uatrib, final Integer keySize) throws IOException, CertificateEncodingException, NoSuchAlgorithmException, InvalidKeyException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { final SecretKey cipherKey = Utils.initEnvelopedData(config, keySize); // Ya que el contenido puede ser grande, lo recuperamos solo una vez final byte[] content2 = parameters.getContent(); // 1. ORIGINATORINFO // obtenemos la lista de certificados final ASN1Set certificates = Utils.fetchCertificatesList(signerCertChain); ASN1Set certrevlist = null; OriginatorInfo origInfo = null; if (signerCertChain.length != 0) { // introducimos una lista vacia en los CRL ya que no podemos // modificar el codigo de bc. final List<ASN1Encodable> crl = new ArrayList<ASN1Encodable>(); certrevlist = SigUtils.createBerSetFromList(crl); origInfo = new OriginatorInfo(certificates, certrevlist); } // 2. RECIPIENTINFOS final Info infos = Utils.initVariables(content2, config, certDest, cipherKey); // 3. MACALGORITHM final AlgorithmIdentifier macAlgorithm = SigUtils.makeAlgId(config.getAlgorithm().getOid()); // 4. DIGESTALGORITMIDENTIFIER final String digestAlgorithm = AOSignConstants.getDigestAlgorithmName(parameters.getSignatureAlgorithm()); final AlgorithmIdentifier digAlgId = SigUtils.makeAlgId(AOAlgorithmID.getOID(digestAlgorithm)); // 5. ENCAPSULATEDCONTENTINFO // si se introduce el contenido o no ContentInfo encInfo = null; final ASN1ObjectIdentifier contentTypeOID = new ASN1ObjectIdentifier(dataType); final ByteArrayOutputStream bOut = new ByteArrayOutputStream(); final CMSProcessable msg = new CMSProcessableByteArray(content2); try { msg.write(bOut); } catch (final CMSException ex) { throw new IOException("Error en la escritura del procesable CMS: " + ex, ex); //$NON-NLS-1$ } encInfo = new ContentInfo(contentTypeOID, new BEROctetString(bOut.toByteArray())); // 6. ATRIBUTOS FIRMADOS ASN1Set authAttr = null; authAttr = generateSignedAtt(signerCertChain[0], digestAlgorithm, content2, dataType, applyTimestamp, atrib); // 7. MAC final byte[] mac = Utils.genMac(autenticationAlgorithm, authAttr.getEncoded(ASN1Encoding.DER), cipherKey); // 8. ATRIBUTOS NO FIRMADOS. ASN1Set unAuthAttr = null; unAuthAttr = Utils.generateUnsignedAtt(uatrib); // construimos el Authenticated data y lo devolvemos return new ContentInfo(PKCSObjectIdentifiers.id_ct_authData, new AuthenticatedData(origInfo, // OriginatorInfo new DERSet(infos.getRecipientInfos()), // ASN1Set macAlgorithm, // macAlgorithm digAlgId, // AlgorithmIdentifier encInfo, // ContentInfo authAttr, // ASN1Set new DEROctetString(mac), // ASN1OctetString unAuthAttr // ASN1Set )).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 cert * Certificado necesario para la firma. * @param digestAlgorithm * Algoritmo Firmado. * @param datos * Datos firmados. * @param datatype * Identifica el tipo del contenido a firmar. * @param timestamp * Introducir TimeStaming * @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 no se encuentra un algoritmo válido. */ private static ASN1Set generateSignedAtt(final X509Certificate cert, final String digestAlgorithm, final byte[] datos, final String datatype, final boolean timestamp, final Map<String, byte[]> 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 if (timestamp) { 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())))); // Serial Number // comentar lo de abajo para version del rfc 3852 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<Map.Entry<String, byte[]>> it = atrib.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()))))); } } return SigUtils.getAttributeSet(new AttributeTable(contexExpecific)); } /*************************************************************************/ /**************** Metodos auxiliares de cifrado **************************/ /*************************************************************************/ /** Método que inserta remitentes en el "OriginatorInfo" de un sobre * de tipo AuthenticatedData. * @param data * fichero que tiene la firma. * @param signerCertificateChain * Cadena de certificados a agregar. * @return La nueva firma AuthenticatedData con los remitentes que * tenía (si los tuviera) con la cadena de certificados * nueva. * @throws IOException Si hay errores de lectura o escritura de datos * @throws CertificateEncodingException Si el certificado del remitente es invalido */ static byte[] addOriginatorInfo(final InputStream data, final X509Certificate[] signerCertificateChain) throws IOException, CertificateEncodingException { final ASN1InputStream is = new ASN1InputStream(data); // LEEMOS EL FICHERO QUE NOS INTRODUCEN final ASN1Sequence dsq = (ASN1Sequence) is.readObject(); is.close(); final Enumeration<?> e = dsq.getObjects(); // Elementos que contienen los elementos OID Data final ASN1ObjectIdentifier doi = (ASN1ObjectIdentifier) e.nextElement(); if (doi.equals(PKCSObjectIdentifiers.id_ct_authData)) { // Contenido de Data final ASN1TaggedObject doj = (ASN1TaggedObject) e.nextElement(); final AuthenticatedData auth = AuthenticatedData.getInstance(doj.getObject()); final AlgorithmIdentifier digAlg = extractAOIfromAuth((ASN1Sequence) doj.getObject()); // Obtenemos los originatorInfo OriginatorInfo origInfo = auth.getOriginatorInfo(); ASN1Set certs = null; if (origInfo != null) { certs = origInfo.getCertificates(); } final OriginatorInfo origInfoChecked = Utils.checkCertificates(signerCertificateChain, certs); if (origInfoChecked != null) { origInfo = origInfoChecked; } // Se crea un nuevo AuthenticatedData a partir de los datos // anteriores con los nuevos originantes. return new ContentInfo(PKCSObjectIdentifiers.id_ct_authData, new AuthenticatedData(origInfo, // OriginatorInfo auth.getRecipientInfos(), // ASN1Set auth.getMacAlgorithm(), // macAlgorithm digAlg, // AlgorithmIdentifier se les ha olvidado a BC implementar el getDigestAlgorithm auth.getEncapsulatedContentInfo(), // ContentInfo auth.getAuthAttrs(), // ASN1Set auth.getMac(), // ASN1OctetString auth.getUnauthAttrs() // ASN1Set )).getEncoded(ASN1Encoding.DER); } return null; } private static AlgorithmIdentifier extractAOIfromAuth(final ASN1Sequence auth) { final Enumeration<?> e = auth.getObjects(); // Elemento 0 : version e.nextElement(); // Elemento 1 : OriginatorInfo e.nextElement(); // Elemento 2 : RecipientsInfo e.nextElement(); // Elemento 3 : MAC Algorithm e.nextElement(); // Elemento 4 : DigestAlgorithm final DERTaggedObject alg = (DERTaggedObject) e.nextElement(); final ASN1Sequence content = (ASN1Sequence) alg.getObject(); final AlgorithmIdentifier aoi = AlgorithmIdentifier.getInstance(content); return aoi; } }