8074935: jdk8 keytool doesn't validate pem files for RFC 1421 correctness, as jdk7 did
authorweijun
Mon, 23 Mar 2015 08:51:51 +0800
changeset 29596 70399c7a7f5a
parent 29595 39c82302bcf6
child 29597 3c8b5093625e
8074935: jdk8 keytool doesn't validate pem files for RFC 1421 correctness, as jdk7 did Reviewed-by: mullan
jdk/src/java.base/share/classes/sun/security/pkcs10/PKCS10.java
jdk/src/java.base/share/classes/sun/security/provider/X509Factory.java
jdk/src/java.base/share/classes/sun/security/tools/keytool/Main.java
jdk/src/java.base/share/classes/sun/security/util/Pem.java
jdk/src/java.base/share/classes/sun/security/x509/X509CertImpl.java
jdk/test/sun/security/provider/X509Factory/BadPem.java
jdk/test/sun/security/tools/keytool/KeyToolTest.java
--- a/jdk/src/java.base/share/classes/sun/security/pkcs10/PKCS10.java	Sun Mar 22 10:27:22 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/pkcs10/PKCS10.java	Mon Mar 23 08:51:51 2015 +0800
@@ -290,8 +290,9 @@
             throw new SignatureException("Cert request was not signed");
 
 
+        byte[] CRLF = new byte[] {'\r', '\n'};
         out.println("-----BEGIN NEW CERTIFICATE REQUEST-----");
-        out.println(Base64.getMimeEncoder().encodeToString(encoded));
+        out.println(Base64.getMimeEncoder(64, CRLF).encodeToString(encoded));
         out.println("-----END NEW CERTIFICATE REQUEST-----");
     }
 
--- a/jdk/src/java.base/share/classes/sun/security/provider/X509Factory.java	Sun Mar 22 10:27:22 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/provider/X509Factory.java	Mon Mar 23 08:51:51 2015 +0800
@@ -28,6 +28,8 @@
 import java.io.*;
 import java.util.*;
 import java.security.cert.*;
+
+import sun.security.util.Pem;
 import sun.security.x509.X509CertImpl;
 import sun.security.x509.X509CRLImpl;
 import sun.security.pkcs.PKCS7;
@@ -633,7 +635,7 @@
 
             checkHeaderFooter(header.toString(), footer.toString());
 
-            return Base64.getMimeDecoder().decode(new String(data, 0, pos));
+            return Pem.decode(new String(data, 0, pos));
         }
     }
 
--- a/jdk/src/java.base/share/classes/sun/security/tools/keytool/Main.java	Sun Mar 22 10:27:22 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/tools/keytool/Main.java	Mon Mar 23 08:51:51 2015 +0800
@@ -80,6 +80,7 @@
 import sun.security.tools.KeyStoreUtil;
 import sun.security.tools.PathList;
 import sun.security.util.DerValue;
+import sun.security.util.Pem;
 import sun.security.x509.*;
 
 import static java.security.KeyStore.*;
@@ -100,6 +101,8 @@
  */
 public final class Main {
 
+    private static final byte[] CRLF = new byte[] {'\r', '\n'};
+
     private boolean debug = false;
     private Command command = null;
     private String sigAlgName = null;
@@ -1266,7 +1269,7 @@
                 sb.append(s);
             }
         }
-        byte[] rawReq = Base64.getMimeDecoder().decode(new String(sb));
+        byte[] rawReq = Pem.decode(new String(sb));
         PKCS10 req = new PKCS10(rawReq);
 
         info.set(X509CertInfo.KEY, new CertificateX509Key(req.getSubjectPublicKeyInfo()));
@@ -1343,7 +1346,7 @@
         crl.sign(privateKey, sigAlgName);
         if (rfc) {
             out.println("-----BEGIN X509 CRL-----");
-            out.println(Base64.getMimeEncoder().encodeToString(crl.getEncodedInternal()));
+            out.println(Base64.getMimeEncoder(64, CRLF).encodeToString(crl.getEncodedInternal()));
             out.println("-----END X509 CRL-----");
         } else {
             out.write(crl.getEncodedInternal());
@@ -2304,7 +2307,7 @@
         if (rfc) {
             X509CRL xcrl = (X509CRL)crl;
             out.println("-----BEGIN X509 CRL-----");
-            out.println(Base64.getMimeEncoder().encodeToString(xcrl.getEncoded()));
+            out.println(Base64.getMimeEncoder(64, CRLF).encodeToString(xcrl.getEncoded()));
             out.println("-----END X509 CRL-----");
         } else {
             out.println(crl.toString());
@@ -2331,7 +2334,7 @@
                 sb.append(s);
             }
         }
