commit abcd0954643eddbf826d96291d44a143038ab750 Author: Martin Balao Date: Sun Oct 10 18:14:01 2021 +0100 RH1991003: Enable the import of plain keys into the NSS software token. This can be individually disabled using -Dcom.redhat.fips.plainKeySupport=false diff --git openjdk.orig/src/java.base/share/classes/java/security/Security.java openjdk/src/java.base/share/classes/java/security/Security.java index ce32c939253..dc7020ce668 100644 --- openjdk.orig/src/java.base/share/classes/java/security/Security.java +++ openjdk/src/java.base/share/classes/java/security/Security.java @@ -82,6 +82,10 @@ public final class Security { public boolean isSystemFipsEnabled() { return SystemConfigurator.isSystemFipsEnabled(); } + @Override + public boolean isPlainKeySupportEnabled() { + return SystemConfigurator.isPlainKeySupportEnabled(); + } }); // doPrivileged here because there are multiple diff --git openjdk.orig/src/java.base/share/classes/java/security/SystemConfigurator.java openjdk/src/java.base/share/classes/java/security/SystemConfigurator.java index 6aa1419dfd0..ecab722848e 100644 --- openjdk.orig/src/java.base/share/classes/java/security/SystemConfigurator.java +++ openjdk/src/java.base/share/classes/java/security/SystemConfigurator.java @@ -55,6 +55,7 @@ final class SystemConfigurator { CRYPTO_POLICIES_BASE_DIR + "/back-ends/java.config"; private static boolean systemFipsEnabled = false; + private static boolean plainKeySupportEnabled = false; private static final String SYSTEMCONF_NATIVE_LIB = "systemconf"; @@ -150,6 +151,16 @@ final class SystemConfigurator { } loadedProps = true; systemFipsEnabled = true; + String plainKeySupport = System.getProperty("com.redhat.fips.plainKeySupport", + "true"); + plainKeySupportEnabled = !"false".equals(plainKeySupport); + if (sdebug != null) { + if (plainKeySupportEnabled) { + sdebug.println("FIPS support enabled with plain key support"); + } else { + sdebug.println("FIPS support enabled without plain key support"); + } + } } } catch (Exception e) { if (sdebug != null) { @@ -177,6 +188,19 @@ final class SystemConfigurator { return systemFipsEnabled; } + /** + * Returns {@code true} if system FIPS alignment is enabled + * and plain key support is allowed. Plain key support is + * enabled by default but can be disabled with + * {@code -Dcom.redhat.fips.plainKeySupport=false}. + * + * @return a boolean indicating whether plain key support + * should be enabled. + */ + static boolean isPlainKeySupportEnabled() { + return plainKeySupportEnabled; + } + /* * OpenJDK FIPS mode will be enabled only if the com.redhat.fips * system property is true (default) and the system is in FIPS mode. diff --git openjdk.orig/src/java.base/share/classes/jdk/internal/access/JavaSecuritySystemConfiguratorAccess.java openjdk/src/java.base/share/classes/jdk/internal/access/JavaSecuritySystemConfiguratorAccess.java index a31e93ec02e..3f3caac64dc 100644 --- openjdk.orig/src/java.base/share/classes/jdk/internal/access/JavaSecuritySystemConfiguratorAccess.java +++ openjdk/src/java.base/share/classes/jdk/internal/access/JavaSecuritySystemConfiguratorAccess.java @@ -27,4 +27,5 @@ package jdk.internal.access; public interface JavaSecuritySystemConfiguratorAccess { boolean isSystemFipsEnabled(); + boolean isPlainKeySupportEnabled(); } diff --git openjdk.orig/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/FIPSKeyImporter.java openjdk/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/FIPSKeyImporter.java new file mode 100644 index 00000000000..bee3a1e1537 --- /dev/null +++ openjdk/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/FIPSKeyImporter.java @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2021, Red Hat, Inc. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.security.pkcs11; + +import java.math.BigInteger; +import java.security.KeyFactory; +import java.security.Provider; +import java.security.Security; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.locks.ReentrantLock; + +import javax.crypto.Cipher; +import javax.crypto.spec.DHPrivateKeySpec; +import javax.crypto.spec.IvParameterSpec; + +import sun.security.jca.JCAUtil; +import sun.security.pkcs11.TemplateManager; +import sun.security.pkcs11.wrapper.CK_ATTRIBUTE; +import sun.security.pkcs11.wrapper.CK_MECHANISM; +import static sun.security.pkcs11.wrapper.PKCS11Constants.*; +import static sun.security.pkcs11.wrapper.PKCS11Exception.*; +import sun.security.pkcs11.wrapper.PKCS11Exception; +import sun.security.rsa.RSAUtil.KeyType; +import sun.security.util.Debug; +import sun.security.util.ECUtil; + +final class FIPSKeyImporter { + + private static final Debug debug = + Debug.getInstance("sunpkcs11"); + + private static P11Key importerKey = null; + private static final ReentrantLock importerKeyLock = new ReentrantLock(); + private static CK_MECHANISM importerKeyMechanism = null; + private static Cipher importerCipher = null; + + private static Provider sunECProvider = null; + private static final ReentrantLock sunECProviderLock = new ReentrantLock(); + + private static KeyFactory DHKF = null; + private static final ReentrantLock DHKFLock = new ReentrantLock(); + + static Long importKey(SunPKCS11 sunPKCS11, long hSession, CK_ATTRIBUTE[] attributes) + throws PKCS11Exception { + long keyID = -1; + Token token = sunPKCS11.getToken(); + if (debug != null) { + debug.println("Private or Secret key will be imported in" + + " system FIPS mode."); + } + if (importerKey == null) { + importerKeyLock.lock(); + try { + if (importerKey == null) { + if (importerKeyMechanism == null) { + // Importer Key creation has not been tried yet. Try it. + createImporterKey(token); + } + if (importerKey == null || importerCipher == null) { + if (debug != null) { + debug.println("Importer Key could not be" + + " generated."); + } + throw new PKCS11Exception(CKR_GENERAL_ERROR); + } + if (debug != null) { + debug.println("Importer Key successfully" + + " generated."); + } + } + } finally { + importerKeyLock.unlock(); + } + } + long importerKeyID = importerKey.getKeyID(); + try { + byte[] keyBytes = null; + byte[] encKeyBytes = null; + long keyClass = 0L; + long keyType = 0L; + Map attrsMap = new HashMap<>(); + for (CK_ATTRIBUTE attr : attributes) { + if (attr.type == CKA_CLASS) { + keyClass = attr.getLong(); + } else if (attr.type == CKA_KEY_TYPE) { + keyType = attr.getLong(); + } + attrsMap.put(attr.type, attr); + } + BigInteger v = null; + if (keyClass == CKO_PRIVATE_KEY) { + if (keyType == CKK_RSA) { + if (debug != null) { + debug.println("Importing an RSA private key..."); + } + keyBytes = sun.security.rsa.RSAPrivateCrtKeyImpl.newKey( + KeyType.RSA, + null, + ((v = attrsMap.get(CKA_MODULUS).getBigInteger()) != null) + ? v : BigInteger.ZERO, + ((v = attrsMap.get(CKA_PUBLIC_EXPONENT).getBigInteger()) != null) + ? v : BigInteger.ZERO, + ((v = attrsMap.get(CKA_PRIVATE_EXPONENT).getBigInteger()) != null) + ? v : BigInteger.ZERO, + ((v = attrsMap.get(CKA_PRIME_1).getBigInteger()) != null) + ? v : BigInteger.ZERO, + ((v = attrsMap.get(CKA_PRIME_2).getBigInteger()) != null) + ? v : BigInteger.ZERO, + ((v = attrsMap.get(CKA_EXPONENT_1).getBigInteger()) != null) + ? v : BigInteger.ZERO, + ((v = attrsMap.get(CKA_EXPONENT_2).getBigInteger()) != null) + ? v : BigInteger.ZERO, + ((v = attrsMap.get(CKA_COEFFICIENT).getBigInteger()) != null) + ? v : BigInteger.ZERO + ).getEncoded(); + } else if (keyType == CKK_DSA) { + if (debug != null) { + debug.println("Importing a DSA private key..."); + } + keyBytes = new sun.security.provider.DSAPrivateKey( + ((v = attrsMap.get(CKA_VALUE).getBigInteger()) != null) + ? v : BigInteger.ZERO, + ((v = attrsMap.get(CKA_PRIME).getBigInteger()) != null) + ? v : BigInteger.ZERO, + ((v = attrsMap.get(CKA_SUBPRIME).getBigInteger()) != null) + ? v : BigInteger.ZERO, + ((v = attrsMap.get(CKA_BASE).getBigInteger()) != null) + ? v : BigInteger.ZERO + ).getEncoded(); + if (token.config.getNssNetscapeDbWorkaround() && + attrsMap.get(CKA_NETSCAPE_DB) == null) { + attrsMap.put(CKA_NETSCAPE_DB, + new CK_ATTRIBUTE(CKA_NETSCAPE_DB, BigInteger.ZERO)); + } + } else if (keyType == CKK_EC) { + if (debug != null) { + debug.println("Importing an EC private key..."); + } + if (sunECProvider == null) { + sunECProviderLock.lock(); + try { + if (sunECProvider == null) { + sunECProvider = Security.getProvider("SunEC"); + } + } finally { + sunECProviderLock.unlock(); + } + } + keyBytes = ECUtil.generateECPrivateKey( + ((v = attrsMap.get(CKA_VALUE).getBigInteger()) != null) + ? v : BigInteger.ZERO, + ECUtil.getECParameterSpec(sunECProvider, + attrsMap.get(CKA_EC_PARAMS).getByteArray())) + .getEncoded(); + if (token.config.getNssNetscapeDbWorkaround() && + attrsMap.get(CKA_NETSCAPE_DB) == null) { + attrsMap.put(CKA_NETSCAPE_DB, + new CK_ATTRIBUTE(CKA_NETSCAPE_DB, BigInteger.ZERO)); + } + } else if (keyType == CKK_DH) { + if (debug != null) { + debug.println("Importing a Diffie-Hellman private key..."); + } + if (DHKF == null) { + DHKFLock.lock(); + try { + if (DHKF == null) { + DHKF = KeyFactory.getInstance( + "DH", P11Util.getSunJceProvider()); + } + } finally { + DHKFLock.unlock(); + } + } + DHPrivateKeySpec spec = new DHPrivateKeySpec + (((v = attrsMap.get(CKA_VALUE).getBigInteger()) != null) + ? v : BigInteger.ZERO, + ((v = attrsMap.get(CKA_PRIME).getBigInteger()) != null) + ? v : BigInteger.ZERO, + ((v = attrsMap.get(CKA_BASE).getBigInteger()) != null) + ? v : BigInteger.ZERO); + keyBytes = DHKF.generatePrivate(spec).getEncoded(); + if (token.config.getNssNetscapeDbWorkaround() && + attrsMap.get(CKA_NETSCAPE_DB) == null) { + attrsMap.put(CKA_NETSCAPE_DB, + new CK_ATTRIBUTE(CKA_NETSCAPE_DB, BigInteger.ZERO)); + } + } else { + if (debug != null) { + debug.println("Unrecognized private key type."); + } + throw new PKCS11Exception(CKR_GENERAL_ERROR); + } + } else if (keyClass == CKO_SECRET_KEY) { + if (debug != null) { + debug.println("Importing a secret key..."); + } + keyBytes = attrsMap.get(CKA_VALUE).getByteArray(); + } + if (keyBytes == null || keyBytes.length == 0) { + if (debug != null) { + debug.println("Private or secret key plain bytes could" + + " not be obtained. Import failed."); + } + throw new PKCS11Exception(CKR_GENERAL_ERROR); + } + importerCipher.init(Cipher.ENCRYPT_MODE, importerKey, + new IvParameterSpec((byte[])importerKeyMechanism.pParameter), + null); + attributes = new CK_ATTRIBUTE[attrsMap.size()]; + attrsMap.values().toArray(attributes); + encKeyBytes = importerCipher.doFinal(keyBytes); + attributes = token.getAttributes(TemplateManager.O_IMPORT, + keyClass, keyType, attributes); + keyID = token.p11.C_UnwrapKey(hSession, + importerKeyMechanism, importerKeyID, encKeyBytes, attributes); + if (debug != null) { + debug.println("Imported key ID: " + keyID); + } + } catch (Throwable t) { + throw new PKCS11Exception(CKR_GENERAL_ERROR); + } finally { + importerKey.releaseKeyID(); + } + return Long.valueOf(keyID); + } + + private static void createImporterKey(Token token) { + if (debug != null) { + debug.println("Generating Importer Key..."); + } + byte[] iv = new byte[16]; + JCAUtil.getSecureRandom().nextBytes(iv); + importerKeyMechanism = new CK_MECHANISM(CKM_AES_CBC_PAD, iv); + try { + CK_ATTRIBUTE[] attributes = token.getAttributes(TemplateManager.O_GENERATE, + CKO_SECRET_KEY, CKK_AES, new CK_ATTRIBUTE[] { + new CK_ATTRIBUTE(CKA_CLASS, CKO_SECRET_KEY), + new CK_ATTRIBUTE(CKA_VALUE_LEN, 256 >> 3)}); + Session s = null; + try { + s = token.getObjSession(); + long keyID = token.p11.C_GenerateKey( + s.id(), new CK_MECHANISM(CKM_AES_KEY_GEN), + attributes); + if (debug != null) { + debug.println("Importer Key ID: " + keyID); + } + importerKey = (P11Key)P11Key.secretKey(s, keyID, "AES", + 256 >> 3, null); + } catch (PKCS11Exception e) { + // best effort + } finally { + token.releaseSession(s); + } + if (importerKey != null) { + importerCipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + } + } catch (Throwable t) { + // best effort + importerKey = null; + importerCipher = null; + // importerKeyMechanism value is kept initialized to indicate that + // Importer Key creation has been tried and failed. + } + } +} diff --git openjdk.orig/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/SunPKCS11.java openjdk/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/SunPKCS11.java index 5d3963ea893..42c72b393fd 100644 --- openjdk.orig/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/SunPKCS11.java +++ openjdk/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/SunPKCS11.java @@ -26,6 +26,9 @@ package sun.security.pkcs11; import java.io.*; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; import java.util.*; import java.security.*; @@ -66,6 +69,26 @@ public final class SunPKCS11 extends AuthProvider { private static final boolean systemFipsEnabled = SharedSecrets .getJavaSecuritySystemConfiguratorAccess().isSystemFipsEnabled(); + private static final boolean plainKeySupportEnabled = SharedSecrets + .getJavaSecuritySystemConfiguratorAccess().isPlainKeySupportEnabled(); + + private static final MethodHandle fipsImportKey; + static { + MethodHandle fipsImportKeyTmp = null; + if (plainKeySupportEnabled) { + try { + fipsImportKeyTmp = MethodHandles.lookup().findStatic( + FIPSKeyImporter.class, "importKey", + MethodType.methodType(Long.class, SunPKCS11.class, + long.class, CK_ATTRIBUTE[].class)); + } catch (Throwable t) { + throw new SecurityException("FIPS key importer initialization" + + " failed", t); + } + } + fipsImportKey = fipsImportKeyTmp; + } + private static final long serialVersionUID = -1354835039035306505L; static final Debug debug = Debug.getInstance("sunpkcs11"); @@ -324,10 +347,15 @@ public final class SunPKCS11 extends AuthProvider { // request multithreaded access first initArgs.flags = CKF_OS_LOCKING_OK; PKCS11 tmpPKCS11; + MethodHandle fipsKeyImporter = null; + if (plainKeySupportEnabled) { + fipsKeyImporter = MethodHandles.insertArguments( + fipsImportKey, 0, this); + } try { tmpPKCS11 = PKCS11.getInstance( library, functionList, initArgs, - config.getOmitInitialize()); + config.getOmitInitialize(), fipsKeyImporter); } catch (PKCS11Exception e) { if (debug != null) { debug.println("Multi-threaded initialization failed: " + e); @@ -343,7 +371,7 @@ public final class SunPKCS11 extends AuthProvider { initArgs.flags = 0; } tmpPKCS11 = PKCS11.getInstance(library, - functionList, initArgs, config.getOmitInitialize()); + functionList, initArgs, config.getOmitInitialize(), fipsKeyImporter); } p11 = tmpPKCS11; diff --git openjdk.orig/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/PKCS11.java openjdk/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/PKCS11.java index 5c0aacd1a67..4d80145cb91 100644 --- openjdk.orig/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/PKCS11.java +++ openjdk/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/PKCS11.java @@ -49,6 +49,7 @@ package sun.security.pkcs11.wrapper; import java.io.File; import java.io.IOException; +import java.lang.invoke.MethodHandle; import java.util.*; import java.security.AccessController; @@ -152,16 +153,28 @@ public class PKCS11 { public static synchronized PKCS11 getInstance(String pkcs11ModulePath, String functionList, CK_C_INITIALIZE_ARGS pInitArgs, - boolean omitInitialize) throws IOException, PKCS11Exception { + boolean omitInitialize, MethodHandle fipsKeyImporter) + throws IOException, PKCS11Exception { // we may only call C_Initialize once per native .so/.dll // so keep a cache using the (non-canonicalized!) path PKCS11 pkcs11 = moduleMap.get(pkcs11ModulePath); if (pkcs11 == null) { + boolean nssFipsMode = fipsKeyImporter != null; if ((pInitArgs != null) && ((pInitArgs.flags & CKF_OS_LOCKING_OK) != 0)) { - pkcs11 = new PKCS11(pkcs11ModulePath, functionList); + if (nssFipsMode) { + pkcs11 = new FIPSPKCS11(pkcs11ModulePath, functionList, + fipsKeyImporter); + } else { + pkcs11 = new PKCS11(pkcs11ModulePath, functionList); + } } else { - pkcs11 = new SynchronizedPKCS11(pkcs11ModulePath, functionList); + if (nssFipsMode) { + pkcs11 = new SynchronizedFIPSPKCS11(pkcs11ModulePath, + functionList, fipsKeyImporter); + } else { + pkcs11 = new SynchronizedPKCS11(pkcs11ModulePath, functionList); + } } if (omitInitialize == false) { try { @@ -1911,4 +1924,69 @@ static class SynchronizedPKCS11 extends PKCS11 { super.C_GenerateRandom(hSession, randomData); } } + +// PKCS11 subclass that allows using plain private or secret keys in +// FIPS-configured NSS Software Tokens. Only used when System FIPS +// is enabled. +static class FIPSPKCS11 extends PKCS11 { + private MethodHandle fipsKeyImporter; + FIPSPKCS11(String pkcs11ModulePath, String functionListName, + MethodHandle fipsKeyImporter) throws IOException { + super(pkcs11ModulePath, functionListName); + this.fipsKeyImporter = fipsKeyImporter; + } + + public synchronized long C_CreateObject(long hSession, + CK_ATTRIBUTE[] pTemplate) throws PKCS11Exception { + // Creating sensitive key objects from plain key material in a + // FIPS-configured NSS Software Token is not allowed. We apply + // a key-unwrapping scheme to achieve so. + if (FIPSPKCS11Helper.isSensitiveObject(pTemplate)) { + try { + return ((Long)fipsKeyImporter.invoke(hSession, pTemplate)) + .longValue(); + } catch (Throwable t) { + throw new PKCS11Exception(CKR_GENERAL_ERROR); + } + } + return super.C_CreateObject(hSession, pTemplate); + } +} + +// FIPSPKCS11 synchronized counterpart. +static class SynchronizedFIPSPKCS11 extends SynchronizedPKCS11 { + private MethodHandle fipsKeyImporter; + SynchronizedFIPSPKCS11(String pkcs11ModulePath, String functionListName, + MethodHandle fipsKeyImporter) throws IOException { + super(pkcs11ModulePath, functionListName); + this.fipsKeyImporter = fipsKeyImporter; + } + + public synchronized long C_CreateObject(long hSession, + CK_ATTRIBUTE[] pTemplate) throws PKCS11Exception { + // See FIPSPKCS11::C_CreateObject. + if (FIPSPKCS11Helper.isSensitiveObject(pTemplate)) { + try { + return ((Long)fipsKeyImporter.invoke(hSession, pTemplate)) + .longValue(); + } catch (Throwable t) { + throw new PKCS11Exception(CKR_GENERAL_ERROR); + } + } + return super.C_CreateObject(hSession, pTemplate); + } +} + +private static class FIPSPKCS11Helper { + static boolean isSensitiveObject(CK_ATTRIBUTE[] pTemplate) { + for (CK_ATTRIBUTE attr : pTemplate) { + if (attr.type == CKA_CLASS && + (attr.getLong() == CKO_PRIVATE_KEY || + attr.getLong() == CKO_SECRET_KEY)) { + return true; + } + } + return false; + } +} } diff --git openjdk.orig/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/PKCS11Exception.java openjdk/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/PKCS11Exception.java index e2d6d371bec..dc5e7eefdd3 100644 --- openjdk.orig/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/PKCS11Exception.java +++ openjdk/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/PKCS11Exception.java @@ -219,6 +219,14 @@ public class PKCS11Exception extends Exception { return "0x" + Functions.toFullHexString((int)errorCode); } + /** + * Constructor taking the error code (the CKR_* constants in PKCS#11) with + * no extra info for the error message. + */ + public PKCS11Exception(long errorCode) { + this(errorCode, null); + } + /** * Constructor taking the error code (the CKR_* constants in PKCS#11) and * extra info for error message.