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.lang.reflect.Field; 023import java.security.KeyException; 024import java.security.PrivateKey; 025import java.util.HashMap; 026import java.util.function.Function; 027 028import org.bouncycastle.asn1.ASN1ObjectIdentifier; 029import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; 030import org.bouncycastle.jce.provider.BouncyCastleProvider; 031import org.bouncycastle.openssl.PEMDecryptorProvider; 032import org.bouncycastle.openssl.PEMEncryptedKeyPair; 033import org.bouncycastle.openssl.PEMKeyPair; 034import org.bouncycastle.openssl.PEMParser; 035import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; 036import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder; 037import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; 038import org.bouncycastle.operator.InputDecryptorProvider; 039import org.bouncycastle.operator.OperatorCreationException; 040import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo; 041import org.bouncycastle.pkcs.PKCSException; 042import sun.misc.Unsafe; 043 044/** 045 * Helper class for loading private keys (PVK or PEM, encrypted or not). 046 * 047 * @author Emmanuel Bourg 048 * @since 2.0 049 */ 050public class PrivateKeyUtils { 051 052 private PrivateKeyUtils() { 053 } 054 055 /** 056 * Load the private key from the specified file. Supported formats are PVK and PEM, 057 * encrypted or not. The type of the file is inferred by trying the supported formats 058 * in sequence until one parses successfully. 059 * 060 * @param file the file to load the key from 061 * @param password the password protecting the key 062 * @return the private key loaded 063 * @throws KeyException if the key cannot be loaded 064 */ 065 public static PrivateKey load(File file, String password) throws KeyException { 066 Exception pemParseException; 067 try { 068 return readPrivateKeyPEM(file, password != null ? password.toCharArray() : null); 069 } catch (Exception e) { 070 pemParseException = e; 071 } 072 073 Exception pvkParseException; 074 try { 075 return PVK.parse(file, password); 076 } catch (Exception e) { 077 pvkParseException = e; 078 } 079 080 KeyException keyException = new KeyException("Failed to load the private key from " + file + " (valid PEM or PVK file expected)"); 081 keyException.addSuppressed(pemParseException); 082 keyException.addSuppressed(pvkParseException); 083 throw keyException; 084 } 085 086 /** 087 * Disables the signature verification of the jar containing the BouncyCastle provider. 088 */ 089 private static void disableJceSecurity() { 090 try { 091 Class<?> jceSecurityClass = Class.forName("javax.crypto.JceSecurity"); 092 Field field = jceSecurityClass.getDeclaredField("verificationResults"); 093 field.setAccessible(true); 094 095 Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); 096 unsafeField.setAccessible(true); 097 Unsafe unsafe = (Unsafe) unsafeField.get(null); 098 099 unsafe.putObject(unsafe.staticFieldBase(field), unsafe.staticFieldOffset(field), new HashMap<Object, Boolean>() { 100 @Override 101 public Boolean get(Object key) { 102 // This is not the provider you are looking for, you don't need to see its identification, move along 103 return Boolean.TRUE; 104 } 105 106 @Override 107 public Boolean computeIfAbsent(Object key, Function<? super Object, ? extends Boolean> mappingFunction) { 108 return super.computeIfAbsent(key, object -> Boolean.TRUE); 109 } 110 }); 111 } catch (Exception e) { 112 e.printStackTrace(); 113 } 114 } 115 116 private static PrivateKey readPrivateKeyPEM(File file, char[] password) throws IOException, OperatorCreationException, PKCSException { 117 try (FileReader reader = new FileReader(file)) { 118 PEMParser parser = new PEMParser(reader); 119 Object object = parser.readObject(); 120 if (object instanceof ASN1ObjectIdentifier) { 121 // ignore the EC key parameters 122 object = parser.readObject(); 123 } 124 125 if (object == null) { 126 throw new IllegalArgumentException("No key found in " + file); 127 } 128 129 if (BouncyCastleProvider.class.getName().startsWith("net.jsign")) { 130 // disable JceSecurity to allow the use of the repackaged BouncyCastle provider 131 disableJceSecurity(); 132 } 133 BouncyCastleProvider provider = new BouncyCastleProvider(); 134 JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(provider); 135 136 if (object instanceof PEMEncryptedKeyPair) { 137 // PKCS1 encrypted key 138 PEMDecryptorProvider decryptionProvider = new JcePEMDecryptorProviderBuilder().setProvider(provider).build(password); 139 PEMKeyPair keypair = ((PEMEncryptedKeyPair) object).decryptKeyPair(decryptionProvider); 140 return converter.getPrivateKey(keypair.getPrivateKeyInfo()); 141 142 } else if (object instanceof PKCS8EncryptedPrivateKeyInfo) { 143 // PKCS8 encrypted key 144 InputDecryptorProvider decryptionProvider = new JceOpenSSLPKCS8DecryptorProviderBuilder().setProvider(provider).build(password); 145 PrivateKeyInfo info = ((PKCS8EncryptedPrivateKeyInfo) object).decryptPrivateKeyInfo(decryptionProvider); 146 return converter.getPrivateKey(info); 147 148 } else if (object instanceof PEMKeyPair) { 149 // PKCS1 unencrypted key 150 return converter.getKeyPair((PEMKeyPair) object).getPrivate(); 151 152 } else if (object instanceof PrivateKeyInfo) { 153 // PKCS8 unencrypted key 154 return converter.getPrivateKey((PrivateKeyInfo) object); 155 156 } else { 157 throw new UnsupportedOperationException("Unsupported PEM object: " + object.getClass().getSimpleName()); 158 } 159 } 160 } 161}