org.codice.ddf.admin.insecure.defaults.service.KeystoreValidator.java Source code

Java tutorial

Introduction

Here is the source code for org.codice.ddf.admin.insecure.defaults.service.KeystoreValidator.java

Source

/**
 * Copyright (c) Codice Foundation
 * <p>
 * This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser
 * General Public License as published by the Free Software Foundation, either version 3 of the
 * License, or any later version.
 * <p>
 * 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
 * Lesser General Public License for more details. A copy of the GNU Lesser General Public License
 * is distributed along with this program and can be found at
 * <http://www.gnu.org/licenses/lgpl.html>.
 */
package org.codice.ddf.admin.insecure.defaults.service;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.codice.ddf.admin.insecure.defaults.service.Alert.Level;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ddf.security.SecurityConstants;

public class KeystoreValidator implements Validator {

    static final String GENERIC_INSECURE_DEFAULTS_MSG = "Unable to determine if keystore [%s] is using insecure defaults. ";

    static final String DEFAULT_KEY_PASSWORD_USED_MSG = "The key for alias [%s] in [%s] is using the default password of [%s].";

    static final String DEFAULT_KEYSTORE_PASSWORD_USED_MSG = "The keystore password for [%s] is the default password of [%s].";

    static final String INVALID_BLACKLIST_KEYSTORE_PASSWORD_MSG = "Unable to determine if keystore [%s] contains insecure default certificates. Error retrieving certificates from Blacklist keystore [%s]. %s.";

    static final String BLACKLIST_KEYSTORE_DOES_NOT_EXIST_MSG = GENERIC_INSECURE_DEFAULTS_MSG
            + "Cannot read Blacklist keystore [%s].";

    static final String CERT_CHAIN_CONTAINS_BLACKLISTED_CERT_MSG = "The certificate chain for alias [%s] in [%s] contains a blacklisted certificate with alias [%s].";

    static final String CERT_IS_BLACKLISTED_MSG = "The certificate for alias [%s] in [%s] is a blacklisted certificate with alias [%s].";

    static final String KEYSTORE_DOES_NOT_EXIST_MSG = GENERIC_INSECURE_DEFAULTS_MSG + "Cannot read keystore.";

    private static final Logger LOGGER = LoggerFactory.getLogger(KeystoreValidator.class);

    List<Alert> alerts;

    private String keystorePassword;

    private Path keystorePath;

    private String defaultKeystorePassword;

    private String blacklistKeystorePassword;

    private Path blacklistKeystorePath;

    private KeyStore blacklistKeystore;

    private String defaultKeyPassword;

    public KeystoreValidator() {
        alerts = new ArrayList<>();
    }

    public void setKeystorePath(Path path) {
        this.keystorePath = path;
    }

    public void setKeystorePassword(String password) {
        this.keystorePassword = password;
    }

    public void setDefaultKeystorePassword(String password) {
        this.defaultKeystorePassword = password;
    }

    public void setBlacklistKeystorePath(Path path) {
        this.blacklistKeystorePath = path;
    }

    public void setBlacklistKeystorePassword(String password) {
        this.blacklistKeystorePassword = password;
    }

    public void setDefaultKeyPassword(String password) {
        this.defaultKeyPassword = password;
    }

    public List<Alert> validate() {
        alerts = new ArrayList<>();

        if (isInitialized()) {
            List<Certificate> blacklistedCertificates = getBlackListedCertificates();
            KeyStore keystore = loadKeystore();
            if (keystore != null) {
                validateKeyPasswords(keystore);
                if (!blacklistedCertificates.isEmpty()) {
                    List<Certificate[]> keystoreCertificateChains = getKeystoreCertificatesChains(keystore);
                    validateKeystoreCertificates(keystore, keystoreCertificateChains, blacklistedCertificates);
                }
            }
        }

        for (Alert alert : alerts) {
            LOGGER.debug("Alert: {}, {}", alert.getLevel(), alert.getMessage());
        }

        return alerts;
    }

