com.bekwam.resignator.commands.UnsignCommand.java Source code

Java tutorial

Introduction

Here is the source code for com.bekwam.resignator.commands.UnsignCommand.java

Source

/*
 * Copyright 2015 Bekwam, Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.bekwam.resignator.commands;

import com.bekwam.resignator.ActiveConfiguration;
import com.google.common.base.Preconditions;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import javax.inject.Named;
import java.io.*;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.function.Consumer;

/**
 * Unsigns a JAR by unpacking it, stripping signature related items from the contents including META-INF/MANIFEST.MF,
 * and repacks
 *
 * @author carl_000
 */
public class UnsignCommand {

    @Inject
    ActiveConfiguration activeConfiguration;
    @Inject
    @Named("UnsignTimeout")
    Integer unsignTimeout;
    private Logger logger = LoggerFactory.getLogger(UnsignCommand.class);
    private Path tempDir = null;

    public static void main(String[] args) throws Exception {
        UnsignCommand cmd = new UnsignCommand();
        cmd.unsignJAR(
                Paths.get("C:\\Users\\carl_000\\git\\resignator-repos-1\\resignator\\mavenpomupdater-1.3.1.jar"),
                Paths.get("C:\\Users\\carl_000\\.resignator\\myoutputjar.jar"), s -> System.out.println(s));
    }

    public void unsignJAR(Path sourceJARFile, Path targetJARFile, Consumer<String> observer)
            throws CommandExecutionException {

        if (logger.isDebugEnabled()) {
            logger.debug("[UNSIGN] sourceJARFilePath={}, targetJARFilePath={}", sourceJARFile, targetJARFile);
        }

        //
        // Verify source jar
        //
        File sourceJarFile = verifySource(sourceJARFile);
        observer.accept("Verifying source JAR '" + sourceJarFile.getName() + "'");

        if (logger.isDebugEnabled()) {
            logger.debug("[UNSIGN] source jar file name={}", sourceJarFile.getName());
        }

        //
        // Create a temporary and unique folder
        //
        observer.accept("Creating temp dir");
        Path appDir = createTempDir();

        Preconditions.checkNotNull(tempDir);

        //
        // Register cleanup handler
        //
        observer.accept("Registering cleanup handler");
        registerCleanup();

        //
        // Copy jarFile to temp folder
        //
        observer.accept("Copying JAR '" + sourceJarFile.getName() + "'");
        Path workingJarFile = copyJAR(sourceJarFile);

        //
        // Unpack JAR
        //
        unJAR(workingJarFile.toString(), tempDir);
        observer.accept("Unpacking JAR '" + workingJarFile.toString() + "'");
        observer.accept("Deleting working JAR file");
        workingJarFile.toFile().delete(); // don't include for later re-jar operation

        //
        // Locate .SF files. Remove these and corresponding signature blocks like .RSA.
        //
        observer.accept("Removing old signature blocks");
        File metaInfDir = new File(tempDir.toFile(), "META-INF");
        removeSigs(metaInfDir);

        //
        // Strip the SHA.*\: entries from the MANIFEST.MF (ok to leave Name: lines?)
        //
        observer.accept("Editing MANIFEST.MF");
        editManifest(metaInfDir);

        //
        // Repack JAR
        //
        observer.accept("Repacking JAR '" + targetJARFile.toString() + "'");
        repackJAR(targetJARFile, appDir);
    }

    private File verifySource(Path sourceJARFilePath) throws CommandExecutionException {
        File sourceJarFile = sourceJARFilePath.toFile();
        if (!sourceJarFile.exists()) {
            String msg = String.format("source jar file %s does not exist", sourceJARFilePath);
            logger.error(msg);
            throw new CommandExecutionException(msg);
        }
        return sourceJarFile;
    }

    private Path createTempDir() throws CommandExecutionException {
        Path appDir = Paths.get(System.getProperty("user.home"), ".resignator");

        try {
            tempDir = Files.createTempDirectory(appDir, "");
        } catch (IOException exc) {
            String msg = String.format("can't create tempDir %s", tempDir.toString());
            logger.error(msg, exc);
            throw new CommandExecutionException(msg);
        }
        return appDir;
    }

