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.ByteArrayInputStream; 25 import java.io.ByteArrayOutputStream; 26 import java.io.IOException; 27 import java.security.MessageDigest; 28 import java.util.ArrayList; 29 import java.util.List; 30 import java.util.stream.Collectors; 31 import java.util.jar.Manifest; 32 33 import sun.security.util.ManifestDigester; 34 35 import org.testng.annotations.DataProvider; 36 import org.testng.annotations.Factory; 37 import org.testng.annotations.BeforeMethod; 38 import org.testng.annotations.Test; 39 40 import static java.nio.charset.StandardCharsets.UTF_8; 41 import static org.testng.Assert.*; 42 43 /** 44 * @test 45 * @bug 8217375 46 * @modules java.base/sun.security.util 47 * @compile ../../tools/jarsigner/Utils.java 48 * @run testng ReproduceRaw 49 * @summary Verifies that {@link ManifestDigester} can reproduce parts of 50 * manifests in their binary form so that {@link JarSigner} can rely on 51 * {@link ManifestDigester.Entry#reproduceRaw} to write in a map view 52 * unmodified entries back also unmodified in their binary form. 53 * <p> 54 * See also<ul> 55 * <li>{@link PreserveRawManifestEntryAndDigest} with end to end tests 56 * with {@code jarsigner} tool and</li> 57 * <li>{@link FindHeaderEndVsManifestDigesterFindFirstSection} about 58 * identifying the binary portion of only main attributes and more extensive 59 * main attributes digesting tests while this one test here is more about 60 * reproducing individual sections and that they result in the same 61 * digests.</li> 62 * </ul> 63 */ 64 public class ReproduceRaw { 65 66 static final boolean VERBOSE = false; 67 68 @DataProvider(name = "parameters") parameters()69 public static Object[][] parameters() { 70 List<Object[]> tests = new ArrayList<>(); 71 for (String lineBreak : new String[] { "\n", "\r", "\r\n" }) { 72 for (boolean oldStyle : new Boolean[] { false, true }) { 73 for (boolean workaround : new Boolean[] { false, true }) { 74 tests.add(new Object[] { lineBreak, oldStyle, workaround }); 75 } 76 } 77 } 78 return tests.toArray(new Object[tests.size()][]); 79 } 80 81 @Factory(dataProvider = "parameters") createTests(String lineBreak, boolean oldStyle, boolean digestWorkaround)82 public static Object[] createTests(String lineBreak, 83 boolean oldStyle, boolean digestWorkaround) { 84 return new Object[]{ 85 new ReproduceRaw(lineBreak, oldStyle, digestWorkaround) 86 }; 87 } 88 89 final String lineBreak; 90 final boolean oldStyle; 91 final boolean digestWorkaround; 92 ReproduceRaw(String lineBreak, boolean oldStyle, boolean digestWorkaround)93 public ReproduceRaw(String lineBreak, 94 boolean oldStyle, boolean digestWorkaround) { 95 this.lineBreak = lineBreak; 96 this.oldStyle = oldStyle; 97 this.digestWorkaround = digestWorkaround; 98 } 99 100 @BeforeMethod verbose()101 public void verbose() { 102 System.out.println("lineBreak = " + 103 Utils.escapeStringWithNumbers(lineBreak)); 104 System.out.println("oldStyle = " + oldStyle); 105 System.out.println("digestWorkaround = " + digestWorkaround); 106 } 107 108 class EchoMessageDigest extends MessageDigest { 109 110 ByteArrayOutputStream buf; 111 EchoMessageDigest()112 EchoMessageDigest() { 113 super("echo"); 114 } 115 116 @Override engineReset()117 protected void engineReset() { 118 buf = new ByteArrayOutputStream(); 119 } 120 121 @Override engineUpdate(byte input)122 protected void engineUpdate(byte input) { 123 buf.write(input); 124 } 125 126 @Override engineUpdate(byte[] i, int o, int l)127 protected void engineUpdate(byte[] i, int o, int l) { 128 buf.write(i, o, l); 129 } 130 engineDigest()131 @Override protected byte[] engineDigest() { 132 return buf.toByteArray(); 133 } 134 135 } 136 137 /** 138 * similar to corresponding part of {@link JarSigner#sign0} 139 * (stripped down to the code for reproducing the old manifest entry by 140 * entry which was too difficult to achieve using the real JarSigner code 141 * in the test here) 142 */ reproduceRawManifest(byte[] mfRawBytes, boolean mainAttsProperlyDelimited, boolean sectionProperlyDelimited)143 byte[] reproduceRawManifest(byte[] mfRawBytes, 144 boolean mainAttsProperlyDelimited, 145 boolean sectionProperlyDelimited) throws IOException { 146 Manifest manifest = new Manifest(new ByteArrayInputStream(mfRawBytes)); 147 ManifestDigester oldMd = new ManifestDigester(mfRawBytes); 148 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 149 150 // main attributes 151 assertEquals(oldMd.getMainAttsEntry().isProperlyDelimited(), 152 mainAttsProperlyDelimited); 153 oldMd.getMainAttsEntry().reproduceRaw(baos); 154 155 // individual sections 156 for (String key : manifest.getEntries().keySet()) { 157 assertEquals(oldMd.get(key).isProperlyDelimited(), 158 sectionProperlyDelimited); 159 oldMd.get(key).reproduceRaw(baos); 160 } 161 162 return baos.toByteArray(); 163 } 164 regExscape(String expr)165 static String regExscape(String expr) { 166 for (int i = 0; i < expr.length(); i++) { 167 if (expr.charAt(i) == '\r' || expr.charAt(i) == '\n') { 168 expr = expr.substring(0, i) + "\\" + expr.substring(i++); 169 } 170 } 171 return expr; 172 } 173 digest(byte[] manifest, String section)174 byte[] digest(byte[] manifest, String section) { 175 MessageDigest digester = new EchoMessageDigest(); 176 ManifestDigester md = new ManifestDigester(manifest); 177 ManifestDigester.Entry entry = section == null ? 178 md.getMainAttsEntry(oldStyle) : md.get(section, oldStyle); 179 return digestWorkaround ? 180 entry.digestWorkaround(digester) : 181 entry.digest(digester); 182 } 183 test(byte[] originalManifest, boolean mainAttsProperlyDelimited, boolean sectionProperlyDelimited)184 void test(byte[] originalManifest, boolean mainAttsProperlyDelimited, 185 boolean sectionProperlyDelimited) throws Exception { 186 Utils.echoManifest(originalManifest, "original manifest"); 187 byte[] reproducedManifest = reproduceRawManifest(originalManifest, 188 mainAttsProperlyDelimited, sectionProperlyDelimited); 189 Utils.echoManifest(reproducedManifest, "reproduced manifest"); 190 191 // The reproduced manifest is not necessarily completely identical to 192 // the original if it contained superfluous blank lines. 193 // It's sufficient that the digests are equal and as an additional 194 // check, the reproduced manifest is here compared to the original 195 // without more than double line breaks. 196 if (!lineBreak.repeat(2).equals(new String(originalManifest, UTF_8))) { 197 assertEquals( 198 new String(reproducedManifest, UTF_8), 199 new String(originalManifest, UTF_8).replaceAll( 200 regExscape(lineBreak) + "(" + regExscape(lineBreak) + ")+", 201 lineBreak.repeat(2))); 202 } 203 204 // compare digests of reproduced manifest entries with digests of 205 // original manifest entries 206 assertEquals(digest(originalManifest, null), 207 digest(reproducedManifest, null)); 208 for (String key : new Manifest(new ByteArrayInputStream( 209 originalManifest)).getEntries().keySet()) { 210 assertEquals(digest(originalManifest, key), 211 digest(reproducedManifest, key)); 212 } 213 214 // parse and compare original and reproduced manifests as manifests 215 assertEquals(new Manifest(new ByteArrayInputStream(originalManifest)), 216 new Manifest(new ByteArrayInputStream(reproducedManifest))); 217 } 218 test(byte[] originalManifest, boolean mainAttsProperlyDelimited)219 void test(byte[] originalManifest, boolean mainAttsProperlyDelimited) 220 throws Exception { 221 // assert all individual sections properly delimited particularly useful 222 // when no individual sections present 223 test(originalManifest, mainAttsProperlyDelimited, true); 224 } 225 226 @Test testManifestStartsWithBlankLine()227 public void testManifestStartsWithBlankLine() throws Exception { 228 test(lineBreak.getBytes(UTF_8), true); 229 test(lineBreak.repeat(2).getBytes(UTF_8), true); 230 } 231 232 @Test testEOFAndNoLineBreakAfterMainAttributes()233 public void testEOFAndNoLineBreakAfterMainAttributes() throws Exception { 234 assertThrows(RuntimeException.class, () -> 235 test("Manifest-Version: 1.0".getBytes(UTF_8), false) 236 ); 237 } 238 239 @Test testEOFAndNoBlankLineAfterMainAttributes()240 public void testEOFAndNoBlankLineAfterMainAttributes() throws Exception { 241 test(("Manifest-Version: 1.0" + lineBreak).getBytes(UTF_8), false); 242 } 243 244 @Test testNormalMainAttributes()245 public void testNormalMainAttributes() throws Exception { 246 test(("Manifest-Version: 1.0" + 247 lineBreak.repeat(2)).getBytes(UTF_8), true); 248 } 249 250 @Test testExtraLineBreakAfterMainAttributes()251 public void testExtraLineBreakAfterMainAttributes() throws Exception { 252 test(("Manifest-Version: 1.0" + 253 lineBreak.repeat(3)).getBytes(UTF_8), true); 254 } 255 256 @Test testIndividualSectionNoLineBreak()257 public void testIndividualSectionNoLineBreak() throws Exception { 258 assertNull(new ManifestDigester(( 259 "Manifest-Version: 1.0" + lineBreak + 260 lineBreak + 261 "Name: Section-Name" + lineBreak + 262 "Key: Value" 263 ).getBytes(UTF_8)).get("Section-Name")); 264 } 265 266 @Test testIndividualSectionOneLineBreak()267 public void testIndividualSectionOneLineBreak() throws Exception { 268 test(( 269 "Manifest-Version: 1.0" + lineBreak + 270 lineBreak + 271 "Name: Section-Name" + lineBreak + 272 "Key: Value" + lineBreak 273 ).getBytes(UTF_8), true, false); 274 } 275 276 @Test testNormalIndividualSectionTwoLineBreak()277 public void testNormalIndividualSectionTwoLineBreak() throws Exception { 278 test(( 279 "Manifest-Version: 1.0" + lineBreak + 280 lineBreak + 281 "Name: Section-Name" + lineBreak + 282 "Key: Value" + lineBreak.repeat(2) 283 ).getBytes(UTF_8), true, true); 284 } 285 286 @Test testExtraLineBreakAfterIndividualSection()287 public void testExtraLineBreakAfterIndividualSection() throws Exception { 288 test(( 289 "Manifest-Version: 1.0" + lineBreak + 290 lineBreak + 291 "Name: Section-Name" + lineBreak + 292 "Key: Value" + lineBreak.repeat(3) 293 ).getBytes(UTF_8), true, true); 294 } 295 296 @Test testIndividualSections()297 public void testIndividualSections() throws Exception { 298 test(( 299 "Manifest-Version: 1.0" + lineBreak + 300 lineBreak + 301 "Name: Section-Name" + lineBreak + 302 "Key: Value" + lineBreak + 303 lineBreak + 304 "Name: Section-Name" + lineBreak + 305 "Key: Value" + lineBreak + 306 lineBreak 307 ).getBytes(UTF_8), true, true); 308 } 309 310 @Test testExtraLineBreakBetweenIndividualSections()311 public void testExtraLineBreakBetweenIndividualSections() throws Exception { 312 test(( 313 "Manifest-Version: 1.0" + lineBreak + 314 lineBreak + 315 "Name: Section-Name" + lineBreak + 316 "Key: Value" + lineBreak + 317 lineBreak.repeat(2) + 318 "Name: Section-Name" + lineBreak + 319 "Key: Value" + lineBreak + 320 lineBreak 321 ).getBytes(UTF_8), true, true); 322 } 323 324 } 325