blob: 669cf707aee3e6933e78812f3e3da654d4eb6247 [file] [log] [blame]
/*
* 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");
}
}