Skip to content

Commit 673430b

Browse files
committed
refactor into separate projects and improve zip examination
1 parent a3ed717 commit 673430b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1290
-403
lines changed

crxtool/pom.xml renamed to crxtool-core/pom.xml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
<artifactId>crxtool-parent</artifactId>
99
<version>0.4-SNAPSHOT</version>
1010
</parent>
11-
<artifactId>crxtool</artifactId>
12-
<name>crxtool</name>
11+
<artifactId>crxtool-core</artifactId>
12+
<name>crxtool-core</name>
1313
<description>Library for Chrome extension file analysis</description>
1414
<dependencies>
1515
<dependency>
@@ -65,5 +65,11 @@
6565
<artifactId>nanohttpd-server</artifactId>
6666
<scope>test</scope>
6767
</dependency>
68+
<dependency>
69+
<groupId>${project.groupId}</groupId>
70+
<artifactId>crxtool-testing</artifactId>
71+
<version>${project.version}</version>
72+
<scope>test</scope>
73+
</dependency>
6874
</dependencies>
6975
</project>

crxtool/src/main/java/io/github/mike10004/crxtool/BasicCrxParser.java renamed to crxtool-core/src/main/java/io/github/mike10004/crxtool/BasicCrxParser.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,28 @@ public class BasicCrxParser implements CrxParser {
3434
*/
3535
public BasicCrxParser() {}
3636

37+
private void checkMagicNumber(String magicNumber) throws CrxParsingException {
38+
if (!"Cr24".equals(magicNumber)) {
39+
throw new CrxParsingException("incorrect magic number: " + magicNumber);
40+
}
41+
}
42+
3743
@Override
3844
public CrxMetadata parseMetadata(InputStream crxInput) throws IOException {
3945
LittleEndianDataInputStream in = new LittleEndianDataInputStream(crxInput);
4046
byte[] magicNumberBytes = new byte[4];
4147
in.readFully(magicNumberBytes);
4248
String magicNumber = new String(magicNumberBytes, StandardCharsets.US_ASCII);
49+
checkMagicNumber(magicNumber);
4350
int version = Ints.checkedCast(UnsignedInteger.fromIntBits(in.readInt()).longValue());
4451
int pubkeyLength = Ints.checkedCast(UnsignedInteger.fromIntBits(in.readInt()).longValue());
4552
int signatureLength = Ints.checkedCast(UnsignedInteger.fromIntBits(in.readInt()).longValue());
46-
checkState(pubkeyLength <= MAX_SANE_PUBKEY_LENGTH, "public key length is insane: %s", pubkeyLength);
47-
checkState(signatureLength <= MAX_SANE_SIGNATURE_LENGTH, "signature length is insane: %s", signatureLength);
53+
if (pubkeyLength <= 0 || pubkeyLength > MAX_SANE_PUBKEY_LENGTH) {
54+
throw new CrxParsingException(String.format("public key length is insane: %s", pubkeyLength));
55+
}
56+
if (signatureLength <= 0 || signatureLength > MAX_SANE_SIGNATURE_LENGTH) {
57+
throw new CrxParsingException(String.format("signature length is insane: %s", signatureLength));
58+
}
4859
byte[] pubkeyBytes = new byte[pubkeyLength];
4960
ByteStreams.readFully(crxInput, pubkeyBytes);
5061
byte[] signatureBytes = new byte[signatureLength];

crxtool/src/main/java/io/github/mike10004/crxtool/CrxPacker.java renamed to crxtool-core/src/main/java/io/github/mike10004/crxtool/CrxPacker.java

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import com.google.common.io.ByteSource;
44

55
import javax.annotation.Nullable;
6-
import java.io.ByteArrayOutputStream;
76
import java.io.IOException;
87
import java.io.OutputStream;
98
import java.nio.file.Path;
@@ -13,7 +12,6 @@
1312
import java.security.PrivateKey;
1413
import java.security.Signature;
1514
import java.security.SignatureException;
16-
import java.util.zip.ZipOutputStream;
1715

1816
/**
1917
* Interface defining methods to pack Chrome extensions.
@@ -46,22 +44,7 @@ default void packExtension(Path extensionDir, KeyPair keyPair, OutputStream outp
4644
* @throws SignatureException if thrown by {@link java.security.Signature#update(byte[])} or {@link Signature#sign()}
4745
*/
4846
default void packExtension(Path extensionDir, @Nullable ZipConfig zipConfig, KeyPair keyPair, OutputStream output) throws IOException, NoSuchAlgorithmException, InvalidKeyException, SignatureException {
49-
ByteArrayOutputStream zipBuffer = new ByteArrayOutputStream(1024);
50-
try (ZipOutputStream zipOutputStream = new ZipOutputStream(zipBuffer)) {
51-
java.nio.file.Files.walkFileTree(extensionDir, new ZippingFileVisitor(extensionDir, zipOutputStream));
52-
if (zipConfig != null) {
53-
if (zipConfig.comment != null) {
54-
zipOutputStream.setComment(zipConfig.comment);
55-
}
56-
if (zipConfig.method != null) {
57-
zipOutputStream.setMethod(zipConfig.method);
58-
}
59-
if (zipConfig.level != null) {
60-
zipOutputStream.setLevel(zipConfig.level);
61-
}
62-
}
63-
}
64-
byte[] zipBytes = zipBuffer.toByteArray();
47+
byte[] zipBytes = Zipping.zipDirectory(extensionDir, zipConfig);
6548
packExtension(ByteSource.wrap(zipBytes), keyPair, output);
6649
}
6750

crxtool/src/main/java/io/github/mike10004/crxtool/CrxParser.java renamed to crxtool-core/src/main/java/io/github/mike10004/crxtool/CrxParser.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,19 @@ public interface CrxParser {
2323
static CrxParser getDefault() {
2424
return BasicCrxParser.getDefaultInstance();
2525
}
26+
27+
@SuppressWarnings("unused")
28+
class CrxParsingException extends IOException {
29+
public CrxParsingException(String message) {
30+
super(message);
31+
}
32+
33+
public CrxParsingException(String message, Throwable cause) {
34+
super(message, cause);
35+
}
36+
37+
public CrxParsingException(Throwable cause) {
38+
super(cause);
39+
}
40+
}
2641
}

crxtool/src/main/java/io/github/mike10004/crxtool/KeyPairs.java renamed to crxtool-core/src/main/java/io/github/mike10004/crxtool/KeyPairs.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,11 @@ static PublicKey extractPublicKey(PrivateKey privateKey) throws NoSuchAlgorithmE
4848
return publicKey;
4949
}
5050

51+
52+
// https://stackoverflow.com/a/24689684/2657036
53+
public static KeyPair generateRsKeyPair(SecureRandom random) throws NoSuchAlgorithmException {
54+
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
55+
keyGen.initialize(1024, random);
56+
return keyGen.generateKeyPair();
57+
}
5158
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package io.github.mike10004.crxtool;
2+
3+
import javax.annotation.Nullable;
4+
import java.io.ByteArrayOutputStream;
5+
import java.io.IOException;
6+
import java.nio.file.Path;
7+
import java.util.zip.ZipOutputStream;
8+
9+
public class Zipping {
10+
11+
private Zipping() {}
12+
13+
14+
public static byte[] zipDirectory(Path extensionDir, @Nullable ZipConfig zipConfig) throws IOException {
15+
ByteArrayOutputStream zipBuffer = new ByteArrayOutputStream(1024);
16+
try (ZipOutputStream zipOutputStream = new ZipOutputStream(zipBuffer)) {
17+
java.nio.file.Files.walkFileTree(extensionDir, new ZippingFileVisitor(extensionDir, zipOutputStream));
18+
if (zipConfig != null) {
19+
if (zipConfig.comment != null) {
20+
zipOutputStream.setComment(zipConfig.comment);
21+
}
22+
if (zipConfig.method != null) {
23+
zipOutputStream.setMethod(zipConfig.method);
24+
}
25+
if (zipConfig.level != null) {
26+
zipOutputStream.setLevel(zipConfig.level);
27+
}
28+
}
29+
}
30+
byte[] zipBytes = zipBuffer.toByteArray();
31+
return zipBytes;
32+
}
33+
}

crxtool/src/test/java/io/github/mike10004/crxtool/BasicCrxPackerTest.java renamed to crxtool-core/src/test/java/io/github/mike10004/crxtool/BasicCrxPackerTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.google.common.io.CountingOutputStream;
66
import com.google.common.io.Files;
77
import com.google.common.io.LittleEndianDataOutputStream;
8+
import io.github.mike10004.crxtool.testing.Unzippage;
89
import org.junit.Rule;
910
import org.junit.Test;
1011
import org.junit.rules.TemporaryFolder;
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package io.github.mike10004.crxtool;
2+
3+
import com.google.common.io.ByteSource;
4+
import com.google.common.io.Resources;
5+
import io.github.mike10004.crxtool.CrxParser.CrxParsingException;
6+
import io.github.mike10004.crxtool.testing.Unzippage;
7+
import org.junit.Rule;
8+
import org.junit.Test;
9+
import org.junit.rules.TemporaryFolder;
10+
11+
import java.io.ByteArrayInputStream;
12+
import java.io.EOFException;
13+
import java.io.File;
14+
import java.io.FileInputStream;
15+
import java.io.IOException;
16+
import java.io.InputStream;
17+
import java.net.URL;
18+
import java.nio.charset.StandardCharsets;
19+
import java.nio.file.Files;
20+
import java.util.Arrays;
21+
import java.util.Random;
22+
23+
import static org.junit.Assert.assertArrayEquals;
24+
import static org.junit.Assert.assertEquals;
25+
import static org.junit.Assert.assertNotNull;
26+
27+
public class BasicCrxParserTest {
28+
29+
@Rule
30+
public TemporaryFolder temporaryFolder = new TemporaryFolder();
31+
32+
private Random random = new Random(getClass().getName().hashCode());
33+
34+
@Test
35+
public void parseMetadata() throws Exception {
36+
BasicCrxParser parser = new BasicCrxParser();
37+
CrxMetadata metadata;
38+
Unzippage unzippage;
39+
try (InputStream in = Tests.getMakePageRedCrxResource().openStream()) {
40+
metadata = parser.parseMetadata(in);
41+
unzippage = Unzippage.unzip(in);
42+
}
43+
System.out.format("headerLength=%s%nid=%s%npubkey=%s%nsignature=%s%n", metadata.headerLength(), metadata.id, metadata.pubkeyBase64, metadata.signatureBase64);
44+
assertEquals("id", "dnogaomdbgfngjgalaoggcfahgeibfdc", metadata.id);
45+
unzippage.allEntries().forEach(entry -> {
46+
System.out.format("%s%n", entry);
47+
});
48+
for (String filename : Arrays.asList("background.js", "manifest.json")) {
49+
ByteSource entryBytes = unzippage.getFileBytes(filename);
50+
assertNotNull("entry bytes present expected", entryBytes);
51+
URL reference = getClass().getResource("/make_page_red/" + filename);
52+
byte[] actualBytes = entryBytes.read();
53+
byte[] expectedBytes = Resources.toByteArray(reference);
54+
assertArrayEquals("file bytes", expectedBytes, actualBytes);
55+
}
56+
}
57+
58+
@Test(expected = CrxParsingException.class)
59+
public void parseMetadata_randomBytes() throws Exception {
60+
byte[] bytes = new byte[10 * 1024];
61+
random.nextBytes(bytes);
62+
try (InputStream in = new ByteArrayInputStream(bytes)) {
63+
new BasicCrxParser().parseMetadata(in);
64+
}
65+
}
66+
67+
@Test(expected = CrxParsingException.class)
68+
public void parseMetadata_zipFile() throws Exception {
69+
File zipFile = createRandomZipFile();
70+
try (InputStream in = new FileInputStream(zipFile)) {
71+
new BasicCrxParser().parseMetadata(in);
72+
}
73+
}
74+
75+
@Test(expected = EOFException.class)
76+
public void parseMetadata_prematureEof() throws Exception {
77+
byte[] bytes = "Cr24".getBytes(StandardCharsets.US_ASCII);
78+
new BasicCrxParser().parseMetadata(new ByteArrayInputStream(bytes));
79+
}
80+
81+
@Test(expected = CrxParsingException.class)
82+
public void parseMetadata_blankAfterMagicNumber() throws Exception {
83+
byte[] magic = "Cr24".getBytes(StandardCharsets.US_ASCII);
84+
byte[] bytes = new byte[10 * 1024];
85+
System.arraycopy(magic, 0, bytes, 0, magic.length);
86+
CrxMetadata md = new BasicCrxParser().parseMetadata(new ByteArrayInputStream(bytes));
87+
System.out.println(md);
88+
}
89+
90+
private File createRandomZipFile() throws IOException {
91+
File root = temporaryFolder.newFolder();
92+
File file1 = new File(root, "file1.dat");
93+
byte[] bytes1 = new byte[1024];
94+
File file2 = new File(root, "file2.dat");
95+
byte[] bytes2 = new byte[10 * 1024];
96+
97+
random.nextBytes(bytes1);
98+
random.nextBytes(bytes2);
99+
Files.write(file1.toPath(), bytes1);
100+
Files.write(file2.toPath(), bytes2);
101+
byte[] zipData = Zipping.zipDirectory(root.toPath(), null);
102+
File zipFile = temporaryFolder.newFile();
103+
Files.write(zipFile.toPath(), zipData);
104+
return zipFile;
105+
}
106+
107+
}

crxtool/src/test/java/io/github/mike10004/crxtool/Tests.java renamed to crxtool-core/src/test/java/io/github/mike10004/crxtool/Tests.java

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import java.nio.file.Path;
2020
import java.nio.file.attribute.BasicFileAttributes;
2121
import java.security.KeyPair;
22-
import java.security.KeyPairGenerator;
2322
import java.security.NoSuchAlgorithmException;
2423
import java.security.SecureRandom;
2524
import java.util.ArrayList;
@@ -79,6 +78,12 @@ public static File getAddFooterManifestFile() {
7978
}
8079
}
8180

