1 /*
   2  * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.oracle.ipack.packer;
  27 
  28 import com.oracle.ipack.blobs.VirtualBlob;
  29 import com.oracle.ipack.blobs.WrapperBlob;
  30 import com.oracle.ipack.macho.CodeSignatureCommand;
  31 import com.oracle.ipack.macho.MachoCommand;
  32 import com.oracle.ipack.macho.MachoHeader;
  33 import com.oracle.ipack.macho.SegmentCommand;
  34 import com.oracle.ipack.signature.CodeDirectoryBlob;
  35 import com.oracle.ipack.signature.EmbeddedSignatureBlob;
  36 import com.oracle.ipack.signature.Requirement;
  37 import com.oracle.ipack.signature.RequirementBlob;
  38 import com.oracle.ipack.signature.RequirementsBlob;
  39 import com.oracle.ipack.signer.Signer;
  40 import com.oracle.ipack.util.DataCopier;
  41 import com.oracle.ipack.util.HashingOutputStream;
  42 import com.oracle.ipack.util.LsbDataInputStream;
  43 import com.oracle.ipack.util.LsbDataOutputStream;
  44 import com.oracle.ipack.util.NullOutputStream;
  45 import com.oracle.ipack.util.PageHashingOutputStream;
  46 import java.io.BufferedInputStream;
  47 import java.io.BufferedOutputStream;
  48 import java.io.ByteArrayOutputStream;
  49 import java.io.DataOutputStream;
  50 import java.io.File;
  51 import java.io.FileInputStream;
  52 import java.io.IOException;
  53 import java.io.InputStream;
  54 import java.io.OutputStream;
  55 import java.util.List;
  56 import java.util.zip.ZipEntry;
  57 import java.util.zip.ZipOutputStream;
  58 import org.bouncycastle.cms.CMSException;
  59 
  60 final class ExecutablePacker {
  61     private static final int RESERVED_SIGNATURE_BLOB_SIZE = 9000;
  62 
  63     private final ZipOutputStream zipStream;
  64     private final File baseDir;
  65     private final String appPath;
  66     private final String appName;
  67     private final String appIdentifier;
  68     private final Signer signer;
  69 
  70     private byte[] codeResourcesHash;
  71     private byte[] infoPlistHash;
  72 
  73     ExecutablePacker(final ZipOutputStream zipStream,
  74                      final File baseDir,
  75                      final String appPath,
  76                      final String appName,
  77                      final String appIdentifier,
  78                      final Signer signer) {
  79         this.zipStream = zipStream;
  80         this.baseDir = baseDir;
  81         this.appPath = appPath;
  82         this.appName = appName;
  83         this.appIdentifier = appIdentifier;
  84         this.signer = signer;
  85     }
  86 
  87     void setCodeResourcesHash(final byte[] codeResourcesHash) {
  88         this.codeResourcesHash = codeResourcesHash;
  89     }
  90 
  91     void setInfoPlistHash(final byte[] infoPlistHash) {
  92         this.infoPlistHash = infoPlistHash;
  93     }
  94 
  95     void execute() throws IOException {
  96         final InputStream execInputStream =
  97                 new BufferedInputStream(new FileInputStream(
  98                         new File(baseDir, appPath + appName)));
  99         try {
 100             final MachoHeader header =
 101                     MachoHeader.read(new LsbDataInputStream(execInputStream));
 102             final int oldHeaderSize = header.getSize();
 103 
 104             final SegmentCommand linkeditSegment =
 105                     header.findSegment("__LINKEDIT");
 106             if (linkeditSegment == null) {
 107                 throw new IOException("Linkedit segment not found");
 108             }
 109 
 110             CodeSignatureCommand codeSignatureCommand =
 111                     (CodeSignatureCommand) header.findCommand(
 112                                                MachoCommand.LC_CODE_SIGNATURE);
 113             if (codeSignatureCommand == null) {
 114                 // no previous signature in the executable
 115                 codeSignatureCommand = new CodeSignatureCommand();
 116                 codeSignatureCommand.setDataOffset(
 117                         linkeditSegment.getFileOffset()
 118                             + linkeditSegment.getFileSize());
 119                 header.addCommand(codeSignatureCommand);
 120             }
 121 
 122             final int codeLimit = codeSignatureCommand.getDataOffset();
 123             final EmbeddedSignatureBlob embeddedSignatureBlob =
 124                     createEmbeddedSignatureBlob(
 125                             appIdentifier,
 126                             signer.getSubjectName(),
 127                             codeLimit);
 128 
 129             // update the header with information about the new embedded
 130             // code signature
 131             final int reservedForEmbeddedSignature =
 132                     (embeddedSignatureBlob.getSize() + 15) & ~15;
 133             codeSignatureCommand.setDataSize(reservedForEmbeddedSignature);
 134             final int newLinkeditSize =
 135                     codeLimit - linkeditSegment.getFileOffset()
 136                               + reservedForEmbeddedSignature;
 137             linkeditSegment.setFileSize(newLinkeditSize);
 138             linkeditSegment.setVmSize((newLinkeditSize + 0xfff) & ~0xfff);
 139             final int newHeaderSize = header.getSize();
 140 
 141             final int firstSectionOffset = getFirstSectionFileOffset(header);
 142             if (newHeaderSize > firstSectionOffset) {
 143                 throw new IOException("Patched header too long");
 144             }
 145 
 146             // we assume that there is only padding between the header and the
 147             // first section, so we can skip some of it in the input stream
 148 
 149             execInputStream.skip(newHeaderSize - oldHeaderSize);
 150 
 151             // start the executable zip entry
 152             final String entryName = appPath + appName;
 153             System.out.println("Adding " + entryName);
 154             zipStream.putNextEntry(new ZipEntry(entryName));
 155             try {
 156                 final PageHashingOutputStream hashingStream =
 157                         new PageHashingOutputStream(zipStream);
 158 
 159                 // store the patched header
 160                 writeHeader(hashingStream, header);
 161 
 162                 // copy the rest of the executable up to the codeLimit
 163                 final DataCopier dataCopier = new DataCopier();
 164                 // no need to use buffered stream, because the data is copied in
 165                 // large chunks
 166                 dataCopier.copyStream(hashingStream, execInputStream,
 167                                       codeLimit - newHeaderSize);
 168 
 169                 // finalize the last page hash
 170                 hashingStream.flush();
 171                 hashingStream.commitPageHash();
 172 
 173                 // update the code directory blob with hashes
 174                 final byte[] requirementsBlobHash =
 175                         calculateRequirementsBlobHash(
 176                             embeddedSignatureBlob.getRequirementsSubBlob());
 177                 updateHashes(embeddedSignatureBlob.getCodeDirectorySubBlob(),
 178                              hashingStream.getPageHashes(),
 179                              infoPlistHash,
 180                              requirementsBlobHash,
 181                              codeResourcesHash);
 182 
 183                 // sign the embedded signature blob
 184                 signEmbeddedSignatureBlob(embeddedSignatureBlob, signer);
 185 
 186                 // write the embedded signature blob and padding
 187                 writeEmbeddedSignatureBlob(zipStream, embeddedSignatureBlob,
 188                                            reservedForEmbeddedSignature);
 189 
 190             } finally {
 191                 zipStream.closeEntry();
 192             }
 193         } finally {
 194             execInputStream.close();
 195         }
 196 
 197     }
 198 
 199     private static int getFirstSectionFileOffset(final MachoHeader header)
 200             throws IOException {
 201         // assumes that the first section in the file is a text section of the
 202         // text segment
 203 
 204         final SegmentCommand textSegment = header.findSegment("__TEXT");
 205         if (textSegment == null) {
 206             System.out.println(header);
 207             throw new IOException("Text segment not found");
 208         }
 209 
 210         final SegmentCommand.Section textSection =
 211                 textSegment.findSection("__text");
 212         if (textSection == null) {
 213             throw new IOException("Text section not found");
 214         }
 215 
 216         return textSection.getOffset();
 217     }
 218 
 219 
 220     private static EmbeddedSignatureBlob createEmbeddedSignatureBlob(
 221             final String appIdentifier,
 222             final String subjectName,
 223             final int codeLimit) {
 224         final CodeDirectoryBlob codeDirectoryBlob =
 225                 new CodeDirectoryBlob(appIdentifier, codeLimit);
 226 
 227         final RequirementsBlob requirementsBlob = new RequirementsBlob(1);
 228         final RequirementBlob designatedRequirementBlob =
 229                 new RequirementBlob(
 230                     Requirement.createDefault(appIdentifier, subjectName));
 231         requirementsBlob.setSubBlob(
 232                 0, RequirementsBlob.KSEC_DESIGNATED_REQUIREMENT_TYPE,
 233                 designatedRequirementBlob);
 234 
 235         final VirtualBlob reservedForSignatureBlob =
 236                 new VirtualBlob(0, RESERVED_SIGNATURE_BLOB_SIZE - 8);
 237 
 238         final EmbeddedSignatureBlob embeddedSignatureBlob =
 239                 new EmbeddedSignatureBlob();
 240         embeddedSignatureBlob.setCodeDirectorySubBlob(codeDirectoryBlob);
 241         embeddedSignatureBlob.setRequirementsSubBlob(requirementsBlob);
 242         embeddedSignatureBlob.setSignatureSubBlob(reservedForSignatureBlob);
 243 
 244         return embeddedSignatureBlob;
 245     }
 246 
 247     private static void signEmbeddedSignatureBlob(
 248             final EmbeddedSignatureBlob embeddedSignatureBlob,
 249             final Signer signer) throws IOException {
 250         final CodeDirectoryBlob codeDirectoryBlob =
 251                 embeddedSignatureBlob.getCodeDirectorySubBlob();
 252         final ByteArrayOutputStream bos =
 253                 new ByteArrayOutputStream(codeDirectoryBlob.getSize());
 254         final DataOutputStream os = new DataOutputStream(bos);
 255         try {
 256             codeDirectoryBlob.write(os);
 257         } finally {
 258             os.close();
 259         }
 260 
 261         final byte[] signature;
 262         try {
 263             signature = signer.sign(bos.toByteArray());
 264         } catch (final CMSException e) {
 265             throw new IOException("Failed to sign executable", e);
 266         }
 267 
 268         embeddedSignatureBlob.setSignatureSubBlob(
 269                 new WrapperBlob(signature));
 270     }
 271 
 272     private static void writeHeader(
 273             final OutputStream dataStream,
 274             final MachoHeader header) throws IOException {
 275         final LsbDataOutputStream headerStream =
 276                 new LsbDataOutputStream(new BufferedOutputStream(dataStream));
 277 
 278         try {
 279             header.write(headerStream);
 280         } finally {
 281             headerStream.flush();
 282         }
 283     }
 284 
 285     private static void writeEmbeddedSignatureBlob(
 286             final OutputStream dataStream,
 287             final EmbeddedSignatureBlob embeddedSignatureBlob,
 288             final int reservedForEmbeddedSignature) throws IOException {
 289         final int realEmbeddedSignatureSize =
 290                 embeddedSignatureBlob.getSize();
 291         if (realEmbeddedSignatureSize > reservedForEmbeddedSignature) {
 292             throw new IOException("Embedded signature too large");
 293         }
 294 
 295         final DataOutputStream signatureStream =
 296                 new DataOutputStream(new BufferedOutputStream(dataStream));
 297         try {
 298             embeddedSignatureBlob.write(signatureStream);
 299 
 300             // add padding
 301             for (int i = reservedForEmbeddedSignature
 302                              - realEmbeddedSignatureSize; i > 0; --i) {
 303                 signatureStream.writeByte(0);
 304             }
 305         } finally {
 306             signatureStream.flush();
 307         }
 308     }
 309 
 310     private static void updateHashes(
 311             final CodeDirectoryBlob codeDirectoryBlob,
 312             final List<byte[]> pageHashes,
 313             final byte[] infoPlistHash,
 314             final byte[] requirementsHash,
 315             final byte[] codeResourcesHash) {
 316         int i = 0;
 317         for (final byte[] pageHash: pageHashes) {
 318             codeDirectoryBlob.setCodeSlot(i++, pageHash);
 319         }
 320 
 321         if (infoPlistHash != null) {
 322             codeDirectoryBlob.setInfoPlistSlot(infoPlistHash);
 323         }
 324 
 325         if (requirementsHash != null) {
 326             codeDirectoryBlob.setRequirementsSlot(requirementsHash);
 327         }
 328 
 329         if (codeResourcesHash != null) {
 330             codeDirectoryBlob.setCodeResourcesSlot(codeResourcesHash);
 331         }
 332     }
 333 
 334     private static byte[] calculateRequirementsBlobHash(
 335             final RequirementsBlob requirementsBlob) {
 336         final HashingOutputStream hashingStream =
 337                 new HashingOutputStream(new NullOutputStream());
 338 
 339         try {
 340             final DataOutputStream dataStream =
 341                     new DataOutputStream(hashingStream);
 342             try {
 343                 requirementsBlob.write(dataStream);
 344             } finally {
 345                 dataStream.close();
 346             }
 347 
 348             return hashingStream.calculateHash();
 349         } catch (final IOException e) {
 350             // won't happen
 351             return null;
 352         }
 353     }
 354 }