Example #1
0
  /** Write the signature file to the given output stream. */
  private void generateSignatureFile(Manifest manifest, OutputStream out)
      throws IOException, GeneralSecurityException {
    out.write(("Signature-Version: 1.0\r\n").getBytes());
    out.write(("Created-By: 1.0 (Android SignApk)\r\n").getBytes());

    // BASE64Encoder base64 = new BASE64Encoder();
    MessageDigest md = MessageDigest.getInstance("SHA1");
    PrintStream print =
        new PrintStream(new DigestOutputStream(new ByteArrayOutputStream(), md), true, "UTF-8");

    // Digest of the entire manifest
    manifest.write(print);
    print.flush();

    out.write(("SHA1-Digest-Manifest: " + Base64.encode(md.digest()) + "\r\n\r\n").getBytes());

    Map<String, Attributes> entries = manifest.getEntries();
    for (Map.Entry<String, Attributes> entry : entries.entrySet()) {
      if (canceled) break;
      progressHelper.progress(ProgressEvent.PRORITY_NORMAL, "Generating signature file");
      // Digest of the manifest stanza for this entry.
      String nameEntry = "Name: " + entry.getKey() + "\r\n";
      print.print(nameEntry);
      for (Map.Entry<Object, Object> att : entry.getValue().entrySet()) {
        print.print(att.getKey() + ": " + att.getValue() + "\r\n");
      }
      print.print("\r\n");
      print.flush();

      out.write(nameEntry.getBytes());
      out.write(("SHA1-Digest: " + Base64.encode(md.digest()) + "\r\n\r\n").getBytes());
    }
  }
Example #2
0
  /** Add the SHA1 of every file to the manifest, creating it if necessary. */
  private Manifest addDigestsToManifest(Map<String, ZioEntry> entries)
      throws IOException, GeneralSecurityException {
    Manifest input = null;
    ZioEntry manifestEntry = entries.get(JarFile.MANIFEST_NAME);
    if (manifestEntry != null) {
      input = new Manifest();
      input.read(manifestEntry.getInputStream());
    }
    Manifest output = new Manifest();
    Attributes main = output.getMainAttributes();
    if (input != null) {
      main.putAll(input.getMainAttributes());
    } else {
      main.putValue("Manifest-Version", "1.0");
      main.putValue("Created-By", "1.0 (Android SignApk)");
    }

    // BASE64Encoder base64 = new BASE64Encoder();
    MessageDigest md = MessageDigest.getInstance("SHA1");
    byte[] buffer = new byte[512];
    int num;

    // We sort the input entries by name, and add them to the
    // output manifest in sorted order.  We expect that the output
    // map will be deterministic.

    TreeMap<String, ZioEntry> byName = new TreeMap<String, ZioEntry>();
    byName.putAll(entries);

    boolean debug = getLogger().isDebugEnabled();
    if (debug) getLogger().debug("Manifest entries:");
    for (ZioEntry entry : byName.values()) {
      if (canceled) break;
      String name = entry.getName();
      if (debug) getLogger().debug(name);
      if (!entry.isDirectory()
          && !name.equals(JarFile.MANIFEST_NAME)
          && !name.equals(CERT_SF_NAME)
          && !name.equals(CERT_RSA_NAME)
          && (stripPattern == null || !stripPattern.matcher(name).matches())) {

        progressHelper.progress(ProgressEvent.PRORITY_NORMAL, "Generating manifest");
        InputStream data = entry.getInputStream();
        while ((num = data.read(buffer)) > 0) {
          md.update(buffer, 0, num);
        }

        Attributes attr = null;
        if (input != null) {
          java.util.jar.Attributes inAttr = input.getAttributes(name);
          if (inAttr != null) attr = new Attributes(inAttr);
        }
        if (attr == null) attr = new Attributes();
        attr.putValue("SHA1-Digest", Base64.encode(md.digest()));
        output.getEntries().put(name, attr);
      }
    }

    return output;
  }
Example #3
0
  // Loads one of the built-in keys (media, platform, shared, testkey)
  public void loadKeys(String name) throws IOException, GeneralSecurityException {

    keySet = loadedKeys.get(name);
    if (keySet != null) return;

    keySet = new KeySet();
    keySet.setName(name);
    loadedKeys.put(name, keySet);

    if (KEY_NONE.equals(name)) return;

    progressHelper.progress(ProgressEvent.PRORITY_IMPORTANT, "Loading certificate and private key");

    // load the private key
    URL privateKeyUrl = getClass().getResource("/keys/" + name + ".pk8");
    keySet.setPrivateKey(readPrivateKey(privateKeyUrl, null));

    // load the certificate
    URL publicKeyUrl = getClass().getResource("/keys/" + name + ".x509.pem");
    keySet.setPublicKey(readPublicKey(publicKeyUrl));

    // load the signature block template
    URL sigBlockTemplateUrl = getClass().getResource("/keys/" + name + ".sbt");
    if (sigBlockTemplateUrl != null) {
      keySet.setSigBlockTemplate(readContentAsBytes(sigBlockTemplateUrl));
    }
  }
