8189997: Enhance keystore mechanisms
authorweijun
Fri, 12 Jan 2018 08:06:24 +0800
changeset 49783 977c6dd636bd
parent 49782 7cbb8bd1fc29
child 49784 d28ec9f8d528
8189997: Enhance keystore mechanisms 8194259: keytool error: java.io.IOException: Invalid secret key format Reviewed-by: mullan, valeriep, rriggs, ahgross
src/java.base/share/classes/com/sun/crypto/provider/JceKeyStore.java
src/java.base/share/classes/com/sun/crypto/provider/KeyProtector.java
src/java.base/share/classes/com/sun/crypto/provider/SealedObjectForKeyProtector.java
src/java.base/share/classes/com/sun/crypto/provider/ai.java
src/java.base/share/classes/javax/crypto/SealedObject.java
src/java.base/share/classes/jdk/internal/misc/JavaxCryptoSealedObjectAccess.java
src/java.base/share/classes/jdk/internal/misc/SharedSecrets.java
src/java.base/share/conf/security/java.security
--- a/src/java.base/share/classes/com/sun/crypto/provider/JceKeyStore.java	Wed Jan 03 09:05:11 2018 -0800
+++ b/src/java.base/share/classes/com/sun/crypto/provider/JceKeyStore.java	Fri Jan 12 08:06:24 2018 +0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1998, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -943,8 +943,10 @@
             // First run a custom filter
             long nestedDepth = info.depth();
             if ((nestedDepth == 1 &&
-                info.serialClass() != SealedObjectForKeyProtector.class) ||
-                nestedDepth > MAX_NESTED_DEPTH) {
+                        info.serialClass() != SealedObjectForKeyProtector.class) ||
+                    (nestedDepth > MAX_NESTED_DEPTH &&
+                        info.serialClass() != null &&
+                        info.serialClass() != Object.class)) {
                 return Status.REJECTED;
             }
 
--- a/src/java.base/share/classes/com/sun/crypto/provider/KeyProtector.java	Wed Jan 03 09:05:11 2018 -0800
+++ b/src/java.base/share/classes/com/sun/crypto/provider/KeyProtector.java	Fri Jan 12 08:06:24 2018 +0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1998, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -26,8 +26,6 @@
 package com.sun.crypto.provider;
 
 import java.io.IOException;
-import java.io.Serializable;
-import java.security.Security;
 import java.security.Key;
 import java.security.PrivateKey;
 import java.security.Provider;
@@ -35,7 +33,6 @@
 import java.security.MessageDigest;
 import java.security.GeneralSecurityException;
 import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
 import java.security.UnrecoverableKeyException;
 import java.security.AlgorithmParameters;
 import java.security.spec.InvalidParameterSpecException;
@@ -44,7 +41,6 @@
 import javax.crypto.Cipher;
 import javax.crypto.CipherSpi;
 import javax.crypto.SecretKey;
-import javax.crypto.IllegalBlockSizeException;
 import javax.crypto.SealedObject;
 import javax.crypto.spec.*;
 import sun.security.x509.AlgorithmId;
@@ -347,7 +343,7 @@
                                                       SunJCE.getInstance(),
                                                       "PBEWithMD5AndTripleDES");
             cipher.init(Cipher.DECRYPT_MODE, skey, params);
-            return (Key)soForKeyProtector.getObject(cipher);
+            return soForKeyProtector.getKey(cipher);
         } catch (NoSuchAlgorithmException ex) {
             // Note: this catch needed to be here because of the
             // later catch of GeneralSecurityException
--- a/src/java.base/share/classes/com/sun/crypto/provider/SealedObjectForKeyProtector.java	Wed Jan 03 09:05:11 2018 -0800
+++ b/src/java.base/share/classes/com/sun/crypto/provider/SealedObjectForKeyProtector.java	Fri Jan 12 08:06:24 2018 +0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1998, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -25,6 +25,8 @@
 
 package com.sun.crypto.provider;
 
+import jdk.internal.misc.SharedSecrets;
+
 import java.io.*;
 import java.security.*;
 import javax.crypto.*;
@@ -33,6 +35,16 @@
 
     static final long serialVersionUID = -3650226485480866989L;
 
+    /**
+     * The InputStreamFilter for a Key object inside this SealedObject. It can
+     * be either provided as a {@link Security} property or a system property
+     * (when provided as latter, it shadows the former). If the result of this
+     * filter is {@link java.io.ObjectInputFilter.Status.UNDECIDED}, the system
+     * level filter defined by jdk.serialFilter will be consulted. The value
+     * of this property uses the same format of jdk.serialFilter.
+     */
+    private static final String KEY_SERIAL_FILTER = "jceks.key.serialFilter";
+
     SealedObjectForKeyProtector(Serializable object, Cipher c)
             throws IOException, IllegalBlockSizeException {
         super(object, c);
@@ -59,4 +71,87 @@
         }
         return params;
     }
