package com.fp.firmas.rules.common; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.math.BigInteger; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.Security; import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Enumeration; import java.util.List; import javax.persistence.NoResultException; import javax.persistence.Query; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.ocsp.BasicOCSPResp; import org.bouncycastle.cert.ocsp.CertificateID; import org.bouncycastle.cert.ocsp.CertificateStatus; import org.bouncycastle.cert.ocsp.OCSPException; import org.bouncycastle.cert.ocsp.OCSPReq; import org.bouncycastle.cert.ocsp.OCSPReqBuilder; import org.bouncycastle.cert.ocsp.OCSPResp; import org.bouncycastle.cert.ocsp.RevokedStatus; import org.bouncycastle.cert.ocsp.SingleResp; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; import com.fp.base.persistence.TfirmDatosFirmante; import com.fp.common.logger.APPLogger; import com.fp.firmas.rules.common.enumerator.SignType; import com.fp.firmas.rules.common.exception.FirmasException; import com.fp.firmas.rules.common.resources.FirmMessages; import com.fp.persistence.commondb.PersistenceHelper; import com.fp.persistence.pfirmas.param.TfirmCertificado; import com.fp.persistence.pgeneral.gene.TgeneParameters; import com.fp.persistence.pgeneral.gene.TgeneParametersKey; import com.itextpdf.text.DocumentException; import com.itextpdf.text.Font; import com.itextpdf.text.Rectangle; import com.itextpdf.text.pdf.AcroFields; import com.itextpdf.text.pdf.PdfReader; import com.itextpdf.text.pdf.PdfSignatureAppearance; import com.itextpdf.text.pdf.PdfStamper; import com.itextpdf.text.pdf.parser.PdfReaderContentParser; import com.itextpdf.text.pdf.parser.Vector; import com.itextpdf.text.pdf.security.BouncyCastleDigest; import com.itextpdf.text.pdf.security.CertificateInfo; import com.itextpdf.text.pdf.security.CertificateInfo.X500Name; import com.itextpdf.text.pdf.security.CertificateUtil; import com.itextpdf.text.pdf.security.DigestAlgorithms; import com.itextpdf.text.pdf.security.ExternalDigest; import com.itextpdf.text.pdf.security.ExternalSignature; import com.itextpdf.text.pdf.security.MakeSignature; import com.itextpdf.text.pdf.security.MakeSignature.CryptoStandard; import com.itextpdf.text.pdf.security.OcspClient; import com.itextpdf.text.pdf.security.OcspClientBouncyCastle; import com.itextpdf.text.pdf.security.PrivateKeySignature; import com.itextpdf.text.pdf.security.TSAClient; /** * Clase que contiene metodos utilitarios para manejar certificados * * @author dcruz * */ public class CertificateUtils { /* * CONSTANTES DESIGNADAS SEGUN LA NUEVA ESTRUCTURA DEL BCE */ // OIDs de Campos del Certificado: public static final String OID_CEDULA_PASAPORTE = FirmMessages.getString("oid.cedula_pasaporte"); public static final String OID_NOMBRES = FirmMessages.getString("oid.nombres_persona"); public static final String OID_APELLIDO_1 = FirmMessages.getString("oid.apellido_persona1"); public static final String OID_APELLIDO_2 = FirmMessages.getString("oid.apellido_persona2"); public static final String OID_CARGO = FirmMessages.getString("oid.cargo"); public static final String OID_INSTITUCION = FirmMessages.getString("oid.institucion"); public static final String OID_DIRECCION = FirmMessages.getString("oid.direccion"); public static final String OID_TELEFONO = FirmMessages.getString("oid.telefono"); public static final String OID_CIUDAD = FirmMessages.getString("oid.ciudad"); public static final String OID_RAZON_SOCIAL = FirmMessages.getString("oid.razon_social"); public static final String OID_RUC = FirmMessages.getString("oid.ruc"); public static final String X509 = "X.509"; static { BouncyCastleProvider provider = new BouncyCastleProvider(); Security.addProvider(provider); } /** * Retorna el certificado del usuario contenido en la firma * * @param store el store en el que se va a buscar los certificados * @param aliases los alias que estan dentro del keystore * @return el {@link java.security.Certificate} * @throws KeyStoreException */ public synchronized static Certificate obtainCertificateInAlias(KeyStore store, Enumeration aliases) throws KeyStoreException { Certificate certs[] = null; synchronized (aliases) { int i = 0; while (aliases.hasMoreElements()) { String alias = aliases.nextElement(); //System.out.println("Se imprime los alias: " + i + " " + alias); certs = store.getCertificateChain(alias); for (int j = 0; j < certs.length; j++) { X509Certificate certificate = (X509Certificate) certs[j]; boolean[] usages = certificate.getKeyUsage(); // System.out.println("Certificado en primera posicion usado: " + j + " " + usages[0]); if (usages[0]) { return certificate; } } i++; } } return null; } /** * Se devuelve todos los datos de la firma del usuario en base al certificado enviado * * @param certificate el certificado enviado * @return Los datos del certificado de dicho usuario */ public static TfirmDatosFirmante obtainDataForCertificate(Certificate certificate) { X509Certificate signedCertificate = (X509Certificate) certificate; TfirmDatosFirmante datosFirmante = new TfirmDatosFirmante(); if (signedCertificate.getExtensionValue(OID_CEDULA_PASAPORTE) != null) { datosFirmante.setIdentificacion(new String(signedCertificate.getExtensionValue(OID_CEDULA_PASAPORTE)).trim()); } if (signedCertificate.getExtensionValue(OID_NOMBRES) != null) { datosFirmante.setNombre(new String(signedCertificate.getExtensionValue(OID_NOMBRES)).trim()); } if (signedCertificate.getExtensionValue(OID_APELLIDO_1) != null) { datosFirmante.setApellido1(new String(signedCertificate.getExtensionValue(OID_APELLIDO_1)).trim()); if (signedCertificate.getExtensionValue(OID_APELLIDO_2) != null) { datosFirmante.setApellido2(new String(signedCertificate.getExtensionValue(OID_APELLIDO_2)).trim()); } else { datosFirmante.setApellido2(""); } } if (signedCertificate.getExtensionValue(OID_TELEFONO) != null) { datosFirmante.setTelefono(new String(signedCertificate.getExtensionValue(OID_TELEFONO)).trim()); } if (signedCertificate.getExtensionValue(OID_CARGO) != null) { datosFirmante.setCargo(new String(signedCertificate.getExtensionValue(OID_CARGO)).trim()); } if (signedCertificate.getExtensionValue(OID_CIUDAD) != null) { datosFirmante.setCiudad(new String(signedCertificate.getExtensionValue(OID_CIUDAD)).trim()); } if (signedCertificate.getExtensionValue(OID_DIRECCION) != null) { datosFirmante.setDireccion(new String(signedCertificate.getExtensionValue(OID_DIRECCION)).trim()); } if (signedCertificate.getExtensionValue(OID_INSTITUCION) != null) { datosFirmante.setInstitucion(new String(signedCertificate.getExtensionValue(OID_INSTITUCION)).trim()); } if (signedCertificate.getExtensionValue(OID_RAZON_SOCIAL) != null) { datosFirmante.setInstitucion(new String(signedCertificate.getExtensionValue(OID_RAZON_SOCIAL)).trim()); } if (signedCertificate.getExtensionValue(OID_RUC) != null) { datosFirmante.setRuc(new String(signedCertificate.getExtensionValue(OID_RUC)).trim()); } datosFirmante.setFechaInicioVigencia(signedCertificate.getNotBefore()); datosFirmante.setFechaVigencia(signedCertificate.getNotAfter()); return datosFirmante; } /** * Valida que el certificado del usuario sea un certificado valido contra el servicio OCSP del proveedor * * @param certificateUser * @throws FileNotFoundException * @throws CertificateException */ public static void validateOcsCertificate(Certificate certificateUser) throws FileNotFoundException, CertificateException, FirmasException{ // System.out.println(((X509Certificate) certificateUser).getIssuerDN().getName()); CertificateFactory cf = CertificateFactory.getInstance(X509); InputStream subordStream = new FileInputStream(FirmMessages.getString("dir.ruta.base.repositorio") + "/" + FirmMessages.getString("nombre.certificado.subordinado")); X509Certificate subordCertificate = (X509Certificate) cf.generateCertificate(subordStream); check((X509Certificate) certificateUser, subordCertificate); try{ subordStream.close(); }catch(Exception e){} } /** * Valida que los certificados enviados sean validos por la entidad certificadora * * @param issuerCert certificado del usuario * @param x509Cert certificado que firmo el certificado expedido */ public static void check(X509Certificate issuerCert, X509Certificate x509Cert) throws FirmasException{ OutputStream out = null; DataOutputStream dataOut =null; InputStream in =null; try { BigInteger serialNumber = issuerCert.getSerialNumber(); X509CertificateHolder holder; try { holder = new X509CertificateHolder(x509Cert.getEncoded()); } catch (IOException e) { throw new RuntimeException(e); } CertificateID id = new CertificateID(new JcaDigestCalculatorProviderBuilder().setProvider(BouncyCastleProvider.PROVIDER_NAME).build() .get(CertificateID.HASH_SHA1), holder, serialNumber); OCSPReqBuilder ocspGen = new OCSPReqBuilder(); ocspGen.addRequest(id); OCSPReq ocspReq = ocspGen.build(); // Ir al OCSP String ocspUrl = CertificateUtil.getOCSPURL(issuerCert); if (ocspUrl == null) { APPLogger.getLogger().error("URL de OCSP is null"); return; } URL url; try { url = new URL(ocspUrl); } catch (MalformedURLException e) { throw new RuntimeException(e); } HttpURLConnection con; OCSPResp ocspResponse; try { con = (HttpURLConnection) url.openConnection(); con.setRequestProperty("Content-Type", "application/ocsp-request"); con.setRequestProperty("Accept", "application/ocsp-response"); con.setDoOutput(true); out = con.getOutputStream(); dataOut = new DataOutputStream(new BufferedOutputStream(out)); dataOut.write(ocspReq.getEncoded()); APPLogger.getLogger().info("Estado de respuesta de la peticion=" + con.getResponseCode()); /* * Se parsea la respuesta y se obtiene el estado del certificado retornado por el OCSP */ in = (InputStream) con.getContent(); byte[] resp = read(in); // Read the reponse ocspResponse = new OCSPResp(resp); } catch (IOException e) { throw new FirmasException("FIR-0001", "ERROR AL ESTABLECER CONEXI\u00d3N AL SERVICIO: {0}", url); } int status = ocspResponse.getStatus(); APPLogger.getLogger().info("status=" + status); BasicOCSPResp basicResponse = (BasicOCSPResp) ocspResponse.getResponseObject(); if (basicResponse != null) { SingleResp[] responses = basicResponse.getResponses(); SingleResp response = responses[0]; CertificateStatus certStatus = response.getCertStatus(); if (certStatus instanceof RevokedStatus) { APPLogger.getLogger().error("REVOKED"); RevokedStatus revokedStatus = (RevokedStatus) certStatus; APPLogger.getLogger().error("Reason: " + revokedStatus.getRevocationReason()); APPLogger.getLogger().error("Date: " + revokedStatus.getRevocationTime()); throw new FirmasException("FIR-0002", "ERROR, CERTIFICADO REVOCADO POR {0} CON FECHA {1}", revokedStatus.getRevocationReason(), revokedStatus.getRevocationTime()); } } } catch (OCSPException e) { throw new RuntimeException(e); } catch (CertificateEncodingException e) { throw new RuntimeException(e); } catch (OperatorCreationException e) { throw new RuntimeException(e); } finally{ try { dataOut.close(); if(in!=null) { in.close(); } out.close(); } catch (IOException e) {} } } /** * Devuelve los nombres de firmas de un documento PDF * * @param docStream el documento a verificar * @return un lista de nombres de firmas */ public static List obtainNameSigns(InputStream docStream) { PdfReader docPdf = null; try { docPdf = new PdfReader(docStream); AcroFields af = docPdf.getAcroFields(); return af.getSignatureNames(); } catch (IOException e) { APPLogger.getLogger().error("Error al obtener los nombres del documentoss"); return null; } finally { if(docPdf != null){ docPdf.close(); } } } /** * Metodo para firmar documentos * @param fileSrc * @param usuario * @param password * @param compania * @param reason * @param location * @param rectangle * @param numPage * @param nameSign * @return * @throws Exception */ public static byte[] sign(InputStream fileSrc, String usuario, String password, Integer compania, String reason, String location, Rectangle rectangle, int numPage, String nameSign, String pathCertificate) throws Exception { String path = pathCertificate; FileInputStream ceritificateInputStream = new FileInputStream(path); return sign(fileSrc, ceritificateInputStream, password, reason, location, false, true, rectangle, numPage, nameSign); } /** * Metodo para firmar documentos * @param fileSrc * @param usuario * @param password * @param compania * @param reason * @param location * @param rectangle * @param numPage * @param nameSign * @return * @throws Exception */ public static byte[] sign(InputStream fileSrc, String usuario, String password, Integer compania, String reason, String location, Rectangle rectangle, int numPage, String nameSign) throws Exception { String path = getPath(usuario, compania); FileInputStream ceritificateInputStream = new FileInputStream(path); return sign(fileSrc, ceritificateInputStream, password, reason, location, false, true, rectangle, numPage, nameSign); } /** * Método que firma un documento en base los parámetros indicados * * @param fileSrc archivo origen * @param fileCert archivo destino * @param password contrasena * @param reason razón o motivo de la firma. * @param location localización coemtario adicional de la firma. * @param withTS tiene TS estamapado de tiempo. * @param withOCSP tiene OCSP verificacion de la firma por el la entidad certificadora BCE. * @param rectangle lugar localizaci´n firma * @param numPage número pagina * @param nameSign nombre firma * @return el documento firmado */ private static byte[] sign(InputStream fileSrc, InputStream fileCert, String password, String reason, String location, boolean withTS, boolean withOCSP, Rectangle rectangle, int numPage, String nameSign) { OutputStream outputStream =null; PdfStamper stamper = null; try { PdfReader pdfReader = new PdfReader(fileSrc); PDFRendererListener listener = new PDFRendererListener(); Integer numberPage = pdfReader.getNumberOfPages(); PdfReaderContentParser parser =new PdfReaderContentParser(pdfReader); parser.processContent(numberPage, listener); List listaI = listener.getListaImagenes(); float llx = listaI.get(3).get(1);//50; float lly = listaI.get(3).get(1);//210; float urx = llx+250;//305; float ury = lly+60;//277; rectangle = new Rectangle(llx, lly, urx, ury); outputStream = new ByteArrayOutputStream(512); stamper = PdfStamper.createSignature(pdfReader, outputStream, '\0'); PdfSignatureAppearance appearance = stamper.getSignatureAppearance(); KeyStore store = KeyStore.getInstance("PKCS12"); store.load(fileCert, password.toCharArray()); String alias = store.aliases().nextElement(); ExternalSignature signature = new PrivateKeySignature((PrivateKey) store.getKey(alias, password.toCharArray()), DigestAlgorithms.SHA256, "SunRsaSign"); appearance.setLayer2Text(generateSignText(store.getCertificateChain(alias), reason, location)); appearance.setLayer2Font(new Font(Font.FontFamily.UNDEFINED, 8F)); appearance.setVisibleSignature(rectangle, numPage, nameSign); TSAClient tsc = null; if (withTS) { } OcspClient ocsp = null; if (withOCSP) { ocsp = new OcspClientBouncyCastle(); } ExternalDigest externalDigest = new BouncyCastleDigest(); MakeSignature.signDetached(appearance, externalDigest, signature, store.getCertificateChain(alias), null, ocsp, tsc, 15000, CryptoStandard.CADES); return ((ByteArrayOutputStream) outputStream).toByteArray(); } catch (IOException e) { APPLogger.getLogger().error(e); e.printStackTrace(); } catch (DocumentException e) { APPLogger.getLogger().error(e); e.printStackTrace(); } catch (KeyStoreException e) { APPLogger.getLogger().error(e); e.printStackTrace(); } catch (NoSuchAlgorithmException e) { APPLogger.getLogger().error(e); e.printStackTrace(); } catch (CertificateException e) { APPLogger.getLogger().error(e); e.printStackTrace(); } catch (UnrecoverableKeyException e) { APPLogger.getLogger().error(e); e.printStackTrace(); } catch (GeneralSecurityException e) { APPLogger.getLogger().error(e); e.printStackTrace(); } finally{ try{ fileCert.close(); fileSrc.close(); try { stamper.close(); } catch (Exception e2) { } if(outputStream!=null) { outputStream.close(); } } catch (IOException e){ APPLogger.getLogger().error(e); } } return null; } public static byte[] sign(InputStream fileSrc, String usuario, String password, String reason, String location, String nameSign, String pathCertificate, SignType signType) throws Exception { String path = pathCertificate; FileInputStream certificateInputStream = null; try{ certificateInputStream = new FileInputStream(path); return positionSign(fileSrc, certificateInputStream, password, reason, location, false, true, nameSign,signType); }finally{ if(certificateInputStream !=null){ certificateInputStream.close(); } } } /** * Método que firma un documento en base los parámetros indicados * @param fileSrc * @param fileCert * @param password * @param reason * @param location * @param withTS * @param withOCSP * @param nameSign * @param signType * @return */ private static byte[] positionSign(InputStream fileSrc, InputStream fileCert, String password, String reason, String location, boolean withTS, boolean withOCSP, String nameSign, SignType signType) { OutputStream outputStream =null; PdfStamper stamper = null; try { PdfReader pdfReader = new PdfReader(fileSrc); PDFRendererListener listener = new PDFRendererListener(); Integer numPage = pdfReader.getNumberOfPages(); PdfReaderContentParser parser =new PdfReaderContentParser(pdfReader); parser.processContent(numPage, listener); List listaI = listener.getListaImagenes(); float llx = listaI.get(listaI.size()-signType.getIndice()).get(0); float lly = listaI.get(listaI.size()-signType.getIndice()).get(1); float urx = llx+250;//305; float ury = lly+60;//277; Rectangle rectangle = new Rectangle(llx, lly, urx, ury); outputStream = new ByteArrayOutputStream(512); stamper = PdfStamper.createSignature(pdfReader, outputStream, '\0'); PdfSignatureAppearance appearance = stamper.getSignatureAppearance(); KeyStore store = KeyStore.getInstance("PKCS12"); store.load(fileCert, password.toCharArray()); String alias = store.aliases().nextElement(); ExternalSignature signature = new PrivateKeySignature((PrivateKey) store.getKey(alias, password.toCharArray()), DigestAlgorithms.SHA256, "SunRsaSign"); appearance.setLayer2Text(generateSignText(store.getCertificateChain(alias), reason, location)); appearance.setLayer2Font(new Font(Font.FontFamily.UNDEFINED, 8F)); appearance.setVisibleSignature(rectangle, numPage, nameSign); TSAClient tsc = null; if (withTS) { } OcspClient ocsp = null; if (withOCSP) { ocsp = new OcspClientBouncyCastle(); } ExternalDigest externalDigest = new BouncyCastleDigest(); MakeSignature.signDetached(appearance, externalDigest, signature, store.getCertificateChain(alias), null, ocsp, tsc, 15000, CryptoStandard.CADES); return ((ByteArrayOutputStream) outputStream).toByteArray(); } catch (IOException e) { APPLogger.getLogger().error(e); e.printStackTrace(); } catch (DocumentException e) { APPLogger.getLogger().error(e); e.printStackTrace(); } catch (KeyStoreException e) { APPLogger.getLogger().error(e); e.printStackTrace(); } catch (NoSuchAlgorithmException e) { APPLogger.getLogger().error(e); e.printStackTrace(); } catch (CertificateException e) { APPLogger.getLogger().error(e); e.printStackTrace(); } catch (UnrecoverableKeyException e) { APPLogger.getLogger().error(e); e.printStackTrace(); } catch (GeneralSecurityException e) { APPLogger.getLogger().error(e); e.printStackTrace(); } finally{ try{ fileCert.close(); fileSrc.close(); try { stamper.close(); } catch (Exception e2) { } if(outputStream!=null) { outputStream.close(); } } catch (IOException e){ APPLogger.getLogger().error(e); } } return null; } /** * Arma documento el texto a mostrar de la plantilla * @param chains certificados * @param reason razón * @param location lugar * @return una layout de la firma */ private static String generateSignText(Certificate[] chains, String reason, String location){ StringBuffer textLayout = new StringBuffer("Firmado digitalmente por: "); String name = null; X500Name x500name = CertificateInfo.getSubjectFields((X509Certificate)chains[0]); if (x500name != null) { name = x500name.getField("CN"); if (name == null) { name = x500name.getField("E"); } } if(name == null){ name = ""; } textLayout.append(name).append("\n"); if(reason != null){ textLayout.append("Raz\u00f3n: ").append(reason).append("\n"); } /* if(location != null){ textLayout.append("Lugar: ").append(location).append("\n"); } */ SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss"); textLayout.append("Fecha: ").append(format.format(new Date(System.currentTimeMillis()))); return textLayout.toString(); } private static byte[] read(InputStream in) throws IOException { ByteArrayOutputStream bos = null; try { bos = new ByteArrayOutputStream(); int next = in.read(); while (next > -1) { bos.write(next); next = in.read(); } bos.flush(); return bos.toByteArray(); } finally { if(in != null){ in.close(); } if(bos != null){ bos.close(); } } } /** * Entrega el path en el que se encuentra el certificado digital. * * @param cusuario Codigo de usuario. * @param ccompania Codigo de compania. * @return String * @throws Exception */ private static String getPath(String cusuario, Integer ccompania) throws Exception { TfirmCertificado tfirmcertificado = getTfirmCertificado(cusuario); TgeneParametersKey tgeneParametersKey = new TgeneParametersKey("PATH_CERTIFICADO_BCE", ccompania); TgeneParameters tgeneParameters = TgeneParameters.find(PersistenceHelper.getEntityManager(), tgeneParametersKey); String path = tgeneParameters.getTextvalue() + "/" + tfirmcertificado.getPk().getIdcertificado(); return path; } /** * Sentencia que entrega la defincion vigente de un certificado digital para un usuario. */ private static final String JPQL = "from TfirmCertificado where t.codigousuario = :cusuario"; /** * Entrega defincion de certificados digitales vigente. * * @param cusuario Codigo de usuario. * @return TfirmCertificado. * @throws Exception */ private static TfirmCertificado getTfirmCertificado(String cusuario) throws Exception { TfirmCertificado tfirmCertificado = null; Query qry = PersistenceHelper.getEntityManager().createQuery(JPQL); qry.setParameter("cusuario", cusuario); try { tfirmCertificado = (TfirmCertificado) qry.getSingleResult(); } catch (NoResultException e) { throw new FirmasException("FIR-0001", "PARAMETROS DEL CERTIFICADO DIGITAL NO DEFINIDO PARA EL USUARIO: {0}", cusuario); } return tfirmCertificado; } /** * Método para validar el password ingresado * @param path * @param usuario * @param password * @param compania * @return * @throws Exception */ public static boolean validatePassword(String path, String usuario, String password, Integer compania) { FileInputStream certificateInputStream =null; try { certificateInputStream = new FileInputStream(path); KeyStore ks = KeyStore.getInstance("PKCS12"); ks.load(certificateInputStream, password.toCharArray()); return true; } catch (Exception e) { return false; }finally{ try { if(certificateInputStream!=null) { certificateInputStream.close(); } } catch (IOException e) {} } } }