-        PKCS10 req = new PKCS10(Base64.getMimeDecoder().decode(new String(sb)));
+        PKCS10 req = new PKCS10(Pem.decode(new String(sb)));
 
         PublicKey pkey = req.getSubjectPublicKeyInfo();
         out.printf(rb.getString("PKCS.10.Certificate.Request.Version.1.0.Subject.s.Public.Key.s.format.s.key."),
@@ -3115,7 +3118,7 @@
     {
         if (rfc) {
             out.println(X509Factory.BEGIN_CERT);
-            out.println(Base64.getMimeEncoder().encodeToString(cert.getEncoded()));
+            out.println(Base64.getMimeEncoder(64, CRLF).encodeToString(cert.getEncoded()));
             out.println(X509Factory.END_CERT);
         } else {
             out.write(cert.getEncoded()); // binary
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/sun/security/util/Pem.java	Mon Mar 23 08:51:51 2015 +0800
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2015, 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 sun.security.util;
+
+import java.io.IOException;
+import java.util.Base64;
+
+/**
+ * The Length interface defines the length of an object
+ */
+public class Pem {
+
+    /**
+     * Decodes a PEM-encoded block.
+     *
+     * @param input the input string, according to RFC 1421, can only contain
+     *              characters in the base-64 alphabet and whitespaces.
+     * @return the decoded bytes
+     * @throws java.io.IOException if input is invalid
+     */
+    public static byte[] decode(String input) throws IOException {
+        byte[] src = input.replaceAll("\\s+", "").getBytes();
+        try {
+            return Base64.getDecoder().decode(src);
+        } catch (IllegalArgumentException e) {
+            throw new IOException(e);
+        }
+    }
+}
--- a/jdk/src/java.base/share/classes/sun/security/x509/X509CertImpl.java	Sun Mar 22 10:27:22 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/x509/X509CertImpl.java	Mon Mar 23 08:51:51 2015 +0800
@@ -271,7 +271,7 @@
                         der = new DerValue(decstream.toByteArray());
                         break;
                     } else {
-                        decstream.write(Base64.getMimeDecoder().decode(line));
+                        decstream.write(Pem.decode(line));
                     }
                 }
             } catch (IOException ioe2) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/provider/X509Factory/BadPem.java	Mon Mar 23 08:51:51 2015 +0800
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2015, 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.
+ *
+ * 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.
+ */
+
+/*
+ * @test
+ * @bug 8074935
+ * @summary jdk8 keytool doesn't validate pem files for RFC 1421 correctness, as jdk7 did
+ */
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.PrintStream;
+import java.security.KeyStore;
+import java.security.cert.CertificateException;
+import java.util.Arrays;
+import java.util.Base64;
+
+import sun.security.provider.X509Factory;
+import java.security.cert.CertificateFactory;
+import java.io.ByteArrayInputStream;
+
+public class BadPem {
+
+    public static void main(String[] args) throws Exception {
+        String ks = System.getProperty("test.src", ".")
+                + "/../../../../javax/net/ssl/etc/keystore";
+        String pass = "passphrase";
+        String alias = "dummy";
+
+        KeyStore keyStore = KeyStore.getInstance("JKS");
+        keyStore.load(new FileInputStream(ks), pass.toCharArray());
+        byte[] cert = keyStore.getCertificate(alias).getEncoded();
+
+        ByteArrayOutputStream bout = new ByteArrayOutputStream();
+        PrintStream pout = new PrintStream(bout);
+        byte[] CRLF = new byte[] {'\r', '\n'};
+        pout.println(X509Factory.BEGIN_CERT);
+        for (int i=0; i<cert.length; i += 48) {
+            int blockLen = (cert.length > i + 48) ? 48 : (cert.length - i);
+            pout.println("!" + Base64.getEncoder()
+                    .encodeToString(Arrays.copyOfRange(cert, i, i + blockLen)));
+        }
+        pout.println(X509Factory.END_CERT);
+
+        CertificateFactory cf = CertificateFactory.getInstance("X.509");
+
+        try {
+            cf.generateCertificate(new ByteArrayInputStream(bout.toByteArray()));
+            throw new Exception("Should fail");
+        } catch (CertificateException e) {
+            // Good
+        }
+    }
+}
+
--- a/jdk/test/sun/security/tools/keytool/KeyToolTest.java	Sun Mar 22 10:27:22 2015 -0700
+++ b/jdk/test/sun/security/tools/keytool/KeyToolTest.java	Mon Mar 23 08:51:51 2015 +0800
@@ -56,6 +56,8 @@
  * NSS PKCS11 config file are changed, DSA not supported now.
  */
 
+import java.nio.file.Files;
+import java.nio.file.Paths;
 import java.security.KeyStore;
 import sun.security.x509.*;
 import java.io.*;
@@ -1222,6 +1224,24 @@
         remove("mykey.cert");
     }
 
+    // 8074935: jdk8 keytool doesn't validate pem files for RFC 1421 correctness
+    static void checkPem(String file) throws Exception {
+        boolean maybeLast = false;
+        for (String s: Files.readAllLines(Paths.get(file))) {
+            if (s.isEmpty()) continue;
+            if (s.startsWith("---")) continue;
+            if (maybeLast) {
+                throw new Exception("Last line already seen");
+            }
+            if (s.length() > 64) {
+                throw new Exception(s);
+            }
+            if (s.length() < 64) {
+                maybeLast = true;
+            }
+        }
+    }
+
     void v3extTest(String keyAlg) throws Exception {
         KeyStore ks;
         remove("x.jks");
@@ -1588,12 +1608,14 @@
                 "-rfc -file test.req");
         // printcertreq
         testOK("", "-printcertreq -file test.req");
+        checkPem("test.req");
         // issue: deny KU, change criticality of 1.2.3 and 1.2.4,
         // change content of BC, add 2.3.4
         testOK("", simple+"-gencert -alias ca -infile test.req -ext " +
                 "honored=all,-KU,1.2.3:critical,1.2.4:non-critical " +
                 "-ext BC=2 -ext 2.3.4=01020304 " +
                 "-debug -rfc -outfile test.cert");
+        checkPem("test.cert");
         testOK("", simple+"-importcert -file test.cert -alias a");
         ks = loadStore("x.jks", "changeit", "JKS");
         X509CertImpl a = (X509CertImpl)ks.getCertificate("a");