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.ASN1ObjectIdentifier;
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, OperatorCreationException, PKCSException {
076        try (FileReader reader = new FileReader(file)) {
077            PEMParser parser = new PEMParser(reader);
078            Object object = parser.readObject();
079            if (object instanceof ASN1ObjectIdentifier) {
080                // ignore the EC key parameters
081                object = parser.readObject();
082            }
083            
084            if (object == null) {
085                throw new IllegalArgumentException("No key found in " + file);
086            }
087            
088            BouncyCastleProvider provider = new BouncyCastleProvider();
089            JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(provider);
090
091            if (object instanceof PEMEncryptedKeyPair) {
092                // PKCS1 encrypted key
093                PEMDecryptorProvider decryptionProvider = new JcePEMDecryptorProviderBuilder().setProvider(provider).build(password.toCharArray());
094                PEMKeyPair keypair = ((PEMEncryptedKeyPair) object).decryptKeyPair(decryptionProvider);
095                return converter.getPrivateKey(keypair.getPrivateKeyInfo());
096
097            } else if (object instanceof PKCS8EncryptedPrivateKeyInfo) {
098                // PKCS8 encrypted key
099                InputDecryptorProvider decryptionProvider = new JceOpenSSLPKCS8DecryptorProviderBuilder().setProvider(provider).build(password.toCharArray());
100                PrivateKeyInfo info = ((PKCS8EncryptedPrivateKeyInfo) object).decryptPrivateKeyInfo(decryptionProvider);
101                return converter.getPrivateKey(info);
102                
103            } else if (object instanceof PEMKeyPair) {
104                // PKCS1 unencrypted key
105                return converter.getKeyPair((PEMKeyPair) object).getPrivate();
106                
107            } else if (object instanceof PrivateKeyInfo) {
108                // PKCS8 unencrypted key
109                return converter.getPrivateKey((PrivateKeyInfo) object);
110                
111            } else {
112                throw new UnsupportedOperationException("Unsupported PEM object: " + object.getClass().getSimpleName());
113            }
114        }
115    }
116}