blob: 600c9a83f07c80ad66e1c1f0273073a4a8fb0ffa [file] [log] [blame]
Stuart McCullochf3173222012-06-07 21:57:32 +00001package aQute.bnd.signing;
2
3import java.io.*;
4import java.security.*;
5import java.security.KeyStore.PrivateKeyEntry;
6import java.util.*;
7import java.util.jar.*;
8import java.util.regex.*;
9
10import aQute.lib.base64.*;
11import aQute.lib.io.*;
12import aQute.lib.osgi.*;
13
14/**
15 * This class is used with the aQute.lib.osgi package, it signs jars with DSA
Stuart McCulloch4482c702012-06-15 13:27:53 +000016 * signature. -sign: md5, sha1
Stuart McCullochf3173222012-06-07 21:57:32 +000017 */
18public class Signer extends Processor {
Stuart McCulloch4482c702012-06-15 13:27:53 +000019 static Pattern METAINFDIR = Pattern.compile("META-INF/[^/]*");
20 String digestNames[] = new String[] {
21 "MD5"
22 };
23 File keystoreFile = new File("keystore");
24 String password;
25 String alias;
Stuart McCullochf3173222012-06-07 21:57:32 +000026
Stuart McCulloch4482c702012-06-15 13:27:53 +000027 public void signJar(Jar jar) {
28 if (digestNames == null || digestNames.length == 0)
29 error("Need at least one digest algorithm name, none are specified");
Stuart McCullochf3173222012-06-07 21:57:32 +000030
Stuart McCulloch4482c702012-06-15 13:27:53 +000031 if (keystoreFile == null || !keystoreFile.getAbsoluteFile().exists()) {
32 error("No such keystore file: " + keystoreFile);
33 return;
34 }
Stuart McCullochf3173222012-06-07 21:57:32 +000035
Stuart McCulloch4482c702012-06-15 13:27:53 +000036 if (alias == null) {
37 error("Private key alias not set for signing");
38 return;
39 }
Stuart McCullochf3173222012-06-07 21:57:32 +000040
Stuart McCulloch4482c702012-06-15 13:27:53 +000041 MessageDigest digestAlgorithms[] = new MessageDigest[digestNames.length];
Stuart McCullochf3173222012-06-07 21:57:32 +000042
Stuart McCulloch4482c702012-06-15 13:27:53 +000043 getAlgorithms(digestNames, digestAlgorithms);
Stuart McCullochf3173222012-06-07 21:57:32 +000044
Stuart McCulloch4482c702012-06-15 13:27:53 +000045 try {
46 Manifest manifest = jar.getManifest();
47 manifest.getMainAttributes().putValue("Signed-By", "Bnd");
Stuart McCullochf3173222012-06-07 21:57:32 +000048
Stuart McCulloch4482c702012-06-15 13:27:53 +000049 // Create a new manifest that contains the
50 // Name parts with the specified digests
Stuart McCullochf3173222012-06-07 21:57:32 +000051
Stuart McCulloch4482c702012-06-15 13:27:53 +000052 ByteArrayOutputStream o = new ByteArrayOutputStream();
53 manifest.write(o);
54 doManifest(jar, digestNames, digestAlgorithms, o);
55 o.flush();
56 byte newManifestBytes[] = o.toByteArray();
57 jar.putResource("META-INF/MANIFEST.MF", new EmbeddedResource(newManifestBytes, 0));
Stuart McCullochf3173222012-06-07 21:57:32 +000058
Stuart McCulloch4482c702012-06-15 13:27:53 +000059 // Use the bytes from the new manifest to create
60 // a signature file
Stuart McCullochf3173222012-06-07 21:57:32 +000061
Stuart McCulloch4482c702012-06-15 13:27:53 +000062 byte[] signatureFileBytes = doSignatureFile(digestNames, digestAlgorithms, newManifestBytes);
63 jar.putResource("META-INF/BND.SF", new EmbeddedResource(signatureFileBytes, 0));
Stuart McCullochf3173222012-06-07 21:57:32 +000064
Stuart McCulloch4482c702012-06-15 13:27:53 +000065 // Now we must create an RSA signature
66 // this requires the private key from the keystore
Stuart McCullochf3173222012-06-07 21:57:32 +000067
Stuart McCulloch4482c702012-06-15 13:27:53 +000068 KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
Stuart McCullochf3173222012-06-07 21:57:32 +000069
Stuart McCulloch4482c702012-06-15 13:27:53 +000070 KeyStore.PrivateKeyEntry privateKeyEntry = null;
Stuart McCullochf3173222012-06-07 21:57:32 +000071
Stuart McCulloch4482c702012-06-15 13:27:53 +000072 java.io.FileInputStream keystoreInputStream = null;
73 try {
74 keystoreInputStream = new java.io.FileInputStream(keystoreFile);
75 char[] pw = password == null ? new char[0] : password.toCharArray();
Stuart McCullochf3173222012-06-07 21:57:32 +000076
Stuart McCulloch4482c702012-06-15 13:27:53 +000077 keystore.load(keystoreInputStream, pw);
78 keystoreInputStream.close();
79 privateKeyEntry = (PrivateKeyEntry) keystore.getEntry(alias, new KeyStore.PasswordProtection(pw));
80 }
81 catch (Exception e) {
82 error("No able to load the private key from the give keystore(" + keystoreFile.getAbsolutePath()
83 + ") with alias " + alias + " : " + e);
84 return;
85 }
86 finally {
87 IO.close(keystoreInputStream);
88 }
89 PrivateKey privateKey = privateKeyEntry.getPrivateKey();
Stuart McCullochf3173222012-06-07 21:57:32 +000090
Stuart McCulloch4482c702012-06-15 13:27:53 +000091 Signature signature = Signature.getInstance("MD5withRSA");
92 signature.initSign(privateKey);
Stuart McCullochf3173222012-06-07 21:57:32 +000093
Stuart McCulloch4482c702012-06-15 13:27:53 +000094 signature.update(signatureFileBytes);
Stuart McCullochf3173222012-06-07 21:57:32 +000095
Stuart McCulloch4482c702012-06-15 13:27:53 +000096 signature.sign();
Stuart McCullochf3173222012-06-07 21:57:32 +000097
Stuart McCulloch4482c702012-06-15 13:27:53 +000098 // TODO, place the SF in a PCKS#7 structure ...
99 // no standard class for this? The following
100 // is an idea but we will to have do ASN.1 BER
101 // encoding ...
Stuart McCullochf3173222012-06-07 21:57:32 +0000102
Stuart McCulloch4482c702012-06-15 13:27:53 +0000103 ByteArrayOutputStream tmpStream = new ByteArrayOutputStream();
104 jar.putResource("META-INF/BND.RSA", new EmbeddedResource(tmpStream.toByteArray(), 0));
105 }
106 catch (Exception e) {
107 error("During signing: " + e);
108 }
109 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000110
Stuart McCulloch4482c702012-06-15 13:27:53 +0000111 private byte[] doSignatureFile(String[] digestNames, MessageDigest[] algorithms, byte[] manbytes)
112 throws IOException {
113 ByteArrayOutputStream out = new ByteArrayOutputStream();
114 PrintWriter ps = IO.writer(out);
115 ps.print("Signature-Version: 1.0\r\n");
Stuart McCullochf3173222012-06-07 21:57:32 +0000116
Stuart McCulloch4482c702012-06-15 13:27:53 +0000117 for (int a = 0; a < algorithms.length; a++) {
118 if (algorithms[a] != null) {
119 byte[] digest = algorithms[a].digest(manbytes);
120 ps.print(digestNames[a] + "-Digest-Manifest: ");
121 ps.print(new Base64(digest));
122 ps.print("\r\n");
123 }
124 }
125 return out.toByteArray();
126 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000127
Stuart McCulloch4482c702012-06-15 13:27:53 +0000128 private void doManifest(Jar jar, String[] digestNames, MessageDigest[] algorithms, OutputStream out)
129 throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +0000130
Stuart McCulloch4482c702012-06-15 13:27:53 +0000131 for (Map.Entry<String,Resource> entry : jar.getResources().entrySet()) {
132 String name = entry.getKey();
133 if (!METAINFDIR.matcher(name).matches()) {
Stuart McCulloch669423b2012-06-26 16:34:24 +0000134 out.write("\r\n".getBytes("UTF-8"));
135 out.write("Name: ".getBytes("UTF-8"));
Stuart McCulloch4482c702012-06-15 13:27:53 +0000136 out.write(name.getBytes("UTF-8"));
Stuart McCulloch669423b2012-06-26 16:34:24 +0000137 out.write("\r\n".getBytes("UTF-8"));
Stuart McCullochf3173222012-06-07 21:57:32 +0000138
Stuart McCulloch4482c702012-06-15 13:27:53 +0000139 digest(algorithms, entry.getValue());
140 for (int a = 0; a < algorithms.length; a++) {
141 if (algorithms[a] != null) {
142 byte[] digest = algorithms[a].digest();
143 String header = digestNames[a] + "-Digest: " + new Base64(digest) + "\r\n";
144 out.write(header.getBytes());
145 }
146 }
147 }
148 }
149 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000150
Stuart McCulloch4482c702012-06-15 13:27:53 +0000151 private void digest(MessageDigest[] algorithms, Resource r) throws Exception {
152 InputStream in = r.openInputStream();
153 byte[] data = new byte[1024];
154 int size = in.read(data);
155 while (size > 0) {
156 for (int a = 0; a < algorithms.length; a++) {
157 if (algorithms[a] != null) {
158 algorithms[a].update(data, 0, size);
159 }
160 }
161 size = in.read(data);
162 }
163 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000164
Stuart McCulloch4482c702012-06-15 13:27:53 +0000165 private void getAlgorithms(String[] digestNames, MessageDigest[] algorithms) {
166 for (int i = 0; i < algorithms.length; i++) {
167 String name = digestNames[i];
168 try {
169 algorithms[i] = MessageDigest.getInstance(name);
170 }
171 catch (NoSuchAlgorithmException e) {
172 error("Specified digest algorithm " + digestNames[i] + ", but not such algorithm was found: " + e);
173 }
174 }
175 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000176
Stuart McCulloch4482c702012-06-15 13:27:53 +0000177 public void setPassword(String string) {
178 password = string;
179 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000180
Stuart McCulloch4482c702012-06-15 13:27:53 +0000181 public void setKeystore(File keystore) {
182 this.keystoreFile = keystore;
183 }
184
185 public void setAlias(String string) {
186 this.alias = string;
187 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000188}