1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package io.earcam.instrumental.archive.sign;
20
21 import static io.earcam.instrumental.archive.AbstractAsJarBuilder.CREATED_BY;
22 import static io.earcam.instrumental.archive.AbstractAsJarBuilder.V1_0;
23 import static io.earcam.instrumental.archive.ArchiveResourceSource.ResourceSourceLifecycle.FINAL;
24 import static java.nio.charset.StandardCharsets.UTF_8;
25 import static java.util.jar.Attributes.Name.SIGNATURE_VERSION;
26
27 import java.io.ByteArrayOutputStream;
28 import java.security.KeyStore;
29 import java.security.MessageDigest;
30 import java.util.jar.Attributes;
31 import java.util.jar.Attributes.Name;
32 import java.util.jar.Manifest;
33 import java.util.stream.Stream;
34
35 import io.earcam.instrumental.archive.ArchiveConfigurationPlugin;
36 import io.earcam.instrumental.archive.ArchiveRegistrar;
37 import io.earcam.instrumental.archive.ArchiveResource;
38 import io.earcam.instrumental.archive.ArchiveResourceListener;
39 import io.earcam.instrumental.archive.ArchiveResourceSource;
40 import io.earcam.instrumental.archive.ManifestProcessor;
41 import io.earcam.unexceptional.Closing;
42 import io.earcam.utilitarian.security.OpenedKeyStore;
43 import io.earcam.utilitarian.security.Signatures;
44
45
46
47
48
49
50
51
52 public class WithSignature extends WithDigest implements ArchiveConfigurationPlugin, ManifestProcessor, ArchiveResourceSource, ArchiveResourceListener {
53
54 private static final String META_INF = "META-INF/";
55
56 private static final String DEFAULT_SIGNATURE_FILENAME = "SIGNATURE";
57 private static final String DEFAULT_SIGNATURE_ALGORITHM = "SHA512withRSA";
58
59 private KeyStore keyStore;
60 private String keyAlias;
61 private char[] keyPassword;
62 private String signatureFilename = DEFAULT_SIGNATURE_FILENAME;
63 private String signatureAlgorithm = DEFAULT_SIGNATURE_ALGORITHM;
64 private Manifest signatures = new Manifest();
65 private String createdBy = WithSignature.class.getPackage().getName();
66
67
68 WithSignature()
69 {}
70
71
72
73
74
75
76
77
78
79 public static WithSignature withSignature()
80 {
81 return new WithSignature();
82 }
83
84
85 @Override
86 public void added(ArchiveResource resource)
87 {
88 if(!resource.name().startsWith(META_INF + signatureFilename + '.')) {
89 super.added(resource);
90 }
91 }
92
93
94 @Override
95 public Stream<ArchiveResource> drain(ResourceSourceLifecycle stage)
96 {
97 if(stage == FINAL) {
98 byte[] sign = sign();
99 ArchiveResource sf = new ArchiveResource(META_INF + signatureFilename + ".SF", sign);
100 OpenedKeyStore openedKeyStore = new OpenedKeyStore(keyStore, keyAlias, keyPassword);
101 ArchiveResource cert = new ArchiveResource(META_INF + signatureFilename + ".RSA", Signatures.sign(sign, openedKeyStore, signatureAlgorithm));
102 return Stream.of(sf, cert);
103 }
104 return Stream.empty();
105 }
106
107
108 private byte[] sign()
109 {
110 Attributes mainAttributes = signatures.getMainAttributes();
111 mainAttributes.put(SIGNATURE_VERSION, V1_0);
112 mainAttributes.put(new Name(CREATED_BY), createdBy);
113 mainAttributes.put(new Name(digester.getAlgorithm() + "-Digest-Manifest"), manifestDigest());
114
115 mainAttributes.put(new Name(digester.getAlgorithm() + "-Digest-Manifest-Main-Attributes"), manifestMainDigest());
116
117 manifest.getEntries().keySet().forEach(this::addSigned);
118
119 ByteArrayOutputStream out = new ByteArrayOutputStream();
120 Closing.closeAfterAccepting(out, signatures::write);
121 return out.toByteArray();
122 }
123
124
125 private String manifestDigest()
126 {
127 ByteArrayOutputStream out = new ByteArrayOutputStream();
128 Closing.closeAfterAccepting(out, manifest::write);
129 return base64Digest(out.toByteArray());
130 }
131
132
133 private String manifestMainDigest()
134 {
135 StringBuilder main = new StringBuilder();
136 attributesAsString(manifest.getMainAttributes(), main);
137 return base64Digest(newline(main).toString().getBytes(UTF_8));
138 }
139
140
141 private void addSigned(String resourceName)
142 {
143 Attributes signature = new Attributes();
144 signatures.getEntries().put(resourceName, signature);
145
146 Attributes attributes = manifest.getAttributes(resourceName);
147
148 StringBuilder sig = make72Safe(newline(new StringBuilder("Name: ").append(resourceName)));
149 attributesAsString(attributes, sig);
150
151 String digested = base64Digest(sig.toString().getBytes(UTF_8));
152 signature.putValue(digester.getAlgorithm() + "-Digest", digested);
153 }
154
155
156 private static final StringBuilder newline(StringBuilder string)
157 {
158 return string.append("\r\n");
159 }
160
161
162 private void attributesAsString(Attributes attributes, StringBuilder string)
163 {
164 attributes.forEach((k, v) -> string.append(make72Safe(newline(new StringBuilder(k.toString()).append(": ").append(v)))));
165 newline(string);
166 }
167
168
169 private static StringBuilder make72Safe(StringBuilder line)
170 {
171 int length = line.length();
172 if(length > 72) {
173 int i = 70;
174 while(i < length - 2) {
175 line.insert(i, "\r\n ");
176 i += 72;
177 length += 3;
178 }
179 }
180 return line;
181 }
182
183
184 @Override
185 public void attach(ArchiveRegistrar core)
186 {
187 super.attach(core);
188 core.registerResourceSource(this);
189 }
190
191
192
193
194 @Override
195 public WithSignature digestedBy(StandardDigestAlgorithms hash)
196 {
197 super.digestedBy(hash);
198 return this;
199 }
200
201
202 @Override
203 public WithSignature digestedBy(MessageDigest digest)
204 {
205 super.digestedBy(digest);
206 return this;
207 }
208
209
210
211
212
213
214
215
216
217
218 public WithSignature store(KeyStore store)
219 {
220 this.keyStore = store;
221 return this;
222 }
223
224
225
226
227
228
229
230
231 public WithSignature alias(String alias)
232 {
233 this.keyAlias = alias;
234 return this;
235 }
236
237
238
239
240
241
242
243
244 public WithSignature password(char[] password)
245 {
246 this.keyPassword = password;
247 return this;
248 }
249
250
251
252
253
254
255
256
257 public WithSignature signatureFileName(String signatureFilename)
258 {
259 this.signatureFilename = signatureFilename;
260 return this;
261 }
262
263
264
265
266
267
268
269
270
271
272 public WithSignature signatureAlgorithm(StandardSignatureAlgorithms algorithm)
273 {
274 return signatureAlgorithm(algorithm.algorithm());
275 }
276
277
278
279
280
281
282
283
284
285
286 public WithSignature signatureAlgorithm(String algorithm)
287 {
288 this.signatureAlgorithm = algorithm;
289 return this;
290 }
291
292
293
294
295
296
297
298
299
300
301 public WithSignature createdBy(String author)
302 {
303 this.createdBy = author;
304 return this;
305 }
306
307 }