View Javadoc
1   /*-
2    * #%L
3    * io.earcam.instrumental.archive.sign
4    * %%
5    * Copyright (C) 2018 earcam
6    * %%
7    * SPDX-License-Identifier: (BSD-3-Clause OR EPL-1.0 OR Apache-2.0 OR MIT)
8    * 
9    * You <b>must</b> choose to accept, in full - any individual or combination of 
10   * the following licenses:
11   * <ul>
12   * 	<li><a href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause</a></li>
13   * 	<li><a href="https://www.eclipse.org/legal/epl-v10.html">EPL-1.0</a></li>
14   * 	<li><a href="https://www.apache.org/licenses/LICENSE-2.0">Apache-2.0</a></li>
15   * 	<li><a href="https://opensource.org/licenses/MIT">MIT</a></li>
16   * </ul>
17   * #L%
18   */
19  package io.earcam.instrumental.archive.sign;
20  
21  //EARCAM_SNIPPET_BEGIN: imports
22  import static io.earcam.instrumental.archive.Archive.archive;
23  import static io.earcam.instrumental.archive.AsJar.asJar;
24  import static io.earcam.instrumental.archive.sign.StandardDigestAlgorithms.SHA512;
25  import static io.earcam.instrumental.archive.sign.StandardSignatureAlgorithms.SHA256_WITH_RSA;
26  import static io.earcam.instrumental.archive.sign.WithDigest.withDigest;
27  import static io.earcam.instrumental.archive.sign.WithSignature.withSignature;
28  import static io.earcam.utilitarian.security.Certificates.certificate;
29  import static io.earcam.utilitarian.security.KeyStores.keyStore;
30  //EARCAM_SNIPPET_END: imports
31  import static org.hamcrest.MatcherAssert.assertThat;
32  import static org.hamcrest.Matchers.*;
33  
34  import java.net.URLClassLoader;
35  import java.nio.file.Path;
36  import java.nio.file.Paths;
37  import java.security.KeyPair;
38  import java.security.KeyStore;
39  import java.security.cert.X509Certificate;
40  import java.util.Arrays;
41  import java.util.List;
42  import java.util.UUID;
43  
44  import org.junit.jupiter.api.BeforeAll;
45  import org.junit.jupiter.api.Test;
46  
47  import io.earcam.acme.AmSigned;
48  import io.earcam.acme.in.intentionally.very.lengthy.packagename.safe72.ok.AmAlsoSigned;
49  import io.earcam.instrumental.archive.ArchiveConstruction;
50  import io.earcam.instrumental.lade.ClassLoaders;
51  import io.earcam.instrumental.lade.InMemoryClassLoader;
52  import io.earcam.utilitarian.io.IoStreams;
53  import io.earcam.utilitarian.security.Keys;
54  import io.earcam.utilitarian.security.Signatures;
55  
56  /**
57   * /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/bin/jarsigner -J-Djava.security.debug=jar
58   * -verbose:all -certs -sigalg SHA256withRSA -sigfile SIGNATURE -keystore /tmp/foo.ks -verify /tmp/foo-signed.jar
59   *
60   * /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/bin/jarsigner -verbose:all -signedjar
61   * /tmp/foo-signed.jar -sigalg SHA256withRSA -sigfile SIG -keystore /tmp/foo.ks /tmp/foo-unsigned.jar alias
62   * 
63   * openssl asn1parse -in ./META-INF/SIGNATURE.RSA -inform der -i
64   * 
65   */
66  public class WithSignatureTest {
67  
68  	private static final Path TEST_DIR = Paths.get(".", "target", "test", UUID.randomUUID().toString());
69  
70  	// EARCAM_SNIPPET_BEGIN: fields
71  	private static final KeyPair keys = Keys.rsa();
72  
73  	private static final String alias = "alias";
74  	private static final char[] password = "password".toCharArray();
75  	private static final String subject = "subject";
76  
77  	private static final X509Certificate certificate = certificate(keys, subject).toX509();
78  	private static final KeyStore keyStore = keyStore(alias, password, keys, certificate);
79  	// EARCAM_SNIPPET_END: fields
80  
81  
82  	@BeforeAll
83  	public static void initialize()
84  	{
85  		TEST_DIR.toFile().mkdirs();
86  	}
87  
88  
89  	@Test
90  	public void amSignedOnFilesystem() throws Exception
91  	{
92  		Path file = TEST_DIR.resolve("am-signed.jar");
93  
94  		createSignedJar().to(file);
95  
96  		try(URLClassLoader loader = ClassLoaders.selfFirstClassLoader(file)) {
97  			Class<?> loaded = loader.loadClass(cn(AmAlsoSigned.class));
98  
99  			Object[] signers = loaded.getSigners();
100 
101 			assertThat(signers, is(not(nullValue())));
102 			assertThat(signers, is(not(emptyArray())));
103 
104 			assertThat(Arrays.toString(signers), hasToString(allOf(
105 					containsString("CN=acme"),
106 					containsString("CN=subject"),
107 					containsStringIgnoringCase("Signature Algorithm: SHA256withRSA"))));
108 
109 			byte[] bytes = IoStreams.readAllBytes(loaded.getResourceAsStream("/META-INF/SIG.RSA"));
110 			List<X509Certificate> certificates = Signatures.certificatesFromSignature(bytes);
111 
112 			assertThat(certificates, contains(certificate));
113 		}
114 	}
115 
116 
117 	@Test
118 	public void amSignedInMemory() throws Exception
119 	{
120 		byte[] jarBytes = createSignedJar().toByteArray();
121 
122 		try(InMemoryClassLoader loader = ClassLoaders.inMemoryClassLoader().jar(jarBytes)) {
123 			Class<?> loaded = loader.loadClass(cn(AmAlsoSigned.class));
124 
125 			Object[] signers = loaded.getSigners();
126 
127 			assertThat(signers, is(not(nullValue())));
128 			assertThat(signers, is(not(emptyArray())));
129 
130 			assertThat(Arrays.toString(signers), hasToString(allOf(
131 					containsString("CN=acme"),
132 					containsString("CN=subject"),
133 					containsStringIgnoringCase("Signature Algorithm: SHA256withRSA"))));
134 
135 			byte[] bytes = IoStreams.readAllBytes(loader.getResourceAsStream("META-INF/SIG.RSA"));
136 			List<X509Certificate> certificates = Signatures.certificatesFromSignature(bytes);
137 
138 			assertThat(certificates, contains(certificate));
139 		}
140 	}
141 
142 
143 	// EARCAM_SNIPPET_BEGIN: method
144 	private ArchiveConstruction createSignedJar()
145 	{
146 		return archive()
147 				.configured(asJar())
148 				.configured(withSignature()
149 						.digestedBy(SHA512)
150 						.store(keyStore)
151 						.alias(alias)
152 						.password(password)
153 						.signatureAlgorithm(SHA256_WITH_RSA)
154 						.signatureFileName("SIG")
155 						.createdBy("necessity"))
156 				.with(AmSigned.class)
157 				.with(AmAlsoSigned.class);
158 	}
159 	// EARCAM_SNIPPET_END: method
160 
161 
162 	private static String cn(Class<?> type)
163 	{
164 		return type.getTypeName();
165 	}
166 
167 
168 	@Test
169 	public void amNotSignedInMemory() throws Exception
170 	{
171 		byte[] jarBytes = createDigestedButNotSignedJar().toByteArray();
172 
173 		try(InMemoryClassLoader loader = ClassLoaders.inMemoryClassLoader().jar(jarBytes)) {
174 			Class<?> loaded = loader.loadClass(cn(AmAlsoSigned.class));
175 
176 			assertThat(loaded.getSigners(), is(nullValue()));
177 		}
178 	}
179 
180 
181 	private ArchiveConstruction createDigestedButNotSignedJar()
182 	{
183 		return archive()
184 				.configured(withDigest()
185 						.digestedBy(SHA512))
186 				.with(AmSigned.class)
187 				.with(AmAlsoSigned.class);
188 	}
189 
190 
191 	@Test
192 	public void amNotSignedOnFilesystem() throws Exception
193 	{
194 		Path file = TEST_DIR.resolve("am-not-signed.jar");
195 		createDigestedButNotSignedJar().to(file);
196 		try(URLClassLoader loader = ClassLoaders.selfFirstClassLoader(file)) {
197 			Class<?> loaded = loader.loadClass(cn(AmAlsoSigned.class));
198 
199 			assertThat(loaded.getSigners(), is(nullValue()));
200 		}
201 	}
202 }