All Downloads are FREE. Search and download functionalities are using the official Maven repository.

alpine.security.crypto.KeyManager Maven / Gradle / Ivy

There is a newer version: 3.1.1
Show newest version
/*
 * This file is part of Alpine.
 *
 * 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.
 *
 * SPDX-License-Identifier: Apache-2.0
 * Copyright (c) Steve Springett. All Rights Reserved.
 */
package alpine.security.crypto;

import alpine.Config;
import alpine.common.logging.Logger;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamConstants;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * Class that manages Alpine-generated default private, public, and secret keys.
 *
 * @author Steve Springett
 * @since 1.0.0
 */
public final class KeyManager {

    /**
     * Defines the type of key.
     */
    enum KeyType {
        PRIVATE,
        PUBLIC,
        SECRET
    }

    private static final Logger LOGGER = Logger.getLogger(KeyManager.class);
    private static final KeyManager INSTANCE = new KeyManager();
    private KeyPair keyPair;
    private SecretKey secretKey;

    /**
     * Private constructor.
     */
    private KeyManager() {
        initialize();
    }

    /**
     * Returns an INSTANCE of the KeyManager.
     *
     * @return an instance of the KeyManager
     * @since 1.0.0
     */
    public static KeyManager getInstance() {
        return INSTANCE;
    }