81+
public static KeyPair generateRsaKeyPair(long seed) throws NoSuchAlgorithmException {
82+
byte[] seedBytes = Longs.toByteArray(seed);
83+
SecureRandom random = new SecureRandom(seedBytes);
84+
return KeyPairs.generateRsKeyPair(random);
85+
}
86+
8287
public static class DirDiff {
8388
public final ImmutableMap<String, Optional<ByteSource>> referenceOnly;
8489
public final ImmutableMap<String, Optional<ByteSource>> queryOnly;
@@ -203,13 +208,4 @@ protected void visitFile(Path referenceFile, Path queryFile) {
203208
return new DirDiff(referenceOnly, queryOnly, differents);
204209
}
205210

206-
// https://stackoverflow.com/a/24689684/2657036
207-
public static KeyPair generateRsaKeyPair(long seed) throws NoSuchAlgorithmException {
208-
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
209-
byte[] seedBytes = Longs.toByteArray(seed);
210-
SecureRandom random = new SecureRandom(seedBytes);
211-
keyGen.initialize(1024, random);
212-
return keyGen.generateKeyPair();
213-
}
214-
215211
}

crxtool/src/test/java/io/github/mike10004/crxtool/ZippingFileVisitorTest.java renamed to crxtool-core/src/test/java/io/github/mike10004/crxtool/ZippingFileVisitorTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.google.common.collect.ImmutableSet;
44
import com.google.common.io.ByteSource;
55
import com.google.common.io.Files;
6+
import io.github.mike10004.crxtool.testing.Unzippage;
67
import org.junit.Rule;
78
import org.junit.Test;
89
import org.junit.rules.TemporaryFolder;

