Skip to content

Commit c129ce4

Browse files
eirbjowangweij
authored andcommitted
8300259: Add test coverage for processing of pending block files in signed JARs
Reviewed-by: weijun
1 parent b569742 commit c129ce4

File tree

1 file changed

+173
-0
lines changed

1 file changed

+173
-0
lines changed
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/*
2+
* Copyright (c) 2023, 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+
/**
25+
* @test
26+
* @modules java.base/sun.security.tools.keytool
27+
* @summary JARs with pending block files (where .RSA comes before .SF) should verify correctly
28+
*/
29+
30+
import jdk.security.jarsigner.JarSigner;
31+
32+
import java.io.File;
33+
import java.io.InputStream;
34+
import java.io.OutputStream;
35+
import java.nio.charset.StandardCharsets;
36+
import java.nio.file.Files;
37+
import java.nio.file.Path;
38+
import java.security.KeyStore;
39+
import java.util.Collections;
40+
import java.util.jar.*;
41+
import java.util.zip.ZipEntry;
42+
import java.util.zip.ZipFile;
43+
import java.util.zip.ZipOutputStream;
44+
45+
public class SignedJarPendingBlock {
46+
47+
public static void main(String[] args) throws Exception {
48+
Path jar = createJarFile();
49+
Path signed = signJarFile(jar);
50+
Path pendingBlocks = moveBlockFirst(signed);
51+
Path invalid = invalidate(pendingBlocks);
52+
53+
// 1: Regular signed JAR with no pending blocks should verify
54+
checkSigned(signed);
55+
56+
// 2: Signed jar with pending blocks should verify
57+
checkSigned(pendingBlocks);
58+
59+
// 3: Invalid signed jar with pending blocks should throw SecurityException
60+
try {
61+
checkSigned(invalid);
62+
throw new Exception("Expected invalid digest to be detected");
63+
} catch (SecurityException se) {
64+
// Ignore
65+
}
66+
}
67+
68+
private static void checkSigned(Path b) throws Exception {
69+
try (JarFile jf = new JarFile(b.toFile(), true)) {
70+
71+
JarEntry je = jf.getJarEntry("a.txt");
72+
try (InputStream in = jf.getInputStream(je)) {
73+
in.transferTo(OutputStream.nullOutputStream());
74+
}
75+
}
76+
}
77+
78+
/**
79+
* Invalidate signed file by modifying the contents of "a.txt"
80+
*/
81+
private static Path invalidate(Path s) throws Exception{
82+
Path invalid = Path.of("pending-block-file-invalidated.jar");
83+
84+
try (ZipFile zip = new ZipFile(s.toFile());
85+
ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(invalid))) {
86+
87+
for (ZipEntry ze : Collections.list(zip.entries())) {
88+
String name = ze.getName();
89+
out.putNextEntry(new ZipEntry(name));
90+
91+
if (name.equals("a.txt")) {
92+
// Change the contents of a.txt to trigger SignatureException
93+
out.write("b".getBytes(StandardCharsets.UTF_8));
94+
} else {
95+
try (InputStream in = zip.getInputStream(ze)) {
96+
in.transferTo(out);
97+
}
98+
}
99+
}
100+
}
101+
return invalid;
102+
}
103+
104+
private static Path moveBlockFirst(Path s) throws Exception {
105+
Path b = Path.of("pending-block-file-blockfirst.jar");
106+
try (ZipFile in = new ZipFile(s.toFile());
107+
ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(b))) {
108+
109+
copy("META-INF/MANIFEST.MF", in, out);
110+
111+
// Switch the order of the RSA and SF files
112+
copy("META-INF/SIGNER.RSA", in, out);
113+
copy("META-INF/SIGNER.SF", in, out);
114+
115+
copy("a.txt", in, out);
116+
}
117+
return b;
118+
}
119+
120+
/**
121+
* Copy an entry from a ZipFile to a ZipOutputStream
122+
*/
123+
private static void copy(String name, ZipFile in, ZipOutputStream out) throws Exception {
124+
out.putNextEntry(new ZipEntry(name));
125+
try (InputStream is = in.getInputStream(in.getEntry(name))) {
126+
is.transferTo(out);
127+
}
128+
}
129+
130+
private static Path signJarFile(Path j) throws Exception {
131+
Path s = Path.of("pending-block-file-signed.jar");
132+
133+
Files.deleteIfExists(Path.of("ks"));
134+
135+
sun.security.tools.keytool.Main.main(
136+
("-keystore ks -storepass changeit -keypass changeit -dname" +
137+
" CN=SIGNER" +" -alias r -genkeypair -keyalg rsa").split(" "));
138+
139+
char[] pass = "changeit".toCharArray();
140+
141+
KeyStore ks = KeyStore.getInstance(new File("ks"), pass);
142+
143+
KeyStore.PrivateKeyEntry pke = (KeyStore.PrivateKeyEntry)
144+
ks.getEntry("r", new KeyStore.PasswordProtection(pass));
145+
146+
JarSigner signer = new JarSigner.Builder(pke)
147+
.digestAlgorithm("SHA-256")
148+
.signatureAlgorithm("SHA256withRSA")
149+
.signerName("SIGNER")
150+
.build();
151+
152+
try (ZipFile in = new ZipFile(j.toFile());
153+
OutputStream out = Files.newOutputStream(s)) {
154+
signer.sign(in, out);
155+
}
156+
157+
return s;
158+
}
159+
160+
/**
161+
* Create a jar file with single entry "a.txt" containing "a"
162+
*/
163+
private static Path createJarFile() throws Exception {
164+
Path jar = Path.of("pending-block-file.jar");
165+
Manifest manifest = new Manifest();
166+
manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
167+
try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(jar),manifest)) {
168+
out.putNextEntry(new JarEntry("a.txt"));
169+
out.write("a".getBytes(StandardCharsets.UTF_8));
170+
}
171+
return jar;
172+
}
173+
}

0 commit comments

Comments
 (0)