    /**
     * Initializes the KeyManager
     */
    private void initialize() {
        createKeysIfNotExist();
        if (keyPair == null) {
            try {
                loadKeyPair();
            } catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) {
                LOGGER.error("An error occurred loading key pair");
                LOGGER.error(e.getMessage());
            }
        }
        if (secretKey == null) {
            try {
                if (secretKeyHasOldFormat()) {
                    loadSecretKey();
                } else {
                    loadEncodedSecretKey();
                }
            } catch (IOException | ClassNotFoundException e) {
                LOGGER.error("An error occurred loading secret key");
                LOGGER.error(e.getMessage());
            }
        }
    }

    /**
     * Checks if the keys exists. If not, they will be created.
     */
    private void createKeysIfNotExist() {
        if (!keyPairExists()) {
            try {
                final KeyPair keyPair = generateKeyPair();
                save(keyPair);
            } catch (NoSuchAlgorithmException e) {
                LOGGER.error("An error occurred generating new keypair");
                LOGGER.error(e.getMessage());
            } catch (IOException e) {
                LOGGER.error("An error occurred saving newly generated keypair");
                LOGGER.error(e.getMessage());
            }
        }
        if (!secretKeyExists()) {
            try {
                final SecretKey secretKey = generateSecretKey();
                saveEncoded(secretKey);
            } catch (NoSuchAlgorithmException e) {
                LOGGER.error("An error occurred generating new secret key");
                LOGGER.error(e.getMessage());
            } catch (IOException e) {
                LOGGER.error("An error occurred saving newly generated secret key");
                LOGGER.error(e.getMessage());
            }
        }
    }

    /**
     * Generates a key pair.
     *
     * @return a KeyPair (public / private keys)
     * @throws NoSuchAlgorithmException if the algorithm cannot be found
     * @since 1.0.0
     */
    public KeyPair generateKeyPair() throws NoSuchAlgorithmException {
        LOGGER.info("Generating new key pair");
        final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
        final SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
        keyGen.initialize(4096, random);
        return this.keyPair = keyGen.generateKeyPair();
    }

    /**
     * Generates a secret key.
     *
     * @return a SecretKey
     * @throws NoSuchAlgorithmException if the algorithm cannot be found
     * @since 1.0.0
     */
    public SecretKey generateSecretKey() throws NoSuchAlgorithmException {
        final KeyGenerator keyGen = KeyGenerator.getInstance("AES");
        final SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
        keyGen.init(256, random);
        return this.secretKey = keyGen.generateKey();
    }

    /**
     * Retrieves the path where the keys should be stored.
     * @param keyType the type of key
     * @return a File representing the path to the key
     */
    private File getKeyPath(final KeyType keyType) {
        if (keyType == KeyType.SECRET) {
            final String secretKeyPath = Config.getInstance().getProperty(Config.AlpineKey.SECRET_KEY_PATH);
            if (secretKeyPath != null) {
                return Paths.get(secretKeyPath).toFile();
            }
        } else if (keyType == KeyType.PRIVATE) {
            final String privateKeyPath = Config.getInstance().getProperty(Config.AlpineKey.PRIVATE_KEY_PATH);
            if (privateKeyPath != null) {
                return Paths.get(privateKeyPath).toFile();
            }
        } else if (keyType == KeyType.PUBLIC) {
            final String publicKeyPath = Config.getInstance().getProperty(Config.AlpineKey.PUBLIC_KEY_PATH);
            if (publicKeyPath != null) {
                return Paths.get(publicKeyPath).toFile();
            }
        }
        return new File(Config.getInstance().getDataDirectorty()
                + File.separator
                + "keys" + File.separator
                + keyType.name().toLowerCase() + ".key");
    }

    /**
     * Given the type of key, this method will return the File path to that key.
     * @param key the type of key
     * @return a File representing the path to the key
     */
    private File getKeyPath(final Key key) {
        KeyType keyType = null;
        if (key instanceof PrivateKey) {
            keyType = KeyType.PRIVATE;
        } else if (key instanceof PublicKey) {
            keyType = KeyType.PUBLIC;
        } else if (key instanceof SecretKey) {
            keyType = KeyType.SECRET;
        }
        return getKeyPath(keyType);
    }

    /**
     * Saves a key pair.
     *
     * @param keyPair the key pair to save
     * @throws IOException if the files cannot be written
     * @since 1.0.0
     */
    public void save(final KeyPair keyPair) throws IOException {
        LOGGER.info("Saving key pair");
        final PrivateKey privateKey = keyPair.getPrivate();
        final PublicKey publicKey = keyPair.getPublic();

        // Store Public Key
        final File publicKeyFile = getKeyPath(publicKey);
        publicKeyFile.getParentFile().mkdirs(); // make directories if they do not exist
        final X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKey.getEncoded());
        try (OutputStream fos = Files.newOutputStream(publicKeyFile.toPath())) {
            fos.write(x509EncodedKeySpec.getEncoded());
        }

        // Store Private Key.
        final File privateKeyFile = getKeyPath(privateKey);
        privateKeyFile.getParentFile().mkdirs(); // make directories if they do not exist
        final PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKey.getEncoded());
        try (OutputStream fos = Files.newOutputStream(privateKeyFile.toPath())) {
            fos.write(pkcs8EncodedKeySpec.getEncoded());
        }
    }

    /**
     * Saves a secret key.
     *
     * @param key the SecretKey to save
     * @throws IOException if the file cannot be written
     * @since 1.0.0
     * @deprecated Use {@link #saveEncoded(SecretKey)} instead
     */
    @Deprecated(forRemoval = true)
    public void save(final SecretKey key) throws IOException {
        final File keyFile = getKeyPath(key);
        keyFile.getParentFile().mkdirs(); // make directories if they do not exist
        try (OutputStream fos = Files.newOutputStream(keyFile.toPath());
             ObjectOutputStream oout = new ObjectOutputStream(fos)) {
            oout.writeObject(key);
        }
    }

    /**
     * Saves a secret key in encoded format.
     *
     * @param key the SecretKey to save
     * @throws IOException if the file cannot be written
     * @since 2.2.0
     */
    public void saveEncoded(final SecretKey key) throws IOException {
        final File keyFile = getKeyPath(key);
        keyFile.getParentFile().mkdirs(); // make directories if they do not exist
        try (OutputStream fos = Files.newOutputStream(keyFile.toPath())) {
            fos.write(key.getEncoded());
        }
    }

    /**
     * Loads a key pair.
     * @return a KeyPair
     * @throws IOException if the file cannot be read
     * @throws NoSuchAlgorithmException if the algorithm cannot be found
     * @throws InvalidKeySpecException if the algorithm's key spec is incorrect
     */
    private KeyPair loadKeyPair() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        // Read Private Key
        final File filePrivateKey = getKeyPath(KeyType.PRIVATE);

        // Read Public Key
        final File filePublicKey = getKeyPath(KeyType.PUBLIC);

        byte[] encodedPrivateKey;
        byte[] encodedPublicKey;

        try (InputStream pvtfis = Files.newInputStream(filePrivateKey.toPath());
             InputStream pubfis = Files.newInputStream(filePublicKey.toPath())) {

            encodedPrivateKey = new byte[(int) filePrivateKey.length()];
            pvtfis.read(encodedPrivateKey);

            encodedPublicKey = new byte[(int) filePublicKey.length()];
            pubfis.read(encodedPublicKey);
        }

        // Generate KeyPair
        final KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        final X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(encodedPublicKey);
        final PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
        final PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(encodedPrivateKey);
        final PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);

        return this.keyPair = new KeyPair(publicKey, privateKey);
    }

    /**
     * Loads the secret key.
     * @return a SecretKey
     * @throws IOException            if the file cannot be read
     * @throws ClassNotFoundException if deserialization of the SecretKey fails
     * @deprecated Use {@link #loadEncodedSecretKey()}
     */
    @Deprecated(forRemoval = true)
    SecretKey loadSecretKey() throws IOException, ClassNotFoundException {
        final File file = getKeyPath(KeyType.SECRET);
        SecretKey key;
        try (InputStream fis = Files.newInputStream(file.toPath());
             ObjectInputStream ois = new ObjectInputStream(fis)) {

            key = (SecretKey) ois.readObject();
        }
        return this.secretKey = key;
    }

    /**
     * Loads the encoded secret key.
     *
     * @return a SecretKey
     * @throws IOException if the file cannot be read
     * @since 2.2.0
     */
    SecretKey loadEncodedSecretKey() throws IOException {
        final File file = getKeyPath(KeyType.SECRET);
        try (InputStream fis = Files.newInputStream(file.toPath())) {
            final byte[] encodedKey = fis.readAllBytes();
            return this.secretKey = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        }
    }

    /**
     * Checks to see if the key pair exists. Both (public and private) need to exist to return true.
     *
     * @return true if keypair exists, false if not
     * @since 1.0.0
     */
    public boolean keyPairExists() {
        return getKeyPath(KeyType.PUBLIC).exists() && getKeyPath(KeyType.PRIVATE).exists();
    }

    /**
     * Checks to see if the secret key exists.
     *
     * @return true if secret key exists, false if not
     * @since 1.0.0
     */
    public boolean secretKeyExists() {
        return getKeyPath(KeyType.SECRET).exists();
    }

    /**
     * Checks if the secret key was stored in the old Java Object Serialization format.
     *
     * @return {@code true} when the old format is detected, otherwise {@code false}
     * @throws IOException When reading the secret key file could not be read
     * @since 2.2.0
     */
    boolean secretKeyHasOldFormat() throws IOException {
        try (final InputStream fis = Files.newInputStream(getKeyPath(KeyType.SECRET).toPath())) {
            return ByteBuffer.wrap(fis.readNBytes(2)).getShort() == ObjectStreamConstants.STREAM_MAGIC;
        }
    }

    /**
     * Returns the keypair.
     *
     * @return the KeyPair
     * @since 1.0.0
     */
    public KeyPair getKeyPair() {
        return keyPair;
    }

    /**
     * Returns only the public key.
     *
     * @return the PublicKey
     * @since 1.0.0
     */
    public PublicKey getPublicKey() {
        return (keyPair != null) ? keyPair.getPublic() : null;
    }

    /**
     * Returns only the private key.
     *
     * @return the PrivateKey
     * @since 1.0.0
     */
    public PrivateKey getPrivateKey() {
        return (keyPair != null) ? keyPair.getPrivate() : null;
    }

    /**
     * Returns the secret key.
     *
     * @return the SecretKey
     * @since 1.0.0
     */
    public SecretKey getSecretKey() {
        return secretKey;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy