package eu.europa.esig.dss.asic.signature; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.List; import java.util.zip.CRC32; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; import eu.europa.esig.dss.AbstractSignatureParameters; import eu.europa.esig.dss.DSSDocument; import eu.europa.esig.dss.DSSException; import eu.europa.esig.dss.DSSUnsupportedOperationException; import eu.europa.esig.dss.InMemoryDocument; import eu.europa.esig.dss.SignatureValue; import eu.europa.esig.dss.ToBeSigned; import eu.europa.esig.dss.asic.ASiCExtractResult; import eu.europa.esig.dss.asic.ASiCParameters; import eu.europa.esig.dss.asic.ASiCUtils; import eu.europa.esig.dss.asic.AbstractASiCContainerExtractor; import eu.europa.esig.dss.signature.AbstractSignatureService; import eu.europa.esig.dss.signature.MultipleDocumentsSignatureService; import eu.europa.esig.dss.utils.Utils; import eu.europa.esig.dss.validation.CertificateVerifier; public abstract class AbstractASiCSignatureService<SP extends AbstractSignatureParameters> extends AbstractSignatureService<SP> implements MultipleDocumentsSignatureService<SP> { private static final long serialVersionUID = 243114076381526665L; private static final String ZIP_ENTRY_DETACHED_FILE = "detached-file"; private static final String ZIP_ENTRY_MIMETYPE = "mimetype"; private ASiCExtractResult archiveContent = new ASiCExtractResult(); protected AbstractASiCSignatureService(CertificateVerifier certificateVerifier) { super(certificateVerifier); } protected void assertCanBeSign(List<DSSDocument> documents, final ASiCParameters asicParameters) { if (!canBeSigned(documents, asicParameters)) { // First verify if the file can be signed throw new DSSUnsupportedOperationException("You only can sign an ASiC container by using the same type of container and of signature"); } } abstract boolean canBeSigned(List<DSSDocument> documents, ASiCParameters asicParameters); @Override public ToBeSigned getDataToSign(DSSDocument toSignDocument, SP parameters) throws DSSException { return getDataToSign(Arrays.asList(toSignDocument), parameters); } @Override public DSSDocument signDocument(DSSDocument toSignDocument, SP parameters, SignatureValue signatureValue) throws DSSException { return signDocument(Arrays.asList(toSignDocument), parameters, signatureValue); } protected void extractCurrentArchive(DSSDocument archive) { AbstractASiCContainerExtractor extractor = getArchiveExtractor(archive); archiveContent = extractor.extract(); } abstract AbstractASiCContainerExtractor getArchiveExtractor(DSSDocument archive); protected List<DSSDocument> getEmbeddedSignatures() { return archiveContent.getSignatureDocuments(); } protected List<DSSDocument> getEmbeddedManifests() { return archiveContent.getManifestDocuments(); } protected List<DSSDocument> getEmbeddedSignedDocuments() { return archiveContent.getSignedDocuments(); } protected DSSDocument getEmbeddedMimetype() { return archiveContent.getMimeTypeDocument(); } protected void copyExistingArchiveWithSignatureList(DSSDocument archiveDocument, List<DSSDocument> signaturesToAdd, ByteArrayOutputStream baos) { ZipOutputStream zos = null; try { zos = new ZipOutputStream(baos); copyArchiveContentWithoutSignatures(archiveDocument, zos); storeSignatures(signaturesToAdd, zos); } catch (IOException e) { throw new DSSException("Unable to extend the ASiC container", e); } finally { Utils.closeQuietly(zos); } } private void copyArchiveContentWithoutSignatures(DSSDocument archiveDocument, ZipOutputStream zos) throws IOException { ZipInputStream zis = null; try { zis = new ZipInputStream(archiveDocument.openStream()); ZipEntry entry; while ((entry = zis.getNextEntry()) != null) { final String name = entry.getName(); final ZipEntry newEntry = new ZipEntry(name); if (!isSignatureFilename(name)) { zos.putNextEntry(newEntry); Utils.copy(zis, zos); } } } finally { Utils.closeQuietly(zis); } } abstract boolean isSignatureFilename(String name); protected DSSDocument buildASiCContainer(List<DSSDocument> documentsToBeSigned, List<DSSDocument> signatures, List<DSSDocument> manifestDocuments, ASiCParameters asicParameters) { ByteArrayOutputStream baos = null; ZipOutputStream zos = null; try { baos = new ByteArrayOutputStream(); zos = new ZipOutputStream(baos); if (ASiCUtils.isASiCE(asicParameters)) { storeASICEManifest(manifestDocuments, zos); } storeSignatures(signatures, zos); storeSignedFiles(documentsToBeSigned, zos); storeMimetype(asicParameters, zos); storeZipComment(asicParameters, zos); } catch (IOException e) { throw new DSSException("Unable to build the ASiC Container", e); } finally { Utils.closeQuietly(zos); Utils.closeQuietly(baos); } return new InMemoryDocument(baos.toByteArray(), null, ASiCUtils.getMimeType(asicParameters)); } private void storeASICEManifest(List<DSSDocument> manifestDocuments, ZipOutputStream zos) throws IOException { for (DSSDocument manifestDocument : manifestDocuments) { final ZipEntry entrySignature = new ZipEntry(manifestDocument.getName()); zos.putNextEntry(entrySignature); manifestDocument.writeTo(zos); } } abstract void storeSignatures(List<DSSDocument> signaturesToAdd, ZipOutputStream zos) throws IOException; private void storeSignedFiles(final List<DSSDocument> detachedDocuments, final ZipOutputStream zos) throws IOException { for (DSSDocument detachedDocument : detachedDocuments) { InputStream is = null; try { final String detachedDocumentName = detachedDocument.getName(); final String name = detachedDocumentName != null ? detachedDocumentName : ZIP_ENTRY_DETACHED_FILE; final ZipEntry entryDocument = new ZipEntry(name); zos.setLevel(ZipEntry.DEFLATED); zos.putNextEntry(entryDocument); is = detachedDocument.openStream(); Utils.copy(is, zos); } finally { Utils.closeQuietly(is); } } } private void storeMimetype(final ASiCParameters asicParameters, final ZipOutputStream zos) throws IOException { final byte[] mimeTypeBytes = ASiCUtils.getMimeTypeString(asicParameters).getBytes("UTF-8"); final ZipEntry entryMimetype = getZipEntryMimeType(mimeTypeBytes); zos.putNextEntry(entryMimetype); Utils.write(mimeTypeBytes, zos); } private ZipEntry getZipEntryMimeType(final byte[] mimeTypeBytes) { final ZipEntry entryMimetype = new ZipEntry(ZIP_ENTRY_MIMETYPE); entryMimetype.setMethod(ZipEntry.STORED); entryMimetype.setSize(mimeTypeBytes.length); entryMimetype.setCompressedSize(mimeTypeBytes.length); final CRC32 crc = new CRC32(); crc.update(mimeTypeBytes); entryMimetype.setCrc(crc.getValue()); return entryMimetype; } protected void storeZipComment(final ASiCParameters asicParameters, final ZipOutputStream zos) { if (asicParameters.isZipComment()) { zos.setComment(ASiCUtils.MIME_TYPE_COMMENT + ASiCUtils.getMimeTypeString(asicParameters)); } } }