| /* |
| * 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.DataOutputStream; |
| import java.io.IOException; |
| import java.security.MessageDigest; |
| import java.security.NoSuchAlgorithmException; |
| import java.util.Iterator; |
| import java.util.jar.Attributes; |
| |
| import org.bouncycastle.util.encoders.Base64Encoder; |
| |
| import com.motorola.studio.android.common.log.StudioLogger; |
| |
| /** |
| * A Class representing a Manifest Entry. |
| */ |
| public class ManifestEntry |
| { |
| /** |
| * New line string according Jar specification |
| */ |
| public static final String MANIFEST_NEW_LINE = "\r\n"; |
| |
| public static final String ENTRY_NAME_ATTRIBUTE = "Name: "; |
| |
| public static final String UTF8_CHARSET = "UTF-8"; |
| |
| /** |
| * Safe line size limit according Jar Specification |
| */ |
| public static final int SAFE_LIMIT = 72; |
| |
| private final String name; |
| |
| private final Attributes attributes; |
| |
| /** |
| * Create a new ManifestEntry with the desired name and attributes |
| * @param name |
| * @param attr |
| */ |
| public ManifestEntry(String name, Attributes attr) |
| { |
| this.name = name; |
| this.attributes = attr; |
| } |
| |
| /** |
| * Get the manifest as it will be written in the Manifest file |
| * @return a byte array representing the manifest entry |
| */ |
| public byte[] toManifestEntryBytes() |
| { |
| byte[] result = null; |
| DataOutputStream dataOut = null; |
| ByteArrayOutputStream stream = null; |
| try |
| { |
| stream = new ByteArrayOutputStream(); |
| dataOut = new DataOutputStream(stream); |
| String nameField = wrap72bytes(ENTRY_NAME_ATTRIBUTE + name); |
| dataOut.writeBytes(nameField); |
| dataOut.writeBytes(MANIFEST_NEW_LINE); |
| dataOut.writeBytes(getAttributesString()); |
| dataOut.writeBytes(MANIFEST_NEW_LINE); |
| dataOut.writeBytes(MANIFEST_NEW_LINE); |
| result = stream.toString().getBytes(UTF8_CHARSET); |
| |
| } |
| catch (IOException e) |
| { |
| StudioLogger.error(ManifestEntry.class, "Error getting manifest like bytes"); |
| } |
| finally |
| { |
| try |
| { |
| if (dataOut != null) |
| { |
| dataOut.close(); |
| } |
| if (stream != null) |
| { |
| stream.close(); |
| } |
| } |
| catch (IOException e) |
| { |
| StudioLogger |
| .error("Could not close stream while writing manifest" + e.getMessage()); |
| } |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Digest the entire entry |
| * @return a byte array with the SHA-1 sum of this entry |
| */ |
| public byte[] digest() |
| { |
| byte[] digested = null; |
| try |
| { |
| MessageDigest digester = MessageDigest.getInstance("SHA-1"); |
| digester.reset(); |
| digester.update(toManifestEntryBytes()); |
| digested = digester.digest(); |
| } |
| catch (NoSuchAlgorithmException e) |
| { |
| StudioLogger.error(ManifestEntry.class, "Error digesting manifest bytes"); |
| } |
| return digested; |
| } |
| |
| /** |
| * Get this Entry attributes as it will be written in the Manifest file |
| * @return |
| */ |
| private String getAttributesString() |
| { |
| StringBuilder builder = new StringBuilder(); |
| Iterator<Object> it = attributes.keySet().iterator(); |
| while (it.hasNext()) |
| { |
| Object next = it.next(); |
| String line = wrap72bytes(next + ": " + attributes.get(next)); |
| if (it.hasNext()) |
| { |
| line += MANIFEST_NEW_LINE; |
| } |
| builder.append(line); |
| } |
| return builder.toString(); |
| } |
| |
| /** |
| * Wrap a string into another string with at most 72 bytes per line |
| * According Jar spec, each line must have at most 72 bytes |
| * @param line |
| * @return the wrapped string |
| */ |
| public static String wrap72bytes(String line) |
| { |
| String returnString = line; |
| |
| if (line.length() > SAFE_LIMIT) |
| { |
| int maximumLength = SAFE_LIMIT - MANIFEST_NEW_LINE.length(); |
| StringBuilder wrapped = new StringBuilder(); |
| int index = maximumLength; |
| String first = line.substring(0, index); //get first SAFE_LIMIT - MANIFEST_NEW_LINE.length() bytes |
| first += MANIFEST_NEW_LINE; |
| wrapped.append(first); |
| while (index < line.length()) |
| { |
| String medium = " "; |
| if ((index + maximumLength) < line.length()) |
| { |
| medium += line.substring(index, index + maximumLength); //get the maximum length minus the blank space size |
| } |
| else |
| { |
| medium += line.substring(index); |
| } |
| wrapped.append(medium); |
| index += maximumLength; |
| } |
| returnString = wrapped.toString(); |
| } |
| |
| return returnString; |
| } |
| |
| /** |
| * Get this entry name |
| * @return this entry name |
| */ |
| public String getName() |
| { |
| return name; |
| } |
| |
| /** |
| * Get this entry ready to be written in the Signature File |
| * @return this entry ready to be written in the Signature File |
| * @throws IOException if some error occurs during encoding |
| */ |
| public String toDigestedManifestEntry() throws IOException |
| { |
| Base64Encoder encoder = new Base64Encoder(); |
| StringBuilder builder = new StringBuilder(); |
| ByteArrayOutputStream output = null; |
| |
| try |
| { |
| output = new ByteArrayOutputStream(); |
| |
| builder.append(wrap72bytes(ENTRY_NAME_ATTRIBUTE + name)); |
| builder.append(MANIFEST_NEW_LINE); |
| builder.append(ISignConstants.SHA1_DIGEST + ": "); |
| |
| byte[] digest = digest(); |
| encoder.encode(digest, 0, digest.length, output); |
| |
| builder.append(output.toString()); |
| builder.append(MANIFEST_NEW_LINE); |
| } |
| finally |
| { |
| if (output != null) |
| { |
| try |
| { |
| output.close(); |
| } |
| catch (IOException e) |
| { |
| StudioLogger.error("Could not close stream: " + e.getMessage()); |
| } |
| } |
| } |
| return builder.toString(); |
| } |
| } |