CodeSignManager.java

/*
 * #%L
 * xcode-maven-plugin
 * %%
 * Copyright (C) 2012 SAP AG
 * %%
 * 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.
 * #L%
 */
package com.sap.prd.mobile.ios.mios;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;

public class CodeSignManager
{
  public static void verify(File appFolder, boolean noStrictVerify) throws IOException
  {
    if (noStrictVerify) {
      checkExitValue(exec(new String[] { "/usr/bin/codesign", "--verify --no-strict -v",
          "\"" + appFolder.getAbsolutePath() + "\"" }));
    }
    else {
      checkExitValue(exec(new String[] { "/usr/bin/codesign", "--verify -v", "\"" + appFolder.getAbsolutePath() + "\"" }));
    }
  }

  public static void sign(String codeSignIdentity, File appFolder, boolean force) throws IOException
  {
    String[] cmd = new String[] { "/usr/bin/codesign", "--preserve-metadata", "-s", "\"" + codeSignIdentity + "\"",
        "\"" + appFolder.getAbsolutePath() + "\"" };
    if (force)
    {
      cmd = (String[]) ArrayUtils.add(cmd, "-f");
    }
    ExecResult exec = exec(cmd);
    checkExitValue(exec);
  }

  public static ExecResult getCodesignEntitlementsInformation(File appFolder) throws IOException
  {
    ExecResult result = exec(new String[] { "/usr/bin/codesign", "-d --entitlements -",
        "\"" + appFolder.getAbsolutePath() + "\"" });
    checkExitValue(result);
    return result;
  }

  public static ExecResult getSecurityCMSInformation(File appFolder) throws IOException
  {
    ExecResult result = exec(new String[] { "/usr/bin/security", "cms", "-D -i",
        "\"" + appFolder.getAbsolutePath() + "/embedded.mobileprovision\"" });
    checkExitValue(result);
    return result;
  }

  public static void verify(ExecResult result1, ExecResult result2) throws ExecutionResultVerificationException
  {
    if (!result2.equals(result1)) {
      throw new ExecutionResultVerificationException(result1, result2);
    }
  }

  private static ExecResult exec(String[] cmd) throws IOException
  {
    String cmdStr = StringUtils.join(cmd, " ");
    System.out.println("Invoking " + cmdStr);

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    PrintStream ps = new PrintStream(baos, true, "UTF-8");
    try {
      int exitValue = Forker.forkProcess(ps, null, "bash", "-c", cmdStr);
      return new ExecResult(cmdStr, baos.toString("UTF-8"), exitValue);
    }
    finally {
      ps.close();
    }
  }

  private static void checkExitValue(ExecResult exec)
  {
    if (exec.exitCode != 0)
    {
      throw new IllegalStateException("Command failed, check log for details (" +
            "exit code = " + exec.exitCode +
            ", command = '" + exec.command +
            "', result = '" + exec.result + "')");
    }
  }

  static class ExecResult
  {
    public ExecResult(String command, String result, int exitCode)
    {
      this.command = command;
      this.result = result;
      this.exitCode = exitCode;
    }

    public final String command;
    public final String result;
    public final int exitCode;

    @Override
    public String toString()
    {
      return "ExecResult [command=" + command + ", result=" + result
            + ", exitCode=" + exitCode + "]";
    }

    @Override
    public int hashCode()
    {
      final int prime = 31;
      int result = 1;
      result = prime * result + ((command == null) ? 0 : command.hashCode());
      result = prime * result + exitCode;
      result = prime * result
            + ((this.result == null) ? 0 : this.result.hashCode());
      return result;
    }

    @Override
    public boolean equals(Object obj)
    {
      if (this == obj)
        return true;
      if (obj == null)
        return false;
      if (getClass() != obj.getClass())
        return false;
      ExecResult other = (ExecResult) obj;
      if (command == null) {
        if (other.command != null)
          return false;
      }
      else if (!command.equals(other.command))
        return false;
      if (exitCode != other.exitCode)
        return false;
      if (result == null) {
        if (other.result != null)
          return false;
      }
      else if (!result.equals(other.result))
        return false;
      return true;
    }
  }

  static class ExecutionResultVerificationException extends Exception
  {
    private static final long serialVersionUID = 1L;
    private final ExecResult result1;
    private final ExecResult result2;

    public ExecutionResultVerificationException(ExecResult result1, ExecResult result2)
    {
      super();
      this.result1 = result1;
      this.result2 = result2;
    }
    
    @Override
    public String getMessage()
    {
      return String.format("Verification failed, results differ. Result 1: '%s', Result 2: '%s'", result1, result2);
    }
  }
}