    private void registerCleanup() {
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                if (logger.isDebugEnabled()) {
                    logger.debug("[UNSIGN] shutting down unsign jar command");
                }
                if (tempDir != null) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("[UNSIGN] tempDir is not null");
                    }
                    try {
                        Files.walkFileTree(tempDir, new SimpleFileVisitor<Path>() {
                            @Override
                            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
                                    throws IOException {
                                if (logger.isDebugEnabled()) {
                                    logger.debug("[UNSIGN] deleting file={}", file.getFileName());
                                }
                                Files.delete(file);
                                return FileVisitResult.CONTINUE;
                            }

                            @Override
                            public FileVisitResult postVisitDirectory(Path dir, IOException exc)
                                    throws IOException {
                                if (exc == null) {
                                    if (logger.isDebugEnabled()) {
                                        logger.debug("[UNSIGN] deleting dir={}", dir.getFileName());
                                    }
                                    Files.delete(dir);
                                    return FileVisitResult.CONTINUE;
                                } else {
                                    // directory iteration failed
                                    throw exc;
                                }
                            }
                        });
                    } catch (Exception exc) {
                        logger.error("error removing tempDir=" + tempDir, exc);
                    }
                }
            }
        });
    }

    private Path copyJAR(File sourceJarFile) throws CommandExecutionException {
        Path workingJarFile = Paths.get(tempDir.toString(), sourceJarFile.getName());

        try {
            Files.copy(Paths.get(sourceJarFile.getAbsolutePath()), workingJarFile);
        } catch (IOException exc) {
            String msg = String.format("can't copy %s to %s", sourceJarFile.getAbsolutePath(),
                    workingJarFile.toString());
            logger.error(msg, exc);
            throw new CommandExecutionException(msg);
        }
        return workingJarFile;
    }

    public void copyJAR(String sourceJarFileName, String targetJarFileName) throws CommandExecutionException {

        Path sourceJarFile = Paths.get(sourceJarFileName);
        Path targetJarFile = Paths.get(targetJarFileName);

        try {

            Files.copy(sourceJarFile, targetJarFile, StandardCopyOption.REPLACE_EXISTING);

        } catch (IOException exc) {
            String msg = String.format("can't copy %s to %s", sourceJarFileName, targetJarFileName);
            logger.error(msg, exc);
            throw new CommandExecutionException(msg);
        }
    }

    private void removeSigs(File metaInfDir) {
        File[] sfFiles = metaInfDir.listFiles(pathname -> StringUtils.endsWith(pathname.getName(), ".SF"));
        for (File sf : sfFiles) {
            File[] dsfFiles = metaInfDir.listFiles(
                    pathname -> StringUtils.startsWith(pathname.getName(), FilenameUtils.getBaseName(sf.getName()))
                            && !StringUtils.endsWith(pathname.getName(), ".SF"));
            for (File dsf : dsfFiles) {
                if (logger.isDebugEnabled()) {
                    logger.debug("[UNSIGN] deleting dsf={}", dsf.getName());
                }
                dsf.delete();
            }
            if (logger.isDebugEnabled()) {
                logger.debug("[UNSIGN] deleting sf={}", sf.getName());
            }
            sf.delete();
        }
    }

    private void editManifest(File metaInfDir) {
        File manifestMF = new File(metaInfDir, "MANIFEST.MF");
        File tmpManifestMF = new File(metaInfDir, "MANIFEST.MF-" + RandomStringUtils.randomAlphanumeric(10));

        try (BufferedReader br = new BufferedReader(new FileReader(manifestMF));
                BufferedWriter bw = new BufferedWriter(new FileWriter(tmpManifestMF))) {
            String line;
            while ((line = br.readLine()) != null) {
                if (line.contains("Digest:") || line.contains("Digest-Manifest:")
                        || line.contains("Digest-Manifest-Main-Attributes:")) {
                    continue;
                }
                bw.write(line);
                bw.newLine();
            }

        } catch (IOException exc) {
            logger.error("error processing MANIFEST.MF", exc);
        }

        String absPath = manifestMF.getAbsolutePath();
        boolean wasDeleted = manifestMF.delete();
        if (logger.isDebugEnabled()) {
            logger.debug("[UNSIGN] wasDeleted={}", wasDeleted);
        }
        boolean wasRenamed = tmpManifestMF.renameTo(new File(absPath)); // swap
        if (logger.isDebugEnabled()) {
            logger.debug("[UNSIGN] wasRenamed={}", wasRenamed);
        }
    }

    private void repackJAR(Path targetJARFilePath, Path appDir) throws CommandExecutionException {
        Preconditions.checkNotNull(activeConfiguration.getJarCommand());
        String[] cmdAndArgs = { activeConfiguration.getJarCommand().toString(), "cMf", targetJARFilePath.toString(),
                "-C", tempDir.toAbsolutePath().toString(), "." };

        CommandExecutor cmd = new CommandExecutor();
        cmd.exec(cmdAndArgs);
    }

    private void unJAR(String zipFile, Path tempDir) throws CommandExecutionException {

        Preconditions.checkNotNull(activeConfiguration.getJarCommand());

        String[] cmdAndArgs = { activeConfiguration.getJarCommand().toString(), "xf", zipFile };

        Path defaultWorkingDir = Paths.get(System.getProperty("user.dir"));

        CommandExecutor cmd = new CommandExecutor(unsignTimeout, tempDir, defaultWorkingDir);
        cmd.exec(cmdAndArgs);
    }
}