crxtool-maven-plugin/pom.xml

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
3+
<parent>
4+
<artifactId>crxtool-parent</artifactId>
5+
<groupId>com.github.mike10004</groupId>
6+
<version>0.4-SNAPSHOT</version>
7+
</parent>
8+
<modelVersion>4.0.0</modelVersion>
9+
<artifactId>crxtool-maven-plugin</artifactId>
10+
<packaging>maven-plugin</packaging>
11+
<name>crxtool-maven-plugin Maven Mojo</name>
12+
<url>http://maven.apache.org</url>
13+
<build>
14+
<plugins>
15+
<!-- creates the plugin.xml descriptor resource -->
16+
<plugin>
17+
<groupId>org.apache.maven.plugins</groupId>
18+
<artifactId>maven-plugin-plugin</artifactId>
19+
<version>3.5</version>
20+
</plugin>
21+
</plugins>
22+
</build>
23+
<dependencies>
24+
<dependency>
25+
<groupId>org.apache.maven</groupId>
26+
<artifactId>maven-plugin-api</artifactId>
27+
<version>3.5.2</version>
28+
</dependency>
29+
<dependency>
30+
<groupId>org.apache.maven</groupId>
31+
<artifactId>maven-project</artifactId>
32+
<version>2.2.1</version>
33+
</dependency>
34+
<dependency>
35+
<groupId>org.apache.maven</groupId>
36+
<artifactId>maven-core</artifactId>
37+
<version>3.5.2</version>
38+
</dependency>
39+
<dependency>
40+
<groupId>junit</groupId>
41+
<artifactId>junit</artifactId>
42+
<scope>test</scope>
43+
</dependency>
44+
<dependency>
45+
<groupId>org.apache.maven.plugin-tools</groupId>
46+
<artifactId>maven-plugin-annotations</artifactId>
47+
<version>3.5</version>
48+
<scope>provided</scope><!-- annotations are needed only to build the plugin -->
49+
</dependency>
50+
<dependency>
51+
<groupId>${project.groupId}</groupId>
52+
<artifactId>crxtool-core</artifactId>
53+
<version>${project.version}</version>
54+
</dependency>
55+
<dependency>
56+
<groupId>com.github.mike10004</groupId>
57+
<artifactId>crxtool-testing</artifactId>
58+
<version>0.4-SNAPSHOT</version>
59+
<scope>test</scope>
60+
</dependency>
61+
</dependencies>
62+
</project>

0 commit comments

Comments
 (0)