/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.motorolamobility.studio.android.certmanager.packaging.sign; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; import java.security.KeyStoreException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import java.util.jar.Attributes; import org.bouncycastle.util.encoders.Base64Encoder; import com.motorola.studio.android.common.log.StudioLogger; import com.motorolamobility.studio.android.certmanager.CertificateManagerActivator; import com.motorolamobility.studio.android.certmanager.exception.KeyStoreManagerException; import com.motorolamobility.studio.android.certmanager.packaging.PackageFile; import com.motorolamobility.studio.android.certmanager.ui.model.IKeyStoreEntry; /** * Utility class used to sign package files. */ public class PackageFileSigner { public static final String MOTODEV_STUDIO = "MOTODEV Studio"; /** * Signs a package file * * @param packageFile * the package file to sign * @param certificateAlias * the signing certificate alias * @param createdBy * Created-By manifest attribute * @throws SignException * if a processing error occurs during the signing process * @throws UnrecoverableKeyException */ public static void signPackage(PackageFile packageFile, IKeyStoreEntry keystoreEntry, String keyEntryPassword, String createdBy) throws SignException, UnrecoverableKeyException { try { Base64Encoder encoder = new Base64Encoder(); MessageDigest messageDigest = MessageDigest.getInstance(ISignConstants.SHA1); addFilesDigestsToManifest(packageFile, encoder, messageDigest); addSignatureFiles(packageFile, keystoreEntry, keyEntryPassword, encoder, createdBy); } catch (UnrecoverableKeyException e) { throw e; } catch (Exception e) { StudioLogger.error(PackageFileSigner.class, "Error signing package", e); throw new SignException(e.getMessage(), e); } } /** * Remove package signature files * * @param packageFile * @throws IOException */ public static void removePackageSignature(PackageFile packageFile) throws IOException { packageFile.removeMetaEntryFiles(); } /** * Generates the digests for all the files in the package and puts them in * the manifest * * @param packageFile * the package file being signed * @param encoder * the BASE64 encoder * @param messageDigest * the message digest * @throws IOException * if an I/O error occurs when reading the files contained in * the package */ private static void addFilesDigestsToManifest(PackageFile packageFile, Base64Encoder encoder, MessageDigest messageDigest) throws IOException { InputStream fileInputStream = null; ReadableByteChannel rc = null; ByteArrayOutputStream encodedStream = null; // for each entry in the package file for (String entryName : packageFile.getEntryNames()) { File file = packageFile.getEntryFile(entryName); if (file.isFile()) { try { // read the file contents fileInputStream = new FileInputStream(file); rc = Channels.newChannel(fileInputStream); ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length()); rc.read(byteBuffer); // compute the digest messageDigest.reset(); byte[] digestedArray = messageDigest.digest(byteBuffer.array()); encodedStream = new ByteArrayOutputStream(); encoder.encode(digestedArray, 0, digestedArray.length, encodedStream); String digestedMessage = encodedStream.toString(); // put the digest in the manifest file Attributes jarEntryAttributes = new Attributes(); jarEntryAttributes.putValue(ISignConstants.SHA1_DIGEST, digestedMessage); packageFile.getManifest().getEntries().put(entryName, jarEntryAttributes); } finally { try { if (encodedStream != null) { encodedStream.close(); } if (rc != null) { rc.close(); } if (fileInputStream != null) { fileInputStream.close(); } } catch (IOException e) { StudioLogger.error("Could not close stream while signing package. " + e.getMessage()); } } } } } /** * Adds the signature file and the signature block file to the package * * @param packageFile * the package file being signed * @param certificateAlias * the signing certificate alias * @param encoder * the BASE64 encoder * @param messageDigest * the message digest * @param createdBy * Created-By manifest attribute * @throws SignException * if a processing error occurs during the signing process * @throws IOException * if an I/O error occurs during the signing process * @throws NoSuchAlgorithmException * @throws KeyStoreManagerException * @throws KeyStoreException * @throws UnrecoverableKeyException */ private static void addSignatureFiles(PackageFile packageFile, IKeyStoreEntry keystoreEntry, String keyEntryPassword, Base64Encoder encoder, String createdBy) throws IOException, SignException, UnrecoverableKeyException, KeyStoreException, KeyStoreManagerException, NoSuchAlgorithmException { // signature file SignatureFile signatureFile = new SignatureFile(packageFile, keystoreEntry.getAlias(), encoder, createdBy); File sigFile = File.createTempFile(CertificateManagerActivator.TEMP_FILE_PREFIX, null); FileOutputStream sigFileOutStream = null; try { sigFileOutStream = new FileOutputStream(sigFile); signatureFile.write(sigFileOutStream); } finally { if (sigFileOutStream != null) { try { sigFileOutStream.close(); } catch (IOException e) { StudioLogger .error("Could not close stream while adding signature files to package. " + e.getMessage()); } } } packageFile.setTempEntryFile(signatureFile.toString(), sigFile); // signature block file SignatureBlockFile signatureBlockFile = new SignatureBlockFile(signatureFile, keystoreEntry, keyEntryPassword); File sigBlockFile = File.createTempFile(CertificateManagerActivator.TEMP_FILE_PREFIX, null); FileOutputStream sigBlockFileOutStream = null; try { sigBlockFileOutStream = new FileOutputStream(sigBlockFile); signatureBlockFile.write(sigBlockFileOutStream); } finally { if (sigBlockFileOutStream != null) { try { sigBlockFileOutStream.close(); } catch (IOException e) { StudioLogger .error("Could not close stream while adding signature files to package. " + e.getMessage()); } } } packageFile.setTempEntryFile(signatureBlockFile.toString(), sigBlockFile); } }