#include "getopt_cpp.h"

#include <cstdint>
#include <exception>
#include <stdexcept>

#include "AndroidCertificate.h"
#include "JarCertificate.h"
#include "NativeCertificate.h"
#include "Pkcs7.h"
#include "PublicKey.h"
#include "Utils.h"

using namespace std;

enum {
  kEngKeyId = 0,
  kUsrKeyId = 1
};

static void Usage(const GetOpt& options, const char* progname) {
  cout << "Usage: " << progname << " [OPTION...]\n\n";
  cout << "To sign native applications:\n";
  cout << "\t" << progname << " --cmd sign --path=/system/bin/app --five-sig=AwICoMV...w== --key=./config/root.key\n";
  cout << "To sign Android applications:\n";
  cout << "\t" << progname << " --cmd sign --pkg-name=com.samsung.secure_storage --path=/system/priv-app/SecureStorageTestMU/SecureStorageTestMU1.apk --pkg-key=./META-INF/CERT.RSA --five-sig=AwICoMV...w== --key=./config/root.key\n";
  cout << "To sign Jar file (as Android application, but w/o pkg-key):\n";
  cout << "\t" << progname << " --cmd sign --pkg-name=/system/framework/services.jar --path=/system/framework/services.jar --five-sig=AwICoMV...w== --key=./config/root.key\n";
  cout << "To generate request for native applications:\n";
  cout << "\t" << progname << " --cmd request --path=/system/bin/app --five-sig=AwICoMV...w== --request\n";
  cout << "To create certificate with remote signature:\n";
  cout << "\t" << progname << " --cmd combine --request=AwICoMV...w== --signature=BwreCoM...a== --pub-key=./config/root.key\n";
  cout << "To verify certificate:\n";
  cout << "\t" << progname << " --cmd verify --cert=BwreCoM...a== --pub-key=./config/root.key\n\n";
  cout << "All options:\n";
  cout << options.GetHelp() << endl;
}

int main(int argc, char* argv[]) {
  GetOpt options;
  using V = GetOpt::Value;
  V& option_help = options.Add('h', "help", "", "display help message and exit");
  V& option_command = options.Add('c', "cmd", "CMD", "cmd: sign,request,combine,verify");
  V& option_path = options.Add(0, "path", "PATH", "path to file on device");
  V& option_sig = options.Add(0, "five-sig", "SIG", "base64 encoded five signature");
  V& option_user = options.Add('u', "user", "", "provide USER private key (ENG by default)");
  V& option_key = options.Add(0, "key", "FILE", "path to private key");
  V& option_public_key = options.Add(0, "pub-key", "FILE", "path to public key (for combine and verify commands)");
  V& option_package_name = options.Add(0, "pkg-name", "FILE", "android package name");
  V& option_package_key = options.Add(0, "pkg-key", "FILE", "android package key file");
  V& option_cert = options.Add(0, "cert", "CERT", "base64 encoded certificate (for verify cmd)");
  V& option_request = options.Add(0, "request", "REQ", "generate request for remote signing (for combine command)");
  V& option_signature = options.Add(0, "signature", "SIGN", "create certificate with remote signing (for combine command)");
  V& option_dump = options.Add(0, "dump", "BASE64-DUMP", "print certificate to stdout");

  if (options.Run(argc, argv)) {
    return 1;
  }

  if (option_help) {
    Usage(options, argv[0]);
    return 0;
  }

  try {
    if (option_dump) {
      ProcessCertificate *certificate =
          new ProcessCertificate(option_dump.GetAs<string>());
      certificate->Dump();
      return 0;
    }

    string cmd = option_command.GetAs<string>();
    if (cmd == "sign" || cmd == "request") {
      if (!option_path || !option_sig) {
        Usage(options, argv[0]);
        return -1;
      }

      ProcessCertificate *certificate = NULL;
      long key_id = option_user ? kUsrKeyId : kEngKeyId;

      if (option_package_name) {
        if (!option_package_key) {
          // Jar file case
          certificate = new JarCertificate(
              option_package_name.GetAs<string>(),
              option_sig.GetAs<string>(), key_id);
        } else {
          // Apk file case
          certificate = new AndroidCertificate(
              option_package_name.GetAs<string>(),
              option_package_key.GetAs<string>(),
              option_sig.GetAs<string>(), key_id);
        }
      } else {
        certificate = new NativeCertificate(
            option_path.GetAs<string>(),
            option_sig.GetAs<string>(), key_id);
      }

      if (cmd == "sign") {
        if (!option_key) {
          Usage(options, argv[0]);
          return -1;
        }

        PrivateKey pa_root_private_key(static_cast<const string&>(option_key));
        certificate->Sign(pa_root_private_key);

        cout << option_path.GetAs<string>() << " " << certificate->ToAsn1Buffer()
                     << endl;
      } else {
        // cmd == "request"
        cout << option_path.GetAs<string>() << " " << certificate->GetRequest()
                     << endl;
      }
    } else if (cmd == "combine") {
      if (!option_path || !option_request || !option_signature || !option_public_key) {
        Usage(options, argv[0]);
        return -1;
      }

      ProcessCertificate *certificate = NULL;
      certificate = new ProcessCertificate(
          option_request.GetAs<string>(),
          option_signature.GetAs<string>());

      PublicKey key(static_cast<const string&>(option_public_key));
      if (!certificate->Verify(key)) {
        cout << "Signature is invalid.\n";
        return 1;
      }

      cout << option_path.GetAs<string>() << " " << certificate->ToAsn1Buffer()
                 << endl;
    } else if (cmd == "verify") {
      if (!option_public_key || !option_cert) {
        Usage(options, argv[0]);
        return -1;
      }

      ProcessCertificate *certificate = NULL;
      certificate = new ProcessCertificate(option_cert.GetAs<string>());

      PublicKey key(static_cast<const string&>(option_public_key));

      if (certificate->Verify(key)) {
        cout << "Certificate is valid.\n";
        return 0;
      } else {
        cout << "Certificate is invalid.\n";
        return 1;
      }
    } else {
      Usage(options, argv[0]);
      return -1;
    }
  } catch (std::runtime_error& e) {
    cerr << "Error: " << e.what() << endl;
    return -1;
  }

  return 0;
}
