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 (<tt>.pvk</tt>
054     * for PVK files, <tt>.pem</tt> for PEM files).
055     * 
056     * @param file
057     * @param password
058     */
059    public static PrivateKey load(File file, String password) throws KeyException {
060        try {
061            if (file.getName().endsWith(".pvk")) {
062                return PVK.parse(file, password);
063            } else if (file.getName().endsWith(".pem")) {
064                return readPrivateKeyPEM(file, password);
065            }
066        } catch (Exception e) {
067            throw new KeyException("Failed to load the private key from " + file, e);
068        }
069        
070        throw new IllegalArgumentException("Unsupported private key format (PEM or PVK file expected");
071    }
072
073    private static PrivateKey readPrivateKeyPEM(File file, String password) throws IOException, GeneralSecurityException, OperatorCreationException, PKCSException {
074        try (FileReader reader = new FileReader(file)) {
075            PEMParser parser = new PEMParser(reader);
076            Object object = parser.readObject();
077            
078            if (object == null) {
079                throw new IllegalArgumentException("No key found in " + file);
080            }
081            
082            BouncyCastleProvider provider = new BouncyCastleProvider();
083            JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(provider);
084
085            if (object instanceof PEMEncryptedKeyPair) {
086                // PKCS1 encrypted key
087                PEMDecryptorProvider decryptionProvider = new JcePEMDecryptorProviderBuilder().setProvider(provider).build(password.toCharArray());
088                PEMKeyPair keypair = ((PEMEncryptedKeyPair) object).decryptKeyPair(decryptionProvider);
089                return converter.getPrivateKey(keypair.getPrivateKeyInfo());
090
091            } else if (object instanceof PKCS8EncryptedPrivateKeyInfo) {
092                // PKCS8 encrypted key
093                InputDecryptorProvider decryptionProvider = new JceOpenSSLPKCS8DecryptorProviderBuilder().setProvider(provider).build(password.toCharArray());
094                PrivateKeyInfo info = ((PKCS8EncryptedPrivateKeyInfo) object).decryptPrivateKeyInfo(decryptionProvider);
095                return converter.getPrivateKey(info);
096                
097            } else if (object instanceof PEMKeyPair) {
098                // PKCS1 unencrypted key
099                return converter.getKeyPair((PEMKeyPair) object).getPrivate();
100                
101            } else if (object instanceof PrivateKeyInfo) {
102                // PKCS8 unencrypted key
103                return converter.getPrivateKey((PrivateKeyInfo) object);
104                
105            } else {
106                throw new UnsupportedOperationException("Unsupported PEM object: " + object.getClass().getSimpleName());
107            }
108        }
109    }
110}