blob: bed91be4be4e46722afc7653b64e3aaadc84af55 [file] [log] [blame]
Stuart McCulloch26e7a5a2011-10-17 10:31:43 +00001package aQute.bnd.signing;
2
3import java.io.*;
4import java.security.*;
5import java.security.KeyStore.*;
6import java.util.*;
7import java.util.jar.*;
8import java.util.regex.*;
9
10import aQute.lib.base64.*;
11import aQute.lib.osgi.*;
12
13/**
14 * This class is used with the aQute.lib.osgi package, it signs jars with DSA
15 * signature.
16 *
17 * -sign: md5, sha1
18 */
19public class Signer extends Processor {
20 static Pattern METAINFDIR = Pattern.compile("META-INF/[^/]*");
21 String digestNames[] = new String[] { "MD5" };
22 File keystoreFile = new File("keystore");
23 String password;
24 String alias;
25
26 public void signJar(Jar jar) {
27 if (digestNames == null || digestNames.length == 0)
28 error("Need at least one digest algorithm name, none are specified");
29
30 if (keystoreFile == null || !keystoreFile.getAbsoluteFile().exists()) {
31 error("No such keystore file: " + keystoreFile);
32 return;
33 }
34
35 if (alias == null) {
36 error("Private key alias not set for signing");
37 return;
38 }
39
40 MessageDigest digestAlgorithms[] = new MessageDigest[digestNames.length];
41
42 getAlgorithms(digestNames, digestAlgorithms);
43
44 try {
45 Manifest manifest = jar.getManifest();
46 manifest.getMainAttributes().putValue("Signed-By", "Bnd");
47
48 // Create a new manifest that contains the
49 // Name parts with the specified digests
50
51 ByteArrayOutputStream o = new ByteArrayOutputStream();
52 manifest.write(o);
53 doManifest(jar, digestNames, digestAlgorithms, o);
54 o.flush();
55 byte newManifestBytes[] = o.toByteArray();
56 jar.putResource("META-INF/MANIFEST.MF", new EmbeddedResource(
57 newManifestBytes, 0));
58
59 // Use the bytes from the new manifest to create
60 // a signature file
61
62 byte[] signatureFileBytes = doSignatureFile(digestNames,
63 digestAlgorithms, newManifestBytes);
64 jar.putResource("META-INF/BND.SF", new EmbeddedResource(
65 signatureFileBytes, 0));
66
67 // Now we must create an RSA signature
68 // this requires the private key from the keystore
69
70 KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
71
72 KeyStore.PrivateKeyEntry privateKeyEntry = null;
73
74 try {
75 java.io.FileInputStream keystoreInputStream = new java.io.FileInputStream(
76 keystoreFile);
77 keystore.load(keystoreInputStream, password == null ? null
78 : password.toCharArray());
79 keystoreInputStream.close();
80 privateKeyEntry = (PrivateKeyEntry) keystore.getEntry(
81 alias, new KeyStore.PasswordProtection(password
82 .toCharArray()));
83 } catch (Exception e) {
84 error("No able to load the private key from the give keystore("+keystoreFile.getAbsolutePath()+") with alias "+alias+" : "
85 + e);
86 return;
87 }
88 PrivateKey privateKey = privateKeyEntry.getPrivateKey();
89
90 Signature signature = Signature.getInstance("MD5withRSA");
91 signature.initSign(privateKey);
92
93 signature.update(signatureFileBytes);
94
95 signature.sign();
96
97 // TODO, place the SF in a PCKS#7 structure ...
98 // no standard class for this? The following
99 // is an idea but we will to have do ASN.1 BER
100 // encoding ...
101
102
103
104 ByteArrayOutputStream tmpStream = new ByteArrayOutputStream();
105 jar.putResource("META-INF/BND.RSA", new EmbeddedResource(tmpStream
106 .toByteArray(), 0));
107 } catch (Exception e) {
108 error("During signing: " + e);
109 }
110 }
111
112 private byte[] doSignatureFile(String[] digestNames,
113 MessageDigest[] algorithms, byte[] manbytes) {
114 ByteArrayOutputStream out = new ByteArrayOutputStream();
115 PrintStream ps = new PrintStream(out);
116 ps.print("Signature-Version: 1.0\r\n");
117
118 for (int a = 0; a < algorithms.length; a++) {
119 if (algorithms[a] != null) {
120 byte[] digest = algorithms[a].digest(manbytes);
121 ps.print(digestNames[a] + "-Digest-Manifest: ");
122 ps.print(new Base64(digest));
123 ps.print("\r\n");
124 }
125 }
126 return out.toByteArray();
127 }
128
129 private void doManifest(Jar jar, String[] digestNames,
130 MessageDigest[] algorithms, OutputStream out) throws Exception {
131
132 for (Map.Entry<String,Resource> entry : jar.getResources().entrySet()) {
133 String name = entry.getKey();
134 if (!METAINFDIR.matcher(name).matches()) {
135 out.write("\r\n".getBytes());
136 out.write("Name: ".getBytes());
137 out.write(name.getBytes());
138 out.write("\r\n".getBytes());
139
140 digest(algorithms, entry.getValue());
141 for (int a = 0; a < algorithms.length; a++) {
142 if (algorithms[a] != null) {
143 byte[] digest = algorithms[a].digest();
144 String header = digestNames[a] + "-Digest: "
145 + new Base64(digest) + "\r\n";
146 out.write(header.getBytes());
147 }
148 }
149 }
150 }
151 }
152
153 private void digest(MessageDigest[] algorithms, Resource r)
154 throws Exception {
155 InputStream in = r.openInputStream();
156 byte[] data = new byte[1024];
157 int size = in.read(data);
158 while (size > 0) {
159 for (int a = 0; a < algorithms.length; a++) {
160 if (algorithms[a] != null) {
161 algorithms[a].update(data, 0, size);
162 }
163 }
164 size = in.read(data);
165 }
166 }
167
168 private void getAlgorithms(String[] digestNames, MessageDigest[] algorithms) {
169 for (int i = 0; i < algorithms.length; i++) {
170 String name = digestNames[i];
171 try {
172 algorithms[i] = MessageDigest.getInstance(name);
173 } catch (NoSuchAlgorithmException e) {
174 error("Specified digest algorithm " + digestNames[i]
175 + ", but not such algorithm was found: " + e);
176 }
177 }
178 }
179
180 public void setPassword(String string) {
181 password = string;
182 }
183
184 public void setKeystore(File keystore) {
185 this.keystoreFile = keystore;
186 }
187
188 public void setAlias(String string) {
189 this.alias = string;
190 }
191}