OSDN Git Service

original
[gb-231r1-is01/Gingerbread_2.3.3_r1_IS01.git] / sdk / sdkmanager / libs / sdklib / src / com / android / sdklib / internal / build / SignedJarBuilder.java
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.android.sdklib.internal.build;
18
19 import com.android.sdklib.internal.build.SignedJarBuilder.IZipEntryFilter.ZipAbortException;
20
21 import sun.misc.BASE64Encoder;
22 import sun.security.pkcs.ContentInfo;
23 import sun.security.pkcs.PKCS7;
24 import sun.security.pkcs.SignerInfo;
25 import sun.security.x509.AlgorithmId;
26 import sun.security.x509.X500Name;
27
28 import java.io.ByteArrayOutputStream;
29 import java.io.File;
30 import java.io.FileInputStream;
31 import java.io.FilterOutputStream;
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.io.OutputStream;
35 import java.io.PrintStream;
36 import java.security.DigestOutputStream;
37 import java.security.GeneralSecurityException;
38 import java.security.MessageDigest;
39 import java.security.NoSuchAlgorithmException;
40 import java.security.PrivateKey;
41 import java.security.Signature;
42 import java.security.SignatureException;
43 import java.security.cert.X509Certificate;
44 import java.util.Map;
45 import java.util.jar.Attributes;
46 import java.util.jar.JarEntry;
47 import java.util.jar.JarFile;
48 import java.util.jar.JarOutputStream;
49 import java.util.jar.Manifest;
50 import java.util.zip.ZipEntry;
51 import java.util.zip.ZipInputStream;
52
53 /**
54  * A Jar file builder with signature support.
55  */
56 public class SignedJarBuilder {
57     private static final String DIGEST_ALGORITHM = "SHA1";
58     private static final String DIGEST_ATTR = "SHA1-Digest";
59     private static final String DIGEST_MANIFEST_ATTR = "SHA1-Digest-Manifest";
60
61     /** Write to another stream and also feed it to the Signature object. */
62     private static class SignatureOutputStream extends FilterOutputStream {
63         private Signature mSignature;
64
65         public SignatureOutputStream(OutputStream out, Signature sig) {
66             super(out);
67             mSignature = sig;
68         }
69
70         @Override
71         public void write(int b) throws IOException {
72             try {
73                 mSignature.update((byte) b);
74             } catch (SignatureException e) {
75                 throw new IOException("SignatureException: " + e);
76             }
77             super.write(b);
78         }
79
80         @Override
81         public void write(byte[] b, int off, int len) throws IOException {
82             try {
83                 mSignature.update(b, off, len);
84             } catch (SignatureException e) {
85                 throw new IOException("SignatureException: " + e);
86             }
87             super.write(b, off, len);
88         }
89     }
90
91     private JarOutputStream mOutputJar;
92     private PrivateKey mKey;
93     private X509Certificate mCertificate;
94     private Manifest mManifest;
95     private BASE64Encoder mBase64Encoder;
96     private MessageDigest mMessageDigest;
97
98     private byte[] mBuffer = new byte[4096];
99
100     /**
101      * Classes which implement this interface provides a method to check whether a file should
102      * be added to a Jar file.
103      */
104     public interface IZipEntryFilter {
105
106         /**
107          * An exception thrown during packaging of a zip file into APK file.
108          * This is typically thrown by implementations of
109          * {@link IZipEntryFilter#checkEntry(String)}.
110          */
111         public static class ZipAbortException extends Exception {
112             private static final long serialVersionUID = 1L;
113
114             public ZipAbortException() {
115                 super();
116             }
117
118             public ZipAbortException(String format, Object... args) {
119                 super(String.format(format, args));
120             }
121
122             public ZipAbortException(Throwable cause, String format, Object... args) {
123                 super(String.format(format, args), cause);
124             }
125
126             public ZipAbortException(Throwable cause) {
127                 super(cause);
128             }
129         }
130
131
132         /**
133          * Checks a file for inclusion in a Jar archive.
134          * @param archivePath the archive file path of the entry
135          * @return <code>true</code> if the file should be included.
136          * @throws ZipAbortException if writing the file should be aborted.
137          */
138         public boolean checkEntry(String archivePath) throws ZipAbortException;
139     }
140
141     /**
142      * Creates a {@link SignedJarBuilder} with a given output stream, and signing information.
143      * <p/>If either <code>key</code> or <code>certificate</code> is <code>null</code> then
144      * the archive will not be signed.
145      * @param out the {@link OutputStream} where to write the Jar archive.
146      * @param key the {@link PrivateKey} used to sign the archive, or <code>null</code>.
147      * @param certificate the {@link X509Certificate} used to sign the archive, or
148      * <code>null</code>.
149      * @throws IOException
150      * @throws NoSuchAlgorithmException
151      */
152     public SignedJarBuilder(OutputStream out, PrivateKey key, X509Certificate certificate)
153             throws IOException, NoSuchAlgorithmException {
154         mOutputJar = new JarOutputStream(out);
155         mOutputJar.setLevel(9);
156         mKey = key;
157         mCertificate = certificate;
158
159         if (mKey != null && mCertificate != null) {
160             mManifest = new Manifest();
161             Attributes main = mManifest.getMainAttributes();
162             main.putValue("Manifest-Version", "1.0");
163             main.putValue("Created-By", "1.0 (Android)");
164
165             mBase64Encoder = new BASE64Encoder();
166             mMessageDigest = MessageDigest.getInstance(DIGEST_ALGORITHM);
167         }
168     }
169
170     /**
171      * Writes a new {@link File} into the archive.
172      * @param inputFile the {@link File} to write.
173      * @param jarPath the filepath inside the archive.
174      * @throws IOException
175      */
176     public void writeFile(File inputFile, String jarPath) throws IOException {
177         // Get an input stream on the file.
178         FileInputStream fis = new FileInputStream(inputFile);
179         try {
180
181             // create the zip entry
182             JarEntry entry = new JarEntry(jarPath);
183             entry.setTime(inputFile.lastModified());
184
185             writeEntry(fis, entry);
186         } finally {
187             // close the file stream used to read the file
188             fis.close();
189         }
190     }
191
192     /**
193      * Copies the content of a Jar/Zip archive into the receiver archive.
194      * <p/>An optional {@link IZipEntryFilter} allows to selectively choose which files
195      * to copy over.
196      * @param input the {@link InputStream} for the Jar/Zip to copy.
197      * @param filter the filter or <code>null</code>
198      * @throws IOException
199      * @throws ZipAbortException if the {@link IZipEntryFilter} filter indicated that the write
200      *                           must be aborted.
201      */
202     public void writeZip(InputStream input, IZipEntryFilter filter)
203             throws IOException, ZipAbortException {
204         ZipInputStream zis = new ZipInputStream(input);
205
206         try {
207             // loop on the entries of the intermediary package and put them in the final package.
208             ZipEntry entry;
209             while ((entry = zis.getNextEntry()) != null) {
210                 String name = entry.getName();
211
212                 // do not take directories or anything inside a potential META-INF folder.
213                 if (entry.isDirectory() || name.startsWith("META-INF/")) {
214                     continue;
215                 }
216
217                 // if we have a filter, we check the entry against it
218                 if (filter != null && filter.checkEntry(name) == false) {
219                     continue;
220                 }
221
222                 JarEntry newEntry;
223
224                 // Preserve the STORED method of the input entry.
225                 if (entry.getMethod() == JarEntry.STORED) {
226                     newEntry = new JarEntry(entry);
227                 } else {
228                     // Create a new entry so that the compressed len is recomputed.
229                     newEntry = new JarEntry(name);
230                 }
231
232                 writeEntry(zis, newEntry);
233
234                 zis.closeEntry();
235             }
236         } finally {
237             zis.close();
238         }
239     }
240
241     /**
242      * Closes the Jar archive by creating the manifest, and signing the archive.
243      * @throws IOException
244      * @throws GeneralSecurityException
245      */
246     public void close() throws IOException, GeneralSecurityException {
247         if (mManifest != null) {
248             // write the manifest to the jar file
249             mOutputJar.putNextEntry(new JarEntry(JarFile.MANIFEST_NAME));
250             mManifest.write(mOutputJar);
251
252             // CERT.SF
253             Signature signature = Signature.getInstance("SHA1with" + mKey.getAlgorithm());
254             signature.initSign(mKey);
255             mOutputJar.putNextEntry(new JarEntry("META-INF/CERT.SF"));
256             writeSignatureFile(new SignatureOutputStream(mOutputJar, signature));
257
258             // CERT.*
259             mOutputJar.putNextEntry(new JarEntry("META-INF/CERT." + mKey.getAlgorithm()));
260             writeSignatureBlock(signature, mCertificate, mKey);
261         }
262
263         mOutputJar.close();
264     }
265
266     /**
267      * Adds an entry to the output jar, and write its content from the {@link InputStream}
268      * @param input The input stream from where to write the entry content.
269      * @param entry the entry to write in the jar.
270      * @throws IOException
271      */
272     private void writeEntry(InputStream input, JarEntry entry) throws IOException {
273         // add the entry to the jar archive
274         mOutputJar.putNextEntry(entry);
275
276         // read the content of the entry from the input stream, and write it into the archive.
277         int count;
278         while ((count = input.read(mBuffer)) != -1) {
279             mOutputJar.write(mBuffer, 0, count);
280
281             // update the digest
282             if (mMessageDigest != null) {
283                 mMessageDigest.update(mBuffer, 0, count);
284             }
285         }
286
287         // close the entry for this file
288         mOutputJar.closeEntry();
289
290         if (mManifest != null) {
291             // update the manifest for this entry.
292             Attributes attr = mManifest.getAttributes(entry.getName());
293             if (attr == null) {
294                 attr = new Attributes();
295                 mManifest.getEntries().put(entry.getName(), attr);
296             }
297             attr.putValue(DIGEST_ATTR, mBase64Encoder.encode(mMessageDigest.digest()));
298         }
299     }
300
301     /** Writes a .SF file with a digest to the manifest. */
302     private void writeSignatureFile(OutputStream out)
303             throws IOException, GeneralSecurityException {
304         Manifest sf = new Manifest();
305         Attributes main = sf.getMainAttributes();
306         main.putValue("Signature-Version", "1.0");
307         main.putValue("Created-By", "1.0 (Android)");
308
309         BASE64Encoder base64 = new BASE64Encoder();
310         MessageDigest md = MessageDigest.getInstance(DIGEST_ALGORITHM);
311         PrintStream print = new PrintStream(
312                 new DigestOutputStream(new ByteArrayOutputStream(), md),
313                 true, "UTF-8");
314
315         // Digest of the entire manifest
316         mManifest.write(print);
317         print.flush();
318         main.putValue(DIGEST_MANIFEST_ATTR, base64.encode(md.digest()));
319
320         Map<String, Attributes> entries = mManifest.getEntries();
321         for (Map.Entry<String, Attributes> entry : entries.entrySet()) {
322             // Digest of the manifest stanza for this entry.
323             print.print("Name: " + entry.getKey() + "\r\n");
324             for (Map.Entry<Object, Object> att : entry.getValue().entrySet()) {
325                 print.print(att.getKey() + ": " + att.getValue() + "\r\n");
326             }
327             print.print("\r\n");
328             print.flush();
329
330             Attributes sfAttr = new Attributes();
331             sfAttr.putValue(DIGEST_ATTR, base64.encode(md.digest()));
332             sf.getEntries().put(entry.getKey(), sfAttr);
333         }
334
335         sf.write(out);
336     }
337
338     /** Write the certificate file with a digital signature. */
339     private void writeSignatureBlock(Signature signature, X509Certificate publicKey,
340             PrivateKey privateKey)
341             throws IOException, GeneralSecurityException {
342         SignerInfo signerInfo = new SignerInfo(
343                 new X500Name(publicKey.getIssuerX500Principal().getName()),
344                 publicKey.getSerialNumber(),
345                 AlgorithmId.get(DIGEST_ALGORITHM),
346                 AlgorithmId.get(privateKey.getAlgorithm()),
347                 signature.sign());
348
349         PKCS7 pkcs7 = new PKCS7(
350                 new AlgorithmId[] { AlgorithmId.get(DIGEST_ALGORITHM) },
351                 new ContentInfo(ContentInfo.DATA_OID, null),
352                 new X509Certificate[] { publicKey },
353                 new SignerInfo[] { signerInfo });
354
355         pkcs7.encodeSignedData(mOutputJar);
356     }
357 }