    private boolean isInitialized() {
        int errors = 0;

        if (keystorePath == null || (keystorePath != null && StringUtils.isBlank(keystorePath.toString()))) {
            alerts.add(new Alert(Level.WARN,
                    "Unable to determine if keystore is using insecure defaults. No keystore path provided."));
            return false;
        }

        if (blacklistKeystorePath == null
                || (blacklistKeystorePath != null && StringUtils.isBlank(blacklistKeystorePath.toString()))) {
            alerts.add(new Alert(Level.WARN, String.format(GENERIC_INSECURE_DEFAULTS_MSG, keystorePath)
                    + "No Blacklist keystore path provided."));
            return false;
        }

        if (StringUtils.isBlank(keystorePassword)) {
            errors++;
            alerts.add(new Alert(Level.WARN, String.format(GENERIC_INSECURE_DEFAULTS_MSG, keystorePath.toString())
                    + "No keystore password provided."));
        }

        if (StringUtils.isBlank(blacklistKeystorePassword)) {
            errors++;
            alerts.add(new Alert(Level.WARN,
                    String.format(GENERIC_INSECURE_DEFAULTS_MSG, keystorePath) + "Password for Blacklist keystore ["
                            + blacklistKeystorePath.toString() + "] was not provided."));
        }

        return errors == 0;
    }

    private KeyStore loadKeystore() {
        KeyStore keystore = null;

        try {
            keystore = KeyStore.getInstance(System.getProperty(SecurityConstants.KEYSTORE_TYPE));
        } catch (KeyStoreException e) {
            LOGGER.warn(String.format(GENERIC_INSECURE_DEFAULTS_MSG, keystorePath.toString()), e);
            alerts.add(new Alert(Level.WARN,
                    String.format(GENERIC_INSECURE_DEFAULTS_MSG, keystorePath) + e.getMessage() + "."));
            return null;
        }

        if (!new File(keystorePath.toString()).canRead()) {
            alerts.add(new Alert(Level.WARN, String.format(KEYSTORE_DOES_NOT_EXIST_MSG, keystorePath)));
            return null;
        }

        try (FileInputStream fis = new FileInputStream(keystorePath.toString())) {

            if (StringUtils.isNotBlank(keystorePassword)) {
                keystore.load(fis, keystorePassword.toCharArray());

                if (StringUtils.equals(keystorePassword, defaultKeystorePassword)) {
                    alerts.add(new Alert(Level.WARN, String.format(DEFAULT_KEYSTORE_PASSWORD_USED_MSG,
                            keystorePath.toString(), defaultKeystorePassword)));
                }
            }
        } catch (NoSuchAlgorithmException | CertificateException | IOException e) {
            keystore = null;
            LOGGER.warn(String.format(GENERIC_INSECURE_DEFAULTS_MSG, keystorePath), e);
            alerts.add(new Alert(Level.WARN,
                    String.format(GENERIC_INSECURE_DEFAULTS_MSG, keystorePath) + e.getMessage() + "."));
        }

        return keystore;
    }

    private void validateKeyPasswords(KeyStore keystore) {
        try {
            Enumeration<String> aliases = keystore.aliases();
            while (aliases.hasMoreElements()) {
                String alias = (String) aliases.nextElement();
                if (keystore.entryInstanceOf(alias, KeyStore.PrivateKeyEntry.class)
                        || keystore.entryInstanceOf(alias, KeyStore.SecretKeyEntry.class)) {
                    if (StringUtils.isNotBlank(defaultKeyPassword)) {
                        // See if we can access the key using the default key password. If we
                        // cannot, we
                        // know that we are using a non-default password.
                        Key key = keystore.getKey(alias, defaultKeyPassword.toCharArray());
                        if (key != null) {
                            alerts.add(new Alert(Level.WARN, String.format(DEFAULT_KEY_PASSWORD_USED_MSG, alias,
                                    keystorePath, defaultKeyPassword)));
                        }
                    } else {
                        alerts.add(new Alert(Level.WARN, String.format(GENERIC_INSECURE_DEFAULTS_MSG, keystorePath)
                                + "No key password provided."));
                    }
                }
            }
        } catch (UnrecoverableKeyException e) {
            // Key is not using default key password.
        } catch (KeyStoreException | NoSuchAlgorithmException e) {
            LOGGER.warn(String.format(GENERIC_INSECURE_DEFAULTS_MSG, keystorePath), e);
            alerts.add(new Alert(Level.WARN,
                    String.format(GENERIC_INSECURE_DEFAULTS_MSG, keystorePath) + e.getMessage() + "."));
        }
    }