+
+    final Key getKey(Cipher c)
+            throws IOException, ClassNotFoundException, IllegalBlockSizeException,
+            BadPaddingException {
+
+        try (ObjectInputStream ois = SharedSecrets.getJavaxCryptoSealedObjectAccess()
+                .getExtObjectInputStream(this, c)) {
+            AccessController.doPrivileged(
+                    (PrivilegedAction<Void>) () -> {
+                        ois.setObjectInputFilter(DeserializationChecker.ONE_FILTER);
+                        return null;
+                    });
+            try {
+                @SuppressWarnings("unchecked")
+                Key t = (Key) ois.readObject();
+                return t;
+            } catch (InvalidClassException ice) {
+                String msg = ice.getMessage();
+                if (msg.contains("REJECTED")) {
+                    throw new IOException("Rejected by the"
+                            + " jceks.key.serialFilter or jdk.serialFilter"
+                            + " property", ice);
+                } else {
+                    throw ice;
+                }
+            }
+        }
+    }
+
+    /**
+     * The filter for the content of a SealedObjectForKeyProtector.
+     *
+     * First, the jceks.key.serialFilter will be consulted. If the result
+     * is UNDECIDED, the system level jdk.serialFilter will be consulted.
+     */
+    private static class DeserializationChecker implements ObjectInputFilter {
+
+        private static final ObjectInputFilter ONE_FILTER;
+
+        static {
+            String prop = AccessController.doPrivileged(
+                    (PrivilegedAction<String>) () -> {
+                        String tmp = System.getProperty(KEY_SERIAL_FILTER);
+                        if (tmp != null) {
+                            return tmp;
+                        } else {
+                            return Security.getProperty(KEY_SERIAL_FILTER);
+                        }
+                    });
+            ONE_FILTER = new DeserializationChecker(prop == null ? null
+                    : ObjectInputFilter.Config.createFilter(prop));
+        }
+
+        private final ObjectInputFilter base;
+
+        private DeserializationChecker(ObjectInputFilter base) {
+            this.base = base;
+        }
+
+        @Override
+        public ObjectInputFilter.Status checkInput(
+                ObjectInputFilter.FilterInfo info) {
+
+            if (info.serialClass() == Object.class) {
+                return Status.UNDECIDED;
+            }
+
+            if (base != null) {
+                Status result = base.checkInput(info);
+                if (result != Status.UNDECIDED) {
+                    return result;
+                }
+            }
+
+            ObjectInputFilter defaultFilter =
+                    ObjectInputFilter.Config.getSerialFilter();
+            if (defaultFilter != null) {
+                return defaultFilter.checkInput(info);
+            }
+
+            return Status.UNDECIDED;
+        }
+    }
 }
--- a/src/java.base/share/classes/com/sun/crypto/provider/ai.java	Wed Jan 03 09:05:11 2018 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-/*
- * Copyright (c) 2001, 2007, Oracle and/or its affiliates. All rights reserved.
- * 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 com.sun.crypto.provider;
-
-import java.io.IOException;
-import java.io.Serializable;
-import java.io.ObjectStreamException;
-import java.security.AlgorithmParameters;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.SealedObject;
-import javax.crypto.spec.*;
-
-/**
- * This class is introduced to workaround a problem in
- * the SunJCE provider shipped in JCE 1.2.1: the class
- * SealedObjectForKeyProtector was obfuscated due to a mistake.
- *
- * In order to retrieve secret keys in a JCEKS KeyStore written
- * by the SunJCE provider in JCE 1.2.1, this class will be used.
- *
- * @author Valerie Peng
- *
- *
- * @see JceKeyStore
- */
-
-final class ai extends javax.crypto.SealedObject {
-
-    static final long serialVersionUID = -7051502576727967444L;
-
-    ai(SealedObject so) {
-        super(so);
-    }
-
-    Object readResolve() throws ObjectStreamException {
-        return new SealedObjectForKeyProtector(this);
-    }
-}
--- a/src/java.base/share/classes/javax/crypto/SealedObject.java	Wed Jan 03 09:05:11 2018 -0800
+++ b/src/java.base/share/classes/javax/crypto/SealedObject.java	Fri Jan 12 08:06:24 2018 +0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -25,6 +25,8 @@
 
 package javax.crypto;
 
+import jdk.internal.misc.SharedSecrets;
+
 import java.io.*;
 import java.security.AlgorithmParameters;
 import java.security.Key;
