test/jdk/sun/security/tools/jarsigner/DigestDontIgnoreCase.java
changeset 57488 94691d8e746f
equal deleted inserted replaced
57487:643978a35f6e 57488:94691d8e746f
       
     1 /*
       
     2  * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.
       
     8  *
       
     9  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    12  * version 2 for more details (a copy is included in the LICENSE file that
       
    13  * accompanied this code).
       
    14  *
       
    15  * You should have received a copy of the GNU General Public License version
       
    16  * 2 along with this work; if not, write to the Free Software Foundation,
       
    17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    18  *
       
    19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    20  * or visit www.oracle.com if you need additional information or have any
       
    21  * questions.
       
    22  */
       
    23 
       
    24 import java.io.IOException;
       
    25 import java.io.OutputStream;
       
    26 import java.nio.file.Files;
       
    27 import java.nio.file.Path;
       
    28 import java.util.Arrays;
       
    29 import java.util.Map;
       
    30 import java.util.jar.JarFile;
       
    31 import java.util.jar.JarOutputStream;
       
    32 import java.util.jar.JarEntry;
       
    33 import jdk.test.lib.SecurityTools;
       
    34 import org.testng.annotations.Test;
       
    35 import org.testng.annotations.BeforeClass;
       
    36 
       
    37 import static java.nio.charset.StandardCharsets.UTF_8;
       
    38 
       
    39 /**
       
    40  * @test
       
    41  * @bug 8217375
       
    42  * @library /test/lib
       
    43  * @run testng DigestDontIgnoreCase
       
    44  * @summary Check that existing manifest digest entries are taken for valid
       
    45  * only if they match the actual digest value also taking upper and lower
       
    46  * case of the base64 encoded form of the digests into account.
       
    47  */
       
    48 /*
       
    49  * <pre>mfDigest.equalsIgnoreCase(base64Digests[i])</pre>
       
    50  * previously in JarSigner.java on on line 985
       
    51  * @see jdk.security.jarsigner.JarSigner#updateDigests
       
    52  */
       
    53 public class DigestDontIgnoreCase {
       
    54 
       
    55     static final String KEYSTORE_FILENAME = "test.jks";
       
    56 
       
    57     static final String DUMMY_FILE1 = "dummy1.txt";
       
    58     static final byte[] DUMMY_CONTENTS1 = DUMMY_FILE1.getBytes(UTF_8);
       
    59     static final String DUMMY_FILE2 = "dummy2.txt";
       
    60     static final byte[] DUMMY_CONTENTS2 = DUMMY_FILE2.getBytes(UTF_8);
       
    61 
       
    62     byte[] goodSignedManifest;
       
    63 
       
    64     @BeforeClass
       
    65     public void prepareCertificate() throws Exception {
       
    66         SecurityTools.keytool("-genkeypair -keyalg DSA -keystore "
       
    67                 + KEYSTORE_FILENAME + " -storepass changeit -keypass changeit"
       
    68                 + " -alias a -dname CN=X").shouldHaveExitValue(0);
       
    69     }
       
    70 
       
    71     void prepareJarFile(String filename, Map<String, byte[]> contents)
       
    72             throws IOException {
       
    73         try (OutputStream out = Files.newOutputStream(Path.of(filename));
       
    74                 JarOutputStream jos = new JarOutputStream(out)) {
       
    75             for (Map.Entry<String, byte[]> entry : contents.entrySet()) {
       
    76                 JarEntry je = new JarEntry(entry.getKey());
       
    77                 jos.putNextEntry(je);
       
    78                 jos.write(entry.getValue());
       
    79                 jos.closeEntry();
       
    80             }
       
    81         }
       
    82     }
       
    83 
       
    84     @BeforeClass(dependsOnMethods = "prepareCertificate")
       
    85     public void prepareGoodSignedManifest() throws Exception {
       
    86         String filename = "prepare.jar";
       
    87         prepareJarFile(filename, Map.of(DUMMY_FILE1, DUMMY_CONTENTS1));
       
    88         SecurityTools.jarsigner("-keystore " + KEYSTORE_FILENAME +
       
    89                 " -storepass changeit -verbose -debug " + filename + " a")
       
    90                 .shouldHaveExitValue(0);
       
    91         goodSignedManifest = Utils.readJarManifestBytes(filename);
       
    92         Utils.echoManifest(goodSignedManifest,
       
    93                 "reference manifest with one file signed");
       
    94     }
       
    95 
       
    96     void testWithManifest(String filename, byte[] manifestBytes)
       
    97             throws Exception {
       
    98         Utils.echoManifest(manifestBytes,
       
    99                 "going to test " + filename + " with manifest");
       
   100         prepareJarFile(filename, Map.of(
       
   101                 JarFile.MANIFEST_NAME, manifestBytes,
       
   102                 DUMMY_FILE1, DUMMY_CONTENTS1, // with digest already in manifest
       
   103                 DUMMY_FILE2, DUMMY_CONTENTS2)); // causes manifest update
       
   104         Utils.echoManifest(Utils.readJarManifestBytes(filename),
       
   105                 filename + " created with manifest");
       
   106         SecurityTools.jarsigner("-keystore " + KEYSTORE_FILENAME +
       
   107                 " -storepass changeit -debug -verbose " + filename + " a")
       
   108                 .shouldHaveExitValue(0);
       
   109         Utils.echoManifest(Utils.readJarManifestBytes(filename),
       
   110                 filename + " signed resulting in manifest");
       
   111         SecurityTools.jarsigner("-verify -strict -keystore " +
       
   112                 KEYSTORE_FILENAME + " -storepass changeit -debug -verbose " +
       
   113                 filename + " a").shouldHaveExitValue(0);
       
   114     }
       
   115 
       
   116     @Test
       
   117     public void verifyDigestGoodCase() throws Exception {
       
   118         testWithManifest("good.jar", goodSignedManifest);
       
   119     }
       
   120 
       
   121     @Test
       
   122     public void testDigestHeaderNameCase() throws Exception {
       
   123         byte[] mfBadHeader = new String(goodSignedManifest, UTF_8).
       
   124                 replace("SHA-256-Digest", "sha-256-dIGEST").getBytes(UTF_8);
       
   125         testWithManifest("switch-header-name-case.jar", mfBadHeader);
       
   126     }
       
   127 
       
   128     @Test
       
   129     public void testDigestWrongCase() throws Exception {
       
   130         byte[] mfBadDigest = switchCase(goodSignedManifest, "Digest");
       
   131         testWithManifest("switch-digest-case.jar", mfBadDigest);
       
   132     }
       
   133 
       
   134     byte[] switchCase(byte[] manifest, String attrName) {
       
   135         byte[] wrongCase = Arrays.copyOf(manifest, manifest.length);
       
   136         byte[] name = (attrName + ":").getBytes(UTF_8);
       
   137         int matched = 0; // number of bytes before position i matching attrName
       
   138         for (int i = 0; i < wrongCase.length; i++) {
       
   139             if (wrongCase[i] == '\r' &&
       
   140                     (i == wrongCase.length - 1 || wrongCase[i + 1] == '\n')) {
       
   141                 continue;
       
   142             } else if ((wrongCase[i] == '\r' || wrongCase[i] == '\n')
       
   143                     && (i == wrongCase.length - 1 || wrongCase[i + 1] != ' ')) {
       
   144                 matched = 0;
       
   145             } else if (matched == name.length) {
       
   146                 wrongCase[i] = switchCase(wrongCase[i]);
       
   147             } else if (name[matched] == wrongCase[i]) {
       
   148                 matched++;
       
   149             } else {
       
   150                 matched = 0;
       
   151             }
       
   152         }
       
   153         return wrongCase;
       
   154     }
       
   155 
       
   156     byte switchCase(byte c) {
       
   157         if (c >= 'A' && c <= 'Z') {
       
   158             return (byte) ('a' + (c - 'A'));
       
   159         } else if (c >= 'a' && c <= 'z') {
       
   160             return (byte) ('A' + (c - 'a'));
       
   161         } else {
       
   162             return c;
       
   163         }
       
   164     }
       
   165 
       
   166 }