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