@@ -287,17 +289,7 @@
         throws IOException, ClassNotFoundException, IllegalBlockSizeException,
             BadPaddingException
     {
-        /*
-         * Unseal the object
-         */
-        byte[] content = c.doFinal(this.encryptedContent);
-
-        /*
-         * De-serialize it
-         */
-        // creating a stream pipe-line, from b to a
-        ByteArrayInputStream b = new ByteArrayInputStream(content);
-        ObjectInput a = new extObjectInputStream(b);
+        ObjectInput a = getExtObjectInputStream(c);
         try {
             Object obj = a.readObject();
             return obj;
@@ -417,17 +409,7 @@
             throw new RuntimeException(iape.getMessage());
         }
 
-        /*
-         * Unseal the object
-         */
-        byte[] content = c.doFinal(this.encryptedContent);
-
-        /*
-         * De-serialize it
-         */
-        // creating a stream pipe-line, from b to a
-        ByteArrayInputStream b = new ByteArrayInputStream(content);
-        ObjectInput a = new extObjectInputStream(b);
+        ObjectInput a = getExtObjectInputStream(c);
         try {
             Object obj = a.readObject();
             return obj;
@@ -450,6 +432,19 @@
         if (encodedParams != null)
             encodedParams = encodedParams.clone();
     }
+
+    // This method is also called inside SealedObjectForKeyProtector.java.
+    private ObjectInputStream getExtObjectInputStream(Cipher c)
+            throws BadPaddingException, IllegalBlockSizeException, IOException {
+
+        byte[] content = c.doFinal(this.encryptedContent);
+        ByteArrayInputStream b = new ByteArrayInputStream(content);
+        return new extObjectInputStream(b);
+    }
+
+    static {
+        SharedSecrets.setJavaxCryptoSealedObjectAccess((obj,c) -> obj.getExtObjectInputStream(c));
+    }
 }
 
 final class extObjectInputStream extends ObjectInputStream {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/jdk/internal/misc/JavaxCryptoSealedObjectAccess.java	Fri Jan 12 08:06:24 2018 +0800
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * 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 jdk.internal.misc;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.SealedObject;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+
+public interface JavaxCryptoSealedObjectAccess {
+    ObjectInputStream getExtObjectInputStream(
+            SealedObject sealed, Cipher cipher)
+            throws BadPaddingException, IllegalBlockSizeException, IOException;
+}
--- a/src/java.base/share/classes/jdk/internal/misc/SharedSecrets.java	Wed Jan 03 09:05:11 2018 -0800
+++ b/src/java.base/share/classes/jdk/internal/misc/SharedSecrets.java	Fri Jan 12 08:06:24 2018 +0800
@@ -25,6 +25,7 @@
 
 package jdk.internal.misc;
 
+import javax.crypto.SealedObject;
 import java.io.ObjectInputFilter;
 import java.lang.module.ModuleDescriptor;
 import java.util.ResourceBundle;
@@ -71,6 +72,7 @@
     private static JavaObjectInputStreamAccess javaObjectInputStreamAccess;
     private static JavaObjectInputFilterAccess javaObjectInputFilterAccess;
     private static JavaIORandomAccessFileAccess javaIORandomAccessFileAccess;
+    private static JavaxCryptoSealedObjectAccess javaxCryptoSealedObjectAccess;
 
     public static JavaUtilJarAccess javaUtilJarAccess() {
         if (javaUtilJarAccess == null) {
@@ -324,4 +326,15 @@
         }
         return javaIORandomAccessFileAccess;
     }
+
+    public static void setJavaxCryptoSealedObjectAccess(JavaxCryptoSealedObjectAccess jcsoa) {
+        javaxCryptoSealedObjectAccess = jcsoa;
+    }
+
+    public static JavaxCryptoSealedObjectAccess getJavaxCryptoSealedObjectAccess() {
+        if (javaxCryptoSealedObjectAccess == null) {
+            unsafe.ensureClassInitialized(SealedObject.class);
+        }
+        return javaxCryptoSealedObjectAccess;
+    }
 }
--- a/src/java.base/share/conf/security/java.security	Wed Jan 03 09:05:11 2018 -0800
+++ b/src/java.base/share/conf/security/java.security	Fri Jan 12 08:06:24 2018 +0800
@@ -895,6 +895,9 @@
 # Patterns are separated by ";" (semicolon).
 # Whitespace is significant and is considered part of the pattern.
 #
+# If the system property jdk.serialFilter is also specified, it supersedes
+# the security property value defined here.
+#
 # If a pattern includes a "=", it sets a limit.
 # If a limit appears more than once the last value is used.
 # Limits are checked before classes regardless of the order in the
@@ -1005,3 +1008,20 @@
 # It is not guaranteed to be examined and used by other implementations.
 #
 #com.sun.CORBA.ORBIorTypeCheckRegistryFilter=binary_class_name;binary_class_name
+
+#
+# JCEKS Encrypted Key Serial Filter
+#
+# This filter, if configured, is used by the JCEKS KeyStore during the
+# deserialization of the encrypted Key object stored inside a key entry.
+# If not configured or the filter result is UNDECIDED (i.e. none of the patterns
+# matches), the filter configured by jdk.serialFilter will be consulted.
+#
+# If the system property jceks.key.serialFilter is also specified, it supersedes
+# the security property value defined here.
+#
+# The filter pattern uses the same format as jdk.serialFilter. The default
+# pattern allows java.lang.Enum, java.security.KeyRep, java.security.KeyRep$Type,
+# and javax.crypto.spec.SecretKeySpec and rejects all the others.
+jceks.key.serialFilter = java.base/java.lang.Enum;java.base/java.security.KeyRep;\
+  java.base/java.security.KeyRep$Type;java.base/javax.crypto.spec.SecretKeySpec;!*