/* * 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.IOException; import java.io.OutputStream; import java.util.HashMap; import java.util.Map; import java.util.jar.Attributes; import java.util.jar.Manifest; 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.packaging.PackageFile; /** * This class implements the package signature file, that follows the jar * signing process. */ public class SignatureFile { /** * The package file */ private final PackageFile packageFile; /** * The base encoder */ private Base64Encoder encoder = new Base64Encoder(); /** * Manifest Created-By attribute */ private final String createdBy; /** * Default Constructor * * @param packageFile * the signed package file to be signed * @param alias * the certificate alias * @param encoder * the BASE64 encoder * @param createdBy * Created-By manifest attribute */ public SignatureFile(PackageFile packageFile, String alias, Base64Encoder encoder, String createdBy) { this.packageFile = packageFile; this.encoder = encoder; this.createdBy = createdBy; } /** * Return the filename with relative path from root (normally * META-INF/alias.SF). */ @Override public String toString() { return CertificateManagerActivator.METAFILES_DIR + CertificateManagerActivator.JAR_SEPARATOR + ISignConstants.SIGNATURE_FILE_NAME + ISignConstants.SIGNATURE_FILE_NAME_EXTENSION; } /** * Writes this file to an output stream. * * @param outputStream * the stream to write this file * @throws IOException * if an I/O error occurs during the signing process * @throws SignException * if a processing error occurs during the signing process */ public void write(OutputStream outputStream) throws IOException, SignException { // the manifest file Manifest manifestFile = this.packageFile.getManifest(); // the manifest digester ManifestDigester manifestDigester = new ManifestDigester(manifestFile); // the signature file to be constructed Manifest signatureFile = new Manifest(); // the manifest digested main attributes byte[] digestedMainAttributes = manifestDigester.getDigestedManifestMainAttributes(); // the digest of entire manifest byte[] digestedManifest = manifestDigester.getDigestedManifest(); // put the required main attributes to a valid signature file // (Version, CreatedBy, Main Attrib digest, Manifest digest) Attributes signatureFileMainAtt = signatureFile.getMainAttributes(); signatureFileMainAtt.putValue(ISignConstants.SIGNATURE_VERSION_KEY, ISignConstants.SIGNATURE_VERSION_VALUE); signatureFileMainAtt.putValue(CertificateManagerActivator.CREATED_BY_FIELD, this.createdBy); ByteArrayOutputStream stream = null; try { stream = new ByteArrayOutputStream(); encoder.encode(digestedMainAttributes, 0, digestedMainAttributes.length, stream); String encodedMainAttributesDigest = stream.toString(); stream.reset(); encoder.encode(digestedManifest, 0, digestedManifest.length, stream); String encodedManifestDigest = stream.toString(); signatureFileMainAtt.putValue(ISignConstants.SHA1_DIGEST_MANIFEST_MAIN, encodedMainAttributesDigest); signatureFileMainAtt.putValue(ISignConstants.SHA1_DIGEST_MANIFEST, encodedManifestDigest); } finally { if (stream != null) { try { stream.close(); } catch (IOException e) { StudioLogger.error("Could not close stream writing signature file. " + e.getMessage()); } } } // calculate the digest from each entry of manifest ByteArrayOutputStream baos = null; try { baos = new ByteArrayOutputStream(); manifestFile.write(baos); Map<String, Attributes> manifestEntries = manifestFile.getEntries(); Map<String, Attributes> signatureFileEntries = signatureFile.getEntries(); HashMap<String, ManifestEntry> entries = manifestDigester.getEntries(); for (String manifestEntryKey : manifestEntries.keySet()) { ManifestEntry signatureFileEntry = entries.get(manifestEntryKey); byte[] digestedArray = signatureFileEntry.digest(); ByteArrayOutputStream encodedStream = null; try { encodedStream = new ByteArrayOutputStream(); this.encoder.encode(digestedArray, 0, digestedArray.length, encodedStream); String digestedValue = encodedStream.toString(); Attributes signatureFileAtt = new Attributes(); signatureFileAtt.putValue(ISignConstants.SHA1_DIGEST, digestedValue); signatureFileEntries.put(manifestEntryKey, signatureFileAtt); } finally { try { if (encodedStream != null) { encodedStream.close(); } } catch (IOException e) { StudioLogger.error("Could not close stream: " + e.getMessage()); } } } } catch (IOException e) { StudioLogger.error(SignatureFile.class, "I/O error digesting manifest entries: " + e.getMessage()); throw new SignException("I/O error digesting manifest entries", e); } finally { try { if (baos != null) { baos.close(); } } catch (IOException e) { StudioLogger.error("Could not close stream: " + e.getMessage()); } } // I/O exceptions below are thrown unmodified signatureFile.write(outputStream); StudioLogger.info(SignatureFile.class, "Signature file was written"); } }