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.FileReader;
021import java.io.IOException;
022import java.security.GeneralSecurityException;
023import java.security.KeyException;
024import java.security.PrivateKey;
025
026import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
027import org.bouncycastle.jce.provider.BouncyCastleProvider;
028import org.bouncycastle.openssl.PEMDecryptorProvider;
029import org.bouncycastle.openssl.PEMEncryptedKeyPair;
030import org.bouncycastle.openssl.PEMKeyPair;
031import org.bouncycastle.openssl.PEMParser;
032import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
033import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
034import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
035import org.bouncycastle.operator.InputDecryptorProvider;
036import org.bouncycastle.operator.OperatorCreationException;
037import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
038import org.bouncycastle.pkcs.PKCSException;
039
040/**
041 * Helper class for loading private keys (PVK or PEM, encrypted or not).
042 * 
043 * @author Emmanuel Bourg
044 * @since 2.0
045 */
046public class PrivateKeyUtils {
047
048    private PrivateKeyUtils() {
049    }
050
051    /**
052     * Load the private key from the specified file. Supported formats are PVK and PEM,
053     * encrypted or not. The type of the file is inferred from its extension (<code>.pvk</code>
054     * for PVK files, <code>.pem</code> for PEM files).
055     * 
056     * @param file     the file to load the key from
057     * @param password the password protecting the key
058     * @return the private key loaded
059     * @throws KeyException if the key cannot be loaded
060     */
061    public static PrivateKey load(File file, String password) throws KeyException {
062        try {
063            if (file.getName().endsWith(".pvk")) {
064                return PVK.parse(file, password);
065            } else if (file.getName().endsWith(".pem")) {
066                return readPrivateKeyPEM(file, password);
067            }
068        } catch (Exception e) {
069            throw new KeyException("Failed to load the private key from " + file, e);
070        }
071        
072        throw new IllegalArgumentException("Unsupported private key format (PEM or PVK file expected");
073    }
074
075    private static PrivateKey readPrivateKeyPEM(File file, String password) throws IOException, GeneralSecurityException, OperatorCreationException, PKCSException {
076        try (FileReader reader = new FileReader(file)) {
077            PEMParser parser = new PEMParser(reader);
078            Object object = parser.readObject();
079            
080            if (object == null) {
081                throw new IllegalArgumentException("No key found in " + file);
082            }
083            
084            BouncyCastleProvider provider = new BouncyCastleProvider();
085            JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(provider);
086
087            if (object instanceof PEMEncryptedKeyPair) {
088                // PKCS1 encrypted key
089                PEMDecryptorProvider decryptionProvider = new JcePEMDecryptorProviderBuilder().setProvider(provider).build(password.toCharArray());
090                PEMKeyPair keypair = ((PEMEncryptedKeyPair) object).decryptKeyPair(decryptionProvider);
091                return converter.getPrivateKey(keypair.getPrivateKeyInfo());
092
093            } else if (object instanceof PKCS8EncryptedPrivateKeyInfo) {
094                // PKCS8 encrypted key
095                InputDecryptorProvider decryptionProvider = new JceOpenSSLPKCS8DecryptorProviderBuilder().setProvider(provider).build(password.toCharArray());
096                PrivateKeyInfo info = ((PKCS8EncryptedPrivateKeyInfo) object).decryptPrivateKeyInfo(decryptionProvider);
097                return converter.getPrivateKey(info);
098                
099            } else if (object instanceof PEMKeyPair) {
100                // PKCS1 unencrypted key
101                return converter.getKeyPair((PEMKeyPair) object).getPrivate();
102                
103            } else if (object instanceof PrivateKeyInfo) {
104                // PKCS8 unencrypted key
105                return converter.getPrivateKey((PrivateKeyInfo) object);
106                
107            } else {
108                throw new UnsupportedOperationException("Unsupported PEM object: " + object.getClass().getSimpleName());
109            }
110        }
111    }
112}