Example #4
0
 /** Copy all the files from input to output. */
 private void copyFiles(Map<String, ZioEntry> input, ZipOutput output) throws IOException {
   int i = 1;
   for (ZioEntry inEntry : input.values()) {
     if (canceled) break;
     progressHelper.progress(
         ProgressEvent.PRORITY_NORMAL,
         String.format("Copying zip entry %d of %d", i, input.size()));
     i += 1;
     output.write(inEntry);
   }
 }
Example #5
0
  /**
   * Sign the file using the given public key cert, private key, and signature block template. The
   * signature block template parameter may be null, but if so android-sun-jarsign-support.jar must
   * be in the classpath.
   */
  public void signZip(String inputZipFilename, String outputZipFilename)
      throws IOException, GeneralSecurityException {
    File inFile = new File(inputZipFilename).getCanonicalFile();
    File outFile = new File(outputZipFilename).getCanonicalFile();

    if (inFile.equals(outFile)) {
      throw new IllegalArgumentException(
          "Input and output files are the same.  Specify a different name for the output.");
    }

    progressHelper.initProgress();
    progressHelper.progress(
        ProgressEvent.PRORITY_IMPORTANT, "Parsing the input's central directory");

    ZipInput input = ZipInput.read(inputZipFilename);
    signZip(input.getEntries(), new FileOutputStream(outputZipFilename), outputZipFilename);
  }
Example #6
0
 /**
  * Copy all the files in a manifest from input to output. We set the modification times in the
  * output to a fixed time, so as to reduce variation in the output file and make incremental OTAs
  * more efficient.
  */
 private void copyFiles(
     Manifest manifest, Map<String, ZioEntry> input, ZipOutput output, long timestamp)
     throws IOException {
   Map<String, Attributes> entries = manifest.getEntries();
   List<String> names = new ArrayList<String>(entries.keySet());
   Collections.sort(names);
   int i = 1;
   for (String name : names) {
     if (canceled) break;
     progressHelper.progress(
         ProgressEvent.PRORITY_NORMAL,
         String.format("Copying zip entry %d of %d", i, names.size()));
     i += 1;
     ZioEntry inEntry = input.get(name);
     inEntry.setTime(timestamp);
     output.write(inEntry);
   }
 }
