Main.java

/*
 * Copyright (C) 2019 sw4j.org
 *
 * This program 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 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.sw4j.tool.p12breaker;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.sw4j.tool.p12breaker.breaker.P12Breaker;
import org.sw4j.tool.p12breaker.permutation.PermutationConfiguration;
import org.sw4j.tool.p12breaker.permutation.PermutationGenerator;

/**
 *
 * @author Uwe Plonus
 */
public class Main {

    private final Logger logger = Logger.getLogger(Main.class.getName());

    public static void main(final String... args) {
        new Main().run(args);
    }

    public void run(final String... args) {
        CommandLine cl = parseCommandLine(args);

        if (cl.hasOption("h")) {
            System.exit(0);
        }

        PermutationConfiguration.Builder builder = PermutationConfiguration.builder();
        if (cl.hasOption("u")) {
            builder.permutationString(cl.getOptionValue("u"));
        } else if (cl.hasOption("c")) {
            String characters = cl.getOptionValue("c");
            builder.useDigits(characters.contains("d"));
            builder.useUpperCaseCharacters(characters.contains("A"));
            builder.useLowerCaseCharacters(characters.contains("a"));
            builder.useSpecialCharacters(characters.contains("s"));
            builder.useBlank(characters.contains("b"));
        }
        if (cl.hasOption("n")) {
            try {
                builder.minimumLength(Integer.parseInt(cl.getOptionValue("n")));
            } catch (NumberFormatException nfex) {
                logger.log(Level.INFO, "Unable to parse minimum length, using default");
            }
        }
        if (cl.hasOption("x")) {
            try {
                builder.maximumLength(Integer.parseInt(cl.getOptionValue("x")));
            } catch (NumberFormatException nfex) {
                logger.log(Level.INFO, "Unable to parse maximum length, using default");
            }
        }
        int threads = Runtime.getRuntime().availableProcessors();
        if (cl.hasOption("t")) {
            try {
                threads = Integer.parseInt(cl.getOptionValue("t"));
            } catch (NumberFormatException nfex) {
                logger.log(Level.INFO, "Unable to parse threads, using default");
            }
        }

        BlockingQueue<String> permutationQueue = new SynchronousQueue<>();
        PermutationConfiguration configuration = builder.build();
        PermutationGenerator generator = new PermutationGenerator(configuration, permutationQueue);

        List<String> files = cl.getArgList();
        for (String fileName: files) {
            File file = new File(fileName);
            if (Files.isReadable(file.toPath())) {

                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                try {
                    Files.copy(file.toPath(), baos);
                    byte[] p12data = baos.toByteArray();

                    final ExecutorService breakerService = new ThreadPoolExecutor(1, threads, 10, TimeUnit.SECONDS,
                            new LinkedBlockingQueue<>(threads * 10), new ThreadPoolExecutor.CallerRunsPolicy());
                    final ExecutorService permutationService = Executors.newSingleThreadExecutor();
                    final ExecutorService distributorService = Executors.newSingleThreadExecutor();
                    final ExecutorService stopService = Executors.newSingleThreadExecutor();

                    P12Breaker.PasswordResultListener listener = (String password) -> {
                        stopService.submit(() -> {
                            System.out.println(String.format("\n%s: %s", fileName, password));
                            permutationService.shutdownNow();
                            distributorService.shutdown();
                            breakerService.shutdown();
                        });
                    };

                    AtomicInteger counter = new AtomicInteger();
                    AtomicInteger countPasswords = new AtomicInteger();
                    ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(0);
                    scheduler.scheduleAtFixedRate(() -> {
                        int count = counter.getAndSet(0);
                        System.out.print(String.format("Passwords per second: %d  \r", count));
                    }, 1, 1, TimeUnit.SECONDS);

                    distributorService.submit(() -> {
                        while (true) {
                            try {
                                ByteArrayInputStream bais = new ByteArrayInputStream(p12data);
                                final String permutation = permutationQueue.take();
                                breakerService.submit(() -> {
                                    P12Breaker breaker = new P12Breaker(bais,
                                            permutation.toCharArray(), listener);
                                    breaker.testPassword();
                                    counter.incrementAndGet();
                                    int passwordsTested = countPasswords.incrementAndGet();
                                    if (passwordsTested % 10000 == 0) {
                                        System.out.print(String.format("\nPassword tested: %s\n", permutation));
                                    }
                                });
                            } catch (InterruptedException ex) {
                                logger.info("Distributor interrupted. Terminating.");
                            }
                        }
                    });

                    permutationService.submit(() -> {
                        try {
                            generator.createPermutations();
                        } catch (InterruptedException ex) {
                            logger.info("Distributor interrupted. Terminating.");
                        }
                    });
                } catch (IOException ioex) {
                    logger.info(String.format("File %s not readable. Ignoring.", fileName));
                }

            } else {
                logger.info(String.format("File %s not readable. Ignoring.", fileName));
            }
        }

    }

    private CommandLine parseCommandLine(final String... args) {
        Options clOptions = new Options();

        clOptions.addOption(Option
                .builder("h")
                .longOpt("help")
                .hasArg(false)
                .required(false)
                .desc("Show this help.")
                .build()
        );

        clOptions.addOption(Option
                .builder("t")
                .longOpt("threads")
                .hasArg(true)
                .required(false)
                .argName("threads")
                .desc("The number of threads to use (defaults to the number of processors).")
                .build()
        );

        clOptions.addOption(Option
                .builder("n")
                .longOpt("min")
                .hasArg(true)
                .required(false)
                .argName("min-length")
                .desc("The minimum number of characters in the password.")
                .build()
        );

        clOptions.addOption(Option
                .builder("x")
                .longOpt("max")
                .hasArg(true)
                .required(false)
                .argName("max-length")
                .desc("The maximum number of characters in the password.")
                .build()
        );

        clOptions.addOption(Option
                .builder("c")
                .longOpt("char")
                .hasArg(true)
                .required(false)
                .argName("characters")
                .desc("The characters to use:\n" +
                        "    d: digits (default)\n" +
                        "    A: upper case characters (default)\n" +
                        "    a: lower case characters (default)\n" +
                        "    s: special characters (<>-_.:,;#'+*~?\\()[]/{}&%$\"!)\n" +
                        "    b: blank")
                .build()
        );

        clOptions.addOption(Option
                .builder("u")
                .longOpt("user")
                .hasArg(true)
                .required(false)
                .argName("user characters")
                .desc("Use user defined characters")
                .build()
        );

        CommandLineParser parser = new DefaultParser();
        CommandLine cl = new CommandLine.Builder().build();
        try {
            cl = parser.parse(clOptions, args, true);
        } catch (ParseException pex) {
            logger.log(Level.WARNING, "Problems while parsing the command line", pex);
        }

        if (cl.hasOption("h")) {
            HelpFormatter formatter = new HelpFormatter();
            formatter.printHelp("p12breaker [OPTION]... [FILE]...", clOptions);
        }

        return cl;
    }

}