    private List<Certificate> getBlackListedCertificates() {
        List<Certificate> blacklistedCertificates = new ArrayList<>();

        if (!new File(blacklistKeystorePath.toString()).canRead()) {
            alerts.add(new Alert(Level.WARN,
                    String.format(BLACKLIST_KEYSTORE_DOES_NOT_EXIST_MSG, keystorePath, blacklistKeystorePath)));
            return blacklistedCertificates;
        }

        try (FileInputStream fis = new FileInputStream(blacklistKeystorePath.toString())) {
            blacklistKeystore = KeyStore.getInstance(System.getProperty(SecurityConstants.KEYSTORE_TYPE));
            blacklistKeystore.load(fis, blacklistKeystorePassword.toCharArray());

            Enumeration<String> aliases = blacklistKeystore.aliases();
            while (aliases.hasMoreElements()) {
                String alias = (String) aliases.nextElement();
                Certificate certificate = blacklistKeystore.getCertificate(alias);
                blacklistedCertificates.add(certificate);
            }
        } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e) {
            String msg = String.format(INVALID_BLACKLIST_KEYSTORE_PASSWORD_MSG, keystorePath, blacklistKeystorePath,
                    e.getMessage());
            LOGGER.warn(msg, e);
            alerts.add(new Alert(Level.WARN, msg));
        }

        return blacklistedCertificates;
    }

    private List<Certificate[]> getKeystoreCertificatesChains(KeyStore keystore) {
        List<Certificate[]> keystoreCertificateChains = new ArrayList<>();

        try {
            Enumeration<String> aliases = keystore.aliases();
            while (aliases.hasMoreElements()) {
                String alias = (String) aliases.nextElement();
                Certificate[] certificateChain = keystore.getCertificateChain(alias);
                if (certificateChain != null) {
                    keystoreCertificateChains.add(certificateChain);
                } else {
                    Certificate certificate = keystore.getCertificate(alias);
                    keystoreCertificateChains.add(new Certificate[] { certificate });
                }
            }
        } catch (KeyStoreException e) {
            LOGGER.warn(String.format(GENERIC_INSECURE_DEFAULTS_MSG, keystorePath), e);
        }

        return keystoreCertificateChains;
    }

    private void validateKeystoreCertificates(KeyStore keystore, List<Certificate[]> keystoreCertificateChains,
            List<Certificate> blackistedCertificates) {
        for (Certificate[] certificateChain : keystoreCertificateChains) {
            // validate each certificate chain against the blacklist
            validateCertificateChain(certificateChain, blackistedCertificates, keystore);
        }
    }

    private void validateCertificateChain(Certificate[] certificateChain, List<Certificate> blacklistedCertificates,
            KeyStore keystore) {
        Certificate headCertificate = certificateChain[0];
        for (Certificate certificate : certificateChain) {
            // validate each certificate in the certificate chain against the blacklist
            validateAgainstBlacklist(headCertificate, certificate, blacklistedCertificates, keystore,
                    certificateChain.length);
        }
    }

    private void validateAgainstBlacklist(Certificate headCertificate, Certificate certificate,
            List<Certificate> blacklistedCertificates, KeyStore keystore, int certChainLength) {
        for (Certificate blackListedCertificate : blacklistedCertificates) {
            try {
                if (areCertificatesEqual(certificate, blackListedCertificate)) {
                    String msg = null;
                    if (certChainLength > 1) {
                        msg = String.format(CERT_CHAIN_CONTAINS_BLACKLISTED_CERT_MSG,
                                keystore.getCertificateAlias(headCertificate), keystorePath,
                                blacklistKeystore.getCertificateAlias(blackListedCertificate));
                    } else {
                        msg = String.format(CERT_IS_BLACKLISTED_MSG, keystore.getCertificateAlias(headCertificate),
                                keystorePath, blacklistKeystore.getCertificateAlias(blackListedCertificate));
                    }
                    alerts.add(new Alert(Level.WARN, msg));

                }
            } catch (CertificateEncodingException | KeyStoreException e) {
                LOGGER.warn(String.format(GENERIC_INSECURE_DEFAULTS_MSG, keystorePath), e);
                alerts.add(new Alert(Level.WARN,
                        String.format(GENERIC_INSECURE_DEFAULTS_MSG, keystorePath) + e.getMessage()));
            }
        }
    }

    private boolean areCertificatesEqual(Certificate certificate, Certificate blacklistedCertificate)
            throws CertificateEncodingException {
        return Arrays.equals(certificate.getEncoded(), blacklistedCertificate.getEncoded());
    }
}