Example #7
0
  /**
   * Sign the and signature block template. The signature block template parameter may be null, but
   * if so android-sun-jarsign-support.jar must be in the classpath.
   */
  public void signZip(
      Map<String, ZioEntry> zioEntries, OutputStream outputStream, String outputZipFilename)
      throws IOException, GeneralSecurityException {
    boolean debug = getLogger().isDebugEnabled();

    progressHelper.initProgress();
    if (keySet == null) {
      if (!keymode.startsWith(MODE_AUTO))
        throw new IllegalStateException("No keys configured for signing the file!");

      // Auto-determine which keys to use
      String keyName = this.autoDetectKey(keymode, zioEntries);
      if (keyName == null)
        throw new AutoKeyException(
            "Unable to auto-select key for signing " + new File(outputZipFilename).getName());

      autoKeyObservable.notifyObservers(keyName);

      loadKeys(keyName);
    }

    ZipOutput zipOutput = null;

    try {

      zipOutput = new ZipOutput(outputStream);

      if (KEY_NONE.equals(keySet.getName())) {
        progressHelper.setProgressTotalItems(zioEntries.size());
        progressHelper.setProgressCurrentItem(0);
        copyFiles(zioEntries, zipOutput);
        return;
      }

      // Calculate total steps to complete for accurate progress percentages.
      int progressTotalItems = 0;
      for (ZioEntry entry : zioEntries.values()) {
        String name = entry.getName();
        if (!entry.isDirectory()
            && !name.equals(JarFile.MANIFEST_NAME)
            && !name.equals(CERT_SF_NAME)
            && !name.equals(CERT_RSA_NAME)
            && (stripPattern == null || !stripPattern.matcher(name).matches())) {
          progressTotalItems += 3; // digest for manifest, digest in sig file, copy data
        }
      }
      progressTotalItems += 1; // CERT.RSA generation
      progressHelper.setProgressTotalItems(progressTotalItems);
      progressHelper.setProgressCurrentItem(0);

      // Assume the certificate is valid for at least an hour.
      long timestamp = keySet.getPublicKey().getNotBefore().getTime() + 3600L * 1000;

      // MANIFEST.MF
      // progress(ProgressEvent.PRORITY_NORMAL, JarFile.MANIFEST_NAME);
      Manifest manifest = addDigestsToManifest(zioEntries);
      if (canceled) return;
      ZioEntry ze = new ZioEntry(JarFile.MANIFEST_NAME);
      ze.setTime(timestamp);
      manifest.write(ze.getOutputStream());
      zipOutput.write(ze);

      // CERT.SF
      // progress( ProgressEvent.PRORITY_NORMAL, CERT_SF_NAME);

      // Can't use default Signature on Android.  Although it generates a signature that can be
      // verified by jarsigner,
      // the recovery program appears to require a specific algorithm/mode/padding.  So we use the
      // custom ZipSignature instead.
      // Signature signature = Signature.getInstance("SHA1withRSA");
      ZipSignature signature = new ZipSignature();
      signature.initSign(keySet.getPrivateKey());

      //        	if (getLogger().isDebugEnabled()) {
      //        		getLogger().debug(String.format("Signature provider=%s, alg=%s, class=%s",
      //        				signature.getProvider().getName(),
      //        				signature.getAlgorithm(),
      //        				signature.getClass().getName()));
      //        	}

      ze = new ZioEntry(CERT_SF_NAME);
      ze.setTime(timestamp);

      ByteArrayOutputStream out = new ByteArrayOutputStream();
      generateSignatureFile(manifest, out);
      if (canceled) return;
      byte[] sfBytes = out.toByteArray();
      if (debug) {
        getLogger()
            .debug(
                "Signature File: \n" + new String(sfBytes) + "\n" + HexDumpEncoder.encode(sfBytes));
      }
      ze.getOutputStream().write(sfBytes);
      zipOutput.write(ze);
      signature.update(sfBytes);
      byte[] signatureBytes = signature.sign();

      if (debug) {

        MessageDigest md = MessageDigest.getInstance("SHA1");
        md.update(sfBytes);
        byte[] sfDigest = md.digest();
        getLogger().debug("Sig File SHA1: \n" + HexDumpEncoder.encode(sfDigest));

        getLogger().debug("Signature: \n" + HexDumpEncoder.encode(signatureBytes));

        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.DECRYPT_MODE, keySet.getPublicKey());

        byte[] tmpData = cipher.doFinal(signatureBytes);
        getLogger().debug("Signature Decrypted: \n" + HexDumpEncoder.encode(tmpData));

        //        		getLogger().debug( "SHA1 ID: \n" +
        // HexDumpEncoder.encode(AlgorithmId.get("SHA1").encode()));

        //        		// Compute signature using low-level APIs.
        //                byte[] beforeAlgorithmIdBytes =  { 0x30, 0x21 };
        //                // byte[] algorithmIdBytes = {0x30, 0x09, 0x06, 0x05, 0x2B, 0x0E, 0x03,
        // 0x02, 0x1A, 0x05, 0x00 };
        //                byte[] algorithmIdBytes =  AlgorithmId.get("SHA1").encode();
        //                byte[] afterAlgorithmIdBytes = { 0x04, 0x14 };
        //                cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        //                cipher.init(Cipher.ENCRYPT_MODE, privateKey);
        //                getLogger().debug( "Cipher: " + cipher.getAlgorithm() + ", blockSize = " +
        // cipher.getBlockSize());
        //
        //                cipher.update( beforeAlgorithmIdBytes);
        //                cipher.update( algorithmIdBytes);
        //                cipher.update( afterAlgorithmIdBytes);
        //                cipher.update( sfDigest);
        //                byte[] tmpData2 = cipher.doFinal();
        //                getLogger().debug( "Signature : \n" + HexDumpEncoder.encode(tmpData2));

      }

      // CERT.RSA
      progressHelper.progress(ProgressEvent.PRORITY_NORMAL, "Generating signature block file");
      ze = new ZioEntry(CERT_RSA_NAME);
      ze.setTime(timestamp);
      writeSignatureBlock(
          keySet.getSigBlockTemplate(),
          signatureBytes,
          keySet.getPublicKey(),
          ze.getOutputStream());
      zipOutput.write(ze);
      if (canceled) return;

      // Everything else
      copyFiles(manifest, zioEntries, zipOutput, timestamp);
      if (canceled) return;

    } finally {
      zipOutput.close();
      if (canceled) {
        try {
          if (outputZipFilename != null) new File(outputZipFilename).delete();
        } catch (Throwable t) {
          getLogger().warning(t.getClass().getName() + ":" + t.getMessage());
        }
      }
    }
  }