8157404: Unable to read certain PKCS12 keystores from SequenceInputStream
authorweijun
Tue, 02 Apr 2019 10:17:30 +0800
changeset 54372 9ac5d41abf68
parent 54371 72e44c1e7dc6
child 54373 13935056b05e
8157404: Unable to read certain PKCS12 keystores from SequenceInputStream Reviewed-by: xuelei
src/java.base/share/classes/sun/security/util/DerIndefLenConverter.java
src/java.base/share/classes/sun/security/util/DerInputStream.java
src/java.base/share/classes/sun/security/util/DerValue.java
--- a/src/java.base/share/classes/sun/security/util/DerIndefLenConverter.java	Tue Apr 02 00:23:31 2019 +0800
+++ b/src/java.base/share/classes/sun/security/util/DerIndefLenConverter.java	Tue Apr 02 10:17:30 2019 +0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1998, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2019, 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,7 +26,9 @@
 package sun.security.util;
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.Arrays;
 
 /**
  * A package private utility class to convert indefinite length DER
@@ -143,6 +145,10 @@
     /**
      * Parse the length and if it is an indefinite length then add
      * the current position to the <code>ndefsList</code> vector.
+     *
+     * @return the length of definite length data next, or -1 if there is
+     *         not enough bytes to determine it
+     * @throws IOException if invalid data is read
      */
     private int parseLength() throws IOException {
         int curLen = 0;
@@ -160,7 +166,7 @@
                 throw new IOException("Too much data");
             }
             if ((dataSize - dataPos) < (lenByte + 1)) {
-                throw new IOException("Too little data");
+                return -1;
             }
             for (int i = 0; i < lenByte; i++) {
                 curLen = (curLen << 8) + (data[dataPos++] & 0xff);
@@ -314,10 +320,10 @@
      * @param indefData the byte array holding the indefinite
      *        length encoding.
      * @return the byte array containing the definite length
-     *         DER encoding.
+     *         DER encoding, or null if there is not enough data.
      * @exception IOException on parsing or re-writing errors.
      */
-    byte[] convert(byte[] indefData) throws IOException {
+    byte[] convertBytes(byte[] indefData) throws IOException {
         data = indefData;
         dataPos=0; index=0;
         dataSize = data.length;
@@ -328,6 +334,9 @@
         while (dataPos < dataSize) {
             parseTag();
             len = parseLength();
+            if (len < 0) {
+                return null;
+            }
             parseValue(len);
             if (unresolved == 0) {
                 unused = dataSize - dataPos;
@@ -337,7 +346,7 @@
         }
 
         if (unresolved != 0) {
-            throw new IOException("not all indef len BER resolved");
+            return null;
         }
 
         newData = new byte[dataSize + numOfTotalLenBytes + unused];
@@ -354,4 +363,48 @@
 
         return newData;
     }
+
+    /**
+     * Read the input stream into a DER byte array. If an indef len BER is
+     * not resolved this method will try to read more data until EOF is reached.
+     * This may block.
+     *
+     * @param in the input stream with tag and lenByte already read
+     * @param lenByte the length of the length field to remember
+     * @param tag the tag to remember
+     * @return a DER byte array
+     * @throws IOException if not all indef len BER
+     *         can be resolved or another I/O error happens
+     */
+    public static byte[] convertStream(InputStream in, byte lenByte, byte tag)
+            throws IOException {
+        int offset = 2;     // for tag and length bytes
+        int readLen = in.available();
+        byte[] indefData = new byte[readLen + offset];
+        indefData[0] = tag;
+        indefData[1] = lenByte;
+        while (true) {
+            int bytesRead = in.readNBytes(indefData, offset, readLen);
+            if (bytesRead != readLen) {
+                readLen = bytesRead;
+                indefData = Arrays.copyOf(indefData, offset + bytesRead);
+            }
+            DerIndefLenConverter derIn = new DerIndefLenConverter();
+            byte[] result = derIn.convertBytes(indefData);
+            if (result == null) {
+                int next = in.read(); // This could block, but we need more
+                if (next == -1) {
+                    throw new IOException("not all indef len BER resolved");
+                }
+                int more = in.available();
+                // expand array to include next and more
+                indefData = Arrays.copyOf(indefData, offset + readLen + 1 + more);
+                indefData[offset + readLen] = (byte)next;
+                offset = offset + readLen + 1;
+                readLen = more;
+            } else {
+                return result;
+            }
+        }
+    }
 }
--- a/src/java.base/share/classes/sun/security/util/DerInputStream.java	Tue Apr 02 00:23:31 2019 +0800
+++ b/src/java.base/share/classes/sun/security/util/DerInputStream.java	Tue Apr 02 10:17:30 2019 +0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1996, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2019, 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
@@ -27,11 +27,9 @@
 
 import java.io.InputStream;
 import java.io.IOException;
-import java.io.EOFException;
 import java.util.Date;
 import java.util.Vector;
 import java.math.BigInteger;
-import java.io.DataInputStream;
 
 /**
  * A DER input stream, used for parsing ASN.1 DER-encoded data such as
@@ -130,7 +128,12 @@
                 System.arraycopy(data, offset, inData, 0, len);
 
                 DerIndefLenConverter derIn = new DerIndefLenConverter();
-                buffer = new DerInputBuffer(derIn.convert(inData), allowBER);
+                byte[] result = derIn.convertBytes(inData);
+                if (result == null) {
+                    throw new IOException("not all indef len BER resolved");
+                } else {
+                    buffer = new DerInputBuffer(result, allowBER);
+                }
             }
         } else {
             buffer = new DerInputBuffer(data, offset, len, allowBER);
@@ -389,16 +392,9 @@
 
         if (len == -1) {
            // indefinite length encoding found
-           int readLen = buffer.available();
-           int offset = 2;     // for tag and length bytes
-           byte[] indefData = new byte[readLen + offset];
-           indefData[0] = tag;
-           indefData[1] = lenByte;
-           DataInputStream dis = new DataInputStream(buffer);
-           dis.readFully(indefData, offset, readLen);
-           dis.close();
-           DerIndefLenConverter derIn = new DerIndefLenConverter();
-           buffer = new DerInputBuffer(derIn.convert(indefData), buffer.allowBER);
+           buffer = new DerInputBuffer(
+                   DerIndefLenConverter.convertStream(buffer, lenByte, tag),
+                   buffer.allowBER);
 
            if (tag != buffer.read())
                 throw new IOException("Indefinite length encoding" +
--- a/src/java.base/share/classes/sun/security/util/DerValue.java	Tue Apr 02 00:23:31 2019 +0800
+++ b/src/java.base/share/classes/sun/security/util/DerValue.java	Tue Apr 02 10:17:30 2019 +0800
@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 1996, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2019, 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
@@ -257,16 +257,9 @@
         length = DerInputStream.getLength(lenByte, in);
         if (length == -1) {  // indefinite length encoding found
             DerInputBuffer inbuf = in.dup();
-            int readLen = inbuf.available();
-            int offset = 2;     // for tag and length bytes
-            byte[] indefData = new byte[readLen + offset];
-            indefData[0] = tag;
-            indefData[1] = lenByte;
-            DataInputStream dis = new DataInputStream(inbuf);
-            dis.readFully(indefData, offset, readLen);
-            dis.close();
-            DerIndefLenConverter derIn = new DerIndefLenConverter();
-            inbuf = new DerInputBuffer(derIn.convert(indefData), in.allowBER);
+            inbuf = new DerInputBuffer(
+                    DerIndefLenConverter.convertStream(inbuf, lenByte, tag),
+                    in.allowBER);
             if (tag != inbuf.read())
                 throw new IOException
                         ("Indefinite length encoding not supported");
@@ -277,7 +270,7 @@
             // indefinite form is encoded by sending a length field with a
             // length of 0. - i.e. [1000|0000].
             // the object is ended by sending two zero bytes.
-            in.skip(length + offset);
+            in.skip(length + 2);
         } else {
 
             buffer = in.dup();
@@ -389,16 +382,8 @@
         byte lenByte = (byte)in.read();
         length = DerInputStream.getLength(lenByte, in);
         if (length == -1) { // indefinite length encoding found
-            int readLen = in.available();
-            int offset = 2;     // for tag and length bytes
-            byte[] indefData = new byte[readLen + offset];
-            indefData[0] = tag;
-            indefData[1] = lenByte;
-            DataInputStream dis = new DataInputStream(in);
-            dis.readFully(indefData, offset, readLen);
-            dis.close();
-            DerIndefLenConverter derIn = new DerIndefLenConverter();
-            in = new ByteArrayInputStream(derIn.convert(indefData));
+            in = new ByteArrayInputStream(
+                    DerIndefLenConverter.convertStream(in, lenByte, tag));
             if (tag != in.read())
                 throw new IOException
                         ("Indefinite length encoding not supported");