001/**
002 * Copyright 2017 Emmanuel Bourg
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package net.jsign;
018
019import java.io.File;
020import java.io.FileInputStream;
021import java.io.IOException;
022import java.nio.ByteBuffer;
023import java.security.KeyStore;
024import java.security.KeyStoreException;
025import java.security.Provider;
026
027/**
028 * Helper class for loading KeyStores (JKS, JCEKS, PKCS#12 or PKCS#11).
029 * 
030 * @author Emmanuel Bourg
031 * @since 2.0
032 */
033public class KeyStoreUtils {
034
035    private KeyStoreUtils() {
036    }
037
038    /**
039     * Load the keystore from the specified file.
040     * 
041     * @param keystore   the file containing the keystore
042     * @param storetype  the type of the keystore (either JKS, JCEKS, PKCS12 or PKCS11).
043     *                   If null the type is inferred from the extension of the file (.p12 or .pfx for PKCS#12 keystores)
044     * @param storepass  The password of the keystore
045     * @param provider   The security provider used to load the keystore (must be specified for PKCS#11 keystores)
046     * @return the keystore loaded
047     * @throws KeyStoreException thrown if the keystore cannot be loaded
048     */
049    public static KeyStore load(File keystore, String storetype, String storepass, Provider provider) throws KeyStoreException {
050        return load(keystore != null ? keystore.getPath() : null, storetype, storepass, provider);
051    }
052
053    /**
054     * Load the keystore from the specified path.
055     *
056     * @param keystore   the path to the keystore
057     * @param storetype  the type of the keystore (either JKS, JCEKS, PKCS12 or PKCS11).
058     *                   If null the type is inferred from the extension of the file (.p12 or .pfx for PKCS#12 keystores)
059     * @param storepass  The password of the keystore
060     * @param provider   The security provider used to load the keystore (must be specified for PKCS#11 keystores)
061     * @return the keystore loaded
062     * @throws KeyStoreException thrown if the keystore cannot be loaded
063     */
064    public static KeyStore load(String keystore, String storetype, String storepass, Provider provider) throws KeyStoreException {
065        if (keystore != null && storetype == null) {
066            storetype = getType(keystore);
067        }
068        
069        KeyStore ks;
070        try {
071            if (provider != null) {
072                ks = KeyStore.getInstance(storetype, provider);
073            } else {
074                ks = KeyStore.getInstance(storetype);
075            }
076        } catch (KeyStoreException e) {
077            throw new KeyStoreException("keystore type '" + storetype + "' is not supported", e);
078        }
079
080        boolean filebased = "JKS".equals(storetype) || "JCEKS".equals(storetype) || "PKCS12".equals(storetype);
081        if (filebased && (keystore == null || !new File(keystore).exists())) {
082            throw new KeyStoreException("The keystore " + keystore + " couldn't be found");
083        }
084        
085        try {
086            try (FileInputStream in = !filebased ? null : new FileInputStream(keystore)) {
087                ks.load(in, storepass != null ? storepass.toCharArray() : null);
088            }
089        } catch (Exception e) {
090            throw new KeyStoreException("Unable to load the keystore " + keystore, e);
091        }
092        
093        return ks;
094    }
095
096    /**
097     * Guess the type of the keystore from the header or the extension of the file.
098     *
099     * @param keystore   the path to the keystore
100     */
101    static String getType(String keystore) throws KeyStoreException {
102        if (keystore == null) {
103            return null;
104        }
105
106        // guess the type of the keystore from the header of the file
107        File file = new File(keystore);
108        if (file.exists()) {
109            try (FileInputStream in = new FileInputStream(file)) {
110                byte[] header = new byte[4];
111                in.read(header);
112                ByteBuffer buffer = ByteBuffer.wrap(header);
113                if (buffer.get(0) == 0x30) {
114                    return "PKCS12";
115                } else if ((buffer.getInt(0) & 0xFFFFFFFFL) == 0xCECECECEL) {
116                    return "JCEKS";
117                } else if ((buffer.getInt(0) & 0xFFFFFFFFL) == 0xFEEDFEEDL) {
118                    return "JKS";
119                }
120            } catch (IOException e) {
121                throw new KeyStoreException("Unable to load the keystore " + keystore, e);
122            }
123        }
124
125        // guess the type of the keystore from the extension of the file
126        String filename = keystore.toLowerCase();
127        if (filename.endsWith(".p12") || filename.endsWith(".pfx")) {
128            return "PKCS12";
129        } else if (filename.endsWith(".jceks")) {
130            return "JCEKS";
131        } else if (filename.endsWith(".jks")) {
132            return "JKS";
133        } else {
134            return null;
135        }
136    }
137}